diff --git a/.clang-format b/.clang-format index c66172b1ec5..6e17f17def2 100644 --- a/.clang-format +++ b/.clang-format @@ -11,8 +11,8 @@ # Webkit style was loosely based on the Qt style BasedOnStyle: WebKit -Standard: Cpp11 -ColumnLimit: 0 +Standard: c++17 +ColumnLimit: 160 # Disable reflow of qdoc comments: indentation rules are different. # Translation comments are also excluded @@ -48,11 +48,7 @@ AlwaysBreakTemplateDeclarations: true # clang-format does not handle that AllowShortFunctionsOnASingleLine: Inline -# The coding style specifies some include order categories, but also tells to -# separate categories with an empty line. It does not specify the order within -# the categories. Since the SortInclude feature of clang-format does not -# re-order includes separated by empty lines, the feature is not used. -SortIncludes: false +SortIncludes: true # macros for which the opening brace stays attached ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ] @@ -61,4 +57,6 @@ ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCH MaxEmptyLinesToKeep: 2 KeepEmptyLinesAtTheStartOfBlocks: false -SortIncludes: true +# Properly format c++11 initializer +SpaceBeforeCpp11BracedList: false +Cpp11BracedListStyle: true \ No newline at end of file diff --git a/.craft.ini b/.craft.ini index 861a04e2ecf..b0935f7e6c2 100644 --- a/.craft.ini +++ b/.craft.ini @@ -1,23 +1,25 @@ [General] Branch = master ShallowClone = False +# Cloned repo which contains blueprints for openssl 3.4.4 and 3.4.5 +CraftUrl=https://github.com/owncloud/craft.git # Variables defined here override the default value -# The variable names are casesensitive +# The variable names are case-sensitive [Variables] -#Values need to be overwritten to create a chache +#Values need to be overwritten to create a cache UseCache = True CreateCache = True -ownCloudVersion = 2.10 +ownCloudVersion = 6.0 # this version must match the target in the blueprint # update MacDeploymentTarget when updating Qt -QtVersion = 5.15.2 -CachePatchLvl = 3 +QtVersion = 6.8.3 +CachePatchLvl = 0 # set WORKSPACE to cwd, override on ci -# with legacy fallbac APPVEYOR_BUILD_FOLDER +# with legacy fallback APPVEYOR_BUILD_FOLDER WORKSPACE = ${Variables:APPVEYOR_BUILD_FOLDER} APPVEYOR_BUILD_FOLDER = ${Variables:Root} CiBuild = True @@ -29,8 +31,9 @@ CiBuild = True [GeneralSettings] Version/ConfigVersion = 6 -#https://github.com/qt/qtbase/blob/5.15/mkspecs/common/macx.conf#L8 -General/MacDeploymentTarget = 10.13 +## Set the minimum supported MacOs version, this is also limited by Qt +## https://github.com/qt/qtbase/blob/6.8/.cmake.conf#L54C5-L54C35 +#MacDeploymentTarget = 12 Packager/Destination=${Variables:WORKSPACE}/binaries Paths/Python = C:/Python36 @@ -43,6 +46,7 @@ Packager/CacheDir = ${Variables:WORKSPACE}/cache Packager/UseCache = ${Variables:UseCache} Packager/CreateCache = ${Variables:CreateCache} Packager/CacheVersion = ${Variables:ownCloudVersion}/Qt_${Variables:QtVersion}_${Variables:CachePatchLvl} +Packager/PackageDebugSymbols = False ; Packager/RepositoryUrl = https://files.kde.org/craft/ Packager/RepositoryUrl = https://download.owncloud.com/desktop/craft/cache/ Compile/BuildType = RelWithDebInfo @@ -57,28 +61,41 @@ CodeSigning/MacKeychainPath = sign-${Env:DRONE_BUILD_NUMBER}.keychain [BlueprintSettings] # skip msys in normal builds its only needed for the cache +dev-utils/msys-base.ignored = True dev-utils/msys.ignored = True +# in the current cache that pulls in the uncached lxml which takes ages to build +dev-utils/gtk-doc.ignored = True + binary/mysql.ignored = True libs/dbus.ignored = True libs/icu.ignored = True -libs/llvm-meta.ignored = True - -libs/qt5.version = ${Variables:QtVersion} -libs/qt5/qtbase.useLtcg = True -libs/qt5/qtbase.withMysql = False -libs/qt5/qtbase.withDBus = False -[windows-msvc2019_64-cl] -General/ABI = windows-msvc2019_64-cl - -[windows-msvc2019_64-cl-debug] -General/ABI = windows-msvc2019_64-cl +libs/qt6.version = ${Variables:QtVersion} +libs/qt6/qtbase.withDBus = False +qt-libs/qtkeychain.buildWithQt6 = True + +# pinning versions to fix security issues +libs/libcurl.version = 7.84.0 +libs/openssl.version = 3.4.5 +libs/libpng.version = 1.6.45 +libs/freetype.version = 2.14.1 + +# pinning versions to have a stable cache +dev-utils/cmake-base.version = 3.31.9 +libs/glib.version = 2.81.0 +libs/libxml2.version = 2.12.7 +libs/libxslt.version = 1.1.42 +# 78.1 causes cache misses - libxml2 requires 78.x +libs/icu.version = 76.1 + +[windows-cl-msvc2022-x86_64] +General/ABI = windows-cl-msvc2022-x86_64 + +[windows-cl-msvc2022-x86_64-debug] +General/ABI = windows-cl-msvc2022-x86_64 Compile/BuildType = Debug -[windows-msvc2019_32-cl] -General/ABI = windows-msvc2019_32-cl - [macos-64-clang] General/ABI = macos-64-clang # Packager/PackageType = MacPkgPackager @@ -87,13 +104,20 @@ General/ABI = macos-64-clang General/ABI = macos-64-clang Compile/BuildType = Debug +[macos-clang-arm64] +General/ABI = macos-clang-arm64 + +[macos-clang-arm64-debug] +General/ABI = macos-clang-arm64 +Compile/BuildType = Debug + [linux-64-gcc] General/ABI = linux-64-gcc [linux-64-gcc-BlueprintSettings] libs/dbus.ignored = False -libs/qt5/qtbase.withDBus = True -dev-utils/7zip.useCentosBasedBuild=True +libs/icu.ignored = False +libs/qt6/qtbase.withDBus = True [linux-64-gcc-debug] General/ABI = linux-64-gcc @@ -101,8 +125,8 @@ Compile/BuildType = Debug [linux-64-gcc-debug-BlueprintSettings] libs/dbus.ignored = False -libs/qt5/qtbase.withDBus = True -dev-utils/7zip.useCentosBasedBuild=True +libs/icu.ignored = False +libs/qt6/qtbase.withDBus = True [Env] CRAFT_CODESIGN_CERTIFICATE = diff --git a/.craft.shelf b/.craft.shelf index 4b4ee9ab87c..b2b09f5a83d 100644 --- a/.craft.shelf +++ b/.craft.shelf @@ -1,3 +1,182 @@ [General] version = 2 -blueprintrepositories = https://github.com/owncloud/craft-blueprints-owncloud.git|master|;https://invent.kde.org/packaging/craft-blueprints-kde.git|master| +blueprintrepositories = https://invent.kde.org/packaging/craft-blueprints-kde.git|master|;https://github.com/owncloud/craft-blueprints-owncloud.git|master| + +[core/cacert] +version = 2025-09-09 + +[craft/craft-blueprints-kde] +version = master +revision = 945d9fa + +[craft/craft-blueprints-owncloud] +version = master +revision = d28bcaf + +[craft/craft-core] +version = master +revision = 6cf4d10fb3024c9038bbda0cd26a220ffefab3ff + +[dev-utils/7zip-base] +version = 25.01 + +[dev-utils/cmake] +version = latest + +[dev-utils/cmake-base] +version = 3.31.9 + +[dev-utils/icoutils] +version = 0.32.3 + +[dev-utils/jom] +version = 1_1_3 + +[dev-utils/kshimgen] +version = 0.6.1 + +[dev-utils/nasm] +version = 2.16.03 + +[dev-utils/ninja] +version = 1.13.1 + +[dev-utils/perl] +version = 5.40.1 + +[dev-utils/pkgconf] +version = 2.5.1 + +[dev-utils/symsorter] +version = 0.7.0 + +[kde/frameworks/extra-cmake-modules] +version = 6.20.0 + +[kde/frameworks/tier1/syntax-highlighting] +version = 6.20.0 + +[libs/brotli] +version = 1.1.0 + +[libs/cairo] +version = 1.18.4 + +[libs/expat] +version = 2.7.3 + +[libs/freetype] +version = 2.14.1 + +[libs/gettext] +version = 0.22.3 + +[libs/giflib] +version = 5.2.1 + +[libs/glib] +version = 2.81.0 + +[libs/harfbuzz] +version = 11.2.0 + +[libs/kdsingleapplication] +version = 1.2.0 + +[libs/libb2] +version = 0.98.1 + +[libs/libbzip2] +version = 1.0.8 + +[libs/libffi] +version = 3.4.7 + +[libs/libjpeg-turbo] +version = 3.0.3 + +[libs/liblzma] +version = 5.2.3 + +[libs/libpng] +version = 1.6.45 + +[libs/libre-graph-api-cpp-qt-client] +version = 1.0.4 + +[libs/libunistring] +version = 1.4.1 + +[libs/libxml2] +version = 2.12.7 + +[libs/libzstd] +version = 1.5.7 + +[libs/llvm] +version = 20.1.7 + +[libs/nlohmann-json] +version = 3.12.0 + +[libs/openssl] +version = 3.4.5 + +[libs/pcre2] +version = 10.44 + +[libs/pixman] +version = 0.46.4 + +[libs/python] +version = 3.11.11 + +[libs/qt6/qtbase] +version = 6.8.3 + +[libs/qt6/qtdeclarative] +version = 6.8.3 + +[libs/qt6/qtimageformats] +version = 6.8.3 + +[libs/qt6/qtlanguageserver] +version = 6.8.3 + +[libs/qt6/qtscxml] +version = 6.8.3 + +[libs/qt6/qtshadertools] +version = 6.8.3 + +[libs/qt6/qtsvg] +version = 6.8.3 + +[libs/qt6/qttools] +version = 6.8.3 + +[libs/qt6/qttranslations] +version = 6.8.3 + +[libs/runtime] +version = 17 + +[libs/sqlite] +version = 3.50.4 + +[libs/tiff] +version = 4.4.0 + +[libs/webp] +version = 1.3.2 + +[libs/zlib] +version = 1.3.1 + +[owncloud/libcrashreporter-qt] +version = master +revision = 96da290 + +[qt-libs/qtkeychain] +version = 0.15.0 + diff --git a/.drone.env b/.drone.env deleted file mode 100644 index e54b074eb37..00000000000 --- a/.drone.env +++ /dev/null @@ -1,4 +0,0 @@ -# To run the GUI tests we use owncloud-test-middleware(https://github.com/owncloud/owncloud-test-middleware) -# Set the commit id to use the correct version of the middleware - -MIDDLEWARE_COMMITID=078c01157006d4fc1bb7a4d466b6a93af9a47d1b diff --git a/.drone.star b/.drone.star deleted file mode 100644 index e5c224d2384..00000000000 --- a/.drone.star +++ /dev/null @@ -1,524 +0,0 @@ -# -# We are building GCC with make and Clang with ninja, the combinations are more -# or less arbitrarily chosen. We just want to check that both compilers and both -# CMake generators work. It's unlikely that a specific generator only breaks -# with a specific compiler. -# - -dir = { - "base": "/drone", -} - -def main(ctx): - build_trigger = { - "ref": [ - "refs/heads/master", - "refs/heads/2.**", - "refs/tags/**", - "refs/pull/**", - ], - } - pipelines = [ - # check the format of gui test code - gui_tests_format(build_trigger), - # Check starlark - check_starlark( - ctx, - build_trigger, - ), - # Build changelog - changelog( - ctx, - trigger = build_trigger, - depends_on = [], - ), - build_and_test_client( - ctx, - "clang", - "clang++", - "Debug", - "Ninja", - trigger = build_trigger, - ), - gui_tests(ctx, trigger = build_trigger, version = "latest"), - notification( - name = "build", - trigger = build_trigger, - depends_on = [ - "check-starlark", - "changelog", - "clang-debug-ninja", - ], - ), - ] - return pipelines - -def whenOnline(dict): - if not "when" in dict: - dict["when"] = {} - - if not "instance" in dict: - dict["when"]["instance"] = [] - - dict["when"]["instance"].append("drone.owncloud.com") - - return dict - -def whenPush(dict): - if not "when" in dict: - dict["when"] = {} - - if not "event" in dict: - dict["when"]["event"] = [] - - dict["when"]["event"].append("push") - - return dict - -def from_secret(name): - return { - "from_secret": name, - } - -def check_starlark(ctx, trigger = {}, depends_on = []): - return { - "kind": "pipeline", - "type": "docker", - "name": "check-starlark", - "steps": [ - { - "name": "format-check-starlark", - "image": "owncloudci/bazel-buildifier", - "pull": "always", - "commands": [ - "buildifier --mode=check .drone.star", - ], - }, - { - "name": "show-diff", - "image": "owncloudci/bazel-buildifier", - "pull": "always", - "commands": [ - "buildifier --mode=fix .drone.star", - "git diff", - ], - "when": { - "status": [ - "failure", - ], - }, - }, - ], - "depends_on": depends_on, - "trigger": trigger, - } - -def build_and_test_client(ctx, c_compiler, cxx_compiler, build_type, generator, trigger = {}, depends_on = []): - build_command = "ninja" if generator == "Ninja" else "make" - pipeline_name = c_compiler + "-" + build_type.lower() + "-" + build_command - build_dir = "build-" + pipeline_name - - return { - "kind": "pipeline", - "name": pipeline_name, - "platform": { - "os": "linux", - "arch": "amd64", - }, - "steps": [ - { - "name": "submodules", - "image": "docker:git", - "commands": [ - "git submodule update --init --recursive", - ], - }, - ] + - build_client(ctx, c_compiler, cxx_compiler, build_type, generator, build_command, build_dir) + - [ - { - "name": "ctest", - "image": "owncloudci/client", - "pull": "always", - "environment": { - "LC_ALL": "C.UTF-8", - }, - "commands": [ - 'cd "' + build_dir + '"', - "useradd -m -s /bin/bash tester", - "chown -R tester:tester .", - "su-exec tester ctest --output-on-failure -LE nodrone", - ], - }, - ], - "trigger": trigger, - "depends_on": depends_on, - } - -def gui_tests(ctx, trigger = {}, depends_on = [], filterTags = [], version = "daily-master-qa"): - pipeline_name = "GUI-tests" - build_dir = "build-" + pipeline_name - squish_parameters = "--retry 1 --reportgen stdout --reportgen json,/drone/src/test/guiTestReport" - - if (len(filterTags) > 0): - for tags in filterTags: - squish_parameters += " --tags " + tags - pipeline_name += "-" + tags - - return { - "kind": "pipeline", - "name": pipeline_name, - "platform": { - "os": "linux", - "arch": "amd64", - }, - "steps": [ - { - "name": "submodules", - "image": "docker:git", - "commands": [ - "git submodule update --init --recursive", - ], - }, - ] + - installCore(version) + - setupServerAndApp(2) + - fixPermissions() + - owncloudLog() + - setGuiTestReportDir() + - build_client(ctx, "gcc", "g++", "Debug", "Ninja", "ninja", build_dir) + - [ - { - "name": "GUItests", - "image": "owncloudci/squish:latest", - "pull": "always", - "environment": { - "LICENSEKEY": from_secret("squish_license_server"), - "CLIENT_REPO": "/drone/src/", - "MIDDLEWARE_URL": "http://testmiddleware:3000/", - "BACKEND_HOST": "http://owncloud/", - "SECURE_BACKEND_HOST": "https://owncloud/", - "SERVER_INI": "/drone/src/test/gui/drone/server.ini", - "SQUISH_PARAMETERS": squish_parameters, - }, - }, - ] + - showGuiTestResult(), - "services": testMiddleware() + - owncloudService() + - databaseService(), - "trigger": trigger, - "depends_on": depends_on, - } - -def build_client(ctx, c_compiler, cxx_compiler, build_type, generator, build_command, build_dir): - return [ - { - "name": "cmake", - "image": "owncloudci/client", - "pull": "always", - "environment": { - "LC_ALL": "C.UTF-8", - }, - "commands": [ - 'mkdir -p "' + build_dir + '"', - 'cd "' + build_dir + '"', - 'cmake -G"' + generator + '" -DCMAKE_C_COMPILER="' + c_compiler + '" -DCMAKE_CXX_COMPILER="' + cxx_compiler + '" -DCMAKE_BUILD_TYPE="' + build_type + '" -DBUILD_TESTING=1 -DWITH_LIBCLOUDPROVIDERS=ON -S ..', - ], - }, - { - "name": build_command, - "image": "owncloudci/client", - "pull": "always", - "environment": { - "LC_ALL": "C.UTF-8", - }, - "commands": [ - 'cd "' + build_dir + '"', - build_command + " -j4", - ], - }, - ] - -def gui_tests_format(trigger): - return { - "kind": "pipeline", - "type": "docker", - "name": "guitestformat", - "steps": [ - { - "name": "black", - "image": "cytopia/black", - "pull": "always", - "commands": [ - "cd /drone/src/test/gui", - "black --check --diff .", - ], - }, - ], - "trigger": trigger, - } - -def changelog(ctx, trigger = {}, depends_on = []): - repo_slug = ctx.build.source_repo if ctx.build.source_repo else ctx.repo.slug - result = { - "kind": "pipeline", - "type": "docker", - "name": "changelog", - "clone": { - "disable": True, - }, - "steps": [ - { - "name": "clone", - "image": "plugins/git-action:1", - "pull": "always", - "settings": { - "actions": [ - "clone", - ], - "remote": "https://github.com/%s" % (repo_slug), - "branch": ctx.build.source if ctx.build.event == "pull_request" else "master", - "path": "/drone/src", - "netrc_machine": "github.com", - "netrc_username": from_secret("github_username"), - "netrc_password": from_secret("github_token"), - }, - }, - { - "name": "generate", - "image": "toolhippie/calens:latest", - "pull": "always", - "commands": [ - "calens >| CHANGELOG.md", - ], - }, - { - "name": "diff", - "image": "owncloudci/alpine:latest", - "pull": "always", - "commands": [ - "git diff", - ], - }, - { - "name": "output", - "image": "toolhippie/calens:latest", - "pull": "always", - "commands": [ - "cat CHANGELOG.md", - ], - }, - { - "name": "publish", - "image": "plugins/git-action:1", - "pull": "always", - "settings": { - "actions": [ - "commit", - "push", - ], - "message": "Automated changelog update [skip ci]", - "branch": "master", - "author_email": "devops@owncloud.com", - "author_name": "ownClouders", - "netrc_machine": "github.com", - "netrc_username": from_secret("github_username"), - "netrc_password": from_secret("github_token"), - }, - "when": { - "ref": { - "exclude": [ - "refs/pull/**", - "refs/tags/**", - ], - }, - }, - }, - ], - "trigger": trigger, - "depends_on": depends_on, - } - - return result - -def make(target, path, image = "owncloudci/transifex:latest"): - return { - "name": target, - "image": image, - "pull": "always", - "environment": { - "TX_TOKEN": from_secret("tx_token"), - }, - "commands": [ - 'cd "' + path + '"', - "make " + target, - ], - } - -def notification(name, depends_on = [], trigger = {}): - trigger = dict(trigger) - if not "status" in trigger: - trigger["status"] = [] - - trigger["status"].append("success") - trigger["status"].append("failure") - - return { - "kind": "pipeline", - "name": "notifications-" + name, - "platform": { - "os": "linux", - "arch": "amd64", - }, - "clone": { - "disable": True, - }, - "steps": [ - { - "name": "notification", - "image": "plugins/slack", - "pull": "always", - "settings": { - "webhook": from_secret("slack_webhook"), - "channel": "desktop-ci", - }, - }, - ], - "trigger": trigger, - "depends_on": depends_on, - } - -def databaseService(): - service = { - "name": "mysql", - "image": "mysql:8.0", - "pull": "always", - "environment": { - "MYSQL_USER": "owncloud", - "MYSQL_PASSWORD": "owncloud", - "MYSQL_DATABASE": "owncloud", - "MYSQL_ROOT_PASSWORD": "owncloud", - }, - "command": ["--default-authentication-plugin=mysql_native_password"], - } - return [service] - -def installCore(version): - stepDefinition = { - "name": "install-core", - "image": "owncloudci/core", - "pull": "always", - "settings": { - "version": version, - "core_path": "/drone/src/server", - "db_type": "mysql", - "db_name": "owncloud", - "db_host": "mysql", - "db_username": "owncloud", - "db_password": "owncloud", - }, - } - return [stepDefinition] - -def setupServerAndApp(logLevel): - return [{ - "name": "setup-owncloud-server", - "image": "owncloudci/php:7.4", - "pull": "always", - "commands": [ - "cd /drone/src/server/", - "php occ a:e testing", - "php occ config:system:set trusted_domains 1 --value=owncloud", - "php occ log:manage --level %s" % logLevel, - "php occ config:list", - "php occ config:system:set skeletondirectory --value=/var/www/owncloud/server/apps/testing/data/tinySkeleton", - "php occ config:system:set sharing.federation.allowHttpFallback --value=true --type=bool", - ], - }] - -def owncloudService(): - return [{ - "name": "owncloud", - "image": "owncloudci/php:7.4", - "pull": "always", - "environment": { - "APACHE_WEBROOT": "/drone/src/server/", - "APACHE_CONFIG_TEMPLATE": "ssl", - "APACHE_SSL_CERT_CN": "server", - "APACHE_SSL_CERT": "%s/%s.crt" % (dir["base"], "server"), - "APACHE_SSL_KEY": "%s/%s.key" % (dir["base"], "server"), - "APACHE_LOGGING_PATH": "/dev/null", - }, - "commands": [ - "cat /etc/apache2/templates/base >> /etc/apache2/templates/ssl", - "/usr/local/bin/apachectl -e debug -D FOREGROUND", - ], - }] - -def testMiddleware(): - return [{ - "name": "testmiddleware", - "image": "owncloudci/nodejs:14", - "pull": "always", - "environment": { - "MIDDLEWARE_HOST": "testmiddleware", - "BACKEND_HOST": "http://owncloud", - }, - "commands": [ - ". ./.drone.env", - "git clone https://github.com/owncloud/owncloud-test-middleware.git /drone/src/middleware", - "cd /drone/src/middleware", - "git checkout $MIDDLEWARE_COMMITID", - "yarn install", - "yarn start", - ], - }] - -def owncloudLog(): - return [{ - "name": "owncloud-log", - "image": "owncloud/ubuntu:16.04", - "pull": "always", - "detach": True, - "commands": [ - "tail -f /drone/src/server/data/owncloud.log", - ], - }] - -def fixPermissions(): - return [{ - "name": "fix-permissions", - "image": "owncloudci/php:7.4", - "pull": "always", - "commands": [ - "cd /drone/src/server", - "chown www-data * -R", - ], - }] - -def setGuiTestReportDir(): - return [{ - "name": "create-gui-test-report-directory", - "image": "owncloud/ubuntu:16.04", - "pull": "always", - "commands": [ - "mkdir /drone/src/test/guiTestReport", - "chmod ugo+rwx /drone/src/test/guiTestReport", - ], - }] - -def showGuiTestResult(): - return [{ - "name": "show-gui-test-result", - "image": "python", - "pull": "always", - "commands": [ - "python /drone/src/test/gui/TestLogParser.py /drone/src/test/guiTestReport/results.json", - ], - "when": { - "status": [ - "failure", - ], - }, - }] diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..508837c318c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: ownCloud GmbH - A Kiteworks Company and ownCloud contributors +# SPDX-License-Identifier: Apache-2.0 +# +# Managed by owncloud/admin — edit codeowners/ownership.yml, not this file. +# Generated by scripts/sync-codeowners.py. Last matching pattern wins. +* @owncloud/desktop-maintainers diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000000..b3735ad5a91 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,127 @@ +name: "Bug report" +description: "Report a bug to help us improve the ownCloud desktop client" +labels: ["bug"] + +body: +- type: markdown + attributes: + value: | + We appreciate you taking the time to make the ownCloud desktop client better by reporting a bug. In order for us to quickly grasp the problem, please do not hesitate to provide as many details as possible. There is no need to be afraid of overloading the issue. + + In case you have little experience in reporting bugs, we kindly ask you to read https://www.chiark.greenend.org.uk/~sgtatham/bugs.html, which provides a great introduction to the matter. + + **Please note: this is an issue tracker.** Questions are best asked in our [**community forum**](https://central.owncloud.org) or our [**community chat**](https://talk.owncloud.org)! + +- type: checkboxes + id: presubmission-checks + attributes: + label: Pre-submission Checks + description: | + Before submitting your bug report, please make sure you fulfill the following criteria. Otherwise, your issue will most likely be dismissed. + options: + - label: I [checked for similar issues](https://github.com/owncloud/client/issues?q=), but could not find any. I also checked the closed issues. I could not contribute additional information to any existing issue. + required: true + - label: I will take the time to fill in all the required fields. I know that the bug report may be dismissed otherwise due to lack of information. + required: true + +- type: textarea + id: description + attributes: + label: Describe the bug + description: Please provide a detailed, precise description of the bug. Please do not hesitate to add as many details as possible, every information is relevant. + placeholder: While clicking on button [...], the application unexpectedly [...] + validations: + required: true + +- type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: Please explain which behavior you expected from the application. We need to know what you think should have happened. + placeholder: "I expected to see [...]" + validations: + required: true + +- type: textarea + id: reproduce + attributes: + label: Steps to reproduce the issue + description: | + Ideally, we can reproduce the issue using steps you describe to us. This allows us to perform our own tests and thus better understand the issue. + Please don't hesitate to add as many details as possible. + placeholder: | + 1. Enter [...] into [...] + 2. Click on [...] + 3. Scroll down to [...] + 4. See the error message + +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: Please add screenshots if possible that visualize your problem and/or show how to reproduce the bug. + placeholder: Add your screenshots here! + +- type: textarea + id: logs + attributes: + label: Logs + description: | + Please add (complete) log contents or files to this section. Logs contain very valuable information. **Make sure to redact personal/private information!** + If your logs are longer than 10 lines, you should paste them on a pastebin service (e.g., [Gist](https://gist.github.com)) and insert links here. + placeholder: Add your logs here! + +- type: textarea + id: client-version + attributes: + label: Client version number + description: Please provide the **ownCloud desktop client and Qt version number** (see the *Version* tab in the *About* dialog, which can be found in the client settings). + placeholder: | + Example: + ownCloud 2.10.1 (build 7188) 82eefd Apr 5 2022 11:25:27 + Libraries Qt 5.12.11, OpenSSL 1.1.1k 25 Mar 2021 + Using virtual files plugin: suffix + ubuntu-5.4.0-109-generic + validations: + required: true + +- type: input + id: os-version + attributes: + label: Desktop environment (Linux only) + description: Please provide a precise description of the desktop environment version used on your Linux-based operating system. + placeholder: "Example: Fedora 36 with GNOME 3 version 42.0, ..." + +- type: input + id: linux-client-packaging + attributes: + label: Client package version and origin (Linux only) + description: In case you use a Linux-based operating system, please describe how you installed the ownCloud desktop client, and where you installed it from (e.g., from your distribution, from the ownCloud-provided mirrors, etc.). + placeholder: "Example: Installed from https://download.owncloud.com/desktop/ownCloud/stable/latest/" + +- type: input + id: windows-install-path + attributes: + label: Installation path (Windows only) + description: In case you use Windows, we'd like to know which path you installed the client into. + placeholder: "Example: C:\\Program Files\\ownCloud" + +- type: textarea + id: server-info + attributes: + label: Server information + description: | + Please include all relevant information (e.g., server operating system version, web server, database and PHP version (applicable to ownCloud 10 only), storage backend (in case external storage is used). + placeholder: | + ownCloud 10.9.1 (stable) on Ubuntu 20.04 using PHP 7.3 + + PHP 7.4.3 (cli) (built: Mar 2 2022 15:36:52) ( NTS ) + Copyright (c) The PHP Group + Zend Engine v3.4.0, Copyright (c) Zend Technologies + with Zend OPcache v7.4.3, Copyright (c), by Zend Technologies + +- type: textarea + id: additional-context + attributes: + label: Additional context + description: Any other information you'd like to include. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000..7ed9749931c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,12 @@ +blank_issues_enabled: false + +contact_links: + - name: 📚 Official ownCloud Documentation + url: https://doc.owncloud.com/docs/next/client_releases.html + about: You can find a detailed user guide in our official documentation. Please refer to this document first before asking a question! + - name: 💡 Discussion Forum + url: https://central.owncloud.org/ + about: Interact with our community. Feel free to ask questions there! + - name: 💬 ownCloud Talk + url: https://talk.owncloud.com + about: Chat with other users and the developers! diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000000..5432c7a2d4a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,59 @@ +name: "Feature request" +description: "Propose a new feature for the ownCloud desktop client" +labels: ["enhancement"] + +body: +- type: markdown + attributes: + value: | + Thank you for your interest in making the ownCloud desktop client better! Your feature proposals are highly welcome. + + Please try to describe your feature as precisely as possible. If we fully understand a feature request, we can discuss it with you a lot better. Please don't hesitate to add screenshots or mockups, which greatly aide + + **Please note: this is an issue tracker.** Questions are best asked in our [**community forum**](https://central.owncloud.org) or our [**community chat**](https://talk.owncloud.org)! + +- type: checkboxes + id: presubmission-checks + attributes: + label: Pre-submission Checks + description: | + Before submitting your feature proposal, please make sure you fulfill the following criteria. Otherwise, your issue will most likely be dismissed. + options: + - label: I [checked for similar issues](https://github.com/owncloud/client/issues?q=), but could not find any. I also checked the closed issues. I could not contribute additional information to any existing issue. + required: true + - label: I will take the time to fill in all the required fields. I know that the bug report may be dismissed otherwise due to lack of information. + required: true + +- type: textarea + id: description + attributes: + label: Feature description + description: | + Please describe your suggestion, including as many details as possible. + Is your feature request related to an existing issue? In this case, please describe the problem or provide a link to the issue. + placeholder: I would like to have another button that [...] + validations: + required: true + +- type: textarea + id: proposed-solution + attributes: + label: Proposed solution + description: Please explain the solution you have in mind. + placeholder: Instead of using a text field, I would like a dropdown containing a list of choices [...] + validations: + required: true + +- type: textarea + id: alternatives + attributes: + label: Alternative solutions you considered + description: | + Please list alternative solutions or features you considered to cover your use case. Feel free to discuss their pros and cons. + +- type: textarea + id: additional-context + attributes: + label: Additional context + description: Any other information you'd like to include, for instance, screenshots, mockups, etc. + diff --git a/.github/ISSUE_TEMPLATE/qa_issue.yml b/.github/ISSUE_TEMPLATE/qa_issue.yml new file mode 100644 index 00000000000..811f8548923 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/qa_issue.yml @@ -0,0 +1,78 @@ +name: "QA issue" +description: "Issues created by the ownCloud desktop client QA team" +labels: ["QA:team"] +title: "[QA] " + +body: +- type: markdown + attributes: + value: | + **This form is meant for the ownCloud desktop client QA team!** In case you want to report a bug or request a feature, please use the corresponding forms. + + **Please note: this is an issue tracker.** Questions are best asked in our [**community forum**](https://central.owncloud.org) or our [**community chat**](https://talk.owncloud.org)! + +- type: checkboxes + id: presubmission-checks + attributes: + label: Pre-submission Checks + description: | + Before submitting your feature proposal, please make sure you fulfill the following criteria. Otherwise, your issue will most likely be dismissed. + options: + - label: I [checked for similar issues](https://github.com/owncloud/client/issues?q=), but could not find any. I also checked the closed issues. I could not contribute additional information to any existing issue. + required: true + - label: I will take the time to fill in all the required fields. I know that the bug report may be dismissed otherwise due to lack of information. + required: true + +- type: textarea + id: description + attributes: + label: Describe the QA issue + description: | + Please describe your issue, including as many details as possible. + Is your issue related to an existing issue? In this case, please describe the problem or provide a link to the issue. + placeholder: "While testing ..., I noticed the following problem: ..." + validations: + required: true + +- type: textarea + id: reproduce + attributes: + label: Steps to reproduce the issue + description: | + Ideally, we can reproduce the issue using steps you describe to us. This allows us to perform our own tests and thus better understand the issue. + Please don't hesitate to add as many details as possible. + placeholder: | + 1. Enter [...] into [...] + 2. Click on [...] + 3. Scroll down to [...] + 4. See the error message + validations: + required: true + +- type: textarea + id: screenshots + attributes: + label: Screenshots + description: Please add screenshots if possible that visualize your problem and/or show how to reproduce the bug. + placeholder: Add your screenshots here! + validations: + required: true + +- type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: Please explain which behavior you expected from the application. We need to know what you think should have happened. + placeholder: "I expected to see [...]" + +- type: textarea + id: actual-behavior + attributes: + label: Actual behavior + description: Please explain what behavior you actually experienced. Please do not hesitate to provide a detailed description. The better we understand what has happened, the easier we can solve the problem. + placeholder: "The client unexpectedly [...]" + +- type: markdown + attributes: + value: | + **This form is meant for the ownCloud desktop client QA team!** In case you want to report a bug or request a feature, please use the corresponding forms. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..ae63a0d0874 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + day: sunday + time: '22:00' + timezone: Etc/UTC + open-pull-requests-limit: 5 + groups: + minor-and-patch: + update-types: + - minor + - patch diff --git a/.github/issue_template.md b/.github/issue_template.md deleted file mode 100644 index 8b5aa85a885..00000000000 --- a/.github/issue_template.md +++ /dev/null @@ -1,58 +0,0 @@ - - - -### Expected behaviour -Tell us what should happen - -### Actual behaviour -Tell us what happens instead - -### Steps to reproduce -1. -2. -3. - -### Server configuration -Operating system: - -Web server: - -Database: - -PHP version: - -ownCloud version: - -Storage backend (external storage): - -### Client configuration -Client version: - -Operating system: - -OS language: - -Qt version used by client package (Linux only, see also Settings dialog): - -Client package (From ownCloud or distro) (Linux only): - -Installation path of client: - -### Logs - -Please use Gist (https://gist.github.com/) or a similar code paster for longer -logs. - -```Template for output < 10 lines``` - -1. Client [logfile](https://doc.owncloud.com/desktop/latest/appendices/troubleshooting.html#log-files) - -2. Web server error log: - -3. Server logfile: ownCloud log (data/owncloud.log): - diff --git a/.github/patch_release_template.md b/.github/patch_release_template.md deleted file mode 100644 index 706d5eaed67..00000000000 --- a/.github/patch_release_template.md +++ /dev/null @@ -1,78 +0,0 @@ -## Reason - -* Needed for branded client X.x.x release (link to branded release ticket) - -### Template -[Patch Release Template](https://github.com/owncloud/client/blob/master/.github/patch_release_template.md) - -__TODO__: [patch_release_template.md](https://github.com/owncloud/client/blob/master/.github/patch_release_template.md) is used for this ticket because it's the newest, updated version for a release ticket template. Check if anything is missing from [release_template.md](https://github.com/owncloud/client/blob/master/.github/release_template.md) and merge here. So we can use the same template for a patch, minor or major release in future. - -### Prerequisites - -* [ ] QA: Update [Test Plans](https://confluence.owncloud.com/display/OG/Desktop+Client+Test+Plan+Maintenance) -* [ ] QA: Inform the docu team on rocketchat ``#documentation-internal`` about an upcoming major or minor release -* [ ] QA: Check the translations coming from https://github.com/owncloud/client/actions/workflows/translate.yml -* [ ] QA: Make sure squish tests are running successfully on X.x branch: go to https://github.com/owncloud/client, click on 'commits' above the source tree, click on green checkmark of latest commit, click 'Details' on 'continious-integration/drone/push' and check GUI-tests-@smokeTest -* [ ] DEV: Check for new OpenSSL version -* [ ] DEV: Check [dependencies](https://confluence.owncloud.com/display/OG/Dependencies) for updates -* [ ] DEV: Prepare the release in a `X.x` version branch (a patch release is maintained in the minor release branch) -* [ ] DEV: bump VERSION.cmake in master to say 2.(x+1).x unless already done. - -### Build - -* [ ] DEV: Tag and build [builds](https://confluence.owncloud.com/display/OG/Build+and+Tags#BuildandTags-Sprintbuild) for theme 'ownCloud' and 'testpilotcloud' (includes ChangeLog for the tag on https://github.com/owncloud/client/releases/) -* [ ] QA: Ping #documentation-internal: Changelog is ready. (open doc issues for already known doc-relevant items) -* [ ] DEV: Provide 'testpilotcloud' on **Beta** update channel -* [ ] Beta/RC [Communication](https://confluence.owncloud.com/display/OG/Marketing+and+Communication) - * [ ] Website links for beta (needed for the following posts) - * [ ] Central post https://central.owncloud.org/tags/c/news/desktop - * [ ] All other stakeholders -* [ ] QA: Check Crash reporter (WIN/Mac/Linux Appimage: start 'owncloud --debug' on cmd line, system tray right click menu: 'Crash now - qt fatal') - -### QA - -* [ ] DEV: [Smash box test](https://drone.owncloud.com/owncloud/smashbox-testing) Make sure tests run on latest version -* [ ] QA: Run [automated tests](https://confluence.owncloud.com/display/OG/Automated+Tests) (includes [Smoke test](https://confluence.owncloud.com/display/OG/Manual+Tests#ManualTests-DEVSmokeTest)) - * [ ] All Linux platform install and gpg test ssh://git@gitea.owncloud.services:2222/client/linux-docker-install.git - * [ ] manually deploy a linux download repo, or use a download repo from https://download.owncloud.com/desktop/ownCloud/testing/ - * [ ] in defs.sh edit `repo=` and update `platform_docker_images=` - * [ ] RUN.sh -> paste the log/test_YYYYMMDD_hhmm.log file into the client release ticket. - * [ ] TO DO: extend 08-appimage.sh ("xvfb-run") -* [ ] QA: [Antivirus scan](https://confluence.owncloud.com/display/OG/Virus+Scanning) -* [ ] QA: Create testplan according to release type (patch release: add tests in a comment, for minor release: create a separate ticket), for testplan templates see https://confluence.owncloud.com/display/OG/Desktop+Client+Release+Process and link here -* [ ] QA: Add changelog testing as a comment (to above testplan ticket or here), changeLog issues see https://github.com/owncloud/client/releases/ and link here - -### Final Rebuild after QA Approval - -* [ ] DEV: Tag and build [builds](https://confluence.owncloud.com/display/OG/Build+and+Tags#BuildandTags-Sprintbuild) for theme 'ownCloud' and 'testpilotcloud' for final build - * [ ] TODO: create confluence page with info about final DEV release steps [WIP: Final release steps](https://confluence.owncloud.com/display/OG/WIP%3A+Final+release+steps+-+check+outdated+pages+first) -* [ ] DEV: Adjust [Linux Templates](https://confluence.owncloud.com/display/OG/Branching+Off#BranchingOff-Linuxtemplates) to support the next patch release version (e.g. 2.9.1) @dschmidt @fmoc -* [ ] DEV: Ping ``#release-coordination`` so that marketing can do their [actions](https://confluence.owncloud.com/display/OG/Marketing+and+Communication#MarketingandCommunication-Marketingtasks) -* [ ] DEV: Create [a (draft) release](https://github.com/owncloud/client/releases) with Download links - save as a draft until smoke tested -* [ ] QA: Create a new release issue for a branded release if needed [Branded Client Release Template](https://confluence.owncloud.com/pages/viewpage.action?spaceKey=OG&title=Desktop+Client+Release+Process) -* [ ] QA: Give [heads-up](https://confluence.owncloud.com/display/OG/Marketing+and+Communication#MarketingandCommunication-Heads-upbeforethefinalrelease) before the final release - -### Final QA - -* [ ] QA: [Smoke test](https://confluence.owncloud.com/display/OG/Manual+Tests#ManualTests-SmokeTest) -* [ ] DEV: Publish the release in GitHub -* [ ] QA: Check [documentation](https://confluence.owncloud.com/display/OG/Documentation) - -### Communicate the Availability - -* [ ] Final [Marketing and Communication](https://confluence.owncloud.com/display/OG/Marketing+and+Communication) - * [ ] Website links for final release (needed for the following posts) - * [ ] QA: Central post https://central.owncloud.org/tags/c/news/desktop - * [ ] QA: Inform on ``#updates`` channel, so that marketing knows about the new release - * [ ] QA: Inform [packagers](https://confluence.owncloud.com/display/OG/Marketing+and+Communication#MarketingandCommunication-Packagers) - ping @dragotin (openSUSE) - - -### Final Infrastructure Check - -* [ ] QA/DEV: Update [stable channel](https://confluence.owncloud.com/display/OG/Online+Updater%2C+Crash+reporter%2C+Transifex#OnlineUpdater,Crashreporter,Transifex-UpdatetheUpdater) in the owncloud hosted auto updater -* [ ] QA: Ensure that the [client patch release template](https://github.com/owncloud/client/blob/master/.github/patch_release_template.md) is up to date and in sync with other client templates -* [ ] QA: Ensure that the [client test plan template](https://github.com/owncloud/QA/blob/master/Desktop/Regression_Test_Plan_Patch_Release.md) is up to date. - -### A Few Days After the Release - -* [ ] DEV: Check the [crash reporter](https://confluence.owncloud.com/display/OG/Online+Updater%2C+Crash+reporter%2C+Transifex#OnlineUpdater,Crashreporter,Transifex-CrashReporter) for bad/frequent crashes diff --git a/.github/release_template.md b/.github/release_template.md index ef95c8d14d5..69a4bfa14bf 100644 --- a/.github/release_template.md +++ b/.github/release_template.md @@ -1,115 +1,89 @@ - - -Open an issue called 'Release 2.x.0' in client repository and copy below text into a task and tick the items: -
- -Major/Minor release template. Enter here, when we have three estimated dates: - -* Date of feature freeze -* Date of QA start -* Date of final - - -### Before Branching Off: - -* [ ] Dev: Make sure previous minor/major version's branch is merged into current master branch e.g. 2.6 into master "one flow" - @TheOneRing @guruz -* [ ] Dev: Check [dependencies](https://handbook.owncloud.com/release_processes/client/dependencies.html) for updates - @TheOneRing @guruz -* [ ] Dev: Update [ChangeLog](https://handbook.owncloud.com/release_processes/client/change_log.html) - @TheOneRing @guruz -* [ ] QA: Update [Test Plans](https://handbook.owncloud.com/release_processes/client/testlink.html) - @HanaGemela @jnweiger -* [ ] QA: Review list of [supported platforms](https://handbook.owncloud.com/release_processes/client/supported_platforms.html) - @HanaGemela @jnweiger @TheOneRing @guruz -* [ ] QA: Update [documentation](https://handbook.owncloud.com/release_processes/client/documentation.html) - @HanaGemela @jnweiger -* [ ] QA: Check Sprint Board for remaining issues - @HanaGemela @jnweiger - -### On the Day of the First Daily Build of the New Branch: - -* [ ] Dev: Internally announce it feature freeze - @TheOneRing @guruz -* [ ] Dev: Edit [`VERSION.cmake`](https://handbook.owncloud.com/release_processes/client/branch.html#version-cmake) - @TheOneRing @guruz -* [ ] QA: Adjust [Linux Templates](https://handbook.owncloud.com/release_processes/client/branch.html#linux-templates) - @HanaGemela @jnweiger -* [ ] QA: Adjust [ownBrander](https://handbook.owncloud.com/release_processes/client/branch.html#ownbrander) - @HanaGemela @jnweiger -* [ ] QA: Adjust [AppVeyor](https://handbook.owncloud.com/release_processes/client/branch.html#appveyor) - @HanaGemela @jnweiger -* [ ] QA: Adjust [drone](https://handbook.owncloud.com/release_processes/client/branch.html#drone) - @HanaGemela @jnweiger -* [ ] QA: Adjust [translation jobs](https://handbook.owncloud.com/release_processes/client/branch.html#translations) - @HanaGemela @jnweiger -* [ ] QA: Use `obs-copyprj.sh` to backup the desktop project to `desktop:client-2.6.x` (unless already done) - @HanaGemela @jnweiger -* [ ] Dev: Start running automated tests on the dailies - @TheOneRing @guruz - -### After the First Daily Build of the New Branch: - -* [ ] Announce the new branch to community and advertise dailies for public testing - marketing -* [ ] QA: [Antivirus scan](https://handbook.owncloud.com/release_processes/client/virus.html) - @HanaGemela @jnweiger - -### For All Sprint Builds: - -* [ ] Add latest updates to Changelog in the client source repository -* [ ] Branch off a release branch called VERSION-rcX or VERSION-betaX (without v, v is for tags) -* [ ] Edit ```VERSION.cmake``` to set the suffix to beta1, beta2 etc in the release branch -* [ ] (TODO: move to patch-release checklist) Make sure to increase the version number of the branched of release, e.g. if you release 2.3.2 then you should change VERSION.cmake in 2.3 to 2.3.3 since that branch now will be 2.3.3 -* [ ] Create [builds](https://handbook.owncloud.com/release_processes/client/build.html#sprint-build) for theme 'ownCloud' and 'testpilotcloud' @jnweiger @hvonreth -* [ ] Check if *tar.xz.asc files are there. If not follow the [instructions](https://github.com/owncloud/enterprise/wiki/Desktop-Signing-Knowledge) -* [ ] Run [the smoke test](https://handbook.owncloud.com/release_processes/client/smoke_test.html) -* [ ] Linux: Run [test](https://gitea.owncloud.services/client/linux-docker-install/src/branch/master/RUN.sh) with repo=https://download.opensuse.org/repositories/isv:/ownCloud:/desktop:/testing -* [ ] Linux: add/remove [build targets](https://handbook.owncloud.com/release_processes/client/supported_platforms.html) @hvonreth @jnweiger -* [ ] Create a [signed tag](https://github.com/owncloud/enterprise/wiki/Desktop-Signing-Knowledge) using ```git tag -u E94E7B37 tagname``` @guruz @jnweiger (TODO: is this still needed?) -* [ ] Update the wordpress content at owncloud.org/download @florian - marketing -* [ ] Inform packagers: @dragotin (openSUSE) - marketing -* [ ] Announce on [central](https://central.owncloud.org) (copy old announcement, link to changelog, download links etc) TODO: itemize what goes into the announcement: deprecation warnings. ... - marketing -* [ ] Inform community mailinglists devel@owncloud.org and testpilots@owncloud.org (make sure to mention it is an rc). Link to the central post so discussion happens there. - marketing -* [ ] Check crash reporter after some days @guruz @hvonreth -* [ ] Update unstable channel in the owncloud hosted auto updater. Instructions [here](https://github.com/owncloud/enterprise/blob/master/client_update_checker/README.md#deploy) and [here](https://handbook.owncloud.com/release_processes/client/desktop.html#update-the-updater) @hgemela @jnweiger - -### One Week Before the Final Release (Skip this section for patch releases): - -* [ ] Communicate the release schedule on rocket-chat #release-coordination and mailinglist release-coordination@owncloud.com. Give a high level overview of the upcoming new features, changes etc. -* [ ] Ensure marketing is aware (marketing@owncloud.com) and prepared for the release (social, .com website, cust.communications) - 1 week before minor, 2 weeks before major (minor/major is about impact) -* [ ] Inform Achim (ageissel@owncloud.com) and GCX that the next version will be in 1 week (gcx@owncloud.com) - marketing - -### One Day Before the Final Release: -* [ ] Check [crash reporter](https://handbook.owncloud.com/release_processes/client/desktop.html#crash-reporter) for bad crashes of this RC (same crash happening to many users) @guruz @hvonreth -* [ ] Check the translations coming from transifex: All synchronized? TODO: (20181109jw: where? how?) -* [ ] Review drone results: `make test` TODO: Mac, [Lin](https://drone.owncloud.services/client/build-linux), Win? -* [ ] Run smashbox (20180719 jw: FIXME: add details, how?) (ask @dschmidt, put link to smashbox results here) -* [ ] Inform product management and marketing and #general channel in rocker chat that we are 1 day out - -### On Release Day (for the Final Release): -For major, minor, and patch releases, but skip this section for ALPHA/BETA - -* [ ] Add last updates to Changelog in the client source repository @hvonreth @hgemela -* [ ] Branch off a release branch once QA is done, tag with a release vA.B.C (with v, as v is for tags) @hvonreth @guruz -* [ ] Edit ```VERSION.cmake``` hange suffix from 'git' or 'rc' to empty string "". Commit the result to the release branch only @hvonreth @guruz -* [ ] Create [builds](https://handbook.owncloud.com/release_processes/client/build.html#final-build) for theme 'ownCloud' and 'testpilotcloud' @jnweiger @hvonreth -* [ ] Check if *tar.xz.asc files are [here](https://download.owncloud.com/desktop/testing). If not follow the [instructions](https://github.com/owncloud/enterprise/wiki/Desktop-Signing-Knowledge) -* [ ] Branch isv:ownCloud:desktop to isv:ownCloud:desktop:client-X.Y.Z using [obs_integration](https://github.com/owncloud/administration/blob/master/jenkins/obs_integration/) the Linux packages will always land in the :testing repository - @jnweiger - ```obs-deepcopy-prj.sh isv:ownCloud:desktop isv:ownCloud:desktop:client-2.5.1``` -* [ ] Run [the smoke test](https://handbook.owncloud.com/release_processes/client/smoke_test.html) -* [ ] Linux: Run [test](https://gitea.owncloud.services/client/linux-docker-install/src/branch/master/RUN.sh) repo=https://download.opensuse.org/repositories/isv:/ownCloud:/desktop:/testing - @jnweiger -* [ ] Win/Mac Copy builds from ```testing``` to ```stable``` on download.owncloud.com, double check the download links. (make sure the .asc is there too. -* [ ] Linux: also copy the *.linux-repo.html files from ```testing``` to ```stable``` **and** edit away the `:testing` strings. -* [ ] Linux: disable publishing on project isv:ownCloud:desktop -* [ ] Linux: copy from testing to released in OBS: -  ```obs-deepcopy-prj.sh isv:ownCloud:desktop:testing isv:ownCloud:desktop``` -  ```obs-deepcopy-prj.sh isv:ownCloud:testpilot:testing isv:ownCloud:testpilot``` -* [ ] Linux: Re-enable OBS publishing on the project after official release date and if all distros build (check for accidentially disabled packages too) -* [ ] Test all advertised download links to have the expected version -* [ ] Linux: Wait until everything is built and published, then disable publishing on project isv:ownCloud:desktop -* [ ] Create git [signed](https://github.com/owncloud/enterprise/wiki/Desktop-Signing-Knowledge) tag in client repository using ```git tag -u E94E7B37 tagname``` -* [ ] Increment version number in daily builds. Special case: after the last release in a branch, jump forward to the 'next release branch'... That may mean, this is nightly is the same as edge then. -* [ ] Create same tag for MSI code - @dschmidt -* [ ] Create same tag for Windows toolchain - @dschmidt -* [ ] Create same tag (actually a symlink) for macOS toolchain - @dschmidt -* [ ] Create a (draft) release [here](https://github.com/owncloud/client/releases) -* [ ] 1h later check [changelog on website](https://owncloud.org/changelog/desktop-client/) -> it pulls from the master branch ChangeLog file hourly. - @jnweiger -* [ ] Update [org website](https://owncloud.org/download/#owncloud-desktop-client) -> Download ownCloud -> click open 'Desktop Client', edit win/mac/lin, each all three tabs "Production", "Technical Preview" [disabled], "Test pilot" enabled, edit the links. -* [ ] Add the previous release to [older version](https://owncloud.org/download/older-versions/) @jnweiger + fwittek -* [ ] Ping marketing to do their [actions](https://handbook.owncloud.com/release_processes/client/marketing.html) -* [ ] Take pride and celebrate! -* [ ] Tell GCX to increment the minimum supported version for enterprise customers - @mstingl -* [ ] Check if [minimum.supported.desktop.version](https://github.com/owncloud/core/blob/master/config/config.sample.php#L1152) needs to be updated in server - * [ ] Ensure that the [client release template](https://github.com/owncloud/client/edit/notes-from-the-etherpad/.github/release_template.md) is up to date -* [ ] After OBS built everything, disable publishing in OBS to prevent that accidential rebuilds hit the end users.- @jnweiger - -## A few days after the release (for final release) - -* [ ] Check the crash reporter if auto update is a good idea or we need a new release -* [ ] Update the owncloud hosted auto [updater](https://github.com/owncloud/enterprise/blob/master/client_update_checker/README.md#deploy) +## Reason + +* Needed for branded client X.x.x release (link to branded release ticket) + +### Template +[Release Template](https://github.com/owncloud/client/blob/master/.github/release_template.md) + +__TODO__: +* Check if anything is missing from [release_template_outdated_2020.md](https://github.com/owncloud/client/blob/master/.github/release_template_outdated_2020.md) and merge here. We use the same template for a patch, minor or major release now. +* For each item add a link to the respective action if available + +### Administration + +* [ ] QA: Update [Test Plans](https://infinite.owncloud.com/f/31e6d44f-f373-557c-9ab3-1748fc0c650d$4994cd9c-1c17-4254-829a-f5ef6e1ff7e3%21c0c0d47e-5da5-4254-8b02-bd8e97d43dfb) +* [ ] Update [supported platforms](https://doc.owncloud.com/desktop/next/installing.html#system-requirements) @DeepDiver1975 +* [ ] QA: Check the translations coming from transifex: https://github.com/owncloud/client/commits/ -> Filter based on a release branch/tag and search for `[tx] updated client translations from transifex [skip ci]` +* [ ] DEV: Check for new OpenSSL version +* [ ] DEV: Update [dependencies](https://infinite.owncloud.com/f/31e6d44f-f373-557c-9ab3-1748fc0c650d$4994cd9c-1c17-4254-829a-f5ef6e1ff7e3%21d231d3ed-498f-42b8-87e0-87420e66e16c) +* [ ] DEV: For a major release create `X` version branch + * [ ] QA: In drone adjust the branch for nightly [GUI tests](https://infinite.owncloud.com/f/31e6d44f-f373-557c-9ab3-1748fc0c650d$4994cd9c-1c17-4254-829a-f5ef6e1ff7e3%21be0a8bd0-0029-4335-9c4a-98303d89aa9f) @individual-it +* [ ] QA: Communicate documentation changes + * [ ] Inform documentation team about the start of testing phase (latest a week before the release!). They'll prepare a PR with respective doc version + * [ ] Open issues in ``docs-client-desktop`` repo for already known doc-relevant items and mark them accordingly, e.g. backport to 2.X.x necessary +* [ ] Decide if the prerelease stage will be public or internal @DeepDiver1975 + +### Copy for Each Build (Beta/RC) + +* [ ] DEV: Tag (Beta or RC) and build [builds](https://gitea.owncloud.services/client/do_release) for theme 'ownCloud' and 'testpilotcloud' +* [ ] QA: [Smoke test](https://infinite.owncloud.com/f/31e6d44f-f373-557c-9ab3-1748fc0c650d$4994cd9c-1c17-4254-829a-f5ef6e1ff7e3%21bc6b66c2-84e3-4a92-926c-c7fa2492f85e) +* [ ] DEV: GitHub release +* [ ] Beta/RC Communication + * [ ] TODO + * [ ] For public prerelease: Write/edit Central post https://central.owncloud.org/tags/c/news/desktop with link to GitHub release +* [ ] DEV: Prepare the update server for new version (AppImages included) + * [ ] DEV: Provide 'testpilotcloud' on **Beta** update channel + +### QA + +* [ ] QA: [Antivirus scan](https://www.virustotal.com/) the first RC +* [ ] QA: [Upload](https://confluence.owncloud.com/display/OG/Upload+linux+gpg+keys+to+key+server) linux gpg keys to key server +* [ ] QA: Check Crash reporter: start 'owncloud --debug' on cmd line, system tray right click menu: 'Crash now - qt fatal' -> report window not empty, sending the report works + * [ ] Windows + * [ ] macOS + * [ ] AppImage (Linux) +* [Automated tests](https://infinite.owncloud.com/f/31e6d44f-f373-557c-9ab3-1748fc0c650d$4994cd9c-1c17-4254-829a-f5ef6e1ff7e3%21073f546f-59af-42f2-8a47-1480a051b6cc) (for the first beta and the last RC): + * [ ] QA: GUI tests passed on a tag + * [ ] QA: All [Linux platform install](https://infinite.owncloud.com/f/31e6d44f-f373-557c-9ab3-1748fc0c650d$4994cd9c-1c17-4254-829a-f5ef6e1ff7e3%21bd275422-5fff-414e-aa67-23cad9b4aac8) +* Manual tests: + * [ ] QA: [Changelog](https://github.com/owncloud/client/blob/master/CHANGELOG.md) test + * [ ] QA: Regression test + * [ ] QA: Branded regression test + +### Prerequisites for final release + +* [ ] DEV: Create vX.Y.Z release tag in client repo and everywhere else +* [ ] DEV: bump VERSION.cmake in master to say X.(Y+1).Z unless already done. + +### Final Rebuild after QA Approval + +* [ ] QA: Inform documentation team that the tag for the final release will be set a day or at least half a day __before__ (only for a major/minor release). They'll merge docs PR before that. +* [ ] DEV: Create final release tag (e.g., `vX.Y.Z`) +* [ ] DEV: Create [builds](https://confluence.owncloud.com/display/OG/Build+and+Tags#BuildandTags-Tags) for themes 'ownCloud' and 'testpilotcloud' for final release tag +* [ ] DEV: Update version for future builds +* [ ] QA: Check [squish tests](https://infinite.owncloud.com/f/31e6d44f-f373-557c-9ab3-1748fc0c650d$4994cd9c-1c17-4254-829a-f5ef6e1ff7e3%21073f546f-59af-42f2-8a47-1480a051b6cc) running successfully on [drone](https://drone.owncloud.com/owncloud/client) for the final tag vX.Y.Z +* [ ] QA: Create a new release issue for a branded release if needed [Branded Client Release Template](https://github.com/owncloud/enterprise/blob/master/internal_release_templates/internal_client_release_template.md) + +### Final Steps + +* [ ] QA: [Smoke test](https://infinite.owncloud.com/f/31e6d44f-f373-557c-9ab3-1748fc0c650d$4994cd9c-1c17-4254-829a-f5ef6e1ff7e3%21bc6b66c2-84e3-4a92-926c-c7fa2492f85e) +* [ ] DEV: Publish the release in GitHub +* [ ] QA: Update lines 4 and 5 of [install docs](https://github.com/owncloud/docs-client-desktop/blob/master/modules/ROOT/pages/installing.adoc) with the final build number. Merge into master and backport to the respective branch +* [ ] QA: Check that [documentation](https://doc.owncloud.com/desktop/next/) offers the new version +* [ ] DEV: Merge version branch into master +* [ ] DEV: Update [SBOM](https://cloud.owncloud.com/f/6072843) + +### [Marketing and Communication](https://confluence.owncloud.com/display/OG/Marketing+and+Communication) + +* [ ] QA: Inform Kiteworks marketing to update links on https://owncloud.com/desktop-app/ (provide links from GitHub releases) and [wiki de](https://de.wikipedia.org/wiki/OwnCloud), [wiki en](https://en.wikipedia.org/wiki/OwnCloud), [wikidata](https://www.wikidata.org/wiki/Q20763576)) +* [ ] QA: Central post https://central.owncloud.org/tags/c/news/desktop + +### Infrastructure Check + +* [ ] QA/DEV: Update [stable channel](https://confluence.owncloud.com/display/OG/Online+Updater%2C+Crash+reporter%2C+Transifex#OnlineUpdater,Crashreporter,Transifex-UpdatetheUpdater) in the owncloud hosted auto updater +* [ ] QA: Check the linux download pages whether the URLs are correct (e.g. contain stable, not testing) +* [ ] QA: Ensure that the [client release template](https://github.com/owncloud/client/blob/master/.github/release_template.md) is up to date + +### A Few Days After the Release + +* [ ] DEV: Check the [crash reporter](https://confluence.owncloud.com/display/OG/Online+Updater%2C+Crash+reporter%2C+Transifex#OnlineUpdater,Crashreporter,Transifex-CrashReporter) for bad/frequent crashes diff --git a/.github/release_template_outdated_2020.md b/.github/release_template_outdated_2020.md new file mode 100644 index 00000000000..01d632f458e --- /dev/null +++ b/.github/release_template_outdated_2020.md @@ -0,0 +1,116 @@ + + +Open an issue called 'Release 2.x.0' in client repository and copy below text into a task and tick the items: +
+ +Major/Minor release template. Enter here, when we have three estimated dates: + +* Date of feature freeze +* Date of QA start +* Date of final + + +### Before Branching Off: + +* [ ] Dev: Make sure previous minor/major version's branch is merged into current master branch e.g. 2.6 into master "one flow" - @TheOneRing @guruz +* [ ] Dev: Check [dependencies](https://handbook.owncloud.com/release_processes/client/dependencies.html) for updates - @TheOneRing @guruz +* [ ] Dev: Update [ChangeLog](https://handbook.owncloud.com/release_processes/client/change_log.html) - @TheOneRing @guruz +* [ ] QA: Update [Test Plans](https://handbook.owncloud.com/release_processes/client/testlink.html) - @HanaGemela @jnweiger +* [ ] QA: Review list of [supported platforms](https://handbook.owncloud.com/release_processes/client/supported_platforms.html) - @HanaGemela @jnweiger @TheOneRing @guruz +* [ ] QA: Update [documentation](https://handbook.owncloud.com/release_processes/client/documentation.html) - @HanaGemela @jnweiger +* [ ] QA: Check Sprint Board for remaining issues - @HanaGemela @jnweiger + +### On the Day of the First Daily Build of the New Branch: + +* [ ] Dev: Internally announce it feature freeze - @TheOneRing @guruz +* [ ] Dev: Edit [`VERSION.cmake`](https://handbook.owncloud.com/release_processes/client/branch.html#version-cmake) - @TheOneRing @guruz +* [ ] QA: Adjust [Linux Templates](https://handbook.owncloud.com/release_processes/client/branch.html#linux-templates) - @HanaGemela @jnweiger +* [ ] QA: Adjust [ownBrander](https://handbook.owncloud.com/release_processes/client/branch.html#ownbrander) - @HanaGemela @jnweiger +* [ ] QA: Adjust [AppVeyor](https://handbook.owncloud.com/release_processes/client/branch.html#appveyor) - @HanaGemela @jnweiger +* [ ] QA: Adjust [drone](https://handbook.owncloud.com/release_processes/client/branch.html#drone) - @HanaGemela @jnweiger +* [ ] QA: Adjust [translation jobs](https://handbook.owncloud.com/release_processes/client/branch.html#translations) - @HanaGemela @jnweiger +* [ ] QA: Use `obs-copyprj.sh` to backup the desktop project to `desktop:client-2.6.x` (unless already done) - @HanaGemela @jnweiger +* [ ] QA: Adjust branch of Cron Job `nightly-2-x` to the next release branch @HanaGemela @individual-it +* [ ] Dev: Start running automated tests on the dailies - @TheOneRing @guruz + +### After the First Daily Build of the New Branch: + +* [ ] Announce the new branch to community and advertise dailies for public testing - marketing +* [ ] QA: [Antivirus scan](https://handbook.owncloud.com/release_processes/client/virus.html) - @HanaGemela @jnweiger + +### For All Sprint Builds: + +* [ ] Add latest updates to Changelog in the client source repository +* [ ] Branch off a release branch called VERSION-rcX or VERSION-betaX (without v, v is for tags) +* [ ] Edit ```VERSION.cmake``` to set the suffix to beta1, beta2 etc in the release branch +* [ ] (TODO: move to patch-release checklist) Make sure to increase the version number of the branched of release, e.g. if you release 2.3.2 then you should change VERSION.cmake in 2.3 to 2.3.3 since that branch now will be 2.3.3 +* [ ] Create [builds](https://handbook.owncloud.com/release_processes/client/build.html#sprint-build) for theme 'ownCloud' and 'testpilotcloud' @jnweiger @hvonreth +* [ ] Check if *tar.xz.asc files are there. If not follow the [instructions](https://github.com/owncloud/enterprise/wiki/Desktop-Signing-Knowledge) +* [ ] Run [the smoke test](https://handbook.owncloud.com/release_processes/client/smoke_test.html) +* [ ] Linux: Run [test](https://gitea.owncloud.services/client/linux-docker-install/src/branch/master/RUN.sh) with repo=https://download.opensuse.org/repositories/isv:/ownCloud:/desktop:/testing +* [ ] Linux: add/remove [build targets](https://handbook.owncloud.com/release_processes/client/supported_platforms.html) @hvonreth @jnweiger +* [ ] Create a [signed tag](https://github.com/owncloud/enterprise/wiki/Desktop-Signing-Knowledge) using ```git tag -u E94E7B37 tagname``` @guruz @jnweiger (TODO: is this still needed?) +* [ ] Update the wordpress content at owncloud.org/download @florian - marketing +* [ ] Inform packagers: @dragotin (openSUSE) - marketing +* [ ] Announce on [central](https://central.owncloud.org) (copy old announcement, link to changelog, download links etc) TODO: itemize what goes into the announcement: deprecation warnings. ... - marketing +* [ ] Inform community mailinglists devel@owncloud.org and testpilots@owncloud.org (make sure to mention it is an rc). Link to the central post so discussion happens there. - marketing +* [ ] Check crash reporter after some days @guruz @hvonreth +* [ ] Update unstable channel in the owncloud hosted auto updater. Instructions [here](https://github.com/owncloud/enterprise/blob/master/client_update_checker/README.md#deploy) and [here](https://handbook.owncloud.com/release_processes/client/desktop.html#update-the-updater) @hgemela @jnweiger + +### One Week Before the Final Release (Skip this section for patch releases): + +* [ ] Communicate the release schedule on rocket-chat #release-coordination and mailinglist release-coordination@owncloud.com. Give a high level overview of the upcoming new features, changes etc. +* [ ] Ensure marketing is aware (marketing@owncloud.com) and prepared for the release (social, .com website, cust.communications) - 1 week before minor, 2 weeks before major (minor/major is about impact) +* [ ] Inform Achim (ageissel@owncloud.com) and GCX that the next version will be in 1 week (gcx@owncloud.com) - marketing + +### One Day Before the Final Release: +* [ ] Check [crash reporter](https://handbook.owncloud.com/release_processes/client/desktop.html#crash-reporter) for bad crashes of this RC (same crash happening to many users) @guruz @hvonreth +* [ ] Check the translations coming from transifex: All synchronized? TODO: (20181109jw: where? how?) +* [ ] Review drone results: `make test` TODO: Mac, [Lin](https://drone.owncloud.services/client/build-linux), Win? +* [ ] Run smashbox (20180719 jw: FIXME: add details, how?) (ask @dschmidt, put link to smashbox results here) +* [ ] Inform product management and marketing and #general channel in rocker chat that we are 1 day out + +### On Release Day (for the Final Release): +For major, minor, and patch releases, but skip this section for ALPHA/BETA + +* [ ] Add last updates to Changelog in the client source repository @hvonreth @hgemela +* [ ] Branch off a release branch once QA is done, tag with a release vA.B.C (with v, as v is for tags) @hvonreth @guruz +* [ ] Edit ```VERSION.cmake``` hange suffix from 'git' or 'rc' to empty string "". Commit the result to the release branch only @hvonreth @guruz +* [ ] Create [builds](https://handbook.owncloud.com/release_processes/client/build.html#final-build) for theme 'ownCloud' and 'testpilotcloud' @jnweiger @hvonreth +* [ ] Check if *tar.xz.asc files are [here](https://download.owncloud.com/desktop/testing). If not follow the [instructions](https://github.com/owncloud/enterprise/wiki/Desktop-Signing-Knowledge) +* [ ] Branch isv:ownCloud:desktop to isv:ownCloud:desktop:client-X.Y.Z using [obs_integration](https://github.com/owncloud/administration/blob/master/jenkins/obs_integration/) the Linux packages will always land in the :testing repository - @jnweiger + ```obs-deepcopy-prj.sh isv:ownCloud:desktop isv:ownCloud:desktop:client-2.5.1``` +* [ ] Run [the smoke test](https://handbook.owncloud.com/release_processes/client/smoke_test.html) +* [ ] Linux: Run [test](https://gitea.owncloud.services/client/linux-docker-install/src/branch/master/RUN.sh) repo=https://download.opensuse.org/repositories/isv:/ownCloud:/desktop:/testing - @jnweiger +* [ ] Win/Mac Copy builds from ```testing``` to ```stable``` on download.owncloud.com, double check the download links. (make sure the .asc is there too. +* [ ] Linux: also copy the *.linux-repo.html files from ```testing``` to ```stable``` **and** edit away the `:testing` strings. +* [ ] Linux: disable publishing on project isv:ownCloud:desktop +* [ ] Linux: copy from testing to released in OBS: +  ```obs-deepcopy-prj.sh isv:ownCloud:desktop:testing isv:ownCloud:desktop``` +  ```obs-deepcopy-prj.sh isv:ownCloud:testpilot:testing isv:ownCloud:testpilot``` +* [ ] Linux: Re-enable OBS publishing on the project after official release date and if all distros build (check for accidentially disabled packages too) +* [ ] Test all advertised download links to have the expected version +* [ ] Linux: Wait until everything is built and published, then disable publishing on project isv:ownCloud:desktop +* [ ] Create git [signed](https://github.com/owncloud/enterprise/wiki/Desktop-Signing-Knowledge) tag in client repository using ```git tag -u E94E7B37 tagname``` +* [ ] Increment version number in daily builds. Special case: after the last release in a branch, jump forward to the 'next release branch'... That may mean, this is nightly is the same as edge then. +* [ ] Create same tag for MSI code - @dschmidt +* [ ] Create same tag for Windows toolchain - @dschmidt +* [ ] Create same tag (actually a symlink) for macOS toolchain - @dschmidt +* [ ] Create a (draft) release [here](https://github.com/owncloud/client/releases) +* [ ] 1h later check [changelog on website](https://owncloud.org/changelog/desktop-client/) -> it pulls from the master branch ChangeLog file hourly. - @jnweiger +* [ ] Update [org website](https://owncloud.org/download/#owncloud-desktop-client) -> Download ownCloud -> click open 'Desktop Client', edit win/mac/lin, each all three tabs "Production", "Technical Preview" [disabled], "Test pilot" enabled, edit the links. +* [ ] Add the previous release to [older version](https://owncloud.org/download/older-versions/) @jnweiger + fwittek +* [ ] Ping marketing to do their [actions](https://handbook.owncloud.com/release_processes/client/marketing.html) +* [ ] Take pride and celebrate! +* [ ] Tell GCX to increment the minimum supported version for enterprise customers - @mstingl +* [ ] Check if [minimum.supported.desktop.version](https://github.com/owncloud/core/blob/master/config/config.sample.php#L1152) needs to be updated in server + * [ ] Ensure that the [client release template](https://github.com/owncloud/client/edit/notes-from-the-etherpad/.github/release_template.md) is up to date +* [ ] After OBS built everything, disable publishing in OBS to prevent that accidential rebuilds hit the end users.- @jnweiger + +## A few days after the release (for final release) + +* [ ] Check the crash reporter if auto update is a good idea or we need a new release +* [ ] Update the owncloud hosted auto [updater](https://github.com/owncloud/enterprise/blob/master/client_update_checker/README.md#deploy) diff --git a/.github/workflows/.craft.ps1 b/.github/workflows/.craft.ps1 index b1e5a31a946..5050ce56381 100644 --- a/.github/workflows/.craft.ps1 +++ b/.github/workflows/.craft.ps1 @@ -1,10 +1,20 @@ if ($IsWindows) { - $python = (Get-Command py).Source + $python=(python -c "import sys; print(sys.executable)") + Write-Host "Python ${python}" } else { $python = (Get-Command python3).Source } -$command = @("${env:HOME}/craft/CraftMaster/CraftMaster/CraftMaster.py", "--config", "${env:GITHUB_WORKSPACE}/.craft.ini", "--target", "${env:CRAFT_TARGET}", "--variables", "WORKSPACE=${env:HOME}/craft") + $args + +$RepoRoot = "{0}/../../" -f ([System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition)) +$command = @("${env:HOME}/craft/CraftMaster/CraftMaster/CraftMaster.py", + "--config", "${RepoRoot}/.craft.ini", + "--config-override", "${RepoRoot}/.github/workflows/craft_override.ini", + "--target", "${env:CRAFT_TARGET}", + "--variables", "WORKSPACE=${env:HOME}/craft") + $args Write-Host "Exec: ${python} ${command}" & $python @command +if ($LASTEXITCODE -ne 0) { + exit 1 +} diff --git a/.github/workflows/.run-clang-tidy.ps1 b/.github/workflows/.run-clang-tidy.ps1 new file mode 100644 index 00000000000..a929252fc2c --- /dev/null +++ b/.github/workflows/.run-clang-tidy.ps1 @@ -0,0 +1,7 @@ +cd "${env:GITHUB_WORKSPACE}" + +# allow errors as some files can't be compiled on all platforms +$oldErrorActio = $ErrorActionPreference +$ErrorActionPreference="Continue" +git ls-files *.cpp | ForEach-Object -Parallel {clang-tidy -p $env:BUILD_DIR/compile_commands.json $_ } -ThrottleLimit 5 | Tee-Object -Path $env:TMPDIR/clang-tidy.log +$ErrorActionPreference = $oldErrorActio diff --git a/.github/workflows/.sonar.ps1 b/.github/workflows/.sonar.ps1 index a7c865cb136..2ee049c9123 100644 --- a/.github/workflows/.sonar.ps1 +++ b/.github/workflows/.sonar.ps1 @@ -1,4 +1,4 @@ -$env:SONAR_SCANNER_VERSION = "4.4.0.2170" +$env:SONAR_SCANNER_VERSION = "5.0.0.2966" $env:SONAR_DIRECTORY = [System.IO.Path]::Combine($(get-location).Path,".sonar") $env:SONAR_SCANNER_HOME = "$env:SONAR_DIRECTORY/sonar-scanner-$env:SONAR_SCANNER_VERSION-windows" rm $env:SONAR_SCANNER_HOME -Force -Recurse -ErrorAction SilentlyContinue diff --git a/.github/workflows/branded-client.yml b/.github/workflows/branded-client.yml new file mode 100644 index 00000000000..ddef52a439c --- /dev/null +++ b/.github/workflows/branded-client.yml @@ -0,0 +1,82 @@ +name: GUI Tests (Branded Client) + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + +env: + NPM_GHERLINT: "@gherlint/gherlint@1.1.0" + +jobs: + gui-tests: + runs-on: ubuntu-latest + container: owncloudci/squish:fedora-42-8.1.0-qt68x-linux64 + env: + PLAYWRIGHT_BROWSERS_PATH: /__w/client/client/test/gui/.playwright + GITHUB_REPO: ${{ github.repository }} + GITHUB_RUN_NUMBER: ${{ github.run_number }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build client + run: | + mkdir -p build + cd build + cmake -G"Ninja" \ + -DWITH_EXTERNAL_BRANDING=https://x-access-token:${{ secrets.KW_THEME_PAT }}@github.com/owncloud/client-desktop-theme-kiteworks.git \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_TESTING=OFF \ + -S .. + ninja + + - name: Install Python Modules + run: | + export PATH=$PATH:/github/home/.local/bin + make -C test/gui install + python3.10 -m pip list -v + + - name: Create GUI test report directory + run: | + mkdir test/gui/guiReportUpload/screenshots -p + chmod 777 test/gui -R + + - name: Map predefined users + run: echo "${{ secrets.PREDEFINED_USERS }}" | base64 -d > test/gui/users.json + + - name: Run GUI tests + id: run_gui_test + env: + LICENSEKEY: ${{ secrets.SQUISH_LICENSEKEY }} + GUI_TEST_REPORT_DIR: /__w/client/client/test/gui/guiReportUpload + CLIENT_REPO: . + CLIENT_NAME: ${{ secrets.CLIENT_NAME }} + PERSONAL_SYNC_FOLDER: ${{ secrets.PERSONAL_SYNC_FOLDER }} + PREDEFINED_USERS: users.json + OCIS_URL: ${{secrets.BACKEND_HOST}} + BACKEND_HOST: ${{secrets.BACKEND_HOST}} + SECURE_BACKEND_HOST: ${{secrets.BACKEND_HOST}} + OCIS: true + SERVER_INI: test/gui/ci/client_server.ini + SQUISH_PARAMETERS: > + --testsuite test/gui + --reportgen html,test/gui/guiReportUpload + --envvar QT_LOGGING_RULES=sync.httplogger=true;gui.socketapi=false + --tags ~@skip --tags ~@skipOnLinux --tags @predefined_users + --abortOnFail + STACKTRACE_FILE: test/gui/guiReportUpload/stacktrace.log + OWNCLOUD_CORE_DUMP: 1 + RECORD_VIDEO_ON_FAILURE: false + COREPACK_ENABLE_STRICT: 0 + HOME: /home/headless + run: bash /dockerstartup/entrypoint.sh + + - name: Upload GUI test result + if: ${{ failure() && steps.run_gui_test.outcome == 'failure' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: gui-report-branded-client-${{ github.run_number }} + path: test/gui/guiReportUpload + if-no-files-found: warn + retention-days: 7 diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 00000000000..cbffdb040ef --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,17 @@ +name: Changelog + +on: + pull_request: + push: + branches: [ master ] + tags: [ "*" ] + +permissions: + contents: read + +jobs: + changelog: + uses: owncloud/reusable-workflows/.github/workflows/calens.yml@main + secrets: + APP_ID: ${{ secrets.CALENS_APP_ID }} + APP_PRIVATE_KEY: ${{ secrets.CALENS_APP_PRIVATE_KEY }} diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml deleted file mode 100644 index d32c3add942..00000000000 --- a/.github/workflows/clang-format.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: test-clang-format - -on: [pull_request] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 2 - - uses: owncloud-ci/git-clang-format-lint@master diff --git a/.github/workflows/craft_override.ini b/.github/workflows/craft_override.ini new file mode 100644 index 00000000000..c27c3412264 --- /dev/null +++ b/.github/workflows/craft_override.ini @@ -0,0 +1,8 @@ +[GeneralSettings] +# creating cache files on github makes no sense +Packager/CreateCache = False + +[BlueprintSettings] +# no longer preinstalled on the github images and only required to build dev-utils/breakpad-tools +dev-utils/python2.ignored = True +dev-utils/breakpad-tools.ignored = True diff --git a/.github/workflows/gui-tests.yml b/.github/workflows/gui-tests.yml new file mode 100644 index 00000000000..1299cf7034d --- /dev/null +++ b/.github/workflows/gui-tests.yml @@ -0,0 +1,152 @@ +name: GUI Tests + +on: + pull_request: + push: + branches: [ master ] + tags: [ "*" ] + schedule: + - cron: "0 0 * * *" + +env: + NPM_GHERLINT: "@gherlint/gherlint@1.1.0" + +jobs: + lint-gui-test: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + container: + image: owncloudci/squish:fedora-42-8.1.0-qt68x-linux64 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup NodeJs + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 18 + + - name: Python lint + run: | + export PATH=$PATH:/github/home/.local/bin + make -C test/gui install + make -C test/gui python-lint + + - name: Gherkin lint + run: | + npm install -g ${{ env.NPM_GHERLINT }} + make -C test/gui gherkin-lint + + gui-tests: + runs-on: ubuntu-latest + container: + image: owncloudci/squish:fedora-42-8.1.0-qt68x-linux64 + env: + PLAYWRIGHT_BROWSERS_PATH: /__w/client/client/test/gui/.playwright + GITHUB_REPO: ${{ github.repository }} + GITHUB_RUN_NUMBER: ${{ github.run_number }} + services: + ocis: + image: owncloud/ocis-rolling:20260610 + ports: + - 9200:9200 + env: + OCIS_URL: https://ocis:9200 + OCIS_LOG_LEVEL: "error" + OCIS_LOG_PRETTY: "true" + OCIS_LOG_COLOR: "true" + OCIS_INSECURE: "true" + PROXY_ENABLE_BASIC_AUTH: "true" + OCIS_ADMIN_USER_ID: "some-admin-user-id-0000-000000000000" + IDM_ADMIN_PASSWORD: "admin" + OCIS_SYSTEM_USER_ID: "some-system-user-id-000-000000000000" + OCIS_SYSTEM_USER_API_KEY: "some-system-user-machine-auth-api-key" + OCIS_JWT_SECRET: "some-ocis-jwt-secret" + OCIS_MACHINE_AUTH_API_KEY: "some-ocis-machine-auth-api-key" + OCIS_TRANSFER_SECRET: "some-ocis-transfer-secret" + COLLABORATION_WOPIAPP_SECRET: "some-wopi-secret" + IDM_SVC_PASSWORD: "some-ldap-idm-password" + GRAPH_LDAP_BIND_PASSWORD: "some-ldap-idm-password" + IDM_REVASVC_PASSWORD: "some-ldap-reva-password" + GROUPS_LDAP_BIND_PASSWORD: "some-ldap-reva-password" + USERS_LDAP_BIND_PASSWORD: "some-ldap-reva-password" + AUTH_BASIC_LDAP_BIND_PASSWORD: "some-ldap-reva-password" + IDM_IDPSVC_PASSWORD: "some-ldap-idp-password" + IDP_LDAP_BIND_PASSWORD: "some-ldap-idp-password" + GATEWAY_STORAGE_USERS_MOUNT_ID: "storage-users-1" + STORAGE_USERS_MOUNT_ID: "storage-users-1" + GRAPH_APPLICATION_ID: "application-1" + OCIS_SERVICE_ACCOUNT_ID: "service-account-id" + OCIS_SERVICE_ACCOUNT_SECRET: "service-account-secret" + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build client + run: | + mkdir -p build + cd build + cmake -G"Ninja" -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=OFF -S .. + ninja + + - name: Install Python Modules + run: | + export PATH=$PATH:/github/home/.local/bin + make -C test/gui install + python3.10 -m pip list -v + + - name: Create GUI test report directory + run: | + mkdir test/gui/guiReportUpload/screenshots -p + chmod 777 test/gui -R + + - name: Wait for ocis service + run: | + echo "Waiting for ocis" + # 150s timeout + for i in {1..30}; do + if curl -kfsSL https://ocis:9200 > /dev/null; then + echo "ocis is up ✅" + exit 0 + fi + echo "Retrying in 5s..." + sleep 5 + done + echo "❌ ocis failed to start" && exit 1 + + - name: Run GUI test + id: run_gui_test + env: + LICENSEKEY: ${{ secrets.SQUISH_LICENSEKEY }} + GUI_TEST_REPORT_DIR: /__w/client/client/test/gui/guiReportUpload + CLIENT_REPO: . + OCIS_URL: https://ocis:9200 + BACKEND_HOST: https://ocis:9200 + SECURE_BACKEND_HOST: https://ocis:9200 + OCIS: true + SERVER_INI: test/gui/ci/server.ini + SQUISH_PARAMETERS: > + --testsuite test/gui + --reportgen html,test/gui/guiReportUpload + --envvar QT_LOGGING_RULES=sync.httplogger=true;gui.socketapi=false + --tags ~@skip --tags ~@skipOnLinux + ${{ + !contains(github.event.pull_request.title, 'full-ci') + && '--abortOnFail' || '' + }} + STACKTRACE_FILE: test/gui/guiReportUpload/stacktrace.log + OWNCLOUD_CORE_DUMP: 1 + RECORD_VIDEO_ON_FAILURE: false + COREPACK_ENABLE_STRICT: 0 + HOME: /home/headless + run: bash /dockerstartup/entrypoint.sh + + - name: Upload GUI test result + if: ${{ failure() && steps.run_gui_test.outcome == 'failure' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: gui-report-ocis-${{ github.run_number }} + path: test/gui/guiReportUpload + if-no-files-found: warn + retention-days: 7 diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml new file mode 100644 index 00000000000..94cdcf77b73 --- /dev/null +++ b/.github/workflows/lint-pr-title.yml @@ -0,0 +1,18 @@ +name: Lint PR title + +on: + pull_request: + types: + - opened + - edited + - synchronize + +permissions: {} + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 45fe44ae1b4..ee4da54fc67 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,8 +4,15 @@ on: push: branches: - master - - '[0-9]+.*' + - '[0-9]+' pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: {} defaults: run: @@ -14,23 +21,37 @@ defaults: jobs: # ------------------------------------------------------------------------------------------------------------------------------------------ build: - name: Build ownCloud - runs-on: ${{ matrix.os }} + permissions: + # actions/upload-artifact doesn't need contents: write + contents: read strategy: + fail-fast: true matrix: include: - - target: windows-msvc2019_64-cl - os: windows-latest - fetch-depth: 0 + - name: windows-cl-x86_64 + target: windows-cl-msvc2022-x86_64 + os: windows-2022 container: - - target: macos-64-clang - os: macos-latest - fetch-depth: 1 + useSonarCloud: ${{ github.event_name != 'pull_request' }} + testOptions: --options '[Compile]Jobs=1' + # TODO: align the target with the name + - name: macos-clang-arm64 + target: macos-clang-arm64 + os: macos-15 container: - - target: linux-64-gcc - os: ubuntu-18.04 - container: kdeorg/centos7-craft - fetch-depth: 1 + useSonarCloud: + testOptions: + - name: linux-gcc-x86_64 + target: linux-64-gcc + os: ubuntu-latest + container: owncloudci/appimage-build:sles15-amd64 + useSonarCloud: + testOptions: + name: ${{ matrix.name }} + #let the other builds run even if one fails + #continue-on-error: true + runs-on: ${{ matrix.os }} + env: CRAFT_TARGET: ${{ matrix.target }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -39,14 +60,23 @@ jobs: container: ${{ matrix.container }} steps: - - name: Check out source code - uses: actions/checkout@v2 + - name: Check out full source code for tooling + if: ${{ matrix.useSonarCloud || matrix.name == 'linux-gcc-x86_64' }} + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - fetch-depth: ${{ matrix.fetch-depth }} - submodules: true + fetch-depth: 0 + + - name: Check out latest commit + if: ${{ !matrix.useSonarCloud && matrix.name != 'linux-gcc-x86_64' }} + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + if: matrix.name == 'windows-cl-x86_64' + with: + python-version: '3.12' - name: Restore cache - uses: actions/cache@v2 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/cache key: ${{ runner.os }}-${{ matrix.target }} @@ -65,55 +95,93 @@ jobs: - name: Prepare run: | + New-Item -ItemType Directory "${env:GITHUB_WORKSPACE}/binaries/" + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set forceAsserts=true owncloud/owncloud-client + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set srcDir="${env:GITHUB_WORKSPACE}" owncloud/owncloud-client if ($IsWindows) { & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c dev-utils/nsis } elseif($IsLinux) { - sudo apt-get update -y - sudo apt-get install -y mesa-common-dev libglu1-mesa-dev libmtdev1 libgl1-mesa-dev libmtdev1 libinput-dev libxkbcommon0 libxcb-icccm4 libxcb-image0 libxcb-render-util0 libxcb-xkb1 libxkbcommon-x11-0 libxcb-keysyms1 libxcb-xinerama0 libsm6 & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c dev-utils/linuxdeploy } - name: Install dependencies run: | - & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --install-deps owncloud/owncloud-client + if ("${{ matrix.target }}" -eq "linux-64-gcc" ) { + # build with appimage updater and others + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set enableAppImageUpdater=true owncloud-client + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set enableCrashReporter=true owncloud/owncloud-client + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set enableAutoUpdater=true owncloud/owncloud-client + } + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --install-deps owncloud/owncloud-client + + - name: QML format lint + if: matrix.name == 'linux-gcc-x86_64' + run: | + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c libs/qt6/qttools + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --run pwsh -c "git ls-files *.qml | %{ qmlformat -i `$_}" + $diff = git diff + if ($diff) { + $diff + exit 1 + } - - name: Build + - name: clang-format format lint + if: matrix.name == 'linux-gcc-x86_64' run: | - if ("${{ matrix.target }}" -eq "windows-msvc2019_64-cl") { - & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache --src-dir "${env:GITHUB_WORKSPACE}" --configure owncloud/owncloud-client - $env:BUILD_DIR = $(& "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --get buildDir -q owncloud-client) - & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --run pwsh "${env:GITHUB_WORKSPACE}/.github/workflows/.sonar.ps1" --build - & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache --src-dir "${env:GITHUB_WORKSPACE}" --install --qmerge owncloud/owncloud-client - } else { - & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache --src-dir "${env:GITHUB_WORKSPACE}" owncloud/owncloud-client + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c libs/llvm + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --run git clang-format --force ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} + $diff = git diff + if ($diff) { + $diff + exit 1 } - - name: Run tests + - name: Build + if: ${{ !matrix.useSonarCloud }} run: | - & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache --src-dir "${env:GITHUB_WORKSPACE}" --test owncloud/owncloud-client + if ("${{ matrix.target }}" -eq "macos-64-clang-debug" ) { + # https://api.kde.org/ecm/module/ECMEnableSanitizers.html + # address;leak;undefined + # clang: error: unsupported option '-fsanitize=leak' for target 'x86_64-apple-darwin21.6.0' + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set args="-DECM_ENABLE_SANITIZERS='address;undefined'" owncloud/owncloud-client + } + if ("${{ matrix.target }}" -eq "linux-64-gcc" ) { + # build with appimage updater and others + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set enableAppImageUpdater=true owncloud-client + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set enableCrashReporter=true owncloud/owncloud-client + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set enableAutoUpdater=true owncloud/owncloud-client + } + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache owncloud/owncloud-client - - name: Sonarcloud - if: ${{ matrix.target == 'windows-msvc2019_64-cl' }} + - name: Build for SonarCloud + if: ${{ matrix.useSonarCloud }} run: | + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache --configure owncloud/owncloud-client $env:BUILD_DIR = $(& "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --get buildDir -q owncloud-client) + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --run pwsh "${env:GITHUB_WORKSPACE}/.github/workflows/.sonar.ps1" --build + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache --install --qmerge owncloud/owncloud-client & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --run pwsh "${env:GITHUB_WORKSPACE}/.github/workflows/.sonar.ps1" --analyze + - name: Run tests + run: | + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache --test ${{ matrix.testOptions }} owncloud/owncloud-client + - name: Package run: | - & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache --src-dir "${env:GITHUB_WORKSPACE}" --package owncloud/owncloud-client + if ("${{ matrix.target }}" -eq "linux-64-gcc" ) { + # package without crash reporter + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --set enableCrashReporter=false owncloud/owncloud-client + } + & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --no-cache --package owncloud/owncloud-client - name: Prepare artifacts run: | - New-Item -ItemType Directory "${env:GITHUB_WORKSPACE}/binaries/" -ErrorAction SilentlyContinue - Copy-Item "$env:HOME/craft/binaries/*" "${env:GITHUB_WORKSPACE}/binaries/" + Copy-Item "$env:HOME/craft/binaries/*" "${env:GITHUB_WORKSPACE}/binaries/" -ErrorAction SilentlyContinue & "${env:GITHUB_WORKSPACE}/.github/workflows/.craft.ps1" -c --shelve "${env:GITHUB_WORKSPACE}/.craft.shelf" Copy-Item "${env:GITHUB_WORKSPACE}/.craft.shelf" "${env:GITHUB_WORKSPACE}/binaries/" - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ matrix.os }} - ${{ matrix.target }} + name: ${{ matrix.name }} path: ${{ github.workspace }}/binaries/* - - - diff --git a/.github/workflows/run-in-docker.sh b/.github/workflows/run-in-docker.sh deleted file mode 100644 index e70c30bcca1..00000000000 --- a/.github/workflows/run-in-docker.sh +++ /dev/null @@ -1,32 +0,0 @@ -#! /bin/bash - -image="$1" -shift - -commands="$@" - -if [[ "$image" == "" ]] || [[ "${commands[@]}" == "" ]]; then - echo "Usage: bash $0 " - exit 1 -fi - -set -exo pipefail - -extra_args=() - -if tty -s; then - extra_args+=("-t") -fi - -docker run \ - --rm \ - --user "$(id -u)" \ - -i "${extra_args[@]}" \ - -e TX_TOKEN \ - -e HOME \ - -v "$HOME:$HOME" \ - -v "$(readlink -f .)":/ws \ - --entrypoint sh \ - -w /ws \ - "$image" \ - -c "${commands[@]}" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 2e4abd89e42..00000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: "Close stale issues" -on: - schedule: - - cron: "0 0 * * *" - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - debug-only: false - exempt-all-milestones: true - exempt-issue-labels: "blue-ticket, p1-urgent, p2-high, p3-medium, p4-low" - days-before-stale: 30 - stale-issue-message: "This issue was marked stale because it has been open for 30 days with no activity. Remove the stale label or comment or this will be closed in 7 days." - days-before-close: 7 - close-issue-message: "The issue was marked as stale for 7 days and closed automatically." - start-date: "2021-01-01T00:00:00Z" diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index b88e438ae3a..ce95a807992 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -5,56 +5,87 @@ on: schedule: - cron: "0 3 * * *" -defaults: - run: - shell: pwsh +permissions: {} jobs: update-translations: + permissions: + contents: read + strategy: + matrix: + # current transifex plan only allows 1 additional branch + branch: ['master', '6'] + runs-on: ubuntu-latest env: TX_TOKEN: ${{ secrets.TX_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive - ref: "2.10" + fetch-depth: 0 + ref: ${{ matrix.branch }} + persist-credentials: false + + - name: install-deps + run: sudo apt install -y qt6-l10n-tools + + - name: l10n-remove-old + run: | + rm translations/*.ts + git checkout translations/client_en.ts - name: l10n-read - run: bash .github/workflows/run-in-docker.sh rabits/qt:5.12-desktop "cd translations && make l10n-read" + run: /usr/lib/qt6/bin/lupdate src -no-obsolete -ts translations/client_en.ts - - name: l10n-push - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - run: bash .github/workflows/run-in-docker.sh owncloudci/transifex:latest "cd translations && make l10n-push" + - name: l10n-push-source + if: ${{ matrix.branch != 'master' }} + uses: transifex/cli-action@584fd205cbe598773b5a81ce711fa44842678189 # v2 + with: + token: ${{ secrets.TX_TOKEN }} + args: push -s --branch "${{ matrix.branch }}" - - name: l10n-pull - run: bash .github/workflows/run-in-docker.sh owncloudci/transifex:latest "cd translations && make l10n-pull" + - name: l10n-push-source + if: ${{ matrix.branch == 'master' }} + uses: transifex/cli-action@584fd205cbe598773b5a81ce711fa44842678189 # v2 + with: + token: ${{ secrets.TX_TOKEN }} + args: push -s + + - name: fix-transifex-action + run: rm -rf /tmp/tx - - name: l10n-write - run: bash .github/workflows/run-in-docker.sh owncloudci/transifex:latest "cd translations && make l10n-write" + - name: l10n-pull + if: ${{ matrix.branch != 'master' }} + uses: transifex/cli-action@584fd205cbe598773b5a81ce711fa44842678189 # v2 + with: + token: ${{ secrets.TX_TOKEN }} + args: pull --force --all --silent --branch "${{ matrix.branch }}" - - name: l10n-clean - run: bash .github/workflows/run-in-docker.sh owncloudci/transifex:latest "cd translations && make l10n-clean" + - name: l10n-pull + if: ${{ matrix.branch == 'master' }} + uses: transifex/cli-action@584fd205cbe598773b5a81ce711fa44842678189 # v2 + with: + token: ${{ secrets.TX_TOKEN }} + args: pull --force --all --silent - - name: update-repo-before-commit - run: | - git status - git stash - git pull --ff-only origin + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + app-id: ${{ secrets.TRANSLATION_APP_ID }} + private-key: ${{ secrets.TRANSLATION_APP_PRIVATE_KEY }} - - name: commit and push - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - run: | - if( -not (git stash list)) { - git stash pop - install -d -m 0700 ~/.ssh - Set-Content -Value "${{ secrets.DEPLOYMENT_SSH_KEY }}" -Path ~/.ssh/id_ed25519 - chmod 0600 ~/.ssh/id_ed25519 - git config user.name "ownClouders" - git config user.email "devops@owncloud.com" - git add translations/ - git commit -m "[tx] updated client translations from transifex [skip ci]" - git push git@github.com:owncloud/client.git - } + - name: Create or update translations PR + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + token: ${{ steps.app-token.outputs.token }} + branch: chore/translations-update-${{ matrix.branch }} + base: ${{ matrix.branch }} + commit-message: "chore: update translations from transifex" + title: "chore: update translations from transifex" + body: "Automated translation update from Transifex. This pull request is updated on each sync run — merging it will close it and a fresh one will be opened on the next change." + delete-branch: true + sign-commits: true diff --git a/.gitignore b/.gitignore index 474a9f4ed54..3b8359b14af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ +/branding/ .gitmodules *build*/ *flymake* CMakeLists.txt.user* +.vs/ +CMakeSettings.json *~ *.autosave doc/_build/* @@ -174,4 +177,6 @@ FakesAssemblies/ # squish test test/gui/config.ini +test/gui/reports */**/__pycache__/ +test/gui/users.json diff --git a/.gitmodules b/.gitmodules index 1db1256f40d..e69de29bb2d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "src/3rdparty/libcrashreporter-qt"] - path = src/3rdparty/libcrashreporter-qt - url = git://github.com/dschmidt/libcrashreporter-qt.git diff --git a/.tx/config b/.tx/config new file mode 100644 index 00000000000..5e975d908f7 --- /dev/null +++ b/.tx/config @@ -0,0 +1,12 @@ +[main] +host = https://www.transifex.com + +[o:owncloud-org:p:owncloud:r:client] +source_file = translations/client_en.ts +source_lang = en +file_filter = translations/client_.ts +type = QT +minimum_perc = 75 + +# de_DE holds the formal translation which we want as default +lang_map = de_DE: de, de: de-informal, pt_PT: pt, cs_CZ: cs, et_EE: et, fi_FI: fi, ja_JP: ja, hu_HU: hu, sk_SK: client_sk, th_TH: th diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ccfb60a73..ccc0eb535de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,51 +1,2564 @@ -Changelog for ownCloud Desktop Client [unreleased] (UNRELEASED) -======================================= +# Table of Contents + +* [Changelog for unreleased](#changelog-for-owncloud-desktop-client-unreleased-unreleased) +* [Changelog for 6.0.3](#changelog-for-owncloud-desktop-client-603-2026-01-07) +* [Changelog for 6.0.2](#changelog-for-owncloud-desktop-client-602-2025-11-03) +* [Changelog for 6.0.1](#changelog-for-owncloud-desktop-client-601-2025-10-10) +* [Changelog for 6.0.0](#changelog-for-owncloud-desktop-client-600-2025-09-08) +* [Changelog for 5.3.2](#changelog-for-owncloud-desktop-client-532-2025-01-20) +* [Changelog for 5.3.1](#changelog-for-owncloud-desktop-client-531-2024-06-11) +* [Changelog for 5.3.0](#changelog-for-owncloud-desktop-client-530-2024-06-06) +* [Changelog for 5.2.1](#changelog-for-owncloud-desktop-client-521-2024-01-04) +* [Changelog for 5.2.0](#changelog-for-owncloud-desktop-client-520-2023-11-27) +* [Changelog for 5.1.2](#changelog-for-owncloud-desktop-client-512-2023-11-10) +* [Changelog for 5.1.1](#changelog-for-owncloud-desktop-client-511-2023-11-09) +* [Changelog for 5.1.0](#changelog-for-owncloud-desktop-client-510-2023-11-02) +* [Changelog for 5.0.0](#changelog-for-owncloud-desktop-client-500-2023-10-05) +* [Changelog for 4.2.0](#changelog-for-owncloud-desktop-client-420-2023-08-07) +* [Changelog for 4.1.0](#changelog-for-owncloud-desktop-client-410-2023-06-15) +* [Changelog for 4.0.0](#changelog-for-owncloud-desktop-client-400-2023-05-11) +* [Changelog for 3.2.1](#changelog-for-owncloud-desktop-client-321-2023-03-07) +* [Changelog for 3.2.0](#changelog-for-owncloud-desktop-client-320-2023-02-15) +* [Changelog for 3.1.0](#changelog-for-owncloud-desktop-client-310-2023-01-25) +* [Changelog for 3.0.0](#changelog-for-owncloud-desktop-client-300-2022-11-30) +* [Changelog for 2.11.1](#changelog-for-owncloud-desktop-client-2111-2022-08-31) +* [Changelog for 2.11.0](#changelog-for-owncloud-desktop-client-2110-2022-08-18) +* [Changelog for 2.10.1](#changelog-for-owncloud-desktop-client-2101-2022-04-05) +* [Changelog for 2.10.0](#changelog-for-owncloud-desktop-client-2100-2022-01-17) +* [Changelog for 2.9.2](#changelog-for-owncloud-desktop-client-292-2021-11-24) +* [Changelog for 2.9.1](#changelog-for-owncloud-desktop-client-291-2021-10-13) +* [Changelog for 2.9.0](#changelog-for-owncloud-desktop-client-290-2021-09-08) +* [Changelog for 2.8.2](#changelog-for-owncloud-desktop-client-282-2021-05-28) +* [Changelog for 2.8.1](#changelog-for-owncloud-desktop-client-281-2021-05-21) +* [Changelog for 2.8.0](#changelog-for-owncloud-desktop-client-280-2021-05-06) +* [Changelog for 2.7.6](#changelog-for-owncloud-desktop-client-276-2021-02-04) +* [Changelog for 2.7.5](#changelog-for-owncloud-desktop-client-275-2021-01-28) +* [Changelog for 2.7.4](#changelog-for-owncloud-desktop-client-274-2020-12-21) +* [Changelog for 2.7.3](#changelog-for-owncloud-desktop-client-273-2020-12-11) +* [Changelog for 2.7.2](#changelog-for-owncloud-desktop-client-272-2020-12-02) +* [Changelog for 2.7.1](#changelog-for-owncloud-desktop-client-271-2020-11-18) +* [Changelog for 2.7.0](#changelog-for-owncloud-desktop-client-270-2020-11-13) +* [Changelog for 2.6.3](#changelog-for-owncloud-desktop-client-263-2020-06-10) +* [Changelog for 2.6.2](#changelog-for-owncloud-desktop-client-262-2020-02-21) +* [Changelog for 2.6.1 versions and below](#changelog-for-261-versions-and-below) +# Changelog for ownCloud Desktop Client [unreleased] (UNRELEASED) + The following sections list the changes in ownCloud Desktop Client unreleased relevant to ownCloud admins and users. -[unreleased]: https://github.com/owncloud/client/compare/v2.9.2...master +[unreleased]: https://github.com/owncloud/client/compare/v6.0.3...master + +## Summary + +* Bugfix - Fix compatibility with extra-cmake-modules-6.19.0: [#12374](https://github.com/owncloud/client/pull/12374) +* Change - Remove capability to upload conflict files: [#12369](https://github.com/owncloud/client/pull/12369) + +## Details + +* Bugfix - Fix compatibility with extra-cmake-modules-6.19.0: [#12374](https://github.com/owncloud/client/pull/12374) + + Fix runtime crash at startup with ECM 6.19. + + https://github.com/owncloud/client/pull/12374 + +* Change - Remove capability to upload conflict files: [#12369](https://github.com/owncloud/client/pull/12369) + + None of the supported servers implements (or plans on implementing) this + feature. This removal includes the `OWNCLOUD_UPLOAD_CONFLICT_FILES` environment + variable. + + https://github.com/owncloud/client/pull/12369 + +# Changelog for ownCloud Desktop Client [6.0.3] (2026-01-07) + +The following sections list the changes in ownCloud Desktop Client 6.0.3 relevant to +ownCloud admins and users. + +[6.0.3]: https://github.com/owncloud/client/compare/v6.0.2...v6.0.3 + +## Summary + +* Security - Fixing medium security vulnerability in password input handling: [#12430](https://github.com/owncloud/client/pull/12430) +* Change - Third party upgrades in distributed binaries: [#12364](https://github.com/owncloud/client/pull/12364) + +## Details + +* Security - Fixing medium security vulnerability in password input handling: [#12430](https://github.com/owncloud/client/pull/12430) + + CVE and security advisory will follow. + + https://github.com/owncloud/client/pull/12430 + https://github.com/owncloud/client/pull/12431 + +* Change - Third party upgrades in distributed binaries: [#12364](https://github.com/owncloud/client/pull/12364) + + We updated OpenSSL to 3.4.3 + + https://github.com/owncloud/client/pull/12364 + https://github.com/owncloud/client/pull/12432 + +# Changelog for ownCloud Desktop Client [6.0.2] (2025-11-03) + +The following sections list the changes in ownCloud Desktop Client 6.0.2 relevant to +ownCloud admins and users. + +[6.0.2]: https://github.com/owncloud/client/compare/v6.0.1...v6.0.2 + +## Summary + +* Security - Fixing high security vulnerability in MacOS client: [#12379](https://github.com/owncloud/client/pull/12379) + +## Details + +* Security - Fixing high security vulnerability in MacOS client: [#12379](https://github.com/owncloud/client/pull/12379) + + CVE: CVE-2025-64441 + + https://github.com/owncloud/client/pull/12379 + https://github.com/owncloud/client/pull/12389 + https://github.com/owncloud/client/pull/12390 + +# Changelog for ownCloud Desktop Client [6.0.1] (2025-10-10) + +The following sections list the changes in ownCloud Desktop Client 6.0.1 relevant to +ownCloud admins and users. + +[6.0.1]: https://github.com/owncloud/client/compare/v6.0.0...v6.0.1 + +## Summary + +* Bugfix - Fix VFS checkbox in folder wizard: [#12305](https://github.com/owncloud/client/pull/12305) +* Bugfix - Reverting UTF-8 normalisation change: [#12334](https://github.com/owncloud/client/pull/12334) +* Change - Move to trash-bin is enabled now by default: [#12338](https://github.com/owncloud/client/pull/12338) + +## Details + +* Bugfix - Fix VFS checkbox in folder wizard: [#12305](https://github.com/owncloud/client/pull/12305) + + On the last page of the folder wizard, there is a checkbox to select/deselect + VFS. By default this is checked. The bug was that after unchecking this + checkbox, it could not be checked again. This has now been fixed. + + https://github.com/owncloud/client/pull/12305 + +* Bugfix - Reverting UTF-8 normalisation change: [#12334](https://github.com/owncloud/client/pull/12334) + + The UTF-8 normalization change as per #12039 has been reverted. + + https://github.com/owncloud/client/pull/12334 + https://github.com/owncloud/client/pull/12039 + +* Change - Move to trash-bin is enabled now by default: [#12338](https://github.com/owncloud/client/pull/12338) + + To protect users from data loss the "move to trash" option is now enabled by + default. + + https://github.com/owncloud/client/pull/12338 + +# Changelog for ownCloud Desktop Client [6.0.0] (2025-09-08) + +The following sections list the changes in ownCloud Desktop Client 6.0.0 relevant to +ownCloud admins and users. + +[6.0.0]: https://github.com/owncloud/client/compare/v5.3.2...v6.0.0 + +## Summary + +* Bugfix - Fix location loading on Mac: [#6761](https://github.com/owncloud/enterprise/issues/6761) +* Bugfix - Schedule syncs after an abort: [#11396](https://github.com/owncloud/client/pull/11396) +* Bugfix - Fix description of manage public links action: [#11450](https://github.com/owncloud/client/issues/11450) +* Bugfix - Fix early-use crash in the folder watcher on Linux: [#11460](https://github.com/owncloud/client/issues/11460) +* Bugfix - Properly encode scope and prompt items in OAuth URL: [#11472](https://github.com/owncloud/client/issues/11472) +* Bugfix - Client stuck in reconnecting state after application start: [#11478](https://github.com/owncloud/client/pull/11478) +* Bugfix - Strip user information from new-connection URL: [#11497](https://github.com/owncloud/client/issues/11497) +* Bugfix - Support logging redirect: [#11581](https://github.com/owncloud/client/pull/11581) +* Bugfix - Don't store null certificates in the account: [#11621](https://github.com/owncloud/client/issues/11621) +* Bugfix - Fix crash for folders that could not be initialized: [#11664](https://github.com/owncloud/client/pull/11664) +* Bugfix - Make open account in browser accessible for keyboard navigation: [#11772](https://github.com/owncloud/client/issues/11772) +* Bugfix - Don't log misleading error if dynamic registration failed: [#11839](https://github.com/owncloud/client/pull/11839) +* Bugfix - Fix crash after resource job fails: [#12045](https://github.com/owncloud/client/pull/12045) +* Bugfix - Remove any existing ResourceCache directories for accounts: [#12214](https://github.com/owncloud/client/pull/12214) +* Bugfix - Delete account-specific caches when an account is removed: [#12224](https://github.com/owncloud/client/pull/12224) +* Change - Revert local folder name back to pre 3.0 behavior: [#6390](https://github.com/owncloud/enterprise/issues/6390) +* Change - Make messages translatable that occur early in start-up: [#11142](https://github.com/owncloud/client/issues/11142) +* Change - Remove unused command line option --language: [#11245](https://github.com/owncloud/client/issues/11245) +* Change - Windows: always use fusion style: [#11275](https://github.com/owncloud/client/issues/11275) +* Change - Deprecate suffix VFS: [#11325](https://github.com/owncloud/client/issues/11325) +* Change - Removal of selective sync tree from account status page: [#11357](https://github.com/owncloud/client/issues/11357) +* Change - Fix three badly styled warning messages: [#11412](https://github.com/owncloud/client/issues/11412) +* Change - Removed support for limiting sync of big new folder & external storage: [#11517](https://github.com/owncloud/client/pull/11517) +* Change - Set the 'No' button as default action in the Unknown Certificate dialog: [#11531](https://github.com/owncloud/client/issues/11531) +* Change - Remove `Remove all files` dialog: [#11569](https://github.com/owncloud/client/issues/11569) +* Change - Rewrite of the spaces browser: [#11595](https://github.com/owncloud/client/pull/11595) +* Change - New update channel for ownCloud infinite scale: [#11742](https://github.com/owncloud/client/issues/11742) +* Change - Remove support for branded shell extensions on Linux: [#11743](https://github.com/owncloud/client/issues/11743) +* Change - Only allow to add un-synced spaces: [#11752](https://github.com/owncloud/client/issues/11752) +* Change - Rewrite of the login widget in QML: [#11856](https://github.com/owncloud/client/pull/11856) +* Change - Only support APFS on macOS: [#12044](https://github.com/owncloud/client/pull/12044) +* Change - Bump minimum macOS version to 13.0: [#12245](https://github.com/owncloud/client/pull/12245) +* Change - Update the sync exclude list for Infinite Scale: [#12262](https://github.com/owncloud/client/pull/12262) +* Enhancement - Add option to pause synchronization on metered connections: [#4808](https://github.com/owncloud/client/issues/4808) +* Enhancement - Add option to move files to trash instead of deleting: [#9001](https://github.com/owncloud/client/issues/9001) +* Enhancement - Prevent same default sync folder for multiple accounts with Spaces: [#11204](https://github.com/owncloud/client/issues/11204) +* Enhancement - Use less modal dialogs: [#11304](https://github.com/owncloud/client/issues/11304) +* Enhancement - Include recent changes in scheduled syncs: [#11347](https://github.com/owncloud/client/pull/11347) +* Enhancement - Improve handling of a server blocking an unsupported client: [#11424](https://github.com/owncloud/client/issues/11424) +* Enhancement - Ask user to restart application when update is available on Windows: [#11442](https://github.com/owncloud/client/pull/11442) +* Enhancement - New Folder status: [#11491](https://github.com/owncloud/client/pull/11491) +* Enhancement - Pause sync when behind a captive portal: [#11533](https://github.com/owncloud/client/issues/11533) +* Enhancement - Support for prompt_values_supported in openid-configuration: [#11729](https://github.com/owncloud/client/pull/11729) +* Enhancement - New branding option: automatically sync new spaces: [#11751](https://github.com/owncloud/client/issues/11751) +* Enhancement - Display how many spaces are un-synced: [#11760](https://github.com/owncloud/client/pull/11760) + +## Details + +* Bugfix - Fix location loading on Mac: [#6761](https://github.com/owncloud/enterprise/issues/6761) + + https://github.com/owncloud/enterprise/issues/6761 + +* Bugfix - Schedule syncs after an abort: [#11396](https://github.com/owncloud/client/pull/11396) + + We fixed a bug where the client stopped scheduling syncs after a sync was + aborted. + + https://github.com/owncloud/client/pull/11396 + +* Bugfix - Fix description of manage public links action: [#11450](https://github.com/owncloud/client/issues/11450) + + https://github.com/owncloud/client/issues/11450 + +* Bugfix - Fix early-use crash in the folder watcher on Linux: [#11460](https://github.com/owncloud/client/issues/11460) + + In a few error scenarios, the folder watcher on Linux shows a dialog with an + error message. A crash has been fixed where this occurred before the GUI was + fully initialised. + + https://github.com/owncloud/client/issues/11460 + https://github.com/owncloud/client/pull/11475 + +* Bugfix - Properly encode scope and prompt items in OAuth URL: [#11472](https://github.com/owncloud/client/issues/11472) + + Fixed a bug where the scope and prompt items of an OAuth query would not be + encoded, resulting in an invalid request. + + https://github.com/owncloud/client/issues/11472 + https://github.com/owncloud/client/pull/11479 + +* Bugfix - Client stuck in reconnecting state after application start: [#11478](https://github.com/owncloud/client/pull/11478) + + We fixed a bug where the client was starting OAuth authentication but did not + prompt the user properly to renew authentication. This resulted in the client + apparently getting stuck in the "reconnecting" state. + + https://github.com/owncloud/client/pull/11478 + +* Bugfix - Strip user information from new-connection URL: [#11497](https://github.com/owncloud/client/issues/11497) + + Credentials in the URL are not supported for some time. This fix strips them + from the URL before storage and using it to authenticate the client to the + server. + + https://github.com/owncloud/client/issues/11497 + https://github.com/owncloud/client/pull/11509 + +* Bugfix - Support logging redirect: [#11581](https://github.com/owncloud/client/pull/11581) + + We now log when all urls when a request was redirected. + + https://github.com/owncloud/client/pull/11581 + +* Bugfix - Don't store null certificates in the account: [#11621](https://github.com/owncloud/client/issues/11621) + + https://github.com/owncloud/client/issues/11621 + +* Bugfix - Fix crash for folders that could not be initialized: [#11664](https://github.com/owncloud/client/pull/11664) + + We fixed a bug where interacting with a folder in the file browser could lead to + a crash if the folder was not properly initialized. + + https://github.com/owncloud/client/pull/11664 + +* Bugfix - Make open account in browser accessible for keyboard navigation: [#11772](https://github.com/owncloud/client/issues/11772) + + On the account settings page, the status label contained the URL of the server. + This URL was not accessible with keyboard navigation or when a screen-reader was + used. Now there is an "Open in Web Browser" action in the pop-up menu of the + "Manage Account" button (which is next to the status label), and the URL has + been removed from the status label. + + https://github.com/owncloud/client/issues/11772 + https://github.com/owncloud/client/issues/11800 + https://github.com/owncloud/client/pull/11810 + +* Bugfix - Don't log misleading error if dynamic registration failed: [#11839](https://github.com/owncloud/client/pull/11839) + + https://github.com/owncloud/client/pull/11839 + +* Bugfix - Fix crash after resource job fails: [#12045](https://github.com/owncloud/client/pull/12045) + + When a network error occurs while fetching a resource (e.g. a space image), no + icon is available, and a crash would occur. This is fixed by returning an empty + icon. + + https://github.com/owncloud/client/pull/12045 + +* Bugfix - Remove any existing ResourceCache directories for accounts: [#12214](https://github.com/owncloud/client/pull/12214) + + Some versions of the client failed to remove the temporary cache directory on + exit. This can result in multiple GB of cached space images. These cache + directories are now cleaned up before creating a new cache. + + https://github.com/owncloud/client/pull/12214 + +* Bugfix - Delete account-specific caches when an account is removed: [#12224](https://github.com/owncloud/client/pull/12224) + + This includes the cache for space images, as well as certain cached network + jobs. + + https://github.com/owncloud/client/pull/12224 + +* Change - Revert local folder name back to pre 3.0 behavior: [#6390](https://github.com/owncloud/enterprise/issues/6390) + + Due to user requests, we reverted the folder name from + + ``` + ownCloud - Albert@owncloud.com + ownCloud - Katherine@owncloud.org + ownCloud - Marie@owncloud.com + ``` + + Back to + + ``` + ownCloud + ownCloud (1) + ownCloud (2) + ``` + + https://github.com/owncloud/enterprise/issues/6390 + +* Change - Make messages translatable that occur early in start-up: [#11142](https://github.com/owncloud/client/issues/11142) + + These messages include the command-line messages (both errors and the help + text). To do this, the deprecated `--confdir` option has been removed. This + option was only used on windows before roaming profiles were supported. + + https://github.com/owncloud/client/issues/11142 + https://github.com/owncloud/client/issues/11246 + https://github.com/owncloud/client/pull/11300 + +* Change - Remove unused command line option --language: [#11245](https://github.com/owncloud/client/issues/11245) + + This option was used before the UI language could be chosen in the settings. The + option --list-languages is also removed, as it no longer serves a purpose. + + https://github.com/owncloud/client/issues/11245 + https://github.com/owncloud/client/pull/11270 + +* Change - Windows: always use fusion style: [#11275](https://github.com/owncloud/client/issues/11275) + + We already used the fusion style on Windows for years if the system used dpi + scaling. Since Qt6.5 the fusion style also support the dark theme on Windows. + Those two reasons convinced us that always using the fusion style on Windows + will result in the best experience for the users. + + https://github.com/owncloud/client/issues/11275 + +* Change - Deprecate suffix VFS: [#11325](https://github.com/owncloud/client/issues/11325) + + We decided to deprecate the suffix VFS implementations and thus prepare its + removal in 7.0. + + https://github.com/owncloud/client/issues/11325 + +* Change - Removal of selective sync tree from account status page: [#11357](https://github.com/owncloud/client/issues/11357) + + We removed the selective sync support from the main widget and moved it to its + own dialog, available through the context menu of the sync folder. + + Having both the sync status and the selective sync settings in the same widget + caused a lot of problems and overhead over the years. Additionally, it required + us to maintain a lot of special and basically duplicated code, as we already had + a dedicated widget to handle selective sync. + + https://github.com/owncloud/client/issues/11357 + https://github.com/owncloud/client/issues/11357 + https://github.com/owncloud/client/issues/8102 + https://github.com/owncloud/client/issues/5423 + https://github.com/owncloud/client/issues/4080 + https://github.com/owncloud/client/pull/11524 + +* Change - Fix three badly styled warning messages: [#11412](https://github.com/owncloud/client/issues/11412) + + There were 3 labels for warning messages that were nearly unreadable in dark + mode due to bad styling. These have been changed to only use a bold font, but + are now displayed with a big warning icon on their left. The places are: the + account settings page, the folder wizard remote path page, and the log browser. + + https://github.com/owncloud/client/issues/11412 + https://github.com/owncloud/client/pull/11470 + +* Change - Removed support for limiting sync of big new folder & external storage: [#11517](https://github.com/owncloud/client/pull/11517) + + We removed an option which allowed to block new folders bigger than a set limit, + from being synchronized automatically, when selective synchronization is used. + Additionally, we removed the option that allowed to block automatic + synchronization of external storage. + + https://github.com/owncloud/client/pull/11517 + +* Change - Set the 'No' button as default action in the Unknown Certificate dialog: [#11531](https://github.com/owncloud/client/issues/11531) + + When an unknown certificate is used by the server, the safe action is to reject + this certificate. So the default action for this dialog should be not to accept + this unknown certificate. + + https://github.com/owncloud/client/issues/11531 + +* Change - Remove `Remove all files` dialog: [#11569](https://github.com/owncloud/client/issues/11569) + + We no longer ask the user whether all files should be removed but just sync. + + https://github.com/owncloud/client/issues/11569 + https://github.com/owncloud/client/issues/7304 + +* Change - Rewrite of the spaces browser: [#11595](https://github.com/owncloud/client/pull/11595) + + https://github.com/owncloud/client/pull/11595 + +* Change - New update channel for ownCloud infinite scale: [#11742](https://github.com/owncloud/client/issues/11742) + + The release series 6 is a long term release. As version 7 will drop the support + for ownCloud 10, users will either have to select the ownCloud infinite scale + update channel in the about-dialog or manually install a newer release. This + change only affects the unbranded version. + + https://github.com/owncloud/client/issues/11742 + +* Change - Remove support for branded shell extensions on Linux: [#11743](https://github.com/owncloud/client/issues/11743) + + From now on user will have to install dolphin-owncloud nautilus-owncloud instead + of a branded version. + + https://github.com/owncloud/client/issues/11743 + +* Change - Only allow to add un-synced spaces: [#11752](https://github.com/owncloud/client/issues/11752) + + We now prevent users from syncing the same space multiple times. + + https://github.com/owncloud/client/issues/11752 + https://github.com/owncloud/client/pull/11759 + +* Change - Rewrite of the login widget in QML: [#11856](https://github.com/owncloud/client/pull/11856) + + https://github.com/owncloud/client/pull/11856 + +* Change - Only support APFS on macOS: [#12044](https://github.com/owncloud/client/pull/12044) + + Apple File System in contrary to HFS+ is Unicode normalization preserving. To + support this properly the upcoming version 6 of desktop client will support NFC + and NFD encoding on all client platforms based on the individual + characteristics. + + https://github.com/owncloud/client/pull/12044 + +* Change - Bump minimum macOS version to 13.0: [#12245](https://github.com/owncloud/client/pull/12245) + + https://github.com/owncloud/client/pull/12245 + https://github.com/owncloud/client/pull/12246 + +* Change - Update the sync exclude list for Infinite Scale: [#12262](https://github.com/owncloud/client/pull/12262) + + Files with the extension `.space` and `.psec` are now excluded from sync by + default. + + https://github.com/owncloud/client/pull/12262 + https://github.com/owncloud/client/pull/12093 + +* Enhancement - Add option to pause synchronization on metered connections: [#4808](https://github.com/owncloud/client/issues/4808) + + On platforms that support metered-connection detection, an option is now + available to pause folder synchronization when the network connection switches + to metered-connection. When synchronization is paused, force-syncing can still + be done. + + https://github.com/owncloud/client/issues/4808 + https://github.com/owncloud/client/pull/11305 + +* Enhancement - Add option to move files to trash instead of deleting: [#9001](https://github.com/owncloud/client/issues/9001) + + An option has been added that when files on the server are deleted, the client + will move them to the system trash bin instead of deleting those files. + + https://github.com/owncloud/client/issues/9001 + https://github.com/owncloud/client/pull/11320 + +* Enhancement - Prevent same default sync folder for multiple accounts with Spaces: [#11204](https://github.com/owncloud/client/issues/11204) + + This is done by tagging the sync folder by an extended attribute (Linux/macOS) + or an alternative data stream (Windows). + + https://github.com/owncloud/client/issues/11204 + https://github.com/owncloud/client/pull/11364 + +* Enhancement - Use less modal dialogs: [#11304](https://github.com/owncloud/client/issues/11304) + + As the rather high number of modal dialogs we used caused different issues on + different platforms, we decided to use them less often and try other concepts. + + https://github.com/owncloud/client/issues/11304 + https://github.com/owncloud/client/issues/11032 + https://github.com/owncloud/client/issues/11208 + +* Enhancement - Include recent changes in scheduled syncs: [#11347](https://github.com/owncloud/client/pull/11347) + + When starting a new sync we now also include the recent change events in the + discovery. + + https://github.com/owncloud/client/pull/11347 + +* Enhancement - Improve handling of a server blocking an unsupported client: [#11424](https://github.com/owncloud/client/issues/11424) + + We improved the handling of HTTP 403 status codes. + + https://github.com/owncloud/client/issues/11424 + +* Enhancement - Ask user to restart application when update is available on Windows: [#11442](https://github.com/owncloud/client/pull/11442) + + Instead of a tray message, we now display a modal dialog on Windows to inform + the user about a downloaded update. Also, we now can prompt the user to restart + the application and apply the update immediately, adding a bit of user comfort. + + https://github.com/owncloud/client/pull/11442 + +* Enhancement - New Folder status: [#11491](https://github.com/owncloud/client/pull/11491) + + We rewrote the Folder status page. + + https://github.com/owncloud/client/issues/11527 + https://github.com/owncloud/client/pull/11491 + +* Enhancement - Pause sync when behind a captive portal: [#11533](https://github.com/owncloud/client/issues/11533) + + When the operating system detects a captive portal, stop all outgoing network + requests. These requests will fail with an SSL certificate mismatch error. When + the captive portal is "gone", synchronisation will be restarted. + + https://github.com/owncloud/client/issues/11533 + https://github.com/owncloud/client/pull/11567 + +* Enhancement - Support for prompt_values_supported in openid-configuration: [#11729](https://github.com/owncloud/client/pull/11729) + + We implemented support for idP's to specify the supported prompt values. + + https://github.com/owncloud/client/pull/11729 + https://github.com/owncloud/client/pull/12163 + +* Enhancement - New branding option: automatically sync new spaces: [#11751](https://github.com/owncloud/client/issues/11751) + + A branding option was added to add new sync connections for newly discovered + spaces. + + https://github.com/owncloud/client/issues/11751 + https://github.com/owncloud/client/pull/11774 + +* Enhancement - Display how many spaces are un-synced: [#11760](https://github.com/owncloud/client/pull/11760) + + https://github.com/owncloud/client/pull/11760 + +# Changelog for ownCloud Desktop Client [5.3.2] (2025-01-20) + +The following sections list the changes in ownCloud Desktop Client 5.3.2 relevant to +ownCloud admins and users. + +[5.3.2]: https://github.com/owncloud/client/compare/v5.3.1...v5.3.2 + +## Summary + +* Security - Fixing high security vulnerability in Windows Installer: [#12010](https://github.com/owncloud/client/issues/12010) +* Bugfix - OAuth: Prevent logout when refreshing token: [#11980](https://github.com/owncloud/client/issues/11980) +* Bugfix - Crash when checking the sync file status: [#11981](https://github.com/owncloud/client/issues/11981) + +## Details + +* Security - Fixing high security vulnerability in Windows Installer: [#12010](https://github.com/owncloud/client/issues/12010) + + CVE: CVE-2025-24926 + + https://github.com/owncloud/client/issues/12010 + +* Bugfix - OAuth: Prevent logout when refreshing token: [#11980](https://github.com/owncloud/client/issues/11980) + + Prevent the user being logged out when the network disappears during OAuth token + refresh. + + https://github.com/owncloud/client/issues/11980 + https://github.com/owncloud/client/pull/11984 + https://github.com/owncloud/client/pull/12005 + +* Bugfix - Crash when checking the sync file status: [#11981](https://github.com/owncloud/client/issues/11981) + + Sometimes a `Folder` is created for a local path that we cannot sync (e.g. it's + not writable). This folder will not have a sync engine. If the engine of this + `Folder` is accessed, this will lead to a crash. + + The fix is to check for the existence of a sync engine in the + `Folder::canSync()` method. + + https://github.com/owncloud/client/issues/11981 + https://github.com/owncloud/client/pull/11982 + https://github.com/owncloud/client/pull/12000 + +# Changelog for ownCloud Desktop Client [5.3.1] (2024-06-11) + +The following sections list the changes in ownCloud Desktop Client 5.3.1 relevant to +ownCloud admins and users. + +[5.3.1]: https://github.com/owncloud/client/compare/v5.3.0...v5.3.1 + +## Summary + +* Bugfix - Ensure the Windows shell extension is linked statically: [#11691](https://github.com/owncloud/client/issues/11691) + +## Details + +* Bugfix - Ensure the Windows shell extension is linked statically: [#11691](https://github.com/owncloud/client/issues/11691) + + We fixed a regression where + https://cmake.org/cmake/help/latest/policy/CMP0091.html caused our shell + extension no longer to be linked statically. + + https://github.com/owncloud/client/issues/11691 + +# Changelog for ownCloud Desktop Client [5.3.0] (2024-06-06) + +The following sections list the changes in ownCloud Desktop Client 5.3.0 relevant to +ownCloud admins and users. + +[5.3.0]: https://github.com/owncloud/client/compare/v5.2.1...v5.3.0 + +## Summary + +* Bugfix - Fix early-use crash in the folder watcher on Linux: [#11460](https://github.com/owncloud/client/issues/11460) +* Bugfix - Client stuck in `reconnecting`: [#11467](https://github.com/owncloud/client/pull/11467) +* Bugfix - Ensure folders are scheduled only once: [#11552](https://github.com/owncloud/client/issues/11552) +* Change - Revert local folder name back to pre 3.0 behavior: [#6390](https://github.com/owncloud/enterprise/issues/6390) +* Enhancement - Add support to provide a list of ports for the OAuth process: [#11276](https://github.com/owncloud/client/issues/11276) +* Enhancement - Support `Active Directory Federation Service` as identity provider: [#11646](https://github.com/owncloud/client/issues/11646) + +## Details + +* Bugfix - Fix early-use crash in the folder watcher on Linux: [#11460](https://github.com/owncloud/client/issues/11460) + + In a few error scenarios, the folder watcher on Linux shows a dialog with an + error message. A crash has been fixed where this occurred before the GUI was + fully initialised. + + https://github.com/owncloud/client/issues/11460 + https://github.com/owncloud/client/pull/11475 + +* Bugfix - Client stuck in `reconnecting`: [#11467](https://github.com/owncloud/client/pull/11467) + + Properly handle errors during the update of the server settings. Due to an + unhandled result, the client could get stuck in a `reconnecting` state. + + https://github.com/owncloud/client/pull/11467 + +* Bugfix - Ensure folders are scheduled only once: [#11552](https://github.com/owncloud/client/issues/11552) + + We fixed a bug where a folder could be scheduled multiple times. + + https://github.com/owncloud/client/issues/11552 + +* Change - Revert local folder name back to pre 3.0 behavior: [#6390](https://github.com/owncloud/enterprise/issues/6390) + + Due to user requests, we reverted the folder name from + + ``` + ownCloud - Albert@owncloud.com + ownCloud - Katherine@owncloud.org + ownCloud - Marie@owncloud.com + ``` + + Back to + + ``` + ownCloud + ownCloud (1) + ownCloud (2) + ``` + + https://github.com/owncloud/enterprise/issues/6390 + +* Enhancement - Add support to provide a list of ports for the OAuth process: [#11276](https://github.com/owncloud/client/issues/11276) + + We added a branding option that enables us to specify a list of ports that are + used for the local server during the OAuth process. + + https://github.com/owncloud/client/issues/11276 + +* Enhancement - Support `Active Directory Federation Service` as identity provider: [#11646](https://github.com/owncloud/client/issues/11646) + + We changed the OAuth workflow to support `Active Directory Federation Service` + + https://github.com/owncloud/client/issues/11646 + +# Changelog for ownCloud Desktop Client [5.2.1] (2024-01-04) + +The following sections list the changes in ownCloud Desktop Client 5.2.1 relevant to +ownCloud admins and users. + +[5.2.1]: https://github.com/owncloud/client/compare/v5.2.0...v5.2.1 + +## Summary + +* Bugfix - Client stuck in reconnecting: [#11274](https://github.com/owncloud/client/issues/11274) +* Enhancement - Improved reliability for persisting settings: [#11413](https://github.com/owncloud/client/pull/11413) + +## Details + +* Bugfix - Client stuck in reconnecting: [#11274](https://github.com/owncloud/client/issues/11274) + + We fixed a bug where the client got stuck while reconnecting to the server. + + https://github.com/owncloud/client/issues/11274 + +* Enhancement - Improved reliability for persisting settings: [#11413](https://github.com/owncloud/client/pull/11413) + + We improved the reliability of saving accounts. This might fix an issue where + sync connections were lost at random. + + https://github.com/owncloud/client/issues/11411 + https://github.com/owncloud/client/pull/11413 + +# Changelog for ownCloud Desktop Client [5.2.0] (2023-11-27) + +The following sections list the changes in ownCloud Desktop Client 5.2.0 relevant to +ownCloud admins and users. + +[5.2.0]: https://github.com/owncloud/client/compare/v5.1.2...v5.2.0 + +## Summary + +* Bugfix - Fix URL resolution for app provider: [#11296](https://github.com/owncloud/client/issues/11296) +* Bugfix - Fix crash on unhandled status code on rename check: [#11379](https://github.com/owncloud/client/issues/11379) + +## Details + +* Bugfix - Fix URL resolution for app provider: [#11296](https://github.com/owncloud/client/issues/11296) + + We fixed a bug in the URL resolution for the app provider. If owncloud is + installed to a non-root directory we computed a wrong URL for the app provider + urls. This could trigger a connection validation in the client, which intern + triggered a request to the app provider. Resulting in an endless loop rendering + the client unusable. + + https://github.com/owncloud/client/issues/11296 + +* Bugfix - Fix crash on unhandled status code on rename check: [#11379](https://github.com/owncloud/client/issues/11379) + + We fixed a bug that caused the application to crash instead of just aborting the + current sync. + + https://github.com/owncloud/client/issues/11379 + +# Changelog for ownCloud Desktop Client [5.1.2] (2023-11-10) + +The following sections list the changes in ownCloud Desktop Client 5.1.2 relevant to +ownCloud admins and users. + +[5.1.2]: https://github.com/owncloud/client/compare/v5.1.1...v5.1.2 + +## Summary + +* Bugfix - Fix crash when keychain job takes longer than expected: [#11361](https://github.com/owncloud/client/pull/11361) + +## Details + +* Bugfix - Fix crash when keychain job takes longer than expected: [#11361](https://github.com/owncloud/client/pull/11361) + + In 5.1.1, we introduced a timeout for keychain jobs that killed them when their + execution time exceeded 5 seconds and log this to allow debugging of problems + related to secrets handling. + + Apparently, there are valid cases in which these jobs can take longer than the + expected timeout, e.g., when the user needs to unlock their keychain first or + configure it otherwise. + + We now let the jobs run indefinitely and log every 5 seconds whether the job is + still running. + + https://github.com/owncloud/client/pull/11361 + +# Changelog for ownCloud Desktop Client [5.1.1] (2023-11-09) + +The following sections list the changes in ownCloud Desktop Client 5.1.1 relevant to +ownCloud admins and users. + +[5.1.1]: https://github.com/owncloud/client/compare/v5.1.0...v5.1.1 + +## Summary + +* Bugfix - Prevent permanent deletion of files when disabling VFS: [#11331](https://github.com/owncloud/client/issues/11331) + +## Details + +* Bugfix - Prevent permanent deletion of files when disabling VFS: [#11331](https://github.com/owncloud/client/issues/11331) + + When a user disabled the virtual files feature on Windows, all currently + dehydrated files were deleted accidentally. On the server those files where + still available in the trash bin. This regression was introduced in the previous + release 5.0.0. + + https://github.com/owncloud/client/issues/11331 + +# Changelog for ownCloud Desktop Client [5.1.0] (2023-11-02) + +The following sections list the changes in ownCloud Desktop Client 5.1.0 relevant to +ownCloud admins and users. + +[5.1.0]: https://github.com/owncloud/client/compare/v5.0.0...v5.1.0 + +## Summary + +* Bugfix - Crash when accepting a notification: [#11226](https://github.com/owncloud/client/issues/11226) +* Bugfix - Fix crash on start-up when starting shell integration: [#11280](https://github.com/owncloud/client/issues/11280) +* Bugfix - Properly schedule the sync after an account was added: [#11308](https://github.com/owncloud/client/issues/11308) +* Bugfix - Don't start credentials save jobs during shutdown: [#11313](https://github.com/owncloud/client/pull/11313) + +## Details + +* Bugfix - Crash when accepting a notification: [#11226](https://github.com/owncloud/client/issues/11226) + + We fixed a potential crash when clicking on a notification. + + https://github.com/owncloud/client/issues/11226 + +* Bugfix - Fix crash on start-up when starting shell integration: [#11280](https://github.com/owncloud/client/issues/11280) + + A possible crash has been fixed that could occur during start-up, when the shell + integration started doing requests before the client itself completed starting + up. + + https://github.com/owncloud/client/issues/11280 + https://github.com/owncloud/client/pull/11288 + +* Bugfix - Properly schedule the sync after an account was added: [#11308](https://github.com/owncloud/client/issues/11308) + + We fixed a bug where a folder was scheduled to be synced before the account + reported it was ready. This resulted in the sync having no effect and the folder + was then only synced once we polled the etag. + + https://github.com/owncloud/client/issues/11308 + +* Bugfix - Don't start credentials save jobs during shutdown: [#11313](https://github.com/owncloud/client/pull/11313) + + Due to a bug we "re-saved" the credentials during application shutdown. As the + application was quitting while the jobs where running we might have encountered + corrupted credentials or crashes. + + https://github.com/owncloud/client/pull/11313 + +# Changelog for ownCloud Desktop Client [5.0.0] (2023-10-05) + +The following sections list the changes in ownCloud Desktop Client 5.0.0 relevant to +ownCloud admins and users. + +[5.0.0]: https://github.com/owncloud/client/compare/v4.2.0...v5.0.0 + +## Summary + +* Bugfix - Avoid duplicate notifications when selective sync is enabled: [#5682](https://github.com/owncloud/enterprise/issues/5682) +* Bugfix - VFS Placeholders can now be replaced with folders: [#10223](https://github.com/owncloud/client/issues/10223) +* Bugfix - Authentication dialog no longer appears again and again: [#10351](https://github.com/owncloud/client/issues/10351) +* Bugfix - Possible deadlock during log setup: [#10905](https://github.com/owncloud/client/pull/10905) +* Bugfix - Do not create default sync root when loading accounts: [#10919](https://github.com/owncloud/client/issues/10919) +* Bugfix - Update capabilities and other info after connect: [#10978](https://github.com/owncloud/client/issues/10978) +* Bugfix - Account activity and crash after an account was removed: [#10990](https://github.com/owncloud/client/issues/10990) +* Bugfix - Crash during application shutdown: [#11016](https://github.com/owncloud/client/issues/11016) +* Bugfix - Hide hidden folders again in the selective sync view: [#11047](https://github.com/owncloud/client/issues/11047) +* Bugfix - Fix check if a file is a placeholder: [#11107](https://github.com/owncloud/client/issues/11107) +* Bugfix - Hydration state of file after a directory was replaced with a file: [#11162](https://github.com/owncloud/client/pull/11162) +* Bugfix - Only synchronize after the server settings where refreshed: [#11168](https://github.com/owncloud/client/issues/11168) +* Bugfix - Consistently use the same icon for folders: [#11190](https://github.com/owncloud/client/pull/11190) +* Bugfix - Branding of folder status overlay: [#11192](https://github.com/owncloud/client/pull/11192) +* Bugfix - Creation of folder on the server: [#11232](https://github.com/owncloud/client/issues/11232) +* Bugfix - Selective sync when not the remote root is synced: [#11233](https://github.com/owncloud/client/issues/11233) +* Change - Extract Nautilus: [#8991](https://github.com/owncloud/client/issues/8991) +* Change - Remove support for sidebar entries for non-vfs setups on Windows: [#10788](https://github.com/owncloud/client/issues/10788) +* Change - Remove support for client side system proxy credentials: [#10866](https://github.com/owncloud/client/pull/10866) +* Change - Modernize systray menu: [#10939](https://github.com/owncloud/client/issues/10939) +* Change - We removed the pre 2.9 credentials migration: [#11081](https://github.com/owncloud/client/pull/11081) +* Change - Remove libcloudproviders integration: [#11148](https://github.com/owncloud/client/issues/11148) +* Change - Disable context menu item to get a public link for a space: [#11242](https://github.com/owncloud/client/issues/11242) +* Enhancement - Store proxy password securely: [#261](https://github.com/owncloud/client/issues/261) +* Enhancement - Windows VFS: Prevent rename to ignored file name: [#8216](https://github.com/owncloud/client/issues/8216) +* Enhancement - Change how all files deleted is handled: [#8360](https://github.com/owncloud/client/issues/8360) +* Enhancement - Port from QtSingleApplication to KDSingleApplication: [#8432](https://github.com/owncloud/client/issues/8432) +* Enhancement - Enable crash reporter in commandline client: [#8991](https://github.com/owncloud/client/issues/8991) +* Enhancement - Make "Show files versions…" context menu action available: [#10197](https://github.com/owncloud/client/issues/10197) +* Enhancement - Log http request when it is sent: [#10313](https://github.com/owncloud/client/issues/10313) +* Enhancement - Allow selective sync of spaces in folder wizard: [#10596](https://github.com/owncloud/client/issues/10596) +* Enhancement - Send a language header in all http requests: [#10619](https://github.com/owncloud/client/issues/10619) +* Enhancement - Help user fix problems on the last setup wizard page: [#10680](https://github.com/owncloud/client/issues/10680) +* Enhancement - Display a progress spinner during the initial setup: [#10751](https://github.com/owncloud/client/issues/10751) +* Enhancement - Reduce how often file changes are handled: [#10825](https://github.com/owncloud/client/pull/10825) +* Enhancement - Persist filter settings for Not Synced tab: [#10928](https://github.com/owncloud/client/pull/10928) +* Enhancement - `--cmd` argument added to the GUI client: [#10976](https://github.com/owncloud/client/issues/10976) + +## Details + +* Bugfix - Avoid duplicate notifications when selective sync is enabled: [#5682](https://github.com/owncloud/enterprise/issues/5682) + + We no longer show multiple messages about files which were excluded from the + sync because they were excluded by the users. + + https://github.com/owncloud/enterprise/issues/5682 + +* Bugfix - VFS Placeholders can now be replaced with folders: [#10223](https://github.com/owncloud/client/issues/10223) + + If a local VFS placeholder is replaced with a folder on the server, the client + previously failed to populate the change. + + https://github.com/owncloud/client/issues/10223 + +* Bugfix - Authentication dialog no longer appears again and again: [#10351](https://github.com/owncloud/client/issues/10351) + + https://github.com/owncloud/client/issues/10351 + +* Bugfix - Possible deadlock during log setup: [#10905](https://github.com/owncloud/client/pull/10905) + + If an error occurred during the setup of the log file, it was possible that the + client got deadlocked. + + https://github.com/owncloud/client/pull/10905 + +* Bugfix - Do not create default sync root when loading accounts: [#10919](https://github.com/owncloud/client/issues/10919) + + Creating the default sync root is only needed at initial setup. If a user + chooses to point all folders for Spaces outside that default sync root, and + deletes the default sync root folder, it will now no longer be created. + + https://github.com/owncloud/client/issues/10919 + https://github.com/owncloud/client/pull/11128 + +* Bugfix - Update capabilities and other info after connect: [#10978](https://github.com/owncloud/client/issues/10978) + + We fixed a bug where server infos might have only been updated several minutes + after the client connected to the server. + + https://github.com/owncloud/client/issues/10978 + +* Bugfix - Account activity and crash after an account was removed: [#10990](https://github.com/owncloud/client/issues/10990) + + We fixed a bug where an account performed actions after it was removed. + + https://github.com/owncloud/client/issues/10990 + +* Bugfix - Crash during application shutdown: [#11016](https://github.com/owncloud/client/issues/11016) + + We fixed a where the main instance was accessed after it was destroyed. + + https://github.com/owncloud/client/issues/11016 + +* Bugfix - Hide hidden folders again in the selective sync view: [#11047](https://github.com/owncloud/client/issues/11047) + + https://github.com/owncloud/client/issues/11047 + +* Bugfix - Fix check if a file is a placeholder: [#11107](https://github.com/owncloud/client/issues/11107) + + Previously, files with zero bytes in size were considered placeholders. This is + not correct, and has been fixed by using the correct API to check for + placeholders. + + https://github.com/owncloud/client/issues/11107 + +* Bugfix - Hydration state of file after a directory was replaced with a file: [#11162](https://github.com/owncloud/client/pull/11162) + + If a synced directory was replaced with a file on the server, we downloaded it + instead of replacing the folder with a VirtualFile placeholder. + + https://github.com/owncloud/client/pull/11162 + +* Bugfix - Only synchronize after the server settings where refreshed: [#11168](https://github.com/owncloud/client/issues/11168) + + We fixed an issue where the sync was started on outdated server settings. + + https://github.com/owncloud/client/issues/11168 + +* Bugfix - Consistently use the same icon for folders: [#11190](https://github.com/owncloud/client/pull/11190) + + In 4.0 we introduced a new icon for sync connections. In some places however we + continued to use the system provided icon for folders, this resulted in an + inconsistent application look. + + https://github.com/owncloud/client/pull/11190 + +* Bugfix - Branding of folder status overlay: [#11192](https://github.com/owncloud/client/pull/11192) + + We fixed a bug where branded overlay icons for the display of the sync status + where not used. + + https://github.com/owncloud/client/pull/11192 + +* Bugfix - Creation of folder on the server: [#11232](https://github.com/owncloud/client/issues/11232) + + We fixed an issue in the FolderSyncWizard when the user provided a path with + multiple slashes (//folder). + + https://github.com/owncloud/client/issues/11232 + +* Bugfix - Selective sync when not the remote root is synced: [#11233](https://github.com/owncloud/client/issues/11233) + + https://github.com/owncloud/client/issues/11233 + +* Change - Extract Nautilus: [#8991](https://github.com/owncloud/client/issues/8991) + + While the crash reporter still requires a graphical user interface to work, this + change nevertheless simplifies reporting crashes from the commandline client a + lot. + + https://github.com/owncloud/client/issues/8991 + +* Change - Remove support for sidebar entries for non-vfs setups on Windows: [#10788](https://github.com/owncloud/client/issues/10788) + + For vfs setups, this is handled by Windows itself. + + https://github.com/owncloud/client/issues/10788 + https://github.com/owncloud/client/pull/10910 + +* Change - Remove support for client side system proxy credentials: [#10866](https://github.com/owncloud/client/pull/10866) + + We removed the support to query and store credentials for the system proxy. + + https://github.com/owncloud/client/pull/10866 + +* Change - Modernize systray menu: [#10939](https://github.com/owncloud/client/issues/10939) + + We had a closer look on the system tray and cleaned it up. + + https://github.com/owncloud/client/issues/10939 + https://github.com/owncloud/client/pull/10949 + https://github.com/owncloud/client/pull/10999 + +* Change - We removed the pre 2.9 credentials migration: [#11081](https://github.com/owncloud/client/pull/11081) + + Migrating credentials from a 2.8 or older client is no longer possible. + + https://github.com/owncloud/client/pull/11081 + +* Change - Remove libcloudproviders integration: [#11148](https://github.com/owncloud/client/issues/11148) + + Since its implementation in 2019 our support our for it was 'limited'. + Additionally, as far as we can tell it was not picked up by any major Linux + distribution. + + https://github.com/owncloud/client/issues/11148 + https://github.com/owncloud/client/pull/11157 + +* Change - Disable context menu item to get a public link for a space: [#11242](https://github.com/owncloud/client/issues/11242) + + The context menu item "Create and copy public link to clipboard" didn't work + correctly when used with Spaces: users were always redirected to the web + frontend. This menu item is now disabled, waiting for a server-side changes. + + https://github.com/owncloud/client/issues/11242 + https://github.com/owncloud/client/pull/11079 + +* Enhancement - Store proxy password securely: [#261](https://github.com/owncloud/client/issues/261) + + https://github.com/owncloud/client/issues/261 + +* Enhancement - Windows VFS: Prevent rename to ignored file name: [#8216](https://github.com/owncloud/client/issues/8216) + + We now prevent renaming of virtual files to ignored file names. This basically + resulted in deletion on the server and a local placeholder that pointed to a + non-existing file. + + https://github.com/owncloud/client/issues/8216 + https://github.com/owncloud/client-desktop-vfs-win/pull/63 + +* Enhancement - Change how all files deleted is handled: [#8360](https://github.com/owncloud/client/issues/8360) + + When the client detects that all files are deleted on either side of the sync, + the client now aborts the sync and asks for a user decision. Afterwords a new + sync is started. + + Previously we blocked the current sync until the user responded which could lead + to unwanted side effects. + + https://github.com/owncloud/client/issues/8360 + +* Enhancement - Port from QtSingleApplication to KDSingleApplication: [#8432](https://github.com/owncloud/client/issues/8432) + + Instead of relying on an old copy of QtSingleApplication we use + https://github.com/KDAB/KDSingleApplication + + https://github.com/owncloud/client/issues/8432 + https://github.com/owncloud/client/pull/10754 + https://github.com/owncloud/client/pull/10758 + +* Enhancement - Enable crash reporter in commandline client: [#8991](https://github.com/owncloud/client/issues/8991) + + While the crash reporter still requires a graphical user interface to work, this + change nevertheless simplifies reporting crashes from the commandline client a + lot. + + https://github.com/owncloud/client/issues/8991 + +* Enhancement - Make "Show files versions…" context menu action available: [#10197](https://github.com/owncloud/client/issues/10197) + + https://github.com/owncloud/client/issues/10197 + +* Enhancement - Log http request when it is sent: [#10313](https://github.com/owncloud/client/issues/10313) + + We now log the http request when it is sent, not when it is created. This will + enable us to print more headers set by the platform and measure the request time + more accurately. + + https://github.com/owncloud/client/issues/10313 + +* Enhancement - Allow selective sync of spaces in folder wizard: [#10596](https://github.com/owncloud/client/issues/10596) + + When manually configuring the synchronization of spaces, we now show the + selective sync page again to allow users to selectively disable the + synchronization of remote directories. + + This prevents the unnecessary synchronization of files until the user can use + the account settings dialog to configure selective sync. + + https://github.com/owncloud/client/issues/10596 + https://github.com/owncloud/client/pull/11044 + +* Enhancement - Send a language header in all http requests: [#10619](https://github.com/owncloud/client/issues/10619) + + All http requests to the server will now contain the language for the client. + This can be used by the server to localize responses like error messages. + + https://github.com/owncloud/client/issues/10619 + https://github.com/owncloud/client/pull/11020 + +* Enhancement - Help user fix problems on the last setup wizard page: [#10680](https://github.com/owncloud/client/issues/10680) + + When there are problems within the advanced configuration on the last setup + wizard page, we no longer just show an error message but also "un-hide" those + settings to give the user a chance of fixing the problem. + + https://github.com/owncloud/client/issues/10680 + https://github.com/owncloud/client/pull/11029 + +* Enhancement - Display a progress spinner during the initial setup: [#10751](https://github.com/owncloud/client/issues/10751) + + We now display a progress spinner after the setup wizard, until the client is + done setting the account up. + + https://github.com/owncloud/client/issues/10751 + +* Enhancement - Reduce how often file changes are handled: [#10825](https://github.com/owncloud/client/pull/10825) + + We no longer handle file changes every 1s but gather them for 10s and handle + them then. + + This should reduce the amount of unnecessary checksum computations and attempted + syncs. + + https://github.com/owncloud/client/pull/10825 + +* Enhancement - Persist filter settings for Not Synced tab: [#10928](https://github.com/owncloud/client/pull/10928) + + We now store the filter settings in the Not Synced tab. So after a restart of + the client, the filter will still be the same as the last time. + + https://github.com/owncloud/client/issues/10884 + https://github.com/owncloud/client/pull/10928 + +* Enhancement - `--cmd` argument added to the GUI client: [#10976](https://github.com/owncloud/client/issues/10976) + + In 4.1 we added a `--cmd` argument to the AppImage to allow users to use the + commandline client contained in the AppImage more easily. + + The `--cmd` argument is now supported on all platforms to support consistency. + + https://github.com/owncloud/client/issues/10976 + +# Changelog for ownCloud Desktop Client [4.2.0] (2023-08-07) + +The following sections list the changes in ownCloud Desktop Client 4.2.0 relevant to +ownCloud admins and users. + +[4.2.0]: https://github.com/owncloud/client/compare/v4.1.0...v4.2.0 + +## Summary + +* Bugfix - Wrong davUser in cmd client: [#10946](https://github.com/owncloud/client/issues/10946) +* Bugfix - Missing mtime in oCIS uploads: [#10950](https://github.com/owncloud/client/issues/10950) + +## Details + +* Bugfix - Wrong davUser in cmd client: [#10946](https://github.com/owncloud/client/issues/10946) + + We fixed a bug where the wrong dav user was used in the URL, when the username + from the credentials differed from the username used on the server. + + https://github.com/owncloud/client/issues/10946 + +* Bugfix - Missing mtime in oCIS uploads: [#10950](https://github.com/owncloud/client/issues/10950) + + When uploading a file to oCIS the server ignores the X-OC-Mtime header and + expects the mtime to be part of the Upload-Metadata header. We now provide both. + + https://github.com/owncloud/client/issues/10950 + +# Changelog for ownCloud Desktop Client [4.1.0] (2023-06-15) + +The following sections list the changes in ownCloud Desktop Client 4.1.0 relevant to +ownCloud admins and users. + +[4.1.0]: https://github.com/owncloud/client/compare/v4.0.0...v4.1.0 + +## Summary + +* Bugfix - Follow same site redirects in the Wizard: [#5743](https://github.com/owncloud/enterprise/issues/5743) +* Bugfix - Show systray status when a sync error occurred: [#10834](https://github.com/owncloud/client/issues/10834) +* Bugfix - Owncloudcmd: Ensure plugin path is initialized on Linux: [#10842](https://github.com/owncloud/client/issues/10842) +* Bugfix - Use correct username with basic auth and LDAP: [#10856](https://github.com/owncloud/client/issues/10856) +* Enhancement - Allow users to call commandline client from AppImage: [#10888](https://github.com/owncloud/client/issues/10888) + +## Details + +* Bugfix - Follow same site redirects in the Wizard: [#5743](https://github.com/owncloud/enterprise/issues/5743) + + We fixed a bug where the client did not follow same site redirects during the + setup. If the used URL https://test.com/owncloud redirected to https://test.com + the new URL was not correctly used for the newly created account. + + https://github.com/owncloud/enterprise/issues/5743 + +* Bugfix - Show systray status when a sync error occurred: [#10834](https://github.com/owncloud/client/issues/10834) + + The systray menu for the ownCloud icon has an entry that shows the sync status. + However, when a sync error occurred, the entry would have no text in it. This + has now been fixed. + + https://github.com/owncloud/client/issues/10834 + +* Bugfix - Owncloudcmd: Ensure plugin path is initialized on Linux: [#10842](https://github.com/owncloud/client/issues/10842) + + https://github.com/owncloud/client/issues/10842 + +* Bugfix - Use correct username with basic auth and LDAP: [#10856](https://github.com/owncloud/client/issues/10856) + + With basic auth the name from the login credentials might not align with the + name on the server. We corrected the assumption and no longer change the + username. + + https://github.com/owncloud/client/issues/10856 + +* Enhancement - Allow users to call commandline client from AppImage: [#10888](https://github.com/owncloud/client/issues/10888) + + When the AppImage is called with the new commandline parameter --cmd, it will + internally dispatch the call to the contained owncloudcmd binary. Otherwise, the + call will be forwarded to the regular owncloud GUI binary as before. + + https://github.com/owncloud/client/issues/10888 + +# Changelog for ownCloud Desktop Client [4.0.0] (2023-05-11) + +The following sections list the changes in ownCloud Desktop Client 4.0.0 relevant to +ownCloud admins and users. + +[4.0.0]: https://github.com/owncloud/client/compare/v3.2.1...v4.0.0 + +## Summary + +* Bugfix - Display sync date in system tray context menu when files where ignored: [#8953](https://github.com/owncloud/client/issues/8953) +* Bugfix - Fix computation of sync status when multiple folders are synced: [#9270](https://github.com/owncloud/client/issues/9270) +* Bugfix - Don't abort local folder deletions on unrelated errors: [#9311](https://github.com/owncloud/client/issues/9311) +* Bugfix - Fix update channel dropdown: [#10251](https://github.com/owncloud/client/issues/10251) +* Bugfix - Select keep files as default action in all files removed dialog: [#10254](https://github.com/owncloud/client/issues/10254) +* Bugfix - Undefined client state after fetching credentials failed: [#10408](https://github.com/owncloud/client/issues/10408) +* Bugfix - Escape navigation elements from user name: [#10427](https://github.com/owncloud/client/issues/10427) +* Bugfix - Under certain conditions the folder watcher could drop events: [#10458](https://github.com/owncloud/client/pull/10458) +* Bugfix - Properly handle OAuth invalidation: [#10494](https://github.com/owncloud/client/issues/10494) +* Bugfix - Clear conflict errors after they were resolved: [#10569](https://github.com/owncloud/client/issues/10569) +* Bugfix - Set the expected download content length in the job properly: [#10607](https://github.com/owncloud/client/pull/10607) +* Bugfix - UI freeze when multiple modal dialogs displayed on macOS: [#10621](https://github.com/owncloud/client/issues/10621) +* Bugfix - Ensure the account state is properly persisted: [#10648](https://github.com/owncloud/client/issues/10648) +* Bugfix - Reconnect got stuck if the server supports app providers: [#10666](https://github.com/owncloud/client/issues/10666) +* Change - Implement server guided migration to spaces: [#9945](https://github.com/owncloud/client/issues/9945) +* Enhancement - Display `This space is either disabled or deleted` for gone spaces: [#9700](https://github.com/owncloud/client/issues/9700) +* Enhancement - Sync virtual files that are locked by office etc: [#9829](https://github.com/owncloud/client/issues/9829) +* Enhancement - Replace term (un)pause with a more suitable one: [#10231](https://github.com/owncloud/client/issues/10231) +* Enhancement - Check for long paths when these are not enabled on Windows: [#10264](https://github.com/owncloud/client/issues/10264) +* Enhancement - Implement a possibility to Desktop.ini icon updates on Windows: [#10361](https://github.com/owncloud/client/issues/10361) +* Enhancement - Disable DPI scaling factor rounding on Windows: [#10406](https://github.com/owncloud/client/issues/10406) +* Enhancement - Display quota for Spaces: [#10453](https://github.com/owncloud/client/issues/10453) +* Enhancement - New WebFinger workflow: [#10517](https://github.com/owncloud/client/issues/10517) +* Enhancement - Branding: Allow the addition of buttons to open a URL: [#10518](https://github.com/owncloud/client/issues/10518) +* Enhancement - Build the client without the auto updater by default: [#10537](https://github.com/owncloud/client/issues/10537) +* Enhancement - Improve presentation of connection state: [#10557](https://github.com/owncloud/client/issues/10557) +* Enhancement - Display open in web in file browser context menu, on sync roots: [#10572](https://github.com/owncloud/client/issues/10572) +* Enhancement - Display space subtitle if available: [#10577](https://github.com/owncloud/client/issues/10577) +* Enhancement - Open sharing options in Web: [#10590](https://github.com/owncloud/client/pull/10590) +* Enhancement - Display the space name provided by the server: [#10602](https://github.com/owncloud/client/pull/10602) +* Enhancement - Always point to the latest documentation: [#10634](https://github.com/owncloud/client/issues/10634) +* Enhancement - More user-friendly error handling in the cmd client: [#10637](https://github.com/owncloud/client/pull/10637) +* Enhancement - Don't invalidate OAuth refresh token on ConnectionRefusedError: [#10654](https://github.com/owncloud/client/issues/10654) + +## Details + +* Bugfix - Display sync date in system tray context menu when files where ignored: [#8953](https://github.com/owncloud/client/issues/8953) + + https://github.com/owncloud/client/issues/8953 + +* Bugfix - Fix computation of sync status when multiple folders are synced: [#9270](https://github.com/owncloud/client/issues/9270) + + https://github.com/owncloud/client/issues/9270 + https://github.com/owncloud/client/pull/10536 + https://github.com/owncloud/client/pull/10642 + +* Bugfix - Don't abort local folder deletions on unrelated errors: [#9311](https://github.com/owncloud/client/issues/9311) + + We fixed a bug where folder deletions where not performed if any sync error + occurred anywhere in the sync tree. + + https://github.com/owncloud/client/issues/9311 + +* Bugfix - Fix update channel dropdown: [#10251](https://github.com/owncloud/client/issues/10251) + + When switching to the beta update channel in testpilotcloud, a warning will pop + up. When canceled, the selection should reset. This did not work reliably in + localized clients due to the use of string comparisons. Using a numeric index to + keep track of the old value fixes the problem. + + https://github.com/owncloud/client/issues/10251 + https://github.com/owncloud/client/pull/10609 + https://github.com/owncloud/client/pull/10627 + +* Bugfix - Select keep files as default action in all files removed dialog: [#10254](https://github.com/owncloud/client/issues/10254) + + https://github.com/owncloud/client/issues/10254 + +* Bugfix - Undefined client state after fetching credentials failed: [#10408](https://github.com/owncloud/client/issues/10408) + + We fixed a bug where the client got stuck in a "No connection configured" state + if an error occurred during the retrieval of the credentials from the system's + keychain. The client then required a manual log out and log in. + + https://github.com/owncloud/client/issues/10408 + https://github.com/owncloud/client/pull/10505 + +* Bugfix - Escape navigation elements from user name: [#10427](https://github.com/owncloud/client/issues/10427) + + We use the user name as folder name, if a user name contains path navigation + elements we now escape them. + + https://github.com/owncloud/client/issues/10427 + +* Bugfix - Under certain conditions the folder watcher could drop events: [#10458](https://github.com/owncloud/client/pull/10458) + + https://github.com/owncloud/client/pull/10458 + +* Bugfix - Properly handle OAuth invalidation: [#10494](https://github.com/owncloud/client/issues/10494) + + https://github.com/owncloud/client/issues/10494 + https://github.com/owncloud/client/issues/10495 + +* Bugfix - Clear conflict errors after they were resolved: [#10569](https://github.com/owncloud/client/issues/10569) + + https://github.com/owncloud/client/issues/10569 + +* Bugfix - Set the expected download content length in the job properly: [#10607](https://github.com/owncloud/client/pull/10607) + + https://github.com/owncloud/client/pull/10607 + +* Bugfix - UI freeze when multiple modal dialogs displayed on macOS: [#10621](https://github.com/owncloud/client/issues/10621) + + Due to incorrect handling of the dialogs, the UI could freeze. + + https://github.com/owncloud/client/issues/10621 + +* Bugfix - Ensure the account state is properly persisted: [#10648](https://github.com/owncloud/client/issues/10648) + + We didn't properly persist the login/logout state. + + https://github.com/owncloud/client/issues/10648 + +* Bugfix - Reconnect got stuck if the server supports app providers: [#10666](https://github.com/owncloud/client/issues/10666) + + https://github.com/owncloud/client/issues/10666 + +* Change - Implement server guided migration to spaces: [#9945](https://github.com/owncloud/client/issues/9945) + + We added a feature that allows limited automatic migration of existing syncs to + oCIS spaces. + + https://github.com/owncloud/client/issues/9945 + https://github.com/owncloud/ocis/issues/3574 + +* Enhancement - Display `This space is either disabled or deleted` for gone spaces: [#9700](https://github.com/owncloud/client/issues/9700) + + We now provide a less technical error when a space was disabled or deleted. + + https://github.com/owncloud/client/issues/9700 + +* Enhancement - Sync virtual files that are locked by office etc: [#9829](https://github.com/owncloud/client/issues/9829) + + We now upload locked files again when Windows virtual files are used. This was + disabled in 2.9.0 as it caused the file metadata and the locked files to get out + of sync. The new solution implements explicit handling of outdated placeholders. + + https://github.com/owncloud/client/issues/9829 + +* Enhancement - Replace term (un)pause with a more suitable one: [#10231](https://github.com/owncloud/client/issues/10231) + + We used the terms pause and unpause synchronization where the synchronization + was actually terminated and restarted. + + Now, we use the terms stop and start synchronization, which better communicate + the actual behavior. + + https://github.com/owncloud/client/issues/10231 + https://github.com/owncloud/client/issues/10529 + +* Enhancement - Check for long paths when these are not enabled on Windows: [#10264](https://github.com/owncloud/client/issues/10264) + + https://github.com/owncloud/client/issues/10264 + https://github.com/owncloud/client/issues/10677 + https://github.com/owncloud/client/pull/10352 + https://github.com/owncloud/client/pull/10679 + +* Enhancement - Implement a possibility to Desktop.ini icon updates on Windows: [#10361](https://github.com/owncloud/client/issues/10361) + + We implemented an option which allows to disable the automatic update of the + folder icon for sync folders on Windows. + + https://github.com/owncloud/client/issues/10361 + +* Enhancement - Disable DPI scaling factor rounding on Windows: [#10406](https://github.com/owncloud/client/issues/10406) + + Qt5 By default rounds up the selected scale for values > .5 We now use the + actual scale factor. + + https://github.com/owncloud/client/issues/10406 + +* Enhancement - Display quota for Spaces: [#10453](https://github.com/owncloud/client/issues/10453) + + Quota is now displayed for all folders. + + https://github.com/owncloud/client/issues/10453 + +* Enhancement - New WebFinger workflow: [#10517](https://github.com/owncloud/client/issues/10517) + + OwnCloud Infinite Scale servers may provide a new WebFinger service which + clients can use to discover an actual instance they should connect to. + + This is useful when a large organization operates multiple instances to + distribute the load on multiple servers, or single departments operate their own + servers, but they all use a common entrypoint URL and authentication system. + + https://github.com/owncloud/client/issues/10517 + https://github.com/owncloud/client/pull/10665 + https://owncloud.dev/services/webfinger/ + +* Enhancement - Branding: Allow the addition of buttons to open a URL: [#10518](https://github.com/owncloud/client/issues/10518) + + We added a branding option that allows to add buttons to the toolbar. Those + buttons will then open a hardcoded web page. + + https://github.com/owncloud/client/issues/10518 + +* Enhancement - Build the client without the auto updater by default: [#10537](https://github.com/owncloud/client/issues/10537) + + https://github.com/owncloud/client/issues/10537 + +* Enhancement - Improve presentation of connection state: [#10557](https://github.com/owncloud/client/issues/10557) + + Don't display `No connection configured` when disconnected but `Disconnected + from:`. Introduce new state, `Connecting to`. + + https://github.com/owncloud/client/issues/10557 + +* Enhancement - Display open in web in file browser context menu, on sync roots: [#10572](https://github.com/owncloud/client/issues/10572) + + https://github.com/owncloud/client/issues/10572 + +* Enhancement - Display space subtitle if available: [#10577](https://github.com/owncloud/client/issues/10577) + + If a subtitle is set on a spaced we now display it instead of the local path. + + https://github.com/owncloud/client/issues/10577 + +* Enhancement - Open sharing options in Web: [#10590](https://github.com/owncloud/client/pull/10590) + + When selecting "Share…" in the file browser context menu, we now not only + display that file in the web view but also directly show the sharing options. + + https://github.com/owncloud/client/pull/10590 + +* Enhancement - Display the space name provided by the server: [#10602](https://github.com/owncloud/client/pull/10602) + + If a space is renamed on the server, this change is now displayed in the client. + + https://github.com/owncloud/client/pull/10602 + +* Enhancement - Always point to the latest documentation: [#10634](https://github.com/owncloud/client/issues/10634) + + We used a URL for the documentation that contained the client version. While + this ensured a used would get the documentation matching to their client, this + only worked with the last two released clients. So users of a pre-release or of + a very old client where served a 404. + + https://github.com/owncloud/client/issues/10634 + +* Enhancement - More user-friendly error handling in the cmd client: [#10637](https://github.com/owncloud/client/pull/10637) + + https://github.com/owncloud/client/pull/10637 + +* Enhancement - Don't invalidate OAuth refresh token on ConnectionRefusedError: [#10654](https://github.com/owncloud/client/issues/10654) + + https://github.com/owncloud/client/issues/10654 + +# Changelog for ownCloud Desktop Client [3.2.1] (2023-03-07) + +The following sections list the changes in ownCloud Desktop Client 3.2.1 relevant to +ownCloud admins and users. + +[3.2.1]: https://github.com/owncloud/client/compare/v3.2.0...v3.2.1 + +## Summary + +* Bugfix - Handle hide VirtualFiles options as disable VirtualFiles: [#5612](https://github.com/owncloud/enterprise/issues/5612) +* Bugfix - Fix crash in Linux AppImage updater: [#10552](https://github.com/owncloud/client/issues/10552) +* Bugfix - Fix encoding issue with basic authentication: [#10564](https://github.com/owncloud/client/issues/10564) + +## Details + +* Bugfix - Handle hide VirtualFiles options as disable VirtualFiles: [#5612](https://github.com/owncloud/enterprise/issues/5612) + + Due to legacy reasons the branding flag and its function differ. + + https://github.com/owncloud/enterprise/issues/5612 + +* Bugfix - Fix crash in Linux AppImage updater: [#10552](https://github.com/owncloud/client/issues/10552) + + In between the releases 3.0.0 and 3.1.0, a dependency was updated, causing an + ABI incompatibility with another library. We resolved this by rebuilding both + libraries. + + https://github.com/owncloud/client/issues/10552 + +* Bugfix - Fix encoding issue with basic authentication: [#10564](https://github.com/owncloud/client/issues/10564) + + In 3.0 we introduced an encoding issue for basic authentication. + + https://github.com/owncloud/client/issues/10564 + +# Changelog for ownCloud Desktop Client [3.2.0] (2023-02-15) + +The following sections list the changes in ownCloud Desktop Client 3.2.0 relevant to +ownCloud admins and users. + +[3.2.0]: https://github.com/owncloud/client/compare/v3.1.0...v3.2.0 + +## Summary + +* Bugfix - Enable next button upon user input on WebFinger setup wizard page: [#10441](https://github.com/owncloud/client/issues/10441) +* Bugfix - Only hide non VFS settings when enforced and on Windows: [#10456](https://github.com/owncloud/client/issues/10456) +* Bugfix - Use selective sync settings from FolderWizard dialog: [#10468](https://github.com/owncloud/client/issues/10468) +* Bugfix - ShowVirtualFilesOption no longer influences the use of VFS: [#10472](https://github.com/owncloud/client/issues/10472) +* Bugfix - Fix generation of Windows sidebar entry when VFS is disabled: [#10481](https://github.com/owncloud/client/pull/10481) +* Bugfix - Empty credentials for signed out accounts: [#10500](https://github.com/owncloud/client/issues/10500) + +## Details + +* Bugfix - Enable next button upon user input on WebFinger setup wizard page: [#10441](https://github.com/owncloud/client/issues/10441) + + After a change to how the next button is controlled by user input on the page, + we forgot to make this change on the WebFinger page, too. + + https://github.com/owncloud/client/issues/10441 + +* Bugfix - Only hide non VFS settings when enforced and on Windows: [#10456](https://github.com/owncloud/client/issues/10456) + + https://github.com/owncloud/client/issues/10456 + +* Bugfix - Use selective sync settings from FolderWizard dialog: [#10468](https://github.com/owncloud/client/issues/10468) + + Due to a bug the user selection was not persisted. + + https://github.com/owncloud/client/issues/10468 + +* Bugfix - ShowVirtualFilesOption no longer influences the use of VFS: [#10472](https://github.com/owncloud/client/issues/10472) + + https://github.com/owncloud/client/issues/10472 + +* Bugfix - Fix generation of Windows sidebar entry when VFS is disabled: [#10481](https://github.com/owncloud/client/pull/10481) + + https://github.com/owncloud/client/pull/10481 + +* Bugfix - Empty credentials for signed out accounts: [#10500](https://github.com/owncloud/client/issues/10500) + + We fixed a bug where we saved empty credentials for logged out accounts. Under + certain conditions we now tried access the server without any authentication. + + https://github.com/owncloud/client/issues/10500 + +# Changelog for ownCloud Desktop Client [3.1.0] (2023-01-25) + +The following sections list the changes in ownCloud Desktop Client 3.1.0 relevant to +ownCloud admins and users. + +[3.1.0]: https://github.com/owncloud/client/compare/v3.0.0...v3.1.0 + +## Summary + +* Bugfix - Fix the display of the version string for released builds: [#10329](https://github.com/owncloud/client/pull/10329) +* Bugfix - Only clear cookies if enabled in theme, clear cookies before OAuth2: [#10338](https://github.com/owncloud/client/pull/10338) +* Bugfix - Display `Add a Space` instead of `Add Folder Sync Connection` with oCIS: [#10340](https://github.com/owncloud/client/pull/10340) +* Bugfix - Mac: Don't inherit the environment of the installer after an update: [#10346](https://github.com/owncloud/client/issues/10346) +* Bugfix - Correctly detect timeouts during token refresh: [#10373](https://github.com/owncloud/client/pull/10373) +* Bugfix - Open in web if debug logs are turned off: [#10387](https://github.com/owncloud/client/pull/10387) +* Bugfix - We fixed building the client on non linux unix systems: [#10398](https://github.com/owncloud/client/issues/10398) +* Enhancement - Add a `Reconnect` option to the account menu, when disconnected: [#10294](https://github.com/owncloud/client/issues/10294) +* Enhancement - (Re)introduce "sync hidden files" parameter in owncloudcmd: [#10390](https://github.com/owncloud/client/issues/10390) + +## Details + +* Bugfix - Fix the display of the version string for released builds: [#10329](https://github.com/owncloud/client/pull/10329) + + We removed a trailing `-` in the version string of released clients. + + https://github.com/owncloud/client/pull/10329 + +* Bugfix - Only clear cookies if enabled in theme, clear cookies before OAuth2: [#10338](https://github.com/owncloud/client/pull/10338) + + We fixed a bug that enabled the explicit cookie clearing required for F5 BIG-IP + setups unconditionally. We fixed a bug where the cookie clearing was not + performed during OAuth2. + + https://github.com/owncloud/client/pull/10338 + +* Bugfix - Display `Add a Space` instead of `Add Folder Sync Connection` with oCIS: [#10340](https://github.com/owncloud/client/pull/10340) + + We fixed a bug where the wrong text was displayed on the "add" button. + + https://github.com/owncloud/client/pull/10340 + +* Bugfix - Mac: Don't inherit the environment of the installer after an update: [#10346](https://github.com/owncloud/client/issues/10346) + + https://github.com/owncloud/client/issues/10346 + +* Bugfix - Correctly detect timeouts during token refresh: [#10373](https://github.com/owncloud/client/pull/10373) + + https://github.com/owncloud/client/pull/10373 + +* Bugfix - Open in web if debug logs are turned off: [#10387](https://github.com/owncloud/client/pull/10387) + + Due to a bug opening the browser only worked if logging was enabled. + + https://github.com/owncloud/client/pull/10387 + +* Bugfix - We fixed building the client on non linux unix systems: [#10398](https://github.com/owncloud/client/issues/10398) + + https://github.com/owncloud/client/issues/10398 + +* Enhancement - Add a `Reconnect` option to the account menu, when disconnected: [#10294](https://github.com/owncloud/client/issues/10294) + + We added a `Reconnect` button to the account menu, this allows to trigger a + manual reconnect try. Note: The client would try to reconnect by itself at some + point. + + https://github.com/owncloud/client/issues/10294 + +* Enhancement - (Re)introduce "sync hidden files" parameter in owncloudcmd: [#10390](https://github.com/owncloud/client/issues/10390) + + There used to be an option to enable the synchronization of hidden files using + the -h parameter which collided with the --help option and subsequently was + removed. A new --sync-hidden-files parameter was introduced to fill in the + missing feature. + + https://github.com/owncloud/client/issues/10390 + +# Changelog for ownCloud Desktop Client [3.0.0] (2022-11-30) + +The following sections list the changes in ownCloud Desktop Client 3.0.0 relevant to +ownCloud admins and users. + +[3.0.0]: https://github.com/owncloud/client/compare/v2.11.1...v3.0.0 + +## Summary + +* Bugfix - Windows VFS fixed some failing downloads: [#49](https://github.com/owncloud/client-desktop-vfs-win/pull/49) +* Bugfix - Don't trigger ignore list when files are locked on the server: [#5382](https://github.com/owncloud/enterprise/issues/5382) +* Bugfix - Properly resume upload with a partial local discovery: [#5382](https://github.com/owncloud/enterprise/issues/5382) +* Bugfix - Add request time and other missing data to .owncloudsync.log: [#7348](https://github.com/owncloud/client/issues/7348) +* Bugfix - Don't display a context-menu on the root folder: [#8595](https://github.com/owncloud/client/issues/8595) +* Bugfix - Fix copy URL location for private links: [#9048](https://github.com/owncloud/client/issues/9048) +* Bugfix - Fix status of files uploaded with TUS: [#9472](https://github.com/owncloud/client/pull/9472) +* Bugfix - Don't unset implicit log flush: [#9515](https://github.com/owncloud/client/pull/9515) +* Bugfix - The condition for the read only files menu was inverted: [#9574](https://github.com/owncloud/client/issues/9574) +* Bugfix - Deadlock in folder context menu in a folder selection dialog: [#9681](https://github.com/owncloud/client/issues/9681) +* Bugfix - Fix never ending sync: [#9725](https://github.com/owncloud/client/issues/9725) +* Bugfix - Fix adding bookmarks on Gtk+ 3 based desktops: [#9752](https://github.com/owncloud/client/pull/9752) +* Bugfix - Stop the activity spinner when the request failed: [#9798](https://github.com/owncloud/client/issues/9798) +* Bugfix - Changes during upload of a file could still trigger the ignore list: [#9924](https://github.com/owncloud/client/issues/9924) +* Bugfix - We fixed a crash: [#10017](https://github.com/owncloud/client/pull/10017) +* Bugfix - Sync status changes are now directly displayed: [#10101](https://github.com/owncloud/client/issues/10101) +* Change - Make share-dialog preview be more resilient: [#8938](https://github.com/owncloud/client/issues/8938) +* Change - We no longer persist cookies: [#9495](https://github.com/owncloud/client/issues/9495) +* Change - We removed support for ownCloud servers < 10.0: [#9578](https://github.com/owncloud/client/issues/9578) +* Change - Drop socket upload job: [#9585](https://github.com/owncloud/client/issues/9585) +* Change - Remove support for Windows 7 sidebar links: [#9618](https://github.com/owncloud/client/pull/9618) +* Change - Rewrote TLS error handling: [#9655](https://github.com/owncloud/client/issues/9655) +* Change - We removed the TLS certificate button from the account page: [#9675](https://github.com/owncloud/client/pull/9675) +* Change - Add "open in web editor" feature: [#9724](https://github.com/owncloud/client/issues/9724) +* Change - Don't display error state when server is unreachable: [#9790](https://github.com/owncloud/client/issues/9790) +* Change - Windows: Update the folder icon on every start: [#10184](https://github.com/owncloud/client/issues/10184) +* Change - Don't guess remote folder in owncloudcmd: [#10193](https://github.com/owncloud/client/issues/10193) +* Change - When connected to oCIS, open the browser instead of the sharing dialog: [#10206](https://github.com/owncloud/client/issues/10206) +* Change - Owncloudcmd oCIS support: [#10239](https://github.com/owncloud/client/pull/10239) +* Enhancement - Display `Show ownCloud` instead of `Settings` in systray: [#8234](https://github.com/owncloud/client/issues/8234) +* Enhancement - Built-in AppImage self-updater: [#8923](https://github.com/owncloud/client/issues/8923) +* Enhancement - Don't query private links if disabled on the server: [#8998](https://github.com/owncloud/client/issues/8998) +* Enhancement - Add CMakeOption WITH_AUTO_UPDATER: [#9082](https://github.com/owncloud/client/issues/9082) +* Enhancement - Support for oCIS Spaces: [#9154](https://github.com/owncloud/client/pull/9154) +* Enhancement - Rewrite wizard from scratch: [#9249](https://github.com/owncloud/client/issues/9249) +* Enhancement - Remove use of legacy DAV endpoint: [#9538](https://github.com/owncloud/client/pull/9538) +* Enhancement - Set Windows VFS placeholders readonly if needed: [#9598](https://github.com/owncloud/client/issues/9598) +* Enhancement - Create continuous log files: [#9731](https://github.com/owncloud/client/issues/9731) +* Enhancement - Display a correct error when the wrong user was authenticated: [#9772](https://github.com/owncloud/client/issues/9772) +* Enhancement - We improved the performance for local filesystem actions: [#9910](https://github.com/owncloud/client/pull/9910) +* Enhancement - We improved the performance of db access: [#9918](https://github.com/owncloud/client/pull/9918) +* Enhancement - Reduce CPU load during discovery: [#9919](https://github.com/owncloud/client/pull/9919) +* Enhancement - Remove app name from connection error message: [#9923](https://github.com/owncloud/client/issues/9923) +* Enhancement - Allow HTTP/1.1 pipelining: [#9930](https://github.com/owncloud/client/pull/9930/) +* Enhancement - Improve look and feel of many dialogs on macOS: [#9995](https://github.com/owncloud/client/issues/9995) +* Enhancement - Windows VFS download speed improvement: [#10031](https://github.com/owncloud/client/issues/10031) +* Enhancement - Add a `prefer: minimal` header to PROPFIND: [#10104](https://github.com/owncloud/client/pull/10104) +* Enhancement - Allow creation of sync roots with long paths: [#10135](https://github.com/owncloud/client/pull/10135/) +* Enhancement - Windows add longPath awareness: [#10136](https://github.com/owncloud/client/pull/10136) +* Enhancement - Estimate duration of network requests in httplogger: [#10142](https://github.com/owncloud/client/pull/10142) +* Enhancement - Tweak logging format: [#10310](https://github.com/owncloud/client/pull/10310) + +## Details + +* Bugfix - Windows VFS fixed some failing downloads: [#49](https://github.com/owncloud/client-desktop-vfs-win/pull/49) + + We fixed an api issue where some downloads in the Explorer caused infinite + download restarts. + + https://github.com/owncloud/client-desktop-vfs-win/pull/49 + +* Bugfix - Don't trigger ignore list when files are locked on the server: [#5382](https://github.com/owncloud/enterprise/issues/5382) + + https://github.com/owncloud/enterprise/issues/5382 + +* Bugfix - Properly resume upload with a partial local discovery: [#5382](https://github.com/owncloud/enterprise/issues/5382) + + https://github.com/owncloud/enterprise/issues/5382 + https://github.com/owncloud/client/pull/10200 + +* Bugfix - Add request time and other missing data to .owncloudsync.log: [#7348](https://github.com/owncloud/client/issues/7348) + + Some parameters were missing for some operations. This fix makes the log more + complete and more useful as a result. + + https://github.com/owncloud/client/issues/7348 + +* Bugfix - Don't display a context-menu on the root folder: [#8595](https://github.com/owncloud/client/issues/8595) + + https://github.com/owncloud/client/issues/8595 + +* Bugfix - Fix copy URL location for private links: [#9048](https://github.com/owncloud/client/issues/9048) + + We fixed a bug where a placeholder was copied to the clipboard instead of the + URL. + + https://github.com/owncloud/client/issues/9048 + +* Bugfix - Fix status of files uploaded with TUS: [#9472](https://github.com/owncloud/client/pull/9472) + + Setting the upload status of files uploaded with TUS failed as we were still + using the file. + + https://github.com/owncloud/client/pull/9472 + +* Bugfix - Don't unset implicit log flush: [#9515](https://github.com/owncloud/client/pull/9515) + + Since https://github.com/owncloud/client/pull/9515 we flush the log if the + output is stdout. We fixed a bug which disabled it again. + + https://github.com/owncloud/client/pull/9515 + +* Bugfix - The condition for the read only files menu was inverted: [#9574](https://github.com/owncloud/client/issues/9574) + + We fixed a bug where we displayed solutions to fix issues with read only files + for readable files and vise versa. + + https://github.com/owncloud/client/issues/9574 + +* Bugfix - Deadlock in folder context menu in a folder selection dialog: [#9681](https://github.com/owncloud/client/issues/9681) + + We fixed a deadlock when a user requested a context menu in a folder selection + dialog on Windows. + + https://github.com/owncloud/client/issues/9681 + +* Bugfix - Fix never ending sync: [#9725](https://github.com/owncloud/client/issues/9725) + + Under certain conditions an upload could enter a state were it would never + finish. The client would wait for that upload so only a restart of the client or + a manual abort of the sync could resolve the issue. + + https://github.com/owncloud/client/issues/9725 + +* Bugfix - Fix adding bookmarks on Gtk+ 3 based desktops: [#9752](https://github.com/owncloud/client/pull/9752) + + We used to add those bookmarks in a Gtk+ 2 compatible way only. Now, bookmarks + are added to the file belonging to Gtk+ 3, dropping support for end-of-life Gtk+ + 2. The bookmarks are now shown again for all Gtk+ 3 compatible file browsers, + including Thunar, Nautilus, Nemo, Caja, etc. + + https://github.com/owncloud/client/pull/9752 + +* Bugfix - Stop the activity spinner when the request failed: [#9798](https://github.com/owncloud/client/issues/9798) + + If the server did not provide the activity endpoint we always displayed a + progress spinner. + + https://github.com/owncloud/client/issues/9798 + +* Bugfix - Changes during upload of a file could still trigger the ignore list: [#9924](https://github.com/owncloud/client/issues/9924) + + We fixed another issue where changes during an upload could cause the file to be + ignored for an increasing amount of time. + + https://github.com/owncloud/client/issues/9924 + +* Bugfix - We fixed a crash: [#10017](https://github.com/owncloud/client/pull/10017) + + We fixed a crash that could occur after a folder reported a setup error + + https://github.com/owncloud/client/pull/10017 + +* Bugfix - Sync status changes are now directly displayed: [#10101](https://github.com/owncloud/client/issues/10101) + + https://github.com/owncloud/client/issues/10101 + +* Change - Make share-dialog preview be more resilient: [#8938](https://github.com/owncloud/client/issues/8938) + + We no longer enforce png thumbnails. We no longer replace the file icon if the + thumbnail is invalid. + + https://github.com/owncloud/client/issues/8938 + https://github.com/owncloud/client/pull/8939 + +* Change - We no longer persist cookies: [#9495](https://github.com/owncloud/client/issues/9495) + + We no longer persist cookies over multiple client sessions. + + https://github.com/owncloud/client/issues/9495 + +* Change - We removed support for ownCloud servers < 10.0: [#9578](https://github.com/owncloud/client/issues/9578) + + https://github.com/owncloud/client/issues/9578 + +* Change - Drop socket upload job: [#9585](https://github.com/owncloud/client/issues/9585) + + https://github.com/owncloud/client/issues/9585 + +* Change - Remove support for Windows 7 sidebar links: [#9618](https://github.com/owncloud/client/pull/9618) + + We removed the support for Windows < 10 sidebar links. + + https://github.com/owncloud/client/pull/9618 + +* Change - Rewrote TLS error handling: [#9655](https://github.com/owncloud/client/issues/9655) + + We rewrote the way we handle TLS errors. + + https://github.com/owncloud/client/issues/9655 + https://github.com/owncloud/client/pull/9643 + https://github.com/owncloud/client/pull/9667 + +* Change - We removed the TLS certificate button from the account page: [#9675](https://github.com/owncloud/client/pull/9675) + + https://github.com/owncloud/client/pull/9675 + +* Change - Add "open in web editor" feature: [#9724](https://github.com/owncloud/client/issues/9724) + + We now provide the option to open files in an online office suite from the local + file browser context menu, provided the server offers integration with one of + the supported services. + + https://github.com/owncloud/client/issues/9724 + +* Change - Don't display error state when server is unreachable: [#9790](https://github.com/owncloud/client/issues/9790) + + We no longer display a network error if the server is currently unavailable. + + https://github.com/owncloud/client/issues/9790 + +* Change - Windows: Update the folder icon on every start: [#10184](https://github.com/owncloud/client/issues/10184) + + The ownCloud installation path might have changed, causing the desktop.ini to + point at the wrong path. We now update the icon location on every application + start. + + https://github.com/owncloud/client/issues/10184 + +* Change - Don't guess remote folder in owncloudcmd: [#10193](https://github.com/owncloud/client/issues/10193) + + The commandline client was modified to explicitly accept remote folder, the + remote folder must no longer be encoded in the server URL. + + https://github.com/owncloud/client/issues/10193 + +* Change - When connected to oCIS, open the browser instead of the sharing dialog: [#10206](https://github.com/owncloud/client/issues/10206) + + When connected to oCIS, we now open the browser and navigate to the file the + user wanted to share instead of opening the legacy sharing dialog. + + https://github.com/owncloud/client/issues/10206 + +* Change - Owncloudcmd oCIS support: [#10239](https://github.com/owncloud/client/pull/10239) + + When using oCIS and spaces with the cmd client the additional parameter + `--server` is required. `--server` specifies the URL to the server, while the + positional parameter 'server_url' specifies the WebDAV URL. + + https://github.com/owncloud/client/pull/10239 + +* Enhancement - Display `Show ownCloud` instead of `Settings` in systray: [#8234](https://github.com/owncloud/client/issues/8234) + + We changed the menu entry to align its name with is function. + + https://github.com/owncloud/client/issues/8234 + +* Enhancement - Built-in AppImage self-updater: [#8923](https://github.com/owncloud/client/issues/8923) + + In release 2.10, we introduced a preview on our future AppImage packaging for + Linux distributions. Now, these AppImages can self-update using a built-in + `libappimageupdate` based updater and ownCloud's update infrastructure. + + https://github.com/owncloud/client/issues/8923 + https://github.com/owncloud/client/pull/9376 + +* Enhancement - Don't query private links if disabled on the server: [#8998](https://github.com/owncloud/client/issues/8998) + + https://github.com/owncloud/client/issues/8998 + https://github.com/owncloud/client/pull/9840 + https://github.com/owncloud/client/pull/9964 + +* Enhancement - Add CMakeOption WITH_AUTO_UPDATER: [#9082](https://github.com/owncloud/client/issues/9082) + + WITH_AUTO_UPDATER allows to build the client without the auto updater. + + https://github.com/owncloud/client/issues/9082 + +* Enhancement - Support for oCIS Spaces: [#9154](https://github.com/owncloud/client/pull/9154) + + We added support to sync oCIS Spaces. + + https://github.com/owncloud/client/pull/9154 + https://github.com/owncloud/client/pull/9575/ + +* Enhancement - Rewrite wizard from scratch: [#9249](https://github.com/owncloud/client/issues/9249) + + We completely rewrote the wizard from scratch. The new wizard provides greater + flexibility and makes adding new features easier in the future. It has also been + redesigned to improve the user experience. + + https://github.com/owncloud/client/issues/9249 + https://github.com/owncloud/client/pull/9482 + https://github.com/owncloud/client/pull/9563 + https://github.com/owncloud/client/pull/9566 + https://github.com/owncloud/client/pull/9577 + https://github.com/owncloud/client/pull/9596 + https://github.com/owncloud/client/pull/9606 + https://github.com/owncloud/client/pull/9621 + https://github.com/owncloud/client/pull/9629 + https://github.com/owncloud/client/pull/9636 + https://github.com/owncloud/client/pull/9637 + https://github.com/owncloud/client/pull/9642 + https://github.com/owncloud/client/pull/9643 + https://github.com/owncloud/client/pull/9697 + https://github.com/owncloud/client/pull/9720 + https://github.com/owncloud/client/pull/9746 + +* Enhancement - Remove use of legacy DAV endpoint: [#9538](https://github.com/owncloud/client/pull/9538) + + We no longer guess the DAV endpoint depending on the chunking-ng feature. + + https://github.com/owncloud/client/pull/9538 + +* Enhancement - Set Windows VFS placeholders readonly if needed: [#9598](https://github.com/owncloud/client/issues/9598) + + We now properly set the read only flag on Windows virtual files. + + https://github.com/owncloud/client/issues/9598 + https://github.com/owncloud/client-desktop-vfs-win/issues/24 + +* Enhancement - Create continuous log files: [#9731](https://github.com/owncloud/client/issues/9731) + + Previously, when logging was enabled, we started a new log file for every sync. + This worked quite well if you sync a single account and a single folder. With + spaces however we have a multitude of sync folders, which resulted in hundreds + of tiny log files. + + Now, as soon as a log file's size exceeds 100 MiB, a new log file is started, + and the old one is moved and compressed. The option to delete log files older + than 4h was replaced by an option to keep a number of log files. + + https://github.com/owncloud/client/issues/9731 + +* Enhancement - Display a correct error when the wrong user was authenticated: [#9772](https://github.com/owncloud/client/issues/9772) + + When the wrong user was authenticated using OAuth we used to display a + misleading message. We now also style the html response the client provides to + the file browser. + + https://github.com/owncloud/client/issues/9772 + https://github.com/owncloud/client/pull/9813 + +* Enhancement - We improved the performance for local filesystem actions: [#9910](https://github.com/owncloud/client/pull/9910) + + https://github.com/owncloud/client/pull/9910 + +* Enhancement - We improved the performance of db access: [#9918](https://github.com/owncloud/client/pull/9918) + + We removed a check for the existence of the db that was executed before every + access to the db. + + The check was introduced in #6049 to prevent crashes if the db does not exist or + is removed during runtime. We nowadays gracefully handle missing dbs on startup, + removing the db at runtime is too much of a corner case to sacrifice that much + performance, however. + + https://github.com/owncloud/client/pull/9918 + +* Enhancement - Reduce CPU load during discovery: [#9919](https://github.com/owncloud/client/pull/9919) + + https://github.com/owncloud/client/pull/9919 + +* Enhancement - Remove app name from connection error message: [#9923](https://github.com/owncloud/client/issues/9923) + + We removed the app name from some connection messages. `No connection to + ownCloud at http://..` was misleading as the server could have any other + branding. + + https://github.com/owncloud/client/issues/9923 + +* Enhancement - Allow HTTP/1.1 pipelining: [#9930](https://github.com/owncloud/client/pull/9930/) + + Under certain conditions, this change can result in a better network + utilization. + + https://github.com/owncloud/client/pull/9930/ + +* Enhancement - Improve look and feel of many dialogs on macOS: [#9995](https://github.com/owncloud/client/issues/9995) + + https://github.com/owncloud/client/issues/9995 + +* Enhancement - Windows VFS download speed improvement: [#10031](https://github.com/owncloud/client/issues/10031) + + We improved the performance of downloads performed on virtual files in the + Windows Explorer. + + https://github.com/owncloud/client/issues/10031 + +* Enhancement - Add a `prefer: minimal` header to PROPFIND: [#10104](https://github.com/owncloud/client/pull/10104) + + This will not return missing attribs in the reply in a 404 not found status + propset. That reduces the amount of transferred data significant. + + https://github.com/owncloud/client/pull/10104 + +* Enhancement - Allow creation of sync roots with long paths: [#10135](https://github.com/owncloud/client/pull/10135/) + + Until now, we were only able to create a .sync_journal.db in a path with less + than 260 characters. + + https://github.com/owncloud/client/pull/10135/ + +* Enhancement - Windows add longPath awareness: [#10136](https://github.com/owncloud/client/pull/10136) + + Requires Windows 10 newer than 1607 and the registry key to be enabled see: + https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later + + https://github.com/owncloud/client/pull/10136 + +* Enhancement - Estimate duration of network requests in httplogger: [#10142](https://github.com/owncloud/client/pull/10142) + + We now include an estimated duration in the httplogger. + + https://github.com/owncloud/client/pull/10142 + +* Enhancement - Tweak logging format: [#10310](https://github.com/owncloud/client/pull/10310) + + The logging format is now better parseable for 3rdparty apps that ease + debugging. + + https://github.com/owncloud/client/pull/10310 + +# Changelog for ownCloud Desktop Client [2.11.1] (2022-08-31) + +The following sections list the changes in ownCloud Desktop Client 2.11.1 relevant to +ownCloud admins and users. + +[2.11.1]: https://github.com/owncloud/client/compare/v2.11.0...v2.11.1 + +## Summary + +* Bugfix - Fix configuration of selective sync from account settings: [#10058](https://github.com/owncloud/client/pull/10058) + +## Details + +* Bugfix - Fix configuration of selective sync from account settings: [#10058](https://github.com/owncloud/client/pull/10058) + + We fixed a bug that prevented the directory tree in the account settings window + from being expanded beyond the root directory level. The problem was introduced + in 8d0dd36d2. + + https://github.com/owncloud/client/pull/10058 + https://github.com/owncloud/client/pull/10065 + +# Changelog for ownCloud Desktop Client [2.11.0] (2022-08-18) + +The following sections list the changes in ownCloud Desktop Client 2.11.0 relevant to +ownCloud admins and users. + +[2.11.0]: https://github.com/owncloud/client/compare/v2.10.1...v2.11.0 + +## Summary + +* Bugfix - Windows VFS: Keep file attributes and pin state: [#34](https://github.com/owncloud/client-desktop-vfs-win/pull/34) +* Bugfix - Compare usernames case-insensitive: [#5174](https://github.com/owncloud/enterprise/issues/5174) +* Bugfix - Use UTF-8 for .owncloudsync.log: [#9571](https://github.com/owncloud/client/pull/9571) +* Bugfix - Crash when interacting with a folder in an error state: [#9600](https://github.com/owncloud/client/issues/9600) +* Bugfix - Database was recreated after its removal: [#9791](https://github.com/owncloud/client/issues/9791) +* Bugfix - We fixed a potential crash: [#9864](https://github.com/owncloud/client/issues/9864) +* Bugfix - Windows VFS: Files in an existing folder are dehydrated: [#9966](https://github.com/owncloud/client/pull/9966) +* Bugfix - Run next scheduled sync after a folder was removed: [#9969](https://github.com/owncloud/client/issues/9969) +* Enhancement - Throttle the UI updates during sync: [#9832](https://github.com/owncloud/client/issues/9832) +* Enhancement - Run vfs downloads with a high priority: [#9836](https://github.com/owncloud/client/pull/9836) +* Enhancement - Don't abort sync if a user requests a file: [#9956](https://github.com/owncloud/client/pull/9956) + +## Details + +* Bugfix - Windows VFS: Keep file attributes and pin state: [#34](https://github.com/owncloud/client-desktop-vfs-win/pull/34) + + When a user selected "Always keep on this device" on a cloud only file, we lost + that information. "Always keep on this device" only worked on already present + files. + + https://github.com/owncloud/client-desktop-vfs-win/pull/34 + +* Bugfix - Compare usernames case-insensitive: [#5174](https://github.com/owncloud/enterprise/issues/5174) + + We fixed a bug where the user name was compared with the name provided by the + server in a case-sensitive way. + + https://github.com/owncloud/enterprise/issues/5174 + +* Bugfix - Use UTF-8 for .owncloudsync.log: [#9571](https://github.com/owncloud/client/pull/9571) + + We fixed a bug where Unicode file names were not correctly displayed in + .owncloudsync.log. + + https://github.com/owncloud/client/pull/9571 + +* Bugfix - Crash when interacting with a folder in an error state: [#9600](https://github.com/owncloud/client/issues/9600) + + We fixed a crash when using the context menu on a folder that encountered an + error and was not using virtual files. + + https://github.com/owncloud/client/issues/9600 + +* Bugfix - Database was recreated after its removal: [#9791](https://github.com/owncloud/client/issues/9791) + + We fixed a bug where the database was recreated during the removal of a sync + folder connection. + + https://github.com/owncloud/client/issues/9791 + +* Bugfix - We fixed a potential crash: [#9864](https://github.com/owncloud/client/issues/9864) + + https://github.com/owncloud/client/issues/9864 + +* Bugfix - Windows VFS: Files in an existing folder are dehydrated: [#9966](https://github.com/owncloud/client/pull/9966) + + We fixed a bug, when a user selects an existing folder as sync root we + previously dehydrated all existing files. + + https://github.com/owncloud/client/pull/9966 + +* Bugfix - Run next scheduled sync after a folder was removed: [#9969](https://github.com/owncloud/client/issues/9969) + + We fixed a bug where we did not start another sync when a folder that was + currently syncing was removed. + + https://github.com/owncloud/client/issues/9969 + +* Enhancement - Throttle the UI updates during sync: [#9832](https://github.com/owncloud/client/issues/9832) + + We reduced the number of UI updates during the sync, especially with Windows vfs + files this should improve the performance by a lot. + + https://github.com/owncloud/client/issues/9832 + https://github.com/owncloud/client/pull/9863 + +* Enhancement - Run vfs downloads with a high priority: [#9836](https://github.com/owncloud/client/pull/9836) + + This should reduce the probability for timeouts when downloading vfs files in + the Windows explorer. + + https://github.com/owncloud/client/issues/9832 + https://github.com/owncloud/client/pull/9836 + +* Enhancement - Don't abort sync if a user requests a file: [#9956](https://github.com/owncloud/client/pull/9956) + + Previously we aborted any running sync if a user requested a file that was not + yet available locally. This was done to ensure the user does not need to wait + for the current sync to finish. However, in today's code both actions the + download and the sync can run in parallel. + + https://github.com/owncloud/client/issues/9832 + https://github.com/owncloud/client/pull/9956 + +# Changelog for ownCloud Desktop Client [2.10.1] (2022-04-05) + +The following sections list the changes in ownCloud Desktop Client 2.10.1 relevant to +ownCloud admins and users. + +[2.10.1]: https://github.com/owncloud/client/compare/v2.10.0...v2.10.1 + +## Summary + +* Bugfix - Fix potential download failure for renamed file with Windows VFS: [#18](https://github.com/owncloud/client-desktop-vfs-win/pull/18) +* Bugfix - Possible crash when downloading a virtual file on Windows: [#21](https://github.com/owncloud/client-desktop-vfs-win/pull/21) +* Bugfix - Don't publish upload if we can't finish the transaction in the client: [#5052](https://github.com/owncloud/enterprise/issues/5052) +* Bugfix - Do not strip trailing whitespace from a file or folder name: [#9030](https://github.com/owncloud/client/issues/9030) +* Bugfix - Remove outdated translations: [#9105](https://github.com/owncloud/client/issues/9105) +* Bugfix - Progress reporting for TUS uploads: [#9121](https://github.com/owncloud/client/issues/9121) +* Bugfix - Fix crash if a database error occurs: [#9147](https://github.com/owncloud/client/issues/9147) +* Bugfix - --version showed incorrect information about VFS support: [#9155](https://github.com/owncloud/client/issues/9155) +* Bugfix - Client warns about non-existing files: [#9236](https://github.com/owncloud/client/issues/9236) +* Bugfix - Mention source file in activity tab when renaming: [#9238](https://github.com/owncloud/client/issues/9238) +* Bugfix - Fix crash on remove account: [#9367](https://github.com/owncloud/client/issues/9367) +* Bugfix - Raise ssl issue dialog above the wizard: [#9375](https://github.com/owncloud/client/pull/9375) +* Bugfix - Fallback to ownCloud sidebar icons on Mac if none provided in branding: [#9381](https://github.com/owncloud/client/pull/9381) +* Bugfix - Immediately retry upload if file changed during sync: [#9382](https://github.com/owncloud/client/issues/9382) +* Bugfix - Don't reset change time on upload: [#9383](https://github.com/owncloud/client/issues/9383) +* Bugfix - Fix toggling launch-on-login for macOS: [#9387](https://github.com/owncloud/client/issues/9387) +* Bugfix - Fix translated icon names in desktop file with ownBrander themes: [#9390](https://github.com/owncloud/client/pull/9390) +* Bugfix - Add open local/remote folder options: [#9405](https://github.com/owncloud/client/issues/9405) +* Bugfix - Fix possible crash: [#9417](https://github.com/owncloud/client/issues/9417) +* Bugfix - Improve filter pop-up menu and button: [#9425](https://github.com/owncloud/client/issues/9425) +* Bugfix - Ensure proper setup of network jobs on retries: [#9437](https://github.com/owncloud/client/pull/9437) +* Bugfix - Fix Account Filter for Server Activity tab: [#9481](https://github.com/owncloud/client/pull/9481) +* Bugfix - If required clear cookies in more scenarios: [#9489](https://github.com/owncloud/client/pull/9489) +* Bugfix - Possible crash when removing non-existing folder: [#9533](https://github.com/owncloud/client/issues/9533) +* Bugfix - Can't stop basic auth login: [#9545](https://github.com/owncloud/client/issues/9545) +* Enhancement - Retry token refresh multiple times before logout: [#9245](https://github.com/owncloud/client/issues/9245) +* Enhancement - Don't log error when checking removed file for changes: [#9304](https://github.com/owncloud/client/issues/9304) +* Enhancement - Leave password field in share dialog enabled on errors: [#9336](https://github.com/owncloud/client/issues/9336) +* Enhancement - Provide informal German translations: [#9460](https://github.com/owncloud/client/issues/9460) +* Enhancement - Always flush log when logging to stdout: [#9515](https://github.com/owncloud/client/pull/9515) +* Enhancement - Retry update after 10 minutes: [#9522](https://github.com/owncloud/client/issues/9522) +* Enhancement - Added branding parameter to disallow duplicated folder sync pairs: [#9523](https://github.com/owncloud/client/issues/9523) + +## Details + +* Bugfix - Fix potential download failure for renamed file with Windows VFS: [#18](https://github.com/owncloud/client-desktop-vfs-win/pull/18) + + When a dehydrated file is renamed and immediately opened, the subsequent + download might try to create a file with the original (un-renamed) name. + + https://github.com/owncloud/client-desktop-vfs-win/pull/18 + +* Bugfix - Possible crash when downloading a virtual file on Windows: [#21](https://github.com/owncloud/client-desktop-vfs-win/pull/21) + + We fixed a bug that might have caused crashes when working with virtual files on + Windows. + + https://github.com/owncloud/client-desktop-vfs-win/pull/21 + +* Bugfix - Don't publish upload if we can't finish the transaction in the client: [#5052](https://github.com/owncloud/enterprise/issues/5052) + + When a file gets locked during an upload we aborted after the upload finished on + the server. Resulting in a divergence of the local and remote state which could + lead to conflicts. + + https://github.com/owncloud/enterprise/issues/5052 + +* Bugfix - Do not strip trailing whitespace from a file or folder name: [#9030](https://github.com/owncloud/client/issues/9030) + + https://github.com/owncloud/client/issues/9030 + https://github.com/owncloud/client/pull/9452 + +* Bugfix - Remove outdated translations: [#9105](https://github.com/owncloud/client/issues/9105) + + Due to a bug we were not removing the translations that fell below a required + quality margin, we only stopped updating them. Resulting in even worse + translations in some cases. + + https://github.com/owncloud/client/issues/9105 + +* Bugfix - Progress reporting for TUS uploads: [#9121](https://github.com/owncloud/client/issues/9121) + + We fixed a bug with missing progress reporting in TUS uploads + + https://github.com/owncloud/client/issues/9121 + +* Bugfix - Fix crash if a database error occurs: [#9147](https://github.com/owncloud/client/issues/9147) + + We no longer crash if a database error occurs on startup, instead the folder + will enter an error sate similar to the case that the folder does not exist. + + https://github.com/owncloud/client/issues/9147 + +* Bugfix - --version showed incorrect information about VFS support: [#9155](https://github.com/owncloud/client/issues/9155) + + --version used to always show "Off", even when a VFS plugin was available. This + has been fixed now. + + https://github.com/owncloud/client/issues/9155 + https://github.com/owncloud/client/pull/9457 + +* Bugfix - Client warns about non-existing files: [#9236](https://github.com/owncloud/client/issues/9236) + + We fixed a bug where the client warns about ignored files that where added to + the DB in previous versions of the client and do no longer exist. + + https://github.com/owncloud/client/issues/9236 + +* Bugfix - Mention source file in activity tab when renaming: [#9238](https://github.com/owncloud/client/issues/9238) + + https://github.com/owncloud/client/issues/9238 + https://github.com/owncloud/client/pull/9453 + +* Bugfix - Fix crash on remove account: [#9367](https://github.com/owncloud/client/issues/9367) + + We fixed a potential reference to a deleted item, when an account was removed. + + https://github.com/owncloud/client/issues/9367 + +* Bugfix - Raise ssl issue dialog above the wizard: [#9375](https://github.com/owncloud/client/pull/9375) + + Under certain conditions it was possible that the ssl dialog was hidden behind + the wizard. + + https://github.com/owncloud/client/pull/9375 + +* Bugfix - Fallback to ownCloud sidebar icons on Mac if none provided in branding: [#9381](https://github.com/owncloud/client/pull/9381) + + If a customer does not provide sidebar icons we use the ownCloud sidebar icons. + + https://github.com/owncloud/client/pull/9381 + +* Bugfix - Immediately retry upload if file changed during sync: [#9382](https://github.com/owncloud/client/issues/9382) + + If a file changed during discovery and the actual upload for multiple retries in + a row, changes of it were ignored for a period of time. + + https://github.com/owncloud/client/issues/9382 + +* Bugfix - Don't reset change time on upload: [#9383](https://github.com/owncloud/client/issues/9383) + + We fixed a bug where we reset the change time of Windows placeholder files to + the value in the database during uploads. This cold cause other applications to + detect non-existing changes in that file. + + https://github.com/owncloud/client/issues/9383 + https://github.com/owncloud/client-desktop-vfs-win/pull/16 + +* Bugfix - Fix toggling launch-on-login for macOS: [#9387](https://github.com/owncloud/client/issues/9387) + + This would fail when upgrading the application, and the upgraded version has one + or more letters in the name changed from/to upper-case. + + https://github.com/owncloud/client/issues/9387 + https://github.com/owncloud/client/pull/9433 + +* Bugfix - Fix translated icon names in desktop file with ownBrander themes: [#9390](https://github.com/owncloud/client/pull/9390) + + Fixes broken translated icon reference in desktop entries for some branded build + themes. + + https://github.com/owncloud/client/pull/9390 + +* Bugfix - Add open local/remote folder options: [#9405](https://github.com/owncloud/client/issues/9405) + + Add the "open local/remote folder" context menu items for non-sync-root items + back into the accounts tab in the settings dialog. + + https://github.com/owncloud/client/issues/9405 + https://github.com/owncloud/client/pull/9420 + +* Bugfix - Fix possible crash: [#9417](https://github.com/owncloud/client/issues/9417) + + We change the initialisation of a Windows icon to prevent a possible crash. + + https://github.com/owncloud/client/issues/9417 + +* Bugfix - Improve filter pop-up menu and button: [#9425](https://github.com/owncloud/client/issues/9425) + + - replaced "No filter" option text with "All", to avoid the "No filter is not + enabled" situation - replace the "Filter" label on the button with "1 Filter"/"2 + Filters" when a filter is active, so a user can immediately see that without + having to open the filter pop-up + + https://github.com/owncloud/client/issues/9425 + https://github.com/owncloud/client/pull/9513 + +* Bugfix - Ensure proper setup of network jobs on retries: [#9437](https://github.com/owncloud/client/pull/9437) + + On retries network jobs where not properly setup which could lead to undefined + behaviour. + + https://github.com/owncloud/client/pull/9437 + +* Bugfix - Fix Account Filter for Server Activity tab: [#9481](https://github.com/owncloud/client/pull/9481) + + https://github.com/owncloud/client/pull/9481 + +* Bugfix - If required clear cookies in more scenarios: [#9489](https://github.com/owncloud/client/pull/9489) + + BigIp F5 requires special cookie handling on our side. We only explicitly + cleared the cookies when we hit an unexpected redirect, now we will clear them + also when refreshing our OAuth token. + + https://github.com/owncloud/client/pull/9489 + +* Bugfix - Possible crash when removing non-existing folder: [#9533](https://github.com/owncloud/client/issues/9533) + + https://github.com/owncloud/client/issues/9533 + +* Bugfix - Can't stop basic auth login: [#9545](https://github.com/owncloud/client/issues/9545) + + We fixed a bug where the user was asked for their credentials again and again + with no chance to abort. + + https://github.com/owncloud/client/issues/9545 + +* Enhancement - Retry token refresh multiple times before logout: [#9245](https://github.com/owncloud/client/issues/9245) + + https://github.com/owncloud/client/issues/9245 + https://github.com/owncloud/client/pull/9380 + +* Enhancement - Don't log error when checking removed file for changes: [#9304](https://github.com/owncloud/client/issues/9304) + + We removed some misleading error messages from the log. + + https://github.com/owncloud/client/issues/9304 + +* Enhancement - Leave password field in share dialog enabled on errors: [#9336](https://github.com/owncloud/client/issues/9336) + + The password line edit used to be disabled because the related checkbox was + unchecked upon errors such as failing to satisfy the requirements imposed by the + "password policy" server app. + + Now, the checkbox will not be unchecked, leaving the line edit enabled and + keeping the focus on it. This allows users to enter a new password and try again + without having to enable the checkbox and clicking into the line edit again. + + https://github.com/owncloud/client/issues/9336 + https://github.com/owncloud/client/pull/9508 + +* Enhancement - Provide informal German translations: [#9460](https://github.com/owncloud/client/issues/9460) + + The community was maintaining an informal German translation for years but we + were only able to provide a single version of German in the client. We now ship + both versions, the informal can be selected in the combobox in the advanced + settings. To be able to distinguish between formal and informal locales, we also + include the locale identifier in the dropdown (e.g., "Deutsch (de-informal)"). + + https://github.com/owncloud/client/issues/9460 + https://github.com/owncloud/client/pull/9502 + +* Enhancement - Always flush log when logging to stdout: [#9515](https://github.com/owncloud/client/pull/9515) + + We improved the behaviour of logging to a terminal. + + https://github.com/owncloud/client/pull/9515 + +* Enhancement - Retry update after 10 minutes: [#9522](https://github.com/owncloud/client/issues/9522) + + When an update (check) fails, it is currently retried only when the regular + timeout (10 hours by default) is triggered. With this change, we retry the + update (check) after 10 minutes already. + + https://github.com/owncloud/client/issues/9522 + https://github.com/owncloud/client/pull/9525 + +* Enhancement - Added branding parameter to disallow duplicated folder sync pairs: [#9523](https://github.com/owncloud/client/issues/9523) + + We added a branding parameter to disallow the addition of duplicated folder sync + pairs in the add folder wizard. + + https://github.com/owncloud/client/issues/9523 + +# Changelog for ownCloud Desktop Client [2.10.0] (2022-01-17) + +The following sections list the changes in ownCloud Desktop Client 2.10.0 relevant to +ownCloud admins and users. + +[2.10.0]: https://github.com/owncloud/client/compare/v2.9.2...v2.10.0 -Summary -------- +## Summary -* Bugfix - Add request time and other missing data to .owncloudsync.log: [#7348](https://github.com/owncloud/client/issues/7348) -* Bugfix - Do not ask for credentails at start-up when the user logged out: [#8924](https://github.com/owncloud/client/issues/8924) -* Bugfix - Fix copy url location for private links: [#9048](https://github.com/owncloud/client/issues/9048) +* Bugfix - We fixed a possible crash: [#13](https://github.com/owncloud/client-desktop-vfs-win/pull/13) +* Bugfix - Issues with long file path: [#4896](https://github.com/owncloud/enterprise/issues/4896) +* Bugfix - Do not ask for credentials at start-up when the user logged out: [#8924](https://github.com/owncloud/client/issues/8924) * Bugfix - Immediately propagate changes to the ignore list: [#8975](https://github.com/owncloud/client/issues/8975) * Bugfix - Fix icon name in desktop file with ownBrander themes: [#8992](https://github.com/owncloud/client/issues/8992) +* Bugfix - A folder moved on the server was displayed as outdated: [#9071](https://github.com/owncloud/client/issues/9071) +* Bugfix - Do not sync when `unsyncedfolders` file cannot be read: [#9165](https://github.com/owncloud/client/issues/9165) * Bugfix - Crash when handling locked files: [#9170](https://github.com/owncloud/client/issues/9170) -* Bugfix - Display correct error message for files containign `\:?*"<>|`: [#9223](https://github.com/owncloud/client/pull/9223/) -* Change - Make sharedialog preview be more resilient: [#8938](https://github.com/owncloud/client/issues/8938) +* Bugfix - Don't abort upload if chunk is locked: [#9194](https://github.com/owncloud/client/issues/9194) +* Bugfix - Always restart OAuth2 on error: [#9196](https://github.com/owncloud/client/issues/9196) +* Bugfix - Display correct error message for files containing `\:?*"<>|`: [#9223](https://github.com/owncloud/client/pull/9223/) +* Bugfix - Fix failing dehydration causing files to be moved to trash: [#9257](https://github.com/owncloud/client/pull/9257) +* Bugfix - Do not show Activity tab if server app is disabled or uninstalled: [#9260](https://github.com/owncloud/client/issues/9260) +* Bugfix - Handle file locks for delete jobs: [#9293](https://github.com/owncloud/client/issues/9293) +* Bugfix - Run a full local discovery after we were paused or on a forced sync: [#9341](https://github.com/owncloud/client/issues/9341) +* Bugfix - Infinite sync loop if folder is locked: [#9342](https://github.com/owncloud/client/issues/9342) +* Enhancement - Add the syncroot to the search indexed with Windows VFS: [#12](https://github.com/owncloud/client-desktop-vfs-win/pull/12) +* Enhancement - Reintroduce issue filtering: [#9000](https://github.com/owncloud/client/issues/9000) * Enhancement - Allow to remove broken sync folders: [#9099](https://github.com/owncloud/client/pull/9099) +* Enhancement - Also ignore local repeating errors for a period of time: [#9208](https://github.com/owncloud/client/issues/9208) +* Enhancement - Remove the availability menu from the ui: [#9291](https://github.com/owncloud/client/pull/9291) -Details -------- +## Details -* Bugfix - Add request time and other missing data to .owncloudsync.log: [#7348](https://github.com/owncloud/client/issues/7348) +* Bugfix - We fixed a possible crash: [#13](https://github.com/owncloud/client-desktop-vfs-win/pull/13) - Some parameters were missing for some operations. This fix makes the log more complete and more - useful as a result. + We fixed a possible crash that could happen during the initialisation of the vfs + plugin. - https://github.com/owncloud/client/issues/7348 + https://github.com/owncloud/client-desktop-vfs-win/pull/13 -* Bugfix - Do not ask for credentails at start-up when the user logged out: [#8924](https://github.com/owncloud/client/issues/8924) +* Bugfix - Issues with long file path: [#4896](https://github.com/owncloud/enterprise/issues/4896) - When a user would logout, and quit the client, then on the next start the client would - immediately ask for credentials. This has been fixed by storing the fact that the user logged - out before in the account settings. + We fixed another issue with long Windows paths. - https://github.com/owncloud/client/issues/8924 + https://github.com/owncloud/enterprise/issues/4896 -* Bugfix - Fix copy url location for private links: [#9048](https://github.com/owncloud/client/issues/9048) +* Bugfix - Do not ask for credentials at start-up when the user logged out: [#8924](https://github.com/owncloud/client/issues/8924) - We fixed a bug where a placholder was copied to the clipboard instead of the url. + When a user would log out, and quit the client, then on the next start the + client would immediately ask for credentials. This has been fixed by storing the + fact that the user logged out before in the account settings. - https://github.com/owncloud/client/issues/9048 + https://github.com/owncloud/client/issues/8924 * Bugfix - Immediately propagate changes to the ignore list: [#8975](https://github.com/owncloud/client/issues/8975) - Previously, when changing the ignore list, those changes would not be propagated to existing - sync folders. Only after restarting the client, would these changes be applied. + Previously, when changing the ignore list, those changes would not be propagated + to existing sync folders. Only after restarting the client, would these changes + be applied. https://github.com/owncloud/client/issues/8975 https://github.com/owncloud/client/pull/9149 @@ -57,55 +2570,151 @@ Details https://github.com/owncloud/client/issues/8992 https://github.com/owncloud/client/pull/9150 +* Bugfix - A folder moved on the server was displayed as outdated: [#9071](https://github.com/owncloud/client/issues/9071) + + We fixed a bug where a folder moved on the server was displayed as outdated. + + https://github.com/owncloud/client/issues/9071 + +* Bugfix - Do not sync when `unsyncedfolders` file cannot be read: [#9165](https://github.com/owncloud/client/issues/9165) + + Owncloudcmd now checks if the file specified by --unsyncedfolders exists and can + be read, before starting the sync. If it does not exist, show an error message + and quit immediately. + + https://github.com/owncloud/client/issues/9165 + https://github.com/owncloud/client/pull/9241 + * Bugfix - Crash when handling locked files: [#9170](https://github.com/owncloud/client/issues/9170) - We fixed a crash that could occur when trying to add a locked folder to the databse. + We fixed a crash that could occur when trying to add a locked folder to the + database. https://github.com/owncloud/client/issues/9170 -* Bugfix - Display correct error message for files containign `\:?*"<>|`: [#9223](https://github.com/owncloud/client/pull/9223/) +* Bugfix - Don't abort upload if chunk is locked: [#9194](https://github.com/owncloud/client/issues/9194) + + Since 2.9 we know that we need exclusive file access to a file to properly + handle it with Windows virtual files. Therefore, we checked for the locked state + before we start the upload. Due to a bug we checked that for each file chunk, + now we only check when the upload starts and when it finished completely. + + https://github.com/owncloud/client/issues/9194 + https://github.com/owncloud/client/pull/9264 + https://github.com/owncloud/client/pull/9296 + +* Bugfix - Always restart OAuth2 on error: [#9196](https://github.com/owncloud/client/issues/9196) + + We now always restart the OAuth2 process once we got a result. This will ensure + that a second try after an error occurred can succeed. + + https://github.com/owncloud/client/issues/9196 - While the error message was supposed to be: `File names containing the character '%1' are not - supported on this file system.` +* Bugfix - Display correct error message for files containing `\:?*"<>|`: [#9223](https://github.com/owncloud/client/pull/9223/) + + While the error message was supposed to be: `File names containing the character + '%1' are not supported on this file system.` We displayed: `The file name is a reserved name on this file system.` https://github.com/owncloud/client/pull/9223/ -* Change - Make sharedialog preview be more resilient: [#8938](https://github.com/owncloud/client/issues/8938) +* Bugfix - Fix failing dehydration causing files to be moved to trash: [#9257](https://github.com/owncloud/client/pull/9257) - We no longer enforce png thumbnails. We no longer replace the file icon if the thumbnail is - invalid. + If files where dehydrated by the user the action could fail under certain + conditions which caused a deletion of the file. - https://github.com/owncloud/client/issues/8938 - https://github.com/owncloud/client/pull/8939 + https://github.com/owncloud/client/pull/9257 + https://github.com/owncloud/client-desktop-vfs-win/pull/9 + +* Bugfix - Do not show Activity tab if server app is disabled or uninstalled: [#9260](https://github.com/owncloud/client/issues/9260) + + The Activity app API nowadays returns error responses in case the app is + disabled or uninstalled. This new behavior is now supported in the client. + + https://github.com/owncloud/client/issues/9260 + https://github.com/owncloud/client/pull/9266 + +* Bugfix - Handle file locks for delete jobs: [#9293](https://github.com/owncloud/client/issues/9293) + + We no longer report an error when the client tries to delete a locked file but + wait for the lock to be removed. + + This only works when a file is deleted not on folders. + + https://github.com/owncloud/client/issues/9293 + https://github.com/owncloud/client/pull/9295 + +* Bugfix - Run a full local discovery after we were paused or on a forced sync: [#9341](https://github.com/owncloud/client/issues/9341) + + Previously we did an incremental search which might have skipped some local + changes. + + https://github.com/owncloud/client/issues/9341 + +* Bugfix - Infinite sync loop if folder is locked: [#9342](https://github.com/owncloud/client/issues/9342) + + We fixed a bug that caused an infinite sync loop if an error occurred. + + https://github.com/owncloud/client/issues/9342 + https://github.com/owncloud/client-desktop-vfs-win/pull/14 + +* Enhancement - Add the syncroot to the search indexed with Windows VFS: [#12](https://github.com/owncloud/client-desktop-vfs-win/pull/12) + + Microsoft recommends adding the syncroot to search indexer to improve the + performance with the file status icons. + + https://github.com/owncloud/client-desktop-vfs-win/pull/12 + +* Enhancement - Reintroduce issue filtering: [#9000](https://github.com/owncloud/client/issues/9000) + + We reintroduced a filtering option to the issue table. With the addition of a + Filter button we also made the existing filter by account feature more + accessible. + + https://github.com/owncloud/client/issues/9000 + https://github.com/owncloud/client/pull/9023 * Enhancement - Allow to remove broken sync folders: [#9099](https://github.com/owncloud/client/pull/9099) - In case a folder is no longer available it was not possible to remove the folder. We now made the - remove action available in that case. + In case a folder is no longer available it was not possible to remove the + folder. We now made the remove action available in that case. https://github.com/owncloud/client/pull/9099 -Changelog for ownCloud Desktop Client [2.9.2] (2021-11-24) -======================================= +* Enhancement - Also ignore local repeating errors for a period of time: [#9208](https://github.com/owncloud/client/issues/9208) + + If an error occurs on the server (a URL is not reachable) we try a couple of + times, then we ignore that file for a period of time. We now do the same with + errors that occur locally. + + https://github.com/owncloud/client/issues/9208 + https://github.com/owncloud/client/issues/9133 + +* Enhancement - Remove the availability menu from the ui: [#9291](https://github.com/owncloud/client/pull/9291) + + The availability options should be handled on a folder base and in the file + browser. + + https://github.com/owncloud/client/pull/9291 + +# Changelog for ownCloud Desktop Client [2.9.2] (2021-11-24) + The following sections list the changes in ownCloud Desktop Client 2.9.2 relevant to ownCloud admins and users. [2.9.2]: https://github.com/owncloud/client/compare/v2.9.1...v2.9.2 -Summary -------- +## Summary * Bugfix - Properly handle Windows log off: [#8979](https://github.com/owncloud/client/issues/8979) * Bugfix - Disable vfs option is ignored: [#9159](https://github.com/owncloud/client/issues/9159) -* Bugfix - The client no longer idles after a minor url change: [#9202](https://github.com/owncloud/client/pull/9202) +* Bugfix - The client no longer idles after a minor URL change: [#9202](https://github.com/owncloud/client/pull/9202) * Bugfix - Config migration on systems with a 2.4 and a 2.8 config: [#9224](https://github.com/owncloud/client/issues/9224) * Enhancement - Add branding option to disable icons in the file explorer: [#9167](https://github.com/owncloud/client/issues/9167) * Enhancement - Branding option to disable warning for multiple sync_journal.db's: [#9216](https://github.com/owncloud/client/pull/9216) -Details -------- +## Details * Bugfix - Properly handle Windows log off: [#8979](https://github.com/owncloud/client/issues/8979) @@ -118,57 +2727,58 @@ Details * Bugfix - Disable vfs option is ignored: [#9159](https://github.com/owncloud/client/issues/9159) - We fixed a branding issue where vfs was used even when the parameter was set to disabled. + We fixed a branding issue where vfs was used even when the parameter was not + enabled. https://github.com/owncloud/client/issues/9159 https://github.com/owncloud/enterprise/issues/4820 -* Bugfix - The client no longer idles after a minor url change: [#9202](https://github.com/owncloud/client/pull/9202) +* Bugfix - The client no longer idles after a minor URL change: [#9202](https://github.com/owncloud/client/pull/9202) - When the client detects a change of the url we ask the user to accept the change or if it was only - representational change (demo.com vs demo.com/) we directly accept the change. Due to a bug - the we aborted the sync only after we updated the url. This caused the client to idle for one - minute. + When the client detects a change of the URL we ask the user to accept the change + or if it was only representational change (demo.com vs demo.com/) we directly + accept the change. Due to a bug we aborted the sync only after we updated the + URL. This caused the client to idle for one minute. https://github.com/owncloud/client/pull/9202 * Bugfix - Config migration on systems with a 2.4 and a 2.8 config: [#9224](https://github.com/owncloud/client/issues/9224) - We fixed a bug where the client migrated the old settings from 2.4 to 2.9 instead of the 2.8 - settings. Only branded clients where affected by the issue. + We fixed a bug where the client migrated the old settings from 2.4 to 2.9 + instead of the 2.8 settings. Only branded clients where affected by the issue. https://github.com/owncloud/client/issues/9224 https://github.com/owncloud/client/pull/9226 * Enhancement - Add branding option to disable icons in the file explorer: [#9167](https://github.com/owncloud/client/issues/9167) - We implemented a branding parameter to disable the display of icons in the file explorer - context menu, this only affects Windows and Linux. + We implemented a branding parameter to disable the display of icons in the file + explorer context menu, this only affects Windows and Linux. https://github.com/owncloud/client/issues/9167 * Enhancement - Branding option to disable warning for multiple sync_journal.db's: [#9216](https://github.com/owncloud/client/pull/9216) - We added a branding option that disables the `Multiple accounts are sharing the folder` - warning. In previous client versions a bug caused the creation of new sync journals, causing - false positives in the detection. While this can be handled by the individual user, companies - with multiple hundreds of users may opt to disable the warning. + We added a branding option that disables the `Multiple accounts are sharing the + folder` warning. In previous client versions a bug caused the creation of new + sync journals, causing false positives in the detection. While this can be + handled by the individual user, companies with multiple hundreds of users may + opt to disable the warning. https://github.com/owncloud/client/pull/9216 -Changelog for ownCloud Desktop Client [2.9.1] (2021-10-13) -======================================= +# Changelog for ownCloud Desktop Client [2.9.1] (2021-10-13) + The following sections list the changes in ownCloud Desktop Client 2.9.1 relevant to ownCloud admins and users. [2.9.1]: https://github.com/owncloud/client/compare/v2.9.0...v2.9.1 -Summary -------- +## Summary * Bugfix - Mac multiple dialogs block all input: [#8421](https://github.com/owncloud/client/issues/8421) * Bugfix - Enforce rtl layout with rtl languages: [#8806](https://github.com/owncloud/client/issues/8806) -* Bugfix - Broken url in branded builds: [#8920](https://github.com/owncloud/client/issues/8920) +* Bugfix - Broken URL in branded builds: [#8920](https://github.com/owncloud/client/issues/8920) * Bugfix - Handle use of directory of another Windows VFS sync client: [#8994](https://github.com/owncloud/client/issues/8994) * Bugfix - Building libcloudprovider support fails: [#8996](https://github.com/owncloud/client/issues/8996) * Bugfix - Tables now display local time: [#9006](https://github.com/owncloud/client/issues/9006) @@ -183,12 +2793,12 @@ Summary * Bugfix - Downgrades could trigger deletion of virtual files: [#9114](https://github.com/owncloud/client/issues/9114) * Enhancement - Display the error type in the issue protocol to allow sorting: [#9010](https://github.com/owncloud/client/pull/9010) -Details -------- +## Details * Bugfix - Mac multiple dialogs block all input: [#8421](https://github.com/owncloud/client/issues/8421) - We back ported a fix to a Qt bug which causes multiple dialogs on Mac to block all input. + We back ported a fix to a Qt bug which causes multiple dialogs on Mac to block + all input. https://github.com/owncloud/client/issues/8421 https://bugreports.qt.io/browse/QTBUG-91059 @@ -196,13 +2806,13 @@ Details * Bugfix - Enforce rtl layout with rtl languages: [#8806](https://github.com/owncloud/client/issues/8806) - We fixed a bug where setting the language to a right to left language on mac did not change the - layout of the application. + We fixed a bug where setting the language to a right to left language on Mac did + not change the layout of the application. https://github.com/owncloud/client/issues/8806 https://github.com/owncloud/client/pull/8981 -* Bugfix - Broken url in branded builds: [#8920](https://github.com/owncloud/client/issues/8920) +* Bugfix - Broken URL in branded builds: [#8920](https://github.com/owncloud/client/issues/8920) We fixed a string issue with branded builds resulting in invalid urls. @@ -210,9 +2820,9 @@ Details * Bugfix - Handle use of directory of another Windows VFS sync client: [#8994](https://github.com/owncloud/client/issues/8994) - We now better handle setup issues during the initialisation of virtual files support. - Especially the case that a user tries to use a directory managed by a competitor which until now - caused a crash. + We now better handle setup issues during the initialisation of virtual files + support. Especially the case that a user tries to use a directory managed by a + competitor which until now caused a crash. https://github.com/owncloud/client/issues/8994 @@ -243,8 +2853,8 @@ Details * Bugfix - Crash on missing or unreadable sync root: [#9016](https://github.com/owncloud/client/issues/9016) - We fixed an issue where the client crashed after a user deleted the sync root or lost access to the - directory. + We fixed an issue where the client crashed after a user deleted the sync root or + lost access to the directory. https://github.com/owncloud/client/issues/9016 https://github.com/owncloud/client/pull/9017 @@ -252,17 +2862,21 @@ Details * Bugfix - A upgrade to 2.9 causes the usage of a new journal file: [#9019](https://github.com/owncloud/client/issues/9019) - We fixed a bug where the name of the sync journal was not properly saved to the settings. This - caused a bug when migration to 2.9, so a new a new sync journal was created. + We fixed a bug where the name of the sync journal was not properly saved to the + settings. This caused a bug when migration to 2.9, so a new sync journal was + created. - This not only caused the loss of some selective sync settings, but also caused the display of the - following warning message: + This not only caused the loss of some selective sync settings, but also caused + the display of the following warning message: - ``` Multiple accounts are sharing the folder. This configuration is know to lead to dataloss - and is no longer supported. Please consider removing this folder from the account and adding it - again. ``` + ``` + Multiple accounts are sharing the folder. + This configuration is know to lead to dataloss and is no longer supported. + Please consider removing this folder from the account and adding it again. + ``` - We also removed the account info infix from the sync db used with the cmd client. + We also removed the account info infix from the sync db used with the cmd + client. https://github.com/owncloud/client/issues/9019 https://github.com/owncloud/client/pull/9028 @@ -271,7 +2885,8 @@ Details * Bugfix - Properly deployment of Qt translations Mac and Window: [#9022](https://github.com/owncloud/client/issues/9022) - We fixed a deployment bug which prevented the translation of some components to be loaded. + We fixed a deployment bug which prevented the translation of some components to + be loaded. https://github.com/owncloud/client/issues/9022 https://invent.kde.org/packaging/craft/-/commit/77c114917826480f294d0432f147c9e9f7d19e21 @@ -284,15 +2899,17 @@ Details * Bugfix - When a folder is removed we leave a database behind: [#9057](https://github.com/owncloud/client/issues/9057) - We fixed a bug where we left an empty `sync_journal.db` behind, when we removed a - folder/account. As we use the presence of `sync_journal.db` to determine whether the folder - is used by a sync client this prevented using an old folder in a new setup. + We fixed a bug where we left an empty `sync_journal.db` behind, when we removed + a folder/account. As we use the presence of `sync_journal.db` to determine + whether the folder is used by a sync client this prevented using an old folder + in a new setup. https://github.com/owncloud/client/issues/9057 * Bugfix - Dehydrating placeholders failed if the file is read only: [#9093](https://github.com/owncloud/client/issues/9093) - We fixed a bug where dehydrating a read only file failed without any apparent reason. + We fixed a bug where dehydrating a read only file failed without any apparent + reason. https://github.com/owncloud/client/issues/9093 https://gitea.owncloud.services/client/client-plugin-vfs-win/pulls/33 @@ -305,20 +2922,20 @@ Details * Enhancement - Display the error type in the issue protocol to allow sorting: [#9010](https://github.com/owncloud/client/pull/9010) - We now display the error type in the not synced protocol and allow to sort by the error type. + We now display the error type in the not synced protocol and allow to sort by + the error type. https://github.com/owncloud/client/issues/9000 https://github.com/owncloud/client/pull/9010 -Changelog for ownCloud Desktop Client [2.9.0] (2021-09-08) -======================================= +# Changelog for ownCloud Desktop Client [2.9.0] (2021-09-08) + The following sections list the changes in ownCloud Desktop Client 2.9.0 relevant to ownCloud admins and users. [2.9.0]: https://github.com/owncloud/client/compare/v2.8.2...v2.9.0 -Summary -------- +## Summary * Bugfix - Settings migration from v2.4: [#4597](https://github.com/owncloud/enterprise/issues/4597) * Bugfix - VFS support for folders in the drive root: [#4639](https://github.com/owncloud/enterprise/issues/4639) @@ -328,45 +2945,44 @@ Summary * Bugfix - Don't recommend non default sync option: [#8317](https://github.com/owncloud/client/issues/8317) * Bugfix - Limit min window size to 2/3 available: [#8366](https://github.com/owncloud/client/issues/8366) * Bugfix - Set up default locale correctly: [#8367](https://github.com/owncloud/client/issues/8367) +* Bugfix - Relaunching the client on macOS will show the settings dialog: [#8377](https://github.com/owncloud/client/issues/8377) * Bugfix - Only show the settings if they are explicitly requested on start: [#8590](https://github.com/owncloud/client/issues/8590) -* Bugfix - Ignore consecutive errors for a pereiode of time: [#8672](https://github.com/owncloud/client/issues/8672) +* Bugfix - Ignore consecutive errors for a period of time: [#8672](https://github.com/owncloud/client/issues/8672) * Bugfix - Properly print the sync events in .owncloudsync.log: [#8729](https://github.com/owncloud/client/issues/8729) * Bugfix - Correctly handle file path comparison on Windows: [#8732](https://github.com/owncloud/client/issues/8732) * Bugfix - Locked files are not correctly synced: [#8761](https://github.com/owncloud/client/issues/8761) * Bugfix - Prompt the user of the cmd client what to do if all files where removed: [#8804](https://github.com/owncloud/client/issues/8804) -* Bugfix - Relaunching the client on macOS will show the settings dialog: [#8377](https://github.com/owncloud/client/issues/8377) * Bugfix - Fix config migrations from versions 2.8 to 2.9: [#8824](https://github.com/owncloud/client/issues/8824) * Bugfix - Always exclude .owncloud files: [#8836](https://github.com/owncloud/client/pull/8836) * Bugfix - Don't crash if a certain move is undone: [#8837](https://github.com/owncloud/client/issues/8837) * Bugfix - Prevent sync root sharing: [#8849](https://github.com/owncloud/client/issues/8849) * Bugfix - Removed support for client side certificates: [#8864](https://github.com/owncloud/client/pull/8864) * Bugfix - The `Re-open Browser` now always does what it says: [#8866](https://github.com/owncloud/client/pull/8866) -* Bugfix - Start oauth/password prompt if password is wrong during start up: [#8901](https://github.com/owncloud/client/issues/8901) -* Bugfix - Handle timeouts occurring during oauth: [#8940](https://github.com/owncloud/client/pull/8940) +* Bugfix - Start OAuth/password prompt if password is wrong during start up: [#8901](https://github.com/owncloud/client/issues/8901) +* Bugfix - Handle timeouts occurring during OAuth: [#8940](https://github.com/owncloud/client/pull/8940) * Change - Don't ask the user to switch to http: [#8231](https://github.com/owncloud/client/issues/8231) -* Change - Remove support for http redirects: [#8293](https://github.com/owncloud/client/pull/8293) * Change - We no longer try to locate an ownCloud install at /owncloud: [#8273](https://github.com/owncloud/client/issues/8273) +* Change - Remove support for http redirects: [#8293](https://github.com/owncloud/client/pull/8293) * Change - Ignore the desktop.ini file in every directory, not only in top dir: [#8298](https://github.com/owncloud/client/issues/8298) * Change - Add support for dynamic client registration with OIDC: [#8350](https://github.com/owncloud/client/pull/8350/) -* Change - Include full os version in the about dialog: [#8374](https://github.com/owncloud/client/pull/8374) +* Change - Include full os version in the about-dialog: [#8374](https://github.com/owncloud/client/pull/8374) * Change - We removed the support for async jobs using OC-JobStatus-Location: [#8398](https://github.com/owncloud/client/pull/8398) * Change - Add a branding option to skip the advanced setup page: [#8665](https://github.com/owncloud/client/issues/8665) -* Enhancement - Prefer 127.0.0.1 as oauth redirect url: [#4542](https://github.com/owncloud/enterprise/issues/4542) +* Enhancement - Make crash report IDs easy to copy: [#25](https://github.com/dschmidt/libcrashreporter-qt/pull/25) +* Enhancement - Prefer 127.0.0.1 as OAuth redirect URL: [#4542](https://github.com/owncloud/enterprise/issues/4542) * Enhancement - Display an icon in the Windows explorer context menu: [#4627](https://github.com/owncloud/client/issues/4627) * Enhancement - Show last sync date in tray menu: [#5644](https://github.com/owncloud/client/issues/5644) -* Enhancement - Display the information state in case we encountered ignored errors: [#8858](https://github.com/owncloud/client/pull/8858) -* Enhancement - Make crash report IDs easy to copy: [#25](https://github.com/dschmidt/libcrashreporter-qt/pull/25) +* Enhancement - Consider a remote poll interval coming with the server capabilities: [#5947](https://github.com/owncloud/client/issues/5947) * Enhancement - We reworked the tables: [#8158](https://github.com/owncloud/client/issues/8158) * Enhancement - Provide a socket api call to get the client icon and: [#8464](https://github.com/owncloud/client/issues/8464) * Enhancement - Add language picker to general settings: [#8466](https://github.com/owncloud/client/issues/8466) * Enhancement - Attach the last 20 log lines to a crash report: [#8467](https://github.com/owncloud/client/issues/8467) * Enhancement - Mention the local file name when a file name clash occurs: [#8609](https://github.com/owncloud/client/issues/8609) -* Enhancement - Consider a remote poll interval coming with the server capabilities: [#5947](https://github.com/owncloud/client/issues/5947) * Enhancement - Improved handling of errors during local file updates: [#8787](https://github.com/owncloud/client/pull/8787) * Enhancement - Retry sync on `502 Bad Gateway`: [#8811](https://github.com/owncloud/client/issues/8811) +* Enhancement - Display the information state in case we encountered ignored errors: [#8858](https://github.com/owncloud/client/pull/8858) -Details -------- +## Details * Bugfix - Settings migration from v2.4: [#4597](https://github.com/owncloud/enterprise/issues/4597) @@ -382,16 +2998,16 @@ Details * Bugfix - Keep share link names in sync with server: [#7549](https://github.com/owncloud/client/issues/7549) - When the name of a share link is changed, e.g., to an empty string, the server may not apply this, - but assign a fallback value (e.g., the link ID). The client therefore needs to re-check the name - after sending a change request. + When the name of a share link is changed, e.g., to an empty string, the server + may not apply this, but assign a fallback value (e.g., the link ID). The client + therefore needs to re-check the name after sending a change request. https://github.com/owncloud/client/issues/7549 https://github.com/owncloud/client/pull/8546 * Bugfix - SQLite wal file grows to several gigabyte: [#7646](https://github.com/owncloud/client/issues/7646) - We fixed a bug where the SQLite wal file growed until the client was quit. + We fixed a bug where the SQLite wal file grew until the client was quit. https://github.com/owncloud/client/issues/7646 @@ -409,28 +3025,37 @@ Details * Bugfix - Limit min window size to 2/3 available: [#8366](https://github.com/owncloud/client/issues/8366) - When scaling was used the window could become bigger than the screen. The size is now limited to - 2/3 of the screen. + When scaling was used the window could become bigger than the screen. The size + is now limited to 2/3 of the screen. https://github.com/owncloud/client/issues/8366 * Bugfix - Set up default locale correctly: [#8367](https://github.com/owncloud/client/issues/8367) - Fixes the formatting in locale-dependent widgets, e.g., date pickers, like the one in the - "share link" window. + Fixes the formatting in locale-dependent widgets, e.g., date pickers, like the + one in the "share link" window. https://github.com/owncloud/client/issues/8367 https://github.com/owncloud/client/pull/8541 https://github.com/owncloud/client/pull/8617 +* Bugfix - Relaunching the client on macOS will show the settings dialog: [#8377](https://github.com/owncloud/client/issues/8377) + + Relaunching the ownCloud client when it is already running, would seemingly do + nothing at all. To make this more consistent with other macOS applications, + relaunching will now open the settings dialog. + + https://github.com/owncloud/client/issues/8377 + https://github.com/owncloud/client/pull/8812 + * Bugfix - Only show the settings if they are explicitly requested on start: [#8590](https://github.com/owncloud/client/issues/8590) - We now only display the settings when the user requested it on start and not every time the - application is started a second time. + We now only display the settings when the user requested it on start and not + every time the application is started a second time. https://github.com/owncloud/client/issues/8590 -* Bugfix - Ignore consecutive errors for a pereiode of time: [#8672](https://github.com/owncloud/client/issues/8672) +* Bugfix - Ignore consecutive errors for a period of time: [#8672](https://github.com/owncloud/client/issues/8672) We fixed a bug where certain errors caused a sync run every 30 seconds @@ -438,21 +3063,22 @@ Details * Bugfix - Properly print the sync events in .owncloudsync.log: [#8729](https://github.com/owncloud/client/issues/8729) - We fixed a bug in the .owncloudsync.log logger which caused enum values to be printed as a number - rather than a string. + We fixed a bug in the .owncloudsync.log logger which caused enum values to be + printed as a number rather than a string. https://github.com/owncloud/client/issues/8729 * Bugfix - Correctly handle file path comparison on Windows: [#8732](https://github.com/owncloud/client/issues/8732) - We fixed a bug in which a change in the casing og the sync root made the client ignore changes in it. + We fixed a bug in which a change in the casing og the sync root made the client + ignore changes in it. https://github.com/owncloud/client/issues/8732 * Bugfix - Locked files are not correctly synced: [#8761](https://github.com/owncloud/client/issues/8761) - We fixed an issue where files locked by office etc, where not correctly synced, when Windows - Virtual files are enabled. + We fixed an issue where files locked by office etc., where not correctly synced, + when Windows Virtual files are enabled. https://github.com/owncloud/client/issues/8761 https://github.com/owncloud/client/issues/8765 @@ -466,31 +3092,25 @@ Details https://github.com/owncloud/client/issues/8804 -* Bugfix - Relaunching the client on macOS will show the settings dialog: [#8377](https://github.com/owncloud/client/issues/8377) - - Relaunching the ownCloud client when it is already running, would seemingly do nothing at all. - To make this more consistent with other macOS applications, relaunching will now open the - settings dialog. - - https://github.com/owncloud/client/issues/8377 - https://github.com/owncloud/client/pull/8812 - * Bugfix - Fix config migrations from versions 2.8 to 2.9: [#8824](https://github.com/owncloud/client/issues/8824) - Due to a value change of an internal Qt configuration variable, the configuration data could - not be migrated on many systems. We fixed this by implementing an additional migration path. + Due to a value change of an internal Qt configuration variable, the + configuration data could not be migrated on many systems. We fixed this by + implementing an additional migration path. - Furthermore, we removed the dependency on said value within the GUI, and use the values - explicitly from the theme to display the correct values on UI elements such as buttons. + Furthermore, we removed the dependency on said value within the GUI, and use the + values explicitly from the theme to display the correct values on UI elements + such as buttons. https://github.com/owncloud/client/issues/8824 https://github.com/owncloud/client/pull/8860 * Bugfix - Always exclude .owncloud files: [#8836](https://github.com/owncloud/client/pull/8836) - Our Linux virtual files implementation is using the file name extension .owncloud those files - where only ignored if the Linux VFS was enabled. Under some circumstances it could lead to - undefined client states. We now always ignore those files as system reserved. + Our Linux virtual files implementation is using the file name extension + .owncloud those files where only ignored if the Linux VFS was enabled. Under + some circumstances it could lead to undefined client states. We now always + ignore those files as system reserved. https://github.com/owncloud/client/pull/8836 @@ -502,78 +3122,83 @@ Details * Bugfix - Prevent sync root sharing: [#8849](https://github.com/owncloud/client/issues/8849) - Due to legacy reasons it is possible to let two sync connections use the same directory. In - combination with virtual files this was leading to dataloss however. + Due to legacy reasons it is possible to let two sync connections use the same + directory. In combination with virtual files this was leading to data-loss, + however. https://github.com/owncloud/client/issues/8849 https://github.com/owncloud/client/issues/8512 * Bugfix - Removed support for client side certificates: [#8864](https://github.com/owncloud/client/pull/8864) - Client side certificates where never officially supported and where untested in many - scenarios. + Client side certificates where never officially supported and where untested in + many scenarios. https://github.com/owncloud/client/pull/8864 * Bugfix - The `Re-open Browser` now always does what it says: [#8866](https://github.com/owncloud/client/pull/8866) - Under certain conditions the previous authentication run might have failed and the button - became unresponsive, we now start a new authentication in that case. + Under certain conditions the previous authentication run might have failed and + the button became unresponsive, we now start a new authentication in that case. https://github.com/owncloud/client/pull/8866 -* Bugfix - Start oauth/password prompt if password is wrong during start up: [#8901](https://github.com/owncloud/client/issues/8901) +* Bugfix - Start OAuth/password prompt if password is wrong during start up: [#8901](https://github.com/owncloud/client/issues/8901) - If the oauth token was invalid during start up we didn't start the oauth process and the user - needed to manually log out in order to log in again. + If the OAuth token was invalid during start up we didn't start the OAuth process + and the user needed to manually log out in order to log in again. https://github.com/owncloud/client/issues/8901 -* Bugfix - Handle timeouts occurring during oauth: [#8940](https://github.com/owncloud/client/pull/8940) +* Bugfix - Handle timeouts occurring during OAuth: [#8940](https://github.com/owncloud/client/pull/8940) - We now handle timeouts occurring during oauth. + We now handle timeouts occurring during OAuth. https://github.com/owncloud/client/pull/8940 * Change - Don't ask the user to switch to http: [#8231](https://github.com/owncloud/client/issues/8231) - We no longer recommend to use a http connection if a https url was not found. + We no longer recommend to use a http connection if a https URL was not found. https://github.com/owncloud/client/issues/8231 -* Change - Remove support for http redirects: [#8293](https://github.com/owncloud/client/pull/8293) +* Change - We no longer try to locate an ownCloud install at /owncloud: [#8273](https://github.com/owncloud/client/issues/8273) - We no longer follow redirects, when a redirect is detected we will start a connection - validation process that does follow redirects. This change improves the support of APM - solutions which apply special redirects to provide cookie sessions to the client. + We no longer try to locate an ownCloud install in /owncloud if we failed to + connect to a server. - https://github.com/owncloud/client/pull/8293 - https://github.com/owncloud/client/pull/8253 + https://github.com/owncloud/client/issues/8273 -* Change - We no longer try to locate an ownCloud install at /owncloud: [#8273](https://github.com/owncloud/client/issues/8273) +* Change - Remove support for http redirects: [#8293](https://github.com/owncloud/client/pull/8293) - We no longer try to locate an ownCloud install in /owncloud if we failed to connect to a server. + We no longer follow redirects, when a redirect is detected we will start a + connection validation process that does follow redirects. This change improves + the support of APM solutions which apply special redirects to provide cookie + sessions to the client. - https://github.com/owncloud/client/issues/8273 + https://github.com/owncloud/client/pull/8293 + https://github.com/owncloud/client/pull/8253 * Change - Ignore the desktop.ini file in every directory, not only in top dir: [#8298](https://github.com/owncloud/client/issues/8298) - The windows explorer files called desktop.ini were ignored only in the top sync dir so far. They - are now ignored in the sync in all directory levels of the file tree. + The Windows explorer files called desktop.ini were ignored only in the top sync + dir so far. They are now ignored in the sync in all directory levels of the file + tree. https://github.com/owncloud/client/issues/8298 https://github.com/owncloud/client/pull/8299 * Change - Add support for dynamic client registration with OIDC: [#8350](https://github.com/owncloud/client/pull/8350/) - We implemented support for dynamic client registration with an OpenID Connect provider. + We implemented support for dynamic client registration with an OpenID Connect + provider. https://github.com/owncloud/client/pull/8350/ -* Change - Include full os version in the about dialog: [#8374](https://github.com/owncloud/client/pull/8374) +* Change - Include full os version in the about-dialog: [#8374](https://github.com/owncloud/client/pull/8374) - We now include the os version in the about dialog, this might help us to faster pin down os related - issues. + We now include the os version in the about-dialog, this might help us to faster + pin down os related issues. https://github.com/owncloud/client/pull/8374 @@ -589,12 +3214,21 @@ Details https://github.com/owncloud/client/issues/8665 -* Enhancement - Prefer 127.0.0.1 as oauth redirect url: [#4542](https://github.com/owncloud/enterprise/issues/4542) +* Enhancement - Make crash report IDs easy to copy: [#25](https://github.com/dschmidt/libcrashreporter-qt/pull/25) + + Users can now click on crash report IDs to copy them to their personal + clipboard. This way, they can easily reference them in bug reports. + + https://github.com/owncloud/client/issues/8130 + https://github.com/dschmidt/libcrashreporter-qt/pull/25 + https://github.com/owncloud/client/pull/8540 - When using OpenID Connect we now always use http://127.0.0.1 as redirect url instead of - http://localhost, following the recommendations in RFC 8252 - (https://tools.ietf.org/html/rfc8252). For OAuth2 we added a branding parameter which - allows to specify http://127.0.0.1 instead of http://localhost. +* Enhancement - Prefer 127.0.0.1 as OAuth redirect URL: [#4542](https://github.com/owncloud/enterprise/issues/4542) + + When using OpenID Connect we now always use http://127.0.0.1 as redirect URL + instead of http://localhost, following the recommendations in RFC 8252 + (https://tools.ietf.org/html/rfc8252). For OAuth2 we added a branding parameter + which allows to specify http://127.0.0.1 instead of http://localhost. https://github.com/owncloud/enterprise/issues/4542 @@ -611,34 +3245,20 @@ Details https://github.com/owncloud/client/issues/5644 https://github.com/owncloud/client/pull/8547 -* Enhancement - Display the information state in case we encountered ignored errors: [#8858](https://github.com/owncloud/client/pull/8858) - - If syncing a file fails multiple times we mark it as ignored to skip it for a certain amount of - time. If we have ignored files we are not in sync, we now don't display the green icon. - - Additionally this change aligns the icon displayed in the system tray with the icon displayed - in the app. - - Https://github.com/owncloud/client/issues/7715 - https://github.com/owncloud/client/issues/7365 - https://github.com/owncloud/client/issues/7200 - https://github.com/owncloud/client/issues/5860 - - https://github.com/owncloud/client/pull/8858 - -* Enhancement - Make crash report IDs easy to copy: [#25](https://github.com/dschmidt/libcrashreporter-qt/pull/25) +* Enhancement - Consider a remote poll interval coming with the server capabilities: [#5947](https://github.com/owncloud/client/issues/5947) - Users can now click on crash report IDs to copy them to their personal clipboard. This way, they - can easily reference them in bug reports. + This way, admins can configure the remote sync poll interval of clients through + the capabilities settings of the server. Note that the setting in the server + capabilities needs to be done in milliseconds. Default is 30 seconds. - https://github.com/owncloud/client/issues/8130 - https://github.com/dschmidt/libcrashreporter-qt/pull/25 - https://github.com/owncloud/client/pull/8540 + https://github.com/owncloud/client/issues/5947 + https://github.com/owncloud/client/issues/8780 + https://github.com/owncloud/client/pull/8777 * Enhancement - We reworked the tables: [#8158](https://github.com/owncloud/client/issues/8158) - We reworked all the tables in the application to unify their behaviour and improve their - performance. + We reworked all the tables in the application to unify their behaviour and + improve their performance. https://github.com/owncloud/client/issues/8158 https://github.com/owncloud/client/issues/4336 @@ -652,23 +3272,24 @@ Details Add the icon to the dolphin right click menu - We added support to get the ownCloud client icon of the current theme to the socket api. We show - the client icon in the dolphin file browser context menu. + We added support to get the ownCloud client icon of the current theme to the + socket api. We show the client icon in the dolphin file browser context menu. https://github.com/owncloud/client/issues/8464 * Enhancement - Add language picker to general settings: [#8466](https://github.com/owncloud/client/issues/8466) - Users can override the automatically chosen language by selecting a custom language in a - dropdown in the general settings. Furthermore, a --language CLI parameter was added that - serves the same purpose. + Users can override the automatically chosen language by selecting a custom + language in a dropdown in the general settings. Furthermore, a --language CLI + parameter was added that serves the same purpose. https://github.com/owncloud/client/issues/8466 https://github.com/owncloud/client/pull/8493 * Enhancement - Attach the last 20 log lines to a crash report: [#8467](https://github.com/owncloud/client/issues/8467) - We now save the last 20 lines of log to a tempoary file. This file is then part of a crash report. + We now save the last 20 lines of log to a temporary file. This file is then part + of a crash report. https://github.com/owncloud/client/issues/8467 https://github.com/owncloud/client/pull/8469 @@ -680,49 +3301,54 @@ Details https://github.com/owncloud/client/issues/8609 https://github.com/owncloud/client/pull/8630 -* Enhancement - Consider a remote poll interval coming with the server capabilities: [#5947](https://github.com/owncloud/client/issues/5947) - - This way, admins can configure the remote sync poll interval of clients through the - capabilities settings of the server. Note that the setting in the server capabilities needs to - be done in milliseconds. Default is 30 seconds. - - https://github.com/owncloud/client/issues/5947 - https://github.com/owncloud/client/issues/8780 - https://github.com/owncloud/client/pull/8777 - * Enhancement - Improved handling of errors during local file updates: [#8787](https://github.com/owncloud/client/pull/8787) - If a local metadata update fails we now provide the proper error in the ui. In case that the error - was caused by a locked file we now retry the operation. + If a local metadata update fails we now provide the proper error in the ui. In + case that the error was caused by a locked file we now retry the operation. https://github.com/owncloud/client/pull/8787 * Enhancement - Retry sync on `502 Bad Gateway`: [#8811](https://github.com/owncloud/client/issues/8811) - We now treat a `502 Bad Gateway` as an less severer error and directly initialise a retry. + We now treat a `502 Bad Gateway` as a less severe error and directly initialise + a retry. https://github.com/owncloud/client/issues/8811 -Changelog for ownCloud Desktop Client [2.8.2] (2021-05-28) -======================================= +* Enhancement - Display the information state in case we encountered ignored errors: [#8858](https://github.com/owncloud/client/pull/8858) + + If syncing a file fails multiple times we mark it as ignored to skip it for a + certain amount of time. If we have ignored files we are not in sync, we now + don't display the green icon. + + Additionally, this change aligns the icon displayed in the system tray with the + icon displayed in the app. + + Https://github.com/owncloud/client/issues/7715 + https://github.com/owncloud/client/issues/7365 + https://github.com/owncloud/client/issues/7200 + https://github.com/owncloud/client/issues/5860 + + https://github.com/owncloud/client/pull/8858 + +# Changelog for ownCloud Desktop Client [2.8.2] (2021-05-28) + The following sections list the changes in ownCloud Desktop Client 2.8.2 relevant to ownCloud admins and users. [2.8.2]: https://github.com/owncloud/client/compare/v2.8.1...v2.8.2 -Summary -------- +## Summary * Bugfix - Correctly detect network drives: [#8272](https://github.com/owncloud/client/issues/8272) * Bugfix - We fixed a potential crash in the socket api: [#8664](https://github.com/owncloud/client/pull/8664) -Details -------- +## Details * Bugfix - Correctly detect network drives: [#8272](https://github.com/owncloud/client/issues/8272) - We fixed a bug which allowed to use Virtual files on Windows network drives, which is not - supported by Windows. + We fixed a bug which allowed to use Virtual files on Windows network drives, + which is not supported by Windows. https://github.com/owncloud/client/issues/8272 @@ -732,21 +3358,19 @@ Details https://github.com/owncloud/client/pull/8664 -Changelog for ownCloud Desktop Client [2.8.1] (2021-05-21) -======================================= +# Changelog for ownCloud Desktop Client [2.8.1] (2021-05-21) + The following sections list the changes in ownCloud Desktop Client 2.8.1 relevant to ownCloud admins and users. [2.8.1]: https://github.com/owncloud/client/compare/v2.8.0...v2.8.1 -Summary -------- +## Summary * Bugfix - We fixed an issue with the assignment of tags: [#8633](https://github.com/owncloud/client/pull/8633/) * Enhancement - Prevent user from setting up a VFS sync to the root of a drive: [#8615](https://github.com/owncloud/client/pull/8615) -Details -------- +## Details * Bugfix - We fixed an issue with the assignment of tags: [#8633](https://github.com/owncloud/client/pull/8633/) @@ -756,43 +3380,42 @@ Details * Enhancement - Prevent user from setting up a VFS sync to the root of a drive: [#8615](https://github.com/owncloud/client/pull/8615) - We now display a warning when a user tries to sync to a drive like D:\ instead of a folder and - prevent this. Previous versions of the client used to crash. + We now display a warning when a user tries to sync to a drive like D:\ instead + of a folder and prevent this. Previous versions of the client used to crash. https://github.com/owncloud/client/pull/8615 -Changelog for ownCloud Desktop Client [2.8.0] (2021-05-06) -======================================= +# Changelog for ownCloud Desktop Client [2.8.0] (2021-05-06) + The following sections list the changes in ownCloud Desktop Client 2.8.0 relevant to ownCloud admins and users. [2.8.0]: https://github.com/owncloud/client/compare/v2.7.6...v2.8.0 -Summary -------- +## Summary * Bugfix - Fix issues with long path: [#4405](https://github.com/owncloud/enterprise/issues/4405) * Bugfix - Moves in folder sync connections where executed as a delete and upload: [#7838](https://github.com/owncloud/client/issues/7838) -* Bugfix - Fix brandings with space in the name: [#8482](https://github.com/owncloud/client/pull/8482) +* Bugfix - Fix branding with space in the name: [#8482](https://github.com/owncloud/client/pull/8482) * Bugfix - We fixed an issue with the promptDeleteAllFiles setting: [#8484](https://github.com/owncloud/client/issues/8484) * Enhancement - Added branding option to enforce VFS: [#4144](https://github.com/owncloud/enterprise/issues/4144) * Enhancement - Add initial support for backups triggered by an external tool: [#8437](https://github.com/owncloud/client/pull/8437) * Enhancement - Add an icon to the Windows system tray notification: [#8539](https://github.com/owncloud/client/pull/8539) * Enhancement - Encode the log file as UTF-8: [#8550](https://github.com/owncloud/client/pull/8550) -Details -------- +## Details * Bugfix - Fix issues with long path: [#4405](https://github.com/owncloud/enterprise/issues/4405) - We fixed an issue introduced in dd641fae997d71c8396b77def2fa25ad96fdf47f with some - functions and files paths > 260 characters. + We fixed an issue introduced in dd641fae997d71c8396b77def2fa25ad96fdf47f with + some functions and files paths > 260 characters. https://github.com/owncloud/enterprise/issues/4405 * Bugfix - Moves in folder sync connections where executed as a delete and upload: [#7838](https://github.com/owncloud/client/issues/7838) - We fixed a bug where moves in folder sync connections where executed as a delete and upload. + We fixed a bug where moves in folder sync connections where executed as a delete + and upload. https://github.com/owncloud/client/issues/7838 https://github.com/owncloud/enterprise/issues/4428 @@ -800,15 +3423,16 @@ Details https://github.com/owncloud/client/pull/8456 https://github.com/owncloud/client/pull/8459 -* Bugfix - Fix brandings with space in the name: [#8482](https://github.com/owncloud/client/pull/8482) +* Bugfix - Fix branding with space in the name: [#8482](https://github.com/owncloud/client/pull/8482) - We fix a build system issue with brandings containing spaces. + We fix a build system issue with branding containing spaces. https://github.com/owncloud/client/pull/8482 * Bugfix - We fixed an issue with the promptDeleteAllFiles setting: [#8484](https://github.com/owncloud/client/issues/8484) - When promptDeleteAllFiles=false is set the client will now correctly delete all files. + When promptDeleteAllFiles=false is set the client will now correctly delete all + files. https://github.com/owncloud/client/issues/8484 @@ -836,26 +3460,24 @@ Details * Enhancement - Encode the log file as UTF-8: [#8550](https://github.com/owncloud/client/pull/8550) - We fixed an issue where the log file might not have been encoded as UTF-8 and thus scrambled file - names. + We fixed an issue where the log file might not have been encoded as UTF-8 and + thus scrambled file names. https://github.com/owncloud/client/pull/8550 -Changelog for ownCloud Desktop Client [2.7.6] (2021-02-04) -======================================= +# Changelog for ownCloud Desktop Client [2.7.6] (2021-02-04) + The following sections list the changes in ownCloud Desktop Client 2.7.6 relevant to ownCloud admins and users. [2.7.6]: https://github.com/owncloud/client/compare/v2.7.5...v2.7.6 -Summary -------- +## Summary * Bugfix - Fix missing sidebar icons on Mac with branded builds: [#4387](https://github.com/owncloud/enterprise/issues/4387) -* Bugfix - Case sensitive comparison of checksum algorithm: [#8371](https://github.com/owncloud/client/pull/8371) +* Bugfix - Case-sensitive comparison of checksum algorithm: [#8371](https://github.com/owncloud/client/pull/8371) -Details -------- +## Details * Bugfix - Fix missing sidebar icons on Mac with branded builds: [#4387](https://github.com/owncloud/enterprise/issues/4387) @@ -863,23 +3485,22 @@ Details https://github.com/owncloud/enterprise/issues/4387 -* Bugfix - Case sensitive comparison of checksum algorithm: [#8371](https://github.com/owncloud/client/pull/8371) +* Bugfix - Case-sensitive comparison of checksum algorithm: [#8371](https://github.com/owncloud/client/pull/8371) - We fixed a bug where the checksum detection was case sensitive and used a different casing than - the server. + We fixed a bug where the checksum detection was case-sensitive and used a + different casing than the server. https://github.com/owncloud/client/pull/8371 https://github.com/owncloud/client/pull/8376 -Changelog for ownCloud Desktop Client [2.7.5] (2021-01-28) -======================================= +# Changelog for ownCloud Desktop Client [2.7.5] (2021-01-28) + The following sections list the changes in ownCloud Desktop Client 2.7.5 relevant to ownCloud admins and users. [2.7.5]: https://github.com/owncloud/client/compare/v2.7.4...v2.7.5 -Summary -------- +## Summary * Bugfix - Support dark theme with branded client and vanilla icons: [#4363](https://github.com/owncloud/enterprise/issues/4363) * Bugfix - Mac tray icon is scaled wrong: [#8149](https://github.com/owncloud/client/issues/8149) @@ -889,8 +3510,7 @@ Summary * Bugfix - Fix wrong option provided to OIDC: [#8390](https://github.com/owncloud/client/issues/8390) * Change - Third party upgrades in distributed binaries: [#8349](https://github.com/owncloud/client/issues/8349) -Details -------- +## Details * Bugfix - Support dark theme with branded client and vanilla icons: [#4363](https://github.com/owncloud/enterprise/issues/4363) @@ -900,26 +3520,29 @@ Details * Bugfix - Mac tray icon is scaled wrong: [#8149](https://github.com/owncloud/client/issues/8149) - We backported a change to Qt 5.12.10 which fixed the scaling of the system tray icon on Big Sur. + We backported a change to Qt 5.12.10 which fixed the scaling of the system tray + icon on Big Sur. https://github.com/owncloud/client/issues/8149 * Bugfix - Fix potential crashes with the remove all dialog: [#8314](https://github.com/owncloud/client/pull/8314) - We fixed a bug a dialog window belonging to a removed account could still be visible. User action - on that dialog would then cause a crash. + We fixed a bug a dialog window belonging to a removed account could still be + visible. User action on that dialog would then cause a crash. https://github.com/owncloud/client/pull/8314 * Bugfix - Detect file name clash with VirtualFiles enabled: [#8323](https://github.com/owncloud/client/issues/8323) - We fixed an issue where the file name clash detection was not run with VirtualFiles enabled. + We fixed an issue where the file name clash detection was not run with + VirtualFiles enabled. https://github.com/owncloud/client/issues/8323 * Bugfix - Remove stray placeholders: [#8326](https://github.com/owncloud/client/issues/8326) - We fixed a bug where Windows Virtual Files where not handled as such and thus not removed. + We fixed a bug where Windows Virtual Files where not handled as such and thus + not removed. https://github.com/owncloud/client/issues/8326 @@ -931,39 +3554,38 @@ Details * Change - Third party upgrades in distributed binaries: [#8349](https://github.com/owncloud/client/issues/8349) - We updated Qt from 5.12.9 to 5.12.10. We updated OpenSSL from 1.1.1g to 1.1.1i. Linux - dependencies: QtKeychain was updated from 0.10.0 to 0.12.0 + We updated Qt from 5.12.9 to 5.12.10. We updated OpenSSL from 1.1.1g to 1.1.1i. + Linux dependencies: QtKeychain was updated from 0.10.0 to 0.12.0 https://github.com/owncloud/client/issues/8349 -Changelog for ownCloud Desktop Client [2.7.4] (2020-12-21) -======================================= +# Changelog for ownCloud Desktop Client [2.7.4] (2020-12-21) + The following sections list the changes in ownCloud Desktop Client 2.7.4 relevant to ownCloud admins and users. [2.7.4]: https://github.com/owncloud/client/compare/v2.7.3...v2.7.4 -Summary -------- +## Summary * Bugfix - Fix crash when a download is cancelled: [#4329](https://github.com/owncloud/enterprise/issues/4329) * Bugfix - Update Windows launch on start entry: [#7672](https://github.com/owncloud/client/issues/7672) * Bugfix - Log the final http request: [#8289](https://github.com/owncloud/client/pull/8289) * Bugfix - Properly display parent Window when displaying a dialog: [#8313](https://github.com/owncloud/client/issues/8313) -Details -------- +## Details * Bugfix - Fix crash when a download is cancelled: [#4329](https://github.com/owncloud/enterprise/issues/4329) - We fixed a crash, on Windows, when a user cancelled a download of a VirtualFile using the Windows - explorer. + We fixed a crash, on Windows, when a user cancelled a download of a VirtualFile + using the Windows explorer. https://github.com/owncloud/enterprise/issues/4329 * Bugfix - Update Windows launch on start entry: [#7672](https://github.com/owncloud/client/issues/7672) - We fixed a bug where launch on start did not work after a re install to a new location. + We fixed a bug where launch on start did not work after a re-install to a new + location. https://github.com/owncloud/client/issues/7672 @@ -979,46 +3601,42 @@ Details https://github.com/owncloud/client/issues/8313 -Changelog for ownCloud Desktop Client [2.7.3] (2020-12-11) -======================================= +# Changelog for ownCloud Desktop Client [2.7.3] (2020-12-11) + The following sections list the changes in ownCloud Desktop Client 2.7.3 relevant to ownCloud admins and users. [2.7.3]: https://github.com/owncloud/client/compare/v2.7.2...v2.7.3 -Summary -------- +## Summary * Bugfix - Fix handling of errors with the Windows Cloud Filter API: [#8294](https://github.com/owncloud/client/issues/8294) -Details -------- +## Details * Bugfix - Fix handling of errors with the Windows Cloud Filter API: [#8294](https://github.com/owncloud/client/issues/8294) - We fixed a bug where errors during the creation of placeholder files where not correctly - handled. The missing files where than falsely detected as deleted and thus removed from the - server. + We fixed a bug where errors during the creation of placeholder files where not + correctly handled. The missing files where than falsely detected as deleted and + thus removed from the server. https://github.com/owncloud/client/issues/8294 -Changelog for ownCloud Desktop Client [2.7.2] (2020-12-02) -======================================= +# Changelog for ownCloud Desktop Client [2.7.2] (2020-12-02) + The following sections list the changes in ownCloud Desktop Client 2.7.2 relevant to ownCloud admins and users. [2.7.2]: https://github.com/owncloud/client/compare/v2.7.1...v2.7.2 -Summary -------- +## Summary * Bugfix - Correctly detect Windows 10 taskbar theme: [#8244](https://github.com/owncloud/client/issues/8244) * Bugfix - Sync small plaintext files with Windows VFS: [#8248](https://github.com/owncloud/client/issues/8248) * Bugfix - Update "Sync hidden files" button: [#8258](https://github.com/owncloud/client/issues/8258) * Bugfix - Pause sync when displaying remove all dialog: [#8263](https://github.com/owncloud/client/issues/8263) -Details -------- +## Details * Bugfix - Correctly detect Windows 10 taskbar theme: [#8244](https://github.com/owncloud/client/issues/8244) @@ -1028,62 +3646,62 @@ Details * Bugfix - Sync small plaintext files with Windows VFS: [#8248](https://github.com/owncloud/client/issues/8248) - We fixed a bug where small plaintext files where not synced due to a broken interity check. + We fixed a bug where small plaintext files where not synced due to a broken + integrity check. https://github.com/owncloud/client/issues/8248 * Bugfix - Update "Sync hidden files" button: [#8258](https://github.com/owncloud/client/issues/8258) - We fixed a bug that prevented the "Sync hidden files" from displaying the correct value. + We fixed a bug that prevented the "Sync hidden files" from displaying the + correct value. https://github.com/owncloud/client/issues/8258 * Bugfix - Pause sync when displaying remove all dialog: [#8263](https://github.com/owncloud/client/issues/8263) - We now pause the syn process when the all files where removed dialog is displayed. This prevents - multiple dialogs from being displayed. + We now pause the syn process when the all files where removed dialog is + displayed. This prevents multiple dialogs from being displayed. https://github.com/owncloud/client/issues/8263 -Changelog for ownCloud Desktop Client [2.7.1] (2020-11-18) -======================================= +# Changelog for ownCloud Desktop Client [2.7.1] (2020-11-18) + The following sections list the changes in ownCloud Desktop Client 2.7.1 relevant to ownCloud admins and users. [2.7.1]: https://github.com/owncloud/client/compare/v2.7.0...v2.7.1 -Summary -------- +## Summary * Bugfix - Fix client forgetting VirtualFiles mode: [#8229](https://github.com/owncloud/client/pull/8229) * Bugfix - Don't follow redirects on .well-known/openid-configuration: [#8232](https://github.com/owncloud/client/pull/8232) -Details -------- +## Details * Bugfix - Fix client forgetting VirtualFiles mode: [#8229](https://github.com/owncloud/client/pull/8229) - We fixed a migration issue where 2.5 based settings where the client was forgetting the - VirtualFiles settings. + We fixed a migration issue where 2.5 based settings where the client was + forgetting the VirtualFiles settings. https://github.com/owncloud/client/pull/8229 * Bugfix - Don't follow redirects on .well-known/openid-configuration: [#8232](https://github.com/owncloud/client/pull/8232) - We fixed a bug where the client followed redirects for .well-known/openid-configuration. + We fixed a bug where the client followed redirects for + .well-known/openid-configuration. https://github.com/owncloud/openidconnect/issues/20 https://github.com/owncloud/client/pull/8232 -Changelog for ownCloud Desktop Client [2.7.0] (2020-11-13) -======================================= +# Changelog for ownCloud Desktop Client [2.7.0] (2020-11-13) + The following sections list the changes in ownCloud Desktop Client 2.7.0 relevant to ownCloud admins and users. [2.7.0]: https://github.com/owncloud/client/compare/v2.6.3...v2.7.0 -Summary -------- +## Summary * Bugfix - Legacy config migration reverted files to online only: [#7779](https://github.com/owncloud/client/issues/7779) * Bugfix - Some check boxes in the sharing dialog are disabled: [#7877](https://github.com/owncloud/client/issues/7877) @@ -1097,20 +3715,22 @@ Summary * Bugfix - Check whether the local folder supports the virtual file system: [#8131](https://github.com/owncloud/client/issues/8131) * Bugfix - "All Files removed" dialog no longer blocks the application: [#8170](https://github.com/owncloud/client/issues/8170) * Bugfix - We fixed the permissions check for local folders on NTFS: [#8187](https://github.com/owncloud/client/issues/8187) +* Change - Add support for the TUS resume-able upload protocol: [#19](https://github.com/owncloud/product/issues/19) * Change - Don't hardcode a plugin location: [#3839](https://github.com/owncloud/enterprise/issues/3839) * Change - Detect the Windows 10 theme for the system tray: [#7356](https://github.com/owncloud/client/issues/7356) +* Change - Support for OpenID Connect: [#7509](https://github.com/owncloud/client/pull/7509) * Change - When manually adding a folder sync connection, don't display 404 errors: [#7724](https://github.com/owncloud/client/issues/7724) * Change - Enabling native VFS on Windows create two shortcuts in the Explorer: [#7748](https://github.com/owncloud/client/issues/7748) * Change - Redesign the About dialog: [#7749](https://github.com/owncloud/client/issues/7749) * Change - The password dialog is hidden behind a window: [#7833](https://github.com/owncloud/client/issues/7833) -* Change - The client uploads chunks even though the server repports lack of support: [#7862](https://github.com/owncloud/client/issues/7862) +* Change - The client uploads chunks even though the server reports lack of support: [#7862](https://github.com/owncloud/client/issues/7862) * Change - Mac does not display a dock entry: [#7868](https://github.com/owncloud/client/issues/7868) * Change - Option to log HTTP requests and responses: [#7873](https://github.com/owncloud/client/issues/7873) * Change - Add button for `Log Settings` to advanced settings: [#7881](https://github.com/owncloud/client/issues/7881) -* Change - The layout of serveral ui elements is broken: [#7920](https://github.com/owncloud/client/issues/7920) +* Change - The layout of several ui elements is broken: [#7920](https://github.com/owncloud/client/issues/7920) * Change - The client hides the window during the authentication process: [#7922](https://github.com/owncloud/client/pull/7922) * Change - The settings ui shows a prompt for a few seconds: [#7925](https://github.com/owncloud/client/pull/7925) -* Change - Reorgenize Settings: [#7962](https://github.com/owncloud/client/pull/7962) +* Change - Reorganize Settings: [#7962](https://github.com/owncloud/client/pull/7962) * Change - Use the checksum type specified by the server by default: [#7989](https://github.com/owncloud/client/pull/7989) * Change - Move "Choose what to sync" to sync everything settings: [#8018](https://github.com/owncloud/client/pull/8018) * Change - Enable Windows Virtual files by default: [#8019](https://github.com/owncloud/client/pull/8019) @@ -1121,36 +3741,34 @@ Summary * Change - Account display name `servername (username)` to `username@servername`: [#8104](https://github.com/owncloud/client/issues/8104) * Change - Remove update channels from the ownCloud client: [#8127](https://github.com/owncloud/client/issues/8127) * Change - Display the users avatar in the activity list: [#8169](https://github.com/owncloud/client/issues/8169) -* Change - Support for OpenID Connect: [#7509](https://github.com/owncloud/client/pull/7509) -* Change - Add support for the TUS resumeable upload protocol: [#19](https://github.com/owncloud/product/issues/19) -Details -------- +## Details * Bugfix - Legacy config migration reverted files to online only: [#7779](https://github.com/owncloud/client/issues/7779) - We fixed a legacy config migration which reverted all files to online only on every start. + We fixed a legacy config migration which reverted all files to online only on + every start. https://github.com/owncloud/client/issues/7779 * Bugfix - Some check boxes in the sharing dialog are disabled: [#7877](https://github.com/owncloud/client/issues/7877) - We fixed a bug where the servers default sharing permissions where used as limiting factor - instead of a defualt selection. + We fixed a bug where the servers default sharing permissions where used as + limiting factor instead of a default selection. https://github.com/owncloud/client/issues/7877 * Bugfix - Selective sync dialog is displayed when virtual files are enabled: [#7976](https://github.com/owncloud/client/issues/7976) - We hide that dialog now so that it is no longer possible to remove files from synchronisation - when virtual files are enabled. + We hide that dialog now so that it is no longer possible to remove files from + synchronisation when virtual files are enabled. https://github.com/owncloud/client/issues/7976 * Bugfix - Fix support of adler32 checksums with Windows virtual files support: [#7999](https://github.com/owncloud/client/issues/7999) - The validation device reported a size of 0 and thus the computations of the checksums was - aborted. + The validation device reported a size of 0 and thus the computations of the + checksums was aborted. https://github.com/owncloud/client/issues/7999 https://github.com/owncloud/client/pull/8015 @@ -1169,28 +3787,29 @@ Details * Bugfix - The back button on the advanced wizard page no longer gets enabled: [#8051](https://github.com/owncloud/client/issues/8051) - We fixed a bug where the back button in the advanced wizard page get re enabled. + We fixed a bug where the back button in the advanced wizard page get re-enabled. https://github.com/owncloud/client/issues/8051 * Bugfix - Ensure permissions for newly added files are available: [#8066](https://github.com/owncloud/client/issues/8066) - We fixed a bug where newly added files had no server permissions set. Under certain conditions - that was leading to an undefined behaviour. + We fixed a bug where newly added files had no server permissions set. Under + certain conditions that was leading to an undefined behaviour. https://github.com/owncloud/client/issues/8066 https://github.com/owncloud/client/issues/7967 * Bugfix - Remove notifications when the account they belong to was removed: [#8085](https://github.com/owncloud/client/issues/8085) - We fixed a bug where notifications where still displayed after an account was removed. + We fixed a bug where notifications where still displayed after an account was + removed. https://github.com/owncloud/client/issues/8085 * Bugfix - Check whether the local folder supports the virtual file system: [#8131](https://github.com/owncloud/client/issues/8131) - The Windows virtual file system requires NTFS, we now ensure that the folder is using NTFS - before we continue. + The Windows virtual file system requires NTFS, we now ensure that the folder is + using NTFS before we continue. https://github.com/owncloud/client/issues/8131 @@ -1202,15 +3821,23 @@ Details * Bugfix - We fixed the permissions check for local folders on NTFS: [#8187](https://github.com/owncloud/client/issues/8187) - We fixed a bug where the check whether the local folder is writeable returned a wrong result. - This could cause a crash with the virtual file system plugin. + We fixed a bug where the check whether the local folder is writeable returned a + wrong result. This could cause a crash with the virtual file system plugin. https://github.com/owncloud/client/issues/8187 +* Change - Add support for the TUS resume-able upload protocol: [#19](https://github.com/owncloud/product/issues/19) + + With the support of the TUS protocol we are now able to easily and reliably + upload files to oCIS. + + https://github.com/owncloud/product/issues/19 + * Change - Don't hardcode a plugin location: [#3839](https://github.com/owncloud/enterprise/issues/3839) - We no longer hardcode a plugin location only available on the build system. If a setup uses a non - default plugin location, please consider setting the environment variable QT_PLUGIN_PATH. + We no longer hardcode a plugin location only available on the build system. If a + setup uses a non default plugin location, please consider setting the + environment variable QT_PLUGIN_PATH. https://github.com/owncloud/enterprise/issues/3839 @@ -1220,10 +3847,30 @@ Details https://github.com/owncloud/client/issues/7356 +* Change - Support for OpenID Connect: [#7509](https://github.com/owncloud/client/pull/7509) + + The ownCloud desktop client now supports OpenID Connect. OpenID Connect 1.0 is a + simple identity layer on top of the OAuth 2.0 protocol. It allows clients to + verify the identity of the end-user based on the authentication performed by an + authorization server, as well as to obtain basic profile information about the + end-user in an interoperable and REST-like manner. + + OpenID Connect allows clients of all types, including web-based, mobile, and + JavaScript clients, to request and receive information about authenticated + sessions and end-users. The specification suite is extensible, allowing + participants to use optional features such as encryption of identity data, + discovery of OpenID providers, and session management, when it makes sense for + them. + + See http://openid.net/connect/faq/ for a set of answers to frequently asked + questions about OpenID Connect. + + https://github.com/owncloud/client/pull/7509 + * Change - When manually adding a folder sync connection, don't display 404 errors: [#7724](https://github.com/owncloud/client/issues/7724) - We no longer display 404 errors when exploring the folders. A user might not have access to the - full file tree on the server. + We no longer display 404 errors when exploring the folders. A user might not + have access to the full file tree on the server. https://github.com/owncloud/client/issues/7724 @@ -1235,8 +3882,8 @@ Details * Change - Redesign the About dialog: [#7749](https://github.com/owncloud/client/issues/7749) - We redesigned the way the About information is displayed and unified it with the "--version" - switch. + We redesigned the way the About information is displayed and unified it with the + "--version" switch. https://github.com/owncloud/client/issues/7749 https://github.com/owncloud/enterprise/issues/3787 @@ -1248,15 +3895,16 @@ Details https://github.com/owncloud/client/issues/7833 -* Change - The client uploads chunks even though the server repports lack of support: [#7862](https://github.com/owncloud/client/issues/7862) +* Change - The client uploads chunks even though the server reports lack of support: [#7862](https://github.com/owncloud/client/issues/7862) - We now correctly handle the bigfilechunking capability + We now correctly handle the `bigfilechunking` capability https://github.com/owncloud/client/issues/7862 * Change - Mac does not display a dock entry: [#7868](https://github.com/owncloud/client/issues/7868) - We changed the behaviour of the client to display a dock entry when we have a window open. + We changed the behaviour of the client to display a dock entry when we have a + window open. https://github.com/owncloud/client/issues/7868 @@ -1272,7 +3920,7 @@ Details https://github.com/owncloud/client/issues/7881 -* Change - The layout of serveral ui elements is broken: [#7920](https://github.com/owncloud/client/issues/7920) +* Change - The layout of several ui elements is broken: [#7920](https://github.com/owncloud/client/issues/7920) We replace an old layout mechanism with a more advanced one. @@ -1281,8 +3929,8 @@ Details * Change - The client hides the window during the authentication process: [#7922](https://github.com/owncloud/client/pull/7922) - We changed the confusing behavioir and now minimize ownCloud instead. This ensures that the - window stays accessible. + We changed the confusing behaviour and now minimize ownCloud instead. This + ensures that the window stays accessible. https://github.com/owncloud/client/pull/7922 @@ -1292,7 +3940,7 @@ Details https://github.com/owncloud/client/pull/7925 -* Change - Reorgenize Settings: [#7962](https://github.com/owncloud/client/pull/7962) +* Change - Reorganize Settings: [#7962](https://github.com/owncloud/client/pull/7962) We rename "General" to "Settings" and move the "Network" into "Settings" @@ -1300,15 +3948,17 @@ Details * Change - Use the checksum type specified by the server by default: [#7989](https://github.com/owncloud/client/pull/7989) - The default type for computation of the checksum was sha1 independent of the type specified by - the server. Under certain conditions that caused multiple computations of the checksum. + The default type for computation of the checksum was sha1 independent of the + type specified by the server. Under certain conditions that caused multiple + computations of the checksum. https://github.com/owncloud/client/pull/7989 * Change - Move "Choose what to sync" to sync everything settings: [#8018](https://github.com/owncloud/client/pull/8018) - While selective sync is a feature only available when everything is synced, the the option had - its own radio button. We now moved the button to the other sync everything related settings. + While selective sync is a feature only available when everything is synced, the + option had its own radio button. We now moved the button to the other sync + everything related settings. https://github.com/owncloud/client/pull/8018 @@ -1321,28 +3971,30 @@ Details * Change - Move sync hidden files to advanced settings: [#8020](https://github.com/owncloud/client/issues/8020) - We moved the option to sync hidden files from the "Edit ignored Files" dialog into the advanced - settings. + We moved the option to sync hidden files from the "Edit ignored Files" dialog + into the advanced settings. https://github.com/owncloud/client/issues/8020 * Change - Replace the old icons with a unified look: [#8038](https://github.com/owncloud/client/pull/8038) - We replaced the different styles and colors of the icons with a new unified look. + We replaced the different styles and colors of the icons with a new unified + look. https://github.com/owncloud/client/pull/8038 * Change - Sharing dialog is now always on top of the settings dialog: [#8050](https://github.com/owncloud/client/pull/8050) - The sharing dialog is now a sub dialog, so it will be easier to continue sharing a folder. + The sharing dialog is now a sub dialog, so it will be easier to continue sharing + a folder. https://github.com/owncloud/client/pull/8050 * Change - Remove the branding option `wizardSelectiveSyncDefaultNothing`: [#8064](https://github.com/owncloud/client/pull/8064) - The branding option was removed as believe that it did not provide a good user experience. We - recommend `newBigFolderSizeLimit` together with `wizardHideFolderSizeLimitCheckbox` - as a replacement. + The branding option was removed as believe that it did not provide a good user + experience. We recommend `newBigFolderSizeLimit` together with + `wizardHideFolderSizeLimitCheckbox` as a replacement. https://github.com/owncloud/client/pull/8064 @@ -1354,11 +4006,12 @@ Details * Change - Remove update channels from the ownCloud client: [#8127](https://github.com/owncloud/client/issues/8127) - To ensure a maximum of stability user should not replace their productive client with a preview - build. For that exact reason we offer tespilotcloud clients, they can be installed in parallel - and updated via the beta channel. + To ensure a maximum of stability user should not replace their productive client + with a preview build. For that exact reason we offer testpilotcloud clients, + they can be installed in parallel and updated via the beta channel. - If a user is more adventures than the average a manual install of a preview is always possible. + If a user is more adventurous than the average a manual installation of a + preview is always possible. https://github.com/owncloud/client/issues/8127 @@ -1368,40 +4021,14 @@ Details https://github.com/owncloud/client/issues/8169 -* Change - Support for OpenID Connect: [#7509](https://github.com/owncloud/client/pull/7509) - - The ownCloud desktop client now supports OpenID Connect. OpenID Connect 1.0 is a simple - identity layer on top of the OAuth 2.0 protocol. It allows clients to verify the identity of the - end-user based on the authentication performed by an authorization server, as well as to - obtain basic profile information about the end-user in an interoperable and REST-like - manner. - - OpenID Connect allows clients of all types, including web-based, mobile, and JavaScript - clients, to request and receive information about authenticated sessions and end-users. The - specification suite is extensible, allowing participants to use optional features such as - encryption of identity data, discovery of OpenID providers, and session management, when it - makes sense for them. +# Changelog for ownCloud Desktop Client [2.6.3] (2020-06-10) - See http://openid.net/connect/faq/ for a set of answers to frequently asked questions about - OpenID Connect. - - https://github.com/owncloud/client/pull/7509 - -* Change - Add support for the TUS resumeable upload protocol: [#19](https://github.com/owncloud/product/issues/19) - - With the support of the TUS protocol we are now able to easily and reliably upload files to ocis. - - https://github.com/owncloud/product/issues/19 - -Changelog for ownCloud Desktop Client [2.6.3] (2020-06-10) -======================================= The following sections list the changes in ownCloud Desktop Client 2.6.3 relevant to ownCloud admins and users. [2.6.3]: https://github.com/owncloud/client/compare/v2.6.2...v2.6.3 -Summary -------- +## Summary * Bugfix - Client sometimes does not show up when started by a user: [#7018](https://github.com/owncloud/client/issues/7018) * Bugfix - Fix several wrong colored icons in dark mode: [#7043](https://github.com/owncloud/client/issues/7043) @@ -1415,7 +4042,7 @@ Summary * Bugfix - Add Folder Sync Connection sometimes does not warn: [#7741](https://github.com/owncloud/client/issues/7741) * Bugfix - Resize the buttons in the settings view dynamically: [#7744](https://github.com/owncloud/client/issues/7744) * Bugfix - Fix status icon after move errors: [#7759](https://github.com/owncloud/client/issues/7759) -* Bugfix - Fixed a potential crash on cancelation of discovery jobs: [#7760](https://github.com/owncloud/client/pull/7760) +* Bugfix - Fixed a potential crash on cancellation of discovery jobs: [#7760](https://github.com/owncloud/client/pull/7760) * Bugfix - Fix a potential crash on Windows VFS: [#7761](https://github.com/owncloud/client/issues/7761) * Bugfix - Fixed OAuth2 login of user with `+`: [#7762](https://github.com/owncloud/client/issues/7762) * Bugfix - On Windows the share dialog does not open as the top most window: [#7774](https://github.com/owncloud/client/issues/7774) @@ -1426,38 +4053,40 @@ Summary * Change - Restart the client after an update: [#3922](https://github.com/owncloud/enterprise/issues/3922) * Change - Wizard is hidden behind the browser: [#7856](https://github.com/owncloud/client/issues/7856) -Details -------- +## Details * Bugfix - Client sometimes does not show up when started by a user: [#7018](https://github.com/owncloud/client/issues/7018) - We fixed a bug where a client sometimes does not show up when a user request a start. + We fixed a bug where a client sometimes does not show up when a user request a + start. https://github.com/owncloud/client/issues/7018 * Bugfix - Fix several wrong colored icons in dark mode: [#7043](https://github.com/owncloud/client/issues/7043) - We fixed multiple issues where monochrome icons where not converted to match the current - theme. + We fixed multiple issues where monochrome icons where not converted to match the + current theme. https://github.com/owncloud/client/issues/7043 * Bugfix - Fixed bug in public link with password required: [#7247](https://github.com/owncloud/client/issues/7247) - In the sharing dialog, "password required" capabilities lead to incorrect behaviour + In the sharing dialog, "password required" capabilities lead to incorrect + behaviour https://github.com/owncloud/client/issues/7247 * Bugfix - Fixed editing public link expiration date: [#7313](https://github.com/owncloud/client/issues/7313) - In the sharing dialog, allow editing public link expiration date when it is enforced + In the sharing dialog, allow editing public link expiration date when it is + enforced https://github.com/owncloud/client/issues/7313 * Bugfix - Expand file tree also when no folders are synced: [#7336](https://github.com/owncloud/client/issues/7336) - We have fixed the behaviour of the folder Widget, when a account was added and none of its folders - was selected to be synced, the tree was not expanded. + We have fixed the behaviour of the folder Widget, when an account was added and + none of its folders was selected to be synced, the tree was not expanded. https://github.com/owncloud/client/issues/7336 @@ -1469,15 +4098,17 @@ Details * Bugfix - Fixed crash in the setup wizard: [#7709](https://github.com/owncloud/client/issues/7709) - Fixe crash if setup wizard is closed while the virtual file system dialog is open + Fixe crash if setup wizard is closed while the virtual file system dialog is + open https://github.com/owncloud/client/issues/7709 https://github.com/owncloud/client/issues/7711 * Bugfix - Fixed bug in the Virtual File warning dialog: [#7710](https://github.com/owncloud/client/issues/7710) - Don't show virtual file system warning again when the radio button is triggered a second time. - Declining the dialog had no effect as the radio button was already checked + Don't show virtual file system warning again when the radio button is triggered + a second time. Declining the dialog had no effect as the radio button was + already checked https://github.com/owncloud/client/issues/7710 https://github.com/owncloud/client/issues/7712 @@ -1488,15 +4119,15 @@ Details * Bugfix - Add Folder Sync Connection sometimes does not warn: [#7741](https://github.com/owncloud/client/issues/7741) - Warn when adding a folder sync connection of a remote folder for which a subdir is already - synchronized + Warn when adding a folder sync connection of a remote folder for which a subdir + is already synchronized https://github.com/owncloud/client/issues/7741 * Bugfix - Resize the buttons in the settings view dynamically: [#7744](https://github.com/owncloud/client/issues/7744) - Since Qt 5.12 the button text gets elided automatically if the button text would exceed the - button width. + Since Qt 5.12 the button text gets elided automatically if the button text would + exceed the button width. https://github.com/owncloud/client/issues/7744 @@ -1506,27 +4137,27 @@ Details https://github.com/owncloud/client/issues/7759 -* Bugfix - Fixed a potential crash on cancelation of discovery jobs: [#7760](https://github.com/owncloud/client/pull/7760) +* Bugfix - Fixed a potential crash on cancellation of discovery jobs: [#7760](https://github.com/owncloud/client/pull/7760) https://github.com/owncloud/client/pull/7760 * Bugfix - Fix a potential crash on Windows VFS: [#7761](https://github.com/owncloud/client/issues/7761) - We've fixed a potential crash where marking a file as "Always keep on this device" caused the - client to crash. + We've fixed a potential crash where marking a file as "Always keep on this + device" caused the client to crash. https://github.com/owncloud/client/issues/7761 * Bugfix - Fixed OAuth2 login of user with `+`: [#7762](https://github.com/owncloud/client/issues/7762) - Make sure that the `+` in the user name is properly encoded in the URL opened by the browser when - doing an OAuth2 authentication. + Make sure that the `+` in the user name is properly encoded in the URL opened by + the browser when doing an OAuth2 authentication. https://github.com/owncloud/client/issues/7762 * Bugfix - On Windows the share dialog does not open as the top most window: [#7774](https://github.com/owncloud/client/issues/7774) - We now ensure that the our dialogs are correctly raised. + We now ensure that our dialogs are correctly raised. https://github.com/owncloud/client/issues/7774 @@ -1538,8 +4169,8 @@ Details * Bugfix - Don't override cookies with old values: [#7831](https://github.com/owncloud/client/pull/7831) - We fixed a bug where a client somteimes overrode the content of the cookie jar with outdated or - corrupted values + We fixed a bug where a client sometimes overrode the content of the cookie jar + with outdated or corrupted values https://github.com/owncloud/client/pull/7831 @@ -1557,7 +4188,8 @@ Details * Change - Restart the client after an update: [#3922](https://github.com/owncloud/enterprise/issues/3922) - We now start the client after an update, if the client was running before the update. + We now start the client after an update, if the client was running before the + update. https://github.com/owncloud/enterprise/issues/3922 @@ -1567,27 +4199,24 @@ Details https://github.com/owncloud/client/issues/7856 -Changelog for ownCloud Desktop Client [2.6.2] (2020-02-21) -======================================= +# Changelog for ownCloud Desktop Client [2.6.2] (2020-02-21) + The following sections list the changes in ownCloud Desktop Client 2.6.2 relevant to ownCloud admins and users. [2.6.2]: https://github.com/owncloud/client/compare/v2.6.1...v2.6.2 -Summary -------- +## Summary * Change - Add branding option to disable experimental features: [#7755](https://github.com/owncloud/client/issues/7755) -Details -------- +## Details * Change - Add branding option to disable experimental features: [#7755](https://github.com/owncloud/client/issues/7755) https://github.com/owncloud/client/issues/7755 -ChangeLog -========= +# Changelog for 2.6.1 versions and below version 2.6.1 (2020-01-17) (last updated on de0d330c002436454f3fe4929bd707e5f0425949) @@ -1682,7 +4311,7 @@ Some small changes and bug fixes: * GUI: Improve help text in ignore editor (#7162) * GUI: Show restoration items in protocol * Sharing: Fix issues with enforced passwords and expiry (#7246, #7245) -* Sharing: Fix resharing an item in a share with limited permissions (#7275) +* Sharing: Fix re-sharing an item in a share with limited permissions (#7275) * Sharing: Use the default expiration date even when not enforced (#7256) * Sharing: When sharing from context menu, show dialog if share creation fails (#7286) * Sharing: Always show at least readonly permissions (#7429) @@ -1695,7 +4324,7 @@ Some small changes and bug fixes: * Updater: Set correct state on network error (#3933) * Updater: Provide more useful options on update failure (#7217) * Updater: Improve logging (#3475, #7388) -* Updater: Fix Version numer not shown in the user visible string (#7288) +* Updater: Fix Version number not shown in the user visible string (#7288) * DB: Database path for new folders now starts with ".sync_", avoiding the "._" (#5904) * File hashes: Add support for SHA256 and SHA3 * Cmd: Respect chunk sizing environment variables (#7078) @@ -1705,7 +4334,7 @@ Some small changes and bug fixes: * Log: Print critical and fatal messages to stderr * Doc: Migrate the documentation to Antora (#6785) * Doc: Update Windows build instructions -* Doc: Add explanation of how to manually change server url (#6579) +* Doc: Add explanation of how to manually change server URL (#6579) * Doc: List more environment variables * Doc: List more config file options (owncloud/docs#1365) * Build: Fix KDEInstallDirs deprecation warnings (#6922) @@ -1785,7 +4414,7 @@ version 2.5.1 (2018-11-09) * macOS: Improve macdeployqt.py * Discovery: Include path in error message (#6826) * Database: Allow downgrade from 2.6 -* Migration from 2.4: fallback to move file by file if directory move failled (#6807) +* Migration from 2.4: fallback to move file by file if directory move failed (#6807) * owncloudcmd: Read server version and dav user id from the server (#6830) version 2.5.0 (2018-09-18) @@ -1850,12 +4479,12 @@ version 2.5.0 (2018-09-18) * Sync: When detecting a local move, keep the local mtime (#6629) * Sync: Better error handling for local directory parsing (#6610) * Sync: Error if properties are missing (#6317) -* Sync: Recover when the PUT reply (or chunkin's MOVE) is lost (#5106) +* Sync: Recover when the PUT reply (or chunking MOVE) is lost (#5106) * Sync: Do not abort a sync if the server closes the connection (#6516) * Sync: Increase the timeout for the last MOVE/PUT for huge files (#6527) * Sync: Fix renames making hierarchy inversion (#6694) * Sync: RemotePermissions: Fix empty vs null (#4608) -* Sync: Fix the "direction" of the "all file delted" message when the server is reset (#6317) +* Sync: Fix the "direction" of the "all file deleted" message when the server is reset (#6317) * Data-Fingerprint: Fix backup detection when fingerprint is empty * propagateuploadv1: Fixed an assert with ownCloud 5 * Download: Use the from the reply in the error message (#6459, #6459) @@ -2058,9 +4687,9 @@ version 2.3.3 (2017-08-29) * Overlay Icons: Fix potential hangs on Windows * SyncJournalDB: Don't use ._ as filename pattern if that does not work because of SMB storage settings (#5844) * SyncJournalDB: Log reason for sqlite3 opening errors -* Notifications: Proapgate "Dismiss" button action to server (#5922) +* Notifications: Propagate "Dismiss" button action to server (#5922) * Switch Linux build also to Qt 5.6.2 (#5470) -* Stopped maintaining Qt 4 buildability +* Stopped maintaining Qt 4 build ability version 2.3.2 (2017-05-08) * Fix more crashes (thanks to everyone submitting to our crash reporter!) @@ -2082,21 +4711,21 @@ version 2.3.0 (2017-03-03) * Allow to not sync the server's external storages by default * Switch Windows and OS X build to Qt 5.6.2 * Switch to new ownCloud server WebDAV endpoint -* Chunking NG: New file upload chunking algorithmn for ownCloud server 9.2 +* Chunking NG: New file upload chunking algorithm for ownCloud server 9.2 * Allow to sync a folder to multiple different servers (Filename change from .csync_journal.db to _sync_$HASH.db) * Conflicts: Use the local mtime for the conflict file name (#5273) * "Sync now" menu item * SSL Client certificate support improved (Show UI, Store keys in keychain) * Propagator: Upload more small files in parallel * Sync Engine: Read data-fingerprint property to detect backups (#2325) -* GUI: Show link to ceate an app password/token for syncing +* GUI: Show link to create an app password/token for syncing * Share dialog: Add 'Mail link' button * Caja file manager plugin * Make "backup detected" message to not trigger in wrong cases * SyncEngine: Fix renaming of folder when file are changed (#5192) * Fix reconnect bug if status.php intermittently returns wrong data (#5188) * Improve sync scheduling (#5317) -* Overlay icons: Improvements in correctnes +* Overlay icons: Improvements in correctness * Tray menu: Only update on demand to fix Linux desktop integration glitches * Progress: Better time/bandwidth estimations * Network: Follow certain HTTP redirects (#2791) @@ -2170,7 +4799,7 @@ version 2.2.0 (release 2016-05-12) * Improved netrc parser (#4691) * Improved user notifications about ignored files and conflicts (#4761, #3222) * Add warnings for old server versions (#4523) - * Enable tranportation checksums if the server supports based on server capabilities (#3735) + * Enable transportation checksums if the server supports based on server capabilities (#3735) * Default Chunk-size changed to 10MB (#4354) * Documentation Improvements, ie. about overlay icons @@ -2181,7 +4810,7 @@ version 2.2.0 (release 2016-05-12) 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 + * Fix occurrences of "Connection Closed" when a new unauthenticated TCP socket is used * Fix undeliberate WiFi scanning done by Qt Network classes * Several fixes/improvements to the sharing dialog * Several fixes/improvements to the server activity tab @@ -2212,12 +4841,12 @@ 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 + longer provide bandwidth 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) - * For *eml-files don't reupload if size and checksum are unchanged (#3235) + * For *eml-files don't re-upload if size and checksum are unchanged (#3235) * Ensured 403 reply code is handled properly (File Firewall) (#3490) * Reduced number of PROPFIND requests to server(#3964) * GUI: Added Account toolbox widget to keep account actions (#4139) @@ -2225,14 +4854,14 @@ version 2.1 (release 2015-12-03) * FolderMan: Fixed infinite wait on pause (#4093) * Renamed env variables to include unit (#2939) * FolderStatusModel: Attempt to detect removed undecided files (#3612) - * SyncEngine: Don't whipe the white list if the sync was aborted (#4018) + * SyncEngine: Don't wipe the white list if the sync was aborted (#4018) * Quota: Handle special negative value for the quota (#3940) * State app name in update notification (#4020) * PropagateUpload: Fixed double-emission of finished (#3844) * GUI: Ensured folder names which are excluded from sync can be clicked * Shell Integration: Dolphin support, requires KF 5.16 and KDE Application 15.12 * FolderStatusModel: Ensured reset also if a folder was renamed (#4011) - * GUI: Fixed accessiblity of remaing items in full settings toolbar (#3795) + * GUI: Fixed accessibility of remaining items in full settings toolbar (#3795) * Introduced the term "folder sync connection" in more places (#3757) * AccountSettings: Don't disable pause when offline (#4010) * Fixed handling of hidden files (#3980) @@ -2245,7 +4874,7 @@ version 2.1 (release 2015-12-03) * owncloudcmd: Don't do a check that file are older than 2s (#4160) * Fixed getting size for selective sync (#3986) * Re-added close button in the settings window (#3713) - * Added abililty to handle storage limitations gracefully (#3736) + * Added ability to handle storage limitations gracefully (#3736) * Organized patches to our base Qt version into admin/qt/patches * Plus: A lot of unmentioned improvements and fixes @@ -2344,7 +4973,7 @@ version 1.8.2 (release 2015-06-08) * Win32: Allow concurrent OEM (un-)installers (#3272) * Win32: Make Setup/Update Mutex theme-unique (#3272) * HTTP: Add the branding name to the UserAgent string - * ConnectonValidator: Always run with new credentials (#3266) + * ConnectionValidator: Always run with new credentials (#3266) * Recall Feature: Admins can trigger an upload of a file from client to server again (#3246) * Propagator: Add 'Content-Length: 0' header to MKCOL request (#3256) @@ -2353,7 +4982,7 @@ version 1.8.2 (release 2015-06-08) * Fix opening external links for some labels (#3135) * AccountState: Run only a single validator, allow error message overriding (#3236, #3153) - * SyncJournalDB: Minor fixes and simplificatons + * SyncJournalDB: Minor fixes and simplifications * SyncEngine: Force re-read of folder Etags for upgrades from 1.8.0 and 1.8.1 * Propagator: Limit length of temporary file name (#2789) @@ -2417,7 +5046,7 @@ version 1.8.1 (release 2015-05-07) * AccountWizard: Fix auth error handling. #3155 * Documentation fixes * Infrastructure/build fixes - * Win32/OS X: Apply patch from OpenSSL to handle oudated intermediates gracefully #3087 + * Win32/OS X: Apply patch from OpenSSL to handle outdated intermediates gracefully #3087 version 1.8.0 (release 2015-03-17) * Mac OS: HIDPI support @@ -2427,7 +5056,7 @@ version 1.8.0 (release 2015-03-17) * Enhanced usage of parallel HTTP requests for ownCloud 8 servers * Renamed github repository from mirall to client. * Mac OS: Use native notification support - * Selective Sync: allow to enforce selective sync in brandings. + * Selective Sync: allow to enforce selective sync in branding. * Added ability to build on Windows utilizing MingGW * SQLite database fixes if running on FAT filesystems * Improved detection of changing files to upload from local @@ -2449,12 +5078,12 @@ 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 + * Fix hiding of the database files in the sync directories * Compare http download size with the header value to avoid broken 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 + * Fixed detectCookie method of our CookieJar, fix for Shibboleth * Added fallback for distros where XDG_RUNTIME_DIR is undefined * Fix the setup wizard, bug #1989, #2264 * Fix scheduling of ETag check jobs, bug #2553 @@ -2535,7 +5164,7 @@ version 1.6.2 (release 2014-07-28 ) * Fix data corruption while trying to resume and the server does not support it. * HTTP Credentials: Read password from legacy place if not found. - * Shibboleth: Fix the waiting curser that would not disapear (#1915) + * Shibboleth: Fix the waiting cursor that would not disappear (#1915) * Limit memory usage to avoid mem wasting and crashes * Propagator: Fix crash when logging out during upload (#1957) * Propagator_qnam: Fix signal slot connection (#1963) @@ -2566,14 +5195,14 @@ version 1.6.1 (release 2014-06-26 ) version 1.6.0 (release 2014-05-30 ) * Minor GUI improvements * Qt5 compile issues fixed - * Ignore sync log file in filewatcher + * Ignore sync log file in file-watcher * Install libocsync to private library dir and use rpath to localize * Fix reconnect after server disconnect * Fix "unknown action" display in Activity window * Fix memory leaks * Respect XDG_CONFIG_HOME environment var * Handle empty fileids in the journal correctly - * Add abilility to compile libowncloudsync without GUI dependendy + * Add ability to compile libowncloudsync without GUI dependency * Fix SSL error with previously-expired CAs on Windows * Fix incorrect folder pause state after start * Fix a couple of actual potential crashes @@ -2620,7 +5249,7 @@ version 1.5.2 (release 2014-02-26 ) * Fix online state handling (#1441, #1459) * Fix potential crash in c_iconv on Mac OS * Fix certificate chain display in SSLButton - * Fix sporadicly appearing multiple auth prompts on sign-in + * Fix sporadically appearing multiple auth prompts on sign-in * Show correct state icon in Account Settings right away * Re-fetch content that gets deleted from read only shared directories * Do not store the password in the config file, erase existing ones (#1469) @@ -2673,7 +5302,7 @@ version 1.5.0 (release 2013-12-12 ), csync 0.91.4 required * Ask for password if missing * Introduce activity view * Introduce black list for files which could not be synced - * Enabling accessbility by shipping accessibility enables on OS X (#736) + * Enabling accessibility by shipping accessibility enables on OS X (#736) * Toggle Settings window when clicking on systray icon on Win and KDE (#896) * FolderWizard: Sanitize error detection (#1201) * Set proper enable state of blacklist button after the dialog was opened @@ -2705,7 +5334,7 @@ version 1.4.2 (release 2013-10-18 ), csync 0.90.4 required * Fix high CPU load (#1073) * Reconnect if network is unavailable after startup (#1080) * Ensure paused folder stays paused when syncing with more than one folder (#1083) - * Don't show desktop notification when the user doesn't want to (#1093) + * Don't show desktop notification when the user does not want to (#1093) * System tray: Avoid quick flickering up of the ok-icon for the sync prepare state * Progress: Do not show progress if nothing is transmitted * Progress: Show number of deletes. @@ -2726,10 +5355,10 @@ version 1.4.1 (release 2013-09-24 ), csync 0.90.1 required * Fixed various folder wizard issues, bug #992 * Made "Sync started" message optional, fixes bug #934 * Fixed shutdown, avoid crashed config on win32, fixes bug #945 - * Pop up config wizard if no server url is configured, fixes bug #1018 + * Pop up config wizard if no server URL is configured, fixes bug #1018 * Settings: calculate sidebar width dynamically, fixes bug #1020 * Fixed a crash if sync folders were removed, fixes bug #713 - * Do proper resync after network disconnect, fixes bug #1007 + * Do proper re-sync after network disconnect, fixes bug #1007 * Various minor code fixes version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required @@ -2743,15 +5372,15 @@ version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required * Introduce a sync history, persisting results across syncs * 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) + * Add "Show Desktop Notifications" GUI option (enabled by default) top optionally disable sync notifications * Add Help item, pointing to online reference * Implement graphical selection of remote folders in FolderWizard * Allow custom ignore patterns - * Add an editor for ingore patterns - * ALlow to flag certain ignore patterns as discardable + * Add an editor for ignore patterns + * Allow to flag certain ignore patterns as discard-able * Ensure to ship with all valid translations - * Progress Dialog now preserves the last syncned items across sync runs + * Progress Dialog now preserves the last synced items across sync runs * Split Setup Wizard into multiple pages again * Implement "--logfile -" to log to stdout * Add preliminary support for Shibboleth authentication @@ -2810,7 +5439,7 @@ version 1.2.4 (release 2013-04-11 ), csync 0.70.6 required * [Fixes] Allow setting the configuration directory on command line version 1.2.3 (release 2013-04-02 ), csync 0.70.5 required - * [Fixes] Unbreak self-signed certificate handling + * [Fixes] Un-break self-signed certificate handling version 1.2.2 (release 2013-04-02 ), csync 0.70.5 required * [Fixes] Do not crash when local file tree contains symlinks @@ -2829,7 +5458,7 @@ version 1.2.1 (release 2013-02-26 ), csync 0.70.4 required * [Fixes] Stop sync scheduling when configuration wizard starts. * [Fixes] Clear pending network requests when stepping back in config wizard. * [Fixes] User password dialog asynchronous issues. - * [Fixes] Make folderman starting and stoping the scheduling. + * [Fixes] Make folderman starting and stopping the scheduling. * [Fixes] Various minor fixes and cleanups. * [Fixes] Crash on pausing sync * [Fixes] Stale lock file after pausing sync @@ -2840,16 +5469,16 @@ version 1.2.1 (release 2013-02-26 ), csync 0.70.4 required * [Platform] Simplified/fixed credential store usage and custom configs. * [Platform] Added soname version to libowncloudsync. * [Platform] Pull in Qt translations - * [Gui] Make sync result popups less annoyingq + * [Gui] Make sync result popups less annoying * [Gui] Fix for result popup version 1.2.0 (release 2013-01-24 ), csync 0.70.2 required * [GUI] New status dialog to show a detailed list of synced files. * [GUI] New tray notifications about synced files. * [GUI] New platform specific icon set. - * [App] Using cross platform QtKeychain library to store credentials crypted. + * [App] Using cross platform QtKeychain library to store credentials encrypted. * [App] Use cross platform notification for changes in the local file system rather than regular poll. - * [Fixes] Improved SSL Certificate handling and SSL fixes troughout syncing. + * [Fixes] Improved SSL Certificate handling and SSL fixes throughout syncing. * [Fixes] Fixed proxy authentication. * [Fixes] Allow brackets in folder name alias. * [Fixes] Lots of other minor fixes. @@ -2870,13 +5499,13 @@ version 1.1.2 (release 2012-11-26 ), csync 0.60.2 required * [Fixes] lots of other minor fixes. * [GUI] Display error messages in status dialog. * [GUI] GUI fixes for the connection wizard. - * [GUI] Show username for connection in statusdialog. + * [GUI] Show username for connection in status-dialog. * [GUI] Show intro wizard on new connection setup. * [APP] Use CredentialStore to better support various credential backends. * [APP] Handle missing local folder more robust: Create it if missing instead of ignoring. - * [APP] Simplify treewalk code. + * [APP] Simplify tree-walk code. * [Platform] Fix Mac building version 1.1.1 (release 2012-10-18), csync 0.60.1 required @@ -2921,7 +5550,7 @@ version 1.0.5 (release 2012-08-14), csync 0.50.8 required version 1.0.4 (release 2012-08-10), csync 0.50.8 required * [APP] ownCloud is now a single instance app, can not start twice any more. * [APP] Proxy support - * [APP] Handle HTTP redirection correctly, note new url. + * [APP] Handle HTTP redirection correctly, note new URL. * [APP] More relaxed handling of read only directories in the sync paths. * [APP] Started to split off a library with sync functionality, eg for KDE * [APP] Make ownCloud Info class a singleton, more robust. @@ -2934,7 +5563,7 @@ version 1.0.4 (release 2012-08-10), csync 0.50.8 required 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] Loading of self signed certs into NetworkManager (#oc-843) * [Fixes] Win32: Handle SSL dll loading correctly. * [Fixes] Many other small fixes and improvements. @@ -2960,7 +5589,7 @@ version 1.0.2 (release 2012-05-18), csync 0.50.6 required * [GUI] New icon set for ownCloud client * [GUI] No splashscreen any more (oC Bug #498) * [GUI] Russian translation added - * [GUI] Added 'open ownCloud' to traymenu + * [GUI] Added 'open ownCloud' to tray-menu * [GUI] "Pause" and "Resume" instead of Enable/Disable * [Fixes] Long running syncs can be interrupted now. * [Fixes] Dialogs comes to front on click @@ -2985,7 +5614,7 @@ version 1.0.1 (release 2012-04-18), csync 0.50.5 required * [Intern] Backport to Qt Version 4.6 for compat. with older distros. * [Platform] MacOSX porting efforts * [Platform] MacOSX Bundle creation added - * [Platform] Enabled ranslations on Windows. + * [Platform] Enabled translations on Windows. diff --git a/CMakeLists.txt b/CMakeLists.txt index 678047fff0e..c7f937934e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,17 @@ -cmake_minimum_required(VERSION 3.6) +cmake_minimum_required(VERSION 3.18) +# ensure that project() sets PROJECT_VERSION etc. +cmake_policy(SET CMP0048 NEW) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Set this before the first project definition +set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0" CACHE STRING "Minimum macOS deployment version" FORCE) include(VERSION.cmake) -project(client LANGUAGES CXX C VERSION ${MIRALL_VERSION_MAJOR}.${MIRALL_VERSION_MINOR}.${MIRALL_VERSION_PATCH}) +project(owncloud-client LANGUAGES CXX C VERSION ${MIRALL_VERSION_MAJOR}.${MIRALL_VERSION_MINOR}.${MIRALL_VERSION_PATCH}) include(FeatureSummary) -find_package(ECM 5.50.0 REQUIRED NO_MODULE) +find_package(ECM 6.0.0 REQUIRED NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) @@ -15,15 +21,30 @@ set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PAT # disable pointless warning in KDECMakeSettings set(APPLE_SUPPRESS_X11_WARNING ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +find_package(QT 6.8 NAMES Qt6 COMPONENTS Core REQUIRED) + +find_package(Qt6 COMPONENTS Core Concurrent Network Widgets Xml Quick QuickWidgets QuickControls2 REQUIRED) +find_package(Qt6LinguistTools REQUIRED) +get_target_property (QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION) +message(STATUS "Using Qt ${QT_VERSION} (${QT_QMAKE_EXECUTABLE})") + +if (UNIX AND NOT APPLE) + find_package(Qt6 REQUIRED COMPONENTS DBus) +endif() + include(KDEInstallDirs) include(KDECMakeSettings) include(ECMMarkNonGuiExecutable) +include(ECMSetupVersion) include(KDECompilerSettings NO_POLICY_SCOPE) +include(ECMEnableSanitizers) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -find_package(Qt5 REQUIRED COMPONENTS Core) +# while we use qt_add_qml_module instead of ecm_add_qml_module +# include ECMQmlModule to set up the policies +include(ECMQmlModule) if(UNIT_TESTING) message(DEPRECATION "Setting UNIT_TESTING is deprecated please use BUILD_TESTING") @@ -31,50 +52,27 @@ if(UNIT_TESTING) endif() include(CTest) - -include(OCConfigPluginDir) include(OCBundleResources) +include(OCApplyCommonSettings) -if (EXISTS "${PROJECT_SOURCE_DIR}/branding") - set(OEM_THEME_DIR "${PROJECT_SOURCE_DIR}/branding" CACHE STRING "The directory containing a custom theme") -else() - set(OEM_THEME_DIR "${PROJECT_SOURCE_DIR}" CACHE STRING "Define directory containing a custom theme") -endif() include("${CMAKE_CURRENT_LIST_DIR}/THEME.cmake") -set(synclib_NAME "${APPLICATION_EXECUTABLE}sync") - - -# For config.h -include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR}) -# Allows includes based on src/ like #include "common/utility.h" or #include "csync/csync.h" -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR}/src - ${CMAKE_CURRENT_BINARY_DIR}/src -) - -# disable the crashreporter if libcrashreporter-qt is not available or we're building for ARM -if( CMAKE_SYSTEM_PROCESSOR MATCHES "arm" OR NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/3rdparty/libcrashreporter-qt/CMakeLists.txt") - set( WITH_CRASHREPORTER OFF ) -endif() - if(NOT WITH_CRASHREPORTER) message(STATUS "Build of crashreporter disabled.") +else() + # obviously, a crash reported without the URL set is pretty pointless and won't work + if(NOT DEFINED CRASHREPORTER_SUBMIT_URL) + message(FATAL_ERROR "No crash reporter submit URL provided") + endif() endif() include(GenerateExportHeader) include(GetGitRevisionDescription) +# used in src/csync/CMakeLists.txt get_git_head_revision(GIT_REFSPEC GIT_SHA1) -add_definitions( - -DQT_USE_QSTRINGBUILDER - -DQT_MESSAGELOGCONTEXT #enable function name and line number in debug output - -DQT_DEPRECATED_WARNINGS - -DQT_NO_FOREACH -) - # if we cannot get it from git, directly try .tag (packages) # this will work if the tar balls have been properly created # via git-archive. @@ -88,67 +86,45 @@ if ("${GIT_SHA1}" STREQUAL "GITDIR-NOTFOUND") endif() message(STATUS "GIT_SHA1 ${GIT_SHA1}") -set(SYSCONFDIR ${SYSCONF_INSTALL_DIR}) - -# this option removes Http authentication, keychain, shibboleth etc and is intended for -# external authentication mechanisms -option(TOKEN_AUTH_ONLY "TOKEN_AUTH_ONLY" OFF) -if(TOKEN_AUTH_ONLY) - message("Compiling with token authentication") - add_definitions(-DTOKEN_AUTH_ONLY=1) -endif() - option(NO_MSG_HANDLER "Don't redirect QDebug outputs to the log window/file" OFF) -if(NO_MSG_HANDLER) - add_definitions(-DNO_MSG_HANDLER=1) -endif() # this option builds the shell integration option(BUILD_SHELL_INTEGRATION "BUILD_SHELL_INTEGRATION" ON) -# this option builds/installs the generic shell integration icons -option(BUILD_SHELL_INTEGRATION_ICONS "BUILD_SHELL_INTEGRATION_ICONS" ON) - -# this options builds the dolphin integration plugin -option(BUILD_SHELL_INTEGRATION_DOLPHIN "BUILD_SHELL_INTEGRATION_DOLPHIN" ON) - -# this options builds the nautilus (like) integration plugins -option(BUILD_SHELL_INTEGRATION_NAUTILUS "BUILD_SHELL_INTEGRATION_NAUTILUS" ON) +# build the auto updater component +option(WITH_AUTO_UPDATER "WITH_AUTO_UPDATER" OFF) -# this option builds the client -option(BUILD_CLIENT "BUILD_CLIENT" ON) +# build with -DQT_FORCE_ASSERTS +option(FORCE_ASSERTS "FORCE_ASSERTS" OFF) -# this option builds the client documentation -option(BUILD_DOCS "BUILD_DOCS" OFF) +option(WITH_APPIMAGEUPDATER OFF "Enable built-in libappimageupdate based updater on Linux") +add_feature_info(AppImageUpdate WITH_APPIMAGEUPDATER "Built-in libappimageupdate based updater") -# this option creates only libocsync and libowncloudsync (NOTE: BUILD_CLIENT needs to be on) -option(BUILD_LIBRARIES_ONLY "BUILD_LIBRARIES_ONLY" OFF) +option(WITH_EXTERNAL_BRANDING "A URL to an external branding repo" "") +set(WITH_EXTERNAL_BRANDING_TAG "" CACHE STRING "Tag, branch, or commit in the external branding repo to use") -# build the GUI component, when disabled only owncloudcmd is built -option(BUILD_GUI "BUILD_GUI" ON) - -# specify additional vfs plugins -set(VIRTUAL_FILE_SYSTEM_PLUGINS suffix win CACHE STRING "Name of internal plugin in src/libsync/vfs or the locations of virtual file plugins") +# specify additional vfs plugin +set(VIRTUAL_FILE_SYSTEM_PLUGINS off win CACHE STRING "Name of internal plugin in src/libsync/vfs or the locations of virtual file plugins") if(APPLE) set( SOCKETAPI_TEAM_IDENTIFIER_PREFIX "" CACHE STRING "SocketApi prefix (including a following dot) that must match the codesign key's TeamIdentifier/Organizational Unit" ) endif() -if(BUILD_CLIENT) - OPTION(GUI_TESTING "Build with gui introspection features of socket api" OFF) - - if(APPLE) - find_package(Sparkle REQUIRED) - endif(APPLE) +if(APPLE) + find_package(Sparkle REQUIRED) +endif(APPLE) - if(UNIX AND NOT APPLE) - find_package(Inotify REQUIRED) - endif() +if(WITH_APPIMAGEUPDATER) + find_package(AppImageUpdate REQUIRED) +endif() - find_package(ZLIB REQUIRED) - find_package(SQLite3 3.9.0 REQUIRED) +if(UNIX AND NOT APPLE) + find_package(Inotify REQUIRED) endif() +find_package(ZLIB REQUIRED) +find_package(SQLite3 3.9.0 REQUIRED) + # in the ownBrander themes, the icon files are named after the shortname # the theme included in this repository defines a custom icon name, therefore we set the shortname as a fallback if the # theme does not define the variable @@ -156,45 +132,16 @@ if (NOT DEFINED APPLICATION_ICON_NAME) set(APPLICATION_ICON_NAME "${APPLICATION_SHORTNAME}") endif() -if( WIN32 ) - add_definitions( -D__USE_MINGW_ANSI_STDIO=1 ) - add_definitions( -DNOMINMAX ) - # Get APIs from from Vista onwards. - add_definitions(-D_WIN32_WINNT=0x0600) - add_definitions(-DWINVER=0x0600) - add_definitions(-DNTDDI_VERSION=0x0A000003) -endif() - file(GLOB_RECURSE OWNCLOUD_ICONS "${OEM_THEME_DIR}/theme/colored/*-${APPLICATION_ICON_NAME}-icon.png") if (NOT OWNCLOUD_ICONS) generate_legacy_icons(${OEM_THEME_DIR}/theme OWNCLOUD_ICONS) endif() MESSAGE(STATUS "OWNCLOUD_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_ICONS}") -if(APPLE) - list(APPEND OWNCLOUD_BUNDLED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/sync-exclude.lst") -elseif(BUILD_CLIENT) - configure_file(sync-exclude.lst bin/sync-exclude.lst COPYONLY) - if(WIN32) - install(FILES sync-exclude.lst DESTINATION bin) - else() - install(FILES sync-exclude.lst DESTINATION ${SYSCONFDIR}/${APPLICATION_SHORTNAME}) - endif() -endif() - -if(BUILD_CLIENT) - add_subdirectory(src) - if(NOT BUILD_LIBRARIES_ONLY) - if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/admin) - add_subdirectory(admin) - endif(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/admin) - endif(NOT BUILD_LIBRARIES_ONLY) -endif() - -if(BUILD_DOCS) - add_subdirectory(docs) -endif() - +add_subdirectory(src) +if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/admin) + add_subdirectory(admin) +endif(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/admin) if(BUILD_SHELL_INTEGRATION) add_subdirectory(shell_integration) @@ -204,14 +151,5 @@ if(BUILD_TESTING) add_subdirectory(test) endif() -configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) - feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) -if(ECM_VERSION VERSION_GREATER_EQUAL 5.79) - message(STATUS "Suitable ECM ${ECM_VERSION} found, installing clang-format git hook") - include(KDEGitCommitHooks) - kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) -else() - message(WARNING "ECM ${ECM_VERSION} too old, cannot install clang-format git hook") -endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7715be71cf..faf949afd60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,20 +1,21 @@ ## Submitting Desktop Client issues If you have questions about how to use the ownCloud Desktop Client, please -join our [rocket chat channel][rocketchat] or our [forum][forum]. +join our [forum][forum]. We are also available on [IRC][irc]. ### Bug Reporting Guidelines -* __Important__: When reporting an issue provide as much requested information as possible, we :heart: log files. -* __SECURITY__: Report any potential security bug to security@owncloud.com following our [security policy](https://owncloud.org/security/) instead of filing an issue in our bug tracker -* This repository is *only* for issues within the ownCloud desktop client. - Issues in other components should be reported in their own repositores: + +- **Important**: When reporting an issue provide as much requested information as possible, we :heart: log files. +- **SECURITY**: Report any potential security bug to security@owncloud.com following our [security policy](https://owncloud.com/security/) instead of filing an issue in our bug tracker +- This repository is _only_ for issues within the ownCloud desktop client. + Issues in other components should be reported in their own repositories: - [ownCloud server](https://github.com/owncloud/core/issues) - [ownCloud apps](https://github.com/owncloud/apps/issues) (e.g. Calendar, Contacts...) - [Android client](https://github.com/owncloud/android/issues) - [iOS client](https://github.com/owncloud/ios-issues/issues) -* Search the existing issues first, it's likely that your issue was already +- Search the existing issues first, it's likely that your issue was already reported. If your issue appears to be a bug, and hasn't been reported, open a new issue. @@ -22,7 +23,6 @@ If your issue appears to be a bug, and hasn't been reported, open a new issue. Help us to maximize the effort we can spend fixing issues and adding new features, by not reporting duplicate issues. -[rocketchat]: https://talk.owncloud.com/channel/desktop [forum]: https://central.owncloud.org/ [irc]: https://web.libera.chat/?channels=#owncloud @@ -33,15 +33,13 @@ Thanks for wanting to contribute source code to ownCloud. That's great! Before we're able to merge your code to ownCloud Desktop Client, you need to sign our [Contributor Agreement][agreement]. -Please read the [Desktop Client Manual][desktopman] and the [Developer -Manuals][devmanual] to get useful info like how to create your first -application or how to test the ownCloud code with phpunit. +Please read the [Desktop Client Manual][desktopman] to get useful info about the client. -[agreement]: http://owncloud.org/about/contributor-agreement/ -[devmanual]: https://doc.owncloud.com/desktop/latest/appendices/building.html -[desktopman]: http://doc.owncloud.org/desktop +[agreement]: https://owncloud.com/contribute/join-the-development/contributor-agreement/ +[desktopman]: https://doc.owncloud.com/desktop ## Translations + Please submit translations via [Transifex][transifex]. [transifex]: https://www.transifex.com/projects/p/owncloud/ diff --git a/OWNCLOUD.cmake b/OWNCLOUD.cmake index 67748a68de4..3b86996a2f0 100644 --- a/OWNCLOUD.cmake +++ b/OWNCLOUD.cmake @@ -15,6 +15,8 @@ set( WIN_SETUP_BITMAP_PATH "${CMAKE_SOURCE_DIR}/admin/win/nsi" ) set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-background.png" CACHE STRING "The MacOSX installer background image") +set( THEME_INCLUDE "owncloudtheme.h" ) + # set( THEME_INCLUDE "${OEM_THEME_DIR}/mytheme.h" ) # set( APPLICATION_LICENSE "${OEM_THEME_DIR}/license.txt ) diff --git a/PACKAGING.md b/PACKAGING.md new file mode 100644 index 00000000000..98542e3be10 --- /dev/null +++ b/PACKAGING.md @@ -0,0 +1,40 @@ +# Shipping + +This readme file is for distribution packagers and provides some reasoning and hints about shipping the desktop client for a seamless experience on the Linux desktop. + +For that, ownCloud and the distributions need to collaborate. + +Note that this information is valid for the ownCloud Desktop App 6.0 and subsequent releases only and does not apply for releases 5.x and below. +## Integration Problems + +The ownCloud desktop client gets shipped as AppImage because it is effortless to create, independent of the underlying distro and easy to install, as long as the distribution is not providing a package on their own which is frequently updated. + +With AppImage, there comes one problem: For the integration into the desktop file managers which are provided by the distribution (such as Dolphin for KDE and Nautilus), icons and partly also binary code is needed. That is needed to provide the necessary information to enable the file managers to show for example icon overlays. + +So far, the necessary code and resources were bundled with the desktop client code, but now the desktop shell integrations resources were split off, so that they can be released and shipped separately. + +## Solution + +The idea is that everything that is needed to integrate into the desktop apps such as dolphin are part of the Linux distribution that any user chooses to use. Ideally they are packages maintained downstream in the Linux distributions and can be installed via the native package management. + +Since these parts are very rarely changing, these packages are very stable and thus, packaging efforts a very, very low. + +The actual client code, which changes far more often, is either also provided by the Linux distro downstream or by ownCloud as an AppImage. ownCloud chooses to deliver an AppImage because that reduces the effort to package for all distros out there, it is "one fits all". + +User now only have to install the AppImage with the latest client, and the shell integrations from their Linux distro package management. The AppImage based client automagically connects to the shell integration code and the overlay icons and menu additions in the file managers work. + +With that, the user gets on the one hand very frequently updated desktop clients directly from the project, and nice integration with the desktop technology that is delivered from the distribution. + +## Packaging Hints + +Here are some hints for packager of the linux distributions which we kindly ask to change their packages in the distros to help us to provide the best ownCloud client integration experience: + +The shell integrations are available for [KDE Dolphin](https://github.com/owncloud/client-desktop-shell-integration-dolphin), [Nautilus and Caja](https://github.com/owncloud/client-desktop-shell-integration-nautilus). While KDE Dolphin needs compiled code, the latter two are using python. +Each of them should be available as separate distro package, so that they can be suggested to complement "their" file manager. + +All of these packages should depend on the [client-extension-resources](https://github.com/owncloud/client-desktop-shell-integration-resources) package, which basically only contains the overlay icons. They can optionally be branded with a distro specific theme, to maintain a cool desktop experience. + +None of the three file manager integration packages (for Dolphin, Nautilus or Caja) need a dependency on the "big" ownCloud desktop client package anymore, so that they do not change in foreseeable times. + +If the distro decides to ship an own package of the client or lets their user install the provided AppImage, the experience will be perfect in any case. + diff --git a/README.md b/README.md index d72ab63f07c..d81876322a8 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,16 @@ [![Build Status](https://drone.owncloud.com/api/badges/owncloud/client/status.svg)](https://drone.owncloud.com/owncloud/client) [![Build Status](https://github.com/owncloud/client/workflows/ownCloud%20CI/badge.svg)](https://github.com/owncloud/client/actions) - ## Introduction -The ownCloud Desktop Client is a tool to synchronize files from ownCloud Server +The ownCloud Desktop Client is a tool to synchronize files from ownCloud Infinite Scale with your computer. ## Download ### Binary packages -* Refer to the download page https://owncloud.com/desktop-app/ +- Refer to the download page https://owncloud.com/desktop-app/ ### Source code @@ -22,10 +21,16 @@ can be found on the Internet, in particular on GitHub. However, the authoritative repository maintained by the developers is located at https://github.com/owncloud/client. -## Building the source code +### Building from source + +This project uses CMake as build system. Please refer to the cmake documentation +for general instructions on how to use CMake: https://cmake.org/documentation/. + +Further more as meta build system KDE Craft is used. Please refer to the KDE Craft +documentation for instructions on how to use Craft: https://community.kde.org/Craft. -[Building the Client](https://doc.owncloud.org/desktop/building.html) -in the ownCloud Desktop Client manual. +For easy to use instructions on how to build the ownCloud Desktop Client please have +a look at https://github.com/owncloud/ownbuild. ## Reporting issues and contributing @@ -37,32 +42,24 @@ of your report being lost. If you created a patch, please submit a [Pull Request](https://github.com/owncloud/client/pulls). For non-trivial patches, we need you to sign the [Contributor -Agreement](https://owncloud.org/contribute/agreement) before +Agreement](https://owncloud.com/contribute/join-the-development/contributor-agreement/) before we can accept your patch. -If you want to contact us, e.g. before starting a more complex feature, -you can join us at -[RocketChat](https://talk.owncloud.com/channel/desktop). ## Maintainers and Contributors -The current maintainers of this repository are: - -* Hannah von Reth -* Dominik Schmidt - ownCloud Desktop Client is developed by the ownCloud community and [receives patches from a variety of authors](https://github.com/owncloud/client/graphs/contributors). Past maintainers: -* Markus Goetz -* Olivier Goffart -* Christian Kamm -* Thomas Müller -* Klaas Freitag -* Daniel Molkentin -* Andreas Schneider +- Markus Goetz +- Olivier Goffart +- Christian Kamm +- Thomas Müller +- Klaas Freitag +- Daniel Molkentin +- Andreas Schneider ## License @@ -76,4 +73,3 @@ Past maintainers: or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - diff --git a/THEME.cmake b/THEME.cmake index ee316c628cf..cedc9c49f47 100644 --- a/THEME.cmake +++ b/THEME.cmake @@ -1,24 +1,39 @@ +if(WITH_EXTERNAL_BRANDING) + include(FetchContent) + + if(NOT WITH_EXTERNAL_BRANDING_TAG) + set(WITH_EXTERNAL_BRANDING_TAG "master") + endif() + + FetchContent_Declare(branding + GIT_REPOSITORY ${WITH_EXTERNAL_BRANDING} + GIT_TAG ${WITH_EXTERNAL_BRANDING_TAG} + ) + FetchContent_MakeAvailable(branding) + + set(OEM_THEME_DIR ${branding_SOURCE_DIR} CACHE STRING "The directory containing a custom theme") +else() + if (EXISTS "${PROJECT_SOURCE_DIR}/branding") + set(OEM_THEME_DIR "${PROJECT_SOURCE_DIR}/branding" CACHE STRING "The directory containing a custom theme") + else() + set(OEM_THEME_DIR "${PROJECT_SOURCE_DIR}/src/resources/" CACHE STRING "Define directory containing a custom theme") + endif() +endif() + if (EXISTS "${OEM_THEME_DIR}/OEM.cmake") include("${OEM_THEME_DIR}/OEM.cmake") + if (DEFINED WITHOUT_AUTO_UPDATER) + set(WITH_AUTO_UPDATER OFF) + MESSAGE(STATUS "Branding: WITHOUT_AUTO_UPDATER was defined in the theme and therefore the auto updater has been disabled") + endif () + else() include ("${CMAKE_CURRENT_LIST_DIR}/OWNCLOUD.cmake") endif() message(STATUS "Branding: ${APPLICATION_NAME}") -# Default suffix if the theme doesn't define one -if(NOT DEFINED APPLICATION_VIRTUALFILE_SUFFIX) - set(APPLICATION_VIRTUALFILE_SUFFIX "${APPLICATION_SHORTNAME}_virtual" CACHE STRING "Virtual file suffix (not including the .)") -endif() - -# Default dbus name and path -if(NOT DEFINED APPLICATION_CLOUDPROVIDERS_DBUS_NAME) - set(APPLICATION_CLOUDPROVIDERS_DBUS_NAME ${APPLICATION_REV_DOMAIN}) -endif() -if(NOT DEFINED APPLICATION_CLOUDPROVIDERS_DBUS_PATH) - set(APPLICATION_CLOUDPROVIDERS_DBUS_PATH "/${APPLICATION_CLOUDPROVIDERS_DBUS_NAME}") - string(REPLACE "." "/" APPLICATION_CLOUDPROVIDERS_DBUS_PATH ${APPLICATION_CLOUDPROVIDERS_DBUS_PATH}) -endif() +set(APPLICATION_REV_DOMAIN_INSTALLER ${APPLICATION_REV_DOMAIN}) # need this logic to not mess with re/uninstallations via macosx.pkgproj if(${APPLICATION_REV_DOMAIN} STREQUAL "com.owncloud.desktopclient") @@ -40,8 +55,6 @@ if (NOT DEFINED PACKAGE) set(PACKAGE "${LINUX_PACKAGE_SHORTNAME}-client") endif() -set(PROJECT_NAME "${PACKAGE}") - if(NOT CRASHREPORTER_EXECUTABLE) set(CRASHREPORTER_EXECUTABLE "${APPLICATION_EXECUTABLE}_crash_reporter") endif() diff --git a/VERSION.cmake b/VERSION.cmake index 18ca974821d..5620fbede23 100644 --- a/VERSION.cmake +++ b/VERSION.cmake @@ -1,7 +1,7 @@ -set( MIRALL_VERSION_MAJOR 2 ) -set( MIRALL_VERSION_MINOR 11 ) +set( MIRALL_VERSION_MAJOR 7 ) +set( MIRALL_VERSION_MINOR 2 ) set( MIRALL_VERSION_PATCH 0 ) -set( MIRALL_VERSION_YEAR 2021 ) +set( MIRALL_VERSION_YEAR 2026 ) set( MIRALL_SOVERSION 0 ) if ( NOT DEFINED MIRALL_VERSION_SUFFIX ) @@ -15,10 +15,12 @@ endif( NOT DEFINED MIRALL_VERSION_BUILD ) # Used e.g. for libraries Keep at x.y.z. set( MIRALL_VERSION "${MIRALL_VERSION_MAJOR}.${MIRALL_VERSION_MINOR}.${MIRALL_VERSION_PATCH}" ) # Version with Build ID. Used in the installer -set( MIRALL_VERSION_FULL "${MIRALL_VERSION}.${MIRALL_VERSION_BUILD}" ) - -set( MIRALL_VERSION_STRING "${MIRALL_VERSION}${MIRALL_VERSION_SUFFIX}" ) - +set( MIRALL_VERSION_FULL "${MIRALL_VERSION}" ) if( MIRALL_VERSION_BUILD ) - set( MIRALL_VERSION_STRING "${MIRALL_VERSION_STRING} (build ${MIRALL_VERSION_BUILD})" ) + set( MIRALL_VERSION_FULL "${MIRALL_VERSION_FULL}.${MIRALL_VERSION_BUILD}" ) endif( MIRALL_VERSION_BUILD ) + +set( MIRALL_VERSION_STRING "${MIRALL_VERSION_FULL}" ) +if(NOT MIRALL_VERSION_SUFFIX STREQUAL "") + set( MIRALL_VERSION_STRING "${MIRALL_VERSION_STRING}-${MIRALL_VERSION_SUFFIX}" ) +endif() diff --git a/admin/clean-leftovers.ps1 b/admin/clean-leftovers.ps1 new file mode 100644 index 00000000000..faf6c40294d --- /dev/null +++ b/admin/clean-leftovers.ps1 @@ -0,0 +1,31 @@ +Param +( + [Parameter(Mandatory=$true)] + [string]$ProductName +) + +# https://github.com/MicrosoftDocs/winrt-api/issues/1130 +$SyncRootManager = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SyncRootManager" +$GetRootItem = Get-Item $SyncRootManager +Get-ChildItem -Path $SyncRootManager | ForEach-Object { + # legacy clients always used that prefix owncloud_ + + $name = $_.Name.Substring($GetRootItem.Name.Length + 1) + if ($name.StartsWith("OC-TEST", "CurrentCultureIgnoreCase") -or $name.StartsWith("owncloud_", "CurrentCultureIgnoreCase") -or $name.StartsWith("$ProductName", "CurrentCultureIgnoreCase")) { + Write-Host $_ + Remove-Item -Recurse $_.PsPath + } +} + +Get-ChildItem -Path "HKCU:\Software\Classes\CLSID\" | ForEach-Object { + $key = (get-itemproperty $_.PsPath)."(default)" + if ($key) { + # unit tests (OC-TEST) leave stuff behind + if ($key.StartsWith("OC-TEST", "CurrentCultureIgnoreCase") -or $key.StartsWith("$ProductName", "CurrentCultureIgnoreCase")) { + Write-Host $key, $_ + Remove-Item -Recurse $_.PsPath + } + } +} +Get-Process explorer | Stop-Process +Pause diff --git a/admin/osx/CMakeLists.txt b/admin/osx/CMakeLists.txt index 720015c0dd9..93c1d7229e6 100644 --- a/admin/osx/CMakeLists.txt +++ b/admin/osx/CMakeLists.txt @@ -1,5 +1,5 @@ -# Check if varialbe MAC_INSTALLER_BACKGROUND_FILE is defined. That might come +# Check if variable MAC_INSTALLER_BACKGROUND_FILE is defined. That might come # from the OEM.cmake for branded clients or from OWNCLOUD.cmake for the non # branded client. # Make sure that the MAC_INSTALLER_BACKGROUND_FILE contains the full path, ie. @@ -11,7 +11,6 @@ else() set(MAC_INSTALLER_DO_CUSTOM_BACKGROUND "false") endif() -find_package(Qt5 5.6 COMPONENTS Core REQUIRED) configure_file(create_mac.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/create_mac.sh @ONLY) configure_file(macosx.pkgproj.cmake ${CMAKE_CURRENT_BINARY_DIR}/macosx.pkgproj @ONLY) configure_file(pre_install.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/pre_install.sh @ONLY) diff --git a/admin/osx/post_install.sh.cmake b/admin/osx/post_install.sh.cmake index 65040078358..8044ca29249 100644 --- a/admin/osx/post_install.sh.cmake +++ b/admin/osx/post_install.sh.cmake @@ -20,7 +20,7 @@ fi if [[ -f "${INSTALLER_TEMP}/OC_RESTART_NEEDED" ]]; then if [[ "${COMMAND_LINE_INSTALL}" = "" ]]; then - /bin/launchctl asuser "${LOGGED_IN_USER_ID}" /usr/bin/open -g "/Applications/@APPLICATION_EXECUTABLE@.app" + /bin/launchctl kickstart "gui/${LOGGED_IN_USER_ID}/@APPLICATION_REV_DOMAIN@" fi fi diff --git a/changelog/2.10.0_2022-01-17/4896.md b/changelog/2.10.0_2022-01-17/4896.md new file mode 100644 index 00000000000..cc22d357097 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/4896.md @@ -0,0 +1,6 @@ +Bugfix: Issues with long file path + +We fixed another issue with long Windows paths. + + +https://github.com/owncloud/enterprise/issues/4896 diff --git a/changelog/2.10.0_2022-01-17/8924.md b/changelog/2.10.0_2022-01-17/8924.md new file mode 100644 index 00000000000..155f3555566 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/8924.md @@ -0,0 +1,9 @@ +Bugfix: Do not ask for credentials at start-up when the user logged out + +When a user would log out, and quit the client, then on the next start +the client would immediately ask for credentials. This has been fixed by +storing the fact that the user logged out before in the account +settings. + +https://github.com/owncloud/client/issues/8924 + diff --git a/changelog/2.10.0_2022-01-17/9000.md b/changelog/2.10.0_2022-01-17/9000.md new file mode 100644 index 00000000000..d7b2c903abe --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9000.md @@ -0,0 +1,7 @@ +Enhancement: Reintroduce issue filtering + +We reintroduced a filtering option to the issue table. +With the addition of a Filter button we also made the existing filter by account feature more accessible. + +https://github.com/owncloud/client/issues/9000 +https://github.com/owncloud/client/pull/9023 diff --git a/changelog/2.10.0_2022-01-17/9071.md b/changelog/2.10.0_2022-01-17/9071.md new file mode 100644 index 00000000000..7f06ad929cc --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9071.md @@ -0,0 +1,5 @@ +Bugfix: A folder moved on the server was displayed as outdated + +We fixed a bug where a folder moved on the server was displayed as outdated. + +https://github.com/owncloud/client/issues/9071 diff --git a/changelog/unreleased/9099 b/changelog/2.10.0_2022-01-17/9099.md similarity index 100% rename from changelog/unreleased/9099 rename to changelog/2.10.0_2022-01-17/9099.md diff --git a/changelog/unreleased/9149 b/changelog/2.10.0_2022-01-17/9149.md similarity index 100% rename from changelog/unreleased/9149 rename to changelog/2.10.0_2022-01-17/9149.md diff --git a/changelog/unreleased/9150 b/changelog/2.10.0_2022-01-17/9150.md similarity index 100% rename from changelog/unreleased/9150 rename to changelog/2.10.0_2022-01-17/9150.md diff --git a/changelog/2.10.0_2022-01-17/9170.md b/changelog/2.10.0_2022-01-17/9170.md new file mode 100644 index 00000000000..22c9f2d6e38 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9170.md @@ -0,0 +1,6 @@ +Bugfix: Crash when handling locked files + +We fixed a crash that could occur when trying to add a locked folder to the database. + + +https://github.com/owncloud/client/issues/9170 diff --git a/changelog/2.10.0_2022-01-17/9194.md b/changelog/2.10.0_2022-01-17/9194.md new file mode 100644 index 00000000000..6af7377dd3c --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9194.md @@ -0,0 +1,9 @@ +Bugfix: Don't abort upload if chunk is locked + +Since 2.9 we know that we need exclusive file access to a file to properly handle it with Windows virtual files. +Therefore, we checked for the locked state before we start the upload. +Due to a bug we checked that for each file chunk, now we only check when the upload starts and when it finished completely. + +https://github.com/owncloud/client/issues/9194 +https://github.com/owncloud/client/pull/9264 +https://github.com/owncloud/client/pull/9296 diff --git a/changelog/2.10.0_2022-01-17/9196.md b/changelog/2.10.0_2022-01-17/9196.md new file mode 100644 index 00000000000..18503f1df1e --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9196.md @@ -0,0 +1,6 @@ +Bugfix: Always restart OAuth2 on error + +We now always restart the OAuth2 process once we got a result. +This will ensure that a second try after an error occurred can succeed. + +https://github.com/owncloud/client/issues/9196 diff --git a/changelog/2.10.0_2022-01-17/9208.md b/changelog/2.10.0_2022-01-17/9208.md new file mode 100644 index 00000000000..c6db7aa811b --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9208.md @@ -0,0 +1,7 @@ +Enhancement: Also ignore local repeating errors for a period of time + +If an error occurs on the server (a URL is not reachable) we try a couple of times, then we ignore that file for a period of time. +We now do the same with errors that occur locally. + +https://github.com/owncloud/client/issues/9208 +https://github.com/owncloud/client/issues/9133 diff --git a/changelog/2.10.0_2022-01-17/9223.md b/changelog/2.10.0_2022-01-17/9223.md new file mode 100644 index 00000000000..28c698b8815 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9223.md @@ -0,0 +1,10 @@ +Bugfix: Display correct error message for files containing `\:?*"<>|` + +While the error message was supposed to be: +`File names containing the character '%1' are not supported on this file system.` + +We displayed: +`The file name is a reserved name on this file system.` + +https://github.com/owncloud/client/pull/9223/ + diff --git a/changelog/2.10.0_2022-01-17/9241.md b/changelog/2.10.0_2022-01-17/9241.md new file mode 100644 index 00000000000..541279e55a5 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9241.md @@ -0,0 +1,8 @@ +Bugfix: Do not sync when `unsyncedfolders` file cannot be read + +owncloudcmd now checks if the file specified by --unsyncedfolders exists +and can be read, before starting the sync. If it does not exist, show an +error message and quit immediately. + +https://github.com/owncloud/client/issues/9165 +https://github.com/owncloud/client/pull/9241 diff --git a/changelog/2.10.0_2022-01-17/9257.md b/changelog/2.10.0_2022-01-17/9257.md new file mode 100644 index 00000000000..56626eee627 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9257.md @@ -0,0 +1,7 @@ +Bugfix: Fix failing dehydration causing files to be moved to trash + +If files where dehydrated by the user the action could fail under certain conditions +which caused a deletion of the file. + +https://github.com/owncloud/client/pull/9257 +https://github.com/owncloud/client-desktop-vfs-win/pull/9 diff --git a/changelog/2.10.0_2022-01-17/9266.md b/changelog/2.10.0_2022-01-17/9266.md new file mode 100644 index 00000000000..af394474673 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9266.md @@ -0,0 +1,7 @@ +Bugfix: Do not show Activity tab if server app is disabled or uninstalled + +The Activity app API nowadays returns error responses in case the app is disabled or uninstalled. +This new behavior is now supported in the client. + +https://github.com/owncloud/client/issues/9260 +https://github.com/owncloud/client/pull/9266 diff --git a/changelog/2.10.0_2022-01-17/9291.md b/changelog/2.10.0_2022-01-17/9291.md new file mode 100644 index 00000000000..e12db36ca1b --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9291.md @@ -0,0 +1,6 @@ +Enhancement: Remove the availability menu from the ui + +The availability options should be handled on a folder base and in the file browser. + + +https://github.com/owncloud/client/pull/9291 \ No newline at end of file diff --git a/changelog/2.10.0_2022-01-17/9293.md b/changelog/2.10.0_2022-01-17/9293.md new file mode 100644 index 00000000000..515f55ba276 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9293.md @@ -0,0 +1,9 @@ +Bugfix: Handle file locks for delete jobs + +We no longer report an error when the client tries to delete a locked file +but wait for the lock to be removed. + +This only works when a file is deleted not on folders. + +https://github.com/owncloud/client/issues/9293 +https://github.com/owncloud/client/pull/9295 diff --git a/changelog/2.10.0_2022-01-17/9341.md b/changelog/2.10.0_2022-01-17/9341.md new file mode 100644 index 00000000000..20996c04a72 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9341.md @@ -0,0 +1,5 @@ +Bugfix: Run a full local discovery after we were paused or on a forced sync + +Previously we did an incremental search which might have skipped some local changes. + +https://github.com/owncloud/client/issues/9341 diff --git a/changelog/2.10.0_2022-01-17/9342.md b/changelog/2.10.0_2022-01-17/9342.md new file mode 100644 index 00000000000..b9b57a62c47 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/9342.md @@ -0,0 +1,6 @@ +Bugfix: Infinite sync loop if folder is locked + +We fixed a bug that caused an infinite sync loop if an error occurred. + +https://github.com/owncloud/client/issues/9342 +https://github.com/owncloud/client-desktop-vfs-win/pull/14 diff --git a/changelog/2.10.0_2022-01-17/client-desktop-vfs-win-12.md b/changelog/2.10.0_2022-01-17/client-desktop-vfs-win-12.md new file mode 100644 index 00000000000..aa7c3aae0e5 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/client-desktop-vfs-win-12.md @@ -0,0 +1,6 @@ +Enhancement: Add the syncroot to the search indexed with Windows VFS + +Microsoft recommends adding the syncroot to search indexer to improve +the performance with the file status icons. + +https://github.com/owncloud/client-desktop-vfs-win/pull/12 diff --git a/changelog/2.10.0_2022-01-17/client-desktop-vfs-win-13.md b/changelog/2.10.0_2022-01-17/client-desktop-vfs-win-13.md new file mode 100644 index 00000000000..8d17c2789b5 --- /dev/null +++ b/changelog/2.10.0_2022-01-17/client-desktop-vfs-win-13.md @@ -0,0 +1,6 @@ +Bugfix: We fixed a possible crash + +We fixed a possible crash that could happen during the +initialisation of the vfs plugin. + +https://github.com/owncloud/client-desktop-vfs-win/pull/13 diff --git a/changelog/2.10.1_2022-04-05/9030.md b/changelog/2.10.1_2022-04-05/9030.md new file mode 100644 index 00000000000..9a94738e1f2 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9030.md @@ -0,0 +1,4 @@ +Bugfix: Do not strip trailing whitespace from a file or folder name + +https://github.com/owncloud/client/issues/9030 +https://github.com/owncloud/client/pull/9452 diff --git a/changelog/2.10.1_2022-04-05/9105.md b/changelog/2.10.1_2022-04-05/9105.md new file mode 100644 index 00000000000..a7a24718f63 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9105.md @@ -0,0 +1,7 @@ +Bugfix: Remove outdated translations + +Due to a bug we were not removing the translations that fell below +a required quality margin, we only stopped updating them. +Resulting in even worse translations in some cases. + +https://github.com/owncloud/client/issues/9105 diff --git a/changelog/2.10.1_2022-04-05/9121.md b/changelog/2.10.1_2022-04-05/9121.md new file mode 100644 index 00000000000..0286285df1e --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9121.md @@ -0,0 +1,5 @@ +Bugfix: Progress reporting for TUS uploads + +We fixed a bug with missing progress reporting in TUS uploads + +https://github.com/owncloud/client/issues/9121 diff --git a/changelog/2.10.1_2022-04-05/9147.md b/changelog/2.10.1_2022-04-05/9147.md new file mode 100644 index 00000000000..588345ff514 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9147.md @@ -0,0 +1,7 @@ +Bugfix: Fix crash if a database error occurs + +We no longer crash if a database error occurs on startup, +instead the folder will enter an error sate similar to the case +that the folder does not exist. + +https://github.com/owncloud/client/issues/9147 diff --git a/changelog/2.10.1_2022-04-05/9155.md b/changelog/2.10.1_2022-04-05/9155.md new file mode 100644 index 00000000000..bd2e8310164 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9155.md @@ -0,0 +1,7 @@ +Bugfix: --version showed incorrect information about VFS support + +--version used to always show "Off", even when a VFS plugin was available. +This has been fixed now. + +https://github.com/owncloud/client/issues/9155 +https://github.com/owncloud/client/pull/9457 diff --git a/changelog/2.10.1_2022-04-05/9236.md b/changelog/2.10.1_2022-04-05/9236.md new file mode 100644 index 00000000000..4aab7f8d565 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9236.md @@ -0,0 +1,6 @@ +Bugfix: Client warns about non-existing files + +We fixed a bug where the client warns about ignored files that where added to the DB in previous versions of the client +and do no longer exist. + +https://github.com/owncloud/client/issues/9236 diff --git a/changelog/2.10.1_2022-04-05/9238.md b/changelog/2.10.1_2022-04-05/9238.md new file mode 100644 index 00000000000..9558e568826 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9238.md @@ -0,0 +1,4 @@ +Bugfix: mention source file in activity tab when renaming + +https://github.com/owncloud/client/issues/9238 +https://github.com/owncloud/client/pull/9453 diff --git a/changelog/2.10.1_2022-04-05/9245.md b/changelog/2.10.1_2022-04-05/9245.md new file mode 100644 index 00000000000..fbc66d6cee6 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9245.md @@ -0,0 +1,4 @@ +Enhancement: Retry token refresh multiple times before logout + +https://github.com/owncloud/client/issues/9245 +https://github.com/owncloud/client/pull/9380 diff --git a/changelog/2.10.1_2022-04-05/9304.md b/changelog/2.10.1_2022-04-05/9304.md new file mode 100644 index 00000000000..0621fd00088 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9304.md @@ -0,0 +1,5 @@ +Enhancement: Don't log error when checking removed file for changes + +We removed some misleading error messages from the log. + +https://github.com/owncloud/client/issues/9304 diff --git a/changelog/2.10.1_2022-04-05/9336.md b/changelog/2.10.1_2022-04-05/9336.md new file mode 100644 index 00000000000..9e91b6364df --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9336.md @@ -0,0 +1,11 @@ +Enhancement: Leave password field in share dialog enabled on errors + +The password line edit used to be disabled because the related checkbox was unchecked upon errors +such as failing to satisfy the requirements imposed by the "password policy" server app. + +Now, the checkbox will not be unchecked, leaving the line edit enabled and keeping the focus on +it. This allows users to enter a new password and try again without having to enable the checkbox +and clicking into the line edit again. + +https://github.com/owncloud/client/issues/9336 +https://github.com/owncloud/client/pull/9508 diff --git a/changelog/2.10.1_2022-04-05/9367.md b/changelog/2.10.1_2022-04-05/9367.md new file mode 100644 index 00000000000..e56db6c2b16 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9367.md @@ -0,0 +1,5 @@ +Bugfix: Fix crash on remove account + +We fixed a potential reference to a deleted item, when an account was removed. + +https://github.com/owncloud/client/issues/9367 diff --git a/changelog/2.10.1_2022-04-05/9375.md b/changelog/2.10.1_2022-04-05/9375.md new file mode 100644 index 00000000000..cbf99d53cf0 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9375.md @@ -0,0 +1,5 @@ +Bugfix: Raise ssl issue dialog above the wizard + +Under certain conditions it was possible that the ssl dialog was hidden behind the wizard. + +https://github.com/owncloud/client/pull/9375 diff --git a/changelog/2.10.1_2022-04-05/9381.md b/changelog/2.10.1_2022-04-05/9381.md new file mode 100644 index 00000000000..75db35677a8 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9381.md @@ -0,0 +1,6 @@ +Bugfix: Fallback to ownCloud sidebar icons on Mac if none provided in branding + +If a customer does not provide sidebar icons we use the ownCloud sidebar icons. + + +https://github.com/owncloud/client/pull/9381 diff --git a/changelog/2.10.1_2022-04-05/9382.md b/changelog/2.10.1_2022-04-05/9382.md new file mode 100644 index 00000000000..ba5f5bc2d6e --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9382.md @@ -0,0 +1,5 @@ +Bugfix: Immediately retry upload if file changed during sync + +If a file changed during discovery and the actual upload for multiple retries in a row, changes of it were ignored for a period of time. + +https://github.com/owncloud/client/issues/9382 diff --git a/changelog/2.10.1_2022-04-05/9383.md b/changelog/2.10.1_2022-04-05/9383.md new file mode 100644 index 00000000000..ed58d537c0c --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9383.md @@ -0,0 +1,7 @@ +Bugfix: Don't reset change time on upload + +We fixed a bug where we reset the change time of Windows placeholder files to the value in the database during uploads. +This cold cause other applications to detect non-existing changes in that file. + +https://github.com/owncloud/client/issues/9383 +https://github.com/owncloud/client-desktop-vfs-win/pull/16 diff --git a/changelog/2.10.1_2022-04-05/9387.md b/changelog/2.10.1_2022-04-05/9387.md new file mode 100644 index 00000000000..86f51164667 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9387.md @@ -0,0 +1,7 @@ +Bugfix: Fix toggling launch-on-login for macOS + +This would fail when upgrading the application, and the upgraded version +has one or more letters in the name changed from/to upper-case. + +https://github.com/owncloud/client/issues/9387 +https://github.com/owncloud/client/pull/9433 diff --git a/changelog/2.10.1_2022-04-05/9390.md b/changelog/2.10.1_2022-04-05/9390.md new file mode 100644 index 00000000000..f8c51f718b6 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9390.md @@ -0,0 +1,6 @@ +Bugfix: Fix translated icon names in desktop file with ownBrander themes + +Fixes broken translated icon reference in desktop entries for some branded build themes. + +https://github.com/owncloud/client/pull/9390 + diff --git a/changelog/2.10.1_2022-04-05/9417.md b/changelog/2.10.1_2022-04-05/9417.md new file mode 100644 index 00000000000..61aa80d6537 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9417.md @@ -0,0 +1,5 @@ +Bugfix: Fix possible crash + +We change the initialisation of a Windows icon to prevent a possible crash. + +https://github.com/owncloud/client/issues/9417 diff --git a/changelog/2.10.1_2022-04-05/9420.md b/changelog/2.10.1_2022-04-05/9420.md new file mode 100644 index 00000000000..e7babf24bd8 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9420.md @@ -0,0 +1,7 @@ +Bugfix: add open local/remote folder options + +Add the "open local/remote folder" context menu items for non-sync-root +items back into the accounts tab in the settings dialog. + +https://github.com/owncloud/client/issues/9405 +https://github.com/owncloud/client/pull/9420 diff --git a/changelog/2.10.1_2022-04-05/9425.md b/changelog/2.10.1_2022-04-05/9425.md new file mode 100644 index 00000000000..1c6ca9d4de3 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9425.md @@ -0,0 +1,3 @@ +Bugfix: Fix Account Filter for Server Activity tab + +https://github.com/owncloud/client/pull/9481 diff --git a/changelog/2.10.1_2022-04-05/9437.md b/changelog/2.10.1_2022-04-05/9437.md new file mode 100644 index 00000000000..2cf859ce96a --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9437.md @@ -0,0 +1,6 @@ +Bugfix: Ensure proper setup of network jobs on retries + +On retries network jobs where not properly setup which could +lead to undefined behaviour. + +https://github.com/owncloud/client/pull/9437 diff --git a/changelog/2.10.1_2022-04-05/9460.md b/changelog/2.10.1_2022-04-05/9460.md new file mode 100644 index 00000000000..b707e090f5d --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9460.md @@ -0,0 +1,10 @@ +Enhancement: Provide informal German translations + +The community was maintaining an informal German translation for years but we were only able to +provide a single version of German in the client. +We now ship both versions, the informal can be selected in the combobox in the advanced settings. +To be able to distinguish between formal and informal locales, we also include the locale +identifier in the dropdown (e.g., "Deutsch (de-informal)"). + +https://github.com/owncloud/client/issues/9460 +https://github.com/owncloud/client/pull/9502 diff --git a/changelog/2.10.1_2022-04-05/9489.md b/changelog/2.10.1_2022-04-05/9489.md new file mode 100644 index 00000000000..5951a12fb31 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9489.md @@ -0,0 +1,7 @@ +Bugfix: If required clear cookies in more scenarios + +BigIp F5 requires special cookie handling on our side. +We only explicitly cleared the cookies when we hit an unexpected redirect, +now we will clear them also when refreshing our OAuth token. + +https://github.com/owncloud/client/pull/9489 diff --git a/changelog/2.10.1_2022-04-05/9513.md b/changelog/2.10.1_2022-04-05/9513.md new file mode 100644 index 00000000000..837d7dc994b --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9513.md @@ -0,0 +1,7 @@ +Bugfix: improve filter pop-up menu and button + +- replaced "No filter" option text with "All", to avoid the "No filter is not enabled" situation +- replace the "Filter" label on the button with "1 Filter"/"2 Filters" when a filter is active, so a user can immediately see that without having to open the filter pop-up + +https://github.com/owncloud/client/issues/9425 +https://github.com/owncloud/client/pull/9513 diff --git a/changelog/2.10.1_2022-04-05/9515.md b/changelog/2.10.1_2022-04-05/9515.md new file mode 100644 index 00000000000..e75aecf2baf --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9515.md @@ -0,0 +1,5 @@ +Enhancement: Always flush log when logging to stdout + +We improved the behaviour of logging to a terminal. + +https://github.com/owncloud/client/pull/9515 diff --git a/changelog/2.10.1_2022-04-05/9523.md b/changelog/2.10.1_2022-04-05/9523.md new file mode 100644 index 00000000000..3d53d1045f4 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9523.md @@ -0,0 +1,6 @@ +Enhancement: Added branding parameter to disallow duplicated folder sync pairs + +We added a branding parameter to disallow the addition of duplicated folder sync pairs in +the add folder wizard. + +https://github.com/owncloud/client/issues/9523 diff --git a/changelog/2.10.1_2022-04-05/9525.md b/changelog/2.10.1_2022-04-05/9525.md new file mode 100644 index 00000000000..a3744dad2a4 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9525.md @@ -0,0 +1,7 @@ +Enhancement: Retry update after 10 minutes + +When an update (check) fails, it is currently retried only when the regular timeout (10 hours +by default) is triggered. With this change, we retry the update (check) after 10 minutes already. + +https://github.com/owncloud/client/issues/9522 +https://github.com/owncloud/client/pull/9525 diff --git a/changelog/2.10.1_2022-04-05/9533.md b/changelog/2.10.1_2022-04-05/9533.md new file mode 100644 index 00000000000..4bb8fc43931 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9533.md @@ -0,0 +1,4 @@ +Bugfix: Possible crash when removing non-existing folder + + +https://github.com/owncloud/client/issues/9533 diff --git a/changelog/2.10.1_2022-04-05/9545.md b/changelog/2.10.1_2022-04-05/9545.md new file mode 100644 index 00000000000..e7c3835f9b0 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/9545.md @@ -0,0 +1,5 @@ +Bugfix: Can't stop basic auth login + +We fixed a bug where the user was asked for their credentials again and again with no chance to abort. + +https://github.com/owncloud/client/issues/9545 diff --git a/changelog/2.10.1_2022-04-05/client-desktop-vfs-win-18.md b/changelog/2.10.1_2022-04-05/client-desktop-vfs-win-18.md new file mode 100644 index 00000000000..51f48061050 --- /dev/null +++ b/changelog/2.10.1_2022-04-05/client-desktop-vfs-win-18.md @@ -0,0 +1,5 @@ +Bugfix: Fix potential download failure for renamed file with Windows VFS + +When a dehydrated file is renamed and immediately opened, the subsequent download might try to create a file with the original (un-renamed) name. + +https://github.com/owncloud/client-desktop-vfs-win/pull/18 diff --git a/changelog/2.10.1_2022-04-05/client-desktop-vfs-win-21.md b/changelog/2.10.1_2022-04-05/client-desktop-vfs-win-21.md new file mode 100644 index 00000000000..a3a693d8e0a --- /dev/null +++ b/changelog/2.10.1_2022-04-05/client-desktop-vfs-win-21.md @@ -0,0 +1,5 @@ +Bugfix: Possible crash when downloading a virtual file on Windows + +We fixed a bug that might have caused crashes when working with virtual files on Windows. + +https://github.com/owncloud/client-desktop-vfs-win/pull/21 diff --git a/changelog/2.10.1_2022-04-05/enterprise-5052.md b/changelog/2.10.1_2022-04-05/enterprise-5052.md new file mode 100644 index 00000000000..57728bc5c6f --- /dev/null +++ b/changelog/2.10.1_2022-04-05/enterprise-5052.md @@ -0,0 +1,6 @@ +Bugfix: Don't publish upload if we can't finish the transaction in the client + +When a file gets locked during an upload we aborted after the upload finished on the server. +Resulting in a divergence of the local and remote state which could lead to conflicts. + +https://github.com/owncloud/enterprise/issues/5052 diff --git a/changelog/2.11.0_2022-08-18/5174.md b/changelog/2.11.0_2022-08-18/5174.md new file mode 100644 index 00000000000..fd7d9c3a4b0 --- /dev/null +++ b/changelog/2.11.0_2022-08-18/5174.md @@ -0,0 +1,5 @@ +Bugfix: Compare usernames case-insensitive + +We fixed a bug where the user name was compared with the name provided by the server in a case-sensitive way. + +https://github.com/owncloud/enterprise/issues/5174 diff --git a/changelog/2.11.0_2022-08-18/9571.md b/changelog/2.11.0_2022-08-18/9571.md new file mode 100644 index 00000000000..07f5f5a2326 --- /dev/null +++ b/changelog/2.11.0_2022-08-18/9571.md @@ -0,0 +1,5 @@ +Bugfix: Use UTF-8 for .owncloudsync.log + +We fixed a bug where Unicode file names were not correctly displayed in .owncloudsync.log. + +https://github.com/owncloud/client/pull/9571 diff --git a/changelog/2.11.0_2022-08-18/9600.md b/changelog/2.11.0_2022-08-18/9600.md new file mode 100644 index 00000000000..f78da0a9e8c --- /dev/null +++ b/changelog/2.11.0_2022-08-18/9600.md @@ -0,0 +1,5 @@ +Bugfix: Crash when interacting with a folder in an error state + +We fixed a crash when using the context menu on a folder that encountered an error and was not using virtual files. + +https://github.com/owncloud/client/issues/9600 diff --git a/changelog/2.11.0_2022-08-18/9791.md b/changelog/2.11.0_2022-08-18/9791.md new file mode 100644 index 00000000000..d8506bd65ba --- /dev/null +++ b/changelog/2.11.0_2022-08-18/9791.md @@ -0,0 +1,5 @@ +Bugfix: Database was recreated after its removal + +We fixed a bug where the database was recreated during the removal of a sync folder connection. + +https://github.com/owncloud/client/issues/9791 diff --git a/changelog/2.11.0_2022-08-18/9832.md b/changelog/2.11.0_2022-08-18/9832.md new file mode 100644 index 00000000000..c4ad20ca6c8 --- /dev/null +++ b/changelog/2.11.0_2022-08-18/9832.md @@ -0,0 +1,7 @@ +Enhancement: Throttle the UI updates during sync + +We reduced the number of UI updates during the sync, +especially with Windows vfs files this should improve the performance by a lot. + +https://github.com/owncloud/client/issues/9832 +https://github.com/owncloud/client/pull/9863 diff --git a/changelog/2.11.0_2022-08-18/9836.md b/changelog/2.11.0_2022-08-18/9836.md new file mode 100644 index 00000000000..f66399aa476 --- /dev/null +++ b/changelog/2.11.0_2022-08-18/9836.md @@ -0,0 +1,6 @@ +Enhancement: Run vfs downloads with a high priority + +This should reduce the probability for timeouts when downloading vfs files in the Windows explorer. + +https://github.com/owncloud/client/pull/9836 +https://github.com/owncloud/client/issues/9832 diff --git a/changelog/2.11.0_2022-08-18/9864.md b/changelog/2.11.0_2022-08-18/9864.md new file mode 100644 index 00000000000..08dcd8fb5ee --- /dev/null +++ b/changelog/2.11.0_2022-08-18/9864.md @@ -0,0 +1,3 @@ +Bugfix: We fixed a potential crash + +https://github.com/owncloud/client/issues/9864 diff --git a/changelog/2.11.0_2022-08-18/9956.md b/changelog/2.11.0_2022-08-18/9956.md new file mode 100644 index 00000000000..58a9107a432 --- /dev/null +++ b/changelog/2.11.0_2022-08-18/9956.md @@ -0,0 +1,8 @@ +Enhancement: Don't abort sync if a user requests a file + +Previously we aborted any running sync if a user requested a file that was not yet available locally. +This was done to ensure the user does not need to wait for the current sync to finish. +However, in today's code both actions the download and the sync can run in parallel. + +https://github.com/owncloud/client/pull/9956 +https://github.com/owncloud/client/issues/9832 diff --git a/changelog/2.11.0_2022-08-18/9966.md b/changelog/2.11.0_2022-08-18/9966.md new file mode 100644 index 00000000000..8cb42ae162c --- /dev/null +++ b/changelog/2.11.0_2022-08-18/9966.md @@ -0,0 +1,5 @@ +Bugfix: Windows VFS: Files in an existing folder are dehydrated + +We fixed a bug, when a user selects an existing folder as sync root we previously dehydrated all existing files. + +https://github.com/owncloud/client/pull/9966 diff --git a/changelog/2.11.0_2022-08-18/9969.md b/changelog/2.11.0_2022-08-18/9969.md new file mode 100644 index 00000000000..fa7afbfde4e --- /dev/null +++ b/changelog/2.11.0_2022-08-18/9969.md @@ -0,0 +1,5 @@ +Bugfix: Run next scheduled sync after a folder was removed + +We fixed a bug where we did not start another sync when a folder that was currently syncing was removed. + +https://github.com/owncloud/client/issues/9969 diff --git a/changelog/2.11.0_2022-08-18/client-desktop-vfs-win-34.md b/changelog/2.11.0_2022-08-18/client-desktop-vfs-win-34.md new file mode 100644 index 00000000000..5ed7e873ee7 --- /dev/null +++ b/changelog/2.11.0_2022-08-18/client-desktop-vfs-win-34.md @@ -0,0 +1,6 @@ +Bugfix: Windows VFS: Keep file attributes and pin state + +When a user selected "Always keep on this device" on a cloud only file, we lost that information. +"Always keep on this device" only worked on already present files. + +https://github.com/owncloud/client-desktop-vfs-win/pull/34 diff --git a/changelog/2.11.1_2022-08-31/10058.md b/changelog/2.11.1_2022-08-31/10058.md new file mode 100644 index 00000000000..22520018ee3 --- /dev/null +++ b/changelog/2.11.1_2022-08-31/10058.md @@ -0,0 +1,7 @@ +Bugfix: Fix configuration of selective sync from account settings + +We fixed a bug that prevented the directory tree in the account settings window from being expanded beyond the root directory level. +The problem was introduced in 8d0dd36d2. + +https://github.com/owncloud/client/pull/10058 +https://github.com/owncloud/client/pull/10065 diff --git a/changelog/2.6.2_2020-02-21/7755 b/changelog/2.6.2_2020-02-21/7755.md similarity index 100% rename from changelog/2.6.2_2020-02-21/7755 rename to changelog/2.6.2_2020-02-21/7755.md diff --git a/changelog/2.6.3_2020-06-10/3922 b/changelog/2.6.3_2020-06-10/3922.md similarity index 100% rename from changelog/2.6.3_2020-06-10/3922 rename to changelog/2.6.3_2020-06-10/3922.md diff --git a/changelog/2.6.3_2020-06-10/7018 b/changelog/2.6.3_2020-06-10/7018.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7018 rename to changelog/2.6.3_2020-06-10/7018.md diff --git a/changelog/2.6.3_2020-06-10/7043 b/changelog/2.6.3_2020-06-10/7043.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7043 rename to changelog/2.6.3_2020-06-10/7043.md diff --git a/changelog/2.6.3_2020-06-10/7247 b/changelog/2.6.3_2020-06-10/7247.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7247 rename to changelog/2.6.3_2020-06-10/7247.md diff --git a/changelog/2.6.3_2020-06-10/7313 b/changelog/2.6.3_2020-06-10/7313.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7313 rename to changelog/2.6.3_2020-06-10/7313.md diff --git a/changelog/2.6.3_2020-06-10/7336 b/changelog/2.6.3_2020-06-10/7336 deleted file mode 100644 index 2ceeeffbab1..00000000000 --- a/changelog/2.6.3_2020-06-10/7336 +++ /dev/null @@ -1,6 +0,0 @@ -Bugfix: Expand file tree also when no folders are synced - -We have fixed the behaviour of the folder Widget, -when a account was added and none of its folders was selected to be synced, the tree was not expanded. - -https://github.com/owncloud/client/issues/7336 diff --git a/changelog/2.6.3_2020-06-10/7336.md b/changelog/2.6.3_2020-06-10/7336.md new file mode 100644 index 00000000000..9a852cd0294 --- /dev/null +++ b/changelog/2.6.3_2020-06-10/7336.md @@ -0,0 +1,6 @@ +Bugfix: Expand file tree also when no folders are synced + +We have fixed the behaviour of the folder Widget, +when an account was added and none of its folders was selected to be synced, the tree was not expanded. + +https://github.com/owncloud/client/issues/7336 diff --git a/changelog/2.6.3_2020-06-10/7700 b/changelog/2.6.3_2020-06-10/7700.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7700 rename to changelog/2.6.3_2020-06-10/7700.md diff --git a/changelog/2.6.3_2020-06-10/7709 b/changelog/2.6.3_2020-06-10/7709.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7709 rename to changelog/2.6.3_2020-06-10/7709.md diff --git a/changelog/2.6.3_2020-06-10/7710 b/changelog/2.6.3_2020-06-10/7710.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7710 rename to changelog/2.6.3_2020-06-10/7710.md diff --git a/changelog/2.6.3_2020-06-10/7722 b/changelog/2.6.3_2020-06-10/7722.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7722 rename to changelog/2.6.3_2020-06-10/7722.md diff --git a/changelog/2.6.3_2020-06-10/7741 b/changelog/2.6.3_2020-06-10/7741.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7741 rename to changelog/2.6.3_2020-06-10/7741.md diff --git a/changelog/2.6.3_2020-06-10/7744 b/changelog/2.6.3_2020-06-10/7744.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7744 rename to changelog/2.6.3_2020-06-10/7744.md diff --git a/changelog/2.6.3_2020-06-10/7759 b/changelog/2.6.3_2020-06-10/7759.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7759 rename to changelog/2.6.3_2020-06-10/7759.md diff --git a/changelog/2.6.3_2020-06-10/7760 b/changelog/2.6.3_2020-06-10/7760 deleted file mode 100644 index 6f50007518b..00000000000 --- a/changelog/2.6.3_2020-06-10/7760 +++ /dev/null @@ -1,4 +0,0 @@ -Bugfix: Fixed a potential crash on cancelation of discovery jobs - - -https://github.com/owncloud/client/pull/7760 diff --git a/changelog/2.6.3_2020-06-10/7760.md b/changelog/2.6.3_2020-06-10/7760.md new file mode 100644 index 00000000000..6dc8999f0c4 --- /dev/null +++ b/changelog/2.6.3_2020-06-10/7760.md @@ -0,0 +1,4 @@ +Bugfix: Fixed a potential crash on cancellation of discovery jobs + + +https://github.com/owncloud/client/pull/7760 diff --git a/changelog/2.6.3_2020-06-10/7761 b/changelog/2.6.3_2020-06-10/7761.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7761 rename to changelog/2.6.3_2020-06-10/7761.md diff --git a/changelog/2.6.3_2020-06-10/7762 b/changelog/2.6.3_2020-06-10/7762.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7762 rename to changelog/2.6.3_2020-06-10/7762.md diff --git a/changelog/2.6.3_2020-06-10/7774 b/changelog/2.6.3_2020-06-10/7774 deleted file mode 100644 index 041cf3772ef..00000000000 --- a/changelog/2.6.3_2020-06-10/7774 +++ /dev/null @@ -1,5 +0,0 @@ -Bugfix: On Windows the share dialog does not open as the top most window - -We now ensure that the our dialogs are correctly raised. - -https://github.com/owncloud/client/issues/7774 diff --git a/changelog/2.6.3_2020-06-10/7774.md b/changelog/2.6.3_2020-06-10/7774.md new file mode 100644 index 00000000000..c849fbc47be --- /dev/null +++ b/changelog/2.6.3_2020-06-10/7774.md @@ -0,0 +1,5 @@ +Bugfix: On Windows the share dialog does not open as the top most window + +We now ensure that our dialogs are correctly raised. + +https://github.com/owncloud/client/issues/7774 diff --git a/changelog/2.6.3_2020-06-10/7799 b/changelog/2.6.3_2020-06-10/7799.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7799 rename to changelog/2.6.3_2020-06-10/7799.md diff --git a/changelog/2.6.3_2020-06-10/7831 b/changelog/2.6.3_2020-06-10/7831 deleted file mode 100644 index 56a11bd2925..00000000000 --- a/changelog/2.6.3_2020-06-10/7831 +++ /dev/null @@ -1,6 +0,0 @@ -Bugfix: Don't override cookies with old values - -We fixed a bug where a client somteimes overrode the content of the cookie jar -with outdated or corrupted values - -https://github.com/owncloud/client/pull/7831 diff --git a/changelog/2.6.3_2020-06-10/7831.md b/changelog/2.6.3_2020-06-10/7831.md new file mode 100644 index 00000000000..e1234630b3b --- /dev/null +++ b/changelog/2.6.3_2020-06-10/7831.md @@ -0,0 +1,6 @@ +Bugfix: Don't override cookies with old values + +We fixed a bug where a client sometimes overrode the content of the cookie jar +with outdated or corrupted values + +https://github.com/owncloud/client/pull/7831 diff --git a/changelog/2.6.3_2020-06-10/7856 b/changelog/2.6.3_2020-06-10/7856.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7856 rename to changelog/2.6.3_2020-06-10/7856.md diff --git a/changelog/2.6.3_2020-06-10/7874 b/changelog/2.6.3_2020-06-10/7874.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7874 rename to changelog/2.6.3_2020-06-10/7874.md diff --git a/changelog/2.6.3_2020-06-10/7878 b/changelog/2.6.3_2020-06-10/7878.md similarity index 100% rename from changelog/2.6.3_2020-06-10/7878 rename to changelog/2.6.3_2020-06-10/7878.md diff --git a/changelog/2.7.0_2020-11-13/3839 b/changelog/2.7.0_2020-11-13/3839.md similarity index 100% rename from changelog/2.7.0_2020-11-13/3839 rename to changelog/2.7.0_2020-11-13/3839.md diff --git a/changelog/2.7.0_2020-11-13/7356 b/changelog/2.7.0_2020-11-13/7356.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7356 rename to changelog/2.7.0_2020-11-13/7356.md diff --git a/changelog/2.7.0_2020-11-13/7724 b/changelog/2.7.0_2020-11-13/7724.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7724 rename to changelog/2.7.0_2020-11-13/7724.md diff --git a/changelog/2.7.0_2020-11-13/7748 b/changelog/2.7.0_2020-11-13/7748.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7748 rename to changelog/2.7.0_2020-11-13/7748.md diff --git a/changelog/2.7.0_2020-11-13/7749 b/changelog/2.7.0_2020-11-13/7749.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7749 rename to changelog/2.7.0_2020-11-13/7749.md diff --git a/changelog/2.7.0_2020-11-13/7779 b/changelog/2.7.0_2020-11-13/7779.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7779 rename to changelog/2.7.0_2020-11-13/7779.md diff --git a/changelog/2.7.0_2020-11-13/7833 b/changelog/2.7.0_2020-11-13/7833.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7833 rename to changelog/2.7.0_2020-11-13/7833.md diff --git a/changelog/2.7.0_2020-11-13/7862 b/changelog/2.7.0_2020-11-13/7862 deleted file mode 100644 index 22498681bfd..00000000000 --- a/changelog/2.7.0_2020-11-13/7862 +++ /dev/null @@ -1,5 +0,0 @@ -Change: The client uploads chunks even though the server repports lack of support - -We now correctly handle the bigfilechunking capability - -https://github.com/owncloud/client/issues/7862 diff --git a/changelog/2.7.0_2020-11-13/7862.md b/changelog/2.7.0_2020-11-13/7862.md new file mode 100644 index 00000000000..46a902820a1 --- /dev/null +++ b/changelog/2.7.0_2020-11-13/7862.md @@ -0,0 +1,5 @@ +Change: The client uploads chunks even though the server reports lack of support + +We now correctly handle the `bigfilechunking` capability + +https://github.com/owncloud/client/issues/7862 diff --git a/changelog/2.7.0_2020-11-13/7868 b/changelog/2.7.0_2020-11-13/7868.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7868 rename to changelog/2.7.0_2020-11-13/7868.md diff --git a/changelog/2.7.0_2020-11-13/7873 b/changelog/2.7.0_2020-11-13/7873.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7873 rename to changelog/2.7.0_2020-11-13/7873.md diff --git a/changelog/2.7.0_2020-11-13/7877 b/changelog/2.7.0_2020-11-13/7877 deleted file mode 100644 index 15b28c1bb2e..00000000000 --- a/changelog/2.7.0_2020-11-13/7877 +++ /dev/null @@ -1,6 +0,0 @@ -Bugfix: Some check boxes in the sharing dialog are disabled - -We fixed a bug where the servers default sharing permissions -where used as limiting factor instead of a defualt selection. - -https://github.com/owncloud/client/issues/7877 diff --git a/changelog/2.7.0_2020-11-13/7877.md b/changelog/2.7.0_2020-11-13/7877.md new file mode 100644 index 00000000000..d2a37740f4a --- /dev/null +++ b/changelog/2.7.0_2020-11-13/7877.md @@ -0,0 +1,6 @@ +Bugfix: Some check boxes in the sharing dialog are disabled + +We fixed a bug where the servers default sharing permissions +where used as limiting factor instead of a default selection. + +https://github.com/owncloud/client/issues/7877 diff --git a/changelog/2.7.0_2020-11-13/7881 b/changelog/2.7.0_2020-11-13/7881.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7881 rename to changelog/2.7.0_2020-11-13/7881.md diff --git a/changelog/2.7.0_2020-11-13/7920 b/changelog/2.7.0_2020-11-13/7920 deleted file mode 100644 index 7193323b75f..00000000000 --- a/changelog/2.7.0_2020-11-13/7920 +++ /dev/null @@ -1,6 +0,0 @@ -Change: The layout of serveral ui elements is broken - -We replace an old layout mechanism with a more advanced one. - -https://github.com/owncloud/client/issues/7920 -https://github.com/owncloud/client/issues/7941 diff --git a/changelog/2.7.0_2020-11-13/7920.md b/changelog/2.7.0_2020-11-13/7920.md new file mode 100644 index 00000000000..c00a21a4ae6 --- /dev/null +++ b/changelog/2.7.0_2020-11-13/7920.md @@ -0,0 +1,6 @@ +Change: The layout of several ui elements is broken + +We replace an old layout mechanism with a more advanced one. + +https://github.com/owncloud/client/issues/7920 +https://github.com/owncloud/client/issues/7941 diff --git a/changelog/2.7.0_2020-11-13/7922 b/changelog/2.7.0_2020-11-13/7922 deleted file mode 100644 index c7a06af3a3a..00000000000 --- a/changelog/2.7.0_2020-11-13/7922 +++ /dev/null @@ -1,6 +0,0 @@ -Change: The client hides the window during the authentication process - -We changed the confusing behavioir and now minimize ownCloud instead. -This ensures that the window stays accessible. - -https://github.com/owncloud/client/pull/7922 diff --git a/changelog/2.7.0_2020-11-13/7922.md b/changelog/2.7.0_2020-11-13/7922.md new file mode 100644 index 00000000000..ec6c8af798a --- /dev/null +++ b/changelog/2.7.0_2020-11-13/7922.md @@ -0,0 +1,6 @@ +Change: The client hides the window during the authentication process + +We changed the confusing behaviour and now minimize ownCloud instead. +This ensures that the window stays accessible. + +https://github.com/owncloud/client/pull/7922 diff --git a/changelog/2.7.0_2020-11-13/7925 b/changelog/2.7.0_2020-11-13/7925.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7925 rename to changelog/2.7.0_2020-11-13/7925.md diff --git a/changelog/2.7.0_2020-11-13/7962 b/changelog/2.7.0_2020-11-13/7962 deleted file mode 100644 index fdb197bc6a0..00000000000 --- a/changelog/2.7.0_2020-11-13/7962 +++ /dev/null @@ -1,5 +0,0 @@ -Change: Reorgenize Settings - -We rename "General" to "Settings" and move the "Network" into "Settings" - -https://github.com/owncloud/client/pull/7962 diff --git a/changelog/2.7.0_2020-11-13/7962.md b/changelog/2.7.0_2020-11-13/7962.md new file mode 100644 index 00000000000..760f8f941b8 --- /dev/null +++ b/changelog/2.7.0_2020-11-13/7962.md @@ -0,0 +1,5 @@ +Change: Reorganize Settings + +We rename "General" to "Settings" and move the "Network" into "Settings" + +https://github.com/owncloud/client/pull/7962 diff --git a/changelog/2.7.0_2020-11-13/7976 b/changelog/2.7.0_2020-11-13/7976.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7976 rename to changelog/2.7.0_2020-11-13/7976.md diff --git a/changelog/2.7.0_2020-11-13/7989 b/changelog/2.7.0_2020-11-13/7989.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7989 rename to changelog/2.7.0_2020-11-13/7989.md diff --git a/changelog/2.7.0_2020-11-13/7999 b/changelog/2.7.0_2020-11-13/7999.md similarity index 100% rename from changelog/2.7.0_2020-11-13/7999 rename to changelog/2.7.0_2020-11-13/7999.md diff --git a/changelog/2.7.0_2020-11-13/8018 b/changelog/2.7.0_2020-11-13/8018 deleted file mode 100644 index 0015fb35af4..00000000000 --- a/changelog/2.7.0_2020-11-13/8018 +++ /dev/null @@ -1,7 +0,0 @@ -Change: Move "Choose what to sync" to sync everything settings - -While selective sync is a feature only available when everything is synced, -the the option had its own radio button. -We now moved the button to the other sync everything related settings. - -https://github.com/owncloud/client/pull/8018 diff --git a/changelog/2.7.0_2020-11-13/8018.md b/changelog/2.7.0_2020-11-13/8018.md new file mode 100644 index 00000000000..d9dad3abe6d --- /dev/null +++ b/changelog/2.7.0_2020-11-13/8018.md @@ -0,0 +1,7 @@ +Change: Move "Choose what to sync" to sync everything settings + +While selective sync is a feature only available when everything is synced, +the option had its own radio button. +We now moved the button to the other sync everything related settings. + +https://github.com/owncloud/client/pull/8018 diff --git a/changelog/2.7.0_2020-11-13/8019 b/changelog/2.7.0_2020-11-13/8019.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8019 rename to changelog/2.7.0_2020-11-13/8019.md diff --git a/changelog/2.7.0_2020-11-13/8020 b/changelog/2.7.0_2020-11-13/8020.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8020 rename to changelog/2.7.0_2020-11-13/8020.md diff --git a/changelog/2.7.0_2020-11-13/8027 b/changelog/2.7.0_2020-11-13/8027.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8027 rename to changelog/2.7.0_2020-11-13/8027.md diff --git a/changelog/2.7.0_2020-11-13/8038 b/changelog/2.7.0_2020-11-13/8038.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8038 rename to changelog/2.7.0_2020-11-13/8038.md diff --git a/changelog/2.7.0_2020-11-13/8042 b/changelog/2.7.0_2020-11-13/8042.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8042 rename to changelog/2.7.0_2020-11-13/8042.md diff --git a/changelog/2.7.0_2020-11-13/8050 b/changelog/2.7.0_2020-11-13/8050.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8050 rename to changelog/2.7.0_2020-11-13/8050.md diff --git a/changelog/2.7.0_2020-11-13/8051 b/changelog/2.7.0_2020-11-13/8051 deleted file mode 100644 index d3963f9c242..00000000000 --- a/changelog/2.7.0_2020-11-13/8051 +++ /dev/null @@ -1,5 +0,0 @@ -Bugfix: The back button on the advanced wizard page no longer gets enabled - -We fixed a bug where the back button in the advanced wizard page get re enabled. - -https://github.com/owncloud/client/issues/8051 diff --git a/changelog/2.7.0_2020-11-13/8051.md b/changelog/2.7.0_2020-11-13/8051.md new file mode 100644 index 00000000000..4465db391d0 --- /dev/null +++ b/changelog/2.7.0_2020-11-13/8051.md @@ -0,0 +1,5 @@ +Bugfix: The back button on the advanced wizard page no longer gets enabled + +We fixed a bug where the back button in the advanced wizard page get re-enabled. + +https://github.com/owncloud/client/issues/8051 diff --git a/changelog/2.7.0_2020-11-13/8064 b/changelog/2.7.0_2020-11-13/8064.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8064 rename to changelog/2.7.0_2020-11-13/8064.md diff --git a/changelog/2.7.0_2020-11-13/8066 b/changelog/2.7.0_2020-11-13/8066.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8066 rename to changelog/2.7.0_2020-11-13/8066.md diff --git a/changelog/2.7.0_2020-11-13/8085 b/changelog/2.7.0_2020-11-13/8085.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8085 rename to changelog/2.7.0_2020-11-13/8085.md diff --git a/changelog/2.7.0_2020-11-13/8104 b/changelog/2.7.0_2020-11-13/8104.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8104 rename to changelog/2.7.0_2020-11-13/8104.md diff --git a/changelog/2.7.0_2020-11-13/8127 b/changelog/2.7.0_2020-11-13/8127 deleted file mode 100644 index 5c8ecaea876..00000000000 --- a/changelog/2.7.0_2020-11-13/8127 +++ /dev/null @@ -1,8 +0,0 @@ -Change: Remove update channels from the ownCloud client - -To ensure a maximum of stability user should not replace their productive client with a preview build. -For that exact reason we offer tespilotcloud clients, they can be installed in parallel and updated via the beta channel. - -If a user is more adventures than the average a manual install of a preview is always possible. - -https://github.com/owncloud/client/issues/8127 diff --git a/changelog/2.7.0_2020-11-13/8127.md b/changelog/2.7.0_2020-11-13/8127.md new file mode 100644 index 00000000000..09bbe66f2dd --- /dev/null +++ b/changelog/2.7.0_2020-11-13/8127.md @@ -0,0 +1,8 @@ +Change: Remove update channels from the ownCloud client + +To ensure a maximum of stability user should not replace their productive client with a preview build. +For that exact reason we offer testpilotcloud clients, they can be installed in parallel and updated via the beta channel. + +If a user is more adventurous than the average a manual installation of a preview is always possible. + +https://github.com/owncloud/client/issues/8127 diff --git a/changelog/2.7.0_2020-11-13/8131 b/changelog/2.7.0_2020-11-13/8131.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8131 rename to changelog/2.7.0_2020-11-13/8131.md diff --git a/changelog/2.7.0_2020-11-13/8169 b/changelog/2.7.0_2020-11-13/8169.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8169 rename to changelog/2.7.0_2020-11-13/8169.md diff --git a/changelog/2.7.0_2020-11-13/8170 b/changelog/2.7.0_2020-11-13/8170.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8170 rename to changelog/2.7.0_2020-11-13/8170.md diff --git a/changelog/2.7.0_2020-11-13/8187 b/changelog/2.7.0_2020-11-13/8187.md similarity index 100% rename from changelog/2.7.0_2020-11-13/8187 rename to changelog/2.7.0_2020-11-13/8187.md diff --git a/changelog/2.7.0_2020-11-13/pr-7509 b/changelog/2.7.0_2020-11-13/pr-7509.md similarity index 100% rename from changelog/2.7.0_2020-11-13/pr-7509 rename to changelog/2.7.0_2020-11-13/pr-7509.md diff --git a/changelog/2.7.0_2020-11-13/product-19 b/changelog/2.7.0_2020-11-13/product-19 deleted file mode 100644 index 4a36145dff8..00000000000 --- a/changelog/2.7.0_2020-11-13/product-19 +++ /dev/null @@ -1,7 +0,0 @@ -Change: Add support for the TUS resumeable upload protocol - -With the support of the TUS protocol we are now able to easily and reliably -upload files to ocis. - - -https://github.com/owncloud/product/issues/19 diff --git a/changelog/2.7.0_2020-11-13/product-19.md b/changelog/2.7.0_2020-11-13/product-19.md new file mode 100644 index 00000000000..cf5e5028567 --- /dev/null +++ b/changelog/2.7.0_2020-11-13/product-19.md @@ -0,0 +1,7 @@ +Change: Add support for the TUS resume-able upload protocol + +With the support of the TUS protocol we are now able to easily and reliably +upload files to oCIS. + + +https://github.com/owncloud/product/issues/19 diff --git a/changelog/2.7.1_2020-11-18/8229 b/changelog/2.7.1_2020-11-18/8229.md similarity index 100% rename from changelog/2.7.1_2020-11-18/8229 rename to changelog/2.7.1_2020-11-18/8229.md diff --git a/changelog/2.7.1_2020-11-18/8232 b/changelog/2.7.1_2020-11-18/8232.md similarity index 100% rename from changelog/2.7.1_2020-11-18/8232 rename to changelog/2.7.1_2020-11-18/8232.md diff --git a/changelog/2.7.2_2020-12-02/8244 b/changelog/2.7.2_2020-12-02/8244.md similarity index 100% rename from changelog/2.7.2_2020-12-02/8244 rename to changelog/2.7.2_2020-12-02/8244.md diff --git a/changelog/2.7.2_2020-12-02/8248 b/changelog/2.7.2_2020-12-02/8248 deleted file mode 100644 index dec7ebd57fe..00000000000 --- a/changelog/2.7.2_2020-12-02/8248 +++ /dev/null @@ -1,6 +0,0 @@ -Bugfix: Sync small plaintext files with Windows VFS - -We fixed a bug where small plaintext files where not synced -due to a broken interity check. - -https://github.com/owncloud/client/issues/8248 diff --git a/changelog/2.7.2_2020-12-02/8248.md b/changelog/2.7.2_2020-12-02/8248.md new file mode 100644 index 00000000000..ad0f2b5cdfd --- /dev/null +++ b/changelog/2.7.2_2020-12-02/8248.md @@ -0,0 +1,6 @@ +Bugfix: Sync small plaintext files with Windows VFS + +We fixed a bug where small plaintext files where not synced +due to a broken integrity check. + +https://github.com/owncloud/client/issues/8248 diff --git a/changelog/2.7.2_2020-12-02/8258 b/changelog/2.7.2_2020-12-02/8258.md similarity index 100% rename from changelog/2.7.2_2020-12-02/8258 rename to changelog/2.7.2_2020-12-02/8258.md diff --git a/changelog/2.7.2_2020-12-02/8263 b/changelog/2.7.2_2020-12-02/8263.md similarity index 100% rename from changelog/2.7.2_2020-12-02/8263 rename to changelog/2.7.2_2020-12-02/8263.md diff --git a/changelog/2.7.3_2020-12-11/8294 b/changelog/2.7.3_2020-12-11/8294.md similarity index 100% rename from changelog/2.7.3_2020-12-11/8294 rename to changelog/2.7.3_2020-12-11/8294.md diff --git a/changelog/2.7.4_2020-12-21/4329 b/changelog/2.7.4_2020-12-21/4329.md similarity index 100% rename from changelog/2.7.4_2020-12-21/4329 rename to changelog/2.7.4_2020-12-21/4329.md diff --git a/changelog/2.7.4_2020-12-21/7672 b/changelog/2.7.4_2020-12-21/7672 deleted file mode 100644 index cd6caadac5b..00000000000 --- a/changelog/2.7.4_2020-12-21/7672 +++ /dev/null @@ -1,6 +0,0 @@ -Bugfix: Update Windows launch on start entry - -We fixed a bug where launch on start did not work after a re install -to a new location. - -https://github.com/owncloud/client/issues/7672 diff --git a/changelog/2.7.4_2020-12-21/7672.md b/changelog/2.7.4_2020-12-21/7672.md new file mode 100644 index 00000000000..9cb80e16f91 --- /dev/null +++ b/changelog/2.7.4_2020-12-21/7672.md @@ -0,0 +1,6 @@ +Bugfix: Update Windows launch on start entry + +We fixed a bug where launch on start did not work after a re-install +to a new location. + +https://github.com/owncloud/client/issues/7672 diff --git a/changelog/2.7.4_2020-12-21/8289 b/changelog/2.7.4_2020-12-21/8289.md similarity index 100% rename from changelog/2.7.4_2020-12-21/8289 rename to changelog/2.7.4_2020-12-21/8289.md diff --git a/changelog/2.7.4_2020-12-21/8313 b/changelog/2.7.4_2020-12-21/8313.md similarity index 100% rename from changelog/2.7.4_2020-12-21/8313 rename to changelog/2.7.4_2020-12-21/8313.md diff --git a/changelog/2.7.5_2021-01-28/4363 b/changelog/2.7.5_2021-01-28/4363.md similarity index 100% rename from changelog/2.7.5_2021-01-28/4363 rename to changelog/2.7.5_2021-01-28/4363.md diff --git a/changelog/2.7.5_2021-01-28/8149 b/changelog/2.7.5_2021-01-28/8149.md similarity index 100% rename from changelog/2.7.5_2021-01-28/8149 rename to changelog/2.7.5_2021-01-28/8149.md diff --git a/changelog/2.7.5_2021-01-28/8314 b/changelog/2.7.5_2021-01-28/8314.md similarity index 100% rename from changelog/2.7.5_2021-01-28/8314 rename to changelog/2.7.5_2021-01-28/8314.md diff --git a/changelog/2.7.5_2021-01-28/8323 b/changelog/2.7.5_2021-01-28/8323.md similarity index 100% rename from changelog/2.7.5_2021-01-28/8323 rename to changelog/2.7.5_2021-01-28/8323.md diff --git a/changelog/2.7.5_2021-01-28/8326 b/changelog/2.7.5_2021-01-28/8326.md similarity index 100% rename from changelog/2.7.5_2021-01-28/8326 rename to changelog/2.7.5_2021-01-28/8326.md diff --git a/changelog/2.7.5_2021-01-28/8349 b/changelog/2.7.5_2021-01-28/8349.md similarity index 100% rename from changelog/2.7.5_2021-01-28/8349 rename to changelog/2.7.5_2021-01-28/8349.md diff --git a/changelog/2.7.5_2021-01-28/8390 b/changelog/2.7.5_2021-01-28/8390.md similarity index 100% rename from changelog/2.7.5_2021-01-28/8390 rename to changelog/2.7.5_2021-01-28/8390.md diff --git a/changelog/2.7.6_2021-02-04/4387 b/changelog/2.7.6_2021-02-04/4387.md similarity index 100% rename from changelog/2.7.6_2021-02-04/4387 rename to changelog/2.7.6_2021-02-04/4387.md diff --git a/changelog/2.7.6_2021-02-04/8371 b/changelog/2.7.6_2021-02-04/8371 deleted file mode 100644 index 7849dfb1ce2..00000000000 --- a/changelog/2.7.6_2021-02-04/8371 +++ /dev/null @@ -1,7 +0,0 @@ -Bugfix: Case sensitive comparison of checksum algorithm - -We fixed a bug where the checksum detection was case sensitive -and used a different casing than the server. - -https://github.com/owncloud/client/pull/8371 -https://github.com/owncloud/client/pull/8376 diff --git a/changelog/2.7.6_2021-02-04/8371.md b/changelog/2.7.6_2021-02-04/8371.md new file mode 100644 index 00000000000..cd16eebd4e5 --- /dev/null +++ b/changelog/2.7.6_2021-02-04/8371.md @@ -0,0 +1,7 @@ +Bugfix: Case-sensitive comparison of checksum algorithm + +We fixed a bug where the checksum detection was case-sensitive +and used a different casing than the server. + +https://github.com/owncloud/client/pull/8371 +https://github.com/owncloud/client/pull/8376 diff --git a/changelog/2.8.0_2021-05-06/4405 b/changelog/2.8.0_2021-05-06/4405.md similarity index 100% rename from changelog/2.8.0_2021-05-06/4405 rename to changelog/2.8.0_2021-05-06/4405.md diff --git a/changelog/2.8.0_2021-05-06/7838 b/changelog/2.8.0_2021-05-06/7838.md similarity index 100% rename from changelog/2.8.0_2021-05-06/7838 rename to changelog/2.8.0_2021-05-06/7838.md diff --git a/changelog/2.8.0_2021-05-06/8179 b/changelog/2.8.0_2021-05-06/8179.md similarity index 100% rename from changelog/2.8.0_2021-05-06/8179 rename to changelog/2.8.0_2021-05-06/8179.md diff --git a/changelog/2.8.0_2021-05-06/8437 b/changelog/2.8.0_2021-05-06/8437.md similarity index 100% rename from changelog/2.8.0_2021-05-06/8437 rename to changelog/2.8.0_2021-05-06/8437.md diff --git a/changelog/2.8.0_2021-05-06/8482 b/changelog/2.8.0_2021-05-06/8482 deleted file mode 100644 index 68b173eb399..00000000000 --- a/changelog/2.8.0_2021-05-06/8482 +++ /dev/null @@ -1,5 +0,0 @@ -Bugfix: Fix brandings with space in the name - -We fix a build system issue with brandings containing spaces. - -https://github.com/owncloud/client/pull/8482 diff --git a/changelog/2.8.0_2021-05-06/8482.md b/changelog/2.8.0_2021-05-06/8482.md new file mode 100644 index 00000000000..3c5212f4647 --- /dev/null +++ b/changelog/2.8.0_2021-05-06/8482.md @@ -0,0 +1,5 @@ +Bugfix: Fix branding with space in the name + +We fix a build system issue with branding containing spaces. + +https://github.com/owncloud/client/pull/8482 diff --git a/changelog/2.8.0_2021-05-06/8484 b/changelog/2.8.0_2021-05-06/8484.md similarity index 100% rename from changelog/2.8.0_2021-05-06/8484 rename to changelog/2.8.0_2021-05-06/8484.md diff --git a/changelog/2.8.0_2021-05-06/8539 b/changelog/2.8.0_2021-05-06/8539.md similarity index 100% rename from changelog/2.8.0_2021-05-06/8539 rename to changelog/2.8.0_2021-05-06/8539.md diff --git a/changelog/2.8.0_2021-05-06/8550 b/changelog/2.8.0_2021-05-06/8550.md similarity index 100% rename from changelog/2.8.0_2021-05-06/8550 rename to changelog/2.8.0_2021-05-06/8550.md diff --git a/changelog/2.8.1_2021-05-21/8615 b/changelog/2.8.1_2021-05-21/8615.md similarity index 100% rename from changelog/2.8.1_2021-05-21/8615 rename to changelog/2.8.1_2021-05-21/8615.md diff --git a/changelog/2.8.1_2021-05-21/8633 b/changelog/2.8.1_2021-05-21/8633.md similarity index 100% rename from changelog/2.8.1_2021-05-21/8633 rename to changelog/2.8.1_2021-05-21/8633.md diff --git a/changelog/2.8.2_2021-05-28/8272 b/changelog/2.8.2_2021-05-28/8272.md similarity index 100% rename from changelog/2.8.2_2021-05-28/8272 rename to changelog/2.8.2_2021-05-28/8272.md diff --git a/changelog/2.8.2_2021-05-28/8664 b/changelog/2.8.2_2021-05-28/8664.md similarity index 100% rename from changelog/2.8.2_2021-05-28/8664 rename to changelog/2.8.2_2021-05-28/8664.md diff --git a/changelog/2.9.0_2021-09-08/4542 b/changelog/2.9.0_2021-09-08/4542 deleted file mode 100644 index dc52c48247a..00000000000 --- a/changelog/2.9.0_2021-09-08/4542 +++ /dev/null @@ -1,8 +0,0 @@ -Enhancement: Prefer 127.0.0.1 as oauth redirect url - -When using OpenID Connect we now always use http://127.0.0.1 -as redirect url instead of http://localhost, following the recommendations in RFC 8252 (https://tools.ietf.org/html/rfc8252). -For OAuth2 we added a branding parameter which allows to specify http://127.0.0.1 instead -of http://localhost. - -https://github.com/owncloud/enterprise/issues/4542 diff --git a/changelog/2.9.0_2021-09-08/4542.md b/changelog/2.9.0_2021-09-08/4542.md new file mode 100644 index 00000000000..b9cba74e602 --- /dev/null +++ b/changelog/2.9.0_2021-09-08/4542.md @@ -0,0 +1,8 @@ +Enhancement: Prefer 127.0.0.1 as OAuth redirect URL + +When using OpenID Connect we now always use http://127.0.0.1 +as redirect URL instead of http://localhost, following the recommendations in RFC 8252 (https://tools.ietf.org/html/rfc8252). +For OAuth2 we added a branding parameter which allows to specify http://127.0.0.1 instead +of http://localhost. + +https://github.com/owncloud/enterprise/issues/4542 diff --git a/changelog/2.9.0_2021-09-08/4597 b/changelog/2.9.0_2021-09-08/4597.md similarity index 100% rename from changelog/2.9.0_2021-09-08/4597 rename to changelog/2.9.0_2021-09-08/4597.md diff --git a/changelog/2.9.0_2021-09-08/4627 b/changelog/2.9.0_2021-09-08/4627.md similarity index 100% rename from changelog/2.9.0_2021-09-08/4627 rename to changelog/2.9.0_2021-09-08/4627.md diff --git a/changelog/2.9.0_2021-09-08/4639 b/changelog/2.9.0_2021-09-08/4639.md similarity index 100% rename from changelog/2.9.0_2021-09-08/4639 rename to changelog/2.9.0_2021-09-08/4639.md diff --git a/changelog/2.9.0_2021-09-08/5644 b/changelog/2.9.0_2021-09-08/5644.md similarity index 100% rename from changelog/2.9.0_2021-09-08/5644 rename to changelog/2.9.0_2021-09-08/5644.md diff --git a/changelog/2.9.0_2021-09-08/7549 b/changelog/2.9.0_2021-09-08/7549.md similarity index 100% rename from changelog/2.9.0_2021-09-08/7549 rename to changelog/2.9.0_2021-09-08/7549.md diff --git a/changelog/2.9.0_2021-09-08/7646 b/changelog/2.9.0_2021-09-08/7646 deleted file mode 100644 index e80453ed8b9..00000000000 --- a/changelog/2.9.0_2021-09-08/7646 +++ /dev/null @@ -1,5 +0,0 @@ -Bugfix: SQLite wal file grows to several gigabyte - -We fixed a bug where the SQLite wal file growed until the client was quit. - -https://github.com/owncloud/client/issues/7646 diff --git a/changelog/2.9.0_2021-09-08/7646.md b/changelog/2.9.0_2021-09-08/7646.md new file mode 100644 index 00000000000..79c4f24943f --- /dev/null +++ b/changelog/2.9.0_2021-09-08/7646.md @@ -0,0 +1,5 @@ +Bugfix: SQLite wal file grows to several gigabyte + +We fixed a bug where the SQLite wal file grew until the client was quit. + +https://github.com/owncloud/client/issues/7646 diff --git a/changelog/2.9.0_2021-09-08/7715 b/changelog/2.9.0_2021-09-08/7715 deleted file mode 100644 index 96618ed1c91..00000000000 --- a/changelog/2.9.0_2021-09-08/7715 +++ /dev/null @@ -1,13 +0,0 @@ -Enhancement: Display the information state in case we encountered ignored errors - -If syncing a file fails multiple times we mark it as ignored to skip it for a certain amount of time. -If we have ignored files we are not in sync, we now don't display the green icon. - -Additionally this change aligns the icon displayed in the system tray with the icon displayed in the app. - -https://github.com/owncloud/client/issues/7715 -https://github.com/owncloud/client/issues/7365 -https://github.com/owncloud/client/issues/7200 -https://github.com/owncloud/client/issues/5860 - -https://github.com/owncloud/client/pull/8858 diff --git a/changelog/2.9.0_2021-09-08/7715.md b/changelog/2.9.0_2021-09-08/7715.md new file mode 100644 index 00000000000..5d22face547 --- /dev/null +++ b/changelog/2.9.0_2021-09-08/7715.md @@ -0,0 +1,13 @@ +Enhancement: Display the information state in case we encountered ignored errors + +If syncing a file fails multiple times we mark it as ignored to skip it for a certain amount of time. +If we have ignored files we are not in sync, we now don't display the green icon. + +Additionally, this change aligns the icon displayed in the system tray with the icon displayed in the app. + +https://github.com/owncloud/client/issues/7715 +https://github.com/owncloud/client/issues/7365 +https://github.com/owncloud/client/issues/7200 +https://github.com/owncloud/client/issues/5860 + +https://github.com/owncloud/client/pull/8858 diff --git a/changelog/2.9.0_2021-09-08/8076 b/changelog/2.9.0_2021-09-08/8076.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8076 rename to changelog/2.9.0_2021-09-08/8076.md diff --git a/changelog/2.9.0_2021-09-08/8130 b/changelog/2.9.0_2021-09-08/8130.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8130 rename to changelog/2.9.0_2021-09-08/8130.md diff --git a/changelog/2.9.0_2021-09-08/8158 b/changelog/2.9.0_2021-09-08/8158.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8158 rename to changelog/2.9.0_2021-09-08/8158.md diff --git a/changelog/2.9.0_2021-09-08/8231 b/changelog/2.9.0_2021-09-08/8231 deleted file mode 100644 index 44b4c198f9f..00000000000 --- a/changelog/2.9.0_2021-09-08/8231 +++ /dev/null @@ -1,6 +0,0 @@ -Change: Don't ask the user to switch to http - -We no longer recommend to use a http connection if a https url -was not found. - -https://github.com/owncloud/client/issues/8231 diff --git a/changelog/2.9.0_2021-09-08/8231.md b/changelog/2.9.0_2021-09-08/8231.md new file mode 100644 index 00000000000..1eb97f3c2eb --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8231.md @@ -0,0 +1,6 @@ +Change: Don't ask the user to switch to http + +We no longer recommend to use a http connection if a https URL +was not found. + +https://github.com/owncloud/client/issues/8231 diff --git a/changelog/2.9.0_2021-09-08/8253 b/changelog/2.9.0_2021-09-08/8253.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8253 rename to changelog/2.9.0_2021-09-08/8253.md diff --git a/changelog/2.9.0_2021-09-08/8273 b/changelog/2.9.0_2021-09-08/8273.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8273 rename to changelog/2.9.0_2021-09-08/8273.md diff --git a/changelog/2.9.0_2021-09-08/8299 b/changelog/2.9.0_2021-09-08/8299 deleted file mode 100644 index 4b747b5a275..00000000000 --- a/changelog/2.9.0_2021-09-08/8299 +++ /dev/null @@ -1,8 +0,0 @@ -Change: Ignore the desktop.ini file in every directory, not only in top dir - -The windows explorer files called desktop.ini were ignored only in the top -sync dir so far. They are now ignored in the sync in all directory levels -of the file tree. - -https://github.com/owncloud/client/issues/8298 -https://github.com/owncloud/client/pull/8299 diff --git a/changelog/2.9.0_2021-09-08/8299.md b/changelog/2.9.0_2021-09-08/8299.md new file mode 100644 index 00000000000..070ba2a1ddb --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8299.md @@ -0,0 +1,8 @@ +Change: Ignore the desktop.ini file in every directory, not only in top dir + +The Windows explorer files called desktop.ini were ignored only in the top +sync dir so far. They are now ignored in the sync in all directory levels +of the file tree. + +https://github.com/owncloud/client/issues/8298 +https://github.com/owncloud/client/pull/8299 diff --git a/changelog/2.9.0_2021-09-08/8317 b/changelog/2.9.0_2021-09-08/8317.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8317 rename to changelog/2.9.0_2021-09-08/8317.md diff --git a/changelog/2.9.0_2021-09-08/8350 b/changelog/2.9.0_2021-09-08/8350.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8350 rename to changelog/2.9.0_2021-09-08/8350.md diff --git a/changelog/2.9.0_2021-09-08/8366 b/changelog/2.9.0_2021-09-08/8366.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8366 rename to changelog/2.9.0_2021-09-08/8366.md diff --git a/changelog/2.9.0_2021-09-08/8367 b/changelog/2.9.0_2021-09-08/8367.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8367 rename to changelog/2.9.0_2021-09-08/8367.md diff --git a/changelog/2.9.0_2021-09-08/8374 b/changelog/2.9.0_2021-09-08/8374 deleted file mode 100644 index 5f87c21c59b..00000000000 --- a/changelog/2.9.0_2021-09-08/8374 +++ /dev/null @@ -1,6 +0,0 @@ -Change: Include full os version in the about dialog - -We now include the os version in the about dialog, this might help us to faster -pin down os related issues. - -https://github.com/owncloud/client/pull/8374 diff --git a/changelog/2.9.0_2021-09-08/8374.md b/changelog/2.9.0_2021-09-08/8374.md new file mode 100644 index 00000000000..30b991ad334 --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8374.md @@ -0,0 +1,6 @@ +Change: Include full os version in the about-dialog + +We now include the os version in the about-dialog, this might help us to faster +pin down os related issues. + +https://github.com/owncloud/client/pull/8374 diff --git a/changelog/2.9.0_2021-09-08/8398 b/changelog/2.9.0_2021-09-08/8398.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8398 rename to changelog/2.9.0_2021-09-08/8398.md diff --git a/changelog/2.9.0_2021-09-08/8464 b/changelog/2.9.0_2021-09-08/8464.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8464 rename to changelog/2.9.0_2021-09-08/8464.md diff --git a/changelog/2.9.0_2021-09-08/8466 b/changelog/2.9.0_2021-09-08/8466.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8466 rename to changelog/2.9.0_2021-09-08/8466.md diff --git a/changelog/2.9.0_2021-09-08/8467 b/changelog/2.9.0_2021-09-08/8467 deleted file mode 100644 index e3b4e76fc5f..00000000000 --- a/changelog/2.9.0_2021-09-08/8467 +++ /dev/null @@ -1,7 +0,0 @@ -Enhancement: Attach the last 20 log lines to a crash report - -We now save the last 20 lines of log to a tempoary file. -This file is then part of a crash report. - -https://github.com/owncloud/client/issues/8467 -https://github.com/owncloud/client/pull/8469 diff --git a/changelog/2.9.0_2021-09-08/8467.md b/changelog/2.9.0_2021-09-08/8467.md new file mode 100644 index 00000000000..05b52ba4ce0 --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8467.md @@ -0,0 +1,7 @@ +Enhancement: Attach the last 20 log lines to a crash report + +We now save the last 20 lines of log to a temporary file. +This file is then part of a crash report. + +https://github.com/owncloud/client/issues/8467 +https://github.com/owncloud/client/pull/8469 diff --git a/changelog/2.9.0_2021-09-08/8590 b/changelog/2.9.0_2021-09-08/8590.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8590 rename to changelog/2.9.0_2021-09-08/8590.md diff --git a/changelog/2.9.0_2021-09-08/8609 b/changelog/2.9.0_2021-09-08/8609.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8609 rename to changelog/2.9.0_2021-09-08/8609.md diff --git a/changelog/2.9.0_2021-09-08/8665 b/changelog/2.9.0_2021-09-08/8665.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8665 rename to changelog/2.9.0_2021-09-08/8665.md diff --git a/changelog/2.9.0_2021-09-08/8672 b/changelog/2.9.0_2021-09-08/8672 deleted file mode 100644 index f164cdec116..00000000000 --- a/changelog/2.9.0_2021-09-08/8672 +++ /dev/null @@ -1,5 +0,0 @@ -Bugfix: Ignore consecutive errors for a pereiode of time - -We fixed a bug where certain errors caused a sync run every 30 seconds - -https://github.com/owncloud/client/issues/8672 diff --git a/changelog/2.9.0_2021-09-08/8672.md b/changelog/2.9.0_2021-09-08/8672.md new file mode 100644 index 00000000000..0fae0ea2a76 --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8672.md @@ -0,0 +1,5 @@ +Bugfix: Ignore consecutive errors for a period of time + +We fixed a bug where certain errors caused a sync run every 30 seconds + +https://github.com/owncloud/client/issues/8672 diff --git a/changelog/2.9.0_2021-09-08/8729 b/changelog/2.9.0_2021-09-08/8729.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8729 rename to changelog/2.9.0_2021-09-08/8729.md diff --git a/changelog/2.9.0_2021-09-08/8732 b/changelog/2.9.0_2021-09-08/8732.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8732 rename to changelog/2.9.0_2021-09-08/8732.md diff --git a/changelog/2.9.0_2021-09-08/8761 b/changelog/2.9.0_2021-09-08/8761 deleted file mode 100644 index 112e851c9d1..00000000000 --- a/changelog/2.9.0_2021-09-08/8761 +++ /dev/null @@ -1,10 +0,0 @@ -Bugfix: Locked files are not correctly synced - -We fixed an issue where files locked by office etc, where not correctly synced, -when Windows Virtual files are enabled. - -https://github.com/owncloud/client/issues/8761 -https://github.com/owncloud/client/issues/8765 -https://github.com/owncloud/client/issues/8766 -https://github.com/owncloud/client/pull/8763 -https://github.com/owncloud/client/pull/8768 diff --git a/changelog/2.9.0_2021-09-08/8761.md b/changelog/2.9.0_2021-09-08/8761.md new file mode 100644 index 00000000000..0f195865e13 --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8761.md @@ -0,0 +1,10 @@ +Bugfix: Locked files are not correctly synced + +We fixed an issue where files locked by office etc., where not correctly synced, +when Windows Virtual files are enabled. + +https://github.com/owncloud/client/issues/8761 +https://github.com/owncloud/client/issues/8765 +https://github.com/owncloud/client/issues/8766 +https://github.com/owncloud/client/pull/8763 +https://github.com/owncloud/client/pull/8768 diff --git a/changelog/2.9.0_2021-09-08/8780 b/changelog/2.9.0_2021-09-08/8780.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8780 rename to changelog/2.9.0_2021-09-08/8780.md diff --git a/changelog/2.9.0_2021-09-08/8787 b/changelog/2.9.0_2021-09-08/8787.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8787 rename to changelog/2.9.0_2021-09-08/8787.md diff --git a/changelog/2.9.0_2021-09-08/8804 b/changelog/2.9.0_2021-09-08/8804.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8804 rename to changelog/2.9.0_2021-09-08/8804.md diff --git a/changelog/2.9.0_2021-09-08/8811 b/changelog/2.9.0_2021-09-08/8811 deleted file mode 100644 index a8005341a14..00000000000 --- a/changelog/2.9.0_2021-09-08/8811 +++ /dev/null @@ -1,5 +0,0 @@ -Enhancement: Retry sync on `502 Bad Gateway` - -We now treat a `502 Bad Gateway` as an less severer error and directly initialise a retry. - -https://github.com/owncloud/client/issues/8811 diff --git a/changelog/2.9.0_2021-09-08/8811.md b/changelog/2.9.0_2021-09-08/8811.md new file mode 100644 index 00000000000..c5deff90c0e --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8811.md @@ -0,0 +1,5 @@ +Enhancement: Retry sync on `502 Bad Gateway` + +We now treat a `502 Bad Gateway` as a less severe error and directly initialise a retry. + +https://github.com/owncloud/client/issues/8811 diff --git a/changelog/2.9.0_2021-09-08/8812 b/changelog/2.9.0_2021-09-08/8812.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8812 rename to changelog/2.9.0_2021-09-08/8812.md diff --git a/changelog/2.9.0_2021-09-08/8824 b/changelog/2.9.0_2021-09-08/8824.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8824 rename to changelog/2.9.0_2021-09-08/8824.md diff --git a/changelog/2.9.0_2021-09-08/8836 b/changelog/2.9.0_2021-09-08/8836.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8836 rename to changelog/2.9.0_2021-09-08/8836.md diff --git a/changelog/2.9.0_2021-09-08/8837 b/changelog/2.9.0_2021-09-08/8837.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8837 rename to changelog/2.9.0_2021-09-08/8837.md diff --git a/changelog/2.9.0_2021-09-08/8849 b/changelog/2.9.0_2021-09-08/8849 deleted file mode 100644 index 72ef2ffcc7c..00000000000 --- a/changelog/2.9.0_2021-09-08/8849 +++ /dev/null @@ -1,8 +0,0 @@ -Bugfix: Prevent sync root sharing - -Due to legacy reasons it is possible to let two sync connections -use the same directory. -In combination with virtual files this was leading to dataloss however. - -https://github.com/owncloud/client/issues/8849 -https://github.com/owncloud/client/issues/8512 diff --git a/changelog/2.9.0_2021-09-08/8849.md b/changelog/2.9.0_2021-09-08/8849.md new file mode 100644 index 00000000000..7ec954111fa --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8849.md @@ -0,0 +1,8 @@ +Bugfix: Prevent sync root sharing + +Due to legacy reasons it is possible to let two sync connections +use the same directory. +In combination with virtual files this was leading to data-loss, however. + +https://github.com/owncloud/client/issues/8849 +https://github.com/owncloud/client/issues/8512 diff --git a/changelog/2.9.0_2021-09-08/8864 b/changelog/2.9.0_2021-09-08/8864.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8864 rename to changelog/2.9.0_2021-09-08/8864.md diff --git a/changelog/2.9.0_2021-09-08/8866 b/changelog/2.9.0_2021-09-08/8866.md similarity index 100% rename from changelog/2.9.0_2021-09-08/8866 rename to changelog/2.9.0_2021-09-08/8866.md diff --git a/changelog/2.9.0_2021-09-08/8901 b/changelog/2.9.0_2021-09-08/8901 deleted file mode 100644 index 9bfcddb93a1..00000000000 --- a/changelog/2.9.0_2021-09-08/8901 +++ /dev/null @@ -1,6 +0,0 @@ -Bugfix: Start oauth/password prompt if password is wrong during start up - -If the oauth token was invalid during start up we didn't start the oauth process -and the user needed to manually log out in order to log in again. - -https://github.com/owncloud/client/issues/8901 diff --git a/changelog/2.9.0_2021-09-08/8901.md b/changelog/2.9.0_2021-09-08/8901.md new file mode 100644 index 00000000000..cc72edc28e2 --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8901.md @@ -0,0 +1,6 @@ +Bugfix: Start OAuth/password prompt if password is wrong during start up + +If the OAuth token was invalid during start up we didn't start the OAuth process +and the user needed to manually log out in order to log in again. + +https://github.com/owncloud/client/issues/8901 diff --git a/changelog/2.9.0_2021-09-08/8940 b/changelog/2.9.0_2021-09-08/8940 deleted file mode 100644 index 2e25cbbb083..00000000000 --- a/changelog/2.9.0_2021-09-08/8940 +++ /dev/null @@ -1,5 +0,0 @@ -Bugfix: Handle timeouts occurring during oauth - -We now handle timeouts occurring during oauth. - -https://github.com/owncloud/client/pull/8940 diff --git a/changelog/2.9.0_2021-09-08/8940.md b/changelog/2.9.0_2021-09-08/8940.md new file mode 100644 index 00000000000..e076d9a7af6 --- /dev/null +++ b/changelog/2.9.0_2021-09-08/8940.md @@ -0,0 +1,5 @@ +Bugfix: Handle timeouts occurring during OAuth + +We now handle timeouts occurring during OAuth. + +https://github.com/owncloud/client/pull/8940 diff --git a/changelog/2.9.1_2021-10-13/8421 b/changelog/2.9.1_2021-10-13/8421.md similarity index 100% rename from changelog/2.9.1_2021-10-13/8421 rename to changelog/2.9.1_2021-10-13/8421.md diff --git a/changelog/2.9.1_2021-10-13/8806 b/changelog/2.9.1_2021-10-13/8806 deleted file mode 100644 index 927e89536ca..00000000000 --- a/changelog/2.9.1_2021-10-13/8806 +++ /dev/null @@ -1,7 +0,0 @@ -Bugfix: Enforce rtl layout with rtl languages - -We fixed a bug where setting the language to a right to left language on mac -did not change the layout of the application. - -https://github.com/owncloud/client/issues/8806 -https://github.com/owncloud/client/pull/8981 diff --git a/changelog/2.9.1_2021-10-13/8806.md b/changelog/2.9.1_2021-10-13/8806.md new file mode 100644 index 00000000000..9534fccafa6 --- /dev/null +++ b/changelog/2.9.1_2021-10-13/8806.md @@ -0,0 +1,7 @@ +Bugfix: Enforce rtl layout with rtl languages + +We fixed a bug where setting the language to a right to left language on Mac +did not change the layout of the application. + +https://github.com/owncloud/client/issues/8806 +https://github.com/owncloud/client/pull/8981 diff --git a/changelog/2.9.1_2021-10-13/8920 b/changelog/2.9.1_2021-10-13/8920 deleted file mode 100644 index e5a2519046d..00000000000 --- a/changelog/2.9.1_2021-10-13/8920 +++ /dev/null @@ -1,5 +0,0 @@ -Bugfix: Broken url in branded builds - -We fixed a string issue with branded builds resulting in invalid urls. - -https://github.com/owncloud/client/issues/8920 diff --git a/changelog/2.9.1_2021-10-13/8920.md b/changelog/2.9.1_2021-10-13/8920.md new file mode 100644 index 00000000000..ca66797ecbf --- /dev/null +++ b/changelog/2.9.1_2021-10-13/8920.md @@ -0,0 +1,5 @@ +Bugfix: Broken URL in branded builds + +We fixed a string issue with branded builds resulting in invalid urls. + +https://github.com/owncloud/client/issues/8920 diff --git a/changelog/2.9.1_2021-10-13/8994 b/changelog/2.9.1_2021-10-13/8994.md similarity index 100% rename from changelog/2.9.1_2021-10-13/8994 rename to changelog/2.9.1_2021-10-13/8994.md diff --git a/changelog/2.9.1_2021-10-13/8996 b/changelog/2.9.1_2021-10-13/8996.md similarity index 100% rename from changelog/2.9.1_2021-10-13/8996 rename to changelog/2.9.1_2021-10-13/8996.md diff --git a/changelog/2.9.1_2021-10-13/9006 b/changelog/2.9.1_2021-10-13/9006.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9006 rename to changelog/2.9.1_2021-10-13/9006.md diff --git a/changelog/2.9.1_2021-10-13/9010 b/changelog/2.9.1_2021-10-13/9010.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9010 rename to changelog/2.9.1_2021-10-13/9010.md diff --git a/changelog/2.9.1_2021-10-13/9012 b/changelog/2.9.1_2021-10-13/9012.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9012 rename to changelog/2.9.1_2021-10-13/9012.md diff --git a/changelog/2.9.1_2021-10-13/9014 b/changelog/2.9.1_2021-10-13/9014.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9014 rename to changelog/2.9.1_2021-10-13/9014.md diff --git a/changelog/2.9.1_2021-10-13/9016 b/changelog/2.9.1_2021-10-13/9016.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9016 rename to changelog/2.9.1_2021-10-13/9016.md diff --git a/changelog/2.9.1_2021-10-13/9019 b/changelog/2.9.1_2021-10-13/9019 deleted file mode 100644 index a4058229f17..00000000000 --- a/changelog/2.9.1_2021-10-13/9019 +++ /dev/null @@ -1,19 +0,0 @@ -Bugfix: A upgrade to 2.9 causes the usage of a new journal file - -We fixed a bug where the name of the sync journal was not properly saved to the settings. -This caused a bug when migration to 2.9, so a new a new sync journal was created. - -This not only caused the loss of some selective sync settings, but also caused the display of the following warning message: - -``` -Multiple accounts are sharing the folder. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. -``` - -We also removed the account info infix from the sync db used with the cmd client. - -https://github.com/owncloud/client/issues/9019 -https://github.com/owncloud/client/pull/9028 -https://github.com/owncloud/client/pull/9046 -https://github.com/owncloud/client/pull/9054 diff --git a/changelog/2.9.1_2021-10-13/9019.md b/changelog/2.9.1_2021-10-13/9019.md new file mode 100644 index 00000000000..87edf40389f --- /dev/null +++ b/changelog/2.9.1_2021-10-13/9019.md @@ -0,0 +1,19 @@ +Bugfix: A upgrade to 2.9 causes the usage of a new journal file + +We fixed a bug where the name of the sync journal was not properly saved to the settings. +This caused a bug when migration to 2.9, so a new sync journal was created. + +This not only caused the loss of some selective sync settings, but also caused the display of the following warning message: + +``` +Multiple accounts are sharing the folder. +This configuration is know to lead to dataloss and is no longer supported. +Please consider removing this folder from the account and adding it again. +``` + +We also removed the account info infix from the sync db used with the cmd client. + +https://github.com/owncloud/client/issues/9019 +https://github.com/owncloud/client/pull/9028 +https://github.com/owncloud/client/pull/9046 +https://github.com/owncloud/client/pull/9054 diff --git a/changelog/2.9.1_2021-10-13/9022 b/changelog/2.9.1_2021-10-13/9022.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9022 rename to changelog/2.9.1_2021-10-13/9022.md diff --git a/changelog/2.9.1_2021-10-13/9026 b/changelog/2.9.1_2021-10-13/9026.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9026 rename to changelog/2.9.1_2021-10-13/9026.md diff --git a/changelog/2.9.1_2021-10-13/9057 b/changelog/2.9.1_2021-10-13/9057.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9057 rename to changelog/2.9.1_2021-10-13/9057.md diff --git a/changelog/2.9.1_2021-10-13/9093 b/changelog/2.9.1_2021-10-13/9093.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9093 rename to changelog/2.9.1_2021-10-13/9093.md diff --git a/changelog/2.9.1_2021-10-13/9114 b/changelog/2.9.1_2021-10-13/9114.md similarity index 100% rename from changelog/2.9.1_2021-10-13/9114 rename to changelog/2.9.1_2021-10-13/9114.md diff --git a/changelog/2.9.2_2021-11-24/8979 b/changelog/2.9.2_2021-11-24/8979.md similarity index 100% rename from changelog/2.9.2_2021-11-24/8979 rename to changelog/2.9.2_2021-11-24/8979.md diff --git a/changelog/2.9.2_2021-11-24/9159 b/changelog/2.9.2_2021-11-24/9159 deleted file mode 100644 index 88b89bf2823..00000000000 --- a/changelog/2.9.2_2021-11-24/9159 +++ /dev/null @@ -1,6 +0,0 @@ -Bugfix: Disable vfs option is ignored - -We fixed a branding issue where vfs was used even when the parameter was set to disabled. - -https://github.com/owncloud/client/issues/9159 -https://github.com/owncloud/enterprise/issues/4820 diff --git a/changelog/2.9.2_2021-11-24/9159.md b/changelog/2.9.2_2021-11-24/9159.md new file mode 100644 index 00000000000..e67f122efc2 --- /dev/null +++ b/changelog/2.9.2_2021-11-24/9159.md @@ -0,0 +1,6 @@ +Bugfix: Disable vfs option is ignored + +We fixed a branding issue where vfs was used even when the parameter was not enabled. + +https://github.com/owncloud/client/issues/9159 +https://github.com/owncloud/enterprise/issues/4820 diff --git a/changelog/2.9.2_2021-11-24/9167 b/changelog/2.9.2_2021-11-24/9167.md similarity index 100% rename from changelog/2.9.2_2021-11-24/9167 rename to changelog/2.9.2_2021-11-24/9167.md diff --git a/changelog/2.9.2_2021-11-24/9202 b/changelog/2.9.2_2021-11-24/9202 deleted file mode 100644 index ee2326afd4d..00000000000 --- a/changelog/2.9.2_2021-11-24/9202 +++ /dev/null @@ -1,8 +0,0 @@ -Bugfix: The client no longer idles after a minor url change - -When the client detects a change of the url we ask the user to accept -the change or if it was only representational change (demo.com vs demo.com/) we directly accept the change. -Due to a bug the we aborted the sync only after we updated the url. -This caused the client to idle for one minute. - -https://github.com/owncloud/client/pull/9202 diff --git a/changelog/2.9.2_2021-11-24/9202.md b/changelog/2.9.2_2021-11-24/9202.md new file mode 100644 index 00000000000..e402a4ea69d --- /dev/null +++ b/changelog/2.9.2_2021-11-24/9202.md @@ -0,0 +1,8 @@ +Bugfix: The client no longer idles after a minor URL change + +When the client detects a change of the URL we ask the user to accept +the change or if it was only representational change (demo.com vs demo.com/) we directly accept the change. +Due to a bug we aborted the sync only after we updated the URL. +This caused the client to idle for one minute. + +https://github.com/owncloud/client/pull/9202 diff --git a/changelog/2.9.2_2021-11-24/9216 b/changelog/2.9.2_2021-11-24/9216.md similarity index 100% rename from changelog/2.9.2_2021-11-24/9216 rename to changelog/2.9.2_2021-11-24/9216.md diff --git a/changelog/2.9.2_2021-11-24/9224 b/changelog/2.9.2_2021-11-24/9224.md similarity index 100% rename from changelog/2.9.2_2021-11-24/9224 rename to changelog/2.9.2_2021-11-24/9224.md diff --git a/changelog/3.0.0_2022-11-30/10007.md b/changelog/3.0.0_2022-11-30/10007.md new file mode 100644 index 00000000000..544b29c212c --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10007.md @@ -0,0 +1,6 @@ +Bugfix: Don't unset implicit log flush + +Since https://github.com/owncloud/client/pull/9515 we flush the log if the output is stdout. +We fixed a bug which disabled it again. + +https://github.com/owncloud/client/pull/9515 diff --git a/changelog/3.0.0_2022-11-30/10017.md b/changelog/3.0.0_2022-11-30/10017.md new file mode 100644 index 00000000000..09f061aef9e --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10017.md @@ -0,0 +1,5 @@ +Bugfix: We fixed a crash + +We fixed a crash that could occur after a folder reported a setup error + +https://github.com/owncloud/client/pull/10017 diff --git a/changelog/3.0.0_2022-11-30/10031.md b/changelog/3.0.0_2022-11-30/10031.md new file mode 100644 index 00000000000..08af859ca57 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10031.md @@ -0,0 +1,5 @@ +Enhancement: Windows VFS download speed improvement + +We improved the performance of downloads performed on virtual files in the Windows Explorer. + +https://github.com/owncloud/client/issues/10031 diff --git a/changelog/3.0.0_2022-11-30/10101.md b/changelog/3.0.0_2022-11-30/10101.md new file mode 100644 index 00000000000..84b8b564f69 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10101.md @@ -0,0 +1,4 @@ +Bugfix: Sync status changes are now directly displayed + + +https://github.com/owncloud/client/issues/10101 diff --git a/changelog/3.0.0_2022-11-30/10104.md b/changelog/3.0.0_2022-11-30/10104.md new file mode 100644 index 00000000000..24540e6094c --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10104.md @@ -0,0 +1,6 @@ +Enhancement: Add a `prefer: minimal` header to PROPFIND + +This will not return missing attribs in the reply in a 404 not found status propset. +That reduces the amount of transferred data significant. + +https://github.com/owncloud/client/pull/10104 diff --git a/changelog/3.0.0_2022-11-30/10135.md b/changelog/3.0.0_2022-11-30/10135.md new file mode 100644 index 00000000000..45f114d2359 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10135.md @@ -0,0 +1,5 @@ +Enhancement: Allow creation of sync roots with long paths + +Until now, we were only able to create a .sync_journal.db in a path with less than 260 characters. + +https://github.com/owncloud/client/pull/10135/ diff --git a/changelog/3.0.0_2022-11-30/10136.md b/changelog/3.0.0_2022-11-30/10136.md new file mode 100644 index 00000000000..452350dbcbd --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10136.md @@ -0,0 +1,6 @@ +Enhancement: Windows add longPath awareness + +Requires Windows 10 newer than 1607 and the registry key to be enabled see: +https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later + +https://github.com/owncloud/client/pull/10136 diff --git a/changelog/3.0.0_2022-11-30/10142.md b/changelog/3.0.0_2022-11-30/10142.md new file mode 100644 index 00000000000..9359491194e --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10142.md @@ -0,0 +1,5 @@ +Enhancement: Estimate duration of network requests in httplogger + +We now include an estimated duration in the httplogger. + +https://github.com/owncloud/client/pull/10142 diff --git a/changelog/3.0.0_2022-11-30/10184.md b/changelog/3.0.0_2022-11-30/10184.md new file mode 100644 index 00000000000..181a92f02fb --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10184.md @@ -0,0 +1,6 @@ +Change: Windows: Update the folder icon on every start + +The ownCloud installation path might have changed, causing the desktop.ini to point at the wrong path. +We now update the icon location on every application start. + +https://github.com/owncloud/client/issues/10184 diff --git a/changelog/3.0.0_2022-11-30/10193.md b/changelog/3.0.0_2022-11-30/10193.md new file mode 100644 index 00000000000..5021e9b5a20 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10193.md @@ -0,0 +1,5 @@ +Change: Don't guess remote folder in owncloudcmd + +The commandline client was modified to explicitly accept remote folder, the remote folder must no longer be encoded in the server URL. + +https://github.com/owncloud/client/issues/10193 diff --git a/changelog/3.0.0_2022-11-30/10206.md b/changelog/3.0.0_2022-11-30/10206.md new file mode 100644 index 00000000000..71bbd3b5b82 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10206.md @@ -0,0 +1,5 @@ +Change: When connected to oCIS, open the browser instead of the sharing dialog + +When connected to oCIS, we now open the browser and navigate to the file the user wanted to share instead of opening the legacy sharing dialog. + +https://github.com/owncloud/client/issues/10206 diff --git a/changelog/3.0.0_2022-11-30/10239.md b/changelog/3.0.0_2022-11-30/10239.md new file mode 100644 index 00000000000..10ba6a528e4 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10239.md @@ -0,0 +1,6 @@ +Change: owncloudcmd oCIS support + +When using oCIS and spaces with the cmd client the additional parameter `--server` is required. +`--server` specifies the URL to the server, while the positional parameter 'server_url' specifies the WebDAV URL. + +https://github.com/owncloud/client/pull/10239 diff --git a/changelog/3.0.0_2022-11-30/10310.md b/changelog/3.0.0_2022-11-30/10310.md new file mode 100644 index 00000000000..e6a84228c53 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/10310.md @@ -0,0 +1,6 @@ +Enhancement: Tweak logging format + +The logging format is now better parseable for 3rdparty apps +that ease debugging. + +https://github.com/owncloud/client/pull/10310 diff --git a/changelog/3.0.0_2022-11-30/49.md b/changelog/3.0.0_2022-11-30/49.md new file mode 100644 index 00000000000..ef2e80aaa44 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/49.md @@ -0,0 +1,5 @@ +Bugfix: Windows VFS fixed some failing downloads + +We fixed an api issue where some downloads in the Explorer caused infinite download restarts. + +https://github.com/owncloud/client-desktop-vfs-win/pull/49 diff --git a/changelog/3.0.0_2022-11-30/5382.md b/changelog/3.0.0_2022-11-30/5382.md new file mode 100644 index 00000000000..8d2447de681 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/5382.md @@ -0,0 +1,3 @@ +Bugfix: Don't trigger ignore list when files are locked on the server + +https://github.com/owncloud/enterprise/issues/5382 diff --git a/changelog/3.0.0_2022-11-30/5382_2.md b/changelog/3.0.0_2022-11-30/5382_2.md new file mode 100644 index 00000000000..a1559ee9b92 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/5382_2.md @@ -0,0 +1,4 @@ +Bugfix: Properly resume upload with a partial local discovery + +https://github.com/owncloud/enterprise/issues/5382 +https://github.com/owncloud/client/pull/10200 diff --git a/changelog/unreleased/7348 b/changelog/3.0.0_2022-11-30/7348.md similarity index 100% rename from changelog/unreleased/7348 rename to changelog/3.0.0_2022-11-30/7348.md diff --git a/changelog/3.0.0_2022-11-30/8234.md b/changelog/3.0.0_2022-11-30/8234.md new file mode 100644 index 00000000000..b9fe403991c --- /dev/null +++ b/changelog/3.0.0_2022-11-30/8234.md @@ -0,0 +1,5 @@ +Enhancement: Display `Show ownCloud` instead of `Settings` in systray + +We changed the menu entry to align its name with is function. + +https://github.com/owncloud/client/issues/8234 diff --git a/changelog/3.0.0_2022-11-30/8595.md b/changelog/3.0.0_2022-11-30/8595.md new file mode 100644 index 00000000000..f146f7dd284 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/8595.md @@ -0,0 +1,4 @@ +Bugfix: Don't display a context-menu on the root folder + + +https://github.com/owncloud/client/issues/8595 diff --git a/changelog/3.0.0_2022-11-30/8923.md b/changelog/3.0.0_2022-11-30/8923.md new file mode 100644 index 00000000000..143523aa326 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/8923.md @@ -0,0 +1,8 @@ +Enhancement: Built-in AppImage self-updater + +In release 2.10, we introduced a preview on our future AppImage packaging for Linux distributions. +Now, these AppImages can self-update using a built-in `libappimageupdate` based updater and +ownCloud's update infrastructure. + +https://github.com/owncloud/client/issues/8923 +https://github.com/owncloud/client/pull/9376 diff --git a/changelog/3.0.0_2022-11-30/8938.md b/changelog/3.0.0_2022-11-30/8938.md new file mode 100644 index 00000000000..d0f2184d444 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/8938.md @@ -0,0 +1,7 @@ +Change: Make share-dialog preview be more resilient + +We no longer enforce png thumbnails. +We no longer replace the file icon if the thumbnail is invalid. + +https://github.com/owncloud/client/issues/8938 +https://github.com/owncloud/client/pull/8939 diff --git a/changelog/3.0.0_2022-11-30/8998.md b/changelog/3.0.0_2022-11-30/8998.md new file mode 100644 index 00000000000..e851ae4b71a --- /dev/null +++ b/changelog/3.0.0_2022-11-30/8998.md @@ -0,0 +1,5 @@ +Enhancement: Don't query private links if disabled on the server + +https://github.com/owncloud/client/issues/8998 +https://github.com/owncloud/client/pull/9840 +https://github.com/owncloud/client/pull/9964 diff --git a/changelog/3.0.0_2022-11-30/9048.md b/changelog/3.0.0_2022-11-30/9048.md new file mode 100644 index 00000000000..67a28daf41f --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9048.md @@ -0,0 +1,5 @@ +Bugfix: Fix copy URL location for private links + +We fixed a bug where a placeholder was copied to the clipboard instead of the URL. + +https://github.com/owncloud/client/issues/9048 diff --git a/changelog/3.0.0_2022-11-30/9082.md b/changelog/3.0.0_2022-11-30/9082.md new file mode 100644 index 00000000000..97cf81b1527 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9082.md @@ -0,0 +1,5 @@ +Enhancement: Add CMakeOption WITH_AUTO_UPDATER + +WITH_AUTO_UPDATER allows to build the client without the auto updater. + +https://github.com/owncloud/client/issues/9082 diff --git a/changelog/3.0.0_2022-11-30/9472.md b/changelog/3.0.0_2022-11-30/9472.md new file mode 100644 index 00000000000..463fe6da16a --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9472.md @@ -0,0 +1,6 @@ +Bugfix: Fix status of files uploaded with TUS + +Setting the upload status of files uploaded with TUS failed as we +were still using the file. + +https://github.com/owncloud/client/pull/9472 diff --git a/changelog/3.0.0_2022-11-30/9482.md b/changelog/3.0.0_2022-11-30/9482.md new file mode 100644 index 00000000000..606cd0804fa --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9482.md @@ -0,0 +1,22 @@ +Enhancement: Rewrite wizard from scratch + +We completely rewrote the wizard from scratch. The new wizard provides greater +flexibility and makes adding new features easier in the future. It has also +been redesigned to improve the user experience. + +https://github.com/owncloud/client/issues/9249 +https://github.com/owncloud/client/pull/9482 +https://github.com/owncloud/client/pull/9563 +https://github.com/owncloud/client/pull/9566 +https://github.com/owncloud/client/pull/9577 +https://github.com/owncloud/client/pull/9596 +https://github.com/owncloud/client/pull/9606 +https://github.com/owncloud/client/pull/9621 +https://github.com/owncloud/client/pull/9629 +https://github.com/owncloud/client/pull/9636 +https://github.com/owncloud/client/pull/9637 +https://github.com/owncloud/client/pull/9642 +https://github.com/owncloud/client/pull/9643 +https://github.com/owncloud/client/pull/9697 +https://github.com/owncloud/client/pull/9720 +https://github.com/owncloud/client/pull/9746 diff --git a/changelog/3.0.0_2022-11-30/9495.md b/changelog/3.0.0_2022-11-30/9495.md new file mode 100644 index 00000000000..e6bab9ed7c1 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9495.md @@ -0,0 +1,5 @@ +Change: We no longer persist cookies + +We no longer persist cookies over multiple client sessions. + +https://github.com/owncloud/client/issues/9495 diff --git a/changelog/3.0.0_2022-11-30/9538.md b/changelog/3.0.0_2022-11-30/9538.md new file mode 100644 index 00000000000..ae73b3bc43e --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9538.md @@ -0,0 +1,5 @@ +Enhancement: Remove use of legacy DAV endpoint + +We no longer guess the DAV endpoint depending on the chunking-ng feature. + +https://github.com/owncloud/client/pull/9538 diff --git a/changelog/3.0.0_2022-11-30/9574.md b/changelog/3.0.0_2022-11-30/9574.md new file mode 100644 index 00000000000..eb332605672 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9574.md @@ -0,0 +1,6 @@ +Bugfix: The condition for the read only files menu was inverted + +We fixed a bug where we displayed solutions to fix issues with +read only files for readable files and vise versa. + +https://github.com/owncloud/client/issues/9574 diff --git a/changelog/3.0.0_2022-11-30/9575.md b/changelog/3.0.0_2022-11-30/9575.md new file mode 100644 index 00000000000..7e3bdf6f08c --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9575.md @@ -0,0 +1,6 @@ +Enhancement: Support for oCIS Spaces + +We added support to sync oCIS Spaces. + +https://github.com/owncloud/client/pull/9154 +https://github.com/owncloud/client/pull/9575/ diff --git a/changelog/3.0.0_2022-11-30/9578.md b/changelog/3.0.0_2022-11-30/9578.md new file mode 100644 index 00000000000..2cfde962f8e --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9578.md @@ -0,0 +1,3 @@ +Change: We removed support for ownCloud servers < 10.0 + +https://github.com/owncloud/client/issues/9578 diff --git a/changelog/3.0.0_2022-11-30/9585.md b/changelog/3.0.0_2022-11-30/9585.md new file mode 100644 index 00000000000..2c8e33787ca --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9585.md @@ -0,0 +1,3 @@ +Change: Drop socket upload job + +https://github.com/owncloud/client/issues/9585 diff --git a/changelog/3.0.0_2022-11-30/9598.md b/changelog/3.0.0_2022-11-30/9598.md new file mode 100644 index 00000000000..acdb7d27bf0 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9598.md @@ -0,0 +1,6 @@ +Enhancement: Set Windows VFS placeholders readonly if needed + +We now properly set the read only flag on Windows virtual files. + +https://github.com/owncloud/client/issues/9598 +https://github.com/owncloud/client-desktop-vfs-win/issues/24 diff --git a/changelog/3.0.0_2022-11-30/9618.md b/changelog/3.0.0_2022-11-30/9618.md new file mode 100644 index 00000000000..0d956cd9518 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9618.md @@ -0,0 +1,5 @@ +Change: Remove support for Windows 7 sidebar links + +We removed the support for Windows < 10 sidebar links. + +https://github.com/owncloud/client/pull/9618 diff --git a/changelog/3.0.0_2022-11-30/9655.md b/changelog/3.0.0_2022-11-30/9655.md new file mode 100644 index 00000000000..1b2dcda8c9e --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9655.md @@ -0,0 +1,7 @@ +Change: Rewrote TLS error handling + +We rewrote the way we handle TLS errors. + +https://github.com/owncloud/client/issues/9655 +https://github.com/owncloud/client/pull/9643 +https://github.com/owncloud/client/pull/9667 diff --git a/changelog/3.0.0_2022-11-30/9675.md b/changelog/3.0.0_2022-11-30/9675.md new file mode 100644 index 00000000000..23ac73ac419 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9675.md @@ -0,0 +1,3 @@ +Change: We removed the TLS certificate button from the account page + +https://github.com/owncloud/client/pull/9675 diff --git a/changelog/3.0.0_2022-11-30/9681.md b/changelog/3.0.0_2022-11-30/9681.md new file mode 100644 index 00000000000..f56edc43eca --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9681.md @@ -0,0 +1,5 @@ +Bugfix: Deadlock in folder context menu in a folder selection dialog + +We fixed a deadlock when a user requested a context menu in a folder selection dialog on Windows. + +https://github.com/owncloud/client/issues/9681 diff --git a/changelog/3.0.0_2022-11-30/9724.md b/changelog/3.0.0_2022-11-30/9724.md new file mode 100644 index 00000000000..17bc5eb30e8 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9724.md @@ -0,0 +1,5 @@ +Change: Add "open in web editor" feature + +We now provide the option to open files in an online office suite from the local file browser context menu, provided the server offers integration with one of the supported services. + +https://github.com/owncloud/client/issues/9724 diff --git a/changelog/3.0.0_2022-11-30/9725.md b/changelog/3.0.0_2022-11-30/9725.md new file mode 100644 index 00000000000..53c5e821a99 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9725.md @@ -0,0 +1,6 @@ +Bugfix: Fix never ending sync + +Under certain conditions an upload could enter a state were it would never finish. +The client would wait for that upload so only a restart of the client or a manual abort of the sync could resolve the issue. + +https://github.com/owncloud/client/issues/9725 diff --git a/changelog/3.0.0_2022-11-30/9731.md b/changelog/3.0.0_2022-11-30/9731.md new file mode 100644 index 00000000000..e41997bb2ab --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9731.md @@ -0,0 +1,10 @@ +Enhancement: Create continuous log files + +Previously, when logging was enabled, we started a new log file for every sync. +This worked quite well if you sync a single account and a single folder. +With spaces however we have a multitude of sync folders, which resulted in hundreds of tiny log files. + +Now, as soon as a log file's size exceeds 100 MiB, a new log file is started, and the old one is moved and compressed. +The option to delete log files older than 4h was replaced by an option to keep a number of log files. + +https://github.com/owncloud/client/issues/9731 diff --git a/changelog/3.0.0_2022-11-30/9752.md b/changelog/3.0.0_2022-11-30/9752.md new file mode 100644 index 00000000000..222be1acbf5 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9752.md @@ -0,0 +1,7 @@ +Bugfix: Fix adding bookmarks on Gtk+ 3 based desktops + +We used to add those bookmarks in a Gtk+ 2 compatible way only. +Now, bookmarks are added to the file belonging to Gtk+ 3, dropping support for end-of-life Gtk+ 2. +The bookmarks are now shown again for all Gtk+ 3 compatible file browsers, including Thunar, Nautilus, Nemo, Caja, etc. + +https://github.com/owncloud/client/pull/9752 diff --git a/changelog/3.0.0_2022-11-30/9772.md b/changelog/3.0.0_2022-11-30/9772.md new file mode 100644 index 00000000000..1a1d44c2057 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9772.md @@ -0,0 +1,7 @@ +Enhancement: Display a correct error when the wrong user was authenticated + +When the wrong user was authenticated using OAuth we used to display a misleading message. +We now also style the html response the client provides to the file browser. + +https://github.com/owncloud/client/issues/9772 +https://github.com/owncloud/client/pull/9813 diff --git a/changelog/3.0.0_2022-11-30/9790.md b/changelog/3.0.0_2022-11-30/9790.md new file mode 100644 index 00000000000..de0f1614195 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9790.md @@ -0,0 +1,5 @@ +Change: Don't display error state when server is unreachable + +We no longer display a network error if the server is currently unavailable. + +https://github.com/owncloud/client/issues/9790 diff --git a/changelog/3.0.0_2022-11-30/9798.md b/changelog/3.0.0_2022-11-30/9798.md new file mode 100644 index 00000000000..82a364ae08d --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9798.md @@ -0,0 +1,5 @@ +Bugfix: Stop the activity spinner when the request failed + +If the server did not provide the activity endpoint we always displayed a progress spinner. + +https://github.com/owncloud/client/issues/9798 diff --git a/changelog/3.0.0_2022-11-30/9910.md b/changelog/3.0.0_2022-11-30/9910.md new file mode 100644 index 00000000000..68b9c021210 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9910.md @@ -0,0 +1,3 @@ +Enhancement: We improved the performance for local filesystem actions + +https://github.com/owncloud/client/pull/9910 diff --git a/changelog/3.0.0_2022-11-30/9918.md b/changelog/3.0.0_2022-11-30/9918.md new file mode 100644 index 00000000000..5e06076be10 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9918.md @@ -0,0 +1,9 @@ +Enhancement: We improved the performance of db access + +We removed a check for the existence of the db that was executed before every access to the db. + +The check was introduced in #6049 to prevent crashes if the db does not exist or is removed during runtime. +We nowadays gracefully handle missing dbs on startup, removing the db at runtime is too much of a corner case +to sacrifice that much performance, however. + +https://github.com/owncloud/client/pull/9918 diff --git a/changelog/3.0.0_2022-11-30/9919.md b/changelog/3.0.0_2022-11-30/9919.md new file mode 100644 index 00000000000..e093798ba7f --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9919.md @@ -0,0 +1,3 @@ +Enhancement: Reduce CPU load during discovery + +https://github.com/owncloud/client/pull/9919 diff --git a/changelog/3.0.0_2022-11-30/9923.md b/changelog/3.0.0_2022-11-30/9923.md new file mode 100644 index 00000000000..256ca829c48 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9923.md @@ -0,0 +1,6 @@ +Enhancement: Remove app name from connection error message + +We removed the app name from some connection messages. +`No connection to ownCloud at http://..` was misleading as the server could have any other branding. + +https://github.com/owncloud/client/issues/9923 diff --git a/changelog/3.0.0_2022-11-30/9924.md b/changelog/3.0.0_2022-11-30/9924.md new file mode 100644 index 00000000000..3a93901d1d4 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9924.md @@ -0,0 +1,5 @@ +Bugfix: Changes during upload of a file could still trigger the ignore list + +We fixed another issue where changes during an upload could cause the file to be ignored for an increasing amount of time. + +https://github.com/owncloud/client/issues/9924 diff --git a/changelog/3.0.0_2022-11-30/9930.md b/changelog/3.0.0_2022-11-30/9930.md new file mode 100644 index 00000000000..3cc3deef49f --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9930.md @@ -0,0 +1,5 @@ +Enhancement: Allow HTTP/1.1 pipelining + +Under certain conditions, this change can result in a better network utilization. + +https://github.com/owncloud/client/pull/9930/ diff --git a/changelog/3.0.0_2022-11-30/9995.md b/changelog/3.0.0_2022-11-30/9995.md new file mode 100644 index 00000000000..bdf9ffcda25 --- /dev/null +++ b/changelog/3.0.0_2022-11-30/9995.md @@ -0,0 +1,3 @@ +Enhancement: Improve look and feel of many dialogs on macOS + +https://github.com/owncloud/client/issues/9995 diff --git a/changelog/3.1.0_2023-01-25/10294.md b/changelog/3.1.0_2023-01-25/10294.md new file mode 100644 index 00000000000..1c28976f2f2 --- /dev/null +++ b/changelog/3.1.0_2023-01-25/10294.md @@ -0,0 +1,6 @@ +Enhancement: Add a `Reconnect` option to the account menu, when disconnected + +We added a `Reconnect` button to the account menu, this allows to trigger a manual reconnect try. +Note: The client would try to reconnect by itself at some point. + +https://github.com/owncloud/client/issues/10294 diff --git a/changelog/3.1.0_2023-01-25/10329.md b/changelog/3.1.0_2023-01-25/10329.md new file mode 100644 index 00000000000..d16b62d836f --- /dev/null +++ b/changelog/3.1.0_2023-01-25/10329.md @@ -0,0 +1,5 @@ +Bugfix: Fix the display of the version string for released builds + +We removed a trailing `-` in the version string of released clients. + +https://github.com/owncloud/client/pull/10329 diff --git a/changelog/3.1.0_2023-01-25/10338.md b/changelog/3.1.0_2023-01-25/10338.md new file mode 100644 index 00000000000..f5d968b46a3 --- /dev/null +++ b/changelog/3.1.0_2023-01-25/10338.md @@ -0,0 +1,6 @@ +Bugfix: Only clear cookies if enabled in theme, clear cookies before OAuth2 + +We fixed a bug that enabled the explicit cookie clearing required for F5 BIG-IP setups unconditionally. +We fixed a bug where the cookie clearing was not performed during OAuth2. + +https://github.com/owncloud/client/pull/10338 diff --git a/changelog/3.1.0_2023-01-25/10340.md b/changelog/3.1.0_2023-01-25/10340.md new file mode 100644 index 00000000000..ecce923e3cf --- /dev/null +++ b/changelog/3.1.0_2023-01-25/10340.md @@ -0,0 +1,5 @@ +Bugfix: Display `Add a Space` instead of `Add Folder Sync Connection` with oCIS + +We fixed a bug where the wrong text was displayed on the "add" button. + +https://github.com/owncloud/client/pull/10340 diff --git a/changelog/3.1.0_2023-01-25/10346.md b/changelog/3.1.0_2023-01-25/10346.md new file mode 100644 index 00000000000..aecbb6e5df2 --- /dev/null +++ b/changelog/3.1.0_2023-01-25/10346.md @@ -0,0 +1,3 @@ +Bugfix: Mac: Don't inherit the environment of the installer after an update + +https://github.com/owncloud/client/issues/10346 diff --git a/changelog/3.1.0_2023-01-25/10373.md b/changelog/3.1.0_2023-01-25/10373.md new file mode 100644 index 00000000000..86c0994d44b --- /dev/null +++ b/changelog/3.1.0_2023-01-25/10373.md @@ -0,0 +1,3 @@ +Bugfix: Correctly detect timeouts during token refresh + +https://github.com/owncloud/client/pull/10373 diff --git a/changelog/3.1.0_2023-01-25/10387.md b/changelog/3.1.0_2023-01-25/10387.md new file mode 100644 index 00000000000..610cee3b7f1 --- /dev/null +++ b/changelog/3.1.0_2023-01-25/10387.md @@ -0,0 +1,5 @@ +Bugfix: Open in web if debug logs are turned off + +Due to a bug opening the browser only worked if logging was enabled. + +https://github.com/owncloud/client/pull/10387 diff --git a/changelog/3.1.0_2023-01-25/10390.md b/changelog/3.1.0_2023-01-25/10390.md new file mode 100644 index 00000000000..72e96b63d08 --- /dev/null +++ b/changelog/3.1.0_2023-01-25/10390.md @@ -0,0 +1,6 @@ +Enhancement: (Re)introduce "sync hidden files" parameter in owncloudcmd + +There used to be an option to enable the synchronization of hidden files using the -h parameter which collided with the --help option and subsequently was removed. +A new --sync-hidden-files parameter was introduced to fill in the missing feature. + +https://github.com/owncloud/client/issues/10390 diff --git a/changelog/3.1.0_2023-01-25/10398.md b/changelog/3.1.0_2023-01-25/10398.md new file mode 100644 index 00000000000..eb73e75dbb4 --- /dev/null +++ b/changelog/3.1.0_2023-01-25/10398.md @@ -0,0 +1,3 @@ +Bugfix: We fixed building the client on non linux unix systems + +https://github.com/owncloud/client/issues/10398 diff --git a/changelog/3.2.0_2023-02-15/10441.md b/changelog/3.2.0_2023-02-15/10441.md new file mode 100644 index 00000000000..4a614e293f7 --- /dev/null +++ b/changelog/3.2.0_2023-02-15/10441.md @@ -0,0 +1,6 @@ +Bugfix: Enable next button upon user input on WebFinger setup wizard page + +After a change to how the next button is controlled by user input on the page, +we forgot to make this change on the WebFinger page, too. + +https://github.com/owncloud/client/issues/10441 diff --git a/changelog/3.2.0_2023-02-15/10456.md b/changelog/3.2.0_2023-02-15/10456.md new file mode 100644 index 00000000000..28bb4501b78 --- /dev/null +++ b/changelog/3.2.0_2023-02-15/10456.md @@ -0,0 +1,3 @@ +Bugfix: Only hide non VFS settings when enforced and on Windows + +https://github.com/owncloud/client/issues/10456 diff --git a/changelog/3.2.0_2023-02-15/10468.md b/changelog/3.2.0_2023-02-15/10468.md new file mode 100644 index 00000000000..9b370e6a507 --- /dev/null +++ b/changelog/3.2.0_2023-02-15/10468.md @@ -0,0 +1,5 @@ +Bugfix: Use selective sync settings from FolderWizard dialog + +Due to a bug the user selection was not persisted. + +https://github.com/owncloud/client/issues/10468 diff --git a/changelog/3.2.0_2023-02-15/10472.md b/changelog/3.2.0_2023-02-15/10472.md new file mode 100644 index 00000000000..191ded376b8 --- /dev/null +++ b/changelog/3.2.0_2023-02-15/10472.md @@ -0,0 +1,3 @@ +Bugfix: showVirtualFilesOption no longer influences the use of VFS + +https://github.com/owncloud/client/issues/10472 diff --git a/changelog/3.2.0_2023-02-15/10481.md b/changelog/3.2.0_2023-02-15/10481.md new file mode 100644 index 00000000000..2a6ecb131d1 --- /dev/null +++ b/changelog/3.2.0_2023-02-15/10481.md @@ -0,0 +1,3 @@ +Bugfix: Fix generation of Windows sidebar entry when VFS is disabled + +https://github.com/owncloud/client/pull/10481 diff --git a/changelog/3.2.0_2023-02-15/10500.md b/changelog/3.2.0_2023-02-15/10500.md new file mode 100644 index 00000000000..40c8ca4bfd6 --- /dev/null +++ b/changelog/3.2.0_2023-02-15/10500.md @@ -0,0 +1,6 @@ +Bugfix: Empty credentials for signed out accounts + +We fixed a bug where we saved empty credentials for logged out accounts. +Under certain conditions we now tried access the server without any authentication. + +https://github.com/owncloud/client/issues/10500 diff --git a/changelog/3.2.1_2023-03-07/10552.md b/changelog/3.2.1_2023-03-07/10552.md new file mode 100644 index 00000000000..10e43d04906 --- /dev/null +++ b/changelog/3.2.1_2023-03-07/10552.md @@ -0,0 +1,6 @@ +Bugfix: Fix crash in Linux AppImage updater + +In between the releases 3.0.0 and 3.1.0, a dependency was updated, causing an ABI +incompatibility with another library. We resolved this by rebuilding both libraries. + +https://github.com/owncloud/client/issues/10552 diff --git a/changelog/3.2.1_2023-03-07/10564.md b/changelog/3.2.1_2023-03-07/10564.md new file mode 100644 index 00000000000..2233e14d17e --- /dev/null +++ b/changelog/3.2.1_2023-03-07/10564.md @@ -0,0 +1,5 @@ +Bugfix: Fix encoding issue with basic authentication + +In 3.0 we introduced an encoding issue for basic authentication. + +https://github.com/owncloud/client/issues/10564 diff --git a/changelog/3.2.1_2023-03-07/enterprise-5612.md b/changelog/3.2.1_2023-03-07/enterprise-5612.md new file mode 100644 index 00000000000..ff7f5ebe31a --- /dev/null +++ b/changelog/3.2.1_2023-03-07/enterprise-5612.md @@ -0,0 +1,5 @@ +Bugfix: Handle hide VirtualFiles options as disable VirtualFiles + +Due to legacy reasons the branding flag and its function differ. + +https://github.com/owncloud/enterprise/issues/5612 diff --git a/changelog/4.0.0_2023-05-11/10231.md b/changelog/4.0.0_2023-05-11/10231.md new file mode 100644 index 00000000000..4a7549680f4 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10231.md @@ -0,0 +1,10 @@ +Enhancement: Replace term (un)pause with a more suitable one + +We used the terms pause and unpause synchronization where the synchronization was actually +terminated and restarted. + +Now, we use the terms stop and start synchronization, which better communicate the actual +behavior. + +https://github.com/owncloud/client/issues/10231 +https://github.com/owncloud/client/issues/10529 diff --git a/changelog/4.0.0_2023-05-11/10251.md b/changelog/4.0.0_2023-05-11/10251.md new file mode 100644 index 00000000000..014274e276d --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10251.md @@ -0,0 +1,10 @@ +Bugfix: Fix update channel dropdown + +When switching to the beta update channel in testpilotcloud, a warning will +pop up. When canceled, the selection should reset. This did not work reliably +in localized clients due to the use of string comparisons. Using a numeric +index to keep track of the old value fixes the problem. + +https://github.com/owncloud/client/issues/10251 +https://github.com/owncloud/client/pull/10609 +https://github.com/owncloud/client/pull/10627 diff --git a/changelog/4.0.0_2023-05-11/10254.md b/changelog/4.0.0_2023-05-11/10254.md new file mode 100644 index 00000000000..35dc141fa0d --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10254.md @@ -0,0 +1,3 @@ +Bugfix: Select keep files as default action in all files removed dialog + +https://github.com/owncloud/client/issues/10254 diff --git a/changelog/4.0.0_2023-05-11/10352.md b/changelog/4.0.0_2023-05-11/10352.md new file mode 100644 index 00000000000..c6b12d554ac --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10352.md @@ -0,0 +1,6 @@ +Enhancement: Check for long paths when these are not enabled on Windows + +https://github.com/owncloud/client/issues/10264 +https://github.com/owncloud/client/pull/10352 +https://github.com/owncloud/client/issues/10677 +https://github.com/owncloud/client/pull/10679 diff --git a/changelog/4.0.0_2023-05-11/10361.md b/changelog/4.0.0_2023-05-11/10361.md new file mode 100644 index 00000000000..d23c6b9cd19 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10361.md @@ -0,0 +1,6 @@ +Enhancement: Implement a possibility to Desktop.ini icon updates on Windows + +We implemented an option which allows to disable the automatic update of the folder icon for +sync folders on Windows. + +https://github.com/owncloud/client/issues/10361 diff --git a/changelog/4.0.0_2023-05-11/10406.md b/changelog/4.0.0_2023-05-11/10406.md new file mode 100644 index 00000000000..7cbe472f32c --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10406.md @@ -0,0 +1,6 @@ +Enhancement: Disable DPI scaling factor rounding on Windows + +Qt5 By default rounds up the selected scale for values > .5 +We now use the actual scale factor. + +https://github.com/owncloud/client/issues/10406 diff --git a/changelog/4.0.0_2023-05-11/10408.md b/changelog/4.0.0_2023-05-11/10408.md new file mode 100644 index 00000000000..646b6e16eea --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10408.md @@ -0,0 +1,8 @@ +Bugfix: Undefined client state after fetching credentials failed + +We fixed a bug where the client got stuck in a "No connection configured" state if an +error occurred during the retrieval of the credentials from the system's keychain. +The client then required a manual log out and log in. + +https://github.com/owncloud/client/issues/10408 +https://github.com/owncloud/client/pull/10505 diff --git a/changelog/4.0.0_2023-05-11/10427.md b/changelog/4.0.0_2023-05-11/10427.md new file mode 100644 index 00000000000..06120dbd2df --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10427.md @@ -0,0 +1,5 @@ +Bugfix: Escape navigation elements from user name + +We use the user name as folder name, if a user name contains path navigation elements we now escape them. + +https://github.com/owncloud/client/issues/10427 diff --git a/changelog/4.0.0_2023-05-11/10453.md b/changelog/4.0.0_2023-05-11/10453.md new file mode 100644 index 00000000000..2ce1684b576 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10453.md @@ -0,0 +1,5 @@ +Enhancement: Display quota for Spaces + +Quota is now displayed for all folders. + +https://github.com/owncloud/client/issues/10453 diff --git a/changelog/4.0.0_2023-05-11/10458.md b/changelog/4.0.0_2023-05-11/10458.md new file mode 100644 index 00000000000..815d906eace --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10458.md @@ -0,0 +1,3 @@ +Bugfix: Under certain conditions the folder watcher could drop events + +https://github.com/owncloud/client/pull/10458 diff --git a/changelog/4.0.0_2023-05-11/10494.md b/changelog/4.0.0_2023-05-11/10494.md new file mode 100644 index 00000000000..6db774844b4 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10494.md @@ -0,0 +1,4 @@ +Bugfix: Properly handle OAuth invalidation + +https://github.com/owncloud/client/issues/10494 +https://github.com/owncloud/client/issues/10495 diff --git a/changelog/4.0.0_2023-05-11/10517.md b/changelog/4.0.0_2023-05-11/10517.md new file mode 100644 index 00000000000..7292b22a459 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10517.md @@ -0,0 +1,13 @@ +Enhancement: New WebFinger workflow + +ownCloud Infinite Scale servers may provide a new WebFinger service which +clients can use to discover an actual instance they should connect to. + +This is useful when a large organization operates multiple instances to +distribute the load on multiple servers, or single departments operate their +own servers, but they all use a common entrypoint URL and authentication +system. + +https://github.com/owncloud/client/issues/10517 +https://github.com/owncloud/client/pull/10665 +https://owncloud.dev/services/webfinger/ diff --git a/changelog/4.0.0_2023-05-11/10518.md b/changelog/4.0.0_2023-05-11/10518.md new file mode 100644 index 00000000000..2a7bb4cd1fb --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10518.md @@ -0,0 +1,6 @@ +Enhancement: Branding: Allow the addition of buttons to open a URL + +We added a branding option that allows to add buttons to the toolbar. +Those buttons will then open a hardcoded web page. + +https://github.com/owncloud/client/issues/10518 diff --git a/changelog/4.0.0_2023-05-11/10537.md b/changelog/4.0.0_2023-05-11/10537.md new file mode 100644 index 00000000000..b39831ddcae --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10537.md @@ -0,0 +1,3 @@ +Enhancement: Build the client without the auto updater by default + +https://github.com/owncloud/client/issues/10537 diff --git a/changelog/4.0.0_2023-05-11/10557.md b/changelog/4.0.0_2023-05-11/10557.md new file mode 100644 index 00000000000..e9c687f6c0b --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10557.md @@ -0,0 +1,6 @@ +Enhancement: Improve presentation of connection state + +Don't display `No connection configured` when disconnected but `Disconnected from:`. +Introduce new state, `Connecting to`. + +https://github.com/owncloud/client/issues/10557 diff --git a/changelog/4.0.0_2023-05-11/10569.md b/changelog/4.0.0_2023-05-11/10569.md new file mode 100644 index 00000000000..6033de18637 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10569.md @@ -0,0 +1,3 @@ +Bugfix: Clear conflict errors after they were resolved + +https://github.com/owncloud/client/issues/10569 diff --git a/changelog/4.0.0_2023-05-11/10572.md b/changelog/4.0.0_2023-05-11/10572.md new file mode 100644 index 00000000000..8cdcfe985dd --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10572.md @@ -0,0 +1,3 @@ +Enhancement: Display open in web in file browser context menu, on sync roots + +https://github.com/owncloud/client/issues/10572 diff --git a/changelog/4.0.0_2023-05-11/10577.md b/changelog/4.0.0_2023-05-11/10577.md new file mode 100644 index 00000000000..9e073a35305 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10577.md @@ -0,0 +1,5 @@ +Enhancement: Display space subtitle if available + +If a subtitle is set on a spaced we now display it instead of the local path. + +https://github.com/owncloud/client/issues/10577 diff --git a/changelog/4.0.0_2023-05-11/10590.md b/changelog/4.0.0_2023-05-11/10590.md new file mode 100644 index 00000000000..8b62866b0f0 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10590.md @@ -0,0 +1,6 @@ +Enhancement: Open sharing options in Web + +When selecting "Share…" in the file browser context menu, we now +not only display that file in the web view but also directly show the sharing options. + +https://github.com/owncloud/client/pull/10590 diff --git a/changelog/4.0.0_2023-05-11/10602.md b/changelog/4.0.0_2023-05-11/10602.md new file mode 100644 index 00000000000..9f4f5b060f9 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10602.md @@ -0,0 +1,5 @@ +Enhancement: Display the space name provided by the server + +If a space is renamed on the server, this change is now displayed in the client. + +https://github.com/owncloud/client/pull/10602 diff --git a/changelog/4.0.0_2023-05-11/10607.md b/changelog/4.0.0_2023-05-11/10607.md new file mode 100644 index 00000000000..fbc59164063 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10607.md @@ -0,0 +1,3 @@ +Bugfix: Set the expected download content length in the job properly + +https://github.com/owncloud/client/pull/10607 diff --git a/changelog/4.0.0_2023-05-11/10621.md b/changelog/4.0.0_2023-05-11/10621.md new file mode 100644 index 00000000000..91b43b656e3 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10621.md @@ -0,0 +1,5 @@ +Bugfix: UI freeze when multiple modal dialogs displayed on macOS + +Due to incorrect handling of the dialogs, the UI could freeze. + +https://github.com/owncloud/client/issues/10621 diff --git a/changelog/4.0.0_2023-05-11/10634.md b/changelog/4.0.0_2023-05-11/10634.md new file mode 100644 index 00000000000..78bdd626bb0 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10634.md @@ -0,0 +1,7 @@ +Enhancement: Always point to the latest documentation + +We used a URL for the documentation that contained the client version. +While this ensured a used would get the documentation matching to their client, this only worked with the last two released clients. +So users of a pre-release or of a very old client where served a 404. + +https://github.com/owncloud/client/issues/10634 diff --git a/changelog/4.0.0_2023-05-11/10637.md b/changelog/4.0.0_2023-05-11/10637.md new file mode 100644 index 00000000000..41ec3580cdf --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10637.md @@ -0,0 +1,3 @@ +Enhancement: More user-friendly error handling in the cmd client + +https://github.com/owncloud/client/pull/10637 diff --git a/changelog/4.0.0_2023-05-11/10648.md b/changelog/4.0.0_2023-05-11/10648.md new file mode 100644 index 00000000000..71c27cb79bd --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10648.md @@ -0,0 +1,5 @@ +Bugfix: Ensure the account state is properly persisted + +We didn't properly persist the login/logout state. + +https://github.com/owncloud/client/issues/10648 diff --git a/changelog/4.0.0_2023-05-11/10654.md b/changelog/4.0.0_2023-05-11/10654.md new file mode 100644 index 00000000000..76dd2926834 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10654.md @@ -0,0 +1,3 @@ +Enhancement: Don't invalidate OAuth refresh token on ConnectionRefusedError + +https://github.com/owncloud/client/issues/10654 diff --git a/changelog/4.0.0_2023-05-11/10666.md b/changelog/4.0.0_2023-05-11/10666.md new file mode 100644 index 00000000000..a5951e279dc --- /dev/null +++ b/changelog/4.0.0_2023-05-11/10666.md @@ -0,0 +1,3 @@ +Bugfix: Reconnect got stuck if the server supports app providers + +https://github.com/owncloud/client/issues/10666 diff --git a/changelog/4.0.0_2023-05-11/8953.md b/changelog/4.0.0_2023-05-11/8953.md new file mode 100644 index 00000000000..33f4c505c17 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/8953.md @@ -0,0 +1,3 @@ +Bugfix: Display sync date in system tray context menu when files where ignored + +https://github.com/owncloud/client/issues/8953 diff --git a/changelog/4.0.0_2023-05-11/9270.md b/changelog/4.0.0_2023-05-11/9270.md new file mode 100644 index 00000000000..65972e6f473 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/9270.md @@ -0,0 +1,5 @@ +Bugfix: Fix computation of sync status when multiple folders are synced + +https://github.com/owncloud/client/issues/9270 +https://github.com/owncloud/client/pull/10536 +https://github.com/owncloud/client/pull/10642 diff --git a/changelog/4.0.0_2023-05-11/9311.md b/changelog/4.0.0_2023-05-11/9311.md new file mode 100644 index 00000000000..7dfea7e8c11 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/9311.md @@ -0,0 +1,6 @@ +Bugfix: Don't abort local folder deletions on unrelated errors + +We fixed a bug where folder deletions where not performed if any sync error +occurred anywhere in the sync tree. + +https://github.com/owncloud/client/issues/9311 diff --git a/changelog/4.0.0_2023-05-11/9700.md b/changelog/4.0.0_2023-05-11/9700.md new file mode 100644 index 00000000000..7b79bddf146 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/9700.md @@ -0,0 +1,5 @@ +Enhancement: Display `This space is either disabled or deleted` for gone spaces + +We now provide a less technical error when a space was disabled or deleted. + +https://github.com/owncloud/client/issues/9700 diff --git a/changelog/4.0.0_2023-05-11/9829.md b/changelog/4.0.0_2023-05-11/9829.md new file mode 100644 index 00000000000..652dfc59c15 --- /dev/null +++ b/changelog/4.0.0_2023-05-11/9829.md @@ -0,0 +1,7 @@ +Enhancement: Sync virtual files that are locked by office etc + +We now upload locked files again when Windows virtual files are used. +This was disabled in 2.9.0 as it caused the file metadata and the locked files to get out of sync. +The new solution implements explicit handling of outdated placeholders. + +https://github.com/owncloud/client/issues/9829 \ No newline at end of file diff --git a/changelog/4.0.0_2023-05-11/9945.md b/changelog/4.0.0_2023-05-11/9945.md new file mode 100644 index 00000000000..5e5ea61596d --- /dev/null +++ b/changelog/4.0.0_2023-05-11/9945.md @@ -0,0 +1,6 @@ +Change: Implement server guided migration to spaces + +We added a feature that allows limited automatic migration of existing syncs to oCIS spaces. + +https://github.com/owncloud/client/issues/9945 +https://github.com/owncloud/ocis/issues/3574 diff --git a/changelog/4.1.0_2023-06-15/10842.md b/changelog/4.1.0_2023-06-15/10842.md new file mode 100644 index 00000000000..ebbb0a62325 --- /dev/null +++ b/changelog/4.1.0_2023-06-15/10842.md @@ -0,0 +1,3 @@ +Bugfix: owncloudcmd: Ensure plugin path is initialized on Linux + +https://github.com/owncloud/client/issues/10842 diff --git a/changelog/4.1.0_2023-06-15/10856.md b/changelog/4.1.0_2023-06-15/10856.md new file mode 100644 index 00000000000..e1d68e3c4f4 --- /dev/null +++ b/changelog/4.1.0_2023-06-15/10856.md @@ -0,0 +1,6 @@ +Bugfix: Use correct username with basic auth and LDAP + +With basic auth the name from the login credentials might not align with the name on the server. +We corrected the assumption and no longer change the username. + +https://github.com/owncloud/client/issues/10856 diff --git a/changelog/4.1.0_2023-06-15/10860.md b/changelog/4.1.0_2023-06-15/10860.md new file mode 100644 index 00000000000..677ad5cca6d --- /dev/null +++ b/changelog/4.1.0_2023-06-15/10860.md @@ -0,0 +1,8 @@ +Bugfix: Show systray status when a sync error occurred + +The systray menu for the ownCloud icon has an entry that shows the sync +status. However, when a sync error occurred, the entry would have no +text in it. This has now been fixed. + +https://github.com/owncloud/client/issues/10834 + diff --git a/changelog/4.1.0_2023-06-15/10888.md b/changelog/4.1.0_2023-06-15/10888.md new file mode 100644 index 00000000000..c0d481bc0dc --- /dev/null +++ b/changelog/4.1.0_2023-06-15/10888.md @@ -0,0 +1,7 @@ +Enhancement: Allow users to call commandline client from AppImage + +When the AppImage is called with the new commandline parameter --cmd, it will +internally dispatch the call to the contained owncloudcmd binary. Otherwise, +the call will be forwarded to the regular owncloud GUI binary as before. + +https://github.com/owncloud/client/issues/10888 diff --git a/changelog/4.1.0_2023-06-15/enterprise-5743.md b/changelog/4.1.0_2023-06-15/enterprise-5743.md new file mode 100644 index 00000000000..cb3a83086dd --- /dev/null +++ b/changelog/4.1.0_2023-06-15/enterprise-5743.md @@ -0,0 +1,6 @@ +Bugfix: Follow same site redirects in the Wizard + +We fixed a bug where the client did not follow same site redirects during the setup. +If the used URL https://test.com/owncloud redirected to https://test.com the new URL was not correctly used for the newly created account. + +https://github.com/owncloud/enterprise/issues/5743 diff --git a/changelog/4.2.0_2023-08-07/10946.md b/changelog/4.2.0_2023-08-07/10946.md new file mode 100644 index 00000000000..e87fa1daef0 --- /dev/null +++ b/changelog/4.2.0_2023-08-07/10946.md @@ -0,0 +1,5 @@ +Bugfix: Wrong davUser in cmd client + +We fixed a bug where the wrong dav user was used in the URL, when the username from the credentials differed from the username used on the server. + +https://github.com/owncloud/client/issues/10946 diff --git a/changelog/4.2.0_2023-08-07/10950.md b/changelog/4.2.0_2023-08-07/10950.md new file mode 100644 index 00000000000..ce59a975f7d --- /dev/null +++ b/changelog/4.2.0_2023-08-07/10950.md @@ -0,0 +1,7 @@ +Bugfix: Missing mtime in oCIS uploads + +When uploading a file to oCIS the server ignores the X-OC-Mtime header +and expects the mtime to be part of the Upload-Metadata header. +We now provide both. + +https://github.com/owncloud/client/issues/10950 diff --git a/changelog/5.0.0_2023-10-05/10197.md b/changelog/5.0.0_2023-10-05/10197.md new file mode 100644 index 00000000000..9e56a03c445 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10197.md @@ -0,0 +1,3 @@ +Enhancement: Make "Show files versions…" context menu action available + +https://github.com/owncloud/client/issues/10197 diff --git a/changelog/5.0.0_2023-10-05/10223.md b/changelog/5.0.0_2023-10-05/10223.md new file mode 100644 index 00000000000..67520e2a089 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10223.md @@ -0,0 +1,5 @@ +Bugfix: VFS Placeholders can now be replaced with folders + +If a local VFS placeholder is replaced with a folder on the server, the client previously failed to populate the change. + +https://github.com/owncloud/client/issues/10223 diff --git a/changelog/5.0.0_2023-10-05/10313.md b/changelog/5.0.0_2023-10-05/10313.md new file mode 100644 index 00000000000..30316f3b7fc --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10313.md @@ -0,0 +1,6 @@ +Enhancement: Log http request when it is sent + +We now log the http request when it is sent, not when it is created. +This will enable us to print more headers set by the platform and measure the request time more accurately. + +https://github.com/owncloud/client/issues/10313 diff --git a/changelog/5.0.0_2023-10-05/10351.md b/changelog/5.0.0_2023-10-05/10351.md new file mode 100644 index 00000000000..4c2842333a2 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10351.md @@ -0,0 +1,3 @@ +Bugfix: Authentication dialog no longer appears again and again + +https://github.com/owncloud/client/issues/10351 diff --git a/changelog/5.0.0_2023-10-05/10596.md b/changelog/5.0.0_2023-10-05/10596.md new file mode 100644 index 00000000000..a201ad002e8 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10596.md @@ -0,0 +1,11 @@ +Enhancement: Allow selective sync of spaces in folder wizard + +When manually configuring the synchronization of spaces, we now show the +selective sync page again to allow users to selectively disable the +synchronization of remote directories. + +This prevents the unnecessary synchronization of files until the user can use +the account settings dialog to configure selective sync. + +https://github.com/owncloud/client/issues/10596 +https://github.com/owncloud/client/pull/11044 diff --git a/changelog/5.0.0_2023-10-05/10619.md b/changelog/5.0.0_2023-10-05/10619.md new file mode 100644 index 00000000000..95ba0bec353 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10619.md @@ -0,0 +1,8 @@ +Enhancement: send a language header in all http requests + +All http requests to the server will now contain the language for the +client. This can be used by the server to localize responses like error +messages. + +https://github.com/owncloud/client/issues/10619 +https://github.com/owncloud/client/pull/11020 diff --git a/changelog/5.0.0_2023-10-05/10680.md b/changelog/5.0.0_2023-10-05/10680.md new file mode 100644 index 00000000000..1b0cfd47df2 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10680.md @@ -0,0 +1,8 @@ +Enhancement: Help user fix problems on the last setup wizard page + +When there are problems within the advanced configuration on the last setup +wizard page, we no longer just show an error message but also "un-hide" those +settings to give the user a chance of fixing the problem. + +https://github.com/owncloud/client/issues/10680 +https://github.com/owncloud/client/pull/11029 diff --git a/changelog/5.0.0_2023-10-05/10751.md b/changelog/5.0.0_2023-10-05/10751.md new file mode 100644 index 00000000000..7168e91d100 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10751.md @@ -0,0 +1,6 @@ +Enhancement: Display a progress spinner during the initial setup + +We now display a progress spinner after the setup wizard, until +the client is done setting the account up. + +https://github.com/owncloud/client/issues/10751 diff --git a/changelog/5.0.0_2023-10-05/10788.md b/changelog/5.0.0_2023-10-05/10788.md new file mode 100644 index 00000000000..e12d42ee019 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10788.md @@ -0,0 +1,6 @@ +Change: Remove support for sidebar entries for non-vfs setups on Windows + +For vfs setups, this is handled by Windows itself. + +https://github.com/owncloud/client/issues/10788 +https://github.com/owncloud/client/pull/10910 diff --git a/changelog/5.0.0_2023-10-05/10825.md b/changelog/5.0.0_2023-10-05/10825.md new file mode 100644 index 00000000000..d7be3663e6f --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10825.md @@ -0,0 +1,7 @@ +Enhancement: Reduce how often file changes are handled + +We no longer handle file changes every 1s but gather them for 10s and handle them then. + +This should reduce the amount of unnecessary checksum computations and attempted syncs. + +https://github.com/owncloud/client/pull/10825 diff --git a/changelog/5.0.0_2023-10-05/10866.md b/changelog/5.0.0_2023-10-05/10866.md new file mode 100644 index 00000000000..d87959cc6d9 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10866.md @@ -0,0 +1,5 @@ +Change: Remove support for client side system proxy credentials + +We removed the support to query and store credentials for the system proxy. + +https://github.com/owncloud/client/pull/10866 diff --git a/changelog/5.0.0_2023-10-05/10905.md b/changelog/5.0.0_2023-10-05/10905.md new file mode 100644 index 00000000000..41664de0f7b --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10905.md @@ -0,0 +1,5 @@ +Bugfix: Possible deadlock during log setup + +If an error occurred during the setup of the log file, it was possible that the client got deadlocked. + +https://github.com/owncloud/client/pull/10905 diff --git a/changelog/5.0.0_2023-10-05/10919.md b/changelog/5.0.0_2023-10-05/10919.md new file mode 100644 index 00000000000..a2f42be89a6 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10919.md @@ -0,0 +1,9 @@ +Bugfix: Do not create default sync root when loading accounts + +Creating the default sync root is only needed at initial setup. If a +user chooses to point all folders for Spaces outside that default sync +root, and deletes the default sync root folder, it will now no longer be +created. + +https://github.com/owncloud/client/issues/10919 +https://github.com/owncloud/client/pull/11128 diff --git a/changelog/5.0.0_2023-10-05/10928.md b/changelog/5.0.0_2023-10-05/10928.md new file mode 100644 index 00000000000..fb8f3e04ded --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10928.md @@ -0,0 +1,8 @@ +Enhancement: Persist filter settings for Not Synced tab + +We now store the filter settings in the Not Synced tab. So after a +restart of the client, the filter will still be the same as the last +time. + +https://github.com/owncloud/client/pull/10928 +https://github.com/owncloud/client/issues/10884 diff --git a/changelog/5.0.0_2023-10-05/10939.md b/changelog/5.0.0_2023-10-05/10939.md new file mode 100644 index 00000000000..b1f228d4d41 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10939.md @@ -0,0 +1,7 @@ +Change: Modernize systray menu + +We had a closer look on the system tray and cleaned it up. + +https://github.com/owncloud/client/issues/10939 +https://github.com/owncloud/client/pull/10949 +https://github.com/owncloud/client/pull/10999 diff --git a/changelog/5.0.0_2023-10-05/10976.md b/changelog/5.0.0_2023-10-05/10976.md new file mode 100644 index 00000000000..a079c4bd381 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10976.md @@ -0,0 +1,7 @@ +Enhancement: `--cmd` argument added to the GUI client + +In 4.1 we added a `--cmd` argument to the AppImage to allow users to use the commandline client contained in the AppImage more easily. + +The `--cmd` argument is now supported on all platforms to support consistency. + +https://github.com/owncloud/client/issues/10976 diff --git a/changelog/5.0.0_2023-10-05/10978.md b/changelog/5.0.0_2023-10-05/10978.md new file mode 100644 index 00000000000..09d7f292d0e --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10978.md @@ -0,0 +1,6 @@ +Bugfix: Update capabilities and other info after connect + +We fixed a bug where server infos might have only been updated several minutes +after the client connected to the server. + +https://github.com/owncloud/client/issues/10978 diff --git a/changelog/5.0.0_2023-10-05/10990.md b/changelog/5.0.0_2023-10-05/10990.md new file mode 100644 index 00000000000..f0b2356b065 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/10990.md @@ -0,0 +1,5 @@ +Bugfix: Account activity and crash after an account was removed + +We fixed a bug where an account performed actions after it was removed. + +https://github.com/owncloud/client/issues/10990 diff --git a/changelog/5.0.0_2023-10-05/11016.md b/changelog/5.0.0_2023-10-05/11016.md new file mode 100644 index 00000000000..6e38fca8397 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11016.md @@ -0,0 +1,5 @@ +Bugfix: Crash during application shutdown + +We fixed a where the main instance was accessed after it was destroyed. + +https://github.com/owncloud/client/issues/11016 diff --git a/changelog/5.0.0_2023-10-05/11047.md b/changelog/5.0.0_2023-10-05/11047.md new file mode 100644 index 00000000000..01fd57c8450 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11047.md @@ -0,0 +1,3 @@ +Bugfix: Hide hidden folders again in the selective sync view + +https://github.com/owncloud/client/issues/11047 diff --git a/changelog/5.0.0_2023-10-05/11081.md b/changelog/5.0.0_2023-10-05/11081.md new file mode 100644 index 00000000000..e4c678f828d --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11081.md @@ -0,0 +1,5 @@ +Change: We removed the pre 2.9 credentials migration + +Migrating credentials from a 2.8 or older client is no longer possible. + +https://github.com/owncloud/client/pull/11081 diff --git a/changelog/5.0.0_2023-10-05/11150.md b/changelog/5.0.0_2023-10-05/11150.md new file mode 100644 index 00000000000..75243162f22 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11150.md @@ -0,0 +1,7 @@ +Change: Extract Nautilus + +While the crash reporter still requires a graphical user interface to work, this +change nevertheless simplifies reporting crashes from the commandline client +a lot. + +https://github.com/owncloud/client/issues/8991 diff --git a/changelog/5.0.0_2023-10-05/11157.md b/changelog/5.0.0_2023-10-05/11157.md new file mode 100644 index 00000000000..76fc6f8e04b --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11157.md @@ -0,0 +1,7 @@ +Change: Remove libcloudproviders integration + +Since its implementation in 2019 our support our for it was 'limited'. +Additionally, as far as we can tell it was not picked up by any major Linux distribution. + +https://github.com/owncloud/client/issues/11148 +https://github.com/owncloud/client/pull/11157 diff --git a/changelog/5.0.0_2023-10-05/11162.md b/changelog/5.0.0_2023-10-05/11162.md new file mode 100644 index 00000000000..d826e56e9e2 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11162.md @@ -0,0 +1,6 @@ +Bugfix: Hydration state of file after a directory was replaced with a file + +If a synced directory was replaced with a file on the server, we downloaded it instead of +replacing the folder with a VirtualFile placeholder. + +https://github.com/owncloud/client/pull/11162 diff --git a/changelog/5.0.0_2023-10-05/11168.md b/changelog/5.0.0_2023-10-05/11168.md new file mode 100644 index 00000000000..e7acdfb1b1a --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11168.md @@ -0,0 +1,5 @@ +Bugfix: Only synchronize after the server settings where refreshed + +We fixed an issue where the sync was started on outdated server settings. + +https://github.com/owncloud/client/issues/11168 diff --git a/changelog/5.0.0_2023-10-05/11190.md b/changelog/5.0.0_2023-10-05/11190.md new file mode 100644 index 00000000000..2e200584197 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11190.md @@ -0,0 +1,7 @@ +Bugfix: Consistently use the same icon for folders + +In 4.0 we introduced a new icon for sync connections. +In some places however we continued to use the system provided icon for folders, +this resulted in an inconsistent application look. + +https://github.com/owncloud/client/pull/11190 diff --git a/changelog/5.0.0_2023-10-05/11192.md b/changelog/5.0.0_2023-10-05/11192.md new file mode 100644 index 00000000000..a1b6f1fcbcb --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11192.md @@ -0,0 +1,5 @@ +Bugfix: Branding of folder status overlay + +We fixed a bug where branded overlay icons for the display of the sync status where not used. + +https://github.com/owncloud/client/pull/11192 diff --git a/changelog/5.0.0_2023-10-05/11232.md b/changelog/5.0.0_2023-10-05/11232.md new file mode 100644 index 00000000000..131c483dbf6 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11232.md @@ -0,0 +1,5 @@ +Bugfix: Creation of folder on the server + +We fixed an issue in the FolderSyncWizard when the user provided a path with multiple slashes (//folder). + +https://github.com/owncloud/client/issues/11232 diff --git a/changelog/5.0.0_2023-10-05/11233.md b/changelog/5.0.0_2023-10-05/11233.md new file mode 100644 index 00000000000..4ac5ff4a683 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11233.md @@ -0,0 +1,3 @@ +Bugfix: Selective sync when not the remote root is synced + +https://github.com/owncloud/client/issues/11233 diff --git a/changelog/5.0.0_2023-10-05/11242.md b/changelog/5.0.0_2023-10-05/11242.md new file mode 100644 index 00000000000..ee7c6adb7f5 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/11242.md @@ -0,0 +1,9 @@ +Change: Disable context menu item to get a public link for a space + +The context menu item "Create and copy public link to clipboard" didn't +work correctly when used with Spaces: users were always redirected to +the web frontend. This menu item is now disabled, waiting for a +server-side changes. + +https://github.com/owncloud/client/issues/11242 +https://github.com/owncloud/client/pull/11079 diff --git a/changelog/5.0.0_2023-10-05/261.md b/changelog/5.0.0_2023-10-05/261.md new file mode 100644 index 00000000000..f5b1125721b --- /dev/null +++ b/changelog/5.0.0_2023-10-05/261.md @@ -0,0 +1,3 @@ +Enhancement: Store proxy password securely + +https://github.com/owncloud/client/issues/261 diff --git a/changelog/5.0.0_2023-10-05/8216.md b/changelog/5.0.0_2023-10-05/8216.md new file mode 100644 index 00000000000..1fae8e749de --- /dev/null +++ b/changelog/5.0.0_2023-10-05/8216.md @@ -0,0 +1,7 @@ +Enhancement: Windows VFS: Prevent rename to ignored file name + +We now prevent renaming of virtual files to ignored file names. +This basically resulted in deletion on the server and a local placeholder that pointed to a non-existing file. + +https://github.com/owncloud/client/issues/8216 +https://github.com/owncloud/client-desktop-vfs-win/pull/63 diff --git a/changelog/5.0.0_2023-10-05/8360.md b/changelog/5.0.0_2023-10-05/8360.md new file mode 100644 index 00000000000..46aa8903dfa --- /dev/null +++ b/changelog/5.0.0_2023-10-05/8360.md @@ -0,0 +1,9 @@ +Enhancement: Change how all files deleted is handled + +When the client detects that all files are deleted on either side of the sync, +the client now aborts the sync and asks for a user decision. +Afterwords a new sync is started. + +Previously we blocked the current sync until the user responded which could lead to unwanted side effects. + +https://github.com/owncloud/client/issues/8360 diff --git a/changelog/5.0.0_2023-10-05/8432.md b/changelog/5.0.0_2023-10-05/8432.md new file mode 100644 index 00000000000..dba4099951f --- /dev/null +++ b/changelog/5.0.0_2023-10-05/8432.md @@ -0,0 +1,7 @@ +Enhancement: Port from QtSingleApplication to KDSingleApplication + +Instead of relying on an old copy of QtSingleApplication we use https://github.com/KDAB/KDSingleApplication + +https://github.com/owncloud/client/issues/8432 +https://github.com/owncloud/client/pull/10754 +https://github.com/owncloud/client/pull/10758 diff --git a/changelog/5.0.0_2023-10-05/8991.md b/changelog/5.0.0_2023-10-05/8991.md new file mode 100644 index 00000000000..63b2c070c53 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/8991.md @@ -0,0 +1,7 @@ +Enhancement: Enable crash reporter in commandline client + +While the crash reporter still requires a graphical user interface to work, this +change nevertheless simplifies reporting crashes from the commandline client +a lot. + +https://github.com/owncloud/client/issues/8991 diff --git a/changelog/5.0.0_2023-10-05/client-desktop-vfs-win-19.md b/changelog/5.0.0_2023-10-05/client-desktop-vfs-win-19.md new file mode 100644 index 00000000000..ae943589bb5 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/client-desktop-vfs-win-19.md @@ -0,0 +1,7 @@ +Bugfix: Fix check if a file is a placeholder + +Previously, files with zero bytes in size were considered placeholders. +This is not correct, and has been fixed by using the correct API to +check for placeholders. + +https://github.com/owncloud/client/issues/11107 diff --git a/changelog/5.0.0_2023-10-05/enterprise-5682.md b/changelog/5.0.0_2023-10-05/enterprise-5682.md new file mode 100644 index 00000000000..577aafd8ca0 --- /dev/null +++ b/changelog/5.0.0_2023-10-05/enterprise-5682.md @@ -0,0 +1,5 @@ +Bugfix: Avoid duplicate notifications when selective sync is enabled + +We no longer show multiple messages about files which were excluded from the sync because they were excluded by the users. + +https://github.com/owncloud/enterprise/issues/5682 diff --git a/changelog/5.1.0_2023-11-02/11226.md b/changelog/5.1.0_2023-11-02/11226.md new file mode 100644 index 00000000000..0ba258d0d1b --- /dev/null +++ b/changelog/5.1.0_2023-11-02/11226.md @@ -0,0 +1,5 @@ +Bugfix: Crash when accepting a notification + +We fixed a potential crash when clicking on a notification. + +https://github.com/owncloud/client/issues/11226 diff --git a/changelog/5.1.0_2023-11-02/11288.md b/changelog/5.1.0_2023-11-02/11288.md new file mode 100644 index 00000000000..a7fe47e890a --- /dev/null +++ b/changelog/5.1.0_2023-11-02/11288.md @@ -0,0 +1,8 @@ +Bugfix: Fix crash on start-up when starting shell integration + +A possible crash has been fixed that could occur during start-up, when +the shell integration started doing requests before the client itself +completed starting up. + +https://github.com/owncloud/client/issues/11280 +https://github.com/owncloud/client/pull/11288 diff --git a/changelog/5.1.0_2023-11-02/11308.md b/changelog/5.1.0_2023-11-02/11308.md new file mode 100644 index 00000000000..7508d38fe7d --- /dev/null +++ b/changelog/5.1.0_2023-11-02/11308.md @@ -0,0 +1,6 @@ +Bugfix: Properly schedule the sync after an account was added + +We fixed a bug where a folder was scheduled to be synced before the account reported it was ready. +This resulted in the sync having no effect and the folder was then only synced once we polled the etag. + +https://github.com/owncloud/client/issues/11308 diff --git a/changelog/5.1.0_2023-11-02/11313.md b/changelog/5.1.0_2023-11-02/11313.md new file mode 100644 index 00000000000..c0f90d79ea4 --- /dev/null +++ b/changelog/5.1.0_2023-11-02/11313.md @@ -0,0 +1,6 @@ +Bugfix: Don't start credentials save jobs during shutdown + +Due to a bug we "re-saved" the credentials during application shutdown. +As the application was quitting while the jobs where running we might have encountered corrupted credentials or crashes. + +https://github.com/owncloud/client/pull/11313 diff --git a/changelog/5.1.1_2023-11-09/11331.md b/changelog/5.1.1_2023-11-09/11331.md new file mode 100644 index 00000000000..464b319ef2c --- /dev/null +++ b/changelog/5.1.1_2023-11-09/11331.md @@ -0,0 +1,7 @@ +Bugfix: Prevent permanent deletion of files when disabling VFS + +When a user disabled the virtual files feature on Windows, all currently dehydrated files were deleted accidentally. +On the server those files where still available in the trash bin. +This regression was introduced in the previous release 5.0.0. + +https://github.com/owncloud/client/issues/11331 diff --git a/changelog/5.1.2_2023-11-10/11361.md b/changelog/5.1.2_2023-11-10/11361.md new file mode 100644 index 00000000000..444a6a80f5a --- /dev/null +++ b/changelog/5.1.2_2023-11-10/11361.md @@ -0,0 +1,11 @@ +Bugfix: Fix crash when keychain job takes longer than expected + +In 5.1.1, we introduced a timeout for keychain jobs that killed them when their execution time exceeded 5 seconds and +log this to allow debugging of problems related to secrets handling. + +Apparently, there are valid cases in which these jobs can take longer than the expected timeout, e.g., when the user +needs to unlock their keychain first or configure it otherwise. + +We now let the jobs run indefinitely and log every 5 seconds whether the job is still running. + +https://github.com/owncloud/client/pull/11361 diff --git a/changelog/5.2.0_2023-11-27/11296.md b/changelog/5.2.0_2023-11-27/11296.md new file mode 100644 index 00000000000..74770dd9b0d --- /dev/null +++ b/changelog/5.2.0_2023-11-27/11296.md @@ -0,0 +1,8 @@ +Bugfix: Fix URL resolution for app provider + +We fixed a bug in the URL resolution for the app provider. +If owncloud is installed to a non-root directory we computed a wrong URL for the app provider urls. +This could trigger a connection validation in the client, which intern triggered a request to the app provider. +Resulting in an endless loop rendering the client unusable. + +https://github.com/owncloud/client/issues/11296 diff --git a/changelog/5.2.0_2023-11-27/11379.md b/changelog/5.2.0_2023-11-27/11379.md new file mode 100644 index 00000000000..26f55bd5943 --- /dev/null +++ b/changelog/5.2.0_2023-11-27/11379.md @@ -0,0 +1,5 @@ +Bugfix: Fix crash on unhandled status code on rename check + +We fixed a bug that caused the application to crash instead of just aborting the current sync. + +https://github.com/owncloud/client/issues/11379 diff --git a/changelog/5.2.1_2024-01-04/11274.md b/changelog/5.2.1_2024-01-04/11274.md new file mode 100644 index 00000000000..2eb2016cf3f --- /dev/null +++ b/changelog/5.2.1_2024-01-04/11274.md @@ -0,0 +1,5 @@ +Bugfix: Client stuck in reconnecting + +We fixed a bug where the client got stuck while reconnecting to the server. + +https://github.com/owncloud/client/issues/11274 diff --git a/changelog/5.2.1_2024-01-04/11413.md b/changelog/5.2.1_2024-01-04/11413.md new file mode 100644 index 00000000000..40d6612e02b --- /dev/null +++ b/changelog/5.2.1_2024-01-04/11413.md @@ -0,0 +1,7 @@ +Enhancement: Improved reliability for persisting settings + +We improved the reliability of saving accounts. +This might fix an issue where sync connections were lost at random. + +https://github.com/owncloud/client/pull/11413 +https://github.com/owncloud/client/issues/11411 diff --git a/changelog/5.3.0_2024-06-06/11276.md b/changelog/5.3.0_2024-06-06/11276.md new file mode 100644 index 00000000000..d5179ab10cf --- /dev/null +++ b/changelog/5.3.0_2024-06-06/11276.md @@ -0,0 +1,5 @@ +Enhancement: Add support to provide a list of ports for the OAuth process + +We added a branding option that enables us to specify a list of ports that are used for the local server during the OAuth process. + +https://github.com/owncloud/client/issues/11276 diff --git a/changelog/5.3.0_2024-06-06/11460.md b/changelog/5.3.0_2024-06-06/11460.md new file mode 100644 index 00000000000..8d8b3aa97b7 --- /dev/null +++ b/changelog/5.3.0_2024-06-06/11460.md @@ -0,0 +1,8 @@ +Bugfix: Fix early-use crash in the folder watcher on Linux + +In a few error scenarios, the folder watcher on Linux shows a dialog +with an error message. A crash has been fixed where this occurred before +the GUI was fully initialised. + +https://github.com/owncloud/client/issues/11460 +https://github.com/owncloud/client/pull/11475 diff --git a/changelog/5.3.0_2024-06-06/11467.md b/changelog/5.3.0_2024-06-06/11467.md new file mode 100644 index 00000000000..0d5986b66d9 --- /dev/null +++ b/changelog/5.3.0_2024-06-06/11467.md @@ -0,0 +1,6 @@ +Bugfix: Client stuck in `reconnecting` + +Properly handle errors during the update of the server settings. +Due to an unhandled result, the client could get stuck in a `reconnecting` state. + +https://github.com/owncloud/client/pull/11467 diff --git a/changelog/5.3.0_2024-06-06/11552.md b/changelog/5.3.0_2024-06-06/11552.md new file mode 100644 index 00000000000..20baa751a9f --- /dev/null +++ b/changelog/5.3.0_2024-06-06/11552.md @@ -0,0 +1,5 @@ +Bugfix: Ensure folders are scheduled only once + +We fixed a bug where a folder could be scheduled multiple times. + +https://github.com/owncloud/client/issues/11552 diff --git a/changelog/5.3.0_2024-06-06/11646.md b/changelog/5.3.0_2024-06-06/11646.md new file mode 100644 index 00000000000..5030a79d160 --- /dev/null +++ b/changelog/5.3.0_2024-06-06/11646.md @@ -0,0 +1,5 @@ +Enhancement: Support `Active Directory Federation Service` as identity provider + +We changed the OAuth workflow to support `Active Directory Federation Service` + +https://github.com/owncloud/client/issues/11646 diff --git a/changelog/5.3.0_2024-06-06/enterprise-6390.md b/changelog/5.3.0_2024-06-06/enterprise-6390.md new file mode 100644 index 00000000000..c1bcabac07b --- /dev/null +++ b/changelog/5.3.0_2024-06-06/enterprise-6390.md @@ -0,0 +1,18 @@ +Change: Revert local folder name back to pre 3.0 behavior + +Due to user requests, we reverted the folder name from +``` +ownCloud - Albert@owncloud.com +ownCloud - Katherine@owncloud.org +ownCloud - Marie@owncloud.com +``` + +back to + + ``` +ownCloud +ownCloud (1) +ownCloud (2) +``` + +https://github.com/owncloud/enterprise/issues/6390 diff --git a/changelog/5.3.1_2024-06-11/11691.md b/changelog/5.3.1_2024-06-11/11691.md new file mode 100644 index 00000000000..2b09d7cbcc7 --- /dev/null +++ b/changelog/5.3.1_2024-06-11/11691.md @@ -0,0 +1,5 @@ +Bugfix: Ensure the Windows shell extension is linked statically + +We fixed a regression where https://cmake.org/cmake/help/latest/policy/CMP0091.html caused our shell extension no longer to be linked statically. + +https://github.com/owncloud/client/issues/11691 diff --git a/changelog/5.3.2_2025-01-20/12000.md b/changelog/5.3.2_2025-01-20/12000.md new file mode 100644 index 00000000000..1ac50869b99 --- /dev/null +++ b/changelog/5.3.2_2025-01-20/12000.md @@ -0,0 +1,12 @@ +Bugfix: crash when checking the sync file status + +Sometimes a `Folder` is created for a local path that we cannot sync +(e.g. it's not writable). This folder will not have a sync engine. If +the engine of this `Folder` is accessed, this will lead to a crash. + +The fix is to check for the existence of a sync engine in the +`Folder::canSync()` method. + +https://github.com/owncloud/client/issues/11981 +https://github.com/owncloud/client/pull/11982 +https://github.com/owncloud/client/pull/12000 diff --git a/changelog/5.3.2_2025-01-20/12005.md b/changelog/5.3.2_2025-01-20/12005.md new file mode 100644 index 00000000000..5e9a540dd99 --- /dev/null +++ b/changelog/5.3.2_2025-01-20/12005.md @@ -0,0 +1,7 @@ +Bugfix: OAuth: Prevent logout when refreshing token + +Prevent the user being logged out when the network disappears during OAuth token refresh. + +https://github.com/owncloud/client/issues/11980 +https://github.com/owncloud/client/pull/11984 +https://github.com/owncloud/client/pull/12005 diff --git a/changelog/5.3.2_2025-01-20/installer-5.3.2.md b/changelog/5.3.2_2025-01-20/installer-5.3.2.md new file mode 100644 index 00000000000..bdfd49a16a3 --- /dev/null +++ b/changelog/5.3.2_2025-01-20/installer-5.3.2.md @@ -0,0 +1,5 @@ +Security: Fixing high security vulnerability in Windows Installer + +CVE: CVE-2025-24926 + +https://github.com/owncloud/client/issues/12010 diff --git a/changelog/6.0.0_2025-09-08/10980.md b/changelog/6.0.0_2025-09-08/10980.md new file mode 100644 index 00000000000..69ba31b075b --- /dev/null +++ b/changelog/6.0.0_2025-09-08/10980.md @@ -0,0 +1,8 @@ +Enhancement: Ask user to restart application when update is available on Windows + +Instead of a tray message, we now display a modal dialog on Windows to inform +the user about a downloaded update. Also, we now can prompt the user to +restart the application and apply the update immediately, adding a bit of user +comfort. + +https://github.com/owncloud/client/pull/11442 diff --git a/changelog/6.0.0_2025-09-08/11204.md b/changelog/6.0.0_2025-09-08/11204.md new file mode 100644 index 00000000000..90e3fa9a2cc --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11204.md @@ -0,0 +1,7 @@ +Enhancement: Prevent same default sync folder for multiple accounts with Spaces + +This is done by tagging the sync folder by an extended attribute +(Linux/macOS) or an alternative data stream (Windows). + +https://github.com/owncloud/client/issues/11204 +https://github.com/owncloud/client/pull/11364 diff --git a/changelog/6.0.0_2025-09-08/11245.md b/changelog/6.0.0_2025-09-08/11245.md new file mode 100644 index 00000000000..f5ccffa47d5 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11245.md @@ -0,0 +1,8 @@ +Change: Remove unused command line option --language + +This option was used before the UI language could be chosen in the +settings. The option --list-languages is also removed, as it no longer +serves a purpose. + +https://github.com/owncloud/client/issues/11245 +https://github.com/owncloud/client/pull/11270 diff --git a/changelog/6.0.0_2025-09-08/11275.md b/changelog/6.0.0_2025-09-08/11275.md new file mode 100644 index 00000000000..7138e519044 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11275.md @@ -0,0 +1,8 @@ +Change: Windows: always use fusion style + +We already used the fusion style on Windows for years if the system used dpi scaling. +Since Qt6.5 the fusion style also support the dark theme on Windows. +Those two reasons convinced us that always using the fusion style on Windows will result +in the best experience for the users. + +https://github.com/owncloud/client/issues/11275 diff --git a/changelog/6.0.0_2025-09-08/11300.md b/changelog/6.0.0_2025-09-08/11300.md new file mode 100644 index 00000000000..94f935affe5 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11300.md @@ -0,0 +1,10 @@ +Change: Make messages translatable that occur early in start-up + +These messages include the command-line messages (both errors and the +help text). To do this, the deprecated `--confdir` option has been +removed. This option was only used on windows before roaming profiles +were supported. + +https://github.com/owncloud/client/issues/11142 +https://github.com/owncloud/client/issues/11246 +https://github.com/owncloud/client/pull/11300 diff --git a/changelog/6.0.0_2025-09-08/11304.md b/changelog/6.0.0_2025-09-08/11304.md new file mode 100644 index 00000000000..f9314fb95b2 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11304.md @@ -0,0 +1,8 @@ +Enhancement: Use less modal dialogs + +As the rather high number of modal dialogs we used caused different issues on different platforms, +we decided to use them less often and try other concepts. + +https://github.com/owncloud/client/issues/11304 +https://github.com/owncloud/client/issues/11032 +https://github.com/owncloud/client/issues/11208 diff --git a/changelog/6.0.0_2025-09-08/11320.md b/changelog/6.0.0_2025-09-08/11320.md new file mode 100644 index 00000000000..c2186afbfe7 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11320.md @@ -0,0 +1,8 @@ +Enhancement: Add option to move files to trash instead of deleting + +An option has been added that when files on the server are deleted, +the client will move them to the system trash bin instead of deleting +those files. + +https://github.com/owncloud/client/issues/9001 +https://github.com/owncloud/client/pull/11320 diff --git a/changelog/6.0.0_2025-09-08/11325.md b/changelog/6.0.0_2025-09-08/11325.md new file mode 100644 index 00000000000..9d82e069d6f --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11325.md @@ -0,0 +1,5 @@ +Change: Deprecate suffix VFS + +We decided to deprecate the suffix VFS implementations and thus prepare its removal in 7.0. + +https://github.com/owncloud/client/issues/11325 diff --git a/changelog/6.0.0_2025-09-08/11347.md b/changelog/6.0.0_2025-09-08/11347.md new file mode 100644 index 00000000000..a58729274cf --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11347.md @@ -0,0 +1,5 @@ +Enhancement: Include recent changes in scheduled syncs + +When starting a new sync we now also include the recent change events in the discovery. + +https://github.com/owncloud/client/pull/11347 diff --git a/changelog/6.0.0_2025-09-08/11357.md b/changelog/6.0.0_2025-09-08/11357.md new file mode 100644 index 00000000000..7b8ab8ab488 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11357.md @@ -0,0 +1,14 @@ +Change: Removal of selective sync tree from account status page + +We removed the selective sync support from the main widget and moved it to its own dialog, +available through the context menu of the sync folder. + +Having both the sync status and the selective sync settings in the same widget caused a lot of problems and overhead over the years. +Additionally, it required us to maintain a lot of special and basically duplicated code, as we already had a dedicated widget to handle selective sync. + +https://github.com/owncloud/client/issues/11357 +https://github.com/owncloud/client/issues/11357 +https://github.com/owncloud/client/issues/8102 +https://github.com/owncloud/client/issues/5423 +https://github.com/owncloud/client/issues/4080 +https://github.com/owncloud/client/pull/11524 diff --git a/changelog/6.0.0_2025-09-08/11396.md b/changelog/6.0.0_2025-09-08/11396.md new file mode 100644 index 00000000000..0152740990c --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11396.md @@ -0,0 +1,5 @@ +Bugfix: Schedule syncs after an abort + +We fixed a bug where the client stopped scheduling syncs after a sync was aborted. + +https://github.com/owncloud/client/pull/11396 diff --git a/changelog/6.0.0_2025-09-08/11412.md b/changelog/6.0.0_2025-09-08/11412.md new file mode 100644 index 00000000000..0d1cf3beb64 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11412.md @@ -0,0 +1,10 @@ +Change: Fix three badly styled warning messages + +There were 3 labels for warning messages that were nearly unreadable in +dark mode due to bad styling. These have been changed to only use a bold +font, but are now displayed with a big warning icon on their left. The +places are: the account settings page, the folder wizard remote path +page, and the log browser. + +https://github.com/owncloud/client/issues/11412 +https://github.com/owncloud/client/pull/11470 diff --git a/changelog/6.0.0_2025-09-08/11424.md b/changelog/6.0.0_2025-09-08/11424.md new file mode 100644 index 00000000000..cab7fa30339 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11424.md @@ -0,0 +1,5 @@ +Enhancement: Improve handling of a server blocking an unsupported client + +We improved the handling of HTTP 403 status codes. + +https://github.com/owncloud/client/issues/11424 diff --git a/changelog/6.0.0_2025-09-08/11450.md b/changelog/6.0.0_2025-09-08/11450.md new file mode 100644 index 00000000000..b714fa8bd88 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11450.md @@ -0,0 +1,3 @@ +Bugfix: Fix description of manage public links action + +https://github.com/owncloud/client/issues/11450 diff --git a/changelog/6.0.0_2025-09-08/11460.md b/changelog/6.0.0_2025-09-08/11460.md new file mode 100644 index 00000000000..8d8b3aa97b7 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11460.md @@ -0,0 +1,8 @@ +Bugfix: Fix early-use crash in the folder watcher on Linux + +In a few error scenarios, the folder watcher on Linux shows a dialog +with an error message. A crash has been fixed where this occurred before +the GUI was fully initialised. + +https://github.com/owncloud/client/issues/11460 +https://github.com/owncloud/client/pull/11475 diff --git a/changelog/6.0.0_2025-09-08/11472.md b/changelog/6.0.0_2025-09-08/11472.md new file mode 100644 index 00000000000..0083cf6cc66 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11472.md @@ -0,0 +1,7 @@ +Bugfix: Properly encode scope and prompt items in OAuth URL + +Fixed a bug where the scope and prompt items of an OAuth query would not +be encoded, resulting in an invalid request. + +https://github.com/owncloud/client/issues/11472 +https://github.com/owncloud/client/pull/11479 diff --git a/changelog/6.0.0_2025-09-08/11478.md b/changelog/6.0.0_2025-09-08/11478.md new file mode 100644 index 00000000000..4044bf53c9a --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11478.md @@ -0,0 +1,7 @@ +Bugfix: Client stuck in reconnecting state after application start + +We fixed a bug where the client was starting OAuth authentication but +did not prompt the user properly to renew authentication. +This resulted in the client apparently getting stuck in the "reconnecting" state. + +https://github.com/owncloud/client/pull/11478 diff --git a/changelog/6.0.0_2025-09-08/11491.md b/changelog/6.0.0_2025-09-08/11491.md new file mode 100644 index 00000000000..645e9498b8b --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11491.md @@ -0,0 +1,6 @@ +Enhancement: New Folder status + +We rewrote the Folder status page. + +https://github.com/owncloud/client/pull/11491 +https://github.com/owncloud/client/issues/11527 diff --git a/changelog/6.0.0_2025-09-08/11497.md b/changelog/6.0.0_2025-09-08/11497.md new file mode 100644 index 00000000000..ca8cc85f496 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11497.md @@ -0,0 +1,8 @@ +Bugfix: strip user information from new-connection URL + +Credentials in the URL are not supported for some time. This fix strips +them from the URL before storage and using it to authenticate the client +to the server. + +https://github.com/owncloud/client/issues/11497 +https://github.com/owncloud/client/pull/11509 diff --git a/changelog/6.0.0_2025-09-08/11517.md b/changelog/6.0.0_2025-09-08/11517.md new file mode 100644 index 00000000000..06aed724d56 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11517.md @@ -0,0 +1,6 @@ +Change: Removed support for limiting sync of big new folder & external storage + +We removed an option which allowed to block new folders bigger than a set limit, from being synchronized automatically, when selective synchronization is used. +Additionally, we removed the option that allowed to block automatic synchronization of external storage. + +https://github.com/owncloud/client/pull/11517 diff --git a/changelog/6.0.0_2025-09-08/11531.md b/changelog/6.0.0_2025-09-08/11531.md new file mode 100644 index 00000000000..7afaf8b8c35 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11531.md @@ -0,0 +1,7 @@ +Change: Set the 'No' button as default action in the Unknown Certificate dialog + +When an unknown certificate is used by the server, the safe action is to +reject this certificate. So the default action for this dialog should be +not to accept this unknown certificate. + +https://github.com/owncloud/client/issues/11531 diff --git a/changelog/6.0.0_2025-09-08/11533.md b/changelog/6.0.0_2025-09-08/11533.md new file mode 100644 index 00000000000..1e0a45aa212 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11533.md @@ -0,0 +1,9 @@ +Enhancement: Pause sync when behind a captive portal + +When the operating system detects a captive portal, stop all outgoing +network requests. These requests will fail with an SSL certificate +mismatch error. When the captive portal is "gone", synchronisation will +be restarted. + +https://github.com/owncloud/client/issues/11533 +https://github.com/owncloud/client/pull/11567 diff --git a/changelog/6.0.0_2025-09-08/11569.md b/changelog/6.0.0_2025-09-08/11569.md new file mode 100644 index 00000000000..6fb236b2d12 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11569.md @@ -0,0 +1,6 @@ +Change: Remove `Remove all files` dialog + +We no longer ask the user whether all files should be removed but just sync. + +https://github.com/owncloud/client/issues/11569 +https://github.com/owncloud/client/issues/7304 diff --git a/changelog/6.0.0_2025-09-08/11581.md b/changelog/6.0.0_2025-09-08/11581.md new file mode 100644 index 00000000000..993a189beef --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11581.md @@ -0,0 +1,5 @@ +Bugfix: Support logging redirect + +We now log when all urls when a request was redirected. + +https://github.com/owncloud/client/pull/11581 diff --git a/changelog/6.0.0_2025-09-08/11595.md b/changelog/6.0.0_2025-09-08/11595.md new file mode 100644 index 00000000000..743979fbd32 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11595.md @@ -0,0 +1,3 @@ +Change: Rewrite of the spaces browser + +https://github.com/owncloud/client/pull/11595 diff --git a/changelog/6.0.0_2025-09-08/11621.md b/changelog/6.0.0_2025-09-08/11621.md new file mode 100644 index 00000000000..eb6bdc85910 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11621.md @@ -0,0 +1,3 @@ +Bugfix: Don't store null certificates in the account + +https://github.com/owncloud/client/issues/11621 diff --git a/changelog/6.0.0_2025-09-08/11664.md b/changelog/6.0.0_2025-09-08/11664.md new file mode 100644 index 00000000000..065f8a4bfac --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11664.md @@ -0,0 +1,5 @@ +Bugfix: Fix crash for folders that could not be initialized + +We fixed a bug where interacting with a folder in the file browser could lead to a crash if the folder was not properly initialized. + +https://github.com/owncloud/client/pull/11664 diff --git a/changelog/6.0.0_2025-09-08/11729.md b/changelog/6.0.0_2025-09-08/11729.md new file mode 100644 index 00000000000..0de8cec04bd --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11729.md @@ -0,0 +1,6 @@ +Enhancement: Support for prompt_values_supported in openid-configuration + +We implemented support for idP's to specify the supported prompt values. + +https://github.com/owncloud/client/pull/11729 +https://github.com/owncloud/client/pull/12163 diff --git a/changelog/6.0.0_2025-09-08/11742.md b/changelog/6.0.0_2025-09-08/11742.md new file mode 100644 index 00000000000..31dd4c711f1 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11742.md @@ -0,0 +1,7 @@ +Change: New update channel for ownCloud infinite scale + +The release series 6 is a long term release. +As version 7 will drop the support for ownCloud 10, users will either have to select the ownCloud infinite scale update channel in the about-dialog or manually install a newer release. +This change only affects the unbranded version. + +https://github.com/owncloud/client/issues/11742 diff --git a/changelog/6.0.0_2025-09-08/11743.md b/changelog/6.0.0_2025-09-08/11743.md new file mode 100644 index 00000000000..539eef3a2f5 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11743.md @@ -0,0 +1,6 @@ +Change: Remove support for branded shell extensions on Linux + +From now on user will have to install dolphin-owncloud nautilus-owncloud +instead of a branded version. + +https://github.com/owncloud/client/issues/11743 diff --git a/changelog/6.0.0_2025-09-08/11752.md b/changelog/6.0.0_2025-09-08/11752.md new file mode 100644 index 00000000000..277d596f626 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11752.md @@ -0,0 +1,7 @@ +Change: Only allow to add un-synced spaces + +We now prevent users from syncing the same space multiple times. + + +https://github.com/owncloud/client/issues/11752 +https://github.com/owncloud/client/pull/11759 diff --git a/changelog/6.0.0_2025-09-08/11760.md b/changelog/6.0.0_2025-09-08/11760.md new file mode 100644 index 00000000000..63f2111f7e1 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11760.md @@ -0,0 +1,3 @@ +Enhancement: Display how many spaces are un-synced + +https://github.com/owncloud/client/pull/11760 diff --git a/changelog/6.0.0_2025-09-08/11774.md b/changelog/6.0.0_2025-09-08/11774.md new file mode 100644 index 00000000000..3bd534d8a5d --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11774.md @@ -0,0 +1,7 @@ +Enhancement: New branding option: automatically sync new spaces + +A branding option was added to add new sync connections for newly +discovered spaces. + +https://github.com/owncloud/client/issues/11751 +https://github.com/owncloud/client/pull/11774 diff --git a/changelog/6.0.0_2025-09-08/11810.md b/changelog/6.0.0_2025-09-08/11810.md new file mode 100644 index 00000000000..337bd6b839f --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11810.md @@ -0,0 +1,11 @@ +Bugfix: Make open account in browser accessible for keyboard navigation + +On the account settings page, the status label contained the URL of the +server. This URL was not accessible with keyboard navigation or when a +screen-reader was used. Now there is an "Open in Web Browser" action in +the pop-up menu of the "Manage Account" button (which is next to the +status label), and the URL has been removed from the status label. + +https://github.com/owncloud/client/issues/11772 +https://github.com/owncloud/client/issues/11800 +https://github.com/owncloud/client/pull/11810 diff --git a/changelog/6.0.0_2025-09-08/11839.md b/changelog/6.0.0_2025-09-08/11839.md new file mode 100644 index 00000000000..79cbdeef7ff --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11839.md @@ -0,0 +1,3 @@ +Bugfix: Don't log misleading error if dynamic registration failed + +https://github.com/owncloud/client/pull/11839 diff --git a/changelog/6.0.0_2025-09-08/11856.md b/changelog/6.0.0_2025-09-08/11856.md new file mode 100644 index 00000000000..a73353d02f2 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/11856.md @@ -0,0 +1,3 @@ +Change: Rewrite of the login widget in QML + +https://github.com/owncloud/client/pull/11856 diff --git a/changelog/6.0.0_2025-09-08/12044.md b/changelog/6.0.0_2025-09-08/12044.md new file mode 100644 index 00000000000..eb715b8a924 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/12044.md @@ -0,0 +1,7 @@ +Change: Only support APFS on macOS + +Apple File System in contrary to HFS+ is Unicode normalization preserving. +To support this properly the upcoming version 6 of desktop client will support +NFC and NFD encoding on all client platforms based on the individual characteristics. + +https://github.com/owncloud/client/pull/12044 diff --git a/changelog/6.0.0_2025-09-08/12045.md b/changelog/6.0.0_2025-09-08/12045.md new file mode 100644 index 00000000000..3569b1436ec --- /dev/null +++ b/changelog/6.0.0_2025-09-08/12045.md @@ -0,0 +1,7 @@ +Bugfix: Fix crash after resource job fails + +When a network error occurs while fetching a resource (e.g. a space +image), no icon is available, and a crash would occur. This is fixed by +returning an empty icon. + +https://github.com/owncloud/client/pull/12045 diff --git a/changelog/6.0.0_2025-09-08/12093.md b/changelog/6.0.0_2025-09-08/12093.md new file mode 100644 index 00000000000..dd3f0de352f --- /dev/null +++ b/changelog/6.0.0_2025-09-08/12093.md @@ -0,0 +1,6 @@ +Change: Update the sync exclude list for Infinite Scale + +Files with the extension `.space` and `.psec` are now excluded from sync by default. + +https://github.com/owncloud/client/pull/12262 +https://github.com/owncloud/client/pull/12093 \ No newline at end of file diff --git a/changelog/6.0.0_2025-09-08/12214.md b/changelog/6.0.0_2025-09-08/12214.md new file mode 100644 index 00000000000..c0a3ccdcd51 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/12214.md @@ -0,0 +1,8 @@ +Bugfix: Remove any existing ResourceCache directories for accounts + +Some versions of the client failed to remove the temporary cache +directory on exit. This can result in multiple GB of cached space +images. These cache directories are now cleaned up before creating a new +cache. + +https://github.com/owncloud/client/pull/12214 diff --git a/changelog/6.0.0_2025-09-08/12224.md b/changelog/6.0.0_2025-09-08/12224.md new file mode 100644 index 00000000000..053f08667bd --- /dev/null +++ b/changelog/6.0.0_2025-09-08/12224.md @@ -0,0 +1,5 @@ +Bugfix: Delete account-specific caches when an account is removed + +This includes the cache for space images, as well as certain cached network jobs. + +https://github.com/owncloud/client/pull/12224 diff --git a/changelog/6.0.0_2025-09-08/12245.md b/changelog/6.0.0_2025-09-08/12245.md new file mode 100644 index 00000000000..5d4d2e342d1 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/12245.md @@ -0,0 +1,4 @@ +Change: Bump minimum macOS version to 13.0 + +https://github.com/owncloud/client/pull/12245 +https://github.com/owncloud/client/pull/12246 \ No newline at end of file diff --git a/changelog/6.0.0_2025-09-08/4808.md b/changelog/6.0.0_2025-09-08/4808.md new file mode 100644 index 00000000000..4ca761fa4c0 --- /dev/null +++ b/changelog/6.0.0_2025-09-08/4808.md @@ -0,0 +1,9 @@ +Enhancement: Add option to pause synchronization on metered connections + +On platforms that support metered-connection detection, an option is now +available to pause folder synchronization when the network connection +switches to metered-connection. When synchronization is paused, force-syncing can +still be done. + +https://github.com/owncloud/client/issues/4808 +https://github.com/owncloud/client/pull/11305 diff --git a/changelog/6.0.0_2025-09-08/enterprise-6390.md b/changelog/6.0.0_2025-09-08/enterprise-6390.md new file mode 100644 index 00000000000..c1bcabac07b --- /dev/null +++ b/changelog/6.0.0_2025-09-08/enterprise-6390.md @@ -0,0 +1,18 @@ +Change: Revert local folder name back to pre 3.0 behavior + +Due to user requests, we reverted the folder name from +``` +ownCloud - Albert@owncloud.com +ownCloud - Katherine@owncloud.org +ownCloud - Marie@owncloud.com +``` + +back to + + ``` +ownCloud +ownCloud (1) +ownCloud (2) +``` + +https://github.com/owncloud/enterprise/issues/6390 diff --git a/changelog/6.0.0_2025-09-08/enterprise-6761.md b/changelog/6.0.0_2025-09-08/enterprise-6761.md new file mode 100644 index 00000000000..7c18b5b0c6b --- /dev/null +++ b/changelog/6.0.0_2025-09-08/enterprise-6761.md @@ -0,0 +1,3 @@ +Bugfix: Fix location loading on Mac + +https://github.com/owncloud/enterprise/issues/6761 diff --git a/changelog/6.0.1_2025-10-10/.gitkeep b/changelog/6.0.1_2025-10-10/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/changelog/6.0.1_2025-10-10/12305.md b/changelog/6.0.1_2025-10-10/12305.md new file mode 100644 index 00000000000..a334fdeee44 --- /dev/null +++ b/changelog/6.0.1_2025-10-10/12305.md @@ -0,0 +1,8 @@ +Bugfix: Fix VFS checkbox in folder wizard + +On the last page of the folder wizard, there is a checkbox to +select/deselect VFS. By default this is checked. The bug was that +after unchecking this checkbox, it could not be checked again. +This has now been fixed. + +https://github.com/owncloud/client/pull/12305 diff --git a/changelog/6.0.1_2025-10-10/12334.md b/changelog/6.0.1_2025-10-10/12334.md new file mode 100644 index 00000000000..c775d84b5e4 --- /dev/null +++ b/changelog/6.0.1_2025-10-10/12334.md @@ -0,0 +1,6 @@ +Bugfix: reverting UTF-8 normalisation change + +The UTF-8 normalization change as per #12039 has been reverted. + +https://github.com/owncloud/client/pull/12334 +https://github.com/owncloud/client/pull/12039 \ No newline at end of file diff --git a/changelog/6.0.1_2025-10-10/12338.md b/changelog/6.0.1_2025-10-10/12338.md new file mode 100644 index 00000000000..b847ac3da4a --- /dev/null +++ b/changelog/6.0.1_2025-10-10/12338.md @@ -0,0 +1,6 @@ +Change: move to trash-bin is enabled now by default + +To protect users from data loss the "move to trash" option is now +enabled by default. + +https://github.com/owncloud/client/pull/12338 \ No newline at end of file diff --git a/changelog/6.0.2_2025-11-03/12379.md b/changelog/6.0.2_2025-11-03/12379.md new file mode 100644 index 00000000000..eb9022d24fb --- /dev/null +++ b/changelog/6.0.2_2025-11-03/12379.md @@ -0,0 +1,7 @@ +Security: Fixing high security vulnerability in MacOS client + +CVE: CVE-2025-64441 + +https://github.com/owncloud/client/pull/12379 +https://github.com/owncloud/client/pull/12389 +https://github.com/owncloud/client/pull/12390 diff --git a/changelog/6.0.3_2026-01-07/12364.md b/changelog/6.0.3_2026-01-07/12364.md new file mode 100644 index 00000000000..7ddc8484582 --- /dev/null +++ b/changelog/6.0.3_2026-01-07/12364.md @@ -0,0 +1,6 @@ +Change: Third party upgrades in distributed binaries + +We updated OpenSSL to 3.4.3 + +https://github.com/owncloud/client/pull/12364 +https://github.com/owncloud/client/pull/12432 \ No newline at end of file diff --git a/changelog/6.0.3_2026-01-07/12431.md b/changelog/6.0.3_2026-01-07/12431.md new file mode 100644 index 00000000000..04072400d67 --- /dev/null +++ b/changelog/6.0.3_2026-01-07/12431.md @@ -0,0 +1,7 @@ +Security: Fixing medium security vulnerability in password input handling + +CVE and security advisory will follow. + + +https://github.com/owncloud/client/pull/12430 +https://github.com/owncloud/client/pull/12431 diff --git a/changelog/CHANGELOG.md b/changelog/CHANGELOG.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/changelog/CHANGELOG.tmpl b/changelog/CHANGELOG.tmpl index e0f90ef9e67..fcbde4a1059 100644 --- a/changelog/CHANGELOG.tmpl +++ b/changelog/CHANGELOG.tmpl @@ -1,7 +1,13 @@ +# Table of Contents + +{{ range . -}} + * [Changelog for {{ .Version }}](#changelog-for-owncloud-desktop-client-{{ .Version | replace "." ""}}-{{ .Date | lower -}}) +{{ end -}} + * [Changelog for 2.6.1 versions and below](#changelog-for-261-versions-and-below) {{ $allVersions := . }} {{- range $index, $changes := . }}{{ with $changes -}} -Changelog for ownCloud Desktop Client [{{ .Version }}] ({{ .Date }}) -======================================= +# Changelog for ownCloud Desktop Client [{{ .Version }}] ({{ .Date }}) + The following sections list the changes in ownCloud Desktop Client {{ .Version }} relevant to ownCloud admins and users. @@ -21,14 +27,12 @@ ownCloud admins and users. [{{ .Version }}]: https://github.com/owncloud/client/compare/v2.6.1...v{{ .Version }} {{- end }} -Summary -------- +## Summary {{ range $entry := .Entries }}{{ with $entry }} * {{ .Type }} - {{ .Title }}: [#{{ .PrimaryID }}]({{ .PrimaryURL }}) {{- end }}{{ end }} -Details -------- +## Details {{ range $entry := .Entries }}{{ with $entry }} * {{ .Type }} - {{ .Title }}: [#{{ .PrimaryID }}]({{ .PrimaryURL }}) {{ range $par := .Paragraphs }} @@ -47,8 +51,7 @@ Details {{ end }}{{ end -}} {{/* Start of old changelog */ -}} -ChangeLog -========= +# Changelog for 2.6.1 versions and below version 2.6.1 (2020-01-17) (last updated on de0d330c002436454f3fe4929bd707e5f0425949) @@ -143,7 +146,7 @@ Some small changes and bug fixes: * GUI: Improve help text in ignore editor (#7162) * GUI: Show restoration items in protocol * Sharing: Fix issues with enforced passwords and expiry (#7246, #7245) -* Sharing: Fix resharing an item in a share with limited permissions (#7275) +* Sharing: Fix re-sharing an item in a share with limited permissions (#7275) * Sharing: Use the default expiration date even when not enforced (#7256) * Sharing: When sharing from context menu, show dialog if share creation fails (#7286) * Sharing: Always show at least readonly permissions (#7429) @@ -156,7 +159,7 @@ Some small changes and bug fixes: * Updater: Set correct state on network error (#3933) * Updater: Provide more useful options on update failure (#7217) * Updater: Improve logging (#3475, #7388) -* Updater: Fix Version numer not shown in the user visible string (#7288) +* Updater: Fix Version number not shown in the user visible string (#7288) * DB: Database path for new folders now starts with ".sync_", avoiding the "._" (#5904) * File hashes: Add support for SHA256 and SHA3 * Cmd: Respect chunk sizing environment variables (#7078) @@ -166,7 +169,7 @@ Some small changes and bug fixes: * Log: Print critical and fatal messages to stderr * Doc: Migrate the documentation to Antora (#6785) * Doc: Update Windows build instructions -* Doc: Add explanation of how to manually change server url (#6579) +* Doc: Add explanation of how to manually change server URL (#6579) * Doc: List more environment variables * Doc: List more config file options (owncloud/docs#1365) * Build: Fix KDEInstallDirs deprecation warnings (#6922) @@ -246,7 +249,7 @@ version 2.5.1 (2018-11-09) * macOS: Improve macdeployqt.py * Discovery: Include path in error message (#6826) * Database: Allow downgrade from 2.6 -* Migration from 2.4: fallback to move file by file if directory move failled (#6807) +* Migration from 2.4: fallback to move file by file if directory move failed (#6807) * owncloudcmd: Read server version and dav user id from the server (#6830) version 2.5.0 (2018-09-18) @@ -311,12 +314,12 @@ version 2.5.0 (2018-09-18) * Sync: When detecting a local move, keep the local mtime (#6629) * Sync: Better error handling for local directory parsing (#6610) * Sync: Error if properties are missing (#6317) -* Sync: Recover when the PUT reply (or chunkin's MOVE) is lost (#5106) +* Sync: Recover when the PUT reply (or chunking MOVE) is lost (#5106) * Sync: Do not abort a sync if the server closes the connection (#6516) * Sync: Increase the timeout for the last MOVE/PUT for huge files (#6527) * Sync: Fix renames making hierarchy inversion (#6694) * Sync: RemotePermissions: Fix empty vs null (#4608) -* Sync: Fix the "direction" of the "all file delted" message when the server is reset (#6317) +* Sync: Fix the "direction" of the "all file deleted" message when the server is reset (#6317) * Data-Fingerprint: Fix backup detection when fingerprint is empty * propagateuploadv1: Fixed an assert with ownCloud 5 * Download: Use the from the reply in the error message (#6459, #6459) @@ -519,9 +522,9 @@ version 2.3.3 (2017-08-29) * Overlay Icons: Fix potential hangs on Windows * SyncJournalDB: Don't use ._ as filename pattern if that does not work because of SMB storage settings (#5844) * SyncJournalDB: Log reason for sqlite3 opening errors -* Notifications: Proapgate "Dismiss" button action to server (#5922) +* Notifications: Propagate "Dismiss" button action to server (#5922) * Switch Linux build also to Qt 5.6.2 (#5470) -* Stopped maintaining Qt 4 buildability +* Stopped maintaining Qt 4 build ability version 2.3.2 (2017-05-08) * Fix more crashes (thanks to everyone submitting to our crash reporter!) @@ -543,21 +546,21 @@ version 2.3.0 (2017-03-03) * Allow to not sync the server's external storages by default * Switch Windows and OS X build to Qt 5.6.2 * Switch to new ownCloud server WebDAV endpoint -* Chunking NG: New file upload chunking algorithmn for ownCloud server 9.2 +* Chunking NG: New file upload chunking algorithm for ownCloud server 9.2 * Allow to sync a folder to multiple different servers (Filename change from .csync_journal.db to _sync_$HASH.db) * Conflicts: Use the local mtime for the conflict file name (#5273) * "Sync now" menu item * SSL Client certificate support improved (Show UI, Store keys in keychain) * Propagator: Upload more small files in parallel * Sync Engine: Read data-fingerprint property to detect backups (#2325) -* GUI: Show link to ceate an app password/token for syncing +* GUI: Show link to create an app password/token for syncing * Share dialog: Add 'Mail link' button * Caja file manager plugin * Make "backup detected" message to not trigger in wrong cases * SyncEngine: Fix renaming of folder when file are changed (#5192) * Fix reconnect bug if status.php intermittently returns wrong data (#5188) * Improve sync scheduling (#5317) -* Overlay icons: Improvements in correctnes +* Overlay icons: Improvements in correctness * Tray menu: Only update on demand to fix Linux desktop integration glitches * Progress: Better time/bandwidth estimations * Network: Follow certain HTTP redirects (#2791) @@ -631,7 +634,7 @@ version 2.2.0 (release 2016-05-12) * Improved netrc parser (#4691) * Improved user notifications about ignored files and conflicts (#4761, #3222) * Add warnings for old server versions (#4523) - * Enable tranportation checksums if the server supports based on server capabilities (#3735) + * Enable transportation checksums if the server supports based on server capabilities (#3735) * Default Chunk-size changed to 10MB (#4354) * Documentation Improvements, ie. about overlay icons @@ -642,7 +645,7 @@ version 2.2.0 (release 2016-05-12) 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 + * Fix occurrences of "Connection Closed" when a new unauthenticated TCP socket is used * Fix undeliberate WiFi scanning done by Qt Network classes * Several fixes/improvements to the sharing dialog * Several fixes/improvements to the server activity tab @@ -673,12 +676,12 @@ 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 + longer provide bandwidth 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) - * For *eml-files don't reupload if size and checksum are unchanged (#3235) + * For *eml-files don't re-upload if size and checksum are unchanged (#3235) * Ensured 403 reply code is handled properly (File Firewall) (#3490) * Reduced number of PROPFIND requests to server(#3964) * GUI: Added Account toolbox widget to keep account actions (#4139) @@ -686,14 +689,14 @@ version 2.1 (release 2015-12-03) * FolderMan: Fixed infinite wait on pause (#4093) * Renamed env variables to include unit (#2939) * FolderStatusModel: Attempt to detect removed undecided files (#3612) - * SyncEngine: Don't whipe the white list if the sync was aborted (#4018) + * SyncEngine: Don't wipe the white list if the sync was aborted (#4018) * Quota: Handle special negative value for the quota (#3940) * State app name in update notification (#4020) * PropagateUpload: Fixed double-emission of finished (#3844) * GUI: Ensured folder names which are excluded from sync can be clicked * Shell Integration: Dolphin support, requires KF 5.16 and KDE Application 15.12 * FolderStatusModel: Ensured reset also if a folder was renamed (#4011) - * GUI: Fixed accessiblity of remaing items in full settings toolbar (#3795) + * GUI: Fixed accessibility of remaining items in full settings toolbar (#3795) * Introduced the term "folder sync connection" in more places (#3757) * AccountSettings: Don't disable pause when offline (#4010) * Fixed handling of hidden files (#3980) @@ -706,7 +709,7 @@ version 2.1 (release 2015-12-03) * owncloudcmd: Don't do a check that file are older than 2s (#4160) * Fixed getting size for selective sync (#3986) * Re-added close button in the settings window (#3713) - * Added abililty to handle storage limitations gracefully (#3736) + * Added ability to handle storage limitations gracefully (#3736) * Organized patches to our base Qt version into admin/qt/patches * Plus: A lot of unmentioned improvements and fixes @@ -805,7 +808,7 @@ version 1.8.2 (release 2015-06-08) * Win32: Allow concurrent OEM (un-)installers (#3272) * Win32: Make Setup/Update Mutex theme-unique (#3272) * HTTP: Add the branding name to the UserAgent string - * ConnectonValidator: Always run with new credentials (#3266) + * ConnectionValidator: Always run with new credentials (#3266) * Recall Feature: Admins can trigger an upload of a file from client to server again (#3246) * Propagator: Add 'Content-Length: 0' header to MKCOL request (#3256) @@ -814,7 +817,7 @@ version 1.8.2 (release 2015-06-08) * Fix opening external links for some labels (#3135) * AccountState: Run only a single validator, allow error message overriding (#3236, #3153) - * SyncJournalDB: Minor fixes and simplificatons + * SyncJournalDB: Minor fixes and simplifications * SyncEngine: Force re-read of folder Etags for upgrades from 1.8.0 and 1.8.1 * Propagator: Limit length of temporary file name (#2789) @@ -878,7 +881,7 @@ version 1.8.1 (release 2015-05-07) * AccountWizard: Fix auth error handling. #3155 * Documentation fixes * Infrastructure/build fixes - * Win32/OS X: Apply patch from OpenSSL to handle oudated intermediates gracefully #3087 + * Win32/OS X: Apply patch from OpenSSL to handle outdated intermediates gracefully #3087 version 1.8.0 (release 2015-03-17) * Mac OS: HIDPI support @@ -888,7 +891,7 @@ version 1.8.0 (release 2015-03-17) * Enhanced usage of parallel HTTP requests for ownCloud 8 servers * Renamed github repository from mirall to client. * Mac OS: Use native notification support - * Selective Sync: allow to enforce selective sync in brandings. + * Selective Sync: allow to enforce selective sync in branding. * Added ability to build on Windows utilizing MingGW * SQLite database fixes if running on FAT filesystems * Improved detection of changing files to upload from local @@ -910,12 +913,12 @@ 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 + * Fix hiding of the database files in the sync directories * Compare http download size with the header value to avoid broken 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 + * Fixed detectCookie method of our CookieJar, fix for Shibboleth * Added fallback for distros where XDG_RUNTIME_DIR is undefined * Fix the setup wizard, bug #1989, #2264 * Fix scheduling of ETag check jobs, bug #2553 @@ -996,7 +999,7 @@ version 1.6.2 (release 2014-07-28 ) * Fix data corruption while trying to resume and the server does not support it. * HTTP Credentials: Read password from legacy place if not found. - * Shibboleth: Fix the waiting curser that would not disapear (#1915) + * Shibboleth: Fix the waiting cursor that would not disappear (#1915) * Limit memory usage to avoid mem wasting and crashes * Propagator: Fix crash when logging out during upload (#1957) * Propagator_qnam: Fix signal slot connection (#1963) @@ -1027,14 +1030,14 @@ version 1.6.1 (release 2014-06-26 ) version 1.6.0 (release 2014-05-30 ) * Minor GUI improvements * Qt5 compile issues fixed - * Ignore sync log file in filewatcher + * Ignore sync log file in file-watcher * Install libocsync to private library dir and use rpath to localize * Fix reconnect after server disconnect * Fix "unknown action" display in Activity window * Fix memory leaks * Respect XDG_CONFIG_HOME environment var * Handle empty fileids in the journal correctly - * Add abilility to compile libowncloudsync without GUI dependendy + * Add ability to compile libowncloudsync without GUI dependency * Fix SSL error with previously-expired CAs on Windows * Fix incorrect folder pause state after start * Fix a couple of actual potential crashes @@ -1081,7 +1084,7 @@ version 1.5.2 (release 2014-02-26 ) * Fix online state handling (#1441, #1459) * Fix potential crash in c_iconv on Mac OS * Fix certificate chain display in SSLButton - * Fix sporadicly appearing multiple auth prompts on sign-in + * Fix sporadically appearing multiple auth prompts on sign-in * Show correct state icon in Account Settings right away * Re-fetch content that gets deleted from read only shared directories * Do not store the password in the config file, erase existing ones (#1469) @@ -1134,7 +1137,7 @@ version 1.5.0 (release 2013-12-12 ), csync 0.91.4 required * Ask for password if missing * Introduce activity view * Introduce black list for files which could not be synced - * Enabling accessbility by shipping accessibility enables on OS X (#736) + * Enabling accessibility by shipping accessibility enables on OS X (#736) * Toggle Settings window when clicking on systray icon on Win and KDE (#896) * FolderWizard: Sanitize error detection (#1201) * Set proper enable state of blacklist button after the dialog was opened @@ -1166,7 +1169,7 @@ version 1.4.2 (release 2013-10-18 ), csync 0.90.4 required * Fix high CPU load (#1073) * Reconnect if network is unavailable after startup (#1080) * Ensure paused folder stays paused when syncing with more than one folder (#1083) - * Don't show desktop notification when the user doesn't want to (#1093) + * Don't show desktop notification when the user does not want to (#1093) * System tray: Avoid quick flickering up of the ok-icon for the sync prepare state * Progress: Do not show progress if nothing is transmitted * Progress: Show number of deletes. @@ -1187,10 +1190,10 @@ version 1.4.1 (release 2013-09-24 ), csync 0.90.1 required * Fixed various folder wizard issues, bug #992 * Made "Sync started" message optional, fixes bug #934 * Fixed shutdown, avoid crashed config on win32, fixes bug #945 - * Pop up config wizard if no server url is configured, fixes bug #1018 + * Pop up config wizard if no server URL is configured, fixes bug #1018 * Settings: calculate sidebar width dynamically, fixes bug #1020 * Fixed a crash if sync folders were removed, fixes bug #713 - * Do proper resync after network disconnect, fixes bug #1007 + * Do proper re-sync after network disconnect, fixes bug #1007 * Various minor code fixes version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required @@ -1204,15 +1207,15 @@ version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required * Introduce a sync history, persisting results across syncs * 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) + * Add "Show Desktop Notifications" GUI option (enabled by default) top optionally disable sync notifications * Add Help item, pointing to online reference * Implement graphical selection of remote folders in FolderWizard * Allow custom ignore patterns - * Add an editor for ingore patterns - * ALlow to flag certain ignore patterns as discardable + * Add an editor for ignore patterns + * Allow to flag certain ignore patterns as discard-able * Ensure to ship with all valid translations - * Progress Dialog now preserves the last syncned items across sync runs + * Progress Dialog now preserves the last synced items across sync runs * Split Setup Wizard into multiple pages again * Implement "--logfile -" to log to stdout * Add preliminary support for Shibboleth authentication @@ -1271,7 +1274,7 @@ version 1.2.4 (release 2013-04-11 ), csync 0.70.6 required * [Fixes] Allow setting the configuration directory on command line version 1.2.3 (release 2013-04-02 ), csync 0.70.5 required - * [Fixes] Unbreak self-signed certificate handling + * [Fixes] Un-break self-signed certificate handling version 1.2.2 (release 2013-04-02 ), csync 0.70.5 required * [Fixes] Do not crash when local file tree contains symlinks @@ -1290,7 +1293,7 @@ version 1.2.1 (release 2013-02-26 ), csync 0.70.4 required * [Fixes] Stop sync scheduling when configuration wizard starts. * [Fixes] Clear pending network requests when stepping back in config wizard. * [Fixes] User password dialog asynchronous issues. - * [Fixes] Make folderman starting and stoping the scheduling. + * [Fixes] Make folderman starting and stopping the scheduling. * [Fixes] Various minor fixes and cleanups. * [Fixes] Crash on pausing sync * [Fixes] Stale lock file after pausing sync @@ -1301,16 +1304,16 @@ version 1.2.1 (release 2013-02-26 ), csync 0.70.4 required * [Platform] Simplified/fixed credential store usage and custom configs. * [Platform] Added soname version to libowncloudsync. * [Platform] Pull in Qt translations - * [Gui] Make sync result popups less annoyingq + * [Gui] Make sync result popups less annoying * [Gui] Fix for result popup version 1.2.0 (release 2013-01-24 ), csync 0.70.2 required * [GUI] New status dialog to show a detailed list of synced files. * [GUI] New tray notifications about synced files. * [GUI] New platform specific icon set. - * [App] Using cross platform QtKeychain library to store credentials crypted. + * [App] Using cross platform QtKeychain library to store credentials encrypted. * [App] Use cross platform notification for changes in the local file system rather than regular poll. - * [Fixes] Improved SSL Certificate handling and SSL fixes troughout syncing. + * [Fixes] Improved SSL Certificate handling and SSL fixes throughout syncing. * [Fixes] Fixed proxy authentication. * [Fixes] Allow brackets in folder name alias. * [Fixes] Lots of other minor fixes. @@ -1331,13 +1334,13 @@ version 1.1.2 (release 2012-11-26 ), csync 0.60.2 required * [Fixes] lots of other minor fixes. * [GUI] Display error messages in status dialog. * [GUI] GUI fixes for the connection wizard. - * [GUI] Show username for connection in statusdialog. + * [GUI] Show username for connection in status-dialog. * [GUI] Show intro wizard on new connection setup. * [APP] Use CredentialStore to better support various credential backends. * [APP] Handle missing local folder more robust: Create it if missing instead of ignoring. - * [APP] Simplify treewalk code. + * [APP] Simplify tree-walk code. * [Platform] Fix Mac building version 1.1.1 (release 2012-10-18), csync 0.60.1 required @@ -1382,7 +1385,7 @@ version 1.0.5 (release 2012-08-14), csync 0.50.8 required version 1.0.4 (release 2012-08-10), csync 0.50.8 required * [APP] ownCloud is now a single instance app, can not start twice any more. * [APP] Proxy support - * [APP] Handle HTTP redirection correctly, note new url. + * [APP] Handle HTTP redirection correctly, note new URL. * [APP] More relaxed handling of read only directories in the sync paths. * [APP] Started to split off a library with sync functionality, eg for KDE * [APP] Make ownCloud Info class a singleton, more robust. @@ -1395,7 +1398,7 @@ version 1.0.4 (release 2012-08-10), csync 0.50.8 required 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] Loading of self signed certs into NetworkManager (#oc-843) * [Fixes] Win32: Handle SSL dll loading correctly. * [Fixes] Many other small fixes and improvements. @@ -1421,7 +1424,7 @@ version 1.0.2 (release 2012-05-18), csync 0.50.6 required * [GUI] New icon set for ownCloud client * [GUI] No splashscreen any more (oC Bug #498) * [GUI] Russian translation added - * [GUI] Added 'open ownCloud' to traymenu + * [GUI] Added 'open ownCloud' to tray-menu * [GUI] "Pause" and "Resume" instead of Enable/Disable * [Fixes] Long running syncs can be interrupted now. * [Fixes] Dialogs comes to front on click @@ -1446,7 +1449,7 @@ version 1.0.1 (release 2012-04-18), csync 0.50.5 required * [Intern] Backport to Qt Version 4.6 for compat. with older distros. * [Platform] MacOSX porting efforts * [Platform] MacOSX Bundle creation added - * [Platform] Enabled ranslations on Windows. + * [Platform] Enabled translations on Windows. diff --git a/changelog/unreleased/12369.md b/changelog/unreleased/12369.md new file mode 100644 index 00000000000..cd1eb15785d --- /dev/null +++ b/changelog/unreleased/12369.md @@ -0,0 +1,7 @@ +Change: Remove capability to upload conflict files + +None of the supported servers implements (or plans on implementing) this +feature. This removal includes the `OWNCLOUD_UPLOAD_CONFLICT_FILES` +environment variable. + +https://github.com/owncloud/client/pull/12369 diff --git a/changelog/unreleased/12374.md b/changelog/unreleased/12374.md new file mode 100644 index 00000000000..5b7e47ce23e --- /dev/null +++ b/changelog/unreleased/12374.md @@ -0,0 +1,5 @@ +Bugfix: Fix compatibility with extra-cmake-modules-6.19.0 + +Fix runtime crash at startup with ECM 6.19. + +https://github.com/owncloud/client/pull/12374 diff --git a/changelog/unreleased/8924 b/changelog/unreleased/8924 deleted file mode 100644 index f912bf333ce..00000000000 --- a/changelog/unreleased/8924 +++ /dev/null @@ -1,9 +0,0 @@ -Bugfix: Do not ask for credentails at start-up when the user logged out - -When a user would logout, and quit the client, then on the next start -the client would immediately ask for credentials. This has been fixed by -storing the fact that the user logged out before in the account -settings. - -https://github.com/owncloud/client/issues/8924 - diff --git a/changelog/unreleased/8938 b/changelog/unreleased/8938 deleted file mode 100644 index 38cc724a319..00000000000 --- a/changelog/unreleased/8938 +++ /dev/null @@ -1,7 +0,0 @@ -Change: Make sharedialog preview be more resilient - -We no longer enforce png thumbnails. -We no longer replace the file icon if the thumbnail is invalid. - -https://github.com/owncloud/client/issues/8938 -https://github.com/owncloud/client/pull/8939 diff --git a/changelog/unreleased/9048 b/changelog/unreleased/9048 deleted file mode 100644 index e939f859925..00000000000 --- a/changelog/unreleased/9048 +++ /dev/null @@ -1,5 +0,0 @@ -Bugfix: Fix copy url location for private links - -We fixed a bug where a placholder was copied to the clipboard instead of the url. - -https://github.com/owncloud/client/issues/9048 diff --git a/changelog/unreleased/9170 b/changelog/unreleased/9170 deleted file mode 100644 index baf7102acad..00000000000 --- a/changelog/unreleased/9170 +++ /dev/null @@ -1,6 +0,0 @@ -Bugfix: Crash when handling locked files - -We fixed a crash that could occur when trying to add a locked folder to the databse. - - -https://github.com/owncloud/client/issues/9170 diff --git a/changelog/unreleased/9223 b/changelog/unreleased/9223 deleted file mode 100644 index e8411e96dba..00000000000 --- a/changelog/unreleased/9223 +++ /dev/null @@ -1,10 +0,0 @@ -Bugfix: Display correct error message for files containign `\:?*"<>|` - -While the error message was supposed to be: -`File names containing the character '%1' are not supported on this file system.` - -We displayed: -`The file name is a reserved name on this file system.` - -https://github.com/owncloud/client/pull/9223/ - diff --git a/client.qrc b/client.qrc deleted file mode 100644 index 56da8b519ee..00000000000 --- a/client.qrc +++ /dev/null @@ -1,29 +0,0 @@ - - - resources/font-awesome/light/folder-solid.svg - resources/font-awesome/light/cog-solid.svg - resources/font-awesome/light/bolt-solid.svg - resources/font-awesome/light/lock-solid.svg - resources/font-awesome/light/lock-open-solid.svg - resources/font-awesome/light/user-solid.svg - resources/font-awesome/light/ellipsis-h-solid.svg - resources/font-awesome/light/trash-solid.svg - resources/font-awesome/light/bell-solid.svg - resources/font-awesome/light/power-off-solid.svg - resources/font-awesome/light/plus-solid.svg - resources/font-awesome/light/exclamation-triangle-solid.svg - - resources/font-awesome/dark/folder-solid.svg - resources/font-awesome/dark/cog-solid.svg - resources/font-awesome/dark/bolt-solid.svg - resources/font-awesome/dark/lock-solid.svg - resources/font-awesome/dark/lock-open-solid.svg - resources/font-awesome/dark/user-solid.svg - resources/font-awesome/dark/ellipsis-h-solid.svg - resources/font-awesome/dark/trash-solid.svg - resources/font-awesome/dark/bell-solid.svg - resources/font-awesome/dark/power-off-solid.svg - resources/font-awesome/dark/plus-solid.svg - resources/font-awesome/dark/exclamation-triangle-solid.svg - - diff --git a/cmake/modules/AddCMockaTest.cmake b/cmake/modules/AddCMockaTest.cmake deleted file mode 100644 index df5af0a79ba..00000000000 --- a/cmake/modules/AddCMockaTest.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# - ADD_CHECK_TEST(test_name test_source linklib1 ... linklibN) - -# Copyright (c) 2007 Daniel Gollub -# Copyright (c) 2007-2010 Andreas Schneider -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -enable_testing() -include(CTest) - -if(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW) - set(CMAKE_C_FLAGS_PROFILING "-g -O0 -Wall -W -Wshadow -Wunused-variable -Wunused-parameter -Wunused-function -Wunused -Wno-system-headers -Wwrite-strings -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Compiler Flags") - set(CMAKE_SHARED_LINKER_FLAGS_PROFILING " -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Linker Flags") - set(CMAKE_MODULE_LINKER_FLAGS_PROFILING " -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Linker Flags") - set(CMAKE_EXEC_LINKER_FLAGS_PROFILING " -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Linker Flags") -endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW) - -function (ADD_CMOCKA_TEST _testName _testSource) - add_executable(${_testName} ${_testSource}) - set_target_properties(${_testName} PROPERTIES C_STANDARD 99) - ecm_mark_nongui_executable(${_testName}) - target_link_libraries(${_testName} ${ARGN}) - add_test(NAME ${_testName} COMMAND $) -endfunction (ADD_CMOCKA_TEST) diff --git a/cmake/modules/FindCMocka.cmake b/cmake/modules/FindCMocka.cmake deleted file mode 100644 index 65a13a76d20..00000000000 --- a/cmake/modules/FindCMocka.cmake +++ /dev/null @@ -1,49 +0,0 @@ -# - Try to find CMocka -# Once done this will define -# -# CMOCKA_ROOT_DIR - Set this variable to the root installation of CMocka -# -# Read-Only variables: -# CMOCKA_FOUND - system has CMocka -# CMOCKA_INCLUDE_DIR - the CMocka include directory -# CMOCKA_LIBRARIES - Link these to use CMocka -# CMOCKA_DEFINITIONS - Compiler switches required for using CMocka -# -#============================================================================= -# Copyright (c) 2011-2012 Andreas Schneider -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# - -find_path(CMOCKA_INCLUDE_DIR - NAMES - cmocka.h - PATHS - ${CMOCKA_ROOT_DIR}/include -) - -find_library(CMOCKA_LIBRARY - NAMES - cmocka - PATHS - ${CMOCKA_ROOT_DIR}/lib -) - -if (CMOCKA_LIBRARY) - set(CMOCKA_LIBRARIES - ${CMOCKA_LIBRARIES} - ${CMOCKA_LIBRARY} - ) -endif (CMOCKA_LIBRARY) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(CMocka DEFAULT_MSG CMOCKA_LIBRARIES CMOCKA_INCLUDE_DIR) - -# show the CMOCKA_INCLUDE_DIR and CMOCKA_LIBRARIES variables only in the advanced view -mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARIES) diff --git a/cmake/modules/FindSMBClient.cmake b/cmake/modules/FindSMBClient.cmake deleted file mode 100644 index 730498eddc7..00000000000 --- a/cmake/modules/FindSMBClient.cmake +++ /dev/null @@ -1,66 +0,0 @@ -# - Try to find SMBClient -# Once done this will define -# -# SMBCLIENT_FOUND - system has SMBClient -# SMBCLIENT_INCLUDE_DIRS - the SMBClient include directory -# SMBCLIENT_LIBRARIES - Link these to use SMBClient -# SMBCLIENT_DEFINITIONS - Compiler switches required for using SMBClient -# -# Copyright (c) 2013 Andreas Schneider -# -# Redistribution and use is allowed according to the terms of the New -# BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. -# - - -if (SMBCLIENT_LIBRARIES AND SMBCLIENT_INCLUDE_DIRS) - # in cache already - set(SMBCLIENT_FOUND TRUE) -else (SMBCLIENT_LIBRARIES AND SMBCLIENT_INCLUDE_DIRS) - find_package(PkgConfig) - if (PKG_CONFIG_FOUND) - pkg_check_modules(_SMBCLIENT smbclient) - endif (PKG_CONFIG_FOUND) - - find_path(SMBCLIENT_INCLUDE_DIR - NAMES - libsmbclient.h - PATHS - ${_SMBCLIENT_INCLUDEDIR} - /usr/include - /usr/local/include - /opt/local/include - /sw/include - ) - - find_library(SMBCLIENT_LIBRARY - NAMES - smbclient - PATHS - ${_SMBCLIENT_LIBDIR} - /usr/lib - /usr/local/lib - /opt/local/lib - /sw/lib - ) - - set(SMBCLIENT_INCLUDE_DIRS - ${SMBCLIENT_INCLUDE_DIR} - ) - - if (SMBCLIENT_LIBRARY) - set(SMBCLIENT_LIBRARIES - ${SMBCLIENT_LIBRARIES} - ${SMBCLIENT_LIBRARY} - ) - endif (SMBCLIENT_LIBRARY) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(SMBClient DEFAULT_MSG SMBCLIENT_LIBRARIES SMBCLIENT_INCLUDE_DIRS) - - # show the SMBCLIENT_INCLUDE_DIRS and SMBCLIENT_LIBRARIES variables only in the advanced view - mark_as_advanced(SMBCLIENT_INCLUDE_DIRS SMBCLIENT_LIBRARIES) - -endif (SMBCLIENT_LIBRARIES AND SMBCLIENT_INCLUDE_DIRS) - diff --git a/cmake/modules/OCAddVfsPlugin.cmake b/cmake/modules/OCAddVfsPlugin.cmake new file mode 100644 index 00000000000..aa081421a4a --- /dev/null +++ b/cmake/modules/OCAddVfsPlugin.cmake @@ -0,0 +1,37 @@ +include(OCApplyCommonSettings) + +function(add_vfs_plugin) + set(options "") + set(oneValueArgs NAME) + set(multiValueArgs SRC LIBS) + cmake_parse_arguments(__PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + add_library(vfs_${__PLUGIN_NAME} MODULE + ${__PLUGIN_SRC} + ) + apply_common_target_settings(vfs_${__PLUGIN_NAME}) + + + set_target_properties(vfs_${__PLUGIN_NAME} PROPERTIES OUTPUT_NAME "ownCloud_vfs_${__PLUGIN_NAME}") + + target_link_libraries(vfs_${__PLUGIN_NAME} + libsync + ${__PLUGIN_LIBS} + ) + if(APPLE) + set_target_properties(vfs_${__PLUGIN_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "$/../PlugIns/") + # make the plugins available to the tests + add_custom_command(TARGET vfs_${__PLUGIN_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} + ARGS -E create_symlink "$" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$" MAIN_DEPENDENCY "vfs_${__PLUGIN_NAME}") + + else() + install(TARGETS vfs_${__PLUGIN_NAME} DESTINATION "${KDE_INSTALL_PLUGINDIR}") + endif() + + if (TARGET owncloud) + add_dependencies(owncloud vfs_${__PLUGIN_NAME}) + endif() + if (TARGET cmd) + add_dependencies(cmd vfs_${__PLUGIN_NAME}) + endif() +endfunction() diff --git a/cmake/modules/OCApplyCommonSettings.cmake b/cmake/modules/OCApplyCommonSettings.cmake new file mode 100644 index 00000000000..0d35cbb10a4 --- /dev/null +++ b/cmake/modules/OCApplyCommonSettings.cmake @@ -0,0 +1,73 @@ +include(OCRcVersion) + +if (MSVC) + # ecm sets /W3 we set /W4 + string(REGEX REPLACE "/W3" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +endif() + +# common target settings that are used by all important targets +function(apply_common_target_settings targetName) + add_windows_version_info(${targetName}) + if(FORCE_ASSERTS) + target_compile_definitions(${targetName} + PRIVATE + QT_FORCE_ASSERTS + ) + endif() + + target_compile_definitions(${targetName} + PRIVATE + QT_NO_CAST_TO_ASCII + QT_NO_URL_CAST_FROM_STRING + QT_NO_CAST_FROM_BYTEARRAY + QT_USE_QSTRINGBUILDER + QT_MESSAGELOGCONTEXT # enable function name and line number in debug output + QT_NO_FOREACH + QT_DISABLE_DEPRECATED_BEFORE=0x060200 + ) + # These are added by ECM: + remove_definitions(-DQT_NO_KEYWORDS) # We are not a library, and we don't use a 3th party signal/slot library. + remove_definitions(-DQT_NO_CAST_FROM_ASCII) # Our source code is UTF-8 + + if(WIN32) + target_compile_definitions(${targetName} + PRIVATE + # Get APIs from from Win8 onwards. + _WIN32_WINNT=_WIN32_WINNT_WIN8 + WINVER=_WIN32_WINNT_WIN8 + NTDDI_VERSION=NTDDI_WIN10_RS2 + ) + endif() + + if(MSVC) + target_compile_options(${targetName} + PRIVATE + # enable linter like warnings with msvc + # this includes deprecations + # https://learn.microsoft.com/en-us/cpp/build/reference/compiler-option-warning-level?view=msvc-170 + /W4 + # treat unhandled switch cases as error + /we4062 + # Worder + /w15038 + # werror on unused function + # The given function is local and not referenced in the body of the module; therefore, the function is dead code. + /we4505 + # 4505 for anonymous namespaces, apparently undocumented https://developercommunity.visualstudio.com/t/warning-C4505-missing-on-anonymous-names/10413660 + /we5245 + /we4930 + # A variable is declared and initialized but not used. + /we4189 + # Our source code is UTF-8, so is the log file (the only file we write) + /utf-8 + ) + elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") + target_compile_options(${targetName} + PRIVATE + # treat unhandled switch cases as error + -Werror=switch + -Werror=unused-function + -Werror=unused-but-set-variable + ) + endif() +endfunction() diff --git a/cmake/modules/OCBundleResources.cmake b/cmake/modules/OCBundleResources.cmake index d205549d84b..e5bfc351c9f 100644 --- a/cmake/modules/OCBundleResources.cmake +++ b/cmake/modules/OCBundleResources.cmake @@ -21,7 +21,7 @@ endfunction() function(__addIcon QRC_PATH THEME ICON_NAME) - set(options) + set(options "REQUIRED") set(oneValueArgs SRC_PATH) set(multiValueArgs) cmake_parse_arguments(_ICON "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -44,6 +44,8 @@ function(__addIcon QRC_PATH THEME ICON_NAME) ALIAS ${iconAlias} ) else() + message(STATUS "Icon not found: ${OEM_THEME_DIR}/${icon}") + set(SIZES "16;22;32;48;64;128;256;512;1024") foreach(size ${SIZES}) set(icon "theme/${_ICON_SRC_PATH}/${ICON_NAME}-${size}.png") @@ -54,6 +56,10 @@ function(__addIcon QRC_PATH THEME ICON_NAME) FILE_PATH ${OEM_THEME_DIR}/${icon} ALIAS ${iconAlias} ) + else() + if (_ICON_REQUIRED) + message(FATAL_ERROR "Icon ${ICON_NAME} not found in ${THEME} theme at ${OEM_THEME_DIR}") + endif() endif() endforeach() endif() @@ -70,31 +76,51 @@ function(__write_qrc_file_footer QRC_PATH) file(APPEND ${QRC_PATH} "\n") endfunction() +# add the icons for url buttons to the theme +function(__addUrlIcons QRC_PATH) + file(GLOB_RECURSE OWNCLOUD_URL_ICONS "${OEM_THEME_DIR}/theme/universal/urlIcons/*") + foreach(icon ${OWNCLOUD_URL_ICONS}) + get_filename_component(iconName ${icon} NAME_WE) + __addIcon(${QRC_PATH} "universal" "urlIcons/${iconName}") + endforeach() +endfunction() + function(generate_theme TARGET OWNCLOUD_SIDEBAR_ICONS_OUT) - if(NOT "${OEM_THEME_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}") + if(NOT "${OEM_THEME_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}/src/resources/") set(QRC_PATH ${CMAKE_CURRENT_BINARY_DIR}/theme.qrc) __write_qrc_file_header(${QRC_PATH} theme) + # ownbrander __addIcon(${QRC_PATH} "universal" "${APPLICATION_ICON_NAME}-icon" SRC_PATH "colored") __addIcon(${QRC_PATH} "universal" "wizard_logo" SRC_PATH "colored") + # full theme + __addIcon(${QRC_PATH} "universal" "${APPLICATION_ICON_NAME}-icon") + __addIcon(${QRC_PATH} "universal" "wizard_logo") + __addIcon(${QRC_PATH} "universal" "wizard_logo_dark") + __addIcon(${QRC_PATH} "universal" "wizard_footer_logo") + + __addUrlIcons(${QRC_PATH}) - set(STATES "ok;error;information;offline;pause;sync") - set(THEMES "colored;dark;black;white") - foreach(theme ${THEMES}) - foreach(state ${STATES}) - __addIcon(${QRC_PATH} ${theme} "state-${state}") + foreach(theme IN ITEMS colored dark black white) + foreach(state IN ITEMS ok error information offline pause sync) + __addIcon(${QRC_PATH} ${theme} "state-${state}" REQUIRED) endforeach() endforeach() __write_qrc_file_footer(${QRC_PATH}) target_sources(${TARGET} PRIVATE ${QRC_PATH}) + target_compile_definitions(${TARGET} PRIVATE BRANDING_AVAILABLE) # add executable icon on windows and osx file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${OEM_THEME_DIR}/theme/colored/*-${APPLICATION_ICON_NAME}-sidebar.png") else() file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${OEM_THEME_DIR}/theme/colored/*-${APPLICATION_ICON_NAME}-icon-sidebar.png") endif() + if (NOT OWNCLOUD_SIDEBAR_ICONS) + message(WARNING "The branding does not provide sidebar icons falling back to vanilla icons") + file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${PROJECT_SOURCE_DIR}/src/resources/theme/colored/*-owncloud-icon-sidebar.png") + endif() set(${OWNCLOUD_SIDEBAR_ICONS_OUT} ${OWNCLOUD_SIDEBAR_ICONS} PARENT_SCOPE) endfunction() diff --git a/cmake/modules/OCConfigPluginDir.cmake b/cmake/modules/OCConfigPluginDir.cmake deleted file mode 100644 index af6421833e1..00000000000 --- a/cmake/modules/OCConfigPluginDir.cmake +++ /dev/null @@ -1,35 +0,0 @@ -function(IS_SUBDIR ROOT CHILD OUT) - file(RELATIVE_PATH REL_PATH "${ROOT}" "${CHILD}") - string(REGEX MATCH "^\.\./" NOT_SUBDIR "${REL_PATH}") - if (NOT_SUBDIR) - set(${OUT} FALSE PARENT_SCOPE) - else() - set(${OUT} TRUE PARENT_SCOPE) - endif() -endfunction() - - -if (UNIX AND NOT APPLE) - set(OC_PLUGIN_DIR ${KDE_INSTALL_FULL_PLUGINDIR}) - IS_SUBDIR("${CMAKE_INSTALL_PREFIX}" "${OC_PLUGIN_DIR}" _is_subdir) - if (NOT _is_subdir) - if (KDE_INSTALL_USE_QT_SYS_PATHS) - message(FATAL_ERROR "Using KDE_INSTALL_USE_QT_SYS_PATHS with a non bundled Qt is not supported.") - else() - message(FATAL_ERROR "KDE_INSTALL_PLUGINDIR must be located in CMAKE_INSTALL_PREFIX") - endif() - endif() - - include(ECMQueryQmake) - query_qmake(_qt_plugin_dir QT_INSTALL_PLUGINS TRY) - # any sub dir of _qt_plugin_dir is sufficient - IS_SUBDIR("${_qt_plugin_dir}" "${OC_PLUGIN_DIR}" _is_subdir) - if (_is_subdir) - # no need to add a additional plugin dir - unset(OC_PLUGIN_DIR) - else() - # set plugin dir to a path relative to the binary - file(RELATIVE_PATH OC_PLUGIN_DIR "${CMAKE_INSTALL_FULL_BINDIR}" "${OC_PLUGIN_DIR}") - message(STATUS "Adding additional plugin path: ${OC_PLUGIN_DIR}") - endif() -endif() diff --git a/cmake/modules/OCRcVersion.cmake b/cmake/modules/OCRcVersion.cmake new file mode 100644 index 00000000000..379cf0b1030 --- /dev/null +++ b/cmake/modules/OCRcVersion.cmake @@ -0,0 +1,31 @@ +set(_VERSION_SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}) + +include(${PROJECT_SOURCE_DIR}/VERSION.cmake) +include(${PROJECT_SOURCE_DIR}/THEME.cmake) + +function(add_windows_version_info targetName) + if(NOT WIN32) + return() + endif() + + if(MIRALL_VERSION_BUILD) + set(OC_RC_VERSION_BUILD ${MIRALL_VERSION_BUILD}) + else() + set(OC_RC_VERSION_BUILD 0) + endif() + + get_target_property(TARGET_TYPE ${targetName} TYPE) + if(${TARGET_TYPE} STREQUAL "EXECUTABLE") + set(OC_RC_TYPE "VFT_APP") + elseif(${TARGET_TYPE} STREQUAL "SHARED_LIBRARY" OR ${TARGET_TYPE} STREQUAL "MODULE_LIBRARY") + set(OC_RC_TYPE "VFT_DLL") + else() + # only create version.rc for dll's and executables + return() + endif() + + set(OC_RC_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${targetName}-oc_rc_version.rc) + configure_file(${_VERSION_SOURCE_DIR}/version.rc.in ${OC_RC_OUTPUT} @ONLY) + + target_sources(${targetName} PRIVATE ${OC_RC_OUTPUT}) +endfunction() diff --git a/cmake/modules/version.rc.in b/cmake/modules/version.rc.in new file mode 100644 index 00000000000..f77e3091d0c --- /dev/null +++ b/cmake/modules/version.rc.in @@ -0,0 +1,34 @@ +#include + +VS_VERSION_INFO VERSIONINFO + FILEVERSION @MIRALL_VERSION_MAJOR@,@MIRALL_VERSION_MINOR@,@MIRALL_VERSION_PATCH@,@OC_RC_VERSION_BUILD@ + PRODUCTVERSION @MIRALL_VERSION_MAJOR@,@MIRALL_VERSION_MINOR@,@MIRALL_VERSION_PATCH@,@OC_RC_VERSION_BUILD@ + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE @OC_RC_TYPE@ + FILESUBTYPE VFT2_UNKNOWN + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + BEGIN + VALUE "CompanyName", "@APPLICATION_VENDOR@\0" + VALUE "LegalCopyright", "Copyright (C) 2014-@MIRALL_VERSION_YEAR@ @APPLICATION_VENDOR@\0" + VALUE "FileVersion", "@MIRALL_VERSION_FULL@\0" + VALUE "Comments", "@MIRALL_VERSION_STRING@\0" + VALUE "FileDescription", "@APPLICATION_NAME@\0" + VALUE "ProductName", "@APPLICATION_NAME@\0" + VALUE "ProductVersion", "@MIRALL_VERSION_STRING@\0" + END + END + BLOCK "VarFileInfo" + BEGIN + // English US, Unicode + VALUE "Translation", 0x0409, 1200 + END +END diff --git a/config.h.in b/config.h.in deleted file mode 100644 index 14061ba5fc5..00000000000 --- a/config.h.in +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef CONFIG_H -#define CONFIG_H - -#cmakedefine USE_INOTIFY 1 -#cmakedefine WITH_QTKEYCHAIN 1 -#cmakedefine WITH_CRASHREPORTER -#cmakedefine CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@" -#define SOCKETAPI_TEAM_IDENTIFIER_PREFIX "@SOCKETAPI_TEAM_IDENTIFIER_PREFIX@" - -#cmakedefine APPLICATION_DOMAIN "@APPLICATION_DOMAIN@" -#cmakedefine THEME_CLASS @THEME_CLASS@ -#cmakedefine THEME_INCLUDE "@THEME_INCLUDE@" - -#cmakedefine APPLICATION_NAME "@APPLICATION_NAME@" -#cmakedefine APPLICATION_VENDOR "@APPLICATION_VENDOR@" -#cmakedefine APPLICATION_REV_DOMAIN "@APPLICATION_REV_DOMAIN@" -#cmakedefine APPLICATION_SHORTNAME "@APPLICATION_SHORTNAME@" -#cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@" -#cmakedefine APPLICATION_UPDATE_URL "@APPLICATION_UPDATE_URL@" -#cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@" -#cmakedefine APPLICATION_VIRTUALFILE_SUFFIX "@APPLICATION_VIRTUALFILE_SUFFIX@" -#define APPLICATION_DOTVIRTUALFILE_SUFFIX "." APPLICATION_VIRTUALFILE_SUFFIX -#cmakedefine APPLICATION_CLOUDPROVIDERS_DBUS_NAME "@APPLICATION_CLOUDPROVIDERS_DBUS_NAME@" -#cmakedefine APPLICATION_CLOUDPROVIDERS_DBUS_PATH "@APPLICATION_CLOUDPROVIDERS_DBUS_PATH@" - -#cmakedefine SYSCONFDIR "@SYSCONFDIR@" -#cmakedefine OC_PLUGIN_DIR "@OC_PLUGIN_DIR@" - -#cmakedefine01 GUI_TESTING - -#endif diff --git a/mirall.desktop.in b/mirall.desktop.in deleted file mode 100644 index 4f9722efb8e..00000000000 --- a/mirall.desktop.in +++ /dev/null @@ -1,218 +0,0 @@ -[Desktop Entry] -Categories=Utility;X-SuSE-SyncUtility; -Type=Application -Exec=@APPLICATION_EXECUTABLE@ --showsettings -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 -MimeType=application/vnd.@APPLICATION_EXECUTABLE@; -Actions=Settings;Quit; - -# Translations -Comment[oc]=@APPLICATION_NAME@ sincronizacion del client -GenericName[oc]=Dorsièr de Sincronizacion -Name[oc]=@APPLICATION_NAME@ sincronizacion del client -Icon[oc]=@APPLICATION_EXECUTABLE@ -Comment[ar]=@APPLICATION_NAME@ زبون مزامنة مكتبي -GenericName[ar]=مزامنة المجلد -Name[ar]=@APPLICATION_NAME@ زبون مزامنة مكتبي -Icon[ar]=@APPLICATION_EXECUTABLE@ -Comment[bg_BG]=@APPLICATION_NAME@ клиент за десктоп синхронизация -GenericName[bg_BG]=Синхронизиране на папката -Name[bg_BG]=@APPLICATION_NAME@ клиент десктоп синхронизация -Icon[bg_BG]=@APPLICATION_EXECUTABLE@ -Comment[ca]=Client de sincronització d'escriptori @APPLICATION_NAME@ -GenericName[ca]=Sincronització de carpetes -Name[ca]=Client de sincronització d'escriptori @APPLICATION_NAME@ -Icon[ca]=@APPLICATION_EXECUTABLE@ -Comment[da]=@APPLICATION_NAME@ skrivebordsklient til synkronisering -GenericName[da]=Mappesynkronisering -Name[da]=@APPLICATION_NAME@ skrivebordsklient til synk -Icon[da]=@APPLICATION_EXECUTABLE@ -Comment[de]=@APPLICATION_NAME@ Desktop-Synchronisationsclient -GenericName[de]=Ordner-Synchronisation -Name[de]=@APPLICATION_NAME@ Desktop-Synchronisationsclient -Icon[de]=@APPLICATION_EXECUTABLE@ -Comment[ja_JP]=@APPLICATION_NAME@ デスクトップ同期クライアント -GenericName[ja_JP]=フォルダー同期 -Name[ja_JP]=@APPLICATION_NAME@ デスクトップ同期クライアント -Icon[ja_JP]=@APPLICATION_EXECUTABLE@ -Comment[el]=@ΟΝΟΜΑ_ΕΦΑΡΜΟΓΗΣ@ συγχρονισμός επιφάνειας εργασίας πελάτη -GenericName[el]=Συγχρονισμός φακέλου -Name[el]=@ΟΝΟΜΑ_ΕΦΑΡΜΟΓΗΣ@ συγχρονισμός επιφάνειας εργασίας πελάτη -Icon[el]=@APPLICATION_EXECUTABLE@ -Comment[en_GB]=@APPLICATION_NAME@ desktop synchronisation client -GenericName[en_GB]=Folder Sync -Name[en_GB]=@APPLICATION_NAME@ desktop sync client -Icon[en_GB]=@APPLICATION_EXECUTABLE@ -Comment[es]=@APPLICATION_NAME@ cliente de sincronización de escritorio -GenericName[es]=Sincronización de carpeta -Name[es]=@APPLICATION_NAME@ cliente de sincronización de escritorio -Icon[es]=@APPLICATION_EXECUTABLE@ -Comment[de_DE]=@APPLICATION_NAME@ Desktop-Synchronisationsclient -GenericName[de_DE]=Ordner-Synchronisation -Name[de_DE]=@APPLICATION_NAME@ Desktop-Synchronisationsclient -Icon[de_DE]=@APPLICATION_EXECUTABLE@ -Comment[eu]=@APPLICATION_NAME@ mahaigaineko sinkronizazio bezeroa -GenericName[eu]=Karpetaren sinkronizazioa -Name[eu]=@APPLICATION_NAME@ mahaigaineko sinkronizazio bezeroa -Icon[eu]=@APPLICATION_EXECUTABLE@ -GenericName[fa]=همسان سازی پوشه‌ها -Name[fa]=@APPLICATION_EXECUTABLE@ نسخه‌ی همسان سازی مشتری -Icon[fa]=@APPLICATION_EXECUTABLE@ -Comment[fr]=Client de synchronisation @APPLICATION_NAME@ -GenericName[fr]=Synchronisation de dossier -Name[fr]=Client de synchronisation @APPLICATION_NAME@ -Icon[fr]=@APPLICATION_EXECUTABLE@ -Comment[gl]=@APPLICATION_NAME@ cliente de sincronización para escritorio -GenericName[gl]=Sincronizar Cartafol -Name[gl]=@APPLICATION_NAME@ cliente de sincronización para escritorio -Icon[gl]=@APPLICATION_EXECUTABLE@ -Comment[he]=@APPLICATION_NAME@ לקוח סנכון שולחן עבודה -GenericName[he]=סנכון תיקייה -Name[he]=@APPLICATION_NAME@ לקוח סנכרון שולחן עבודה -Icon[he]=@APPLICATION_EXECUTABLE@ -Comment[ia]=@APPLICATION_NAME@ cliente de synchronisation pro scriptorio -GenericName[ia]=Synchronisar Dossier -Name[ia]=@APPLICATION_NAME@ cliente de synchronisation pro scriptorio -Icon[ia]=@APPLICATION_EXECUTABLE@ -Comment[id]=Klien sinkronisasi desktop @APPLICATION_NAME@ -GenericName[id]=Folder Sync -Name[id]=Klien sync desktop @APPLICATION_NAME@ -Icon[id]=@APPLICATION_EXECUTABLE@ -Comment[is]=@APPLICATION_NAME@ skjáborðsforrit samstillingar -GenericName[is]=Samstilling möppu -Name[is]=@APPLICATION_NAME@ skjáborðsforrit samstillingar -Icon[is]=@APPLICATION_EXECUTABLE@ -Comment[it]=Client di sincronizzazione del desktop di @APPLICATION_NAME@ -GenericName[it]=Sincronizzazione cartella -Name[it]=Client di sincronizzazione del desktop di @APPLICATION_NAME@ -Icon[it]=@APPLICATION_EXECUTABLE@ -Comment[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트 -GenericName[ko]=폴더 동기화 -Name[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트 -Icon[ko]=@APPLICATION_EXECUTABLE@ -Comment[lo]=@APPLICATION_NAME@ ການປະສານຂໍ້ມູນຄອມພິວເຕີລູກຂ່າຍ -GenericName[lo]=ໂຟນເດີຊິງ -Name[lo]=@APPLICATION_NAME@ ຊິງຄອມພິວເຕີລູກຂ່າຍ -Icon[lo]=@APPLICATION_EXECUTABLE@ -Comment[mk]=@APPLICATION_NAME@ десктор клиент за синхронизација -GenericName[mk]=Папка за синхронизација -Name[mk]=@APPLICATION_NAME@ десктор клиент за синхронизација -Icon[mk]=@APPLICATION_EXECUTABLE@ -Comment[hu_HU]=@APPLICATION_NAME@ asztali szinkronizációs kliens -GenericName[hu_HU]=Mappaszinkronizálás -Name[hu_HU]=@APPLICATION_NAME@ asztali szinkronizációs kliens -Icon[hu_HU]=@APPLICATION_EXECUTABLE@ -Comment[af_ZA]=@APPLICATION_NAME@ werkskermsinchroniseerkliënt -GenericName[af_ZA]=Vouersinchronisering -Name[af_ZA]=@APPLICATION_NAME@ werkskermsinchroniseerkliënt -Icon[af_ZA]=@APPLICATION_EXECUTABLE@ -Comment[nl]=@APPLICATION_NAME@ desktop synchronisatie client -GenericName[nl]=Mappen sync -Name[nl]=@APPLICATION_NAME@ desktop sync client -Icon[nl]=@APPLICATION_EXECUTABLE@ -Comment[et_EE]=@APPLICATION_NAME@ sünkroonimise klient töölauale -GenericName[et_EE]=Kaustade sünkroonimine -Name[et_EE]=@APPLICATION_NAME@ sünkroonimise klient töölauale -Icon[et_EE]=@APPLICATION_EXECUTABLE@ -Comment[pl]=@APPLICATION_NAME@ klient synchronizacji dla komputerów stacjonarnych -GenericName[pl]=Folder Synchronizacji -Name[pl]=@APPLICATION_NAME@ klient synchronizacji dla komputerów stacjonarnych -Icon[pl]=@APPLICATION_EXECUTABLE@ -Comment[pt_BR]=@APPLICATION_NAME@ cliente de sincronização do computador -GenericName[pt_BR]=Sincronização de Pasta -Name[pt_BR]=@APPLICATION_NAME@ cliente de sincronização de desktop -Icon[pt_BR]=@APPLICATION_EXECUTABLE@ -Comment[cs_CZ]=@APPLICATION_NAME@ počítačový synchronizační klient -GenericName[cs_CZ]=Synchronizace adresáře -Name[cs_CZ]=@APPLICATION_NAME@ počítačový synchronizační klient -Icon[cs_CZ]=@APPLICATION_EXECUTABLE@ -Comment[ru]=Настольный клиент синхронизации @APPLICATION_NAME@ -GenericName[ru]=Синхронизация каталогов -Name[ru]=Настольный клиент синхронизации @APPLICATION_NAME@ -Icon[ru]=@APPLICATION_EXECUTABLE@ -Comment[sl]=@APPLICATION_NAME@ ‒ Program za usklajevanje datotek z namizjem -GenericName[sl]=Usklajevanje map -Name[sl]=@APPLICATION_NAME@ ‒ Program za usklajevanje datotek z namizjem -Icon[sl]=@APPLICATION_EXECUTABLE@ -Comment[sq]=Klient njëkohësimesh @APPLICATION_NAME@ për desktop -GenericName[sq]=Njëkohësim Dosjesh -Name[sq]=Klient njëkohësimesh @APPLICATION_NAME@ për desktop -Icon[sq]=@APPLICATION_EXECUTABLE@ -Comment[fi_FI]=@APPLICATION_NAME@ työpöytäsynkronointisovellus -GenericName[fi_FI]=Kansion synkronointi -Name[fi_FI]=@APPLICATION_NAME@ työpöytäsynkronointisovellus -Icon[fi_FI]=@APPLICATION_EXECUTABLE@ -Comment[sv]=@APPLICATION_NAME@ desktop synkroniseringsklient -GenericName[sv]=Mappsynk -Name[sv]=@APPLICATION_NAME@ desktop synk-klient -Icon[sv]=@APPLICATION_EXECUTABLE@ -Comment[tr]=@APPLICATION_NAME@ masaüstü eşitleme istemcisi -GenericName[tr]=Dosya Eşitleme -Name[tr]=@APPLICATION_NAME@ masaüstü eşitleme istemcisi -Icon[tr]=@APPLICATION_EXECUTABLE@ -Comment[uk]=Настільний клієнт синхронізації @APPLICATION_NAME@ -GenericName[uk]=Синхронізація теки -Name[uk]=Настільний клієнт синхронізації @APPLICATION_NAME@ -Icon[uk]=@APPLICATION_EXECUTABLE@ -Comment[ro]=@APPLICATION_NAME@ client de sincronizare pe desktop -GenericName[ro]=Sincronizare director -Name[ro]=@APPLICATION_NAME@ client de sincronizare pe desktop -Icon[ro]=@APPLICATION_EXECUTABLE@ -Comment[zh_CN]=@APPLICATION_NAME@ 桌面同步客户端 -GenericName[zh_CN]=文件夹同步 -Name[zh_CN]=@APPLICATION_NAME@ 桌面同步客户端 -Icon[zh_CN]=@APPLICATION_EXECUTABLE@ -Comment[zh_HK]=桌面版同步客户端 -Comment[zh_TW]=@APPLICATION_NAME@ 桌面同步客戶端 -GenericName[zh_TW]=資料夾同步 -Name[zh_TW]=@APPLICATION_NAME@ 桌面同步客戶端 -Icon[zh_TW]=@APPLICATION_EXECUTABLE@ -Comment[es_AR]=Cliente de sincronización para escritorio @APPLICATION_NAME@ -GenericName[es_AR]=Sincronización de directorio -Name[es_AR]=Cliente de sincronización para escritorio @APPLICATION_NAME@ -Icon[es_AR]=@APPLICATION_EXECUTABLE@ -Comment[lt_LT]=@APPLICATION_NAME@ darbalaukio sinchronizavimo programa -GenericName[lt_LT]=Katalogo sinchnorizacija -Name[lt_LT]=@APPLICATION_NAME@ darbalaukio programa -Icon[lt_LT]=@APPLICATION_EXECUTABLE@ -Comment[th_TH]=@APPLICATION_NAME@ ประสานข้อมูลด้วยโปรแกรมบนเดสก์ท็อป -GenericName[th_TH]=ประสานข้อมูลโฟลเดอร์ -Name[th_TH]= @APPLICATION_NAME@ ประสานข้อมูลด้วยโปรแกรมบนเดสก์ท็อป -Icon[th_TH]=@APPLICATION_EXECUTABLE@ -Comment[es_MX]=Cliente de escritorio para sincronziación de @APPLICATION_NAME@ -GenericName[es_MX]=Sincronización de Carpetas -Name[es_MX]=Cliente de escritorio para sincronziación de @APPLICATION_NAME@ -Icon[es_MX]=@APPLICATION_EXECUTABLE@ -Comment[nb_NO]=@APPLICATION_NAME@ skrivebordssynkroniseringsklient -GenericName[nb_NO]=Mappesynkronisering -Name[nb_NO]=@APPLICATION_NAME@ skrivebordssynkroniseringsklient -Icon[nb_NO]=@APPLICATION_EXECUTABLE@ -Comment[nn_NO]=@APPLICATION_NAME@ klient for å synkronisera frå skrivebord -GenericName[nn_NO]=Mappe synkronisering -Name[nn_NO]=@APPLICATION_NAME@ klient for å synkronisera frå skrivebord -Icon[nn_NO]=@APPLICATION_EXECUTABLE@ -Comment[pt_PT]=@APPLICATION_NAME@ - Cliente de Sincronização para PC -GenericName[pt_PT]=Sincronizar Pasta -Name[pt_PT]=@APPLICATION_NAME@ - Cliente de Sincronização para PC -Icon[pt_PT]=@APPLICATION_EXECUTABLE@ -Icon[km]=@APPLICATION_EXECUTABLE@ -Comment[lb]=@APPLICATION_NAME@ Desktop Synchronisatioun Client -GenericName[lb]=Dossier Dync -Name[lb]=@APPLICATION_NAME@ Desktop Sync Client -Icon[lb]=@APPLICATION_EXECUTABLE@ - - -[Desktop Action Settings] -Exec=@APPLICATION_EXECUTABLE@ --showsettings -Name=Show @APPLICATION_NAME@ settings -Icon=@APPLICATION_ICON_NAME@ - -[Desktop Action Quit] -Exec=@APPLICATION_EXECUTABLE@ --quit -Name=Quit @APPLICATION_NAME@ -Icon=@APPLICATION_ICON_NAME@ diff --git a/owncloud.desktop.in b/owncloud.desktop.in new file mode 100644 index 00000000000..8137aa02134 --- /dev/null +++ b/owncloud.desktop.in @@ -0,0 +1,170 @@ +[Desktop Entry] +Categories=Utility;X-SuSE-SyncUtility; +Type=Application +Exec=@APPLICATION_EXECUTABLE@ --showsettings +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 +MimeType=application/vnd.@APPLICATION_EXECUTABLE@; +Actions=Settings;Quit; +StartupWMClass=@APPLICATION_EXECUTABLE@ + +# Translations +Comment[oc]=@APPLICATION_NAME@ sincronizacion del client +GenericName[oc]=Dorsièr de Sincronizacion +Name[oc]=@APPLICATION_NAME@ sincronizacion del client +Comment[ar]=@APPLICATION_NAME@ زبون مزامنة مكتبي +GenericName[ar]=مزامنة المجلد +Name[ar]=@APPLICATION_NAME@ زبون مزامنة مكتبي +Comment[bg_BG]=@APPLICATION_NAME@ клиент за десктоп синхронизация +GenericName[bg_BG]=Синхронизиране на папката +Name[bg_BG]=@APPLICATION_NAME@ клиент десктоп синхронизация +Comment[ca]=Client de sincronització d'escriptori @APPLICATION_NAME@ +GenericName[ca]=Sincronització de carpetes +Name[ca]=Client de sincronització d'escriptori @APPLICATION_NAME@ +Comment[da]=@APPLICATION_NAME@ skrivebordsklient til synkronisering +GenericName[da]=Mappesynkronisering +Name[da]=@APPLICATION_NAME@ skrivebordsklient til synk +Comment[de]=@APPLICATION_NAME@ Desktop-Synchronisationsclient +GenericName[de]=Ordner-Synchronisation +Name[de]=@APPLICATION_NAME@ Desktop-Synchronisationsclient +Comment[ja_JP]=@APPLICATION_NAME@ デスクトップ同期クライアント +GenericName[ja_JP]=フォルダー同期 +Name[ja_JP]=@APPLICATION_NAME@ デスクトップ同期クライアント +Comment[el]=@APPLICATION_NAME@ συγχρονισμός επιφάνειας εργασίας πελάτη +GenericName[el]=Συγχρονισμός φακέλου +Name[el]=@APPLICATION_NAME@ συγχρονισμός επιφάνειας εργασίας πελάτη +Comment[en_GB]=@APPLICATION_NAME@ desktop synchronisation client +GenericName[en_GB]=Folder Sync +Name[en_GB]=@APPLICATION_NAME@ desktop sync client +Comment[es]=@APPLICATION_NAME@ cliente de sincronización de escritorio +GenericName[es]=Sincronización de carpeta +Name[es]=@APPLICATION_NAME@ cliente de sincronización de escritorio +Comment[de_DE]=@APPLICATION_NAME@ Desktop-Synchronisationsclient +GenericName[de_DE]=Ordner-Synchronisation +Name[de_DE]=@APPLICATION_NAME@ Desktop-Synchronisationsclient +Comment[eu]=@APPLICATION_NAME@ mahaigaineko sinkronizazio bezeroa +GenericName[eu]=Karpetaren sinkronizazioa +Name[eu]=@APPLICATION_NAME@ mahaigaineko sinkronizazio bezeroa +GenericName[fa]=همسان سازی پوشه‌ها +Name[fa]=@APPLICATION_EXECUTABLE@ نسخه‌ی همسان سازی مشتری +Comment[fr]=Client de synchronisation @APPLICATION_NAME@ +GenericName[fr]=Synchronisation de dossier +Name[fr]=Client de synchronisation @APPLICATION_NAME@ +Comment[gl]=@APPLICATION_NAME@ cliente de sincronización para escritorio +GenericName[gl]=Sincronizar Cartafol +Name[gl]=@APPLICATION_NAME@ cliente de sincronización para escritorio +Comment[he]=@APPLICATION_NAME@ לקוח סנכון שולחן עבודה +GenericName[he]=סנכון תיקייה +Name[he]=@APPLICATION_NAME@ לקוח סנכרון שולחן עבודה +Comment[ia]=@APPLICATION_NAME@ cliente de synchronisation pro scriptorio +GenericName[ia]=Synchronisar Dossier +Name[ia]=@APPLICATION_NAME@ cliente de synchronisation pro scriptorio +Comment[id]=Klien sinkronisasi desktop @APPLICATION_NAME@ +GenericName[id]=Folder Sync +Name[id]=Klien sync desktop @APPLICATION_NAME@ +Comment[is]=@APPLICATION_NAME@ skjáborðsforrit samstillingar +GenericName[is]=Samstilling möppu +Name[is]=@APPLICATION_NAME@ skjáborðsforrit samstillingar +Comment[it]=Client di sincronizzazione del desktop di @APPLICATION_NAME@ +GenericName[it]=Sincronizzazione cartella +Name[it]=Client di sincronizzazione del desktop di @APPLICATION_NAME@ +Comment[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트 +GenericName[ko]=폴더 동기화 +Name[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트 +Comment[lo]=@APPLICATION_NAME@ ການປະສານຂໍ້ມູນຄອມພິວເຕີລູກຂ່າຍ +GenericName[lo]=ໂຟນເດີຊິງ +Name[lo]=@APPLICATION_NAME@ ຊິງຄອມພິວເຕີລູກຂ່າຍ +Comment[mk]=@APPLICATION_NAME@ десктор клиент за синхронизација +GenericName[mk]=Папка за синхронизација +Name[mk]=@APPLICATION_NAME@ десктор клиент за синхронизација +Comment[hu_HU]=@APPLICATION_NAME@ asztali szinkronizációs kliens +GenericName[hu_HU]=Mappaszinkronizálás +Name[hu_HU]=@APPLICATION_NAME@ asztali szinkronizációs kliens +Comment[af_ZA]=@APPLICATION_NAME@ werkskermsinchroniseerkliënt +GenericName[af_ZA]=Vouersinchronisering +Name[af_ZA]=@APPLICATION_NAME@ werkskermsinchroniseerkliënt +Comment[nl]=@APPLICATION_NAME@ desktop synchronisatie client +GenericName[nl]=Mappen sync +Name[nl]=@APPLICATION_NAME@ desktop sync client +Comment[et_EE]=@APPLICATION_NAME@ sünkroonimise klient töölauale +GenericName[et_EE]=Kaustade sünkroonimine +Name[et_EE]=@APPLICATION_NAME@ sünkroonimise klient töölauale +Comment[pl]=@APPLICATION_NAME@ klient synchronizacji dla komputerów stacjonarnych +GenericName[pl]=Folder Synchronizacji +Name[pl]=@APPLICATION_NAME@ klient synchronizacji dla komputerów stacjonarnych +Comment[pt_BR]=@APPLICATION_NAME@ cliente de sincronização do computador +GenericName[pt_BR]=Sincronização de Pasta +Name[pt_BR]=@APPLICATION_NAME@ cliente de sincronização de desktop +Comment[cs_CZ]=@APPLICATION_NAME@ počítačový synchronizační klient +GenericName[cs_CZ]=Synchronizace adresáře +Name[cs_CZ]=@APPLICATION_NAME@ počítačový synchronizační klient +Comment[ru]=Настольный клиент синхронизации @APPLICATION_NAME@ +GenericName[ru]=Синхронизация каталогов +Name[ru]=Настольный клиент синхронизации @APPLICATION_NAME@ +Comment[sl]=@APPLICATION_NAME@ ‒ Program za usklajevanje datotek z namizjem +GenericName[sl]=Usklajevanje map +Name[sl]=@APPLICATION_NAME@ ‒ Program za usklajevanje datotek z namizjem +Comment[sq]=Klient njëkohësimesh @APPLICATION_NAME@ për desktop +GenericName[sq]=Njëkohësim Dosjesh +Name[sq]=Klient njëkohësimesh @APPLICATION_NAME@ për desktop +Comment[fi_FI]=@APPLICATION_NAME@ työpöytäsynkronointisovellus +GenericName[fi_FI]=Kansion synkronointi +Name[fi_FI]=@APPLICATION_NAME@ työpöytäsynkronointisovellus +Comment[sv]=@APPLICATION_NAME@ desktop synkroniseringsklient +GenericName[sv]=Mappsynk +Name[sv]=@APPLICATION_NAME@ desktop synk-klient +Comment[tr]=@APPLICATION_NAME@ masaüstü eşitleme istemcisi +GenericName[tr]=Dosya Eşitleme +Name[tr]=@APPLICATION_NAME@ masaüstü eşitleme istemcisi +Comment[uk]=Настільний клієнт синхронізації @APPLICATION_NAME@ +GenericName[uk]=Синхронізація теки +Name[uk]=Настільний клієнт синхронізації @APPLICATION_NAME@ +Comment[ro]=@APPLICATION_NAME@ client de sincronizare pe desktop +GenericName[ro]=Sincronizare director +Name[ro]=@APPLICATION_NAME@ client de sincronizare pe desktop +Comment[zh_CN]=@APPLICATION_NAME@ 桌面同步客户端 +GenericName[zh_CN]=文件夹同步 +Name[zh_CN]=@APPLICATION_NAME@ 桌面同步客户端 +Comment[zh_HK]=桌面版同步客户端 +Comment[zh_TW]=@APPLICATION_NAME@ 桌面同步客戶端 +GenericName[zh_TW]=資料夾同步 +Name[zh_TW]=@APPLICATION_NAME@ 桌面同步客戶端 +Comment[es_AR]=Cliente de sincronización para escritorio @APPLICATION_NAME@ +GenericName[es_AR]=Sincronización de directorio +Name[es_AR]=Cliente de sincronización para escritorio @APPLICATION_NAME@ +Comment[lt_LT]=@APPLICATION_NAME@ darbalaukio sinchronizavimo programa +GenericName[lt_LT]=Katalogo sinchnorizacija +Name[lt_LT]=@APPLICATION_NAME@ darbalaukio programa +Comment[th_TH]=@APPLICATION_NAME@ ประสานข้อมูลด้วยโปรแกรมบนเดสก์ท็อป +GenericName[th_TH]=ประสานข้อมูลโฟลเดอร์ +Name[th_TH]= @APPLICATION_NAME@ ประสานข้อมูลด้วยโปรแกรมบนเดสก์ท็อป +Comment[es_MX]=Cliente de escritorio para sincronziación de @APPLICATION_NAME@ +GenericName[es_MX]=Sincronización de Carpetas +Name[es_MX]=Cliente de escritorio para sincronziación de @APPLICATION_NAME@ +Comment[nb_NO]=@APPLICATION_NAME@ skrivebordssynkroniseringsklient +GenericName[nb_NO]=Mappesynkronisering +Name[nb_NO]=@APPLICATION_NAME@ skrivebordssynkroniseringsklient +Comment[nn_NO]=@APPLICATION_NAME@ klient for å synkronisera frå skrivebord +GenericName[nn_NO]=Mappe synkronisering +Name[nn_NO]=@APPLICATION_NAME@ klient for å synkronisera frå skrivebord +Comment[pt_PT]=@APPLICATION_NAME@ - Cliente de Sincronização para PC +GenericName[pt_PT]=Sincronizar Pasta +Name[pt_PT]=@APPLICATION_NAME@ - Cliente de Sincronização para PC +Comment[lb]=@APPLICATION_NAME@ Desktop Synchronisatioun Client +GenericName[lb]=Dossier Dync +Name[lb]=@APPLICATION_NAME@ Desktop Sync Client + + +[Desktop Action Settings] +Exec=@APPLICATION_EXECUTABLE@ --showsettings +Name=Show @APPLICATION_NAME@ settings +Icon=@APPLICATION_ICON_NAME@ + +[Desktop Action Quit] +Exec=@APPLICATION_EXECUTABLE@ --quit +Name=Quit @APPLICATION_NAME@ +Icon=@APPLICATION_ICON_NAME@ diff --git a/owncloudcmd.desktop.in b/owncloudcmd.desktop.in new file mode 100644 index 00000000000..26a6eed02f2 --- /dev/null +++ b/owncloudcmd.desktop.in @@ -0,0 +1,12 @@ +[Desktop Entry] +Categories=Utility;X-SuSE-SyncUtility; +Type=Application +Terminal=true +Exec=@APPLICATION_EXECUTABLE@cmd +Name=@APPLICATION_NAME@ commandline sync client +Comment=@APPLICATION_NAME@ commandline synchronization client +GenericName=Folder Sync Commandline +Icon=@APPLICATION_ICON_NAME@ +Keywords=@APPLICATION_NAME@;syncing;file;sharing; +X-AppImage-Integrate=false +NoDisplay=true diff --git a/resources/font-awesome/dark/bell-solid.svg b/resources/font-awesome/dark/bell-solid.svg deleted file mode 100644 index e5f2f535ebb..00000000000 --- a/resources/font-awesome/dark/bell-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/bolt-solid.svg b/resources/font-awesome/dark/bolt-solid.svg deleted file mode 100644 index 6aa1c1edc2f..00000000000 --- a/resources/font-awesome/dark/bolt-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/cog-solid.svg b/resources/font-awesome/dark/cog-solid.svg deleted file mode 100644 index 956c337cac6..00000000000 --- a/resources/font-awesome/dark/cog-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/ellipsis-h-solid.svg b/resources/font-awesome/dark/ellipsis-h-solid.svg deleted file mode 100644 index a2e36e3c143..00000000000 --- a/resources/font-awesome/dark/ellipsis-h-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/exclamation-triangle-solid.svg b/resources/font-awesome/dark/exclamation-triangle-solid.svg deleted file mode 100644 index 4d53f45aa4d..00000000000 --- a/resources/font-awesome/dark/exclamation-triangle-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/folder-solid.svg b/resources/font-awesome/dark/folder-solid.svg deleted file mode 100644 index 7642e349fb5..00000000000 --- a/resources/font-awesome/dark/folder-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/lock-open-solid.svg b/resources/font-awesome/dark/lock-open-solid.svg deleted file mode 100644 index c3a547e2403..00000000000 --- a/resources/font-awesome/dark/lock-open-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/lock-solid.svg b/resources/font-awesome/dark/lock-solid.svg deleted file mode 100644 index 94c5e7d77c6..00000000000 --- a/resources/font-awesome/dark/lock-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/plus-solid.svg b/resources/font-awesome/dark/plus-solid.svg deleted file mode 100644 index 44e1f858b2c..00000000000 --- a/resources/font-awesome/dark/plus-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/power-off-solid.svg b/resources/font-awesome/dark/power-off-solid.svg deleted file mode 100644 index 28baa2ec97f..00000000000 --- a/resources/font-awesome/dark/power-off-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/trash-solid.svg b/resources/font-awesome/dark/trash-solid.svg deleted file mode 100644 index 2190928cf30..00000000000 --- a/resources/font-awesome/dark/trash-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/dark/user-solid.svg b/resources/font-awesome/dark/user-solid.svg deleted file mode 100644 index 5a4de015c77..00000000000 --- a/resources/font-awesome/dark/user-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/bell-solid.svg b/resources/font-awesome/light/bell-solid.svg deleted file mode 100644 index 57216ba94f0..00000000000 --- a/resources/font-awesome/light/bell-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/bolt-solid.svg b/resources/font-awesome/light/bolt-solid.svg deleted file mode 100644 index 04351727e4b..00000000000 --- a/resources/font-awesome/light/bolt-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/cog-solid.svg b/resources/font-awesome/light/cog-solid.svg deleted file mode 100644 index 1daa61e1e4b..00000000000 --- a/resources/font-awesome/light/cog-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/ellipsis-h-solid.svg b/resources/font-awesome/light/ellipsis-h-solid.svg deleted file mode 100644 index 6583cbcbcad..00000000000 --- a/resources/font-awesome/light/ellipsis-h-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/exclamation-triangle-solid.svg b/resources/font-awesome/light/exclamation-triangle-solid.svg deleted file mode 100644 index 630af9961b1..00000000000 --- a/resources/font-awesome/light/exclamation-triangle-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/folder-solid.svg b/resources/font-awesome/light/folder-solid.svg deleted file mode 100644 index 1b7d671395b..00000000000 --- a/resources/font-awesome/light/folder-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/lock-open-solid.svg b/resources/font-awesome/light/lock-open-solid.svg deleted file mode 100644 index 83395d26371..00000000000 --- a/resources/font-awesome/light/lock-open-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/lock-solid.svg b/resources/font-awesome/light/lock-solid.svg deleted file mode 100644 index 4c1822deff4..00000000000 --- a/resources/font-awesome/light/lock-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/plus-solid.svg b/resources/font-awesome/light/plus-solid.svg deleted file mode 100644 index 2499ffba23b..00000000000 --- a/resources/font-awesome/light/plus-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/power-off-solid.svg b/resources/font-awesome/light/power-off-solid.svg deleted file mode 100644 index ec12e78db4a..00000000000 --- a/resources/font-awesome/light/power-off-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/trash-solid.svg b/resources/font-awesome/light/trash-solid.svg deleted file mode 100644 index 9c525044820..00000000000 --- a/resources/font-awesome/light/trash-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/light/user-solid.svg b/resources/font-awesome/light/user-solid.svg deleted file mode 100644 index 6ae817ff29a..00000000000 --- a/resources/font-awesome/light/user-solid.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/resources/font-awesome/update_color.ps1 b/resources/font-awesome/update_color.ps1 deleted file mode 100644 index b1fe755e069..00000000000 --- a/resources/font-awesome/update_color.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -<# - .SYNOPSIS - This script replaces the color in all svg files in this folder -#> -$COLOR=$Args[0] -Write-Host "Color:" $COLOR -if (-not $COLOR) { - Write-Host "Please specify color" - Write-Host "update_corlor.ps1 `"#B5B6BB`"" - exit(1) -} -Get-ChildItem "*.svg" | % { - $tmp = Get-Content $_ - $tmp = $tmp -replace "fill=`"\S+`"", "fill=`"$COLOR`"" - Set-Content -Path $_ -Value $tmp - -} diff --git a/shell_integration/CMakeLists.txt b/shell_integration/CMakeLists.txt index 2ad9e86aba7..f4c270ccdfa 100644 --- a/shell_integration/CMakeLists.txt +++ b/shell_integration/CMakeLists.txt @@ -1,24 +1,6 @@ if (APPLE) add_subdirectory(MacOSX) endif() -if(BUILD_SHELL_INTEGRATION_ICONS) - add_subdirectory(icons) -endif() - -if( UNIX AND NOT APPLE ) - if(BUILD_SHELL_INTEGRATION_NAUTILUS) - add_subdirectory(nautilus) - endif() - - if(BUILD_SHELL_INTEGRATION_DOLPHIN) - find_package(KF5 "5.16" COMPONENTS KIO) - if(KF5_FOUND) - add_subdirectory(dolphin) - else() - message("Dolphin plugin disabled: KDE Frameworks 5.16 not found") - endif() - endif() -endif() if(MSVC) add_subdirectory(windows) diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h index 803c5270ffb..01333b74408 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.h @@ -13,17 +13,16 @@ */ +#import "SyncClientProxy.h" #import #import -#import "SyncClientProxy.h" -@interface FinderSync : FIFinderSync -{ - SyncClientProxy *_syncClientProxy; - NSMutableSet *_registeredDirectories; - NSString *_shareMenuTitle; - NSMutableDictionary *_strings; - NSMutableArray *_menuItems; +@interface FinderSync : FIFinderSync { + SyncClientProxy *_syncClientProxy; + NSMutableSet *_registeredDirectories; + NSString *_shareMenuTitle; + NSMutableDictionary *_strings; + NSMutableArray *_menuItems; } @end diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m index a0f791882c2..d1ebeac566a 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/FinderSync.m @@ -20,190 +20,175 @@ @implementation FinderSync - (instancetype)init { - self = [super init]; - - FIFinderSyncController *syncController = [FIFinderSyncController defaultController]; - NSBundle *extBundle = [NSBundle bundleForClass:[self class]]; - // This was added to the bundle's Info.plist to get it from the build system - NSString *socketApiPrefix = [extBundle objectForInfoDictionaryKey:@"SocketApiPrefix"]; - - NSImage *ok = [extBundle imageForResource:@"ok.icns"]; - NSImage *ok_swm = [extBundle imageForResource:@"ok_swm.icns"]; - NSImage *sync = [extBundle imageForResource:@"sync.icns"]; - NSImage *warning = [extBundle imageForResource:@"warning.icns"]; - NSImage *error = [extBundle imageForResource:@"error.icns"]; - - [syncController setBadgeImage:ok label:@"Up to date" forBadgeIdentifier:@"OK"]; - [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC"]; - [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW"]; - [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE"]; - [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR"]; - [syncController setBadgeImage:ok_swm label:@"Shared" forBadgeIdentifier:@"OK+SWM"]; - [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC+SWM"]; - [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW+SWM"]; - [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"]; - [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"]; - - // The Mach port name needs to: - // - Be prefixed with the code signing Team ID - // - Then infixed with the sandbox App Group - // - The App Group itself must be a prefix of (or equal to) the application bundle identifier - // We end up in the official signed client with: 9B5WD74GWJ.com.owncloud.desktopclient.socketApi - // With ad-hoc signing (the '-' signing identity) we must drop the Team ID. - // When the code isn't sandboxed (e.g. the OC client or the legacy overlay icon extension) - // the OS doesn't seem to put any restriction on the port name, so we just follow what - // the sandboxed App Extension needs. - // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24 - NSString *serverName = [socketApiPrefix stringByAppendingString:@".socketApi"]; - //NSLog(@"FinderSync serverName %@", serverName); - - _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; - _registeredDirectories = [[NSMutableSet alloc] init]; - _strings = [[NSMutableDictionary alloc] init]; - - [_syncClientProxy start]; - return self; + self = [super init]; + + FIFinderSyncController *syncController = [FIFinderSyncController defaultController]; + NSBundle *extBundle = [NSBundle bundleForClass:[self class]]; + // This was added to the bundle's Info.plist to get it from the build system + NSString *socketApiPrefix = [extBundle objectForInfoDictionaryKey:@"SocketApiPrefix"]; + + NSImage *ok = [extBundle imageForResource:@"ok.icns"]; + NSImage *ok_swm = [extBundle imageForResource:@"ok_swm.icns"]; + NSImage *sync = [extBundle imageForResource:@"sync.icns"]; + NSImage *warning = [extBundle imageForResource:@"warning.icns"]; + NSImage *error = [extBundle imageForResource:@"error.icns"]; + + [syncController setBadgeImage:ok label:@"Up to date" forBadgeIdentifier:@"OK"]; + [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC"]; + [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW"]; + [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE"]; + [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR"]; + [syncController setBadgeImage:ok_swm label:@"Shared" forBadgeIdentifier:@"OK+SWM"]; + [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"SYNC+SWM"]; + [syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW+SWM"]; + [syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"]; + [syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"]; + + // The Mach port name needs to: + // - Be prefixed with the code signing Team ID + // - Then infixed with the sandbox App Group + // - The App Group itself must be a prefix of (or equal to) the application bundle identifier + // We end up in the official signed client with: 9B5WD74GWJ.com.owncloud.desktopclient.socketApi + // With ad-hoc signing (the '-' signing identity) we must drop the Team ID. + // When the code isn't sandboxed (e.g. the OC client or the legacy overlay icon extension) + // the OS doesn't seem to put any restriction on the port name, so we just follow what + // the sandboxed App Extension needs. + // https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24 + NSString *serverName = [socketApiPrefix stringByAppendingString:@".socketApi"]; + // NSLog(@"FinderSync serverName %@", serverName); + + _syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName]; + _registeredDirectories = [[NSMutableSet alloc] init]; + _strings = [[NSMutableDictionary alloc] init]; + + [_syncClientProxy start]; + return self; } #pragma mark - Primary Finder Sync protocol methods - (void)requestBadgeIdentifierForURL:(NSURL *)url { - BOOL isDir; - if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory: &isDir] == NO) { - NSLog(@"ERROR: Could not determine file type of %@", [url path]); - isDir = NO; - } - - NSString* normalizedPath = [[url path] decomposedStringWithCanonicalMapping]; - [_syncClientProxy askForIcon:normalizedPath isDirectory:isDir]; + BOOL isDir; + if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDir] == NO) { + NSLog(@"ERROR: Could not determine file type of %@", [url path]); + isDir = NO; + } + + NSString *normalizedPath = [[url path] decomposedStringWithCanonicalMapping]; + [_syncClientProxy askForIcon:normalizedPath isDirectory:isDir]; } #pragma mark - Menu and toolbar item support -- (NSString*) selectedPathsSeparatedByRecordSeparator +- (NSString *)selectedPathsSeparatedByRecordSeparator { - FIFinderSyncController *syncController = [FIFinderSyncController defaultController]; - NSMutableString *string = [[NSMutableString alloc] init]; - [syncController.selectedItemURLs enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { - if (string.length > 0) { - [string appendString:@"\x1e"]; // record separator - } - NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping]; - [string appendString:normalizedPath]; - }]; - return string; + FIFinderSyncController *syncController = [FIFinderSyncController defaultController]; + NSMutableString *string = [[NSMutableString alloc] init]; + [syncController.selectedItemURLs enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if (string.length > 0) { + [string appendString:@"\x1e"]; // record separator + } + NSString *normalizedPath = [[obj path] decomposedStringWithCanonicalMapping]; + [string appendString:normalizedPath]; + }]; + return string; } - (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu { - FIFinderSyncController *syncController = [FIFinderSyncController defaultController]; - NSMutableSet *rootPaths = [[NSMutableSet alloc] init]; - [syncController.directoryURLs enumerateObjectsUsingBlock: ^(id obj, BOOL *stop) { - [rootPaths addObject:[obj path]]; - }]; - - // The server doesn't support sharing a root directory so do not show the option in this case. - // It is still possible to get a problematic sharing by selecting both the root and a child, - // but this is so complicated to do and meaningless that it's not worth putting this check - // also in shareMenuAction. - __block BOOL onlyRootsSelected = YES; - [syncController.selectedItemURLs enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) { - if (![rootPaths member:[obj path]]) { - onlyRootsSelected = NO; - *stop = YES; - } - }]; - - NSString *paths = [self selectedPathsSeparatedByRecordSeparator]; - // calling this IPC calls us back from client with several MENU_ITEM entries and then our askOnSocket returns again - [_syncClientProxy askOnSocket:paths query:@"GET_MENU_ITEMS"]; - - id contextMenuTitle = [_strings objectForKey:@"CONTEXT_MENU_TITLE"]; - if (contextMenuTitle && !onlyRootsSelected) { - NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; - NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@""]; - NSMenuItem *subMenuItem = [menu addItemWithTitle:contextMenuTitle action:nil keyEquivalent:@""]; - subMenuItem.submenu = subMenu; - subMenuItem.image = [[NSBundle mainBundle] imageForResource:@"app.icns"]; - - // There is an annoying bug in macOS (at least 10.13.3), it does not use/copy over the representedObject of a menu item - // So we have to use tag instead. - int idx = 0; - for (NSArray* item in _menuItems) { - NSMenuItem *actionItem = [subMenu addItemWithTitle:[item valueForKey:@"text"] - action:@selector(subMenuActionClicked:) - keyEquivalent:@""]; - [actionItem setTag:idx]; - [actionItem setTarget:self]; - NSString *flags = [item valueForKey:@"flags"]; // e.g. "d" - if ([flags rangeOfString:@"d"].location != NSNotFound) { - [actionItem setEnabled:false]; - } - idx++; - } - return menu; - } - return nil; + FIFinderSyncController *syncController = [FIFinderSyncController defaultController]; + NSMutableSet *rootPaths = [[NSMutableSet alloc] init]; + [syncController.directoryURLs enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { [rootPaths addObject:[obj path]]; }]; + + NSString *paths = [self selectedPathsSeparatedByRecordSeparator]; + // calling this IPC calls us back from client with several MENU_ITEM entries and then our askOnSocket returns again + [_syncClientProxy askOnSocket:paths query:@"GET_MENU_ITEMS"]; + + id contextMenuTitle = [_strings objectForKey:@"CONTEXT_MENU_TITLE"]; + if (contextMenuTitle && _menuItems.count != 0) { + NSMenu *menu = [[NSMenu alloc] initWithTitle:@""]; + NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@""]; + NSMenuItem *subMenuItem = [menu addItemWithTitle:contextMenuTitle action:nil keyEquivalent:@""]; + subMenuItem.submenu = subMenu; + subMenuItem.image = [[NSBundle mainBundle] imageForResource:@"app.icns"]; + + // There is an annoying bug in macOS (at least 10.13.3), it does not use/copy over the representedObject of a menu item + // So we have to use tag instead. + int idx = 0; + for (NSArray *item in _menuItems) { + NSMenuItem *actionItem = [subMenu addItemWithTitle:[item valueForKey:@"text"] action:@selector(subMenuActionClicked:) keyEquivalent:@""]; + [actionItem setTag:idx]; + [actionItem setTarget:self]; + NSString *flags = [item valueForKey:@"flags"]; // e.g. "d" + if ([flags rangeOfString:@"d"].location != NSNotFound) { + [actionItem setEnabled:false]; + } + idx++; + } + return menu; + } + return nil; } -- (void)subMenuActionClicked:(id)sender { - long idx = [(NSMenuItem*)sender tag]; - NSString *command = [[_menuItems objectAtIndex:idx] valueForKey:@"command"]; - NSString *paths = [self selectedPathsSeparatedByRecordSeparator]; - [_syncClientProxy askOnSocket:paths query:command]; +- (void)subMenuActionClicked:(id)sender +{ + long idx = [(NSMenuItem *)sender tag]; + NSString *command = [[_menuItems objectAtIndex:idx] valueForKey:@"command"]; + NSString *paths = [self selectedPathsSeparatedByRecordSeparator]; + [_syncClientProxy askOnSocket:paths query:command]; } #pragma mark - SyncClientProxyDelegate implementation -- (void)setResultForPath:(NSString*)path result:(NSString*)result +- (void)setResultForPath:(NSString *)path result:(NSString *)result { - NSString *normalizedPath = [path decomposedStringWithCanonicalMapping]; - [[FIFinderSyncController defaultController] setBadgeIdentifier:result forURL:[NSURL fileURLWithPath:normalizedPath]]; + NSString *normalizedPath = [path decomposedStringWithCanonicalMapping]; + [[FIFinderSyncController defaultController] setBadgeIdentifier:result forURL:[NSURL fileURLWithPath:normalizedPath]]; } -- (void)reFetchFileNameCacheForPath:(NSString*)path +- (void)reFetchFileNameCacheForPath:(NSString *)path { } -- (void)registerPath:(NSString*)path +- (void)registerPath:(NSString *)path { - assert(_registeredDirectories); - [_registeredDirectories addObject:[NSURL fileURLWithPath:path]]; - [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories; + assert(_registeredDirectories); + [_registeredDirectories addObject:[NSURL fileURLWithPath:path]]; + [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories; } -- (void)unregisterPath:(NSString*)path +- (void)unregisterPath:(NSString *)path { - [_registeredDirectories removeObject:[NSURL fileURLWithPath:path]]; - [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories; + [_registeredDirectories removeObject:[NSURL fileURLWithPath:path]]; + [FIFinderSyncController defaultController].directoryURLs = _registeredDirectories; } -- (void)setString:(NSString*)key value:(NSString*)value +- (void)setString:(NSString *)key value:(NSString *)value { - [_strings setObject:value forKey:key]; + [_strings setObject:value forKey:key]; } - (void)resetMenuItems { - _menuItems = [[NSMutableArray alloc] init]; + _menuItems = [[NSMutableArray alloc] init]; } -- (void)addMenuItem:(NSDictionary *)item { - [_menuItems addObject:item]; +- (void)addMenuItem:(NSDictionary *)item +{ + [_menuItems addObject:item]; } - (void)connectionDidDie { - [_strings removeAllObjects]; - [_registeredDirectories removeAllObjects]; - // For some reason the FIFinderSync cache doesn't seem to be cleared for the root item when - // we reset the directoryURLs (seen on macOS 10.12 at least). - // First setting it to the FS root and then setting it to nil seems to work around the issue. - [FIFinderSyncController defaultController].directoryURLs = [NSSet setWithObject:[NSURL fileURLWithPath:@"/"]]; - // This will tell Finder that this extension isn't attached to any directory - // until we can reconnect to the sync client. - [FIFinderSyncController defaultController].directoryURLs = nil; + [_strings removeAllObjects]; + [_registeredDirectories removeAllObjects]; + // For some reason the FIFinderSync cache doesn't seem to be cleared for the root item when + // we reset the directoryURLs (seen on macOS 10.12 at least). + // First setting it to the FS root and then setting it to nil seems to work around the issue. + [FIFinderSyncController defaultController].directoryURLs = [NSSet setWithObject:[NSURL fileURLWithPath:@"/"]]; + // This will tell Finder that this extension isn't attached to any directory + // until we can reconnect to the sync client. + [FIFinderSyncController defaultController].directoryURLs = nil; } @end - diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h index 1d0fd74b858..027aaef726b 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.h @@ -16,30 +16,29 @@ @protocol SyncClientProxyDelegate -- (void)setResultForPath:(NSString*)path result:(NSString*)result; -- (void)reFetchFileNameCacheForPath:(NSString*)path; -- (void)registerPath:(NSString*)path; -- (void)unregisterPath:(NSString*)path; -- (void)setString:(NSString*)key value:(NSString*)value; +- (void)setResultForPath:(NSString *)path result:(NSString *)result; +- (void)reFetchFileNameCacheForPath:(NSString *)path; +- (void)registerPath:(NSString *)path; +- (void)unregisterPath:(NSString *)path; +- (void)setString:(NSString *)key value:(NSString *)value; - (void)resetMenuItems; - (void)addMenuItem:(NSDictionary *)item; - (void)connectionDidDie; @end @protocol ChannelProtocol -- (void)sendMessage:(NSData*)msg; +- (void)sendMessage:(NSData *)msg; @end -@interface SyncClientProxy : NSObject -{ - NSString *_serverName; - NSDistantObject *_remoteEnd; +@interface SyncClientProxy : NSObject { + NSString *_serverName; + NSDistantObject *_remoteEnd; } -@property (weak) id delegate; +@property (weak) id delegate; -- (instancetype)initWithDelegate:(id)arg1 serverName:(NSString*)serverName; +- (instancetype)initWithDelegate:(id)arg1 serverName:(NSString *)serverName; - (void)start; -- (void)askOnSocket:(NSString*)path query:(NSString*)verb; -- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir; +- (void)askOnSocket:(NSString *)path query:(NSString *)verb; +- (void)askForIcon:(NSString *)path isDirectory:(BOOL)isDir; @end diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m index 656f7700328..e92759ea9c0 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m +++ b/shell_integration/MacOSX/OwnCloudFinderSync/FinderSyncExt/SyncClientProxy.m @@ -24,138 +24,133 @@ - (void)registerTransmitter:(id)tx; @implementation SyncClientProxy -- (instancetype)initWithDelegate:(id)arg1 serverName:(NSString*)serverName +- (instancetype)initWithDelegate:(id)arg1 serverName:(NSString *)serverName { - self = [super init]; - - self.delegate = arg1; - _serverName = serverName; - _remoteEnd = nil; + self = [super init]; - return self; + self.delegate = arg1; + _serverName = serverName; + _remoteEnd = nil; + + return self; } #pragma mark - Connection setup - (void)start { - if (_remoteEnd) - return; - - // Lookup the server connection - NSConnection *conn = [NSConnection connectionWithRegisteredName:_serverName host:nil]; - - if (!conn) { - // Could not connect to the sync client - [self scheduleRetry]; - return; - } - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(connectionDidDie:) - name:NSConnectionDidDieNotification - object:conn]; - - NSDistantObject *server = (NSDistantObject *)[conn rootProxy]; - assert(server); - - // This saves a few Mach messages, enable "Distributed Objects" in the scheme's Run diagnostics to watch - [server setProtocolForProxy:@protocol(ServerProtocol)]; - - // Send an object to the server to act as the channel rx, we'll receive the tx through registerTransmitter - [server registerClient:self]; + if (_remoteEnd) + return; + + // Lookup the server connection + NSConnection *conn = [NSConnection connectionWithRegisteredName:_serverName host:nil]; + + if (!conn) { + // Could not connect to the sync client + [self scheduleRetry]; + return; + } + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionDidDie:) name:NSConnectionDidDieNotification object:conn]; + + NSDistantObject *server = (NSDistantObject *)[conn rootProxy]; + assert(server); + + // This saves a few Mach messages, enable "Distributed Objects" in the scheme's Run diagnostics to watch + [server setProtocolForProxy:@protocol(ServerProtocol)]; + + // Send an object to the server to act as the channel rx, we'll receive the tx through registerTransmitter + [server registerClient:self]; } - (void)registerTransmitter:(id)tx; { - // The server replied with the distant object that we will use for tx - _remoteEnd = (NSDistantObject *)tx; - [_remoteEnd setProtocolForProxy:@protocol(ChannelProtocol)]; - - // Everything is set up, start querying - [self askOnSocket:@"" query:@"GET_STRINGS"]; + // The server replied with the distant object that we will use for tx + _remoteEnd = (NSDistantObject *)tx; + [_remoteEnd setProtocolForProxy:@protocol(ChannelProtocol)]; + + // Everything is set up, start querying + [self askOnSocket:@"" query:@"GET_STRINGS"]; } - (void)scheduleRetry { - [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(start) userInfo:nil repeats:NO]; + [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(start) userInfo:nil repeats:NO]; } -- (void)connectionDidDie:(NSNotification*)notification +- (void)connectionDidDie:(NSNotification *)notification { -#pragma unused(notification) - _remoteEnd = nil; - [_delegate connectionDidDie]; - - [self scheduleRetry]; +#pragma unused(notification) + _remoteEnd = nil; + [_delegate connectionDidDie]; + + [self scheduleRetry]; } #pragma mark - Communication logic -- (void)sendMessage:(NSData*)msg +- (void)sendMessage:(NSData *)msg { - NSString *answer = [[NSString alloc] initWithData:msg encoding:NSUTF8StringEncoding]; - - // Cut the trailing newline. We always only receive one line from the client. - answer = [answer substringToIndex:[answer length] - 1]; - NSArray *chunks = [answer componentsSeparatedByString: @":"]; - - if( [[chunks objectAtIndex:0] isEqualToString:@"STATUS"] ) { - NSString *result = [chunks objectAtIndex:1]; - NSString *path = [chunks objectAtIndex:2]; - if( [chunks count] > 3 ) { - for( int i = 2; i < [chunks count]-1; i++ ) { - path = [NSString stringWithFormat:@"%@:%@", - path, [chunks objectAtIndex:i+1] ]; - } - } - [_delegate setResultForPath:path result:result]; - } else if( [[chunks objectAtIndex:0] isEqualToString:@"UPDATE_VIEW"] ) { - NSString *path = [chunks objectAtIndex:1]; - [_delegate reFetchFileNameCacheForPath:path]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"REGISTER_PATH"] ) { - NSString *path = [chunks objectAtIndex:1]; - [_delegate registerPath:path]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"UNREGISTER_PATH"] ) { - NSString *path = [chunks objectAtIndex:1]; - [_delegate unregisterPath:path]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"GET_STRINGS"] ) { - // BEGIN and END messages, do nothing. - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"STRING"] ) { - [_delegate setString:[chunks objectAtIndex:1] value:[chunks objectAtIndex:2]]; - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"GET_MENU_ITEMS"] ) { - if ([[chunks objectAtIndex:1] isEqualToString:@"BEGIN"]) { - [_delegate resetMenuItems]; - } else if ([[chunks objectAtIndex:1] isEqualToString:@"END"]) { - // Don't do anything special, the askOnSocket call in FinderSync menuForMenuKind will return after this line - } - } else if( [[chunks objectAtIndex:0 ] isEqualToString:@"MENU_ITEM"] ) { - NSMutableDictionary *item = [[NSMutableDictionary alloc] init]; - [item setValue:[chunks objectAtIndex:1] forKey:@"command"]; // e.g. "COPY_PRIVATE_LINK" - [item setValue:[chunks objectAtIndex:2] forKey:@"flags"]; // e.g. "d" - [item setValue:[chunks objectAtIndex:3] forKey:@"text"]; // e.g. "Copy private link to clipboard" - [_delegate addMenuItem:item]; - } else { - NSLog(@"SyncState: Unknown command %@", [chunks objectAtIndex:0]); - } + NSString *answer = [[NSString alloc] initWithData:msg encoding:NSUTF8StringEncoding]; + + // Cut the trailing newline. We always only receive one line from the client. + answer = [answer substringToIndex:[answer length] - 1]; + NSArray *chunks = [answer componentsSeparatedByString:@":"]; + + if ([[chunks objectAtIndex:0] isEqualToString:@"STATUS"]) { + NSString *result = [chunks objectAtIndex:1]; + NSString *path = [chunks objectAtIndex:2]; + if ([chunks count] > 3) { + for (int i = 2; i < [chunks count] - 1; i++) { + path = [NSString stringWithFormat:@"%@:%@", path, [chunks objectAtIndex:i + 1]]; + } + } + [_delegate setResultForPath:path result:result]; + } else if ([[chunks objectAtIndex:0] isEqualToString:@"UPDATE_VIEW"]) { + NSString *path = [chunks objectAtIndex:1]; + [_delegate reFetchFileNameCacheForPath:path]; + } else if ([[chunks objectAtIndex:0] isEqualToString:@"REGISTER_PATH"]) { + NSString *path = [chunks objectAtIndex:1]; + [_delegate registerPath:path]; + } else if ([[chunks objectAtIndex:0] isEqualToString:@"UNREGISTER_PATH"]) { + NSString *path = [chunks objectAtIndex:1]; + [_delegate unregisterPath:path]; + } else if ([[chunks objectAtIndex:0] isEqualToString:@"GET_STRINGS"]) { + // BEGIN and END messages, do nothing. + } else if ([[chunks objectAtIndex:0] isEqualToString:@"STRING"]) { + [_delegate setString:[chunks objectAtIndex:1] value:[chunks objectAtIndex:2]]; + } else if ([[chunks objectAtIndex:0] isEqualToString:@"GET_MENU_ITEMS"]) { + if ([[chunks objectAtIndex:1] isEqualToString:@"BEGIN"]) { + [_delegate resetMenuItems]; + } else if ([[chunks objectAtIndex:1] isEqualToString:@"END"]) { + // Don't do anything special, the askOnSocket call in FinderSync menuForMenuKind will return after this line + } + } else if ([[chunks objectAtIndex:0] isEqualToString:@"MENU_ITEM"]) { + NSMutableDictionary *item = [[NSMutableDictionary alloc] init]; + [item setValue:[chunks objectAtIndex:1] forKey:@"command"]; // e.g. "COPY_PRIVATE_LINK" + [item setValue:[chunks objectAtIndex:2] forKey:@"flags"]; // e.g. "d" + [item setValue:[chunks objectAtIndex:3] forKey:@"text"]; // e.g. "Copy private link to clipboard" + [_delegate addMenuItem:item]; + } else { + NSLog(@"SyncState: Unknown command %@", [chunks objectAtIndex:0]); + } } -- (void)askOnSocket:(NSString*)path query:(NSString*)verb +- (void)askOnSocket:(NSString *)path query:(NSString *)verb { - NSString *query = [NSString stringWithFormat:@"%@:%@\n", verb,path]; - - @try { - [_remoteEnd sendMessage:[query dataUsingEncoding:NSUTF8StringEncoding]]; - } @catch(NSException* e) { - // Do nothing and wait for connectionDidDie - } + NSString *query = [NSString stringWithFormat:@"%@:%@\n", verb, path]; + + @try { + [_remoteEnd sendMessage:[query dataUsingEncoding:NSUTF8StringEncoding]]; + } @catch (NSException *e) { + // Do nothing and wait for connectionDidDie + } } -- (void)askForIcon:(NSString*)path isDirectory:(BOOL)isDir +- (void)askForIcon:(NSString *)path isDirectory:(BOOL)isDir { - NSString *verb = isDir ? @"RETRIEVE_FOLDER_STATUS" : @"RETRIEVE_FILE_STATUS"; - [self askOnSocket:path query:verb]; + NSString *verb = isDir ? @"RETRIEVE_FOLDER_STATUS" : @"RETRIEVE_FILE_STATUS"; + [self askOnSocket:path query:verb]; } @end - diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj index 5d987c86f2b..4e46f194068 100644 --- a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.pbxproj @@ -343,7 +343,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = desktopclient/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -386,7 +386,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = desktopclient/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -434,7 +434,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = FinderSyncExt/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; OC_APPLICATION_NAME = ownCloud; OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; @@ -483,7 +483,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = FinderSyncExt/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.10; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; OC_APPLICATION_NAME = ownCloud; OC_APPLICATION_REV_DOMAIN = com.owncloud.desktopclient; diff --git a/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000000..18d981003d6 --- /dev/null +++ b/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/shell_integration/dolphin/CMakeLists.txt b/shell_integration/dolphin/CMakeLists.txt deleted file mode 100644 index 6a06517c29e..00000000000 --- a/shell_integration/dolphin/CMakeLists.txt +++ /dev/null @@ -1,59 +0,0 @@ -project(dolphin-owncloud) - -cmake_minimum_required(VERSION 2.8.12) -include(FeatureSummary) -set(QT_MIN_VERSION "5.3.0") -set(KF5_MIN_VERSION "5.16.0") - -find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Network) - -find_package(ECM 1.2.0 REQUIRED CONFIG) -set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - -find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS CoreAddons KIO) - -set_package_properties(DolphinVcs PROPERTIES - DESCRIPTION "the Dolphin plugin library" - URL "http://dolphin.kde.org/" - TYPE REQUIRED - PURPOSE "Provides plugin interfaces for Dolphin." -) - -set(KDE_INSTALL_DIRS_NO_DEPRECATED TRUE) -include(KDEInstallDirs) -# kcoreaddons_add_plugin uses deprecated VAR PLUGIN_INSTALL_DIR -# when that is fixed and you want to remove this workaround, -# you need to _require_ the new enough kcoreaddons -set(PLUGIN_INSTALL_DIR "${KDE_INSTALL_PLUGINDIR}") -include(KDECMakeSettings) -include(KDECompilerSettings) -include(ECMMarkNonGuiExecutable) -include(GenerateExportHeader) - -include_directories(${CMAKE_CURRENT_BINARY_DIR}) -add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) - -if (NOT DEFINED APPLICATION_EXECUTABLE ) - set ( APPLICATION_EXECUTABLE "owncloud" ) -endif() - -#---HELPER--- -set(OWNCLOUDDOLPHINHELPER ${APPLICATION_EXECUTABLE}dolphinpluginhelper) -add_library(${OWNCLOUDDOLPHINHELPER} SHARED ownclouddolphinpluginhelper.cpp) -target_link_libraries(${OWNCLOUDDOLPHINHELPER} Qt5::Network Qt5::Gui) -generate_export_header(${OWNCLOUDDOLPHINHELPER} BASE_NAME ownclouddolphinpluginhelper) -install(TARGETS ${OWNCLOUDDOLPHINHELPER} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -#---OVERLAY PLUGIN--- -set(OWNCLOUDDOLPHINOVERLAYPLUGIN ${APPLICATION_EXECUTABLE}dolphinoverlayplugin) -kcoreaddons_add_plugin(${OWNCLOUDDOLPHINOVERLAYPLUGIN} INSTALL_NAMESPACE "kf5/overlayicon" - JSON ownclouddolphinoverlayplugin.json SOURCES ownclouddolphinoverlayplugin.cpp) -target_link_libraries(${OWNCLOUDDOLPHINOVERLAYPLUGIN} KF5::CoreAddons KF5::KIOCore KF5::KIOWidgets ${OWNCLOUDDOLPHINHELPER}) - -#---ACTION PLUGIN--- -set(OWNCLOUDDOLPHINACTIONPLUGIN ${APPLICATION_EXECUTABLE}dolphinactionplugin) -add_library(${OWNCLOUDDOLPHINACTIONPLUGIN} MODULE ownclouddolphinactionplugin.cpp) -target_link_libraries(${OWNCLOUDDOLPHINACTIONPLUGIN} KF5::CoreAddons KF5::KIOCore KF5::KIOWidgets ${OWNCLOUDDOLPHINHELPER}) -configure_file(ownclouddolphinactionplugin.desktop.in ${OWNCLOUDDOLPHINACTIONPLUGIN}.desktop ESCAPE_QUOTES @ONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${OWNCLOUDDOLPHINACTIONPLUGIN}.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) -install(TARGETS ${OWNCLOUDDOLPHINACTIONPLUGIN} DESTINATION ${KDE_INSTALL_PLUGINDIR}) diff --git a/shell_integration/dolphin/README b/shell_integration/dolphin/README deleted file mode 100644 index f79615c0a6e..00000000000 --- a/shell_integration/dolphin/README +++ /dev/null @@ -1,11 +0,0 @@ -- Recompile and install recent enough version of dolphin and kio (git from oct 2015) - -- Build and install the plugin - -- Make sure to set XDG_DATA_DIRS=$PREFIX/share, QT_PLUGIN_PATH=$PREFIX/lib64/plugins/ - -- After installing, run - kdeinit5 --noincremental - -- restart dolphin (make sure to kill all instances) - diff --git a/shell_integration/dolphin/ownclouddolphinactionplugin.cpp b/shell_integration/dolphin/ownclouddolphinactionplugin.cpp deleted file mode 100644 index 016968115fc..00000000000 --- a/shell_integration/dolphin/ownclouddolphinactionplugin.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2014 by Olivier Goffart -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ownclouddolphinpluginhelper.h" - -class OwncloudDolphinPluginAction : public KAbstractFileItemActionPlugin -{ - Q_OBJECT -public: - explicit OwncloudDolphinPluginAction(QObject* parent, const QList&) - : KAbstractFileItemActionPlugin(parent) { } - - QList actions(const KFileItemListProperties& fileItemInfos, QWidget* parentWidget) override - { - auto helper = OwncloudDolphinPluginHelper::instance(); - if (!helper->isConnected() || !fileItemInfos.isLocal()) - return {}; - - // If any of the url is outside of a sync folder, return an empty menu. - const QList urls = fileItemInfos.urlList(); - const auto paths = helper->paths(); - QByteArray files; - for (const auto &url : urls) { - QDir localPath(url.toLocalFile()); - auto localFile = localPath.canonicalPath(); - if (!std::any_of(paths.begin(), paths.end(), [&](const QString &s) { - return localFile.startsWith(s); - })) - return {}; - - if (!files.isEmpty()) - files += '\x1e'; // Record separator - files += localFile.toUtf8(); - } - - if (helper->version() < "1.1") { // in this case, lexicographic order works - return legacyActions(fileItemInfos, parentWidget); - } - - auto menu = new QMenu(parentWidget); - QEventLoop loop; - auto con = connect(helper, &OwncloudDolphinPluginHelper::commandRecieved, this, [&](const QByteArray &cmd) { - if (cmd.startsWith("GET_MENU_ITEMS:END")) { - loop.quit(); - } else if (cmd.startsWith("MENU_ITEM:")) { - auto args = QString::fromUtf8(cmd).split(QLatin1Char(':')); - if (args.size() < 4) - return; - auto action = menu->addAction(args.mid(3).join(QLatin1Char(':'))); - if (args.value(2).contains(QLatin1Char('d'))) - action->setDisabled(true); - auto call = args.value(1).toLatin1(); - connect(action, &QAction::triggered, [helper, call, files] { - helper->sendCommand(QByteArray(call + ":" + files + "\n")); - }); - } else if (cmd.startsWith("MENU_SEPARATOR:")) { - menu->addSeparator(); - } - }); - QTimer::singleShot(500, &loop, SLOT(quit())); // add a timeout to be sure we don't freeze dolphin - helper->sendCommand(QByteArray("GET_MENU_ITEMS:" + files + "\n")); - - helper->sendGetClientIconCommand(16); // get client icon with size 16x16 - - loop.exec(QEventLoop::ExcludeUserInputEvents); - disconnect(con); - if (menu->actions().isEmpty()) { - delete menu; - return {}; - } - - auto menuaction = new QAction(parentWidget); - menuaction->setText(helper->contextMenuTitle()); - menuaction->setIcon(QIcon(helper->clientIcon())); - menuaction->setMenu(menu); - - return { menuaction }; - } - - - QList legacyActions(const KFileItemListProperties &fileItemInfos, QWidget *parentWidget) - { - QList urls = fileItemInfos.urlList(); - if (urls.count() != 1) - return {}; - QDir localPath(urls.first().toLocalFile()); - auto localFile = localPath.canonicalPath(); - auto helper = OwncloudDolphinPluginHelper::instance(); - auto menuaction = new QAction(parentWidget); - menuaction->setText(helper->contextMenuTitle()); - auto menu = new QMenu(parentWidget); - menuaction->setMenu(menu); - - auto shareAction = menu->addAction(helper->shareActionTitle()); - connect(shareAction, &QAction::triggered, this, [localFile, helper] { - helper->sendCommand(QByteArray("SHARE:" + localFile.toUtf8() + "\n")); - }); - - if (!helper->copyPrivateLinkTitle().isEmpty()) { - auto copyPrivateLinkAction = menu->addAction(helper->copyPrivateLinkTitle()); - connect(copyPrivateLinkAction, &QAction::triggered, this, [localFile, helper] { - helper->sendCommand(QByteArray("COPY_PRIVATE_LINK:" + localFile.toUtf8() + "\n")); - }); - } - - if (!helper->emailPrivateLinkTitle().isEmpty()) { - auto emailPrivateLinkAction = menu->addAction(helper->emailPrivateLinkTitle()); - connect(emailPrivateLinkAction, &QAction::triggered, this, [localFile, helper] { - helper->sendCommand(QByteArray("EMAIL_PRIVATE_LINK:" + localFile.toUtf8() + "\n")); - }); - } - return { menuaction }; - } - -}; - -K_PLUGIN_FACTORY(OwncloudDolphinPluginActionFactory, registerPlugin();) - -#include "ownclouddolphinactionplugin.moc" diff --git a/shell_integration/dolphin/ownclouddolphinactionplugin.desktop.in b/shell_integration/dolphin/ownclouddolphinactionplugin.desktop.in deleted file mode 100644 index 9d8a062ca8f..00000000000 --- a/shell_integration/dolphin/ownclouddolphinactionplugin.desktop.in +++ /dev/null @@ -1,6 +0,0 @@ -[Desktop Entry] -Type=Service -Name=@APPLICATION_NAME@Action -ServiceTypes=KFileItemAction/Plugin -MimeType=application/octet-stream;inode/directory; -X-KDE-Library=@APPLICATION_EXECUTABLE@dolphinactionplugin diff --git a/shell_integration/dolphin/ownclouddolphinoverlayplugin.cpp b/shell_integration/dolphin/ownclouddolphinoverlayplugin.cpp deleted file mode 100644 index 56300f4dfb0..00000000000 --- a/shell_integration/dolphin/ownclouddolphinoverlayplugin.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2014 by Olivier Goffart -#include -#include -#include -#include -#include -#include "ownclouddolphinpluginhelper.h" - -class OwncloudDolphinPlugin : public KOverlayIconPlugin -{ - Q_PLUGIN_METADATA(IID "com.owncloud.ovarlayiconplugin" FILE "ownclouddolphinoverlayplugin.json") - Q_OBJECT - - typedef QHash StatusMap; - StatusMap m_status; - -public: - - OwncloudDolphinPlugin() { - auto helper = OwncloudDolphinPluginHelper::instance(); - QObject::connect(helper, &OwncloudDolphinPluginHelper::commandRecieved, - this, &OwncloudDolphinPlugin::slotCommandRecieved); - } - - QStringList getOverlays(const QUrl& url) override { - auto helper = OwncloudDolphinPluginHelper::instance(); - if (!helper->isConnected()) - return QStringList(); - if (!url.isLocalFile()) - return QStringList(); - QDir localPath(url.toLocalFile()); - const QByteArray localFile = localPath.canonicalPath().toUtf8(); - - helper->sendCommand(QByteArray("RETRIEVE_FILE_STATUS:" + localFile + "\n")); - - StatusMap::iterator it = m_status.find(localFile); - if (it != m_status.constEnd()) { - return overlaysForString(*it); - } - return QStringList(); - } - -private: - QStringList overlaysForString(const QByteArray &status) { - QStringList r; - if (status.startsWith("NOP")) - return r; - - if (status.startsWith("OK")) - r << "vcs-normal"; - if (status.startsWith("SYNC") || status.startsWith("NEW")) - r << "vcs-update-required"; - if (status.startsWith("IGNORE") || status.startsWith("WARN")) - r << "vcs-locally-modified-unstaged"; - if (status.startsWith("ERROR")) - r << "vcs-conflicting"; - - if (status.contains("+SWM")) - r << "document-share"; - - return r; - } - - void slotCommandRecieved(const QByteArray &line) { - - QList tokens = line.split(':'); - if (tokens.count() != 3) - return; - if (tokens[0] != "STATUS" && tokens[0] != "BROADCAST") - return; - if (tokens[2].isEmpty()) - return; - - const QByteArray name = tokens[2]; - QByteArray &status = m_status[name]; // reference to the item in the hash - if (status == tokens[1]) - return; - status = tokens[1]; - - emit overlaysChanged(QUrl::fromLocalFile(QString::fromUtf8(name)), overlaysForString(status)); - } -}; - -#include "ownclouddolphinoverlayplugin.moc" diff --git a/shell_integration/dolphin/ownclouddolphinoverlayplugin.json b/shell_integration/dolphin/ownclouddolphinoverlayplugin.json deleted file mode 100644 index a0e25f82f5e..00000000000 --- a/shell_integration/dolphin/ownclouddolphinoverlayplugin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "KPlugin": { - "Description": "Overlay icon for owncloud", - "ServiceTypes": [ - "KOverlayIconPlugin" - ] - } -} \ No newline at end of file diff --git a/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp b/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp deleted file mode 100644 index 27c2daf3fdb..00000000000 --- a/shell_integration/dolphin/ownclouddolphinpluginhelper.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2014 by Olivier Goffart -#include -#include -#include -#include -#include "ownclouddolphinpluginhelper.h" -#include "config.h" -#include -#include - -Q_LOGGING_CATEGORY(lcPluginHelper, "owncloud.dolphin", QtInfoMsg) - -OwncloudDolphinPluginHelper* OwncloudDolphinPluginHelper::instance() -{ - static OwncloudDolphinPluginHelper self; - return &self; -} - -OwncloudDolphinPluginHelper::OwncloudDolphinPluginHelper() -{ - connect(&_socket, &QLocalSocket::connected, this, &OwncloudDolphinPluginHelper::slotConnected); - connect(&_socket, &QLocalSocket::readyRead, this, &OwncloudDolphinPluginHelper::slotReadyRead); - _connectTimer.start(45 * 1000, Qt::VeryCoarseTimer, this); - tryConnect(); -} - -void OwncloudDolphinPluginHelper::timerEvent(QTimerEvent *e) -{ - if (e->timerId() == _connectTimer.timerId()) { - tryConnect(); - return; - } - QObject::timerEvent(e); -} - -bool OwncloudDolphinPluginHelper::isConnected() const -{ - return _socket.state() == QLocalSocket::ConnectedState; -} - -void OwncloudDolphinPluginHelper::sendCommand(const char* data) -{ - _socket.write(data); - _socket.flush(); -} - -void OwncloudDolphinPluginHelper::sendGetClientIconCommand(int size) -{ - const QByteArray cmd = QByteArrayLiteral("V2/GET_CLIENT_ICON:"); - const QByteArray newLine = QByteArrayLiteral("\n"); - const QJsonObject args { { QStringLiteral("size"), size } }; - const QJsonObject obj { { QStringLiteral("id"), QString::number(_msgId++) }, { QStringLiteral("arguments"), args } }; - const auto json = QJsonDocument(obj).toJson(QJsonDocument::Compact); - sendCommand(QByteArray(cmd + json + newLine)); -} - -void OwncloudDolphinPluginHelper::slotConnected() -{ - sendCommand("VERSION:\n"); - sendCommand("GET_STRINGS:\n"); -} - -void OwncloudDolphinPluginHelper::tryConnect() -{ - if (_socket.state() != QLocalSocket::UnconnectedState) { - return; - } - - QString socketPath = QStandardPaths::locate(QStandardPaths::RuntimeLocation, - APPLICATION_SHORTNAME, - QStandardPaths::LocateDirectory); - if(socketPath.isEmpty()) - return; - - _socket.connectToServer(socketPath + QLatin1String("/socket")); -} - -void OwncloudDolphinPluginHelper::slotReadyRead() -{ - while (_socket.bytesAvailable()) { - _line += _socket.readLine(); - if (!_line.endsWith("\n")) { - continue; - } - QByteArray line; - qSwap(line, _line); - line.chop(1); - if (line.isEmpty()) - continue; - const int firstColon = line.indexOf(':'); - if (firstColon == -1) { - continue; - } - // get the command (at begin of line, before first ':') - const QByteArray command = line.left(firstColon); - // rest of line contains the information - const QByteArray info = line.mid(firstColon + 1); - - if (command == QByteArrayLiteral("REGISTER_PATH")) { - const QString file = QString::fromUtf8(info); - _paths.append(file); - continue; - } else if (command == QByteArrayLiteral("STRING")) { - auto args = QString::fromUtf8(info).split(':'); - if (args.size() >= 2) { - _strings[args[0]] = args.mid(1).join(':'); - } - continue; - } else if (command == QByteArrayLiteral("VERSION")) { - auto args = info.split(':'); - if (args.size() >= 2) { - auto version = args.value(1); - _version = version; - } - if (!_version.startsWith("1.")) { - // Incompatible version, disconnect forever - _connectTimer.stop(); - _socket.disconnectFromServer(); - return; - } - } else if (command == QByteArrayLiteral("V2/GET_CLIENT_ICON_RESULT")) { - QJsonParseError error; - auto json = QJsonDocument::fromJson(info, &error).object(); - if (error.error != QJsonParseError::NoError) { - qCWarning(lcPluginHelper) << "Error while parsing result: " << error.error; - continue; - } - - auto jsonArgs = json.value("arguments").toObject(); - if (jsonArgs.isEmpty()) { - auto jsonErr = json.value("error").toObject(); - qCWarning(lcPluginHelper) << "Error getting client icon: " << jsonErr; - continue; - } - - const QByteArray pngBase64 = jsonArgs.value("png").toString().toUtf8(); - QByteArray png = QByteArray::fromBase64(pngBase64); - - QPixmap pixmap; - bool isLoaded = pixmap.loadFromData(png, "PNG"); - if (isLoaded) { - _clientIcon = pixmap; - } - } - - emit commandRecieved(line); - } -} diff --git a/shell_integration/dolphin/ownclouddolphinpluginhelper.h b/shell_integration/dolphin/ownclouddolphinpluginhelper.h deleted file mode 100644 index 71388c651c4..00000000000 --- a/shell_integration/dolphin/ownclouddolphinpluginhelper.h +++ /dev/null @@ -1,77 +0,0 @@ -/****************************************************************************** - * Copyright (C) 2014 by Olivier Goffart -#include -#include -#include -#include -#include "ownclouddolphinpluginhelper_export.h" - -class OWNCLOUDDOLPHINPLUGINHELPER_EXPORT OwncloudDolphinPluginHelper : public QObject { - Q_OBJECT -public: - static OwncloudDolphinPluginHelper *instance(); - - bool isConnected() const; - void sendCommand(const char *data); - void sendGetClientIconCommand(int size); - - QVector paths() const { return _paths; } - - QString contextMenuTitle() const - { - return _strings.value("CONTEXT_MENU_TITLE", "ownCloud"); - } - QString shareActionTitle() const - { - return _strings.value("SHARE_MENU_TITLE", "Share..."); - } - QPixmap clientIcon() const - { - return _clientIcon; - } - - QString copyPrivateLinkTitle() const { return _strings["COPY_PRIVATE_LINK_MENU_TITLE"]; } - QString emailPrivateLinkTitle() const { return _strings["EMAIL_PRIVATE_LINK_MENU_TITLE"]; } - - QByteArray version() { return _version; } - -signals: - void commandRecieved(const QByteArray &cmd); - -protected: - void timerEvent(QTimerEvent*) override; - -private: - OwncloudDolphinPluginHelper(); - void slotConnected(); - void slotReadyRead(); - void tryConnect(); - QLocalSocket _socket; - QByteArray _line; - QVector _paths; - QBasicTimer _connectTimer; - - QMap _strings; - QByteArray _version; - QPixmap _clientIcon; - int _msgId = 1; -}; diff --git a/shell_integration/icons/1024x1024/oC_error.png b/shell_integration/icons/1024x1024/oC_error.png deleted file mode 100644 index dcde8e10d67..00000000000 Binary files a/shell_integration/icons/1024x1024/oC_error.png and /dev/null differ diff --git a/shell_integration/icons/1024x1024/oC_error_shared.png b/shell_integration/icons/1024x1024/oC_error_shared.png deleted file mode 100644 index 3869cda773c..00000000000 Binary files a/shell_integration/icons/1024x1024/oC_error_shared.png and /dev/null differ diff --git a/shell_integration/icons/1024x1024/oC_ok.png b/shell_integration/icons/1024x1024/oC_ok.png deleted file mode 100644 index 8ff64fb9b0f..00000000000 Binary files a/shell_integration/icons/1024x1024/oC_ok.png and /dev/null differ diff --git a/shell_integration/icons/1024x1024/oC_ok_shared.png b/shell_integration/icons/1024x1024/oC_ok_shared.png deleted file mode 100644 index 386e529dfa7..00000000000 Binary files a/shell_integration/icons/1024x1024/oC_ok_shared.png and /dev/null differ diff --git a/shell_integration/icons/1024x1024/oC_sync.png b/shell_integration/icons/1024x1024/oC_sync.png deleted file mode 100644 index 561d18abe75..00000000000 Binary files a/shell_integration/icons/1024x1024/oC_sync.png and /dev/null differ diff --git a/shell_integration/icons/1024x1024/oC_sync_shared.png b/shell_integration/icons/1024x1024/oC_sync_shared.png deleted file mode 100644 index 607718fd4f6..00000000000 Binary files a/shell_integration/icons/1024x1024/oC_sync_shared.png and /dev/null differ diff --git a/shell_integration/icons/1024x1024/oC_warn.png b/shell_integration/icons/1024x1024/oC_warn.png deleted file mode 100644 index 8b04b118f14..00000000000 Binary files a/shell_integration/icons/1024x1024/oC_warn.png and /dev/null differ diff --git a/shell_integration/icons/1024x1024/oC_warn_shared.png b/shell_integration/icons/1024x1024/oC_warn_shared.png deleted file mode 100644 index 40c5a97b089..00000000000 Binary files a/shell_integration/icons/1024x1024/oC_warn_shared.png and /dev/null differ diff --git a/shell_integration/icons/128x128/oC_error.png b/shell_integration/icons/128x128/oC_error.png deleted file mode 100644 index 84ba8779d09..00000000000 Binary files a/shell_integration/icons/128x128/oC_error.png and /dev/null differ diff --git a/shell_integration/icons/128x128/oC_error_shared.png b/shell_integration/icons/128x128/oC_error_shared.png deleted file mode 100644 index 112b849a868..00000000000 Binary files a/shell_integration/icons/128x128/oC_error_shared.png and /dev/null differ diff --git a/shell_integration/icons/128x128/oC_ok.png b/shell_integration/icons/128x128/oC_ok.png deleted file mode 100644 index 809673b74da..00000000000 Binary files a/shell_integration/icons/128x128/oC_ok.png and /dev/null differ diff --git a/shell_integration/icons/128x128/oC_ok_shared.png b/shell_integration/icons/128x128/oC_ok_shared.png deleted file mode 100644 index c17ae78ee4a..00000000000 Binary files a/shell_integration/icons/128x128/oC_ok_shared.png and /dev/null differ diff --git a/shell_integration/icons/128x128/oC_sync.png b/shell_integration/icons/128x128/oC_sync.png deleted file mode 100644 index d3c5a1b7376..00000000000 Binary files a/shell_integration/icons/128x128/oC_sync.png and /dev/null differ diff --git a/shell_integration/icons/128x128/oC_sync_shared.png b/shell_integration/icons/128x128/oC_sync_shared.png deleted file mode 100644 index 72a49ffe16f..00000000000 Binary files a/shell_integration/icons/128x128/oC_sync_shared.png and /dev/null differ diff --git a/shell_integration/icons/128x128/oC_warn.png b/shell_integration/icons/128x128/oC_warn.png deleted file mode 100644 index cc2359db7f9..00000000000 Binary files a/shell_integration/icons/128x128/oC_warn.png and /dev/null differ diff --git a/shell_integration/icons/128x128/oC_warn_shared.png b/shell_integration/icons/128x128/oC_warn_shared.png deleted file mode 100644 index b4968b5fcb7..00000000000 Binary files a/shell_integration/icons/128x128/oC_warn_shared.png and /dev/null differ diff --git a/shell_integration/icons/16x16/oC_error.png b/shell_integration/icons/16x16/oC_error.png deleted file mode 100644 index 3c45db60fb1..00000000000 Binary files a/shell_integration/icons/16x16/oC_error.png and /dev/null differ diff --git a/shell_integration/icons/16x16/oC_error_shared.png b/shell_integration/icons/16x16/oC_error_shared.png deleted file mode 100644 index d1418881d5b..00000000000 Binary files a/shell_integration/icons/16x16/oC_error_shared.png and /dev/null differ diff --git a/shell_integration/icons/16x16/oC_ok.png b/shell_integration/icons/16x16/oC_ok.png deleted file mode 100644 index f8a687c8234..00000000000 Binary files a/shell_integration/icons/16x16/oC_ok.png and /dev/null differ diff --git a/shell_integration/icons/16x16/oC_ok_shared.png b/shell_integration/icons/16x16/oC_ok_shared.png deleted file mode 100644 index 98f4e9bb64b..00000000000 Binary files a/shell_integration/icons/16x16/oC_ok_shared.png and /dev/null differ diff --git a/shell_integration/icons/16x16/oC_sync.png b/shell_integration/icons/16x16/oC_sync.png deleted file mode 100644 index e9361ca70e7..00000000000 Binary files a/shell_integration/icons/16x16/oC_sync.png and /dev/null differ diff --git a/shell_integration/icons/16x16/oC_sync_shared.png b/shell_integration/icons/16x16/oC_sync_shared.png deleted file mode 100644 index 8464afc9828..00000000000 Binary files a/shell_integration/icons/16x16/oC_sync_shared.png and /dev/null differ diff --git a/shell_integration/icons/16x16/oC_warn.png b/shell_integration/icons/16x16/oC_warn.png deleted file mode 100644 index 990aa4bd229..00000000000 Binary files a/shell_integration/icons/16x16/oC_warn.png and /dev/null differ diff --git a/shell_integration/icons/16x16/oC_warn_shared.png b/shell_integration/icons/16x16/oC_warn_shared.png deleted file mode 100644 index 944690cc1d1..00000000000 Binary files a/shell_integration/icons/16x16/oC_warn_shared.png and /dev/null differ diff --git a/shell_integration/icons/256x256/oC_error.png b/shell_integration/icons/256x256/oC_error.png deleted file mode 100644 index 1db988c5904..00000000000 Binary files a/shell_integration/icons/256x256/oC_error.png and /dev/null differ diff --git a/shell_integration/icons/256x256/oC_error_shared.png b/shell_integration/icons/256x256/oC_error_shared.png deleted file mode 100644 index 884f8d995e5..00000000000 Binary files a/shell_integration/icons/256x256/oC_error_shared.png and /dev/null differ diff --git a/shell_integration/icons/256x256/oC_ok.png b/shell_integration/icons/256x256/oC_ok.png deleted file mode 100644 index e01f13e76f9..00000000000 Binary files a/shell_integration/icons/256x256/oC_ok.png and /dev/null differ diff --git a/shell_integration/icons/256x256/oC_ok_shared.png b/shell_integration/icons/256x256/oC_ok_shared.png deleted file mode 100644 index 112e132ad47..00000000000 Binary files a/shell_integration/icons/256x256/oC_ok_shared.png and /dev/null differ diff --git a/shell_integration/icons/256x256/oC_sync.png b/shell_integration/icons/256x256/oC_sync.png deleted file mode 100644 index 571e86247c5..00000000000 Binary files a/shell_integration/icons/256x256/oC_sync.png and /dev/null differ diff --git a/shell_integration/icons/256x256/oC_sync_shared.png b/shell_integration/icons/256x256/oC_sync_shared.png deleted file mode 100644 index 100eaa13920..00000000000 Binary files a/shell_integration/icons/256x256/oC_sync_shared.png and /dev/null differ diff --git a/shell_integration/icons/256x256/oC_warn.png b/shell_integration/icons/256x256/oC_warn.png deleted file mode 100644 index 1ea61c97379..00000000000 Binary files a/shell_integration/icons/256x256/oC_warn.png and /dev/null differ diff --git a/shell_integration/icons/256x256/oC_warn_shared.png b/shell_integration/icons/256x256/oC_warn_shared.png deleted file mode 100644 index 026ae9ce5d1..00000000000 Binary files a/shell_integration/icons/256x256/oC_warn_shared.png and /dev/null differ diff --git a/shell_integration/icons/32x32/oC_error.png b/shell_integration/icons/32x32/oC_error.png deleted file mode 100644 index 9541677c5fa..00000000000 Binary files a/shell_integration/icons/32x32/oC_error.png and /dev/null differ diff --git a/shell_integration/icons/32x32/oC_error_shared.png b/shell_integration/icons/32x32/oC_error_shared.png deleted file mode 100644 index 4c916621327..00000000000 Binary files a/shell_integration/icons/32x32/oC_error_shared.png and /dev/null differ diff --git a/shell_integration/icons/32x32/oC_ok.png b/shell_integration/icons/32x32/oC_ok.png deleted file mode 100644 index 5565e57c990..00000000000 Binary files a/shell_integration/icons/32x32/oC_ok.png and /dev/null differ diff --git a/shell_integration/icons/32x32/oC_ok_shared.png b/shell_integration/icons/32x32/oC_ok_shared.png deleted file mode 100644 index f2cc4b9e3ff..00000000000 Binary files a/shell_integration/icons/32x32/oC_ok_shared.png and /dev/null differ diff --git a/shell_integration/icons/32x32/oC_sync.png b/shell_integration/icons/32x32/oC_sync.png deleted file mode 100644 index 7066c546bbc..00000000000 Binary files a/shell_integration/icons/32x32/oC_sync.png and /dev/null differ diff --git a/shell_integration/icons/32x32/oC_sync_shared.png b/shell_integration/icons/32x32/oC_sync_shared.png deleted file mode 100644 index e3c5db599fb..00000000000 Binary files a/shell_integration/icons/32x32/oC_sync_shared.png and /dev/null differ diff --git a/shell_integration/icons/32x32/oC_warn.png b/shell_integration/icons/32x32/oC_warn.png deleted file mode 100644 index 8766b93c012..00000000000 Binary files a/shell_integration/icons/32x32/oC_warn.png and /dev/null differ diff --git a/shell_integration/icons/32x32/oC_warn_shared.png b/shell_integration/icons/32x32/oC_warn_shared.png deleted file mode 100644 index e25e383b6ae..00000000000 Binary files a/shell_integration/icons/32x32/oC_warn_shared.png and /dev/null differ diff --git a/shell_integration/icons/48x48/oC_error.png b/shell_integration/icons/48x48/oC_error.png deleted file mode 100644 index 17db62122b3..00000000000 Binary files a/shell_integration/icons/48x48/oC_error.png and /dev/null differ diff --git a/shell_integration/icons/48x48/oC_error_shared.png b/shell_integration/icons/48x48/oC_error_shared.png deleted file mode 100644 index 6f206429aac..00000000000 Binary files a/shell_integration/icons/48x48/oC_error_shared.png and /dev/null differ diff --git a/shell_integration/icons/48x48/oC_ok.png b/shell_integration/icons/48x48/oC_ok.png deleted file mode 100644 index cb87c7b2d1a..00000000000 Binary files a/shell_integration/icons/48x48/oC_ok.png and /dev/null differ diff --git a/shell_integration/icons/48x48/oC_ok_shared.png b/shell_integration/icons/48x48/oC_ok_shared.png deleted file mode 100644 index 27db6f8b592..00000000000 Binary files a/shell_integration/icons/48x48/oC_ok_shared.png and /dev/null differ diff --git a/shell_integration/icons/48x48/oC_sync.png b/shell_integration/icons/48x48/oC_sync.png deleted file mode 100644 index 60b5a5d4773..00000000000 Binary files a/shell_integration/icons/48x48/oC_sync.png and /dev/null differ diff --git a/shell_integration/icons/48x48/oC_sync_shared.png b/shell_integration/icons/48x48/oC_sync_shared.png deleted file mode 100644 index ab244bb0070..00000000000 Binary files a/shell_integration/icons/48x48/oC_sync_shared.png and /dev/null differ diff --git a/shell_integration/icons/48x48/oC_warn.png b/shell_integration/icons/48x48/oC_warn.png deleted file mode 100644 index 1f993f29dd3..00000000000 Binary files a/shell_integration/icons/48x48/oC_warn.png and /dev/null differ diff --git a/shell_integration/icons/48x48/oC_warn_shared.png b/shell_integration/icons/48x48/oC_warn_shared.png deleted file mode 100644 index 150b693aa68..00000000000 Binary files a/shell_integration/icons/48x48/oC_warn_shared.png and /dev/null differ diff --git a/shell_integration/icons/512x512/oC_error.png b/shell_integration/icons/512x512/oC_error.png deleted file mode 100644 index b8aade12a50..00000000000 Binary files a/shell_integration/icons/512x512/oC_error.png and /dev/null differ diff --git a/shell_integration/icons/512x512/oC_error_shared.png b/shell_integration/icons/512x512/oC_error_shared.png deleted file mode 100644 index 8dad568055a..00000000000 Binary files a/shell_integration/icons/512x512/oC_error_shared.png and /dev/null differ diff --git a/shell_integration/icons/512x512/oC_ok.png b/shell_integration/icons/512x512/oC_ok.png deleted file mode 100644 index 9b274525a48..00000000000 Binary files a/shell_integration/icons/512x512/oC_ok.png and /dev/null differ diff --git a/shell_integration/icons/512x512/oC_ok_shared.png b/shell_integration/icons/512x512/oC_ok_shared.png deleted file mode 100644 index e3008168de0..00000000000 Binary files a/shell_integration/icons/512x512/oC_ok_shared.png and /dev/null differ diff --git a/shell_integration/icons/512x512/oC_sync.png b/shell_integration/icons/512x512/oC_sync.png deleted file mode 100644 index 41db98155cd..00000000000 Binary files a/shell_integration/icons/512x512/oC_sync.png and /dev/null differ diff --git a/shell_integration/icons/512x512/oC_sync_shared.png b/shell_integration/icons/512x512/oC_sync_shared.png deleted file mode 100644 index 12e5eb7e170..00000000000 Binary files a/shell_integration/icons/512x512/oC_sync_shared.png and /dev/null differ diff --git a/shell_integration/icons/512x512/oC_warn.png b/shell_integration/icons/512x512/oC_warn.png deleted file mode 100644 index cd61c04d161..00000000000 Binary files a/shell_integration/icons/512x512/oC_warn.png and /dev/null differ diff --git a/shell_integration/icons/512x512/oC_warn_shared.png b/shell_integration/icons/512x512/oC_warn_shared.png deleted file mode 100644 index d5439c1300a..00000000000 Binary files a/shell_integration/icons/512x512/oC_warn_shared.png and /dev/null differ diff --git a/shell_integration/icons/64x64/oC_error.png b/shell_integration/icons/64x64/oC_error.png deleted file mode 100644 index e56df7c3d19..00000000000 Binary files a/shell_integration/icons/64x64/oC_error.png and /dev/null differ diff --git a/shell_integration/icons/64x64/oC_error_shared.png b/shell_integration/icons/64x64/oC_error_shared.png deleted file mode 100644 index b43af69ecb0..00000000000 Binary files a/shell_integration/icons/64x64/oC_error_shared.png and /dev/null differ diff --git a/shell_integration/icons/64x64/oC_ok.png b/shell_integration/icons/64x64/oC_ok.png deleted file mode 100644 index 0eb21dd6bf9..00000000000 Binary files a/shell_integration/icons/64x64/oC_ok.png and /dev/null differ diff --git a/shell_integration/icons/64x64/oC_ok_shared.png b/shell_integration/icons/64x64/oC_ok_shared.png deleted file mode 100644 index 80aafa2aefd..00000000000 Binary files a/shell_integration/icons/64x64/oC_ok_shared.png and /dev/null differ diff --git a/shell_integration/icons/64x64/oC_sync.png b/shell_integration/icons/64x64/oC_sync.png deleted file mode 100644 index faf65f003dc..00000000000 Binary files a/shell_integration/icons/64x64/oC_sync.png and /dev/null differ diff --git a/shell_integration/icons/64x64/oC_sync_shared.png b/shell_integration/icons/64x64/oC_sync_shared.png deleted file mode 100644 index a62d5a3e293..00000000000 Binary files a/shell_integration/icons/64x64/oC_sync_shared.png and /dev/null differ diff --git a/shell_integration/icons/64x64/oC_warn.png b/shell_integration/icons/64x64/oC_warn.png deleted file mode 100644 index 11aab672522..00000000000 Binary files a/shell_integration/icons/64x64/oC_warn.png and /dev/null differ diff --git a/shell_integration/icons/64x64/oC_warn_shared.png b/shell_integration/icons/64x64/oC_warn_shared.png deleted file mode 100644 index de3181a6c3a..00000000000 Binary files a/shell_integration/icons/64x64/oC_warn_shared.png and /dev/null differ diff --git a/shell_integration/icons/72x72/oC_error.png b/shell_integration/icons/72x72/oC_error.png deleted file mode 100644 index e5fd84c8535..00000000000 Binary files a/shell_integration/icons/72x72/oC_error.png and /dev/null differ diff --git a/shell_integration/icons/72x72/oC_error_shared.png b/shell_integration/icons/72x72/oC_error_shared.png deleted file mode 100644 index 381a638d28e..00000000000 Binary files a/shell_integration/icons/72x72/oC_error_shared.png and /dev/null differ diff --git a/shell_integration/icons/72x72/oC_ok.png b/shell_integration/icons/72x72/oC_ok.png deleted file mode 100644 index fe1e85ea372..00000000000 Binary files a/shell_integration/icons/72x72/oC_ok.png and /dev/null differ diff --git a/shell_integration/icons/72x72/oC_ok_shared.png b/shell_integration/icons/72x72/oC_ok_shared.png deleted file mode 100644 index d7342f393ef..00000000000 Binary files a/shell_integration/icons/72x72/oC_ok_shared.png and /dev/null differ diff --git a/shell_integration/icons/72x72/oC_sync.png b/shell_integration/icons/72x72/oC_sync.png deleted file mode 100644 index 6fd229e5648..00000000000 Binary files a/shell_integration/icons/72x72/oC_sync.png and /dev/null differ diff --git a/shell_integration/icons/72x72/oC_sync_shared.png b/shell_integration/icons/72x72/oC_sync_shared.png deleted file mode 100644 index 3794be7f4c2..00000000000 Binary files a/shell_integration/icons/72x72/oC_sync_shared.png and /dev/null differ diff --git a/shell_integration/icons/72x72/oC_warn.png b/shell_integration/icons/72x72/oC_warn.png deleted file mode 100644 index 93fe8ce3348..00000000000 Binary files a/shell_integration/icons/72x72/oC_warn.png and /dev/null differ diff --git a/shell_integration/icons/72x72/oC_warn_shared.png b/shell_integration/icons/72x72/oC_warn_shared.png deleted file mode 100644 index 882740a7ca3..00000000000 Binary files a/shell_integration/icons/72x72/oC_warn_shared.png and /dev/null differ diff --git a/shell_integration/icons/CMakeLists.txt b/shell_integration/icons/CMakeLists.txt deleted file mode 100644 index 86f66da5cf4..00000000000 --- a/shell_integration/icons/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -if( UNIX AND NOT APPLE ) - - SET(ICON_DIR ${DATA_INSTALL_DIR}/icons/hicolor) - - FOREACH(size 128x128 16x16 256x256 32x32 48x48 64x64 72x72) - file(GLOB files "${size}/*.png") - FOREACH( file ${files} ) - # the GLOB returns a absolute path. Make it relative by replacing the current src dir by nothing - STRING(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/${size}/" "" shortFile ${file}) - STRING(REPLACE "oC" ${APPLICATION_SHORTNAME} brandedName ${shortFile}) - install(FILES ${file} DESTINATION ${ICON_DIR}/${size}/apps RENAME ${brandedName}) - ENDFOREACH(file) - ENDFOREACH(size) - -endif() diff --git a/shell_integration/icons/SVG/Error.svg b/shell_integration/icons/SVG/Error.svg deleted file mode 100644 index c258d3a5f7c..00000000000 --- a/shell_integration/icons/SVG/Error.svg +++ /dev/null @@ -1,3824 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eJzsvemS3ciVJvgEeIc7P8pMaitGweHuWDRjZXbXKs1IKplStU1NWxrFDGWyi0mmkUyppZeZh5kX -m/Odzd1xEQuToW5JHeEW5A1cwAH4cpbvbH/zv/3yixf7r9795vZFvOl33d/8zfH97cuP797/ZMdH -dz998+b7Dx/f49CPfvXjXRhvejpp/9P5Sz3xX27ff3j97u1P+Cv+8oKrf7R/9c3rb3+8+9GP6civ -X398c0vH/vXl+7ev33598+F3X//YbkXXnl5+pG97un68CWkX0k+GBV+/fPu7lx8+vP7jLTqPc6Rj -h3ffv/2Kuji8++90MMfdixDDLix592LGNf/4+le3H9Yn3YQh48T+JoyZTk78P12AL+ii07tX3397 -+/bjL9+/e3X74cPx3Zt37z/8ZHf8w8u3u5+//Jq+ebn799s3b979fnd48/LVf3b0/vnLy+s3t/Sq -3778uAsDXnz/0zB8efj+9ZuvfvH9t7+5pUEY5wGH45fc5T9/oL6oW3zG4enLn35LR764/fiRHpZu -iMH71T8c6segg9x+9B+/pKG4pbu9/8/bt//1x9rt+3ff4QhdOyZ6w2Gmtxt2vXz769tvv3tDQyvD -MMw3mU+h/+o/9Fx6GT7vRcgB36WUdqmf6GOMi5xThun2d69vf/+T3S/evb2Vsdi///iFzFNKfS// -yje/+v7N7ft/fvv6I54RhxYZjJ+/++r2DZ3v11/evOQx4BbKv3LCr1++//r2I83tuzfff+TlNtsd -aLB/9vIPt5ixIDf4p+9u3/763b/wM74Y4nQzjjTfU5D/427I/UJjkOkOM98kDMNuCNlvbq2350OP -6M9uNNFM/ZLm7p/ev/769duf6GNOX/7D+9dflfmctfF73MzV72K/8sD07h8/3r7VF6BVdPx5tSr6 -m59/Qfc7v/3q+O5bTMAHbIRbujGtlDfvvpbv/DN/Q5d//508O//9Jc3VL9+/fos+u1/wN/OXv3zz -PX31D+/fff/dT9/+9l33I9ny/3L7ivY1TedXu3/6zX+jP2gX/+Llq2++vv3j7etX37y9/Xj7YXeg -ZU7H/dybl6+/+/G93dJLvr/dyZd0Jf9p/z989en2t7TXyuVy9B//H5reN29u3774v7//8PLjH799 -99X3b+jb89vf3b55993t7uXbr3ZEdb57+Aa/fPPy7cv3Oz5u/de97n769uPt+5f/+fH173bfvqRb -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 - - - EvPNkp8s5kV7+/yQlznf0OgNTxTyYr1pyMs0cSmBOYDcIU8XVhpfnEit4r69ophX6+ISA/eGvDxl -t59nUVo2LUrLj3kJqUWpQojqAnQ1OlhK2hrMcqk+Nc3Nq6qe2v+lGSppuL2Zi2WTm1W5sjt3/tF0 -CDNRT6tWIMvFM8wulS1cM852mniW210cW9D/wqPs/22fz5VjZ6fI/t65kvzr4J/r3ueWAV2xHGUw -nWdmXOfc2zettc6crkJOzCQzdFrKkvmKSLLFq6CGmL1ukVPhwfW2Y5VYf2InpGTJ9KdLx7n0i34m -nlCxcnsE/QX1xU/S4kVgj6V8kTBALWHUcdp7qWGUmc72WsUI5HXkXPaBqxhJ6vqFeBEUZilidNQc -5RnlEsx5Gr7OydVhBgqKsam28VXSRpWR2WDG4oZueLDqXNO+U0Pj3lepgeaTlhQYVWvN6imWVH+N -qsOKHhtEl0WESK/gq6GaJ1VtpUkwxt6rEJhv2ax3Gt3HTCsTdOpsxuqvVLANV63fbuvEmmtA22Bt -01Hbdrxq65/9NtzdbWHdlapb2ti0vGru7dZVOnG01Ndm623TX+eN9NdHhWRbULaBZbtrXLaJ0VtH -6Rkwu06D6kF63SYqu52M+FHpiA165YpEqyI+qMoivg5jmIlViRAgCS5JCMi0umLJfanJOacbopep -Tv34mf0w12ZHDpwUSF+Jhe1LTcibydJv0uSQJMAeH9OG/8jndfNZXHnYLHk3hCYQVY0VibHwXqGo -uqyFFbUIdUknKxbFyFGs6lqcJJ/zwvmcBe8T/KeUnz6tUL7aV23tpRZhS2gM7YpzCN5Rua41WerN -c61wmspvrXO3tcOG61rtvjbd6bNWe62l7hEOa1v+aMVUveH0dN5sp3vadW1fb919XzZW2Ue2buMQ -U/RRYx/EMVq8osdV4APCHhD0gJCHExMqSeMtZQnPXJRwYj4fwSZ7YnfC1SYuiNNzauEjqhFy2Rvi -56rsmno7MSJjCm1wNeqcjl2lxE6qS5ny6qor4J9WaW10Vqlmo7pqZ473jbK6ryrazFVFm8x6amKZ -qJfqJlXqdEmX7TLWrMWipLVVnQpKoOhBhSwU4EFrJpUALWvr+KC7Mpiv2x31Rdrc1Z/8syEWW37s -yNYVjqGlGyXPkCK4HsTKqpwAO5ZZMYG6kIA6lXUeQWs+ZXteDoLonXMpJWDxtMD0xInsXDmPAcqD -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 -HJVu/fuwQ8leGoOYw3GHbdb9WkezDpfhnl5kEzEEezQhCVCHmo9PZau203zNe0Pe29Zf04bjcFzS -PmTgwnFcH9qjWXwghta+wO4x4P6R9n3OM+Arqqkmgnv2yfOwmBT9SjF55jv6HMsHy2mYD5/TdXrJ -mNU5Fjnvc7z6+hjHNddRjn9fa/GNXmsyrI3vcDjWL/7tT7799j/fWumv/vpnv/nN17/+5Vd/9Q9f -/c3Xv/r6Z7/5+udoqEkjdciN77gePvO9s0gljhf9y59/85tvf/3VX/zjb779X3/1s4/f/PLvv/r3 -duXti3/7V7cmex/717/++rfffP27r/7m29/9gx3qTXznd17+b7+5lem///qbv/9P/th/xOP8b36P -C9Krffv99/jfv/n5b/7Tv3Gf7DWCw9X/3a+//d33Hfc/fPOLrz58+6tvvv6Hr/7nr//p+47+6df/ -8TdfffPLr/7HX3/7y99838F/++2vHsd+73B/+PY/+3C37zqU6fGbb/jsX/3tP/3qa7/w8p1Dxgv+ -9de//njvWP0Nv/Pq/O9n//c9yPeY/NNXP/32Yz/nMQv/8kf/zV/8Vf3qL3/5c7+E/f7J13//zS9d -8t9/8d/+9be/+NXPfv7tP/763/xo+LcKOVb499azemgJ0x6JuDhvifbKv9B//4/f/chgZmIvvUX/ -pJ//0/3P/+cW/u6LdfniP3zxf/5fyxc/t1P+Jtyu8ZqOK/W40xc/HaX5RD8dr/Bp6XSFX/7Bi9zH -+ic/+/j/Mvk1qW+bZ17tvO9/Zy/9v8hMqMutpy4ZF7eNhZVxHFe7laW4Ybdr1Z9uK4F/1PjHbT1Q -02NI3tQR7zSO7daF8AsX3oq1C5s5lAeCtpdLgm29N7Vrl0fw8UdGy3qC5kgdtPjPqXdb1aBEL531 -hUDop/BntSI6nUnlJK0LK2FIsQ4iaZdJpLPtGAWkyf35eZT0UXxLraBKsEC4rqcdZRD9eDHrYVdq -0eVHOdK9qezafkWzAEHmVdSasBvKNMF6NmiaG9dgL6QbjV5hriquTjjvK6hFgHqvtvVe+2U2R2YA -ZHdgY2ubUssvZsGuXekwd8aYesGcuT0dkTjzpMIorETPkcIxqt2rbMZ9c+4ewhQ2JhK5bHHmVg47 -08GnRDJcJFG7oO2E1e7pZYhse/uqat/NSgfZ4NZdaTXsKTUF1mUr8VtnQYfJjIhjxBWlOuXFAGAH -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 - - - 3BuCs368jSH4s09/Nw5f/vr6RTcIQYqgfPmHm+s/tSZ4J+CRkD/DxWjrfzj4w7dsI8ifPv/X1Vc3 -lwc//+z0+sUvDobDT9v///b9wev2z3D454PhOAfv3Hg4HKdQxlLwg6uD8+2HMvo4hPbDOPrs4sYP -QyzFHf4NzbWX+c/f/tV++e/th/9qD7Snvj8Mh78//F//ezh8gY/++eCoaUjO+3B45MfjUnM8vDoI -xyUPaSSpplgO43HNfmzPjOV4LC4dluPsXW1PjKl1NoyHZwdjez2n2Eix9SSFQxeO84iX3Hg81tZn -l49LrJVPxBhze8ljiCPaGY5jLuNh+0BJvrRPt3GN3pMQkmu9H/zgsseXGsmVdOiPhdIaCUPKh60H -Y2g9aN9JIR62r9QQ8ELrSUnte67NbB7aX9yxy7kcjuG4jc4d1ta/mtvH07HPrYNnB+nY+ZIzvj74 -4A4xpFjbJ9uEhOoOjzCZyYdyyFkrob1z1EY1uOpJCu3ho9ZKSa3vY25vte8c1eNhGELFh0IeMAFH -rS/DGEf0pXBGMW1lKIdt1oahdfII0+YwPMxaSnipjZjr0WbNxyzrUqofD12bcoyIhDY56GhMQwzy -rbZsY/GY3NQ+2nrYGvK+YEna11LjD37Nj7KMeBPvuXjcet3eaxMYQ219ar1uHIofWm9dSBjamPxQ -yRGh8Srea9PpXZTeDLmx7xEXfizCbJ7zOOO+s4NXBz/7skmiuw15+7MvAdsaMPlrXozPPvsUuOE/ -39z1ogDtwc/Wnvj8m9Nvzz9rB/aLP53effO7X+kjn65WWx/6/Sn0wZvPzl/e3J7rs//jjc39RWXO -z75sIuLLg/HY18YBHEtwjTeuGh+ltjllJ405lcN0XP2ACWg8EdvGbbzX1otz1Nh1SORv12bJOU6u -7KQ2ba7gpdo4vTZGLo31uf38ca7tL9xJue2/w6O2xm2zJrBuTmilMVjKbUs1Aj512FbapZJkI/nS -dkho22Fs/XX12KfWudaBlNqudG0Bh9GDf10KeMP5xvdtuRw2e67Ycm6MHrw5usZHTTyMoa1iY4IU -KnZF+0PApEByjE0IYNOW1nabkFwgY9qOiikGEELg+I8y5qoxLyatLflhI7gxYEy5vZ3JbcW3ruG7 -KVanGynlxvptSkrCFsCsUQ7UY9lYpQ2zcTEeIKOB+9ujufU6JQhV7ho224QaeBiEtr/ww3FIEHyy -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 -fv555371K2ed8cXPxxamJ4Z6O8KBKp/Dasw/+bij1hdZrEwQ0a1HH39KYQnpqQw0dw2MTS+unn7m -Oedf+N2LL/3R5Vf85MqrrrrqpwDA/135kysu/9GlF3/3wvPPOfP01cXpsYGu5kClhywpPOX4o7fS -yK6DgvEQPeFUg9lRWtvY0T8+u7Ljy1/9xncuuezKn//y2t9cd/0NN9508803/wEA+L+bbrzh+ut+ -c+0vf37lZZd85xtf/fKOldnx/o7G2lKH2XDqCTxkM4/oAQdC1h9/qsHiLPc394xML+8467xvff+y -q67Zed2Nt9x2x1333Hvf/Q888MCDCMCP+++795677rjtlhuv23nNVZd9/1vnnbVjeXqkp9lf7rQY -Tj0eisGBB2QYWYgpME9bAKKnQEQDLZHxubUzvvatS674xbXX3fzHO++5/6GHH3n08SeefOppAM8A -gP//1JNPPP7oIw8/dP89d/7x5uuu/cUVl3zra2eszY1HWgIQ2VMAsluA6cokrpj3hx627biTC82O -8kBr38TiF87+xkWX/3zn9bfefs8Df3nksSefef7Fl1/5x65XX311Nw3g565/vPLyi88/8+Rjj/zl -gXtuv/X6nT+//KJvnP2FxYm+1kC5w1x48nHbDjsUS0FmEMUk1W095qT8EqrM3wIQ3fGVb15y5X9c -d8sdf37ob4899eyLL+/a/fo/33xrz563AbyDAP7as+etN//5+u5dL7/47FOP/e2hP99xy3X/ceUl -3/zKDoBsi7+MKsk/6ZituowRFmD6bwccdOjhRx2fV0z4apsjENELL/3pzhtvu/vBvz3+zAsv73rt -n2/ueefd997/4IMPEXwEAP/64IP333v3nT1v/vO1XS+/8Mzjf3vw7ttu3PnTSy+EyEaaa31Ecd7x -Rx1+6EEH/FsGcEXMP3gLIGmhxVXV2D02/wWA6M9+fdPt9/7l0aeff+XV1wGaAMsPP/r4X//61yci -AEUff/QhwBeg+/qrrzz/9KN/uff2m379M4DsF+bHuhurXJZCQNgtB2dACCCmBx5y2Lbj84xkWbB9 -aPbzZ38TIHrzHfc9/PizL+16/c23333/A4jlJ59++ul/SwAo/gTi+8H777795uu7Xnr28Yfvu+Nm -gOw3z/787FB7sIw05h2/7bBDkBCkiekBBwLmn1hgdlWHI9HYl79+yVU7b7rjvr8+8dzLu9/Y8877 -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 - - - diff --git a/shell_integration/icons/SVG/Error_Shared.svg b/shell_integration/icons/SVG/Error_Shared.svg deleted file mode 100644 index 3e272205003..00000000000 --- a/shell_integration/icons/SVG/Error_Shared.svg +++ /dev/null @@ -1,4769 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eJzsvemSHcl1JvgE8Q53fsiMbBOS4eHusXDG2uyuErtFisaiKGk0bWUgKlmFFgooA1Bkky8zDzMv -Nuc7m7vHjVxQSHaL7Ey3BG7GjfCI8OUs39n+5v/45Rcv9l+9++3ti3jT77q/+Zvj+9uXH9+9/+mO -j+5+9ubN9x8+vsehH/3qx7sw3vR00v5n85d64m9u3394/e7tT/kr/vKCq3+0f/XN629/vPvRj+nI -r19/fHNLx/7xv375xTcv399+dfPh91//2G5GV59efqTve+phvAlpF9JPhxlfv3z7+5cfPrz+0y26 -j3OkY4d337/96vXbrw/v/gcdDNPuRYhhF5a8ezEv9P3fv/7V7Yf1STc5zyPO7G/CmOnsdBPCPOCS -mzBkuuz07tX3396+/fjL9+9e3X74cHz35t37Dz/dHf/48u3u5y+/pm9e7v719s2bd3/YHd68fPXv -HY1B/vLy+s0tve63Lz/uwoCX3/8sDF8evn/95qtffP/tb29pIMZ5wOH4JXf5Tx+oL+oWn3F4+vJn -39KRL24/fqTHpRtiAH/1d4f6Meggtx/92y9pMG7pbu///fbtf/uxdvv+3Xc4QteOiV5xmOn1hl0v -3/769tvv3tDgykAM803mU+i/+g89l16Gz3sRcsB3KaVd6mnwdjEuck4Zptvfv779w093v3j39lbG -Yv/+4xcyUyn1vfwr3/zq+ze37//p7euPeEYcWmQwfv7uq9s3dL5ff3nzkseAWyj/ygm/fvn+69uP -NLvv3nz/kZfcbHegwf6Hl3+8xYwFucE/fnf79tfvfsPP+GKI08040sRPQf6PuyH3C41BpjvMfJMw -DLshZL95KP/KPdAj+rMbTTRTv6S5+8f3r79+/fan+pjTl3/3/vVXZT5nbfweN3P1u9ivPDC9+8eP -t2/1BWgVHX9erYr+5udf0P3Ob786vvsWE/ABW+GWbkwr5c27r+U7/8zf0OXffyfPzn9/SXP1y/ev -36LP7hf8zfzlL998T1/93ft333/3s7e/e9f9SLb9b25f0d6m6fxq94+//e/0B+3kX7x89c3Xt3+6 -ff3qm7e3H28/7A60zOm4n3vz8vV3P763W3rJ97c7+ZKu5D/t/4evPt3+jvZauVyO/v3/Q9P75s3t -2xf/9/cfXn7807fvvvr+DX17fvv72zfvvrvdvXz71e6fX77/7uEb/PLNy7cv3+/4uPVf97r72duP -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 -etOY1sNbaXPs4Y+j0q1/H3Yo2UtjEHM47rDNul/raNbhMtzTi2wihmCPJiQB6lDz8als1Xaar3lv -yHvb+mvacByOS9qHDFw4jutDezSLD8TQ2hfYPQbcP9K+z3kGfEU11URwzz55HhaTol8pJs98R59j -+WA5DfPhc7pOLxmzOsci532OV18f47jmOsrx72stvtFrTYa18R0Ox/rFv/3Jt9/+51sr/dVf/+w3 -v/n617/86q/+4au/+fpXX//sN1//HA01aaQOufEd18NnvncWqcTxon/5829+8+2vv/qLf/zNt//r -r3728Ztf/v1X/96uvH3xb//q1mTvY//611//9puvf/fV33z7u3+wQ72J7/zOy//tN7cy/fdff/P3 -/8kf+494nP/N73FBerVvv/8e//s3P//Nf/o37pO9RnC4+r/79be/+77j/odvfvHVh29/9c3X//DV -//z1P33f0T/9+j/+5qtvfvnV//jrb3/5m+87+G+//dXj2O8d7g/f/mcf7vZdhzI9fvMNn/2rv/2n -X33tF16+c8h4wb/++tcf7x2rv+F3Xp3//ez/vgf5HpN/+uqn337s5zxm4V/+6L/5i7+qX/3lL3/u -l7DfP/n677/5pUv++y/+27/+9he/+tnPv/3HX/+bHw3/ViHHCv/eelYPLWHaIxEX5y3RXvkX+u// -8bsfGcxM7KW36J/083+6//n/3MLffbEuX/yHL/7P/2v54ud2yt+E2zVe03GlHnf64qejNJ/op+MV -Pi2drvDLP3iR+1j/5Gcf/18mvyb1bfPMq533/e/spf8XmQl1ufXUJePitrGwMo7jareyFDfsdq36 -020l8I8a/7itB2p6DMmbOuKdxrHduhB+4cJbsXZhM4fyQND2ckmwrfemdu3yCD7+yGhZT9AcqYMW -/zn1bqsalOils74QCP0U/qxWRKczqZykdWElDCnWQSTtMol0th2jgDS5Pz+Pkj6Kb6kVVAkWCNf1 -tKMMoh8vZj3sSi26/ChHujeVXduvaBYgyLyKWhN2Q5kmWM8GTXPjGuyFdKPRK8xVxdUJ530FtQhQ -79W23mu/zObIDIDsDmxsbVNq+cUs2LUrHebOGFMvmDO3pyMSZ55UGIWV6DlSOEa1e5XNuG/O3UOY -wsZEIpctztzKYWc6+JRIhoskahe0nbDaPb0MkW1vX1Xtu1npIBvcuiuthj2lpsC6bCV+6yzoMJkR -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 -t06GC8Oncv7S1YU765cHrtGT0dLBox/XF+7qcnu5c+9+e6XZvH4wNtT63msYL1tZu3o7e/NwtPLp -3ZJ+M3dr8bi/Plx8vD06vHk/vXK6tqK/f7n0qXv95aWrK/vJk92JnX2fFfvZix/f/rS4s9x8cfag -Ybvb7xbuHq29W7g9bB7fXLl19XRt5uHe/qWrhKy1j8CTV/bfvcyWugvdV7f3lw5Hy4fZGxGh4+O1 -lbZY/3nh7oPrL7kfmPJw+f3B+z78dO3nlcd7jy8vzeWfvl/cnrvS4zm8au2dXrpafJq52V5tmx9n -lg/1hzt3F6+qazeXfrj18ebC8vXna8vl6Y37L55cObzTbreO8KfOzdX99UMeWSTzrWzQ+f7j7c77 -J3tL3asPrs8Nbv50uri+fe1nnP/swp0nh+rS1fTOi3cLi7329eOb957dmc+Of7rXybL54b5aHLQf -i5tHt4Xvsb3yZPgC0JZdL7OXKtm73Vmeb8H+imf3ZuZulUvdbPOYV/B6/erC8uO7V16u3irMEPbl -8dv0yv1suf/+5t0Xe29vy90r76jb+72rsKD76Y0ruCVv05fpjz3E0/2lo9l0zpLmi731RLy78mxl -vnX32trlm28GOEqKf3hPvVCTS1eT3e8ea/r55v21u/anuy9Xn3Lz5VurH7kz+Vo+BtJ9ldy8f3/1 -llx5cHDP9vPy3t07e59+eE876ScM/W0sGTsKNFp64ifwrpqAmLm3hY1KTTBzeWnlA6EaJOADnb5J -P7UXd1Y+3VzZn3/682qrdf37pXT3+Y/F5tVXzxc3lpc2V/a3Oz8v/Pzu9sGlq0v69c4HRuabdO/N -6gdx88WSfrW4sbby6eWH5c6ndP7O/vHVg7Xl/RsCEHj/Y5Zt7fWr8fLtn4+fLm6s33i6tjK795Rx -4xDNtA+7Pzpp/njzwYvWz7yg+2neWrizM/pucefJ6HR8aTXMBnhwG/FqcNl1tQ0nZ2N5dOnq6pu9 -qwfy490HK8naTwuKSODux7trK0AdN27dXOoX7+t7FWM23Fi3EUw5Dw6Hp4QlWEuIpyfrDxc/PBFE -MXdvntz5uDaz/7S5mNzdeSNnv39/lycSoyM93SrKtZlrJ7PLh+nW0erN9eZaRalwAF73kcNsr+4h -hS7AoTq+Bku7Mrt8cLg6zO60n28tZm/ky/oebD7qvoj6/u7h6tyt3WLSlhRH5dPlS1cXd57t3QQO -c79YWVp/fTRpttQyaPfwdbYPh2ZVJvKhfjZOOaPNtdnN7p21FfNG3rz/8OPcpavVumBV7f211RWT -LaVzGy+I4TTFoxe3aNCV+Y8nN5Y+jfaOl7q9F/3FncNX30MXT2/6Dk5Wb/V/kGvX5rK3i1v7h9fh -s3fXltKb64fMLWdX9q8/SmG2/UPmgqsvXj1yLBwGePpuR60tHNx7hQz+48quPny5+Pxqexi3u7a4 -tfO2m3/qzt0ljlYJAhil+nv/SXJ36WjmpLN296GYCXj7m60brRAnIEsCZi2S3dPVmXs/v6wkTe2v -QMkzo+wAplduj/09/3jzzuPhPnDs9hWzJE67bxe3Pz5Ztn8tFj8u3H203IQmH18AF1i/tiTenH5Y -3D7d0dVfqTHwMQAcLwzat6/xboXnc/713Qcby70se9q6kjwZvV1Ins4cr8I/e0vJU7G3yD89wZS6 -T2de3QdYayGEiYMl+gJGCVu27IdPZw6W3Yef4KfZYpO/sV+7Abgf7uxkDdo92IKfPq1wE9v4hEfx -08NGYT80yvFKvYtg8v6LJ0d7K3Y2PCWcHPUCc8BReJnUI05qdmmbYbYzWhWNgl/XUeSnXB90/a7F -DfRHa3n7IPj6yWjjLjR6/gD+ebsYopKGn3m/4LB498cABYzPN9d/cL28X6wwC6PEGzV5az93I2rb -4EeRlx86ZMFEbfO39x2eInRwu+pXpheiHBx5fA5nrOUiW0Ir4J9cj/av0a9IFrgvZxDGFLJ4c3k9 -XFVFKuPIoj/gKJOQxWslTDh0BGvGxhORSrQRrNki0GOsvmr4xo7nu4hnExxSXu7b+9Gg2B8P+uo+ -nZdqt+JdZdxhj9wcMDHpqNgDiZ/hAQiJlIb6xPuCf/9cVJ9LL4TyV7y0isb8xlff8DE8WP5DZEhb -YpdLGKOTjGPPphvuJ1oQLoNXev2nWPW7sbjz4mlvbeF6uQOa/r4MBUax2ANZebC+9vHZ69trl280 -QYol5e7t3En7ue9Bc1k5Wnh55fnBcuf9h4XV9tHP11iUBeqEGW2ebc6EasfNl2TOgC0WGzQ00ZWD -udkHrPptPp79qZJxkbWVXFl4cX2mWD483Xqz8vTpz0vhgpY+lItbvZWNhe33g13QYLsLxyIe5SbZ -Drd/SLof7s59XOvX/zr/3exbMEJfnKy2b+3ML6W35FxgN4ql3YOV8sbqo0q3Qk3pyWlRIcZqYT/l -G4HpFioRYJqibVt01/r7rJ6NdfBKw2dz/flnWz8t4rxAUwJt3bz5M8bJBUwT1JT+pHFyAdPEan2e -QudwaWwyR7bv+6PKQsm7zcEjuwfPrx5Z/ejVkyVSYys8VV39d1kWZEg7e+rRTytPl9LrMMDs7tKP -C29vLP149d2I1OHkyUORTcBTaIsQ2d9PnzZXHn/KWhXawLJAxFn0L/YeZS+66zuLG8+fArnD6WTi -rCZyllHB+Lyfpjf8AXi41E0P3pL2f+lqpf+PT7TcWHl4dLWE4/XjXXRqtOGnW6OHJ9/tFsXNO/qH -uNv1M4yUu9eZwzxc3O4u3ISvD34W7a0HG3Lh+eXCWqo7N0pA1pE+x+iXb46uLB/+dPk2TPjDB7f7 -75qw3Cac/aWjZzfBENnc1/6A3EbD4P3Skbx3ufqDJ/bk9t5xN8FtehLZyOnS2t3Vn5q+l2zt/bsb -V9ZWe6dwKuXV4jhnyrr1/fzbPGuvP1n6cXNhU86+Pv7BTvT09dXFH3/YfoG2tvvDy0/K4rh/LcmO -Tnc3k/WTZvfhcKMc1Ea2p7I29pce+dJVT5JrC4PB4XN9+9nLBzTAneTBnY9o3S0n+w== - - - xfLjoO+Nk6VHS/p0KRG3ng8vR3vw9v1icefpnP/Djv652VkkW2z11sLguyeP1m7M3tLArl6CfXNr -rwfSaWtt7cPs5UNcqVq4u/u+t/Dyx5vXbh/e3O6A8Cs+EZ0H3HnMgfFobuXJ4MZHprFxirm8ep9b -+vMbdXax0yvTxWskAdEPc975VZXTcfrpDSaCpxdMwf7+cmfYSdGrMPcW9IfVn5drPV7Ayg+6/fEy -SG53epV4uPy0S01ui/zyD3D2Z19vjhafvhZ7vI33nv6wt7J3XNxyxDfzHdjpD5KV5UdXWBjN9S/f -b7IjgL1nL++Z2+XHa6XF06PN+aVuc0tAp9s9UjsuXeURf3iev1v98P7qW5j8weWFn5fez3iJdejp -Drhu+v1VwETxGGXOweru/JtOrMMQx35LtruV1+sHoCllL5+dbMeym1tmd9Vqufp+Y/cD6CZ3kOHs -z662byY/LeqZ4kdgfy9z1gXs8OrKwt0Hz6+tPD7uvF0yL141F3+8sXmwuLVzcw3Oy9WH77KlI7E3 -CFUVFij35lcev12/gcrGFjUB7nx3e9Lk851r32+vfbhy7fXKPuBrqZt/f2vlyczsDCk3/uyTJnFw -9Qhoovjuzv7324cTBtUgRLeGqzdvpPuheMO9Ovmw9H7hxfejkR95Hbb44HI6Wl8Di2/J7DTnoj9d -v5w8fvvwru3Cy1fy0D77tL9/u3P9x2eg0t1aXFt5/ejGpKVF7czNS1fvvOu//Wlaoxc3sh/adzZq -Te6U72ePyI92+4eDO53VW/03AjijEYvbZu3dw+PRjevY7pPjMGNUdGUIsuTZISsyD9JXD5d/WH7x -oEYb9vZicPktqM0/b8EmL/wcaIe2q6QJ6H15cukqXYBM2IMmnM+naXpv7edHd1e666ZSgu0mvr9y -eWV/+2N/ufPd9wYIW28s9u4/7k+iIdwXJLqNfvukeLY/LxY3bq8+XbizdBJq3nZmd2cWBnPFKfLk -hcXs3d4PK/vFx/nVN+XoWg2fxafB9R2Q++9fL/YPN24AxmJi8eoS/fpTNXmY1IPHAZXcv5eqSQRS -HC2u7ixmxRapjR9W9n8cgkRmVmFn++jK2vLiu4O15YWDrUff3xOPF4v5o8XJTRZvfFp4sfO4tXZ3 -ZRTMBjjxle8Wbr8sDxbyk8EH8kuOj7IqZ2/QXgEVbR5edKWs5sW93H3YfXZ883728BHIl5/f3Hnt -N0oBo1wdopv+R6COT9cWXuzvnAZ930+fnK40t/bmFm7/8OkoUM7hn530u7Xesr168cf150W5Drt/ -DNIvQv/17vxh+f6jQ3WpQ3Q82jhdefR+OFy5tbu2enf2+6vPgRZf/7x0vHHfjNPLj9+B+Dp6g5rS -HTEDfE5fBVVtZ35lf2cAxl6yfxycwMfDdx8fXl27/HRhcOX9aGW/P2hOINgCpObBLN7tvFpb3n+g -quNj7RdUDjJ0xLKKtfb9q0+dRJqjw5WH96+WoGIUKftg2y/eDH23d9ZmHu+9ROPyJ+QRr1bKu+/R -VDi9WRmA7pZH3ZiZzc3gzkvQih5+WtwQSwrM7AdHC3fvn1wJz6JTg+CnvfkAi8nw1pv0/nO9umjm -nv6IjEKMj0KNaKLv1+TORJn03dp6b7VtejDyXHN2ArKuL2bbz9b+f/a+A86N4vr/l18C2GDZ4I6x -zenudCed2qpL10/1eu+9+nqT7iCEBBt+hlBDgNASisGhBkiIqaFXU03HFFMMBhsbbNNDkv/v95+Z -XUm7q93Vqtiy7Pny4axb7U15896bN2/mzQNj0FJdNFm0Rs5dC6B8mZWvCDmwnojB7AqlAcyk9RlS -UZLKYHtyXPgYP+B8J2aAENdPFBH6AncNYKpuwtXX4jU5C4unpv0vqyahQWcmGcnTUjiGNg4p60KZ -V9paSqMOmHU67GOWwnS0lKA2Ms1wc8GSNQv1RW22ek2uN9BTuguiolhB2MfGprpZC/ggj9Gm7V5X -L5GbSbfgqRLN5BdFYz0DcFciDW7WEEW1k+UzTlXVdL/fUhqdDbSaPr+gWjJcfanmDrCSkXrdLmtt -MdvYmC6uLUcL5Zw14xkZYNk+Yg7aQhwWQAXQ2C4NGmxQi4BNkdPkUlZlKgEBO9VC7+VXADEsSwc1 -uzVcNUtkgbqbCnPH3Aq6Dc4urI5f7qAITzsqXbZuNKEo832ttqByBNoSdilPXqNogSuUSu1wWnYu -ZdD6X3J3jWnNQEeaxwotzYOuIqvHNwO3vtVueam60aFrGPflDGRnjIM+1yroNedPjyMrEurkgr4B -pracLbL2Tg3SpgSkcFtreoqBrTdJ5KyZHfCyVe9kllNL+4vAxjBiGonMPK2z1QF7u87tLhwETF43 -W98fnn/NaNGL6ADXARWAfZr0wB7JrSiqUxWCcRnMmqDXgpjT1Teb2lVkVWtSoUFoZrEXmI4mBuzG -0f5+c2NDR6PVUuAodgy19afCFVZqYW6NodNZVpabGtgsZv8ZsMfcPRmuwpyOBmB2WmqUhF413Qe0 -fOsomHPXTBXVjpmaGEq4p7nQp2hOdZaVGgDbp9mnuBSqf9e+utVS3zU7IZGBNx15zkGTOTVXNds+ -bM6fznKBVVDWtDvX3UdxY0A/BVSTXydResxHLSqqM5R5hZZUJzo7ABrS4bDU9U/mAmt8Kr1THXRu -Zvf399Vn63uyqu1jal29p7m1eQKMy4w96M0jX6ns7J11d3fMDsHTFplobaSeUNUb0UDQB5uAOYbI -0VijVTeBgW+QF030DvoCuo1RYqd+HM2BtDMWjGIbgfHaOQsI2CIDaq1mNvgtyWNNRfUNlvog7Wyj -hSPloOfjTQyXJaRydxOYoqYIXxGRWzlF84kiYtkzc+udWcPavlxVS5m9yKoqgeVlDIO+IFkNGER8 -lPcPDmXhuYAxZe8rqiorhtulgy2QDnpGk2pSweQw0JbT0zQ5AlavWteIzO2yTQ67OpuHGoDCUbdw -vN5tbqhqG6E0TLm7nbdst2ImZ9LVZ1aY7BUKxbBloqRGCVfibG90DZj3K9yZgJ3TW13qFuug3dhk -UNNGf7hTNVhU7/RlmafLegrywNpYDhZXuTpmfYFSsvKAvPRmj+Y5O4Zb0miMFigHLevBnDPOV4Sh -xjFU2K92low5mlhTPqGvaU9zy8vsVjAu1a2qSVr/GX5wc0AzDMP1p8nVW+8yBTUjo+ZWaDgU9yOH -td9D0mpDPhW4RjYQ/UyfS7arp3tEVTRRIK12d8s79FAY2p2l0jVEsHn+srs6c0qqimvN2c0NvaAv -A27KRASsRmmLUmIQjD4w8xSggna6r96cndYh6miRydaldFUobUDqlCogQ/U+V19+MSiqIWMq3dbg -aku3DTaBNTJoWTH0y2s6i+qH+mZgG8/ID4SF6eFlKylVKBrFB4NaeqhwJgvzAiL6W8ETd/Ki/sne -ARjUQ90YBGO0J8nX2Ncw0UvguFco5N69oclTi4f7g0f7eF6b6JlivcJz2RSrPnj5F2w8uhJuqocd -h0VdAUaeR50JhFJEcQ/N8MTomG9G0xe8/dAfkFcyMZoCk7kxI/EomtvY119N9Uz4g41zQmvOCaS5 -Y+Z9gxWR+eDgfUn+7yToXCT9CecBR2W+qduiLGjs1RJaZYVaWTA0Y4Cf9MbcmmxD4IuawCf0RY6h -oH7GDsw1z2hxWm1eD1xn5Qe+1Svzas1DUoWhOE+q1mbWSmRSZf5orlRR2WqTqoaGwVfdazRS5Wx2 -nVRV0eyUqokKPaHNa5Gj6k1Sh6LG6NP7gJVgco4aC6q68w12q8EKdMJ4K3IyIuENfksUdw04JDKv -Nz+vt0g1VVlaWGbz5VuLc5s07slWY6PL295KOFvdLfXuvKK8Ph0w5yaoWgzpNUqHOqsW1Fdu5usu -2SGNodyWDigW8lK5z+vN8dWDSlQlhNZYR3Yj2DKf1T3TqO+cHM0g+jN0qOaqYLHeDsKXC8q2zirz -PdIM1HE0Ls5RjdZn9Rit0zkj4FfPGPjrFiez0nZvR0V7DXelHkunKbukU8NZaWd6JVj8sKoNVmoe -LvPIuCvNlcq9Pp3My11pta7dmKrPzgpWKpEFq/VlqirUPJWahuQ9mS0u7kqNinZlrn6Qu6ep7k6j -RJbWMDVWztVXwm0vK+Sp1JyWPuFV5PFU2tJJuNdU1gUrRQeCA9V6pLkZutKues5KPcX6el7y6lvz -67tRpYAXe13MMW0CnNw+m18Oq1WEjqq8xdA2plOASo2TIaxUTtipSqszM1mVmkzjXVPBSoOcTFbb -5e0YmqjnqbSwx2xxp+s4K+0s6K7lq7RYIks3yHPaufuaK+3wpQ0N1HJXWuNQ5E+nj5dzVarMGcvO -D1YKxoXJSlkFrYVV3JUaW1oIt42o5Kw01T1oSa8d11dxVSqREe6ODjdPX81pstGJCgdfpT2ER9Xd -yF2phyjKHJBbWlClEhmbwDNpqgKq0ha1nEXggkpjHkVeV/uom1Fpay5RblHrYKVZrEolMl9q8cCk -ubZHrQfVWqbY/Fve1trDU6k5zTLa7+7kq9RJVGR12FClkMdYfS2Zzi4c8VbWclZal5Np4K20bKDO -QHBVCjW/sU1N1HUpUrn66kstKx6obGtVyDkrbcycGOSttG6oprsXVSqRhfa1zUM0lk3lcldabpA1 -uAtz87grnSpN5aoU6GRYbWOXJ3WGh8BtVURTQZmTu9KKfFdXZ01HB2elHVWjJahSOL+E9nWkxdTv -5qm03Up0+MbU3JVWjkyNV9msBlal0O0Fq+0qUU/wEtgrq1NKeSptaSBcw+OlnJVaK9Wp0sIONTxG -2Z+RPc0WmtkuSxdVaa9BwRIaRWt5TxaqVJ+ZLy9m9rSc6M7KLoKVqoKVglpgtaDYkWm/0s/zsiv1 -TWbkUZXOFChZPZW297QpyErtLboSpiJUeX0N+VJQC6hWE6qVStSor6BSx0yIKixONZOVFujK1CxF -qJoyVpIzjSE921GGKoW1UNWme709vROwUoJVqddbNDDp599SKatSn2kkm5ppCrJrNCzypk4OdNdL -KPthoKfLzGxUcUNG52Q277ctNbrxRr5vh4jibtls8NsQzQ9UrzOD56/BGGQoHf52jQxYWN+arbqu -eupb36g1RCrNE0OpzVzfk0qxuiS7nfdbqz6rtpv/26GezswAxUK/L9JntPbxfluunCjQ83/bW7Um -J/gti2LmtKrUvt5inr+2FmcU5zb4yG/XZE7bWH/boBz2G6drdKnZIRRr6C2tm+D6ntRyTuWol/fb -JlmvVsr/bbszO89PMY7vu2WdlnTeb0dmqqeKeb8dbdTba4LfhlBsfNSd38n316BJNTkm3m9L9cb8 -Zn6KyfomeuvK+f46Q5pR0p7F+62rqKJ3gPfbUn1Bqo6fYkVSfboim+dbUzHhysvy9zlbnsP6Vllf -4yugvnVoctlSWVzf7ckqCn5v9BlVtcwVmIPoz2xzUPqnWzYGv52ilqPuLhOpeuyTvg== - - - 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 -ZAucNe+gHZQ3ZCiqIFE7wI+WJjnisQ4d+gw/6elvVgwa4TMjuY5ltKbG3d3Y4nAqrQq321XRaPV7 -QsdNxEBvthntewacGq2kB94i1jUYpWMQ6rEIXINROgYlkbkGo3QMSmQRuQajdAxCr4JI1+DkGfmS -HIkMblN2uSb66VuUEpkMPKkbmJmdgi+YuuwDg8MT5T2nDXgluhTyPwL8B39abCk6vTVFbzKBX0zw -aXmvRF532kSfIqV8QiLr0hZ5Z5zDfTPDkxM93tNSsuGj5oryhhJnSnYK9WJOihy0g+gCr4LnCrgj -2gXa1iUhUorA/82nSmZBxYSGsFL1Np8GHpeCjyPgl1NTdERKRUpbB5HSD9+uJRvtHh4byKa1v7bH -NwM6IFfAv/dItM6BU4b7Bmo99pTmSkkbKF1vRKWjDzqdRWMyWc0paqtNo9ObUjpSDDrQVQvsc/OQ -mPfhd/6/8f9rTTFA6lkQ6YwpqABAbdQ+Z89MT3aKzmwwmwhJc8mx/4eBgYGBgRFv/FdUSHSrMTCO -LEQnp1iiD28caK7ASDokmiUx2Eg0R2AkJRLNtkcuEj3yGIcPEs3LRwQSPcgYhzkSzQ== - - - 4IchEj2kGEccEs3ySY9EDyDGkY5ES0DyIdEjhoHBRqJlIgmQ6CHCwBBCouXjUESixwQDIwIkWlwO -FSR6HDAwokGi5SbBSDT5MTBiRaJlKDFINNUxMOKDREvSQUWiiY2BEX8kWqoOBhJNYwyMA4VEy9YB -RKJJi4FxMJBoOYs/Ek1RDIyDh0RLWzyRaFpiYBxsJFrm4oBEkxADI2FItPDFhEQTDwMj8Ui0FEaD -RNMMA+NQQaJlMQIkmlQYGIccEi2UopBoImFgHKJItGiGQaLJg4FxSCPRAsqLRBMGAyMJkGgx5UCi -SYKBkTRItLAykGhiYGAkGRItshQSTQYMjKREogUXSy4GRvTAwouBkaTAkouBkbzAkouBkbzAkouB -kbzAkouBkaTAwouBkbzAkouBkbzAkouBkbzAkouBkaTAwouBkbzAkouBkbzAkouBkaTAwouBkbzA -kouBkaTAwouBkbzAkouBkaTAwouBkbzAkouBkaTAwouBkbzAkouBkaTAwouBkaTAwouBkbzAkouB -kaTAwouBkbzAkouBkaTAwouBkaTAwouBkbzAkouBkaTAwouBkaTAwouBkbzAkouBkaTAwouBkaTA -wouBkaTAwouBkbzAkouBkaTAwouBkaTAwouBkaTAwouBkbzAkouBkaTAwouBkaTAwouBkaTAwouB -kaTAwouBkaTAwouBkbzAwouBkaTAkouBkaTAwouBkaTAwouBkaTAwouBkaTAwouBkaTAwouBkaTA -wouBkaTAwouBkbzAwouBkaTAwouBkaTAwouBkaTAwouBkYzADisMjCQFFl4MjCQFFl4MjCQFFl4M -jCQFFl4MjCQFFl4MjCQFvgYHAyNJgYUXAyNJgYUXAyMZ8X9MJLo5GBgYYoGFFwMjSYGFFwMjSYGF -FwMjSfF/IUh0izAwMEQBCy8GRjIiVHKx8GJgJAWw8GJgJCk4hRfLLwbGoQ8svBgYyQg+ycXCi4Fx -iAMLLwZGkkJAeLH8YmAcysDCi4GRjBCWXCy8GBiHLMIKL5ZfDIxDE1h4MTCSEWIkFwsvBsYhCJHC -i+UXA+NQAxZeDIxkhHjJxfKLgXFIAQsvBkYyIlLJxcKLgXGIIArhxfKLgXEoAAsvBkYyIjrJxfKL -gZFwYOHFwEhGxCK5WH4xMBIILLwYGMmI2CUXyy8GRkKAhRcDIxkRL8nF8ouBcZARX+HF8ouBcXAQ -d8nFwouBcRBwICQXyy8GxkHAgRNeLL8YGAcOB1RysfxiYBwgHATJxcKLgRF3HBzJxfKLgRF3HEzh -xfKLgREvHGTJxfKLgREXJERysfxiYMSIBEouFl4MjFiQWOHF8ouBER0SLbgUEk0GDIwkQ6JFloFE -EwMDI2mQaGHlQKJJgoGRBEi0mPIi0YTBwDikkWgBDYNEkwcD4xBFokVTFBJNJAyMQw6JFsrIkGhq -YWAcKki0LEaDRNMMAyPxSLQURo9EUw4DI5FItPzFAYkmIQbGwUaiZS6eSDQtMTAOHhItbfFHoimK -gXEwkGg5O4BINGkxMA4UEi1bBwOJpjEGRvyRaKk6qEg0sTEw4oZEC1MCkGiSY2DEikTLUIKRaPJj -YESDRMvNIYREDwUGRgRItLgcokj0sGBgCCHR8pEESPQQYWCwkWiZSD4kesQwMLDYxopEDyDGEYdE -s/xhiEQPKcZhjkQz+JGCRI8zxuGDRPPyEY1EDz5GUiLRbIshhERzB8Yhh0SzJAbGYQUssBgYhxOw -nGJgYGBgYGAcIojvsgLjMMPPQpDoFiUH/NT6bxYOXSqGjnQCAUn1c4hfMIAe+cmYaIKRYDT50ACi -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 - - - diff --git a/shell_integration/icons/SVG/OK.svg b/shell_integration/icons/SVG/OK.svg deleted file mode 100644 index fa47be1a9ed..00000000000 --- a/shell_integration/icons/SVG/OK.svg +++ /dev/null @@ -1,3781 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eJzsvemS3ciVJvgEeIc7P8pMaitGweHuWDRjZXbXKs1IKplStU1NWxrFDGWyi0mmkUyppZeZh5kX -m/Odzd1xEQuToW5JHeEW5A1cwAH4cpbvbH/zv/3yixf7r9795vZFvOl33d/8zfH97cuP797/ZMdH -dz998+b7Dx/f49CPfvXjXRhvejpp/9P5Sz3xX27ff3j97u1P+Cv+8oKrf7R/9c3rb3+8+9GP6civ -X398c0vHzu/fv3t/8+F3X//YbkRXnl5+pO96unq8CWkX0k8iejm+fPu7lx8+vP7jLbqOc6Rjh3ff -v/3q9duvD+/+Ox3McfcixLALS969mBf6/h9f/+r2w/qkmzAknNjfhDHTyYn+TwFX0DeZrjq9e/X9 -t7dvP/7y/btXtx8+HN+9eff+w092xz+8fLv7+cuv6ZuXu3+/ffPm3e93hzcvX/1nR6+fv7y8fnNL -b/rty4+7MOC99z8Nw5eH71+/+eoX33/7m1sag3EecDh+yV3+8wfqi7rFZxyevvzpt3Tki9uPH+lp -6YZ461/9w6F+DDrI7Uf/8Usai1u62/v/vH37X3+s3b5/9x2O0LUjXnGY6fWGXS/f/vr22+/e0NjK -OAzzTeZT6L/6Dz2XXobPexFywHcppV3qJ/oY4yLnlGG6/d3r29//ZPeLd29vZSz27z9+IROVUt/L -v/LNr75/c/v+n9++/ohnxKFFBuPn7766fUPn+/WXNy95DLiF8q+c8OuX77++/UiT++7N9x95tc12 -Bxrsn738wy1mLMgN/um727e/fvcv/IwvhjjdjCNN/BTk/7gbcr/QGGS6w8w3CcOwG0L2m1vr7fnQ -I/qzG000U7+kufun96+/fv32J/qY05f/8P71V2U+Z238Hjdz9bvYrzwwvfvHj7dv9QVoFR1/Xq2K -/ubnX9D9zm+/Or77FhPwATvhlm5MK+XNu6/lO//M39Dl338nz85/f0lz9cv3r9+iz+4X/M385S/f -fE9f/cP7d99/99O3v33X/Uh2/L/cvqJtTdP51e6ffvPf6A/axL94+eqbr2//ePv61Tdvbz/eftgd -aJnTcT/35uXr7358b7f0ku9vd/IlXcl/2v8PX326/S3ttXK5HP3H/4em982b27cv/u/vP7z8+Mdv -3331/Rv69vz2d7dv3n13u3v59qvdv758/93DN/jlm5dvX77f8XHrv+5199O3H2/fv/zPj69/t/v2 -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 -D38clW79+7BDyV4ag5jDcYdt1v1aR7MOl+GeXmQTMQR7NCEJUIeaj09lq7bTfM17Q97b1l/ThuNw -XNI+ZODCcVwf2qNZfCCG1r7A7jHg/pH2fc4z4CuqqSaCe/bJ87CYFP1KMXnmO/ocywfLaZgPn9N1 -esmY1TkWOe9zvPr6GMc111GOf19r8Y1eazKsje9wONYv/u1Pvv32P99a6a/++me/+c3Xv/7lV3/1 -D1/9zde/+vpnv/n652ioSSN1yI3vuB4+872zSCWOF/3Ln3/zm29//dVf/ONvvv1ff/Wzj9/88u+/ -+vd25e2Lf/tXtyZ7H/vXv/76t998/buv/ubb3/2DHepNfOd3Xv5vv7mV6b//+pu//0/+2H/E4/xv -fo8L0qt9+/33+N+/+flv/tO/cZ/sNYLD1f/dr7/93fcd9z9884uvPnz7q2++/oev/uev/+n7jv7p -1//xN19988uv/sdff/vL33zfwX/77a8ex37vcH/49j/7cLfvOpTp8Ztv+Oxf/e0//eprv/DynUPG -C/7117/+eO9Y/Q2/8+r872f/9z3I95j801c//fZjP+cxC//yR//NX/xV/eovf/lzv4T9/snXf//N -L13y33/x3/71t7/41c9+/u0//vrf/Gj4two5Vvj31rN6aAnTHom4OG+J9sq/0H//j9/9yGBmYi+9 -Rf+kn//T/c//5xb+7ot1+eI/fPF//l/LFz+3U/4m3K7xmo4r9bjTFz8dpflEPx2v8GnpdIVf/sGL -3Mf6Jz/7+P8y+TWpb5tnXu28739nL/2/yEyoy62nLhkXt42FlXEcV7uVpbhht2vVn24rgX/U+Mdt -PVDTY0je1BHvNI7t1oXwCxfeirULmzmUB4K2l0uCbb03tWuXR/DxR0bLeoLmSB20+M+pd1vVoEQv -nfWFQOin8Ge1IjqdSeUkrQsrYUixDiJpl0mks+0YBaTJ/fl5lPRRfEutoEqwQLiupx1lEP14Meth -V2rR5Uc50r2p7Np+RbMAQeZV1JqwG8o0wXo2aJob12AvpBuNXmGuKq5OOO8rqEWAeq+29V77ZTZH -ZgBkd2Bja5tSyy9mwa5d6TB3xph6wZy5PR2ROPOkwiisRM+RwjGq3atsxn1z7h7CFDYmErlsceZW -DjvTwadEMlwkUbug7YTV7ulliGx7+6pq381KB9ng1l1pNewpNQXWZSvxW2dBh8mMiGPEFaU65cUA -YAdJs6pAndelh3uG+HftcARw66jEkdUbu8TuCFLxOkh5rmXXtTbnMRGK0mnN7mUNHnCkqxI/t7Gw -6o3E9ltlNWheyiop1ZJBqiaNMwUUQUBc6JI4vZWWWSSwNCLZKlbo5iVvdqJ4uHcZIWZB7eI2N1Tz -JbpJJTitctHOc6FQwNVSRxM7MRHiSyLpgFG4kDcJiY0rSLxCnorjWFwLtdg7TTXbNkimL5JSShhX -gV76WqQpgzlOVH91iHlJNMc3RYV8NRLRqioGWq0lgNrH3+rByC5IKdBi4CzXvvTud9MiYg4tFHiW -yxqM7JVu6bnYmfdXvaxhdtlc0gRDSpn8ZfdUssOmCiQ8LMpaLMghppZrs/yWgYUARKqMkQt0omiD -yyClALJuAJcRSfTi3yVVoVdxSlhXU5jqdaFEmCdApe6XCSbFG16xYRcJIl3zihgIFqQUM5zc+hSb -A3Q1us6u1T6+muhUaG+8Br6gJWp8B+CdVSuJhBAgEiOApDL+VLsAFDZnnLn7iFOHU9XCQc/RZmcq -xIWkLqq/vqwaQ2deEBDz/CKVoYScDGZDoZLRUf8hJdYC92FxnkFDTRHBYil81b+2C+Doatpmg02i -AXVdTMXSjmk3ZI6BXUa8rOqGoR9as1WO5B613XQzsaQ48/4k5jZe2t0APmAblcSGWQj/htigmkw7 -k0JcdjNm3UlBKwgQpzm4G/gG7+8YDgB9A1rIJBphxvyFC+89hgimlzpzlIK1p5cTHwSNCOadRkD+ -0U9bL2qKT++KEJjesZqAcnGSWttlp1XvzKYjRDgDUu9W0byc8hSdG0DNNtoBTKCzViKoBOziKGWe -CaOtDtp0f7HCnguFdvX+EoTrxXIXWG7TUZoiVcphVU77uuCtIiZ8uAKkEhxbxPBz7ShtpbfgOjyN -f1Q2RpqF1rjbZnOLNnjhGN2Tre7sapd9epWMn9Ci36bAEtTd6AAxb3tlP/2Hq2qjDaQxBRsYZYdb -Eym8p16Uzu8bHc2Hxeq12ot1sUtgd6tQfh3DUYTFD11HarULFuUT7SyXwYN12EmgyX1B/FdcCmCC -XGqqGRUS8W8KDm43/FAfbzG7AhoTaOt1Gs8s+Mdjz9O2XTTQ0fgDkcuu3zLLFuwncQFblNoG/7Kx -5pFgy4AN5P52Te/BhOgCSg/ym7lQMHTVHulsrVCItqtSng58ImshMFtugW387EcVcjz3u5LiBdMv -BZv37dppLrzHcRF3NIbfShli9UaiwsbHyFJxGr3ctNcUGpSqjwgbDdMvh4jC6RWdetm76TR6wNd7 -mLe1c3mXItViAY8m+NNLFz53B8JGKEuL3n+UBLWVdbmfZdsMRprGpXtur1/IFw8+FjpTKLCgI1Is -GTvgkLf+RqAZuouOd5OA2u847WTz5FrWVkRJepFA/M4SUOJ3GomAnUV2+V4sAHKvl7UCnnx+kiSE -bl6qLva6jdg/egvgBYomFFvGFEWdQG+D4bKBSltPaTdl6De2feYuYfuYPQhXIqwIWaqbjAjSWw5a -ulltspSrLFI7bU/FKNtDpxXTpaoI0YVbNV0Kg4udxnDT/YGBQl85mYNFQGEGN74JEVcIEeY/WK2k -usEIVxtrGaeV62ipYDZokbUM78VwOqU2QkFENsDS79kjQdFPAu/6iQ0S1dd5EnYcL7QL9gAdWm1Z -bn6VHfCKZg05MYzy4rbFNpxNjS/3oqFGAzaPFHh+J95LMvJzNHVuTexplxpV1duzleaOTEh0Im3f -Ml7zuIVtwbqu9wuYiuXWqjtNsKt9xw+Kbpwkm4R5c5vVLO3mezb6kZTwbSHQC1W6RDek8aAQ2c3j -gHVhWGhZu123LjgWy8XFaEp4v6lKhzbad6qw8K3Dl1cpO0k/F+g0ygJ2dpQ4il6nBkwAiuAcBIC5 -riVOCyFoL40NAHj9W3+Q4aLCaRMYiWDQTRCjubOl9qNunYj/Q5E2/TsbKRrpIaKv8fWUt2Gq79Zm -LAETQxyKGt3LAgBUgG/57S4LEyDUSK4shnvTRUDzGQLpa65T3KPlmdpthGtCkc3f6DI6q/ZBJeps -UmxXCuy0QovyNhyFwjy0h9zLcx8E7Ktr3C2EizWWbiuTHqVkyom733bOmQKdBVYAyzuPKrv5b7hv -rLAUUOkUznoKDxzMe4Fy8QuL83DDbIO3pRwtBfG1MR9ChlHfiOdAcdF/sVcWT66nsDmhBtfQqJZ7 -flIUt63kq0n3u8AekNzuIGQ5tKWZ1iQNvJHOJSttKXhbMmxklFlvZpYg2JvKwfw162XWhcjRvGcW -4bldmvq+Yi4QKnD4zLjVEfshwbHFzfia1AWv/mKVuMCtKq1N/z4Ljuhbe8dvW52nOYz9oMs+IGEd -ktJd0KwGIbS4CfHOXNfLmFp9n+RmR1GjqhF/2WnNlgdCqSi6UhXAuoRuze92qWLF+o3jLK2hspgF -qrMOyiYWs9zszdhUFrOuTLWuZqeU1VI07I+yU1anZu2C1RDXP/ouatYMKZxmavv24MsXll3cqvZD -hQhSkh9g3+pwHMTwh2DfzJq4nTGLGpXVSuGkkSmFOZsgk2VksfiX696UTrfLN6KIXPkcDHU5SOX+ -ciEUURo7Ag2TclZDMC7rFNL5KnJOGpOJnp1gYoMUcRZAdSpqfathTxHJwuCILjJ41G7P8157Tr9L -g7cMWvJP4a9hdSm6u9GWak3NZal7/BZOEUgLrJI4Bo1/8ISLbYkYirdtWECuCXYfyVi1khEnkAC1 -7ovGBM0FXt6LsJ40SWxW6CQBb4/AgEkuMD4EcCd8cjuNmlYHnhLcBt6g7OnNgDwwghgN2PjOuFe1 -iAoo61VGeDWlfmw+0ov7uAQTR+fmhJ8UISY/gmO9F8vhTAFyf5hFYmFxdw+hLB2Coc1hKrRI4K1T -WzunVWv+WIjF2mkn7aBSq95qDd1VEWqSMccVtaDdD3nZUv/grkO5PzoFSjAvNVvQ9/Q4zLBMweUg -BObuuXDlrisGFbbipWDxZT4X7eyKYpTNGGN0OzQhlq0YCXUik/yid5o/aR5TP8wyJQ== - - - JatYuEW6L/OoVV4MFAOSqqBODWMdzb1VK0FeLkegQLptnuIVqSHKe3EgkNUAPjTACO5H9+8A60xT -lXi1ptIGWNxerTJadDeAB9xq22ulFw8EI739kM2l1Y6z2BJRjVVAB4vFzHSty7t7JK2eAbdRlNuD -9hWs0CGBmZUI1uCCZPuRkgaPqBmylIUwV7cNugQO5AjnppQ+lUtuPlvUeQimpQnQYDUfMATm8SwW -j86joD+7t0+miYVtQnDrV1PSdl5I1yVQYTCKVz2nUYHiWU0SGxmyJQS2+3HqDV5VLG/RtJSgZuEa -yzE1abS3U9XM5kUwTN7aYn4TI6OVka94EvjE+jY8jMXjfEuAPhH7ZrAkieB6210P6nq732FTFHFx -KktJsIqFiBW99khl9F4jtgdKDPgbjuJryEENgU04V8Z5lMogiLVeBiGSAhuaCIumlN3/ysuzVuCt -FbDZDrKD1rFLPnoYVh5hP45HJt7LC2UkUI4ddSPNmaIIGCokeRkDrAKNx/0PeTGAwoQA1SJUi4hQ -mhTeVqPslLelWCfTiJIFNlF1I7QWtKQF2o5i0qXYcff0jK4FYe0UT/ZQssjUjTNdaZC4E+4S1vdp -aDtKCulM2U5IMji4XRZ8UvuBeGtBVrEMmZNddQmgOPseiyqkRzNmWQGfVIFCO6QruAUHujEEH12r -luM6h8MEQ3AYSIiRB3fJEhmrLtubJQXIFdaGNuYtSMGgBo+qjNLVkzKaccoe5nE4b4qwe28GkdON -BKy9TWxTir5tJlXbjyQKSR+BRcSZ0o2HgzTEmbZiDjexkeyyt8Sdutk9j0svuRknZGgAP9PpXECk -sGQXJTXLPkkcG8TODKmCHbpD9X0Ot2M/jYNYEZyU2NBSlr2Ox93G6r0vGzeyQUSgVw4rDFLeTwtk -bZ4nRW/t1fBLLN+iVhICZZihu/d7BYYqh5lqJQ5jWHOrJ8C0wWGRLeihvXWJRR2bz9iQCueBGJsS -B4uQG1NyWd7Fzgyp+COFB0FA3fOwykHglV27JfXWjODC3bZaYtYSE8vi40WRqzArkayiQT66fymp -1Sgd3goteFQU1HaY8aW3VLoEyRh9v3ZvwBHYEGFzf4rNUlKDBMDDmvF3Fy6iba5KvmgkBEdJ/Hn8 -bc/ZTHEPR+2WOCdQpLjlsvlUIvrNIOgz0iCnLH9If+qXsyxchDFkXZbT8NDlgg6S3WKJaYVKCqib -dmbq87W8CFJrOVDB1q6jS2Kvtpx3HndGBQL/EJRLSkKD2O4QUiiBmuDniHgU63QX9SgRUR+gokYp -2x2qR8jgtibcSvQI8x4Vq8wXwYvbDHVLJxBiCYaZEgcbAThqTrxkC7KgiOb/nYdQaqFOAOd6wds3 -D872N+U3N5K3m3Dcjp4H3bYWplkch5+kag+6CEBfSAm7I5EWnRhCVBIxSJJ/7COUEIgZB1Cv3cJq -oQUIC98++6kUx6m4lOhTyceYL/cKHEdEWY08XiNDHnW1fpVeICS3cF2t3+tatkisRqxQNeXMJ44j -6IzEMLbwGTdngerOAPVpxOHUn6YIysK6Y5eCF/I2875g3K2+Bbr1GvFZpMpP4jlbtKhYJkN2hd6T -Gm3pQ1kaGa+T93qZdFd8DujBZt8HxBVFyyyDflhu1U6EB/dSJl8dgxvASNoD6VmiPJNoq9cEoCi8 -VJaY3T1pTM1c4AJuBL4bLThU9IPptWEHXJdjVB2OuE4guGoFHmz2XxCpUayR3xZuxeraFIS5/7R4 -PhepcmqraMlOO8xy1qcc7E2Fm6TwQUtcYsIhJbEhKdxrCqCcfqULFYZEwWNJIo+hrIJU7j1XVQCq -+JTsQZLpCs5jZzFNQPSpPu8I26jM4Tot2U9YSW50UVcLIU7h4G3Go1VrPOpuwSb6adSpt6HWd2GU -qQBCguV0nLotgMK2zdOyPEbR2uONqkF1qRRVPWh6ptWg+yJKVqXoqIkinCFgQz/sUCyJ4tqD8Pul -9MWaJy545auQ/U/7jPc2YhLVRUuiBPwtqfveA3PXuQ9EKmQ8zLaE29fiV9XVl2oXarzkafUYKrwm -8gqQ6CEOm8P6WTayUTt7E+XgJSKIx2IrvGjbWO3Mg6IFeqdER8u4Wm/WbiGtSAXJkSgKbCvQeVrV -Bi1WWla1WbaN7yiYug+eU7gI2hQA+4nSbwKjF7DVYo1nv9Vxi1WFIG3LfV1W0aXwKxKAbjd1LNHz -xZnnGnHby/SdXQ/NUK6IwS3mfEkiFK516f6/0hSL99ztisIuXjSH00EHBydarYaK2byCYyNErDkF -t9qiCHq1mArbsDIxqETw4DAbBdca+5+13a5ul6JKTVUEJu9LCYf+/iTnGTV9x+2Efh/nmZdJWgWN -5QuIqhP3bEYgih47FJTbVS3O6CjpSqoYhK2tedYOu2KP1M/qof5qZjCCiyoNGR8EsttqETCRE2eK -UKVkAjK5VBzAQTu/D8BE+a0IGYKoVJISZZILEpygKTBwxCM3LUHLX2zHqadRftL082KxH425kkmH -hejv65yk99HrKhSKlLAlHprV9yCUHsEfxyemygx7VYJ7ISHoWUysqqMqq7SROpfgsrvttDDqNAIW -asb2chPMlI0gXSCN8tSyzshOW27m8c0+fMZpgeF6kBDBJlJ/+y9cumv/hurPCkwue0wdJ3wBQiXn -aRjb4mL86GeaVSoAPUr/kBDgRqL0Qc2qJLCzt7AZMIYVQCwRrGb/jhCZPHoCV4y7wnbbHqUOxeK1 -bLoaMbwcM+nQpgKG2Ggash0joSWRrtp1kYr0ggKZS3Zw82I6jhFZVrOGlTxRGz8w5aAi6wbytC4A -OsVzuKHjTpME4B7FGwudXyhnf8nVS15F1kPJRFk83YJ53qKsCsBDuQbH7p5budzvwZwUoqFwM4vw -SfYwNgnB28PCMq4in0iMc5xVcpGFIZ20CnY+shBUwWgnwTGn5VDhTnlux2FhUQVOVe2FRRKTmgjB -tXtBnGiheTbzHJCoWEYFkc0sEFW2ZNxCtIZIt2qQpxZ0k83D0yJR5StGz3L2MxeFbLFfDDoY47PY -UwhNtVmMXs+wRliH5tIKWxD9tKrHln8uzqsq/1RxBl0aNKcWAa9NyNZWVXht1aOI8jtpat4MbbbJ -wYRvYN3yPOXyA9yJ72G1dlgBWJeEYM0pJz911YjpLFZUI8RhcSYwKeSlAUVLzKJQXaYwgENVW0Sf -gDoTY9u8QhIb2WomXX1zcXMKKUdaa82cyv1pmrVyGZD/1mtJVe0LHqbK84C9vrXylRmSQsEPy1nJ -RoWWFuP3OQR1SlWISh1BxjzDO60RmrHcth1mqqeYOY7E3DFs/qg3IyGnUVQLZ/3Chus0KP9i2Sdy -il6MX7cokiyeuuHBDH8G4qFFJACnUVG9tOTn3JqFBcMuVNyzkwpmjuL2qPl/sfYpdsp6GiE0BzSv -wssYpYQNTNfqaJNUt25a9FbvgmA/ThOECq1ClKsmjItr7+I61tp2Zc2fKlx1GnkkdvO2dnaRq2lz -MOYnTKFLCKDW7W9nAbktxBV7AqoQZfxqJVoNZNsFdGuM3RGrobyV+LRh/ZxWvIXglO4+bf9opcdP -ER7HZUI12l2nFW+1QMxEcCoGYjapqenValjrYSaAMFkXEe1YwyHpLnmExRGXQ+0p/0qRjeF1w88s -fjm8QKtn2+mVbz25T7pDaxGL37DkWXSLjCIjKZGzhlGEjo282b3k5WxgbwWit4rX9qAN2K2SkiLb -/epJEwXCYE0Tu4IyZbsqPpX5IgpCpgpy7qyJJQJ1CvsW08iaSu1mh33o+xPKD1WJbeulizLbUKwC -f15I5d+Dr0rr81AcU2UmZzXSbluPq5WFnsYTTNxUlTAK6a4GI6Xa1p0scBTcEbRBbZDbFYkGagYD -W3irRJ8JruLw7I5z9NFjqwu5xhDySBdTVtVCu+Eqay/bR1WMkHVG5eHqiNIq08UAFRI/iBTMGEVp -Ite1buZI7tWhqVfSBKd4W3TSbnEAtuqcVasXV/K62tfEkrwqR2zsIFTvEJMm+1uveDNPTQm5R6Pv -FUW4LsbwsSvRS13kOsQoLwM1FIJDM1B01ThZMvB+RNWmba07+2wKsh9IiChFXL2WHn9VAP2gCzBh -qMyMoidVBzCtoDuXC1SjXuFwogU2D7JpONQRzeC55blidAnqADw71PPlPh4aVc4zhssVXQtRJrtZ -KanULmEHoqpCKjr9GalRjbSWhDhHCnKjtc7TDDbiGYJvJbSHsQyQPAVecdpyGq2Rt0AfFnxEQD2K -IRvrgN6SgW3X5MQ74AUoTQSaBR5wWJORIoOONKWzRKNQVb2laoCqeliUPi7+aRRUSkoTQ8032z3O -QcOA0KIo+yDTQxKFEJ3whVUbSdzkjN6rYu6RMDIuay4uxOkJYu2OHybTF0Fb9jytWfRyN9TmYsEf -LmMIVsUU/GnYtv0kpTduS5iaqN3LVwUcXgzua93Ub2xhDZ0m+4hi/EvxVQFYVMGMW2q6Kk1HvI5W -ieic2syf2WnU83vthO6IzoqfDlBOJuGmykBvYlOz2MGKPfAgGVVt5gKgrtY8Xhh7oOJWp+LjUirn -JeltxSrURCpATqkQCwu75LJM8jWaR655oSOS1U8wiUvhZxnievM1gyEXqaBtNXUkPh4V7ZNkQ4WJ -1wj/3Cxh8hHLaZHQD2FXqa9t9U4n7GhLZtN+j2r9rdvRlo+tXtDM8jZTkRkmcqvm2ygxfyWHdIdJ -Kpj3Fu0uCMRn4FVZJomuGKRy4KjPlRpuEW5Fot4c4ldKSzbFxfotd9Y+IV6jYVGFM7E7JuCisorD -Xdl2Wpjxg2+grUWbnNToHuksi79XbeAqzhX7QlZnKZ5QLN1qec7N65GQyEBVQIESOVVgHXmeVQNT -bFREPXRZ3uhwdPemiIL8rWUoV6FNTn4MZVLq4SEloxBTZJIwSM7DsesSi150KtZfuSoTQ+LH5uLm -mzIwA00uBc2E0W4UjYVt89qh6/KkPDHCVTGY1SoXYbsS1IEla3YzZgVWqLr0nh6oxlVBMfNlAYfT -auoter95IxTJ1FUBlM1TCNT5KjTFmYrura6z7MxmARtJFbEhL6SYHBL9VFGVfkZVFcWqhwCe4GC5 -VgsBqp6VFWJ5AELc5kCs1khiZ5YoGVy9FJhCmN0LVhQtQUL3kQSbN+YiXBhEBVyJgDOICoRwJYtR -rVZGqjtGSaaynpcxbFlbBda7Oo1Xs72bwGGb9/JF4xRBCq51L+lLYDggEKqOvBbPBFGGoKlL6hCX -wabq5iUXAumqdqYFttUTo7J6JPeaEMVw2B2gK+N9XkcUKm4eKNE7qIfvVpCKzZgt7oYY26qNdDRa -nNGpu0Y4CChCYsBQignby265R7EQOzw78VG6w9jAz6F+8sBkokYm1+OxmQuB0AZDEuOfsmz8eJ4K -keJEW6WquFHfGrO9uASNi8SyMEjOsHiMPqloK7B+pt3NEkbadCu4Y7oWaakA/1Ippw== - - - tHcpthvoKRQLK06GxBzYLcPWjJrAFnON7OaqYIbK761i6YrCHGhgDhUZLa3XYKvEVqaXysCahedU -ZNKMA03qHgBGTgu1qr7s47QAVIuGFyaj9ivM31vvUp6RdbIonFMuULV47v1k99Q6l0gVayia5oyw -UiPkah34hByVY6SwaFMhXKRfgM6TckCyRfR6OT2jiJQ7WZb5cokyDstpwXIkUmWmbFbS3jt0dF4I -jOMIbeLl+Rhyh3zR0w2HHi4/VZqrri21/dDoRgGvkpyYikoUUsu2eRnPxg6hoB/3hZ6C9JisNKox -T6WmDtcheHx7FOvjBqsjkApX0E0JvsvVWUXqqpLjzWtN0KBRG0D988Fyw56wpErz5CSw2LjW3HJX -/EX9wr72VV6uZbdUc3DUbqONa3FkWT2sAl1IsLozA6fqKAhY6m4ZvgXlsopX92p2puKmIrc5Inux -WUyJ+AC5Z1Xvn0YNbPsP5dwsiuuwzcHSP5GmW6LDQM0lVL3jXZL7IptiRSWBmZBnKohVi1nbm0J1 -qlWl5w7BGbvnan1QH3xYzS5Rw4GwNPeAc/Dye6WTNVmJXJ7ODEQCrqhMS+0QW1Pzj2327M9UndH6 -sDdDNcgmNBWtq3xJcLB4SMJZKhbHVJcYDQj2tcnkRAyWfiCrRCXlQpSXgVbFH3co1ldksX8M0tBS -1rllkLqCh9iEybYYXoBNulbjQmuvAd3UcOjo1IudZ6UgXL6u1jq24GMK+qhFzl8VJixSigRVc6Ec -1+novISj1b+hTU9LJ2Y5LOK63GYET6wGqzvYLMZLOtTsCgLEUZC3CUBA9AyXcdZxKRk4YD4dmyXM -TKdjnRanzjKpyZjQP/2RJdfV+LSq2e20aW7uA8fhcKr3jny7IOSLa3Wyw2YciX1EnUOH6ysRcZHS -PDxIxWZcvXuSdLGADI7mXVR8WWAG6AnQ2qNbhqaiYxnNs9Pa7OnhVkHEvbfKBCGajyGPzta+PtaE -qsZHtfzUt1FK2iypTfWQaF+BCkPbErNf3Gq1BIHauIS8SUGRNdRetiz+zk0NVQ9QCKoe37NYdA3V -Z2SFl00qItxn1P7y3gIOWbwpQbemvppVVg1URapgt9IYC8KcbrN6ZIIgjKmj0yJp6hUGuPSyj/TB -80syKKmTN2A00L2AxzSH+hUx/pzh6O+C/f2jLzQg8xj3TofbXQ1o54FTpAKBgn5aHG3sFy61lRNS -lQNoydELfViy3hSbOrpjt1Yz872rkwWkDXtTc9ChyWg6eAmff0hEIqwnAT9UF+laBJT9E1rUxqYj -Dus0iXCKrlWN3dk0inDta7Hg3mqnqdAYweU+Ex1pGPzEdIo3GQmWlcqaWix1R1i9RJMnVGtYEkBu -c3OWvopBo/+YOjBV1qG3jihW2jaDJ1F3F/aGii8huVt9dPbNgk+A9ozLXtugkHy8amQXdof3YArq -HLNuMZT5OMtq1Ra/mdB1icBE+7I11+hmOYM3Cm0JIaDe1Ga7W2kWcRoW9KaCOVu7EugsGlXOpQs3 -+QQs5Mtbo1PQjNHXTgvh6prpVkC3GiC18GMRoqtXalvjt04i77mtKeOBFI8Tbwdz5XBMA9qJozIN -GfVq6tBeTOUWeSYOIoxAW79CDlFBdTiyixA+1Am6W6RHBZPsNCDvyXHp9bcbpdVKIniBxeFmfFPr -0fB7GUqZQohlR3/XJmQ+wIYtZbyBZ7KfhwmiAZv460UOxIW7zLWiRI5c9hSMA59C8hPqLwSFolUF -5mlUYeDv4ZJZXzyFyddam5aOzXGaBVaaspx+TaVElyoHvhDItDr2F/CuFDmnuYzf9VpW4032L4ps -lXcimI5NLsWmYxTuR7Cc926IoDq3JHPl9rubKJlVO0V5H4YHmgRKVwquVLURAjPLvcwoj8IqUJu1 -c4WqrfCUAVs6apWkZuQX4+lDYsEklCJB37fy/L2bAlTj623T/VHlQwp+bepatPCrykQpSG2ChrBd -lsCZgnDsaWsxnnlzm8YNlaQWjS20h6qXSj2dQs6OPNjqrTSO0BOnWVDxktWswOZhXaaWesASVFPG -ZRjOHz3eV04BR1uchUoGYdfQZSswiNYMU6Z5TamFCQNWhLTqYY1oaoQU1FUxvtSTrDPQE8sWjWmH -2Tj0CohFGwF5DVH4ic/dkrlUmS+eLiNFQpQHmUjNT5XNb0ETTwYFqKBtGxKVzVEgiNNtFsU0E2gx -9PPXF/vwGSeFMpfWz7FYtbzmA8ktq4tYnHSX4/RlD68tQLLKbTo8ZhoBy1UlQEfwCBPCrB7qtFjL -HmXvSJbIGR4BXSf+Rir+T4LPqo5xvxj0JS/GGD1lQd+IntmlbLCGXnacVgSEJ6BEJlU72xE+BFKh -TIHIfhI0oCGRWYbEvG7mC6WRkiT+AbuHLBpKNFVtiCsn8DCOA8Zcku20a+01QohKiRO3PqMak5Q9 -eZRVL6xmdcc7gMY2e9e26pjTRKkVm6WITnWKeDKiKAuBMluhslLo2Xq18ytIyhJSNLJm2NclH939 -PMybyeMiqr+ZntdvK7Pu9XAhpHmImLW9zmIQtKaRazSnbHXUkc0rzIET2atV7VtcS/AUeoII8FKX -fkTMA4etNGfyFgaIelIFO9cU3d5qRJmVWorIhVasMJ+LW4YyeKgl4Z2WMkmGyokuJc2uKj+SKgL4 -q4sFERloqykLic1Xh3npxxH0OtUcewjovwuIo7QMBodUaDt4rlQaqilVgEObdVLURFs8HX6YM62a -iyojjQ8RNE3OHDFCaYq0NS8Lkv68LMOsLIs6eEAPVSgWM1TrFBYz9dSSfztC8RKcVQGUVY2vSjhY -aV5TZ1u1nITwBKhN2qJ7db/cs4cRSD2gmAino1FalxiwcNaVT5tndlpelo2WVJhJe4KlLcEzf3nZ -vCFbhm+ocq/NGgYUhD4Wz9nsnoR768rPuT2TOLbiSXjeCXFbDdH5YxOezjR7BjYb+lKZ4ctxopAI -6VnpSUro1T2ItK4G6SmXR2c2bwiz3TnqCeLMVM9i2eXE3XX67asZf5hBIirrFelXJd9X2zVos6dM -yGrgqHqhNhjoWS943Dt6B76wAT1AHS8CFRrVo9ztJOdAeYqv+2JJJ3tW+o1OS+UptCaJYiaEbowY -jMKW1eD40kWUVH0+AFYdaoYj8mUnruwFuqWxPRWLRNnDXuaSrs3pTPgk1WJyxQIrXVIGvWhSJY8W -Q+6mvEbIRRfJHaOXVsBSPzUvFdk98ggizFgzVEopiVCrF1KqsbYEtsHpcLRLpEGokLv3A7DpajMc -AaURQ6CbBSZBCOkhdFBIYp+AV1yO26nGdf/uIRPkGilDqlKUX5N3rZKkEhWsg79tBYvNem0WNZwT -m1N/x+YQWF1CkrxEAqdLF+9Mr4ELShJ7F3jBHtgCLokBVTlZP47aeSGi2k6/W92RdF6xAJfFgSLG -TeBRWfo1QFUIUKr4ZT0MpLI5hVYEkDxsWB3jgPSOpa6r53AJayir2+qAw6g4dLWJtVL7UZZIs6/e -jq35QbA/JPaSzfrJJRWLXtk92dtUklBGyWYJEzszpUTQGAyC3LYuVteDoL+07ewSe1h6Y+t03GKI -DDiyYFmlQLWEWVDlQqa2UuqrIfVQdWoWJLXjeytdElOg6LnyOFDClNcnpkaNb5ewc2efaot0CFgy -Vqy7R7HzZu0AjKvqIlNiz7oG4mgcFw0BplQD7XFzaNtsuxTc4+HI+UVLi9o2hw0tIgKgI1W56t3h -FuNE68DnciqgIaZ7OvGktlsdY1tVs75wW82BIn0cP3Zkysvg8zA9ja3uKv7RJPEOPpHVK1cZUpVn -SbVTAkPZ7SBplpqwM1NKJ4yKDM3yI59nlaK06mMdUsW6555TfCkhFeAt0BUB5bo1bTo0nFTvWitZ -Hlo8kCh233bYcdoTCAJfKGi95Wpl3mtETKpKGLUfqvrZ2HWFYkeReBLTu4S9zV2VLlWrACRKCibI -HfCu2ioqqGvos81vUvbo0j0uh9kV6tHpxs7SLK5mFfrNzSnaoVQ0pBgXhhh4pWrTpkoI94Ldwa++ -dIdD/qPwIPI4YhdWVVLddU7JpAxSKhgeAVyUXpghFjBrpucpVq9b+eAOpLmB624M1oAnKod939Fg -N0jzIQGrZHeGM8MxcpgFYaVGfjgFv3Wj1FZTSCngWKmUVV8JeyudQte+GmS6ovtmCB+uUiVthx+3 -VZccITlCckZ9i0NxWR3MYgnmhfEUdDvxD0ms7MLBeuNMtSdSn7HaUcppINgEPHab54sNhUAXddba -AhX6NEow/DjbJEq6Rx4aoBt2H902S3EW5ytCoopxO+66JLFJRoT97CfaygEM9CL7PkqqYZvVAMc5 -rObCXMzLESaKSuXUgE1qkIwUUMbEyK0YBGgiT4uBT6JvBqOjkNTWw91CdXSfo4RKjSjNNOkh+CLz -gxaHYQCrmbi7oEWEWEejiTc0qKanCn7OIQq901Zg5JR0A/DLMt4XyxtoFWANHQqFGhyggJsJe+3h -ZKDyqWtYOlYsMuuC41KYNih8fBiF9qsJNLYCVo+SX2HE3MsJpCoBXqFiaM0lyiIY1tO3iUMI7VvW -0KieT8LNjmoUEx1O3dJEc3tfmOtERaeEBut3qGKDsyheC4RIXdchI7vxxUZFsOAI3LyleYe8N7tL -wAZyDRXGeZQqOVTOrJ3MGs0cg9tcV/ttftnhdXh+DAGRswQakCsSSUSoki6kC8/LgWyOYrEYbPBV -AGmsqtuClmCPUqRqo5ZHMUAkgtnXpChDEIhIOYyHZVtVrEvk6kD5UzYhQyTyB9Zk1l9N4EqbtTK1 -w5uiL8MUwIPGBFGrWhQuueJDRlxLglXHKOqv38Qt6V27Ekb7sNVJD4wspXNxYgU1OBbz6s1zDol9 -aVe+eRgfVNVhVBLJsg+JBiShx10I/IDiwLo8qGnF2gDpZQYirXRIUYqKFAAMoYVcL8EkWWT0dKeQ -opUrtgxi1po1i2dSFXZV+Vh6PyEJqNUI1ootQQEB4cdc5rVt4Y2Qz2bn3GsvA0JoSA/VAeAghl79 -KM2ZZikwtQ0kVn1zdUDekvApAnZv5cdUxte8VrlaPCa+nIICm1SOxq1abFj5pPjNSEUE1kUkmi+V -o12+0W6h5IrXO229g2qzYpsrhaqRxOhGkW9l+H2ZPouAlITRB0QMSXh1hD4xtNBnu3pTrp6r0rRq -auXxo+6Vr9pUtpEirbdaR5/eYou6NOpOdxPKwbis85j4JrFBnSVe0WYMVnGWnEz1oNDsBibAYiNk -vWJq/ufV2uAnnruftv3YW71FNbL8WHjR+fsYPCAJW4dkVa2m0pKmqhRhU6+yC2wv80L2OAgYWWLv -oCioTgfXneqOzesvtJWpFlDrz9N9FP7hJwhrc7UgrwwzTKTTOU85SnqxGe6cApSySfiQm1cm0XIi -wdFD1tqE86jmhZxKhpy1C5qXadhpIazW5KI6GMpxmgON6G7q67ltgiF6bP1eUWFLfA== - - - VZi1DjEugbo11iHLjZBR4nW3YrAdDq2yR+haNVckyzGKIvwrFMrmLXqqGDoVd4icXAgAz4jO+RSe -joMROSSA/djaNsGODQI7rZja7UctVghAbksAq/D8qGmv2MvroyH0/jZh7v3UryV8bwUt6NvB6tuV -uJU7mb+Fhx9hcpOtTltE6ZuMgsRERTkoc7LkPkr2fpGd79FDq0gwwdXXvvkaLgQCVk12RKHVeO80 -MngGGgP1uDkxbmtevro1D9JS3CwwgOJJOtYmtWPqIKtbhrmkaQmSgjOirlJgLpsBtahYqGxGnoMX -rN7p1Up9iYuyiVC+cxHvDEGEJGUZuYyeEENUJCcsVpszoBhXC5TotJTyvJDa0v/iYHW7VZbTUSnu -AVEHVq98Ut9QcYQwImvw9l1qJNi8UfsdIf6c8WfVDH1n3dAfebFFFJ8zgdliHKP791H7/aFUgZ8+ -7j/87JtfGqPbt/9R7GP/7tff/uOvvoNc8FXe9Lnoxx5VUTAbv9jIEE5sZAgmNrIi6N2Zjaxg7kxs -ZMUyVwMbGaTdbzYyKIpnNjJJJjYyO2ZmI9PVJjYyJDMbGZI3G1mBN2RiIyti1RzZyMRZ/GAjK7ir -eghnI7s1wMxGVpTPerKRlUTZCzayQt/4xEYGeM6bjQzpzEaGZC9tYCNDcg/Dg41M0omNTJKtDWxk -ulYrExtZSWyzYCODj/x24ZKNjKzxk41sPY+JjYww28xGNkgG7qtB6mxkYBLBwdtZxghAvdnIbsP2 -wUbGc81sZKsokI8HG5mkaxnYyCSZ2MiQvNnIkE5sZAhmNjIk947+YCOzFP3IRgbg1MRG1gUDG1kK -g42Mt57ZyAi7vdnIDKNyZCNb1d44spF1yfRFUmpsZAT7ZjYyvvWbjQxAi5mNTNgV50xGJtlERibJ -REamkOwinLaBjEzS4xrIyJDMZGRWp/AkIwOpZSYjWxXVHcnICHu+yMjKGoAzIYUtfiIjQxO+yMgk -HMnIENwD1snIXno3NkyrhYLF/k1GVmlDn8jIaqkzGVkV6udERlajSjrJyCSZyMiqSomfZGSV8pWJ -jAzJTEaG5E1GVrHmJzKyWs4HGVm9Fe2bjKwKGmsgI6vqJhvJyGotLzIyySYyMkkmMjIkbzKyqp6J -kYwMyUxGhuRNRlbL8SAjq0ILGsjI3t/xg3/nShH6k4wM4URGpqNGMjIELzIyhBMZGYKJjOzW1m8y -siKKtU5GVlSMOJCRFYH1PcjIChUeIxkZe+JERlbg91r3mYwM4XqdnYxMgpGMrNTlTUaG8HbbOhkZ -gomMTIInGVkRJdHRycgK2cORjKxQrfMgIyugD65bJyMrhpTbychSMJKRdaFnx0owCgXNmFb7k4wM -VTKRkRUx1uydjKwLOhlZypyMTO8xkpEVSpqeZGSlbjMZmQR162RkOu1JRlbUCT+QkRVxhXQyMn6/ -yMhKLTMZWVGt5kBG1gUDGVkKg4yMi09kZNhoLzKyAsjQSEZWiFqPZGQpGMnIUhhkZEXQagMZWdmO -NxlZUYx8ICMr1BWPZGQFF+hJRlYI9o9kZPc8mcnIytXeZGQIJzKywnYxkpHVZXmTkSGcyMgQTGRk -VfDHDzIyhBMZmQQjGZkEDzKyohTDQEaGWpnIyF6qLvY6ylheZGSkJycyMmp1JjIygIBeZGQIJzIy -4QeNZGRCGXqSkUk4kpHptJGMTBd+kpHhKU9kZHjlExkZedcXGRmvMpGRcdpERiZP+klGJg98JCMz -lzzJyPTzSUYm4UhGJsFIRqZ7PcjIeNuJjIxHnsjIUjCQkaUsyMgIEcxkZBWQkRcZWdVGOZKRVfoT -RzKyWo83GRlHTWRk4qWdyMhEYfwiIxPn7ERGxihMZGQpGMnIUhhkZAJPGcnIaiC7jmRktV4zGVkl -pzySkaVgJCPrQicjq/iiIxkZFMEvMjIeYSIjq9s6k5EJm+RJRibhSEYmwUhGVrfyJiNDOJGRIZjI -yKrq1R5kZDzTREYmFuORjMwmxYOMrLZtJiMzkuSBjKwLBtaiLvTId1WrYycj4+4vMjLmzkRGJvLi -kYwsBSMZWQqDjKyqJWMgIxNH9JOMjK89kpFh1Hcysvw1kpGlMMjIuMZERgbL94uMrOL3j2RkLIeJ -jMxAcSYyMmHnjGRkCCYysnqUNxmZwosjGRmCiYxMRzzIyAhTTmRkVQ2fAxkZ6DZPMjLglyYyMvTz -REbWBQMZWRc6GRnabyIj42YvMjI9wUhGhmAkIxMAz5OMzIQDGZnOGsnI7M0eZGTsIxMZGfvjREbW -BQMZWQqDjKzKBx7JyAQC+CIj0weYyMgE+DSSkQH49CIjE1bSSEbG4p/IyCoRsicZmRykkYwMT2Ui -I0vBtKxDGGRklUqWkYysrtubjKwux0xGVpc6k5HVYNcdyciwukYysqK4w0xGdjsDExnZbUPPZGQY -ik8yMslGMjITDGRkJniQkSGcyMgkGMnIJHiSkd3b90xGhmAkI+P3k4ysoLtHMrICCtJIRoYf8yIj -KwH3FGRkCCYyMrk/TzIyhBMZmQkGMjKdRiRlJCMrQAeOZGRFcGEDGRkB3xcZGa7DSEZGvHoiI0vB -SEaWwiQjY9xmMrJyrG8ysiJE3JGMrFA7P5GR3Y/zCTIypDMZGZKZjKyoT+BJRoZ0IiNDMJKR6YAX -GVkRWttIRlYE0T+SkRU6KV9kZEhnMjIkMxnZvdt/goxM0omMTGeOZGS6+IuMjFeYyMgK83ciI+uS -kYwspUFGVrRFDWRk5WxvMrJy1ZmMDKUxkZGlYCIj69IgIysiUh+oxwp1Ay8yskIRwkRGZiM4kpF1 -yUhGNkidjKxE3auTkellXmRkkk5kZEhmMjIkbzIypDMZmSQTGZkkLzIypBMZGY86kZGVs73JyFIZ -51EoppGMLAUTGVmXBhmZXX4kI8PffZOREYadycgUa5zIyIgEvsjICBiOZGQKNI5kZCmYyMi61MjI -KgS7IxkZwYQ3GRnSmYwMyUxGVhLPaiQjQzqTkUkykZHpzBcZWbnKg4yMIZzJyLpkJCPr0iAjYy3M -ZGTlON5kZLbRjmRkxGhmMrJBkmRkXRZUQkV17CMZGWrwTUamGTeRkbHMJjKyIlv+SUaGdCYjk2Qi -I9OZLzIySScysiJg85GMDMmbjIxHm8nICtm0iYxskAxkZF0aZGTl2B9kZAS73mRkZTseZGQEjScy -soK//iIjK0JuHMnIsDZmMjKCgC8ystL2BxlZae1BRlYS93wkIyvleJCRkdKdycgGyUBG1qVBRlYs -DdXJyLjli4xMOeSJjAzJsSwDGZkkLzIySScyMl1rIiPTW77IyAi2z2RkhM33dRnIyAZJJyMbhEZG -VpR86TRjxJ+fZGSFmqOJjKzUayYjK/X8BBkZ0jcZWTH41dLJyGRdTmRkg2QgI+vSICMrBE4nMjKU -ypuMjL16JiPDoZjJyLpkJCPr0iAjQ1/PZGRERF9kZOTDZjIyokcTGRnBizcZGcGwmYyME99kZIRQ -ZjIysGxnMjJwad9kZGp6nMjIDFloJCNLyUhGlkInI1M/0EBGJpziJxmZ2h1HMjKaEScyslfgOCLK -30tGVqO7cCIjq+v2ICPTcRMZWVX96ZOMrNKePpGR4ebOZGSM+5uMTNKJjAzJTEaG5E1Gpu84kZEh -mcjIFC17kZExfSYyskoWYyIjI9r6JiMjZjeTkdW9PMjI6n59goyMQPBIRkakZiYjI8DxJiNDOpGR -6bCRjAzBm4xM0omMTAGUiYwMyZuMTFmFiYzMCuQGMrLKX15kZOJOGMnICCvNZGRqN36SkQnmfiIj -o3l5IiOrysg+ycjoZp7JyPRGIxkZw/wiI0M4kZEhmMnIhGP+IiNTeG0iI5NkIiOT5EVGhnQmI1NH -9URGxuC8ycgIYE1kZKJgmMjI4N18k5HR9ziTkYmnYSIjY1xfZGTEw2YyMtC1JzIytOObjKyqY3wk -IyNk9iYjQzqTkbGKZjKyKsTDJxkZ0pmMTJKJjEySFxmZ0hQjGRkPMZORceKbjOzW9g8ysqo65pGM -DJX4IiMzPTmQkUmVTmRkbyUc+vvzkZFZvuBBRoYeG8nIarBjBBlZPdqLjAzZREaGYCIjk+BJRibh -QEZmB3UyMn6/yMikREcysqpw4kBGVgXo+CAjQ49PZGTahEYyMvT6i4xMyn4kI6uqBB7IyCR4kpEh -nMjIJBjJyHTak4wMM2UiI6utzmRkr2/24TNOCwzXNxkZ0v3YBjIyGbitDWRkSNp5PMjIJJVVGmRk -kqg6NMjIdK39uGYyMhnDExkZFSQTGRnxyzcZWVFaaiQjw8uZyMgofBF22ERGhnRt10BGpgKZiYxM -xyzbTEZWyvkgI9MNlnMgI0NS1+tBRkbxxkxGZo7bSEZG/PVNRkYhxkxGRjR7IiMrwml+kpERgp/J -yEokxpOMDGv/TUaG6zyTkSncOZGRKXD6IiMjQjCTkZWkMQ4ysiIOlCcZmeIWExkZkpmMTJIXGRnS -mYxMkoGMzK70JCPjyWYyMkZsIiNTnOFFRkasZyYjUxRxJCMrIlV5kJFx3kRGxveYycgIwb7JyAgr -zGRkmhQjGRl5iTcZWSHBOZGREeaZyci4+JuMjDk2k5FxrZmMrJArfpGR8SQzGZlCSyMZGbd8k5FJ -OpKRSTCRkSF5kZGVvc5kZDZcIxkZkjcZGU86k5EVYW4MZGRvLfk5t2bsrRcZWREHc+1kZBgwIxkZ -B7zIyCQcycgQqLg9yMgQ7IQLRzIyhCdAeEFGJsFIRobgWq5jIiMrYgEfyMiKOJpakpEVTKFrncnI -VD48kpFRhbhtxECNjIywzIuMDOFIRlYE/TuQkUlwlWsiI0MoXMsgI0MgNNEgI5OAUsqRjIzwzbqt -V5KRsTlNZGSku15kZKi9iYyM2tGJjIzStScZGemOiYyMcPFERlbkrD3IyFA2IxlZudaZjEza6ElG -RtJkIiNTam0gIyMl+CIjUwRqJCMr8IEte5KR8QlfZGQK/I1kZEXFsQMZGQHKFxmZulAGMjLiphMZ -GRv+i4yMsyYyMtTSREam4OqTjAzhREbGI01kZBI8ychKQNIEGZmdNpCRceEXGVkJVusgIysKIicZ -WVEfzIOMTPp1JCMros8eyMiKqHUeZGQoscqaDjIyldveGjvJyJjUKPCBjEwxypGMrIicYO9kZDzi -i4yMTWEiIyOFO5GRkfp9kZGpOmAkI+O0iYwMW+hFRsZzT2RkRfStpZORoVGfZGS4uRMZmdTuSEZW -yvomI5NwJCOTYF87GVlRhfc2k5EhnMjIeMaJjAzB6WeFo0Ob1ERGtgokYUkyMvUYPcnIZGGOZGSr -6mFbJyMjCPMiI5O3PpKR4Q1PZGR47kstMxmZhMfVycgQFOL0QUaGQKbvSEZWhNXdycj4fT9RJyMz -wUxGppPK1cnIiqi0aicjU4vSdsxkZMRCRjIy7LgT7zvIyFal6WYyMmIxExlZUa3lQA== - - - RqYQwpOMzHrFOhkZozqRkRVlhh9kZIpsbMtARlYSoDTIyLBL3mRkSuuPZGSFYNJIRlaEsv0gI2ML -mcnIlGSbyMjMEn6SkZVgtksyMuzoNxkZZ89kZCzvmYysHNcnyMhmqRb0Ee0uTkZWoiprIiOTdCIj -k2QiI0PyJiNDOpORFVU4j2RkSN5kZGygMxmZMv4TGRkb+JuMTNKJjAzJREaG4EVGhnAmI1M+eSQj -Q/AmI8OwmMnIkMxkZBgkbzKyIo6WgYyMxM9ERkbN2IuMTH1XExkZSfqZjKzW9gkyMlIzMxnZkB5w -MjKKmd9kZFX0MyMZmSQTGZnOfJGRIZ3JyJAMZGT8fJORUaw6k5EprjORkVUaQl5kZEhnMrIqWp+R -jAzJi4wM4URGpkGcyMgkeZKRVWU9BzIykmkzGVkVG+pMRqYywomMjFkyk5GpDOFFRlYUgx/JyDhz -JiOrard9kJFVw4/qZGS03s1kZHW9PkFGVsvyICPjzJmMrEIq9yIjq2B4TWRkvPdMRkb95IuMDOFM -RmaSgYxsOi/JyCSdyMiQzGRkSN5kZDXwmZKMrC5BXBNkZCzRNxmZSjknMjI9xURGVkWB+yQjUyPi -SEam8vuJjIyuxCcZGTpgIiND50xkZJSrvcjI5JeMZGQlGl6CjKw6GNhIRiaFM5CRsTZHMjK1TbzI -yCSdyMiQzGRkVUCcTzIyNVhMZGSWZR7JyCR5kZFRcT+RkVEEP5GRKXf4JCOzcPlARqa2n5GMjHzZ -m4xMqI0TGVkH+3UyMhGHv8jISObNZGQE32cyMmOxfZKRGeHtSEamWP5IRsYt32RkKi+fyMjUbjOR -kRnL+ZOMTNm1iYxMkomMTGe+yMhAhJzIyFS9P5KR1WBhGcnI6lYfZGTWXDKSkZFNeZOR6cyJjKwq -VDeQkcEp/CYj4zlmMrIqM20kI1M6+UVGVsUSNZKR0cAxk5HR+vAiI1PR+kRGxkPMZGQ0ILzJyKrY -UUYyMgZ6JCNTu9WLjMw6twYyMgQzGRmSNxkZ0pGMTA1JExmZJC8yMuWsJzIyy3GNZGTq33iSkfHe -MxkZq2EiIyMd+iYjSxrlJCPjUhMZGY/wJiMz6UxGRnJ9JiNjms9kZOq9e5GRkR2eyciE5zuRkYkz -/klGRrp4IiPTlx3JyJi/LzIyVsNERlY19QYyMnT2i4xMNT4DGRk1ohMZGXv6k4yMiqKJjKxKJz/I -yDA1JjIy8q4TGRlHvMjIMHgnMrKiFPxARkah5JOMrGzrTEZGEGYiI1Ov8IOMjNzRREamEMNARvaK -GH/OcPS/HBmZCgReZGQqEJjIyFQOMJKRSfAiI6tqZh7IyJiMExkZDYQvMjKEExmZBCMZmR3xICPT -tUYyMgQTGRmCFxlZbVeQkdXtnMnIULsvMjLU/0RGxtKfyMioA3uRkbH/T2RklJJMZGSs8BcZmepL -BjIyVY2MZGQIXmRkEo5kZBKMZGS6zpOMDGD6iYyMEreRjKzRW/IkIxM+mGAg4yj5BHsnI+uCgROr -C52MTGDwuLRONCbI/b3/tnsthkYUx9BgNZKRCfb5QUYm2UhGJsFIRobgRUaGcCIja4AojWRkTYb4 -TEYGPvVIRga2/0hG1n8PZGQpDDIygNNBzw4yMjDeFUoYyciE827hBxMCy67AdJCRpWAa+BAGGZlh -2JckI2PgVe88kJEJSZ/KnmAjA+ZeFsLIRiYhLf5BRyYBxpjTkfFb+8pARybZuiYdmagXcImDjgyB -TIiRjozZomhO0JHxuSY6MrEzPOnI2JUnOjLZBSMdmRoLX3Rkkk50ZEhmOrK3+vy928KfREem8NeT -jqwoLT7QkRE6m+jItFk+6ciU1hrpyCQY6ch02pOODOFIR6bQ5khHpvakJx2Z4oAjHRm1DBMdGUUQ -LzoyIComOjK1mI10ZGoKe9KRSTjSkUkw0pGpROJBR0aSZKAjU7vcQEemHMqTjozigZGOjKGf6Mhe -X+zDZ5wUyl2+6MjoDJ3pyHTcREeG5E1HJulER6Yg5kRHVmSWPunICJHMdGSVyMhERwb+0puOTC25 -Ex1ZFe/5SEeGL/CmI5N0oiOrwkIe6ciQvOnI1D0+0ZHJAZ3oyGpAYE90ZHLQJzoynIGZjgyX5E1H -VoXkOdKRUUY30ZGlYKQj60LP16uhf6Ijq3sGfgc6sqqcyEhHpkK+gY7Mfj/oyFIYdGT2OiMdGUrz -TUcm6URHJslER4bkTUcml22iI6v7MdORCSn0RUdWtWJHOjJ88ZmObJAMtRNdGnRk3HKmI2Og33Rk -QoaY6MgU9hrpyLpgpCPr0qAjU63hREdWW2C6j3RknDnTkSmGNtGRVUUjnnRkhAgnOjLCszMdGYHY -Nx2ZwrMTHZk8sImOTFmJFx2ZpBMdGZKZjgzJm45MIdSJjkySiY7MjnnSkRE6nunICLzOdGRvXfk5 -t2dSx286Mkq0Jzqysh4POjIatt50ZEhnOrIi02mkIyulfoKOTNKRjkwnTnRkuviLjszS7wMdWVHf -+EhHVkT99KQjwxue6cjIrc90ZCUpFEc6MpWtTHRkkkx0ZFba8qQjk3SiI5NkpCPTLV90ZFTOzHRk -fJKZjqxLRjoyk450ZMUM7yAbo172QUemap5ORyZYrpGOjELDNx2ZIBEmOjLVyA10ZJSgvOjIEpUg -jyrHg47MJDMdWcqCjkx1KRMdGRU/bzoyK1kc6cgwqmY6si4Z6cgGqdORlT2AQYNmjNrKNx1ZsQDU -OR63PujIVHn0pCOTcKIjk2SiI0PyoiMrioCOdGRFsc2RjgzJm45MwaeRjkygcBMdmc2PJx1ZEYL1 -SEfGYMx0ZINkYCAapE5HporUiY6M0rk3HZmaXCc6MuEFjnRkKRjpyFIYdGRcfqIjYza/6ciYAjMd -GR7CTEfWJSMdmUlHOjJKH2Y6sqK+tycdGbXeMx1ZiZaApCMT3uOLjkyAjyMdmfAdJzoy9aS+6MiQ -znRkSGY6Mh3zoiMrtT3oyFTJP9GRlbJ9go4MFTfTkalAa6IjGyQDHdkgFR0ZV5rpyLjjm45MzzHR -kSGZ6cgkedGRmXSkI5NkoiOzt3zSkRXBxY50ZGy2Mx3ZIBnoyLo06MiKggkjHVkR+8WTjkzfZKIj -M6DdkY6MEr43HRnLZKYjE2LpREfG7vCmI5P/ONGRUR0y05F1yaQMUhp0ZOXafjzTkVk7/ZOOzKCF -Rjoy4BNHOjLy3286MkMyGunIIkM80ZFhlM50ZFUADiMdGb1Cbzoyk450ZCYZ6chM8qQjw2Ce6cgk -mejIJHnRkSkVPdCR8XumI8M8f9KRUbwy05HV7XjQkQnn7EVHhnSmI0My05HJtXvSkQlFcKIjmyTq -NNeJLzoy4vcTHRkt2CMdGVHyJx0ZftBMR4ZkpiPrkpGOzKQDHRlDOtGRCVzkSUemqp6BjqzWNtOR -Acr8oiNDONGRCbp5pCOjW/pJR4ZsoiNDMNGR6YgnHZkgV0Y6MhJnEx0ZBTMvOjKEEx0ZgomODMGL -jkzCgY5MZ410ZLruk46sym0d6MhouJvoyCR40pHlUUlHVrWTJdWYMLcfdGSVSPtAR4bamOnIUjLS -kaUw6Mjw7yY6MqEXPOnIKKSZ6Mg0QCMdWQpGOrIudDoyIlcTHVmNDMJARybZSEdW12WiI8ODftCR -IZroyCQY6Mj0+0lHxoUnOjKeb6Yjq+v5CTqyVL55GEAJEx1ZSkY6shQGHZldvtORVdV7zHRklBVN -dGQWcu10ZESp33RkhIFnOjKFXSc6spSMdGQmHOjIVM050pHdZtebjgzhREeGYKQj0+8nHRnCiY5M -gpGOrIpi8UFHxpcb6cjqUic6svg90JGFKOjIlC4f6cgqFtiTjkz750hHpirJgY6s/x7oyFIYnUBV -cGwDHRn67EVHpmk10pGx8ic6MgK9LzoyhBMdmQQDHZnOetKRSTjSkSGY6MgQvOjIeKROR6bW7oGO -rP8e6MhMONCRqVpzoCNThO1JR0Y9xUhHJiDZkY6sqjXwQUcmKNeRjgzjYKIjI/b4oiOr2zHRkSlA -OdKRqTbpSUdGyHqiIwNTYKIjS8FIR9aFTkemSpiRjkx3e9CRKT0+0pEhmOjIJHjSkUk40pHpOgMd -WVZdjXRkhH8nOjLVDP1/7P1JsuxIsmWJTsVGkAQpULW9+2fx5pDj/2cvZhZhVjyKIAqyoMhGtNzv -NkCPKgopuNgr48i2kHBkW3Qc2YgcUoDGFG3+4Miowso4MoXdC45sUFnwgyND/MWR6bMKjoxVX8KR -rX8nHFlogSMb9GpkHJkGh4Qjo0oVHJnW+QVHZsIPjmyJgSPD+S/jyLQz+ODI+n1VHJlKcQqOjB6y -XxyZAgMFR0Zf6Q+OTK1NBUem0FfGkamA54MjU0gy48jUFVJwZEsoOLKtBo4M84yCI1NP5RdHRudQ -xpH97TZ/cGTfCPG/GX/+vziy/304sntYTXSmkamESSNlaKqve42cQ9Lsduv8hCK7vSRkkcger2da -ILLXbdUKh0z0ZEhMgSEDp6xlXVDIOGK8GUL20nGxEWSvt+MsAhlr3eupALJHdvXeWETQQyZCnZCc -19LiT/CelT924Zl7bf7YeVn9d+DHptdDFPqYueqNDR+D8dHuzR7Tsmi+V0WPjdsGxEUe0xyuJW1w -x1TOe5+9Ysck9nts6piEc9wbOqaPme3KzLHpkdCFHFNaSjEvp4nJYkIBngQc04breJ/FGzttBbtx -Y0vIbKslLtiYipXfzRBT/WH/IY1pZaOHZx3zWCBmYcbUX3DdFTImTd3KizEG9XQ+GzEmwmnvdyWM -qUIIG44gjPXLdq8LMKYHZFyt8sW62ystvJjWX2fkO/VQbyHhxZYYeLF+Wu/Kpoa5DViBi8kppJ2J -QXZsqNhRr/ixMGJHvEvRXOf1ERUidhH03QgxVfy85w9DrJut+kaI9Xh5giCmQsr2ww9Tm7PbORg+ -bFiYYtPDhll1V3jYa6VQix3Wgy0XTLDRrSWvoMOIzeshD1HWJec9FzhM/eNr2HJMzz2sRTKwYfjg -3Peihv0OjzGtWcWSXm8s4gs0jLvBhQ9oGKntt80FDeNJUplmooahKci3qGH2vPVnU8PsSe6zUsP0 -wFPwu6hh9l7MZ1PDyDAe/a7UMHr1tNxd1DAVqpCsW9QwzKeO2xKZixpGPvQ4z40N44k++72xYcqd -UUyYuWGIfG5ww1Bm29gwCXbdMjZMKvdkYcOkkNhY2DAKdKZ/+EpWKU3OvnVhwxq+Pue5uWHfW/mf -uNVaxpLnTdwwiRaYd24YR+leBTdMglkFb2wY1uEkkR08scyGAhtGg6sNeMshocFCS9iwRtHgXDww -rfN4GzM1THEitcGEpqw2PebODKPbQpZ0CRkm6zL5MgcxDCszbYYcGCZrgfcpuDDlSTUkBi2MsL02 -fQ4LYz2oJpiEIFAxv1xwAxWmxJ7iM0EKu+6IBa2IlIwBaTNxThg+PEdfFJ/4d6aELQ== - - - zXNWBvqZi/6ld1uzQUaEXa9RCUKTZZAcrQMQtv69+WAhOR5MX/5tCw6m+iJgyokNdoWBp6PB9G9v -IafVTOfoi2Yw2OVtogG9utz1JLhg6hTRm5+xYLwwqrB1KphMnWTxEVCw9e/EBAstkGDaLNIa56yv -Oy5MAoKpq0z9pKHJbFxrtsCBxb8zDSy0gIGpEZlCZGeBvfSSFBIYpefixjgITIHFYcYD9m+PSmQM -mGKxrWnZ5jt0OjK1CQ0MGJaeGq0zBoycgpY/gQFjnlMBeGDANMsYazthwCRyR8PzTYL4KAsD1jAe -vgsFTBptTkEBQ9C7GxQwBJDkyT6zEdof18KAaZwgzhkYsM/IFbOXykeoi8wYMKUFcaEIDJhqZAih -BAZMFjy042QMmMSmPvTAgOHcQ8+/Y8Dw9zGvmY0BQ7SBztr3dVa3oRGXPT5XA2emgGmDivV5UMC0 -GbYaR6eAKd1pgbtEAev0Jd4LAoYLpaxQF2XwcNuFDAFj36u9ekDAEJSxCAwYgs7PGDDExQDjXxSw -OwMMQTukxADTb2X7FAwwfWGucDDAlpAYYEsLBpj25Q8rnmB7Ka9qO4rMAFMOmfF9H6dc1EFLqkPA -VCjVFVDLEDAdRuNwQMDgwTIDB9wLcvBNf2WCgLVo01yqLgPPdkDAlpAhYEsMCBimJTSrOt6r4aiK -U/OO18sjzTpY4yjlrOZYDLD4d0aALc0JYJRN6ORgezXGvvMpBDD9fQKG66jH2srCORs/kGYbxwUA -Q2QucgAYCObXMGHkfBoOcL0XAJhEduYBAJPAexAAMAmMzxkAhqNrN+4lADDAwXrMA+1lT8Q5CwBM -Fcrnkw4CS3y9i/+1hQQK2qKHmlm8apAJtJf+Ot14GQCm54Z1yjrqdTuAAIAtIQPAlhgAMJgEWgoG -24uVrZyJEv9Lt5rF6zpI90F3CwDY+lcGgC0xAGD6ELNjcrQXhSUYve/6Qfx759YaTipzLPyXedco -Lbr5X9jVNGuZ8Y3b6Qaezv+SwBIi87+I56mIMPhfEvBpDv4XR5AqTxZhRzMj3OB/yaoHH7pAe8lQ -hvxuBoDh7Kue5nXQGYtRt+RbQuJ/bdH5Xxr5buu3MP/OdpiBX8Z/ddpfKFixwQnBCN54MGF5w3C3 -6V+mQVx3/qxOAtXk9C/7XZpMEv1L8wfTTdC/NC2yMgn61xYS/WuJQf/q7GL/XtUVv8Z1T8Xdhf7F -5Vfsdak4LFF64GuI13cQGf6FN5HWZwH/0ovPUB5YL9En+OAM/2KXowXEOkqWf91Nj9hzhlDe6RAD -/kXxnpaqAf/SrolhKsO/KHXWMiPgXw0fWzOnZ5ndoNneb4F/ER3QlXCwF4Yfx2gZ/oUBgl6ROOY4 -LPYf8K/3tIxUYn8h3fdCf/Hv81nkr9chXRn8pVy10nXB/Xpjie7YL/2bFstE/Xr98wL6hfnqewfz -S76u7JQ28ism8CB+4Wz63gv4dfneNPO+zFBp0b4u74YK2Jc2MnpoMuvr8vVMoL4ujzME6Uvn6MnL -oK/bA7PB+dJ0pz1oYL5wnvobNTLl63GP76B8abLTaxuQr/h3ZnyFthBfbzzYQfgCb0TEISO+dOPZ -1y7El5YYZwSI7Wn9G/Z4FwviS6rNhoH4wvZqeqWWrcSpvfcC3GXxLZU670B8YUvBV3PGF0fE67RM -k6hVGBT4OuMLN6zLbYhhfFFMcvaK+Gq0oSbClwSLAwXhS4VKLL8L4Qt1+MfZVWxn7Ggsz8pns2jM -hC/9ABuHnfD1vpvtRatgonqpDsI9txtTzt+VC1IXzl6zFZwX7jB907w0CmijH4Ur8e/C8lpioLwa -GHJnjuHaoO9qJnWZ5aUH6H3cJNqO01UhQBUsr61klldSneUlpxtmImd58UsaDZWJ5YWqjfBieUmx -6FuwvKTM4+mV5UUugjhvsLwsO+GPPywvFC1uC8tLqtYVgfLCMJeyeod0Nao6nqugvNbQuo6SRxA5 -FEd5LaGgvLYaKC/7+CuBu/SQt7XDjsJ6enpmz8cp/DfcV2VF59ieZdsGBfEUJXSSF7E/1fgFyWsJ -heS1VSN5WVwQUJKRvDD80oxYSF7UkHK/guSF4dcdx3EHzQyqV5IXfl42NjjJy4x+73eTvMw8jN6r -RPLSsEaoaJG8qD+1XJOTvLaSSV5bDZKX3oQx5iZ06a2hSqyQvGzW1OgRqjAv/d4cr/XvRfEKJQg8 -6qFkHAk0l24jKeCC8OJJI/+2jlNFm7rAFsOL19HW7InhJdU3ZM7wQjmd6kWghDMP38guhpe93tfc -DC8phLUWwwsTozc4X8Hw0nfzM2PIVtyUZFIwvJKSGF5bDYaXLK1sJgs21+vRl4Lweg+DEizxmWa/ -EgAvbbUJLGd+lzpySTQGvuuOpEfQu2TnooVZhnfB3Xj/WegubWyPdm0iFx7h5GUTuAsLwnNsMbxQ -FrZrC4natcSAdilzoPX3gnZpI/Ve1lkTnRDKuzKwBrJLxb3acCxi1+WmWAXYJZGLFLwuuBOHw7lY -hfk2vNC6MDG8nn8WrEsVdGGkTtJhCxvVtTUjdWkg4zkOCBdmlllgRXXYwLxE4M4Z1XV7nLSQumTI -SyI8g7qoqT3s5SPacEW6JzBdW0iUriUGpOvx2N3qCMDyblKZn3oENKZZBmIdNyM/H4yurWRG11aD -0aWHBoflxejSc+VXZjO6SAETwQpGl4I7uAstSBce/IZ3SJAuBhHG54B06UzM9AukS2EOKrUXpEud -JOy0FnwLYND5VkZXj53lPgzDHZZBHuJfSmZ0LdEZXTTJaLR1Rhf2vfpGmdFFD6DKM4PRpQ49yvKC -0fWJ6ka493/K6GL3phV1YXS1GaG8YHRx3KB7zhldOJoyAmdGF0/fTRuWM7o0jvK2LUaXLrvVfmRG -F+psG9FFm7wiKAvRJYW9YkF0cRcZLgPRJYVcUSC6iGf5emQRuvTssMZciC5BMFq/N6Hrb376p8C5 -FE8jIL7gXGqjJ+2y4FwiUWHLXuBcitBaANLgXIqiEAdccC7FHwjiFDiXVKuecDgXhz3OxsAMU6W9 -JMkznAsVswTvuCa8YaliZ3NJIaZb2FyE+hldg81l1WLXtdhc/fCux8LmAiXArB5wLsV82PAuOBfN -tzQlJjgXtu96ioPNpU5eSjqDzdVJe55vZXOptddKEYLNxS+yoKrDuXSZbamR6VxScQBceC4pBJMX -ngtnb9bhGc9F8Ev3dOG5UDRxLDwXCnuLjOeSSnhs4bnoMGbBGHguXR4buDKeSxEmK3oIPhdUghsI -mfO5hKLEh7jwudQIyNu9+FygC9p8Np9L15anrQC6FLJie7AAXZ19jsqhHdCl22WYmgzo0vxgJJ8A -dCmqZW4lGdAl1SyqA9Clb45z5wJ0mY3F8VRAF8YVpNID0IVicTIHdKFcV+VzkUGA3hqALn0Jxqjg -c+k8q4rIfC51HPoA4XwuqjmYDoPPxZjBoj8DumyQHOcmdDGQ2njhhK7vEByj979H6LKA/tEKoUuP -E24iTujibdOuPAhd/fCMWiJ09bCnDUKXBMIVQehCUMwqE7oQ9R4GosuOOs/F6JKgsFZGdDGU6jkP -RJduC2UugeiyipmnFUSXxnHG70B0MQcpih6IroYLk3n4rWfeHALkpuOIrkYH7vMsRBeCEq4Z0UVh -1j0WoYt/v/bHIHRxlsIQmdClNQreXkHo0pdmaRaYpc89+8+/+FgoIvmOyueSN+mN6bFby1Gdgxuw -07kU19RPLXCuywuGF5tLwjXejeai7ueelcyltS9hvwBzadKOwJbeavE9LLmdsFzKFWktv6hcsldi -CedQrotWv7cyufANfedGcundexyjRX0aT8r8IXJdbkCxgFz68OPZOK5LbpfvqDSu6zF2y4Jx3Xc8 -vM7i4qu2SuKSRzsr/wBxve6vGRwuhcQsypk5XNhW8x2Dw9UiM704XFrRk+ssHC4svZkxgsNFSJLd -WHC4iG4Sm8wcLqJPl5eY8Xo2I/i2zeFSfIE3pHC4iDq8z7M5XFIsYBYcLpT+g+Gis4uoamC4UAi4 -GoaL03gyMoYLXzPtHILCpetlkUancBEp4JMzhYtC4NehXrZxUNM420mncPHcsnNMFC5cshlxAsOl -22G1a4HhUpyOtUymcMmScPT5z4JwvU4kCgbX4z2kBcHFGmTYBptd9zu88DMAXO8VRYCJv6VpVf4o -C7+ldUoUY1IIqzztgYtzgm89r1f7Bnvr9ZXDYm+pvpJ6wYzeeiOg4uQt/VsD3wJvUYH5FuxWFGEG -dYvL40wv0tdaU0yvOl/9Yo8nTxZy675sRRHErd9R79+caBX00YyScVtAhsdYtC0VK5LxddqWysC8 -lm2FEaVpERysLZWPqaQ7UFvqzFIQLZO25GWh9zVAW9g0PnNxthQ30hIrW0WBt77vRdmCPaRCc4Ns -aTnztl4YW6qz1Z4imms04504g9nMPa1JIAO2VEqKqaQ/zrjZ3nPxtfTvt8+C17q0LHnGomup0+iU -6aTnEuk8+vuzma0lCN/Z5kJr3d1Bqk7WeipTC9tu3CecqbWMyYOpRVO6KoB2vlxpB96uQGopbosP -UCC1qDfWN0xELVAGHliiRp1Ov3sBtcxC/+4FqEXqi4YxB2qRyDLsAGkmrfvvitNSmOjpC6alAUje -EM7SwqxYBskJpXX56ipIWrf7qgZIS9s9BTcyR0vNFMprOEZLOy+9j0HRetz5PEO0FFPRABAMrWeY -NWwgtIBBz6cQtJRk1Mo8NA3Dmh6Dn6V/axTM+CxzUJmLnnWbO2ews+QeqUcio7MeBx4FOUutwdRY -GjlL/vinRSAXOOvxWsTgZgF6Pq6FzYIBo8hqomYJxaj3M6BZqj9VfCwcrtRcfB9nZmYpVvgarY2o -Lg76V1/ErKfbbJKBWdqA6isFL0vJK2568LLIOB13wWWRT3/6omXpJEvwOy3ruSyvl2FZajXULQtW -FnDRvkhZ07exCZQ1o3rUOVkBrApM1mnfLEOyzmmJ/WBk6d+XHUPiWVMlQ1UiZCnqpHhaALIUR9MY -sv6c6iv7WfhYEze2RccSZb5ZsTC8IY8RZjaW7A9Os+N9zYjy7/6Oe5GxhrUnZy7W8PhCYLE0ZmoZ -EVQsTQnDQFULiiWNimLnywzZvRx9IbHGY8vPTMRivBZtzIlYsovuf1NhALH4dzsLEEvn9OdePCwl -lY7+LhyWkpon7OVNw1LlzIEHs8GwNAWcCYVldUMZhSXgh35ZkLBUIPvalMAQJ5MpRQUzB4v2petc -HCzVwWuSDQwWw30vECzwzHA3nYGFKabvYpjKntce6ELAIu193AuAxRpBT10AsFj18B4kABZJUAIC -AcAiQ6VRaAGwbA06K//KWGr3xl9BpGH7mPFX5MFJYgb+Sq+rrdUCf9Waz3UFf1VVnOba6ttw/lWL -uqTCv0IlMhn8KxQmxeBfsQ6zWH7iX7Hs01u2+FeN2t53bP6VFCshzPwrzXf44i3+FQ== - - - WXI2I8G/oviDqGDmX6ESLXb8lQQKcwJ/JYHrlOhXAKG6F7taElg52JnxV1Ju66RJ+CstAkicLPyV -FIvfRH5Ga4fnBt6Q8Ff0EVqPn+GvyBPzIDr+CjtIo4Bt/FVsABb9Cl86q+By+hU7vebtIYt+xUaF -yHHQr1Lg3elXquK1QplMv2rwTvq96Vcos234FSeed2VfSbTwgKOvJDDVG/pK/7SISEZfKc5sW6VA -XxEwsZ1rRNnV9GAr+YS+kmr1ct6V32DIeHmHdQPJq3b+oq+kHrQnBfvKLiGlLi2CP7dF0zP7il12 -FFn00+J/VIgv9tXyqEvsK6ro7qtv9hWZCMqng31F4p7nNrOvGjHu9m72Fd5I/KFgX4F60TyT2Vf4 -HmqtH+wrWCbEJYJ9hT0lUZDMvsLU1i51dBeEd95iX+mtJ2KY0VcYTVptzbMHrbVts15Q+RxY1d5G -X0m0XH7EFk2hKN0tpMqJi32Fyisa7Cu8t2w8dfaVFMtyZPZVwwzIMYiWkx6+3FjsK72f9lmZfUUl -I0N3sK/4Fjwywb6y5t85KvuKWLFlDY19Rd25Vfi8UcrSLbCW2FcaACgwDfYVDr0UCTr7isib7l9m -X7GFUFtFsK9adHkE+6rhPHWfBX7FcMMS2OlXej1xKQz8FR0D1MNk/BUqMfXAX0npCX6lf1sgK7Gv -6Ct4/TSbpkjevm2zr1Dio5fljYJUpJODfaWIDnX4wb4iNac7mtlXFoh+xmJf0euiuS/YV0pHWR4u -s6+wCGRiCvaVO8v2xb6CU80Lk9lXSpZZj9vaYspKmCVCsK/IoBDdyewr46ue72ZfESPXGjWaKvQn -bSua2VeUVvPeBfuKNhNmrWBfGVT77ZV9RVaImxzsK8sTHc9mX3EmFzyzr5QVOr2P39wZ9aX1lAb7 -qhn0YxT2VXsiBxbsKxoSKPkO9pXyFFajkdlXnMnTH+wrjCEZ/Z195dHdfwr6Sl/DliSBvuos0O53 -o69I1kaZTpCvlNkiqrTIV2QZmeeDfEWu0CpcEvqKku13OcQr7CkqCkNLoK+Uk7KbndFXnYHhnht9 -petMhZyjr2gqsmbdhL4y447mo/51WjeSrQbO9dC98UmJfSXVArLGvqIPx2KMzr5C8VXLQl+REibe -G+grSx5ZIQABYDoXmPIS+Uo/m+ctwFckhsnmO/hKaUYWFZl75cRe95SnZU0PI0WSAb7SF7ABPYOv -UCnAzeirvkpLA32lp9y2DYG+ouHsxichoa+UeGVlFOQrrGMZrYJ8BaDc3CqWM5DysCquDe4Vt1Xh -7+BekarQKiBzr8xm874X94oguZYfwb3SaM2cXgooVTdDLbs1KGHVr0V7cK80n1MKkcFX2mAQkV9l -XBCyj7eAryjOUTg7wFes65+5uFeNJs12Fe4Vluk4Vzj3Sis9/nJwrzRIaBRI2KtnFXVblOGOccih -VyDTrrNAr+Q5rZVkMK8UMyBr6cyr36jtvxkR/j+HvCLl3h1ltZBXpNxjR2rOJa8HigN5hXD7iXuS -Vsvu4l3pIeRaBu+KihZ29im9h8O7KgyDd2WW7++zeFd2xHUX3hWfpck7eFcSWK0G76rRCmFt7It3 -1R5vlFed0et9NQG80mDLnjIDrzTos3wI4JVeeCJt0WmroipimBl4pUmfuEoAr1ScwaUO4JXe7NP6 -l8vbbkF2411RhXEZLom0lAQK4DLvCpG6L4/LIiicErwrPscwWpt/03H6mwt31bvXOQXJqhMiPt/C -u8KAKvOuOvuAxLvaQsIubdF5V7iNJ94Vnu4/vCsNkZl31ac7pgbvCl9hRZkT7woNipTzrhA0Ywbv -SoKm+4y76pieH23hrroqDLUKDtyVBPYamXfVMYOzJnsrVxi+dnfg1f53Al4tMYBXnaTNBl7JRfwD -vMJJPAOvZPxdgFdLKFc+xABemUv6Bl51s7MowCu82jPwSkbqH+AVYgZeISTglf79C7xCS8ArzP0z -8ErCB3ilx6UAr3TDyNgEygr/f4b3szqhkR6Igw7fnATvila6xztJll8S6uksK3hXUggfBe7qO3T+ -D6eE/yXcFdEuItoJd8XSXzNf4K60DyXqFrgr5khrhU4zKRY3Y9Gu+Dc5WKddcRbrtUS7kmhRRKNd -EcekbNMdFIhLEsFLtCvLvpqVpUVXjscB4067ahRq9afQrrTxZocVtCtirFTxOO2K9ig9aJl2haj/ -GrQrhGlMLAv3HJ5PzbgrumX0Yjvvin4aLckDeEUSZDoQK/KH2oGz2XDglS6+LX4cePW5Z//5Nx8L -ZRat/SEBr5SNf60IwYFXHMfNvZehky+GCvBKaotefttV4xEaAq0rR7RyJ96Vdk4W1w3elZa/LLAX -x0phTi98SPtjulBH5mJR9ImvTvCutAOwepHMu0Il7Be8Kyk8Z4t3JYUKnMK7oluaBU3wrth0cl2C -d9XNY7lX3hWbcopog3dFpSD5r+BdaSfCBqHwrtrrucDFu1JNmkfYHHi1lUy8Sqrnz2lhZ0kVKCtG -n+v5pxCvtPG87yBuSKUuzkL5hrwyYbiH0WK7LDWgV/2IzXVAr/oRvR0ZeoVK0XZAr1AYaAJ6JcW+ -fYZesV3r01S3vTg8muvQK/woLQCZoFed9/b0FIBFOy4v9groVVJSJcNWA3qlP3lq87RgVrrUNL5k -5hVmCGy21mEKdjH4OvNqC5l5tdVgXlG9d7sFHKFvteMsm79AXllN9TSDMDOmUtyM5zOul3Zr1m+W -kVcNkHuzO0wXDBtyDzj7RXF/w4K8IixLvVEgr9h8EfcN5BWZCIREvEJsTrOyemY1wD3ugGhdVWo4 -sGb0RLwicPrYYRZLGOHz5bwrO+Jo/xTeFQiW02tprfTv8jRN4K6+I+W/OT3PqEnMtKsZO5GgXZ3h -KRawq9NdkArr6nT33IW6Ys103f8s0tUZfaAZdIUtiA/LuiU66xrjn4W5Ot21L0OuSJ43myj0iQT2 -z3cjrqAJMYgnwhVuSRQUOuBKXUhRTkZdiyH5+j8Fb6XdPzmhoFtJuB1aRcj99PVJYVtJpEEm0FZn -xLwDbaW/9trHLLCVXqd/FtTqPCyCuphWS8hIK8R7A62UcNba0GhVd0QhF85KtTSKjZpCScPdFszq -fa1OMKOs1OdPTji83B8vNwhGlaxpFCnJIKvouQ/tOsKk0DFWVxhEJozV0gJjpVIRkmVBsXqeKABN -22jK/mbfDCuivIRjgmG1lcywSqozrN4nOgQdTaXLZM3ziWD1eOp5iaotp/85+FWPU10Kv+qJQHXg -qx43BQl4lXDzNGpkeJVIrBaac3aVunIs7+zoKoUoSMlmctUTCVsHVz1XZMidUsWD4Amdha1Sxbnl -251aJUP+wDdZQcASEqZmi54OVHDaXEM9m/a3YKJ1rACr4KyOtsXbm/cWr2oJmVe1xOBVPe4tszhU -r9vIF1wVUf7HG+VWkYC1OgSuaisZV2VqM4Nad5TqURXsGCqZPljaOdGq7jusLl2M4vjFqrqWo2NC -VV3DplcHVWFR7laVeLrBPXiiOdkvptgHtJcHpUqlX/ytgFRdI+aXxKi63GU4CFWXm/UtQJW6V7lJ -mU91ul/IEs/hbb1Bp9pCglNtETbV6WZQC00lB1m6kDKZSl/gtWprmr5V3xVuoWCpJLBczlQqxMcc -RNkunZHdDyYVP6xZ1fNCUlHDcNn1oCh4WnHc4lGtfycaVWjBojq9Q2u1j8qQioB77jLVlQ+vJDaI -qlY0Bx7nUGmzwjOWKFRiFStyHAwqSM7Hs9FSaiyyj00EKjZ0FgR1VSl8L+2IsWop+dXeahCo9LJE -eQQAKusFd8+KBaAye5sxN4BqFfssBBV+2La0Sggq89NxhwDb3keetjCoSB5lUaUUrblLKbOhemHM -1CsjqEztoc7bj3N/YJsSUK5QnigzMTuoRaBCoGY1CFQoVv6QCFTkgxnbHUFFIwKjdiCo6Mzn8mQI -Fe8Gi8eAUNEAyJkBocJty8xhEoSq4ZHhVhX2Cr+e8lsQKnZb+tmJQYWJXfMWUxhURbGHQed5S/BC -ULXXp+1AUGkwsGy4M6jonr6dI+gMKnoTuGnBoJJiu7RgUG0lM6hM/VvKvWtf4l4CwaDCFuOx2szF -oKK8RmvcgFC1y3tKA0LFuHraImVBqCSynw8IFUbABqWydb/GcioMEoRK4jPmYlDxtmlXEQwqDtC1 -zQwqvEK0vAkGVePw+14MKm1MmQMyg0oi+7FgUGHarbKeYFDR1q/JPjOoELXuCggVp7VnWRTyue5Z -uBdPmoX+3o9AULFHoVh3RA/vYZNERlCtoxaCqjFPub+NezrbXnIzqDQbWzWcH6PSyKeFj42PJCiZ -QbXEYFBpGUF8JOhSdOe3qyCocHXQRVsHuRdCEKji3xlAtTTnTzUcNv9uZ/CnWkT0E38K7XgXfsrW -fecb+CkWiro0iT8lzV1hzWt8ukl48KcQFEnM/Cl9Mq9m8KcscXz1DZbSIsy8oBJ+ag+867AragiD -P7WUzJ9aYvCn7ONn4KdYOutFzvwpeugUkAuRv6o9lfOnFDa2TVvmT4Gd4okJ/hRh0OaRblsEhZL5 -UyaOsfhTlFNq2Rv8KYslPU/hT9HchI9BNPvLz7T5UY8ZZZCVyvwp3DPGXPgp/q2ZO/BTdC29RrFa -+CndOHbpjp/SdeNdDf7UEhKAamlBoLIgHHB2H+G00qLuKBGomD6JAcZRKlY0aLSN9uvfiUC1xGiU -UUiHSq1gS632xUyg4slSmco6SrUUYHGdQNXc8y4DqAyvcC3+FP8e9kuthU0nYRuRAFSI3fFSFA5Q -7q5MuQOojFchWk4CUOkb2WmxLycD5wCq/e8EoDJxJFdQIgHXueBSBLxoxk0EKusbvNNRj/+eQFBp -oc0iNSOoMBNVbXAgqKyRu42FoFIkkKVZRlDpKIZFR1ARLnzbYktRIiRXqEygUhCZ27COal5QGQSq -JWQC1RadQEVJyrSUMQFn/ppe5YygImGtLXYgqDq0eQNVDTMpVuDwfAqCClFXKRBUfI7ODwbVKn/K -DCoMA7plo1jVUb2jYTIYVFtIDKotOoOqR2Yn6FIK/hbBlkmPDb0hKg5Odj4YVOpNt5a2xKBC1GXN -DCp9lgbGQFCx5gP05QyqLSQI1RKDQqXdDcuC7c6pJ1HrimPNpPzRg8Wdh/a84gjhuQuGaomBocK0 -jvbyFmvJ1y5DxlDRpaCAVWCo1KVPx3tMaoeVcWUI1TtttA0G1etmw5lBpWFf+4tAUD1ecxt0qb81 -oVlSJgKVQsgsi1xTO6uzp9jWJOoUl8F5U2N5qjluanr9d6FNqQ+nu6PVtDvIKipQU5/o7L8Z+v2/ -pKn/faSpSc2dknGa6YM1JRV421Kl/L39tyHTaCVVtXdToSvrCH8IVRSpyod+h6F6e41GKFomr+9U -n+RBN9exoVNzqPQZYmKzCRllvqaQAbdjRGBUUe7r5zWjlkujG2hSivPYUSyHJs4qtw== - - - fdJiT03t+wTT6xHnmCA7VNWhqqqHn6116j/9OTd8airdNo2BzaJ5MI9B17zZiQw1NwtiJ2jswk8N -FUCo6EPUWNb1Ah5y8Y0oCYCqefmNCF2bQEV7ymOqIahk2HQanNMhVM04eVIShaoZJQ6Ip2GoNNkA -wzyDQ6XC2GF/M/CjeAGrf1hduI3Ph3Mi7qg2kv9AcD70OfZvzhJjR49EHKO9icjzYpWSjtlKbxuO -lNTpQGAlMKaahPtltbcCWDb7xddGUolHec6WVH0v8H0D6yL9H215hR8WonOZeqKK5iqVwDTKEMhI -DTd6LqV0YWdRwlhVqpJGEskBSoCBOKInZ7A9vo1ju+BU+mq3GNNgk43nOA3HKgdT6FRLOBOdKkT1 -schOUL96DGFyaXgQR5U6YVmPh2LXVUnm3tJxCmEdTLCv97tvJd+RpR6qcPm7bzPeRWeu6F5fXD9V -9wa3SqDtLkw4b5qShwMKQzOOY5CrEBuDwmXoKhR79S5r8YBnLn56pw4obnhQzrul41+O6+BZMR7Q -SKCqs+O1v7laSYfqqwY1O6+1+A6yWuLXaCACYQW+vG2BEx/N0D2pk4r+W5yM6QWFMlFeY6ETZVCF -WGXMlOuWFPsO7KteU+r4GzOn1SNNxSfs2To3zGqqIYLboW4xReTni2uOPQCyAp3YVNmFDbqcJPGz -B9tpgVZpJ1crqiqptZuT8j7d7ttiWc3nsqsu1RakspNup51Jul/KELiSM4NlNd9mnGCeRe0kJq5s -0G3d53fKGuMQFTb6PRhm8aa6GC3x25rqDjiHjThYDE6tDLsNs0GyQrto9G0W0ERhjNAaSG/6JAKp -N0rjc6CspA4hWAVRJuAqBf4sil1mZRP11OnMyF7Nl/qAh2/GyvTvkP9HF093DZLV9z7+x+/zVK2u -XmZMtgJlJfVvovH6GcVtOO6J+hltwaUA410mW//lZ7b39koe5eMnxgJ3c0UAHTwXXz8zsqBTAAZn -l2IZM+Wv96BM389oR8FrGgonNllpUQQSx5kB+O3kET2Xr6y0bsuW34uarBTm+5qXLXY2KIClFQUE -FyK05PtY++fqQB9sMG4L3OELKYX5FYU2Zimjv15IFQOB4lV63FQYYtsRqqVvjIq8crQZr1c7loh/ -TfnTtMtqpQj9Tgh8x+XoA1YQS7k382qrWnIowjtFOTjxi5xWzMVAoLlmKfY35WaqqtR9nD7its9i -3N2KKDs9FmJLPEn38JP0sGCep+z51PbzxWCv50FLpdlioKsxULk1lHHZccQ6OZNvr6rD2HNLhVML -U0HxgUmZmW2+bQFHX+RjH76izoMXT2Vd+mo3N06BBNX+N/eD3Urvm4uV1NeKXfQXqHHulL8obMDO -50iKLf4U8lB51jpOSQUtLjuYNxVKLqU9m5GV1NMoWRN/cGdyEIKZ6i20Dpl7o7J05t+x5o5nF0ih -KT2g+5LJP11weBwIA5g11SUlBLu+CQGFqVINdbnqOGIFU8ZZ07kbi5kllWUaZbkaXaYmGoHkdSVx -3JrynHiHeZUubJZUKo2lUt4k5W9haeX39hhfGDGcpgQ6S+pzuPcpBVQoDBDdQa0o2iewonU3kkln -gcrLmrf8akh6LieyQKD5DpcxYZ7WjW4VdwugdSo0wEj0uhf6eaq0zFssqM84qVu/TFntllJba67q -7T5ZkkwrhyQuexpjyobphdFCjeGVxQxndh+UMcPj0+fwQTm8+E5KEkh1Xc4QIvAKh+/2CMTZjFxO -Ii2S9/pVf+/5NDNITQY6EwT2Gp1OhWfPALwEUUvqeXdrs7CqXind/k00UP9u3tO4kFqox5FMNVHG -8Bf59E+6hCDFxPPYF9Y2iZhT+S35G547wxwdsVuhQ+tev9HE050WTpXCsBJTHpP4njx8GK1C4Uzm -4ZGPU300ONrbQ9aqPb7UEXA+G66lw/5WNg/rM4o8zuZQ+q6OduqQtUe6FQsOhT8p20/10u3j5rB9 -YgeYKxrTUsa9+Vpbla/FSxhVOWtKPR1/pR/098FtK5x5sMh+03HKe0xZiKqstj9ZUfV5WG1tVZtA -bdNPKn015qiuj4oujaS3xq9Q4tJemrDTcSqMeGxUwAD7xDz1NUfTxdtCtUlwWAwMhcele+erFIs7 -9LGRWyfOp7e5z9q11dLxVFxXiqKQUmzc12eFpY6+2+yPP2mqlTq1N32GTbFk3+1hOd+t2Jl4pJ35 -OI24t81JVAYmJflgbFWl/NqEn+oNPsygllFNX+JvbfVshRM7YeQ3HadtrraQ+gP4WiUlOa5vdbhl -s/7CqzVvn74sPNVO2mnOOTeFSw8CS5UQtbOYijXpo870T03EweHaqtqguXFyGeyPFQBQ4nHC91Oi -JhT7pjhZJlUvzjzcvxmnqVNc12Gh/sBxnbQIX2ZgzDpIyjW9idt+s7oiWcvomLDWkvqcr70k/nq9 -1illEfDbznzoBjraMhk7acqfhtOzHzlw97hYShpSklzGswV7oR/byqbDXruz5visR2IpwQGM+cBV -R5lo8GQdp8Uqk7D+5N2b+WwuPBdfBPizVAY4BbdxzcDkciBMFtbPtRFdpnYzXGc1zInyt5Zi/Rr8 -Sr7WvSldmphYGen6YDOryZeVEUg8Gt2Wos6DAHUltVnl6clu/Tb3jwNe7GWxjBDWHbmUAIijBgWm -0/3I1WoyukW3GhAs31yech9/ppEKjegnbuOLjeVpe4VT0Tw+PJT/ig1573Or2kwRe9FTZtvqpeRh -YKsKteMfKOcwRfv6dCeQqVKzSzOtqs0C2TUVe2NlA51AW2ZKjkkthkWdes5YWo+xqV0TEzaNKzKq -1rqLaMm4lvD/srw8/F2Ko8xw42VQYaLVSvVvbWo9lMHuQmQwRVSAA4Upwt8tU2Yo7psvlS61rpAu -OUAKtnQtpHAVOx0aL8rieE3Rll76MnukwZSOPOyzaLmULxgXR3VaT/zF4UEhvbODjcHwCUJuhlz9 -wzfpfTyb6TUBRNHu9thmRMrdaDX0EkD2aXrKpCy0l1RbWSnLMm87k1epYxrVTbkICakKLIx3poBb -87SxmfXK1DSr+GtnEareSiWt2I/q1QjMlzY2XTOj8pq4mBmfT52BTtlJyrPxAFulskuWaf3xuffx -Pr85HpscKDSOvzim7fUVbrMTcWe5OlFAe9Khecmn7Lg27Etqt7j8ZdXCUgYhqiO2Dhr7z78HljPD -zkjqqZCGfV7jMM2oQ6grbYrBoRwPwnKImlpmTQWo5XZ7EdBQaaxu5et+un9fTiO+KQv2JfVvZ3S6 -Ouw4lv5SoH1N4V5YJmOD/oZpKECKC9WuIvxiUb+1GdCvnq+vYiUE7Us/gXG9v2ZIMDF/04L78fXG -VtSbHQHqrUZh/2SCe6wFGBe4iYWEdqih2AbssCD7Pu6CSnPyrFgcainXu0FgW729elSbuVtvhKro -ZGEx1W1TFLs82lLoLd/H6TJqaSuXbfNVXcrcILCtCnGtfdxUvy3LR+A12j4etoXT9Vl9YqiPdqOq -a6aXGUChqjek0LismD5XjE6Svs+0gfJ2y0EU4tF3M5MLFNbgqj8LppdUW1+HTZW+7MPwJotSHad7 -YvvmUOzJixF7HYcjK1HkyynnS+ECRbx3qY8j5O0vaE//unWM9uGNpWgo/+XxZTan+zgFUYllUyfn -IU7bXsqZIvwsFAo97bm6yRQTQ721mGYDpb7tpdznZoJtVTEdtXw/ttcjjqvnSRU3mngH9gC+WpH6 -EHWWenQ77u859eO4l2f3ZJaCQ9vMt8cQ0o0JhvKQOByW9OJMrXNQVrhTNoiaVIcaT8QE01W0DGBz -t5mt9Gfb525VFR7KVeilGJpclDkB9jW1nNNIGcJ/+Rjbb3lErsMwwVWGbHp9/FbGHRm5pDVLeigX -OqbGZoFpcCtWP90gY3btpBOPnbKj+zjl/jU06+ObB4NP5Zzt18TUpbqB+zQV2z8Uwu3hJsaZJApn -22QwVL05yhWwXJdyaZlmmazT/ub98rP7JoPN84gzDzNpmkoXkswbl5HBtqKGpL4GkVDbZfYjUy6V -mvWUzYX5pZgcgaWl2KWVB0vLxzWq+F+SZhgUK1RAhtACwL4S0WbO8sC35fNm83yScnAWzNNDr3Wg -MqIBCJtwAd+TE0GEEdskwXe4Nb0C73+jy9yKxUlnPLGuDnpn2rCkiDBhSbm228JWu9c4DUuzWZ6Z -/Ip2eu9lScu24s+sfC3xbEmX4/Dr1U9LnaBoGUXaN7auqFw0/XRmkoMW0AvlsiC4MgHDEueLF6bU -wSsrBKn4SCkB4N+iW74tKceyNN9is7LEQWKJK0H5r4Ln+d/2PaeN3+moi8IAfbQFVkkU6FFS9D6Y -YZPW6Dm3+v/zj7MsYwRNWHd2JZGGY7STMjZMaqun+19ORXZ5vf4mCKtsUKfkVDowlJi0Lae/jnui -wmK+BmzaSowgNkGEqmJLqsHEiGB4Bi2tiVCUPi7Q8S5ymHJ9VsrQukPkZeilB0xLY2X1FBz5W5T6 -uifAYQrEMYh39ewIHKYTX60MuworAhx2UoWvvbw26WoWPw3RbFMc+dtzyG5Pi5VQbBM6Y6EWx4WP -R7+d7L2V89zosK3S0CuA/LSZRHkJAlt/n/3wrRTSi5HgxOxJS25NG5pUT2pwNfEejmz+RrUj3v0/ -BYixmVR2ZBAaOiN37NFKqbS9cZwC4lIoTJFCQg9l7RBUra743yD1pEsrIzFb1j/mlazLTxmPLuIC -iKGyKnhnhKO6519eb0mR0jUs2ppjxQhxln9NvYgICscx7TapyZrAnBUKzI0Q01NEpcwxrUfuVNKF -qbA5PlgRXy99mJsjpghh681GGyriTmVppsz1RzeO2ClE6Et2fW6OmILRw15Pzfn/KOxDfFP/tniv -1l8n2fBzc8ROTGfJrp9wxDjM0vIn+3EJVqkwz80RQz0oeDit8p4IzOOfBEhMCvFrlEi0kPBg5J3u -pELA67itNmCSK9Bv1WMSRcsRHFI1x1DjnSoaTutc1xDy2jpHkRuip0OmXyO+6mVRq6GsksoeTxU1 -K9yu6hZCNySbNXNdbYPEztMTzvoaVqusX6S6E2zpyDE8ti7RbjYwYhKHbpD65kQROzE78sNo8zkh -6anw53x2pTexOu3ZpVKThdJfU6CIoVBjoNVbUMSkvlxqhfeJzGlZwhJTrYQW/hq+FNW6NShiJ91+ -jSoeIGKn8X4GVTZAxM4LM+eH+qYFEfvb4fobHt5cOvOmLuNx01tdV/W9DTkqBE9J0TQ2FSpuwWTj -1NZecYiBJcjFIElWUPdxMcQUnH07dWTdvJgVcmvKCg54P84Qk0rty6A18e28Ra/CuihiiEmxtYXO -DIaYVFtt8XkaGaRQoKa9pfZiKNd1oqygAFkSPfy3Gz/pS1htoPYetFX218tR9LQFROxv268x4uFF -pe/4BEPWbfVrLA7tuVmiPZshZuPkRLTl6dN9qNBhqqb6DsIxfv97CDFLTRC0PzZDTA== - - - A9lNEE+rBe5oZIi7O9Cf01OJyt0ERuzEz/d0UUtiKa+KUVAUbUQZkfTZ2ctmtV7d4w52mPmcmdmB -dgDE0qRETJXhVI87aWTSfwQimUBPMzDReH7ej32rhRPToE50qHen+zAlkQUQmhNLomNaadRKZFue -Y3ohmVRrghhWtSkFv0iUriSh/OZWgFzrrXtYeosGNpTX/yZ9bpxJVEOJmO1+oLKt09iiBj2jYoBf -Na0+83sv//MvPi9a2N7KwWjNtOhig05LTdoqNFcRGgtgvqmOU1ZGynzkhvr0DRhD1aq10zmlvbMU -Rc2lkJ8YUZHVn+Q5wWKZcOMTsW+cqD2gxo5fE7UuP0G+BdQhKaYaUUX2dcm0C2LJN5x1oQqfrryP -ppJFGpPamI6l0mVAgol1cvNiQiCDF8JijQ2rEzCVxoVhbXEnCrgxKUPZXpTgjaka5W84uRm1/Uc2 -L/mVF9MNkufwqVHL92COTTz2tXUgO6+dHd6ZtxVzaucytbNV0mTEs/NfHsu3L6tOXYqZIns/CNSq -io0+wm4LlUUd09Z6MMVoABB1jOAoOzv1jB8eVr2odJvnfq4VQXgvLwak61TfzXYWUpS7UwRj6law -9IlOBOIa72srHex3J12QWnqyGNK3lULlLyylZ595EOCFvN3tuL+Jxr6FNpx80mEFiQs7NiEiab8L -FVIVJWancXMD9NoTh7CPfjZ3TLGg+R5eqgpi5Lx8X0qMWsCQaV582isFd0znUV6sDbQyA7ofXmfo -LgYK2NqmvR+bmaOwA8VBWm4DHuOhYBfX3U4RGithgmujxyZQAT0YyktzwwVYsnpRH9L14bZpVIVV -0Mf0jP3dmklMg+FUn7Vqaal2npjWaTJ43g0g0zfp1C09tyU0CT1p7MRuRIVHAj6p7k69AQvugWrh -0tcgSRIYeu7XUS7j8O3a/a49+wT2oJICFdHrYeVyaZiRYFmsQ70h1oyw+gb1TS3t83g7nYrxWJmo -zJ+6/M8o+W/O2VqIMR9R2hk8MvUCsCHCYEFtXVrbvG7OjN2wDplRbrjimKhT01tzYIQUSvw77Qca -lHXFqVBpbY+kUomdosZfsIksSONSVonjsvUaMKAe26bSASXlpYBB1vBa5T3dk0fHuT0/qaUm9UFG -XkOLWi20Ru6WZqMAdJKREZ4vamKknnjGvV46LxMgitGkPIzmj88ox7tDrlLv+3VVj5MUK0Y7PJuK -QjnJ8W7fQQV9qOpVRRUrhGkwhtdqsfSnlDRjC4kndHCoNB6S6aUPkinqb7k5bjCXlzlHqjDvb/dy -cvGjwmBeM1Zo3vGuWPMzDltCUfAxrTX5pHJxocw0FrFJYYlGgeATpXnD86QarqglVQny9e78C6E0 -/UqH/fClrfiVXJo88++A865q4eP0epjDTZ6H7FXsT057Bv7uLfvYHqZkdkditacRGAYeZcO6I9oV -0YcnFA2P03lswBkdO9TIandH/eT02p3puBytDij5VZJ9Yc50og0yw82INYrdWqizgCWarWCtdk4d -b8fIL9DDLfPzUKd57PuZdEZKYckz8vCNi3+zJbIhBjlTdVHD/RH06TxQKEE+07qHjalUdueT4PRj -LsOcd3nllGaO9di1qC/Vb2dKVG80gSmxpS7/Dtfgu74bgqYR0PJfw7s8KUfmyg43sdLDT2J5zEVC -IwbK55O9FzwQpuZt9VqWe2yXl96JlxIDwYT8AxfabU2VNLaHpTvTfALV0vOkrWhUcFGs8Libu22x -RpRRMP9SAXBEvvPZZLQJtUK//XKz1CGTBAb60/eRGpwtzKhF0BsdIKuEuFtpLWM4BXmK4YL7e+LL -gg6PKnWp7MEIqgNtfLzhQ2FjFRENSuQpC742LU3qQQhR916PrL6sxTulqHRGinJBnLjK20WpJdam -OwA1TV4gZEGdb0jvFtFIxbqCnMbK1T5/Wm3CoFSYiaQ7PU3hBZLjiuitX3l5bKUDrtWaX/UpyjLp -OIOoAd0bNvpsjJpUdjvN3ZWkdKUJenOyhhSW1igLpiaEkodOjaYmmBhVe/ooeGoo3Vj3C6jGefRW -P45Uu7yql/odpXDpBDv1+ASbnTOxxHSrSy3HBh4Jl10MsuWDfKGBSxZcTeEg2zmph/j1v3gpfIgh -uV5C4hiETfuVEWtkCXnLjbEmu1O2snJTM8paM5NmPfsLtEaIhZKM+/byGiV++JEqeNJbqHWQvSMq -StvLsRjDb0O+TkW1+Cjl1OmvFNfQXq5749Y0LdlQxuUk53D6wgSqjeZPo5GexNMXb42VHE/Z471l -Wrlbml1bxwCu6WwyxNR4UMqOZ6UuH+gpKKs+Q482N3CtqvTAz2gumsZbm1FeZkr0IEllyygHD8bw -GZFfcDBKuyqURqJUyqr1V0OVBghFmw17SAm4Hks9jgdlH7enfPTEBm9NE/Kc0ZjIyHutBNttFkIT -6PpludhdYAYcvVsC2DKvp5dQSWFJDB1d/XljQ54kntY8eFuMmAz39KMU5Jvw0Umn3qmcRo2J7JxU -20XHFMyUlnJbWudgXr9aIfmRGkcsDfSQG1IqiodKAlP7jdlVZ4MSwDW63KyVs3t10/t6mYDgJI0A -ULOazK729yCuKVk0o3SJlzBlKg7zcldl92shjnODPVQNTNuZVLo7URhhn9PeHM5UoJEdxIpHAmsf -rhIo0sPFqkMK/7TaL/0zar9Ul3tTyizDDMw3juG1u0+kJNRZw5blfjZ0TSrVj1KtCFqQwstraYjP -SKHJS0JA1yQeGimJ/SoYr4tI5OteFiHNa2TvZzHXTvKw2h2qGIT+OFoIHpqjWd+rXpJKS7ono0WN -3P1Nzg9OiJ4SK6p/3YuRwgjKY2SHFMy1STpAw93jdFudafsHCiDBCUhRg8t1beja1B6QSBsGg9Ra -nhac6XAm6JkUxEoFXOqhi+rdqXnXrnV0pTxn5CIjBPU0C0j3JxkgTIFbqVBS+Svgh7vvTSpWP6oB -vVlo3XO/kTIxZosi3zUuB8pEsAqBfN6irqGyxKEQiCZBPZHdFY25UqylQUpQ1ybID0qILu8Pu2Lt -ItttRlc5D/FZz7mBRtSi2vjtbFm+BXXXoisR5NKyytJ9bVPXaPwk4/qYtwtdCFZNdUWxkBqGbqt+ -CuyahgFruJrOFNC44zWgt7sHqJOG+UjZtSCvsdvpVNKeFvma0R/Egph5S5ksrSyIr0VwDBca214N -iyrLZW5YOablsSHRTR6exV5DJcJ+D7s4Uv52NSeKJYW0EmGckBJkFzpPXj+TSctS4K8r5EGOx+P3 -d3J8Ur/BjbHBdUZxM8Ykl9EiyBMpp8nNPccmsFkA/7FCY3rgaJaij/A8rR1TqTwrn7+e7TF/asZQ -2FHDA47oJyipZtWjhhG+fUxRZj4YbMozWvPf64bBygbYxklLYK0bVOZtVdGKT0f1giq7byzFb/fR -IbvAFkJtY9rH629eFgI6NoSNGnp7Cw/HMsNKvb28zXqebquYQAm3NTJ/rGKVeRuXZR8PRhuxM5Qq -50w2iPexIWwn/F29wUx9mJC+/swGdpdadd4S1Q8Eh+3sR4xw0VtBy402PDIzNjSCl74s54p1Jn/T -OiyVhFPAkPraCV3i7I/PqPAiHMSmL2JrFS0bMZJm6fZYLJ4mA7LdemqpfwoSm/KDnWIyGkFkLypm -ny0AutXGnfBczVtidfBRkU/aT8kpRYL1JRhtqDzBzFvrGkwsyBxGxwrIGk/NEWvWtaY0UX+hW+OV -94QnWqR1uXVETDokTFsVxoMnyzs+SEqUrEq1ELVUO4+CFT5+NGu0g6ciZYHYSKkTAm+veS9a/u0x -8wWC4rSs0HKurx60bRExiVnrWoIa15aXUrNukWZla1lr6LMXi+3ExkGJkOaoCH0ULiuk007L5DEQ -SFkoNlNNCxCbcv80hA36Ah970q2SCdahmN6n9yXoZi8Qm5LXLJj0kFlm7b594NKUKxLbeUc8TPNz -WGQpnY2fBGAkmst0b7F7GE4IOHF4dZhbWrY9Znag2BMeZCfPX7exnyW+xnCb73MJKwVJ1pvA3flH -Na3k3hXBMJ8vvXe07T4byqYKqJtvpo23vPIm43Sz/OHCsmklQqGDdolkKJUjtpCeoif0CSgarEdM -8cIFlNGa2LpItRKh34KKAfX+kKTXekJfmr4fTTjhkjqxNHnsTOPAHz5KQQehLF2rx9cwOwvSNvAy -PM2bDeM/whXgaZrtxr/x7H8zWv5/jtRGYUOX55v2JkFqo7CBjXSolDEQNBeT6bYiA0Y8CXs+p2+c -qvxh1v16RCkP1SNqw/MRoYKR7flFWHmI2XVzeEVRHwPxTKY+jrm9oSYCM3we6Vlt0BhxFLVlNzM8 -1XsCA9W6RUpQ287mXhCaybt3WfXhTrUamkkydub06JUdl687tH/XdziN//XuNu8Ta9huPZML3aa1 -AmYydLOrG18lMXbx/4YMu1rX6XGt464DhE2ZeC95AQxtvJitTlOs0F6OVattVSq1dyoYePxMC+cc -0Smuz2L0Oe79ZJ+XRgECTc3Jhdfw4eA1e+tTLTTxii+O29/LqE9J6snWgpD85a3oW2kbKLZVLQoZ -zHDUBqV54eJ50jl2tiVwnry4z7ZFfTGP/B3Gc9MhFlNXLDqq7iRSiwfo9bABu58OnTUW333bykHK -SvBI9YDeNMvPE5eNZk3hYN1OFvfTToxC41Mbm8O8Iqx25I7NQTMr0i2AL41qjK1263s71TlwmVGh -QUFU43Q9tyvR9k6VAakbVy8AvN1SUdPMkkPJd2OrfRjf7ZSDyByWO1Crju7G3/Wz/UIQ3k7aJ+1F -s5dBkYdGo9q1EW+nwYgQjbz1YutqcX3AIy+2xNbLFpA3xGY7IBxXTrYspx1FWkLK8WiOlRKcNz1K -f2PS5LW1kjH5DlKBq2FIZoIqNLOqlFBs0e/1Vfu4MX19LC7Qy5pN/SLNSHnLlwzVNhGv0d6kWCBT -46qiz9/x9384r/wv4d4IttnDeW/e2yTvPy0faC6Wcmt4Y3LU1ouZ98WNMs/QSs7RzaweWxrMaIp9 -rOvWU3rNWovAJAf2TSqxTNTDenVICyCQIVHPllICKEF+I/5oXjXNQjwq22jWCN0sbqyCD7yDDBnd -t6uIe8RcRn+j/c78WRxeS8ccaXbVlAf/DZWllKwPFChBIQfT3QWUopBhJy4CnFI6BJo6kSprKFQI -Rv+mo2E85g6FslKx0y079L1k2aP7YWstVexTtPG5k//5F58WErO0umgpHhQ4dde+lIQMN/bmOKys -hpdVSGlaj6EEBQ71peLw9og0SA2PuVrI54qOAIBXkQO93bVQ5U/mCAQVjYYJ35BPMKv32IpN6a+F -UpaqCduM66b7c2v7QW5WKaKFgUPFXOzqhoGTwkOHpx3bfYPJNFOWIYXaMNkhgM6e1stvvnE6DtYf -NUWPfdZiQBAXoAb6jArVCYXsdX83fAHcemKMc7f3adw4KbrqtwWJVU1otZvdKHBLIA== - - - Bxej2RK9NAETBYo55ZRweXu575bfDYE7Sc+MNx8X6QXjfdq/rfR8VwMu8TZHTfs5elSawyg0nFrf -jorE9uA5veq+H4ZUQNEqaOAYwjfwSDO1+kGAY5fYp6lm0GI1YZcpinsMD3db1VFETHhvde1bODtD -vlV1lX7T0YuSSkW2quIBKhy1OmS/9jr/QRfamkffRITAmAMnl3UcwzHtBhME3Bba3Ai4pD6GWqDM -8j5tA+2R+GE5Yyw1gwGnM62QrburOfE7Hs51xQiAdNtTLwac4pT9tqthzU0KkVKi3ByBp2gwi3bK -soIBR4yY2i89i1itapdHVaLWl2w3je1qoZYFgUMlnCCfQfpkNcU+bkRqDXTX6w10OrOtZtTXUuio -mF9d4ZEnwyqS7xxzXGFqGjtQVZQp3a0sLrFwrQxIHnXPBn7Hyn9z3lZ6+7KR89ggOBWtm3jamm88 -YcmnCYXU9euOX1IWCk6qNVZKxdZT6yieHqYi75Vjwh5RCxFnruFZqzJOvHxMhwfHh8/Itq18MJUC -zWYN2RMM2uv1CIwwp3/OqPUc2ypFG23C3gNYzW31C0dU+oGFU8mNvfft3Vw4CnG4CFRbPFZ25YGa -19ICVqzTzIVx7T1RAR7LYOemTxAjJBsJNBfwJylglRJ0OBUDsd0dJOgpEZtWA6VhkljOUlTxu8ZF -U8li3cbFGbZCv2lo71YpbJs++zdPZm8ewfVj+uuLG+0QyWf8XUtrbqfvuIcvxGNuxFplWkZG7762 -vuKsjmnWCyQ1l8IfXFYOoarL0na7923YOFMub/T32+/awE1PKUwV1pysGboxKlTE5DW8aTNvJZvT -2pDgPWiJZW4J3X3LtiKz9ICfbPVwnt4c4QwrzNiF2cMVXgxjQwWmRbyedFyje9ZaTqHHUUrFoPxu -ehwiPWUaUikbaHc8Jo5zlYJFqYSIWU9ir8QUD/eEIKpKKv3wnLICJ2SZ25FcOImBD3u+BJHD44+0 -P0jZwywzsJNbiv3IaX33qEpB6GJ48vkwzENS+sZNJfUye3Uqcu31eHw4VLBXpWxLsS+rGWyU4w5z -sNAGWEZjSyCIs2rDTMTalhR/c2+kJxaS3anuS4lHoNPUsY5TGTN5LoXtVOW8FCbw1ck7PR2DYbSV -K19R7t2tLWLSEPi0rdh3beE3G8dFW4SNrdP6cW39ob70SO1g7sl8Pcz6AifPMI0FFUDPLv1Ko22s -nFRsCvi46R6g5i2tmmyqmt47yh3aJsup2tN6hdTS14d3Mtx0b1020wxBgblpKN7cOB73slkqZWWM -8KczmpLSNmBuq0JiWAGlLQDnHdWxMjPASvvajDm+B6+SVCyP376NfMHMobAeV0NfX9Ww3VvUYUdr -MJFi1tyXkebsVzYrdF+ouYFVsF5zNQ/IFkVzLo6DKpNvVoIVCrk/37FslWYJtUsRcWBX4H3H422e -Wyj9yVQhXNHHfL9utKwXTswb1jzHtBid9Sg4d06vCdV4XG81VeBRS7PZedsuQzOEfXoo/xURAbzb -13Ei2VuVy+Hb6aWUwSBULKxPrED6LhQBPmd2A2QL7w2fM5cmemGGwefke6ng3nsYek65+APXdmXI -Az1nvlBuRAFTLtLUS/h/fW1qb1OoJwYX+gEqZdMUq7ap92pmmr/Qc6aOUOftx53DlTuUO5Qn6m3c -8szqcg7Lch+6npj3KzSCYmUgbbPnyIZrVlC9SLOjSKJIAD2nVfphl2KB51RK477gp4Hnzu6lSLqI -vkuKEME70zZjRAH6Ow2yIsWq5TnufVEsECx7k2efyJsjEfJcUYb5yI0wE5obPacEAR4cynB30rKy -ynjJhsnIWtF3K0157iDPaTvEPdNDgv2wFDPwtdqWosxNnjP1Nr8n2w8d7lQh6ygi+tivMD/Iajbg -c5QZDbP+c/NIb0RWDJORTa7cvMuyClv4Oane0XebQSPu3WYwGXsODf36cB0SXsASvUHwNmqTFCt0 -wT53mGKXWSmVVcMMQFCvluyPMBDTfTF7stuARSrlcbOte4PopFq5Ieppx02KneR5SHX4c3sYU58V -Raio7rIIi44T23NuQ08+PCw+94qMLS15zXAvPGY4C7hDAAp5yCsVTu/jVGZCqTYznLXguTO7VUPb -v23bdru3qR+jOtKnh6GSDzCmXHMj6baq9gJ8gFRrQWms4pUNKzqRHlX6hHJF0dTwK7iO07WiyuWc -Poxu5Um+PEuVgZRuswJcN06kKutgpbLyE1ff/twSzZiqW7/XvL2lnOIhvqvDCHREuBXfPjoiKgiG -0uwoMgoIhDtVOP0uo/Xb3+LrCrLSYQwOVX7An5tPNEmEYnffi4nWYXKcICumTDlbgKVwbZZFfaiq -biKYzB8gUfda0ySFKc3+HWE4FUMZ6iRUi9e+5j5lEdXHXTdUYxOwOoWSLax7e26XiC21b2u/tJQw -zo1IL3wNJS2MqaViVHZ7PfYv4obRcTuOXb4k1cwz1CWL355ayFocx7M0PQVH38TiG8ww6juMWoeC -17WyC3jmPzNKr4/NrdOttNYhbaRk/ES5kIWXyWIt4ZgLXBcamfCXyrrXZulGpyujY7eSraXYOzzc -4z5Uaj4vjDIfuoG2oIV+0OuWutqnTvzvNCArLAVMTM28J2am106T8cjh6LWOk+nn4VkxIu0KG9Ma -aT/ojGI71dNerrJTea2rWwKFbpwIp/Ftm2OHqv1qY3Gn1sG3WdQehWqFv/9Dpgll7TqfK87s/483 -1YOne6mD2QK8gbWlQp3JZpdqVN1rPI2VUqLpuL1bsfnRq/bXYfi9H9bEwhB00nA53Xc3iHb489JY -oQYMco9yEsEmrTXPKssDnRWfFl8BtTu7d7nSDUGI+jKWAw3jItZRcUVPDsq9w+I2w6/jZhStknlR -MdhS2rXZdkk9rBeIIh+Kbg93eeFvvtbS1XOc2jvooqpYjBF8iQ+3o0ehh+V4UnJ+XF7lo99+djNO -CXubKyLl1uh/PBtxp3CzmYYe3gxJXdTTLHHt6cGl9E25S+rpfiQrgwXZ+bXiqarYmd0H733c4fUM -8pLBMZcSiceaDhfrDjXaBxfsTp+HCXwnUCJDdq00L0svs4cNAT5x4O6W+Dgv66R9pW2TWw0qpG3a -da9pWvUH+vc8vRBEjsEUWJjypkHD9jmhtua9jPJnZDQOjJH2JXZJ6CF06t2cUcsrA1Jr1GpeXqrK -F9KGWnxjDkhjnqPvFJqwYVs923KBobUXJ9K26XfqDQPh1LUVp4W9efWzgrTQF+XZZ4Utz85HK0Bq -yzEX1S/jTpi3o/CWolL3BcRbqoYhsHj38hV01rYaWsFbaNO3wHh0W3U3c4OMJxI3HRbde+a/Eet/ -Mx7+f+F4//vgeKrL+sLxqNYqcDxKDAscr+Oo/APH07tQ4Xjqd61wPC16v3A8hZgrHA+lwPHsmArH -49MKHE9KhePZMvsXjqdFeoXj6WWscDy9Zr9wPLxmqPx3OJ6GjgLH6+TYfuF4fZsiOhzP2DIZjsfa -6wPHk1rheKwK+kxwPHqb7/MHjoda4Hgo50xwPD5r9gLH62FFt+B4mkD/NpALjgf34weO1567wPE0 -vlc43lYyii2pDsdTxebfbJ7geFCWPnA8Vk8FjoejfoHjNXVoXPcPHA+19QTHQylwPClfOJ7UAseT -UOF4Uv6m/R84HlSrAsdjtZfheFtIcLwlBhxPv7rC8TQTfuF4DShrhuPZ/JrheEnJd2SpBsejprbA -8XSvv3C8JgeuAsdrcld4KhsPrbDxUAobrz2gb8/KxkO938TGk1LZeO05/hs2ngqKKhuvEWLObDwt -Uj5svN7eHzZeh+ad2Hj0/n/YeKiFjWd+R09i432H35g4rXhLQ8WXjcetKmw8kvmZjceDV9l4I8rF -FxvPns7MxrNH/5eNp1eksvHsVcpsPFKqHzYePZKFjafKnMrGw3/sw8YjAZzZeDz9hY2nJOEvGw+t -sPFQChtPypeNJ7Wy8aRUNh4FSR82nsoCKhtvYOCU2Hjf+/gfv8/kGD9sPKmVjcdxhY0n5cvGw1W+ -sPHMWiqz8egu/rDxOuC/xMbr1FJmNp5WlF82HvGvwsbTFFnZeJ0Gl+uHjSe1vU9i46EUNh4OER82 -ntS/fV9i43VSF5mNh/Jh48lG7TjuzcbDC62w8VQl+mHjKa70tDOx8VjiFzZeUhIbb6vBxuuLUhXM -OwaCDxtPI01l4+mr9edKbLytJDZeEo2Nx08qbDyocB82Ho4rhY2HMs7ExuPMDxtPamXjdWAyiY0n -4cvGo7K2sPFwzitsvK1kNl5SnY2nv1DZeFrTfdl4tAQWNl6Xp31h420ls/GS6mw8VWNUNh5WGx82 -XqcvILPxiL4WNp6mgS8bTyHsysaj27aw8TCP/bDxyNIUNh4TbGHjaV77svGkVjaelMrGG/hd/7Lx -pFY2Hkph46H8svE62ZLMxtOQVNl43+EyJkzV6HzZeMq6VjaeSpEqG0/GTl82ntTKxsMSqrDxsI76 -sPFQCxuPMwsbj0//sPG0Aa9sPO33KxtPmeUvG2/QgZrZeJihFjYeu/QPG4/tfWHj2YZ/s/H494eN -h1rYeCiFjcdf/GXj6adXNp6+fGXjbSWx8bYYbDxFIiobTyntLxtP6fvKxpMBYGHjqVLty8bTYZWN -B4u5sPHAdn/YeMP6cxMbTxejsvG2ktl4Ww02Hu42hY03wuq3sPFk0lfZeCquq2y8rWQ23laDjUf5 -SmHjqfrgy8bTN6lsvHG2HzYenjEfNh5qYeOhFDbeOPt/w8aTWtl4UiobT8qXjYf9cGHjgfMubDx7 -WH7ZeKofr2w8A4ZnNl5SEhRrqxG0Z7md2Xj6El82nh6sysYD513YeElJbLytBhsPqEZh47Ei/7Dx -9CAUNh5bjM3G2//MbLytBhtPn1PZeBQAfdh4WFAXNt7AhCez8czPqLLxMD4qbDwplY0n5cvGI+xZ -2HhSKhuPY37ZeIqgVjaeLKAqG0+uRB82Hj7VhY2ncb6y8baS2XhJdTaeBs/KxtOf/LLx+CKFjSel -sPFwUPqw8UzNbDxOLGw8+5W/bDxNTJWNp8m3svG2ktl4SXU23mC3nth4eEf+svG4I5mNh5tXYeMN -SmN/2Xi4XxU2noaLysYTZ+XLxmMjV9h42kxVNt5WyjCw1GDjUYtZ2Hijnf8NG48S9sLGG8f4YeON -hZ3ObDxiIpmNh6fMLxsPZ43MxtOkWNl4gJl+2XiIhY1nSmbjmfLLxqPIoLDxUAobD+XDxqNQpLDx -pBQ2noQPG8+WDJmNB1KusPG4MR82Xl+OXsHGo5WxsPHYp33YeJQFFDaeKZmNx5mEhDIbDyuAwsZT -s2Fl45kb2i8bTxubwsZT/L2y8ZKS2HhbDTZex+Y5s/FAgv2y8fSwVDaeVjyVjSf7ki8bT2pl4+HI -Vth4nW6MXzae1MLGwx8lsfE44MPGoxilsPFwaStsPMqHPmw8qZWNJ6Wy8VS09mXjoQ== - - - FjYeZ2Y2Hh/+YePpJxQ2nmpwKhtvK5mNt9Vg43UmuMzGw4fuw8bDy6iw8TSsVDbeVjIbb6vBxtNm -rpDw9DO+bDw9W5WNZ5cxs/GSkth4Ww02Xo/a4mDj8Xs+bDzUwsaTUtl4Ur5sPNJChY1niaLMxkP5 -sPGkVjYe1tKFjdef+d+w8faIvY6TuVVh420ls/G2Gmw8+wuZjacX4cvGoxGssPEIohY2HuYtHzae -QqGFjUcMtbDxtpLZeFs1Np4FWBMbD8O6DxuPquPCxsOwrrDxeliZFTYefnSFjWfW2JmNZ+Z3v2w8 -jX6VjUfFcmHjbSWz8bYabDy9FJWNpxfow8azCTmz8RRMqmy8rWw2XtKcbKVUaGXj6Y5+2Xg8doWN -R01uZuPxfn7YeFIrGw+lsPE488PGs/c9s/GkVDYe1lsfNp6+WmXjqeq4svG2ktl4Ww02nmzZKhtP -T+6XjacHvLLx9CIUNp6m6i8bT2pl42kCr2w8BS0/bLw+rx82HrHNwsbry2Y/s/F6v3/YeObfk9l4 -SUlsvK0GG0+h1sLG05/8sPHIkRc2Hgavx5HYeCgfNh5qYeMZ2yWz8fiVHzYeK8zCxqO6sh2JjZeU -zcbborPxOomlTb3D7OWHjUfIprDx+ngrG0+LvC8bD5/qDxvP6rJb32w81p2FjZeUxMbbarDxtHmo -bDxsHT9sPI18lY1Ha29h420ls/G2Gmw8PUeVjadn7cPGI01f2HiKWRU2HmiLDxuPMaaw8XTil42n -ME1l46lBqbLxAHR92HjD9rvpOHOLymy8rWQ23ladjUf7VWbj4WL9YePRZlrYeOoCrWy8b1Q74t3/ -UzbeiJ7OwsYb7fxh43FcYeNh3/th4/EcFjaehtvKxtPl/7LxUAsbDzeGwsaT8mXjcTsLG09KYeMR -mPuw8fQUFTaeIDOVjcdE9mHjKUJY2XjybKhsPIHgvmw8BaMzG09hn8rGU4Tky8aTWth4HJbZeBK+ -bDzUwsYjAlPYeFK+bDwSHoWNZ7WAiY03tDH6sPHgdWQ2ngJUlY1Hp/cvGw9sQmHjqW+8sPEGyeZf -Np4aySsbj1+U2Xi6zB82nsTCxpNQ2Xg43n/YeMTqChsPpbDxUD5sPKmVjUcze2Hj6eJ82XgKfxU2 -HpiPwsYTDfbLxlOvaWXjgQIpbDxd1w8bT9G0ysYb7JASG0936svG00xR2XgKuX3ZeFIrG09fvLLx -zC/ll42HQ0ph46EUNh7Kh41HliSz8fQlKhtPJ37ZeGpqrWw8qmgKG49R45eNZ+NkYuMxlBY23ncQ -jvH732PjWWril42np6mw8cYmrzzBE5hfNt4wP+bExpNS2XgoHzYeambj2WGJjSfhy8ZjOC1sPN2e -ysazmqVfNp4G9crGY0oqbLyBNdgvG8+MKTIbb9Dyndl4KB82HrV0hY2HUth4nPlh42kNU9l4+v6V -jfe9l//5F58XQqQfNp7U6z4TG48F8JyJjUe49bl/2HioOLIFGw+F6thg4/FZ1/1WNh6L5cLGU6iz -sPFg53zYeJ2kWGbjaRdU2Hh6GHB7K2w8HhFFhRYbj0qgwsazx+isbDzEwsbjDxxPYuNJGe39YeOp -uKSy8Wxjl9l4WGV+2HgK3VU2nsI/hY2n+NuXjYete2Hj9cjeLzaedgNfNh6m94WNR3C0sPEIs37Y -eES6ChuvL+p2sPEUwfiy8YhrFDaelMrGQ/mw8egiLGw8lMTGs0/6ZeNhxlfYeLpihY1HHOLDxqME -vLDxCDdmNh6P8i8bDx/5zMbT/ahsPGKCHzaewg6VjcdDkdl4Smp82XidlUtm4ykMVNl4ROM+bDw9 -Y5WNRzt1YeNBq/qw8fRNKhuP0FNm41Eb+2HjoWY2HkJh41n57A8bzytor8XGs8uV2XhSvmw8fdPK -xlMxXmHjfUfJf3POxgf1w8ajAuwciY1HF1hm41GR92HjoRY2HvV850xsPAhFCioWNh42pDgVBhvP -jEkzG4/Gs+O9Kxuvg6/PbDwqg+bcbDx60972w8ajlrqw8ZhET3UYOxtPUZwvGw+KUGbjYVxd2Hgo -b38rG0+quZQGGw8PYDOM9WwqynP9sPE0JbezvZuNpzmrsvGUNPuy8TDFL2w8M/7PbDx8F37ZeMqZ -VDaeYs2VjUfN+YeNB0Eks/H6237YeIau+GXjkdsrbDxydZmNp8Thl41HGKuw8VQRch/XZuOZJfgv -G48AYmHjmXdBZuMp1Pll49Gwk9l4isFWNh6lVR82nk6sbDx9bmXjEaz9sPHIthY2nr5aZeOhfNh4 -PTyFFhvPzsxsPH36l43XA9q+2Hi0wic2HuiKDxuPa1XYeFynwsbrMJt+2XgaAQcjQbDxKEd+emLj -0VZPmfVm4xEDLWy8DuriSmw8fdcvGw9oXWHjKYtX2Xik3j5sPIoVChtPZ1Y2ntZUXzYe1t6Fjdfh -EvfExqOc9peNRzljYeMZni6z8Sjt/rDxUAsbD+VqiY0npbH6zmw8yrsLG4/QYWHjSXnixFXefrw/ -bDyNVk3h7WDjUZb6YeNpCKtsPLrmj5nYeBoSvmw8qg0LG4/BvLDxmMVG/2HjodLOE2w8KV1pgsXG -k8LSurDxbFpJbDwJf98ssfFM+WHjcV5/ExuvH2xhExuPRrDz/mHjUeuU2XjqdXrGmdh4jXzhDxtP -hUeVjae/WNl4ml2/bDxrzEtsPDozChvP5qZfNl4HGX8kNl4Pk9rFxtM66MvGo+ggs/FYAhU2Hiu8 -DxuPHHNh45H2K2w8W3v/svH6IjAGGw9Y1YeNR71BYeNpKKhsvH6//w0br6q8+Hc0Fzkbr0d5WWHj -oRY2Hkph47Hc/LDxWOMWNl6nBDyz8aR82XiakCsbj1qEwsaj/ubDxkMtbDwphY0n4cPGAxdX2Hhk -uDMbT8KXjaflSmXjSalsPK1zvmw8WmczG48kfGbjYdL6y8bzfU9i4+ETWdh47HI/bDy2aIWNlzIV -zsZTZfeXjTeAH2U2Hkph43Hmh40ntbLxpCQ2nv75ZeMpJl/ZeESTChtPLqpfNp7UysYbUKUyGw8b -6V82nsTCxuMiFjYeyi8bjxhDZuNpH1rZeJZXr2w8KiELG4/UTWHjURjxYeN10gGZjYdbWWHjgX36 -sPGwIi1sPMhGhY2Hc+yHjYffdGHjuZ9lYuPp3f+y8TCBLWw8/fLKxlPm7MPGk1jZeKYkNl45b7Hx -UAsbDz+8wsaT8mXjjeXEFWw8euwLG08v6ZeNRy1qYePxLQobzzref9l4BNczG48uhMLGUxfoh42n -YaCy8bDPLmw8ApIfNh67ncLG69EftNh4YxnCJTYeI09m4+ktLWw8Gko+bDzUwsaTUtl4Ur5sPDpP -ChvPUuCZjYfyYeMpWlfZeIpsVTYeOc0PG88C+JmNR7NUYeMplfdl42HiWdh4ywI62HhKOn7ZeMoz -VjaesgGVjWfs5V82nnGaMxuP7EJh4+lvftl41NAXNh4tSYWNx7f9sPHIqhU2nuXZMhuPMz9sPKXV -KhuPfoXCxhsL9JPZeOMcP2w8a7nJbDzler5sPM4sbDzsWzMbzyPfP2w8fZHKxhss3TIbj2z3h42n -/GBl45GnLWw88q2/bDwq8gsbT1+isvGU2Puy8QZjRWbj6VpnNh59aR82nnW5JTaehMrGk/Jl40nN -bDz6tQobD+XDxiOlXth4ln/LbDxaVn7ZePrdlY1Hbj2z8ZSt/bLxFvZ7sfH0UYWNp6/wZeOZ+k9h -4w2r7E1sPD3plY1Ht+KHjafkdWXjYfFc2HjzmP8NG0/p7MrG494WNh7pnA8bz01x22bjkT4obDyN -4V82HgVJmY0HY6Ow8TTdf9h42oRUNp7elS8bj8qnwsZj/V/YeIN+4F82HsiDwsbTgrCy8XT5vmw8 -JsHCxtNlqWw8mrN/2XjKa1U2HuGKzMb7xrP/zWj5/zk2HoUNHzYehQ2FjUcZQ2bjIXzYeIO+8czG -0yNa2XgUDn3YeGAbChsPpbDx7JhfNh6fV9h4Uiobb9Dr8svGG/NdbDzqXgobT0Pzl42nOaKy8TRA -VDaeytm+bDytFSobTyUxlY2noeDLxqNSJrPxKIApbDwpXzYeamHjoRQ2Hp/1YeMJblDZeOO9Khtv -EhL/ZePh8MZzsY5ja3ElNl5SEo1tq8HGgytg+C6j3gFwuJJgf/Fwa9M4ii7gzMbDRfyXjYdY2Hgo -hY0n5cvGm+34YeNNVX8WNp6UDxtv4s94bTbePGJz4Gy8LWQ2XlLDyZasV9tsPOEDCFMUNh4MAU1n -S5Xnv0XLg423lXI3lhpsPGMk9M3Gm+byUtl4sBqmZ6B4G0RSsKhEhuOh4rwQdDyU6950PAk+R206 -HmKzDjc8V6B8aO+96HhSWJgUOp4eJgsiBh1Pt7HS8UCBfOh4mu8rHY8lR6Hj0ZP5oeOhFjqelErH -+47A/8OZ5X+Jjke47UPHY6NR6HjaBFc6HnPvh45nllCZjodS6Hic+aHjSS10PEKqhY5HhPRDx7OE -d6bjKb5a6XidErtfOp4iAJWOR9i30PHomfvQ8VALHQ+l0PEoC/ml49Enleh4dFJlOh5Znw8dTwGB -QsfT/ah0vO+d/M+/+LSQmv3Q8VQVUel4HFfoeFK+dDzUQscjmlroeFK+dDxt3SodT8vvSsdT+PVL -x6PHudDxqNotdDxtQL50PNRCx5NS6XhSvnQ8+vQLHY99b6HjjTBcL3Q8IgOFjke1Z6HjaR/0peMN -PF0zHU/1hIWOt4RMx9uiFydgo1DoeAxJHzqedr2VjkdJY6Lj2b9/6HhbdDqe/ZxMxxvX/d/Q8VAL -HQ+l0PGkfOl47BMLHW9cd6XjYRT7oeMN3ttMx1MAoNLxkpKKRbYadDz9yUrH04X+0vGw5ih0PKJu -mY63hUzHS6rTICi0LHQ86DAfOp4VxGc6HhG8QsfTBvFLx1NMotDxiAYUOt5ge/dLxyNKXOh47PMK -HY/0yIeOh1roePQ8FjqelC8dj0huoeOhFDqeHfNLx4PUVOh4NO4UOt53rPw3521NyV86nlZohY5n -ZiuZjtf7+G/oeBjGFDoehjGFjkcNzYeOZ7Y1iY7HiYWOx4d/6HhWK5DoeBRBFDpeB0H2S8czU7BM -x1MJQKXj9UX0zHQ8IhCFjodS6HgWpfil46EWOh5KpuPxJz90PAxiCh1Pt6TS8ZZS6HimZjoeqfNE -x+szIqWbjkeZUqLjWZVHpuMpLPyl4+EoUeh4lAhmOp7Wsl863jZzWMf1YMEHHc+Uq9DxQlt0PEpr -Ch2P9rIPHc+KNjMdj+B0oeNtJdPxthp0PL5zoeNx2T50PCOZZDqeYkmVjkcx1S8dD7HQ8VAKHU/K -h46nz690PCmVjiflS8cjypXpeJbcyHQ8e0h+6XiqZKh0PF2MSsdLSgJiJdXpeNTkFjqeqtq+dDza -gAsdD1PITMdbQqbjhbjoePr4QsfTA/2l45GxKHQ87RsqHW8phY5naqbj2X4s0/EwFw== - - - +dDxVO1e6Xg9GiMWHY8itw8dD3vPTMfDy7PQ8Qyd8kvHA59S6HjU1hU6Hsd86Hh9zB86Hr0MhY6n -ONaXjmd7l0zHo7Cs0PGSkuh4WzU6nj6p0vE6RtC/dDy+R6HjUTVX6HgoHzqeqZmOh1LoePYrf+l4 -7AgLHY+oSKHjbSXT8bYadLxOxCHT8fBj+9DxuCeFjmdWy5mOB5jqQ8fTa1LpeLjUFjoeXKAPHY99 -ZKHjqZih0vG2UgaDUBcdT29WpeOZ4cAvHc98mjIdj9Knvul4uOJ/6HjmDJXpeJGoLnQ88mWFjqeS -kkrHU+PUl45naqbjmZLpeKb80vG0bq50PJRCx0P50PHIhyc6Hv0ohY6H+cMPHY83ptDxaBktdDys -5T50vIElS6bjSal0PHZ4v3Q8LB4LHa8o9OJz4oeOp41uoeNpnMh0PPrvf+h49KcUOp6USsdLSqLj -mZrpeFhxFDoeBiwfOh6FRpmON8b8oeMx/H7oeFIrHQ//7kLH0+v0oeNJrHQ83rpCx+OYDx0Pj5pC -xxuckOl42h5/6XhSKx0Ph/5Cx8Mw4kPHQ810PE4sdDw+/EPH00+odDw2S4WOh/Kh4+3jgo43mOFa -kO9s+V7peJrQMx0PpEah420l0/G2GnQ8LUYqHQ/rhw8dDxuRQsdzy41z0/GSkuh4Ww063sCvNtPx -xspQJDoeYqHj2cIy0fFYjP7Q8aRVOh5KpuMhfOh4+vRKx7O0eqbjaXX3peOtcqJ1mLqDCx1vK5mO -t9Wg49kfSHQ8Fuq/dDy6MQsdz+K1iY6nmPeXjgcUr9DxiNgWOt5WMh3P1EzHoxy10PEs8vVLx6Mx -rtDxpBQ6HsKHjod/S6HjoRQ6Hg1vHzqebmWh4+kqFjreEhIdL7RFx7PIYabjAfL40PGYfgsdj6rP -TMfbQqbjLXU1UCkaVel43hh7Vzoej1yh41GGUuh4w/wff+h4BmXJdDyUTMfjxA8dD7XQ8fD0KXQ8 -I9/80vH01RIdjyb6TMdbQqHjmZrpeNSjZjoesbsPHc96Up902Nl+6Hha2n/peDj0FjqeGQtkOp5i -m186no4rdDxCoIWOR83Vh46nsHil4437/aHjbSXT8ZLqdDzKfAodj7/5S8cjsV/oeFIqHQ/lQ8dD -LXQ8PivT8VZlWaHjYWhR6HhURhU6XlISHS+pTscbK4MV1DuFuL90PArOCh1P0f5Kx5OHwpeOh/qh -4+nzKh2PlWam44VQ6HhLDDqetleFjgeoKdPxWD87HU9LykrHM+WXjrfVoOPh0FjoeFB7PnQ8OkYK -HU8zdaXj0Zr3oeNpIVPpeDT3/tLxmEcKHU8RuELHU7XSl45HU1Wm4ykBWul4W8l0vK0GHa8fy1nQ -6Xg0Fn3oePRbZTqe7nal430j1v9mPPz/43S8/09S7+7h1d8ZeqeCVlL9IVLI7Wwm8nOqsqa1KRHv -tCwyj1UH3qk2nr614N29bsxecHfqjzhOW4hTsA47nI27w+7eKELdrLuXNpN/FukOkLiv5gHdyXMV -U/vMudMOcno3FZEVVQ/SERzFwrfvxwvm7sLS+NqYu3OxO4xyt7yjMuTOzA3HP4txF4X2C3GnfRFV -j5lwN24b/xbgDuzD3Hg7bcNYFmW6ncR+jw23k3COe7Pt2Ku1K6Pt5lrEONlORitXs9YvIjQWF0tc -ux4ADcfand67sah2IRSE2hIX007V2O9G1Smh2H+Advris6VjHov2LJqdmCqXZXe2L62COd0SNxYf -vywhsUh2wWcpIDt8bg6rUjZvh8v2xItjp+b8cbXdvGUTh/ekBMVOi+0zcqv2UIeQKHZLDIpdP71h -Z8HpbrNhKww7GbIskx2Jx2bXHfWKH/8Ere6Idyk6Cg83PCqsustS84tUp/eVHHRG1XWzxt+kuh4v -T4DqVBDafjB1CkC4a4ZR6pxTsSF1w43VC6PutaKshagjzDvNYNasC7v1IRZC3Rn4iRCv6MZ2Pt09 -vCUz4+nuEc7rTqdT0xMlOQGn+4ySMW1Z6VSbr9v8ZzZdU689Fz/YdO28HP3mbLp2eqVpgtOhWTTR -a6JQCKAEnK5RxdxnhdM1VbU8R9twOiksXhecTso4+l3hdE1rcFa9AadrV7PE4ILT/c2d1sRR4HTt -6mH45XS6dnld1aLTNS0nu3PnAk+HeHnQhIgnyrQeVsNQ4q13mrLodFK5KYtOJ4UMyqLTSbEmv0yn -a5fvcxedrp3Rtxh4uu+t/E/c6uFtdQVPJ5UUwMLTcZx1mzuernk8otDpJJK4XnQ6KaxJF52uDSvx -KnC6tpwbgk7XqGWcGzrXVALAq5nhdKqqw3M4RDnYna1vNN0lEsz5VDLd5ZbZC0yHuZwcPIJLJ3O5 -96lUOiVoNUwuKJ02EPc9NpNOwuizIunUwSCP4iDSnd6us4B0l6fsMo9OBnyNfb/j6IQS0TyzeE9b -SDC6JQaL7vaO84WY02uPK3om0V2vcSaWKOcmvMeDQ7eEhKHbmlHo9DterzVWRl71Tu/pljUxMF2P -Vc0vAp2E6LOn+S46zwp/TtABzZSLpQau5XRBKyoMKW/H1kW8mfeKehGHz6kqrQHJcvbcEjJ6botO -ntOW0iLlDpS77yD2Je6csCb45YSoJRi98UGdW0KGzm3RmXMKi4BaCOTcexgHIQPnXt+zLd6cjNWG -uzeYcFl0o9DmGpVC3Cbf3De1MZFzDdpc67dXIGTaXAMXMn0HjdVE91L3RZv7m4bDRC3R5qQabTBM -+hoex7eVpfCgNhyk738KbE4irWALNofCGx+wORS69zNtrpF9GBbio2+oYe+BfYXT5r6jX0yBGDVQ -gZNpc3o7sPlYtDmV9hCTWbQ5YgnjGpU2J7W1NjZtDpMl3fRFmyPOofmv0OZQfbg0EwSd2H2MxSqR -D9cQXGBzepgwtV+wOcJFx7w3bI6XQLuZApujc8n9Mfhcwl/v9e6xhz2vvaOJNcdmWbv8xZpDwX8n -aHMocQEWbQ71n4Wa45+6tws1h2IWLhs1Zx/qPE5mJX11K5QJ1NxWEmpui4Ga07b+YVEVCDkRq9i0 -FNTc4jft40RvxKwjWHMa/PsFOjKx5lr0Yy/WHGxjiiSCIWeT1n1X1hzc4g4zx1VdDZ79xZrbSmbN -bTVYc7jF4C4TDLmGda4KqTJrrqmwFz7rOk7ckTk2am4JmTS3xADNiflkGYkAyLX7Dl5oAs3paxCj -3MdBQrRIKJbp+K80J7IvzhyqzXDOmWth07A4c+3xVunCmZNqIYHgzAGy4lUJzlyjF/61Y5ZLkb6a -WTwFZw4yNo4FwY+zJ+Wc/xTOnMhV55MPA7192fzDOj8piS+11Yh+C6rN0LT4cfoSNDoWzpweKsP2 -ruPk90EJenDmkpI4c1sNzlyjI0RPYADkgJBrOZExc3oKDOiwDjujBQHO3P5n5sxtNThz+iAu9OLH -KWyq4wtmTtkFzbv7KI1nxxybMreM6jJmDg+h5u1FvpU8zbt1YeaYLU4HgG2bM8UPz3duzJwUzLsX -Zo5j7P8kEzjtRzUvLcycwsXYEC5+nIZZ8tmFM8foqMl5H3b60jgwc1vJmLmkOmaOUdt7UczFtR1m -41gocwz373ttypxNAJqOnDKHF5FGwQyZM1EM9oDMcR7bvoDM2W/kSyXIHPk4ZsWAzOlFYS20IHNb -yZC5pDpkrrPfltOdx9VZF6niPUPmuB+K/4ZIuuy+N2NOq3J2OIUxh4vUw31s8WPPmBGcHacmXfvs -zJhjN6Zlyj5uPBYaWYy5rZQxYKnBmGsqpgHaHIy5pmJWjXCFMdfm4RXFwZhr+Bw71YC1fwPffJvt -3GLMNWqRT/NC0wDXCHY4yjoYc+1ws+R1lIp3yE4EY+49LbGWEXNod2h/OwmE8/lnAeYQRghOVtBS -CB/BwMuxNnqte43SEAnW4JrgcgI/8KYGW+5dwBFDy6mqx/yRN1nueX2BEGA5WUQZ0ttNfy/fXBes -nHlh/bOgcsqL0VYWTDntuFhbZaTcFeunIMohHCaQFdNpRHEyT05LEVxRAienuRTEe9DkRNVgrMkw -ucfX4gsmp5mUtzxYcltIKLklBkmO8ihNrgGSa1o4MvxnkpweCbbmiyT39zJan9siyf2dZe9sIclJ -NZprkOSkWHVakOTaEdyHTJKTSpV8kOQkMGkGSo4jAuu4vK8adqWvEbQInre/ZY+bYDlKrimYQhFf -Isk1tsLnBslJYJm7QHKtXb4FyCA51OEfZ1exhRGag+T4bNaoGSTX2M0c9wLJve9GyL1vhce978bG -NSYsap8dB9fabdvMTI1r/TLf5HWUXKcotn4iOORCRsYtMYhxjeyY8+1w22jdnQgLMq6pbuFxi3E7 -TpdGMbeFjEtKQsZtNZBxTauL1yuLZA7Oj2nUZyZkHCqU+EDGSSGiuJBxUiZXKyPjWpjEL2QcStAU -QcahsJTOyDipLJSDGKfv+lhfgpPgGtUtgedcJ67xdx2nnDwFDkGM20omxm01iHH2F65rk+C0mW4L -OB+BL0V9bSe4jlNcc7gxzgo72iYx220oPHnySFn6lLDmfc4NjNtKBsZt1YBxbfqQFMA4BS3McyAD -46Q+RIIDGCfluuM47mRU5BdgnFQfKRwYh/LcjodTKokztWYpwLjWTwtzLWCcLuIRhSyY4mwlA+O2 -GsA4vRIDUwYHwbXmNckFGGezqwoWQhVHqN8bFxf/3rC4pTjoSbU2VKYEAU5j3m1FO4kUx+MGAmod -p9I/eDSBilNo1qHTCRUn1SwaAhWHcjo8jiAOZx4ObF+oOFSjAfmSu7Vw0AlUnBSj7mRUnL6bn+mo -uKY0HMmyQMVtJaPithqouHa87q8VCLjXg0KFFPceAQp28Znun+OcuGeVpiVMnPqbLZHqlLjbkzoL -Eqdxk8rdxIiD6/L+swhx2kkfzWY3MAJYzZOiS3y464jn08XzcgeboMNtIcHhlhhsOGVFsI4LNhyD -6tUKGk555dbGJsOpeDKqWshaXN5hX7hwl7vSLCzc5aHIRYVjnHTa2oLCXdNKgxYTTjC/8OMnobKF -TYRbmgPhNJZRBByst+v+EVhlHTY+LxEieSbC3UGbzkC4u9mOpvDg5Ew2jnvj4LRetAJsp8FtIcHg -lhgsODzU3me3VbSwdi+NFpqMyars42bUHwQKbisZBbfVQMFpJMZ6e6HgFEa1K5NQcMqsWc4/UHAK -KRl4JVhwimAcUZq0WHAKlFnHVLDgdKYzGRILThs0qt0XC05bSTZoi/EmL16qdjIKjq5KW2rFYZgj -UeYRm/2lZBTcVh0FZwWrtyUK8CxXyzZfKqPg6Km8hm3QrcxWhu+sfQIF9w05RzD6f4qCY8/Xjrei -4Nr0YOJCwXEcFYSBgpNC/qyg4Jra4G/a0h0Fh92UrcsdBaerb6DqjIJDnW2T4FoYkS4SnO2r71FJ -cNxM3PqDBCeFxFeQ4Aid+cpkgeD0CBl6PkhwCo+07kgnPTTtPioDjjCIloeLAUfEfQ== - - - OnUGBhzhH63NCwOOUj97K2HAKSRjTgvBgMOv4nxnZcBZDuLoiwHHYY+TVggbKoJNLUBmwKEe90bA -ER+xbLgj4Ii+Po4WWtkPshDmIellPFbwdl0LAacHj36FgoAzzAQ1oM6Aw1+bvvVgwNHPDP0qMeDA -A4DzcgQctbVW6Gpxq05G93wrAg7feLo+AgHHL8LNKhhwWAnxpTIEDq8i6gyCAkduQMPDosCRP6DD -J1PgiKSBnQoKHEp/x6bArVxEocBhns7VDgqcGcLjJjHib4ZRaqbAERihtiMwcBjQ3fxyx8Ap7oDH -dMHAKdhjb3dg4KBeUAERGDi8igBkZQ4cmVA6noIDR0oHhJVz4DRAevFq4sApfEocdXHgIFvATssc -OKkEYBYHjkwaFfbBgaOGgHVE5sCR/WctFRy4VUiwOHAo11UxcCQw2MAHB05fgiBCYODIxVH4kTFw -onUYKjIwcI3y5d42Bq5BDaK4M3HgbJAc5wbBMZAyXgQI7jsEx+j974HgLHdwtAqC02iGQ0uA4Nob -ydsAwS1LrQyCk+jB/aAGHbcFMhYIDsViWwkEh8poFCQ4O+40xRwOjtsCYJkEx6AKQCpIcArYm896 -kOA0oGPjVkhwGtQN7xIkOGYkAvVBglPajTKfQoKT6vwSJ8G1sJtaJDgUpYgLCa5h+Gem/ezxEV7/ -k4DgOJFYRQbBaQGD+9oCwenrs3BbILjvvfzPv/i8yIT2HRUDJ5sEsC1BgaMyabwbAqcRnx+cGXAS -x3FvBJyEa7ybAEfN0z0rAE5rY4KEwX/TbB5hML3uipqbv0iivz0eQV3wNwYHTGWN/aYCrrO/Ff0m -k9v5zk1+0/8+TmujPk//e8wf8NvVfBUQ3Df9x+PZ1Dfp7R0V+nY9RgJazDeNVlTABvKNzEirwDcZ -87MzCN5blFEF7q0puUQjUsa9Kaxu3zFwby3S5wv3phW/uRxn3FuD1eG20uDeCGCyWwvcG7FQM8FJ -9hwtCuIX7q0ZSLpt3JviD7wnBfdGVIKWzsC9SbFe0MC9ofQf2ptE6+gM2huK9QJDe+M0noxMe9MX -A6AbsDddLyI6AXsjkmCfnGBvjeIbZ8exsVDwzbabDntryoyys0ywN51nGMqgvel2eN2e097U2M0i -J8PeVIGBKXSw3vQ8sEtz1NsT1puZ9PZ648YCvb3DC1+D8/b6sFIwb5pvx2kezXxbNW1FMSqFwEoT -M85nxps8/ikWCsTb60uKhXhT4exxP5Xw9q7gpgHe9G/GvuC7qbL2fAvd7b0c1+5wNy6Po+NIolNG -61X3q6vuicRLkN1UTkcI1cFuv6PevzkDqx+TEtJMdYNyrcc8oG5CzVmO2aFu05vVC9NNIivkQLqR -JB/WVMaac3phdQG6Tc9PLJ7bPH06ij0/W4fX/1oYdE1f2yyYG+QqCjmM5aaX5229otxUdEzuIXqK -TtkNkUc3kJt8nC/HsS2O2+UeoIvjhl3xPTfGTcLbZ6W4qRH4fsaGuKnh6mRd4dlKOrD46wnhJgLk -2eYmuN1uVrgAbs8Puk1jG9vAhW5TmSmWMAvdpio323TspL1yF76kcnKbgr5uyOTktsbWbP5TwG0a -V9hXBLitOaRpcdsYeVhrZW5bw92p35vbRmbMmiSN2ybm/P1DbVOs6emb2aZNynFZqla3WqmfB4/s -RGy7YlEWwDYtrMmGBK9NrzJhkoxrU98JdaROa3sD2RGwNnW6Uo6YWW2K0DBcRMHv43bJi9QGvHwa -T2mB2kTXoYojRJnWkHMMTJuEy51k1rhr1ja2oOUF5TQ7iMSzbD95PjOh7fFUzgK0PV5PGYA2uCCH -rbgXn+2JcszAs8EoPyzDSz8m0CC+YoKzCRfKWx1sNtXn0hwQZmSyYrwP46QFmo3GcucGEjaGs3DZ -Jpv/8nSvU8tcNjX0Qo4LLJsysPZEBJZNuVuyiZnKRnr/6RvK1o6oOwgo23NFMjEx2dSsyb0MJFsg -fAPINt8wR9o8trkqax3HprHXgGhOYzv9K2YW2xndkIFik3D5UeS+lcMydmUCsSnTTfwuOGxiy7HR -CAzbqTxVN/LbauWY3bJaAWEb4VwVr3eEJjOCTXYVp3sxW95TtQXDKj/dCcEyyhm/NjymsehrKtzA -V+z/z9677cqyLdd1v7K+gKpxydvzMQwb1oNBGPAjIVDHFgFZNCiCgv/es7eIGCOicpMEiCXQgvW2 -V9+ZNbOyMsclLr0FfE1VIcNJaIu9JtGKsb0YTqtDq4Jx8tq4bXlbwGvD23sXeE0Pb59WbGQLz9vH -+Mxd02n9vjZ2TZmtT382dY363atX6JrWMXAdg7mmFg8r6XbimtVBFeKamAiAUAO4pn3v447+jJV6 -SglNZtwavWKnBVksozZ9uxi0tYFPcmWtQSDXXQvUGoanvmtitrwff/AzaI2kPAwz46w1/eTUPgRn -rfXlPJY4a5pEbEAKzho5M8aw4KzZqndWzNoTdk9BWdOK2TLTmbLWsNF2VJGVcH+8AnZR1lrzabRQ -1qpKt0JbXTKOWWtRYFUwa6gESQOzhkJwNTBrjZr907r0FmZNqnFqArPWKH5+DLyGR6wUa4/KmLUW -rocLs0b+3pJV7tOrKZwy3YJZQz36pqxJoJYoKGsSZls2kOusw5rbHLJGVnhmypoUi9plylobnspZ -lDUpHkryjJGWJDfwnExZaxotLMNilDVleXikgrKmqq9hhm+bska/Fmw2h6wp1W6p9YCsqRTOoNsZ -sqYsjC1mA7KWcgAOWVNJs1GKM2StEabs14asocy2GWuceFwVsSbRAhJepinBVgsQ1vRP4jCFsEZZ -HUW8QVgjTEPVahDWBNTgggthTaq5VrsRAtyN0ytPiH5IsUalTFiT+mFQDMSa3ULKb+4IO11eH5oQ -ay2qPwKxprSZVc4HYq1Bhx3e/xd9VretehZirWGyNNtGrFFKQD1JRqy17ub0C7GmM/mNF2Kt0Wf7 -uSti7eejLPexEGtq2GNpvRBrasQzL5iMWGvwWufYiDWd6Sk+D/Q0wfJ0EZmw1pRlo6AnCGv64mur -SP+tKiBpQMiEtRb1BYuwZgoULvcGKycuxBoqS5ZArEnBlmwh1qRYJX9GrDXsnJy4SZ68jeXQ8Ilm -0sM/KyPWKMW0wduXQ1wFuZBArDWYwHNUxBrti2QyHbFG+b1VH51RXuPBvIxY0xhgDUaBWNOg47WQ -jlhTtZlNRRmxxsakU0jqnd8tmmIWYq3hKsZ6OTHWGHdsFe2QNb2o5u0elDV6KS6goomyhkr4Oihr -UnpirOnf4EwzYo2Gi8dPsxmLzPLTNmINJT56ORSp1p5c90Ksqe7dLG0DsUbKkB83I9YsNH6PjVjr -0Vi6EGvKlFnpeEas4QRJA1Eg1txHuC/EGh0zjCkZscaS63QzQXM3VQUTS4ZArNHEQVFwRqwZ7vd4 -NmLNSCj93oi1DvuJUExCrFE/bq/hJ3YUw2vdA7FmzPinV8QaaTU6UgKxZsiCz70Ra5zJ5i4j1vAx -IbIUiDVq9XlmA7HWngDWZ8QaOAUb4aKtQOXx1AUGYq3dXklSEGucyd98Vs/mYxUIgVhrZKfvSljT -ddgyJQhrnUXb9WzCGrnkKCYKwJoiSWacFIA12gRs7nfAmqGZzqMS1qhHfxYTYFigwPwUg7CmWAE/ -eCGsAcGw0iMnrIG+obbKCGs0X1lbcyKsmTVKc9TmeXhQhzR3Ww/e45+UEWtSLSxsiDXalIh0BmIN -xVcyi7BGxrqFtRUDDImtewPWaNagazoB1vS1rfbO+Wp6HQgrBV9NWdD7MoeFhVdzcrRTBKiSVPjo -BPzmfDVdAKNA4auh4oCZCWuKylEJuwhretCtNigIa7TokWzPhDXq0WfbgDVcgnlhA7Bm3W2j8tV6 -uOsGXo0flgaysboUp7cVZbya3gbqzxdeTUs9W5cEXk2jt831pd5TGx6rzTe8mgpAWdAvvJqmemvk -yHw1lRWRIVh8tQbQ/fP8Kny1Ro37YTgmy/vN2wJvgVfTITxeBa+mxbD1TQZeTZst/v7CqxE3ZGZK -dLV7em16pKGvWHMFWw1c33lUtppMHcnPBFpN8QeWuoFWe8WUf2fA+l+PrEYktTsxbZHVKBUITDgq -X5vXwMlqCJefuKdxDcgbq4btFa2egVVTw48FBDJWTaoRZwKrhoIZcWDV7JjTG0lWsXMus2CYkWLR -2MCqSaGquGDV2h0uBlolPt5YtLhqGpCtRzhz1Tr1y6flhm3ZLfcQ6vyjoZm8tP1H4qrpjbL8fnDV -iJBx64OrRkzEZ/YyLtg06Vg1CkpOh2+RSeN7sXvIWDXUMyoKbj+TbNPCqvFZjmzb3KSOoaOZSjto -8IlRIHrINUrFm72waliDFaxaZzuRsWpJSSCvrQZWDUP6jFXD+f8bq6bBtWDVdGEezXOsGvbTBL8T -Vg2RwrbAqqGYFZBj1Yg1nVaptHItRFVZtAdVTTF0CxcFVU2K7V8yVk3vgLeAOlfNYsvecWcdSCFk -rlpS3QNVXmyFq6bo/5urhvt84aqxbSxcta2Un2OpwVUzd/3EVQMq9s1VY31duGqswV9cNdTCVUPJ -XDW6vb+5aoiZqwYfonDVcMd9cdX0NFWumn5Is9oLXpqZo37ar8JVwz/huNJhWmpwMwOrRhfi7U01 -yxEL9XBkGlg1a+7vm6r2Hn3/yVnlX0RVI7pmj2eiqjUYPcMWb8zXis1ZTi2oaky59KFnqhqpszk2 -VA1BC/8FVeNErQgLVE0qwcuAqhE/vY57Q9XoaSJ0mKFqxBvD0dQCOx+P8CyomgoryGwWqJrsM2w/ -F1A1etHMeMTXSHSP8QRmqBoqS6iAqqFMh69ZwOkTGeRMVVNOxsJLjlWjwY5gdnDVSNvMIK9FhrR9 -3J4iuGr6RWyVFVy192/5p9/5vCg9R2dI5qo1snhugU0VNMeZc71XMqCQt8hctRbZvcVVI366BHxE -Ph4Qzlg1RWcswBxYtTYDmhy4NBlGWc1HxqqxyhsZv9ZByD/Hxqpp20EOtWDVUHHGCqyaFB67hVWT -4vWXCatGX7qx0R2rxl6XfExg1bo5dfeKVSMaQEYqsGo9cCwLq6Yn3OxgM1aNAZ3KpsCqqTjPqyGd -q7aVDFZLag+zp+5Y1yCmtcejzhmshi/qFUQXus4+K6dgZDUThltXLYTQVq8oQ/Ad/WKraUy1tpfM -VkOlkD3YaigafRZbTYpdfWarsUfss29SWP+4M1ew1bAZtR6VxFbrvL2H5yIszBLIsGCrJSWVbmw1 -2Gr6kwfbtWCmsb34JMGeWd/e7cPw96aA39BqW8hotaR6BpPSxSuR1VSgOZbDY5DVdKJVjQVZjagd -z2fcL+0NrRUvk9UaLuzNfmEahBQdbjRM+ddXHJiVeyGrER2m1CrIamzxKPwLshopEdp4E1gNsTk0 -jXobKcSCF1itDS9sK2A14rd331w1BP8kBhM74tMqVk1ha3rtgqqmkK/li4Kq9h4pfw== - - - 57w9vRyzQNXmDGq0Q9WOsJILptrhflYFqXac3nsYRDVGmvPaQLXDW2ULTy0Cp4FT01l4uAdN7XDD -xsxSI9Pf+kKpkV84nk1Sg1ZFHWUCqc3HTd2Do3YMjy8FRs3Ij/1Xoagp9WLEA4eoSbicjUbkn/qZ -TzAYfKcZ/dSLoCYBK8wgqOmvPY59DH6aosG/Fjvt8NDtQqeFUMhpiNfmpmmyMRcgkGhXBD83Ne0O -HphJilmzdglm2vNYb3cmpj3elrcAAKqVmhmYpqApgZnMS1tWBSGeH9/zBC3tdIvQTEsLbdHSVO1y -XPeGpd13lMCmPTqVjLNvVJoWTlZ4EKi0rWRU2lYDlfbc0UPpBDQ8C86ERPMbeRmq0UWKIvu1MWl3 -lBVkTNrtcfJFSbuHPwo+Kd9ui1UYacrhW1jQEWn4tnTjYpARVhCEFHEGpBHC7s/io93RshEwNJ4G -Gp8THU0V+OT/A44mjENQwkgnbyHRkLboZLTb844LeKYMMKammYsG13e0LWJm4w8ArlhLyFi0EBcW -7Q43n8CdqSmMxoNMRdOvbf42oWrhT9HEoqItpVDRTG1zU9FUOuF10U47k7kN9ScZiibu08xitAgs -JNrpFkqFiHYOm2U9YoszvRuVYs2nllX6djINTX6vdN8EDE1er/a3nIV2jihLSCi0032mA4SmJNd1 -BvXs5/TDrfoLBu1w05UlKiLH2BwQtC0kBtoSDYF2RDVSENAO90MpADRdwNM2/kxOE+EVC/1MAqvm -DD9DvOdmnx3uc7zQZ3yx1iv5DCPa89rgs2mJ5IU9i39n6FlogTwjFjCe3WB7uO1F6cPVnaetKnhn -cullpgrcmXxDecYS7Exw7M+1UWeQxD/3Jpip54qPzaAzNnYYeIeqghEvNXFTga3kV3upC3TW+r3K -NeCcWbe823sszpl5BZHwds6ZPBfN/y9IZ41gybwq6cz8idxDwXb+kS4uqDMtFouo0g5jAQXpTLF7 -nNUK6czUHuq8/Dh3iLa8AcoZyh1lL9a+uEBnCAMUjxuro1g5RgKdkZRmbHfSmQQSGot01rDQeY7K -OlNZi8UDg3WmHZbNuME6w+DMzHQS60yqZTqDdSbF0o3BOmPTNe+COsNhsHn3LaizovAwcJ43TS/S -mblSHtcinakF27ogHXWmmLgViWzUmbYn1l8WqDMpZhUbqLOkJNSZqQrpHWt/4nYLC3WGdcjtRaUL -dUbFjxUlXOFf6C23i3Uml2drQc2sM6neteasM9ygHX5m2wCN6db+nVhnUu8R5DQtUk43y1yoMw7h -NmfUGeYqZAICdaYsmjtlOepMRTVW+5RRZ1Kt6i9QZ1Kox12oMykWWsyoM1RWZ8E648x2b0dJPjw8 -JveKSlOTQtpBOmtUAHzOTTpDISmYSWf7uCCdNeYvtwhyp2+rMt6os6aYOqkKP0ZlnHcLKyAfYUzJ -qLOtBupMmzICKAthhrVBOyvpTJU3dgPXYbpVPIMOOttC4pwtMTBnCjgR4VmYs7YyBglzhvh5NuWs -Te+bDsqZNr7Wf5kwZxIpEgnKGULzo/grKAQgM+ZMH2+vcGDOGh7tZ9/8Mu3IuVOZcrYLetZh58cj -9IE520rGnG01MGf2ByxbQTPw8BrtgjlTSZKxLkIlgmoBcbfJVazWUJsJc6bgrgVaA3NGBLV54NwK -yZaSMWemjvFrYc6oB2VrFpizprYIGkoz5kyqWUME5kyK+TE75gyBrFjGnEm1zI1TzhDIfAXlrEF8 -dGDaopzpl7SmS6ec6S7aOx2Ys60kztkSF+iMJDthkACYNa3T2tMq6IzZF9v0ddzlG/MAnW0hg86W -uvqLFBwyE7sAmGkU9KBXAp3x1GFFtY4TgJnUQIDOFMiltDFzziQ+7dyYsxa+B4tzxnk8HZlzhtqD -YUbpw3F5N2NwzqSQ+SmcM12ZnynOmVkz4uVsnLMlFM6ZqSM7vRJiOI9NMCOUZr3OCXSmIg2rl1/H -yaj2cfMFG4XuCEJm0hkesfiiBelMSw4DSATpTF/C1nyZdKbjbFx10hkBSfcz8ZxyN0+uAjqjHc18 -JeO4FsWjATrbSgadJdVBZ9TbTM9ym4mw/qZl1hLpjFQ7LYJBOsNrJYhowxyrFaQ8zB9jZ8w/q0Lu -jsas7nZ2gTpbJV4FdUa/Q/esGOtHapRAjQbqLCkJdZZUR531lVcKhJl5zWbFVmV3+Hu7SgKOkTNQ -Z1iYP142s1BnqFREZNQZLWfOvmLeYJ1pd95RZ0sprLOtBuxMlXdmFxd2q+oX0vIlYGdUzVLUqSGz -e4XGop2ZcqeRg5OWGrQzzAUZlIN2o92J3ZVMO2ufKKwN2pnsEyiWCtiZFt94u2TWmRbYDN2BOlOf -K0vAjDpTsS7UgiCd3VGGHAiz8zZb0QI6k0mrrcVcJAntiDNaPxLcjGCGY83Gsr/zzc10bEKBmql7 -qbv5GMiih47CRTR7RYt/Zyj6/+NAs7/88//953/393/+93/1c4L9qf+GwGeTwsOfTfDU2iHIZ1JB -Bi5Vys+wcKGQP5Ny/DzcKAE/m0rkq2RjakVIv56lsH7+43jshZ6qyvj87A6mghvBP5uKy2rRPNUZ -oWkd5WdriUKK3o75ebqkeE0pn3b8vEWTBo0++HwAkih6oSfuN5d90sKgTVVLC+Y4I+AyAcbQ8Pyx -kt6p9OvPlz765qBNpf+m/uBhq/PBxPczg09tKPRaqM9c3MR53ZuEJusdalXm7bWCAnDazadxHxZa -s0qiKVjchqHRt3ObajS0JqbKgeI8tGZkRikJiNYMRijViWiqTDs6iiPRVF047G8GOFdQURF8pyb/ -xueravlnJJ804pBD/pkDevybs4R64pHwYyYr5Z+fkugzPg9LuTena6sCThqqXf18189v+EQB8oFj -4LEV+4uPsmlJ1XWBitSVmz/WiYXNzzGyLw4fVlTRiKUSIUf5WZL+KIc9l1K60Mko4YgrVUksieQk -JUDfnNGsJOX4eQpMCU6aJewPqZebOZwTPKWu1UBpIejnWqC0Jd7m/Khv/TPSnr8OTe9UJKix5meP -sxW7r5p+FENdxymWJqzllAUSYPOllF9kqZfqcIgu2rt4Od1Hv/XJ/bs+G6EmF40+b38blczEMONn -bpwKLAdEDVGddxOSort0+Ks3rPOFeO+nT5QFUkO9P3amsbknMRZXFPu1soXH/uZqwJUhTBPpfeJ5 -qwefLNsxbSAi8aB9tn7aEDjx1jzdkzrBPF/yU7BaSY2Fnz0WOrcIVehfxkw5o0mxa2Cn9phSx9+Y -Oa1wSmPFY89W30w1fit+DhVSKDWg/2jiGOunlmsrT96jdOdYoENJ4sIfFKN/hj+eP8+1FLaH9uz/ -vCNSFlJN7wh3XSorVnuXDjuT8gMpQ3xUzgykmt5owNbHx2uXJ855usLHvZn17v5sVod9aDSBTvzD -TkZLPNF4/H9GLEYX3CCnFovdhtkAqqEx6NOSxx+MMUJrIL3pk1CoHhmNz0FUm3RsaMpSuTdTnHor -NLui2G1W+OHnqePMSKPNB+bMzZWxVtULrZs3aarnq33/jn/y33kKM6qXWcPDAqpJ/ZlovJ5HESKO -u6OeR3t6KRCiqecJoprUn0W5VxapPkDK52Cg6kZUm9hjPn5m5GP1YtC2qvlW7j3zoOjxsClCm56J -ZaBPNwupNlX6ISvefZw5t1uaghrJIVKZ5mCKrBbXW7nU5zHbYXMWIruq9M5w44fxkP63vtjVvT/Y -d1yW6MfCUwrzKwppNimjP17YFQMBQCWlWGW+wmJWfEOQ28Meh6mKvhuLpmOF2aasgpo6U9TWqhj0 -NH9e85uj1X0rysgGYi2ptwWap1gWh35evR+qWGEg0FyzFPub6l/jq6zjCCnYZzHuJmUu0FoSO3kn -vhIPi8wqlMmf2qE+WPY9edCSUZEyguqXVJIPZZx2HMFVzuTqWcD4/nzSuD9NJcw3KXszILkt4D4q -y7ntw1fUe1ir1GWXhv8o1Z+agW+37k3Ks6FrW5XRsrY3+gsUbHONijJoUdd+tjhbscWfwmDzTscp -nq7F5QAzqLd5KQEDsjOX2o29NrF7U23B5XGfKfNQDTlaNS382iSGf5qDod0glTHzgK5bpg1U09Lg -HpvANpWXaFgyHBZ00Iq0q/1XxxFMmPIw05zAVQSBTSrLNB3H6MIE+1EOWF2+2mprXvt5BxovwiKw -SaU6WirlVlJ+Fpb2wthjfOI2epgSCDap9+fxz4P5K4UB4nH/chTVaLCidauXKXPmgdHEtNYiDUk3 -j4aMQfq0wawOlzFhHrhWM/A9m8B2KFrASCRHIK2BjkOlbrcNkBSKHFpC8IRr8RgPl9TWmqt6uw+W -JNMWvwR8D1Y33YbpRWBDjeGVxQxndh+U8Sjk07XqZlAOo8SD1m2Nfix6fk44COUetlYkGHGoFfp0 -WPyqItC3+nnPJ2Mw4U6dCXF9jU6Hwr28tyqqCAbbgcOccuVqntfDhdLt3woi8m8tbwbN+pHel6o1 -Ip6imshRtBHiRT78k07RcKUEhE1fnU3ioCTGf5Kf4bkzzNEjvJSpQKc/WFvsTss9ZNrOSmy6g98h -PxX2SqFwJmDTkY9T9bbWAnLPU+jiUD30qUVaPzaDTYf9LEtuXkyqTQ71arAogFfx8woeqs6+9JiH -wp+UNasaCvdxc/g+Eefb80mKguvhibXUIWM1TTTHUPKcuf+2NIa+UD9l5BYKZ37wVnvSccqZT6xK -vBl/K4r0huvZVrUJ1Db9oPJYY06Etw6NpJfGr1Di1p5M2Ps4xTdvGxXwKj8wuOWzPpvDhmqT4Mci -YSg8Lo93AEsh7iBlcdikEp9A5d6qlO7QpC5FwcmDWrqPHbN8iXRts9/+pKlo6xC0+x42xVIGYA/L -8WzFzlQQWa2c+ziNuJfNSVQqJiUZhCR1Wnie5TbrgWajmi7iZ211b4UTO7HmJx2n0Le2kPoDmIht -5Unm+EudkaPSX3hY8zZfFrIi79pjhxIPAsvuENliKNakjzr2P5mIg8O2VSXX+eFk+MiNPryE5YAJ -yfb12pWXB4DzrOrF+flbPu6q7uiQxaLWEeezKFzSfj7RTKZZB0k5tTYfVHjoO9NHx3j9bPcyqffx -2Evir9djfV9SWGxzzHmZEp5uBy4Fmp5kM82XpAWWqiKHk/78tb+ghCYEe6Fv28qmwx7/ZXHl1iOx -lXuD2LZ6ePmMBk9bxx0+CetPXr2Z5ekisXEhvEqH5/ikHB+cqAfWWRImC2v1va2JBFVvnJyuhv+B -4/rYnp+lo31LLmtuFJsmJlsZ/dwfTIA1+drKiOXKnRV1QgSKbasKRyuOfbBbZ3lvxTSaU4llhLB+ -kVMb9ziKXfRlZuosWg4ZwzLPQjfzzeVBHQZ2pE7V1XDxefD97LZXOBTy5MND+evYkPc+t6rNFLGX -EQWtW8nDwFK1qDcXR43JRPuaG6Rod9hPfYSK54PFprgiW7oJSEJbZiXQL61aP85hng== - - - eLHof0F68Q0mMRHW+E8jwTmJluhdcuEfWF5+/F2Ko8yH5GFQYaLVSvVnCuzszwLGhmiDqTvFmnIO -V2YoMxTHG0ilr05rf5KGKAdD/2MVLChN+4nn2Ui2n7+nDz5RSZNJOW2gtzolCcNuzmdR2WzJQMRl -Wv5kqtfMNgaP3/1PbNLDJyL2YPdQwFeqNiNSrjamKedjP6nFUD/HhrNJZWUllRpPUz6mkMnjTEJC -KkcLR6IpP8Z52NjMemViwqb4K1DHn9lJ8euh/ShfJBBt2th0PQ/NTd8UgD8f7lOzxW1Sjo1w2Op0 -Y2HdR4v5q4xHG8SJEbrF8helbeIr3GxnYSdiWnN2ooD2pAvzPjyKsShtUokJELNQ2bKUoRAVClsH -7HWHRzHC50nqoZCGfV7jMM2oRDa0KR6P32j50Yd11lRh0tSzdbrfwoR2oJ9SkRAtWX8uTiO+KYvS -JvVnZ3S4Ouw4lv5SwLTN4+NRQH1WYNpQCd+ezjTnzEsjhzYD+tbz8VWshMC06Sswrv+sCyjxnVjk -acM2fb2xFbWLRIB6q7JBUL3UZIK79dNf5pOnyCU71KXYBuzjQfZ13AlA6OBZsTjUUlRxExWgW53T -Vp7azF3Dvi0+HvoaT1Hs9mgwVLx+H0eFeiPYZAHCrbQNb9vqcCOceZw25+kOTLaPH9vC8eBG3xrq -3e1xZtcqhdglimLNUiZ3jM6Wvs9koOTzTv8LB/FoGWwp+oKiNbgetgVvk8r6Wpdm6yrRkjS8MULq -OP0m7JuXYk+ej9j7OBWKk16SJ4fobVuZbZcFbVWLh2f9Bb02ChYolKYXofHihPLXHl9mc7qPUxDV -YtnXDnGyvVT0b7l5KBRqwVZMnjyGeuk/2ECRwAll9o1v26pKHg8PsHpwV9Uhingw8epRW/g2qTe/ -nNRPt+N+nlM/jt9SCsms88meyt2HEBqhNfzTK+SzK0kvztQ6B2WFO4/HAmeav8G36S7aPu52y52t -PMe2LV6qtpXUqeulGJpcWJ+rLlwv0HX1Lfy1j7H9eu50GKbDWko2L9TfitoH5wpWoSl4TtJjUvmv -K5Rb98XOVlFSsm9jJ5147MiOruNUtMYGcloJMu/nwSbz3hA3qT/fzFT8EFG09ZCCyRpnaqSUsiBu -9r6flitguT4xi9cKXXsVhXSkXA9f8tkQN12an3mZW9VU3FnJPOVDgLgtZeKkvwaRUFVhfPMXhs96 -2sqct6VmCCwtxW6tCqZbPq7RTvCQNJO1jqbq3pmlno12mphkkgeels+bLfJJt1cGKWhp68CnLZbb -hOOoGy+zHfVwT8Ox2TQIJUBLnp/RZW7F4qQznlhXB008WncpKfJhWxuKSqzDLyKpj/czYbXueWby -Kx88ni1p2Vb8GQqxJZ4t6fL5+P16uqVOUFhGPW1vXVG5afrqzCT6LMYoFeJZEFxhhGGJ84V2Y4Up -9wapZqb1XH4Vd7jub+Va7vJJtBrHQWKJO0G9sYLn+d92ndPH733UaYUBavMhsPo5/FFS9D7wbpNW -7Tm3+m/94yzLGEET1p2dHbiz1JeizfUCfm1V3gZM0aoz5fUawysb1Lk5lQ4MJSZtcvr7uNsrLLRZ -B6q1lDWC2AQRqoo2lezTczQZngGJayK8ht8glUM45I08PeE5kdwUCVbMig3A9bGsnoIjP4tSX/cE -440xhkFczUNivOnEh5WhLDCC8aYwzehapGuTrub1n+HFk/GqJddEdQzmpmMrtgmdsVCL48KARDMV -9kJbUcwvoBtbxYTs5+4pVquZZIAG13Z2al7wkF6MBAdOV2qJ07ShSfVQPlxrJIXr2P+9o9oR7/5n -IW9sJhsZ0Lkhb1ItWvm40yXHKSAuhcKUSXEuD+NMOwQ9h4r/TVJPurWqnrRl/WHO0rr9XsbTNuQN -lVWBECoWjuqef9EKgy/aKDp3ZccIVXiuMRVUCxFBsm/2M6npm8CcFQq0jXnTU6RKGRUs0Kx3qBOS -qVCoY7lqM5FZMUTbrDdFCFtvNtpQEXdgNqPUCZAr3qLT0pZMvsF6UzB62OupOf+Xwj7EN/Vvi/dq -/aXQk655sd6kkh6Uqj0Lh1la3nAlEqhUkLBYb6gfwqZe3k8E5vZPAvYmhfg1SiRaSHi0YbMoNY8E -vLQypF6AXIGOp4F67g4MBX5UzaGoChUNClCx19YtYZ2jyA3R0wM6fFzqaVErXj0VPx6qiFa4XbFt -Qjckm/UD93vD3o7DE866DGqc+UbjJuqOpaVuM+sS7WYD9SZx6AfCQllRRYyb/DD6jQ5oh59pJ67e -PsXqtGcnMPnc9jP+zCamQHpD4a71Y5PepD7c6t7txVIGhiXmIUMJC38NG8UUblqkt4O2QwucA3o7 -DL00GLsAvR3Kl53NHsYFejtkiak3/MCFvdmZF3UZ092AdV/VgEc6KBhXiqaxqTho7SF6eltpyoFF -yckvRVZQv+PivB10zSteqQdG6QGF3Jqygno2FudNKrUvhBOFGNGFPwrroojzxlfR2oIzg/MmlQHP -Pk8jgxTtRqSwF0M5PRC5ggJkSfTwD/et0kVQG6j/oDlSJ1o5ijK0AXr72fZrjLh5UWmApoqGaIsS -lkoGMWpoiabqgeC82ThpNS22PNVQakPFYe3a70E4xu/fh3mz1AQrkGtj3vQ0XRfxxg9OybxpZIgf -t4Y5ZqQSNbRHUyw2x4eLWhJLeShGYSWizYmUEUmfnb1sjA6a9Yg72GGnZe8xX5CpsrY7KBFTZTi9 -iBEOT/+psJhCAS0WNcBazdJtV7UobxrUiQ4NfgG1FGtKIgvwuFWShnlKo1Yi2/Ic00qaUA8vnaNq -k3oQLWRRukoDn5EyrVpvXcPSW7TOoTz+N2mx48x7mpLcGFS2dVhJC5i3w8xbbIV5Wmro+7f80298 -XgiRKgejNdMCvQ1aPjVpY53XLAjLlXIcncIKt95aJChOE6w3VFat9GXxgz0WNZdCfmKsiqyZPDBY -LBNuPCL2rYk9Amrs+NXFoNtvBUwBNSIppj2Srlq3TLsglnwMsw9lBrLknzaXLOobzwjzsVQt0CkF -UtBfilUT8hydCIv7hqglgVTzXnoO24pJAf0mZSjdixLsN1WX/IwnNvD4t2xW8KfjoL9NrrpZCGDx -3yb0gX7xiNjWTmBEbYz0cGnrMjHLnlpCt02AUzDfLlabJKqZIn2v46yMjbbF7kneIMBpbz2YY7Rz -FwGO6OhzWmLTCv0VZ1WpG2uVeLAJdZ1eDUjnq67NthZtmEXwBEYzbW2yCHAENp7HnntMiCdtl735 -akhXK0VRSZZDYUIs9cNqieqqbsf9zDR2FZ7aJ8DPNQQCTlf2s6Y6SPMSK9MdI9mnH0DvPYEI++hj -M+AoAn8+VlIJ8YV440kd7mmBXNg27EDvxYDTeWyAQbZpgav+SQoNb/dTICjIrv2+NotIcQeqgwjH -aV/BQ6FtHOWe2kWBzCVOMDYGbrJ00Q7qfrw8tF9ev3v7mE44jpHnnpsEp2fs56eZBDUYT/VZq5iW -cueJsZ4igIpgBgxOV9IbwUYH3hJ70vfGcedjZbAHdZ4zgU9QiZdOQwYh2NjjCE+rnyXidK5Nu5fQ -noja7NvtYpw5DVQg5WfUtm6E1YKoK7W8z+G9eBPfEm1GiAB6JqkMk79z0lYplk1IZ99oOErAjulW -D3B/VXVK+Vw3x2VK8qLecAUyUac+7nIreQr6VOM/6D/wervzul2JoVQqwVPU+As2k+mzKFG30JX/ -zfAZG+C1btunmheaSoOoYJBT/rQ9reWRVS4ZGwiKqZX7sJT8afMvi2ThedXFqjDOVEpGbRQLFCf1 -oFvh9Np5ObdTjSYF1rMUTJuuc8dcpV4KnY4gwkqxarTL06ko1JNc5/ZC1JxMWa9KqlgiaNJirUsx -FrOX0sYUQZ1tU+M0HloCGD8svQyHwk0gR93hUrvVn+3Lwc2PEoN5ep09mePpqbabeje1i6niY1rn -s5VXLm6cXkZ2KazRqBC8ozbv44lSDVcUk6oG+Xx2AoZYGp0hBkMCdWfVryTTtAu5gqC8yoU/hxfE -XO5qrZIQ/5Pm6MsaTBvZGS5p9ov4co8JHgyhirzIyGhbdJOoGlYQw2gTBDladmjIADFuWQaKdyYB -Yso4u9X8Kiy2IHI60QqjHrem0udeWqmzgiWcrWittk66vsWRI92qXOZSp8EG/EyFoFGoFHjy8A3O -oNka2WCPnElh1GXmDPp0e6Cea+Pk9O3YmVL6zNIJz6TbHlTOOy20QCR0PXbNC0z57kyJuk+PNzLR -V6lrOAl4Y3/yxLeMBNjH+z+pR7Y7e1iVpP4XmeVPW1Q5gqCU8mH8ougKfNPLCrYs+dhOq71TxfKK -JEyYSFpKhe+q0ng8LFrzU6lJ7u1DIXHbJVxUK+i5m2GJpTOtjqK7g/Y0VtbF9LXgcvoGbI6pu8VV -U+YjDPTdN5LU0xJn1CLoiRaQFrVkj9XWMoZTkacgLgYzt18slburTF0qlRRE1TXwSdECUAo1blKs -rg4Txs+xzvwQQ9Sfggul2CGbHf44FZuqq7VDdqPLdVoKjl8AEyRsLZpVgahklrpUC0cemzaHNZF9 -frPihEGt8GGzgPHmFF9QdlzKJs6dHlzRWGbIOQ3mSjMx88Cc0yxmVY09Qeeksg1TpwQ/gPzclCeQ -Yti5q9vSGmVx55hWXMTnVKA1yvb0UYDnUDrCBs9xnn5LVd9gvHWust7HDG1pBTuA3rfNnqPYiRIk -7Uf0y+C7cNrNIF0+SBjePNQLP6fKI7ZOekmsMUGV1LQ9aFbDGUgOfRRnPSMT6NSaZ/kOs4pTa4aW -Dnr2HUHXzFPaykHcolofR9WzVvpWX6PSUCL2CpfpLdQ6yN4RVaXt5ZiP4ZRiXLYntPIO6vKYWWa8 -XHOD6EgyayhjGU4zhGFrHz6KIIGtvaeXXQSJjpUcrYSHN5fpIyzPLtpVoOgoOCBFfHnpt4YCW5wC -5Wq29rOruNtG0VVVL74ppwtaKkd9mSnRhCSVcVZt2Izh00O/KLRCablJplTKKvbXGlcDBKkS8rPU -gHuSBVtdKbZ71hMbJDpNyHNGZyIj77kybNPMjCjAIXqKD0RUmOHZ2C0DbKnXw2uopLAkPrSEVCTy -s+FXEg/rHpwWJCbFPf0oRfkkWFzxM1M9zTltsJ+k3ti7zwh3eXJL6xzs+FcvJF8SJI92MGDfDsvC -81DdPrWrdYJODlXNntHdFb2cWqiy5VK5GHUCYmo0IkDNijK15losOrZoUbvES7hTFXJxlvuJSrsf -YhyqNQi0iTaB1nemWoNG1ObwdAavOzvT6V3IR980OqlEilCJFCn3S3xQCv+04q+jbxydgvLXAeX5 -MM8OwkkU7x6Rk1BrDVsWbdoDRyeV8kepVgUtt/7Ti2kI0Ehp0za5C0cn8aObSPD3YyEFQl98koXH -mhXJ8hejypRE7GNb4X5Y9vLuNHcftr63xPr09snoUVNYRJ91fYyMQu6Gqno8hQ7L/g== - - - 8eiysg0a3SQfoOFuXr7JO5vXcciURlatkxZnveTifIap8jSfMOs/92LLw4IzDDcHTZMaoLWdnp9N -o9Oe2baHM9pS7miPnhGDuptFpFWKsBwQlPm0EqXDKZr65muTiouQUmcXC63Z9hsp1yEScBaMtQyb -lYc0LxHI5y0aHSpLHCqB6BJUMKC7ojFXivU0BJbOzmxWkqnpwBrETl+76F7b6KoJ8vH2o4V1ohjV -xu/T5gOu4qBR/2PHWc87W+x70+iIrpNyPcwKhjYEop8gNQ5rVP6Qve3PotFpGKBe2BZp1KlNjwpS -/OL1ZDbmy8k+aHTsdmiIV7pRka8ZDUIsiJm3ZPCk8VelI9ENxMhz2/ZqWFhZfndW5OGJbFBXk4dn -oehQT48ycXOk9IOpJLJCGKkOVwJjQ+vJ42cyaVkO/HGFRAhWMn7MMpJStO5iRho9qpvpzTiZ9C1R -pCUwP27/bBqdRfB9w0ETHN1S92UbK/oxlcsjiUndXZjeH5oxutf2YNF+4H7ZrHzUIMyXjylKzQeN -TolGuv9si6wcsuAbrBvkcal1AykowlwKUEf5gpKQFy+fgpIkfKYnTqc13wz+5kkISHVrQaOjiJ63 -UKZ32ijRk8RMpuOs6emykgmU8HYjrUZd17jMMN0SbYw2l5Upc6Z5ZVybRqe8moW1Rg871Mef2eAS -U6zOWyL31aDRKaztI1w0V9BzQymp0y2V7LHal7CuWGfyNxVT0rr8IGB4+yD56RH5PnhnF45OF2Jr -FRUM4WzN0u22YDxdBqS7eWpVABU8ugNOGFUf0zxdSdSyANCcreI4Eq7TzCVWCx8l+VRZKTulSLAu -wkabJ9zFVarGL64MU0SCD8YKz80Ra9a9ttrE2zxkFQFnH50pmtbmZj3vuHRIsDXCHQ+erPXsg45d -syrVCkil2nlWsaKPH8067eC/SFk4OnLq2B6IfU5dCKP5be4LBMXpWWEi1KUHqnxg/3UgAqQjuU5d -xG2RZqVrba2hCNu9vHRn1OkM8yrSR7HouT8GpNMl2EAgNm4A6Uw1LXB0h5X2nqg4QelJt02FjgOG -fkRjgnaMgaNT9toWTNpzkVq7Lh+4NCCLR3dcHg9jfg4bLeWzzVBCFfF0l83D/R4+zoMknYNZyGfm -ZdttbgdY11C1AQ+x29jPEl9juM33uYaViqQ7Qoyn1cWQfNeenE2Fpnt8WNQZHEA6bUKuz3F6Kd3H -3h7vTx4bSEfp083G/7QUJet/a7Q/bU86aQjWXlNh93De1jtlbaRaidBwoWoI4qlk6fVTCNVF448m -nPBrZRJkDxoZc90WC2Md3cJ3dGc/tutfUDrltVh6yi0DK0HCFUrEyuBvfjwcUeLZvzNa/q+HpaOy -oWvg0oQUWDoqG3jmQ6WOgXdRY+RlyT4b8YSbWvM5jeOU5TstRI+o1YfKb4nh+bNCBZkXIOTLfVhd -Ii6zKGpkIJ7J1Mcxl3fURGCGzxvdYt424igQSLj38VzvQbNL9yh4kOlUAMg24WMlM5bT/bhjroZm -K5LSxifAdJojbN2hekldgwYIKltXn/eBL223pskFptNaATcZwm9qx1dNDDdfYRq7W+fhca1r1gGC -KRM1KmC0NUagk/U8rBBRyiLToVJ8J6Lv7Wd6OCdaxfVZGn1Q4sk+ToUWFGhSaTr9y+fw4eA0o+2D -kLi/4otMd1iZX1IPthaE5If3oi9F/MwA5iT18MEM4CaRtoE16EHr2NGWwHlK2hxtiwdtwIr8wVjQ -o3SF8YSCnFF2JxEvDXC3HxuwOwZS8jg5GLAvWzlIWQkeqR7Qa+Ygeqj8kw58KkM1V4GannZiVBof -+hk+p6eglFS9fHNA5kkLyi3MVLC1VLWgqPHtIOvVGceMUqLbfd6XK9H3rhz+Q+rG1VOJdeoHLqfT -JyX9Glt9PgamO1QyMoflDtSrc2Dz4vuFANPpV2zTXjR7GZSaaXSqjc2lO4yOZOW+CjYjnLfF9SGh -PBghWzNbYOkQm+2AsFw52LIcdhRpCSmfW3OslMDS6VH62eJPXlurGdPDoQUNw5DsBFVpdli5zLNd -BzXbE/bYx43p62OBih7WbLJyblausIzJUG0TcRqXTooFMjWuKvr8Hn//yXnlXwSmI9hmD+fcYDq2 -GXNaPpB5W1tgM6o5HQTOzEsj4pFnaByhhvXUkmNDwW7mGJHSa9ZbJGWR6aQSy0T19LSlBQ63AiE+ -SkpASpDpLN2N144D6RRdbdYJfVvceFJhN+ztXWQ67f/dJGYYmY6gL3VXmJt129XcJ4mRZ5PpUHu3 -LnACJSiHJwEZMSgKGXbiAtPRJkVI/iGIRCOVvP31b1oagF50V1YqdoZnh8KFh4UkbK2lcZCijdcv -+aff+LSQmLVel7mxdKqJeCgJ+biVOMfp17bArLaiSr5oPYYSWDrUh5LD6RFpxVKHx1wt5HN6SwDK -4khcYVuoqZ9uiFtRcDomfEM+IcleYys2pT8eSgmVot3h9cjUP2n70azy9NlcOlTcxVR4CaAIP+1m -ax3b7uvRoV5UynKkUB8mDQUUe05r5meq5zghS1CUbeHTg0ZBXIAi6B4lqhMs2kPYlAC6dkGnuYv1 -3d+nceMgrvdMCxKrnJDiTW2fAFAtYWwq3RIvL03ARYFo7XV6WHSs+PO5qXTa81ql5T7O0wuXcUrt -31YNussBlzjNUtO+zse81GzAHtG4o0TlHjynl93D5xnW0WCBAyxDuIKINN/3RtKxS+zTVHNooXsX -D8KbMLsuwm3W7o2kO3hvDzN+swWnthdEL/SdcBlcSi4VSerHSxwVbmW/djqJQjfaOpvOxKbAmYMc -wToOTij9Bg0m3RaUe5krKr3Uw5gP1FnSvot91GF1aWM5bQaUzurhDzPWs/uj+N1x5zsGY6/bnnpB -6RSRIDt+R3cTqVWFUbWk5T1lc/exXqoFpSNGbLVfw71WtctjV6r1JdtNJUee20Iti0qHSjhBJ9Ao -q5bH251IrYNOXSNE33VmW92oj6fQL2ccoNgOfVqgwI75nOFqGjtQWpat0hkwHX072Bo8ng18j5W/ -c97WlGyFo7i7OZlO6zMbTrut+cxrpVtNt3mPPm75xVapX7vBzTorgbI2957R08OZ05vlmLCPqIWI -M9fwDDLwwX7/jtyaGdsQvbD0W+SDqRRoNmtAvVPagdYHVfScw/LyXuv52V4p5gmmOJMqaDSVDIhS -XukHp46CY3vvzw2qI/7QbsspEs9DIVCjeBE0K2IUzWwY194TlQqS+zRYHcphJwKr40+Sw5MSuDr8 -YcxYtBuyTj/JsEDgabGcrZxpXDT1mp687lZ7c5hzktJMBHBsuLN/82SqSIkIrh9DjcdlQXDQdQoK -W3c7jcc9jCFutyMekZFRgaC2vtrkjmneC5bUDIU/uLwcQsUtw9yopgHsTDm9099//tCw0zss8YB3 -EbnMg4Kr2F7nzbyVbE7PeU4PX5tdwuPGZVuRf3oQV7ZKryrGzj06Rz+GH+C2mRnDZwMJdFepntvH -Ndp6recUjh2lVAzK5+bYIRJi1/aPsoHmzjWaHqxuR0hhBsZro+z0+RZf0f4SUwh1ZJFKl8KMqcAJ -P4jOXDacxMCHPV+NkghvTSHsSNM6D4k+MxT7ktMb7y8ndehmWPJZdZB6R5PybPDVVslyDkuwuMnv -4cPhz7KLysil2MXCWyrHfdzC4jGnsS30zbVbIiEH7r6ZI6kqwxaS3RH1S4lHoHNd6zi51JHnUthO -Vc5bua4NtjNV6Rg5Rlu58unl3nJOadMKr6gHXYpda3PD2XVc9EXY2DqtIdfWH8+9O1Nx92S+fsz7 -AivPcI2FH0APBbnz596AO6nmU6CPm24C6ubSt826HGMz1r0Zd/PjRuL09IHAUyvDRfvWsJlGUSx+ -NFO8u9H2Lk/fKmVljPDdQVFbAS4QNstb1cvHnocF4NFmVMfKzUBLddXtnWvq6dEyMMxHgJq5cPIF -eIfCelyllH1Vw3aLVbL+0XocheQK7PU5/Fs2K3Rf0Dt2hEyLrRn1jpjIdVk6qlkJVigCcsSOJam3 -pSYGEQftCqLxGDs2ayfLDcpUIZzRyHw97rSsngf1OLPm0eaIOZYeBSfg6TXp1ht5GwMPk1q6zfq0 -XYZmCPv0UP46IgLn09Nx5xFVLpdvp5dSBoOlqmzu6GYWswpF7tNTn416rDk3Bs9smoZ5LIDBo/Cp -kxO9zKFEWUa33FwQPDOGcicK6HaRpl7CP/ja1N6mUFVQ0hpfYNgUq76p52zmmr8geKaOUOflxx3D -lSuUK5Q76m3usO++rfoVxe7nbeY7KJSBnPem4JEN16ygepFmR5FEkQAED+8HuxULgccbY8bg3RB4 -dIxy3rF2SR4ioPY6797cCKQZhUUK1fJ2nPaXY9hDRrXJvU+0RZUKJ8XAK8owI7kRbkJtQ/C00TUT -DuXIScuqR+IhiCEna9rv2cKKZ+wpuXb5byYjDnzr2uW7Q/U3YO64lbYZeKZeZvhk+6FPWFXIrFsR -ffxXzDHw3gw8yoyGef+5e6R3IiuGycjG8IvJnjB/gcCT6i190xwase82h8nYc2joH7ZsWmbAEr1D -cBr9ibfODBodjMMxtqSfm4GHRQ2JeBWR4CDGCXfUl19mzeBuW3Mz8KRauSHqYcdNip1kekh1uPwi -mDb1WVGEiuo2iyDwOLHdx3b05MPD43OvyMCsc1zYF+oWs9BubhGAQkVOT4XT+ziVmVCqzQxnPXhu -zW47W/u3bdsuNzf1Y1RHevdwVPIBxk2R2mbgbbUPNwISR4r9sMbTRkGAMtBaX6OcUTQ1/A6u48xx -4/hla8p2ZOVIxjxLBdvBpkUFMI/5XdlKJfITrPI/6UuaV5UjPm1hqaVUN2MGFqO3OSxFrmNeMTpK -PN2/BQxA94wCApljvbjPclq//C3WS247p49DOBQNfbqt7mynHYr9+j5Kr8PUHExZG7DTdieFe7M8 -6kMlPfeJP0Ci7rSuSS3Uh5frRxiOZkzLzLlq8drH7Kcsonr7VlLB7+DgaatkQJLpuV0ittS+rf3S -UsI5NyK9BtjQPgkLOxWj0gNzx/6FyBeZ9muXL9EWZ+4ZlxvuySi4xXG3hcwsBUffxAIczBg7LgPh -oWB2rTQMpvlqd7PS62uD8PRTWuuQNlLTNkHDw8tksZYgQksEcUM7brPYtMjhYexiHx27O9+GYu/w -cJP7UKn5PHHKpMA+CVroBwUvqRGjVNUaPcWqy9Fizftir62sRw4fzHWcilA+nhUj0j7N/vHwL3RE -sZ3qaU9X2ak83tat0rnnYwKhAykLg4eq/apUjDmw9Pn4HbNqBcg3fO877TrvM858/sK76sk9EiPv -Sbha2lKhzuSzSzXqY2tcRjlid9YL3jcFzzpS73RYu63VUwEchiAt7Snjw3g3IHgY9NJYcTuX03wF -mqVvLavcT1vxYekSEDwdZ/Udt3kAEgLF11XRFNHtqLiiOw/l2mFxc+1fx00vWrXMiw== - - - cg9bGZuCt1Vl99QLRJGPao1IqFK6pr/5WEtXz3Fq76CLquJxuv/r5X70KCSrlKBeyflxur+AvvvR -zTkl/G3OiJRbp7/y/MHAw8/COBPeDEldFFku7cgtPbiUZzPwtqobiyHJzmAN621SiLsqdma3wTsd -97F6BjU3EoiVhYL3R56bgYca7YOLgafPMxd4AiVus3Scll5mD7sEFT0HAm+J04FZ2l6x9AiXW0BN -uC9Eq5nWz93+PY8oBDmaFViY8qRBw/Y5of48qdbLKINGRuPgGAHt4ZbQQ+gAPPpFKLm4b48Vy1nC -zBlOTxtq8f14P9hC4Gkhw7ANEWMSUfvYArPdm4HHPALDSVtxWtibVz8rSPt47+ND60QoESC15ZiL -SoC6FeZ0Ft5SVOq+iHhL1TAEF+8KY0GVqJ/e0ArfQqP8IuPRbdXdzQ00npjgrAAf75l/R6x/Zzz8 -v9Px/uvR8X52JX9Ax5Na6Xidiu9Mx+tYKn/R8YSDrHS8rjr6QscT/PlNx+vqpih0PJRCx7NjKh2P -Tyt0PCmVjme46W86nlDVlY4nImWl4wk2+U3HE4D85CKcjid2ZqHjdXJs33S8vl0RnY7XqdDPdDwQ -xC86ntRKxwOO2+ex6XhSfm7DVel4qIWOh3LMvul4fNbsLdPx+vKiCzqeMLI/G0jrAfvMX1Duv+h4 -7Q5goh9DH2Sm4yUlsdi2GnS8pjX/1LYmqHei8b7peECECx1P11XpeE2MlvOy5M0yukVt3XvQGt9o -fNHxGsyXbzpewzzHRJKjEiodT8rPtH/ujjA78fii4wE9znS8JWQ63hadjqdvXel4DRjTNx1PROlK -xzPCbKbjbaX8Iks1Ol67wl0j6HgN4/dvOl7TKrHQ8ZqWLvevAsdDK3A8lALHa/Jf+BCJTHA81Oux -Mw3pfn++4Hjt/vwBHK+RuctwvEaIOcPxhOZ9wfF6CwOgUDts8ATH01D4huOhFjielJ9b1jcc7z38 -xsRpxVtDsOIXHG+o17/A8UYfFY43cGotcLwR5eILjodS4HiDIupvON5QWU2B40mpcDwpbzie8LkV -jjf6/QXHG/38AzjewMfMmuuwkRtagRU43hj9BcdDK3A8lALHG1j7fcPxpFY4npQKx5PyhuONfn3B -8QYOTl71Ljje+3f8k//O43P+ARxPaoXjcVyB443P+QdwPKkVjielwvHG5/gDOF7HOCLB8Tq1lBmO -J8byG44nunOF42mKrHC8n59CMJovOJ7U9twJjodS4HhdFncvOJ7Un31fguNJqXA8lBccr6uX4nNt -OJ5g6xWO18d8w/G67ADbkeB4YK4LHG8rGY6XVM/j9YWpCugdA8ELjqeRpsLxdGn9PhMcLykbjpdE -g+PxlQocr6sU6wXHk1rheCjjSHC8bl1xX3A8qRWO16HJJDhexy/zG47XR/+C44kyXuF4SUlwvK0G -HE9/ocLxtKZ7w/G64CsFjte15CtwvK1kOF5SHY7XFcEpcDxhyd9wvE5fQIbjddnAFThep/b2G47X -qWzKcLyupqsCx/tZ/v8BHK9DN8lwvC4bkwLHG5/PH8DxpFY43sCSOcPxBobX33A8qRWOh1LgeCjf -cLxOtiTD8TpmJBmO9x4uY8LEVuIFx9OLU+F4KkWqcDxiHy84ntQKx8MSqsDxiMu84HioBY7HmQWO -x6e/4Hh6piocj/hWgePxTrzgeDRaFTgeAbsCx2OX/oLjsb0vcDzb8P9acDz+/YLjoRY4HkqB4/EX -v+F49rkZjqeLr3C8pWQ43hYDjqdIRIXjibH1huM5cSofJ95khuNpjnjD8cZsX3A8YMwFjmdz3Dcc -D9BygePpZlQ43lYyHG+pC46Hu02B443w+i1wvKFS5ALHGwKlFDjeVjIcb6sBxxOnqsLx1Ff3huPp -SiocbxztC46HZ8wLjoda4HgoBY43jv4HcDypFY4HfavA8aS84Xi6tgrHg+dd4Hj2sHzD8YTbqnA8 -I4ZnOF5SEhUrqR60Fwq8wPF0EW84nh6sCseD513geFvJcLylLjjeoI8lw/FAp7/geHoQChxPO4sE -x1v/LHC8rQYcT59T4XgK9r7heEqKVDgew1qB45mfUYXjYXxU4HhSKhyPSeMFxyPsWeB4Uiocj2O+ -4Xj67wrHU5S7wvE01r7geAyQBY6Ho1OB4yUlwfG2GnA8hu4Cx9OffMPxuJACx7NpIMHxcFB6wfFM -zXA8TixwPPuW33A8EokFjqdXpcLxtpLheFsNON5gt57geKyQvuF4/CIZjkeer8DxtIJ/w/Fwvypw -PFqECxxPzcVvOB4buQLH02aqwvG2UoaBUBccb6gMqMDxRjv+AI43PtcXHG98xhccbwR3usDxOlXU -CY7XiZZ8wfE6DggJjtdVdlTgeFqpvuB4iAWOZ0qG45nyDceTWuF4KAWOh/KC43WRKwocT0qB40l4 -wfH6cX7B8bpsrgocT7utNxyvL0evgONJqXA89mkvOJ7UCsczJcPxOJOQUIbjdS1TChyva5otcDyF -r99wPG1sChxP8fcKx0tKguNtNeB4nZqvDMfrV3vD8fSwVDjez8LkC473c0V/AMeTWuF4Uiocr9ON -8Q3Hk1rgeBIyHI8DXnC8jmVfhuP1n2VRheN1BWhecLzONjrD8aRUON7PKuIP4HioBY7HmRmOx4e/ -4HidLU+C4/2sTr/geFvJcLytBhyvM8FlOF6/5x/A8fozvuB4GlYqHG8rGY631YDjdTKBCYXXtW56 -wfG6SjQKHM9uY4bjJSXB8bYacLwetcUBx+P7vOB4qAWOJ6XC8aS84Xj9GV9wPJQCx0N5wfGkVjie -LrbC8fodNfoZjrdG7H2c6g8KHG8rGY631YDj2V/IcDztw99wPMWXKxyPIGqB4ynE+YbjKRRa4HjE -UAscbysZjrdVg+MNEaUzHE8RjzccT2qF40mpcLy+rMwyHE9qheOhFDgeZ77geP3pX3A83cUKx9tK -huMtdcHx9FJUOF6/rjcczybkDMdTMKnC8bay4XhLW2grpUIrHE+D4huOx2NX4Hh63wocT8HgNxxP -aoXjoRQ4Hme+4HioBY7XqTDKcDwpbzieLq3C8bqyhQWOt5QCx9tqwPH6dX7B8RSTe8PxFOOrcDyF -wQscrytU8ILjSa1wvD4jnxRwPAUtX3C8Ps8vOB6xzQLH68tmP8Pxer++4HhKWVc43lYyHC+pDsdT -zLXA8TqD7Rccjxx5geNJuT6fBMdDecHxUAscj88qcDy+5QuOp9RBheMpAXC2T4LjJWXD8ZJocLxO -YunXwt4peP4Nx+uquSpwvD6eCsdT8cAbjif1DcfTx028LSJowrqzwPGWUuB4Ww04XpepXIHj9ev6 -AzieJu0Kx9P+o8LxllLgeFsNOJ7G6wrHU6T2BcdTrq/C8RSzKnA8BUfecDwF4iocTye+4Xja+FU4 -nvaoFY4nb+I3HI9G0wLHM7eoDMfbSobjbdXheFbDm+B4uFi/4Hi0mRY4Hg74BY73jmpHvPufheON -1dOZ4XijHV9wPI4rcDwpbzjekDNAgePhv1XgeLr9bzgeaoHjDbNmTXA827F/w/H4OQscT0qB4xGY -e8Hx9BQVOJ5iLxWON8aqhUhwPCItBY5HbL/A8YgwveB4FEEmOJ7CPhWOh5PHC45n2Y4Ex+OwDMcj -UP6C46EWOB4RmALHI8j7guOR8ChwPKsFTHA8PYBvOJ7xNxIcD9/xAsej0/sbjgc2ocDxKDvOcLxB -svkbjoehfoHj8Y0yHA+LpW84HhZOGY5HAqLA8Szb+A3HI1ZX4HgoBY7nCY8vOB6W8gWOZzb5GY6n -m/OG4xF2yXA8XPkKHE+hjDccT5GkCscDBVLgeBg4fcPxyL8WOB5powzH0yD5huMpOFvheNA+XnA8 -qRWOR76uwPEobXjB8ShJKHA8K2/IcDyUFxyPLEmG4+kiKhyPlN8LjieESYXjDaq6MxxvgFL6guPZ -OJngeAylBY73HoRj/P59cDxLTXzD8TSQFTjeWOSVgOO5y1iF40mscDwpFY6H8oLjoWY4nh2W4Hik -B15wPIbTAsdTMqDC8TSev+F4GtQrHI8pqcDxNMy/4XgM/gWON2j5znA8lBccb2CBmOF4KAWOx5kv -OJ7WMBWOp+uvcLz3b/mn3/i8aGH7huNJPa8jwfFYAM+Z4HhSfha2X3A8VFatAcdDoTo24Hh81nk9 -FY7HYrnA8VQlU+B4Cnm+4XidpFiG43UGigzHU4kPdm8Fjie1zSfB8SgFKnA8jvkcFY6HWOB4/IHP -neB4UkZ7vuB4KkepcDzb2WU4Xj/GH8DxVFRS4Xiduq8Ex+vKar3geArmVzhej/T9guNpO/CG43XI -JhmOR3S0wPGIs77geJ0GggzH64u7HXA8hTDecDwCGwWOJ6XC8VBecDypFY6HkuB49knfcDxdWYXj -6Y4VOB6BiBccr1MFlOF4xBszHK8rK/sNx9N5BY6n36PC8RSxfcPxFHeocDweigzHU1bjDcfryqkW -OJ7iQBWO18/xB3A8PWMVjqfPqnC8rlz1C46nK6lwPGJPGY6nP/mG4/VzVDgeQoHjSXnB8SQWOJ7d -rgzH61QEf8PxdKUVjqdqvALHew+Tv3PS7mpsfcHxOrjwkeB4Xcy+DMfTIW84HmqB43Vy9zPB8aSc -1/UFx+u8Tz3B8VAKHK+z43iuXwWO1wHYZzheBwU2Nxyv6/V62hccj2LqAsdT9eVxnM5T0ssgc+wX -HE9qgeN1TKAzHA/l6a5EzFWq2ZQGHE+KO8Z6OhWFYtIMx1PUpx3cWIfjadKqcDxlzd5wPI2HFY6n -UtoKx1Nl3guOp6RJheMp2FzheJ1N3jccT2NRgeP1p33B8RiuXnC8jolWhuORrMtwPGUa33A84lgF -jte18fmcG46n3/YNxyOCWOB4Cn5VOJ5inW84Hh07GY6nIGyF42l58Ibj6cQKx9MoVuF4RGtfcDyp -FY6nS6twPJQXHK+HqdCC49mZGY6nT3/D8Xpg2xccr2t5leB4HSjLNxyPkbnA8TqM+AzH60CbvuF4 -GgEHdJyA41GPfPcEx9PDb07eG45HELTA8TqsizPB8XStbzie5pUKx1PWuMLxlG5+w/GoVihwPJ1Z -4XhaU73hePoGFY7XbcWY4HganF9wPHVuVTgeY3iB4/Xe/gCOh1rgeChnS3A8KU3L7wLHk1rheLrY -CseTcseJq77983zB8Rrbts+G49G79YLjNXmJFDheo1Z4Jjhee+4/gOP9/O0vOF5XgUqB43WVv4z+ -BcdDvZ4Ex5PSlSdYcDwpLK0LHK/j+57geBJ+rizB8Uz5guNxXn8SHK8rK/cZCY5HJ9hxfcHx1PFU -4HhqdrrHkeB4jYThFxxPnWsVjqe/WOF4muvfcDzrzEtwPN3oCsfTIW84Xgca/0lwvB4utQuOp3XQ -G45H1UGG43U9DAWO13Fq/4bjaVqqcDzyfgWOZ2vvbzheDwTjguNp5f6G43XczzMcTw== - - - Q0GF4/UrkEgZjldVXvwruoscjtejvqzA8VALHA+lwPH69fwBHE9qheN1asAzHE/KG47Xza0ywfEo -RihwPK0K3nA81ALHk1LgeBJecLxurncJjkeKO8PxJLzheB0TkgzHk1LheFrnvOF4XeNIhuMpF1Xg -eCp6e8HxaHMrcDzVDVQ4nmoB33A8ZYsqHG+nKgKOp9LuNxxvEEnNcDyUAsfjzBccT2qF40lJcDz9 -8w3Ho7KwwPEIJxU4nlgobzie1ArHg5lS4HhSXnA8iQWOx00scDyUbzjeIBGb4HjK71U43oDrW+F4 -lEIWOF7HICvD8aiMeMHxOvmADMfTmRWON+hw/objjda/4HjqdaxwPPUwvuF4o3++4Hg6s8LxhjCH -LzjeUD6wwPH0zSscT0WgLzje+NxfcDxTEhyvnLfgeKgFjielwvGkvOF4Y1lxBRxvfAKbFHA8vaRv -OB7FqAWOx1UUON6A5vwNx6PzM8PxaEMocDy1gb7geBoGKhxP406F46mo7g3HY7dT4Hg9GoQWHG8s -R7gEx2PkyXA8vaUFjkdHyQuOh1rgeFIqHE/KG45H60mB41kOPMPxUF5wPDUcVDieqv8rHI+k5guO -ZxH8DMcb1pOb4HjK5b3heLh4Fjje8oAOOB7dQy84HsuwAsfTMqzC8Qy+/A3HM1BzhuMZwybD8QbU -rm84HkX0BY5HT1KB43G1Lzgeqb8CxzPcRIbjceYLjocHTYHj0bBQ4HgjSD8FjgcKo8DxrOcmw/HG -0f4AjseZBY43CBgmON4gi/4Nx9OFVDjeYOmW4Xiku19wPEWvKhyPbokCxzOs1hccj5L8AsfTRVQ4 -nuIPbzgeBJMCx4NalOB4NKa94HjW5pbgeESIChwPt6MXHE9qhuPRsFXgeCgvOB459QLHswRchuPR -s/INx9P3rnA8vREFjqd07RuOt7jfC46njypwPF3CG45n6q8Cx1MEsMLx9KRXOB7tii84HoX4BY6H -x3OB41mj3zccb5g5coLj8dsWOB6htxccT29EheNpwVfheBrD33A8KpIyHE9FrRWOp+n+BcdTCVSF -46kE6g3HG9T0ZzieksQVjqdj3nA8rYkrHE+7rgrHU4HnG46n2sQKx1OAp8Lx6M7+huMpr1XheIQr -MhzvHc/+ndHyfz04HqHaFxyPyoYCx+NrZzgewguON2gcz3A87MsKHE/9T284ntQKx0MpcDw75huO -x+cVOJ6UCseT8objjfksOB4miAWOp6H5DccbVGZnOJ4GiArHI5X+guPpxapwPOJqBY5HrOQFx+Md -zXA8KmAKHI/v9YLjoRY4HkqB4/FZLziemqIqHA8kWobjMVq94HhYvJk7aBzH1uJMcLytZBxbUh2O -B1jA+F2GvYPgcCbB/uLHkVVxlFrOChwPG/FvOB5igeOhFDgeIagXHI+gbIHjKTRf4XhSXnA8vQMF -jmcB6gTHS0KC42014Hiy1PucbcPxlFUY1s2Z4HhABKKCCZUdJNHsgOMlJf8aSw04nkES+objwYbj -BiY4HkttehGDjsdynBVHpuOhXlfbeDwUlneOx6MD/naX58DjIba28XhgPth7Bx4Pk2MWJhmPp4fJ -goiBx9PPWPF45nH7jcfDVqLg8VhyFDweTZkvPB5qweOZ50HG471H4H9yZvkX4fEIt73weB3YUsbj -KVhX8XjMvS88Hum5gsdDKXg8znzh8aQWPB4h1YLHo23rhccjAlnweJ3FfMbjqeTjjceTsUjF49GB -V/B4NM298HioBY+HUvB4lIV84/GU1Ml4PHoKMx6PrM8Lj9evs+Lx9HtUPN77l/zTb3xaSM2+8Hid -rGDG43FcweNJeePxuuUKEx6PaGrB40l54/EUqal4vAHpIuPx5Lv1xuOx1Ct4PA2aFY+nDcgbj4da -8HhSKh5PyhuPR6N+weOx7y14vBGO6wWPR2Sg4PGGYXUSHk/P9xuPx4he8HgqKCx4vC0kPN4SA4+H -j0LB441zRaATHg9324LHo6Yx4fHs3194vC06Hs++TsbjaTh94/FQCx4PpeDxpLzxeOwTCx5vnFfF -4+EU+8LjDd7bjMfDZrvg8baSi0WS6ng8/cmKx2N38cLj4c1R8HhE3TIebwsZj5dUx0FQaVnweGMG -RCDj8XRmxeMRwSt4PG0Q33i8gZV+wuMpSlzxeIoHv/F4RIkLHo99XsHjkR554fFQCx5PSsXjDQyu -vvF4RHILHg+l4PHsmG88niLYFY+n+G/F473Hyt85byvB/cbj/SyeKx6vt+sLj6dmtjceT2rF43XG -m4zHk/LG46FmPB4nFjweH/7C41mtQMLjqSyq4vE6DLJvPJ622hWPpxKAisfrC+mZ8XiU4hQ8HkrB -41m5zjceD7Xg8VAyHo8/+cLjqRyo4vH0k1Q83lYyHs/UjMdTzVDG46lY+BuPR5lSwuPhrlbweKqz -fOPxsJQoeDxKBDMeT3U0bzzednNYx/XrC49nSsXjLS3weJTWFDyeypjeeDwr2sx4PC2xKh5vKxmP -t9XA48lLueLxOq4O33g83dWKx+sUdGY8HsVU33g8xILHQyl4PCkvPJ4+v+LxOu43GY8n5Y3HI8qV -8XjY/BU8nj0k33g81ZFVPJ5uRsXjJSURsbYaeDxqcgseT0WCbzwefcAFj4crZMbjbSHh8ZYYeDx9 -fMHj6YF+4/H0CFQ8nvYNFY+3lYzHMzXj8VSKUfF4uq9vPJ6q3Sser0dnxMLj4e/5wuPh75nxeJh5 -FjwebbsvPJ7UiseTUvF4HPPC4/Uxv/B4NDMUPF7vxx/g8TTSVTwehWUFj7eVjMdLKng8fVLF4+kv -vvF4XEfB40mpeDyUFx7P1IzHQyl4PPuW33i8jltwxuNpzq14vK1kPF5SHY/XiThkPF7v8w/wePwm -BY9nXssZj6cixDceT69JxeNhU1vweJoh3ng89pEFj6dqlYrH20oZDJYaeLz+HH9R8XjmOPCNxzOj -pozHk/VlxuMNQjPfeDyzhsp4vEhUFzye1qYVj6eSkorHU77gjcczNePxTMl4PFO+8XhaN1c8HkrB -46G88HjkwxMeT/+ueDyt0r/xeCqmqXg8becqHg9vuRceT2rF40mpeDx2eN94PDweCx6vKDTjc+IL -j2fOoAmPpy71jMdT/P0bj6ftUMXjSal4vKQkPJ6pGY+ne1rxeDiwvPB4FBplPN4Y8wuPJ2PuNx5P -asXjYeBd8HjqKH/h8SRWPJ6UisfjmBceD5OagsdT5q7i8VTM88bjSa14PCkVjyfljcdDzXg8Tix4 -PD78hcfTV6h4vEHpQcbjobzwePu4wOMNZriFvsOK/QuPNxS/T3g8DScVj7eVjMfbauDxtAGseDy8 -H154PJX8VDwe96rg8ZKS8HhbDTyeAlwVjzciQ5HxeIgFjzfap+LxtNn+xuNJq3g8lIzHQ3jh8fTp -FY832vjC4ykK8MbjrVF6HdavLzzeVjIeb6uBx7M/kPB42o+/8Hgqh6p4PIvXJjyeYt5vPJ5CyRWP -R8S24PG2kvF4pmY8HuWoBY831N7xwuNJrXg8KQWPh/DC40mteDyUgscboEK/8Xj6KQseT3ex4PGW -kPB4Sws8Hrn9gscbWr+98HhMvwWPR9VnxuNtIePxkhoxSlVXFzyexsE3Ho9HruDxNE5UPJ7Cxm88 -ntSKx0PJeDxOfOHxUAseT0rF40l54/F0aQmPRxd9xuNtIePxTM14POpRMx6P2N0Lj6fSkILHwye4 -4PFo5Xrh8bDoLXg8LToqHk9f4Y3H03EFj0cItODxqLl64fFouSt4PNoPCh4vKQmPt9XA41HmU/B4 -/M1vPB6J/YLHw42m4PFQXng81ILH47MyHm9VlhU8Hp0XBY9HZVTB4yUl4fG2Gni8sTNYjr0zo99v -PB4FZwWPR66v4PGwkX/h8VBfeDz65woej5VmxuMtIePxlhh4PNX7FTyeBpWCx6NW1/F42mFUPJ4p -33i8rQYeD4vGgsfTvuSNx+vX+YXHU9VRxePRmvfC4yk0UfF4NPd+4/HUHVbxeIrAFTyeqpXeeDwF -SAseTx0zFY+3lYzH22rg8dQoVPF4aml94/Hot8p4vK4euoLHe0esf2c8/L/j8f7r4fEuNyQpdDy9 -zfeZxGtYmfVi46naRqXkGY2ndRR1LUHGo/dOiZkA46nbU3HSwsUT8+tzPBuLB2ScHaZT8ThiPBmK -99COspF4EMcdig0RT89hO+8KxLtVquJdV0RgNNBCI4+iYqXmtW8vPLwT7+Nz8/CO0yriA4dHSs2R -TIuGZy6IY8PwpltRLBaelsaqjiwovHHZOLlIeGod1nI4OHiC/1xHrxg8if0am4IHSmhcG4Knj5nt -zAy86SuLhcBTU8jZWtDt5PjxmVcG4Gmnx+/u/LvD8dELf7eEzFoLccPvVLX9bKadKi77F/lO9lSg -kOOY26JCC3snKO95VeidNDWHL+Yd5N55b+SdUie9X5V4130KXsS7H4G98wLeqYlfdkqFdycU6n30 -jbvT+3JEDlYP9RIy7m6LjrtTzGJ8MsXuMr+2ArsjV30kJt5nQ+4+9Y5/Ftbu4+9SUO0UHWhjfEHt -vKZrIe3UfvgcX0y7bh76G2nX4+UJop0KR9sXz26o/bUlnN2wYMmm2TnqpMLsHiveWiw7wsGamYJR -N7r1KxaUHYkCPeQhno/X8jnITrHAPW45EooAYRsbY4db93Vtit1rlIzpzUqsmtYx9rgkiF1TTz43 -PyB27TiNERcQu4aj6nlkih3avK5NsUO5+70pdo1q5z4rxa5FofOi2ElhkbsodlLGp1+VYtcOb+1Y -FLt2NksgLordzxxrzR6FYtcwHDuOjbFrWikd/doYu6ZlZ3dAXXDsEE/6LTwyijLbxthJ4L4VjJ1U -fpSFsZNCpmVh7KRQJ1Ywdu30ioeFsWt4LR3H5ti9f8o/xU+tqnna7zLHTiqpgsWx4zjCYsGxk7J9 -3jxAIpEE98LYSbENb2Dsfl4BAheFYtdA9GWMXevWrr3odE2lAoQRM8VO1XdqEFqirO6O1jfD7tQo -ftwVYSfTOXlrL4IdLnTP3AC700o2C76Oqv7j2vQ6bTTYPga87vRMUmHXqdNBZsaBrlPy0dp6nFyn -mh82CglcJ2tHGnGCWyeIH+HFAEMtIVPrtujZNaNHzV+LRXc59Ksg684AUoSoGP2NVaQD67aweXVb -M1ydvsfjNcnK3J8ejiisOtgsw+hp9DpKiH58mvR0GpecQXWiE5w0Wzh07fSO+MWpU8ICymXG1PFe -4erolLrT7VcWpG4LiVG3xEDUaetpEXUnzymnrRtVAHWaoI85tiizOVqSAk+3hEyn26LD6bS+IXoV -bLqHVpxfhUxHTf5h9XXckecw89Z1i2TXRxQkY+makk+Nn8mDAK15bnZh6Vq/PNKXsXRSWTstLF1T -XJWemcDStRGb5oylk2p9reHm10ZAdQJL17Cavn4VKp1EWsYWlQ6FNz6odCjEGjKWrg== - - - kaUYFgqkv0hjDBHZhaV7j34xBao25rSBLGHpOvb4rW8snUqALHYTWDpZKnmDU8LSdczM2thYOsyY -rE3SsXSYNkGHylg6VB8uzSxBJ3YfY/FU5MOBx2Uqnfa9uN8vKp222W6o6VQ6JXTddDNR6fSlbvfR -4HN1Yjf0WSAzP4+/oxlKx6Ya38eA0qGQhwksHUpw+RaWDjUsWqcdRMQwmHQoz2MdCsGk0xc3y8pg -0unSuemLSbeUzKTbYjDptP2/WVQFa64phsimJTPp2u1VBvu4MF1bULpGzY3iGRlKp+Po215QOiDI -TPEBmwOXfbFIS1A6AMcdULGruhu2WQso3VYylG6pC0qHqwwtwgGba3jsqpo8Q+naFY3D6zgBSub4 -tZh0S8hIuiUGka5p9wnULkhzjaHzuH8VIp0uw2KZ6ziQiRYxxVsdnxZGrgykQ7UZzoF0YMgfB9eR -2Wox2hYgnVRCAgtIJ8UAjgGka/SePnbMcjPSpWEFtYB0ILRxfQ7QnD0px9yK3VcSS+kwGN2nzT+s -85OSQFRJ9Si56NsMTQs01+gHOMZW7GJvWxrt457ldexAuq1kIN1SF5Cu0Tmi1WuQ5qCVa2GceXR6 -CmwBvQ47TmtVMCDd+mcB0m01gHT6ILvRAZoTdV7HFx5dU/RhJlGvjLmDBI7OnIeOo/Do8BpqHrT3 -reThJq/Bo+vAoZwUtu3QFGc8nrl5dFJw+V48Oo6xaoJkFicjLual4NHJdQm7wgWakxGQ5b0zkK43 -DyPvww7/YYNHl5TEo9tq8Og0cF7es2J2r+1jdo8FR9fpJ3rOjaNDIQbgODo8iyB4Jhqdib1vGh3n -kdsPGp19Ry4q0eg0IdlSKGh0mnNtLRQ0uq1kGt1Wg0bX2W/r9fb4O2aNHyoYZnoE3H0sROyz3GnG -lipqVmR6zTA63Kbo/Q4YnYYKp5Q6ZK5d7u9ZYHTsxuiqXMcJQ9TDy4rd8VLKGBDqgtE1Fd0QbgsY -XVPR60GzWYLRtfmxjdmC0TXaXh1/wNq/Yf1+PRVGpyVeYxHvmLlGsGNs7hxxlM/pL1IcxWcMKy9h -hn28BzKz6NCu0H52EgjH/WuR6BBGCI5gkBGaspWLQ/e4nc7C0EmAip4pdBraqL8JCJ0q321sNwad -LtVuyUbQ0QV1n5tAdz2x8Hdz4DM215k/Z55Zmz6n/Fnrz4bPaV2ph6qw505fPy30XDwPizyn04ji -ZPDc5e4pizunufTsbWPnbnfDKNQ5KsOhiTl1TjOp9Rs6dG4LiTm3xEDOqb6HQHoQ55qmC4b/jJzT -I8HWfCHnmjqOI+Rtz7Mq9cd5VeRcoxfgvDdyTgpVbAs51+hw8Prm5S4vlWr6QM5JYNIM5hxHcIsz -c64pHKlg+mLOtY+bZS3mXGuW8i/IuUa3cCLOSWCZu4hzrbllTSHOoQ7/OLuLOlObkiDO8dmsUTNx -Tl+AoTuIc8+zWXN0aibK3PNsvlxjwlIMP7hxrUFAr3i51k+LY6+jhkUpVvXPEjJbbomBltOGjNK2 -xYxrqkfQQ13Yck31Dbd7kdtxujWKuS22XFISW26rwZZr3Y29gi3Hl2mERRNbDlXb9cWWk0JEcbHl -pEzuVmbLSWXcW2w5lPvTNlsORUvpwpaTas3KjpbTtdKsvJBxjSqYO0Hk7Fv6+LuP+3h+aKHltpLR -clsNtJz9hTOB5LSZphWuoOUas3bPxymuOdxAZ4Ud2SQWWw6FJ4l/OlmOsOaFoY6T5baSyXJbNbJc -U08JOzYnyyloYd3YmSwn9eZ3C7KclPOK4/glm9fDFLKcVBspgiyHcl/PJstxptYshSzX+mFhrkWW -0020zViQ5baSyXJLXWQ5vRKqBljEuNbcRLKQ5Wx2xYDd1cdi74srF//eVDlXFhEKSyyNvYGK05hH -mrsg5XjcyC+u45pbNy+mnEKzPMOFKSeVZ28x5VAOp8wRxOHMz3NXphzqec7NlJNCHG4x5aSw8y1M -OV2bn+lMuaY03KB9zJlySylMua0GU659Hp/XghX3eFCoIOWejy3ilnhP89kJoJx2+f08K09OYRNL -pDpO7oqkTtDk5NvDOi7B5C6rmlwoOe2kP+3chDgs6THbTCC58xPPp4uygMfIIjByS8gUuS06RE4h -Li3dF0ROm7PnbIUhp7wyA2wg5FQj8/n0TZCTwFIoA+QkcpOCH6fPYRwKfBzl3o5lW/S4c1oJ0YLH -ifoXvv0kVLaw0XFbM3KcxrL2HBsKh5NpFlhlfXx8DhF0eUbHXc0fmEyOw7JrPBUcJ6uK8bk2N05D -UacjzLFxIRRq3BIDGqfoPK9PtF9ovKAIqjRkaDImq7KPm15/sJhxSynMuK0GM04jMRbdixmnMKrd -mcSMU2bNwmfBjFNIyWykAhqnCMbH+icSNE6BMhufAxqnM2E3FGicQilUxS9onLx+2aAtGJw8e0nK -Z2Yc3Ze21IrDMFGizMOZcVvJzLitOjOOnqT7skQB3uYyd6ZUPzPj6L2ksDWYcb17L/Fixr1DzhGM -/meZcez5tMwuzLg2I5gYzLgWxnOLGSeF/FlhxjWlsS9a35wZp53saetyZ8bp7nuJS2LGoc62kXES -LCUSyLhGd/Q1KjKOH5NRM5BxUkh8BTKO0JmvTBYxTo8Qa86FjGt0EV6bGNeuT4XFKXpntj0Bi5O1 -NimjBYvrcibQ2rzA4hQlHvZWAotTSIbY44LFKYBBXKjA4nr0BgQsjsNuJ7JgiKrKaNUCFFgcKvAS -b3YnPmLZcGfFSSGsXFhxZCE0yC5WnBXGnedixXXNuNp8F1Yc+Aom94DFdVz7BToLWBx9z4psZlgc -GAGVEQUrTk3UFLoGK66T0T2eyopTVzVp3cWK4xvpXVqwON1mW3FkWpxUTB8XLk4KoeyFi8MD/oQe -lHBxRNL0my5cHIrmj4WLQ9Feo+DipBJpW7g4mru1bly4ON0exq+Ci1N8iizV4sVBvrj45s6LEyEV -L+rCi1PzJW/34sVBx6ACInhxurc8bQUYp4AXW4UFjOthIBTAOA2Q5OsKMK7xGhz3BsYpKoZhTAHG -SSUAs4BxepPwbV3AOCmsIwowTiqj3QLGoWiPsYBxKOdZeXEkMDQ6LmCcLkJj1OLFNbBiegczL67d -jw0QixfXKHPubfPiNBwS0ynAOBskx7GJcQykNl44Me49BMfo/fuIcZY7+LRKjNNohpNLEOPaE8nb -IMb1T6T5EjFOoof8gy70uSyQsYhxKIptFWIcKuTKQMbZcYcT4mg3/5j9V0HGMahew1I6ZOeUZKOo -ZyHjNKBj91aQcS0wMAsZx4xEoD6QcUq7UeZTkHFSqR9ayLh2Bj4zkHEomGJkZJxUaJxBjEN4/E9C -jONE/KAzMU4LGFzaFjFOl2/Lu4B+vX/LP/3G50U1nc+ovDi51F74YbuFIJVJGEU7LU6VSfrCBRYn -kZVqsOIknOPZqDhqnq5ZSXFaGxMkDFCcZvMIg+l1l4tce56KidPeh9hqUOLkf6XV1ILEae4/+lMZ -cVogzGduRNzpudlFiNMRn/lFiDubLQMWIE4f/rk3Hk7doO0ZlQ533oYMWnA4bdROmsadDXefNhNm -Mtzjns4LDMfe8zwXF+5ndWBB0cKFU1zdrjG4cC3y54sLpyU/+dfChdP+mFKRxYUjgqnt2uLCEQw9 -+qhcOMUBWBUtLlwz5HTbXDgFIHhRCheOsMRz35sLJ4XI2uLCofQvLFyL1s+FhUNRfNaxcJx2PVfF -wunCtHpaVDjdL0I6QYUjlGCfnKhwCuTQAbWocIq+sd8MKlxTR6htLTcVTudZrj+wcPo5rHAvsHDq -AGcLnqlwMhgcfW4onJ6HiQuCMeH4PHb7CQlHTfW4NxFO8xTFlwGEe2JcyTw4TbjjeDYOTqb8UY1K -JbC2+R88vRMMDtfl+94suMfXFIsFp7YSiiUzCu6J6KaT4PRvkhEBghMC63gKBu45/aY5BY7b44w5 -suiPt0kWBtwdmZdAwGFRMsciwH0Pe79zCp5eQ1rwb9AYvEPdaq2Hz4NBfxMeM6r2VqxRIjjlYL8p -rn0M6z5j0anOtosmqER+kxPyp7noH27zUXDflAcG25udvCCzX7bTPG0rJujb+BXQNxKYzeqiliWo -qo4px4rmI7X0HSp+dOLbGe5nGfimwqLezBKZ5xtf48tdurVmkPB0FyIQemoJc1vbGM+M6qsOfEU9 -XUmrFn89sd6Eijya1YgytV/dScBBeru/GG8a2yyvGow31Zm6gb0z3lTmBqI6Id6UvLA1VSDeFPXF -uWkh3lq73K01Ed40rrCxCMJbo5cyWrM0uGrkofYyA96UBbHOuwC8kRqzbkoDvMlE7vrCu11mc7Dg -bupKxVXV2W7HY9vOgnY7fVW2yG76RNIhAXZTqabiJIXrpsYTmg4c66aSmZsKJKe6KWLEoJehbrfX -1y6m2z3CKdiRbmDOp4GXFtFNGB7g9SEqmGoVPc5zo5twOkovxl3zwLEVLS8op9lBZJ5vR4YUlJuK -zk7nJLNSFJWCe+0kN1ljadtfQG6312Mujhs088+5MW7QhfTCZYqbHmi91QvipgLdG6qbu5bJs/H6 -HIXhdjmnbSHcLt+lLoLb7a4/BeAmbIaRE5zfphSs+R0Ev03JW9KJGd9Gfp9UodPbdJ4VHgS97T49 -m5jhbZevKxa7jbKRvslt0/fKGdw2ryi5cm6bxl5q1gLbdtglFmjbMb3gIJhtEk4/iuQ3hSbzrsQ2 -VagQwAtg2+ErgsVrk0+sXGEKr23iu/dr0drGx4K7AWvrHpssrDb5Whxu2vyY7ejPbz+sFdYtE6yL -PHPahgc1FqZteLp+UdqUaRyOTFuQNolWjd2jclKItr4RbeO25W0htBFjgPvhhDZZj3e6s3sUbvoY -nwFtOq3fNsmYvYyyLf3ZeDblWI/LiJWLzgbGhPJ9h7NpPWI13Y5m80KojGbTE6dvuchswztIFpht -DotNFi4bzWJKXwSXTaRQTekLyzYwVK5QNljl2ukHk+3yGtuFZLsff/AzkY2svN4iB7I1BY4ofggg -W8Md/O4VyKZJhAFpAdlImk1c7BzIZqveWXlsBv27No5NK2ZLTWccm861vGrg2PRq2+owcGyt+TRa -cGxV1btsCo0mzmNrUWFVeGyoDJfBY0PRL7Z4bFIszZh5bFIpm188tkb18zM2j02K9UdlHpvmT+wR -F4+NBL5lq9zQV1M4dbqFx4aq6F3g2CRQTBQ4NgmzLb/IddZh3W1OYyMtPDOOrRGnnHfFsTX6sa6+ -cWxSPJbkKSMtSe4LcEjCsTWNFqRYHMemNI89Uo5jU9nXMCrdxrHRsEUxjdPYlGu33HrQ2BoeToqM -ZRqb0jAsZheNbScBgsbWqOq+R6WxtbCvXjQ2lNk2jI0Tj6uy2CQSkQgUmwRWC4Zi0z8JxBQUm0pS -2ZwtFBtxGspWA8XWaLLVYjij2KRSXRUktobnlZeeEP6QwjxRUGxSP3SDBYvNbiG1OA== - - - R8SdLovsZxZbI62pt89ZbMqbUTq/WGwNjOw4M4uNSkCtehaLTY+IFZMHi41aAtJymcXWiLe3Z7PY -dKZVPgSLrUXTU2GxtemF34vFpo49ltaLxdbYiWkfm1lsDbDrHJvFpjNt3g8WWxNVTxeRUWyNEjPd -/kCx6YuvrSINuA3YmtadCcUmkQKDhWIzBVyXm4iVExeLDZUlS7DYpOBftlhsLRBshcXWqIRxNCeJ -8jZ8TbJYbHpD7bMyi41aTBu8ncXGVRy0hzuLrXGj2OwmFhv9i6QyncVG/T1xxWCxqZmRaF5msWkM -cM6Ds9g06FjsLVhsKjezAT+z2NiY0JUdLLYWXTGLxdaGm4kUGBvjjq2incamF/W22rIZOeKAIGQc -GypN94Fjk9ITjE3/HteoLDY6Lh4/zWYsUstP2yy2FlSKwmJTsT3J7sViU+E7XQmLxUbOkB83s9gs -Nn6PzWKjRejm/ntLhVJl5AgLiw3LSLKSwWJzw+G+WGzg2hlTMotNiTzWhYvFpjg7257FYoP1S3wp -s9iMC3w8m8VG2J4NYrDYenP8QWGxUUDOaxgsNnpxmMaCxWZw+adXFluPn3qx2FBstPGGXc40a4bE -YpMfpUWWgsXWo+J2sdiagWVGZbG18FReLDaaM9h/REpcaRQrJcksNs7kbwaLTWkkShCCxSYAs02n -CcWm67BlSqDYOou269koNpLJUU0UJDZl3ohtLRKb2jds7g8Sm7odbCWbUWwUpD8LHqDgq5bDDDaB -YlOrgf3gGcXWIbvoRQ8Um2611fIZio3uK+JSGcVmHirt2Cy2jmHsp28WmxT/pMRik2oFl8Zio0/J -Ip3OYkPxlcxCsZGypl0/UGyW2bo3iY1uDabBRGLT17ZyPAex6XWwagMHsSkNykIjc9gcMf1sDps+ -iJrOBWLTBdgokEFsqFhuZBSbcuuUwi4Umx5020wEio0ePWrQM4pNiWFWS0Fiw07YbEicxNaHRa0K -iE2JYkwQgsPGD4tTQXDY9PhaMDFz2PQ2UIC+OGxa6tm6JDhsDWOs5/4q+FSVjxXnG4dNFaAs6BeH -TVM9NRsFxKa6IjIEC8TWIL9/bBu3QGxahlBGsEBsyr3iLhcctoZHUDsrh02LYWucDA6bNlsW5AwO -G3522L4lDNs9bUhfFLbLh6cFYYPrdx4VwnZ6fmYx2BR/gDscDLZXTPl3Bqz/9RBs1Ap0R6stBBu1 -AjzroVIZwFvoCDaEy0/c07jyr5u/pkfT6imDv6YWQw8IpAykVNA0i7+GMh5TWE/YMad3kqxq54hQ -LP6aFIvGBn9NCmXFhb+m5jTbHGgV/XhkbAHYNCBb4VEGsGlmsMVGANg0LBDUWx3NKg4jhFoAbFog -eIzGAWyqMuHWLwCbhoDDW8fLuMA0Gfw1KkpOp3SRSZNCUV/hr6FSyBb8NZRxPJu/xmc5220DljrO -j3Pj11QB56OAc9U6LbVHIrJxJvnqzF/rbCcyf20rmfiVVO89wrk+89dABHzz1zpL58Rf04VZNC/4 -a/hU46KQ+GuI2EEEfw3l4Bs5f02KVgsFvybRahQCv9YV2qHTPPBrUti/FP6afLWtBzQAbKISsCMI -AFsSEoBtqwFgk+t7AbDJof4NYMOmvgDYZCtfAWxJyT/HUgPAZjb8CcCmn+MFYAMHUABsMut/A9hQ -C4ANJQPYJLwAbIgZwAZIogDYpLwBbHqaKoBNP+RhfqRPGN55O0gBsGmWt2xGHKalBkuU4K/Rhnh7 -V82yxEI9nK0Gf00KQcvAr71H339yVvkX4deIrtnjmfBrinFikrHwa615Tm3h15hyzQQ7T830Fo1N -X0PA8Tzoa5yoFWGhr0kleBn0NeKnNJcHfY2mJiL8mb5GvDGsTy2w8/H/WPQ1FVaY6U2mr8k/w1xR -g75GMxpFTEFfa/EEFvoaKq6bQV9DmU5ps4DTJzLIGb+mnMzHYu7GX6PDDtx7ANhI28xAtEWGVOUJ -5k/hADb9IrbKCgDb+7f80+98XpQ4tdaQBGBTUyndnAvAxnH6wReADUWrrQJgk8oruQBsxE+XgJHI -x6bvwl9TdMb7WLyMoKm2gOan4KrJMcprPtLGnL7ekTltHdb8c2z+mrYd7XI62uKvoWKNFfw1KZYH -C/5ax/NSZY+Zv0ZjOuX5wV9jr9vG2Py1bpbevfLXiAZQWRz8tR45u8Vf0/bnNGesxF9ruIaCKXL+ -mqrzrBwyAGxJSQS2rQaCDecA4rSBVmNYOu9KYNNu12oXQ6VE0PozDcFmwjgqgy2pXobQP7GjDwib -xlTre8kQNlQq2QPChkLMICBsUuzqM4SNPWKffSPFOh2t2k05hA0/UrMJSxC2ztt7zA1h09bfAhcB -YdtKLt1IqkPY9CcPtmsBV9OtpikoM9iwoyA1sA5TrA3/S2ewbSEz2JLqzAFqF6+EYGvh01QQbDqR -yPpCsBG1I2kc90t7Q+vFywg2BSitDzkYbIoOK8K5EGyKA7NyLwg2osNWauUINrZ4bEkDwUZKBCER -2BCb09Wot5FiseAgsEkholAIbMRv774BbAj+SQwmdsSnVf6awtY02wV+TSFf85EJ/Np7pPyd8/aM -esxMX9M+mEL7oK8d/tMt+NrhhlaFvaZMBc2HgV5jVXVem7x2eK9sAa9JjGFZP4nO0vu3sGuHLQIL -dI1Mf+uLuUZ+4Xg2co3aNOooE3FtPh7dDuCanNaiko6qHUNE9opbU+ql3XPT1iRcDlEj8k/9zOep -rLUj2MeBWpNgjpmOWtNfe1oFrclMYkPWjo+FbhdjbQkZsYZ4bcCaWsXNBgh22hXBz41Xu73szCXF -rIlFB1xNk2H/QquFBcwiBdxeqrOIadrfkKDMYLXlVRDi+fGWxsCqneERmrBqSwusmqpdSN4FVe2O -TXPeo1P4OPtmqmnhZI4AwVTbSmaqbTWYas8dTZSOStOAalNVIqppDqG4MEQsp/q1eWoqZGIYyzy1 -O+LkgVO7zXllwdRwmDyPClNTUyihkmCpKX1LHjxQanjTfp5KUiOE3Z8FUru9Z2NR03gatFnIGDXC -beT/naIm3kPgxEgnbyFhk5YYCDUFxs041sloWnJSNZYBagCAR9uiumRjwscWawuJn7bE4Kfdbuez -uGjizuh3Kfg0/doUqS+1NS+aWPi0rWR8Wgur84VPU+mE1UUHFk19n8o6FHqaPmRmMXoEFjvtDA+l -jE47LcUT4DQs7N2pFG++01sOCjaNLTjNtg51ko2tuRI7NO0cMc0kZtppRtOLmKaS/usMPJou9bYf -qfDSDnddWaI2a4zNQUtbQoalbRFW2uHeXAuVpmAoHVqZlKYLeNrmpB33NosFkyaBVXOmpCHec0PS -JDQXYKTxxVqviDScaM9rE9Km1XUsPlr8O9PRluZsNGIBWrBHh62c9PHVzo245xV9VQ5Gu9wKZXHR -ro8VamYqmvAEn2sz0UCOf+6NOlPTFR+biWhs7LT/X6oKRrzUxIloW8mv9laDiNb6vco1AKJZu7z7 -eywgmpkF0fbhQDSZLlordSDR2vAVVkGimUGRmyiw81/p4sJE02KxiO10aNBCoqkzCGu1gkQztYc6 -Lz/OLaItb4ByhnJH2YsZQy0iGgJlZkFEQ6EcIxPRSEoztjsSTQIJjYVE06qZotcCRVNZiyXxA4qm -HZaN9wFFw+HM3HQSFE2qu1g4FE0K6cYFRWvudpSZaFgMNm+/hYlWFB4GzvOu6YVEa1gvUbdjZVPq -weZBCiaaYuJWJLKZaNqe2I8WTDQptlkLJlpSEhPNVIX0+tqfhN9CMNHwDrm9qHQx0aj4saKEGQaG -3nO7oGiyeeYtLlA0qd625lA07KCdkmbbAI3plE1mKJrUewRiTYsUDevmEuhMNA4Bs5WZaLirkBKP -xhhl0ShYXkw0FdXYjJCZaO3wZrXFRJNCPe5iojW6QT9PZaKhshILKBpntntbSvLhYTK5V1SamvTS -BBJNLXdW+BxtzyiUxmQk2j4ukGiN+cs9gtzq27aam4nWFFPHXdOPGf4bLCbaVjITbavBRNOmzCAG -wTrD24DC94REU+WN3cB12GE9W4uItoUERFti8NAUcCLCs3hoLTIGmYeG+Hk2Dq1Nb5wOHJo2vmy8 -Mw9NIiNj4NAQmh/FX0Ehh5t5aPp4e4WDh6YLxR92gc60I+dOZRzaHqHXYefHi8uCh7aVzEPbavDQ -7A/MhUPT/piAaOGhqSTJgnihEkElIO48NAWhbauXeWgK7hqWInhoLSwt1y5nK5mHZuoYm4dGPSgd -I8FDaxrFrKM08dCkujeE89CkWJm389AQyIplHppUGzYch4aAzXLg0BpoSCerLRyafkkDmjkOTXeR -d3rx0LaSgGhbDCIaSXZA9EE6a1DEnlaJaMy+uLau45R4PPFrNHu0LWQiWlIjZEjLlEbjIJ1pFHTu -RyKi8dRhx7iOU6kpqYEgoimQyzCbgWgSn3ZuHhrC8K9NsRnn6ekoQDTUHrAzSh+0mCGKHkA0KZb5 -yUA0XZmfKX9j82bEzNmAaFvIQDRTR7Z6JcRwHr8W6oxQmjU7JyJau6Jefh2n+Cist0CiqYCpWfl/ -QqJhEotBdSDRtOT4GADNkWiKNLLmK0g0HWfFFo5EIyD5WMM1OWVKnz79+lWIaApUmzv8Oq558egi -oiUlEdG2GkQ06m2mZ7mJbPM3n/mFRCPVDhAskGgdp3ZHpw2zrFaQ8rgrEg2VuxZIND4Lqlow0VaJ -V2GiKfprFpbBRKNGidxTMNGSkphoWw0mWt95Ja+tU8S5KrYqu20AX6rC7xQZLCaabAK8ezAx0VCp -iMhMNH0eJuSBRGOdaSw6Z6JtJUPRthpUtBaYiuW3qpcPjwHv2aJqlsKAYfsMq9AILJopdxo5OGmp -gUXDXZBBOXA3jdyM90gtLFr7eGHtwqKFAfWiomnxTQ1bhqLJVIdGiggUqA+Q9WVmosl3BsBPINHu -KEMO1pmsL2lfyEQ0RtMjHUWdqbPQKChOFDT2W84/G+F/F/iz6dyEQj9T91J397FJn8xfWJ2gN4a/ -osW/MxT9/0fy2T9yBcevf/M//6e/zx+sD/rbv/ur//Xv/vwPf/Pn//JXf/m3/+U/+0X8vAP/5i// -/O/+4x8c/b/9zX/881/9T3/+m//zP/z9Oti4eP/kGf/73/z7v/8P/+hX9CP/h7/5v/Qt/+bP//mv -/pc//z//3NH/9s//x9//1c99+R//7m//0z9+9+Ii/l/23m05kuPYEv0C/ANeZCZt24Ay4x7DJxK6 -HM2ULiZKHMnGxmggGk1iCxcOGk1K+pnzI+dtfuzEWu4eWZVZTRSazW4AXaKJBByZkXHx8PDLco+b -b2fP3jsfJzeXNh/DGweHLv/p/Pbs/Ppu6nN4U9P4z+lXbTq4lqubs+md2TL++uBnn/7Of/nr6xfa -BH6P+P3LP9xc/6kx613j16MjIX92/nUb3NofDv7wLf5S5C+f3b5+9Y218/M/nH9/qA== - - - vzQN9hcHw+Gn7f9/+/7g9QFQdxsAu+Hwv7cf/quRvj8Mh78//F//ezh8gaf/fHDUdq8i3MsAex83 -5l5tkhEEhh6wArkJw20Ue3W1vcXVwTU79scDOKvaqYmKZE3YoBGgYOnoRrU8j2g5MiM9wiLrPwyx -WZRAD7YPAELefj9EDTeBNrHLXjHCE3m1SW5dXW2+v6RMr76c/WGQvKP5p5Q8+xScG37R9lZib2D2 -QZQGwP2Ssw8aefZBJBfCWzVr/U3k3sjso0DUozLK7KNGnn90FLE/b307eWrk5cFfD2yTvOZDg8Yb -Blz2BOXnakbGu0lbDOK8fCNVm1htb1nHzBrWwc0/uUZea1yUhR+grn9y2fLmiP/j9QFQUWhAb5no -zbILKM2Fy4ZnH5uo6x9DwZq4HPYaeb2NjHytNKci1FyQR+81SCQzBy+3ZxMs62Kc6wTHQfIAZ4IS -y6jP0uUtRNZjIpF3l59pAxnOzfZTlHi/rF57KguRF6kf8Uf6suRJSdEHOQksmm/l3CxAIUq4DB9j -GEWIhJnwoy7TlBQywXkcI4ND+jEn3dKy49otBCL5pKb4HskgcfcwyQlwDBlXctE+VrVbo+TdkBgz -3RLClazxTTI9XlgHukurEIG3PpTFadqeO9SJT/AUKznJ+o6Um9oANCHrbXtWiFQI+dFmwOsqwPsR -2AAAQ/AtSbeokcpb8EvJEJj6wI9mqQYmZKbwCv/4qlNA3V74D2skRKLEyH4apz8ypipCbnaKLIIm -vzSiXk6qT9bMJxF3Kp2Nmu3EbmkNEZ1uz8EC1xxr1lHVUXoV7LojmQFq+STz4nMhNjPdawfwZyHS -18Ku0oduDeRhyDLaJuJG/ZhjxRwQmRypQgjuGBL1fkqbbhawcBqI0o8VWximfwnRJWlV6/6cKRew -WIp8wXpb4PNT7vbJcQ5RokTmEMF2VIo5UQnGUu/kdJrqIhEGCTKRTAQbxFoRQ0S2aE1ZicMoWzRZ -/OmIk8eCrWwA0kbbncs7CMZBZCHQLM0UlEkeh1RVGHJTBF2QEJ3NpwheuNbwVVl7VmoncRD4sbEE -bjTAMuek+9oBHR/02Vz6BoaQIbEInFzIrG4ko8LMCrEJQWVqwuzkU8TqCKcXX/oy5xp1AxEZpHxi -r8N5sHUGzvR4DhI1ksHAD3hlJ57wNaTQqLsVvoWiEqvYfEU57mUzSn6lyEHnRxXQdMb1BlS69gkH -9CEaQ4RoojxIhQh5oAbdrc1sVBoFom7hlPXQ0KQh3cPOOz1L2gJZA0Nxxqq6hxdTgLlpluFfRUVu -OvWGgryL3hw21Obx8NNvH6Q3x67lelUdOsV+EgXZb6UkPU832nnHijI8JVBm28aEWGo772qT7DUR -a+rWkmKvrra3qOoU8rMbiyw+1ckbDcckUc83kjc/umhbP8rs8LL46ETeaB1hmeQXH53IGx9dtm3K -chE1dvbRibzZehcqbyBvfHTZtn10NDV286PjmnY7te7jmi68jbz50UXb+lGnVS5mH53IG61T+1h+ -dCJvfHTZtunl2bTnjY9O5M3W3ZoKvo288dFl25ua+QOFyV+vr0+vzl8cpg1Bgv37EFES4BCHw4// -aYNt0s+yXYW22qQBAoxgxWrt3W00ffcdShT2gnFNqafUO8tiLaQ2/fFQiE5uhiFyNxjRhgC0UoVf -VMjBM7aHZ5Heow1UoLPQqm+q3OHi6ydgmDCggFKqpKs9cWVkF5M8zsrXnMbKG4+SPB1UCm9rZKWt -A6hBsAD/MARtHX71YRQyb6QXogtjFiKTpeSDxQ/aMkOQJ9qPzMRg9qMpS717McudKMQLBSWjmICR -ayr2ueyStsyo99b+2jRBU6o6+WlINhD4JLySS9GB4Iqj2FckZiUyH1saqEwpVHKU8SG85kd7NhYj -Ql9QYg7WACukC3V0wR519qmxumq0ZK/PBjCNbLPdK124QqibPG7jmgbri87Ym7q1STV+SHYvGni4 -ONensa2WE3L0wem6AdFo28DnQSYHgZKqTaMcujbrtFU3+qqdQHVDZ6/bCgM8bRMWR52xRbdOen+j -8CkwYN66y+pYQuXFPiujpqA7bHTQ56QPculT0Uu6trU6LUUlKp73PgIi+MOfk+x22eLeWnYwK0lk -6qSOTgoOFsXW2/S4wVkDrk+PIF9lEJ13Zv2aOtzkpm6sAbg8W840WC9cGIwrm0i0z/kSlJiqVzET -S1zbFWXQXrBIsrI10eHsBZ7d2oOTLnnGatubVRVN8lSU2Q8oNUHUgUmIHIo9DQXZyEU5CJeD5VFl -hBtHJ8SkM7T4mnWjrO3F0CZUu6GXWgkXImAkxKEoY5cxB6OlURd/kEulQc5yLMizvgs6FCZNxscI -6GkXYLLKXgSgfWu/pg7PPmgdDkYl6H5lD9e6zslKDEobUX3wDcPoG4z1mCj0AZM1Dqra5yAIVOP5 -lAcTz4Pv1FkbJm2qVhOhAACfXSnZh6DsPHRhA3/EqFPKmtGr7W1I4yzJTMDyaLf4XCnZVV0Bxv2F -RilORaMmGGryvlSuMe3jRN/Pepw7FucTWvVV+zAI56MipFwEy9SwYeyvV1d0bG3ykr7PCxtUPrZ5 -3dr/Ex3YuPYH8suVkpk8rCybq3bCeRtum9P6Az3jvSU2kTnbINqJYmyYdboWHTjp60nwvUrC7Pt6 -DiHWmYwFOq7YyZxUm6j9zjQveJgTbWCMOVu7Q+1LLwAlIetuws3rORgx+sOtHZt63FoopnWFiQPf -8EEpDUgydD0hhtFEJBOmlRiLnQkEatij1SYCWWfaKMuIzRqd9Wva/pXFDqVn1fXtzxIe0xmkox76 -vNf8xqmwZ4M+S5axVrO26mIXV7MeWNf05hX+IbMktFBTtVO3wr+p0s3lqlrjEERuZ94FQxqr65gk -jbGqiHdZNGU8Wk2fJU582/fP+pQJ7o90CN0uMX3QiWA2p4lMY26gk4tNBGt9iriL3kS04SCJLTXV -d/G51bR01QRvkdzfNzyvT4eqLEiI72p7I5O4yzXbQetz6uKu6hglFCDb3xtz61LLRi9m5qTaZYLq -bQEeNJN1w6DfoedcBWip0Yg5dmEnl5ZJozx+WIezb1Cq3Ft7P23QahNP7JeNaujrgdRIpTm/ublI -9KbpjGXIvWPD6KxjInDlUza1RNxs+74JYc331aElO128HLNCFhWcSTfVesbQshKLdUFh+UYe/PLZ -XMw2ZOxFidFWLPfXZ92a2INFsIRZoSnZRMoVbcLY1WbS16iN8ORRYh+Zd2vHIdFcQkYxKV3i2MdW -72ERyX+Sb9nJlwezbXE309b+T6dhrab7UtOy07AORQ+56vRI1WCScNng7IjLqurpNUjG+gTDSwPc -8Ww02RHZjFM7DWcdOOlTPpRiDUtVCCOPOuIhxmx8mm12h5A78yZbzCH4acqagmZDg21tRLMH21HQ -t8RmD+7tmlQbFOPC1s35bLwHw+4HmUGqaW1w/6CA4TUHyg/tyxjs3GVkRuemPzuE+MNDgwdeGAqg -n+r7xixqViaNaJCoekmSqmpCG6qzBzEb0jO945FkBBGFVlO2Uyg7f8++7jIgmshaJ6am7ygRRouc -l3JjnlCJl+O31O5j4eNgR2YYrU25YA60psWF/r7UyBNLqUR9tvGfKubJB/t+Vh0MZYLCNAC5fIMN -jNoAsJxOZ5BFaYRIjxaJJZVJNM1WYGVk6UKathTzebMQWSN268Laijut08AZxyeulCz3Moo9G/Vz -Tq/Y7gJSF5c13oXDdPeh5NCg+5qHU19I5XFstNHZQjJJlw340olLAe3kkkjdOdFN3docw6qLt+yt -EY9hmHhrZoYpDUnNCMSnx7yhaol4yqPxSJi0fZptJPsSTAlIxZmhFoMzbX+cNI7s+kBY+Uc+NlkG -hEcIQzsTmtl1wY+8md6BYj4/er70W6awEln+Q5qJ3N28doLL/emT+BjN2nmTfO9dHYoN1UxM0Kqd -D6mamkdr2t5PtTdrjopNcpNSqTfRVeZh3EYMcWo3mRtG8CNCjKaI0R6x1cpmY8Sapwb6QTuqoMC3 -upcr9XXp7qURMdFpXmx/uBL7EnYiHD9Lzhyneakh2MIgZ1efTWbjtE3p7uuAKY1jyNMAJt9dvmcC -p0eJXlFi9FuWZcvKvklwuD4EX03+SppjZ+2tO3bNc1NFhKP0epz0fzvO5aoloyWjueQ7sej73sIL -VUqtyJNr5lXx6kQLcL9t/fzUL68+PgQoQ+398nq4JLkoQv1iejgk06DEmovaBxbPNMuNvkhtYBqE -s29NFuX2gQ1j1EEo09Hwi0k7xSQw88uNFq3IaW1mUF1CTiwf1ww8ZwdZLuozLGsGpQvV/H056INt -04XeLOMAJLMSoBqIxcQpJfPWiV1Xv/Qs9jV05cv2bZTk/pWSiT2bDu5OrlmfbqdM6OQheo3RaLBJ -rhizzgnmZtmHNbummLbGlLW5XeMFHWOH9ySXaEqutjdip5nWBpZdmGzkODOrnbRyJ7TqESGZnous -jdXWNrRtYkFl21apCH6l5KSNV8l+XSk5xqjkocpRB6L4/TWVYmuzOlO8lkU6UkU7vzKy+hI1PX2l -ZFMbini4lahHY7WI2pZ29YOotB26DIvqYAE591gk82OEOBklTgQ2aMkCkTSmTvT9UmwNBxzJq95s -PwuHas3G1A0oqI5b+6Ud9gnVClRYNA1BfFUkm4JNv6rQmCgoOqMryYjZnizqqSJVbX9ojMEeDaqs -JdErhGh7JNG2tPeZCiaPhv6ouajR6dEanfV/Ghir5nAxozdWA+ori2tE009WSh5UK0FetdgvAHSW -oM+q3N7Srn4Q1eyzMUqwwBPIRI6QbLBpkr3OcGWKn9Ci+veqHJtbm9Xv8aLcdSX5Sqly96Wcl6JG -8F7n7rlx4vsetRqRMo8ZIWigxk1/Ir/VnRuqMy6+P+05iw6FKVrDS9m7oRxUJ8d132oRo2Jrtd08 -9t7S4jqxdrv7NKnOAKJ3FvsSZXLZAZsxvxZHpNf1Sslys5+Y8LKLQPQWXxzyaMRJbTRjA2U5nE2j -yofRrTkheMD+4IQznBEkZCXeJDRaLECbB9lAy/7rwHAPiRM/pOZfXxk5lC4/GbQiUZVHZGaLb5A3 -oGTlRpZFP7EGVEJV8zWAluxR1fKW37eOlR4lCnKj55WSpXoaydawVlxToqwlitmZn3dIqvzxhncz -Icwkx+vFbPIkIbMt3z/TQ04vaZUFGi08k6WinywFIpBCdOpe1EzGbe+fbG3VjtQk7gRRGxAavjKy -uuCipKsqMZsvPZsrBgLTIqOTvwLXWJpClM1JTIGp4T3e7bq1AzoPY5Sb60V8Dl2KROH5iWxEU047 -T8au1ynxxBrwvQH1Mo1RFzNJBcWt3z/pCzSp7ePYHQZ5zaLVfmGBRuOSwdw2uUf2NdBtK+SgiOmz -VR8duzE2tTn7/NQvJo9Lu4PGdvi8LiVcmZ1zmhWjnFPNHkGFW2+iIY6ur6UUdCKXIQ== - - - Vd+e7bAkr4HMRQ+mrtn56KWSjXUtTLLIe5sen62NGMYfnjLvutcy9vlROIWXVNAf3BPZnoROr10K -hh+h/N7afeVRlJkRLlfBfqVUr/gIBOxFhiTe2lGVqGMF0VV1SiHt40TfD+pzUaNLieorK8KY275v -ul6WG0D5cOpxD5KHaGThMRCTbEAtpK/EWK0BH03Zw7PWMxadtlaT9ixJQAY0BaqAZkHiZb9UDCEd -IGsfKnSKK6WOblRyUC51k10IfTvWH/5eLeJ2zXKSrrZ+Db34j9dUfpBYXFSJH6HJXSnZbMwqRcJW -ShaYWtV7hYRWNSZSu38dzYqCStzhyfZPQUOokrrCB30S1RvEIdtpOSgAincYeZEuVfzlKyVnbdVl -4ROQ5HJRgARjJxLHL8RgnjVcRJ7sWZ6xQgyD6aa9W8V8PbCGgooLWjyCRtCCMEI0w79INcej7baR -Kkmd0xOc+2YCTXyaCToCTW564koqPgHvJ+tr3+ywi9SQK1Khz1p9g701uLLO/yCidLgzTXyQYFXQ -i4nkmO9jSN1LsDZfSOXP2iiRE/p+CbqlqE5bA2bbFQllrJTMlCtlfhEt/JgNmGewfUz8SLwLdGpX -rlYmy4ZqDUhpSfZBWZbXKg+6Ogy8SAPwkZiVC6ZdKVXd+6xfIW5DXtVWzJAAA4OIrDTbM75kaxZF -kWyDjD5ZF3KItkOyNhB5s5sMVm7bFuqoseYiBQSkUR54QkTdDiGah1QztqwDFmxG1bWa9Vlfq3Eo -lAkhWkCpirvEGqBRE7qJJDPj18YQB9GhQZRJIOpXAh+odp9sxUpnBN/RfbD87XULMgEoImdJ0N9Z -YlVedR2SV6UyhrwbFOVZoUgIEYD7IdhH0tTAaK4EN+iWs6IGfFRlciOa06HYDYcgAzo9KhPzjJBn -iRnUlRLTFQ2o5lAl1coayCXa2eQG47ahO9+L7D4lKrKqdL0OxNC5pQeTUCErj9qHpMF+3n5m29tC -laHjvYpEW+39MYu/gdd4cf18lbu7lFnElEUFidKN+hJVSHpcFab7LUn0ARmqqnmgT8LsXu+Kk8nq -0TRkQuRokgQcKkSWIpFFCKKS4R6jMmhPi7f4x5t9DVEjKDzUgzZhkKhszqBtvg0h52JO36heH7gl -alA/iolN3q8wqBulCa/YG/CDuSt5udRKyU4tengxR2vClaL6vUU13ui08Qr0BGA3Wr8MjpUMnoI2 -NeimN8Da+4M3WKnqpD51iyxplqq06UdV6HjVZXca9eDjOPmHgoL4VRs/+gGnlZ0cWa4JtHl12mjT -bWz8pYtyqonWQFFgB3wVzhpgyR0humojSGrGoKKNn0agl5izAftW56G21jaB5oYuckPxmb2uYWmt -wKpjVYkJF7y9H4qNlLlwfQG9Ka7Y8J0tqp3SRIALOa6pyhmVdoQcemRa7+8SoouqqjPDGTTfzy24 -CLNKQ6Tfqg8Mppo+6VXAZbnmTonOlBKKzf66ho6zYbdZvbM793j6CzGpMMSwks1h6BEVQesqTQ+o -Ir5vIYZk00LESu9AqPo+Y54rI+dBn84K8mLKY9AJj+qrQ57iaMKI5SGsXVdNaFAdA9FJNSzhA6+8 -5USGqjCy910P1xcJ5AlxdHZKMylTiIyYyAz4PrOuW/+1g2L92JGyalgL0UB7eTpieaeTDaAOysij -lkjmo3G0RlOwlSH4zBrwmjaTpynUuzOl1aRLM8gHVO760BsYxqDnnEuKfObNxdk0Aq+WN/LM1Ven -t+kK0fBNuEFSJwZURW00lnOpvx/NCAxqnYJYzZ+cxKlwRPsp5GQeH2Qar5QcO8glFGcm2BimLS4i -5s12ZDQGNe+Q0/vH9KjWoEPqoLrCPBV7fWLEUO3zBobJ3S+PB11Voh2eLokTTjpa5Zx0EJDdPB+N -RlmrH1dIKW++VojnpC3jWc1uyv2Qc/G47/jJwHLpWAPUGTds9ydVK4McEA7Cg91ObRNu70dJ55BB -YaOvlBwVT5q7ToNnU5cZTtRiXMk9dA3YV9/bdZrXVMxhjZoAtQuHKGA63FA72ASk0Ocl9JBPEc3e -IkFS/150wx70qaMdyMWmC/KlGjGZruS8lJwTGa2+YlzHElOwRRRp6oC58V0+aCQXZIMPZKm3LESW -1Q+S5iG9gngwAAtZWN4f+zGDanWjCk5coRerBXizyVM3rgXKB1XDUI9aFQi9QkCIk0COOeXeXdc3 -KBGBQmyKoimCKRRrVcHYRULU1l+OMnTLTVvtZn1QIYdMUFXNqrCHNcC7aUme1G6Qq6mtprm7QRDN -ShSPN4i1m7/FTgqnt0sE0ZB9b9aMoSIatM6iunmzwQfQrNzqVbJs3BNdB7lPgRtKnIBu6FE6LUap -xGj9z6NFOdBqGk2aqDQbq17tKhEBOe5HvXdozl+Dbeh1dQM3O+vW0cKmQrTYDY4aGRZqR/i+8YLp -bGM57ipTVDQRauaqI7P2uRon5wr8KOaIYeE8E6iGT0SjKhFhyFuj5vNsbN+havhWMbLm18nVYqb4 -048uRBZIkPOggw5xVZjqslmygoSohf6wE9RZgVa9GXQ+DaE3wHogohnkZM/63KcwytLy4rKuWeQ8 -NUCwo5zXPvRns+lMQTkWV8hVW0SWiZUGkiSFi1DVOAiq7CocqojnRYi8r1F0gGJK45ikNqIwZ3X2 -MROgULxFGoxR3N2qmxjCeUzdrZAV34NHWTQzCOKlWLzCTLokSREW7hgV5pR6Xo7cRa7PSvrn1ngH -yIBsRUNYqrrB26mTqc1ZWcv3AEjm7rYY5LSRnTI8LmGpNiYVJCNq4XclqpuUDE2aykZZJETW8ZCT -1WI4rrNxZqloe9+w/rknZ7HIpB0pIfVepdF0M3pzrQFNxcndhzFOKmu2FNxxnJCCenO4vD92J0gS -77n2NbpuJ44WtjZBmMVV1Gcg2wom/RYuVYkdhaTbUK+ylA1bzQ00Drh/U0+10Xh47AmCUD36CIYJ -hjSaIBrgx+5pksnLFAAV1LNcg4LXavcDBfF9SwNWRpMg0qLRwLpmP5vphaLnOXdDvbreAwsnAvzb -P6YooQQHRe+VAd+IwLTXTZgnAVWvbFyj4dyippLgVmlV5FJ3X49D97AmcbvazMqNMHQBWOIeHbP6 -qAZvcK+2LWzfmYx+VQOtqMKLSNdgfpGUFVibe47XmlMDjQ6Gm2Mh+ZWS7fRPcuWfEAfTYNYCbWYM -IUY79WqYUu9dssitZTgSGmxEi84wI90mG6vpNcqLkoHardQh+7FvmSF1nZ/e5qDEOBq3JCfJbyMr -mQdt1msEASV9+7RkpYRsniIWopVORb0uXjS7ajC4iOomVjlA1eMhCjQxCPB6MKi6BWuCvCPtBinV -EBgGjZrOFNZmiwsjxCbT12s+WAMT3NbC2ZjZZJntuYjMHeJaqkseew+gZI7Wg9oj9WvJ40mZMPUc -BC31bQ3kMXcQQbJnJ0A5y/sf/WB8uFjKG+tdKRs5Q9f2nFZwf0+HcjWssZxhLixvJAvYQ2lD5+J5 -6NueLf1Z18PkyZDyo4YqGQvvCG9f3NRANqyPeSBI7KnyqoFyBMk+FUwDxBRES+IkVFOIJVu3fBht -08hN8OSjuCYLelJ1z47IcoOPEIcen7bwSzzuKuxgl24ReW/5P7mfG0GsLX2/Wg4p4cvWgCWsR/HR -rIwcrF+sKqHS3FlaZ4fel7W8laHrsEPpRxrgXSl0yWsr7iXLRIjBspi75Kud6b3c1NJPJBVdSPRV -EMigt7EJ04swGQe97EGASf1MHdYSIPjWysipMwLWfKVHeOpnndQokyPcGebK1uxNMKw45SU48V5D -hRost7Jp/3J8gXvs/HS+nzNBCvHLLPTzC6VkVRf3EscWNc4PVjrAsu/G2DGWvODBOhY7bNmJf/hI -tHafdcoTENBCNN3Ii85s+jWdOioRvBqKqHvnJ8hJt3LMB85rIo1YBwN/0swy06OnJjB1wCwqgztF -Sd8SooG+6JaoaukZajeIgmmGmpmlocfGYRX2dSScRIh+MOjclMWEa5r0yPXrpqqh/9qWTCKB4XNw -tnMYCjbbfhqvAU1xg4o3xo2+Q3BZX0AaGC29AeRgU9P0lGjPKlQ9iNZ0tOFNCR0WL4+a/DHNmd/q -KTbFHCxjT4/gg90Z04WKH51935wxYXL9bcH16rOp5xSbR8l3CzZ0UxNE3z/V1VnHe2BsBgb15ngp -5q5iVZ0ASLzqSXOp+6neiFhuktFOLA1nuClrLEjMVYl+yvcabGpC9yh5QTAIcQIEekUmOwUxqRDu -U4NnLXlX3VxhDTqoZQ5AdMm+z6PXXufdbTKzkhUPopmlepvoET19Zr6EtaQuF8XskY+Nosw4vUFT -1Q6J0rjUcWlMtp88m1OZFM3G5U2Z2ZIMNfSGB1VDimLA2vvKmxBioz3aVOMp1988o+atjWL02fvV -9Ihe7ws+bM3HgUYYfSdbXlmUo0uIJRpCkfqHEGPsSOBq7hVXuo4Ve6q10xJ1ul7q5ysdIBWnKjgk -hy43iumkKF7bNdg+4NqBYtDV1d1Zu/aKqTUohesQInBXjBbOqLobMbfeAjpWswPHu6VAsaanafDZ -niy+Zx3LAejX99EEykMNS8uQzBZjI6JDJlDzAr3v8ZkgSecnGuTqhS+ixdCDLasX384RY3wTMpqy -/sRCf8Gyt4hiFyJT0Ta0MxCrVayg9mcN+K6fmdro9fpw7etotNxz6qAJnun7qSejsIqx9krDKN6C -C36ql+Ql9tIjzaGf9KPFRCcoepoC3WY+R6k0Y+8rTzdlpofqk3n8rGSN7zsF8F6Lo2UpMx3WTT9c -9K5WQ5Q7n482wslQF+1wQAOh2AFt0VR1PkrvJ1yDwaxjL9xMMi8T1I+JDugR/OglPJJhHUw3jmuh -FRZft7Qmb6HI2s+R2P2gvvYoGJ7sgfoqNYNlCnDAr5Q8+XA6Xmfox3Y8VtMPwBI1BABx7uCiqRhP -FCCg4FjGtSysQUO6YVJNe/E8PDllVdN+PdEGcohmwg+1N1u8uWNzGAziE3uWm6UwBr+W2qVJffKs -HXC4V8qa9R2SlrqZFfS2Id1wySBWk/Hmuisu6I7SLSO6S/A9x2A6doPXm6LVbKi9gfYt021V5UYD -2Q4zE3e4IzlamZoyjFMPFELIe+4NJBbG0kHICvsJvdSDn3ICeY/XYID7nugR15I6LKMiTOAOnPsW -2wnJIMdBMv6ENmV/E4IoxOh6iZpg4iGkXgEs9kzxkAVLK7OlEbZQ1s54QpCkgbJmD5VhLa3FHBZ0 -vRpxS65LWdMyLBEhAMBqbD84cfDh1h1vjqQcDRNWewBiyimJ41rOv1N3Joi9XFLusfLo1nwbxfJr -QU1WUoLhXSOXYtqmZlviJsFJnhJzJURD7sTjno6MOxt7ANzJToq+ByCiJA4pMZqxzwuK5f2wpi5b -PkjETjGd0KY7xrV008nFiFsxRtNeCBKyHC0rBRV7+DOmXuRhqrWBKzt6GlLpgg7kXg== - - - eCXHsT/brRPbShF4+rB2Isv7U/pKtJTwqNXIg9QhE808DR3IgctZjQ3SoJc3kjuS4VtbE1W5C1ms -IqVwvaF5ZC2cmQapmx8EkWWxQDw6WJ2NpPBWXMLqTdVT7FKjWb3OKDUi+vvRcGYog7JSahlssFZz -JTlAhHsAWc4EELVmVlwDiOCi0qFPgtq+Ka5ZTVGdNil1PS3IlbzSwAQ9jmuZsmtJBVGqcRk5qEyN -Ij6FGHvRIoXpp9wTxXXBT6xZZwyqBR55vWuxUq1W/YZ3vq6bvvJ+6UjWyWpBp4qVXODG1vedSWRn -KfSpdD0iyGWaNqyae/Yc7n1Y6dNTiRlxTfMmYTvABgWDpCI3qSknGnoI955169vO2wQE7mByQwsb -4cnBukU7vTcwmPw2DBeIIXeDVNwwuN6xWjpQHIepAQrS0L0VKyUbDCxKaqSR62gGkaHIcW9cP9lq -7Z+rPa7LzUAVtT0bLDPX8j3yBEWIvXRGRoVCc9eyRge7mycXQLRSBnlK942C/QAxSAV9IXbsaabR -bFs/2qPUF0W3UEdjDnJBSZAI0dr7Brrj5b18NPYDRF1dQuTlQTKv3U7OGj8QplXXck7rJrni1Rox -9Ao9MfQOJDmm5WNe1BA8W00fb6ZUUGLu5bhMDcq5owhYLIAPFoniCcdXSTvC7YWDGTMsySjvV7vt -lceHyENcON6V3EGTH7PeEihf6sV8ynoJNW92Z5kkss6WErueHhTkWkapxyAdgEdOmh3XHE4GD2nE -yaawsmp4skw4G4uVs+ywubG0HrDq0Ra6UpAqwRNW3Y62tzSQu/YOcSYYfoISTY2w1OWyNXkzrykL -Fnbhk6Zw6W4BbbRBTZFeAmBKN33trGbiaXcUZE1rrRJBkWc1AIw7Qrv95C08UvTmG92bBg5k/kZa -3x3yrBvsrDRlFsRoaWITsoDg0KIoBAOoEMprWQxmgWk8Xea9uxIRuR4tF4WeLyFaen2xcH/Ja8kw -IRs8hHgASwQYVMYSKVINk6x1gcrkAypS1EYaSHJmybfgSpT5Vv1Jn1YuiF1IF6vIK/UiDChcTMGE -XjoYqsp31YSlqw0KFzRdkGVNDfMWtYYXy1FbisTk3kNxV+f60DQ/ORz3tJWkFcuYUangCsYU7XVL -lixykabspAk0IYWUQbNoRZFy6H17VsuP6DM7GZBFNEoh+rGnA7k0NWBV64tkSqyUPKjOpCVfTJyU -0bA7QaNXjZh6MovFfUDUiihVzjmTUhauRPJOTPqsOeiqfFaIixxvE3NRnyV4zmSqgTyqeJ8hO7Pl -KY24RszeHzuUnZJnpeLXjqopiRJX56qbtx6r65WnhyXC8Ziw08PciUWAjasDO6yM8Ys6P3MUYIA0 -oSGtzJttdB3WT0UTPkWu3l0peQLgGhoghzWoaFW2wWkdetbpaOBHHMLFgIdOlyz7Xm+z6PVScq6P -NrAyTtqCHRZFb80ikbhRHZezRi0xO0vgSRpwXTsqoneulJyyoY2SGCCgJcu+MNRfI47FADxdqGan -d7IT1qNBZ1ziXQxrVSWztD3onS0BcWPy/tgdsrUnnzTNyoK7HYmYBznPheWDnXXQzdQTkCXEJY32 -1DZLd8PNs9WQozWa8Majlvqhrpg89H2fRc/Uz6swW8dM4vOapJr1MjQSc0c7K6l050qReFVXeTWn -vBwrRoY0Qyvb4QuN11uKx5qBwdQ929ty+MFo6G3yQjAhWvyjSH0/M1AMclCsJDRtidHyUexITbmX -GILQtbMnlV5iqIhXs1sYWhYudzArDKdi8yo8lZKco8JoybzPMN6iQbuTK2Z3jR2az4TFo62J20Id -OmyWskcbHXpCjgoG2IMda90RRbQS7US1pHx0v0O424Ft62ponCLF6K0DxRlm1SrgYQDV0Pbd/s4S -fZQngT046yPoKT2jWo5JtjOftYrBKfUcRi2EbEMwj6Rmzq2U3Axww0QrXjxFMS43uDBaFeiyFqtJ -ca1aCLPNVkqO0Y6upIkjKfYbW6pc9gVi6IdRXatCmGJXTKq4cuVZi2yIHJQHXTUxTMChvK/FB2Vt -nZqZQTzHbNQgQcnLa3KY5TI5FshnJEe1OlLohS+qFTbHk8XZitdseV1pqstZezGZ5LuLrfZCO2mK -PNc1B1Vy3RFU+zmPBrTQgCKS5ckpGzd3dH0jmxZXp33v5J4QmS89oZLrZgMkhHO9AV6EJu02AayL -2ySkAkenEl2NuCZ2s7Li2EvMqjA40QYsPV0VLmvXwA1FrGojj26a9OK668gScwpdJUKLXRdtBkdU -ot0UU+Unc0iN0TaUJXjBS9bBygZNasRo2PSpnGOcas5PYGc46oK9nxTPEoHHtEz4GCwrJNYeJ0Pm -vrBzrP1yduxoJVk5ySJJu/b6pGJbhn6sXTecCv2AGLqU6PnmGJZmvFe5t0GIQ+dai71GCIxoRQ56 -RDGWHg7l9QD2MQMg1Y7xwwyMPW9iMPQqLoPPlrY/4SxjWct3YjaBPjvlbiiqB75ShcdX0WCs3bFY -0pcB2WPpWDZskmJ+WRPLZQrXgVos63tQ3gIx2dbV2o3s1WgKcuqlMukD7vndVjqeDt9s3bUdHSe7 -TC/SEKL5drF4luYRk9WBhUGrKeaxBzan5Gh4sZPJVAsigti1eb34RmqSdYmkHkn4xqfyAMUK/EQC -22xYWs0n6uW90n9NB4zIuO9c0EsBID6g2OgqOagWS7BKDRXnrIUNUu01LDTDAFGHYFmd2ZQmhCh6 -LrolgKLRLja8erMakb4W4fnUOdn1GgfoLp8c1xJt6F4Voh2WZHN9e5TE241TATGW1OtwaZLJsjAJ -ysrwVmYpv6CBliLl5q6U3BS6XhZgtFINPeXIFAfUdBjNKqpxKt8QsymPQUs2hwnLu5alv/i+VdWL -HXGVhcmvlDxlPhdXLHg/5Vrk0APqo2mKccqchuqQVYG2HEo0miy5tOfyLjpg1Y+oS5m/DTG3q042 -N5ofs4Egx37nAUujG9GwYH4NBBm8uTcVvDcg5mZ1zXul3MX3T+xmWYGrF3NUQ+O7Mqo3f6TlOJJo -8SWuthLjdE9VnoCfsZrXzUr3AA3q7M4My/sAnLQjObAMvYHijNyR9Qbjn8pDz7u/xqqprsF67UYR -mjTJ3Kc9fpioFwrRnCM0XswfaIHZRaMnctWqTicv+u3eEOC9rGiludKnXCwQQ012ZGfLv39DAr8B -B0uPnXstb66t9rIIsx6sVYHsBV281Vgi1YrmEWWnxGhFQswd/UZp3Wv2BQ3JxwnvUvs6b6sWua1X -WhEKWVqjafyMg1wpOXTlWqtFwtY3iWC8+gMZZV2t1DL2eL/0uhkaI15+3jYzVH5DBqTaq6zFNWw8 -4f2WYJCLXctXVLkF0fXCY1affgYcMng/Ijf2bFGf+BDXLnzSUkjLfk317c1FmcTI6fXtNYyU9CZi -En3PHUlZq4c7qJCaZpHGMlVwp5spSBKaIbudBEqCJPBotTnXa8ymnsq47Nc0w7zNOAg0x00zbO5i -sfGEZnnzUaoYHElORrJY1ARqAbmj8IJdcRGm+KXleUx4yc2XvQkNy9hjoogFP8dotFnnbVShI9mj -hFesePGUbWNXh4WOF4+T8A09WwgbqSdfULpacMBqMgbRGuRZq2y36MBUnK90QG2/yyP36gWhV50F -sdrFldlbQoEVEfFizJzo+1bbw4vbTIW1uj1CdxvOPz9xgRVlTbIzjAtGtRaTAGBsn02FxWOYqLM2 -VlMJR72gNAkQzapeGpBG81gtdckqRiVJv1XiaGUkneYFssKl5VW6nlmPFJveO8NHsw+W0za6njWz -2a+TqUznZsPWYcpcIfe0EzPY8GixtJO2V61WTkxTx1zvQuq5QwahRN314t40sC4Oe8YdRZ0tk8Xa -sbm1eLrkhlmYlx7g1fZG7BAYe9k5LSJ/pWQzFVHOzJoZJ/dt6u5fZGUmKyNv+XhI6Bg6eqT70PG5 -OmUfZnvWW5ohwxtb+2V1fIduvmkViislGy4x93v0QNRLBbJgj4Roafp5Kic3Dr00gyYXr7Z/bZo3 -szi1NJDNm+9uS4M6jNMFRKWXbhunQimqbtgETYVOevHgqZZv7l7HZQ9O+rleFRin2oud66XYkpba -lzR3RyvS1c0UWzZidSSBmrXwJXGhVuTcO3NSraVdTPW+zIBH0kGcPFdT0sLklWSsSIjJJ8v7Vx8C -EhHGXhQxuSntojhTyor6253eACM6hpaPXI5gqt8+a9mGZutkKawgdbWwFxPRO3KFOIHFfVe/ihwr -Siy9yqB707ikjnJdyxRkFuiVkus4dHWmR8KnKzemGNWyCV3NOogNEzqi4MrIdbrPrJQeEDfsf0nT -Zd8TzI16tb4/XSOtHvXlt6xoeLCSDaxMaPdlgjxYxI/BiJWRe4CyaH5MCN1SqwKYECJruggxW/R5 -+bnpgoJSzAOcuqUWSldYKrPD7X4Cq4RSJX9jtb0NbTw6LeWJCRusPm+jaqiS86h+RmcuA66DNzeG -uldQ1XE0QMabGrCc2cnBvujAyezu+R95Cz2ph418OG7cR//Gi+jHYXYTPaIOKNoH46pt5pGCC+qQ -ZIxO5NUmGRUEcY/Nar2JrcSpgev2zT8f8OazdiwfIhLugT1AgfMKhXegN65J7QF2JbKwWS0W4JLp -TvpPmyH839tg/qvRGiN+fxgOf4++4cpI79gN1HKXe3NRuOt4KGGdvDr4vE1RmyT45ZvKeDjQX9YU -UlRqH4FIQs5lxJYdAIFHiGRg1ZBROjIidNk6gq35vc4Lk0baR+AcKkG+Ddxk2zdr5Et+ezj844/+ -+uliXbUjdO21L+pd8racLPI2kTcXM2oIbUZkl89mC6+1Wjdbxf3kgzVgPVCiNMBbg8Ls2SCXyW22 -OhE3erBO7r2dWt0Y12wOzjZ23WsuDfQDGdA0UVwxpnOuNbjG3vbhDZJ1cY1og9loz4a98e1pikiO -8NzMn3VIp9psdSJufH+d3Hs6tbo2osXoz94omChFXhykPnuv39E25u7pu3cUXQaIZME0XSmZGjfJ -AdfdXOpep3+skdspU+DDknUfmo5R6Htov2ep+9FEUJQrHIWRqhR+b+8WKbG0KTxQRbHpNH+fCQ8l -U7TFKAjEtSYasUisc/rcdyYDgImVXrFJVr1Y63p7Q+97nIZ5ciBkHqbTpKzLjx83/6cHG3KjIokp -r8lwmf5BQu8kZ9R5BREef2EeLBqMc+Rbi+1AIm2BM12/GIYoI2gqS9H1G5ppVHWbDaETiUjgs0VF -RNUyWPKxWERELHq7ZYdHmnSzIcnixLQxpEZE6Yga1ofUiE7ucNgYEheFBYc2hsR2qb7JkHwbnJGJ -EV4bFIkZivHaoLb1d+c9+WbtIYL45R9urv90e3F9d3H99dGRkKlUrP/h4A/f4i/jIH/6/Ivf/ubi -sjVz8Mv+4+F/O/jl336/+sPNi3P8+KuLs7uLm+vT23/90B8+Ofz5P68ur9ufjlq3bg== - - - L756fXf+6heH/3nwy09vb09nT5x9c3H54vb8Gn93h7/83fXd9Df86+5f357jbz//9HdffvrV1+cv -L0/Pvrk7/x+n13fnv7+4+/zsGwz9+svxF4e//Ov1xVl75fP2zeuvN5v57vTytbRz8aI9ue0R6Fd4 -onUOz/zn+x7gkRt+tusg/vXDD64N5V+PfCT/3Hkk//wAIxnD7iP5/uLF3Tc7j0afftQj+ub84utv -7nYekj3+nzt0/bFIlbDrXLy6e/Gr8+8uTtGhnWdk86X3vtaf37y+PTv/9PLbb053lo67D+7iQwzp -q8vXt7uO5fb81evL3dnXHt+Ffd/Q9fEHur790Jl6+/L8t6evX726OL3+7N4xTr3+gQV4rDvpxe4i -/8WHkPkPYbHHvl12X5TdNYoXH0KluHn58tX53f074+nu/j9yhE9v3z+n7RJ31gZe3748PTv//Oz0 -8nx3bWDjpfc+uFffnp/98fU9HPYO9s97HtYllE6YuWc3lze3/+37by7u7lmSdaXuXw9ZP3n6/avs -w87jaUv8+vL09tf//Pbm+vx699Vbvvj+R/nQQZ7cXL+6O32LQU4vPiVLpRnV7X+7TtK/d56Vf38I -B8H4kLE8cmdHfMBQHujt+ECqyJ9uLq7vVg+x/f/zp+nI57pjVyrjn552dJ/GM4325tvz29O7m50t -v7UXHrsq8djVvmeqGb2V+8c9YKncB5RRJzdX3968ulfXe4wy4fT24u6bq/O7i7PnKBuE6357e/rt -N7sP8LFLiJ0P+H/co8auDQSPPuKB3OM6Wh9I+CBm392fTi/u0/6fntB+8NH6YIH9WO26f+w+kn88 -7oH43Qfin8kR+nTs6Id7kh98OH2g9fz9+e3X55jJp6cSPVSaP+Ml+en68WH7QDzRrl345a/OXx5+ -8niBSLCpngHk6Oar/zo/u/vs5vX1i9a3z27ucU/NV7M9evdq54248c77V3x/9jzcjc8GWTU8O2TV -A0b0ESCrUAZo1+n46vTV+W9uz//P6/Prs9233+yt977g99gr0/CuX1/98ezu9Lvz3YXl+ivvfWDX -N5/fXdyd3bPl1sOoePovF5cPGODGO+99hHevb796bp4DjOn1ZdsLO4e/5dUdx2Xf+UCa9F92HN1j -tG6eZxDoWTp6HyIYnnWwZG8C7mIC7oyv3JuAexNwbwLuTcCPzATc2UX2NC3AncX/3gLcW4A//Zhe -3p42jrn8w83Fq70NuLcB9zbg3gbc24A/oQ34q/Nvrs+vv7wH7vE2RuDT0fFeXFye7p5ws5dtH1i2 -7cyrt6cvLl7vrszZ4x8OaXFz++03N5c3X+9sPezl247yLe3l216+PRH5tjOv7uXbXr79+vbmxcX5 -7ceuwp23adhLuKci4fYa3F7CvYWE+6iVuL2Ee0oSbq/D7SXczhLut6ev/+//++rsm/Pb/3l+cfbN -v/Gv6/PbnwKZ8XTk3futaviBdtPblcvb76e320/5Y95POw9+v5/2+2lzP/0/N5f/vjp9dX77+r7y -NXuw4OYIPyxY8Gh8LnDBh4zkkQMG47MDDD5gRB8BYPAjqMb9zGz1B1V/fbH7WD7E6bavLT5X7p6c -XNjXFn+Mi/Ikaos/Fyk21be5PX/xoOJ/T02mPdWK6SxU/VzY7UGDeTJA+submxdaUPyry9Ozf3xy -KKSbb0/PLu7+9d+G4weoqm9VY/wD7anfYJhPb0s9T3T9wwTFI1cXXn1z+uLm+5PnJPs+wFH7ZEb4 -rBMjHovYe07mxM6poi8uXr58/er8wdc/LN5770N81tfLPOjilc1LW3Ye4vy19z5I5aHncoDtvOfk -AHoYP26889hX6ie0fR/LYTH6neXP6b8vrl4/IHzSn3/vy0yJsHMhtse+H9PO5dnPL9svD4p+rL3x -wVStX13w6H0MV8v8SuTD071Z5jnpfnvF6KkrRna32jO8Rmd//d7WQX5k1+89X+1przztlacHdmV/ -L9/jDBi8xSn82FXDZ6xY7O/ne5yyYX8/35OTErvfzbW/n+99qZbP836+h0YAHv39fLtz3CO/n2/3 -gdxTQmF9IH5/hO6P0B8r2x776bm/3XZ/ej5aa+7xn6D7G24/hhP06biqfwxo74EH1Qda1/1Nt89h -SX66fjw9tngrX+Dj1iqf/f2PuweG99d/LAf4Ya//2PUqiaenYu8vAXm8Uv7hXPfYhfx4OOg/237q -lF2Hy592lyH2+Hsf9m/bq6/uB6E9Pelxddqa2rmsz1OSG8w8+/0Ow3uMguMjKIzyrHTfjyH9/mmX -FDl6NjVF3ouj532vzs6m42MvLvLHfTmORy4JnicU8MEa6mMXCfDlYxv95Rle3Pn2QmIPCHwfRu7x -7glKT1D/fpud9ZSkxc7poU/txH3aGvizPKH+9vL89jcXtz+9X+jphKPd4c5xkrvTry7Pv3iYD3Dj -nfe+5vz6c3SX/eb19dmfn55QOc6Hw+Ge3Z4ku/32CbLbuBduT5TbPvvAQBXaQ0j2/svt6fWrl7tf -ePF4uP95ek7eRo17CqqphFnei3L6NFLa9p6Tn3xliMb79PJybw79yK24h4G+RT8+bB+e0y1W/+P/ -/n/fXJ5/dvsATNn+Eiv07gNfYhWezSVWceeBPPI7rB5wG9cTucPqAUz2Edxh9eLi8nT3em9PyTB6 -hjit3QfyyItfjce7R7ne9tbsx7o2Ty1s9zbXgO+l26Nht8cu03aP4e9l2qNdm71Me4SL8th3/gMw -zY8cNfvVzhlDj12I7T6Sp7blnyre9wG89cj3+7D7Afk0M6x3hl0+tQTr3TGJj12+Pf9U8feAH92n -iL/Tk+kpp4g/n9Ppq3uKRD0dGbfzQHaAM6+LtQ90IcnuC/NUBNoDjtNHX03uHtDtWpzr5JvT6+vz -y8/PL8/PHuJkW7743gf56c5Rybcd5PLFD3Yc/eri1beXp2fnV+fXd78//fYJnkm7S/JHfiZNJUkO -Fz+OGz/uOuKnUrHkq519Q4/9NN59JE/l+NoXXXmUYm/3K7GeYNLnA+TBI5fpX+3sJXr0km3nkXwE -ONnrm+udnROnZ2evr17fH5lem5L1V95/rPny+9N/3aM0bCz23entg1Zbnn+8/tq7m91PsJsPMJDL -i+vz052vV212/Nnvb3bHPK+98N5HtvtZtba97PN/4BB23WGbb71/Ztx5g73YPX1enn3/dsvOY/nq -/OsHHNj69Hsfz+05LeKdGfDFi4u7i+8ewHv9hfePWtkZTn57c7U7jpwPf4BIwfn5v3deppcX92X6 -bCLjLz+YZXJ6fXH1AG3hJ0pSedr1O54PbKY8F9jM2bOBzew+kj1s5rGtyGPf78P+ZoInC5w52wNn -ngxwZve1eiqO+j1wZn8+vYeRPBvgzM6K9dMAzuy+ME9FoD3gON0DZ/bAmfd8HD154MwDJPkjP5Pu -Qcs8Y+DM2bMBzuw+kqdyfO2BM49S7D1r4MwD5MEjl+lnzwY4s/tInpq3+B2HiJ4O/OcBzPnQbfaB -lvIJX1r9AIjdfjGebqGSp7MWP10/Pmwfnk3NyDgcxp1NQRn2nx9gDa698f5dML/78k8X/zy/fPXt -xfnllw+LuD9G5elD1sN8OvrIHo789ODIr74Fjvc5onh3lJhPHcI7Ho6fuOFwjO3fw2H7/yft5/bf -T9ofdr7l5ak4GsfdQb57wPIesPzOhnZx/eL85cX1vfcLrJ8A356f3v3qAUy49sYHWLqrm/tW4qPA -MD8WVSo/Z/fwMyyS/NW9vsen4yl+yFg+cl/xez+Gdl2Xp1RV+EF755HLgTO7h+iPr+/ZFU8vkry/ -QuqxyoXT24u7b67O73ZflqckH95mTz12ObGz3/cf9zhP1waCRx/xQO7BBKwPJHxgLvvpE6uegsb9 -2MGsO6/SP3YfyT8e90DuwUquD8Q/k6P06YQ/3kqC7APQb9GPfQB6H4B+SAD6YQmRj9Ensw9A7wPQ -zzMA/XzLSH0kAWieIghBu+GTB50o+6Dzh7DX90HnfdB5H3TeB523LMg+6DyzBh65E3kfdN4HnR/X -yjz2HbOzW+7FxcuXr1+dn9xcN7voendeW7z3/tXxnYX469uXTXP6/GFlRTZeeu+D+9f55eXN97uO -8PLi62/u2t+PzpApvPMY56+991EqEz0X0f7AaNrDGHLjnce+UvuS9Dd7F9zeBfc+R7b7CfDU/XBy -Nn7y9e35+fUnTWk7/+Ti+sXF1zeffHdxc3l+98nt+YtPbm5Pr+8Lnz1BB92+jP39o9k76PYOur2D -TnjsqTroxrJzcOX03xdXr+/uqZy7vo3s+ffOZWnnMZ1ftl8e5HNce+NDVuiDK2AFVeQDg2d+JebK -SrWip7cBnpUv6ll7apq2+dzdNK++PT9rpujtM0z1eABv6iT8+p/fNsv/AZ7S5YsfILD/0FE+2B+8 -fHHvzNk7c/bOnL0z517TEq4bceaoZ4c+nb0zZ+/M2Ttz9s6cvTNn78x5hM6cy/u9HE8ncL33TG3b -Pz9JVz5XO+npuqaeZ22Ot/BxPHa/2zN227yfNO8PJK329TkeqYx4y7owj1tK7J6Tvy/Q8b5Uy7s/ -nV7c53t9ekL7oWjPR1+XY3eOe+R1OXYfyEdYl2N/hH5A2fbYT899eavHeno+u9JWb2WhPvYjdF/a -6mM4Qp8OEODB0mNf1uot+vFh+/Bsylp9+rsvPz/7pjV4fr2/euivH0flp50rfO1LFzwKA2Gf7r+z -lvCeV2bnq3tf7HzrMh991Cz22LfL7ovyr90X5V8f4jR7+fLV+R12xu35iwdJtKcmBf7IkX5UVsLb -L+4zNhcey+K8nyjd01mXvRn3hMy4fQHfH2PGveehHo3xZ7uObnd15UNoKw8Zye7a8IdQhscw7DyS -7y9ePABrqk+//xH53Uf0zflDII798afkAEl7B8iP01wesXX61Eyfp+0AKXsHyOPbLrsvyt4B8kik -wN4BsneAPCa5vneA7B0gT84BctdU7ldn91hje/fHzQ+5P57O+XH20+Fr3jdu8njnCjJfnb46/83t -+f95fX59trvyNnvr/bsfdkaGXr+++uPZ3el3Dyi+sP7Kex/Z2cOcrTsy4YcQOtc3n99d3O0uPV/x -6b9cXD5gqTbeecRr9VTgvC9vTxvnX/7h5mL3lBh5eceR2Zeezqmwr8L09KowDcc7X6Zzd7M77958 -gKE834JSD9A8nno9qedUVGlfhOjJFiF6gFnw8vbmaneLjw9/AE7c11T66XxJf3l9+9Xryyabn6Cz -8XlWg3mAafjI/RP7Sil7R+xDHLFfnN9+f3H71fnl6+uvv7wnVXHvjl0cZR8QjbY7SuhxY9F2H8cj -R6INzw6J9oARfQRItOF4eOa+/53BKE/N9f/8HeZ3zaZ4bi7zux3tpJ/cYb63Aj8KK/Ahe2hvB+7t -wGdrB+6s5uztwL0duLcD93bgR2YH7ly14GmagTsPb28G7s3Ajwo5tTcE94bg3hDcGw== - - - grN+vI0h+LNPfzcOX/76+kU3CEGKoHz5h5vrP7UmeCfgkZA/w8Vo6384+MO3bCPInz7/19VXN5cH -P//s9PrFLw6Gw0/b///2/cHr9s9w+OeD4TgH79x4OBynUMZS8IOrg/PthzL6OIT2wzj67OLGD0Ms -xR3+Dc21l/nP3/7Vfvnv7Yf/ag+0p74/DIe/P/xf/3s4fIGP/vngqGlIzvtweOTH41JzPLw6CMcl -D2kkqaZYDuNxzX5sz4zleCwuHZbj7F1tT4ypdTaMh2cHY3s9p9hIsfUkhUMXjvOIl9x4PNbWZ5eP -S6yVT8QYc3vJY4gj2hmOYy7jYftASb60T7dxjd6TEJJrvR/84LLHlxrJlXToj4XSGglDyoetB2No -PWjfSSEetq/UEPBC60lJ7XuuzWwe2l/cscu5HI7huI3OHdbWv5rbx9Oxz62DZwfp2PmSM74++OAO -MaRY2yfbhITqDo8wmcmHcshZK6G9c9RGNbjqSQrt4aPWSkmt72Nub7XvHNXjYRhCxYdCHjABR60v -wxhH9KVwRjFtZSiHbdaGoXXyCNPmMDzMWkp4qY2Y69Fmzccs61KqHw9dm3KMiIQ2OehoTEMM8q22 -bGPxmNzUPtp62BryvmBJ2tdS4w9+zY+yjHgT77l43Hrd3msTGENtfWq9bhyKH1pvXUgY2pj8UMkR -ofEq3mvT6V2U3gy5se8RF34swmye8zjjvrODVwc/+7JJorsNefuzLwHbGjD5a16Mzz77FLjhP9/c -9aIA7cHP1p74/JvTb88/awf2iz+d3n3zu1/pI5+uVlsf+v0p9MGbz85f3tye67P/443N/UVlzs++ -bCLiy4Px2NfGARxLcI03rhofpbY5ZSeNOZXDdFz9gAloPBHbxm2819aLc9TYdUjkb9dmyTlOruyk -Nm2u4KXaOL02Ri6N9bn9/HGu7S/cSbntv8OjtsZtsyawbk5opTFYym1LNQI+ddhW2qWSZCP50nZI -aNthbP119din1rnWgZTarnRtAYfRg39dCnjD+cb3bbkcNnuu2HJujB68ObrGR008jKGtYmOCFCp2 -RftDwKRAcoxNCGDTltZ2m5BcIGPajoopBhBC4PiPMuaqMS8mrS35YSO4MWBMub2dyW3Ft67huylW -pxsp5cb6bUpKwhbArFEO1GPZWKUNs3ExHiCjgfvbo7n1OiUIVe4aNtuEGngYhLa/8MNxSBB8sos8 -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 -wPQwJe9dPO4kicriah1C18Td/dBT/3xtx+6P9u6LI5v2NXEI0X17P9oNSPrUQ3fffu2lZ82F2j02 -TQm6ziyzOxjx2yzhlXYKU21T/+TG86+6+U5A2Bff2Pn+x3v3ffmV0EsCSTxxRL/ct/fj93e+8SIg -6Z03X3XBxqmBZodZJTlpTYYX2iFckR1Ytfr402TklYa33/Xgk39/+a1d73/86b4vvkr/SkNA0U8/ -fn/XWy///ckH77odXWkYaKgyyIuyuCYwjiuYC9BFkXZ3W3DmzAuuvGnzfY88tf2VtwGyezO5KBJQ -dNfbr2x/6pH7Nt905QVnzgTbPHZt6alZXb5I4Eq9frNnQ+zsS6+59fdbH336n6+89a/3PtzzaXrX -b36658P3/vXWK/98+tGtv7/1mkvPjm3o8deaVcUnrz0m+ztYEa7kpaZtg1PfPe/H19225f5Hn97+ -8hs73k33UtN3d7zx8vanH71/y23X/fi8704NttEvNc3uBlbyqtj1JSpzTUP3yOz3L/jJ9bdv2fqX -bc+98Opb6V0V+9arLzy37S9bt9x+/U8u+P7sSHdDjVlVsj5HV8V+i3oBrwyzu5r7NkQ3XfjT62/7 -/X0PPv5MuhfwPvP4g/f9/rbrf3rhpuiGvmaXHZPl8ALeb1GvNS5WGiu9bQPjsf++4Ipf3HLHH+9P -91rj+/94xy2/uOKC/46ND7R5K43K4pxea/ytb8Uviz56zYmnlWrM1XXtA2Pz3zvvRz/75a/TvSz6 -17/82Y/O+9782EB7XbVZU3raiWuOzuVl0d/6VuIK7rUniWVaS01dW184csbZF/745+ldwf3zH194 -9hmRcF9bXY1FKxOftDbnV3B/i7jY/LAjjjp23SkSgGy1tzkwNDGf/sXm8xNDgWZvNUBUcsq6Y486 -4rBcX2weRxZKwbHHA2Q1pgpXQ3sfvC7+bKHXxZ8Nr4vva29wVZg0ANHjj43zPreI4rjiUnD0muNP -Fpeq9NZqT/qX8HuqrXpVqfjk49ccjfN+qSoG4EUtVh197LqTTi2WaYzplzYwamTFp5607liE6JKQ -lIHskUetPg4WtsigYETRKScct/qoI5cYUYQrXtfksCNWwXIhmZThAAQ94rD9UuHk2/GaIbAISybF -TWAplv1WNyZR2ibjkjFLX4UlgewyKcRDw/ZAL29ExfbALxpFYrssSnHR0D3gC5zFYZmUjaPAcijG -VwAGSKXuyKgrtBASBXwiaW2dKQh+bwvFFsJRUZNoEf2HFbnAv2YRpjGbzTarpQgr0oF/mMZgtpr1 -4FcNeIoZbOCDTm8y2ooCIRHxFfBfYKNIZzECP8xWpLYZNCaz0VQ0I9IB78Vq0pkTz3Q2ncZoMJmK -1DpMp9EZzbRHOkxjKhoRUZ/YQAMG8CWyJZ3egr8k+yOfgL80m/BGKV8zWUDXelpjlEdEj4knJFpk -UyTuzAGOiDaIpEFtbXTBNTGyMDEbCUU3FtnBI12Rti6yUCRr7/IG28dDc+GOjXNheZEKvCoH/0QK -QG6NxYLICMkJejZAUmownc1oBP/X6y1GE3xgspkxixW9MQE+QKJTxm2FNDYbNVaj1Yz/Dr6lseEv -QcNGm5HyhBxr/AGGAXpg5qJEEzYDdZTwy1Yj3gx6ZdNT/wr9RrJMT+2T/CuEExVhJ6CZDohaohvw -Vb3VhkZi0hgtOkvimc5kBR8sNpwtRotBR31EjifxxKYx6c22okRLgLYaq0WnoyJBPBlB4gm+b6R+ -zQZGZ8F01MYoj+KDJZ+QaJFNkbgzBwhHrugUdYpsRTJ5UaCbonX4fzr0H+Qw4Aemtxhgj+ATZjYg -6gAUwVMcDaMJSKYfUFGnMeFfAyQwwidWLP41hJtOjx4CXAwGKCMY5AxAHf6tQWMxmRDdrEDp8fb0 -GoMlLj8mA2jHz4aOn0vyzSkknzr67LQAjtNsMODjtJpNFmRvAH5GfJR6DEPIW/WAV3rEK5PGYrXF -h27SWw340I02izk+dr0pLscGnQE9BCjqzWb8m1YrkGA/ElTwN+ghMBIWzEZQyag3WOFDg8Zmwiln -xnUF8QzYL9S5GXSJIcx1eg1mNtrijDSZLDiNTXoL6sdiA0bXhMZoACw36OIMpg/cL/LgOgVeWYwG -Pf4Ks5hxmgBpwWzEQ5MBi7esN2J6vGUg6joCB6MRdQdGAEYdx9ZiQsKv05j1uOSYLRqdzUqovxGz -6ImxWkwGQjExM/FNM3hvNhC6A6cP+BDQx2Iz4f2YMVv8odmgtxJCZjKQkmex6PCHQGxtBPXNetyi -gU86o4XgkwFwF9dbILfxAdksVhMhzRaznmAzMbmAhzYDTlTw0GKxGfCHRrPZQEiJzWowErw36JCG -AEVHzQPKmYHQGuLqZTPqcMEzGXCMwEMgLsRDzIqZiYcGixEnuwX0g56BGcdmIRhq1BFfTGIlx0Nk -VFh10SJYFzuRDQqMiszkAxvPJ2lwkfgHzRf82Qx/puE4gCky2VsADzPzF8AfZuwxgL/N0GcAf5mh -1yANZug3SIMKRPWMrSaF7rj3AIefpv8Ax52GBwG5KsyHgGwU7EXgNNQh+WPxJXC2ZuBNwNFl6k/g -ApyJRwGHnplPgdMBmQD4j1tFGT4GzvgkLwM+ZvEzII2ZngYcLYuvgfM7yduALST7G/Api8fBipyf -W29SeR1stMlek9g8EHz0DB8EPmTxQnCSJPkhBE2Yngh8zOKL4IKe5I3gFGT6IwR3mR4JfMzikxBM -Z3ol8DGLX8JKEPjYk9BTNv8Ef5zkoeC9JPkoOE5JXgo+AqafAp+yeCo4FZJ8FfxxkrcCH7P4K/jj -JI+FkFymz4JzKclrwXma5Lfgg0zyXHDBSPJd8MdJ3gsuXUn+C26jmB4MobRMHwZ/nOTF4I+Zfgx8 -yuLJsLKd5zFpzjLwaJh63klYPuDZQCNBfczSOngUDeEfZbV1RbWLC7NFeJBk4kzQi7Z9IToRGcN7 -K5LVjs4Oh4O1dbZg83AsHD09PBpsCG8M4l+KUZDSFcm80dCGianYwsQ0wsDvIIMw/g== - - - 0MZwVK0WSaUtobFwRzQ0MR2OisZiodPDRaFIZHYhtBCeA2+KxqLh2MJsNFwUG589Az4BfxL/ulTq -bvaI/h/9ncXa - - - diff --git a/shell_integration/icons/SVG/OK_Shared.svg b/shell_integration/icons/SVG/OK_Shared.svg deleted file mode 100644 index 9584801b60f..00000000000 --- a/shell_integration/icons/SVG/OK_Shared.svg +++ /dev/null @@ -1,4711 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eJzsvemSHcl1JvgE8Q53fsiMbBNS4eHusXDGZHZXiTPiYiyKkkbTVgaiklVooYAyAEU2+TLzMPNi -c76zuXvcyAWFZLfIznRL4GbcCI8IX87yne1v/rdffvFi/9W7396+iDf9rvubvzm+v3358d37n+z4 -6O6nb958/+Hjexz60a9+vAvjTU8n7X86f6kn/ub2/YfX797+hL/iLy+4+kf7V9+8/vbHux/9mI78 -+vXHN7d07Is/vn315RffvHx/+9XNh99//WO7HV1/evmRzuipj/EmpF1IPxkmfP3y7e9ffvjw+k+3 -uEGcIx07vPv+7Vev3359ePff6WCYdi9CDLuw5N2LeaHv//H1r24/rE+6yXkccGZ/E8ZMZ6ebEHBk -Xm7CkOmy07tX3397+/bjL9+/e3X74cPx3Zt37z/8ZHf848u3u5+9/Jq+ebn7t9s3b979YXd48/LV -f3Q0CvnLy+s3t/TC3778uAsDXn//0zB8efj+9Zuvfv79t7+9paEY5wGH45fc5T9/oL6oW3zG4enL -n35LR764/fiRHpduiCH81T8c6segg9x+9O+/pMG4pbu9/4/bt//1x9rt+3ff4QhdOyZ6xWGm1xt2 -vXz769tvv3tDgysDMcw3mU+h/+o/9Fx6GT7vRcgB36WUdqmnwdvFuMg5ZZhuf//69g8/2f383dtb -GYv9+49fyEyl1Pfyr3zzq+/f3L7/57evP+IZcWiRwfjZu69u39D5fv3lzUseA26h/Csn/Prl+69v -P9Lsvnvz/UdedLPdgQb7n17+8RYzFuQGv/ju9u2v3/2Gn/HFEKebcaSJn4L8H3dD7hcag0x3mPkm -YRh2Q8h+81D+lXugR/RnN8IC/SXN3S/ev/769duf6GNOX/7D+9dflfmctfF73MzV72K/8sD07h8/ -3r7VF6BVdPxZtSr6m599Qfc7v/3q+O5bTMAHbIVbujGtlDfvvpbv/DN/Q5d//508O//9Jc3VL9+/ -fos+u5/zN/OXv3zzPX31D+/fff/dT9/+7l33I9n4v7l9RbubpvOr3S9++9/oD9rLP3/56puvb/90 -+/rVN29vP95+2B1omdNxP/fm5evvfnxvt/SS72938iVdyX/a/w9ffbr9He21crkc/cf/h6b3zZvb -ty/+7+8/vPz4p2/fffX9G/r2/Pb3t2/efXe7e/n2q92/vHz/3cM3+OWbl29fvt/xceu/7nX307cf -b9+//I+Pr3+/+/Yl3ZJO+qfXv6cLX9KwPqJ/Wjjf0WjxJXxKe6/7Tqi+esSNXn78hujU7duvPnjf -+7dfv8Hcff/266uxwkvI6Q/3/cUrLNT3u8P77z98s/v1u3dv/BZffHf7p9cv33z3+u2H2zerW+hl -fBUu+k90o1/yBW9/8VaG7fp2PyMmePt+825Etf5T3onOvvsu2Lh/Ifc4vnzz5vXX719+983rV1u3 -+b/0+9+9vt28m1z/Kavuj9/+9t2b1x++LYutOvLLl+8/vn715vaLP9I8ffvoCd+dv3pNNPKOTX/v -OV/84eXHV9/80+vfvn/5/vXtvfsTU/G712+/oj3zxfevP976HTAk4Bpf3364/finj0SHiY7y23z8 -5sIXfOH95i+JkdSs4sWLe3jI0O8Ob6vv/+H9y69eE3sice8X71/SDf929w+3b3774679k5hh3h2+ -6v69+z+6PveJm/0ftaXq78GPD9pS07K31NUHur+nOxwOh+PhfLgc+2M4Dsd4TMd8HI/Tcaa2HPfH -w/F0PB8vp/4UTsMpnfJppDad5tNy2p8Op+PpdDqfLuf+HM7DOXbneE7nfB7P03nmO/D5mdtYfd46 -krj/dSvn4PvYnSJ/oI/0B+7QP/FP0yHf4bxom1dtatq4arlpqbSO/4vS+A75Qqec8jEf8j4vec5T -HnPOKcc85EBTd6ErTumYDmmfljSlMWWSQWMaUqDpv1A/p3iMh7iPCykDUxfHmGOKMQ4x8B3og7Vw -1fp1Gy5X7dy2jv45WaM7pN3ffHl4j+mQRVgWZNBPgdtw9Vn+is3nsphjVz1WWbhznsd5mud5Px/m -E83FZemXsMQlLXkZl2mZl2U5LMflRPN22ff7YR/3NLb7aT/vl/1+f9gf9+f95dAfwmE4xO6QDvkw -HqbDfFgO+8NBFu/sbeI28u/on6fq+KTflcUcfa3qYu7W65ruQrph3+e00O0ugVtf2vlStfrnVLVj -1Q5V23fnvTdewDJ0FxquE23wAw3eQkM40VDmOc1xHuYw99OFFvNpOk6HaT8t0zyNU57SFKdhClM/ -Xmhpn8bjeBj34zLO49RBbxjTGMdhDGOfL7Le0h3tU3901dYLWA6s11y4WnFDtfLkX/ysV2Xv67G3 -/zs/UL4sy5WXp7zknrYcpvlChHWgt0u0MSfaoHvaqCfashdarwNt4kTbeaJtvactfqStfqGxijRm -4zjREO5pMI80rJdu6mmMI432OE008gvNwJHm4jL3NDMDzZCs/juXxXm1ILAM6mXhK4GJ2cS/RKg6 -pk+RN3Y496cL9YKfI1H4PbXlNP/5Se0dp+C+lxO3o7eDtr23xdusbfI2esvSukvyFr0ZqeNdyPcl -JjgRO8zEFiOxx3Ds6abnw4mY5oGIxELEYiKikYl4RKIi4dDTo5xpxI5EYRaiNNN+JJqTiPYM+7Dv -O3q+M5GkI5GmPRGomQjVSAQrEeEaiHz188XGOfBCA7cee1oV/dwv/b4/9Mf+1J/7C63HEAai3UT/ -Qw5jmMIclrAPh3AMp3AOF1qpdMIw0FZJ3ZCHcZiGeViG/XAYjkS0MdkXWsqBNlOMqdlOJA7QrfZ0 -S3okbniEpfpbjkgbuamMsZJUeJN1q0N3teGxjTd8uyIDj9ZID7bncTrxGA08RplGZ+HROfLoXHh0 -8EObloaGKBsNDIZm39HoHJip8Wbg5xIeaT+Z2+Rtbtp+3Tr+h5kJpCbITCYx9XILFpikidwg0gTk -C9uq+1Xjzdz57pZ2WrXrn8v9jckKOBJ23PbPQ13c2WwLP1njDo0YnpnoHZi4zSqJGUULVxTtwBRt -UaYuLNvYNU18R6JuIIG3V2KDwT1xs58Dt723pWnzunX8Dx6VNmGmFRdp5QXiK6AzZ7rZkXbtnlfo -TCuVmCit2MSrbgj4ASe6YL30/Fi6Oal1viO39+H1xgpN2ybIhRT8O6STsBCTBxrX4+kA0dF2GYHT -4WQAqjfzQmeN9CEsQ8g4OUzECMNu2IXduAOU2Pfo8ovub758yi4PH/gJ55Bx8UTiBfdGkkxOO5Os -cKNA4kumD/iPj1Dn/TSX3ub5hvqvn/HpOpWn7Jcx4fo89PL2M4kK/Palx5T7BTfN6AcnExNYAL9a -j+N4M+e4VM/5pN3qk47LgNMHWq+4PE7EjRilXWwyqBviPXTMuxjmm56E7vrJPqcbeZI0Z4w5v1UM -WCGAi9HVkDHyIS29vQbuVd39ky+lOx5Oruebur6twX9x+8373R+AGd7uPrz65g8v3//pdveb11+/ -vf348ZZU+vu/p3tG0fFpBEaawZk+jPNMKh8+EMMa8GGeAsZgJs6NP3seLP7Q53m2PfoEXZTn2Fkn -O+tkx53srJOddbKzTnaYUpq6kf4ell2ebqCYlol46o55YXxWpzdjDkPK1HcMN/PYx9203ExTeKqH -vrv/J3j2JW8Q1Kfr8lM2wT+/ffvy29uvdl/roV2glb9xkKYxGaRF8jQaq0EsVUOuZsmaZWs05tAs -YUPGRptZzoakDVk7q7wNiXvoSOgOKnhfWPiG+H1ivnzYy48I4iKKZxbHkwpnA4vlAbzUtIeOJa6T -CukQ0/csqi8srkNglwaxXZQx6qNnuR4X4CR8BXmepHnWs6FlQ8eGhg39Gtq16NYT6XkJejVr1aJT -z6JPR9alAQoBEgIglAAFMRAEGAggECCggQSFC4M/ewA/JJpCZe4ZnTnQ+83DCOk/ssABYeNAjG1k -8SKQUAFxYk/yw8gyw6D6ViURmFbcN1pzYjCmQJOZ9ZTMEjj+F2FkZqWBxBOTU/b6714FGBFljqzU -nPhf+fug/55W/8p5JAh1kO9ZIrror/x7UinpXB9VkVbBN1X7Ciyag7dBW/SWtJWf0VpHcyJt9rZo -23vTBZuP3goGZHK/qqVdvowqxI3B2+AtaktoPE98oiAJWbGERdGEE/AEhhMGBhRyBSkcGFQ4V7BC -Ejytm2daq4DUjhWoNjSg2r6B1QIDa4nedGygtZOAa52ga2twjXb36Rp2lt208J498A4+ST8K00lX -6Ey6kw7RpXQq3XLH3DV33mn/AmyPTH1mltP3LMXj58QyPkBugbkHRe4EppsU7l4YCjl0rAucWMe6 -KPLN2LdrcqLDsf5Wa9WsMkA9mORFAzfaorw+eYUCODoCOGJcMLLAi1ONjjX9sYJyZrXkYEM30zQw -1qBr/gpJYXxqSjThSdBAXqICSckZ9VS2mgBjhIMIliFHfMjjnNkx4Ca6SCdiuovHLtkt8w3JemPh -Wk/UnzBVELXIjC9FkTLRCXjcsLOhxNfEB1PVyTjc0I1qzv+ZHcnTLDkPC3dCjDloJ4Mx4aoT7Nqq -k3ADrlA9zWd2xE9DbzSGSZ45R3G3oCGPEeoV3gI9RLpP4q4SSQZL6aoSMz63o88TLvpN6aJvxAsG -wdAAh5U2eWv/mljdbv+q29QxtlZablpqmuBE7V/WBvvU6Z91CwzlBf3LlPNgqn+lrlemAf6b2FtX -WwuadmrawduRWf6+OrKvm/DqcmBpWo37TcpAjWnVtsYKCxQzZmaZoZglW0RvE5ZgcgRUlCWpC5vc -YIuB3QIyFWwXM9suIFslBuAGsWGwFePCTO6k0tae5ba5Ix44qeCVmDMOIoCRyHJmMezM3PPAfHTP -Ro6FOCtaZjtHYiY88IKgt+6YeZ9VVDs6459ZaMtsxcssSwy8anq25l1YFoFF78jGAmKxTJSnNHZs -3MtuMQmM5FzYyAcz34HNfDD0LQw5Ti6tRDpdDHxmwgPCeOwgYTPuC5Bz5t+RoSjIhYkh4gBIFLAx -g8dHFqoBJgsohT0jCz7LIpZlK0vwrOvrWAHGIy8Og6CAZVdgfmKsPbB16xq5A25XWyGA2BlaJ0hd -BDynvI94aidM1XF60yXuwukNpRcV4QGU/rCN0vNKbO1nkaV7ke/PrOVgzc1Ybt09BrRJrWeDyvtn -lhHFDDzpApLFgwFVI3DHVuDZ7cADUxyzAqsNOI68fng58FI4qUqA+Z8cisRMXjqeeKDjEEoEhcyK -P/auJmB+J1cVgs/o58L5qxa7Tzi5UI1rOuKtu+uLH9ru7rDfglUfYeb6IRf9WToUW4XC7C1HYUm3 -tNYhIDYtta1bOUO0jhKtG0XrYrE0zU0gXbGDeDs27T5jyMo6Ad7Z1by1aa0fRGxaalquW1eZHdGm -ps1NW5q2XzXFRTr9cGxaaxZZvZf8iNW/Z+o0sLoXmV+Cb8KJYmI+ujBXPbDud2Q+ew== - - - 5kfrWQeEFjiwe0ViDXfsWCEUlXBhjOSgiuGJp+rC6qEoiAODLYnVRNEuoF/MDK3sGeIhWtyptidO -GabvmcaXXeObXOMznU+UyVrrY6Wyc73PNL/ZNb+9a36q+6mLU639FWeniT1Alk4UQPZ6OrI16ex7 -xPaDrPyVFqgLV9aorUbY2nS12bqyNTT6EpHlYLNuMywz2hh62cy7aeWtbLzER0a2WC7suHCk9zyz -60KA80JHjCSzfjirB8PRfRiCejFk9WNYVG08ieK4jUJ0PxSGuAOFiN0PByK2cYhuG4hQiEAWSe3/ -FnVR3OP/5t5vPOky4WcmMYFnOen8zjqzR57TS63Ysx7Q67/G5i5qLTwziHdibn1Snn1kq78JbXu1 -JbLgJl5+JgatnRXOLAYdFCNthKDKT2GupR8R4YS+FLqz7ZNRyFtN7GpfDFrxIkG03gST6xRjY9W8 -1i425A5hYLaPaw+s0ffzpF5di+M6srHL1j4p56CXq3GSexe8OPE8vNpl+4oOWSmbKzVV2uJtX7WD -N57/evW4tiniela9FRJd6U/62JceeC2dWQFg7wgmK1iDPS9HYSHVVtSdKPvwehdiD4K637UDef+x -96msp6x0z1jlXFE/YYErZuit5oSVtwH++fvijbgpZqorV62VrlXYdhHSzPJKFbcD8+EczMmAVaEL -qy1nXUNHdzjY61qbK7eDsfYU5CUr43y3K960csZbueKtPfHEqbSDOiFrbr4DFrlu4z3NERBexA1I -WAUJBTPgXUN/958lpt8lJwBeN3mc+8RRQXkSpKlHgBpbuhVyynAVwIfQL3QyG7WSWuCHG+zAyi78 -tP3ys4aBCEBmX4FxDmxSW6YkfULXRpdDFCDP4bEeh+I92N+T9ipjSlofIM0b4uN9QAf0emnmHvtx -5q5n6iewrwD7W+ArObIFCD5Vh58HDKZNYDDBzO7AIKs2wT3qzJvuRHwbUFt0Wll70ZloVYQryM9Q -oPedSFfqHTawX9io/mAiXaljaJGv2D00s/FGRCwTsgTxdzlrULOTmJomtSvtlQEd1XJ0UWNRaMxD -iTEqBoM7IgcL8yVwJvCmI9OcM8NcF2a2gTnVoK5siTlWZq41TuWHVYRp6RhAY+F9Yjl8UmOuOkEX -Fc50nL5q4bp1zR9XdsfBf8UTMjaWRhESyu/Et1mqX5rfTi2NZj88VfbGIwNXaj4EyOGoa6+I7KDu -gxVD7ZT6Cb+2f4WWGnedVf4Sn66Deh0eVFY7OaeFH+K5I7FRxTvlS4O6bcr/kZ3Fkv8vnzJjN0kx -vMwujLIKcsfejNJmb4s3WcF7XsXSDtr2VWOjvBzv/AT8sfK57B33KF7VJSjF5sxmruavJtTRka45 -JOZja+bZNuvn2ht1VrBx0c+LQlT7jv88+Bd8UE+poewCb8/Vp6X6rNd01R/71WX2l/17qJ5k4ce4 -xtJ5YV4D7HO/3zxeu93WZ1Tbq2v+cB/OM8vmB/aPntkTOq/g120ANqrrZAFhj7B87h2HNSR2EGdJ -RWPZFqkOkQLJjpWbRw3MMjTbOTo7u3dHUvWEFRRFas2l46AuHQujB+LSMVYOHUO38ucorhzFmaN1 -5BjFkYNxDWg4kbWcQWXuvnPjw0UBKQsIEdfOg7e9tqVq7Q9T0W6eShPZ9+K61NH1p0Xna6o1JXVR -1xAR9/03j3/x7186jVOa3A/Woo9WwupjRdVuJau6o6xN/spX9lz5ytaesu4dK2710kZdI+IOJC26 -W5C4BknrpVWyvwN/nfoMybKy1v7sm7ZstGoyu+aPvzd/6PNK770rGuGuSIQqDsFUnus4hKGNQ6jj -gTaDPlRP7TYiP+qojyYOaCOc7SqUrXtMJFsdyFa1dbAYr7puBR9b6x9qd/mAd80flQr8IJpUK7EG -KBVIqaizqtB2il1OrNXua2Sp0mxrdGl2gNLBSYWYGGTqKneUucGaTisw8hqIXBoM8izoU6cA1FCB -71mR9dkjvxgTFFTwBFyQfmEqHDiuIbEJcWS70cQWpIUl2SMjhbAsnWGGZFl24MjD1LGdUjxKIMxK -1NORnazOMHOu5FkENc7uH3XggEeTZNnVqWPlNWtg1OTS61GQFZZaTV5NKqUuGiXFcVIqdAbGuGn2 -O14AmN2DQtpnhbMFyhafI3dUqyWJg5rI983/+5V32l5Nn0c3rR/B3c2x7aTWURE1a6Hz6H5qRz+j -/VvEVL5Np/dp77jXexZ/uvqTSB6zCj/7VsTpKplicrFqVLstfVIR4sHYwDUgcRUd2EISnxroOj8U -6trxevVQV7d0HjcsnRZ0UYdcmKkzmbGzUz9CC4Ca3eIZxeYpiKo5pDbOqCuRpfigLisfVBVXiqxi -XqdHhVz3LqQUASWq+Tm4O4TJIeYQMasxmqasqzwigs7cRb0hjqy8qT+EzqHMoswjzyTPpcwmz2en -UyqTOrGOad4Q0DxCsRteyxQHhVhNqtiCwDbFim4zBqdIFSXipogTtTBRBAkVITqVIIQzFoGhiAhF -GJiunI5lpm2u2e+4a1yPi/OxS6s1Y3ILZjFYnmqD0MoGKXahobI0ZrcQZZEiBC61f8Vs1FoXlwqA -n6r/J4bipwqOd7FEZU3xvxnV0Cc/B+WQR16uZuQTP+rIIdaJhfJJAeBZ2aX5cx47jJRsEvzLWwaD -iUHF8GKYMeCT/zvz78STsme+uvBkHfn3AOHvwPNI8ynkfHRdryA8QVV4QXiA75zh8MIIT2RVemQ9 -WYL/LswWB2aKU8fxfuCHajNjpHio8Bxstr36bp54noASFIPZpExPAZyuNiLcF1hbK9cGVdcwSAkN -teDQIxsjQcUqXGvk+EdBtc7NO0/8xkc2EwZ9X3tRiWnWiOZOApqZoefyyLXuX2PpNXo+VRymKNCH -imEyv+vUXftSITGhwmCShroW+KUYSop5pDJodLVNQ9yl1WVLLfTis3VidnTkLb5nprQwY5p53kZm -T1m9wMURx/24OnfkOvPEH5lvHXgpFIeuKYnHdmY+lhgIHMRlZ+XZdepY9VEHL/HGLdpuMc+JgW4d -SnyXm1IT6N89LT+H03rFz+8zBQDsjfEGomYFId97EoPWuZ8Cw+BhXPoeYHcPXj+y9ynD3gCgpyHk -cXHsvoSTLTe0fHLldPwk3fGTLSHTBOKymQRmweVjKD7DM4Lsrpxzw81CZ+3GdENLsYL7n6Q3eS6S -4LgD6kdCFuM8J35pCEkTegn2goP77VbxcNZtE2T0tN1+HuQ/bkL+I3yBYwX5Bw5IWYjEXJgYZgZn -QfoDkz8j+BeO7wbpY12HEfvItF088s9I49BVWRygu5yVgouyUpt/oaBkVU7E8HsuCRxcJT10rpGG -DaOqOzfc4d4ARfTYqKFD1zjLTFVoRB0YEVwPje4dU3xjincMSVudogm1c0wbHLE2qNem9MPpdGVK -bxxmwpXrmDnP1D5i175hFYaxFqmu/cCKN9j2z1YQvMti4ZFtI1+OtW7Tceyulh5u3WNO+pT2F9dh -yZER2Vggct6FN/lAbHVU+4tkehAjyrV0Jya72TZ9p9iG7HuR504SMebOT8X1qfUDyU0iF/V56q6c -QIQaFJenGqaybEYCVJ0boEqc7tTlDqxe3O1OLIUXpEpc7YqbnUnhrQwu2wUUI3Qse5uTXXYnu1GV -n8Ud7faqJsm+NtcLFd9Uw4richfV7Q6toLtuK6wSI9SwcIXb1skWOvpd7+R+o4XNFq9b5x/TI9s6 -Z9mqWUqn8cE2Pa51jz3xr7bDWiMKlXtOMR1ObutaKpWiKBN1VKlCbaZeXMeIssJR/3Jg7MVCbjT3 -xup7VbGsC/4i2Fd89MT/nxTQ4yPVI1hwqnyP/w8dfxR4r3wqAF9tWFwbGdu0PBqD0bG7vilftTKW -BeTTqJ11JI98LscSS1G5GGqjX7qRZFBb67JXjjbHS4bD+zz9cvP9vWd2+ujXDoR3ta3bXXVYW6Tv -ao+8YffoJ/tr7bBYp0/ERJbLRLp4vASSuU4kvC0k5WWSBANJigAQ9yRUjhL0c7wwW9gz2pcZ2es5 -XwCQoKUjhlUCfADEiV+rKM6MtqrKXHJvubK8rSqbotzfqyjXqnJRlvcVUipxY7EToBSDUKEPNfJw -jTqsEIcmduzUKZ+tMQdBHda4gyAPSSPKBm3BMAjDIToHIxSQ4Hb0dvBWMlwtVaszYfHIdJ4ma1y1 -fNUelS6w28oN+GC7znjprbvvy4cTZV637jEnfUr7i+vQDObr7X0m6W1Pku3INuN6g0+0EYD99xbT -p1s8AbhntP4IXxGJ48MmH3yT1zF8W87rS+u+XlCx7ir/5RoVq7e7bfh8ZSPZtJDMbiHJHi86NPBj -2f4t9DgXItApFciOPUZ19worelCDkCcTvpksGGlg4tA1FEJoxFRRikItCsWo6UZLPah1TkRK66/b -HcruhuNAd0V8HmrH+1v30AkbtO3e1j32xL/aDovXEjxg7vYs2/YraxPyeTq+buVXJp5l/ZVn2d7N -faZZFu+y4l8GS5Ca9g7qY1ZMeqObbVNjujXjbZ1G6OSWvEOVhUN9vSZ3QRs930taZReyDENq6b3O -NGQuaid3VDu6u9qhykBUshCVXESlMbjQVcmJ2pavWtpssW2dfxweaOFxrRPz9dO1TpWfy1O17um6 -+gvtsNhwem2BW5UIZsnIcLYrmfVu+mEI88SGijAjBAEmAJKXprjzrINsHxg3spc9TXeHD53kRxl3 -Ie6Sm3zag3bWzTStohnWhz/PQDFvGijm55iE55iE55iE55iE55iE55iE55iE55iE55iE55iE55iE -55iE55iE55iE55iE55iE/3kxCbXr3DoiocQiFCc1y9fSpjQjCaPzpFUlWcuxSmDlSawe+3NXLZrH -/ZhsdObbs2TU0T+LC7SsflyHOhw81MGymZmDVdRKcuADlsXMnKsOlsQMm67THGbrsIYSzLDXQAaP -XxC3KnbS7HmB4Dcygx7AojM7VWV1pGp/F/6d2Zlqz3byIztT2e9Rfy/lt9MP53KwRFCMqioexVVF -YyiSut7PjGkbIjUoIpXVz27PLP1izLzT0AKJo5CiaWcNMBjUxFSnHiuJx8Cq03XisU6zp1vC9FBh -TaMy6lmhJmPVxqxDxa4BLOGHgaS982wGju6veNaWhJPohzWWUOp27ZU9HzvzAmqG00JSNFxB87ll -hkv27LQoqB4kIkhCMoKQeyLknYndFQW0k0ETT2VUm4OXsjgoLjQSJxqBwN7JEFIO7Jc8EFEeVRq5 -LAOSrI3sY3hkeTPu0xphCSuEpY7/iD4SWxmVihvI1DWISuvPVXt1tamKTZ4x7y5rZwsgOa+TJDfp -lGsQzYC0Nm9zarMbrdJzXYeRnO+15K5DSFoTrppvuyoX8F3hI48IHimxI12VG3hybwvzpjCHiJIh -+MTCydERt0WFlMnSBHeO8hkG2JfolDqDbrHAX6oqGm2pu7viU0hWeFi2+0TpritZsA== - - - nka+6zy3bl1e9T9Z0EvMN0t+spgX7e3zQ17mfEOjNzxRyIv1piEv08SlBOYAcoc8XVhpfHEitYr7 -9opiXq2LSwzcG/LylN1+nkVp2bQoLT/mJaQWpQohqgvQ1ehgKWlrMMul+tQ0N6+qemr/l2aopOH2 -Zi6WTW5W5cru3PlH0yHMRD2tWoEsF88wu1S2cM0422niWW53cWxB/wuPsv+3fT5Xjp2dIvt750ry -r4N/rnufWwZ0xXKUwXSemXGdc2/ftNY6c7oKOTGTzNBpKUvmKyLJFq+CGmL2ukVOhQfX245VYv2J -nZCSJdOfLh3n0i/6mXhCxcrtEfQX1Bc/SYsXgT2W8kXCALWEUcdp76WGUWY622sVI5DXkXPZB65i -JKnrF+JFUJiliNFRc5RnlEsw52n4OidXhxkoKMam2sZXSRtVRmaDGYsbuuHBqnNN+04NjXtfpQaa -T1pSYFStNaunWFL9NaoOK3psEF0WESK9gq+Gap5UtZUmwRh7r0JgvmWz3ml0HzOtTNCpsxmrv1LB -Nly1frutE2uuAW2DtU1Hbdvxqq1/9ttwd7eFdVeqbmlj0/KqubdbV+nE0VJfm623TX+dN9JfHxWS -bUHZBpbtrnHZJkZvHaVnwOw6DaoH6XWbqOx2MuJHpSM26JUrEq2K+KAqi/g6jGEmViVCgCS4JCEg -0+qKJfelJuecbohepjr142f2w1ybHTlwUiB9JRa2LzUhbyZLv0mTQ5IAe3xMG/4jn9fNZ3HlYbPk -3RCaQFQ1ViTGwnuFouqyFlbUItQlnaxYFCNHsaprcZJ8zgvncxa8T/CfUn76tEL5al+1tZdahC2h -MbQrziF4R+W61mSpN8+1wmkqv7XO3dYOG65rtfvadKfPWu21lrpHOKxt+aMVU/WG09N5s53uade1 -fb11933ZWGUf2bqNQ0zRR419EMdo8YoeV4EPCHtA0ANCHk5MqCSNt5QlPHNRwon5fASb7IndCVeb -uCBOz6mFj6hGyGVviJ+rsmvq7cSIjCm0wdWoczp2lRI7qS5lyqurroB/WqW10Vmlmo3qqp053jfK -6r6qaDNXFW0y66mJZaJeqptUqdMlXbbLWLMWi5LWVnUqKIGiBxWyUIAHrZlUArSsreOD7spgvm53 -1Bdpc1d/8s+GWGz5sSNbVziGlm6UPEOK4HoQK6tyAuxYZsUE6kIC6lTWeQSt+ZTteTkIonfOpZSA -xdMC0xMnsnPlPAYoD4LO0hVHMXUPK25hcxVVezBzm3J7k/ywpMaq1AV7O8hPXe+iKEfnov6sNB4h -LRs6zZpwzVfaS/2z9/8r1aVu3cbBtVNwW3P8dGdjMt0t56dtGx2qzO0m2oOuixPDUxde26FyOixu -h1qNUG215nyoodcdb5XgRltbOeaIOPkaMgOuldq0ApuXyi0xABEqpTST+iZmlaQnxY1nxY4Xlb0P -vgaPKqGfDU8eL536LZrvYnBJP1Y+jMWP0XwZt/wZK5/GpfJsrP0bWy/H2textJXjRFfcH1dFaPp7 -2oZz5KaTpMvVn9G6jUNerbJUI3lcLZLt4gxsB+iqeiSlRsM6QN8sAIWURPXcKAqDqgvdVbGcjVI5 -jmeUXW+btq2UE8RbLK7K5Yy6AszYtHg8/0HNelY556w1EnqvoBM695OM6g+StIBqifYfq6Vlskah -NUVl459O9bla11trg+ertqlciuLZ3aWRarvWYB9oG0qvoCJrQXMrHOIhibKSKrsrwfI+SfJ+cZAn -t3OPvdZn79pvr/LeW7Wxbp1/zHe0dG+L69ZdHXr8Ln8kKXmo3UezqHUPnfCpbaPDpqpvWyVIlHJR -yUUhl+pAEF3Z/UnsJuK5NTs4wOl7aHdKzp4DG4MvjgKM95sia6Nk74XwhsdedC0UuilkbQxZm0PM -4YUL5NzlQ3OHhUUGonigFOORmY9KDUZ3PflcLOMmxA0A4TP7EQtEHyeugDFNY5CixkvfR6nXS5f1 -iGuxpFi5NhEM8WYa5hm9FcPDU/SmpYj7xA8/ptiPXrO3R/nem0EQkf4mjrOUPhlR51iCa4bhqt7H -U3T2WXhLzFt4Cx2ljocmrmZ7c2wfsZqzwiZY41X7oRsP97qk60KcB1VxvQanlTe923W2326aekpd -5NsKgVt1AbeKH1GTLXVf/bN7q7Dz/avtRQOyzNgAA0R1TDvt9WVJPO2L1+dGneeRx18NhtN400+x -KvT9uR1hIcP21g9RCvaMKATOF5elRru1vpb2QQzDUtlcf9j1n7VcNyvTpMZkpwvw/iCw7RCwtuKf -Kuhd7JvgL9PRT5q4NHji0lWdv7sk625V9awVrZuMV6u6Z9t1/u6NqooeobMVU2XBNnUEjbrFdu7c -ar+e3qeKoxr8N7I9roTYZQ+bEm+cudOkohIrdfAKaeaio9XROBxq0N+sRrjRI50Wtl3Bq+LeMKXg -YUpbQUqtW5Hlw9H0NKN7Go0ONY3qIWq+tK1zzeyuuMWY6bBVp1/OfRsEtPhg76vPizqkHizKqekQ -fXxS3M/dEeN1vDgH/HSnqYn32QLOR8sgcW85aMXNO88msd+qCX2VbfXBXKv3BdJYAMZmGM0qiMaC -JTg0otNIiBI+M1QR9Re3LXhcvYfMFLdWc2kNxZn1XLmx7j1WaivKvkRMXSpf1YNb9B4dmbIdl7KO -SvFauttMtYlFuYpDOTXhJ03gSXcVb1JFmVTBJW04iYeNtFEibajHthNQI0fd9f0TuP60cu1nd/V5 -3G/TOEZHH+R/oN8Xpdi1j6RyQOV/yd1NDaCmuek0k3Upd8sBI3XKaiv9WN9xUQT/pLnazn7/oeIS -9VMcYEeoS/AaU841pQedr4tNJobbx+a39oM0v8dDFe3BT9U17o4Xe8jPIa93EleJr9sgrm3qj8PV -3db3uyboa5JO9+3qGxeyfk0/Hw5FNCq6HYro+34dipiebvfSwltmVhATiUmcyyAvUxZRF3/OprXN -qyqIzc79vG4+b9cOm7t2gIpVUhcIxuBmKZ4sz3ksAp+4kq+K725DGTLXK+oumJ1xXOW3fHDt2lrj -MfJM8jwLnHHkKTieo7o39g0pAqCBuAv8/WV1s8g8Nz1Gdxur6jFX0pupkKFSMUOjXAb/hGdrkvrX -tj092aJ5xrWMUmW1MwHlWjzZEk083ZXE4iT27RL3WneurRm3URLZafUuq0N9z9XOWpyFjnVkb+eR -kisCY/LIDwKpHvp5sEMp4GvhpzVQvc4Zbe2OjNG1Et/dmRFaF7Y5j29Xjn6oUvodddK7rULpdZ10 -GWGrDnFyzhY2Odu+iUMwllY0Ta0uX4dtXnh3FJ7LXLcF6vp+HOfI7rMTvTenegn9InjWwhni4QSs -7r6D+PM4SVtXD36i7qQkLizz6IlOD/gwgz9HwbWGlHZGJRgbDNOYCwJXiuwONwsNZv2AT9irPidt -Wa4/PMGDg7uMM2r/Nn1xN4Ar76kr/PldyRPRjIt/FT03PJzl7RYpA2y1ft2HG/+FcYuffn5Pn8Xv -NhHF/Jyo5zlRz3OinudEPc+Jep4T9Twn6nlO1POcqOc5Uc9zop7nRD3PiXqeE/U8J+p5TtTznKjn -OVHPc6Ke50Q9z4l6HNV4TtTznKjnOVHPJ7nY5PFmIsLyVB471t1zqp7/qal68qbfUw6Nk/p2xdLN -dl/dYSfqnVsA2p+6Dlop3JS9mQVoDfbvO0XzBbw3RE1kbEFlRNTs1fwb9C6R6ZfAASMX2BVAgHbk -XrNyHD0yFDYuQwZ63sSMDTQRonNl73KLF8eGqtUrbFq+JhVDlsr2dW5iQcUOX2zxbPWaVzYvEUT6 -JlywsmtdBQyKKlN404NtvKe5havTD1OTf2lu/qozB1kzAPSgS8mzCXUKoMnqOuviEWZbm5GGyohU -G4vAcxZfMHvxCBDjz4GZ1MmQIlkoFV4U+Dc6cpRY2hw1Wtaxo461dFsuRwskdqNoUCApVkKpLZZZ -jaMWdw4NUZZLFUEaG1/32ZfLQaNJT8yOzk1UqQZV3RXHcdXC3T+VdNN5gqi4KeikKivB6AtnqpaJ -RB4vukr2tfO6/XvkBSGL4KyO7Bf+vVSz3zdzboRi7JRKFL/2xSa+IRAy6xdIwRU+ODAHFtFm5Nme -OqYMAhLuNYnAsQoYP/ssB5/lsSIIs2cWECXk1CmgfFEJNrDQUMLFzRTeQojt/F7ZkZ/k5z9Vh+u0 -EpvZJoZV21YY1spCiUCoDIqtilArBq4OuPxvu8KWf6GPUydBGbqoD2oMNxelEgxTwmH2JWcFMymi -OuL1NT665XuaGzC6O+wYjfXi2lzhtgczwdjPsROoqoG+aqParFH+WwBYdAAsuF3t3F3BX62Lf+3k -f11Mb8PNv9MIijWEGdRL8ToH0X1RnvRaXaV8tO6Ld6gfW9rHn3V21/P72Nm9ml+b3Wto8wfOb7cy -mz5ufnWGHc2saiV2FYR5v1X96OEyplmODTwthvRzV0HTi9rOawfVreiZqxmW2d1ILHBH29/ZKhtm -t2HKnNemzKvlcm27XFkp20VwXkHa9cyvZ72dc5rvrsW0Gzj7eranhwN2Oo2CKkUxt/ZxO8uHyud4 -Ws9x14Rqb+3h2uTQFNgu87sK6yZNgD0i4zz2OfKHoZ9ZcVumiYNFk7i9e0w2PiDa+M5ImKfqknXY -QBLkgmt6en7Esd6A92lA9GKXlw+ir0s/U4ROP9UOkE/T3efprJuhqnT0E3TWBzTUawW1e0A7ne7S -TSu5s5Y8T92VZhpWmml2zXQummmlZ5xaxbR7pGa6b/IWXVQKDQxbNappt6GbFnWjylPk2qn7ZDJO -PldIOZu1OYcoW7ZrL0zTUJcmALcKwq18HEbNjaVZMLs6DeZjlNkHlde15to1iuu12rq/S2mtNNaL -uT5C/OuuVNahUVllDbW6i6ygvYKpx1Zj7R6hssoqmngVzazLzK7LrHXWM9aR+T8EraOd3AFi1BxY -otKsVddzpbcGi9HuGBfNupJq1XW/Ul+vPX3N9jLrauJ/O0c9TuP5EVruHVptbFD7RqXtKi32Pn22 -1Wb3lUPsvloeB2AY9+uyAoEbaYkN4DVtqbLdg7ps8OlPqs1O6re9pczStIvjy1nJiEy/kZGSI2/2 -UPxVAjSd+F4tEUOnmMW1TlsTkjXQJVOeVkY3xrm62rX7r0P93VRx78un2H2CkjveawzbWzrItaLr -5q7KxFUruskTEExOHC0Y52ho3WWV+cEIm8W/rtTdKgS2JIE4SxKIziEVWn6P0Zwe0JOulaTufh3p -bv23EpEb9ai70o7yI7xK18JyJS53j9R+G+3oSjmqxOZuQ//d0o8eynPkClLnbju9uhDfrQXf5WHV -WOG62gz3w6f9nonvGuX4k4CP7YnvHqEW3zfxV3pS97BavAF8bE29uvR0d0AfhyvfLZt6M7xeGret -xYyvnftsrdXjWn1aKch3uNfx5He1DfYxmvSDmvNabe4eqTO3GnOzOFp1uXtAX96iAg== - - - d+BfshC6B/XldLUEtve+KszdUyBf9dR3VXWGu/XmTexr269yPe21ip3HfuAIymHJAUplUjvzDSTl -AONosjxjibjJFMuHO1TsJ+vy81Vs5BtLT6die3efpWLPWxr2DAU7X2UuW4eT10HnWzJObNpgIkyn -EVQlfr39y9o6B3UtB9XdqqjUXhT9pGGz03hHU9nr6h4bMfbtcNz9fWPLKOO09XPfbe41jnx26zYs -LJ/VuqtDT/CET/7Kzx1WX7qiha0qqc1KRHHPqsHIKrDH468MXSXn21ll/tBpJHFykX92XVRyv/WO -YmU1lgO9anwqHGeIUDZLpu2Roy9qzEriL9b5ta/Tak8ajDEDY6hjiguI5bm0N9Jor5Noi0ijP13J -mn2VMns7WfZmkuwm2et0eSC39VVS3DuS5/J4dZtZd8d721Za3xL2d3XoM3+6qyP7z2vd53bwV9ah -bfbAaftmjsG5MDQ2Ma4l6awEnpoZkeTtTPt4Yg9i2r3qfw28cJZM6J3GRxUM8CDxUA73CF5seUUG -d1+qc4pUGUW6Jt4NqoaEO21Fu60i3djlvpQ+msTdvvNYN4l24yTHmps8eELykozc0pBb8nFLOG75 -xS+dJgwXdFiC4SLnR04atjFqVNykkXGzhnhITIWo//bDKsHJ06eaf3IxZ6irm9pd0Wq8Yayak5SO -Y+zqtq7HsN9oG9W0NELvWKzFbXvsz3nduutDj2p3BFGuwiifov2Fd/jXAaE+d/jc4XOHD3ZYgvvr -AP91oL+146pd18NgBKmr8ny3GQHatq5ots4X4K2r/hgfaPkxrasSDzy2xftad//XG9kNHmjd4099 -fIf3+eF/cus+v4vnDv+HdtjmPt3OibOVD2c7ifUqjfV1PhxLZr2VESdteXYJUt3VuZgdrK6sFXX4 -sWPWG5HHZrzoWIexdDkSgd4WkLSakdd1Is1SUNeHPJbqZqWQ87oqZF0XcrtgT1Wyp7uq29NW7llX -71lVhmzyJHltyOu2XS3yMVUjT92nFJJ8TLsuNvnJ5SUfLDb5v3SHttlPtLmQADXTFg/E1E+0uRfa -2Jm2dKDtjM28pwkdpYIrR2YjoFvqtmKXcsVW3pRzR3sR+5D2X5XLVSzDYhsSu9Aq5S0t59oGWLlP -do/IrdHGX5YIzBKDWaVi0IhZjZc9aa4Ni5QtcbLrKNkqRlZDZDVItquiZEucbImUzRpTHj1edlDH -oN6jZi9uXKYh7jR81kJopQaRNdv/BZubqjZWLVvrNOC2tPXPsNHCZmMLRneHteOxosZV7qPu+tCj -2umu1t391Q9rf+EdFtNory1w++SDkuo2Jo4tzXGZkwTpxiCmx74fpswpaqXazE22D5zkVCtLjRvG -1Sft9fChY7snHQkRYa90thfK2fjGzr8ZM30xhht4L+1iuhlDTs1122e010/Uax7G3dDfRJhkr69f -n+HXT9Mqd+z68OfZZ+OmgTY25Qo87OEel3iWfdYxDyvLvhJ0yTlORDeppyQXLOaaNWfO+SSZnY6e -UkpSRZbUlpL0Uoywkk58WOulT/OjzoHFiPU06rVWjruM21U728TYW2mxi0+zezSXMlilZubeM79Y -rcyL18gULPoah25QaMuBdp3utBU1a1PR1LSxaurh3jpljDNcTNlHIQ+0gSUPcgxcMasqqqabWw64 -5wTttmkJVZ7np+lOshzkfsijlusiTm/5CCSKHhJG5qQFQnjuLgT2mf18yvb+ze373755+eHD7duv -bt/v/vH1t9/evqH9vXm4xDgQXSVympcO1cJowS0g8kpiO3uwziJDUF6Ph62eRelgJx0kK5EnJDo4 -iZbIErseJHqD6n9+Vzx7P7Cb60Tdn9vRp8zfv9y+/v/+37/dffHqmz+8fP8nmrnVgauCf21+7sNV -DZncVG8pyXuKNd0DRjt3oV070B49H1JdSW1c11JzO7rWU+s8G3edi+BcchdW2bebgsVXuZCEAI7d -qmBxoYNW+zy6DXp0K7EWLbbgjrpmccdGPitabPm1SgnzvWbZ8qLFnuCyGP5GLT3IxQc7J7lOcK/S -XZZqhFaPkBNermv1JM3YXWdo2q5kUNynLUzYgoT7bqNk3r7KaXmuQoaK6/Tovv1tRXNaAd1qCWwl -Zl8HA61TDjQRQN06Gl2Xhaen8ERZe89O0eZjtyVy7yLRMKAmEKiEAq0XSlPaunNbsFmD2+ynZYFc -LPspLw56x605tRk9PDCb+zsCvmO3MZun4eRzuapJsT2PdYb9bjWXe9/S65msa9GPHs+1zrN/7prw -m9hs83Y+Dx6GsT2fWlKxq8K69urdAtNzmc22WvmDm7+rKpZHhdTqquXXm9/qlfd/DlvnvbkI6uCM -NnnbVAVitKkGTiWRWx/qil6l8klb+6SiGdsko7szzKLiERVxsMVU19mMmvgNS4Zz5AkniJq8dtHl -IIF95rWxlzS1PMvIBDezowZywSEjbVbXjONy7nj3Ra0xvTAVPnOF6cRZDRfOY3jhjIWZfSX2VsJ6 -XeHrf1ixRivXGB9TsLHzakhPVLCxu8Ov+gdnq63zmdXZzJ4yn9lj89U+z+nznD5xUdVHzeih7552 -Rh+Td/AvOKd0iwZMiU7gSkyJXhSa1TgsEmExh5g5GV8IkStZVx/uy4LwdF1KRe4fXCfxTi2QDnz5 -83dvf/n+9duPr99+/eJFpRzWX3Q//w7fRPnmly8/frx9/5a0xn96+f1vSVfk/3YhjTeZ5KZdCPMN -rYxdXiLSPcRdnOYb4reZH3PP//7rH+i/7/lj0Cf/1z/yn/8nffxvdPAP9Oa7n+3+/b/2u6/kkl8B -grSbWJ+7b+no1Z12/1QftQf6p7qDzYPN9W/p9+/27z+eXr/6+Prd25fv/7j7CaOgf3d49+7N7kf7 -n+pIfHn+6vXHd++/PLx89R80Wl/++vWb2y9/dfvq4493f0sX/O/062/7C1kaC4mokXGgOGetwRUh -6SJ+B2r9ZDkTJdXjv77ECw3I7DjmXZinG8Tx8LtHpIuk95kH6jYvOxKObmgN0ZEp0Te0AnGEhPF5 -F8Z0g/se+bpJrqOzZtp+u5zwDFOQnjJ6SvkGKebpyHxDVECvm2+mYZjoYL5J8xy5e5LJ6LGmhT70 -0+43fF68we6l8xLAMVoP68f/Nzrtdzg1BPlioL4H3JlfLAwAtGkrxOFmmOnBcphuJhqmHd3+hsRP -OjKkm0iSKh1JDGzJIw7hZuyXRFeGG7qIdsKAvqaB7wCTEh0Z5fGGlFCgLuuVmYZ+xHm0Iujt6Dwa -JqI+3FfoMU54HOqM7jneDJPdE9OAzV2dF256pBcdInWaME5IZgqkLQHA6Ue9cLCXp6cee3q0uCAz -6cyvGWVq5pslI/co3gylSl/hyjHoAIVE9GOh4RjpiUiC3g19viG6S/ccaUQxZGFJ9L701HxPWgcT -Luxp4ONMM51pWHqUw+snGTqsBqKqdIQmZyJxW25Jm6ZPNEFDGJG+k8anX1DFLvAM0AKgab6azWM1 -z8QPmFL0adRp7qmnaaZVkUEpB5CtJOsv0FPRqCQ+IitnpBVATEUepo/0DBnLkzYNcbFdWmicZux4 -rM0hzXSEJoHWDB2hoVn0YXI/0N4fsNTp5cKI82iOUKYwjAu9xjzRkYWmD+t8RGVBmhdcmZaBFkig -/sZMz0aTT1zqZoz9wn3R+fQU9CFOkxzhrSVX0teZpo56p7mhtUjc7YZY0sJ90SrFU/QyQBkTr2sy -0XYED+ajaQx0A9pnAzFuOhJuBgCCCU+Ne4dMs6A7nLjyzZyGmUc2YVhwYb/gSMKy6PEMtLwB9voR -uSWGheYyYHNggSR66IV3MxbnNKCvUZ414vH8WUciQpGeI9HupwegqcMWBDVKk3y4WgW48PBbpqzX -BqGYf7x7QYQp0SQDaw0wd5FQwjSSMwgJ/3gRdqRP96DjuEfElkkkVtDXh6+p7xdDpPknIvKCXjiS -bEI7gqaBzn2RmaoM9A3xAFrMRKqmJSE9Lc3Wv2KNvogkBS0obUn3GXAWze91B1Dr881Mwgbto7TQ -bGHPHrBW6TK6PW2/FzQ/84TFvXH7kQgEiQq4Pb0yyVa7wyfc/gVwi5txipE6IJozL73e/XfMeg6H -bYb2d/Xfu7/7+buPxLvevf+KpuFv3UR55/j+3a9uX7752cuP71//dzp596Pj/qcmZPz6d+/efytf -KSckpvnVu9/efrn/6fIlPcgXH/9InLLcvuGXkAz+9StbG8NnrA37lxeCjsO4+9GPd//6L/Tpv3zP -L3ni73AGSWmoeknUASu1H0TGoLEmAop1SyQyEwPfkeRL9J+EhQUCHf0/0ibB5O4m5ZtY2SPx1wH0 -k3Z77OmZRlAOkGS6bJhS2o20Z/NMm5fklXEehC34WbRlx3FZ6DLakyQ3wt47EyPn2zM3RCblWdkQ -LouR+qKlxKVPR2IARLQGWHV4QxKLoS1KnI14OhGyRR+STkdtV1AsGBvw1MtAtGYaaHzpqmmihT3w -3em8xASYHpYXJYkJREyn3TgvN3EEZQs0VDQORKP4aqIZkbRDudUchbEQdyABnk4ipgX1Ykckg5QQ -enSQfTBYWvNcXZbvlbDcSWamt174JJJpaDNg0EhVoSemFc/jQ8z3BgCS3I2ejTtfMj8qdSG8lZ6C -yBlNYkoiB9A4zbSQ9CoiY7HnmZ1GfDmAlNKmI2If00j9DJDdsBgWYT18GW3JDAlqmewy2qopTxjr -HkRzhJ2PSC1uHxeVAEbinAl8DMQTD05M/YbuTXLBhDelVci3IM2A+EXSRwRLnEJATzoxo3BuGk6i -L7TOwJCXHP0ADyPJLHK6nzXLq2KdRPgt2IGpFw4vl9lBkdxodkgAo8v9b7pXxHstN9M0Rx8OvPyk -wstIy4BmnxYZRKUxlgMjWO0sXN0PguKR8LvDI/dwx6BtsswkDeDmkdRRrMWe9EIdxCgjm0nSW7Bi -I2RWSLHDTcLmTOD5xMpG+junoBMdb/CZVhyxI5xEyxviQoachReLWeaGlh9ixnw0UlzAdkmcxpDR -DkyQU2j39MRa/k2pCNh96kUs5/vTt8RyaaiwhGgF91iKxBgXZMnjrmleiIIEHKTZXXgjkm5I/HaA -ZZk34oDtSwIqqbaj0xjSf7GlWKCgPSiiJB0YA8+2UhEcIKVdr6KtRI8pxmesBZoYFpdJCp7Bh0DQ -JqiuNOJDb6SC+BooAw14yjinl7GjtT5DocJ7kU5OkvAs4odsX9gz6XFpVGSTk9hAev+0i5geEsWw -V5hORsxFFnkCk4jIWdQBoOfI3DldzsNKYzYxfSMqQL3wKAkNxJCTYJFgb8eCYRGfiBbGp8e9cPOY -pZfF1jjtM6AcoEOJqQ8tQNrfWL8REzf2vZxAJMeHPi+Q3UAoSV7u8UABGRggjBObGECnQxQxnZ4i -LzYeGFF67F7GENdkeOAAL6EbRRUD6T10KxFtBPWGcoFxyMuIKxIneVj4WfsM0qGTcQ== - - - VP2X2Am/5YK7ULessGFNg2H4AWg9UHb4yeyg3D0TxVtAw4POl2vVvd4kCH/x74kp8O7W6+3P+h5+ -0J4kQ6weiYrro2ZoiKDG7fvoH/7WtMyJMAxlVKC5DqxL2MiRNqfLUwc3Q1TGAR17dMJ8o54evqqn -RWiTiNccoPXaNBNpkSduFgM9dgDtsiVDYoi+l66pTFMy4mbVwsvY2vQIvjxZC12IjtgChtrFe6Je -5tA4mb7bVsggJPDgss1COhSRmn5pthQ/AobWNh60/AxablsTHfcgJ/UGzsReoFDZLifxRhQHJQMZ -iiN0mIpUZGJYMxQhoyc59MJkjOKg1wk+KjVdyrTYp5iSUy8gAT1xcqdvUDqJzk4NEcRZA0qtK6XM -4MbQv42U4r1YDKkJbgYHCPQERpazCRZGuKEssyBv1F1GcRKmpiwgk1yxTJCFlE/8nvtOog0YN8FM -zxnLSvmN3H0aG6aEeZxp9RnnyvTlyJq1sjYs4X5k0lIYYB6hsNPjGZvMEGJAv4yR+oGa3fpBY8rA -EGDfcraNDcLiWM3b8QgR9FwFAIzHuPFnJUPYMRM0sDppfwwuiuA9B1K5G4EFN5rmXJ1Fe7wH0mOC -D5SeHrulkY+Ii5NcOBYxCmgJz5BJWRmzgmGtZTE8FIvuJrFhOGZ6TJfpGGNhGlOLfnj2DGXWBESA -OHhbkyAzFEpmfJWcmaGlYUWaNIqz+kBjreIqlhXzjkaozSpamOgLAI4UiWCycR6wlmnj1gJ0JgLI -XZuYDfiEX9UEcSAUgOEbcR1qbD8AO1CZHlgMUxeT+kEJMorB1KoBlHyGm1R/wJ5mIMEUDKFx09So -IQD4lh76qyorQPIYyzR1BhRkXGJqlB6ggDxKphrhsrDQHjDlCVgcix+1iuVnmSIGCkLkZXFVDa8G -Ylbrc3gzECVT+hggxHi6XsgKOpaM649ytwkQL9BoIgJAXvn+4BYLBFxgjbSASWdPmP2E+j+veAJo -vQGLgS5FXTNkM5EcxGtijgGyIhGMEdASLUlSew3EWYQQhH4U0E5oNl2JtbwwgewjWDc0DLguGxmd -oUFiOy0zKALJPXTVLG8Iivp77p728ZwS35TZV2LRFIoKwEEMSQJ1BNApYPuosJSdRitypqksPQ3g -Akzf7Y5EshkBfNU8WQyCApY3IB0h93TBvzHb0dcEVrWQPrErQ4Er+7EeMtyTZkuHzIY2AlocgCjq -6IcE4sujoZOEIxEr4JWtpZlxNNwBZNPnm1bnTOpatS7oaZGF0teFrB/wc357XWEAyJY0JF+GgYjM -BAJp88SLNWRwYBYbdD0DakvEu8qyB47XA7Zs1j3w1R70w/ZPAAGJ2OS2zQCksthkF7plgZ4073zD -AnqJfZrKvg7ECfvkDJ03f8joPwkGyOQB0OfUL4WIBEhexCMV4VVaE4iK80Q7OQqYGGxap1phIaFr -zDo6RtxwdAa87/QvgGyCghiVBCA7OMJttBRvBIzciC0QzRn3KTSZpBi6u97QKDeWBXMCJ+7AZFnR -cx4QiAmwZUaYwOJ46DIvcee8hEFU4kIVz4HmaSC2cSZe5nyhMS+M/Tgzj1Ueh2dgpeyohgNmhQCG -F7yBc8sAvATk2Zkqq0qLPqrxXqw5FjWdPQew+gWqtHFxP/JK+Soz+3KeigMA3OMQYnUA1qo+NEID -QPgI7d0Fj3LE5BMse973NjosxmCRszbokk6AhJewVv3ICHgjplYiwrrgReqiFVvGJkhmJoFhubKG -bEKAjPYMWg0CZaIcjoilxSQ+7DVW5I+qArBgCBPa2IOFmewIwwXTfRcxYY0jsXzyAWIxLSxsfiji -Km65pCRmNpdqsW9YwzK5NxBt5OXs4jGOwONJia5K0WEB0rpzKZuO0czMlTAOa1LvNjiT2Wm9C8Ri -Uj0OsPXLhX8c6SeTY01HIFWInhT4pqsR0NBhtnFlYxh6Nq0dnexDIwlJGIzrLPSE86ivKJoNzg0m -GbkCRN0yZTAVCWY0VkNdk8Kjjr2pUqZwDT3UPOixppMRyxBDnatubJKLs0lwquHhvNBjp5oSKOMF -acB0Re4rm9HPNEoY9GIEVzelM9BDsvBtqilTfRBLU9RZg4UNje1ZruSy/QxmJNeFQSOJAAYXwVWF -Vs5dlGpw+pnvqZo3LJQMKokQrgr6AMMBVoPr8Hgp7DjX9MM0VSNreACWq2hhjiP02O7YW34ES7I3 -QuNH7UEcoRho0Bk7cghjiMTvswmfhnT4eQUfsb7Kkfqe5ag/m0Ev5fkNoVm9J8M4NhgO8/h4GRTU -DKvBRTb4Cib59Dji1EyjA1Nlug278iXhENdq6SgSZivMoTJfhI6oNYvVgTdf1I7N+cJXAK/ZHo7y -+TZyINC3muOFzZZ0WNG3bkEedXcXfLImAgXGNGJRgE6jJw6H1mSngKZGnRxXdQrm8GtD6RyldYpo -OK7RTAd7G9LqkLCTYEeNlUg7tNyQckegneQbRm1MwYFs5x3HGu42FuOguHOi33P3ip07xzJ03Zma -Y/AN83Ok3pmkg/nOSB3zbxiumwacMbv1wJl3OVIz+XLUhAGzVri84DaNRq5wy4fLH24b2TpSiTLV -UZV43BjjQpHbbBrhyU075Tyz/rggVqxEtcAmliQT6tzQ5HKf26Ma+dDNVi5HumXLZU03gFUSqVvJ -XHB1Q5oLt25va2RgN8u5rOyGO5eni4GvlrvdDujyuVsKTYJ3e2Ij6LvZ0fQBN0y6ymDmy0azcCOn -ayBmBnUlxY2ljTLjJlXTedzo6mqR22Yr5ckNuK5iuY3X1TA3BTfqmluMXa1zo7Jpfm56bhTEcpop -km7EdmXTbd2NUuomcdddi9nc1NtiXq/V4BGsBWPs6jI/BXMdU6lBamUoatV7hEgx5bmo6MQxbmi3 -LUWNn6BestpYq/t08g0iEwoswDRflHGFDkZijbwxHGKQ94zIwD0WKGKclX85ZME0cZmExTi0MbG0 -DMlP0Y+J7sQySI2RlLMMSik9GdxS7miwzKvmyQy+KW9gMA+bJ+1NBQsqQ2FgURmyGlQqQ2vgkw+/ -4VM+SzWK5ZNpWFeZb4PDrt0ucEO4rvyl+qyYT+7hm27bgQXR7sVt92FXlb72UhnhNwgFS9cNO6nQ -XAvjJzY1gnmNCO/naSRWEJgJE1GamfDZEZ4gon4LsrGX8+AaBiZAXTHOMAI8HWKzUMtJJGgsMOV7 -P3C5hOJT7mdHZJ3ac/l5/uxgLXBnvHrF5wVxtSC+7tQwOrPKvAhR/rY9mkQVL3oP8NWl/A0xm89g -DerqrFHMKlU/fkTv98p0GT4KTZ/9h6mnhYV2WlPsgFz6HuANbwgYMc7EenQ5z5699LV+x1fPi2Fz -MQypIQkAcOI8TmVbZRrlOEI5ta0HCCczeFlvUXjJIv9JOQr/2gRTkW93+MNOCFxoEP5ympKO0pVR -l3LLmgqVR/Pz/PGNotUv+EwQ7iAIthVnUZa/rQ9G2MDmihzEUWQdPwC/A5zRkINylu7E0o9tTb3b -q4b6gB/Aeb9sco1KcOozkZ5lFtFCMuwsexXvZ/Vuz3RgtQagTw9sxaolg2FWBVoPsnoLl4sArQ3+ -5cCaIqtSNPgskjGH72EvD3KeKI0wR4kBLytKFXqJEWjFa7pS7V3mdwUO0kPThxsJCD0AJ1HWSLzm -55Mr7UkQHAFNFEdmVs6gBQK8EhU4i3YAC7tcGNQ6AJcXwfpIvI5wR0JYDJuQF8Pk4TYUzLRBNEtw -boAWEEOxFDMjdmkUolRW7AgcY1mcezFaXM4joYhVI+9K42faO86jwj/+ZEA7GFvwp4fBRZ6qeksc -zWznscHIJHX1fAMdL3TFHgXNuOJCdnXw8c+zBf3YHLEPOUPd9VxmqHuA63zOM0AdhkdsXfhD1MuH -rTPsQmjLDLEmYjvRlZjZ9aYvGpDYNdZneUcBbg+5emlIDXlRu4M/FiQH9vPwR4fFdIbV0l+xVm3K -UIA1JXU1keFCkEtgP1EbVhgw4dkiV+qDkMqV+lmmTSaoh8EMbMwmcsmC2cp1Ot+A+7DSypKgdSwG -Vls5tJf4teQ6WV8L4keYIusKhCm0ibxDbNeUnLLyTcpJuuJLN7Yxmpv5BrJn8k1mj21bsX63smFt -CMqmtmEqm78ezUIkbNQLIbGZKQSnUU6dMNlMF+Jlq6E8Rb1qCjG0xXVNL9dU9dUmAd+KYbkm4A9F -qwC4SLSR5SL748WEOJ8ZcVrTkhDokyVA5K8gvuPJueB/+efn0SijkTnuEYGSFnvIIiHcbUcihAhl -jOxyOEKog7kS5HqGuD0SG+inxDap3oJAp4FjkIpJDR42HP5IR8Rt0Cx9Q6QtujiRhRQJy23UKEEQ -7Ija8AOWOYt8DDZygCWwdqWVExQCPAadNsCODLo+W5whx6Yx+eRbJtr/owazZQ5dQ2qshCvhwjNZ -ZCaCHLHJ8zgRpw6RY02Ly9aY9Eo4hSDSkwMn2QIEd48My7B5YQzE/dM4FU+AYE8yR3HNYLgLJkcE -HMIHY5BY1kWAPb4OcD5Cafk09vmkh5bITHj1stn8aiqP3SMC6AbGAPvdwokfFyLrEctcVzutcwSw -wgvrmhpx3rG+D7sXcJnpScJ5kbPYN1/od4g+S3AiQbjjRANyMwMj5+g5nELjhUi1PMD9Ea9zTw+g -taKsUg8cv1YeIMArYnn4/gExvYtQx0+9P/eAmGYSV54gfu6hQf5PEEC3tTYepW3S0AWo9bBHp5FT -HS5xXhbJVregruPEueloZJF9kMPYYapieblWT/pJdiyQBQ5NgNSwCDzYT6z3TRIFjqXRLyaXsvF3 -EaFz6eGgsqh/MQROkhMSqwDsFgrZFX3KhZOcz+IrvMGhWIruMCSLfGDjHR4hJ4snwFlipzBsAzbw -vhdnxXHhG5qeECVG1tQEdvmvTuvtHb2rIML16pa9qgX6YLC6CsRqj06EL8/8qNUbApodVUCXgYDr -Mztr+WAR2Q4yfPWgwv+VTVw++Ig4ZucdmR6QaAnaroCY6qRZwP7SUcjisFNuGNRPwLiCPBgLZYh4 -94eHZIbNW96RhLs0WoiBjwUEN3a89tGi85i5lVENatfyUeXRh8F/YVjTZqgHHBorhASybz/n4igi -E17Os0VR+rLF097Tllh5NluG5flttTbv6avax8NXvo+Z75B6bMtOsjmo9prO03pHGqx2hwi1kQHj -px++/NXtd7cvP95+BbrU0KHF6QUibYcBpkc2OVZZAMB3mcN6FoAh5pIAACkURAOoEwDgqLihWAIA -sET+2hMA0K8a+usEAGDoYpOwBABIA0FXDyUBAPeV2PBaJQAY0iS4licAGFIWd12L/x+QTAG6RB3+ -j+fIbCmx8P8hZ/GOsOj/AUZfHpk6/H+AgZtdMy38f8DzzBxhpeH/wwQ0fWqi/0lXUYOhRf/junFE -1gOL9R+yWYjr6H88f8++PBb9jyvV9K7R/wMc8vlZ6/D/Ieuy9vB/zgHB2qOF/1/P/w== - - - o8SXu2O8HynEcPz/qOH/tAxeSPx81vh5CBXTDKWvD1Hj/2OvEkxGMkEmrC9IKItqldroYYTplDYX -TSuNIrzfIJeyDCEJAIImAEh33B+4EnFKSQAAanf4pPvTJPUShKAZAIbxfw0J5vNSAPzQhDzwDBOy -VCXkgVuZhClYRp4BTnyCdGtGHvYMYyJSEvLAx0uy9FhGHriBMXHyjDzwt2NXiDojDzuHcaSBZeTB -DVlFaDLyQIEbeBdbRp6r57eMPNik/EWTkQf6GCscnpFngIAAoMsz8sCEx8mAmow8A3vBplwy8gyM -+oPMWkYemPr48ZqMPJzbBoqQZ+RhPztAH55pB5596uFZZeThecAwVufN4oJsGXkwIuCljSaF3vTd -NSEPDKHMSywfD9zsWIto8vGAssvwWD6eAGeMIVb5eAKpnoIM1vl44KDD9Njy8YB1JHgOej4erIUE -ct7k48H94Urk6XigTdJOmEo6nuupNO6Ou8lU1ZgAnJ2IeBRIAE5MPXxxHRLAS6ivZIEEwJnZ+80x -ATgx0V4aCybA/mSSmKnCBGiXEXfBajJMgDkuKItjAmCSCZ5vNSbAu0cgBsUEcCGDeo4JwGGJBc4G -EwjG0BwTEO9ArA3DBAIA5yGNLSbAPld8pWECPHnAjR0TwJWCTNSYgDj+6pMAE4DUwg73hglgAWXZ -TRUmgF09s75vmAA8z9jF3zGB66n8wZjAI9PpXGMColCHRqFeYM8Fgr6CBPgc5ILdvSBiOmVxZryz -BwnahSljBO4tGXWuQIGHHqDGBD7xAaSHp8ME/nOgjH8OSOAqm06Y3fRfZdNhIh5zFTU5sLkJHmIa -WMmsAvJhHX2J7Q+GaRGaYCx5HLLHcIJgsgt9HenpZ1k8KLiUeFxrxCh2XI8z6rhSXKZZQMRAB3LL -FM3iU5nSQ9Oqo1hBOcFVLNQVb8aMTWNhmR+JV20JmGWWhD8srBZUgmPbLfAWGkQCxFqH5zIVgnpp -QbwDCYWstViYL0aahP7QBANDQOhRQEAjhkkLlEgACymGizgPUBN4zLwhiWsfPF7RDW9qi1+GI5H4 -0pYgZ3BPsXJpKDQIIa62UGnhkjm1EdVORC3wGuSSZR8LzQ78IQ1NADc6ZyjBwrxB2JklWCA4cw1w -/yZeHNoVeypbWDn0wGXkkAUJPEfPHFpQh6fjoWQu7SwY+mZOpqIKux2oY+HLQZG74NjNgTL2NwIy -wO7qmHseESxWi8yH5zIWlIXu2991fL8dsyQALBHyAU0TgFszVa2TCeBdeWgt5QBc/xeQJs1JwM7P -cY5N4gIOIxBYyYJ8koYJagIETBnPTp1Oh3VweD1rLgUwVMZX6nQ6GDEOTbC0DPwEiVPYSeIGOIZJ -9qkqvQP8zFgysCQQICAJmL5miRiAxmEH16kkmMogQtYSTmBLs8XAUlJAFmJUpU5cASISsXYsvQX4 -M0ytlv8CkoQEeZUkGXgAFSg4kQZ6zjB/WqYNvBcn3KzTcWBkQ5VOhwM2YCu1rB5YmCCUdeoPTCLH -FFqCEHTNvMdSiAyWObNONCJEboyejoSjcuDyawlLWESBSlqnNWFcAwtLc59AzOP4EEuOwmgF27Gr -FCpAItgSY4lWAixSCX4DmooFQRUs2tUJW3gV84NLVhe+ioUtTfuC/a2hq5YahiVWoHGWPsbFU0sw -E7IlJ6rS0ARGgHK2XDUSQUITZcls/ECd8sYPyv0ZcBGPGSEqkoJ2HDWtDqAX5jT+PQAANj/L9fZn -cw876E9CzISkm8kfFZuHM2w076MLzt8ayxUWOR8XOKgIMOVjh8ghXqU2vkCsccAmAM79YCDNLNFV -HMXlc4lvafP5ZION4YHbJSHRVr5uEDHPb6ULC3FvnIeuWn1wPodPgi1RppklJRTUsJ44Tb3O5yhD -bZsBoC8HzNl2YUR2kbRaZVPh7hhT23nIQcoxj7o1mTsNY5sOa1GbquXMIt4UnQQwJR4bKgHyDcbg -mbkiIAsnNBOXnVpl95LB8BRgWVwsPEcYCDfdpMkjRloqJBeljZNGaxntZC7f5itbOPDMaXAwOcKp -NDxAOfy+Sp4DSZAVfCP48B8BWF4nz1lmiWo11mFuf8Zb+M5Ty39owgKn+1AmRVoxRxQ4FxsGCYBq -eB1cQ/qSXi5AXuEccMYy7UDDV+2gM19wGnAx5840Vyx4NTwcrrQlTw5GgqnS1d+1sGAHXaSA9SDG -SuhAmNeI8LdaNBmiYC5+FkD0WZIPioijsVutIDQkgQlcXoIChNlxiarPMqyN3IW9AZXVpTOMB7Ra -l99gOmIq0oh5Icv4mTQIeQUva9IiRE3gEbVISSM6pJLDcdFgOJVKIbKK7l/LriY/mIQL9wSAQioB -zxokXEvJJJdyv54xZ5RX9IQ5SfZFLZDnXtyVPV3OJIY8z5EJXHloZf8xSSiN5docBVMy/YEXVJso -h8gXv5jpIVNi+40n/UwSa1wrM9Mkg2IaD+LIM3sSiUpELDysEuTYKSVRKbGVpShf/A5zatOd0pOQ -jOFaHL0ET5/reSARAmrWYT6AQtjTzqKBcO8US2ocZDzup1VmHOnRE+OEG3Ev08ikqNaRNisOgvzY -dqdxTkx6s4ZBwa2Fcdk6I844mhHQEuIA3oOTepMPJ5i/m8ZnsWAJomMhXH1jX/OvLQuOXe9JcOwu -dQ4cfxaLO/MnrjPg6DuV/Df20hbn5kPTZL/R8Su5b3TGPPWNTkITfqcT5UF6PpMWx1cmvI7383Xh -YYG6cErkoK6uNsBQl6DHIdoa9VBFXcdNQKOfpGGPthdKYKRumCZ+UjeVR1napvNATN2YTbCmbN0S -0alb22M+dfu3aW6URngAqRGRkuRGKU2b40bJkcerGr2yiFajaU3cq9E9i441uujxs4V81nG2RmM9 -HFdpsAfsGp1u4nqNmHv4r5N7DxEubKGKJHbm4fHGzl88JtnZUBO77NzKY5ydoVkYtLO9OlLamaNH -VDv/9KhrZ7NNdLZz4+o8Zdge6V0dqSLCq6MaOW7iQXVAhYgmCN1lDQ9Wd3HE49nLkTruvRy1+HgT -fzyE3oWkJtTeZSkPyXdpy8P2VSRrgvtNbPMcACbXeZYAl/6avDYuI1rSAZckm7w2LnBaCgMVSD3F -gcqsTSIEk2s9W4JIvSWdgorGbdYFlZ9LbgZ1FrHkDSqDtykeREwvqWxMjPdcESrqNxklVBnwtBOm -LFhmCtco6gQWrnZomgvXSzwThqsvTcYM13I8s4bpQZ58w5SlJkeHKlSex8MULk/14WpZkxLElTdP -HWLqnSUXcR2wyUFimqKnKnFd0tOZuMrZpD1xxdTToxTd1RKouILbpFlxPdizsbiqbAlbXKFusrq4 -2u3ZX4q6bhliypE6k0w5ag/iQEBlVlOkoMle44BCOc9hCO/LjzT39KP+bIZwlOc3IGT1nuaCZMNh -iEoZMYNd2pF1dMZnwPCbMksG87SzaWhQmXUDjMrKMFypXUAGP/k6M3yqrESDsdoVa2iXr2wHxHzx -O27WbBGH13wnOQLnm81wunZPGppX9q7hfbq7CyhY04CCHRqpKOiiURPHIGuS40Cl0SWHMp10OeLZ -EDgHRp0MOnZqhNIR1oacOg7rVNehWiXLjuc2tNthX6fwjgwbC3D8uMli4yizMxTDopssNg5ZF/5k -qLazMAe/G07nELnzQ0fRC880sL3lrYbJFx6sqH3h0nagYeZ20Hm+WQiKXGCGhFZ+MHNDkTPMILF1 -pBZZylETbcwCUsQfM5S0YpLZU8p5ZnIpIpebZhrRzCw4LsCZjafIeGYKamVBsxgVmdGMSkWudONT -LX6qhcqFVLdhFUFWLV2NuOv2MJeK3WLmcnOxrNXitRvgXAx3E50J6m7Ia8R5t/eZ0O8WQVcL3HDY -aA9uX3Qlw02Qroe4pbJRV9yeaUqNGTxd63GzaKUaue3U1Sc3r7qG5VbYRg9zY62ra27PNYXOrb6N -2ldOM+XQzMeuPrqRudEy3RbtymixV5u+WuzatVoLzjrBacTVX34I5jumITOx5ZGo9eiBg0XzUNTt -geEbkBnTyAeANxLLX+nt8Budplhlt2WqL9q1IgAgrlK1rU5jg4lj8MDQBHabWaq8uyCJeA4pN2XQ -xMAScsngO4BFc0ayCuDwkwz/KP0YROK3qzPY+EMZ3FKevU5g4+8oyE0ZBAN2ymDV+E8ZVIOJfOAN -R/L5qcEmn0ZDpMpMG2R17emAGz6Hpt+RwAbbukl8JC6GvUUMaLoHOLJpAghNCYGZH5jk1akjIA1H -TqHq56Wg2QctDcXA1HuVP6I6TZNalK4s8UW5ZZ0gozyan+ePb8k2rt7yeU1spqwImiuwSWFTDmrm -maL7SG4a/7vJYHN9liYfrvrxI1U2mXLUss6AcUSW1y0zTem7zmADyWSadbr1PH300tXqBV89L4St -hQBLGAt7FVVAWlpJZmCZoWbNHOzJowDphNDuz4ytN1WJpxDusGBBePqaUVKPNGFMfpJFZVk3nhnL -btYm0NJHsoP+0EbJ1m/2TAnuoAS2C6vkNX7Qks74PrS0NH6gTl6zcZbswtKN7soqmYwf04wz1eYW -Ub9QnSpzTUUq7Cx7D+9n9WLPFGC1AKBCj1UyqW9VbxeVWQ8GjtWeQskoAv13ZOWpzjwCTTawvmQJ -SqB5qM1Nk5hA4QzDKi0kACI1WJl/EwKyPOiTyTsreMvc5lcpD2J5WHClpNK0VC2i9QLmqFO6oL+x -zlsDDYUDMj07DKK9BG+vs8jgqIDZlmyGdRZbppJMy9drnbcGRyVRfzkvKAJT+gpS/bi9JzxkGPTx -Z0N6ZgYU/PlhT5Hnql8zWlCaDwecquUONmSIDeC05M3Q4oJcTQCKKbMm75M0QJmwXJs+mfTgHEVZ -5pxuJPiDrwt/hmb9cC3ouV5nnKYkF3YB7FwQ9SonyPVZ3pElFyk3rJOQlAezZCXl4S2hSXnJWqkp -g2EJUmy4LIVKGdQ61Up5DkvJUibI0rb4RNbZXXy+LQVMWRKWJaYsnTqbjC8xSzrjq9DS0vhirXLX -+JL2k2zVeze+OZq7+Sbyp9J95s9tu7F5O9+0Pgi+sX2gnAA0A+qEwgfeiYlPjdGcZgadNPlMO/ny -1eAP0awaJ4e+uq4o5oqsvtok4H/ezDW0q+g8Lsn0nLnmOXPNJwiFPyjHBDbZNE2xFSXGXnO8SRYD -4JVS8cTyHEj0OXOVKh9C4LBmzmGmeRMCKwmhpFbgMDM2ElUZGDg+mz3vLFEDA9ElxQRsV0rWq3wP -OEkxRNVAIFtqqjjNHVFYepViAoIqRw1Wp0V7Q+8qiWfh6pZR+bc9WNB6NP7kQBK5CED9goBOuEiN -jwPHn6VcDVVCGoN5Xg0pbJeD+g3L0APyZ4O6Tg7iFjmMtlKXqpM0dUHpyNIblBvWaRDKg1m6hPLw -llKhvGOdeqGMhaVoKKMVPN7dRrVO91BG39JClBmy1BFlIusUE2XCy3m2KEpftnjaew== - - - 6grzR/NF6I/va7V5TVvSPhq+6n3EfHc0I+u7yGeg7DObpfV2NN33z5FhoiRIyqOo8m2uK0mWsFS5 -rjgRPyqoeq4ruC9aVgNPdjXRCEXU2fJkVzBlccUgT3bF8oRnYLFkVyO89Icq19UYta6s57pCZrdy -R0t2BQcSycpoya5Q74nTsHqyq5yqupye7Cr7lZbsCuIDp4HxZFcjDN+jlROzZFcjR8GHKtkV4iI5 -ZaAnu0JOCYteLsmuYHjnpL2e7QrSjxTMsnRX8JsZ7Gk93RXOy/Ncpbti1xnM0tVsHrvPS3fFtcfg -XYA0BpGYxqDi0irb1VWyq81MUdOk1fTqXFdI+AWwkW2wd18/7l6kKSGiGebSNtXVOtPVHTen5ZZo -5R4+/eZ8PeosTcOjQlp/9AL+cCTPEgP/uy9IWHj79e5Hh8P+1avvv/3Vu48vca7syselk7hrGv6C -g19/aB4JlANj/S1CSfCEzUi1gTzLTJERCgF1G2JdHCytKPLYQB2O0J6D1V6Lk1w4aCXkpJlNuSck -fkDJM6n92s8l1Qy2GhvaInQaLhEGBREbNBLFp/NnTiSRIdbDNSfCj4i37dULWCYJVHqMQrhm8SyX -pOSDOG8MbPHnEteTmK2ZEHK5TFhXeOMjj0qwFEruaQOHvID6c0B3AyfUQagLQhthj8bzIR/NOFid -TWjh48D0jeTHUSq3QkNFV5ykL2olQWYRo90R08DU3k8zigpLNmeihf6NaWhoVOZELXh1pAoChUap -Ry5zg5fMOJA0WAQh6AP4BuPWUAZ5dOZBq+WiAGSEHRpV3FB+D6yKCWrswcTz7EScfQXgVNMjWSBy -ETE9RdmaAam8kN8AjuYDPKVhzeVbEl8Wqymy4AYkU4QGyyZyANkoxno9lcbFr4K+vZYH7eubaZ6D -JHmzWjARISnAhLxeTCSxhUFSrysTY5L8SuwSafVnIkQvrnlmNWpwJXqySjaR5pwru/J1flachb16 -UZyIYqZ9KLVzsOjDpNKQ19jBHoKfplXhiREOXmEutXoiajwulkjWavpEJINATh4v+xPN6d7LA0UY -trPWeNIiQrFHFTqUTrI6Q7HXxWjliIZFa6XyDa1qEY4GuM1bXaPYI/MFXtnqH0U4XMAblm9oZZLw -RmHmIdRSShGwCWJ7reJShCk/2ZhaZaYIXxuu3GLVm2KCK3Q/lSpPEdmeQKnkSq0GxbPN9easYhRu -mbl2ktaVihhwuGzwhVZ+KubRLtQSVRj9AavTS1nhIfJk1Zqs5FWETyZeQUpixRy0jJzVzYoQhnwa -rb4W1lsPdwmvwRUHdSrzWl1+hIfVanpV52ndrwjnE0R0lCNwJoH/ilzpR6ESaNEz9jqqjmhNsgjC -McbFh4fHAuAr7dBS8yxySGQ/V0cYLrXCYn40zEJEvKIas4pxKoXXIrvkLxpcaAXaIjsMoj6RFXGL -7PyA1ApW7A07bYwa+2c14cBQItcPsrJxMfQW26jl5cCcMsnBPjxchS6iHCi8ZrxaHa5clqyFjbSm -3bBozWcvexeR2Ifrrll5PBzh4eEbWBG92CPj2iRVr9itJcJHmsvLeUE+4IUhBKdOXFUUbrByoVb2 -G9g5BfWVrAIgjsyW8qlUCkTZg5lD/KyaIFwGR1Td8aKDE21sGC3lSitOOMO2iVhAL2A4w+sQ9di9 -0CG84yBzy763eoicihH0wWsmIlEiKrl6bUV+o2iv6TUYOTRQygwy2I+xZu5lxRzBpYwNeslHnDVD -3fCykNw9R3Fa+UjuabJHtTKT4HHsUOOVKIk26/61ipWg+z4jXtkywkuc685Z9cuIqrOIpfEqmSCU -U7mn1NKEdykPq9banJQbekFOzoDpRQGtbufSS/lkqes5sFNvyqX6Z0Q5k+hPqVVCQeJZwiuVRBeS -izhgyI/MqSpf6EftMUr1UtdSvdYOUtLNVjvAq6EWbdYqpnpffqS+Zzlqz+ZVWv35vZrr6j1tafp4 -eGFYGzCvH1uPa6kza8NfatHaBHnN2mYavbatT7fXv/Ul4XVy26Vj5XR9iXnJ3bIMrTRvs1y9hK8v -ay/z60u/lAOut4iXDfaNVEoL617zCsTNliyVim3rlmLGtr1L0eOaDHhtZKMWpXyyUZRSZbmmPKUa -s1GoUrHZqJhXdm6onVeAdqroRaKdcpZi0jWB9aLTToi9LLUTay9f3RB1L3PtxN8rYTuD8IrZzkdk -aLWwtvMbL8DtfOn3fAct1O38y4t5O4/zot8NK/Ti4M4yvYC4s1UvNN6wXy9Iblzaa5Y7Iy9HaoZf -jppg4HXSXXjweuqNjOF1110WsdLsGwcqqcYPmvDjleBdQPKK8Y0g5ZXly3lWfd6FMq9S3whvXsze -hTwveO9SYEZaAR7pWlrEs4labFKlI14uebIqBkrVSKh4B/YjdkEWuh6/uMm6GQZR+ME2MnFGmk5e -tiY747welbFcvsbCE05Vy+FZBR2T1qGrT4l9W1Sgz0CwIiT1WvDPwOG5f1MQMlzNOe+c6hAJdlZ2 -Qq9VjQQfO4TTuEZC6oNkxnCtBVSEL6h1GyiOjBa6CgRSwCESriYJqZymVp0CKrD0kMBM7YL+7xgI -GEIAQhNTq74BO5ARUy0PlzFK54ogtHiWgxqFsZwnaiUoTxgwaaZ44g0HlkAq/RRvGDjxj6qxjC3w -ZJuqm7OmlXSVWO43Se7OOFmqbTwB858MSADgMhyhezwTjNfDqMHvo9ADHBxSQB4kJFoAqUMaAOhQ -CYrmyLmNZsmvwtM4K92LnMEYBJBJP+eNGVUCCItEAOBhUxxGJ8Oc2TRyymJIAHCwxD7h1wSV4VB4 -BNQwqgdxEKbrNGlUV2S+jNQfyP0Bp/44odBPkG1UzmMYApmJvC8UveH6QX7PeeH8L6+aR1t6lRT8 -FVAndRkEkiqvyvFKyPXjwwHMH9utDBtuOWgwQRleyFUgbz4FCZlbmfLbTCV2DJl0+duMJsiqhjzJ -rJOAxbBLWR0cPGSYvK8igAH88rrQYOHlZNO2HBNcV2ZL4G6rNgFniyyJ6LrGheNYLf+EJBlZAwLK -aRCDR1iQbSclUJWh3nEJRq88FFyO92VCsAK4pW3dNPQqb9kWT3jmwWQ8IwUJjpMD9qqRi4QYioUl -EaUqiZ1RNBbJqU+CiSuQPucUitg5dq/TscQh9ou+pJE7HF04XYxSxARSGuaKciYSApni8g2NwuKV -kEV6ZzQYg9hHFBJxUo1EGpMNq5F0rIuJt5yR/RQgpdPqdPaQwB4gAgl7EC7C8z1xORZlNKkfJLFS -YUi0QllwE4ZkjAsLna805obhn7lOmTFBPEQpN2DMMnFMErNZZagJUYuZZQRjvAsca+yexqD5pRiJ -NSaeOGxmngqz9yOvlPGyUFDOU7kBo8HxKuXAKABlLV0kiIOcid/ElHLExJnEae9NojSxBwtdpEAT -jRJHuiP/jx/hbTgvrQiFpcGJ3F0aS5hCBI671Ib1ypvAZAQZbs41D5nbJEAcyVwCziTFhOgwmBaP -qlmwRJmA0KPcjgmdiQQapv4unCaYjoMJUSbEJqSUYCHWhN3EaetpvTFZNKE4QaVn4d8EZ1r9kmPK -BeyEoC5kTxLCq4I4jvKmdmE95aSs1oT6NEKIdRLFon/K6jjt6gGOcBooVyNwhO0McqWqGwl2RyZu -ppIk2DpZFDXVhRa1+BkenfiDEE9RRDfTglBXq0/6kqwspRG7MOnyNKUqwXrJBMIUrzQmteSagsYv -tLiEz1pcgjUTRZFM0cM4k/4+F4UQHc1DNDFEFUecN8xYUqZccvcsFpgSyn1ZZjFXVkHLiLBJKQ1h -Pwhh4tExxZdpP5RjQwZYQcYOY/9JV6JTXMQC48p2yoOkazJRnZXyNI46rqa4J3zgbDam4CeUSUhT -Ma0zDpAQSpiWYkjHS3G9AIcUEqhvKbmoL5l19xo4kcYgkX3lyBTE3tOAGP4YDogkpDXiKfEyqIj8 -RaInuacCLH6egzDeVzlS37Mc9Wcz4Kc8vwFEzVsyiOQj4SiTDZZhUc2QKmDlA6+Alk+Nw17NFDo8 -VqbaIDRfDg61rZaNQnK+vBy28yXo8F6zVB0G9CXtUKEvewUUm83hsKNvIkcmbZ85gFltRoc5fcs6 -FOrb2iHTZvs7tOpkwuFXpyQO0zYUx+Fcp0wO+Tr1cmi4oXIOITs1NJTZCaaD0RVZdcjaia/D2k6g -Hf5uCLnD5E7wDUl3nuCAu/OOYw3MO49xAN950e/5Bgr0O88yW4BxNbcYNMzPLQvOJN364IzUrRQN -w3VjhjNmN3g48y5HaiZfjpowYDYWlxfcFNPIFW6ycfnDzTobR2pRpjqqEo+bklwocpNTIzy5aaqc -Z+YrF8SKmasW2MQW5lKdG8tc8nOjWiMhuvHNJclioDNp0+14tVDq5j4XXt0k6AKumw4bOdhNjC4v -uxnSZepirqxlb7dquozulk8X491C2oj7bkl1tcCNraY5mEm20S/ccqtaiJl2XU1xA3Cjzrih2NUe -Nya7auRG50aFcuO0q1puvjZtzK3cjdLmxnBX7txg7gqgG9YbRbGcp/qkW+hd5XRLfqOausXfNdji -FWBKbvEeqJXh0ZzKXGnmh2DWY4o16K2MRK2AjxAq4PDoivq4zDe04ZaizE9QMRncqpX+EZUWOK2y -gQNM+VklNwBhRNIb8AwHGuQ9o0aeGSAxzpoaxnALJoqo/yU5JRXemFhYhmplEMgUFwkhbqCScp5B -KqUvg13KLQ2eedU8msE45RUM7mFLqr+qwUJlOAw6KsNWQ0xleA2K8ilwuMqnqka1fEYd/CqzbgDZ -tW8JbvkcR3dHmP04arkoXz3sigOJh03gE8gjSAYkFJ5J7GRw40lrpNkBnqAZMVPIIGlnoVAffA+Q -64ZEiRG0F0hovVbLSUj8wPG71g2SnLIbgN/NjshStYfy88qDL7Lgrl/weT1sBtaybjWz4owCdYM4 -ZVVHib6zCmbqD209tS37kZFPYU1q4zTN9Vd68gN6w1em1vDRCYmbtF4mF3tMgJUYBvHOJ9Qzssjs -BZkJoU6X8+zhS1/rl3z1vBo2V8OQWpIAJIezWfjOysilCfHOdx+QnMy2o3qX5l4Tn/jRxOUqgUna -jidSfzNNw2qXVucJ+ShdKX0pd6yoUHkwO6s8u5Kz5vWeCcIdBEF3IpwAoCJ+2xxcBg7ALH4kyygS -T3Vk4VMaelCdplux9GQH9H6vavozzEizP4w73+ak0IkGUPqGJ0LQqFAnG9V5/kLe1+oVn6nBailA -vZaiEY18gOgrdquyo6zvorhAnDnrnMBPkf1FMf6TZcvuYfAMcp4okdBVxKiXBLdCRmj25m1kbbpQ -TWDmFYZ0LVxTAk4qrPnD8sxOq4Awg6Z2Lw8CLxjW1pHcCYpaBGoHOEtU4syqwmLFXEfU22MbMHvs -MPZHsjbqwUXkM5aq0+KjsfRFvkU1XgG9+0G0aaxFDihOPT0YF4Z3FgYv/NEMxdgKjA== - - - Zvp5GIOp7kpLO7Z3RLQP4CB7LjjfsGuoPzmML/JM1RviKKe+9oHImD7uXscKXXGikmZMMyez6scy -9nm2KAKbH1iBOE1aM48IQeKkmDbdiHrikGxfEf4M9cJhQw0QNF9gcF4X5xdbhJkLTfSVKiRWjqvz -vC+o0HOoXnvK0rnc0p6MxQf4hfjDA8EFzfJ3rBWcMhacjzELAC3jBYxwmcdqXDm3qZZJ9OcYtS5K -mSGESLGnms0klCDGcOVCm3FGSyep+i1rYuzF5GorB4EKoSoDzv70OYqLjC/BCEyFXb9sqcKnoc/m -nmRLujpPl33Vl22P5pa+ifTByj6zZ7ft2Lxi2bU2FGVn23AVClCPaqEUNvqFmtgMFarTqKpOnHTC -C/2yNVEeol47hR7aEtugmVe09dUmHX/K0PsxVqH39MeLCag1VyulrZckgeJz7P1z7P0jRgOeGOzD -lhX9YgGxBxoEmT1pRXU4YnCOMzhXcQJKHJF4sGERiEn0BOSxBAgExBqgMOR/TrsB760c1GWjt3CA -4s3QD5KXOCLWGXVU0pLlAjgWsjtQggkN7CtynU6lRYkjHAEKc3LMAZpIUooe1LET9rMI+zGO9LOp -JkFymMJ5TuzZc5LSSdwVCmGkpVePR1hK9DrEbDEVhiUNJWCgw3LeS3gfEs/AfmTkEy5/swXtwZLE -pjEMKwff4br+/2fv3XY+Sa4rvyfgO/SNAY+B5mRGRJ58J5blGdkcWJAF24JhNOhij6btIZugKBJ6 -e+dv7UNEZFazeSiNNHDfdNd/f3mMjNixj2tdxZo9VKpNDZIS2CmxO8Jhr1IwourU4BzOfluFR0KA -+jCYAPRoviGEPle1mkUh0W7LaqlGVb+txxevCfDhR38WV3tMt2GZ3HqcPJZa+J1bVlTttx6Fqp19 -x3jOsTeIYaPSVNdwjwYqzTG4jFf21u1g7IPdW1RTJRT2+fTbHbk2y1B+eY/BadEjI5UVSzvfmtC1 -8Htft95p6r83oPvWTN41GGX/kFvfp2NPHIXThWt+XZ+NTva7hvVfQUPtn0vP/qdAgJDykpkz+TVK -1mEzHZFFFcbPZSFGRc0xasRuTvBCDGBh1FgD2LlaV+3ldFw1eN45RPDbWL5a+nbiYUgYsmtVc307 -pWYOEQaN7g1hbHNxpcKjAMAzHh4dAamenZwUAjAK3XJjRm9Lj5M591sctsQ7xoVWA8N63M9Rrvtz -UX1pcdp49ltpbKeedHxHFNMuEz/GAuWi8u0YLvUjMH7TqFJMoS7HHH3+XFvrX2hTS8j5CB0Nxx3e -hZvXAjqKIPRwT687CF/KHk3M3VTt5eNDoaMu2XzN2yzcg+uvDwc2n6q8c8iOYnU8fWjJ15/H0YdW -X2D3gFj/SPs+x12xmtXkEGUn9snzsJgU/UoxeeY7xiTrTxYTsT99TNjpLXNi52jk3M8R62tkGNm+ -luILDOvNv9JrWUZw7p8DCqSuBhlXFo9u0ZKhsmNQXRZKUuiEMMguKrx3esaoT+Dd0FbJH8eWqGQs -+DKqtuGLGMj5Gq7fbmU9YNdkQU4VZYUw5zejaKmq+glg71ptf3UWpmW4JZAsepBKVQdfbvMmdqHj -qEGV7cjuSdW/c3py3P1OVXg+tgIoxRAwX/Fal0rxhEHoNCtrtddc/cy6eF+mXs+Bzs1VEjzgYXis -KtjQmVTW25PcvtpazRBT/YWBpuMr1cOyPMD2CFjAzvSiWR23W5fcdcIMQJH9otac4UP+QUbIPzMA -yA4hx1Hhz6MeHXUyAICgezYKGFaACb77fPyze54UJ+n4gxFAxrvTY3T7rT/54++u80uhTmP7AQHk -syCAULthcM+j1iEEaPj4qXUu54zsWuf+JG3fz4fWwcMw1p/QOkcNpohQOwQqjLFsVDsqCzjaoHbg -whKFcKqd0ykiZrWj3VDka6F2TqaL38HUDsyTds9R7XDcsu6D2mGXExdIqh0cN1MKo9q5PO7W1Q5k -FUIsS7WjWP5+zmpH4U57Elc7FKqI0Sy0jso+BNg8ap22JBGHaR0KtG6DpnWt8/6af7Lq6Sa6Os7c -RH9oni9fqueTa7eYh/zQPDr59rX5XBetngQVv+t8ime/vF1sGFJIj5j381Q933P3UfP8UXe38/8l -NM/v/wz/NSue21YQMGlRwPqyWA0Je2HuAeuibjUrJwVVRzH7ZvWkYo1WE+4e5KqUszc1PV8G/tUU -OogoiRW13t9yqR5JCQZcVaMGoEqF85nyWvEm0k54qPiLa+1qMFxUQGcREGr8ivrLrX6YurZWDeDh -UAnd6WzQQihqe5y4Gt7RYsXCYKVZd/a9fAm88KA2OCCytSPiJk4yqR5oVQbDjGzYCJQ6U6t6RWc/ -ynN1mB16xgpVi0LLwU3gTDFLFBC5CNUIwo20pgsiUiNiCRDnLvUwhMYEEWilxPh+QntU9XrGox67 -Ue4Iqq6pktxYCO6rr6sK8p/f/88M1XyZMc17lQCBxCb9DjoX4bWxsnHJ70f70mIe2213EPTAgDkI -zyugc67FdIY01lZQsCpZ+3I9zmCe+sQVdswa1d/eDiHdSBjs0hsK2NxT5Ev1gq3HJ++/BXPb/QBU -SN7v+ZM/7gEOXMSywcx9TxDCU/UzxG2+b4z/FeijPzdy86cBooG0sODnECm+6GeX8wZNkOrImEZq -moaEByY2GpDFUVkpVBY4l6ChfcHSJ29n3sdJbxguBF2kXKud1ptv+Du05ET5MKpN5FkWGxatNDXV -wg250NdNmGioqBUAZ25VICJ6v0NgogmIQX+5FaOY7UR9c16BROTN/7yCIy3tRrwkL1/E0EFndJ0O -a8L3wVYThAQ9FFzctGL1J0Pp0KT9wUdYrLg4rcL7ZGhUubcvxj+FOjV8Hfbx6JDWRzjLeJh7lion -rfo8DgZOiCKjSxoue2sQSXmbdXesLwpM9VWgu1VCllrpy8o1cCzT7zTCdXrlwQOnLbzoL82rB4sz -vIQva4EM1pb9o5ljiSMq1HgCAvqIcnO3IBNi5yD2RDB+E/EiydaqrGm1Qqb3V/wwfF/Vx097MTuI -8SDGXkzdiszR3ItVySKQunEv5gRxQ+ZerAvLUfatGFhNBnLciAFNUYlBbsRAua2XQ4hpI9aFrtKm -jZi9qABKFxsxWDHVkCp8IwYCTv0s00bMU1gXtu3EQO2dYqqNnZgjbGjGnRiEmo2wUu7EQI4anW3s -xKVtVgk47cQAkW6lDDsxZ9r38Y3XIAaJ+w87Mc+vDHbuxEUhE8bGd2IQVO1Rx50YXNImmBTbiQsF -A5f2Xd+J31//syRNvi/7a1mTY7Wd+F6ZnnzYfRer7NGHaNdzI95apE2OBeOL5uwvb38L8k/jQX1c -oVyMvuCHC5lwc8OG1AkbsLGvXp++/25JON+IWyZP/sAHuC9ByhCWBN+IcUA+UwLlXzp/+69vH1ab -lAJ34z7caonUqO/DwrAVwE3sw/Q24cvM+3AjWqBwcOzDDVpZwyb1fZg+QoN5GfdhDLW6CQ3V92H1 -JQpBYtyHWyFU7aip2off7xD7cFP/O38Z92Ha2ISKnftwE+/ouvZ9WB1r2j3HzZg2M7Uh5mbc2iIO -xNiL6e60pxv3Yp5w6xuxmnEE2xJbLI1djsw47MT6DEedjvNoS27FDIeX4gxbMdfzF/etmM/gWFi+ -FTfqW4peum/FaqvR2ORWDOG6anZiK2bT0GBNWzHRHMEixlZM15kIG3MrJkrm8JDDVszgW04htmLa -sQS2Ejvx+zvGTpwBnHz9KRynQLzgZSIcp1Q4Wa0Mxwl5Jq3BCMfx3WlZzGicIvM0yGU0ToVHzpiT -wThVRJ3KxNUBrUWWawTjbJaF5RrBOLB/znKsPRjHDbb9aD0YRxrn2LyaKoNxPNHJF8xgHKNxCcQm -gnFcXZ2FUzCOKVmFhBLBOFVdAaOfwThuvuxLxNQ8GMc9rZXQg3GGg7MMwbjKpr+UiOJ5ME7H6aNG -NI5H06d7f8w/Mxr3vVtqxsO+fMbjvvxkSOsAawuY+ykgd29p97zZyhwTe11B/ez3NK6GlP+IyH35 -CMl91wPQ3Xmr95/8CQ9gVxCe2v7/iz31vzTMd3qEEH6rjmfyatezeSVoeLUQMTcF6tOrjSrhyavV -cVct3avVtRSsC68W1mjVNU5eLVxBVq0UXu162lY1ObVQ26oO6PUGL592pT2PjPjk05KEUGVp+rSW -cpDJsCq7ISt/8mk5SSovfVpdHHM/ndq1eC57cmoJ6QthMJxaaFLUdphOrVIxh98ynNrhsEiXCiep -DE7tlAtIp5ZnFZlEOrViE7kc1k5OLewfe3Pqz/RqoeMo5mu6VysyqFMFY+7WirfcPZD0aiFmt1a4 -8GpFNlVlOrlXCznHdnqfTnq1EHk4eqx7taSh5PS8PmNspS+MbyrYDZi2rbZ3mG14Wx4bdt+9rRt+ -BIzL2z0OMKY265ZuRpMOS0HW+DXlwIFjX93FwJ7S60NyrhMaAHIiqsbbOaJ0Lo9bLscbEyAEOSDQ -PZbL0S40lOdqbGx2ZjG7lRUkEHlBG/DYRTzS1hFO8QkE0KqnsPPcrCqqZhBMw2qWXVlWg8vgLQln -ryok9M4lgu1GSn07h5XdHmCORQzkh09IwtnGd72fhjUQYXX7pGz7gIc1YBDF60VD76rXbj65z0uY -gLolNHosskLLwSUkleoYsMUL/1owwZTqSEMf3E6yKL1YgUA1KIdN7pKoXSCGaSGqasRRZjEnNSwi -RMZwwDJQozD3NMBJWY7i2z4tqmG2h9sVULX7mc38gULtBiFy4dDjgsJxEzAR2BUCq7nPtiA75o4M -JajnRW0iY0rEAPdAZVuhKpQEf7jy1Juawk0BUUMj/DAazFnTITBjuPoHzqM2e2vGQj0TKYGJWMbr -R185LnWPhDtafKRLFrMGmftyPmJ0bh11vxJ915ibAFQYyDMO9TUIeIidgIRumVKsTLpSlqAvZ6cQ -HufVTJ2slwhy0qAzTxwQA2pRwCJQTTsSgxEB/IHdnUWkqWxT4NbvqoY8nRIHx64YPV0xaDpjJKRY -dT1zZEQzuZLwUAOHWsnJ3RIsY8v6Ox23G2DDbd24Byp0De1zh1H94QxZcpU+9stvQOmK8tHUIy3k -fLZFUSGWn5wI3AuVm6Ffa/Q0sL4F4Qbf3q1rDQZLpA+rqBiEsLIbEJgkUcGF5tnvRSgSpEo0DLQI -VXqhnQ3rAg4l1djclzgXp5RtW6TTt0Csa1FhdF+iofJZy+jUFRSF6sTHgDfcblDVhmCaYfFqOShA -5W4we42fkdBhdUYeIU2IJmsPpKfqoCwrODVCwRHZFRl34CMo9w4nVjAOfJKSKHlUy+kOl+55WTee -rkXVgHZC2lY08QipLSqmuzd+Shvu+eMFz8fhzBkknOKj4O+cBAVWCtlUby7KJUirqAteL0NusH0W -aJzTR4gF5++wOOQrZ0oCjM2mQs/FGUkDaiRdwGInGtpIll8cixegA7YBWwdqOMu0qw== - - - 0psYJ/zDbokByWhshu2VArpiCAXY8LgweRfutxYdaS9EIxB3aM9Wk4yVnZ1RJxKHbZcB6fQrpWS6 -YUrzuQgI89nyycnfFmqypjfM+rcciUapuyZPjNZ9cSPQnAYVDFab1zH4zd2U/oGEyLo8P+RWrAai -f3BiLbJtc1KAOdiec2cHPg3FmXOMQEcbpyG1KHB4TrN1PZ0OPGa1rE5TIT7zLXC+lXmFCKoafRQL -iQiJyipzsRFsUD/VtCj1HBrtWLwCv92vrS9wLn7QnDApAjpvr3IcXV+A2q1oeOoUQN4VbZl0T1Fb -GpZR6KhyeoA79Rh4303kgKO+I5MgkJDUixiVqvRJ3clTHAf3HHUsx13UQqUuVi+xzPBQ10L3xlUe -tTpgw7JAU/nXMHFyg8BsFl5TbiQ2tM1t89xwAKqmwSz2pd/qBveSo9LBN68qvCOfdZr/9ghHnTdB -vrARBMdmSVeIscDHhqquEBHFD/suQTFFvfr2zORFJ8YO3gXjRp/SsAeI1O2sizQZhOuNlTibFkBj -s2d0E+QeG0tkfEIymDMpTbOHws8CYk2aRioOPZdrtqDU6kLAtB9Hg6DmTNhi1MAqjTOZbO1wMqc0 -7e4b6LN1849aNUZ6MhNZYfKHw5rMYtY0OOWHqZRiMkzrZe0U3YDlThqNMHLRDYJtnIxh3HnFVtNo -VhUyMLtpWDPrtEtNBngNMycNddx0sQ2nMY+Hq9jwaPOzbnSDdA1IBlocINyHAsQdnvPkZpDWImKU -3ogScMQA02Nh8SpoPHk2LN4VJq90gJRSpOQvnSTTlDgBozOFtoQMyz0usQNE9MOIvWg4PursuokK -QPt6uHgiG2A6pxuIsy87aHIX+3HhVvLspxKk4XqiFJVInVxU5WkJBqQrq3yHGB7C3VUPreZTuMX2 -tNVQYwvspKeTINj+Q3igrUa8IF8diUBINa64/spUsltQkUVCUyG0UmMy4GqytJmxuo2lKun54nrV -YaBM+XMmc10e87VZHL+AhVivUP6Xs9BU6s1RHmhboNL0nkTff6s7kM5hw2P1EeYtMp7ZLRStLzZ1 -reCMyN9+Rt42jlMowjky7VqbN832e96bnNLjH6dng31t14SJd1C122VBqf6ucK2pVirHg6yHWHxy -3Lhn8b64Pr67Y+D2b0BfOZ5h/1bUni0tll1808PZPfp335oxv/b5QaI6619zHhHC0dvnXLt3JuPG -yTl5qejpyk3T5q7a5ts1rANAVY5Sh3VwgBjq9+zHnY6Wm2sKzguBA8TSI2BYg3I8VyhPraxOLmHB -pGFr5kq/d9G9upnXNQJQA4oGpNa4P2LF+EjtwjMkn0tqITFoFKugE6ND2XwNhzpDlV5RHhBaj82g -kW9LzchGqYmXChTT4oy0U1e0YIi3ZhaWswNWmx9daas00jeY1O0UW9TLWBJN/1MBAs9H7hPiV8Cg -szNjPzmssaRvOXyB3JVUeuFmWt+9dmeDyP2N/qiDzTj3QWoUtzBGc78EovS0BhHbUmm3UsYpt15B -lcYdc4c+q5Ve5C4OL8R27lff7VNidoJbBcNxYU+Q1BewXJeQ0yrXbGGw+C8xSaS10iVh1eAq7vE1 -0vqhKoPERjeQqLQUBkFKlKTcy2xIsXiUx0ybTKlqweWF6cbcEPRpWArmw6zNsQrCDERihUhhLmr2 -RWdtmpWElWX3puHJApT2T/u0Kk++bjlCVjhFL4magcPe5UxNkb/TcW4X3+/prlzYzty8LGe3sBE0 -RjQsCnsiajJY0GmtswRtYYRVbw09UaIW1r96wauWlHsItTjwb3oSSHbH/EyHA3LTzcgp3SmpkWlP -54UOKmW8P6Tu1weqXjAVjpC6ssUt5/4Sc1VpSPvo7lfxrU07hO+lIDsmXfpoep8ryuLCl+M4K45w -d8+0xOAUKlNe16iKc+eRD7JfKiB0B1PXb16+Js48ruQYRuGsMk1vl//4ovuz+i7b2d1elD7ooeb2 -uneMEpcFEw60isvKUrujzWgIePKj+wdGVgbn7HlYu7lJWjRLhnMv5p1t651zCgJw5grobgYKeOlL -wxwBBWZfH1SPO3CYp+c9NEELvlhiu6Sthh09hTDyQTIckpl7D5pUVaT5lIvQSh7ksZd+mRBMdwth -PlSEevqDR0hofr+sFchxcB+wD1UEoeYhjWBVH/oIaPXPE4Gv+TNGgKx/7gii5YzIWNs0cTIml/Mr -4nY5BzO+N87UDAPmfM5QYc75DClOayNDj7GEMjqZiyyjmNNizGhnLtqMiObCjsDptP4zvhpqIkOw -qUkyVDtpnAzppmbKsG9qrwwPj0ouo8ipCzPSnPoyI9KTXo3AdarfjG2nis4Q+KTKM1QeGj+j6X1T -iKh7bh4fxuh8bjIZxM/NSPw/FujPLStTAbmtZcpg2v4ytZDbZKYf+lYaWYppy81sRm7NmfDI7btn -SsZtvkvTHMgcS5oMmYuZTIvM2YQJ0vM6b8lozgxSN3silZSWUSScZgMq8lLdgMrUVRpjmeKajLZM -hYVx19Nlbv/1rNpoJvbkW5qTmaALizPzeKNh2vN9hxX52Qu7ZZtZw8kA7tnFMJR7BjKM6Z6pHGzu -ntAM07wnPcN8z9zoZOZnDjXdgcyzpsuQ+djRs8i0bTogmdoNFyUSwJMj0/PE4fD0XHI4RT3nPDpP -PTMdTlZmr9MPyyz35K9lNjzdup4xD9evZ9ZHF7EfF65kz9KHu9mz+aNb2rP+4b72yoBwcXsFwegK -t+gaT5dZT6GtJ9xqqVuNxeh+N3EebVt30+nCuhXA3l35hnvpBezp8YO9IyyGjAzwUgolZPSALUr8 -qhFkCB1sVa4RjIDW4V51rQctpBFhZRErZwQ3mmxlnKsIgMBFo2KfKVDSj4uASr9WBF36PSM483F6 -tgji5CtErEep1HzTiAn10Yi4UQ7aGF7qYxthqD7+Earq32kMafXvGaGv/s0jPPauLuGePyB3fgfK -N7X4C2mCnDy/cO2m/b/AZ1mkMg6FTco9FXbjUKHwFw0bEltJi5Wg9uPootCueF/B4anApyRANc3X -ftxlFle/1q2cCmGjfs+Q2KzwR8vD+tNfFuB7v+UPk+KTyL5ysPbVrNsDsJ1fzNJq7CfpBInbSyXn -LsAy54iP7obNRyke2rbhQl3iN/zo/o1JW/Umu6wfvnczMQ3kxVFX+A7h+DlsSR4Wz56Xer3jxx8m -wycnA5CZMv5G3YBFKtDUXF1FmUvCLLECsYlFBzkt1CJMb7q18jianfFLctGzbYrqao769+NcifRr -haLp9xwVUn+2PK4/v+u293v+oB0+PSFiWVIrIDf6F5MU+H5VI0RdCUBHRhjiAgodzsg4v4/yhTlc -KCRxw4+jOqIUQRZNLvuVYIcKHeLi8ASVSKiGEumH9TeKS73e8Qft8ISMwMtWnmuyGW7DSw52SuX+ -kt4madew6YFwu+RvgTqAuWZBqWpBhqKPYLAxnuTbIpp1GKLDZH4LXM5SYlFSQ8crJT5ITP9TAFK9 -v1O9KzYT4kEgLRWRAO4s7hv5ROUdzUemp5Qu1s3B47meJRBUxlOsiUZADJtlncEwU9CeLtYj6O4I -gymEgZ8k/5rZWKqgH+4n2/qcRWe1Iwo68COKd9zaUc2iNf1CkN8AUDzfEIAvjPX+YBQVRTOtHh0E -D3um8Q23AKnIkZDjpRvEaO3epjOPKkyxyv3n6G/eV9C/EJ4mUfD5SxI3FU5FfnEY9RQ7iUmRDzHN -nXuyndc5zjHBA+3DDkKIXYH3lETm43FcXqs58WK/J+ZEsF/1R8OkUMlIPj7h3VPkRPGao+vTh6MS -0tQAxZABBbt4v5YNrch/vdahPwkNER4yt690O+DKVPWvCQnomXH7+OiKovKwOTHuM623LicQmbaS -+HMx0Yqjm/S5SGhW7fAxZRfvegs1axM7D8vJn5fKRTLdMtZSPlmutnz6XJXTW+bqzdHIFR4Dlopg -GtdUGDn+qVTyG6Xymb5lKqn85qnHcl7kQ0zzJ/VizLO37nzr2I+f1OefEwH8ntEdAfz+8aVBzRLO -vSd3s+qfHxDAf0AA/y/ROD61umXjeLa6ZeN4trr1xvGx1S0bx7PVLRvHs9UtG8enVrdsHM9Wt2wc -H1vdsm88Wt3eb/BqG59a3bJtPFvdsm08W92ybXzqd8u28ex3i7bxbHfLvvGp3c36xrPXLfvGs4st -+8anZrfsGx+OC5S+6HbLvvGp2y37xrPbLfvGs9st+8anbrdsHO/dbtE43rvdonN86naLxvHsdsvG -8ex2y8bxqdstG8ez2y0ax6PZ7f0hPwyf2NC0CgHh/MSBprY6nWJHUyOzZby7jqZG9bmirhaLdTS1 -VQAnzhsuNDWmpdF+BpraGkkdi6c5mtpKTQOcu4mmRpGpXiPB1Kx3N2Lajqa2FPOpEk1tPb3UOdHU -uLYWlZ1oaGrUGeu7dDw1LrWfA5za4vuiRaoDTk0oNaRAE06Nnkh11yWc2so0iMWdcGrrbjtqh1Nb -g1o88dRCYmcGoBrrgox7B1SDF0BYc4moBqtwPm0iqmHKXNeIqUZZaNnKF+9Z8OFHnwNTbYL7euPf -P+HvE47sCajGdr6bjnziqX0KzewTaGpfrstCMJ7K+g6nNuDff+LeI5jaCTHM8QJT+713dyi1W7dg -bZmi/mxQat81tP8Kus3/PASXjio0aqSOKRUaqWNKhUZKTKlJIyWmVGqkwJRKheSYUpM6SkypVEeJ -KRXqqENKjeooMaVCHSWmVKqjxJSa1FFiSqU+6qhSro86qNSojzqoVOijDioV+qiDSo36qINKhT7q -oFKhfTqq1KiPOqxU6KMOKxX6qONKjfqo40qFPurIUqGP3nPgz9RHfwQdxy51tA+wTk9UKWEjSh/t -+wwrxc4aoE51f13AQaVILHzZwIUSJP0IKrWaPmqfvPduHVaujfZjRpT6nnsHntQ9BVwbtfIDIcf3 -KSPQ2CdEnYTV74g6AavfEXUCVn9G1AlY/V6X58XPCaiTxYIjoE6A6ndAnQDV74A6Aao/A+oEqH4H -1AlQ/Q6oE6D6M6BOgOp3QJ2W0IcBqBOg+jOgToDqd0CdANUfEmJeyT0B6iSofgLqJKh+IuokqP6E -qJOg+omok6j6iagzfcc/SJf8HlT970OKfYPpfPlpKJqApp/RdBzaHiy4DVIn+Ri/5wq3fbFX9fPc -s++BrP/lE1r/ux5gRNP5Yx9AV/hsaDr/FUDF/ulwOn8KvQ/Vpwt4gVP2gtidSlvIRBqIGsEANeBA -ZUVDJNBrxDg55D6vhyytswejX1g6SlYqll0d0JqwJpWsVCbdJ0bNcLMApkLXnnqyYOfWho7t5bIS -CMFc2YnRsLN5NhRloBWKiXNvij1BT+/Svfp7a7c84OGw3V8yrkTvrVqRpztKZWBC5ZPRdWXFGf7w -5Oy263y8I9ZO8yi+jQX2iirwc7zuSx0awWlc7w1GdXJ9/FHUFD32T8SfZMWOidvhOA== - - - OqGqodXZtdhKVKbf72kFx7E32KMRrRWsbD4+sHUqDM7XpIZuq/GaMRpEdNXjmSMmuHhnj7SRbc2o -jnJkhZ9CAlwkPvGNCN1se8+mFgVRzpg88cXzsJwVeamcPdMdc5blk+VEzKfPCTu9ZU7sHI2c/Dli -fZEMAxtLKYe/r7b4RK9VGSEVwqozOQ+NouaqkE4hgsUKxo1QvduhSvhLndnqRxHiKi9Bb6oZ4YrA -O8QgzeVyOfEs1JO93hPMKts1yoQ+mhMZKY8VuAWrWiDURIXNYZgDvQOwqE8dl6RZn9Vwy2ZWCMcJ -15Qzz81vcO3VAl7m8GDgHN7Sn3gILCg/87SPwWjsqxrQb+O3qJT3Xq3VW8pBP7Azd8VkVzXmZkfh -afn5Yp6XigsPj4TSHL/Hk9wbMJEgb45XSebhKGEKzOJTLSWe9twchJfjUEM0bLcNf44359u9v+af -bFD8WVwZ68g2cVEGS6XhcTBnKCsZoflWEZRQ+Hxf97tOZ3y+rCs1xqrF/X1UGd9583u2tQiU/DE3 -t9PFYRm3/oEp488zN15AZquom90WP87TOHv4Mmfdws5mGdK2IeLQRphBqcVbMzdvZBSkt1x5OvSK -VapJgdPtrnpH66wyxBGioOqgCjjXfhQ2/ybop81BMW9DVDOdOLUOuR9OtKpxopFi0L22KjogtSdH -i8qCskZPGnr6ijbeYllafJEFt4EXtHodisFJ5vKC6+n9ioc39KieWHTbRCHVVNuKuf0qwUInSf1b -m9lp5NG6Y2WX5M/Eijy8ezRyruD5LXrnNTpPCKQ3S5jeuicgWaAbagZv4f1cVbAHwB84gihw3T6o -2k10AyyeS/hs1crV1VBbgTqgD9oL/heUvR51CWI/PrZgvnDfF4OI3YSbALaCgIqJrN8bs2tZ171C -0tWJkOyIxEx7qdAZT6tj1dK6HczUz9aJR3Mrb8CeoN1EzV9wyGvHEZi/QFj8S67azRZzIpdlN4Al -a/MqXKsZNIp1B4VE8V5a1+wDx3EglZwOSbGvexkkECXRc/HRV41Ld0eKOdg1rzJI9sN2TTXn7vXq -IyTWwXpaU8d6eS9WW4xlMgXMSMKaumMIV0yL7VRmzlAYmN+71aVZvxMdRzQoxbjaaK8O6AxkhLLy -SNRown6mUkY1bwRoO1JjTFwpOiA1dUWjGnGqc7Vv6f2TsKPfE8RGh/5Atdk5ybDgM0TkuOLTbNaP -yyha1/J1GQiHnqQtBkGJA6v4pfFa0pnL8GhJ8Ioy3uRjsljLYfVGauM/ZFQublhf2NKO9CL9pApL -SAbszN2wLdXZr92c+CXJM4M0cLQc9I/3+5OfoS3o3hVl1MJOT+gE/BIr6LzNNoXcbOGHuUN2tXmY -Uy2SgvxHQfCaarMRBmXxYlMG3PgXLlMQ2CLmFkC0eSlgvUjf6nWqvySf2CgV7s0Txhkur11MYKKY -fuhJs4cubysxbdq8TQX4KKwu9IMVsHMD2VfYQ+qV4lrR2oONaHQWy/2SC2lXCJlFZkamVut3a0F5 -UfoXWZtjstDyJ+rqVfEtNWHvVgtG/tM4nYGUyHsy4/UO52UDqzMtwI7iwrLcjujhakbrmuayknPX -ogHqtrHYPps9fhPNBkBBObLtMPgtlP2qW267RVlocVZHSpeczaJLetiUxoPw4lax1G12ryLF1JSm -t3u2sOTjOHLXhhIS10rJdM+U5rOhXdUKns/fUNBreb5nzNAcD1q91PESQ0YsjiTuPLLAsOnE/AL3 -xuyTPb5S2X23mr4mn19ICvnVW7NoaE6MW2fa088TiA7xcZ7RqmYvGXNxOawJc5qz12ZNjzm1Ab3y -d9bkP7zCdlojpzea5lJa0ThyIH21rbTpqS5sWpQ8AwOda5fgu9F8+PI+q2OLjUoAwDTQ01NX0PBU -B21CYKDuD6Wjyrqjdt0kV2XtyguUunObVRxuj3qfQhPi+BUXSFliXtAeOenU3RuZUvUe0boc2hm8 -93bOOvxyhIDQ9GsYNX03uDceA4WPXUPjiNcgae4udACKHSh2IdrOsLJOwUf7ZqXeTc0v/yp6guOx -69EaqkhN7o73dFzqtQ47aIlw9LTTrg72HvsxRpT0X27ZKZl29pSmAcBltX+mjUCvmAzDyZYoDkyd -Jsf9hMYe8QnJaL6kNM0cYMIEtp2mEM3PapSeTKZSvQk9j6u4iQxrml/0jnqRb7fSSnPKpbTmcPH0 -1dLio4JQIz1ZhqwmAWilBVm82KJbmbRUC/F7skZX8jF77VYrRpTeO+zaexo7AMVg/p4OvBA2Mqjd -BtrvZvS6OIDTZG6HNRNG+cWI162b7WSnBT8yWvfQTOvi4QRse++Pxk0g7mUdKoMzAVKk2m3D57i3 -OUsYhVuiKucyOy87XNncK1wcQKEuz8jIC5IGPGZX6XBK1vSoKHulZNs8LgpwNmGED27Z4Y3Y4bwd -ge4e7t25ybKZnMA8yDxFlI+SW+FKUqMibJbR4QSvimKZ9EuVj71qd11RLMZ2Fi6uvtbSCWSX47BH -bJZKK2ygaFBlmDZQ1B282IsIkFnx2P3OxcqRVY4bA8s8rIvHHNW0tXiNO/gO0uDuJLFxHsXmFQ+o -knO92B6M6JuNq9hW9oh339rD4AacgYTIrMG+uY8k1g2Vw05s4/n30+Go8gpgblEimPe510FbzaTP -h7kntXb1fOTTCx4w6OO9LrxRqorixdW0vQ7DI86vNWB3bQyp00VDxSCLLAPFHV9CdXR84o8+WQDP -ACuALFN+z7NZs1T/7OfWI4g5O05wiu67xfwhV70Sco1ZRpt+5gBiLtKBf5tsR05oTgNJ0eczWAbS -UeN8Bn+j7CfBLFsVKlYsw9oRBEiUVvvyIuZ23R8tlh9AEnoGX6MNmrGy5WTRQhZICKglsdQpSzov -sxKkD7j3Pc9sEENrUOxV1uMeatcrAH3woK57mg70motQUAivezsMDUaFoWZU6Dm18jfvPgptKEQU -gO1CXTJkC6UqXaveMyFz5KF8+fgHi8e1c4OzgYkXKhzO7+VyZJFQ9PqqqNrcC0DLEE5w3zOAAQiU -w763MIntTN9/GPBz34d9imfQ+MVuo/2syZDRPuV7HlWb26Z93LfGa+3lvbmD6pVW53XQLtvE4gxT -duzGKfnoW6N27eE439kZDbUCDBL83LbPFoDhodTtizAmBoHbHA3Unj2s2LBNmNRmpYX90mQoLXWQ -YIiv5zXbOSoupj0mLCZqgAW6kYYVk1SVFDawboCxSsqtH79IIw3JBuiqm3LgXLRAOw17T4XHt5kd -BiG1sVLeaTfSfb2vYeKEfUlRs0BW0g4VCgV1X4pehL1KZaxcKbdo7ylvuKZu9YJxsVRHcAzTGKFW -bxjPQpJgZwwTW8WvaWAbxgDwczrJDXUrwb0NJbfm+Z2ZUrf4m7BQ7sEJl4AaV5WahePQAAqM9hX3 -LsBBkCEV7ke7/7E0eysDcgdz8Go2E8OVoT5XOiC9HQp0LYsUXpHe4kpr27AqeNjbSgnnijE9T6oI -3AXjMmdx68f9NA4q5/0C4cjpyuzd6e/pOvdacOPV/UL14JfFqN+1ixB+lr0cHqYUebShhSOq4nlQ -j9JXpSn/XrZr92mFQgKk1ke30OX7gg6h4Uz/mBrcFTq19KOp500G+3S3G1lekF7DI+eVNg1xeO4U -yMeAhn/fVBG0th4ZoMRX7TtdQhUie+cUK8jHyLgDZcWVj5HxiUZssHqWO+MY/biIdfRrpWS6Z0rz -2SK+0p8/4jDTW3q0po9FBHRyuCLuM49qxIf66EcMqX+hiDXNXzJiUv2LR9yqz4qIb82TJ+JgfZJF -rKzPw4ipzfM1Ym85rzM+l1M/43jTAsl4X66jDAnGSovA4bgaI7rY16wHIHNZZ5xyWv0Zz0wlkTHP -1CMZG520TYRQUydllDXVVkZjJ+WWUdtUgRnZTS2ZEeBBlWacOPVtxpJTJWfMedLcGZtO/Z7x69wB -Ms6dO8WHMR7ed5SIm+fO81sP9yq+3neoiMHHHpah+mmny5B+7ocZ9u97ZqQH5r01sgh9D/ZEQ9+l -QzBt5iHMPT/yGt0uiPzHbD9EnqTbGZFL+YRkMlm6NEybyN908yfyPLOZFPmgflzkjLrJlbmlyTSL -HFS34CJP1a28yGfN1mDkvbrVmLmxtCwzhzZZoJ5qS0M1s3HdmPWk3WTyZm4vLePM/6Xt3POEo4md -6cQ0xTPlmNZ6piYnoz5TmGn7Z5YzvINMhk4+RCZN3dHItGp6Ipl9nRyWzNKmXxOJ3PR8Mt87OUiZ -F043KjPH4WhlgnnyxjIPnU5b5qrTr8uc9uT+9ePcR4zceDqRmUKffM1MtadL2tPx4bX2tP3o3AL2 -b0iA4QTrGbT5hJ8sjauBGL3pogLcrXSnm5I1KykOv5zGecN4G713IaiIyjScfOl++dgRB0DFGohc -RAtChSsoEDEFWpPFB+CBB6GsMx+s3MrDE2Kk3q4ewCCkbYymY5wjD4s4SL9ShEryfhFQ+Tg+VYRd -+sNHdEYI6/mSEcTpAxFhnj5gYzSoD2wEjXLwM6yUH2kMPuW3zBBV/94RxHqXc3DLH5A/vgM5jMUt -dNicOqp+gdJCOed77pyrsqXN461Ho0dMc7yotMIF+kD1iJZ5P4pCdyX72Y4F3bVba988VfthACmQ -pckr0bdbRWMQNwyJTdZ4rjyuP/tlc+79jj9MiU8iA8nDkvV7r9ajWCVUF1YxPHcXaLsijZuSXYfI -5PjEYZdx2fQrpcDv99FdG5NS4tqohGwkyCTZjGGmX/z+xkkLg+t3BO6jH+fP3i/1eMOPP8yET80E -UDZUeTRqhXsQDe4+FtbubkauPXpqlMoZlyjNcwI0DyEtOGLHiMXe9sDSGdZnP8o0R17GVUvea9A/ -+UAu60/sauz1Xj9ogu/QBL4IybnjLP9iEl6M/bAMRQ+AnTNILh0yqYLhMFuG/UL+2+/2cdA7dAfe -m/r+RS5vtbSq9CcvTOJ/deSjVBfDcfk2ea3H+/2gBx7zAI9ataajBsCPtwq7kOLjWt+aAIGrFf/s -Ks1U57GDh1PxrpYpjjOvEeAHy8M1C1jtTfgMk3EtlAfLYUX11e1FWWsh5SDS8mDYqjwUCOXVIbf6 -c1BtIg99O7x7hTCDIljygpvx0yodbmc2qypQVYyiedWQHyqpllXAc1YiQSVQmLVILbId0CXMT1E6 -gwxkfdO5cQnaeosZu3uouB+3ekimX2t1qPvpns1pqePJwHpWEWY+u5zH83q8IiEQlRTkUNxua7Pr -x3CBnCR092lYYZjZhsGvRMP3bfhABadiKY8PWR0CJD93vfdkVYfmjMhHmGYOCFFGjxkzDEIxVZ/k -LJQnpxYql0Qe43FcXotmEqBC+j2xGVrUleSzyXA4jQjcHp/wLSor33J0bfpoqJ+DIg8fL+KCF3Ha -HFXFXR3CvD8GgUcl5/ILAUqtYrD4kng/Ct3amfHFFSIFziPnBM2x6zZMnX2zbdCmjg== - - - TzGqNVSlktOQ1lkVWuR0FW3VFhVCMa37cTH1+7VyiUz3zIUUjxZLLR8+VuT8krlyczBydeeApRaY -Bja1RX6A1Cj5iULxTF8y1VN88NRgOSfyGaa5kxox59hbaz6V68dP6vHPCQu21y86LNj9gw6T3YDO -7rUH4cL9gD/Agv0AC/YHtez+abBgBCaNKZPsDLufgg3Eh4H4UN2gqsOX2ltaVJQG1odiUhVAxui0 -IVJn5ClFyV8gTmxpc6VTzR3Nmy8WaNC8cY8TFxVcs0MYTJLTFFIauS3nKVQwIEysIwYgChIv7zcI -WLAinrXDsE13bEwLpBwWOzXAEHF+FufsoJVQBIbr5q1zNPmzF0fsVBUQdCU2sg5Qlq7iceQOhOwF -W2JthOXWJ61XsAsHGRAeBTwJnlJgxKVO5zx2wgwvnvsQ3+GencNh0ZMIvcG1GcSUkB3HJj+uZm9O -2O8Qledi1FPiCKmitj1JCgEOo4phbc8gnGhsTjInh9OVCv+RcuNTTTarV8QtxUpILUEA0KL1NViF -KgU3cEVQSFxBdWAqOBmL7yMWLVqthVWR02bJNqdZhCWJjtfXd/wwfGGlqdn2FOfVF75n5HGoovKe -LCqero6DDi4JYX2bMEBfcm+Lk9Hwhx2y7t4QQSobkDim5KZHa9zfy3c7B6n4nZzi434dw/K3jhEg -ZvQKXEuRMqyKM+iVlVjWGlqt57QoRWfsjiojFn4N9cC3YAk6TJK82rKh9TmF+rsZHxnX2RfD+reB -qeLd8iCvw/+ISwkEEOajkTDhyK3R4qsq0zPX9B5mD7SCm+Pv7nuxyl8V/xdheAgM/uxtvMIPshgy -aSOxfiTrOEZAs4Zee1Lhr8WT3rpFpt3mNLuExQVCqDpMEfA+P/2HH/3LQYEFfs16b+In6aDrXmP3 -Jo56WGuAge33xxdn9q0M0Jp8i/cF6Gy4LPf15T3sFJqS//59aGDz7alevV3s+/aiEb4CDewPu/0B -4M89JlyAJMZ+tR/QwP6ZIDMwc4/jqA+XfofL+zJbVMEzYBSNte70wksgFA1uBxqoc+l989ZkiLt2 -Gh67aoNvfS6gAjW/H+KKa6YDwk8W+IIcOpX7U82CFwDuXzQGrQKFpHAg4Gk4zJN7FhIk0rPvixXq -nscQicYdPrelx4WNfDcPq/GGcSHqDur5vF9116o/12oUN/3RAb4U2ML0iuQzCEH1oWiLtw3EYEF1 -o9GbxrQe3kqbYw9/HJVu/fuwQ8leGoOYw3GHbdb9WkezDpfhnl5kEzEEezQhCVCHmo9PZau203zN -e0Pe29Zf04bjcFzSPmTgwnFcH9qjWXwghta+wO4x4P6R9n3OM+Arqqkmgnv2yfOwmBT9SjF55jv6 -HMsHy2mYD5/TdXrJmNU5Fjnvc7z6+hjHNddRjn9fa/GNXmsyrI3vcDjWL/7tT7799j/fWumv/vpn -v/nN17/+5Vd/9Q9f/c3Xv/r6Z7/5+udoqEkjdciN77gePvO9s0gljhf9y59/85tvf/3VX/zjb779 -X3/1s4/f/PLvv/r3duXti3/7V7cmex/717/++rfffP27r/7m29/9gx3qTXznd17+b7+5lem///qb -v/9P/th/xOP8b36PC9Krffv99/jfv/n5b/7Tv3Gf7DWCw9X/3a+//d33Hfc/fPOLrz58+6tvvv6H -r/7nr//p+47+6df/8TdfffPLr/7HX3/7y99838F/++2vHsd+73B/+PY/+3C37zqU6fGbb/jsX/3t -P/3qa7/w8p1Dxgv+9de//njvWP0Nv/Pq/O9n//c9yPeY/NNXP/32Yz/nMQv/8kf/zV/8Vf3qL3/5 -c7+E/f7J13//zS9d8t9/8d/+9be/+NXPfv7tP/763/xo+LcKOVb499azemgJ0x6JuDhvifbKv9B/ -/4/f/chgZmIvvUX/pJ//0/3P/+cW/u6LdfniP3zxf/5fyxc/t1P+Jtyu8ZqOK/W40xc/HaX5RD8d -r/Bp6XSFX/7Bi9zH+ic/+/j/Mvk1qW+bZ17tvO9/Zy/9v8hMqMutpy4ZF7eNhZVxHFe7laW4Ybdr -1Z9uK4F/1PjHbT1Q02NI3tQR7zSO7daF8AsX3oq1C5s5lAeCtpdLgm29N7Vrl0fw8UdGy3qC5kgd -tPjPqXdb1aBEL531hUDop/BntSI6nUnlJK0LK2FIsQ4iaZdJpLPtGAWkyf35eZT0UXxLraBKsEC4 -rqcdZRD9eDHrYVdq0eVHOdK9qezafkWzAEHmVdSasBvKNMF6NmiaG9dgL6QbjV5hriquTjjvK6hF -gHqvtvVe+2U2R2YAZHdgY2ubUssvZsGuXekwd8aYesGcuT0dkTjzpMIorETPkcIxqt2rbMZ9c+4e -whQ2JhK5bHHmVg4708GnRDJcJFG7oO2E1e7pZYhse/uqat/NSgfZ4NZdaTXsKTUF1mUr8VtnQYfJ -jIhjxBWlOuXFAGAHSbOqQJ3XpYd7hvh37XAEcOuoxJHVG7vE7ghS8TpIea5l17U25zERitJpze5l -DR5wpKsSP7exsOqNxPZbZTVoXsoqKdWSQaomjTMFFEFAXOiSOL2VllkksDQi2SpW6OYlb3aieLh3 -GSFmQe3iNjdU8yW6SSU4rXLRznOhUMDVUkcTOzER4ksi6YBRuJA3CYmNK0i8Qp6K41hcC7XYO001 -2zZIpi+SUkoYV4Fe+lqkKYM5TlR/dYh5STTHN0WFfDUS0aoqBlqtJYDax9/qwcguSCnQYuAs1770 -7nfTImIOLRR4lssajOyVbum52Jn3V72sYXbZXNIEQ0qZ/GX3VLLDpgokPCzKWizIIaaWa7P8loGF -AESqjJELdKJog8sgpQCybgCXEUn04t8lVaFXcUpYV1OY6nWhRJgnQKXulwkmxRtesWEXCSJd84oY -CBakFDOc3PoUmwN0NbrOrtU+vproVGhvvAa+oCVqfAfgnVUriYQQIBIjgKQy/lS7ABQ2Z5y5+4hT -h1PVwkHP0WZnKsSFpC6qv76sGkNnXhAQ8/wilaGEnAxmQ6GS0VH/ISXWAvdhcZ5BQ00RwWIpfNW/ -tgvg6GraZoNNogF1XUzF0o5pN2SOgV1GvKzqhqEfWrNVjuQetd10M7GkOPP+JOY2XtrdAD5gG5XE -hlkI/4bYoJpMO5NCXHYzZt1JQSsIEKc5uBv4Bu/vGA4AfQNayCQaYcb8hQvvPYYIppc6c5SCtaeX -Ex8EjQjmnUZA/tFPWy9qik/vihCY3rGagHJxklrbZadV78ymI0Q4A1LvVtG8nPIUnRtAzTbaAUyg -s1YiqATs4ihlngmjrQ7adH+xwp4LhXb1/hKE68VyF1hu01GaIlXKYVVO+7rgrSImfLgCpBIcW8Tw -c+0obaW34Do8jX9UNkaahda422ZzizZ44Rjdk63u7GqXfXqVjJ/Qot+mwBLU3egAMW97ZT/9h6tq -ow2kMQUbGGWHWxMpvKdelM7vGx3Nh8XqtdqLdbFLYHerUH4dw1GExQ9dR2q1CxblE+0sl8GDddhJ -oMl9QfxXXApgglxqqhkVEvFvCg5uN/xQH28xuwIaE2jrdRrPLPjHY8/Ttl000NH4A5HLrt8yyxbs -J3EBW5TaBv+yseaRYMuADeT+dk3vwYToAkoP8pu5UDB01R7pbK1QiLarUp4OfCJrITBbboFt/OxH -FXI897uS4gXTLwWb9+3aaS68x3ERdzSG30oZYvVGosLGx8hScRq93LTXFBqUqo8IGw3TL4eIwukV -nXrZu+k0esDXe5i3tXN5lyLVYgGPJvjTSxc+dwfCRihLi95/lAS1lXW5n2XbDEaaxqV7bq9fyBcP -PhY6UyiwoCNSLBk74JC3/kagGbqLjneTgNrvOO1k8+Ra1lZESXqRQPzOElDidxqJgJ1FdvleLABy -r5e1Ap58fpIkhG5eqi72uo3YP3oL4AWKJhRbxhRFnUBvg+GygUpbT2k3Zeg3tn3mLmH7mD0IVyKs -CFmqm4wI0lsOWrpZbbKUqyxSO21PxSjbQ6cV06WqCNGFWzVdCoOLncZw0/2BgUJfOZmDRUBhBje+ -CRFXCBHmP1itpLrBCFcbaxmnletoqWA2aJG1DO/FcDqlNkJBRDbA0u/ZI0HRTwLv+okNEtXXeRJ2 -HC+0C/YAHVptWW5+lR3wimYNOTGM8uK2xTacTY0v96KhRgM2jxR4fifeSzLyczR1bk3saZcaVdXb -s5XmjkxIdCJt3zJe87iFbcG6rvcLmIrl1qo7TbCrfccPim6cJJuEeXOb1Szt5ns2+pGU8G0h0AtV -ukQ3pPGgENnN44B1YVhoWbtdty44FsvFxWhKeL+pSoc22neqsPCtw5dXKTtJPxfoNMoCdnaUOIpe -pwZMAIrgHASAua4lTgshaC+NDQB4/Vt/kOGiwmkTGIlg0E0Qo7mzpfajbp2I/0ORNv07Gyka6SGi -r/H1lLdhqu/WZiwBE0McihrdywIAVIBv+e0uCxMg1EiuLIZ700VA8xkC6WuuU9yj5ZnabYRrQpHN -3+gyOqv2QSXqbFJsVwrstEKL8jYchcI8tIfcy3MfBOyra9wthIs1lm4rkx6lZMqJu992zpkCnQVW -AMs7jyq7+W+4b6ywFFDpFM56Cg8czHuBcvELi/Nww2yDt6UcLQXxtTEfQoZR34jnQHHRf7FXFk+u -p7A5oQbX0KiWe35SFLet5KtJ97vAHpDc7iBkObSlmdYkDbyRziUrbSl4WzJsZJRZb2aWINibysH8 -Netl1oXI0bxnFuG5XZr6vmIuECpw+My41RH7IcGxxc34mtQFr/5ilbjArSqtTf8+C47oW3vHb1ud -pzmM/aDLPiBhHZLSXdCsBiG0uAnxzlzXy5hafZ/kZkdRo6oRf9lpzZYHQqkoulIVwLqEbs3vdqli -xfqN4yytobKYBaqzDsomFrPc7M3YVBazrky1rmanlNVSNOyPslNWp2btgtUQ1z/6LmrWDCmcZmr7 -9uDLF5Zd3Kr2Q4UIUpIfYN/qcBzE8Idg38yauJ0xixqV1UrhpJEphTmbIJNlZLH4l+velE63yzei -iFz5HAx1OUjl/nIhFFEaOwINk3JWQzAu6xTS+SpyThqTiZ6dYGKDFHEWQHUqan2rYU8RycLgiC4y -eNRuz/Nee06/S4O3DFryT+GvYXUpurvRlmpNzWWpe/wWThFIC6ySOAaNf/CEi22JGIq3bVhArgl2 -H8lYtZIRJ5AAte6LxgTNBV7ei7CeNElsVugkAW+PwIBJLjA+BHAnfHI7jZpWB54S3AbeoOzpzYA8 -MIIYDdj4zrhXtYgKKOtVRng1pX5sPtKL+7gEE0fn5oSfFCEmP4JjvRfL4UwBcn+YRWJhcXcPoSwd -gqHNYSq0SOCtU1s7p1Vr/liIxdppJ+2gUqveag3dVRFqkjHHFbWg3Q952VL/4K5DuT86BUowLzVb -0Pf0OMywTMHlIATm7rlw5a4rBhW24qVg8WU+F+3simKUzRhjdDs0IZatGAl1IpP8og== - - - d5o/aR5TP8wyJSWrWLhFui/zqFVeDBQDkqqgTg1jHc29VStBXi5HoEC6bZ7iFakhyntxIJDVAD40 -wAjuR/fvAOtMU5V4tabSBljcXq0yWnQ3gAfcattrpRcPBCO9/ZDNpdWOs9gSUY1VQAeLxcx0rcu7 -eyStngG3UZTbg/YVrNAhgZmVCNbggmT7kZIGj6gZspSFMFe3DboEDuQI56aUPpVLbj5b1HkIpqUJ -0GA1HzAE5vEsFo/Oo6A/u7dPpomFbUJw61dT0nZeSNclUGEwilc9p1GB4llNEhsZsiUEtvtx6g1e -VSxv0bSUoGbhGssxNWm0t1PVzOZFMEze2mJ+EyOjlZGveBL4xPo2PIzF43xLgD4R+2awJIngettd -D+p6u99hUxRxcSpLSbCKhYgVvfZIZfReI7YHSgz4G47ia8hBDYFNOFfGeZTKIIi1XgYhkgIbmgiL -ppTd/8rLs1bgrRWw2Q6yg9axSz56GFYeYT+ORybeywtlJFCOHXUjzZmiCBgqJHkZA6wCjcf9D3kx -gMKEANUiVIuIUJoU3laj7JS3pVgn04iSBTZRdSO0FrSkBdqOYtKl2HH39IyuBWHtFE/2ULLI1I0z -XWmQuBPuEtb3aWg7SgrpTNlOSDI4uF0WfFL7gXhrQVaxDJmTXXUJoDj7HosqpEczZlkBn1SBQjuk -K7gFB7oxBB9dq5bjOofDBENwGEiIkQd3yRIZqy7bmyUFyBXWhjbmLUjBoAaPqozS1ZMymnHKHuZx -OG+KsHtvBpHTjQSsvU1sU4q+bSZV248kCkkfgUXEmdKNh4M0xJm2Yg43sZHssrfEnbrZPY9LL7kZ -J2RoAD/T6VxApLBkFyU1yz5JHBvEzgypgh26Q/V9DrdjP42DWBGclNjQUpa9jsfdxuq9Lxs3skFE -oFcOKwxS3k8LZG2eJ0Vv7dXwSyzfolYSAmWYobv3ewWGKoeZaiUOY1hzqyfAtMFhkS3oob11iUUd -m8/YkArngRibEgeLkBtTclnexc4MqfgjhQdBQN3zsMpB4JVduyX11ozgwt22WmLWEhPL4uNFkasw -K5GsokE+un8pqdUoHd4KLXhUFNR2mPGlt1S6BMkYfb92b8AR2BBhc3+KzVJSgwTAw5rxdxcuom2u -Sr5oJARHSfx5/G3P2UxxD0ftljgnUKS45bL5VCL6zSDoM9Igpyx/SH/ql7MsXIQxZF2W0/DQ5YIO -kt1iiWmFSgqom3Zm6vO1vAhSazlQwdauo0tir7acdx53RgUC/xCUS0pCg9juEFIogZrg54h4FOt0 -F/UoEVEfoKJGKdsdqkfI4LYm3Er0CPMeFavMF8GL2wx1SycQYgmGmRIHGwE4ak68ZAuyoIjm/52H -UGqhTgDnesHbNw/O9jflNzeSt5tw3I6eB922FqZZHIefpGoPughAX0gJuyORFp0YQlQSMUiSf+wj -lBCIGQdQr93CaqEFCAvfPvupFMepuJToU8nHmC/3ChxHRFmNPF4jQx51tX6VXiAkt3Bdrd/rWrZI -rEasUDXlzCeOI+iMxDC28Bk3Z4HqzgD1acTh1J+mCMrCumOXghfyNvO+YNytvgW69RrxWaTKT+I5 -W7SoWCZDdoXekxpt6UNZGhmvk/d6mXRXfA7owWbfB8QVRcssg35YbtVOhAf3UiZfHYMbwEjaA+lZ -ojyTaKvXBKAovFSWmN09aUzNXOACbgS+Gy04VPSD6bVhB1yXY1QdjrhOILhqBR5s9l8QqVGskd8W -bsXq2hSEuf+0eD4XqXJqq2jJTjvMctanHOxNhZuk8EFLXGLCISWxISncawqgnH6lCxWGRMFjSSKP -oayCVO49V1UAqviU7EGS6QrOY2cxTUD0qT7vCNuozOE6LdlPWEludFFXCyFO4eBtxqNVazzqbsEm -+mnUqbeh1ndhlKkAQoLldJy6LYDCts3TsjxG0drjjapBdakUVT1oeqbVoPsiSlal6KiJIpwhYEM/ -7FAsieLag/D7pfTFmicueOWrkP1P+4z3NmIS1UVLogT8Lan73gNz17kPRCpkPMy2hNvX4lfV1Zdq -F2q85Gn1GCq8JvIKkOghDpvD+lk2slE7exPl4CUiiMdiK7xo21jtzIOiBXqnREfLuFpv1m4hrUgF -yZEoCmwr0Hla1QYtVlpWtVm2je8omLoPnlO4CNoUAPuJ0m8Coxew1WKNZ7/VcYtVhSBty31dVtGl -8CsSgG43dSzR88WZ5xpx28v0nV0PzVCuiMEt5nxJIhSuden+v9IUi/fc7YrCLl40h9NBBwcnWq2G -itm8gmMjRKw5Bbfaogh6tZgK27AyMahE8OAwGwXXGvuftd2ubpeiSk1VBCbvSwmH/v4k5xk1fcft -hH4f55mXSVoFjeULiKoT92xGIIoeOxSU21Utzugo6UqqGIStrXnWDrtij9TP6qH+amYwgosqDRkf -BLLbahEwkRNnilClZAIyuVQcwEE7vw/ARPmtCBmCqFSSEmWSCxKcoCkwcMQjNy1By19sx6mnUX7S -9PNisR+NuZJJh4Xo7+ucpPfR6yoUipSwJR6a1fcglB7BH8cnpsoMe1WCeyEh6FlMrKqjKqu0kTqX -4LK77bQw6jQCFmrG9nITzJSNIF0gjfLUss7ITltu5vHNPnzGaYHhepAQwSZSf/svXLpr/4bqzwpM -LntMHSd8AUIl52kY2+Ji/OhnmlUqAD1K/5AQ4Eai9EHNqiSws7ewGTCGFUAsEaxm/44QmTx6AleM -u8J22x6lDsXitWy6GjG8HDPp0KYChthoGrIdI6Elka7adZGK9IICmUt2cPNiOo4RWVazhpU8URs/ -MOWgIusG8rQuADrFc7ih406TBOAexRsLnV8oZ3/J1UteRdZDyURZPN2Ced6irArAQ7kGx+6eW7nc -78GcFKKhcDOL8En2MDYJwdvDwjKuIp9IjHOcVXKRhSGdtAp2PrIQVMFoJ8Exp+VQ4U55bsdhYVEF -TlXthUUSk5oIwbV7QZxooXk28xyQqFhGBZHNLBBVtmTcQrSGSLdqkKcWdJPNw9MiUeUrRs9y9jMX -hWyxXww6GOOz2FMITbVZjF7PsEZYh+bSClsQ/bSqx5Z/Ls6rKv9UcQZdGjSnFgGvTcjWVlV4bdWj -iPI7aWreDG22ycGEb2Dd8jzl8gPcie9htXZYAViXhGDNKSc/ddWI6SxWVCPEYXEmMCnkpQFFS8yi -UF2mMIBDVVtEn4A6E2PbvEISG9lqJl19c3FzCilHWmvNnMr9aZq1chmQ/9ZrSVXtCx6myvOAvb61 -8pUZkkLBD8tZyUaFlhbj9zkEdUpViEodQcY8wzutEZqx3LYdZqqnmDmOxNwxbP6oNyMhp1FUC2f9 -wobrNCj/YtkncopejF+3KJIsnrrhwQx/BuKhRSQAp1FRvbTk59yahQXDLlTcs5MKZo7i9qj5f7H2 -KXbKehohNAc0r8LLGKWEDUzX6miTVLduWvRW74JgP04ThAqtQpSrJoyLa+/iOtbadmXNnypcdRp5 -JHbztnZ2katpczDmJ0yhSwig1u1vZwG5LcQVewKqEGX8aiVaDWTbBXRrjN0Rq6G8lfi0Yf2cVryF -4JTuPm3/aKXHTxEex2VCNdpdpxVvtUDMRHAqBmI2qanp1WpY62EmgDBZFxHtWMMh6S55hMURl0Pt -Kf9KkY3hdcPPLH45vECrZ9vplW89uU+6Q2sRi9+w5Fl0i4wiIymRs4ZRhI6NvNm95OVsYG8ForeK -1/agDditkpIi2/3qSRMFwmBNE7uCMmW7Kj6V+SIKQqYKcu6siSUCdQr7FtPImkrtZod96PsTyg9V -iW3rpYsy21CsAn9eSOXfg69K6/NQHFNlJmc10m5bj6uVhZ7GE0zcVJUwCumuBiOl2tadLHAU3BG0 -QW2Q2xWJBmoGA1t4q0SfCa7i8OyOc/TRY6sLucYQ8kgXU1bVQrvhKmsv20dVjJB1RuXh6ojSKtPF -ABUSP4gUzBhFaSLXtW7mSO7VoalX0gSneFt00m5xALbqnFWrF1fyutrXxJK8Kkds7CBU7xCTJvtb -r3gzT00JuUej7xVFuC7G8LEr0Utd5DrEKC8DNRSCQzNQdNU4WTLwfkTVpm2tO/tsCrIfSIgoRVy9 -lh5/VQD9oAswYajMjKInVQcwraA7lwtUo17hcKIFNg+yaTjUEc3gueW5YnQJ6gA8O9Tz5T4eGlXO -M4bLFV0LUSa7WSmp1C5hB6KqQio6/RmpUY20loQ4Rwpyo7XO0ww24hmCbyW0h7EMkDwFXnHachqt -kbdAHxZ8REA9iiEb64DekoFt1+TEO+AFKE0EmgUecFiTkSKDjjSls0SjUFW9pWqAqnpYlD4u/mkU -VEpKE0PNN9s9zkHDgNCiKPsg00MShRCd8IVVG0nc5Izeq2LukTAyLmsuLsTpCWLtjh8m0xdBW/Y8 -rVn0cjfU5mLBHy5jCFbFFPxp2Lb9JKU3bkuYmqjdy1cFHF4M7mvd1G9sYQ2dJvuIYvxL8VUBWFTB -jFtquipNR7yOVononNrMn9lp1PN77YTuiM6Knw5QTibhpspAb2JTs9jBij3wIBlVbeYCoK7WPF4Y -e6DiVqfi41Iq5yXpbcUq1EQqQE6pEAsLu+SyTPI1mkeueaEjktVPMIlL4WcZ4nrzNYMhF6mgbTV1 -JD4eFe2TZEOFidcI/9wsYfIRy2mR0A9hV6mvbfVOJ+xoS2bTfo9q/a3b0ZaPrV7QzPI2U5EZJnKr -5tsoMX8lh3SHSSqY9xbtLgjEZ+BVWSaJrhikcuCoz5UabhFuRaLeHOJXSks2xcX6LXfWPiFeo2FR -hTOxOybgorKKw13ZdlqY8YNvoK1Fm5zU6B7pLIu/V23gKs4V+0JWZymeUCzdannOzeuRkMhAVUCB -EjlVYB15nlUDU2xURD10Wd7ocHT3poiC/K1lKFehTU5+DGVS6uEhJaMQU2SSMEjOw7HrEotedCrW -X7kqE0Pix+bi5psyMANNLgXNhNFuFI2FbfPaoevypDwxwlUxmNUqF2G7EtSBJWt2M2YFVqi69J4e -qMZVQTHzZQGH02rqLXq/eSMUydRVAZTNUwjU+So0xZmK7q2us+zMZgEbSRWxIS+kmBwS/VRRlX5G -VRXFqocAnuBguVYLAaqelRVieQBC3OZArNZIYmeWKBlcvRSYQpjdC1YULUFC95EEmzfmIlwYRAVc -iYAziAqEcCWLUa1WRqo7Rkmmsp6XMWxZWwXWuzqNV7O9m8Bhm/fyReMUQQqudS/pS2A4IBCqjrwW -zwRRhqCpS+oQl8Gm6uYlFwLpqnamBbbVE6OyeiT3mhDFcNgdoCvjfV5HFCpuHijRO6iH71aQis2Y -Le6GGNuqjXQ0WpzRqbtGOAgoQmLAUIoJ28tuuUexEDs8O/FRusPYwM+hfvLAZKJGJtfjsZkLgdAG -QxLjn7Js/HieCpHiRFulqrhR3xqzvbgEjYvEsjBIzrB4jD6paCuwfqbdzRJG2nQruA== - - - Y7oWaakA/1Ipp7R3KbYb6CkUCytOhsQc2C3D1oyawBZzjezmqmCGyu+tYumKwhxoYA4VGS2t12Cr -xFaml8rAmoXnVGTSjANN6h4ARk4Ltaq+7OO0AFSLhhcmo/YrzN9b71KekXWyKJxTLlC1eO79ZPfU -OpdIFWsomuaMsFIj5God+IQclWOksGhTIVykX4DOk3JAskX0ejk9o4iUO1mW+XKJMg7LacFyJFJl -pmxW0t47dHReCIzjCG3i5fkYcod80dMNhx4uP1Waq64ttf3Q6EYBr5KcmIpKFFLLtnkZz8YOoaAf -94WegvSYrDSqMU+lpg7XIXh8exTr4warI5AKV9BNCb7L1VlF6qqS481rTdCgURtA/fPBcsOesKRK -8+QksNi41txyV/xF/cK+9lVermW3VHNw1G6jjWtxZFk9rAJdSLC6MwOn6igIWOpuGb4F5bKKV/dq -dqbipiK3OSJ7sVlMifgAuWdV759GDWz7D+XcLIrrsM3B0j+Rpluiw0DNJVS9412S+yKbYkUlgZmQ -ZyqIVYtZ25tCdapVpecOwRm752p9UB98WM0uUcOBsDT3gHPw8nulkzVZiVyezgxEAq6oTEvtEFtT -849t9uzPVJ3R+rA3QzXIJjQVrat8SXCweEjCWSoWx1SXGA0I9rXJ5EQMln4gq0Ql5UKUl4FWxR93 -KNZXZLF/DNLQUta5ZZC6gofYhMm2GF6ATbpW40JrrwHd1HDo6NSLnWelIFy+rtY6tuBjCvqoRc5f -FSYsUooEVXOhHNfp6LyEo9W/oU1PSydmOSziutxmBE+sBqs72CzGSzrU7AoCxFGQtwlAQPQMl3HW -cSkZOGA+HZslzEynY50Wp84yqcmY0D/9kSXX1fi0qtnttGlu7gPH4XCq9458uyDki2t1ssNmHIl9 -RJ1Dh+srEXGR0jw8SMVmXL17knSxgAyO5l1UfFlgBugJ0NqjW4amomMZzbPT2uzp4VZBxL23ygQh -mo8hj87Wvj7WhKrGR7X81LdRStosqU31kGhfgQpD2xKzX9xqtQSB2riEvElBkTXUXrYs/s5NDVUP -UAiqHt+zWHQN1WdkhZdNKiLcZ9T+8t4CDlm8KUG3pr6aVVYNVEWqYLfSGAvCnG6zemSCIIypo9Mi -aeoVBrj0so/0wfNLMiipkzdgNNC9gMc0h/oVMf6c4ejvgv39oy80IPMY906H210NaOeBU6QCgYJ+ -Whxt7BcutZUTUpUDaMnRC31Yst4Umzq6Y7dWM/O9q5MFpA17U3PQocloOngJn39IRCKsJwE/VBfp -WgSU/RNa1MamIw7rNIlwiq5Vjd3ZNIpw7Wux4N5qp6nQGMHlPhMdaRj8xHSKNxkJlpXKmlosdUdY -vUSTJ1RrWBJAbnNzlr6KQaP/mDowVdaht44oVto2gydRdxf2hoovIblbfXT2zYJPgPaMy17boJB8 -vGpkF3aH92AK6hyzbjGU+TjLatUWv5nQdYnARPuyNdfoZjmDNwptCSGg3tRmu1tpFnEaFvSmgjlb -uxLoLBpVzqULN/kELOTLW6NT0IzR104L4eqa6VZAtxogtfBjEaKrV2pb47dOIu+5rSnjgRSPE28H -c+VwTAPaiaMyDRn1aurQXkzlFnkmDiKMQFu/Qg5RQXU4sosQPtQJulukRwWT7DQg78lx6fW3G6XV -SiJ4gcXhZnxT69HwexlKmUKIZUd/1yZkPsCGLWW8gWeyn4cJogGb+OtFDsSFu8y1okSOXPYUjAOf -QvIT6i8EhaJVBeZpVGHg7+GSWV88hcnXWpuWjs1xmgVWmrKcfk2lRJcqB74QyLQ69hfwrhQ5p7mM -3/VaVuNN9i+KbJV3IpiOTS7FpmMU7kewnPduiKA6tyRz5fa7myiZVTtFeR+GB5oESlcKrlS1EQIz -y73MKI/CKlCbtXOFqq3wlAFbOmqVpGbkF+PpQ2LBJJQiQd+38vy9mwJU4+tt0/1R5UMKfm3qWrTw -q8pEKUhtgoawXZbAmYJw7GlrMZ55c5vGDZWkFo0ttIeql0o9nULOjjzY6q00jtATp1lQ8ZLVrMDm -YV2mlnrAElRTxmUYzh893ldOAUdbnIVKBmHX0GUrMIjWDFOmeU2phQkDVoS06mGNaGqEFNRVMb7U -k6wz0BPLFo1ph9k49AqIRRsBeQ1R+InP3ZK5VJkvni4jRUKUB5lIzU+VzW9BE08GBaigbRsSlc1R -IIjTbRbFNBNoMfTz1xf78BknhTKX1s+xWLW85gPJLauLWJx0l+P0ZQ+vLUCyym06PGYaActVJUBH -8AgTwqwe6rRYyx5l70iWyBkeAV0n/kYq/k+Cz6qOcb8Y9CUvxhg9ZUHfiJ7ZpWywhl52nFYEhCeg -RCZVO9sRPgRSoUyByH4SNKAhkVmGxLxu5gulkZIk/gG7hywaSjRVbYgrJ/AwjgPGXJLttGvtNUKI -SokTtz6jGpOUPXmUVS+sZnXHO4DGNnvXtuqY00SpFZuliE51ingyoigLgTJbobJS6Nl6tfMrSMoS -UjSyZtjXJR/d/TzMm8njIqq/mZ7Xbyuz7vVwIaR5iJi1vc5iELSmkWs0p2x11JHNK8yBE9mrVe1b -XEvwFHqCCPBSl35EzAOHrTRn8hYGiHpSBTvXFN3eakSZlVqKyIVWrDCfi1uGMnioJeGdljJJhsqJ -LiXNrio/kioC+KuLBREZaKspC4nNV4d56ccR9DrVHHsI6L8LiKO0DAaHVGg7eK5UGqopVYBDm3VS -1ERbPB1+mDOtmosqI40PETRNzhwxQmmKtDUvC5L+vCzDrCyLOnhAD1UoFjNU6xQWM/XUkn87QvES -nFUBlFWNr0o4WGleU2dbtZyE8ASoTdqie3W/3LOHEUg9oJgIp6NRWpcYsHDWlU+bZ3ZaXpaNllSY -SXuCpS3BM3952bwhW4ZvqHKvzRoGFIQ+Fs/Z7J6Ee+vKz7k9kzi24kl43glxWw3R+WMTns40ewY2 -G/pSmeHLcaKQCOlZ6UlK6NU9iLSuBukpl0dnNm8Is9056gnizFTPYtnlxN11+u2rGX+YQSIq6xXp -VyXfV9s1aLOnTMhq4Kh6oTYY6FkveNw7ege+sAE9QB0vAhUa1aPc7STnQHmKr/tiSSd7VvqNTkvl -KbQmiWImhG6MGIzCltXg+NJFlFR9PgBWHWqGI/JlJ67sBbqlsT0Vi0TZw17mkq7N6Uz4JNVicsUC -K11SBr1oUiWPFkPuprxGyEUXyR2jl1bAUj81LxXZPfIIIsxYM1RKKYlQqxdSqrG2BLbB6XC0S6RB -qJC79wOw6WozHAGlEUOgmwUmQQjpIXRQSGKfgFdcjtupxnX/7iET5BopQ6pSlF+Td62SpBIVrIO/ -bQWLzXptFjWcE5tTf8fmEFhdQpK8RAKnSxfvTK+BC0oSexd4wR7YAi6JAVU5WT+O2nkhotpOv1vd -kXResQCXxYEixk3gUVn6NUBVCFCq+GU9DKSyOYVWBJA8bFgd44D0jqWuq+dwCWsoq9vqgMOoOHS1 -ibVS+1GWSLOv3o6t+UGwPyT2ks36ySUVi17ZPdnbVJJQRslmCRM7M6VE0BgMgty2LlbXg6C/tO3s -EntYemPrdNxiiAw4smBZpUC1hFlQ5UKmtlLqqyH1UHVqFiS143srXRJToOi58jhQwpTXJ6ZGjW+X -sHNnn2qLdAhYMlasu0ex82btAIyr6iJTYs+6BuJoHBcNAaZUA+1xc2jbbLsU3OPhyPlFS4vaNocN -LSICoCNVuerd4RbjROvA53IqoCGmezrxpLZbHWNbVbO+cFvNgSJ9HD92ZMrL4PMwPY2t7ir+0STx -Dj6R1StXGVKVZ0m1UwJD2e0gaZaasDNTSieMigzN8iOfZ5WitOpjHVLFuueeU3wpIRXgLdAVAeW6 -NW06NJxU71orWR5aPJAodt922HHaEwgCXyhoveVqZd5rREyqShi1H6r62dh1hWJHkXgS07uEvc1d -lS5VqwAkSgomyB3wrtoqKqhr6LPNb1L26NI9LofZFerR6cbO0iyuZhX6zc0p2qFUNKQYF4YYeKVq -06ZKCPeC3cGvvnSHQ/6j8CDyOGIXVlVS3XVOyaQMUioYHgFclF6YIRYwa6bnKVavW/ngDqS5getu -DNaAJyqHfd/RYDdI8yEBq2R3hjPDMXKYBWGlRn44Bb91o9RWU0gp4FiplFVfCXsrnULXvhpkuqL7 -ZggfrlIlbYcft1WXHCE5QnJGfYtDcVkdzGIJ5oXxFHQ78Q9JrOzCwXrjTLUnUp+x2lHKaSDYBDx2 -m+eLDYVAF3XW2gIV+jRKMPw42yRKukceGqAbdh/dNktxFucrQqKKcTvuuiSxSUaE/ewn2soBDPQi -+z5KqmGb1QDHOazmwlzMyxEmikrl1IBNapCMFFDGxMitGARoIk+LgU+ibwajo5DU1sPdQnV0n6OE -So0ozTTpIfgi84MWh2EAq5m4u6BFhFhHo4k3NKimpwp+ziEKvdNWYOSUdAPwyzLeF8sbaBVgDR0K -hRocoICbCXvt4WSg8qlrWDpWLDLrguNSmDYofHwYhfarCTS2AlaPkl9hxNzLCaQqAV6hYmjNJcoi -GNbTt4lDCO1b1tConk/CzY5qFBMdTt3SRHN7X5jrREWnhAbrd6hig7MoXguESF3XISO78cVGRbDg -CNy8pXmHvDe7S8AGcg0VxnmUKjlUzqydzBrNHIPbXFf7bX7Z4XV4fgwBkbMEGpArEklEqJIupAvP -y4FsjmKxGGzwVQBprKrbgpZgj1KkaqOWRzFAJILZ16QoQxCISDmMh2VbVaxL5OpA+VM2IUMk8gfW -ZNZfTeBKm7UytcOboi/DFMCDxgRRq1oULrniQ0ZcS4JVxyjqr9/ELelduxJG+7DVSQ+MLKVzcWIF -NTgW8+rNcw6JfWlXvnkYH1TVYVQSybIPiQYkocddCPyA4sC6PKhpxdoA6WUGIq10SFGKihQADKGF -XC/BJFlk9HSnkKKVK7YMYtaaNYtnUhV2VflYej8hCajVCNaKLUEBAeHHXOa1beGNkM9m59xrLwNC -aEgP1QHgIIZe/SjNmWYpMLUNJFZ9c3VA3pLwKQJ2b+XHVMbXvFa5WjwmvpyCAptUjsatWmxY+aT4 -zUhFBNZFJJovlaNdvtFuoeSK1zttvYNqs2KbK4WqkcToRpFvZfh9mT6LgJSE0QdEDEl4dYQ+MbTQ -Z7t6U66eq9K0amrl8aPula/aVLaRIq23Wkef3mKLujTqTncTysG4rPOY+CaxQZ0lXtFmDFZxlpxM -9aDQ7AYmwGIjZL1iav7n1drgJ567n7b92Fu9RTWy/Fh40fn7GDwgCVuHZFWtptKSpqoUYVOvsgts -L/NC9jgIGFli76AoqE4H153qjs3rL7SVqRZQ68/TfRT+4ScIa3O1IK8MM0yk0zlPOUp6sRnunAKU -skn4kJtXJtFyIsHRQ9bahPOo5oWcSoactQual2nYaSGs1uSiOhjKcZoDjehu6uu5bQ== - - - giF6bP1eUWFLfFWYtQ4xLoG6NdYhy42QUeJ1t2KwHQ6tskfoWjVXJMsxiiL8KxTK5i16qhg6FXeI -nFwIAM+IzvkUno6DETkkgP3Y2jbBjg0CO62Y2u1HLVYIQG5LAKvw/Khpr9jL66Mh9P42Ye791K8l -fG8FLejbwerblbiVO5m/hYcfYXKTrU5bROmbjILEREU5KHOy5D5K9n6Rne/RQ6tIMMHV1775Gi4E -AlZNdkSh1XjvNDJ4BhoD9bg5MW5rXr66NQ/SUtwsMIDiSTrWJrVj6iCrW4a5pGkJkoIzoq5SYC6b -AbWoWKhsRp6DF6ze6dVKfYmLsolQvnMR7wxBhCRlGbmMnhBDVCQnLFabM6AYVwuU6LSU8ryQ2tL/ -4mB1u1WW01Ep7gFRB1avfFLfUHGEMCJr8PZdaiTYvFH7HSH+nPFn1Qx9Z93QH3mxRRSfM4HZYhyj -+/dR+/2hVIGfPu4//OybXxqj27f/Uexj/+7X3/7jr76DXPBV3vS56MceVVEwG7/YyBBObGQIJjay -IujdmY2sYO5MbGTFMlcDGxmk3W82MiiKZzYySSY2MjtmZiPT1SY2MiQzGxmSNxtZgTdkYiMrYtUc -2cjEWfxgIyu4q3oIZyO7NcDMRlaUz3qykZVE2Qs2skLf+MRGBnjOm40M6cxGhmQvbWAjQ3IPw4ON -TNKJjUySrQ1sZLpWKxMbWUlss2Ajg4/8duGSjYys8ZONbD2PiY2MMNvMRjZIBu6rQepsZGASwcHb -WcYIQL3ZyG7D9sFGxnPNbGSrKJCPBxuZpGsZ2MgkmdjIkLzZyJBObGQIZjYyJPeO/mAjsxT9yEYG -4NTERtYFAxtZCoONjLee2cgIu73ZyAyjcmQjW9XeOLKRdcn0RVJqbGQE+2Y2Mr71m40MQIuZjUzY -FedMRibZREYmyURGppDsIpy2gYxM0uMayMiQzGRkVqfwJCMDqWUmI1sV1R3JyAh7vsjIyhqAMyGF -LX4iI0MTvsjIJBzJyBDcA9bJyF56NzZMq4WCxf5NRlZpQ5/IyGqpMxlZFernREZWo0o6ycgkmcjI -qkqJn2RklfKViYwMyUxGhuRNRlax5icyslrOBxlZvRXtm4ysChprICOr6iYbychqLS8yMskmMjJJ -JjIyJG8ysqqeiZGMDMlMRobkTUZWy/EgI6tCCxrIyN7f8YN/50oR+pOMDOFERqajRjIyBC8yMoQT -GRmCiYzs1tZvMrIiirVORlZUjDiQkRWB9T3IyAoVHiMZGXviREZW4Pda95mMDOF6nZ2MTIKRjKzU -5U1GhvB22zoZGYKJjEyCJxlZESXR0cnICtnDkYysUK3zICMroA+uWycjK4aU28nIUjCSkXWhZ8dK -MAoFzZhW+5OMDFUykZEVMdbsnYysCzoZWcqcjEzvMZKRFUqanmRkpW4zGZkEdetkZDrtSUZW1Ak/ -kJEVcYV0MjJ+v8jISi0zGVlRreZARtYFAxlZCoOMjItPZGTYaC8ysgLI0EhGVohaj2RkKRjJyFIY -ZGRF0GoDGVnZjjcZWVGMfCAjK9QVj2RkBRfoSUZWCPaPZGT3PJnJyMrV3mRkCCcyssJ2MZKR1WV5 -k5EhnMjIEExkZFXwxw8yMoQTGZkEIxmZBA8ysqIUw0BGhlqZyMheqi72OspYXmRkpCcnMjJqdSYy -MoCAXmRkCCcyMuEHjWRkQhl6kpFJOJKR6bSRjEwXfpKR4SlPZGR45RMZGXnXFxkZrzKRkXHaREYm -T/pJRiYPfCQjM5c8ycj080lGJuFIRibBSEamez3IyHjbiYyMR57IyFIwkJGlLMjICBHMZGQVkJEX -GVnVRjmSkVX6E0cyslqPNxkZR01kZOKlncjIRGH8IiMT5+xERsYoTGRkKRjJyFIYZGQCTxnJyGog -u45kZLVeMxlZJac8kpGlYCQj60InI6v4oiMZGRTBLzIyHmEiI6vbOpORCZvkSUYm4UhGJsFIRla3 -8iYjQziRkSGYyMiq6tUeZGQ800RGJhbjkYzMJsWDjKy2bSYjM5LkgYysCwbWoi70yHdVq2MnI+Pu -LzIy5s5ERiby4pGMLAUjGVkKg4ysqiVjICMTR/STjIyvPZKRYdR3MrL8NZKRpTDIyLjGREYGy/eL -jKzi949kZCyHiYzMQHEmMjJh54xkZAgmMrJ6lDcZmcKLIxkZgomMTEc8yMgIU05kZFUNnwMZGeg2 -TzIy4JcmMjL080RG1gUDGVkXOhkZ2m8iI+NmLzIyPcFIRoZgJCMTAM+TjMyEAxmZzhrJyOzNHmRk -7CMTGRn740RG1gUDGVkKg4ysygceycgEAvgiI9MHmMjIBPg0kpEB+PQiIxNW0khGxuKfyMgqEbIn -GZkcpJGMDE9lIiNLwbSsQxhkZJVKlpGMrK7bm4ysLsdMRlaXOpOR1WDXHcnIsLpGMrKiuMNMRnY7 -AxMZ2W1Dz2RkGIpPMjLJRjIyEwxkZCZ4kJEhnMjIJBjJyCR4kpHd2/dMRoZgJCPj95OMrKC7RzKy -AgrSSEaGH/MiIysB9xRkZAgmMjK5P08yMoQTGZkJBjIynUYkZSQjK0AHjmRkRXBhAxkZAd8XGRmu -w0hGRrx6IiNLwUhGlsIkI2PcZjKycqxvMrIiRNyRjKxQOz+Rkd2P8wkyMqQzGRmSmYysqE/gSUaG -dCIjQzCSkemAFxlZEVrbSEZWBNE/kpEVOilfZGRIZzIyJDMZ2b3bf4KMTNKJjExnjmRkuviLjIxX -mMjICvN3IiPrkpGMLKVBRla0RQ1kZOVsbzKyctWZjAylMZGRpWAiI+vSICMrIlIfqMcKdQMvMrJC -EcJERmYjOJKRdclIRjZInYysRN2rk5HpZV5kZJJOZGRIZjIyJG8yMqQzGZkkExmZJC8yMqQTGRmP -OpGRlbO9ychSGedRKKaRjCwFExlZlwYZmV1+JCPD332TkRGGncnIFGucyMiIBL7IyAgYjmRkCjSO -ZGQpmMjIutTIyCoEuyMZGcGENxkZ0pmMDMlMRlYSz2okI0M6k5FJMpGR6cwXGVm5yoOMjCGcyci6 -ZCQj69IgI2MtzGRk5TjeZGS20Y5kZMRoZjKyQZJkZF0WVEJFdewjGRlq8E1Gphk3kZGxzCYysiJb -/klGhnQmI5NkIiPTmS8yMkknMrIiYPORjAzJm4yMR5vJyArZtImMbJAMZGRdGmRk5dgfZGQEu95k -ZGU7HmRkBI0nMrKCv/4iIytCbhzJyLA2ZjIygoAvMrLS9gcZWWntQUZWEvd8JCMr5XiQkZHSncnI -BslARtalQUZWLA3Vyci45YuMTDnkiYwMybEsAxmZJC8yMkknMjJdayIj01u+yMgIts9kZITN93UZ -yMgGSScjG4RGRlaUfOk0Y8Sfn2RkhZqjiYys1GsmIyv1/AQZGdI3GVkx+NXSychkXU5kZINkICPr -0iAjKwROJzIylMqbjIy9eiYjw6GYyci6ZCQj69IgI0Nfz2RkRERfZGTkw2YyMqJHExkZwYs3GRnB -sJmMjBPfZGSEUGYyMrBsZzIycGnfZGRqepzIyAxZaCQjS8lIRpZCJyNTP9BARiac4icZmdodRzIy -mhEnMrJX4Dgiyt9LRlaju3AiI6vr9iAj03ETGVlV/emTjKzSnj6RkeHmzmRkjPubjEzSiYwMyUxG -huRNRqbvOJGRIZnIyBQte5GRMX0mMrJKFmMiIyPa+iYjI2Y3k5HVvTzIyOp+fYKMjEDwSEZGpGYm -IyPA8SYjQzqRkemwkYwMwZuMTNKJjEwBlImMDMmbjExZhYmMzArkBjKyyl9eZGTiThjJyAgrzWRk -ajd+kpEJ5n4iI6N5eSIjq8rIPsnI6Gaeycj0RiMZGcP8IiNDOJGRIZjJyIRj/iIjU3htIiOTZCIj -k+RFRoZ0JiNTR/VERsbgvMnICGBNZGSiYJjIyODdfJOR0fc4k5GJp2EiI2NcX2RkxMNmMjLQtScy -MrTjm4ysqmN8JCMjZPYmI0M6k5GximYysirEwycZGdKZjEySiYxMkhcZmdIUIxkZDzGTkXHim4zs -1vYPMrKqOuaRjAyV+CIjMz05kJFJlU5kZG8lHPr785GRWb7gQUaGHhvJyGqwYwQZWT3ai4wM2URG -hmAiI5PgSUYm4UBGZgd1MjJ+v8jIpERHMrKqcOJARlYF6PggI0OPT2Rk2oRGMjL0+ouMTMp+JCOr -qgQeyMgkeJKRIZzIyCQYych02pOMDDNlIiOrrc5kZK9v9uEzTgsM1zcZGdL92AYyMhm4rQ1kZEja -eTzIyCSVVRpkZJKoOjTIyHSt/bhmMjIZwxMZGRUkExkZ8cs3GVlRWmokI8PLmcjIKHwRdthERoZ0 -bddARqYCmYmMTMcs20xGVsr5ICPTDZZzICNDUtfrQUZG8cZMRmaO20hGRvz1TUZGIcZMRkY0eyIj -K8JpfpKREYKfychKJMaTjAxr/01Ghus8k5Ep3DmRkSlw+iIjI0Iwk5GVpDEOMrIiDpQnGZniFhMZ -GZKZjEySFxkZ0pmMTJKBjMyu9CQj48lmMjJGbCIjU5zhRUZGrGcmI1MUcSQjKyJVeZCRcd5ERsb3 -mMnICMG+ycgIK8xkZJoUIxkZeYk3GVkhwTmRkRHmmcnIuPibjIw5NpORca2ZjKyQK36RkfEkMxmZ -QksjGRm3fJORSTqSkUkwkZEheZGRlb3OZGQ2XCMZGZI3GRlPOpORFWFuDGRkby35Obdm7K0XGVkR -B3PtZGQYMCMZGQe8yMgkHMnIEKi4PcjIEOyEC0cyMoQnQHhBRibBSEaG4FquYyIjK2IBH8jIijia -WpKRFUyha53JyFQ+PJKRUYW4bcRAjYyMsMyLjAzhSEZWBP07kJFJcJVrIiNDKFzLICNDIDTRICOT -gFLKkYyM8M26rVeSkbE5TWRkpLteZGSovYmMjNrRiYyM0rUnGRnpjomMjHDxREZW5Kw9yMhQNiMZ -WbnWmYxM2uhJRkbSZCIjU2ptICMjJfgiI1MEaiQjK/CBLXuSkfEJX2RkCvyNZGRFxbEDGRkByhcZ -mbpQBjIy4qYTGRkb/ouMjLMmMjLU0kRGpuDqk4wM4URGxiNNZGQSPMnISkDSBBmZnTaQkXHhFxlZ -CVbrICMrCiInGVlRH8yDjEz6dSQjK6LPHsjIiqh1HmRkKLHKmg4yMpXb3ho7yciY1CjwgYxMMcqR -jKyInGDvZGQ84ouMjE1hIiMjhTuRkZH6fZGRqTpgJCPjtImMDFvoRUbGc09kZEX0raWTkaFRn2Rk -uLkTGZnU7khGVsr6JiOTcCQjk2BfOxlZUYX3NpORIZzIyHjGiYwMwelnhaNDm9RERrYKJGFJMjL1 -GD3JyGRhjmRkq+phWycjIwjzIiOTtz6SkeENT2RkeO5LLTMZmYTH1cnIEBTi9EFGhkCm70hGVoTV -3cnI+H0/UScjM8FMRqaTytXJyIqotGonI1OL0nbMZGTEQkYyMuy4E+87yMhWpelmMg== - - - MmIxExlZUa3lQEamEMKTjMx6xToZGaM6kZEVZYYfZGSKbGzLQEZWEqA0yMiwS95kZErrj2RkhWDS -SEZWhLL9ICNjC5nJyJRkm8jIzBJ+kpGVYLZLMjLs6DcZGWfPZGQs75mMrBzXJ8jIZqkW9BHtLk5G -VqIqayIjk3QiI5NkIiND8iYjQzqTkRVVOI9kZEjeZGRsoDMZmTL+ExkZG/ibjEzSiYwMyURGhuBF -RoZwJiNTPnkkI0PwJiPDsJjJyJDMZGQYJG8ysiKOloGMjMTPREZGzdiLjEx9VxMZGUn6mYys1vYJ -MjJSMzMZ2ZAecDIyipnfZGRV9DMjGZkkExmZznyRkSGdyciQDGRk/HyTkVGsOpORKa4zkZFVGkJe -ZGRIZzKyKlqfkYwMyYuMDOFERqZBnMjIJHmSkVVlPQcyMpJpMxlZFRvqTEamMsKJjIxZMpORqQzh -RUZWFIMfycg4cyYjq2q3fZCRVcOP6mRktN7NZGR1vT5BRlbL8iAj48yZjKxCKvciI6tgeE1kZLz3 -TEZG/eSLjAzhTEZmkoGMbDovycgkncjIkMxkZEjeZGQ18JmSjKwuQVwTZGQs0TcZmUo5JzIyPcVE -RlZFgfskI1Mj4khGpvL7iYyMrsQnGRk6YCIjQ+dMZGSUq73IyOSXjGRkJRpegoysOhjYSEYmhTOQ -kbE2RzIytU28yMgkncjIkMxkZFVAnE8yMjVYTGRklmUeycgkeZGRUXE/kZFRBD+RkSl3+CQjs3D5 -QEamtp+RjIx82ZuMTKiNExlZB/t1MjIRh7/IyEjmzWRkBN9nMjJjsX2SkRnh7UhGplj+SEbGLd9k -ZCovn8jI1G4zkZEZy/mTjEzZtYmMTJKJjExnvsjIQIScyMhUvT+SkdVgYRnJyOpWH2Rk1lwykpGR -TXmTkenMiYysKlQ3kJHBKfwmI+M5ZjKyKjNtJCNTOvlFRlbFEjWSkdHAMZOR0frwIiNT0fpERsZD -zGRkNCC8yciq2FFGMjIGeiQjU7vVi4zMOrcGMjIEMxkZkjcZGdKRjEwNSRMZmSQvMjLlrCcyMstx -jWRk6t94kpHx3jMZGathIiMjHfomI0sa5SQj41ITGRmP8CYjM+lMRkZyfSYjY5rPZGTqvXuRkZEd -nsnIhOc7kZGJM/5JRka6eCIj05cdyciYvy8yMlbDREZWNfUGMjJ09ouMTDU+AxkZNaITGRl7+pOM -jIqiiYysSic/yMgwNSYyMvKuExkZR7zIyDB4JzKyohT8QEZGoeSTjKxs60xGRhBmIiNTr/CDjIzc -0URGphDDQEb2ihh/znD0vxwZmQoEXmRkKhCYyMhUDjCSkUnwIiOramYeyMiYjBMZGQ2ELzIyhBMZ -mQQjGZkd8SAj07VGMjIEExkZghcZWW1XkJHV7ZzJyFC7LzIy1P9ERsbSn8jIqAN7kZGx/09kZJSS -TGRkrPAXGZnqSwYyMlWNjGRkCF5kZBKOZGQSjGRkus6TjAxg+omMjBK3kYys0VvyJCMTPphgIOMo -+QR7JyPrgoETqwudjExg8Li0TjQmyP29/7Z7LYZGFMfQYDWSkQn2+UFGJtlIRibBSEaG4EVGhnAi -I2uAKI1kZE2G+ExGBj71SEYGtv9IRtZ/D2RkKQwyMoDTQc8OMjIw3hVKGMnIhPNu4QcTAsuuwHSQ -kaVgGvgQBhmZYdiXJCNj4FXvPJCRCUmfyp5gIwPmXhbCyEYmIS3+QUcmAcaY05HxW/vKQEcm2bom -HZmoF3CJg44MgUyIkY6M2aJoTtCR8bkmOjKxMzzpyNiVJzoy2QUjHZkaC190ZJJOdGRIZjqyt/r8 -vdvCn0RHpvDXk46sKC0+0JEROpvoyLRZPunIlNYa6cgkGOnIdNqTjgzhSEem0OZIR6b2pCcdmeKA -Ix0ZtQwTHRlFEC86MiAqJjoytZiNdGRqCnvSkUk40pFJMNKRqUTiQUdGkmSgI1O73EBHphzKk46M -4oGRjoyhn+jIXl/sw2ecFMpdvujI6Ayd6ch03ERHhuRNRybpREemIOZER1Zklj7pyAiRzHRklcjI -REcG/tKbjkwtuRMdWRXv+UhHhi/wpiOTdKIjq8JCHunIkLzpyNQ9PtGRyQGd6MhqQGBPdGRy0Cc6 -MpyBmY4Ml+RNR1aF5DnSkVFGN9GRpWCkI+tCz9eroX+iI6t7Bn4HOrKqnMhIR6ZCvoGOzH4/6MhS -GHRk9jojHRlK801HJulERybJREeG5E1HJpdtoiOr+zHTkQkp9EVHVrViRzoyfPGZjmyQDLUTXRp0 -ZNxypiNjoN90ZEKGmOjIFPYa6ci6YKQj69KgI1Ot4URHVltguo90ZJw505EphjbRkVVFI550ZIQI -JzoywrMzHRmB2DcdmcKzEx2ZPLCJjkxZiRcdmaQTHRmSmY4MyZuOTCHUiY5MkomOzI550pEROp7p -yAi8znRkb135ObdnUsdvOjJKtCc6srIeDzoyGrbedGRIZzqyItNppCMrpX6CjkzSkY5MJ050ZLr4 -i47M0u8DHVlR3/hIR1ZE/fSkI8MbnunIyK3PdGQlKRRHOjKVrUx0ZJJMdGRW2vKkI5N0oiOTZKQj -0y1fdGRUzsx0ZHySmY6sS0Y6MpOOdGTFDO8gG6Ne9kFHpmqeTkcmWK6RjoxCwzcdmSARJjoy1cgN -dGSUoLzoyBKVII8qx4OOzCQzHVnKgo5MdSkTHRkVP286MitZHOnIMKpmOrIuGenIBqnTkZU9gEGD -ZozayjcdWbEA1Dketz7oyFR59KQjk3CiI5NkoiND8qIjK4qAjnRkRbHNkY4MyZuOTMGnkY5MoHAT -HZnNjycdWRGC9UhHxmDMdGSDZGAgGqROR6aK1ImOjNK5Nx2ZmlwnOjLhBY50ZCkY6chSGHRkXH6i -I2M2v+nImAIzHRkewkxH1iUjHZlJRzoySh9mOrKivrcnHRm13jMdWYmWgKQjE97ji45MgI8jHZnw -HSc6MvWkvujIkM50ZEhmOjId86IjK7U96MhUyT/RkZWyfYKODBU305GpQGuiIxskAx3ZIBUdGVea -6ci445uOTM8x0ZEhmenIJHnRkZl0pCOTZKIjs7d80pEVwcWOdGRstjMd2SAZ6Mi6NOjIioIJIx1Z -EfvFk45M32SiIzOg3ZGOjBK+Nx0Zy2SmIxNi6URHxu7wpiOT/zjRkVEdMtORdcmkDFIadGTl2n48 -05FZO/2TjsyghUY6MuATRzoy8t9vOjJDMhrpyCJDPNGRYZTOdGRVAA4jHRm9Qm86MpOOdGQmGenI -TPKkI8NgnunIJJnoyCR50ZEpFT3QkfF7piPDPH/SkVG8MtOR1e140JEJ5+xFR4Z0piNDMtORybV7 -0pEJRXCiI5sk6jTXiS86MuL3Ex0ZLdgjHRlR8icdGX7QTEeGZKYj65KRjsykAx0ZQzrRkQlc5ElH -pqqegY6s1jbTkQHK/KIjQzjRkQm6eaQjo1v6SUeGbKIjQzDRkemIJx2ZIFdGOjISZxMdGQUzLzoy -hBMdGYKJjgzBi45MwoGOTGeNdGS67pOOrMptHejIaLib6MgkeNKR5VFJR1a1kyXVmDC3H3RklUj7 -QEeG2pjpyFIy0pGlMOjI8O8mOjKhFzzpyCikmejINEAjHVkKRjqyLnQ6MiJXEx1ZjQzCQEcm2UhH -VtdloiPDg37QkSGa6MgkGOjI9PtJR8aFJzoynm+mI6vr+Qk6slS+eRhACRMdWUpGOrIUBh2ZXb7T -kVXVe8x0ZJQVTXRkFnLtdGREqd90ZISBZzoyhV0nOrKUjHRkJhzoyFTNOdKR3WbXm44M4URHhmCk -I9PvJx0ZwomOTIKRjqyKYvFBR8aXG+nI6lInOrL4PdCRhSjoyJQuH+nIKhbYk45M++dIR6YqyYGO -rP8e6MhSGJ1AVXBsAx0Z+uxFR6ZpNdKRsfInOjICvS86MoQTHZkEAx2ZznrSkUk40pEhmOjIELzo -yHikTkem1u6Bjqz/HujITDjQkalac6AjU4TtSUdGPcVIRyYg2ZGOrKo18EFHJijXkY4M42CiIyP2 -+KIjq9sx0ZEpQDnSkak26UlHRsh6oiMDU2CiI0vBSEfWhU5HpkqYkY5Md3vQkSk9PtKR/X/s/Umy -7EiyZYlOxUaQBClQtb37Z/HmkOP/Zy9mFmFWPIogCrKgyEa03O82QI8qCim42EtCwZEh/OLIEDOO -jM9JOLJVdZVxZAr/FhwZNUMZR7aFhCPbouPIRuSQAjSmaPMHR0YVVsaRKexecGSDyoIfHBniL45M -n1VwZKz6Eo5s/TvhyEILHNmgVyPjyDQ4JBwZVargyLTOLzgyE35wZEsMHBnOfxlHpp3BB0fW76vi -yFSKU3Bk9JD94sgUGCg4MvpKf3Bkam0qODKFvjKOTAU8HxyZQpIZR6aukIIjW0LBkW01cGSYZxQc -mXoqvzgyOocyjuxvt/mDI/tGiP/N+PP/xZH978OR3cNqojONTCVMGilDU33da+Qckma3W+cnFNnt -JSGLRPZ4PdMCkb1uq1Y4ZKInQ2IKDBk4ZS3rgkLGEePNELKXjouNIHu9HWcRyFjrXk8FkD2yq/fG -IoIeMhHqhOS8lhZ/gves/LELz9xr88fOy+q/Az82vR6i0MfMVW9s+BiMj3Zv9piWRfO9Knps3DYg -LvKY5nAtaYM7pnLe++wVOyax32NTxySc497QMX3MbFdmjk2PhC7kmNJSink5TUwWEwrwJOCYNlzH -+yze2Gkr2I0bW0JmWy1xwcZUrPxuhpjqD/sPaUwrGz0865jHAjELM6b+guuukDFp6lZejDGop/PZ -iDERTnu/K2FMFULYcARhrF+2e12AMT0g42qVL9bdXmnhxbT+OiPfqYd6CwkvtsTAi/XTelc2Ncxt -wApcTE4h7UwMsmNDxY56xY+FETviXYrmOq+PqBCxi6DvRoip4uc9fxhi3WzVN0Ksx8sTBDEVUrYf -fpjanN3OwfBhw8IUmx42zKq7wsNeK4Va7LAebLlggo1uLXkFHUZsXg95iLIuOe+5wGHqH1/DlmN6 -7mEtkoENwwfnvhc17Hd4jGnNKpb0emMRX6Bh3A0ufEDDSG2/bS5oGE+SyjQTNQxNQb5FDbPnrT+b -GmZPcp+VGqYHnoLfRQ2z92I+mxpGhvHod6WG0aun5e6ihqlQhWTdooZhPnXclshc1DDyocd5bmwY -T/TZ740NU+6MYsLMDUPkc4MbhjLbxoZJsOuWsWFSuScLGyaFxMbChlGgM/3DV7JKaXL2rQsb1vD1 -Oc/NDfveyv/ErdYyljxv4oZJtMC8c8M4SvcquGESzCp4Y8OwDieJ7OCJZTYU2DAaXG3AWw4JDRZa -woY1igbn4oFpncfbmKlhihOpDSY0ZbXpMXdmGN0WsqRLyDBZl8mXOYhhWJlpM+TAMFkLvE/BhSlP -qiExaGGE7bXpc1gY60E1wSQEgYr55YIbqDAl9hSfCVLYdUcsaEWkZAxIm4lzwvDhOQ== - - - +qL4xL8zJWxpnrMy0M9c9C+925oNMiLseo1KEJosg+RoHYCw9e/NBwvJ8WD68m9bcDDVFwFTTmyw -Kww8HQ2mf3sLOa1mOkdfNIPBLm8TDejV5a4nwQVTp4je/IwF44VRha1TwWTqJIuPgIKtfycmWGiB -BNNmkdY4Z33dcWESEExdZeonDU1m41qzBQ4s/p1pYKEFDEyNyBQiOwvspZekkMAoPRc3xkFgCiwO -Mx6wf3tUImPAFIttTcs236HTkalNaGDAsPTUaJ0xYOQUtPwJDBjznArAAwOmWcZY2wkDJpE7Gp5v -EsRHWRiwhvHwXShg0mhzCgoYgt7doIAhgCRP9pmN0P64FgZM4wRxzsCAfUaumL1UPkJdZMaAKS2I -C0VgwFQjQwglMGCy4KEdJ2PAJDb1oQcGDOceev4dA4a/j3nNbAwYog101r6vs7oNjbjs8bkaODMF -TBtUrM+DAqbNsNU4OgVM6U4L3CUKWKcv8V4QMFwoZYW6KIOH2y5kCBj7Xu3VAwKGoIxFYMAQdH7G -gCEuBhj/ooDdGWAI2iElBph+K9unYIDpC3OFgwG2hMQAW1owwLQvf1jxBNtLeVXbUWQGmHLIjO/7 -OOWiDlpSHQKmQqmugFqGgOkwGocDAgYPlhk44F6Qg2/6KxMErEWb5lJ1GXi2AwK2hAwBW2JAwDAt -oVnV8V4NR1Wcmne8Xh5p1sEaRylnNcdigMW/MwJsaU4Ao2xCJwfbqzH2nU8hgOnvEzBcRz3WVhbO -2fiBNNs4LgAYInORA8BAML+GCSPn03CA670AwCSyMw8AmATegwCASWB8zgAwHF27cS8BgAEO1mMe -aC97Is5ZAGCqUD6fdBBY4utd/K8tJFDQFj3UzOJVg0ygvfTX6cbLADA9N6xT1lGv2wEEAGwJGQC2 -xACAwSTQUjDYXqxs5UyU+F+61Sxe10G6D7pbAMDWvzIAbIkBANOHmB2To70oLMHofdcP4t87t9Zw -Uplj4b/Mu0Zp0c3/wq6mWcuMb9xON/B0/pcElhCZ/0U8T0WEwf+SgE9z8L84glR5sgg7mhnhBv9L -Vj340AXaS4Yy5HczAAxnX/U0r4POWIy6Jd8SEv9ri87/0sh3W7+F+Xe2wwz8Mv6r0/5CwYoNTghG -8MaDCcsbhrtN/zIN4rrzZ3USqCanf9nv0mSS6F+aP5hugv6laZGVSdC/tpDoX0sM+ldnF/v3qq74 -Na57Ku4u9C8uv2KvS8VhidIDX0O8voPI8C+8ibQ+C/iXXnyG8sB6iT7BB2f4F7scLSDWUbL86256 -xJ4zhPJOhxjwL4r3tFQN+Jd2TQxTGf5FqbOWGQH/avjYmjk9y+wGzfZ+C/yL6ICuhIO9MPw4Rsvw -LwwQ9IrEMcdhsf+Af72nZaQS+wvpvhf6i3+fzyJ/vQ7pyuAv5aqVrgvu1xtLdMd+6d+0WCbq1+uf -F9AvzFffO5hf8nVlp7SRXzGBB/ELZ9P3XsCvy/emmfdlhkqL9nV5N1TAvrSR0UOTWV+Xr2cC9XV5 -nCFIXzpHT14Gfd0emA3Ol6Y77UED84Xz1N+okSlfj3t8B+VLk51e24B8xb8z4yu0hfh648EOwhd4 -IyIOGfGlG8++diG+tMQ4I0BsT+vfsMe7WBBfUm02DMQXtlfTK7VsJU7tvRfgLotvqdR5B+ILWwq+ -mjO+OCJep2WaRK3CoMDXGV+4YV1uQwzji2KSs1fEV6MNNRG+JFgcKAhfKlRi+V0IX6jDP86uYjtj -R2N5Vj6bRWMmfOkH2DjshK/33WwvWgUT1Ut1EO653Zhy/q5ckLpw9pqt4Lxwh+mb5qVRQBv9KFyJ -fxeW1xID5dXAkDtzDNcGfVczqcssLz1A7+Mm0XacrgoBqmB5bSWzvJLqLC853TATOcuLX9JoqEws -L1RthBfLS4pF34LlJWUeT68sL3IRxHmD5WXZCX/8YXmhaHFbWF5Sta4IlBeGuZTVO6SrUdXxXAXl -tYbWdZQ8gsihOMprCQXltdVAednHXwncpYe8rR12FNbT0zN7Pk7hv+G+Kis6x/Ys2zYoiKcooZO8 -iP2pxi9IXksoJK+tGsnL4oKAkozkheGXZsRC8qKGlPsVJC8Mv+44jjtoZlC9krzw87KxwUleZvR7 -v5vkZeZh9F4lkpeGNUJFi+RF/anlmpzktZVM8tpqkLz0JowxN6FLbw1VYoXkZbOmRo9QhXnp9+Z4 -rX8vilcoQeBRDyXjSKC5dBtJAReEF08a+bd1nCra1AW2GF68jrZmTwwvqb4hc4YXyulULwIlnHn4 -RnYxvOz1vuZmeEkhrLUYXpgYvcH5CoaXvpufGUO24qYkk4LhlZTE8NpqMLxkaWUzWbC5Xo++FITX -exiUYInPNPuVAHhpq01gOfO71JFLojHwXXckPYLeJTsXLcwyvAvuxvvPQndpY3u0axO58AgnL5vA -XVgQnmOL4YWysF1bSNSuJQa0S5kDrb8XtEsbqfeyzprohFDelYE1kF0q7tWGYxG7LjfFKsAuiVyk -4HXBnTgczsUqzLfhhdaFieH1/LNgXaqgCyN1kg5b2KiurRmpSwMZz3FAuDCzzAIrqsMG5iUCd86o -rtvjpIXUJUNeEuEZ1EVN7WEvH9GGK9I9genaQqJ0LTEgXY/H7lZHAJZ3k8r81COgMc0yEOu4Gfn5 -YHRtJTO6thqMLj00OCwvRpeeK78ym9FFCpgIVjC6FNzBXWhBuvDgN7xDgnQxiDA+B6RLZ2KmXyBd -CnNQqb0gXeokYae14FsAg863Mrp67Cz3YRjusAzyEP9SMqNric7ooklGo60zurDv1TfKjC56AFWe -GYwudehRlheMrk9UN8K9/1NGF7s3ragLo6vNCOUFo4vjBt1zzujC0ZQRODO6ePpu2rCc0aVxlLdt -Mbp02a32IzO6UGfbiC7a5BVBWYguKewVC6KLu8hwGYguKeSKAtFFPMvXI4vQpWeHNeZCdAmC0fq9 -CV1/89M/Bc6leBoB8QXnUhs9aZcF5xKJClv2AudShNYCkAbnUhSFOOCCcyn+QBCnwLmkWvWEw7k4 -7HE2BmaYKu0lSZ7hXKiYJXjHNeENSxU7m0sKMd3C5iLUz+gabC6rFruuxebqh3c9FjYXKAFm9YBz -KebDhnfBuWi+pSkxwbmwfddTHGwudfJS0hlsrk7a83wrm0utvVaKEGwufpEFVR3OpctsS41M55KK -A+DCc0khmLzwXDh7sw7PeC6CX7qnC8+Foolj4blQ2FtkPJdUwmMLz0WHMQvGwHPp8tjAlfFcijBZ -0UPwuaAS3EDInM8lFCU+xIXPpUZA3u7F5wJd0Oaz+Vy6tjxtBdClkBXbgwXo6uxzVA7tgC7dLsPU -ZECX5gcj+QSgS1EtcyvJgC6pZlEdgC59c5w7F6DLbCyOpwK6MK4glR6ALhSLkzmgC+W6Kp+LDAL0 -1gB06UswRgWfS+dZVUTmc6nj0AcI53NRzcF0GHwuxgwW/RnQZYPkODehi4HUxgsndH2H4Bi9/z1C -lwX0j1YIXXqccBNxQhdvm3blQejqh2fUEqGrhz1tELokEK4IQheCYlaZ0IWo9zAQXXbUeS5GlwSF -tTKii6FUz3kgunRbKHMJRJdVzDytILo0jjN+B6KLOUhR9EB0NVyYzMNvPfPmECA3HUd0NTpwn2ch -uhCUcM2ILgqz7rEIXfz7tT8GoYuzFIbIhC6tUfD2CkKXvjRLs8Asfe7Zf/7Fx0IRyXdUPpe8SW9M -j91ajuoc3ICdzqW4pn5qgXNdXjC82FwSrvFuNBd1P/esZC6tfQn7BZhLk3YEtvRWi+9hye2E5VKu -SGv5ReWSvRJLOIdyXbT6vZXJhW/oOzeSS+/e4xgt6tN4UuYPketyA4oF5NKHH8/GcV1yu3xHpXFd -j7FbFozrvuPhdRYXX7VVEpc82ln5B4jrdX/N4HApJGZRzszhwraa7xgcrhaZ6cXh0oqeXGfhcGHp -zYwRHC5CkuzGgsNFdJPYZOZwEX26vMSM17MZwbdtDpfiC7whhcNF1OF9ns3hkmIBs+BwofQfDBed -XURVA8OFQsDVMFycxpORMVz4mmnnEBQuXS+LNDqFi0gBn5wpXBQCvw71so2DmsbZTjqFi+eWnWOi -cOGSzYgTGC7dDqtdCwyX4nSsZTKFS5aEo89/FoTrdSJRMLge7yEtCC7WIMM22Oy63+GFnwHgeq8o -Akz8LU2r8kdZ+C2tU6IYk0JY5WkPXJwTfOt5vdo32FuvrxwWe0v1ldQLZvTWGwEVJ2/p3xr4FniL -Csy3YLeiCDOoW1weZ3qRvtaaYnrV+eoXezx5spBb92UriiBu/Y56/+ZEq6CPZpSM2wIyPMaibalY -kYyv07ZUBua1bCuMKE2L4GBtqXxMJd2B2lJnloJombQlLwu9rwHawqbxmYuzpbiRlljZKgq89X0v -yhbsIRWaG2RLy5m39cLYUp2t9hTRXKMZ78QZzGbuaU0CGbClUlJMJf1xxs32nouvpX+/fRa81qVl -yTMWXUudRqdMJz2XSOfR35/NbC1B+M42F1rr7g5SdbLWU5la2HbjPuFMrWVMHkwtmtJVAbTz5Uo7 -8HYFUktxW3yAAqlFvbG+YSJqgTLwwBI16nT63QuoZRb6dy9ALVJfNIw5UItElmEHSDNp3X9XnJbC -RE9fMC0NQPKGcJYWZsUySE4orctXV0HSut1XNUBa2u4puJE5WmqmUF7DMVraeel9DIrW487nGaKl -mIoGgGBoPcOsYQOhBQx6PoWgpSSjVuahaRjW9Bj8LP1bo2DGZ5mDylz0rNvcOYOdJfdIPRIZnfU4 -8CjIWWoNpsbSyFnyxz8tArnAWY/XIgY3C9DzcS1sFgwYRVYTNUsoRr2fAc1S/aniY+Fwpebi+zgz -M0uxwtdobUR1cdC/+iJmPd1mkwzM0gZUXyl4WUpecdODl0XG6bgLLot8+tMXLUsnWYLfaVnPZXm9 -DMtSq6FuWbCygIv2Rcqavo1NoKwZ1aPOyQpgVWCyTvtmGZJ1TkvsByNL/77sGBLPmioZqhIhS1En -xdMCkKU4msaQ9edUX9nPwseauLEtOpYo882KheENeYwws7Fkf3CaHe9rRpR/93fci4w1rD05c7GG -xxcCi6UxU8uIoGJpShgGqlpQLGlUFDtfZsju5egLiTUeW35mIhbjtWhjTsSSXXT/mwoDiMW/21mA -WDqnP/fiYSmpdPR34bCU1DxhL28alipnDjyYDYalKeBMKCyrG8ooLAE/9MuChKUC2demBIY4mUwp -Kpg5WLQvXefiYKkOXpNsYLAY7nuBYIFnhrvpDCxMMX0Xw1T2vPZAFwIWae/jXgAs1gh66gKAxaqH -9yABsEiCEhAIABYZKo1CC4Bla9BZ+VfGUrs3/goiDdvHjL8iD04SM/BXel1trRb4q9Z8riv4q6ri -NNdW34bzr1rUJRX+FSqRyeBfoTApBv+KdZjF8hP/imWf3rLFv2rU9r5j86+kWAlh5g== - - - X2m+wxdv8a/IkrMZCf4VxR9EBTP/CpVoseOvJFCYE/grCVynRL8CCNW92NWSwMrBzoy/knJbJ03C -X2kRQOJk4a+kWPwm8jNaOzw38IaEv6KP0Hr8DH9FnpgH0fFX2EEaBWzjr2IDsOhX+NJZBZfTr9jp -NW8PWfQrNipEjoN+lQLvTr9SFa8VymT6VYN30u9Nv0KZbcOvOPG8K/tKooUHHH0lgane0Ff6p0VE -MvpKcWbbKgX6ioCJ7Vwjyq6mB1vJJ/SVVKuX8678BkPGyzusG0hetfMXfSX1oD0p2Fd2CSl1aRH8 -uS2antlX7LKjyKKfFv+jQnyxr5ZHXWJfUUV3X32zr8hEUD4d7CsS9zy3mX3ViHG3d7Ov8EbiDwX7 -CtSL5pnMvsL3UGv9YF/BMiEuEewr7CmJgmT2Faa2dqmjuyC88xb7Sm89EcOMvsJo0mprnj1orW2b -9YLK58Cq9jb6SqLl8iO2aApF6W4hVU5c7CtUXtFgX+G9ZeOps6+kWJYjs68aZkCOQbSc9PDlxmJf -6f20z8rsKyoZGbqDfcW34JEJ9pU1/85R2VfEii1raOwr6s6twueNUpZugbXEvtIAQIFpsK9w6KVI -0NlXRN50/zL7ii2E2iqCfdWiyyPYVw3nqfss8CuGG5bATr/S64lLYeCv6BigHibjr1CJqQf+SkpP -8Cv92wJZiX1FX8Hrp9k0RfL2bZt9hRIfvSxvFKQinRzsK0V0qMMP9hWpOd3RzL6yQPQzFvuKXhfN -fcG+UjrK8nCZfYVFIBNTsK/cWbYv9hWcal6YzL5Sssx63NYWU1bCLBGCfUUGhehOZl8ZX/V8N/uK -GLnWqNFUoT9pW9HMvqK0mvcu2Fe0mTBrBfvKoNpvr+wrskLc5GBfWZ7oeDb7ijO54Jl9pazQ6X38 -5s6oL62nNNhXzaAfo7Cv2hM5sGBf0ZBAyXewr5SnsBqNzL7iTJ7+YF9hDMno7+wrj+7+U9BX+hq2 -JAn0VWeBdr8bfUWyNsp0gnylzBZRpUW+IsvIPB/kK3KFVuGS0FeUbL/LIV5hT1FRGFoCfaWclN3s -jL7qDAz33OgrXWcq5Bx9RVORNesm9JUZdzQf9a/TupFsNXCuh+6NT0rsK6kWkDX2FX04FmN09hWK -r1oW+oqUMPHeQF9Z8sgKAQgA07nAlJfIV/rZPG8BviIxTDbfwVdKM7KoyNwrJ/a6pzwta3oYKZIM -8JW+gA3oGXyFSgFuRl/1VVoa6Cs95bZtCPQVDWc3PgkJfaXEKyujIF9hHctoFeQrAOXmVrGcgZSH -VXFtcK+4rQp/B/eKVIVWAZl7ZTab9724VwTJtfwI7pVGa+b0UkCpuhlq2a1BCat+LdqDe6X5nFKI -DL7SBoOI/CrjgpB9vAV8RXGOwtkBvmJd/8zFvWo0abarcK+wTMe5wrlXWunxl4N7pUFCo0DCXj2r -qNuiDHeMQw69Apl2nQV6Jc9prSSDeaWYAVlLZ179Rm3/zYjw/znkFSn37iirhbwi5R47UnMueT1Q -HMgrhNtP3JO0WnYX70oPIdcyeFdUtLCzT+k9HN5VYRi8K7N8f5/Fu7IjrrvwrvgsTd7Bu5LAajV4 -V41WCGtjX7yr9nijvOqMXu+rCeCVBlv2lBl4pUGf5UMAr/TCE2mLTlsVVRHDzMArTfrEVQJ4peIM -LnUAr/Rmn9a/XN52C7Ib74oqjMtwSaSlJFAAl3lXiNR9eVwWQeGU4F3xOYbR2vybjtPfXLir3r3O -KUhWnRDx+RbeFQZUmXfV2Qck3tUWEnZpi867wm088a7wdP/hXWmIzLyrPt0xNXhX+Aorypx4V2hQ -pJx3haAZM3hXEjTdZ9xVx/T8aAt31VVhqFVw4K4ksNfIvKuOGZw12Vu5wvC1uwOv9r8T8GqJAbzq -JG028Eou4h/gFU7iGXgl4+8CvFpCufIhBvDKXNI38KqbnUUBXuHVnoFXMlL/AK8QM/AKIQGv9O9f -4BVaAl5h7p+BVxI+wCs9LgV4pRtGxiZQVvj/M7yf1QmN9EAcdPjmJHhXtNI93kmy/JJQT2dZwbuS -QvgocFffofN/OCX8L+GuiHYR0U64K5b+mvkCd6V9KFG3wF0xR1ordJpJsbgZi3bFv8nBOu2Ks1iv -JdqVRIsiGu2KOCZlm+6gQFySCF6iXVn21awsLbpyPA4Yd9pVo1CrP4V2pY03O6ygXRFjpYrHaVe0 -R+lBy7QrRP3XoF0hTGNiWbjn8Hxqxl3RLaMX23lX9NNoSR7AK5Ig04FYkT/UDpzNhgOvdPFt8ePA -q889+8+/+Vgos2jtDwl4pWz8a0UIDrziOG7uvQydfDFUgFdSW/Ty264aj9AQaF05opU78a60c7K4 -bvCutPxlgb04VgpzeuFD2h/ThToyF4uiT3x1gnelHYDVi2TeFSphv+BdSeE5W7wrKVTgFN4V3dIs -aIJ3xaaT6xK8q24ey73yrtiUU0QbvCsqBcl/Be9KOxE2CIV31V7PBS7elWrSPMLmwKutZOJVUj1/ -Tgs7S6pAWTH6XM8/hXiljed9B3FDKnVxFso35JUJwz2MFttlqQG96kdsrgN61Y/o7cjQK1SKtgN6 -hcJAE9ArKfbtM/SK7VqfprrtxeHRXIde4UdpAcgEveq8t6enACzacXmxV0CvkpIqGbYa0Cv9yVOb -pwWz0qWm8SUzrzBDYLO1DlOwi8HXmVdbyMyrrQbziuq92y3gCH2rHWfZ/AXyymqqpxmEmTGV4mY8 -n3G9tFuzfrOMvGqA3JvdYbpg2JB7wNkvivsbFuQVYVnqjQJ5xeaLuG8gr8hEICTiFWJzmpXVM6sB -7nEHROuqUsOBNaMn4hWB08cOs1jCCJ8v513ZEUf7p/CuQLCcXktrpX+Xp2kCd/UdKf/N6XlGTWKm -Xc3YiQTt6gxPsYBdne6CVFhXp7vnLtQVa6br/meRrs7oA82gK2xBfFjWLdFZ1xj/LMzV6a59GXJF -8rzZRKFPJLB/vhtxBU2IQTwRrnBLoqDQAVfqQopyMupaDMnX/yl4K+3+yQkF3UrC7dAqQu6nr08K -20oiDTKBtjoj5h1oK/211z5mga30Ov2zoFbnYRHUxbRaQkZaId4baKWEs9aGRqu6Iwq5cFaqpVFs -1BRKGu62YFbva3WCGWWlPn9ywuHl/ni5QTCqZE2jSEkGWUXPfWjXESaFjrG6wiAyYayWFhgrlYqQ -LAuK1fNEAWjaRlP2N/tmWBHlJRwTDKutZIZVUp1h9T7RIehoKl0ma55PBKvHU89LVG05/c/Br3qc -6lL4VU8EqgNf9bgpSMCrhJunUSPDq0RitdCcs6vUlWN5Z0dXKURBSjaTq55I2Dq46rkiQ+6UKh4E -T+gsbJUqzi3f7tQqGfIHvskKApaQMDVb9HSggtPmGurZtL8FE61jBVgFZ3W0Ld7evLd4VUvIvKol -Bq/qcW+ZxaF63Ua+4KqI8j/eKLeKBKzVIXBVW8m4KlObGdS6o1SPqmDHUMn0wdLOiVZ132F16WIU -xy9W1bUcHROq6ho2vTqoCotyt6rE0w3uwRPNyX4xxT6gvTwoVSr94m8FpOoaMb8kRtXlLsNBqLrc -rG8BqtS9yk3KfKrT/UKWeA5v6w061RYSnGqLsKlON4NaaCo5yNKFlMlU+gKvVVvT9K36rnALBUsl -geVyplIhPuYgynbpjOx+MKn4Yc2qnheSihqGy64HRcHTiuMWj2r9O9GoQgsW1ekdWqt9VIZUBNxz -l6mufHglsUFUtaI58DiHSpsVnrFEoRKrWJHjYFBBcj6ejZZSY5F9bCJQsaGzIKirSuF7aUeMVUvJ -r/ZWg0CllyXKIwBQWS+4e1YsAJXZ24y5AVSr2GchqPDDtqVVQlCZn447BNj2PvK0hUFF8iiLKqVo -zV1KmQ3VC2OmXhlBZWoPdd5+nPsD25SAcoXyRJmJ2UEtAhUCNatBoEKx8odEoCIfzNjuCCoaERi1 -A0FFZz6XJ0OoeDdYPAaEigZAzgwIFW5bZg6TIFQNjwy3qrBX+PWU34JQsdvSz04MKkzsmreYwqAq -ij0MOs9bgheCqr0+bQeCSoOBZcOdQUX39O0cQWdQ0ZvATQsGlRTbpQWDaiuZQWXq31LuXfsS9xII -BhW2GI/VZi4GFeU1WuMGhKpd3lMaECrG1dMWKQtCJZH9fECoMAI2KJWt+zWWU2GQIFQSnzEXg4q3 -TbuKYFBxgK5tZlDhFaLlTTCoGoff92JQaWPKHJAZVBLZjwWDCtNulfUEg4q2fk32mUGFqHVXQKg4 -rT3LopDPdc/CvXjSLPT3fgSCij0KxbojengPmyQygmodtRBUjXnK/W3c09n2kptBpdnYquH8GJVG -Pi18bHwkQckMqiUGg0rLCOIjQZeiO79dBUGFq4Mu2jrIvRCCQBX/zgCqpTl/quGw+Xc7gz/VIqKf -+FNox7vwU7buO9/AT7FQ1KVJ/Clp7gprXuPTTcKDP4WgSGLmT+mTeTWDP2WJ46tvsJQWYeYFlfBT -e+Bdh11RQxj8qaVk/tQSgz9lHz8DP8XSWS9y5k/RQ6eAXIj8Ve2pnD+lsLFt2jJ/CuwUT0zwpwiD -No902yIolMyfMnGMxZ+inFLL3uBPWSzpeQp/iuYmfAyi2V9+ps2Peswog6xU5k/hnjHmwk/xb83c -gZ+ia+k1itXCT+nGsUt3/JSuG+9q8KeWkABUSwsClQXhgLP7CKeVFnVHiUDF9EkMMI5SsaJBo220 -X/9OBKolRqOMQjpUagVbarUvZgIVT5bKVNZRqqUAi+sEquaedxlAZXiFa/Gn+PewX2otbDoJ24gE -oELsjpeicIByd2XKHUBlvArRchKASt/ITot9ORk4B1DtfycAlYkjuYISCbjOBZci4EUzbiJQWd/g -nY56/PcEgkoLbRapGUGFmahqgwNBZY3cbSwElSKBLM0ygkpHMSw6gopw4dsWW4oSIblCZQKVgsjc -hnVU84LKIFAtIROotugEKkpSpqWMCTjz1/QqZwQVCWttsQNB1aHNG6hqmEmxAofnUxBUiLpKgaDi -c3R+MKhW+VNmUGEY0C0bxaqO6h0Nk8Gg2kJiUG3RGVQ9MjtBl1Lwtwi2THps6A1RcXCy88GgUm+6 -tbQlBhWiLmtmUOmzNDAGgoo1H6AvZ1BtIUGolhgUKu1uWBZsd049iVpXHGsm5Y8eLO48tOcVRwjP -XTBUSwwMFaZ1tJe3WEu+dhkyhoouBQWsAkOlLn063mNSO6yMK0Oo3mmjbTCoXjcbzgwqDfvaXwSC -6vGa26BL/a0JzZIyEagUQmZZ5JraWZ09xbYmUae4DM6bGstTzXFT0+u/C21KfTjdHa2m3UFWUYGa -+kRn/83Q7/8lTf3vI01Nau6UjNNMH6wpqcDblirl7+2/DZlGK6mqvZsKXVlH+EOookhVPvQ7DNXb -azRC0TJ5faf6JA+6uY4NnZpDpc8QE5tNyCjzNYUMuB0jAqOKcl8/rxm1XBrdQJNSnA== - - - x45iOTRxVrntkxZ7amrfJ5hejzjHBNmhqg5VVT38bK1T/+nPueFTU+m2aQxsFs2DeQy65s1OZKi5 -WRA7QWMXfmqoAEJFH6LGsq4X8JCLb0RJAFTNy29E6NoEKtpTHlMNQSXDptPgnA6hasbJk5IoVM0o -cUA8DUOlyQYY5hkcKhXGDvubgR/FC1j9w+rCbXw+nBNxR7WR/AeC86HPsX9zlhg7eiTiGO1NRJ4X -q5R0zFZ623CkpE4HAiuBMdUk3C+rvRXAstkvvjaSSjzKc7ak6nuB7xtYF+n/aMsr/LAQncvUE1U0 -V6kEplGGQEZquNFzKaULO4sSxqpSlTSSSA5QAgzEET05g+3xbRzbBafSV7vFmAabbDzHaThWOZhC -p1rCmehUIaqPRXaC+tVjCJNLw4M4qtQJy3o8FLuuSjL3lo5TCOtggn29330r+Y4s9VCFy999m/Eu -OnNF9/ri+qm6N7hVAm13YcJ505Q8HFAYmnEcg1yF2BgULkNXodird1mLBzxz8dM7dUBxw4Ny3i0d -/3JcB8+K8YBGAlWdHa/9zdVKOlRfNajZea3Fd5DVEr9GAxEIK/DlbQuc+GiG7kmdVPTf4mRMLyiU -ifIaC50ogyrEKmOmXLek2HdgX/WaUsffmDmtHmkqPmHP1rlhVlMNEdwOdYspIj9fXHPsAZAV6MSm -yi5s0OUkiZ892E4LtEo7uVpRVUmt3ZyU9+l23xbLaj6XXXWptiCVnXQ77UzS/VKGwJWcGSyr+Tbj -BPMsaicxcWWDbus+v1PWGIeosNHvwTCLN9XFaInf1lR3wDlsxMFicGpl2G2YDZIV2kWjb7OAJgpj -hNZAetMnEUi9URqfA2UldQjBKogyAVcp8GdR7DIrm6inTmdG9mq+1Ac8fDNWpn+H/D+6eLprkKy+ -9/E/fp+nanX1MmOyFSgrqX8TjdfPKG7DcU/Uz2gLLgUY7zLZ+i8/s723V/IoHz8xFribKwLo4Ln4 -+pmRBZ0CMDi7FMuYKX+9B2X6fkY7Cl7TUDixyUqLIpA4zgzAbyeP6Ll8ZaV1W7b8XtRkpTDf17xs -sbNBASytKCC4EKEl38faP1cH+mCDcVvgDl9IKcyvKLQxSxn99UKqGAgUr9LjpsIQ245QLX1jVOSV -o814vdqxRPxryp+mXVYrReh3QuA7LkcfsIJYyr2ZV1vVkkMR3inKwYlf5LRiLgYCzTVLsb8pN1NV -pe7j9BG3fRbj7lZE2emxEFviSbqHn6SHBfM8Zc+ntp8vBns9D1oqzRYDXY2Byq2hjMuOI9bJmXx7 -VR3GnlsqnFqYCooPTMrMbPNtCzj6Ih/78BV1Hrx4KuvSV7u5cQokqPa/uR/sVnrfXKykvlbsor9A -jXOn/EVhA3Y+R1Js8aeQh8qz1nFKKmhx2cG8qVByKe3ZjKyknkbJmviDO5ODEMxUb6F1yNwblaUz -/441dzy7QApN6QHdl0z+6YLD40AYwKypLikh2PVNCChMlWqoy1XHESuYMs6azt1YzCypLNMoy9Xo -MjXRCCSvK4nj1pTnxDvMq3Rhs6RSaSyV8iYpfwtLK7+3x/jCiOE0JdBZUp/DvU8poEJhgOgOakXR -PoEVrbuRTDoLVF7WvOVXQ9JzOZEFAs13uIwJ87RudKu4WwCtU6EBRqLXvdDPU6Vl3mJBfcZJ3fpl -ymq3lNpac1Vv98mSZFo5JHHZ0xhTNkwvjBZqDK8sZjiz+6CMGR6fPocPyuHFd1KSQKrrcoYQgVc4 -fLdHIM5m5HISaZG816/6e8+nmUFqMtCZILDX6HQqPHsG4CWIWlLPu1ubhVX1Sun2b6KB+nfznsaF -1EI9jmSqiTKGv8inf9IlBCkmnse+sLZJxJzKb8nf8NwZ5uiI3QodWvf6jSae7rRwqhSGlZjymMT3 -5OHDaBUKZzIPj3yc6qPB0d4eslbt8aWOgPPZcC0d9reyeVifUeRxNofSd3W0U4esPdKtWHAo/EnZ -fqqXbh83h+0TO8Bc0ZiWMu7N19qqfC1ewqjKWVPq6fgr/aC/D25b4cyDRfabjlPeY8pCVGW1/cmK -qs/Damur2gRqm35S6asxR3V9VHRpJL01foUSl/bShJ2OU2HEY6MCBtgn5qmvOZou3haqTYLDYmAo -PC7dO1+lWNyhj43cOnE+vc191q6tlo6n4rpSFIWUYuO+PissdfTdZn/8SVOt1Km96TNsiiX7bg/L -+W7FzsQj7czHacS9bU6iMjApyQdjqyrl1yb8VG/wYQa1jGr6En9rq2crnNgJI7/pOG1ztYXUH8DX -KinJcX2rwy2b9RderXn79GXhqXbSTnPOuSlcehBYqoSoncVUrEkfdaZ/aiIODtdW1QbNjZPLYH+s -AIASjxO+nxI1odg3xckyqXpx5uH+zThNneK6Dgv1B47rpEX4MgNj1kFSrulN3Pab1RXJWkbHhLWW -1Od87SXx1+u1TimLgN925kM30NGWydhJU/40nJ79yIG7x8VS0pCS5DKeLdgL/dhWNh322p01x2c9 -EksJDmDMB646ykSDJ+s4LVaZhPUn797MZ3PhufgiwJ+lMsApuI1rBiaXA2GysH6ujegytZvhOqth -TpS/tRTr1+BX8rXuTenSxMTKSNcHm1lNvqyMQOLR6LYUdR4EqCupzSpPT3brt7l/HPBiL4tlhLDu -yKUEQBw1KDCd7keuVpPRLbrVgGD55vKU+/gzjVRoRD9xG19sLE/bK5yK5vHhofxXbMh7n1vVZorY -i54y21YvJQ8DW1WoHf9AOYcp2tenO4FMlZpdmmlVbRbIrqnYGysb6ATaMlNyTGoxLOrUc8bSeoxN -7ZqYsGlckVG11l1ES8a1hP+X5eXh71IcZYYbL4MKE61Wqn9rU+uhDHYXIoMpogIcKEwR/m6ZMkNx -33ypdKl1hXTJAVKwpWshhavY6dB4URbHa4q29NKX2SMNpnTkYZ9Fy6V8wbg4qtN64i8ODwrpnR1s -DIZPEHIz5Oofvknv49lMrwkgina3xzYjUu5Gq6GXALJP01MmZaG9pNrKSlmWeduZvEod06huykVI -SFVgYbwzBdyap43NrFemplnFXzuLUPVWKmnFflSvRmC+tLHpmhmV18TFzPh86gx0yk5Sno0H2CqV -XbJM64/PvY/3+c3x2ORAoXH8xTFtr69wm52IO8vViQLakw7NSz5lx7VhX1K7xeUvqxaWMghRHbF1 -0Nh//j2wnBl2RlJPhTTs8xqHaUYdQl1pUwwO5XgQlkPU1DJrKkAtt9uLgIZKY3UrX/fT/ftyGvFN -WbAvqX87o9PVYcex9JcC7WsK98IyGRv0N0xDAVJcqHYV4ReL+q3NgH71fH0VKyFoX/oJjOv9NUOC -ifmbFtyPrze2ot7sCFBvNQr7JxPcYy3AuMBNLCS0Qw3FNmCHBdn3cRdUmpNnxeJQS7neDQLb6u3V -o9rM3XojVEUnC4upbpui2OXRlkJv+T5Ol1FLW7lsm6/qUuYGgW1ViGvt46b6bVk+Aq/R9vGwLZyu -z+oTQ320G1VdM73MAApVvSGFxmXF9LlidJL0faYNlLdbDqIQj76bmVygsAZX/VkwvaTa+jpsqvRl -H4Y3WZTqON0T2zeHYk9ejNjrOBxZiSJfTjlfChco4r1LfRwhb39Be/rXrWO0D28sRUP5L48vsznd -xymISiybOjkPcdr2Us4U4WehUOhpz9VNppgY6q3FNBso9W0v5T43E2yriumo5fuxvR5xXD1PqrjR -xDuwB/DVitSHqLPUo9txf8+pH8e9PLsnsxQc2ma+PYaQbkwwlIfE4bCkF2dqnYOywp2yQdSkOtR4 -IiaYrqJlAJu7zWylP9s+d6uq8FCuQi/F0OSizAmwr6nlnEbKEP7Lx9h+yyNyHYYJrjJk0+vjtzLu -yMglrVnSQ7nQMTU2C0yDW7H66QYZs2snnXjslB3dxyn3r6FZH988GHwq52y/JqYu1Q3cp6nY/qEQ -bg83Mc4kUTjbJoOh6s1RroDlupRLyzTLZJ32N++Xn903GWyeR5x5mEnTVLqQZN64jAy2FTUk9TWI -hNousx+ZcqnUrKdsLswvxeQILC3FLq08WFo+rlHF/5I0w6BYoQIyhBYA9pWINnOWB74tnzeb55OU -g7Ngnh56rQOVEQ1A2IQL+J6cCCKM2CYJvsOt6RV4/xtd5lYsTjrjiXV10DvThiVFhAlLyrXdFrba -vcZpWJrN8szkV7TTey9LWrYVf2bla4lnS7och1+vflrqBEXLKNK+sXVF5aLppzOTHLSAXiiXBcGV -CRiWOF+8MKUOXlkhSMVHSgkA/xbd8m1JOZal+RablSUOEktcCcp/FTzP/7bvOW38TkddFAbooy2w -SqJAj5Ki98EMm7RGz7nV/59/nGUZI2jCurMriTQco52UsWFSWz3d/3Iqssvr9TdBWGWDOiWn0oGh -xKRtOf113BMVFvM1YNNWYgSxCSJUFVtSDSZGBMMzaGlNhKL0cYGOd5HDlOuzUobWHSIvQy89YFoa -K6un4MjfotTXPQEOUyCOQbyrZ0fgMJ34amXYVVgR4LCTKnzt5bVJV7P4aYhmm+LI355DdntarIRi -m9AZC7U4Lnw8+u1k762c50aHbZWGXgHkp80kyksQ2Pr77IdvpZBejAQnZk9acmva0KR6UoOrifdw -ZPM3qh3x7v8pQIzNpLIjg9DQGbljj1ZKpe2N4xQQl0JhihQSeihrh6BqdcX/BqknXVoZidmy/jGv -ZF1+ynh0ERdADJVVwTsjHNU9//J6S4qUrmHR1hwrRoiz/GvqRURQOI5pt0lN1gTmrFBgboSYniIq -ZY5pPXKnki5Mhc3xwYr4eunD3BwxRQhbbzbaUBF3KkszZa4/unHETiFCX7Lrc3PEFIwe9npqzv9H -YR/im/q3xXu1/jrJhp+bI3ZiOkt2/YQjxmGWlj/Zj0uwSoV5bo4Y6kHBw2mV90RgHv8kQGJSiF+j -RKKFhAcj73QnFQJex221AZNcgX6rHpMoWo7gkKo5hhrvVNFwWue6hpDX1jmK3BA9HTL9GvFVL4ta -DWWVVPZ4qqhZ4XZVtxC6IdmsmetqGyR2np5w1tewWmX9ItWdYEtHjuGxdYl2s4ERkzh0g9Q3J4rY -idmRH0abzwlJT4U/57MrvYnVac8ulZoslP6aAkUMhRoDrd6CIib15VIrvE9kTssSlphqJbTw1/Cl -qNatQRE76fZrVPEAETuN9zOosgEidl6YOT/UNy2I2N8O19/w8ObSmTd1GY+b3uq6qu9tyFEheEqK -prGpUHELJhuntvaKQwwsQS4GSbKCuo+LIabg7NupI+vmxayQW1NWcMD7cYaYVGpfBq2Jb+ctehXW -RRFDTIqtLXRmMMSk2mqLz9PIIIUCNe0ttRdDua4TZQUFyJLo4b/d+ElfwmoDtfegrbK/Xo6ipy0g -Yn/bfo0RDy8qfccnGLJuq19jcWjPzRLt2QwxGycnoi1Pn+5DhQ5TNdV3EI7x+99DiA== - - - WWqCoP2xGWIayG6CeFotcEcjQ9zdgf6cnkpU7iYwYid+vqeLWhJLeVWMgqJoI8qIpM/OXjar9eoe -d7DDzOfMzA60AyCWJiViqgynetxJI5P+IxDJBHqagYnG8/N+7FstnJgGdaJDvTvdhymJLIDQnFgS -HdNKo1Yi2/Ic0wvJpFoTxLCqTSn4RaJ0JQnlN7cC5Fpv3cPSWzSwobz+N+lz40yiGkrEbPcDlW2d -xhY16BkVA/yqafWZ33v5n3/xedHC9lYORmumRRcbdFpq0lahuYrQWADzTXWcsjJS5iM31KdvwBiq -Vq2dzintnaUoai6F/MSIiqz+JM8JFsuEG5+IfeNE7QE1dvyaqHX5CfItoA5JMdWIKrKvS6ZdEEu+ -4awLVfh05X00lSzSmNTGdCyVLgMSTKyTmxcTAhm8EBZrbFidgKk0LgxriztRwI1JGcr2ogRvTNUo -f8PJzajtP7J5ya+8mG6QPIdPjVq+B3Ns4rGvrQPZee3s8M68rZhTO5epna2SJiOenf/yWL59WXXq -UswU2ftBoFZVbPQRdluoLOqYttaDKUYDgKhjBEfZ2aln/PCw6kWl2zz3c60Iwnt5MSBdp/putrOQ -otydIhhTt4KlT3QiENd4X1vpYL876YLU0pPFkL6tFCp/YSk9+8yDAC/k7W7H/U009i204eSTDitI -XNixCRFJ+12okKooMTuNmxug1544hH30s7ljigXN9/BSVRAj5+X7UmLUAoZM8+LTXim4YzqP8mJt -oJUZ0P3wOkN3MVDA1jbt/djMHIUdKA7SchvwGA8Fu7judorQWAkTXBs9NoEK6MFQXpobLsCS1Yv6 -kK4Pt02jKqyCPqZn7O/WTGIaDKf6rFVLS7XzxLROk8HzbgCZvkmnbum5LaFJ6EljJ3YjKjwS8El1 -d+oNWHAPVAuXvgZJksDQc7+OchmHb9fud+3ZJ7AHlRSoiF4PK5dLw4wEy2Id6g2xZoTVN6hvammf -x9vpVIzHykRl/tTlf0bJf3PO1kKM+YjSzuCRqReADREGC2rr0trmdXNm7IZ1yIxywxXHRJ2a3poD -I6RQ4t9pP9CgrCtOhUpreySVSuwUNf6CTWRBGpeyShyXrdeAAfXYNpUOKCkvBQyyhtcq7+mePDrO -7flJLTWpDzLyGlrUaqE1crc0GwWgk4yM8HxREyP1xDPu9dJ5mQBRjCblYTR/fEY53h1ylXrfr6t6 -nKRYMdrh2VQUykmOd/sOKuhDVa8qqlghTIMxvFaLpT+lpBlbSDyhg0Ol8ZBML32QTFF/y81xg7m8 -zDlShXl/u5eTix8VBvOasULzjnfFmp9x2BKKgo9prcknlYsLZaaxiE0KSzQKBJ8ozRueJ9VwRS2p -SpCvd+dfCKXpVzrshy9txa/k0uSZfwecd1ULH6fXwxxu8jxkr2J/ctoz8Hdv2cf2MCWzOxKrPY3A -MPAoG9Yd0a6IPjyhaHiczmMDzujYoUZWuzvqJ6fX7kzH5Wh1QMmvkuwLc6YTbZAZbkasUezWQp0F -LNFsBWu1c+p4O0Z+gR5umZ+HOs1j38+kM1IKS56Rh29c/JstkQ0xyJmqixruj6BP54FCCfKZ1j1s -TKWyO58Epx9zGea8yyunNHOsx65Ffal+O1OieqMJTIktdfl3uAbf9d0QNI2Alv8a3uVJOTJXdriJ -lR5+EstjLhIaMVA+n+y94IEwNW+r17LcY7u89E68lBgIJuQfuNBua6qksT0s3ZnmE6iWnidtRaOC -i2KFx93cbYs1ooyC+ZcKgCPync8mo02oFfrtl5ulDpkkMNCfvo/U4GxhRi2C3ugAWSXE3UprGcMp -yFMMF9zfE18WdHhUqUtlD0ZQHWjj4w0fChuriGhQIk9Z8LVpaVIPQoi693pk9WUt3ilFpTNSlAvi -xFXeLkotsTbdAahp8gIhC+p8Q3q3iEYq1hXkNFau9vnTahMGpcJMJN3paQovkBxXRG/9ystjKx1w -rdb8qk9RlknHGUQN6N6w0Wdj1KSy22nuriSlK03Qm5M1pLC0RlkwNSGUPHRqNDXBxKja00fBU0Pp -xrpfQDXOo7f6caTa5VW91O8ohUsn2KnHJ9jsnIklpltdajk28Ei47GKQLR/kCw1csuBqCgfZzkk9 -xK//xUvhQwzJ9RISxyBs2q+MWCNLyFtujDXZnbKVlZuaUdaamTTr2V+gNUIslGTct5fXKPHDj1TB -k95CrYPsHVFR2l6OxRh+G/J1KqrFRymnTn+luIb2ct0bt6ZpyYYyLic5h9MXJlBtNH8ajfQknr54 -a6zkeMoe7y3Tyt3S7No6BnBNZ5MhpsaDUnY8K3X5QE9BWfUZerS5gWtVpQd+RnPRNN7ajPIyU6IH -SSpbRjl4MIbPiPyCg1HaVaE0EqVSVq2/Gqo0QCjabNhDSsD1WOpxPCj7uD3loyc2eGuakOeMxkRG -3msl2G6zEJpA1y/Lxe4CM+Do3RLAlnk9vYRKCkti6Ojqzxsb8iTxtObB22LEZLinH6Ug34SPTjr1 -TuU0akxk56TaLjqmYKa0lNvSOgfz+tUKyY/UOGJpoIfckFJRPFQSmNpvzK46G5QArtHlZq2c3aub -3tfLBAQnaQSAmtVkdrW/B3FNyaIZpUu8hClTcZiXuyq7XwtxnBvsoWpg2s6k0t2Jwgj7nPbmcKYC -jewgVjwSWPtwlUCRHi5WHVL4p9V+6Z9R+6W63JtSZhlmYL5xDK/dfSIloc4atiz3s6FrUql+lGpF -0IIUXl5LQ3xGCk1eEgK6JvHQSEnsV8F4XUQiX/eyCGleI3s/i7l2kofV7lDFIPTH0ULw0BzN+l71 -klRa0j0ZLWrk7m9yfnBC9JRYUf3rXowURlAeIzukYK5N0gEa7h6n2+pM2z9QAAlOQIoaXK5rQ9em -9oBE2jAYpNbytOBMhzNBz6QgVirgUg9dVO9Ozbt2raMr5TkjFxkhqKdZQLo/yQBhCtxKhZLKXwE/ -3H1vUrH6UQ3ozULrnvuNlIkxWxT5rnE5UCaCVQjk8xZ1DZUlDoVANAnqieyuaMyVYi0NUoK6NkF+ -UEJ0eX/YFWsX2W4zusp5iM96zg00ohbVxm9ny/ItqLsWXYkgl5ZVlu5rm7pG4ycZ18e8XehCsGqq -K4qF1DB0W/VTYNc0DFjD1XSmgMYdrwG93T1AnTTMR8quBXmN3U6nkva0yNeM/iAWxMxbymRpZUF8 -LYJjuNDY9mpYVFkuc8PKMS2PDYlu8vAs9hoqEfZ72MWR8rerOVEsKaSVCOOElCC70Hny+plMWpYC -f10hD3I8Hr+/k+OT+g1ujA2uM4qbMSa5jBZBnkg5TW7uOTaBzQL4jxUa0wNHsxR9hOdp7ZhK5Vn5 -/PVsj/lTM4bCjhoecEQ/QUk1qx41jPDtY4oy88FgU57Rmv9eNwxWNsA2TloCa92gMm+rilZ8OqoX -VNl9Yyl+u48O2QW2EGob0z5ef/OyENCxIWzU0NtbeDiWGVbq7eVt1vN0W8UESritkfljFavM27gs -+3gw2oidoVQ5Z7JBvI8NYTvh7+oNZurDhPT1Zzawu9Sq85aofiA4bGc/YoSL3gpabrThkZmxoRG8 -9GU5V6wz+ZvWYakknAKG1NdO6BJnf3xGhRfhIDZ9EVuraNmIkTRLt8di8TQZkO3WU0v9U5DYlB/s -FJPRCCJ7UTH7bAHQrTbuhOdq3hKrg4+KfNJ+Sk4pEqwvwWhD5Qlm3lrXYGJB5jA6VkDWeGqOWLOu -NaWJ+gvdGq+8JzzRIq3LrSNi0iFh2qowHjxZ3vFBUqJkVaqFqKXaeRSs8PGjWaMdPBUpC8RGSp0Q -eHvNe9Hyb4+ZLxAUp2WFlnN99aBti4hJzFrXEtS4tryUmnWLNCtby1pDn71YbCc2DkqENEdF6KNw -WSGddlomj4FAykKxmWpagNiU+6chbNAX+NiTbpVMsA7F9D69L0E3e4HYlLxmwaSHzDJr9+0Dl6Zc -kdjOO+Jhmp/DIkvpbPwkACPRXKZ7i93DcELAicOrw9zSsu0xswPFnvAgO3n+uo39LPE1htt8n0tY -KUiy3gTuzj+qaSX3rgiG+XzpvaNt99lQNlVA3XwzbbzllTcZp5vlDxeWTSsRCh20SyRDqRyxhfQU -PaFPQNFgPWKKFy6gjNbE1kWqlQj9FlQMqPeHJL3WE/rS9P1owgmX1ImlyWNnGgf+8FEKOghl6Vo9 -vobZWZC2gZfhad5sGP8RrgBP02w3/o1n/5vR8v9zpDYKG7o837Q3CVIbhQ1spEOljIGguZhMtxUZ -MOJJ2PM5feNU5Q+z7tcjSnmoHlEbno8IFYxszy/CykPMrpvDK4r6GIhnMvVxzO0NNRGY4fNIz2qD -xoijqC27meGp3hMYqNYtUoLadjb3gtBM3r3Lqg93qtXQTJKxM6dHr+y4fN2h/bu+w2n8r3e3eZ9Y -w3brmVzoNq0VMJOhm13d+CqJsYv/N2TY1bpOj2sddx0gbMrEe8kLYGjjxWx1mmKF9nKsWm2rUqm9 -U8HA42daOOeITnF9FqPPce8n+7w0ChBoak4uvIYPB6/ZW59qoYlXfHHc/l5GfUpST7YWhOQvb0Xf -SttAsa1qUchghqM2KM0LF8+TzrGzLYHz5MV9ti3qi3nk7zCemw6xmLpi0VF1J5FaPECvhw3Y/XTo -rLH47ttWDlJWgkeqB/SmWX6euGw0awoH63ayuJ92YhQan9rYHOYVYbUjd2wOmlmRbgF8aVRjbLVb -39upzoHLjAoNCqIap+u5XYm2d6oMSN24egHg7ZaKmmaWHEq+G1vtw/hupxxE5rDcgVp1dDf+rp/t -F4LwdtI+aS+avQyKPDQa1a6NeDsNRoRo5K0XW1eL6wMeebEltl62gLwhNtsB4bhysmU57SjSElKO -R3OslOC86VH6G5Mmr62VjMl3kApcDUMyE1ShmVWlhGKLfq+v2seN6etjcYFe1mzqF2lGylu+ZKi2 -iXiN9ibFApkaVxV9/o6//8N55X8J90awzR7Oe/PeJnn/aflAc7GUW8Mbk6O2Xsy8L26UeYZWco5u -ZvXY0mBGU+xjXbee0mvWWgQmObBvUolloh7Wq0NaAIEMiXq2lBJACfIb8UfzqmkW4lHZRrNG6GZx -YxV84B1kyOi+XUXcI+Yy+hvtd+bP4vBaOuZIs6umPPhvqCylZH2gQAkKOZjuLqAUhQw7cRHglNIh -0NSJVFlDoUIw+jcdDeMxdyiUlYqdbtmh7yXLHt0PW2upYp+ijc+d/M+/+LSQmKXVRUvxoMCpu/al -JGS4sTfHYWU1vKxCStN6DCUocKgvFYe3R6RBanjM1UI+V3QEALyKHOjtroUqfzJHIKhoNEz4hnyC -Wb3HVmxKfy2UslRN2GZcN92fW9sPcrNKES0MHCrmYlc3DJwUHjo87djuG0ymmbIMKdSGyQ4BdPa0 -Xn7zjdNxsP6oKXrssxYDgrgANdBnVKhOKGSv+7vhC+DWE2Ocu71P48ZJ0VW/LUisag== - - - Qqvd7EaBWwI5uBjNluilCZgoUMwpp4TL28t9t/xuCNxJema8+bhILxjv0/5tpee7GnCJtzlq2s/R -o9IcRqHh1Pp2VCS2B8/pVff9MKQCilZBA8cQvoFHmqnVDwIcu8Q+TTWDFqsJu0xR3GN4uNuqjiJi -wnura9/C2Rnyraqr9JuOXpRUKrJVFQ9Q4ajVIfu11/kPutDWPPomIgTGHDi5rOMYjmk3mCDgttDm -RsAl9THUAmWW92kbaI/ED8sZY6kZDDidaYVs3V3Nid/xcK4rRgCk2556MeAUp+y3XQ1rblKIlBLl -5gg8RYNZtFOWFQw4YsTUfulZxGpVuzyqErW+ZLtpbFcLtSwIHCrhBPkM0ierKfZxI1JroLteb6DT -mW01o76WQkfF/OoKjzwZVpF855jjClPT2IGqokzpbmVxiYVrZUDyqHs28DtW/pvzttLbl42cxwbB -qWjdxNPWfOMJSz5NKKSuX3f8krJQcFKtsVIqtp5aR/H0MBV5rxwT9ohaiDhzDc9alXHi5WM6PDg+ -fEa2beWDqRRoNmvInmDQXq9HYIQ5/XNGrefYVinaaBP2HsBqbqtfOKLSDyycSm7svW/v5sJRiMNF -oNrisbIrD9S8lhawYp1mLoxr74kK8FgGOzd9ghgh2UiguYA/SQGrlKDDqRiI7e4gQU+J2LQaKA2T -xHKWoorfNS6aShbrNi7OsBX6TUN7t0ph2/TZv3kye/MIrh/TX1/caIdIPuPvWlpzO33HPXwhHnMj -1irTMjJ697X1FWd1TLNeIKm5FP7gsnIIVV2Wttu9b8PGmXJ5o7/fftcGbnpKYaqw5mTN0I1RoSIm -r+FNm3kr2ZzWhgTvQUssc0vo7lu2FZmlB/xkq4fz9OYIZ1hhxi7MHq7wYhgbKjAt4vWk4xrds9Zy -Cj2OUioG5XfT4xDpKdOQStlAu+MxcZyrFCxKJUTMehJ7JaZ4uCcEUVVS6YfnlBU4IcvcjuTCSQx8 -2PMliBwef6T9QcoeZpmBndxS7EdO67tHVQpCF8OTz4dhHpLSN24qqZfZq1ORa6/H48Ohgr0qZVuK -fVnNYKMcd5iDhTbAMhpbAkGcVRtmIta2pPibeyM9sZDsTnVfSjwCnaaOdZzKmMlzKWynKuelMIGv -Tt7p6RgMo61c+Ypy725tEZOGwKdtxb5rC7/ZOC7aImxsndaPa+sP9aVHagdzT+brYdYXOHmGaSyo -AHp26VcabWPlpGJTwMdN9wA1b2nVZFPV9N5R7tA2WU7VntYrpJa+PryT4aZ767KZZggKzE1D8ebG -8biXzVIpK2OEP53RlJS2AXNbFRLDCihtATjvqI6VmQFW2tdmzPE9eJWkYnn89m3kC2YOhfW4Gvr6 -qobt3qIOO1qDiRSz5r6MNGe/slmh+0LNDayC9ZqreUC2KJpzcRxUmXyzEqxQyP35jmWrNEuoXYqI -A7sC7zseb/PcQulPpgrhij7m+3WjZb1wYt6w5jmmxeisR8G5c3pNqMbjequpAo9ams3O23YZmiHs -00P5r4gI4N2+jhPJ3qpcDt9OL6UMBqFiYX1iBdJ3oQjwObMbIFt4b/icuTTRCzMMPiffSwX33sPQ -c8rFH7i2K0Me6DnzhXIjCphykaZewv/ra1N7m0I9MbjQD1Apm6ZYtU29VzPT/IWeM3WEOm8/7hyu -3KHcoTxRb+OWZ1aXc1iW+9D1xLxfoREUKwNpmz1HNlyzgupFmh1FEkUC6Dmt0g+7FAs8p1Ia9wU/ -DTx3di9F0kX0XVKECN6ZthkjCtDfaZAVKVYtz3Hvi2KBYNmbPPtE3hyJkOeKMsxHboSZ0NzoOSUI -8OBQhruTlpVVxks2TEbWir5bacpzB3lO2yHumR4S7IelmIGv1bYUZW7ynKm3+T3ZfuhwpwpZRxHR -x36F+UFWswGfo8xomPWfm0d6I7JimIxscuXmXZZV2MLPSfWOvtsMGnHvNoPJ2HNo6NeH65DwApbo -DYK3UZukWKEL9rnDFLvMSqmsGmYAgnq1ZH+EgZjui9mT3QYsUimPm23dG0Qn1coNUU87blLsJM9D -qsOf28OY+qwoQkV1l0VYdJzYnnMbevLhYfG5V2RsaclrhnvhMcNZwB0CUMhDXqlweh+nMhNKtZnh -rAXPndmtGtr+bdu2271N/RjVkT49DJV8gDHlmhtJt1W1F+ADpFoLSmMVr2xY0Yn0qNInlCuKpoZf -wXWcrhVVLuf0YXQrT/LlWaoMpHSbFeC6cSJVWQcrlZWfuPr255ZoxlTd+r3m7S3lFA/xXR1GoCPC -rfj20RFRQTCUZkeRUUAg3KnC6XcZrd/+Fl9XkJUOY3Co8gP+3HyiSSIUu/teTLQOk+MEWTFlytkC -LIVrsyzqQ1V1E8Fk/gCJuteaJilMafbvCMOpGMpQJ6FavPY19ymLqD7uuqEam4DVKZRsYd3bc7tE -bKl9W/ulpYRxbkR64WsoaWFMLRWjstvrsX8RN4yO23Hs8iWpZp6hLln89tRC1uI4nqXpKTj6Jhbf -YIZR32HUOhS8rpVdwDP/mVF6fWxunW6ltQ5pIyXjJ8qFLLxMFmsJx1zgutDIhL9U1r02Szc6XRkd -u5VsLcXe4eEe96FS83lhlPnQDbQFLfSDXrfU1T514n+nAVlhKWBiauY9MTO9dpqMRw5Hr3WcTD8P -z4oRaVfYmNZI+0FnFNupnvZylZ3Ka13dEih040Q4jW/bHDtU7Vcbizu1Dr7NovYoVCv8/R8yTShr -1/lccWb/f7ypHjzdSx3MFuANrC0V6kw2u1Sj6l7jaayUEk3H7d2KzY9etb8Ow+/9sCYWhqCThsvp -vrtBtMOfl8YKNWCQe5STCDZprXlWWR7orPi0+Aqo3dm9y5VuCELUl7EcaBgXsY6KK3pyUO4dFrcZ -fh03o2iVzIuKwZbSrs22S+phvUAU+VB0e7jLC3/ztZaunuPU3kEXVcVijOBLfLgdPQo9LMeTkvPj -8iof/fazm3FK2NtcESm3Rv/j2Yg7hZvNNPTwZkjqop5miWtPDy6lb8pdUk/3I1kZLMjOrxVPVcXO -7D547+MOr2eQlwyOuZRIPNZ0uFh3qNE+uGB3+jxM4DuBEhmya6V5WXqZPWwI8IkDd7fEx3lZJ+0r -bZvcalAhbdOue03Tqj/Qv+fphSByDKbAwpQ3DRq2zwm1Ne9llD8jo3FgjLQvsUtCD6FT7+aMWl4Z -kFqjVvPyUlW+kDbU4htzQBrzHH2n0IQN2+rZlgsMrb04kbZNv1NvGAinrq04LezNq58VpIW+KM8+ -K2x5dj5aAVJbjrmofhl3wrwdhbcUlbovIN5SNQyBxbuXr6CzttXQCt5Cm74FxqPbqruZG2Q8kbjp -sOjeM/+NWP+b8fD/C8f73wfHU13WF45HtVaB41FiWOB4HUflHzie3oUKx1O/a4XjadH7heMpxFzh -eCgFjmfHVDgen1bgeFIqHM+W2b9wPC3SKxxPL2OF4+k1+4Xj4TVD5b/D8TR0FDheJ8f2C8fr2xTR -4XjGlslwPNZeHzie1ArHY1XQZ4Lj0dt8nz9wPNQCx0M5Z4Lj8VmzFzheDyu6BcfTBPq3gVxwPLgf -P3C89twFjqfxvcLxtpJRbEl1OJ4qNv9m8wTHg7L0geOxeipwPBz1CxyvqUPjun/geKitJzgeSoHj -SfnC8aQWOJ6ECseT8jft/8DxoFoVOB6rvQzH20KC4y0x4Hj61RWOp5nwC8drQFkzHM/m1wzHS0q+ -I0s1OB41tQWOp3v9heM1OXAVOF6Tu8JT2XhohY2HUth47QF9e1Y2Hur9JjaelMrGa8/x37DxVFBU -2XiNEHNm42mR8mHj9fb+sPE6NO/ExqP3/8PGQy1sPPM7ehIb7zv8xsRpxVsaKr5sPG5VYeORzM9s -PB68ysYbUS6+2Hj2dGY2nj36v2w8vSKVjWevUmbjkVL9sPHokSxsPFXmVDYe/mMfNh4J4MzG4+kv -bDwlCX/ZeGiFjYdS2HhSvmw8qZWNJ6Wy8ShI+rDxVBZQ2XgDA6fExvvex//4fSbH+GHjSa1sPI4r -bDwpXzYervKFjWfWUpmNR3fxh43XAf8lNl6nljKz8bSi/LLxiH8VNp6myMrG6zS4XD9sPKntfRIb -D6Ww8XCI+LDxpP7t+xIbr5O6yGw8lA8bTzZqx3FvNh5eaIWNpyrRDxtPcaWnnYmNxxK/sPGSkth4 -Ww02Xl+UqmDeMRB82HgaaSobT1+tP1di420lsfGSaGw8flJh40GF+7DxcFwpbDyUcSY2Hmd+2HhS -KxuvA5NJbDwJXzYelbWFjYdzXmHjbSWz8ZLqbDz9hcrG05ruy8ajJbCw8bo87QsbbyuZjZdUZ+Op -GqOy8bDa+LDxOn0BmY1H9LWw8TQNfNl4CmFXNh7dtoWNh3nsh41Hlqaw8ZhgCxtP89qXjSe1svGk -VDbewO/6l40ntbLxUAobD+WXjdfJlmQ2noakysb7DpcxYapG58vGU9a1svFUilTZeDJ2+rLxpFY2 -HpZQhY2HddSHjYda2HicWdh4fPqHjacNeGXjab9f2XjKLH/ZeIMO1MzGwwy1sPHYpX/YeGzvCxvP -Nvybjce/P2w81MLGQylsPP7iLxtPP72y8fTlKxtvK4mNt8Vg4ykSUdl4Sml/2XhK31c2ngwACxtP -lWpfNp4Oq2w8WMyFjQe2+8PGG9afm9h4uhiVjbeVzMbbarDxcLcpbLwRVr+FjSeTvsrGU3FdZeNt -JbPxthpsPMpXChtP1QdfNp6+SWXjjbP9sPHwjPmw8VALGw+lsPHG2f8bNp7UysaTUtl4Ur5sPOyH -CxsPnHdh49nD8svGU/14ZeMZMDyz8ZKSoFhbjaA9y+3MxtOX+LLx9GBVNh4478LGS0pi42012HhA -NQobjxX5h42nB6Gw8dhibDbe/mdm42012Hj6nMrGowDow8bDgrqw8QYmPJmNZ35GlY2H8VFh40mp -bDwpXzYeYc/CxpNS2Xgc88vGUwS1svFkAVXZeHIl+rDx8KkubDyN85WNt5XMxkuqs/E0eFY2nv7k -l43HFylsPCmFjYeD0oeNZ2pm43FiYePZr/xl42liqmw8Tb6VjbeVzMZLqrPxBrv1xMbDO/KXjccd -yWw83LwKG29QGvvLxsP9qrDxNFxUNp44K182Hhu5wsbTZqqy8bZShoGlBhuPWszCxhvt/G/YeJSw -FzbeOMYPG28s7HRm4xETyWw8PGV+2Xg4a2Q2nibFysYDzPTLxkMsbDxTMhvPlF82HkUGhY2HUth4 -KB82HoUihY0npbDxJHzYeLZkyGw8kHKFjceN+bDx+nL0CjYerYyFjcc+7cPGoyygsPFMyWw8ziQk -lNl4WAEUNp6aDSsbz9zQftl42tgUNp7i75WNl5TExttqsPE6Ns+ZjQcS7JeNp4elsvG04qlsPNmX -fNl4UisbD0e2wsbrdGP8svGkFjYe/iiJjccBHzYexSiFjYdLW2HjUT70YeNJrWw8KQ== - - - lY2norUvGw+1sPE4M7Px+PAPG08/obDxVINT2XhbyWy8rQYbrzPBZTYePnQfNh5eRoWNp2GlsvG2 -ktl4Ww02njZzhYSnn/Fl4+nZqmw8u4yZjZeUxMbbarDxetQWBxuP3/Nh46EWNp6UysaT8mXjkRYq -bDxLFGU2HsqHjSe1svGwli5svP7M/4aNt0fsdZzMrQobbyuZjbfVYOPZX8hsPL0IXzYejWCFjUcQ -tbDxMG/5sPEUCi1sPGKohY23lczG26qx8SzAmth4GNZ92HhUHRc2HoZ1hY3Xw8qssPHwoytsPLPG -zmw8M7/7ZeNp9KtsPCqWCxtvK5mNt9Vg4+mlqGw8vUAfNp5NyJmNp2BSZeNtZbPxkuZkK6VCKxtP -d/TLxuOxK2w8anIzG4/388PGk1rZeCiFjceZHzaeve+ZjSelsvGw3vqw8fTVKhtPVceVjbeVzMbb -arDxZMtW2Xh6cr9sPD3glY2nF6Gw8TRVf9l4UisbTxN4ZeMpaPlh4/V5/bDxiG0WNl5fNvuZjdf7 -/cPGM/+ezMZLSmLjbTXYeAq1Fjae/uSHjUeOvLDxMHg9jsTGQ/mw8VALG8/YLpmNx6/8sPFYYRY2 -HtWV7UhsvKRsNt4WnY3XSSxt6h1mLz9sPEI2hY3Xx1vZeFrkfdl4+FR/2HhWl936ZuOx7ixsvKQk -Nt5Wg42nzUNl42Hr+GHjaeSrbDxaewsbbyuZjbfVYOPpOapsPD1rHzYeafrCxlPMqrDxQFt82HiM -MYWNpxO/bDyFaSobTw1KlY0HoOvDxhu2303HmVtUZuNtJbPxtupsPNqvMhsPF+sPG48208LGUxdo -ZeN9o9oR7/6fsvFG9HQWNt5o5w8bj+MKGw/73g8bj+ewsPE03FY2ni7/l42HWth4uDEUNp6ULxuP -21nYeFIKG4/A3IeNp6eosPEEmalsPCayDxtPEcLKxpNnQ2XjCQT3ZeMpGJ3ZeAr7VDaeIiRfNp7U -wsbjsMzGk/Bl46EWNh4RmMLGk/Jl45HwKGw8qwVMbLyhjdGHjQevI7PxFKCqbDw6vX/ZeGATChtP -feOFjTdINv+y8dRIXtl4/KLMxtNl/rDxJBY2noTKxsPx/sPGI1ZX2HgohY2H8mHjSa1sPJrZCxtP -F+fLxlP4q7DxwHwUNp5osF82nnpNKxsPFEhh4+m6fth4iqZVNt5gh5TYeLpTXzaeZorKxlPI7cvG -k1rZePrilY1nfim/bDwcUgobD6Ww8VA+bDyyJJmNpy9R2Xg68cvGU1NrZeNRRVPYeIwav2w8GycT -G4+htLDxvoNwjN//HhvPUhO/bDw9TYWNNzZ55QmewPyy8Yb5MSc2npTKxkP5sPFQMxvPDktsPAlf -Nh7DaWHj6fZUNp7VLP2y8TSoVzYeU1Jh4w2swX7ZeGZMkdl4g5bvzMZD+bDxqKUrbDyUwsbjzA8b -T2uYysbT969svO+9/M+/+LwQIv2w8aRe95nYeCyA50xsPMKtz/3DxkPFkS3YeChUxwYbj8+67rey -8VgsFzaeQp2FjQc758PG6yTFMhtPu6DCxtPDgNtbYePxiCgqtNh4VAIVNp49Rmdl4yEWNh5/4HgS -G0/KaO8PG0/FJZWNZxu7zMbDKvPDxlPorrLxFP4pbDzF375sPGzdCxuvR/Z+sfG0G/iy8TC9L2w8 -gqOFjUeY9cPGI9JV2Hh9UbeDjacIxpeNR1yjsPGkVDYeyoeNRxdhYeOhJDaefdIvGw8zvsLG0xUr -bDziEB82HiXghY1HuDGz8XiUf9l4+MhnNp7uR2XjERP8sPEUdqhsPB6KzMZTUuPLxuusXDIbT2Gg -ysYjGvdh4+kZq2w82qkLGw9a1YeNp29S2XiEnjIbj9rYDxsPNbPxEAobz8pnf9h4XkF7LTaeXa7M -xpPyZePpm1Y2norxChvvO0r+m3M2PqgfNh4VYOdIbDy6wDIbj4q8DxsPtbDxqOc7Z2LjQShSULGw -8bAhxakw2HhmTJrZeDSeHe9d2XgdfH1m41EZNOdm49Gb9rYfNh611IWNxyR6qsPY2XiK4nzZeFCE -MhsP4+rCxkN5+1vZeFLNpTTYeHgAm2GsZ1NRnuuHjacpuZ3t3Ww8zVmVjaek2ZeNhyl+YeOZ8X9m -4+G78MvGU86ksvEUa65sPGrOP2w8CCKZjdff9sPGM3TFLxuP3F5h45Gry2w8JQ6/bDzCWIWNp4qQ -+7g2G88swX/ZeAQQCxvPvAsyG0+hzi8bj4adzMZTDLay8Sit+rDxdGJl4+lzKxuPYO2HjUe2tbDx -9NUqGw/lw8br4Sm02Hh2Zmbj6dO/bLwe0PbFxqMVPrHxQFd82Hhcq8LG4zoVNl6H2fTLxtMIOBgJ -go1HOfLTExuPtnrKrDcbjxhoYeN1UBdXYuPpu37ZeEDrChtPWbzKxiP19mHjUaxQ2Hg6s7LxtKb6 -svGw9i5svA6XuCc2HuW0v2w8yhkLG8/wdJmNR2n3h42HWth4KFdLbDwpjdV3ZuNR3l3YeIQOCxtP -yhMnrvL24/1h42m0agpvBxuPstQPG09DWGXj0TV/zMTG05DwZeNRbVjYeAzmhY3HLDb6DxsPlXae -YONJ6UoTLDaeFJbWhY1n00pi40n4+2aJjWfKDxuP8/qb2Hj9YAub2Hg0gp33DxuPWqfMxlOv0zPO -xMZr5At/2HgqPKpsPP3FysbT7Ppl41ljXmLj0ZlR2Hg2N/2y8TrI+COx8XqY1C42ntZBXzYeRQeZ -jccSqLDxWOF92HjkmAsbj7RfYePZ2vuXjdcXgTHYeMCqPmw86g0KG09DQWXj9fv9b9h4VeXFv6O5 -yNl4PcrLChsPtbDxUAobj+Xmh43HGrew8Tol4JmNJ+XLxtOEXNl41CIUNh71Nx82Hmph40kpbDwJ -HzYeuLjCxiPDndl4Er5sPC1XKhtPSmXjaZ3zZePROpvZeCThMxsPk9ZfNp7vexIbD5/IwsZjl/th -47FFK2y8lKlwNp4qu79svAH8KLPxUAobjzM/bDyplY0nJbHx9M8vG08x+crGI5pU2HhyUf2y8aRW -Nt6AKpXZeNhI/7LxJBY2HhexsPFQftl4xBgyG0/70MrGs7x6ZeNRCVnYeKRuChuPwogPG6+TDshs -PNzKChsP7NOHjYcVaWHjQTYqbDycYz9sPPymCxvP/SwTG0/v/peNhwlsYePpl1c2njJnHzaexMrG -MyWx8cp5i42HWth4+OEVNp6ULxtvLCeuYOPRY1/YeHpJv2w8alELG49vUdh41vH+y8YjuJ7ZeHQh -FDaeukA/bDwNA5WNh312YeMRkPyw8djtFDZej/6gxcYbyxAusfEYeTIbT29pYePRUPJh46EWNp6U -ysaT8mXj0XlS2HiWAs9sPJQPG0/RusrGU2SrsvHIaX7YeBbAz2w8mqUKG0+pvC8bDxPPwsZbFtDB -xlPS8cvGU56xsvGUDahsPGMv/7LxjNOc2XhkFwobT3/zy8ajhr6w8WhJKmw8vu2HjUdWrbDxLM+W -2Xic+WHjKa1W2Xj0KxQ23lign8zGG+f4YeNZy01m4ynX82XjcWZh42Hfmtl4Hvn+YePpi1Q23mDp -ltl4ZLs/bDzlBysbjzxtYeORb/1l41GRX9h4+hKVjafE3peNNxgrMhtP1zqz8ehL+7DxrMstsfEk -VDaelC8bT2pm49GvVdh4KB82Hin1wsaz/Ftm49Gy8svG0++ubDxy65mNp2ztl423sN+LjaePKmw8 -fYUvG8/Ufwobb1hlb2Lj6UmvbDy6FT9sPCWvKxsPi+fCxpvH/G/YeEpnVzYe97aw8UjnfNh4borb -NhuP9EFh42kM/7LxKEjKbDwYG4WNp+n+w8bTJqSy8fSufNl4VD4VNh7r/8LGG/QD/7LxQB4UNp4W -hJWNp8v3ZeMxCRY2ni5LZePRnP3LxlNeq7LxCFdkNt43nv1vRsv/z7HxKGz4sPEobChsPMoYMhsP -4cPGG/SNZzaeHtHKxqNw6MPGA9tQ2HgohY1nx/yy8fi8wsaTUtl4g16XXzbemO9i41H3Uth4Gpq/ -bDzNEZWNpwGisvFUzvZl42mtUNl4KompbDwNBV82HpUymY1HAUxh40n5svFQCxsPpbDx+KwPG09w -g8rGG+9V2XiTkPgvGw+HN56LdRxbiyux8ZKSaGxbDTYeXAHDdxn1DoDDlQT7i4dbm8ZRdAFnNh4u -4r9sPMTCxkMpbDwpXzbebMcPG2+q+rOw8aR82HgTf8Zrs/HmEZsDZ+NtIbPxkhpOtmS92mbjCR9A -mKKw8WAIaDpbqjz/LVoebLytlLux1GDjGSOhbzbeNJeXysaD1TA9A8XbIJKCRSUyHA8V54Wg46Fc -96bjSfA5atPxEJt1uOG5AuVDe+9Fx5PCwqTQ8fQwWRAx6Hi6jZWOBwrkQ8fTfF/peCw5Ch2PnswP -HQ+10PGkVDredwT+H84s/0t0PMJtHzoeG41Cx9MmuNLxmHs/dDyzhMp0PJRCx+PMDx1PaqHjEVIt -dDwipB86niW8Mx1P8dVKx+uU2P3S8RQBqHQ8wr6FjkfP3IeOh1roeCiFjkdZyC8djz6pRMejkyrT -8cj6fOh4CggUOp7uR6Xjfe/kf/7Fp4XU7IeOp6qISsfjuELHk/Kl46EWOh7R1ELHk/Kl42nrVul4 -Wn5XOp7Cr186Hj3OhY5H1W6h42kD8qXjoRY6npRKx5PypePRp1/oeOx7Cx1vhOF6oeMRGSh0PKo9 -Cx1P+6AvHW/g6ZrpeKonLHS8JWQ63ha9OAEbhULHY0j60PG06610PEoaEx3P/v1Dx9ui0/Hs52Q6 -3rju/4aOh1roeCiFjiflS8djn1joeOO6Kx0Po9gPHW/w3mY6ngIAlY6XlFQsstWg4+lPVjqeLvSX -joc1R6HjEXXLdLwtZDpeUp0GQaFloeNBh/nQ8awgPtPxiOAVOp42iF86nmIShY5HNKDQ8Qbbu186 -HlHiQsdjn1foeKRHPnQ81ELHo+ex0PGkfOl4RHILHQ+l0PHsmF86HqSmQsejcafQ8b5j5b85b2tK -/tLxtEIrdDwzW8l0vN7Hf0PHwzCm0PEwjCl0PGpoPnQ8s61JdDxOLHQ8PvxDx7NagUTHowii0PE6 -CLJfOp6ZgmU6nkoAKh2vL6JnpuMRgSh0PJRCx7MoxS8dD7XQ8VAyHY8/+aHjYRBT6Hi6JZWOt5RC -xzM10/FInSc6Xp8RKd10PMqUEh3PqjwyHU9h4S8dD0eJQsejRDDT8bSW/dLxtpnDOq4HCz7oeKZc -hY4X2qLjUVpT6Hi0l33oeFa0mel4BKcLHW8rmY631aDj8Z0LHY/L9qHjGckk0/EUS6p0PIqpful4 -iIWOh1LoeFI+dDx9fqXjSal0PClfOh5RrkzHs+RGpuPZQ/JLx1MlQ6Xj6WJUOl5SEhArqU7Hoya3 -0PFU1fal49EGXOh4mEJmOt4SMh0vxEXH08cXOp4e6C8dj4xFoeNp31DpeEspdDxTMw== - - - Hc/2Y5mOh7nIh46navdKx+vRGLHoeBS5feh42HtmOh5enoWOZ+iUXzoe+JRCx6O2rtDxOOZDx+tj -/tDx6GUodDzFsb50PNu7ZDoehWWFjpeURMfbqtHx9EmVjtcxgv6l4/E9Ch2PqrlCx0P50PFMzXQ8 -lELHs1/5S8djR1joeERFCh1vK5mOt9Wg43UiDpmOhx/bh47HPSl0PLNaznQ8wFQfOp5ek0rHw6W2 -0PHgAn3oeOwjCx1PxQyVjreVMhiEuuh4erMqHc8MB37peObTlOl4lD71TcfDFf9DxzNnqEzHi0R1 -oeORLyt0PJWUVDqeGqe+dDxTMx3PlEzHM+WXjqd1c6XjoRQ6HsqHjkc+PNHx6EcpdDzMH37oeLwx -hY5Hy2ih42Et96HjDSxZMh1PSqXjscP7peNh8VjoeEWhF58TP3Q8bXQLHU/jRKbj0X//Q8ejP6XQ -8aRUOl5SEh3P1EzHw4qj0PEwYPnQ8Sg0ynS8MeYPHY/h90PHk1rpePh3FzqeXqcPHU9ipePx1hU6 -Hsd86Hh41BQ63uCETMfT9vhLx5Na6Xg49Bc6HoYRHzoeaqbjcWKh4/HhHzqefkKl47FZKnQ8lA8d -bx8XdLzBDNeCfGfL90rH04Se6XggNQodbyuZjrfVoONpMVLpeFg/fOh42IgUOp5bbpybjpeURMfb -atDxBn61mY43VoYi0fEQCx3PFpaJjsdi9IeOJ63S8VAyHQ/hQ8fTp1c6nqXVMx1Pq7svHW+VE63D -1B1c6HhbyXS8rQYdz/5AouOxUP+l49GNWeh4Fq9NdDzFvL90PKB4hY5HxLbQ8baS6XimZjoe5aiF -jmeRr186Ho1xhY4npdDxED50PPxbCh0PpdDxaHj70PF0KwsdT1ex0PGWkOh4oS06nkUOMx0PkMeH -jsf0W+h4VH1mOt4WMh1vqauBStGoSsfzxti70vF45AodjzKUQscb5v/4Q8czKEum46FkOh4nfuh4 -qIWOh6dPoeMZ+eaXjqevluh4NNFnOt4SCh3P1EzHox410/GI3X3oeNaT+qTDzvZDx9PS/kvHw6G3 -0PHMWCDT8RTb/NLxdFyh4xECLXQ8aq4+dDyFxSsdb9zvDx1vK5mOl1Sn41HmU+h4/M1fOh6J/ULH -k1LpeCgfOh5qoePxWZmOtyrLCh0PQ4tCx6MyqtDxkpLoeEl1Ot5YGayg3inE/aXjUXBW6HiK9lc6 -njwUvnQ81A8dT59X6XisNDMdL4RCx1ti0PG0vSp0PEBNmY7H+tnpeFpSVjqeKb90vK0GHQ+HxkLH -g9rzoePRMVLoeJqpKx2P1rwPHU8LmUrHo7n3l47HPFLoeIrAFTqeqpW+dDyaqjIdTwnQSsfbSqbj -bTXoeP1YzoJOx6Ox6EPHo98q0/F0tysd7xux/jfj4f8fp+P9f5J6dw+v/s7QOxW0kuoPkUJuZzOR -n1OVNa1NiXinZZF5rDrwTrXx9K0F7+51Y/aCu1N/xHHaQpyCddjhbNwddvdGEepm3b20mfyzSHeA -xH01D+hOnquY2mfOnXaQ07upiKyoepCO4CgWvn0/XjB3F5bG18bcnYvdYZS75R2VIXdmbjj+WYy7 -KLRfiDvti6h6zIS7cdv4twB3YB/mxttpG8ayKNPtJPZ7bLidhHPcm23HXq1dGW031yLGyXYyWrma -tX4RobG4WOLa9QBoONbu9N6NRbULoSDUlriYdqrGfjeqTgnF/gO00xefLR3zWLRn0ezEVLksu7N9 -aRXM6Za4sfj4ZQmJRbILPksB2eFzc1iVsnk7XLYnXhw7NeePq+3mLZs4vCclKHZabJ+RW7WHOoRE -sVtiUOz66Q07C053mw1bYdjJkGWZ7Eg8NrvuqFf8+CdodUe8S9FReLjhUWHVXZaaX6Q6va/koDOq -rps1/ibV9Xh5AlSngtD2g6lTAMJdM4xS55yKDakbbqxeGHWvFWUtRB1h3mkGs2Zd2K0PsRDqzsBP -hHhFN7bz6e7hLZkZT3ePcF53Op2anijJCTjdZ5SMactKp9p83eY/s+maeu25+MGma+fl6Ddn07XT -K00TnA7NooleE4VCACXgdI0q5j4rnK6pquU52obTSWHxuuB0UsbR7wqna1qDs+oNOF27miUGF5zu -b+60Jo4Cp2tXD8Mvp9O1y+uqFp2uaTnZnTsXeDrEy4MmRDxRpvWwGoYSb73TlEWnk8pNWXQ6KWRQ -Fp1OijX5ZTpdu3yfu+h07Yy+xcDTfW/lf+JWD2+rK3g6qaQAFp6O46zb3PF0zeMRhU4nkcT1otNJ -YU266HRtWIlXgdO15dwQdLpGLePc0LmmEgBezQynU1UdnsMhysHubH2j6S6RYM6nkukut8xeYDrM -5eTgEVw6mcu9T6XSKUGrYXJB6bSBuO+xmXQSRp8VSacOBnkUB5Hu9HadBaS7PGWXeXQy4Gvs+x1H -J5SI5pnFe9pCgtEtMVh0t3ecL8ScXntc0TOJ7nqNM7FEOTfhPR4cuiUkDN3WjEKn3/F6rbEy8qp3 -ek+3rImB6Xqsan4R6CREnz3Nd9F5Vvhzgg5oplwsNXAtpwtaUWFIeTu2LuLNvFfUizh8TlVpDUiW -s+eWkNFzW3TynLaUFil3oNx9B7EvceeENcEvJ0QtweiND+rcEjJ0bovOnFNYBNRCIOfewzgIGTj3 -+p5t8eZkrDbcvcGEy6IbhTbXqBTiNvnmvqmNiZxr0OZav70CIdPmGriQ6TtorCa6l7ov2tzfNBwm -aok2J9Vog2HS1/A4vq0shQe14SB9/1NgcxJpBVuwORTe+IDNodC9n2lzjezDsBAffUMNew/sK5w2 -9x39YgrEqIEKnEyb09uBzceizam0h5jMos0RSxjXqLQ5qa21sWlzmCzppi/aHHEOzX+FNofqw6WZ -IOjE7mMsVol8uIbgApvTw4Sp/YLNES465r1hc7wE2s0U2BydS+6PwecS/nqvd4897HntHU2sOTbL -2uUv1hwK/jtBm0OJC7Boc6j/LNQc/9S9Xag5FLNw2ag5+1DncTIr6atboUyg5raSUHNbDNSctvUP -i6pAyIlYxaaloOYWv2kfJ3ojZh3BmtPg3y/QkYk116Ife7HmYBtTJBEMOZu07ruy5uAWd5g5rupq -8Owv1txWMmtuq8Gawy0Gd5lgyDWsc1VIlVlzTYW98FnXceKOzLFRc0vIpLklBmhOzCfLSARArt13 -8EITaE5fgxjlPg4SokVCsUzHf6U5kX1x5lBthnPOXAubhsWZa4+3ShfOnFQLCQRnDpAVr0pw5hq9 -8K8ds1yK9NXM4ik4c5CxcSwIfpw9Kef8p3DmRK46n3wY6O3L5h/W+UlJfKmtRvRbUG2GpsWP05eg -0bFw5vRQGbZ3HSe/D0rQgzOXlMSZ22pw5hodIXoCAyAHhFzLiYyZ01NgQId12BktCHDm9j8zZ26r -wZnTB3GhFz9OYVMdXzBzyi5o3t1HaTw75tiUuWVUlzFzeAg1by/yreRp3q0LM8dscToAbNucKX54 -vnNj5qRg3r0wcxxj/yeZwGk/qnlpYeYULsaGcPHjNMySzy6cOUZHTc77sNOXxoGZ20rGzCXVMXOM -2t6LYi6u7TAbx0KZY7h/32tT5mwC0HTklDm8iDQKZsiciWKwB2SO89j2BWTOfiNfKkHmyMcxKwZk -Ti8Ka6EFmdtKhswl1SFznf22nO48rs66SBXvGTLH/VD8N0TSZfe9GXNalbPDKYw5XKQe7mOLH3vG -jODsODXp2mdnxhy7MS1T9nHjsdDIYsxtpYwBSw3GXFMxDdDmYMw1FbNqhCuMuTYPrygOxlzD59ip -Bqz9G/jm22znFmOuUYt8mheaBrhGsMNR1sGYa4ebJa+jVLxDdiIYc+9pibWMmEO7Q/vbSSCczz8L -MIcwQnCygpZC+AgGXo610Wvda5SGSLAG1wSXE/iBNzXYcu8CjhhaTlU95o+8yXLP6wuEAMvJIsqQ -3m76e/nmumDlzAvrnwWVU16MtrJgymnHxdoqI+WuWD8FUQ7hMIGsmE4jipN5clqK4IoSODnNpSDe -gyYnqgZjTYbJPb4WXzA5zaS85cGS20JCyS0xSHKUR2lyDZBc08KR4T+T5PRIsDVfJLm/l9H63BZJ -7u8se2cLSU6q0VyDJCfFqtOCJNeO4D5kkpxUquSDJCeBSTNQchwRWMflfdWwK32NoEXwvP0te9wE -y1FyTcEUivgSSa6xFT43SE4Cy9wFkmvt8i1ABsmhDv84u4otjNAcJMdns0bNILnGbua4F0jufTdC -7n0rPO59NzauMWFR++w4uNZu22Zmalzrl/kmr6PkOkWx9RPBIRcyMm6JQYxrZMecb4fbRuvuRFiQ -cU11C49bjNtxujSKuS1kXFISMm6rgYxrWl28Xlkkc3B+TKM+MyHjUKHEBzJOChHFhYyTMrlaGRnX -wiR+IeNQgqYIMg6FpXRGxklloRzEOH3Xx/oSnATXqG4JPOc6cY2/6zjl5ClwCGLcVjIxbqtBjLO/ -cF2bBKfNdFvA+Qh8KeprO8F1nOKaw41xVtjRNonZbkPhyZNHytKnhDXvc25g3FYyMG6rBoxr04ek -AMYpaGGeAxkYJ/UhEhzAOCnXHcdxJ6MivwDjpPpI4cA4lOd2PJxSSZypNUsBxrV+WphrAeN0EY8o -ZMEUZysZGLfVAMbplRiYMjgIrjWvSS7AOJtdVbAQqjhC/d64uPj3hsUtxUFPqrWhMiUIcBrzbiva -SaQ4HjcQUOs4lf7BowlUnEKzDp1OqDipZtEQqDiU0+FxBHE483Bg+0LFoRoNyJfcrYWDTqDipBh1 -J6Pi9N38TEfFNaXhSJYFKm4rGRW31UDFteN1f61AwL0eFCqkuPcIULCLz3T/HOfEPas0LWHi1N9s -iVSnxN2e1FmQOI2bVO4mRhxcl/efRYjTTvpoNruBEcBqnhRd4sNdRzyfLp6XO9gEHW4LCQ63xGDD -KSuCdVyw4RhUr1bQcMortzY2GU7Fk1HVQtbi8g77woW73JVmYeEuD0UuKhzjpNPWFhTumlYatJhw -gvmFHz8JlS1sItzSHAinsYwi4GC9XfePwCrrsPF5iRDJMxHuDtp0BsLdzXY0hQcnZ7Jx3BsHp/Wi -FWA7DW4LCQa3xGDB4aH2PrutooW1e2m00GRMVmUfN6P+IFBwW8kouK0GCk4jMdbbCwWnMKpdmYSC -U2bNcv6BglNIycArwYJTBOOI0qTFglOgzDqmggWnM53JkFhw2qBR7b5YcNpKskFbjDd58VK1k1Fw -dFXaUisOwxyJMo/Y7C8lo+C26ig4K1i9LVGAZ7latvlSGQVHT+U1bINuZbYyfGftEyi4b8g5gtH/ -UxQce752vBUF16YHExcKjuOoIAwUnBTyZwUF19QGf9OW7ig47KZsXe4oOF19A1VnFBzqbJsE18KI -dJHgbF99j0qC42bi1h8kOCkkvoIER+jMVyYLBKdHyNDzQYJTeKR1RzrpoWn3URlwhA== - - - QbQ8XAw4Iu7TqTMw4Aj/aG1eGHCU+tlbCQNOIRlzWggGHH4V5zsrA85yEEdfDDgOe5y0QthQEWxq -ATIDDvW4NwKO+Ihlwx0BR/T1cbTQyn6QhTAPSS/jsYK361oIOD149CsUBJxhJqgBdQYc/tr0rQcD -jn5m6FeJAQceAJyXI+CorbVCV4tbdTK651sRcPjG0/URCDh+EW5WwYDDSogvlSFweBVRZxAUOHID -Gh4WBY78AR0+mQJHJA3sVFDgUPo7NgVu5SIKBQ7zdK52UODMEB43iRF/M4xSMwWOwAi1HYGBw4Du -5pc7Bk5xBzymCwZOwR57uwMDB/WCCojAwOFVBCArc+DIhNLxFBw4UjogrJwDpwHSi1cTB07hU+Ko -iwMH2QJ2WubASSUAszhwZNKosA8OHDUErCMyB47sP2up4MCtQoLFgUO5roqBI4HBBj44cPoSBBEC -A0cujsKPjIETrcNQkYGBa5Qv97YxcA1qEMWdiQNng+Q4NwiOgZTxIkBw3yE4Ru9/DwRnuYOjVRCc -RjMcWgIE195I3gYIbllqZRCcRA/uBzXouC2QsUBwKBbbSiA4VEajIMHZcacp5nBw3BYAyyQ4BlUA -UkGCU8DefNaDBKcBHRu3QoLToG54lyDBMSMRqA8SnNJulPkUEpxU55c4Ca6F3dQiwaEoRVxIcA3D -PzPtZ4+P8PqfBATHicQqMghOCxjc1xYITl+fhdsCwX3v5X/+xedFJrTvqBg42SSAbQkKHJVJ490Q -OI34/ODMgJM4jnsj4CRc490EOGqe7lkBcFobEyQM/ptm8wiD6XVX1Nz8RRL97fEI6oK/MThgKmvs -NxVwnf2t6DeZ3M53bvKb/vdxWhv1efrfY/6A367mq4Dgvuk/Hs+mvklv76jQt+sxEtBivmm0ogI2 -kG9kRloFvsmYn51B8N6ijCpwb03JJRqRMu5NYXX7joF7a5E+X7g3rfjN5Tjj3hqsDreVBvdGAJPd -WuDeiIWaCU6y52hREL9wb81A0m3j3hR/4D0puDeiErR0Bu5NivWCBu4Npf/Q3iRaR2fQ3lCsFxja -G6fxZGTam74YAN2Avel6EdEJ2BuRBPvkBHtrFN84O46NhYJvtt102FtTZpSdZYK96TzDUAbtTbfD -6/ac9qbGbhY5GfamCgxMoYP1pueBXZqj3p6w3sykt9cbNxbo7R1e+Bqct9eHlYJ503w7TvNo5tuq -aSuKUSkEVpqYcT4z3uTxT7FQIN5eX1IsxJsKZ4/7qYS3dwU3DfCmfzP2Bd9NlbXnW+hu7+W4doe7 -cXkcHUcSnTJar7pfXXVPJF6C7KZyOkKoDnb7HfX+zRlY/ZiUkGaqG5RrPeYBdRNqznLMDnWb3qxe -mG4SWSEH0o0k+bCmMtac0wurC9Bten5i8dzm6dNR7PnZOrz+18Kga/raZsHcIFdRyGEsN708b+sV -5aaiY3IP0VN0ym6IPLqB3OTjfDmObXHcLvcAXRw37IrvuTFuEt4+K8VNjcD3MzbETQ1XJ+sKz1bS -gcVfTwg3ESDPNjfB7XazwgVwe37QbRrb2AYudJvKTLGEWeg2VbnZpmMn7ZW78CWVk9sU9HVDJie3 -NbZm858CbtO4wr4iwG3NIU2L28bIw1orc9sa7k793tw2MmPWJGncNjHn7x9qm2JNT9/MNm1SjstS -tbrVSv08eGQnYtsVi7IAtmlhTTYkeG16lQmTZFyb+k6oI3Va2xvIjoC1qdOVcsTMalOEhuEiCn4f -t0tepDbg5dN4SgvUJroOVRwhyrSGnGNg2iRc7iSzxl2ztrEFLS8op9lBJJ5l+8nzmQltj6dyFqDt -8XrKALTBBTlsxb34bE+UYwaeDUb5YRle+jGBBvEVE5xNuFDe6mCzqT6X5oAwI5MV430YJy3QbDSW -OzeQsDGchcs22fyXp3udWuayqaEXclxg2ZSBtScisGzK3ZJNzFQ20vtP31C2dkTdQUDZniuSiYnJ -pmZN7mUg2QLhG0C2+YY50uaxzVVZ6zg2jb0GRHMa2+lfMbPYzuiGDBSbhMuPIvetHJaxKxOITZlu -4nfBYRNbjo1GYNhO5am6kd9WK8fsltUKCNsI56p4vSM0mRFssqs4///svcvKLV2TnXcr+wpKax7y -1P7dsLEbRhjcLETpxyqQLSMVEr57v+OJiDkjVm6VoNiiLKzet8eXud5cuTLnIQ7jcS9my3uqtmBY -5ac7IVhGOePXhsc0Fn1NhRv4igV8TVUhw0loi70m0YqxvRhOq0OrgnHy2rhteVvAa8Pbexd4TQ9v -n1ZsZAvP28f4zF3Taf2+NnZNma1PfzZ1jfrdq1fomtYxcB2DuaYWDyvpduKa1UEV4pqYCIBQA7im -fe/jjv6MlXpKCU1m3Bq9YqcFWSyjNn27GLS1gU9yZa1BINddC9Qahqe+a2K2vB9/8DNojaQ8DDPj -rDX95NQ+BGet9eU8ljhrmkRsQArOGjkzxrDgrNmqd1bM2hN2T0FZ04rZMtOZstaw0XZUkZVwf7wC -dlHWWvNptFDWqkq3QltdMo5Za1FgVTBrqARJA7OGQnA1MGuNmv3TuvQWZk2qcWoCs9Yofn4MvIZH -rBRrj8qYtRauhwuzRv7eklXu06spnDLdgllDPfqmrEmgligoaxJmWzaQ66zDmtscskZWeGbKmhSL -2mXKWhueylmUNSkeSvKMkZYkN/CcTFlrGi0sw2KUNWV5eKSCsqaqr2GGb5uyRr8WbDaHrCnVbqn1 -gKypFM6g2xmypiyMLWYDspZyAA5ZU0mzUYozZK0RpuzXhqyhzLYZa5x4XBWxJtECEl6mKcFWCxDW -9E/iMIWwRlkdRbxBWCNMQ9VqENYE1OCCC2FNqrlWuxEC3I3TK0+IfkixRqVMWJP6YVAMxJrdQspv -7gg7XV4fmhBrLao/ArGmtJlVzgdirUGHHd7/F31Wt616FmKtYbI020asUUpAPUlGrLXu5vQLsaYz -+Y0XYq3RZ/u5K2Lt56Ms97EQa2rYY2m9EGtqxDMvmIxYa/Ba59iINZ3pKT4P9DTB8nQRmbDWlGWj -oCcIa/ria6tI/60qIGlAyIS1FvUFi7BmChQu9wYrJy7EGipLlkCsScGWbCHWpFglf0asNeycnLhJ -nryN5dDwiWbSwz8rI9YoxbTB25dDXAW5kECsNZjAc1TEGu2LZDIdsUb5vVUfnVFe48G8jFjTGGAN -RoFY06DjtZCOWFO1mU1FGbHGxqRTSOqd3y2aYhZireEqxno5MdYYd2wV7ZA1vajm7R6UNXopLqCi -ibKGSvg6KGtSemKs6d/gTDNijYaLx0+zGYvM8tM2Yg0lPno5FKnWnlz3Qqyp7t0sbQOxRsqQHzcj -1iw0fo+NWOvRWLoQa8qUWel4RqzhBEkDUSDW3Ee4L8QaHTOMKRmxxpLrdDNBczdVBRNLhkCs0cRB -UXBGrBnu93g2Ys1IKP3eiLUO+4lQTEKsUT9ur+EndhTDa90DsWbM+KdXxBppNTpSArFmyILPvRFr -nMnmLiPW8DEhshSINWr1eWYDsdaeANZnxBo4BRvhoq1A5fHUBQZird1eSVIQa5zJ33xWz+ZjFQiB -WGtkp+9KWNN12DIlCGudRdv1bMIaueQoJgrAmiJJZpwUgDXaBGzud8CaoZnOoxLWqEd/FhNgWKDA -/BSDsKZYAT94IawBwbDSIyesgb6htsoIazRfWVtzIqyZNUpz1OZ5eFCHNHdbD97jn5QRa1ItLGyI -NdqUiHQGYg3FVzKLsEbGuoW1FQMMia17A9Zo1qBrOgHW9LWt9s75anodCCsFX01Z0Psyh4WFV3Ny -tFMEqJJU+OgE/OZ8NV0Ao0Dhq6HigJkJa4rKUQm7CGt60K02KAhrtOiRbM+ENerRZ9uANVyCeWED -sGbdbaPy1Xq46wZejR+WBrKxuhSntxVlvJreBurPF15NSz1blwReTaO3zfWl3lMbHqvNN7yaCkBZ -0C+8mqZ6a+TIfDWVFZEhWHy1BtD98/wqfLVGjfthOCbL+83bAm+BV9MhPF4Fr6bFsPVNBl5Nmy3+ -/sKrETdkZkp0tXt6bXqkoa9YcwVbDVzfeVS2mkwdyc8EWk3xB5a6gVZ7xZT/ZMD6n4+sRiS1OzFt -kdUoFQhMOCpfm9fAyWoIl5+4p3ENyBurhu0VrZ6BVVPDjwUEMlZNqhFnAquGghlxYNXsmNMbSVax -cy6zYJiRYtHYwKpJoaq4YNXaHS4GWiU+3li0uGoakK1HOHPVOvXLp+WGbdkt9xDq/KOhmby0/Ufi -qumNsvx+cNWIkHHrg6tGTMRn9jIu2DTpWDUKSk6Hb5FJ43uxe8hYNdQzKgpuP5Ns08Kq8VmObNvc -pI6ho5lKO2jwiVEgesg1SsWbvbBqWIMVrFpnO5GxaklJIK+tBlYNQ/qMVcP5/xurpsG1YNV0YR7N -c6wa9tMEvxNWDZHCtsCqoZgVkGPViDWdVqm0ci1EVVm0B1VNMXQLFwVVTYrtXzJWTe+At4A6V81i -y95xZx1IIWSuWlLdA1VebIWrpuj/m6uG+3zhqrFtLFy1rZSfY6nBVTN3/cRVAyr2zVVjfV24aqzB -X1w11MJVQ8lcNbq9v7lqiJmrBh+icNVwx31x1fQ0Va6afkiz2gtempmjftqvwlXDP+G40mFaanAz -A6tGF+LtTTXLEQv1cGQaWDVr7u+bqvYeff/RWeWfRFUjumaPZ6KqNRg9wxZvzNeKzVlOLahqTLn0 -oWeqGqmzOTZUDUEL/wVV40StCAtUTSrBy4CqET+9jntD1ehpInSYoWrEG8PR1AI7H4/wLKiaCivI -bBaomuwzbD8XUDV60cx4xNdIdI/xBGaoGipLqICqoUyHr1nA6RMZ5ExVU07GwkuOVaPBjmB2cNVI -28wgr0WGtH3cniK4avpFbJUVXLX3b/mXP/m8KD1HZ0jmqjWyeG6BTRU0x5lzvVcyoJC3yFy1Ftm9 -xVUjfroEfEQ+HhDOWDVFZyzAHFi1NgOaHLg0GUZZzUfGqrHKGxm/1kHIP8fGqmnbQQ61YNVQccYK -rJoUHruFVZPi9ZcJq0ZfurHRHavGXpd8TGDVujl194pVIxpARiqwaj1wLAurpifc7GAzVo0Bncqm -wKqpOM+rIZ2rtpUMVktqD7On7ljXIKa1x6POGayGL+oVRBe6zj4rp2BkNROGW1cthNBWryhD8B39 -YqtpTLW2l8xWQ6WQPdhqKBp9FltNil19ZquxR+yzb1JY/7gzV7DVsBm1HpXEVuu8vYfnIizMEsiw -YKslJZVubDXYavqTB9u1YKaxvfgkwZ5Z397tw/D3poDf0GpbyGi1pHoGk9LFK5HVVKA5lsNjkNV0 -olWNBVmNqB3PZ9wv7Q2tFS+T1Rou7M1+YRqEFB1uNEz511ccmJV7IasRHabUKshqbPEo/AuyGikR -2ngTWA2xOTSNehspxIIXWK0NL2wrYDXit3ffXDUE/yQGEzvi0ypWTWFreu2CqqaQrw== - - - 5YuCqvYeKf/kvD29HLNA1eYMarRD1Y6wkgum2uF+VgWpdpzeexhENUaa89pAtcNbZQtPLQKngVPT -WXi4B03tcMPGzFIj09/6QqmRXzieTVKDVkUdZQKpzcdN3YOjdgyPLwVGzciP/VehqCn1YsQDh6hJ -uJyNRuSf+plPMBh8pxn91IugJgErzCCo6a89jn0Mfpqiwb8WO+3w0O1Cp4VQyGmI1+amabIxFyCQ -aFcEPzc17Q4emEmKWbN2CWba81hvdyamPd6WtwAAqpWaGZimoCmBmcxLW1YFIZ4f3/MELe10i9BM -Swtt0dJU7XJc94al3XeUwKY9OpWMs29UmhZOVngQqLStZFTaVgOV9tzRQ+kENDwLzoRE8xt5GarR -RYoi+7UxaXeUFWRM2u1x8kVJu4c/Cj4p326LVRhpyuFbWNARafi2dONikBFWEIQUcQakEcLuz+Kj -3dGyETA0ngYanxMdTRX45P8DjiaMQ1DCSCdvIdGQtuhktNvzjgt4pgwwpqaZiwbXd7QtYmbjDwCu -WEvIWLQQFxbtDjefwJ2pKYzGg0xF069t/jahauFP0cSioi2lUNFMbXNT0VQ64XXRTjuTuQ31JxmK -Ju7TzGK0CCwk2ukWSoWIdg6bZT1iizO9G5VizaeWVfp2Mg1Nfq903wQMTV6v9rechXaOKEtIKLTT -faYDhKYk13UG9ezn9MOt+gsG7XDTlSUqIsfYHBC0LSQG2hINgXZENVIQ0A73QykANF3A0zb+TE4T -4RUL/UwCq+YMP0O852afHe5zvNBnfLHWK/kMI9rz2uCzaYnkhT2Lf2foWWiBPCMWMJ7dYHu47UXp -w9Wdp60qeGdy6WWmCtyZfEN5xhLsTHDsz7VRZ5DEP/cmmKnnio/NoDM2dhh4h6qCES81cVOBreRX -e6kLdNb6vco14JxZt7zbeyzOmXkFkfB2zpk8F83/L0hnjWDJvCrpzPyJ3EPBdv6RLi6oMy0Wi6jS -DmMBBelMsXuc1QrpzNQe6rz8OHeItrwByhnKHWUv1r64QGcIAxSPG6ujWDlGAp2RlGZsd9KZBBIa -i3TWsNB5jso6U1mLxQODdaYdls24wTrD4MzMdBLrTKplOoN1JsXSjcE6Y9M174I6w2GwefctqLOi -8DBwnjdNL9KZuVIe1yKdqQXbuiAddaaYuBWJbNSZtifWXxaoMylmFRuos6Qk1JmpCukda3/idgsL -dYZ1yO1FpQt1RsWPFSVc4V/oLbeLdSaXZ2tBzawzqd615qwz3KAdfmbbAI3p1v6dWGdS7xHkNC1S -TjfLXKgzDuE2Z9QZ5ipkAgJ1piyaO2U56kxFNVb7lFFnUq3qL1BnUqjHXagzKRZazKgzVFZnwTrj -zHZvR0k+PDwm94pKU5NC2kE6a1QAfM5NOkMhKZhJZ/u4IJ015i+3CHKnb6sy3qizppg6qQo/RmWc -dwsrIB9hTMmos60G6kybMgIoC2GGtUE7K+lMlTd2A9dhulU8gw4620LinC0xMGcKOBHhWZiztjIG -CXOG+Hk25axN75sOypk2vtZ/mTBnEikSCcoZQvOj+CsoBCAz5kwfb69wYM4aHu1n3/wy7ci5U5ly -tgt61mHnxyP0gTnbSsacbTUwZ/YHLFtBM/DwGu2COVNJkrEuQiWCagFxt8lVrNZQmwlzpuCuBVoD -c0YEtXng3ArJlpIxZ6aO8WthzqgHZWsWmLOmtggaSjPmTKpZQwTmTIr5MTvmDIGsWMacSbXMjVPO -EMh8BeWsQXx0YNqinOmXtKZLp5zpLto7HZizrSTO2RIX6IwkO2GQAJg1rdPa0yrojNkX2/R13OUb -8wCdbSGDzpa6+osUHDITuwCYaRT0oFcCnfHUYUW1jhOAmdRAgM4UyKW0MXPOJD7t3JizFr4Hi3PG -eTwdmXOG2oNhRunDcXk3Y3DOpJD5KZwzXZmfKc6ZWTPi5WycsyUUzpmpIzu9EmI4j00wI5Rmvc4J -dKYiDauXX8fJqPZx8wUbhe4IQmbSGR6x+KIF6UxLDgNIBOlMX8LWfJl0puNsXHXSGQFJ9zPxnHI3 -T64COqMdzXwl47gWxaMBOttKBp0l1UFn1NtMz3KbibD+pmXWEumMVDstgkE6w2sliGjDHKsVpDzM -H2NnzD+rQu6OxqzudnaBOlslXgV1Rr9D96wY60dqlECNBuosKQl1llRHnfWVVwqEmXnNZsVWZXf4 -e7tKAo6RM1BnWJg/XjazUGeoVERk1BktZ86+Yt5gnWl33lFnSymss60G7EyVd2YXF3ar6hfS8iVg -Z1TNUtSpIbN7hcainZlyp5GDk5YatDPMBRmUg3aj3YndlUw7a58orA3amewTKJYK2JkW33i7ZNaZ -FtgM3YE6U58rS8CMOlOxLtSCIJ3dUYYcCLPzNlvRAjqTSautxVwkCe2IM1o/EtyMYIZjzcayv/PN -zXRsQoGaqXupu/kYyKKHjsJFNHtFi/9kKPr/40Czf/nX//uv/+of/vqv//bnBPtT/w2BzyaFhz+b -4Km1Q5DPpIIMXKqUn2HhQiF/JuX4ebhRAn42lchXycbUipB+PUth/fzH8dgLPVWV8fnZHUwFN4J/ -NhWX1aJ5qjNC0zrKz9YShRS9HfPzdEnxmlI+7fh5iyYNGn3w+QAkUfRCT9xvLvukhUGbqpYWzHFG -wGUCjKHh+WMlvVPp158vffTNQZtK/039wcNW54OJ72cGn9pQ6LVQn7m4ifO6NwlN1jvUqszbawUF -4LSbT+M+LLRmlURTsLgNQ6Nv5zbVaGhNTJUDxXlozciMUhIQrRmMUKoT0VSZdnQUR6KpunDY3wxw -rqCiIvhOTf6Nz1fV8s9IPmnEIYf8Mwf0+DdnCfXEI+HHTFbKPz8l0Wd8HpZyb07XVgWcNFS7+vmu -n9/wiQLkA8fAYyv2Fx9l05Kq6wIVqSs3f6wTC5ufY2RfHD6sqKIRSyVCjvKzJP1RDnsupXShk1HC -EVeqklgSyUlKgL45o1lJyvHzFJgSnDRL2B9SLzdzOCd4Sl2rgdJC0M+1QGlLvM35Ud/6Z6Q9fx2a -3qlIUGPNzx5nK3ZfNf0ohrqOUyxNWMspCyTA5kspv8hSL9XhEF20d/Fyuo9+65P7d302Qk0uGn3e -/jYqmYlhxs/cOBVYDogaojrvJiRFd+nwV29Y5wvx3k+fKAukhnp/7Exjc09iLK4o9mtlC4/9zdWA -K0OYJtL7xPNWDz5ZtmPaQETiQfts/bQhcOKtebondYJ5vuSnYLWSGgs/eyx0bhGq0L+MmXJGk2LX -wE7tMaWOvzFzWuGUxorHnq2+mWr8VvwcKqRQakD/0cQx1k8t11aevEfpzrFAh5LEhT8oRv8Mfzx/ -nmspbA/t2f95R6QspJreEe66VFas9i4ddiblB1KG+KicGUg1vdGArY+P1y5PnPN0hY97M+vd/dms -DvvQaAKd+IedjJZ4ovH4/4xYjC64QU4tFrsNswFUQ2PQpyWPPxhjhNZAetMnoVA9Mhqfg6g26djQ -lKVyb6Y49VZodkWx26zww89Tx5mRRpsPzJmbK2OtqhdaN2/SVM9X+/4d/+K/8xRmVC+zhocFVJP6 -M9F4PY8iRBx3Rz2P9vRSIERTzxNENak/i3KvLFJ9gJTPwUDVjag2scd8/MzIx+rFoG1V863ce+ZB -0eNhU4Q2PRPLQJ9uFlJtqvRDVrz7OHNutzQFNZJDpDLNwRRZLa63cqnPY7bD5ixEdlXpneHGD+Mh -/W99sat7f7DvuCzRj4WnFOZXFNJsUkZ/vLArBgKASkqxynyFxaz4hiC3hz0OUxV9NxZNxwqzTVkF -NXWmqK1VMehp/rzmN0er+1aUkQ3EWlJvCzRPsSwO/bx6P1SxwkCguWYp9jfVv8ZXWccRUrDPYtxN -ylygtSR28k58JR4WmVUokz+1Q32w7HvyoCWjImUE1S+pJB/KOO04gqucydWzgPH9+aRxf5pKmG9S -9mZAclvAfVSWc9uHr6j3sFapyy4N/1GqPzUD327dm5RnQ9e2KqNlbW/0FyjY5hoVZdCirv1scbZi -iz+FweadjlM8XYvLAWZQb/NSAgZkZy61G3ttYvem2oLL4z5T5qEacrRqWvi1SQz/NAdDu0EqY+YB -XbdMG6impcE9NoFtKi/RsGQ4LOigFWlX+6+OI5gw5WGmOYGrCAKbVJZpOo7RhQn2oxywuny11da8 -9vMONF6ERWCTSnW0VMqtpPwsLO2Fscf4xG30MCUQbFLvz+OfB/NXCgPE4/7lKKrRYEXrVi9T5swD -o4lprUUakm4eDRmD9GmDWR0uY8I8cK1m4Hs2ge1QtICRSI5AWgMdh0rdbhsgKRQ5tITgCdfiMR4u -qa01V/V2HyxJpi1+CfgerG66DdOLwIYawyuLGc7sPijjUcina9XNoBxGiQet2xr9WPT8nHAQyj1s -rUgw4lAr9Omw+FVFoG/1855PxmDCnToT4voanQ6Fe3lvVVQRDLYDhznlytU8r4cLpdu/FUTk31re -DJr1I70vVWtEPEU1kaNoI8SLfPgnnaLhSgkIm746m8RBSYz/JD/Dc2eYo0d4KVOBTn+wttidlnvI -tJ2V2HQHv0N+KuyVQuFMwKYjH6fqba0F5J6n0MWheuhTi7R+bAabDvtZlty8mFSbHOrVYFEAr+Ln -FTxUnX3pMQ+FPylrVjUU7uPm8H0izrfnkxQF18MTa6lDxmqaaI6h5Dlz/21pDH2hfsrILRTO/OCt -9qTjlDOfWJV4M/5WFOkN17OtahOobfpB5bHGnAhvHRpJL41focStPZmw93GKb942KuBVfmBwy2d9 -NocN1SbBj0XCUHhcHu8AlkLcQcrisEklPoHKvVUp3aFJXYqCkwe1dB87ZvkS6dpmv/1JU9HWIWj3 -PWyKpQzAHpbj2YqdqSCyWjn3cRpxL5uTqFRMSjIISeq08DzLbdYDzUY1XcTP2ureCid2Ys1POk6h -b20h9QcwEdvKk8zxlzojR6W/8LDmbb4sZEXetccOJR4Elt0hssVQrEkfdex/MhEHh22rSq7zw8nw -kRt9eAnLAROS7eu1Ky8PAOdZ1Yvz87d83FXd0SGLRa0jzmdRuKT9fKKZTLMOknJqbT6o8NB3po+O -8frZ7mVS7+Oxl8Rfr8f6vqSw2OaY8zIlPN0OXAo0Pclmmi9JCyxVRQ4n/flrf0MJTQj2Qt+2lU2H -Pf7L4sqtR2Ir9waxbfXw8hkNnraOO3wS1p+8ejPL00Vi40J4lQ7P8Uk5PjhRD6yzJEwW1up7WxMJ -qt44OV0N/wPH9bE9P0tH+5Zc1twoNk1MtjL6uT+YAGvytZURy5U7K+qECBTbVhWOVhz7YLfO8t6K -aTSnEssIYf0ipzbucRS76MvM1Fm0HDKGZZ6Fbuaby4M6DOxInaqr4eLz4PvZba9wKOTJh4fyd7Eh -731uVZspYi8jClq3koeBpWpRby6OGpOJ9jU3SNHusJ/6CBXPB4tNcUW2dBOQhLbMSg== - - - oF9atX6cwzzxYtH/gvTiG0xiIqzxn0aCcxIt0bvkwn9kefnxdymOMh+Sh0GFiVYr1Z8psLM/Cxgb -og2m7hRryjlcmaHMUBxvIJW+Oq39SRqiHAz9j1WwoDTtJ55nI9l+/p4++EQlTSbltIHe6pQkDLs5 -n0VlsyUDEZdp+ZOpXjPbGDx+9z+xSQ+fiNiD3UMBX6najEi52pimnI/9pBZD/RwbziaVlZVUajxN -+ZhCJo8zCQmpHC0ciab8GOdhYzPrlYkJm+KvQB1/ZifFr4f2o3yRQLRpY9P1PDQ3fVMA/ny4T80W -t0k5NsJhq9ONhXUfLeavMh5tECdG6BbLX5S2ia9ws52FnYhpzdmJAtqTLsz78CjGorRJJSZAzEJl -y1KGQlQobB2w1x0exQifJ6mHQhr2eY3DNKMS2dCmeDx+o+VHH9ZZU4VJU8/W6X4LE9qBfkpFQrRk -/bk4jfimLEqb1J+d0eHqsONY+ksB0zaPj0cB9VmBaUMlfHs605wzL40c2gzoW8/HV7ESAtOmr8C4 -/rMuoMR3YpGnDdv09cZW1C4SAeqtygZB9VKTCe7WT3+ZT54il+xQl2IbsI8H2ddxJwChg2fF4lBL -UcVNVIBudU5beWozdw37tvh46Gs8RbHbo8FQ8fp9HBXqjWCTBQi30ja8bavDjXDmcdqcpzsw2T5+ -bAvHgxt9a6h3t8eZXasUYpcoijVLmdwxOlv6PpOBks87/S8cxKNlsKXoC4rW4HrYFrxNKutrXZqt -q0RL0vDGCKnj9Juwb16KPXk+Yu/jVChOekmeHKK3bWW2XRa0VS0envUX9NooWKBQml6ExosTyt95 -fJnN6T5OQVSLZV87xMn2UtG/5eahUKgFWzF58hjqpf9gA0UCJ5TZN75tqyp5PDzA6sFdVYco4sHE -q0dt4duk3vxyUj/djvt5Tv04fkspJLPOJ3sqdx9CaITW8E+vkM+uJL04U+sclBXuPB4LnGn+Bt+m -u2j7uNstd7byHNu2eKnaVlKnrpdiaHJhfa66cL1A19W38Hc+xvbrudNhmA5rKdm8UH8rah+cK1iF -puA5SY9J5b+uUG7dFztbRUnJvo2ddOKxIzu6jlPRGhvIaSXIvJ8Hm8x7Q9yk/nwzU/FDRNHWQwom -a5ypkVLKgrjZ+35aroDl+sQsXit07VUU0pFyPXzJZ0PcdGl+5mVuVVNxZyXzlA8B4raUiZP+GkRC -VYXxzV8YPutpK3PelpohsLQUu7UqmG75uEY7wUPSTNY6mqp7Z5Z6NtppYpJJHnhaPm+2yCfdXhmk -oKWtA5+2WG4TjqNuvMx21MM9Dcdm0yCUAC15fkaXuRWLk854Yl0dNPFo3aWkyIdtbSgqsQ6/iKQ+ -3s+E1brnmcmvfPB4tqRlW/FnKMSWeLaky+fj9+vpljpBYRn1tL11ReWm6aszk+izGKNUiGdBcIUR -hiXOF9qNFabcG6SamdZz+VXc4bq/lWu5yyfRahwHiSXuBPXGCp7nf9t1Th+/91GnFQaozYfA6ufw -R0nR+8C7TVq159zq/+IfZ1nGCJqw7uzswJ2lvhRtrhfwa6vyNmCKVp0pr9cYXtmgzs2pdGAoMWmT -09/H3V5hoc06UK2lrBHEJohQVbSpZJ+eo8nwDEhcE+E1/AapHMIhb+TpCc+J5KZIsGJWbACuj2X1 -FBz5WZT6uicYb4wxDOJqHhLjTSc+rAxlgRGMN4VpRtciXZt0Na//DC+ejFctuSaqYzA3HVuxTeiM -hVocFwYkmqmwF9qKYn4B3dgqJmQ/d0+xWs0kAzS4trNT84KH9GIkOHC6Ukucpg1Nqofy4VojKVzH -/u8d1Y54938R8sZmspEBnRvyJtWilY87XXKcAuJSKEyZFOfyMM60Q9BzqPjfJPWkW6vqSVvWH+Ys -rdvvZTxtQ95QWRUIoWLhqO75F60w+KKNonNXdoxQhecaU0G1EBEk+2Y/k5q+CcxZoUDbmDc9RaqU -UcECzXqHOiGZCoU6lqs2E5kVQ7TNelOEsPVmow0VcQdmM0qdALniLTotbcnkG6w3BaOHvZ6a838p -7EN8U/+2eK/WXwo96ZoX600q6UGp2rNwmKXlDVcigUoFCYv1hvohbOrl/URgbv8kYG9SiF+jRKKF -hEcbNotS80jASytD6gXIFeh4Gqjn7sBQ4EfVHIqqUNGgABV7bd0S1jmK3BA9PaDDx6WeFrXi1VPx -46GKaIXbFdsmdEOyWT9wvzfs7Tg84azLoMaZbzRuou5YWuo2sy7RbjZQbxKHfiAslBVVxLjJD6Pf -6IB2+Jl24urtU6xOe3YCk89tP+PPbGIKpDcU7lo/NulN6sOt7t1eLGVgWGIeMpSw8NewUUzhpkV6 -O2g7tMA5oLfD0EuDsQvQ26F82dnsYVygt0OWmHrDD1zYm515UZcx3Q1Y91UNeKSDgnGlaBqbioPW -HqKnt5WmHFiUnPxSZAX1Oy7O20HXvOKVemCUHlDIrSkrqGdjcd6kUvtCOFGIEV34o7AuijhvfBWt -LTgzOG9SGfDs8zQySNFuRAp7MZTTA5ErKECWRA//cN8qXQS1gfoPmiN1opWjKEMboLefbb/GiJsX -lQZoqmiItihhqWQQo4aWaKoeCM6bjZNW02LLUw2lNlQc1q79HoRj/P5zmDdLTbACuTbmTU/TdRFv -/OCUzJtGhvhxa5hjRipRQ3s0xWJzfLioJbGUh2IUViLanEgZkfTZ2cvG6KBZj7iDHXZa9h7zBZkq -a7uDEjFVhtOLGOHw9J8KiykU0GJRA6zVLN12VYvypkGd6NDgF1BLsaYksgCPWyVpmKc0aiWyLc8x -raQJ9fDSOao2qQfRQhalqzTwGSnTqvXWNSy9RescyuN/kxY7zrynKcmNQWVbh5W0gHk7zLzFVpin -pYa+f8u//MHnhRCpcjBaMy3Q26DlU5M21nnNgrBcKcfRKaxw661FguI0wXpDZdVKXxY/2GNRcynk -J8aqyJrJA4PFMuHGI2LfmtgjoMaOX10Muv1WwBRQI5Ji2iPpqnXLtAtiyccw+1BmIEv+aXPJor7x -jDAfS9UCnVIgBf2lWDUhz9GJsLhviFoSSDXvpeewrZgU0G9ShtK9KMF+U3XJz3hiA49/y2YFfzoO -+tvkqpuFABb/bUIf6BePiG3tBEbUxkgPl7YuE7PsqSV02wQ4BfPtYrVJopop0vc6zsrYaFvsnuQN -Apz21oM5Rjt3EeCIjj6nJTat0F9xVpW6sVaJB5tQ1+nVgHS+6tpsa9GGWQRPYDTT1iaLAEdg43ns -uceEeNJ22ZuvhnS1UhSVZDkUJsRSP6yWqK7qdtzPTGNX4al9AvxcQyDgdGU/a6qDNC+xMt0xkn36 -AfTeE4iwjz42A44i8OdjJZUQX4g3ntThnhbIhW3DDvReDDidxwYYZJsWuOqfpNDwdj8FgoLs2u9r -s4gUd6A6iHCc9hU8FNrGUe6pXRTIXOIEY2PgJksX7aDux8tD++X1u7eP6YTjGHnuuUlwesZ+fppJ -UIPxVJ+1imkpd54Y6ykCqAhmwOB0Jb0RbHTgLbEnfW8cdz5WBntQ5zkT+ASVeOk0ZBCCjT2O8LT6 -WSJO59q0ewntiajNvt0uxpnTQAVSfkZt60ZYLYi6Usv7HN6LN/Et0WaECKBnksow+ScnbZVi2YR0 -9o2GowTsmG71APdXVaeUz3VzXKYkL+oNVyATderjLreSp6BPNf6D/gOvtzuv25UYSqUSPEWNv2Az -mT6LEnULXfnfDJ+xAV7rtn2qeaGpNIgKBjnlT9vTWh5Z5ZKxgaCYWrkPS8mfNv+ySBaeV12sCuNM -pWTURrFAcVIPuhVOr52XczvVaFJgPUvBtOk6d8xV6qXQ6QgirBSrRrs8nYpCPcl1bi9EzcmU9aqk -iiWCJi3WuhRjMXspbUwR1Nk2NU7joSWA8cPSy3Ao3ARy1B0utVv92b4c3PwoMZin19mTOZ6earup -d1O7mCo+pnU+W3nl4sbpZWSXwhqNCsE7avM+nijVcEUxqWqQz2cnYIil0RliMCRQd1b9SjJNu5Ar -CMqrXPhzeEHM5a7WKgnxP2mOvqzBtJGd4ZJmv4gv95jgwRCqyIuMjLZFN4mqYQUxjDZBkKNlh4YM -EOOWZaB4ZxIgpoyzW82vwmILIqcTrTDqcWsqfe6llTorWMLZitZq66TrWxw50q3KZS51GmzAz1QI -GoVKgScP3+AMmq2RDfbImRRGXWbOoE+3B+q5Nk5O346dKaXPLJ3wTLrtQeW800ILRELXY9e8wJTv -zpSo+/R4IxN9lbqGk4A39idPfMtIgH28/5N6ZLuzh1VJ6n+RWf60RZUjCEopH8Yviq7AN72sYMuS -j+202jtVLK9IwoSJpKVU+K4qjcfDojU/lZrk3j4UErddwkW1gp67GZZYOtPqKLo7aE9jZV1MXwsu -p2/A5pi6W1w1ZT7CQN99I0k9LXFGLYKeaAFpUUv2WG0tYzgVeQriYjBz+8VSubvK1KVSSUFUXQOf -FC0ApVDjJsXq6jBh/BzrzA8xRP0puFCKHbLZ4Y9Tsam6WjtkN7pcp6Xg+AUwQcLWolkViEpmqUu1 -cOSxaXNYE9nnNytOGNQKHzYLGG9O8QVlx6Vs4tzpwRWNZYac02CuNBMzD8w5zWJW1dgTdE4q2zB1 -SvADyM9NeQIphp27ui2tURZ3jmnFRXxOBVqjbE8fBXgOpSNs8Bzn6bdU9Q3GW+cq633M0JZWsAPo -fdvsOYqdKEHSfkS/DL4Lp90M0uWDhOHNQ73wc6o8Yuukl8QaE1RJTduDZjWcgeTQR3HWMzKBTq15 -lu8wqzi1ZmjpoGffEXTNPKWtHMQtqvVxVD1rpW/1NSoNJWKvcJneQq2D7B1RVdpejvkYTinGZXtC -K++gLo+ZZcbLNTeIjiSzhjKW4TRDGLb24aMIEtjae3rZRZDoWMnRSnh4c5k+wvLsol0Fio6CA1LE -l5d+ayiwxSlQrmZrP7uKu20UXVX14ptyuqClctSXmRJNSFIZZ9WGzRg+PfSLQiuUlptkSqWsYn+t -cTVAkCohP0sNuCdZsNWVYrtnPbFBotOEPGd0JjLynivDNs3MiAIcoqf4QESFGZ6N3TLAlno9vIZK -CkviQ0tIRSI/G34l8bDuwWlBYlLc049SlE+CxRU/M9XTnNMG+0nqjb37jHCXJ7e0zsGOf/VC8iVB -8mgHA/btsCw8D9XtU7taJ+jkUNXsGd1d0cuphSpbLpWLUScgpkYjAtSsKFNrrsWiY4sWtUu8hDtV -IRdnuZ+otPshxqFag0CbaBNofWeqNWhEbQ5PZ/C6szOd3oV89E2jk0qkCJVIkXK/xAel8E8r/jr6 -xtEpKH8dUJ4P8+wgnETx7hE5CbXWsGXRpj1wdFIpf5RqVdBy6z+9mIYAjZQ2bZO7cHQSP7qJBH8/ -FlIg9MUnWXisWZEsfzGqTEnEPrYV7odlL+9Oc/dh63tLrE9vn4weNYVF9FnXx8go5A== - - - bqiqx1PosOwfjy4r26DRTfIBGu7m5Zu8s3kdh0xpZNU6aXHWSy7OZ5gqT/MJs/5zL7Y8LDjDcHPQ -NKkBWtvp+dk0Ou2ZbXs4oy3ljvboGTGou1lEWqUIywFBmU8rUTqcoqlvvjapuAgpdXax0Jptv5Fy -HSIBZ8FYy7BZeUjzEoF83qLRobLEoRKILkEFA7orGnOlWE9DYOnszGYlmZoOrEHs9LWL7rWNrpog -H28/WlgnilFt/D5tPuAqDhr1P3ac9byzxb43jY7oOinXw6xgaEMg+glS47BG5Q/Z2/4sGp2GAeqF -bZFGndr0qCDFL15PZmO+nOyDRsduh4Z4pRsV+ZrRIMSCmHlLBk8af1U6Et1AjDy3ba+GhZXld2dF -Hp7IBnU1eXgWig719CgTN0dKP5hKIiuEkepwJTA2tJ48fiaTluXAH1dIhGAl48csIylF6y5mpNGj -upnejJNJ3xJFWgLz4/bPptFZBN83HDTB0S11X7axoh9TuTySmNTdhen9oRmje20PFu0H7pfNykcN -wnz5mKLUfNDolGik+8+2yMohC77BukEel1o3kIIizKUAdZQvKAl58fIpKEnCZ3ridFrzzeBvnoSA -VLcWNDqK6HkLZXqnjRI9ScxkOs6ani4rmUAJbzfSatR1jcsM0y3RxmhzWZkyZ5pXxrVpdMqrWVhr -9LBDffyZDS4xxeq8JXJfDRqdwto+wkVzBT03lJI63VLJHqt9CeuKdSZ/UzElrcsPAoa3D5KfHpHv -g3d24eh0IbZWUcEQztYs3W4LxtNlQLqbp1YFUMGjO+CEUfUxzdOVRC0LAM3ZKo4j4TrNXGK18FGS -T5WVslOKBOsibLR5wl1cpWr84sowRST4YKzw3ByxZt1rq028zUNWEXD20ZmiaW1u1vOOS4cEWyPc -8eDJWs8+6Ng1q1KtgFSqnWcVK/r40azTDv6LlIWjI6eO7YHY59SFMJrf5r5AUJyeFSZCXXqgygf2 -XwciQDqS69RF3BZpVrrW1hqKsN3LS3dGnc4wryJ9FIue+2NAOl2CDQRi4waQzlTTAkd3WGnviYoT -lJ5021ToOGDoRzQmaMcYODplr23BpD0XqbXr8oFLA7J4dMfl8TDm57DRUj7bDCVUEU932Tzc7+Hj -PEjSOZiFfGZett3mdoB1DVUb8BC7jf0s8TWG23yfa1ipSLojxHhaXQzJd+3J2VRouseHRZ3BAaTT -JuT6HKeX0n3s7fH+5LGBdJQ+3Wz8T0tRsv63RvvT9qSThmDtNRV2D+dtvVPWRqqVCA0XqoYgnkqW -Xj+FUF00/mjCCb9WJkH2oJEx122xMNbRLXxHd/Zju/4FpVNei6Wn3DKwEiRcoUSsDP7mx8MRJZ79 -J6Pl/3xYOiobugYuTUiBpaOygWc+VOoYeBc1Rl6W7LMRT7ipNZ/TOE5ZvtNC9Ihafaj8lhiePytU -kHkBQr7ch9Ul4jKLokYG4plMfRxzeUdNBGb4vNEt5m0jjgKBhHsfz/UeNLt0j4IHmU4FgGwTPlYy -Yzndjzvmami2IiltfAJMpznC1h2ql9Q1aICgsnX1eR/40nZrmlxgOq0VcJMh/KZ2fNXEcPMVprG7 -dR4e17pmHSCYMlGjAkZbYwQ6Wc/DChGlLDIdKsV3IvrefqaHc6JVXJ+l0QclnuzjVGhBgSaVptO/ -fA4fDk4z2j4Iifsrvsh0h5X5JfVga0FIfngv+lLEzwxgTlIPH8wAbhJpG1iDHrSOHW0JnKekzdG2 -eNAGrMgfjAU9SlcYTyjIGWV3EvHSAHf7sQG7YyAlj5ODAfuylYOUleCR6gG9Zg6ih8o/6cCnMlRz -FajpaSdGpfGhn+FzegpKSdXLNwdknrSg3MJMBVtLVQuKGt8Osl6dccwoJbrd5325En3vyuE/pG5c -PZVYp37gcjp9UtKvsdXnY2C6QyUjc1juQL06BzYvvl8IMJ1+xTbtRbOXQamZRqfa2Fy6w+hIVu6r -YDPCeVtcHxLKgxGyNbMFlg6x2Q4Iy5WDLcthR5GWkPK5NcdKCSydHqWfLf7ktbWaMT0cWtAwDMlO -UJVmh5XLPNt1ULM9YY993Ji+Phao6GHNJivnZuUKy5gM1TYRp3HppFggU+Oqos/v8fcfnVf+SWA6 -gm32cM4NpmObMaflA5m3tQU2o5rTQeDMvDQiHnmGxhFqWE8tOTYU7GaOESm9Zr1FUhaZTiqxTFRP -T1ta4HArEOKjpASkBJnO0t147TiQTtHVZp3Qt8WNJxV2w97eRabT/t9NYoaR6Qj6UneFuVm3Xc19 -khh5NpkOtXfrAidQgnJ4EpARg6KQYScuMB1tUoTkH4JINFLJ21//pqUB6EV3ZaViZ3h2KFx4WEjC -1loaBynaeP2Sf/mDTwuJWet1mRtLp5qIh5KQj1uJc5x+bQvMaiuq5IvWYyiBpUN9KDmcHpFWLHV4 -zNVCPqe3BKAsjsQVtoWa+umGuBUFp2PCN+QTkuw1tmJT+uOhlFAp2h1ej0z9k7YfzSpPn82lQ8Vd -TIWXAIrw02621rHtvh4d6kWlLEcK9WHSUECx57RmfqZ6jhOyBEXZFj49aBTEBSiC7lGiOsGiPYRN -CaBrF3Sau1jf/X0aNw7ies+0ILHKCSne1PYJANUSxqbSLfHy0gRcFIjWXqeHRceKP5+bSqc9r1Va -7uM8vXAZp9T+bdWguxxwidMsNe3rfMxLzQbsEY07SlTuwXN62T18nmEdDRY4wDKEK4hI831vJB27 -xD5NNYcWunfxILwJs+si3Gbt3ki6g/f2MOM3W3Bqe0H0Qt8Jl8Gl5FKRpH68xFHhVvZrp5ModKOt -s+lMbAqcOcgRrOPghNJv0GDSbUG5l7mi0ks9jPlAnSXtu9hHHVaXNpbTZkDprB7+MGM9uz+K3x13 -vmMw9rrtqReUThEJsuN3dDeRWlUYVUta3lM2dx/rpVpQOmLEVvs13GtVuzx2pVpfst1UcuS5LdSy -qHSohBN0Ao2yanm83YnUOujUNUL0XWe21Y36eAr9csYBiu3QpwUK7JjPGa6msQOlZdkqnQHT0beD -rcHj2cD3WPkn521NyVY4irubk+m0PrPhtNuaz7xWutV0m/fo45ZfbJX6tRvcrLMSKGtz7xk9PZw5 -vVmOCfuIWog4cw3PIAMf7PfvyK2ZsQ3RC0u/RT6YSoFmswbUO6UdaH1QRc85LC/vtZ6f7ZVinmCK -M6mCRlPJgCjllX5w6ig4tvf+3KA64g/ttpwi8TwUAjWKF0GzIkbRzIZx7T1RqSC5T4PVoRx2IrA6 -/iQ5PCmBq8MfxoxFuyHr9JMMCwSeFsvZypnGRVOv6cnrbrU3hzknKc1EAMeGO/s3T6aKlIjg+jHU -eFwWBAddp6CwdbfTeNzDGOJ2O+IRGRkVCGrrq03umOa9YEnNUPiDy8shVNwyzI1qGsDOlNM7/f3n -Dw07vcMSD3gXkcs8KLiK7XXezFvJ5vSc5/TwtdklPG5cthX5pwdxZav0qmLs3KNz9GP4AW6bmTF8 -NpBAd5XquX1co63Xek7h2FFKxaB8bo4dIiF2bf8oG2juXKPpwep2hBRmYLw2yk6fb/EV7S8xhVBH -Fql0KcyYCpzwg+jMZcNJDHzY89UoifDWFMKONK3zkOgzQ7EvOb3x/nJSh26GJZ9VB6l3NCnPBl9t -lSznsASLm/wePhz+LLuojFyKXSy8pXLcxy0sHnMa20LfXLslEnLg7ps5kqoybCHZHVG/lHgEOte1 -jpNLHXkuhe1U5byV69pgO1OVjpFjtJUrn17uLeeUNq3winrQpdi1NjecXcdFX4SNrdMacm398dy7 -MxV3T+brx7wvsPIM11j4AfRQkDt/7g24k2o+Bfq46Sagbi5926zLMTZj3ZtxNz9uJE5PHwg8tTJc -tG8Nm2kUxeJHM8W7G23v8vStUlbGCN8dFLUV4AJhs7xVvXzseVgAHm1GdazcDLRUV93euaaeHi0D -w3wEqJkLJ1+Adyisx1VK2Vc1bLdYJesfrcdRSK7AXp/Dv2WzQvcFvWNHyLTYmlHviIlcl6WjmpVg -hSIgR+xYknpbamIQcdCuIBqPsWOzdrLcoEwVwhmNzNfjTsvqeVCPM2sebY6YY+lRcAKeXpNuvZG3 -MfAwqaXbrE/bZWiGsE8P5e8iInA+PR13HlHlcvl2eillMFiqyuaObmYxq1DkPj312ajHmnNj8Mym -aZjHAhg8Cp86OdHLHEqUZXTLzQXBM2Mod6KAbhdp6iX8R1+b2tsUqgpKWuMLDJti1Tf1nM1c8xcE -z9QR6rz8uGO4coVyhXJHvc0d9t23Vb+i2P28zXwHhTKQ894UPLLhmhVUL9LsKJIoEoDg4f1gt2Ih -8HhjzBi8GwKPjlHOO9YuyUME1F7n3ZsbgTSjsEihWt6O0/5yDHvIqDa594m2qFLhpBh4RRlmJDfC -TahtCJ42umbCoRw5aVn1SDwEMeRkTfs9W1jxjD0l1y7/zWTEgW9du3x3qP4GzB230jYDz9TLDJ9s -P/QJqwqZdSuij/+KOQbem4FHmdEw7z93j/ROZMUwGdkYfjHZE+YvEHhSvaVvmkMj9t3mMBl7Dg39 -w5ZNywxYoncITqM/8daZQaODcTjGlvRzM/CwqCERryISHMQ44Y768susGdxta24GnlQrN0Q97LhJ -sZNMD6kOl18E06Y+K4pQUd1mEQQeJ7b72I6efHh4fO4VGZh1jgv7Qt1iFtrNLQJQqMjpqXB6H6cy -E0q1meGsB8+t2W1na/+2bdvl5qZ+jOpI7x6OSj7AuClS2wy8rfbhRkDiSLEf1njaKAhQBlrra5Qz -iqaG38F1nDluHL9sTdmOrBzJmGepYDvYtKgA5jG/K1upRH6CVf4nfUnzqnLEpy0stZTqZszAYvQ2 -h6XIdcwrRkeJp/u3gAHonlFAIHOsF/dZTuuXv8V6yW3n9HEIh6KhT7fVne20Q7Ff30fpdZiagylr -A3ba7qRwb5ZHfaik5z7xB0jUndY1qYX68HL9CMPRjGmZOVctXvuY/ZRFVG/fSir4HRw8bZUMSDI9 -t0vEltq3tV9aSjjnRqTXABvaJ2Fhp2JUemDu2L8Q+SLTfu3yJdrizD3jcsM9GQW3OO62kJml4Oib -WICDGWPHZSA8FMyulYbBNF/tblZ6fW0Qnn5Kax3SRmraJmh4eJks1hJEaIkgbmjHbRabFjk8jF3s -o2N359tQ7B0ebnIfKjWfJ06ZFNgnQQv9oOAlNWKUqlqjp1h1OVqseV/stZX1yOGDuY5TEcrHs2JE -2qfZPx7+hY4otlM97ekqO5XH27pVOvd8TCB0IGVh8FC1X5WKMQeWPh+/Y1atAPmG732nXed9xpnP -33hXPblHYuQ9CVdLWyrUmXx2qUZ9bI3LKEfsznrB+6bgWUfqnQ5rt7V6KoDDEKSlPWV8GO8GBA+D -Xhorbudymq9As/StZZX7aSs+LF0CgqfjrL7jNg9AQqD4uiqaIrodFVd056FcOyxurg== - - - /eu46UWrlnlR7mErY1PwtqrsnnqBKPJRrREJVUrX9Dcfa+nqOU7tHXRRVTxO93+93I8ehWSVEtQr -OT9O9xfQdz+6OaeEv80ZkXLr9FeePxh4+FkYZ8KbIamLIsulHbmlB5fybAbeVnVjMSTZGaxhvU0K -cVfFzuw2eKfjPlbPoOZGArGyUPD+yHMz8FCjfXAx8PR55gJPoMRtlo7T0svsYZegoudA4C1xOjBL -2yuWHuFyC6gJ94VoNdP6udu/5xGFIEezAgtTnjRo2D4n1J8n1XoZZdDIaBwcI6A93BJ6CB2AR78I -JRf37bFiOUuYOcPpaUMtvh/vB1sIPC1kGLYhYkwiah9bYLZ7M/CYR2A4aStOC3vz6mcFaR/vfXxo -nQglAqS2HHNRCVC3wpzOwluKSt0XEW+pGobg4l1hLKgS9dMbWuFbaJRfZDy6rbq7uYHGExOcFeDj -PfPviPWfjIf/dzrefz063s+u5Dd0PKmVjtep+M50vI6l8hcdTzjISsfrqqMvdDzBn990vK5uikLH -Qyl0PDum0vH4tELHk1LpeIab/qbjCVVd6XgiUlY6nmCT33Q8AchPLsLpeGJnFjpeJ8f2Tcfr2xXR -6XidCv1MxwNB/KLjSa10POC4fR6bjifl5zZclY6HWuh4KMfsm47HZ83eMh2vLy+6oOMJI/uzgbQe -sM/8BeX+i47X7gAm+jH0QWY6XlISi22rQcdrWvNPbWuCeica75uOB0S40PF0XZWO18RoOS9L3iyj -W9TWvQet8Y3GFx2vwXz5puM1zHNMJDkqodLxpPxM++fuCLMTjy86HtDjTMdbQqbjbdHpePrWlY7X -gDF90/FElK50PCPMZjreVsovslSj47Ur3DWCjtcwfv+m4zWtEgsdr2npcv8qcDy0AsdDKXC8Jv+F -D5HIBMdDvR4705Du9+cLjtfuz2/geI3MXYbjNULMGY4nNO8LjtdbGACF2mGDJziehsI3HA+1wPGk -/NyyvuF47+E3Jk4r3hqCFb/geEO9/gWON/qocLyBU2uB440oF19wPJQCxxsUUX/D8YbKagocT0qF -40l5w/GEz61wvNHvLzje6Odv4HgDHzNrrsNGbmgFVuB4Y/QXHA+twPFQChxvYO33DceTWuF4Uioc -T8objjf69QXHGzg4edW74Hjv3/Ev/juPz/kbOJ7UCsfjuALHG5/zN3A8qRWOJ6XC8cbn+A0cr2Mc -keB4nVrKDMcTY/kNxxPducLxNEVWON7PTyEYzRccT2p77gTHQylwvC6LuxccT+rPvi/B8aRUOB7K -C47X1UvxuTYcT7D1CsfrY77heF12gO1IcDww1wWOt5UMx0uq5/H6wlQF9I6B4AXH00hT4Xi6tH6f -CY6XlA3HS6LB8fhKBY7XVYr1guNJrXA8lHEkOF63rrgvOJ7UCsfr0GQSHK/jl/kNx+ujf8HxRBmv -cLykJDjeVgOOp79Q4Xha073heF3wlQLH61ryFTjeVjIcL6kOx+uK4BQ4nrDkbzhepy8gw/G6bOAK -HK9Te/sNx+tUNmU4XlfTVYHj/Sz/fwPH69BNMhyvy8akwPHG5/MbOJ7UCscbWDJnON7A8Pobjie1 -wvFQChwP5RuO18mWZDhex4wkw/Hew2VMmNhKvOB4enEqHE+lSBWOR+zjBceTWuF4WEIVOB5xmRcc -D7XA8TizwPH49BccT89UheMR3ypwPN6JFxyPRqsCxyNgV+B47NJfcDy29wWOZxv+XwuOx79fcDzU -AsdDKXA8/uI3HM8+N8PxdPEVjreUDMfbYsDxFImocDwxtt5wPCdO5ePEm8xwPM0RbzjemO0LjgeM -ucDxbI77huMBWi5wPN2MCsfbSobjLXXB8XC3KXC8EV6/BY43VIpc4HhDoJQCx9tKhuNtNeB44lRV -OJ766t5wPF1JheONo33B8fCMecHxUAscD6XA8cbRfwPHk1rheNC3ChxPyhuOp2urcDx43gWOZw/L -NxxPuK0KxzNieIbjJSVRsZLqQXuhwAscTxfxhuPpwapwPHjeBY63lQzHW+qC4w36WDIcD3T6C46n -B6HA8bSzSHC89c8Cx9tqwPH0ORWOp2DvG46npEiF4zGsFTie+RlVOB7GRwWOJ6XC8Zg0XnA8wp4F -jielwvE45huOp/+ucDxFuSscT2PtC47HAFngeDg6FTheUhIcb6sBx2PoLnA8/ck3HI8LKXA8mwYS -HA8HpRccz9QMx+PEAsezb/kNxyORWOB4elUqHG8rGY631YDjDXbrCY7HCukbjscvkuF45PkKHE8r -+DccD/erAsejRbjA8dRc/IbjsZErcDxtpiocbytlGAh1wfGGyoAKHG+04zdwvPG5vuB44zO+4Hgj -uNMFjtepok5wvE605AuO13FASHC8rrKjAsfTSvUFx0MscDxTMhzPlG84ntQKx0MpcDyUFxyvi1xR -4HhSChxPwguO14/zC47XZXNV4Hjabb3heH05egUcT0qF47FPe8HxpFY4nikZjseZhIQyHK9rmVLg -eF3TbIHjKXz9huNpY1PgeIq/VzheUhIcb6sBx+vUfGU4Xr/aG46nh6XC8X4WJl9wvJ8r+g0cT2qF -40mpcLxON8Y3HE9qgeNJyHA8DnjB8TqWfRmO13+WRRWO1xWgecHxOtvoDMeTUuF4P6uI38DxUAsc -jzMzHI8Pf8HxOlueBMf7WZ1+wfG2kuF4Ww04XmeCy3C8fs/fwPH6M77geBpWKhxvKxmOt9WA43Uy -gQmF17VuesHxuko0ChzPbmOG4yUlwfG2GnC8HrXFAcfj+7zgeKgFjielwvGkvOF4/RlfcDyUAsdD -ecHxpFY4ni62wvH6HTX6GY63Rux9nOoPChxvKxmOt9WA49lfyHA87cPfcDzFlyscjyBqgeMpxPmG -4ykUWuB4xFALHG8rGY63VYPjDRGlMxxPEY83HE9qheNJqXC8vqzMMhxPaoXjoRQ4Hme+4Hj96V9w -PN3FCsfbSobjLXXB8fRSVDhev643HM8m5AzHUzCpwvG2suF4S1toK6VCKxxPg+IbjsdjV+B4et8K -HE/B4DccT2qF46EUOB5nvuB4qAWO16kwynA8KW84ni6twvG6soUFjreUAsfbasDx+nV+wfEUk3vD -8RTjq3A8hcELHK8rVPCC40mtcLw+I58UcDwFLV9wvD7PLzgesc0Cx+vLZj/D8Xq/vuB4SllXON5W -MhwvqQ7HU8y1wPE6g+0XHI8ceYHjSbk+nwTHQ3nB8VALHI/PKnA8vuULjqfUQYXjKQFwtk+C4yVl -w/GSaHC8TmLp18LeKXj+DcfrqrkqcLw+ngrHU/HAG44n9Q3H08dNvC0iaMK6s8DxllLgeFsNOF6X -qVyB4/Xr+g0cT5N2heNp/1HheEspcLytBhxP43WF4ylS+4LjKddX4XiKWRU4noIjbzieAnEVjqcT -33A8bfwqHE971ArHkzfxG45Ho2mB45lbVIbjbSXD8bbqcDyr4U1wPFysX3A82kwLHA8H/ALHe0e1 -I979X4TjjdXTmeF4ox1fcDyOK3A8KW843pAzQIHj4b9V4Hi6/W84HmqB4w2zZk1wPNuxf8Px+DkL -HE9KgeMRmHvB8fQUFTieYi8VjjfGqoVIcDwiLQWOR2y/wPGIML3geBRBJjiewj4VjoeTxwuOZ9mO -BMfjsAzHI1D+guOhFjgeEZgCxyPI+4LjkfAocDyrBUxwPD2Abzie8TcSHA/f8QLHo9P7G44HNqHA -8Sg7znC8QbL5G46HoX6B4/GNMhwPi6VvOB4WThmORwKiwPEs2/gNxyNWV+B4KAWO5wmPLzgelvIF -jmc2+RmOp5vzhuMRdslwPFz5ChxPoYw3HE+RpArHAwVS4HgYOH3D8ci/FjgeaaMMx9Mg+YbjKThb -4XjQPl5wPKkVjke+rsDxKG14wfEoSShwPCtvyHA8lBccjyxJhuPpIiocj5TfC44nhEmF4w2qujMc -b4BS+oLj2TiZ4HgMpQWO9x6EY/z+c3A8S018w/E0kBU43ljklYDjuctYheNJrHA8KRWOh/KC46Fm -OJ4dluB4pAdecDyG0wLHUzKgwvE0nr/heBrUKxyPKanA8TTMv+F4DP4Fjjdo+c5wPJQXHG9ggZjh -eCgFjseZLzie1jAVjqfrr3C892/5lz/4vGhh+4bjST2vI8HxWADPmeB4Un4Wtl9wPFRWrQHHQ6E6 -NuB4fNZ5PRWOx2K5wPFUJVPgeAp5vuF4naRYhuN1BooMx1OJD3ZvBY4ntc0nwfEoBSpwPI75HBWO -h1jgePyBz53geFJGe77geCpHqXA829llOF4/xm/geCoqqXC8Tt1XguN1ZbVecDwF8yscr0f6fsHx -tB14w/E6ZJMMxyM6WuB4xFlfcLxOA0GG4/XF3Q44nkIYbzgegY0Cx5NS4XgoLzie1ArHQ0lwPPuk -bzierqzC8XTHChyPQMQLjtepAspwPOKNGY7XlZX9huPpvALH0+9R4XiK2L7heIo7VDgeD0WG4ymr -8YbjdeVUCxxPcaAKx+vn+A0cT89YhePpsyocrytX/YLj6UoqHI/YU4bj6U++4Xj9HBWOh1DgeFJe -cDyJBY5ntyvD8ToVwd9wPF1pheOpGq/A8d7D5J+ctLsaW19wvA4ufCQ4XhezL8PxdMgbjoda4Hid -3P1McDwp53V9wfE671NPcDyUAsfr7Die61eB43UA9hmO10GBzQ3H63q9nvYFx6OYusDxVH15HKfz -lPQyyBz7BceTWuB4HRPoDMdDeborEXOVajalAceT4o6xnk5FoZg0w/EU9WkHN9bheJq0KhxPWbM3 -HE/jYYXjqZS2wvFUmfeC4ylpUuF4CjZXOF5nk/cNx9NYVOB4/WlfcDyGqxccr2OileF4JOsyHE+Z -xjccjzhWgeN1bXw+54bj6bd9w/GIIBY4noJfFY6nWOcbjkfHTobjKQhb4XhaHrzheDqxwvE0ilU4 -HtHaFxxPaoXj6dIqHA/lBcfrYSq04Hh2Zobj6dPfcLwe2PYFx+taXiU4XgfK8g3HY2QucLwOIz7D -8TrQpm84nkbAAR0n4HjUI989wfH08JuT94bjEQQtcLwO6+JMcDxd6xuOp3mlwvGUNa5wPKWb33A8 -qhUKHE9nVjie1lRvOJ6+QYXjdVsxJjieBucXHE+dWxWOxxhe4Hi9t9/A8VALHA/lbAmOJ6Vp+V3g -eFIrHE8XW+F4Uu44cdW3f54vOF5j2/bZcDx6t15wvCYvkQLHa9QKzwTHa8/9Gzjez9/+guN1FagU -OF5X+cvoX3A81OtJcDwpXXmCBceTwtK6wPE6vu8Jjifh58oSHM+ULzge5/UnwfG6snKfkeB4dIId -1xccTx1PBY6nZqd7HAmO10gYfsHx1LlW4Xj6ixWOp7n+DcezzrwEx9ONrnA8HfKG43Wg8Z8Ex+vh -UrvgeFoHveF4VB1kOF7Xw1DgeB2n9m84nqalCscj71fgeLb2/obj9UAwLjieVu5vOA== - - - Xsf9PMPxNBRUOF6/AomU4XhV5cW/orvI4Xg96ssKHA+1wPFQChyvX89v4HhSKxyvUwOe4XhS3nC8 -bm6VCY5HMUKB42lV8IbjoRY4npQCx5PwguN1c71LcDxS3BmOJ+ENx+uYkGQ4npQKx9M65w3H6xpH -MhxPuagCx1PR2wuOR5tbgeOpbqDC8VQL+IbjKVtU4Xg7VRFwPJV2v+F4g0hqhuOhFDgeZ77geFIr -HE9KguPpn284HpWFBY5HOKnA8cRCecPxpFY4HsyUAseT8oLjSSxwPG5igeOhfMPxBonYBMdTfq/C -8QZc3wrHoxSywPE6BlkZjkdlxAuO18kHZDiezqxwvEGH8zccb7T+BcdTr2OF46mH8Q3HG/3zBcfT -mRWON4Q5fMHxhvKBBY6nb17heCoCfcHxxuf+guOZkuB45bwFx0MtcDwpFY4n5Q3HG8uKK+B44xPY -pIDj6SV9w/EoRi1wPK6iwPEGNOdvOB6dnxmORxtCgeOpDfQFx9MwUOF4GncqHE9FdW84HrudAsfr -0SC04HhjOcIlOB4jT4bj6S0tcDw6Sl5wPNQCx5NS4XhS3nA8Wk8KHM9y4BmOh/KC46nhoMLxVP1f -4XgkNV9wPIvgZzjesJ7cBMdTLu8Nx8PFs8Dxlgd0wPHoHnrB8ViGFTielmEVjmfw5W84noGaMxzP -GDYZjjegdn3D8SiiL3A8epIKHI+rfcHxSP0VOJ7hJjIcjzNfcDw8aAocj4aFAscbQfopcDxQGAWO -Zz03GY43jvYbOB5nFjjeIGCY4HiDLPo3HE8XUuF4g6VbhuOR7n7B8RS9qnA8uiUKHM+wWl9wPEry -CxxPF1HheIo/vOF4EEwKHA9qUYLj0Zj2guNZm1uC4xEhKnA83I5ecDypGY5Hw1aB46G84Hjk1Asc -zxJwGY5Hz8o3HE/fu8Lx9EYUOJ7StW843uJ+LziePqrA8XQJbzieqb8KHE8RwArH05Ne4Xi0K77g -eBTiFzgeHs8FjmeNft9wvGHmyAmOx29b4HiE3l5wPL0RFY6nBV+F42kMf8PxqEjKcDwVtVY4nqb7 -FxxPJVAVjqcSqDccb1DTn+F4ShJXOJ6OecPxtCaucDztuiocTwWebzieahMrHE8BngrHozv7G46n -vFaF4xGuyHC8dzz7T0bL//ngeIRqX3A8KhsKHI+vneF4CC843qBxPMPxsC8rcDz1P73heFIrHA+l -wPHsmG84Hp9X4HhSKhxPyhuON+az4HiYIBY4nobmNxxvUJmd4XgaICocj1T6C46nF6vC8YirFTge -sZIXHI93NMPxqIApcDy+1wuOh1rgeCgFjsdnveB4aoqqcDyQaBmOx2j1guNh8WbuoHEcW4szwfG2 -knFsSXU4HmAB43cZ9g6Cw5kE+4sfR1bFUWo5K3A8bMS/4XiIBY6HUuB4hKBecDyCsgWOp9B8heNJ -ecHx9A4UOJ4FqBMcLwkJjrfVgOPJUu9ztg3HU1ZhWDdnguMBEYgKJlR2kESzA46XlPxrLDXgeAZJ -6BuOBxuOG5jgeCy16UUMOh7LcVYcmY6Hel1t4/FQWN45Ho8O+NtdngOPh9jaxuOB+WDvHXg8TI5Z -mGQ8nh4mCyIGHk8/Y8XjmcftNx4PW4mCx2PJUfB4NGW+8HioBY9nngcZj/cegf/RmeWfhMcj3PbC -43VgSxmPp2BdxeMx977weKTnCh4PpeDxOPOFx5Na8HiEVAsej7atFx6PCGTB43UW8xmPp5KPNx5P -xiIVj0cHXsHj0TT3wuOhFjweSsHjURbyjcdTUifj8egpzHg8sj4vPF6/zorH0+9R8XjvX/Ivf/Bp -ITX7wuN1soIZj8dxBY8n5Y3H65YrTHg8oqkFjyfljcdTpKbi8Qaki4zHk+/WG4/HUq/g8TRoVjye -NiBvPB5qweNJqXg8KW88Ho36BY/Hvrfg8UY4rhc8HpGBgscbhtVJeDw93288HiN6weOpoLDg8baQ -8HhLDDwePgoFjzfOFYFOeDzcbQsej5rGhMezf3/h8bboeDz7OhmPp+H0jcdDLXg8lILHk/LG47FP -LHi8cV4Vj4dT7AuPN3hvMx4Pm+2Cx9tKLhZJquPx9CcrHo/dxQuPhzdHweMRdct4vC1kPF5SHQdB -pWXB440ZEIGMx9OZFY9HBK/g8bRBfOPxBlb6CY+nKHHF4yke/MbjESUueDz2eQWPR3rkhcdDLXg8 -KRWPNzC4+sbjEckteDyUgsezY77xeIpgVzye4r8Vj/ceK//kvK0E9xuP97N4rni83q4vPJ6a2d54 -PKkVj9cZbzIeT8obj4ea8XicWPB4fPgLj2e1AgmPp7KoisfrMMi+8Xjaalc8nkoAKh6vL6RnxuNR -ilPweCgFj2flOt94PNSCx0PJeDz+5AuPp3KgisfTT1LxeFvJeDxTMx5PNUMZj6di4W88HmVKCY+H -u1rB46nO8o3Hw1Ki4PEoEcx4PNXRvPF4281hHdevLzyeKRWPt7TA41FaU/B4KmN64/GsaDPj8bTE -qni8rWQ83lYDjycv5YrH67g6fOPxdFcrHq9T0JnxeBRTfePxEAseD6Xg8aS88Hj6/IrH67jfZDye -lDcejyhXxuNh81fwePaQfOPxVEdW8Xi6GRWPl5RExNpq4PGoyS14PBUJvvF49AEXPB6ukBmPt4WE -x1ti4PH08QWPpwf6jcfTI1DxeNo3VDzeVjIez9SMx1MpRsXj6b6+8Xiqdq94vB6dEQuPh7/nC4+H -v2fG42HmWfB4tO2+8HhSKx5PSsXjccwLj9fH/MLj0cxQ8Hi9H7/B42mkq3g8CssKHm8rGY+XVPB4 -+qSKx9NffOPxuI6Cx5NS8XgoLzyeqRmPh1LwePYtv/F4HbfgjMfTnFvxeFvJeLykOh6vE3HIeLze -52/wePwmBY9nXssZj6cixDceT69JxeNhU1vweJoh3ng89pEFj6dqlYrH20oZDJYaeLz+HH9T8Xjm -OPCNxzOjpozHk/VlxuMNQjPfeDyzhsp4vEhUFzye1qYVj6eSkorHU77gjcczNePxTMl4PFO+8Xha -N1c8HkrB46G88HjkwxMeT/+ueDyt0r/xeCqmqXg8becqHg9vuRceT2rF40mpeDx2eN94PDweCx6v -KDTjc+ILj2fOoAmPpy71jMdT/P0bj6ftUMXjSal4vKQkPJ6pGY+ne1rxeDiwvPB4FBplPN4Y8wuP -J2PuNx5PasXjYeBd8HjqKH/h8SRWPJ6UisfjmBceD5OagsdT5q7i8VTM88bjSa14PCkVjyfljcdD -zXg8Tix4PD78hcfTV6h4vEHpQcbjobzwePu4wOMNZriFvsOK/QuPNxS/T3g8DScVj7eVjMfbauDx -tAGseDy8H154PJX8VDwe96rg8ZKS8HhbDTyeAlwVjzciQ5HxeIgFjzfap+LxtNn+xuNJq3g8lIzH -Q3jh8fTpFY832vjC4ykK8MbjrVF6HdavLzzeVjIeb6uBx7M/kPB42o+/8Hgqh6p4PIvXJjyeYt5v -PJ5CyRWPR8S24PG2kvF4pmY8HuWoBY831N7xwuNJrXg8KQWPh/DC40mteDyUgscboEK/8Xj6KQse -T3ex4PGWkPB4Sws8Hrn9gscbWr+98HhMvwWPR9VnxuNtIePxkhoxSlVXFzyexsE3Ho9HruDxNE5U -PJ7Cxm88ntSKx0PJeDxOfOHxUAseT0rF40l54/F0aQmPRxd9xuNtIePxTM14POpRMx6P2N0Lj6fS -kILHwye44PFo5Xrh8bDoLXg8LToqHk9f4Y3H03EFj0cItODxqLl64fFouSt4PNoPCh4vKQmPt9XA -41HmU/B4/M1vPB6J/YLHw42m4PFQXng81ILH47MyHm9VlhU8Hp0XBY9HZVTB4yUl4fG2Gni8sTNY -jr0zo99vPB4FZwWPR66v4PGwkX/h8VBfeDz65woej5VmxuMtIePxlhh4PNX7FTyeBpWCx6NW1/F4 -2mFUPJ4p33i8rQYeD4vGgsfTvuSNx+vX+YXHU9VRxePRmvfC4yk0UfF4NPd+4/HUHVbxeIrAFTye -qpXeeDwFSAseTx0zFY+3lYzH22rg8dQoVPF4aml94/Hot8p4vK4euoLHe0es/2Q8/L/j8f7r4fEu -NyQpdDy9zfeZxGtYmfVi46naRqXkGY2ndRR1LUHGo/dOiZkA46nbU3HSwsUT8+tzPBuLB2ScHaZT -8ThiPBmK99COspF4EMcdig0RT89hO+8KxLtVquJdV0RgNNBCI4+iYqXmtW8vPLwT7+Nz8/CO0yri -A4dHSs2RTIuGZy6IY8PwpltRLBaelsaqjiwovHHZOLlIeGod1nI4OHiC/1xHrxg8if0am4IHSmhc -G4Knj5ntzAy86SuLhcBTU8jZWtDt5PjxmVcG4Gmnx+/u/LvD8dELf7eEzFoLccPvVLX9bKadKi77 -F/lO9lSgkOOY26JCC3snKO95VeidNDWHL+Yd5N55b+SdUie9X5V4130KXsS7H4G98wLeqYlfdkqF -dycU6n30jbvT+3JEDlYP9RIy7m6LjrtTzGJ8MsXuMr+2ArsjV30kJt5nQ+4+9Y5/Ftbu4+9SUO0U -HWhjfEHtvKZrIe3UfvgcX0y7bh76G2nX4+UJop0KR9sXz26o/bUlnN2wYMmm2TnqpMLsHiveWiw7 -wsGamYJRN7r1KxaUHYkCPeQhno/X8jnITrHAPW45EooAYRsbY4db93Vtit1rlIzpzUqsmtYx9rgk -iF1TTz43PyB27TiNERcQu4aj6nlkih3avK5NsUO5+70pdo1q5z4rxa5FofOi2ElhkbsodlLGp1+V -YtcOb+1YFLt2NksgLordzxxrzR6FYtcwHDuOjbFrWikd/doYu6ZlZ3dAXXDsEE/6LTwyijLbxthJ -4L4VjJ1UfpSFsZNCpmVh7KRQJ1Ywdu30ioeFsWt4LR3H5ti9f8q/xE+tqnna7zLHTiqpgsWx4zjC -YsGxk7J93jxAIpEE98LYSbENb2Dsfl4BAheFYtdA9GWMXevWrr3odE2lAoQRM8VO1XdqEFqirO6O -1jfD7tQoftwVYSfTOXlrL4IdLnTP3AC700o2C76Oqv7j2vQ6bTTYPga87vRMUmHXqdNBZsaBrlPy -0dp6nFynmh82CglcJ2tHGnGCWyeIH+HFAEMtIVPrtujZNaNHzV+LRXc59Ksg684AUoSoGP2NVaQD -67aweXVbM1ydvsfjNcnK3J8ejiisOtgsw+hp9DpKiH58mvR0GpecQXWiE5w0Wzh07fSO+MWpU8IC -ymXG1PFe4erolLrT7VcWpG4LiVG3xEDUaetpEXUnzymnrRtVAHWaoI85tiizOVqSAk+3hEyn26LD -6bS+IXoVbLqHVpxfhUxHTf5h9XXckecw89Z1i2TXRxQkY+makk+Nn8mDAK15bnZh6Vq/PNKXsXRS -WTstLF1TXJWemcDStRGb5oylk2p9reHm10ZAdQJL17Cavn4VKp1EWsYWlQ6FNz6odA== - - - KMQaMpaukaUYFgqkv0hjDBHZhaV7j34xBao25rSBLGHpOvb4rW8snUqALHYTWDpZKnmDU8LSdczM -2thYOsyYrE3SsXSYNkGHylg6VB8uzSxBJ3YfY/FU5MOBx2Uqnfa9uN8vKp222W6o6VQ6JXTddDNR -6fSlbvfR4HN1Yjf0WSAzP4+/oxlKx6Ya38eA0qGQhwksHUpw+RaWDjUsWqcdRMQwmHQoz2MdCsGk -0xc3y8pg0unSuemLSbeUzKTbYjDptP2/WVQFa64phsimJTPp2u1VBvu4MF1bULpGzY3iGRlKp+Po -215QOiDITPEBmwOXfbFIS1A6AMcdULGruhu2WQso3VYylG6pC0qHqwwtwgGba3jsqpo8Q+naFY3D -6zgBSub4tZh0S8hIuiUGka5p9wnULkhzjaHzuH8VIp0uw2KZ6ziQiRYxxVsdnxZGrgykQ7UZzoF0 -YMgfB9eR2Wox2hYgnVRCAgtIJ8UAjgGka/SePnbMcjPSpWEFtYB0ILRxfQ7QnD0px9yK3VcSS+kw -GN2nzT+s85OSQFRJ9Si56NsMTQs01+gHOMZW7GJvWxrt457ldexAuq1kIN1SF5Cu0Tmi1WuQ5qCV -a2GceXR6CmwBvQ47TmtVMCDd+mcB0m01gHT6ILvRAZoTdV7HFx5dU/RhJlGvjLmDBI7OnIeOo/Do -8BpqHrT3reThJq/Bo+vAoZwUtu3QFGc8nrl5dFJw+V48Oo6xaoJkFicjLual4NHJdQm7wgWakxGQ -5b0zkK43DyPvww7/YYNHl5TEo9tq8Og0cF7es2J2r+1jdo8FR9fpJ3rOjaNDIQbgODo8iyB4Jhqd -ib1vGh3nkdsPGp19Ry4q0eg0IdlSKGh0mnNtLRQ0uq1kGt1Wg0bX2W/r9fb4O2aNHyoYZnoE3H0s -ROyz3GnGlipqVmR6zTA63Kbo/Q4YnYYKp5Q6ZK5d7u9ZYHTsxuiqXMcJQ9TDy4rd8VLKGBDqgtE1 -Fd0QbgsYXVPR60GzWYLRtfmxjdmC0TXaXh1/wNq/Yf1+PRVGpyVeYxHvmLlGsGNs7hxxlM/pL1Ic -xWcMKy9hhn28BzKz6NCu0H52EgjH/WuR6BBGCI5gkBGaspWLQ/e4nc7C0EmAip4pdBraqL8JCJ0q -321sNwadLtVuyUbQ0QV1n5tAdz2x8Hdz4DM215k/Z55Zmz6n/Fnrz4bPaV2ph6qw505fPy30XDwP -izyn04jiZPDc5e4pizunufTsbWPnbnfDKNQ5KsOhiTl1TjOp9Rs6dG4LiTm3xEDOqb6HQHoQ55qm -C4b/jJzTI8HWfCHnmjqOI+Rtz7Mq9cd5VeRcoxfgvDdyTgpVbAs51+hw8Prm5S4vlWr6QM5JYNIM -5hxHcIszc64pHKlg+mLOtY+bZS3mXGuW8i/IuUa3cCLOSWCZu4hzrbllTSHOoQ7/OLuLOlObkiDO -8dmsUTNxTl+AoTuIc8+zWXN0aibK3PNsvlxjwlIMP7hxrUFAr3i51k+LY6+jhkUpVvXPEjJbbomB -ltOGjNK2xYxrqkfQQ13Yck31Dbd7kdtxujWKuS22XFISW26rwZZr3Y29gi3Hl2mERRNbDlXb9cWW -k0JEcbHlpEzuVmbLSWXcW2w5lPvTNlsORUvpwpaTas3KjpbTtdKsvJBxjSqYO0Hk7Fv6+LuP+3h+ -aKHltpLRclsNtJz9hTOB5LSZphWuoOUas3bPxymuOdxAZ4Ud2SQWWw6FJ4l/OlmOsOaFoY6T5baS -yXJbNbJcU08JOzYnyyloYd3YmSwn9eZ3C7KclPOK4/glm9fDFLKcVBspgiyHcl/PJstxptYshSzX -+mFhrkWW0020zViQ5baSyXJLXWQ5vRKqBljEuNbcRLKQ5Wx2xYDd1cdi74srF//eVDlXFhEKSyyN -vYGK05hHmrsg5XjcyC+u45pbNy+mnEKzPMOFKSeVZ28x5VAOp8wRxOHMz3NXphzqec7NlJNCHG4x -5aSw8y1MOV2bn+lMuaY03KB9zJlySylMua0GU659Hp/XghX3eFCoIOWejy3ilnhP89kJoJx2+f08 -K09OYRNLpDpO7oqkTtDk5NvDOi7B5C6rmlwoOe2kP+3chDgs6THbTCC58xPPp4uygMfIIjByS8gU -uS06RE4hLi3dF0ROm7PnbIUhp7wyA2wg5FQj8/n0TZCTwFIoA+QkcpOCH6fPYRwKfBzl3o5lW/S4 -c1oJ0YLHifoXvv0kVLaw0XFbM3KcxrL2HBsKh5NpFlhlfXx8DhF0eUbHXc0fmEyOw7JrPBUcJ6uK -8bk2N05DUacjzLFxIRRq3BIDGqfoPK9PtF9ovKAIqjRkaDImq7KPm15/sJhxSynMuK0GM04jMRbd -ixmnMKrdmcSMU2bNwmfBjFNIyWykAhqnCMbH+icSNE6BMhufAxqnM2E3FGicQilUxS9onLx+2aAt -GJw8e0nKZ2Yc3Ze21IrDMFGizMOZcVvJzLitOjOOnqT7skQB3uYyd6ZUPzPj6L2ksDWYcb17L/Fi -xr1DzhGM/i8y49jzaZldmHFtRjAxmHEtjOcWM04K+bPCjGtKY1+0vjkzTjvZ09blzozT3fcSl8SM -Q51tI+MkWEokkHGN7uhrVGQcPyajZiDjpJD4CmQcoTNfmSxinB4h1pwLGdfoIrw2Ma5dnwqLU/TO -bHsCFidrbVJGCxbX5UygtXmBxSlKPOytBBankAyxxwWLUwCDuFCBxfXoDQhYHIfdTmTBEFWV0aoF -KLA4VOAl3uxOfMSy4c6Kk0JYubDiyEJokF2sOCuMO8/FiuuacbX5Lqw48BVM7gGL67j2C3QWsDj6 -nhXZzLA4MAIqIwpWnJqoKXQNVlwno3s8lRWnrmrSuosVxzfSu7RgcbrNtuLItDipmD4uXJwUQtkL -F4cH/Ak9KOHiiKTpN124OBTNHwsXh6K9RsHFSSXStnBxNHdr3bhwcbo9jF8FF6f4FFmqxYuDfHHx -zZ0XJ0IqXtSFF6fmS97uxYuDjkEFRPDidG952gowTgEvtgoLGNfDQCiAcRogydcVYFzjNTjuDYxT -VAzDmAKMk0oAZgHj9Cbh27qAcVJYRxRgnFRGuwWMQ9EeYwHjUM6z8uJIYGh0XMA4XYTGqMWLa2DF -9A5mXly7HxsgFi+uUebc2+bFaTgkplOAcTZIjmMT4xhIbbxwYtx7CI7R+88R4yx38GmVGKfRDCeX -IMa1J5K3QYzrn0jzJWKcRA/5B13oc1kgYxHjUBTbKsQ4VMiVgYyz4w4nxNFu/jH7r4KMY1C9hqV0 -yM4pyUZRz0LGaUDH7q0g41pgYBYyjhmJQH0g45R2o8ynIOOkUj+0kHHtDHxmIONQMMXIyDip0DiD -GIfw+J+EGMeJ+EFnYpwWMLi0LWKcLt+WdwH9ev+Wf/mDz4tqOp9ReXFyqb3ww3YLQSqTMIp2Wpwq -k/SFCyxOIivVYMVJOMezUXHUPF2zkuK0NiZIGKA4zeYRBtPrLhe59jwVE6e9D7HVoMTJ/0qrqQWJ -09x/9Kcy4rRAmM/ciLjTc7OLEKcjPvOLEHc2WwYsQJw+/HNvPJy6QdszKh3uvA0ZtOBw2qidNI07 -G+4+bSbMZLjHPZ0XGI6953kuLtzP6sCCooULp7i6XWNw4VrkzxcXTkt+8q+FC6f9MaUiiwtHBFPb -tcWFIxh69FG5cIoDsCpaXLhmyOm2uXAKQPCiFC4cYYnnvjcXTgqRtcWFQ+lfWLgWrZ8LC4ei+Kxj -4Tjteq6KhdOFafW0qHC6X4R0ggpHKME+OVHhFMihA2pR4RR9Y78ZVLimjlDbWm4qnM6zXH9g4fRz -WOFeYOHUAc4WPFPhZDA4+txQOD0PExcEY8Lxeez2ExKOmupxbyKc5imKLwMI98S4knlwmnDH8Wwc -nEz5oxqVSmBt8z94eicYHK7L971ZcI+vKRYLTm0lFEtmFNwT0U0nwenfJCMCBCcE1vEUDNxz+k1z -Chy3xxlzZNEfb5MsDLg7Mi+BgMOiZI5FgPse9v7kFDy9hrTg36AxeIe61VoPnweD/iY8ZlTtrVij -RHDKwX5TXPsY1n3GolOdbRdNUIn8JifkT3PRP9zmo+C+KQ8Mtjc7eUFmv2ynedpWTNC38SugbyQw -m9VFLUtQVR1TjhXNR2rpO1T86MS3M9zPMvBNhUW9mSUyzze+xpe7dGvNIOHpLkQg9NQS5ra2MZ4Z -1Vcd+Ip6upJWLf56Yr0JFXk0qxFlar+6k4CD9HZ/Md40tlleNRhvqjN1A3tnvKnMDUR1QrwpeWFr -qkC8KeqLc9NCvLV2uVtrIrxpXGFjEYS3Ri9ltGZpcNXIQ+1lBrwpC2KddwF4IzVm3ZQGeJOJ3PWF -d7vM5mDB3dSViquqs92Ox7adBe12+qpskd30iaRDAuymUk3FSQrXTY0nNB041k0lMzcVSE51U8SI -QS9D3W6vr11Mt3uEU7Aj3cCcTwMvLaKbMDzA60NUMNUqepznRjfhdJRejLvmgWMrWl5QTrODyDzf -jgwpKDcVnZ3OSWalKCoF99pJbrLG0ra/gNxur8dcHDdo5p9zY9ygC+mFyxQ3PdB6qxfETQW6N1Q3 -dy2TZ+P1OQrD7XJO20K4Xb5LXQS3211/CsBN2AwjJzi/TSlY8zsIfpuSt6QTM76N/D6pQqe36Twr -PAh62316NjHD2y5fVyx2G2UjfZPbpu+VM7htXlFy5dw2jb3UrAW27bBLLNC2Y3rBQTDbJJx+FMlv -Ck3mXYltqlAhgBfAtsNXBIvXJp9YucIUXtvEd+/XorWNjwV3A9bWPTZZWG3ytTjctPkx29Gf335Y -K6xbJlgXeea0DQ9qLEzb8HT9orQp0zgcmbYgbRKtGrtH5aQQbX0j2sZty9tCaCPGAPfDCW2yHu90 -Z/co3PQxPgPadFq/bZIxexllW/qz8WzKsR6XESsXnQ2MCeX7DmfTesRquh3N5oVQGc2mJ07fcpHZ -hneQLDDbHBabLFw2msWUvggum0ihmtIXlm1gqFyhbLDKtdMPJtvlNbYLyXY//uBnIhtZeb1FDmRr -ChxR/BBAtoY7+N0rkE2TCAPSArKRNJu42DmQzVa9s/LYDPp3bRybVsyWms44Np1redXAsenVttVh -4Nha82m04NiqqnfZFBpNnMfWosKq8NhQGS6Dx4aiX2zx2KRYmjHz2KRSNr94bI3q52dsHpsU64/K -PDbNn9gjLh4bCXzLVrmhr6Zw6nQLjw1V0bvAsUmgmChwbBJmW36R66zDutucxkZaeGYcWyNOOe+K -Y2v0Y11949ikeCzJU0ZaktwX4JCEY2saLUixOI5NaR57pBzHprKvYVS6jWOjYYtiGqexKdduufWg -sTU8nBQZyzQ2pWFYzC4a204CBI2tUdV9j0pja2FfvWhsKLNtGBsnHldlsUkkIhEoNgmsFgzFpn8S -iCkoNpWksjlbKDbiNJStBoqt0WSrxXBGsUmluipIbA3PKy89IfwhhXmioNikfugGCw== - - - FpvdQmpxjog7XRbZzyy2RlpTb5+z2JQ3o3R+sdgaGNlxZhYblYBa9SwWmx4RKyYPFhu1BKTlMout -EW9vz2ax6UyrfAgWW4ump8Jia9MLvxeLTR17LK0Xi62xE9M+NrPYGmDXOTaLTWfavB8stiaqni4i -o9gaJWa6/YFi0xdfW0UacBuwNa07E4pNIgUGC8VmCrguNxErJy4WGypLlmCxScG/bLHYWiDYCout -UQnjaE4S5W34mmSx2PSG2mdlFhu1mDZ4O4uNqzhoD3cWW+NGsdlNLDb6F0llOouN+nviisFiUzMj -0bzMYtMY4JwHZ7Fp0LHYW7DYVG5mA35msbExoSs7WGwtumIWi60NNxMpMDbGHVtFO41NL+pttWUz -csQBQcg4NlSa7gPHJqUnGJv+Pa5RWWx0XDx+ms1YpJaftllsLagUhcWmYnuS3YvFpsJ3uhIWi42c -IT9uZrFZbPwem8VGi9DN/feWCqXKyBEWFhuWkWQlg8XmhsN9sdjAtTOmZBabEnmsCxeLTXF2tj2L -xQbrl/hSZrEZF/h4NouNsD0bxGCx9eb4g8Jio4Cc1zBYbPTiMI0Fi83g8k+vLLYeP/VisaHYaOMN -u5xp1gyJxSY/SossBYutR8XtYrE1A8uMymJr4am8WGw0Z7D/iJS40ihWSpJZbJzJ3wwWm9JIlCAE -i00AZptOE4pN12HLlECxdRZt17NRbCSTo5ooSGzKvBHbWiQ2tW/Y3B8kNnU72Eo2o9goSH8WPEDB -Vy2HGWwCxaZWA/vBM4qtQ3bRix4oNt1qq+UzFBvdV8SlMorNPFTasVlsHcPYT98sNin+SYnFJtUK -Lo3FRp+SRTqdxYbiK5mFYiNlTbt+oNgss3VvEhvdGkyDicSmr23leA5i0+tg1QYOYlMalIVG5rA5 -YvrZHDZ9EDWdC8SmC7BRIIPYULHcyCg25dYphV0oNj3otpkIFBs9etSgZxSbEsOsloLEhp2w2ZA4 -ia0Pi1oVEJsSxZggBIeNHxanguCw6fG1YGLmsOltoAB9cdi01LN1SXDYGsZYz/1V8KkqHyvONw6b -KkBZ0C8Om6Z6ajYKiE11RWQIFoitQX7/2DZugdi0DKGMYIHYlHvFXS44bA2PoHZWDpsWw9Y4GRw2 -bbYsyBkcNvzssH1LGLZ72pC+KGyXD08LwgbX7zwqhO30/MxisCn+AHc4GGyvmPKfDFj/8yHYqBXo -jlZbCDZqBXjWQ6UygLfQEWwIl5+4p3HlXzd/TY+m1VMGf00thh4QSBlIqaBpFn8NZTymsJ6wY07v -JFnVzhGhWPw1KRaNDf6aFMqKC39NzWm2OdAq+vHI2AKwaUC2wqMMYNPMYIuNALBpWCCotzqaVRxG -CLUA2LRA8BiNA9hUZcKtXwA2DQGHt46XcYFpMvhrVJScTukikyaFor7CX0OlkC34ayjjeDZ/jc9y -ttsGLHWcH+fGr6kCzkcB56p1WmqPRGTjTPLVmb/W2U5k/tpWMvErqd57hHN95q+BCPjmr3WWzom/ -pguzaF7w1/CpxkUh8dcQsYMI/hrKwTdy/poUrRYKfk2i1SgEfq0rtEOneeDXpLB/Kfw1+WpbD2gA -2EQlYEcQALYkJADbVgPAJtf3AmCTQ/0bwIZNfQGwyVa+AtiSkn+OpQaAzWz4E4BNP8cLwAYOoADY -ZNb/BrChFgAbSgawSXgB2BAzgA2QRAGwSXkD2PQ0VQCbfsjD/EifMLzzdpACYNMsb9mMOExLDZYo -wV+jDfH2rppliYV6OFsN/poUgpaBX3uPvv/orPJPwq8RXbPHM+HXFOPEJGPh11rznNrCrzHlmgl2 -nprpLRqbvoaA43nQ1zhRK8JCX5NK8DLoa8RPaS4P+hpNTUT4M32NeGNYn1pg5+P/sehrKqww05tM -X5N/hrmiBn2NZjSKmIK+1uIJLPQ1VFw3g76GMp3SZgGnT2SQM35NOZmPxdyNv0aHHbj3ALCRtpmB -aIsMqcoTzJ/CAWz6RWyVFQC292/5lz/5vChxaq0hCcCmplK6OReAjeP0gy8AG4pWWwXAJpVXcgHY -iJ8uASORj03fhb+m6Iz3sXgZQVNtAc1PwVWTY5TXfKSNOX29I3PaOqz559j8NW072uV0tMVfQ8Ua -K/hrUiwPFvy1juelyh4zf43GdMrzg7/GXreNsflr3Sy9e+WvEQ2gsjj4az1ydou/pu3Pac5Yib/W -cA0FU+T8NVXnWTlkANiSkghsWw0EG84BxGkDrcawdN6VwKbdrtUuhkqJoPVnGoLNhHFUBltSvQyh -f2JHHxA2janW95IhbKhUsgeEDYWYQUDYpNjVZwgbe8Q++0aKdTpatZtyCBt+pGYTliBsnbf3mBvC -pq2/BS4CwraVXLqRVIew6U8ebNcCrqZbTVNQZrBhR0FqYB2mWBv+l85g20JmsCXVmQPULl4JwdbC -p6kg2HQikfWFYCNqR9I47pf2htaLlxFsClBaH3Iw2BQdVoRzIdgUB2blXhBsRIet1MoRbGzx2JIG -go2UCEIisCE2p6tRbyPFYsFBYJNCRKEQ2Ijf3n0D2BD8kxhM7IhPq/w1ha1ptgv8mkK+5iMT+LX3 -SPkn5+0Z9ZiZvqZ9MIX2QV87/Kdb8LXDDa0Ke02ZCpoPA73Gquq8Nnnt8F7ZAl6TGMOyfhKdpfdv -YdcOWwQW6BqZ/tYXc438wvFs5Bq1adRRJuLafDy6HcA1Oa1FJR1VO4aI7BW3ptRLu+emrUm4HKJG -5J/6mc9TWWtHsI8DtSbBHDMdtaa/9rQKWpOZxIasHR8L3S7G2hIyYg3x2oA1tYqbDRDstCuCnxuv -dnvZmUuKWROLDriaJsP+hVYLC5hFCri9VGcR07S/IUGZwWrLqyDE8+MtjYFVO8MjNGHVlhZYNVW7 -kLwLqtodm+a8R6fwcfbNVNPCyRwBgqm2lcxU22ow1Z47migdlaYB1aaqRFTTHEJxYYhYTvVr89RU -yMQwlnlqd8TJA6d2m/PKgqnhMHkeFaamplBCJcFSU/qWPHig1PCm/TyVpEYIuz8LpHZ7z8aipvE0 -aLOQMWqE28j/O0VNvIfAiZFO3kLCJi0xEGoKjJtxrJPRtOSkaiwD1AAAj7ZFdcnGhI8t1hYSP22J -wU+73c5ncdHEndHvUvBp+rUpUl9qa140sfBpW8n4tBZW5wufptIJq4sOLJr6PpV1KPQ0fcjMYvQI -LHbaGR5KGZ12WoonwGlY2LtTKd58p7ccFGwaW3CabR3qJBtbcyV2aNo5YppJzLTTjKYXMU0l/dcZ -eDRd6m0/UuGlHe66skRt1hibg5a2hAxL2yKstMO9uRYqTcFQOrQyKU0X8LTNSTvubRYLJk0Cq+ZM -SUO854akSWguwEjji7VeEWk40Z7XJqRNq+tYfLT4d6ajLc3ZaMQCtGCPDls56eOrnRtxzyv6qhyM -drkVyuKiXR8r1MxUNOEJPtdmooEc/9wbdaamKz42E9HY2Gn/v1QVjHipiRPRtpJf7a0GEa31e5Vr -AESzdnn391hANDMLou3DgWgyXbRW6kCiteErrIJEM4MiN1Fg57/SxYWJpsViEdvp0KCFRFNnENZq -BYlmag91Xn6cW0Rb3gDlDOWOshczhlpENATKzIKIhkI5RiaikZRmbHckmgQSGguJplUzRa8Fiqay -FkviBxRNOywb7wOKhsOZuekkKJpUd7FwKJoU0o0Litbc7Sgz0bAYbN5+CxOtKDwMnOdd0wuJ1rBe -om7HyqbUg82DFEw0xcStSGQz0bQ9sR8tmGhSbLMWTLSkJCaaqQrp9bU/Cb+FYKLhHXJ7UeliolHx -Y0UJMwwMved2QdFk88xbXKBoUr1tzaFo2EE7Jc22ARrTKZvMUDSp9wjEmhYpGtbNJdCZaBwCZisz -0XBXISUejTHKolGwvJhoKqqxGSEz0drhzWqLiSaFetzFRGt0g36eykRDZSUWUDTObPe2lOTDw2Ry -r6g0NemlCSSaWu6s8DnanlEojclItH1cINEa85d7BLnVt201NxOtKaaOu6YfM/w3WEy0rWQm2laD -iaZNmUEMgnWGtwGF7wmJpsobu4HrsMN6thYRbQsJiLbE4KEp4ESEZ/HQWmQMMg8N8fNsHFqb3jgd -ODRtfNl4Zx6aREbGwKEhND+Kv4JCDjfz0PTx9goHD00Xij/sAp1pR86dyji0PUKvw86PF5cFD20r -mYe21eCh2R+YC4em/TEB0cJDU0mSBfFCJYJKQNx5aApC21Yv89AU3DUsRfDQWlharl3OVjIPzdQx -Ng+NelA6RoKH1jSKWUdp4qFJdW8I56FJsTJv56EhkBXLPDSpNmw4Dg0Bm+XAoTXQkE5WWzg0/ZIG -NHMcmu4i7/TioW0lAdG2GEQ0kuyA6IN01qCIPa0S0Zh9cW1dxynxeOLXaPZoW8hEtKRGyJCWKY3G -QTrTKOjcj0RE46nDjnEdp1JTUgNBRFMgl2E2A9EkPu3cPDSE4V+bYjPO09NRgGioPWBnlD5oMUMU -PYBoUizzk4FoujI/U/7G5s2ImbMB0baQgWimjmz1SojhPH4t1BmhNGt2TkS0dkW9/DpO8VFYb4FE -UwFTs/L/hETDJBaD6kCiacnxMQCaI9EUaWTNV5BoOs6KLRyJRkDysYZrcsqUPn369asQ0RSoNnf4 -dVzz4tFFREtKIqJtNYho1NtMz3IT2eZvPvMLiUaqHSBYINE6Tu2OThtmWa0g5XFXJBoqdy2QaHwW -VLVgoq0Sr8JEU/TXLCyDiUaNErmnYKIlJTHRthpMtL7zSl5bp4hzVWxVdtsAvlSF3ykyWEw02QR4 -92BioqFSEZGZaPo8TMgDicY601h0zkTbSoaibTWoaC0wFctvVS8fHgPes0XVLIUBw/YZVqERWDRT -7jRycNJSA4uGuyCDcuBuGrkZ75FaWLT28cLahUULA+pFRdPimxq2DEWTqQ6NFBEoUB8g68vMRJPv -DICfQKLdUYYcrDNZX9K+kIlojKZHOoo6U2ehUVCcKGjst5x/NsL/LvBn07kJhX6m7qXu7mOTPpm/ -sTpBbwx/RYv/ZCj6/4/ks//MFRy//sX/9H/9Q/5gfdC/+/d/+7/++7/+x7//63/623/57/7Tf/CL -+HkH/sW//Ou/+re/Ofp/+/t/+9e//R//+vf/x7/5h3WwcfH+0TP+97//1//wb/6zX9GP/B/+/v/U -t/z7v/6Hv/2f//r//L/svdtyJMexJfoF+Ae8yEzatgFlxj2GTyR0OZopXUyUOJKNjdFANJrEFi4c -NJqU9DPnR87b/NiJtdw9siqzmig0m90AukQTCTgyI+Pi4eGX5R73Pb06f3n3ZZuX39zeXL959qwT -N9/Onr13Pk5uLm0+hjcODl3+0/nt2fn13dTn8Kam8Z/Tr9p0cC1XN2fTO7Nl/PXBzz79nf/y19cv -tAn8HvH7l3+4uf5TY9a7xq9HR0L+7PzrNri1Pxz84Vv8pchfPrt9/eoba+fnfzj//g== - - - UH9pGuwvDobDT9v///b9wesDoO42AHbD4X9vP/xXI31/GA5/f/i//vdw+AJP//ngqO1eRbiXAfY+ -bsy92iQjCAw9YAVyE4bbKPbqanuLq4NrduyPB3BWtVMTFcmasEEjQMHS0Y1qeR7RcmRGeoRF1n8Y -YrMogR5sHwCEvP1+iBpuAm1il71ihCfyapPcurrafH9JmV59OfvDIHlH808pefYpODf8ou2txN7A -7IMoDYD7JWcfNPLsg0guhLdq1vqbyL2R2UeBqEdllNlHjTz/6Chif976dvLUyMuDvx7YJnnNhwaN -Nwy47AnKz9WMjHeTthjEeflGqjax2t6yjpk1rIObf3KNvNa4KAs/QF3/5LLlzRH/x+sDoKLQgN4y -0ZtlF1CaC5cNzz42Udc/hoI1cTnsNfJ6Gxn5WmlORai5II/ea5BIZg5ebs8mWNbFONcJjoPkAc4E -JZZRn6XLW4isx0Qi7y4/0wYynJvtpyjxflm99lQWIi9SP+KP9GXJk5KiD3ISWDTfyrlZgEKUcBk+ -xjCKEAkz4UddpikpZILzOEYGh/RjTrqlZce1WwhE8klN8T2SQeLuYZIT4BgyruSifaxqt0bJuyEx -ZrolhCtZ45tkerywDnSXViECb30oi9O0PXeoE5/gKVZykvUdKTe1AWhC1tv2rBCpEPKjzYDXVYD3 -I7ABAIbgW5JuUSOVt+CXkiEw9YEfzVINTMhM4RX+8VWngLq98B/WSIhEiZH9NE5/ZExVhNzsFFkE -TX5pRL2cVJ+smU8i7lQ6GzXbid3SGiI63Z6DBa451qyjqqP0Kth1RzID1PJJ5sXnQmxmutcO4M9C -pK+FXaUP3RrIw5BltE3Ejfoxx4o5IDI5UoUQ3DEk6v2UNt0sYOE0EKUfK7YwTP8SokvSqtb9OVMu -YLEU+YL1tsDnp9ztk+McokSJzCGC7agUc6ISjKXeyek01UUiDBJkIpkINoi1IoaIbNGashKHUbZo -svjTESePBVvZAKSNtjuXdxCMg8hCoFmaKSiTPA6pqjDkpgi6ICE6m08RvHCt4auy9qzUTuIg8GNj -CdxogGXOSfe1Azo+6LO59A0MIUNiETi5kFndSEaFmRViE4LK1ITZyaeI1RFOL770Zc416gYiMkj5 -xF6H82DrDJzp8RwkaiSDgR/wyk484WtIoVF3K3wLRSVWsfmKctzLZpT8SpGDzo8qoOmM6w2odO0T -DuhDNIYI0UR5kAoR8kANulub2ag0CkTdwinroaFJQ7qHnXd6lrQFsgaG4oxVdQ8vpgBz0yzDv4qK -3HTqDQV5F705bKjN4+Gn3z5Ib45dy/WqOnSK/SQKst9KSXqebrTzjhVleEqgzLaNCbHUdt7VJtlr -ItbUrSXFXl1tb1HVKeRnNxZZfKqTNxqOSaKebyRvfnTRtn6U2eFl8dGJvNE6wjLJLz46kTc+umzb -lOUiauzsoxN5s/UuVN5A3vjosm376Ghq7OZHxzXtdmrdxzVdeBt586OLtvWjTqtczD46kTdap/ax -/OhE3vjosm3Ty7Npzxsfncibrbs1FXwbeeOjy7Y3NfMHCpO/Xl+fXp2/OEwbggT79yGiJMAhDocf -/9MG26SfZbsKbbVJAwQYwYrV2rvbaPruO5Qo7AXjmlJPqXeWxVpIbfrjoRCd3AxD5G4wog0BaKUK -v6iQg2dsD88ivUcbqEBnoVXfVLnDxddPwDBhQAGlVElXe+LKyC4meZyVrzmNlTceJXk6qBTe1shK -WwdQg2AB/mEI2jr86sMoZN5IL0QXxixEJkvJB4sftGWGIE+0H5mJwexHU5Z692KWO1GIFwpKRjEB -I9dU7HPZJW2ZUe+t/bVpgqZUdfLTkGwg8El4JZeiA8EVR7GvSMxKZD62NFCZUqjkKONDeM2P9mws -RoS+oMQcrAFWSBfq6II96uxTY3XVaMlenw1gGtlmu1e6cIVQN3ncxjUN1hedsTd1a5Nq/JDsXjTw -cHGuT2NbLSfk6IPTdQOi0baBz4NMDgIlVZtGOXRt1mmrbvRVO4Hqhs5etxUGeNomLI46Y4tunfT+ -RuFTYMC8dZfVsYTKi31WRk1Bd9jooM9JH+TSp6KXdG1rdVqKSlQ8730ERPCHPyfZ7bLFvbXsYFaS -yNRJHZ0UHCyKrbfpcYOzBlyfHkG+yiA678z6NXW4yU3dWANwebacabBeuDAYVzaRaJ/zJSgxVa9i -Jpa4tivKoL1gkWRla6LD2Qs8u7UHJ13yjNW2N6sqmuSpKLMfUGqCqAOTEDkUexoKspGLchAuB8uj -ygg3jk6ISWdo8TXrRlnbi6FNqHZDL7USLkTASIhDUcYuYw5GS6Mu/iCXSoOc5ViQZ30XdChMmoyP -EdDTLsBklb0IQPvWfk0dnn3QOhyMStD9yh6udZ2TlRiUNqL64BuG0TcY6zFR6AMmaxxUtc9BEKjG -8ykPJp4H36mzNkzaVK0mQgEAPrtSsg9B2Xnowgb+iFGnlDWjV9vbkMZZkpmA5dFu8blSsqu6Aoz7 -C41SnIpGTTDU5H2pXGPax4m+n/U4dyzOJ7Tqq/ZhEM5HRUi5CJapYcPYX6+u6Nja5CV9nxc2qHxs -87q1/yc6sHHtD+SXKyUzeVhZNlfthPM23Dan9Qd6xntLbCJztkG0E8XYMOt0LTpw0teT4HuVhNn3 -9RxCrDMZC3RcsZM5qTZR+51pXvAwJ9rAGHO2dofal14ASkLW3YSb13MwYvSHWzs29bi1UEzrChMH -vuGDUhqQZOh6QgyjiUgmTCsxFjsTCNSwR6tNBLLOtFGWEZs1OuvXtP0rix1Kz6rr258lPKYzSEc9 -9Hmv+Y1TYc8GfZYsY61mbdXFLq5mPbCu6c0r/ENmSWihpmqnboV/U6Wby1W1xiGI3M68C4Y0Vtcx -SRpjVRHvsmjKeLSaPkuc+Lbvn/UpE9wf6RC6XWL6oBPBbE4TmcbcQCcXmwjW+hRxF72JaMNBEltq -qu/ic6tp6aoJ3iK5v294Xp8OVVmQEN/V9kYmcZdrtoPW59TFXdUxSihAtr835tallo1ezMxJtcsE -1dsCPGgm64ZBv0PPuQrQUqMRc+zCTi4tk0Z5/LAOZ9+gVLm39n7aoNUmntgvG9XQ1wOpkUpzfnNz -kehN0xnLkHvHhtFZx0Tgyqdsaom42fZ9E8Ka76tDS3a6eDlmhSwqOJNuqvWMoWUlFuuCwvKNPPjl -s7mYbcjYixKjrVjur8+6NbEHi2AJs0JTsomUK9qEsavNpK9RG+HJo8Q+Mu/WjkOiuYSMYlK6xLGP -rd7DIpL/JN+yky8PZtvibqat/Z9Ow1pN96WmZadhHYoectXpkarBJOGywdkRl1XV02uQjPUJhpcG -uOPZaLIjshmndhrOOnDSp3woxRqWqhBGHnXEQ4zZ+DTb7A4hd+ZNtphD8NOUNQXNhgbb2ohmD7aj -oG+JzR7c2zWpNijGha2b89l4D4bdDzKDVNPa4P5BAcNrDpQf2pcx2LnLyIzOTX92CPGHhwYPvDAU -QD/V941Z1KxMGtEgUfWSJFXVhDZUZw9iNqRnescjyQgiCq2mbKdQdv6efd1lQDSRtU5MTd9RIowW -OS/lxjyhEi/Hb6ndx8LHwY7MMFqbcsEcaE2LC/19qZEnllKJ+mzjP1XMkw/2/aw6GMoEhWkAcvkG -Gxi1AWA5nc4gi9IIkR4tEksqk2iarcDKyNKFNG0p5vNmIbJG7NaFtRV3WqeBM45PXClZ7mUUezbq -55xesd0FpC4ua7wLh+nuQ8mhQfc1D6e+kMrj2Gijs4Vkki4b8KUTlwLaySWRunOim7q1OYZVF2/Z -WyMewzDx1swMUxqSmhGIT495Q9US8ZRH45Ewafs020j2JZgSkIozQy0GZ9r+OGkc2fWBsPKPfGyy -DAiPEIZ2JjSz64IfeTO9A8V8fvR86bdMYSWy/Ic0E7m7ee0El/vTJ/ExmrXzJvneuzoUG6qZmKBV -Ox9SNTWP1rS9n2pv1hwVm+QmpVJvoqvMw7iNGOLUbjI3jOBHhBhNEaM9YquVzcaINU8N9IN2VEGB -b3UvV+rr0t1LI2Ki07zY/nAl9iXsRDh+lpw5TvNSQ7CFQc6uPpvMxmmb0t3XAVMax5CnAUy+u3zP -BE6PEr2ixOi3LMuWlX2T4HB9CL6a/JU0x87aW3fsmuemighH6fU46f92nMtVS0ZLRnPJd2LR972F -F6qUWpEn18yr4tWJFuB+2/r5qV9efXwIUIba++X1cElyUYT6xfRwSKZBiTUXtQ8snmmWG32R2sA0 -CGffmizK7QMbxqiDUKaj4ReTdopJYOaXGy1akdPazKC6hJxYPq4ZeM4OslzUZ1jWDEoXqvn7ctAH -26YLvVnGAUhmJUA1EIuJU0rmrRO7rn7pWexr6MqX7dsoyf0rJRN7Nh3cnVyzPt1OmdDJQ/Qao9Fg -k1wxZp0TzM2yD2t2TTFtjSlrc7vGCzrGDu9JLtGUXG1vxE4zrQ0suzDZyHFmVjtp5U5o1SNCMj0X -WRurrW1o28SCyratUhH8SslJG6+S/bpScoxRyUOVow5E8ftrKsXWZnWmeC2LdKSKdn5lZPUlanr6 -SsmmNhTxcCtRj8ZqEbUt7eoHUWk7dBkW1cECcu6xSObHCHEySpwIbNCSBSJpTJ3o+6XYGg44kle9 -2X4WDtWajakbUFAdt/ZLO+wTqhWosGgagviqSDYFm35VoTFRUHRGV5IRsz1Z1FNFqtr+0BiDPRpU -WUuiVwjR9kiibWnvMxVMHg39UXNRo9OjNTrr/zQwVs3hYkZvrAbUVxbXiKafrJQ8qFaCvGqxXwDo -LEGfVbm9pV39IKrZZ2OUYIEnkIkcIdlg0yR7neHKFD+hRfXvVTk2tzar3+NFuetK8pVS5e5LOS9F -jeC9zt1z48T3PWo1ImUeM0LQQI2b/kR+qzs3VGdcfH/acxYdClO0hpeyd0M5qE6O677VIkbF1mq7 -eey9pcV1Yu1292lSnQFE7yz2JcrksgM2Y34tjkiv65WS5WY/MeFlF4HoLb445NGIk9poxgbKcjib -RpUPo1tzQvCA/cEJZzgjSMhKvElotFiANg+ygZb914HhHhInfkjNv74ycihdfjJoRaIqj8jMFt8g -b0DJyo0si35iDaiEquZrAC3Zo6rlLb9vHSs9ShTkRs8rJUv1NJKtYa24pkRZSxSzMz/vkFT54w3v -ZkKYSY7Xi9nkSUJmW75/poecXtIqCzRaeCZLRT9ZCkQghejUvaiZjNveP9naqh2pSdwJojYgNHxl -ZHXBRUlXVWI2X3o2VwwEpkVGJ38FrrE0hSibk5gCU8N7vNt1awd0HsYoN9eL+By6FInC8xPZiKac -dp6MXa9T4ok14HsD6mUaoy5mkgqKW79/0hdoUtvHsTsM8ppFq/3CAo3GJYO5bXKP7Gug21bIQRHT -Z6s+OnZjbGpz9vmpX0wel3YHje3weV1KuDI75zQrRjmnmj2CCrfeREMcXV9LKehELg== - - - Q6q+PdthSV4DmYseTF2z89FLJRvrWphkkfc2PT5bGzGMPzxl3nWvZezzo3AKL6mgP7gnsj0JnV67 -FAw/Qvm9tfvKoygzI1yugv1KqV7xEQjYiwxJvLWjKlHHCqKr6pRC2seJvh/U56JGlxLVV1aEMbd9 -33S9LDeA8uHU4x4kD9HIwmMgJtmAWkhfibFaAz6asodnrWcsOm2tJu1ZkoAMaApUAc2CxMt+qRhC -OkDWPlToFFdKHd2o5KBc6ia7EPp2rD/8vVrE7ZrlJF1t/Rp68R+vqfwgsbioEj9Ck7tSstmYVYqE -rZQsMLWq9woJrWpMpHb/OpoVBZW4w5Ptn4KGUCV1hQ/6JKo3iEO203JQABTvMPIiXar4y1dKztqq -y8InIMnlogAJxk4kjl+IwTxruIg82bM8Y4UYBtNNe7eK+XpgDQUVF7R4BI2gBWGEaIZ/kWqOR9tt -I1WSOqcnOPfNBJr4NBN0BJrc9MSVVHwC3k/W177ZYRepIVekQp+1+gZ7a3Blnf9BROlwZ5r4IMGq -oBcTyTHfx5C6l2BtvpDKn7VRIif0/RJ0S1GdtgbMtisSylgpmSlXyvwiWvgxGzDPYPuY+JF4F+jU -rlytTJYN1RqQ0pLsg7Isr1UedHUYeJEG4CMxKxdMu1KquvdZv0LchryqrZghAQYGEVlptmd8ydYs -iiLZBhl9si7kEG2HZG0g8mY3Gazcti3UUWPNRQoISKM88ISIuh1CNA+pZmxZByzYjKprNeuzvlbj -UCgTQrSAUhV3iTVAoyZ0E0lmxq+NIQ6iQ4Mok0DUrwQ+UO0+2YqVzgi+o/tg+dvrFmQCUETOkqC/ -s8SqvOo6JK9KZQx5NyjKs0KRECIA90Owj6SpgdFcCW7QLWdFDfioyuRGNKdDsRsOQQZ0elQm5hkh -zxIzqCslpisaUM2hSqqVNZBLtLPJDcZtQ3e+F9l9SlRkVel6HYihc0sPJqFCVh61D0mD/bz9zLa3 -hSpDx3sVibba+2MWfwOv8eL6+Sp3dymziCmLChKlG/UlqpD0uCpM91uS6AMyVFXzQJ+E2b3eFSeT -1aNpyITI0SQJOFSILEUiixBEJcM9RmXQnhZv8Y83+xqiRlB4qAdtwiBR2ZxB23wbQs7FnL5RvT5w -S9SgfhQTm7xfYVA3ShNesTfgB3NX8nKplZKdWvTwYo7WhCtF9XuLarzRaeMV6AnAbrR+GRwrGTwF -bWrQTW+AtfcHb7BS1Ul96hZZ0ixVadOPqtDxqsvuNOrBx3HyDwUF8as2fvQDTis7ObJcE2jz6rTR -ptvY+EsX5VQTrYGiwA74Kpw1wJI7QnTVRpDUjEFFGz+NQC8xZwP2rc5Dba1tAs0NXeSG4jN7XcPS -WoFVx6oSEy54ez8UGylz4foCelNcseE7W1Q7pYkAF3JcU5UzKu0IOfTItN7fJUQXVVVnhjNovp9b -cBFmlYZIv1UfGEw1fdKrgMtyzZ0SnSklFJv9dQ0dZ8Nus3pnd+7x9BdiUmGIYSWbw9AjKoLWVZoe -UEV830IMyaaFiJXegVD1fcY8V0bOgz6dFeTFlMegEx7VV4c8xdGEEctDWLuumtCgOgaik2pYwgde -ecuJDFVhZO+7Hq4vEsgT4ujslGZSphAZMZEZ8H1mXbf+awfF+rEjZdWwFqKB9vJ0xPJOJxtAHZSR -Ry2RzEfjaI2mYCtD8Jk14DVtJk9TqHdnSqtJl2aQD6jc9aE3MIxBzzmXFPnMm4uzaQReLW/kmauv -Tm/TFaLhm3CDpE4MqIraaCznUn8/mhEY1DoFsZo/OYlT4Yj2U8jJPD7INF4pOXaQSyjOTLAxTFtc -RMyb7choDGreIaf3j+lRrUGH1EF1hXkq9vrEiKHa5w0Mk7tfHg+6qkQ7PF0SJ5x0tMo56SAgu3k+ -Go2yVj+ukFLefK0Qz0lbxrOa3ZT7Ieficd/xk4Hl0rEGqDNu2O5PqlYGOSAchAe7ndom3N6Pks4h -g8JGXyk5Kp40d50Gz6YuM5yoxbiSe+gasK++t+s0r6mYwxo1AWoXDlHAdLihdrAJSKHPS+ghnyKa -vUWCpP696IY96FNHO5CLTRfkSzViMl3JeSk5JzJafcW4jiWmYIso0tQBc+O7fNBILsgGH8hSb1mI -LKsfJM1DegXxYAAWsrC8P/ZjBtXqRhWcuEIvVgvwZpOnblwLlA+qhqEetSoQeoWAECeBHHPKvbuu -b1AiAoXYFEVTBFMo1qqCsYuEqK2/HGXolpu22s36oEIOmaCqmlVhD2uAd9OSPKndIFdTW01zd4Mg -mpUoHm8Qazd/i50UTm+XCKIh+96sGUNFNGidRXXzZoMPoFm51atk2bgnug5ynwI3lDgB3dCjdFqM -UonR+p9Hi3Kg1TSaNFFpNla92lUiAnLcj3rv0Jy/BtvQ6+oGbnbWraOFTYVosRscNTIs1I7wfeMF -09nGctxVpqhoItTMVUdm7XM1Ts4V+FHMEcPCeSZQDZ+IRlUiwpC3Rs3n2di+Q9XwrWJkza+Tq8VM -8acfXYgskCDnQQcd4qow1WWzZAUJUQv9YSeoswKtejPofBpCb4D1QEQzyMme9blPYZSl5cVlXbPI -eWqAYEc5r33oz2bTmYJyLK6Qq7aILBMrDSRJChehqnEQVNlVOFQRz4sQeV+j6ADFlMYxSW1EYc7q -7GMmQKF4izQYo7i7VTcxhPOYulshK74Hj7JoZhDES7F4hZl0SZIiLNwxKswp9bwcuYtcn5X0z63x -DpAB2YqGsFR1g7dTJ1Obs7KW7wGQzN1tMchpIztleFzCUm1MKkhG1MLvSlQ3KRmaNJWNskiIrOMh -J6vFcFxn48xS0fa+Yf1zT85ikUk7UkLqvUqj6Wb05loDmoqTuw9jnFTWbCm44zghBfXmcHl/7E6Q -JN5z7Wt03U4cLWxtgjCLq6jPQLYVTPotXKoSOwpJt6FeZSkbtpobaBxw/6aeaqPx8NgTBKF69BEM -EwxpNEE0wI/d0ySTlykAKqhnuQYFr9XuBwri+5YGrIwmQaRFo4F1zX420wtFz3Puhnp1vQcWTgT4 -t39MUUIJDoreKwO+EYFpr5swTwKqXtm4RsO5RU0lwa3Sqsil7r4eh+5hTeJ2tZmVG2HoArDEPTpm -9VEN3uBebVvYvjMZ/aoGWlGFF5GuwfwiKSuwNvccrzWnBhodDDfHQvIrJdvpn+TKPyEOpsGsBdrM -GEKMdurVMKXeu2SRW8twJDTYiBadYUa6TTZW02uUFyUDtVupQ/Zj3zJD6jo/vc1BiXE0bklOkt9G -VjIP2qzXCAJK+vZpyUoJ2TxFLEQrnYp6XbxodtVgcBHVTaxygKrHQxRoYhDg9WBQdQvWBHlH2g1S -qiEwDBo1nSmszRYXRohNpq/XfLAGJrithbMxs8ky23MRmTvEtVSXPPYeQMkcrQe1R+rXkseTMmHq -OQha6tsayGPuIIJkz06Acpb3P/rB+HCxlDfWu1I2coau7Tmt4P6eDuVqWGM5w1xY3kgWsIfShs7F -89C3PVv6s66HyZMh5UcNVTIW3hHevripgWxYH/NAkNhT5VUD5QiSfSqYBogpiJbESaimEEu2bvkw -2qaRm+DJR3FNFvSk6p4dkeUGHyEOPT5t4Zd43FXYwS7dIvLe8n9yPzeCWFv6frUcUsKXrQFLWI/i -o1kZOVi/WFVCpbmztM4OvS9reStD12GH0o80wLtS6JLXVtxLlokQg2Uxd8lXO9N7uamln0gqupDo -qyCQQW9jE6YXYTIOetmDAJP6mTqsJUDwrZWRU2cErPlKj/DUzzqpUSZHuDPMla3Zm2BYccpLcOK9 -hgo1WG5l0/7l+AL32PnpfD9nghTil1no5xdKyaou7iWOLWqcH6x0gGXfjbFjLHnBg3UsdtiyE//w -kWjtPuuUJyCghWi6kRed2fRrOnVUIng1FFH3zk+Qk27lmA+c10QasQ4G/qSZZaZHT01g6oBZVAZ3 -ipK+JUQDfdEtUdXSM9RuEAXTDDUzS0OPjcMq7OtIOIkQ/WDQuSmLCdc06ZHr101VQ/+1LZlEAsPn -4GznMBRstv00XgOa4gYVb4wbfYfgsr6ANDBaegPIwaam6SnRnlWoehCt6WjDmxI6LF4eNfljmjO/ -1VNsijlYxp4ewQe7M6YLFT86+745Y8Lk+tuC69VnU88pNo+S7xZs6KYmiL5/qquzjvfA2AwM6s3x -Usxdxao6AZB41ZPmUvdTvRGx3CSjnVgaznBT1liQmKsS/ZTvNdjUhO5R8oJgEOIECPSKTHYKYlIh -3KcGz1ryrrq5whp0UMscgOiSfZ9Hr73Ou9tkZiUrHkQzS/U20SN6+sx8CWtJXS6K2SMfG0WZcXqD -pqodEqVxqePSmGw/eTanMimajcubMrMlGWroDQ+qhhTFgLX3lTchxEZ7tKnGU66/eUbNWxvF6LP3 -q+kRvd4XfNiajwONMPpOtryyKEeXEEs0hCL1DyHG2JHA1dwrrnQdK/ZUa6cl6nS91M9XOkAqTlVw -SA5dbhTTSVG8tmuwfcC1A8Wgq6u7s3btFVNrUArXIUTgrhgtnFF1N2JuvQV0rGYHjndLgWJNT9Pg -sz1ZfM86lgPQr++jCZSHGpaWIZktxkZEh0yg5gV63+MzQZLOTzTI1QtfRIuhB1tWL76dI8b4JmQ0 -Zf2Jhf6CZW8RxS5EpqJtaGcgVqtYQe3PGvBdPzO10ev14drX0Wi559RBEzzT91NPRmEVY+2VhlG8 -BRf8VC/JS+ylR5pDP+lHi4lOUPQ0BbrNfI5SacbeV55uykwP1Sfz+FnJGt93CuC9FkfLUmY6rJt+ -uOhdrYYodz4fbYSToS7a4YAGQrED2qKp6nyU3k+4BoNZx164mWReJqgfEx3QI/jRS3gkwzqYbhzX -Qissvm5pTd5CkbWfI7H7QX3tUTA82QP1VWoGyxTggF8pefLhdLzO0I/teKymH4AlaggA4tzBRVMx -nihAQMGxjGtZWIOGdMOkmvbieXhyyqqm/XqiDeQQzYQfam+2eHPH5jAYxCf2LDdLYQx+LbVLk/rk -WTvgcK+UNes7JC11MyvobUO64ZJBrCbjzXVXXNAdpVtGdJfge47BdOwGrzdFq9lQewPtW6bbqsqN -BrIdZibucEdytDI1ZRinHiiEkPfcG0gsjKWDkBX2E3qpBz/lBPIer8EA9z3RI64ldVhGRZjAHTj3 -LbYTkkGOg2T8CW3K/iYEUYjR9RI1wcRDSL0CWOyZ4iELllZmSyNsoayd8YQgSQNlzR4qw1paizks -6Ho14pZcl7KmZVgiQgCA1dh+cOLgw6073hxJORomrPYAxJRTEse1nH+n7kwQe7mk3GPl0a35Norl -14KarKQEw7tGLsW0Tc22xE2Ckzwl5kqIhtyJxz0dGXc29gC4k50UfQ9AREkcUmI0Y58XFMv7YU1d -tnyQiJ1iOqFNd4xr6aaTixG3YoymvRAkZDlaVgoq9vBnTL3Iw1RrA1d29DSk0gUdyA== - - - vfBKjmN/tlsntpUi8PRh7USW96f0lWgp4VGrkQepQyaaeRo6kAOXsxobpEEvbyR3JMO3tiaqchey -WEVK4XpD88haODMNUjc/CCLLYoF4dLA6G0nhrbiE1Zuqp9ilRrN6nVFqRPT3o+HMUAZlpdQy2GCt -5kpygAj3ALKcCSBqzay4BhDBRaVDnwS1fVNcs5qiOm1S6npakCt5pYEJehzXMmXXkgqiVOMyclCZ -GkV8CjH2okUK00+5J4rrgp9Ys84YVAs88nrXYqVarfoN73xdN33l/dKRrJPVgk4VK7nAja3vO5PI -zlLoU+l6RJDLNG1YNffsOdz7sNKnpxIz4prmTcJ2gA0KBklFblJTTjT0EO4969a3nbcJCNzB5IYW -NsKTg3WLdnpvYDD5bRguEEPuBqm4YXC9Y7V0oDgOUwMUpKF7K1ZKNhhYlNRII9fRDCJDkePeuH6y -1do/V3tcl5uBKmp7NlhmruV75AmKEHvpjIwKheauZY0OdjdPLoBopQzylO4bBfsBYpAK+kLs2NNM -o9m2frRHqS+KbqGOxhzkgpIgEaK19w10x8t7+WjsB4i6uoTIy4NkXrudnDV+IEyrruWc1k1yxas1 -YugVemLoHUhyTMvHvKgheLaaPt5MqaDE3MtxmRqUc0cRsFgAHywSxROOr5J2hNsLBzNmWJJR3q92 -2yuPD5GHuHC8K7mDJj9mvSVQvtSL+ZT1Emre7M4ySWSdLSV2PT0oyLWMUo9BOgCPnDQ7rjmcDB7S -iJNNYWXV8GSZcDYWK2fZYXNjaT1g1aMtdKUgVYInrLodbW9pIHftHeJMMPwEJZoaYanLZWvyZl5T -FizswidN4dLdAtpog5oivQTAlG762lnNxNPuKMia1lolgiLPagAYd4R2+8lbeKTozTe6Nw0cyPyN -tL475Fk32FlpyiyI0dLEJmQBwaFFUQgGUCGU17IYzALTeLrMe3clInI9Wi4KPV9CtPT6YuH+kteS -YUI2eAjxAJYIMKiMJVKkGiZZ6wKVyQdUpKiNNJDkzJJvwZUo8636kz6tXBC7kC5WkVfqRRhQuJiC -Cb10MFSV76oJS1cbFC5ouiDLmhrmLWoNL5ajthSJyb2H4q7O9aFpfnI47mkrSSuWMaNSwRWMKdrr -lixZ5CJN2UkTaEIKKYNm0Yoi5dD79qyWH9FndjIgi2iUQvRjTwdyaWrAqtYXyZRYKXlQnUlLvpg4 -KaNhd4JGrxox9WQWi/uAqBVRqpxzJqUsXInknZj0WXPQVfmsEBc53ibmoj5L8JzJVAN5VPE+Q3Zm -y1MacY2YvT92KDslz0rFrx1VUxIlrs5VN289VtcrTw9LhOMxYaeHuROLABtXB3ZYGeMXdX7mKMAA -aUJDWpk32+g6rJ+KJnyKXL27UvIEwDU0QA5rUNGqbIPTOvSs09HAjziEiwEPnS5Z9r3eZtHrpeRc -H21gZZy0BTssit6aRSJxozouZ41aYnaWwJM04Lp2VETvXCk5ZUMbJTFAQEuWfWGov0YciwF4ulDN -Tu9kJ6xHg864xLsY1qpKZml70DtbAuLG5P2xO2RrTz5pmpUFdzsSMQ9yngvLBzvroJupJyBLiEsa -7altlu6Gm2erIUdrNOGNRy31Q10xeej7PoueqZ9XYbaOmcTnNUk162VoJOaOdlZS6c6VIvGqrvJq -Tnk5VowMaYZWtsMXGq+3FI81A4Ope7a35fCD0dDb5IVgQrT4R5H6fmagGOSgWElo2hKj5aPYkZpy -LzEEoWtnTyq9xFARr2a3MLQsXO5gVhhOxeZVeColOUeF0ZJ5n2G8RYN2J1fM7ho7NJ8Ji0dbE7eF -OnTYLGWPNjr0hBwVDLAHO9a6I4poJdqJakn56H6HcLcD29bV0DhFitFbB4ozzKpVwMMAqqHtu/2d -JfooTwJ7cNZH0FN6RrUck2xnPmsVg1PqOYxaCNmGYB5JzZxbKbkZ4IaJVrx4imJcbnBhtCrQZS1W -k+JatRBmm62UHKMdXUkTR1LsN7ZUuewLxNAPo7pWhTDFrphUceXKsxbZEDkoD7pqYpiAQ3lfiw/K -2jo1M4N4jtmoQYKSl9fkMMtlciyQz0iOanWk0AtfVCtsjieLsxWv2fK60lSXs/ZiMsl3F1vthXbS -FHmuaw6q5LojqPZzHg1ooQFFJMuTUzZu7uj6RjYtrk773sk9ITJfekIl180GSAjnegO8CE3abQJY -F7dJSAWOTiW6GnFN7GZlxbGXmFVhcKINWHq6KlzWroEbiljVRh7dNOnFddeRJeYUukqEFrsu2gyO -qES7KabKT+aQGqNtKEvwgpesg5UNmtSI0bDpUznHONWcn8DOcNQFez8pniUCj2mZ8DFYVkisPU6G -zH1h51j75ezY0UqycpJFknbt9UnFtgz9WLtuOBX6ATF0KdHzzTEszXivcm+DEIfOtRZ7jRAY0Yoc -9IhiLD0cyusB7GMGQKod44cZGHvexGDoVVwGny1tf8JZxrKW78RsAn12yt1QVA98pQqPr6LBWLtj -saQvA7LH0rFs2CTF/LImlssUrgO1WNb3oLwFYrKtq7Ub2avRFOTUS2XSB9zzu610PB2+2bprOzpO -dplepCFE8+1i8SzNIyarAwuDVlPMYw9sTsnR8GInk6kWRASxa/N68Y3UJOsSST2S8I1P5QGKFfiJ -BLbZsLSaT9TLe6X/mg4YkXHfuaCXAkB8QLHRVXJQLZZglRoqzlkLG6Taa1hohgGiDsGyOrMpTQhR -9Fx0SwBFo11sePVmNSJ9LcLzqXOy6zUO0F0+Oa4l2tC9KkQ7LMnm+vYoibcbpwJiLKnX4dIkk2Vh -EpSV4a3MUn5BAy1Fys1dKbkpdL0swGilGnrKkSkOqOkwmlVU41S+IWZTHoOWbA4TlnctS3/xfauq -FzviKguTXyl5ynwurljwfsq1yKEH1EfTFOOUOQ3VIasCbTmUaDRZcmnP5V10wKofUZcyfxtibled -bG40P2YDQY79zgOWRjeiYcH8GggyeHNvKnhvQMzN6pr3SrmL75/YzbICVy/mqIbGd2VUb/5Iy3Ek -0eJLXG0lxumeqjwBP2M1r5uV7gEa1NmdGZb3AThpR3JgGXoDxRm5I+sNxj+Vh553f41VU12D9dqN -IjRpkrlPe/wwUS8UojlHaLyYP9ACs4tGT+SqVZ1OXvTbvSHAe1nRSnOlT7lYIIaa7MjOln//hgR+ -Aw6WHjv3Wt5cW+1lEWY9WKsC2Qu6eKuxRKoVzSPKTonRioSYO/qN0rrX7Asako8T3qX2dd5WLXJb -r7QiFLK0RtP4GQe5UnLoyrVWi4StbxLBePUHMsq6Wqll7PF+6XUzNEa8/LxtZqj8hgxItVdZi2vY -eML7LcEgF7uWr6hyC6LrhcesPv0MOGTwfkRu7NmiPvEhrl34pKWQlv2a6tubizKJkdPr22sYKelN -xCT6njuSslYPd1AhNc0ijWWq4E43U5AkNEN2OwmUBEng0WpzrteYTT2VcdmvaYZ5m3EQaI6bZtjc -xWLjCc3y5qNUMTiSnIxksagJ1AJyR+EFu+IiTPFLy/OY8JKbL3sTGpaxx0QRC36O0WizztuoQkey -RwmvWPHiKdvGrg4LHS8eJ+EberYQNlJPvqB0teCA1WQMojXIs1bZbtGBqThf6YDafpdH7tULQq86 -C2K1iyuzt4QCKyLixZg50fettocXt5kKa3V7hO42nH9+4gIryppkZxgXjGotJgHA2D6bCovHMFFn -baymEo56QWkSIJpVvTQgjeaxWuqSVYxKkn6rxNHKSDrNC2SFS8urdD2zHik2vXeGj2YfLKdtdD1r -ZrNfJ1OZzs2GrcOUuULuaSdmsOHRYmknba9arZyYpo653oXUc4cMQom668W9aWBdHPaMO4o6WyaL -tWNza/F0yQ2zMC89wKvtjdghMPayc1pE/krJZiqinJk1M07u29Tdv8jKTFZG3vLxkNAxdPRI96Hj -c3XKPsz2rLc0Q4Y3tvbL6vgO3XzTKhRXSjZcYu736IGolwpkwR4J0dL081RObhx6aQZNLl5t/9o0 -b2Zxamkgmzff3ZYGdRinC4hKL902ToVSVN2wCZoKnfTiwVMt39y9jssenPRzvSowTrUXO9dLsSUt -tS9p7o5WpKubKbZsxOpIAjVr4UviQq3IuXfmpFpLu5jqfZkBj6SDOHmupqSFySvJWJEQk0+W968+ -BCQijL0oYnJT2kVxppQV9bc7vQFGdAwtH7kcwVS/fdayDc3WyVJYQepqYS8monfkCnECi/uufhU5 -VpRYepVB96ZxSR3lupYpyCzQKyXXcejqTI+ET1duTDGqZRO6mnUQGyZ0RMGVket0n1kpPSBu2P+S -psu+J5gb9Wp9f7pGWj3qy29Z0fBgJRtYmdDuywR5sIgfgxErI/cAZdH8mBC6pVYFMCFE1nQRYrbo -8/Jz0wUFpZgHOHVLLZSusFRmh9v9BFYJpUr+xmp7G9p4dFrKExM2WH3eRtVQJedR/YzOXAZcB29u -DHWvoKrjaICMNzVgObOTg33RgZPZ3fM/8hZ6Ug8b+XDcuI/+jRfRj8PsJnpEHVC0D8ZV28wjBRfU -IckYncirTTIqCOIem9V6E1uJUwPX7Zt/PuDNZ+1YPkQk3AN7gALnFQrvQG9ck9oD7EpkYbNaLMAl -0530nzZD+L+3wfxXozVG/P4wHP4efcOVkd6xG6jlLvfmonDX8VDCOnl18HmbojZJ8Ms3lfFwoL+s -KaSo1D4CkYScy4gtOwACjxDJwKoho3RkROiydQRb83udFyaNtI/AOVSCfBu4ybZv1siX/PZw+Mcf -/fXTxbpqR+jaa1/Uu+RtOVnkbSJvLmbUENqMyC6fzRZea7Vutor7yQdrwHqgRGmAtwaF2bNBLpPb -bHUibvRgndx7O7W6Ma7ZHJxt7LrXXBroBzKgaaK4YkznXGtwjb3twxsk6+Ia0Qaz0Z4Ne+Pb0xSR -HOG5mT/rkE612epE3Pj+Orn3dGp1bUSL0Z+9UTBRirw4SH32Xr+jbczd03fvKLoMEMmCabpSMjVu -kgOuu7nUvU7/WCO3U6bAhyXrPjQdo9D30H7PUvejiaAoVzgKI1Up/N7eLVJiaVN4oIpi02n+PhMe -SqZoi1EQiGtNNGKRWOf0ue9MBgATK71ik6x6sdb19obe9zgN8+RAyDxMp0lZlx8/bv5PDzbkRkUS -U16T4TL9g4TeSc6o8woiPP7CPFg0GOfItxbbgUTaAme6fjEMUUbQVJai6zc006jqNhtCJxKRwGeL -ioiqZbDkY7GIiFj0dssOjzTpZkOSxYlpY0iNiNIRNawPqRGd3OGwMSQuCgsObQyJ7VJ9kyH5Njgj -EyO8NigSMxTjtUFt6+/Oe/LN2kME8cs/3Fz/6fbi+u7i+uujIyFTqVj/w8EfvsVfxkH+9PkXv/3N -xWVr5uCX/cfD/3bwy7/9fvWHmxfn+PFXF2d3FzfXp7f/+qE/fHL4839eXV63Px21bg== - - - 3V589fru/NUvDv/z4Jef3t6ezp44++bi8sXt+TX+7g5/+bvru+lv+Nfdv749x99+/unvvvz0q6/P -X16enn1zd/4/Tq/vzn9/cff52TcY+vWX4y8Of/nX64uz9srn7ZvXX282893p5Wtp5+JFe3LbI9Cv -8ETrHJ75z/c9wCM3/GzXQfzrhx9cG8q/HvlI/rnzSP75AUYyht1H8v3Fi7tvdh6NPv2oR/TN+cXX -39ztPCR7/D936PpjkSph17l4dffiV+ffXZyiQzvPyOZL732tP795fXt2/unlt9+c7iwddx/cxYcY -0leXr293Hcvt+avXl7uzrz2+C/u+oevjD3R9+6Ez9fbl+W9PX796dXF6/dm9Y5x6/QML8Fh30ovd -Rf6LDyHzH8Jij3277L4ou2sULz6ESnHz8uWr87v7d8bT3f1/5Aif3r5/Ttsl7qwNvL59eXp2/vnZ -6eX57trAxkvvfXCvvj0/++PrezjsHeyf9zysSyidMHPPbi5vbv/b999c3N2zJOtK3b8esn7y9PtX -2Yedx9OW+PXl6e2v//ntzfX59e6rt3zx/Y/yoYM8ubl+dXf6FoOcXnxKlkozqtv/dp2kf+88K//+ -EA6C8SFjeeTOjviAoTzQ2/GBVJE/3Vxc360eYvv/50/Tkc91x65Uxj897eg+jWca7c2357endzc7 -W35rLzx2VeKxq33PVDN6K/ePe8BSuQ8oo05urr69eXWvrvcYZcLp7cXdN1fndxdnz1E2CNf99vb0 -2292H+BjlxA7H/D/uEeNXRsIHn3EA7nHdbQ+kPBBzL67P51e3Kf9Pz2h/eCj9cEC+7Hadf/YfST/ -eNwD8bsPxD+TI/Tp2NEP9yQ/+HD6QOv5+/Pbr88xk09PJXqoNH/GS/LT9ePD9oF4ol278Mtfnb88 -/OTxApFgUz0DyNHNV/91fnb32c3r6xetb5/d3OOemq9me/Tu1c4bceOd96/4/ux5uBufDbJqeHbI -qgeM6CNAVqEM0K7T8dXpq/Pf3J7/n9fn12e7b7/ZW+99we+xV6bhXb+++uPZ3el357sLy/VX3vvA -rm8+v7u4O7tny62HUfH0Xy4uHzDAjXfe+wjvXt9+9dw8BxjT68u2F3YOf8urO47LvvOBNOm/7Di6 -x2jdPM8g0LN09D5EMDzrYMneBNzFBNwZX7k3Afcm4N4E3JuAH5kJuLOL7GlagDuL/70FuLcAf/ox -vbw9bRxz+Yebi1d7G3BvA+5twL0NuLcBf0Ib8Ffn31yfX395D9zjbYzAp6Pjvbi4PN094WYv2z6w -bNuZV29PX1y83l2Zs8c/HNLi5vbbb24ub77e2XrYy7cd5Vvay7e9fHsi8m1nXt3Lt718+/XtzYuL -89uPXYU7b9Owl3BPRcLtNbi9hHsLCfdRK3F7CfeUJNxeh9tLuJ0l3G9PX//f//fV2Tfnt//z/OLs -m3/jX9fntz8FMuPpyLv3W9XwA+2mtyuXt99Pb7ef8se8n3Ye/H4/7ffT5n76f24u/311+ur89vV9 -5Wv2YMHNEX5YsODR+Fzggg8ZySMHDMZnBxh8wIg+AsDgR1CN+5nZ6g+q/vpi97F8iNNtX1t8rtw9 -Obmwry3+GBflSdQWfy5SbKpvc3v+4kHF/56aTHuqFdNZqPq5sNuDBvNkgPSXNzcvtKD4V5enZ//4 -5FBIN9+enl3c/eu/DccPUFXfqsb4B9pTv8Ewn96Wep7o+ocJikeuLrz65vTFzfcnz0n2fYCj9smM -8FknRjwWsfeczImdU0VfXLx8+frV+YOvf1i8996H+Kyvl3nQxSubl7bsPMT5a+99kMpDz+UA23nP -yQH0MH7ceOexr9RPaPs+lsNi9DvLn9N/X1y9fkD4pD//3peZEmHnQmyPfT+mncuzn1+2Xx4U/Vh7 -44OpWr+64NH7GK6W+ZXIh6d7s8xz0v32itFTV4zsbrVneI3O/vq9rYP8yK7fe77a01552itPD+zK -/l6+xxkweItT+LGrhs9Ysdjfz/c4ZcP+fr4nJyV2v5trfz/f+1Itn+f9fA+NADz6+/l257hHfj/f -7gO5p4TC+kD8/gjdH6E/VrY99tNzf7vt/vR8tNbc4z9B9zfcfgwn6NNxVf8Y0N4DD6oPtK77m26f -w5L8dP14emzxVr7Ax61VPvv7H3cPDO+v/1gO8MNe/7HrVRJPT8XeXwLyeKX8w7nusQv58XDQf7b9 -1Cm7Dpc/7S5D7PH3Puzftldf3Q9Ce3rS4+q0NbVzWZ+nJDeYefb7HYb3GAXHR1AY5Vnpvh9D+v3T -Lily9GxqirwXR8/7Xp2dTcfHXlzkj/tyHI9cEjxPKOCDNdTHLhLgy8c2+sszvLjz7YXEHhD4Pozc -490TlJ6g/v02O+spSYud00Of2on7tDXwZ3lC/e3l+e1vLm5/er/Q0wlHu8Od4yR3p19dnn/xMB/g -xjvvfc359efoLvvN6+uzPz89oXKcD4fDPbs9SXb77RNkt3Ev3J4ot332gYEqtIeQ7P2X29PrVy93 -v/Di8XD/8/ScvI0a9xRUUwmzvBfl9GmktO09Jz/5yhCN9+nl5d4c+pFbcQ8DfYt+fNg+PKdbrP7H -//3/vrk8/+z2AZiy/SVW6N0HvsQqPJtLrOLOA3nkd1g94DauJ3KH1QOY7CO4w+rFxeXp7vXenpJh -9AxxWrsP5JEXvxqPd49yve2t2Y91bZ5a2O5trgHfS7dHw26PXabtHsPfy7RHuzZ7mfYIF+Wx7/wH -YJofOWr2q50zhh67ENt9JE9tyz9VvO8DeOuR7/dh9wPyaWZY7wy7fGoJ1rtjEh+7fHv+qeLvAT+6 -TxF/pyfTU04Rfz6n01f3FIl6OjJu54HsAGdeF2sf6EKS3RfmqQi0Bxynj76a3D2g27U418k3p9fX -55efn1+enz3EybZ88b0P8tOdo5JvO8jlix/sOPrVxatvL0/Pzq/Or+9+f/rtEzyTdpfkj/xMmkqS -HC5+HDd+3HXET6ViyVc7+4Ye+2m8+0ieyvG1L7ryKMXe7ldiPcGkzwfIg0cu07/a2Uv06CXbziP5 -CHCy1zfXOzsnTs/OXl+9vj8yvTYl66+8/1jz5fen/7pHadhY7LvT2wettjz/eP21dze7n2A3H2Ag -lxfX56c7X6/a7Piz39/sjnlee+G9j2z3s2pte9nn/8Ah7LrDNt96/8y48wZ7sXv6vDz7/u2Wncfy -1fnXDziw9en3Pp7bc1rEOzPgixcXdxffPYD3+gvvH7WyM5z89uZqdxw5H/4AkYLz83/vvEwvL+7L -9NlExl9+MMvk9Pri6gHawk+UpPK063c8H9hMeS6wmbNnA5vZfSR72MxjW5HHvt+H/c0ETxY4c7YH -zjwZ4Mzua/VUHPV74Mz+fHoPI3k2wJmdFeunAZzZfWGeikB7wHG6B87sgTPv+Th68sCZB0jyR34m -3YOWecbAmbNnA5zZfSRP5fjaA2cepdh71sCZB8iDRy7Tz54NcGb3kTw1b/E7DhE9HfjPA5jzodvs -Ay3lE760+gEQu/1iPN1CJU9nLX66fnzYPjybmpFxOIw7m4Iy7D8/wBpce+P9u2B+9+WfLv55fvnq -24vzyy8fFnF/jMrTh6yH+XT0kT0c+enBkV99Cxzvc0Tx7igxnzqEdzwcP3HD4Rjbv4fD9v9P2s/t -v5+0P+x8y8tTcTSOu4N894DlPWD5nQ3t4vrF+cuL63vvF1g/Ab49P7371QOYcO2ND7B0Vzf3rcRH -gWF+LKpUfs7u4WdYJPmre32PT8dT/JCxfOS+4vd+DO26Lk+pqvCD9s4jlwNndg/RH1/fsyueXiR5 -f4XUY5ULp7cXd99cnd/tvixPST68zZ567HJiZ7/vP+5xnq4NBI8+4oHcgwlYH0j4wFz20ydWPQWN -+7GDWXdepX/sPpJ/PO6B3IOVXB+IfyZH6dMJf7yVBNkHoN+iH/sA9D4A/ZAA9MMSIh+jT2YfgN4H -oJ9nAPr5lpH6SALQPEUQgnbDJw86UfZB5w9hr++Dzvug8z7ovA86b1mQfdB5Zg08cifyPui8Dzo/ -rpV57DtmZ7fci4uXL1+/Oj+5uW520fXuvLZ47/2r4zsL8de3L5vm9PnDyopsvPTeB/ev88vLm+93 -HeHlxdff3LW/H50hU3jnMc5fe++jVCZ6LqL9gdG0hzHkxjuPfaX2Jelv9i64vQvufY5s9xPgqfvh -5Gz85Ovb8/PrT5rSdv7JxfWLi69vPvnu4uby/O6T2/MXn9zcnl7fFz57gg66fRn7+0ezd9DtHXR7 -B53w2FN10I1l5+DK6b8vrl7f3VM5d30b2fPvncvSzmM6v2y/PMjnuPbGh6zQB1fACqrIBwbP/ErM -lZVqRU9vAzwrX9Sz9tQ0bfO5u2lefXt+1kzR22eY6vEA3tRJ+PU/v22W/wM8pcsXP0Bg/6GjfLA/ -ePni3pmzd+bsnTl7Z869piVcN+LMUc8OfTp7Z87embN35uydOXtnzt6Z8widOZf3ezmeTuB675na -tn9+kq58rnbS03VNPc/aHG/h43jsfrdn7LZ5P2neH0ha7etzPFIZ8ZZ1YR63lNg9J39foON9qZZ3 -fzq9uM/3+vSE9kPRno++LsfuHPfI63LsPpCPsC7H/gj9gLLtsZ+e+/JWj/X0fHalrd7KQn3sR+i+ -tNXHcIQ+HSDAg6XHvqzVW/Tjw/bh2ZS1+vR3X35+9k1r8Px6f/XQXz+Oyk87V/jaly54FAbCPt1/ -Zy3hPa/Mzlf3vtj51mU++qhZ7LFvl90X5V+7L8q/PsRp9vLlq/M77Izb8xcPkmhPTQr8kSP9qKyE -t1/cZ2wuPJbFeT9RuqezLnsz7gmZcfsCvj/GjHvPQz0a4892Hd3u6sqH0FYeMpLdteEPoQyPYdh5 -JN9fvHgA1lSffv8j8ruP6Jvzh0Ac++NPyQGS9g6QH6e5PGLr9KmZPk/bAVL2DpDHt112X5S9A+SR -SIG9A2TvAHlMcn3vANk7QJ6cA+Suqdyvzu6xxvbuj5sfcn88nfPj7KfD17xv3OTxzhVkvjp9df6b -2/P/8/r8+mx35W321vt3P+yMDL1+ffXHs7vT7x5QfGH9lfc+srOHOVt3ZMIPIXSubz6/u7jbXXq+ -4tN/ubh8wFJtvPOI1+qpwHlf3p42zr/8w83F7ikx8vKOI7MvPZ1TYV+F6elVYRqOd75M5+5md969 -+QBDeb4FpR6geTz1elLPqajSvgjRky1C9ACz4OXtzdXuFh8f/gCcuK+p9NP5kv7y+var15dNNj9B -Z+PzrAbzANPwkfsn9pVS9o7Yhzhivzi//f7i9qvzy9fXX395T6ri3h27OMo+IBptd5TQ48ai7T6O -R45EG54dEu0BI/oIkGjD8fDMff87g1Gemuv/+TvM75pN8dxc5nc72kk/ucN8bwV+FFbgQ/bQ3g7c -24HP1g7cWc3Z24F7O3BvB+7twI/MDty5asHTNAN3Ht7eDNybgR8VcmpvCO4Nwb0huA== - - - NwRn/XgbQ/Bnn/5uHL789fWLbhCCFEH58g83139qTfBOwCMhf4aL0db/cPCHb9lGkD99/q+rr24u -D37+2en1i18cDIeftv//7fuD1+2f4fDPB8NxDt658XA4TqGMpeAHVwfn2w9l9HEI7Ydx9NnFjR+G -WIo7/Buaay/zn7/9q/3y39sP/9UeaE99fxgOf3/4v/73cPgCH/3zwVHTkJz34fDIj8el5nh4dRCO -Sx7SSFJNsRzG45r92J4Zy/FYXDosx9m72p4YU+tsGA/PDsb2ek6xkWLrSQqHLhznES+58Xisrc8u -H5dYK5+IMeb2kscQR7QzHMdcxsP2gZJ8aZ9u4xq9JyEk13o/+MFljy81kivp0B8LpTUShpQPWw/G -0HrQvpNCPGxfqSHghdaTktr3XJvZPLS/uGOXczkcw3EbnTusrX81t4+nY59bB88O0rHzJWd8ffDB -HWJIsbZPtgkJ1R0eYTKTD+WQs1ZCe+eojWpw1ZMU2sNHrZWSWt/H3N5q3zmqx8MwhIoPhTxgAo5a -X4YxjuhL4Yxi2spQDtusDUPr5BGmzWF4mLWU8FIbMdejzZqPWdalVD8eujblGBEJbXLQ0ZiGGORb -bdnG4jG5qX209bA15H3BkrSvpcYf/JofZRnxJt5z8bj1ur3XJjCG2vrUet04FD+03rqQMLQx+aGS -I0LjVbzXptO7KL0ZcmPfIy78WITZPOdxxn1nB68OfvZlk0R3G/L2Z18CtjVg8te8GJ999ilww3++ -uetFAdqDn6098fk3p9+ef9YO7Bd/Or375ne/0kc+Xa22PvT7U+iDN5+dv7y5Pddn/8cbm/uLypyf -fdlExJcH47GvjQM4luAab1w1Pkptc8pOGnMqh+m4+gET0Hgito3beK+tF+eoseuQyN+uzZJznFzZ -SW3aXMFLtXF6bYxcGutz+/njXNtfuJNy23+HR22N22ZNYN2c0EpjsJTblmoEfOqwrbRLJclG8qXt -kNC2w9j66+qxT61zrQMptV3p2gIOowf/uhTwhvON79tyOWz2XLHl3Bg9eHN0jY+aeBhDW8XGBClU -7Ir2h4BJgeQYmxDApi2t7TYhuUDGtB0VUwwghMDxH2XMVWNeTFpb8sNGcGPAmHJ7O5Pbim9dw3dT -rE43UsqN9duUlIQtgFmjHKjHsrFKG2bjYjxARgP3t0dz63VKEKrcNWy2CTXwMAhtf+GH45Ag+GQX -ee6Q0FZlbKtx5PAFX7kt2p7Bt9omSNi8bSKqy+xgm7vQlpPyMEVK08YxQ9uaFJm1ydm2eGNwuoty -dCJV2uYnqf0xQcC23UtxBaZqK9tGNGc8bKK2Uk1+Fu7x2jrauLE07vFllG2Po6Lt9UL2bJ8rAeK3 -7UdPQgTXYAHbdMlOF4lMpgjto+Dg5CBuG6PF7Dn6IYxRmLFJI4eFa/yTKEdTypAqjfkwZ5jEsTFQ -e8X5wO9QSOYmal3AUUceh/Bsm6BtnCYW2wxF4WJ8A7Pocaq4djg1Wda6PrZdQjFZm/TNODAar2AF -xgKhHo6Htq3YxNjOgzZtcRza75hGh1VrExtzm1h0v0Ycam1BShxIacNw0wKBwWVV2gmGvQH53Zac -DNJ4vE00tlEdR+EGj0MNe76MwopNfuOBsXqeHZ4TA/FQhCOwe0cce+1UxO4l24XI/exzqmRgzhk4 -Kba940sUJpfFwGjJ9ji7fGQDjUtFoI9stv2xHRjRyzBcUVZs4xmxdGObFMd1HiCp2ntBD0q2FSmR -2qg9FtZjNiMO5RnbgRcbM3mXhfFCrtAxsFwZe65tnrZe4yHP+VFYfyw8zR372EbiHMfbJrbpEXL8 -jA4CqnUykhDb2wMlmBtwsNl4Gi+2mc2QuI2xXG6CBhyUqFSB9UQwthPSta4P7eVReDFCFrcDfAAv -te+4WCnm2q5u8rdxSpNzEC11iFQxBjnGG5sOuY2mnfSloO3U9lGbdOgikRwwivBxKvY4k42/oVHU -YWhrQUEIHSPicK9kvkg9hlpS6wiG2aZqpEKRxyyfEYESaiYztiUXsdh+xFBGqHKYXs6Z5z6hcoIF -GEvg3o+jy8qNY0Kn0SWRi6m0JWidxM7BSU553vi9bZo20WNRdowhVSg+PuFvmDcsXVcmIASzh8xs -U5eSqAlt7kaynIPobqvYhuWSU7k0+iZ0WottIZQbuYroZWMCEbvYGm2ZsAYjOtf675vsP1zwHZix -ibq27zOJTcPyYMY2OT5z95Sm6LXVK03/1X5DpQPDsR/Y221Tg0cCBJFM+ghNsA22phB06zXlBqtE -7mwM1/TUIMzoqBO3syAMgeptO1ggwof2H55wubFUO5KHMccivAgdAGdiyTyT21kYyEl1pPjD+dJ2 -dBNteqg3+VmgNLRlhao+cKu2qWydcofQhcdCpTTExJE0isNhjoNNFAaqtOgsug0pHmtbF0wY/iun -rRva8mHGKBhHTGZ7uM1YaFOlKmJbFJ4fPikviiYyYnSJ/NEWsFLGhyLCIPnGlDiHR1E+sZZDU959 -ExM5JWHGkScSNr8cRAXKOsVT09uVFVuLlapDKRDUjps5ijxrSgOFXxNdkQdyU4mCHu4RsgBqONWo -tnitSSdHeayB+3LIOQXR4XzmOeF0itAABSImldsDxk+b7gXXgRVxKlYMAULQN4FyBRmXB3zXYeEa -D7aJzdhTOGAybZdaZHqbkojdOkJDKGJDNRFUeSBnPuFSW08PyzBgC8H4GYUL28eo1TRFpFBX9DSQ -YEw6OaggZ9pIaVG1j7TzbqSq1ORe4ypwF8Q3zimYim0PV9eG2eY0hljljSa8Ihm24mBLjlzbuKbJ -uNYX6KBgd2FbMc0GSuMwoHNNGJTGW2DDoXpuo9x0PQr55GvSoxaT4iCMKJWgeNCwy1VUljQmzFkY -uWmxoZu6kTGrTTlwchS7wLM4eFufUrjhmgY7Kgu247X1HsePyL6oO6UdQ1G0+cZSGbwxDgPHL6ax -aORyivAorrmoWsnDGTuBSnrjRRhw8l5TPjI5tg3ci/rraKVhJwZyejvlRmFmNQCabK5Nmx1lh1DR -abPcpKgwYVurBbeBBaHxFZqZVSzAKzBUs06diG+PVWvqQhOwehR77ummJyfKvjyK+tZOuOjFEMyj -5xkVM3cEZK+XY4xmTVSlkIzYxDJcF6LS4DCEzdLWLYqI4q9tHtrWCcJUIzTeisPH8bhtZ0AA+3Gk -YxWhzJMkOenXWNrB0cbiIdRgZY3CecLITZmGeghVSnW+AbatagpNKlG9SqUZrlQMhyrSrnF5POTZ -KcYEjgoMBh+Ko8yN2EhQYKuwYwVHtT9QQ5W1htpXMQFiSERwD/b8iMOaR//YOt1Uy2xHc2yGE7w5 -o2wL9BKKc9swQ0kqEBsvO56aQ/H6IY9TGsvjwDAQcGOlYBx4tsqRjBHwkI612H6J4hZw8EQdUTmD -skwDdpTPemi+tPLFY4AtFEOQozkNmPUo1h5MrgKTcs525EUH807041jasl7h9GsMV8SiCk1ytGey -aHjtQMie9kHKuiYwgEVWBWgPBedBmxlq7+gh1JjGURQpw0BTyrERsmJqHN22eDO3KBEbC2aYxgNG -iU1Zm4Kd6ZQZhbGyS1SbhkKOG0qmZdwUHB7sTaxkOYaTaq/tWzTeIxw7TZ63B8mJVJTRAQgFboIg -7NHkVhxUZU08iQPtA1j73NlQTJKcw9mLWoQpg07YZqyNQKzS0rYtdi2PDB4lmAawWSkmdHIb2SFk -FU4dLFTT63AS1yaXxFXUfsLexEp65cU80ghq3RI3Eg4SiCwIKHqv0Buw+BHMsGQ7pYk1UdLbYAIZ -rjGXnMTJFdE4+XnKTKfvebGJwB5Nu42UtmkcheNrip66S9M+1FzCoKHejLEqf7blHw+xO2As0I3m -xA7b5DsyI0RLFJOqcYADM1bRcdCpBNWj9Uh5r53KTvwg6qmBv1K4sckDn9SJ0Y4NejV4ViT47GJU -hR8dPI7CiXBqqicPmwq+N5jFzXIGIzYBCE8LjAVlxKYB0hfTjKOCI7gd8x4c6VKiAdzUuUp+aEqm -uGprqqKgws1ih3ETJE1dP4SR0YQqFUKec9RvIiQJpjA4SnmohoFC3dNXpbq704UTXaod4A5boE27 -KM/mf600Gum+oQO2mfB6arbpguEE0Q8FmGcXDFno/fA0y2nc5trBJxtMKLa9JF7jDGc4+SfDNsBh -Q55P9GFjGYoL2ZSBSGaH2pC4sK1RGjrgSDqz4b+hmQq3pjOXrQj0Nn+kQfHsfti2Y2koNZEqUh88 -FWjxOT9UOZ4zTL42h+1bsteatSInxQbLgQ/VM4plCaKJXh2IYz5xn0EaO/XMY3zw1TvqyXTNi0ew -neJORzwOqdrhZL75Km5n2ct0M8vpX2INZnfi0Dmivx4HP51B5mjGjoc13XQbmfoSxLsPB72uRuv4 -KB56GXCgUQqz2IuPu5lOUQxp9osue5Hu0bTZ3PYFffSQEHTaOycWD8w8+ts5QfTSF9XGwCLTFMLW -pjEiTnocrZH7iK6UDAdtOzHGUV30YM12UvAspNQs1CaHsU2wOOg94xw80ujcpqZRHBmM7vk223TX -w2sKZUlcswWSn875Eea0uetp1Sa4IQ7FWV/UNe+pfHgyOY3pkXGO4s2Cjm0Yh+KoN7d8FMnmoKmK -Vx7u5pELKNuz6dkYsacvVX3yMPujnDYLznuzcx42Y7M6y73O+U9/tHP+05cM4D3MN/9XjYmNEhMb -D78/YHjsxUE6/PkvDv/2P9sTW0fGt6s+M8UBJYK3Jab32/Pbr85vTzfCelvDceOWWJyjcSfKRNNK -206nWtFYyCirTqGIgtG8mt7bRpvevGZX/nhAVxXlfNMpPcOMTf0cuIcbR1f80GRCYZjRI7iI6GLj -jCai/nZKIUIN/wgHSs2wWsGBTZMqEK9NmwMLwiMRRAmNw9hk6t/5VILmD39lRQCMOic8ak2QNWNh -ENdihB0IFaJpiPXwhNrJ6Kiu1SThLa+ec48ASxbPVxxpNXoeqieU0xWaSwCD664IvlJvaOLBHy5H -cnLwcppghmzaGX4FoRToZ6XDvTAiIt7sXNux9QWNXY4aRhg/Nnp6UUDx4soZ1c01QKFptlL7GAMt -QmrDGsTBmKgLOgYIaaUMMMwxhXwFSnqBLRroLXQwyQJlyUCdZNF9jAlLPsiaNxMacTo4+wpOBS46 -jBqYzvgf/5SbJYB976trU9hWPcO3P+CpiFVBOIHGaEJTiXYx/CoymsZDNVBZxsmDqHVtttbYupLF -soNvuJlAY+PKNkIOCCYP+BFBiWY9iBoSPT3DIunbTE7NFM4TelhCW7iMEzSAT5vJHRNa8QN8M4ga -RjR3shzCSmfmR2wGMEKhJQ9PbGvnCsdRTXD7NdkrjkGsBA81OCcaoa0hvVvg5zo6DAg2y4CDFZ78 -Aps70BtNtzVdkF+AWyS461UnpxXq+XE580fbhU3jp/11AquDeiOf8YkHqPjRHPAAiw== - - - /oNboFumQpdRYNwWa42wDSO4iXH5FU83qnWZ4RMH5h3oJG3DY+jafJTUhscAVqQ6K74qWhNtKprS -IDvaQQWEvSA2S47sDt1bcKNAA8DgoGlCv6DzAb6nL2haIdTHiNooMT6faNNqXPvvy0HZ6gdHhmyH -fSTzxtwYM3D9YYO3jvbdMMZE07tZpFCcIQyxBNTVIMoQMWmT1fjd03XV4xLwjjkZp4MKBB5qrMzg -Jm3+gslZe2qQQF6bZbopZCS1jZr/HkSZpscXTkyuIJwz0LxP4NANGmNO7Nog1phYOIx5HYkSPdL8 -9/AanGAscFhInH8o1GLng/v7j98yMLppOzC+DYfU1YEY4kUCjzRooWHW5CQeJFokdE5hpyZtRbLB -j55KFkcYg6Nt24RUulvEgVKJnSEleR4oOG28Go4FDNMoQRwc8LHlkRRRleg7gQf6ZEvX3w0TtU9W -cnTrTYbbozEp5Ju6WWi0IyDrgwRig4R/Of+6vSo56IjuLAnftS82o3KFTg+UtYiayynFKL5uDwH2 -eIUA8GRF+JyDtcVvnAWfBlyy7ZSSVXLwHCwniVHC5VQup3u2KIx0w7Eg7AfXCQxkAnygS5ChcVaI -I7RSjJxg6qJNXYbXZEUSNxTnrrExSYgUxbUOzKb8XfB1Vd+QRGM9FIdGAzyG7o9RQmgQm0NJ4hBx -in0qwyC6UxMNHBaEXHXiI0kliQ0lIbQxiKeSpvLIOEQT3kMwTalS3I/dQ5TUN97kDKMvXzAQI/6x -yHAXrcERhnAT2APxDoyAJbo+YcxQn1qM790wP/wTmfPQVrcyHsfVzLTMadiLZe2DHKLSxb9TxXV0 -kzKyn0UopkxIBjxbMGcLGhcfVeVJw4EkcRvZCU0Dl55xxv/iG/TJhdK5UEvnuusZO8mmQw9ExWMN -mRsDQEEQmdzUJ25jBpa5Pbj6Th1i/KEtWiny9c15ezeKHkPnkBIANqRm6Qobi4Ix0F8zkJsyHaPE -KNKGr40rshMKjxbOdBlhbw/UGpzMNIPssFjpXuDcQ/ceiBtr8uxkSx/egaq2dWCLj68OxF9a0f7Q -3VSCdsTBnuwImk/+tiVaLONsqQUWpJKo7RtoYPB9EEcIDd6rk8ZHT4hnhCknDMIQKoKDY1bYRJt/ -RJLHAuVbtjndUgMcOTIS119zQNUs5wRMRK0pE/YYqJ9eKWmAHzIIi1OzCvAWBHGZTQTPkJRYYFRe -IhBCRXSUBK9YbGYS92sSPY0bqfIdOIDg7cs9ZkaPY+MkUfHgdskCVRqaiq+vFAjCNtsF80SdBvpX -lYDdFzJfBU+4QG823YwJ/u6sB3rimXhYFMR5IuFqX2BbiLjjcNBXn7ims0k6eVfScKMfVwdb+job -zGy4AvDhyo+0maI4NiHYqHePEnCErgmXHD1iMo3kVjBJpMbdBCVUVGgJsMxkHgnzRDuCfijHxCdV -Hr9x20S+g50biX44JEOHyElp50EdCHWRoCb4H4ofbOpAW8ALEJVeOGcSPAIXA1Ig2gkxGcdwlPej -ACqpMtO48TwtnJPdz+O2ypcqjml4D0qzS78Ql4j6AF0VjYnRfvwXIWvZjBLfFmexfJv4xPbJMgiB -PE9TKBVlQU+/QDsrPKz8+Uz8/Z0wXQK+vM1d0U12BeAbZDhnhmHNI0I9Bwbg3Li9s4sBLYY8mxYJ -PNE8hwEcq2KFofDS/zlK2JRwaoYCoxwPIwUK3SX8UoVT3xH8mSMsQjq94bIEM/tEhVhcv0SGOfql -km4rOYZPOA+E+cE/ixDD+sRkdWZtztS74G6cx/wcZHqJwt4a7ikmKIk0gS8EEU2eOzJiuN+dSME2 -t/ARwcLMAqFPg6IhEgJER0GjVkmQtidQuXlWwwwXIGiztXi+w3s4SCDPESEIeZGC2lw1wrXVhIyA -EMxQcIz0J0y/PxaDEy4AOZ4ATx4l+J3lLOWRZGM+2TIPl/DGSQiqqBMRyQmhcGGDNCRxC0JLoMgH -hGQQJ0NsC2OEtoXDgZEODxSEq5ATOFjAlBbxhMgcaFQClORjFfB/wlkkNg42N9xZXwDUTPw8ZA2E -j1eARhUEnVMfSdtD1JpOMAoqOwAUIALiRPvB7zw7ZqN8J0cJAiQ4tuATgf5HmBttSUQHEJPFeaY4 -hwFo478f0O0ikT/Z3Y6xI0FvQ3DiBHT8+wgIzglhcNCbgLwI4r+pLpI5oJlg0pClAaZzQxaHFKOS -Ck+Dog1vmfl/qi1v63Wh0T0fx7vx2tEixkfhwCXmhdwAcdd2Gh1IWDAA7uE/+oJ+qFHCrO00CcTT -QXMkYMuR0wU0EuB7i+LrojQRgFair8s5SeQgsmvWi3ejvy+8pXDezTyqW3yum27ZMzhq/chHEEWg -M3cc4FIUbPQItWig2TpKOIy+XSpEo+pL0+/idsiEh4D5xerCRwZKNZhIiB0SZDHgiSY7HH/nIeSB -Y8lV/KQjkKG261fiem+/i7sDh7SThB1RgLI4C6MobVwUauIE1wY6IClzoOJjcSAmIMuAFq60qZ0T -lSBQIvINilVHBsfxDx8GNqijN7osvPbfHSz8+gvH/zI4UNVDN1DqUNRnimLikyBXtjjGXx4sfMDE -WjuGzhspZPUTY1uDQPP67wftTBjFkQMQEB0pyXwXg9q3SF6hFYPXlSkGwoqAAmD2CrmAXjqQ6L0B -AKTQxIgjIb8L1zwYYeG+nzn4F2M9O1jMx2LGFrNKFM48LrOM3WxEdwjhUI+zqgEUck6cCn0rq88f -RwYZrZEInnYSk1142r87mLniF576pTefAmIAVAaTF+D55N4PlBhNPCTOa+PoQWa8Jjjiu6Bo8gHg -KHYVHI5Zu8JxxvOKkMu21G3t4W2szMtqQ4dZ2QgCBvZwVhG+VMVD5empl6M2UguGp5aOrKZNcIGP -CK5s31pw2cnBghdX2/l1Yya+O1jGPhbRkXkERbDUtCqByYHsQuC8jorXUfSpuJlgABTa2ljIcXBq -K7u6DGtwf89CH/PgyCKAQunH3BeGI+FCSUBVDkWQ/xjmYq3ezREBaDOtf6+Q4K0ssPj4alsMZRFp -WW2NxmxGbL6D2qCT6ukY5fEpzhMgjRNPze5qoawhqzCpQlCJFD9ZvdacAmieAXBPOC6bIkihtYLG -KJGWoIwAcAS/BQ5B8HQxIe9EAQMmjH5w6I3YKlcHMllc8oFWMvL3KCWC2s+L/kOizka5IoKLUGSA -vaCNFAvnwAUJHfQLrB+gLfR1UL+ITXr5QbMawPV4Rg5gmmrQdeHKTJKIpofcbAzvhgOX0S9EzdIg -iS8ZeWoebUBVz3ECNf2d0NKqoNyKTKxVIzkx20CiFoBPSZoKKBA4UBCpp8KhzQMfwEDvKK7g4+bZ -3zrPwEEUB6+jTsh3PJ3IUOSzCOmoUq9JJEKbAD4iPs0ThecF3icf8kyyYgCGXQ50tjl1ARBxY0Gr -KBETmi0aGM2S5oDtUJ0YyPxQ65R8mVmL8s6oPnJNjVjEBtvGm8cP5wHGZRDyZMtyrd7FUbSMOmkk -cB6bmsevFlEuQSgKK2eLtDgkKoJ7cOoMdJQpV9PuVU9747VC1mmaGxB1Az2UglOvqk7MWA4TMufM -1Vb+nU/bdwfbIsHzaPGWuOu26OwihPvdAd0CickDg8YNnQEpHXUpzkuihxiNU1bQeVC7/AgplS3R -1u8OtsZkl3HbWYj0TNZZIpUe6bB5/SkPTSBuiUCevZtwBpwTijnS/AYwVA6ywl4TkdRVQmASkT5O -/PQqCThDnroINjq7K/jbo2Kxp0U4liHNedB2GdidRSJPtkcrN0Oa38HBNdBXwChrkGiexsyipH8c -0U+pvnrm1IiLq4OvE5HSST08CIsxqeILJh0zukO0bJEQrig2eHxQ3H+RtD3MYkzqGsqSyBXpLOTm -FYQjsJg0KBcLwogD0sRhPxzR0RmJKQPy2ytMxokiInBORs4Iro0aZwOFW46xfT33wrFYAQxSMyoY -FKmEXtHv1ePQ7LvODBpVeE6Oo+DFRgnfLMbMcPd8Zpazt5hjphYrz5E3oko8K5dQLIFJkn+bwips -yFS5IKssee6Q2xwLTpNAV/NoAHsvCgMpo36qiCe/9pj8KEknDFFzC0c7nxar8k4sEHBq1OByd3gi -QaxoVLSInlzoqz/iOdX693ciBKLUrVBdH3PA5GZPWEvYoAwBmpV4OSXkzsRseQ/KvbCnoBSjRsmh -/waZavAfQ8fGXORhbUjOneUMsezFfB6Xcz1fEIGVA2YrQjJ4iesz1UJEqeIeGU8lJQpqBxYtZ5Np -lUW29iag4LuDLbCDJTRhEeCXBHjTpFRWFgvNQeKzpcV6vqNI9CJ2fwXZKC4iIgMIW87HElDNlIJw -WjD2gHQeWg04/iVC7SylPdk55o7F3/UGttnCXAv+22TSky2cvJgeSu9KmB9khaQ1oPZGFs0yihqx -wGCcHWxBaiywHHO8hxZ+kVkZRIPpIUPaaDEKvkSUK6auFxE2miSHWXeaZ78JmP3uYAuqdoG7nYMp -NKVgA3GxBGVsgW68PFgsMHlinQOWHLJoSJKJZkiRLWiSOebkZCsyZQ4F+O5gK2JggSqYQw9kVmbo -iCWCYhvOoqjugUJRBWFRJJ1C9yQliwpR4UHrBOoZbTELW1LDB3m/UMLwfbrv6Smp9AP3uHim94/w -QLhpZM7F03kkRR9C2bYR3w1SzjOH0mlpAq5+INq4LQiP5DWCF9aS+Dm+Sj84Zxq0wNnPEs0iBfbU -IAluXijiA88SeT+ZvgYEBT2VzMlDxAJArUGz9CS601eD74nS/cb1oFCYrdlyWReLf3awFYozh+ss -cB5nB1vAIEu8yAxUolldjl79mGQHwzBhOpZYFW6UXbMmEh0ggQl1xMaR6WOwTuA3Depe+EKKRzDN -BalYRWQVfMF0/tPplFVXc914g5+20lCnr1mCvBJiCCxWs+CXd4eRorGCcH8tEi+vtDHbFIodCSaA -5uMZIoSLHZzCPHJvE541CAFFcxAepCuDSZajYBayet8aiXVxIPklGbk1zCOEpQ6Qi9dWO4o5l5WT -vPQPH6IUAQIdmZ8gEFZjgUhBUhJRgVilHqw0XxnO5Dk4WwCRV5trtFjF+UoLIm5AmZ0o7gUi5OR3 -jW2Do8CHmrMs0KqCPOJkLps5cEjOoU1s0QJ8NAMoSf8t+qN1ngapABX7hzZXWo8g8WZbQ1RfM+FR -AbDNSPFXea4G1JiQJZCYsdfSFpzxkTXAYFXwxMtaKcBL5GHBDiK35iyzYKpNxjvZwpyzUYl+WHyw -OBknBkc21oie9znaSeTHOhxqgZaaAYFEdszAQgs40QxydEbIUdRYtKJIUG4rEIkilSJYTkGAS8Sq -nPAAZm9YRShJvYym8SG+TC3hC9FABsbgBWVXFPqQUAFEqtiIcmgee+p2skeiFoBAkiC60lFxc9Z4 -NwbTNvzTAhk0A+R8dzCH7CwgPXMwC22XxMpBVoAFI6a+gwRFuimSslfRPHDOykBvFQ== - - - 5IM6R6U4YtaiAwiAQz1I5j+DaVRxjiXVGZPmDrXNW6sAbTy9XVFCqaLG8+iLLDTFdyLjJ5IO6ZYL -RvNltqjzVZ/xhbA1R6HOWjpKR+FRfWUGLBMU0wx8toCnzZbwHR1EQWtpZBU3V4Tf0/7Ik4+jMDaL -uCvnYNtybC7YYkXnq05WibQyi1on5B04NLq6PUdYUXzNUFgLnNYMyiULItu7SCYjs1sh2QFqlNke -ZMnoCR9VIxa+xUFBLC7CSrCvoPhDjNDL1ZTBzNpYYDap6AeRVVV1YYAILFY6GuDIKjM1BhIlLWh1 -kiIVSg8Xa8Izw6sILppPTDdkpeQumrlHg5G/NyUpluXX6Wya9XAxhvk4xdNEVy6Tr4JsAnrGnTsW -9RC1oKAEAw1R1YoVVzmy2aTY4QZ27LuDJbxsAUDbilFTaWImsvjFsFs45Pk0vROrof0sGnJF1ZYq -wSac7w74M7E2wY/y+8gMT0SluVuqQhzogR9jE9toR+PakKSuylNUPLfA2hbItwU2bo6fO5u81mDx -KlFtCSs5BQuhWdHOvFQ4QFACB8Ag2yBL/WDZ6FKrrLH8ICZ5kKOSJooRBtpph4u5ejeCagHja7wR -4cs1gQKsu0AvRXGEf4I1gSpqc3kZnxd3ahXf1yCFEPE7tZhF188Otg1vPgGbc4SwoBjDxJuyNJuE -TRF2iT73AihISqpMQgpaiBhhN2y5GQbxu4MlRnEDwrgd/ffjdIYl+q+qNIEmV7gpqQtC8QN/F4l7 -yUzRZk0sLknMKRGUVt84a+26oDGj1OEpQeM8qAM4sNirGKQQ3C5vA0bOkJNzYOUcennWRuFsKlky -BL4lQHGoSUrt2g2EIsvpLjCMc4zjOgryZIGTnM/ld+9kSwBDwVkUd+pVD8ZHmwvxAyYtiLqc7vmC -zBdsvqSATAmCaxyktkdVbBYKc+CEnqMvWStmA585h29uwjtZfInFA63uD5FRA/Ed3ku+MY5uIrfJ -64xoEe6BejlzFCVBUps4yzkOc4bU5ILTNix6uEANxn+zlqhGfA7b1cqFnBxonZuI7TpAc5KYPFUi -BI8xiUFjzifzhXsnWXBzpCO4oSB4DzOOFeUA54CXEMEsnJAGpQrqWDo5WKNUYYfAIIX1fD4SIJDW -BjqbhPkssdAQmaUYVNIp9Heg0sBEN8x6TnLyQUDCL5HUrp9hMb87WIA1N6GcM6jnyWKS3gESGHIm -StkcMY2vAPGQQHQjKYpC0Etw1eMk2oKJmcNmFriaBfYGlbEV7TXS6DmMhjahB39kySdFCiC7hO7n -qIvPZkRIzJAjOGxm6JI5/GQLQsUJKoFmF4VLGzMxmog90Im/mCicUv/x+mA8/NWPvrDgTdUlfBGx -mnlEGFh4oClXyhJ8irr9c4TqFhDrDIKIt2YwxQWQcQF2RDXpKs5Wr07IoMEFEBTKNUOV4aUF8myB -TZvhvwgf2sSILVBkC47Eis6ZdsHVswXFO4lOF+IPoZw1AjnoSLfsAhCFdzYxUwtQ1Tbk1TZ81hLD -NUPdnB1sweYs8TtzjA+hQRo6URIJOoGSybuJ9uArc0DIEjQyBzKI2i6cFDWMOF21YLVUl9gDmnVz -hMISxbDAOgi+hE4ZZItXwSWMgwYkxehfBLb51jL6vYyQzwKiEtiehU23hVY3wz60+RfBoa0hpM1A -k/o6MXdQsRiOyMp2CLXw0F+GaDS0swzkLIM9y5DQIm60iCxtRhPkY5xnpw4/9pEBCMYCF452eWXu -jJ976zcd+nynDSQTLzqy8jdg9jAevSWFzv3LXK+ZD3qLl3rTXcmXRD5G3VjtRCC0PaqSOffM8ZWZ -827u3Zs5nMjwM5/UFq/VpheFu6vS25o1AY0eFEiBzCSVuHQfKPZsw8Uw90HMvBRwOC2T7Taz8RaZ -amcHc8P/8mCrc2DmQFh4GSAbnQhOkEaek2qyrhE2TWPWuNywnufW9czChNheJKDNEtQ2LTWcQpu2 -3NzWm5snUHA2DZiZfTNTo8+oV4PdxeqKhwbbDBrommu8ZwdLnXiuM28qjFQQNjKM5hlI23KU5llM -8yynjTQowvqpPDSGkPyUNYLPW6pi8ZVF5ax5aa1N/QjgyP/46wH0r0+/PRiohI0osEq3SJP09HU0 -GV3oyxqgmXhKxOJYSSDaD22KIcppg4h7ZdQzAFkBVarLssinZ1IA70XCTkTcD4ki3EVRbGeA7cUJ -iOq4gov1il1MDFig6palbGSNe6HKkRTmLVarrsMdUIQSZt6K7jeEKzAbkRthRfiuo9+6slAu6t41 -joYwzWq0r+iyJVjFDguSchh54YbTpEJ4UkuqEvoD9GF10BF9QdO2VpNM9WpMklRUngP26YWUaN7a -VMpTlHj07IBJSVNVAnhtsD67Ifc14UilNkaa/IhyR4J5x5gk6GEFjzh00fmCVnTk/Ei2DgVu4lwD -kyJOZCouI5dtmlw8GbDBsSpSZRuaCgsrYOnaueulPVmCHpcOuobgAdVP0V0IULCJuFepWGVOeNI0 -sCNWTHaScZYEcDPw+OSEYIWkwGhymvI3Z1TYgX+d13OUSoftIB2IaHCpTQQ3VOMBgiUG1+Q9lAvY -4IroGGOScpeNMEqqjAT8RgmMXHWC560yPOhDlRrl5LFpIo1CbTLTcu4PBVZt972VTtDv8IQUCg4f -zyJI2gh0NFGxpzUzEgS79KeTrMPWzmxEirKuNEqRLtdOFnpf4VzkNA0x84fuvigyOwCXMA4mqS6S -hGXTZASvggwlUWBGBosO976HCWtol8n0h7zuTmulE6ZpMgry+Uzz1CtpRk3QXJsnI+HElA51kvW4 -NzQbEyeKwvYdMJVAf1CXTTQEprVTomJ3UqdfTY8hcCASqWnloi5NpKTK49qL/ane/mrLN7FrvJYG -Dj0LBzooThrb1qv+DOYjsGRj0BDTRIl6bE5v2TOhJ4zMvyXem/ZQ4ZKPxJhcQRI5sUw9i3R4ngb6 -mBOHOUTTQEzlGiGuvWB/tmZXyy/h8zAoR8kkIfyhJql1Y7IVRkw/fOxJeBfLmDTxSOzjdRpV7HH9 -5em5/pXV1m+zT842LLG+zE5HegZdy9R4oQut1p4LWiqc4j5psak1kl4esfaqPWZfWG356kpg+VFx -Pe0kDKOg8qsImFHdG6u1x5xGc3lAJcf9uEFrpn2qG+/25/QTq+VXtSueSexOh8GuCD4f0LOBukZ/ -yirnr6QcncRxOsnrdK69aE+5aZbmX9RuSA1P88EQ++MLjGc3cYs9NcrKCmngZTFrpKBK9saL8pQ1 -v1p+kd1ACElcp5Ibxbh/5QgMdruanvIKBebQ5RqdDRIdQmvv2UPW+mr5QfZiZAE6Zg0yfMC9I94w -85ytpqfMRCbvhzlt4NnInvU3+2P2gdXymyJGWBdQYmKsEIgI7+BGKXIwCi5zNT0H26zolhstsWeD -lqvo2/1de6x/YrXlsytWLBGPhlXwR6zTjA7ZOqv+jON1qo7SUbTOdYo40qe37Bnfd/f8W/J9J8WK -cSzRTmS0VdTAUVPTVtNjTuH/aH90mqI+kaJJM3uxP9XbX235puhk9d0W116dvz775u78+sX5q8M/ -3p5ef30+uz5XraE/vrnk9jhs1twGWAGB4SPGC6SatdGaOsp8vKTXvwgdVcTlB8FLSZ16oRzNXz1a -ti+IFiHa69MP9qdh+pF2bhGQvpIWDWz7zGdftfn4+V+vcZ3xi8Ovb09fXJxf3x0G9wtOTMUcySzZ -vz/7+kAaGAberdNOjsAbHUbim/UPR037yfK5xtUwY8Ph367W38y7vIlL9HBmfna29irywhES2+mj -n11B6cftF/Cu//ArCaka7ejhOyilCVXpnleSXGr62cPHlsVngbFBa/zss4+Qz8bhWfEZPBy4DOEB -bMb7aoq/j1cGBq3eDZsxAj1Jvo+P64ow3dF9XHdkbHe0bYqD3tDRplhvM99gvCNbnh9+uWkQcPUX -rM9AL4pzftz1i2QhuFBiqfe/NMglnvZSrUTv/vBLKcktIRucxxSzMNz7xeTkIqLP3mJi+sbqfPsa -sWQxr5V7R/4D9N7mUS6ZjFq3JhWvFZRJsv8eRQkQTYTOOKwiojGPxGuJrBWhO3xafxKc8sYfj+Zv -H237xNGiI0eLvq6N5OTgN9BcriWYPu2Dq4Plztmyu5a74+RguVVne7lv85ODmQCYv7mt+UUnFnt4 -bRgn62OCXsix/sdfxWd3nNZXd8OFN/m2D/9M+yARB9f2A3EFtQaFeTTl85DBxRoEATJmPtp2G1xe -f/t0k4kc9MDD3z9n6fiqTa6pu/Np/dmX2wY+Ue8f+s++fLvBt/ceOvyfffl2E/CzL7Gdfv7pNW4e -erMF4g5/+bt2avy82Rr+T6d3d+e317/+57c3t3fT/UHD4S8/u7m53Hzm+vSry/Pfvr5olsjaLUMP -MGX+dHvz7/NXr27PYdCc/WNmxwwPtmNYNxnlUoDtRlEquZSnIDRnJEmsSlIWmKCNNULTdK0KMElM -GRTX0BqFsszaJY58lHrLUqpFCSe8xstpuXtQVqTUMlHYqoL30er677z4wwjWt9U6hd23Jm189lH7 -/WQ5K6s3aqs7Kqtwf7fOhHisSAT+AYkMQ4kyT+14c3IN6aa2qpem/vCruMgR2LRNbdUB0O12/KqZ -RT4M4b53EjKXcpR3AF1w7cy+95VUx7lZtNPgsudd1mtWEU6CPev+ONZ1j5B3B+32fcy0wbO7vZL8 -W7Ielm7Geoyor0nYPSvuzoqmqH/2zXaBWn+xpslPTNkmHGyJRKL/n703W2gjSRpGzwv4HeR22wbb -iMqtFq/s3nBDA97bixAFyAiJlkQv8zLn5n+G/+rczYudWDKzMktC4G57unvG33zjgSArl8jI2DIy -gvIaoLinWrkYPgubi+HSfpfre5V926ovJvDSC1vKlm/MTTphOuEsZv6E1QxlVyl8+rcFhsCZ1BrK -Bcghel57oQHHrFdNr0Onf5ti2MTYxxecrWdaMTNhG7aZ6UJQZGEKJCMtxTaVewmiE8V5bFMMwFFj -Fm5ISFRPLthkSxbhJtdBnopr/ayHNmb4N6PtyfGf800U7npapA5C1SFyR/L41CGCyMISH0HieTkQ -RZX4fgWFbaeNcDGGc4oHKyYIH4Q0yzwEe80s4dPDoup3e6wdJMSHB9D8XZ92gTWcLNeRVDdc3ZkZ -N1uf17X2uvWFyj3aFStogNR2Fw2xCfsbAP2Kqo8ngMJPvSUETNn8La2h7fZhrzMaHbcGR2Wv8dNM -Mbfd7bTLn2ZrdVTJKrrozU4CKhG9Z+UH88Gv6/wr/bN+qfZj1ahng4gKITXFe2R5llIAZiqKXFFQ -TKZTJ5Rm3m62hsNywKt4N0us9NW/Ls1x+Qx688uJLXMMD6UfVYI/cq636kcKgcNrzSyl2EqKe5ec -PSbJjEK3Wq4kPs85a+imsINzkica3PUdj0gNgh/94GmRGH5VWBjJLzEMrJdC+o3JxQ== - - - 2etuSjs4l9LiDBmZHzwYkRoEP1aDS2EoN42ROk/tQ0hND3U1yAtMinPW4MoOTs/ueXDXdzwiNQh+ -9IPDOjU95NOAa3pLBCMbQ0+IZIqX8WcOru3gSvjBXd/xiErEP/rBdYFJsjiMkz2oKk81vSCTicY3 -jmcObuzgoiI4VRFcMKIo4h+rwUWS8zNZzFZNg0t6CYjPdhISiWcNnjpq92OLit6CAbPoJz+yAmqm -IyV1qguKrjIpvxyCjU+zKcvOeOSK1ipS84OZ8Ac/KL5CoALXIkuK1N73Fxm/4aREMGcOmvOgwi/X -k5gfSmTRT35UAbilQN0k16ZAIk8KTaOePVzBw0m/s8IvshpGFtFPFRsxCVc/F2mSOkTiGVP8dC4v -MAiYU40oVMBsya+cfuzaH6GLrm0Q/Ojq6JKL9k9xyjd48hq//tE5DPkK4QswbEGTkMomlMsNxazS -dKSy+TwQSOlThbtfJJDmKmv4SFbZT/kRFwMpNyoCU/eltAAukiMVF0byIPuhIeMlbqcwHoz6Si0E -g+b9LNyXFPZBs5V2TG5kijyNftc2JZKHaGM/0JhqNTdN5RZHNeZzTB6U+ilyelsEctlJAqUMEnn1 -pQMVFVoqIEWl8bQ1j4kvBRgiLAooCNhNVElGAsBcOypdltu3qhallN/HgtxemEzW2tEbFRpCJRbL -/GYOVyW0/xT2gPuTWknbjkJHEZQmmQPljEfpEhkhEENACZfabUdGhVQRVFA5RDRCjKUnyg9iP6XU -J7nhN7UMcSjHcDkEYdlJVUHoQ4xdN/VmtJO5sRVyEaIzwWuiyHH7paHqrbR6JjtNMYuMNkO5mCqQ -ZqPZfuqBtjIm9YapRnLN5EODpoRInE9RuC8VFaICoEylHYEfwCGIY5FwoZntn0D2U87SGbTDdwqS -h3BLoNStDOGHLYxxyrOKQHoPR6AErROah3JbaogqddOlYyEgKeW4iMSdRH76hSBKAsnkllqQyf2W -pvQyD4F54Q5jnvC6+LEVgTChmgd5ytey3o7ygWFvnN2bTpZFnHKXT8rttM31x6BU8LqUGoNgTSz3 -pQPa1GjUmUOvkYUbU9udkXnFhkRqkcnlv+mgav5UeJ7Gr/VyWy7OIamw+KVgTEZ5antLtDu7/PIB -QTKtjgwlcEJgIi398k+5smUQCVTgfZUDOSoUREphO2N3hmsaE5HTg85oqRi2b4mE8w4hiJ4aIsgU -SV4D5Sr3pG+Bhso72s4U80EOueNBtTu8hZ8uJ5BGNk8kRStILCPkQi4hdyCIPzNKR83wLbvkQVPP -tymnAwugxFM+pja1Mi5zfDBzvXHa8zEhah8ukX5VkI4NTEyTvpsoKjmHmknBj5LwRyQn4HxeN6Hk -CJEyQAmrMq8MBD+6HAFfWjf57Dl8cd1EWxmVScbvcbgjGR4uYChdC5R4u4pAL+koXhlBfOJYDFGV -RMXp0BxvI9aXKZfVjNgdBpYgyEjl1J8sAHlmoYoKaKeilal/rIWy42pleSALQppK7qdC1Xh4yo75 -0NNtWldW1CCm8FzLw6STm5z5GEGJ5UWaah2iQxGfibgVUImnzDE5AhmeA9kjlummAchxOyrf64Bu -7cSLM6y7qvzeUHLlGog8zQ7IE+a3zpjGz+lkii7ZEeTln+KMgAgUReLWSlIMQU44VZBgoz0Q/teu -lbP1Iyhze0XiASFGefZMKfAIqHLhdsH2xVWMaU1GVSBHXEUFcqRKj8mjT3NUDeL+cyppS/MQeaUe -ZQxLjWNj5G+kNam8BgrXXgFT4zQEiyEubCxdMXZCt/TozjNKkxpuiz1r4eZlpMY4kFs7p6qOd541 -iQwPAKC+a/mzHYMYu4MJSzeG6wUgiBzmCOL3QdLWXiUQasmO3dNmIpBTxSMn4eSukmuzMkjb5RLI -fsoZ76N2UtT6krwE+97YK6BFbWoyzdwCUrsAmVvapYU6BdRY/hNihLO5RrjTfHDHgPSKLNgN1I1S -Y/dMe2XYHq4kEP/GkwCLQF2dOK5zEoNkoL86oCM77C1JI+LEQYWo0bDTGAJa1zZFbnAiUFUvTO0w -oeqgghNm1++ptvrYHxM/AqcPjU61picH9vA7ZacwbllO8XeQcPUellqEF2RiBDwJwyDTOu/C4oqp -ig6TU4ei7bOCKzxMmjPF1veesvjFPBjzpyTZGJDz5gdcXVPNu4j1a64aGYsITbWfQkniUV6JmxDk -pVIItMILO7NiyUk4HLRwQjnQEunWMJCYqETKSKwiouwKKpnsT04okzXlOqh9zNZ3IPb59U9NPdCY -Zi/SIjQbrKGuoZtco7y2/Fh7WfdKoTYpJmhCnxSguTB8KQkUA1og+se8GphmXgWzyhYlmjVe7wp+ -TLOvowZ+9hy+gosKE4bOpUmTUkYch1qbAzKI0gMhSDi9nCLHCRSINC6nnSaB6KOk2gRKxyCBfuaB -SZ47c851lpAFGw1Klpv3p9jJcQZYtrRTC5K5UwQoLZIDOQpMw3aOomXYkkFUkDAYgrM9xjPRTU6u -FcxYW4MqWFcAEsGprICp+9J15nCpKdNdjHJNGeujrdFNLjISbKDmNPweFApWB+vGsDTTFdvj0iiC -y9Q6oKTaaDUgcRIHZE5CSiWCHLPheGIsHJdW/IxUVAQWTjoy8wIQLycGBVKvAloD30kzAKXCCQ1m -yQiS3tSmghoEI32F+Z6dHPtamenjzB2oXUMJAe3q+QVsHagorW7YI+f5JZDjwLamvIg4taQX3bQM -x1o5ay8t1vH9ChQgpQJ65EnyekQoZq9AbSsoN0a4YazMRJtK2UQ8yLPzdJxMhGRSpA12imruSMI3 -dLZnHZibLBraOWX89PBZcqJri1CcNDJcLIKyPEJJCAotAQ/0nKggzxVthPNUcIklBKkkMAXyeGOD -FbjtRyVfqBpBOfs5oh103eV6DEg+q6hDTmUWDcuWMtFTZalQ7BGtQkWGVUBOISjASQX0uHOdVQj2 -Y4Yb4efmtksXNRahOJNuTE3B2kNqSshtiewJXUZ1YMXHaoLNKwgFbC7dBoMan9E7AEymgv8jhEko -n02BN8f4g3sOgDUWZHC7xWnSQ1mNP2rlZXXwozFfR3X47Dl8BQ8SYBLHLILbLcwdmTHMXm5pl1Gg -8JdbmhOLE8hfbuEzfrRoEOhuGUzhOsvc9YEhA8iBnASk2mhxu8TNzdkR7FZ203Aciwo90XSlUzdJ -Y9eFv+AKQf6OKwBq478UDFFunZT0ByHVRZe7tECgvejSnPsfQZW1WIGKCkUV0F504RAZr0B4SU8O -Z4T4iy6nkevCX3Qh1hLGWuJVbb5hcSC/L0VRa5da5Lp7Lm1deLgof8+Fbwslb5a759JUg5ZBqfMB -ZImdb3XPheknMh7C3XM52wtB9p5LcyEh2gNdOaS50BECc2dBFQ7l7gbLXT85EH2K11R21Kod5xOi -IfgeylC9FJ6Iv+rC609VWAQwDdo8rYQ4e9XlQXlw1RUA3VWX4dJmOnc3XTgmoTIPbroMZSshoLvp -steMBLI3WIYdBB7krOUibqZtDUgE+bu6nG6VECQDRU3xRNxFF+p8VCQz9xddTjdEkL/ocioTriFx -h5KVfgRJfz1ByUYQZCpti3RthNl7Lm1rnyKKEncUDKnmDuQJP81r7dgUxd4Kb52mFm3+nku7bc79 -PReaopoRotQYxN9zBUB3z6Wb2mPXuJskNj0Iu3nAkArbLnGqFteS07m/59JW0UWQv+dCXFr0Jl4N -zpTtLfFXppy4E0EyDY6M4glX91xFhnJeZ/7+ylibxoHckaHb56gd1SCg3qzvx9jb3GithqsC867m -lnw5pRKA3EVXAKouujywcBddxl7p4fmzF13G6mp0dAs/X/bRIMe3KrWxnkME2RusgDuEN12csjRo -pymXG4M84+ZafiSMksrlQu5xEniZOzXa9aac2RYLVHvTBWKb1SZpSTS8r2EGjAwruK/hBJ8I9Pc1 -XHCTVu00PyqhRaBAQeTMcSQk3X0NPY7kqTPCpI3adSD6VFKRrArYtUCZprWPnUh0qqqkFIV2Jm6H -MZOwcfsk7JdO6jhVNYB4z1AAs/4zTkpsubpwI2bKioi8WoCwHNz57LD+l4VYx5605qED2S/ZwnVA -t3SyhInknVNRNrmIbw3IF9QOyCCjHDM2dmRWqCI2jr5uy6Occ1Q2OesugtK0DvE7HQDdhY1s2kTS -ub+wkVRHkEHetyvtziHQ+oCl1fpy7yeWTtDn0Y1N4nlPdWXDlcuCbxUF00UDYN1wi6Pwyoar6+o8 -uLPhNNa0LHdn40HhnU0FdE4pyiGFAO+44kq5hPHKwSUcl63ubETF3Jz1RejyIIu3gu7Yo81XNuSE -tJrKwiOpSRqBqO4+OcEy8kZ7aaM4+z2B7M2IqnSC6tIGEz+ltp1wQVss+wjkAo4wp3YFcuabY+ZV -O5tXOuyN64XrPLi2UV6JqSbHYWi0hNQugcvmuaXaL3OndIVIoZI0dfRxRfs60GsffkOsfpqH/ksn -fv21jaJyx5YKfCCeo093bROCAorywIrymIlG9JmmdhEhHWPeyxq9Z56O/ang0IjoRNkizvGR0lRs -PPpYB0fF39vAlqW1s62tph7IbusQpHW5exsHCe9tPMypdrZ0eh7c20h3xkJNVNLzmOBAYTJeER8o -TQWUawfKBdJEm4/OAClrzFhXci0EikSbiL3jtUKeRTIAMZzWZQVeZTop4+5tLM6L4N6mAoUXFx7o -/XfCyafq3kZQ9GckEjUl9Y5Ep3WuBeJV212KZLM/OkUUS0E5zsOPVZMzkEfiP8tSJ5yDsA5SYiN1 -gpOIR0pHJp3ZHERTxGpMdXGTiJSC80FRAfuOXBEgtym2ORGc191GGaNb3sYdJy4jpvd5OKeIdX/g -j0p4T0jwo1ZfxzHz2XP44o4ZYy/kVRqoik4FVVmgKmJ4GBIhAr1tbwtXZMGlHKhiOYOCAw9ywbbz -d3yc6Q1Bnhy5kogDefuI6hhk0TUiZ4uPPqaHAzyEv34nSqapVAeDCiLylN0BohSJvDB3zCpQcBwr -oD+2HCyDIHe2KR5FZREHUIXhMStOoVXC0634CV+1O5A3SWUFc6snAxS2LA4/oJIhMZBy+ToYQ5Bl -I8QzUCUsAUSMtuAPA35MhjuCPNP2kJC1O6BTFbEvwZCsihZMea6hNFEUzYJAL3WwVgiDvGziuoMO -5MiLXBoO6G/akRlFH+cUPhQNwWE1NJXgkog8Bwj0IRRcFIXW5QItPCgKSfBAdzNH9e0J4m/m+Hqf -UF5dzeXkvIq2xp64cP8yZXQFctY8SZja3rMHRWVROEtBGppKowgfrjOgUq8smiZltCeQ1ceMjd1E -kFcWjS3xiECr3mHmslRaUGItdSqF5kH2U0FHN26XWxquemPXBo2qKj8COUOj2bGHidaQ2jWw98St -1fnP6Lo2woppcjndCH+myYmO60AKHAl2xHD5L9437f2AaWb3t3J+sP5NdMDqoqmOng== - - - UxdDkAx8dw7oiA97swfZkSiOqvIaKePshIpIHkP/MxMdDHx9IUXtVKHfxARHzSLAk271cXVa/BAF -lUqODrjhqqnMCJyrp2AumHp9MQCFCKiAqcU6X+wFHMpQVZoaI8NKWHkRnSrnD4r20Mqx8FRhvt+x -7edSsxE7NpiZTI8DyaIP+DuMKzIdSQGcihWplbTAJBJWWliZ4pFeCZ4Q5AVUCLSCzNir8kDcGa7A -GItFQ2V1I/FpbFRPIGSNDX6KJLQ/PaGENvZ9QPQx3z4ESgAVsnYSutpsjuEJlAoACSegvY+bPLS1 -9ce6zHrsExMy2M/j0IUVkIKtbxoSDNriUtYIC7ORWx5UeVMKxzG8N6WCBN4UD3R3JEFnVMYoHpQi -K51PxE3OFjOgJVjeFbhERBJIPq+GZ0E7r9YnqvZxbnJdG8KfoXAqOes01ZSVjaoLFhaAohhYD3Rh -wr6zKpbYyaQw5JifKgV74x7ORDuIxTZqh9n6F6LTXMHIfWhhhSVbokxnENJbtzqQfBEOSCAhLE2y -6GAb0Z41In5vhyvbzj/3YBsOydmrVxXIVEE4FTB34a1s1UcKLVumNcUXa2ZFJ1zb+71Ijeb6atEJ -r5ASWoQpVuQcA3Ly9ywwE7mCR3DqlRXR0alHd08an3plpTst1sVWVCCPlBDoCYof5gUodo+doq1Q -1rMZbJlz6wQbqypVikD+QNVoAqMc+HTmaeI9UTGdeD+mkyMhkEKeo4FZpYimR2V168uQlu9Xq6Xc -3TFOKlCAuwroz6zVm3AnctebcnxfBR5VMtmjnfVLqPbfFoSLCYrvEyLikVwRqw7MitjQQz+7l1SO -V/LNAU0v8Ww2S9wq+BbFuZkDegpBHich0DNy15lDcDBmtQ/B3Nx+SS6mGe6qdApISE7B6kOSUOR2 -QRaFt711YMjLIunmPSW5ApVA0Ht66JWCW2EYClTB9/V5yjmSXHkmSVLbu0hU9SpaVa+iRfUUOvhR -faWX2Z89h68WuyLNeOyKNGOxK9KMxa5IMyF2RZp67Io0Y7Er0kyIXYnbJW5uceyKNBNiV6QZi12R -Zix2RZoJsSvS1GNXpKnHrqDNVVSxIBy7IqtH2i4mRZqx2BVpJsSuSDMWuyJNPXZF2pqT3l+CBqk0 -Y7Er0ozFrkgzIXYlapda5NZjVyh4wtRiV6QZi12RZix2RZoJsSvSjMWuSDMWuyLNhNgVacZiV6QZ -i12RZkLsStSOY1ekGYtdkWZC7Io0Y7Er0ozFrkg9IXZF6nrsitT12BWpJ8SuSD0WuyL1WOyKAzl/ -TBE3c7ErUo/FrsjwlbaNXZF6LHZF6rHYFaknxK54PxcTEtGWHotdkXo8dkXqsdgVqcdiVxzIE36a -19px7Ir3P1auQ6nHY1ekHotdkboeuyL1hNgVqcdiV6Qei12R8cNljl2Reix2BTXVzMemc6VkPSF2 -Reqx2BWpx2JXpJ4QuyL1WOyKVGOxK1JNiF2J2vF1p9RjsSvRWl3sitRjsStSj8WuSD0hdkWaeuyK -NGOxK9JMiF2RZix2RZqx2BVpJsSuSDMWuyLNWOyKNBNiV6QZi12RZix2RZqJsStcuU9PsNMdcK4y -hekYx3Z6xACcaY0kVLPTyZpK65AxO126N/tRZ3U7XeoJdjoCa3Y60WNspztQZKc7YGSnRx+znR4N -wXZ6bSpsWgdTdvZ3sLAANG6nV3gKOqvZ6RHOnZ0e7I2z06MdZDvdgUI73cG6MYzIJrTTnQcpstPr -wMyJzpqdLs2Yne6cVJGdjsCanY6Cq2ankx+sbqdLM2an04GM7XQEjdnppL/FdjqdqthOd6B2jJRx -O70OZNljxux0Gja202l6dTudlhHb6bTY2E6PkBICa3Z6gGJnp0db4ez0YMucnR5srLPTHSiw0yOa -sHY6cqsxO73WkPW+OjArstrAVhsxY3Z6bRlkp0erZfs7wkkFGrPTCcexnU47EdvpziqJ7PRoZ/0S -6nZ6jaDYTo+Ix1mqNWBm1d26nR4M62xmml7NTvdSrLK/A3oKQWN2eoC7qrO6nR7tQzC3up0e7Kqs -hO+4nV4jCTbJnWZbB47Z6Q7m7fQ0lwUlTYNTnVPteAPqL6XhA83RUK44EEf0+KT6gc12b66LKlGI -qBKFBMlBQpv5qxjrnzmDrxDNQNn8YIjAVEdqI2VaeVsdOVrGEO3u4lLWsFRgqhvLFIVyprrBWl6K -IZm77TN87WVB7kuKe4va8f0+dWZvWFgzdtNwiptRtjtrqhurVAvlTfUQ5E31AKiN/5KzQDpT3VqQ -CKlMdWd1IdCa6qbJJfaECq4rK1BRoagCWlMdV0AWk3KmurH5WYQKTHV3HySUN9URa5xV0Jvg1kJ0 -EPtlSgFeUbNUJ7zHzlQ3NpwEF+VNdQTyl85S974U5S11BNltryx1YyOPEZXa7Qvf/AnlLXUAcclR -FVjqxr4wRSBpS3R/6TDuLPDUPSBWgaXuL0TCdpx4hYZgvp+yQMN5eEM9dUxReTs3tQ+TEW/WUPcg -GRjqAdAZ6ql/yucs9ZSf9AkZGOowNcEWpDfUq9tub4GnNkpcyOiitsgY+4FBX/BpDTwNzHwR5A11 -vKfk8+QtdWNzp9E8lN3TLOE/Bpa6sYYILiJxZ9K97vWWurFvqxHkLXVjNRcEWlMdYwv4Kt6b4EjQ -eV6BPOFzIEjQzgkdb6rjNWjmEOyv0HmfpbfU8R6Upaaz1EOIt9QDoLPUsS+LXWuo44h2XqHxynnY -aF+0xQc3EhUDNMyVAisdmS7H/3grHW/PbVeJd/dkjOPASMdN5ggOb6Qbm9dDCG98p+5tvQiM9NQd -4bCd1imTjDPSU+uHitaZUi1pu5+5JV0uIg8gZ6QHoMpI90DljPTU+iPw7FkjPXV6m2LuaL/U2p5u -Z6SnbjuUN74DxlAZ6YAScpEG7TDqImeGl3qWzTmOSAwlnurZjyWUN9KNDSEWyhvpdVEaGenaxpAI -GUUN8vW7UFHUYMLIVkE6OhsIpuyLBFH5beKMHwk5hESYEIyzUIowp1fBL4PjhGCcOMwBndXDBc6j -bGKkCIswIRgnUxVxQjD7fDpMCOYlTvUaugIFQawV0OcEo1TDzNSFG7SwYiPICZbmVlixo4nWJS2n -r5KCcai3A7n1W/oLc4JlxtiTHIW/O+YZANnDJsLEUqnlipVFnyY6YOzOxLPmbuAgcAFZlRehgoR2 -pQNWScGs8AlygqWJLf8VJgVLhfvSR8RbjS9M7eWuTKLAwcSkQTsf6m2FWBU4KCx1BIGDwuEoDBzk -VyAizL3Ej1FEmKHJg8LAwQroAgfZSBMyCBzk+HIho8BB4ThtFTiYVBzO+xCkLegXelsKS0XR7hf2 -KIZxg5xDS8SZwZwsCTKDycLuv/GOWK8ThJnBlMptO5/NS6VWRFUZv3RSBCDvrrY4CNpldhur3jhH -pYhzg3klppodX6mJMDmYCyqMkoPlTukKscJpQkQtF5QtLxfHYiayth9WOw1zg1lOEScHc8Z9cGWW -OgLltw8xKCApD6xIzxnZAYE6jh8RcmqMqhF86gnZHwt27UZnyt/PRVGDwtiNrKIGq6PiowbJrIhP -t/FEHTjZnZESRA16UBg1WAGdcudwHUQNCis+I2UUsFRERwqvAFV8pDDSNdxq+2lC92PR/mM4rS5q -/NhU4i0CZlbAVXGDgkVOJQYM5wKKxQUAncD2cYMW7SqIG6xAYdycB/q4QWGSWOAZ1u5iwYgzEVZa -+rjBhI3USszijYo3roMjxacnThDGbyaDjzHjmrCyvHo8YNEZvTGgJ0qRVsEmRah6cNbtaPl1ZSYK -G5SVmuyvI6S9Kw1oQdosPQHFoKrtyCO4jpCpTiMKRAXcUoz3YlWQ4DrCA6vrCN9ZdR3hB63uAILJ -ubsCaYOFgxMpbV7S6DS7a4voOLuLi0i8CptfJriOEKnl8eF1BOiZJpoyehMcc/DXER4UaggV0CkS -vrNK2xBOnQmUEqGdpuKVF37jHGk4iZME4TNMjoGOTnMADC8k+H6Q9PvALygs6UZAFnQq8B9bQ9Al -siUQvX2iU1/5j63sU4ED2buLKgdyBQocyBUwdy8sOc1XpNRqyklUU345a26kJKs8C84ug+jSp6aH -e6yEvuLCOX4ioLRumMqDzDkKg4MvnW0YnnwA6iI++c51S6t1LuQKFLiQK6A/fLmx+nrlQ84dow5c -yHnhlHO7ZdJekgYbi7eDOqAKd6Z0jSbwPTkRVHUhIWtkYoGawt7rwMx6M6txuRJDNDsrA+JVGO0E -k18sp0qPUFKBAtRVQM+PODUTbUTuevM2nL+RQDVN1jfWr6Hafk4jFREUOkOScYLipyQ1oDeGfYdO -ovlBE+E8sv4+Al0Hbg2sKgl7xRsQUwjyGAmBFnNVZxV6/ZjhNjiK8JtVqQF+SwW7g1V0G+FXHtKD -Vxmr24gAGHKySLj524hMg6ZOta11jn4BLN5SJBKLyIDGz2VyBOa5MtEPCcYRBrnTq7Tl7ioAQwsw -AZi9Baj9OveVMmf+sYl8hThC5qMJYVsp5zVhd66HeoMLlSgPZRjlO+bcjS7ii/gB5ngUSK7eDEXe -lAQmg6AMGQlZDK47D5NF9FqZoWAz+I9zQ90lqY/GYydtgjZDVsXBsTDFvJecOpdgpuD+ZCELa6/B -OkwAcwocuaU81D/jE6b+fa5MfRh+2peQ6SCqN3XEQ/Htr5Tuvj7PeI6FD7+0kBgPDhggzPVWoZVz -D9TRnysh6vuUFYyGcD/5qayHOTSQjVKnh5SXNwGIOe9QweqGn9eBdLnpgDxJdm8jz/GVI6isBWXk -M1UYHEtcytynAnxxdr8ahLh9iEDMFahdKBvjT2DuEGeP8rYJyh1SfakYxikCKyIQxMsdQtmJaUFO -kyfCckB3lIgCo4+t1keiIKJoSoGYB/4cw/0Jn/OTzwfle3R486Bw+R5YBYow2ZAscGkV2CNOssAv -QtJj6WhnJL0mi/bPqlsW1A45TG3zHdvJC7LXXUgRW19hU2Vfi9WBFGdaAxZKpdGM0JmU5tG8lX1Z -iutDs8Q53Vg3wdyOwjmn2DHTNBZrIShEqgdyqBP3ZndXeIdwYSmj2knldM5qx9FxbHfc0YUtexTR -lBfmIU0pF3IbfmtDCSqyVe4+NqRuVLEZTWmV+4PFcHVUKki1+ApW6fmsrGOxB1fURhshawTl84qH -G2Ms1oLt44jziKCUy5IbExQ/503wDlNViZATZaHSeqQJxcQec868QsSUK+JuWZHgDbkdhrOiAzSx -iit56w3JRVyvS6ZCrv6YYboNinmj4nThIb9FfAmGCeMCZnRiRWoS1tzi8h+US9lTC+fQp4zLibPR -PCyQHBXUSxjskMV0JYesI7EmsZQ/rJVsc7l2QhGobIR8LEJRFS/SughVHOwQf873GTiM9nXlKNsM -V8ZJqzuIjKVRoqv6PPwT6gMcl0n3CAlvTZLTuuzXWR0RmZ1LiFl+75qAwSPx1sYd2A== - - - hIWt5nPi2E7K0zEpp9xnzzbLxlQ7fySGBieCCYr0be8pZ3GNmbUSx5XJ6ZhQSl2vaFGwVUIFkbSJ -mStmkyWPe427WkJzSvSXUWJV49eGbjy7ZCvIcvlk/pnqLW5fIiCBfOQNFZe1fwlr0sIfq3Kz29yP -MCoqWgtqTNUiGDQaMxgyGjEesD5ebbix0Vw9yjNwIhgnAsS5rb9LRhdumM1F7+5+cmPin2wj+qlN -Kr5TFOhPjj3Y5vwrHykO2/DNg5+U61MF13XB96IxNg40XwazAuBpNWs+fZndEe7R+hmzcJcpWsSt -1D/4d4Eo/vGIX6k9gnkViN1g2yZozr/aldLPVXP/k2tEP1mnb/S9boyNY1f6BQ6AZDPuf2Wfv6i9 -+U8jmDXmmhdiA54y3GjHwZJDHCcVjqVmPsSf+q09vhRTRrydAUHFpBbTod95bBbSREwwASnFRBZT -oJ8b0pEnHZxoWq0wIKKIhgKUxPsXIz3Y9pggYmrxSMVm4c7F2xpseEwKMZ34uX1J9vBfu5NfjBv8 -Q0jiDzCAgM35jgKUJh6l8eG3+55Wug/+zpsWEZDkFtSv/y2ULUET2nzXBZFIGrRXjoBimkrCAdxv -oaAJm4iwj3AFXt5EaxJ59bvniV7nsnxRKBO10DW8aEckWUQCRTTvIpi3JwbfhIjB90G/hZhJLOHw -UfGfq2gEFYzgJ1E10VEfOsbMF9VDvlHNGNV8Wa3lv4z8/qhWM46GsT2RKt4TGVpblWwMegqpbZwY -a+Q6TtAhvUeUxLLSk9k4GdYIdZyUI1KPZu0kZ7SMNMZHjUTHKLSG0IgkxkmmRlTjZBeRZbQ5LBI9 -MYwTS42cxgkuIsho3l9aZ/pGF19Ww/ovILA/qIGNrby+BRWnGmNUzy9d/TC/OBitdNqjTr/XGvze -uA2gpDG/1O93GzOLj4XZ7PxWdjfLwX7ZHm3/frzb7672Wrvd8qj8fbZxC1qLImM3olQwO5FwXEPG -Oze/Vba6z1qjQec3aEo9qs3WaFQOesV2t9Mut9utbqd38HDQ2XvqejSN+ce9UdR4qzzoDEeDFk5z -5/eT0g493nD1t5P+YBQ18WvxbWgBD087e+WQW92B/14qGjOzjVcvL12FpsmH1d4eL5d+1x+WYAY9 -C5l5UR6N+oNh+/D4dPSvWdi0Rfjvq18vnfqL68bBJapoSf959Tv88gR++AQg8io23r5LGntAvq+2 -UOzbRyV5iq5Qdk0kHEee+xTXxqZ8yLFgkA1BJB+/+2oZRZENwwVYoW3QNbuZAaI44KY+2vKlfZjI -Cn3NlkKacw06UkmaMkNvb5phKA5I3IKKoQqGcHBbwtF+KZ5ugdbEHNbSoduENG1SUa65jF6rCWol -c6pb2eS7gJTij+kzDJ6ngrQppj2gfBxNZUdzaWgSjJqloOPUpl9fxrzUdPVB42Fp0Ya0t74wSbpd -wEQjqIOkWLJE4jeUYIOKYWMJsTxrUEIYqhAt+JkQPqDKqSqv4FqIy5dSDFehyrSGEw1om7hpLksx -x0PewDeJ5ODOCmany5ck1QfEzSRfftFAnEraS9VMUr5elfxwP9ccoYObSfleDLWyT9sAKZRmMrcP -6wA9BcebE0QWDomUCTen2m/QAeEJcZdT+h1oTnUc8TYoGA8zCNHNRo6VQ6i8K2aqIlIBuqLEHDnl -SBDReIWHYYorup8RNl4WcMDJll2cz1yWMxkuE2kVtD5AHacoEPiiAh3cGQaR03cYSY3B81S11/B3 -SPd41QbboqSyr7K4anxibzTGSBmJXGOkOdXZzUCm5cgStbHVNGAOVD6SqiPRBuccZoYViahueA7T -RSG3jB/llBUW0EJPDbW0V/o53vTCnmsXMlotFwfnEvI5R/RrVyokw7oDGfUSTw/njKTHVQiR4DJ8 -74MgKiqcYobGVDSwIDLNGQGplo3qI6B+OE7Ll7LEBj8RBGaWujJh+AO+ccI/ZFRPsoC9phdhqbH1 -IvBrmE+q+FtsAiSTSvtwH7GOkQpwPIRNLpMWnI4Fi6LSyyz8pDBFw+RNHiMvhIAvTG5TvsD4eJaN -S1gOv+ODeoNVpJHSU3yopPALZBdUSFIz3oyt457iTXMi8Xe66WL2QJ/gLSBSeyoZZJDqM/4mKRQB -JCXQx0UVWvJHFJiElQ1pZtK+zUmR7KQigHG1YLXmqRnklQaZNuyVsVmsTMqFKQ0mRoYecFEYTQpf -ZBxFZPAyzECfWTPTOIiR/FQTumJGZgRfbtI3HI5rEgyfxl75bh7LLCUhzjRtIO8Lv/HUkusupPja -EZFmMMZAIzlw7QEaF0fBw0URCdA9KDlAVNqmAoYtA/WFyC4rqB6kpLA1/Ejx6xjEq8H45MwlKkpt -vDwCBBUQ1vx6jj7isMCU8mpRxxyhnGL0RUTP9hDgyaDaBJxeoeBnoiCuDL7ep+y/ia3YYtyrJoBw -jQTjSpMqwW9Zkfcpm5pXYhE87NRQmj7uHfiiboyPR+cztZflEiuiJ3Q+fUJHxg8wBX4bie9fEQcE -oHzzGMpBewqMklOl4fN4rL6B/dIzACqFDm3GBiIcKIxkAkITHFEBGMBErhgwA6DMpYvG5zUGM+NQ -xtmM41cMxv1mkiRGTmECCFKURLLgVxMGr4ApXyyWlQAOZXkFfKITLh2PSMmpQFXClIt4o4GRB6KQ -owpbxITmNAaScz+cLUMr3i6qKkKFTzB4B06qzjguGz/KONKSSpchUYPagXJVA7PjojIFcQptuAAp -flLwG2+NTzVxHEMVxwv8hg4eAug5I3SWIM9FErCPKLVLIG1sLjzojQOx4ODRUyQuP0eyz+TMiw29 -tSXeQBWtsbMcZRTqRCgikOgwiAu+SVPOIGNchmhkVKBmwO8qJ4VI8RMBYBuUGhq/gSOGapixhTJT -ziIGnAcsEMWnCc8QnmuT8CeKK06n2r4GwD6QDmF80GYUASiBOipAGJOAHyUsejLSqJi3EaNAnYjO -LTIkJN4MA6Kltkigh0Qoh4hBpMx/Mmmj2wFgMD9QRrVGiBQMZyDOMIMeHdOMa7VkmNMcByZAgoYE -Cw78Bl82A17gKBiTMx/VGFZBvdA+S26cUU49Hijhl+vwkaYoOiQOjBuAjpkWgCgoaiYYSaecF5KU -OipTQ4Yr9ptS4RnDGAvGQcpFdTbDNGFUm0czfVK3is5DnsWjWAZRnXXFSe6RI3KNL3IlZYqOJR7U -ggPzEUJlwYCTYGUV5M2cszrnrjLNtdP4cNOze+LNRaP63e2O/wbkHFMoVQGAo4tSIbGZ8zHGJKOU -LoB4y3aQURc5URtBQOEHwmE2pVKOFweAr8uA9TmgT836n9KsLKWY2pV6TTmzVmqfTLthUCYjL8dv -FD+tRxmcUPKv3CoaiiXDHNi/VAwNW2TGfkP5MVL7QkXZKpYGCxHIajWOiSJjJQ6I6gqOA7sJmmJC -WgUqF/jyj1hZSkoAvYbXKRtDqBSwAEUtEeuioEgCNULb6DGkxwT4Y30EYud+8vias6AwZWkLk6do -nlBOMMGmEcpCTnpQcIpRNKwy0kPmZEIxWCmlNFOU9IZiU1NMBkZ5TGsD0ehI3fjQ0NIbDA6USWPh -JrI6T2pEJm3tDpRkisV8WnDijDzh55z4KZ5R1G7RQCKpqEi5pUAilHgZ2VSo21PWD7QfyNbNMNow -I+6AWwrDCXxnnNk32PiJfW2FRE2JGTLMFJDR1IDP5gSg3H/EpwSdtwwzKCpqQ0YynhpkCbgcqmBT -QwARQsrv2VFrJ+0biRQTVuQYfg/nXaOewL9TwgEUjCiG8HewkJAQEs5rkKH+UDSA/DClPvyWaqC2 -2gA4JiYAwBeaeY6ZbtFMoReLoIPkVmS4yKscJ4rWrmQ1K0ciI2+StJZlbnNcSitY0ejHF2T1MXDc -U9TGObWETjhM7TgACZb465eAd3NgH1osGHloPAOTTMyVBiocfRmMU8tZLUBt2UhbSJ4PBh0/W1dT -Ojo2wr621AmrpyZxRUmAM+mc5DfJRWxBRT4A3WmVRhxmqfCoGnoYp2xqRARQ3T/ln/siiCK60Qma -K6tNUPLEnN7Ca5ctU2acG2P5EmpNnPwbDQE4mpguAn0LdLqQjkRhgz2RS5GRLRNXKkTwM08p7KMw -hUlwYEDaSioUY9gYpr3k58lUrAMWgOSQu4JQGBEpffL4gs2G5UuicIwY2tBUOFcLDExWpkA3DHHd -hIwS+ELZF0OIFlDjG0LwU00E0FQKCoeUxDHzJMFtLWxdOFVwYl8U/Y5aBPBJfPfLW6jpKRegGqbM -5QXtajSJX201O6TVZmGrHmZYhodQQGoVtyHmLalUBQMomlUQW9HUbS6JoQqkOFIZNSth+MiL61Oi -MylvSBdcjoRsBVmCh5R8UKjRkjci5QxOWrG0Q39XQhlo7JqWyV3HdVxQyiPWQScl/wgdCTJXC1s5 -yx0tdtfxm1sgJUCbIn8Hv/0A/JF6NSewxmHCSjmqIPhZ4QplC3wajezc9olzVDlltvQ1YtHA4+Ho -WXbC6ovN2ooGYs5KEZGldIWPDeKB2BfClC02SK+b4QS4Gn/k48DcQ+QHdJhv26GouLJhlX/OR/QT -QhAR1RSR7/OTUTwOnJ/e107Hmr2CxTYMJwhiy6OkTfuMq7CCQ9lkwHP0vDBjFULYJHREJkjLudWQ -8ShTEUIKhOWjIjmPY0r2DtEJJQ/O2eXEhg8nWpDkMXKHwab5y2lleLg5zapCm8IqIST+JP6J2vgE -MejPZXbF/kiRcrZY1vdsWp2UjXCEsINAuCLQzs6aExmfbJykdFHmxlXeTGzqcIG7lbBNV3CyMFBi -USNhJdZm79Hk0qDTxfmqcvteERUcfndeDWdcPkCcFG6aQZUrY4Cg0rQGE3qRpxM6MjyaUVb5FwU7 -ELAjm+StsIqKHw5InBzBbKlx8hBUhShU2RiXVlFxIl5sQ8olp0pn/2bGKhvunFbkhynsIz6gBH5Y -jJ4bCo5WqLYKtomETXiE20q0jEaRpO8KHFey24iTcxdO/0NYQSaTQqmo2avNxw1651eJYIsVnF8f -3xsQVshDlzOMM0WgK4yGQ1aN9hO53sl9g7c5kjxJ5LWmiHp0dJCW7m02iQYaeRYVl/OR2CNblqjz -0FJgp4xgT6jNmF3gq1pBah+/dg+OQVZYtQC5ASVXR1uJJaSrEu/UqUBmkq+XU+Uqa5uDQsiF1WCH -DauMbjy8PcSHqPhdZh+IIEEiCgBg082hLZaISrNEBzE9KOfVWSKnI6kIBQWlNgFJKQgZmbKv39H8 -yLS0yCzoST4qE+T1ojsA5DpA9im9wUe3J+VXQSGcKd68whaOSzG9Fvm5csvj0eqnNBp4BUG1dFGx -4HOeGltcL3X3AEB0OVVLTCkprWS3G1+zVNMEkuZ3BUgzIuFWXHYMNI6CcpA7rwUsWCgWjGh989QL -egYl6ASxz7+gqH/2+7G0KOi9l7Inz0bvJ+yvxFbsI8ZrJTLB3cHDS4CESRP9SJyqEA== - - - jR+SJcplPBNWz/LsKKfc6c4vxW+dsE4eaZwqd0lDmlw2zX6F2gaqU8ygyXiVmP0mZ58n3WIIdJ3g -lmiblCP8yBanFPQUQxDnJzdWQhIB+bOz+RNCtUidcwFJHT3ClNYU3ddYT1cT2wtcHwW6uYvUuT6w -2B5em9FFmLGWonCvuBGA9pXQrBxazKMLVOClGx4EHBqU7waJD9wS554EpgjqFDsY0LkOm4PmIBEj -XnfgO32Z2IoLVsBJ5BRJatFHdI66q628I/GtJAycuoJV+B5J2AQDuE3CLkjZ2pWClVASURnzCLye -wz6z3KRWqaDUbT71DvI2ZHv4B/KNCjKbleLqmvhJwXcJ+Bf2zNoq0dC9UYhImXGyYsoooYnpU1Y3 -1JBRRxecoJZYn3TnWdrM3gEOJLtKpEsQhG3RT4u6OScmF1wh3r2BY82F8nmR5sMaSIEJQPH3RGSV -uoMzwm1nRY5uLUlxsEkdyH+OeplQwb2ZwosV5qBwYsiwVZIvE1GLpIOm8K4l4+s30msQkDPu0NeF -GpKStsRbhpdQYKFptLpwvngfRM5N1Gbs9AS7ezVfwvJFLw2dc+pTfz2HBxVPMepiaN4CruBkgkQt -WL9BWsOO+cETuu+A3Kmitb3qVPzAWqNATvlKmS7JDF6VotqRExlIdgwrchtQsvyEvdTMojH1NwJA -ouQJz5cmjj5L43CHWnma2OqZKCqEYEdqZrPpgDKUofsIeIWbHM0Tfbs2g1OBr//oUsVmOSJWi/5U -zWccBsrxSQ66pajiNhWvzujSjMgQp4jYSCU/80IhwK9EySeT0ZUPPjKGH1JK+0gJLdFlkzmjg/KA -Av9K0XrgPRR0IY5+O6UqpKEioFP2tTQNFVwkQYWMNdHonYNf+V4bLy7Jj5Jw3AGeBsrQTv5BzhuD -CRhzcqPwkXLvKSv/TILpWfHeM9N83S7pSkTzLQZM1d2So3xG3x/erKsM/SfoNyrwGr2gd6uoC8Bo -Dbe3Gd4cpmR4JiRB6E4djmLe5FdyrE7kyF01BkCRm5cujkkFgPlmOetlgl7Moc8kQxeSy5+DPhTJ -5jDlGoLfKasS5igiD2eOZmGB3I6e7QUDpCzF0cWLsTAZlzHIrKaOXBifrlYfoE6L9nSW86nE3zFK -AReNVI8sKQ08ZRIfvcLG4gow0AI1JrxTRt8RIt62r/qXfG+GEGovWfeh0ATZoCxN9DXdh0F7zPOP -O4sMFf5CbgX0oaUkC1x7W2R1+RIqydpeDebEl9G1i+SIt//K1qhObZkj8pgQCvGiD7ZKE8eVSNEC -dhv9MqQb4gUFm7ra5hDHi21yrbm/SwHLRp8PGoIYLKLprKIliTdseDWc04062Y6pzbWITgk6uf6g -oh8JHWoAAQ0HzS3sOLUeI4V6c6LYS0qBJirle6PU5kNU6OhS9EWhkU+DPDVZcKoVzyRFRz3sjTKM -I0PV3HP0/hHvRuUOlEgegWwSVNFS6FnZQiGoNQiYu8JSm2BHG6pzgh8Y686WrCxRRVNQWAy6tGDS -aFfiE1hyb0vMLUBmLl/vsXDBa2nAPzJbFPV2EfhnI90i6PoMfZSGVok5KTWhJ/cbjW6FjNEk+fUy -6kwYRqcSLselubq1QjoXJHwkOdzJxZDwDgOjYE8akJjGN785/oopdhWaEcTGMGsqWsFU0ZfolG7E -8YpBooi1Ze4RNTgOUTbrDugjT0lwU/oDZZ+1OgeaUnx/R043GtMw6aA6VPBaJUbw5Oz5rNQTkVGU -D0IosSn+jgavsvJfoOqVaN5PunTGiC5U6BHd6MDS7DFTVPYoo9+RfcRDYPEohQEgGbEHDC9AZOMp -9WsQjHRaNVmuqBchOUqb45gSHUjCAsVFyIRvBukL1I3wd+QXmu/6kHMB0aIPBT1B+CsFwrj8wxQw -qki1UXgbChOjS3QcFzPnwO+UiZg4hsaiP5yrkEiPkhxTfkAkvYIJATVGkHb4RUbuSckaNaqHKucN -owrloBSiXwV9JoK8/DKjDOzo+EBJTx2xK5aMAqYkdEukpOuhaYpSTRjWIdHoxV0QhgU3xggkJByK -lL2+BfNEQTelDbr2wxDPjBOSOC82ulcVERKGFuAta2qjwKigIiiGMAVyRyjrYhFWRLaRBfBjJ4k5 -BQQdA9JZ52gyCWlkBTla0LOQUwQBWjmUG4Ai3SQxLxd2kJOzwA0UGJ7IznC1GJ2nMPcWXvRTcsiM -bu/o2ooNmcosQ78oJ0oW7PbXqPfTrzmxAc2xH6gdJmwBasVmEfmj6Jizx1fYHJCOk6N5T+o6rYeM -anIFkeanbY4xaet5o8FjrKptb/6RZVEuCmljxIxwjhKb0wpDgch+QkeccQFFVqvHuzv0WXv3KNoI -eL+R2drlkiID8ZvCZgimBCHQBGWQyNktqDHmCjU/bd04UrKK47xieGLx6g5v8akqFt4g4HVtaqma -3JaG3E9ey6TYaRQ9wjn2C66nNnZps0yZnFZApcpRp/JM7fgSurnQ2agoR37eKEgOI8uCs1FwhoCK -aQKPo12jasGgFeAdlKIGpM/Uu6erq5zzpbgDdnxJ2gAmgGTIB/H3nJlIhpwYOAKVtcHEW4ZkMnmZ -SJlJgbs0KGk4zFXQlU/aqA+Bwz73wbscyOrMEloKZ3vKOK8XUrxM2NtEebtQ5mfWM0vBBCSUEmPt -MzITkCsRFSGbQXagsspPGo3Et7isvJLBgJcjxwRy99a2ApKgZMMUh1UE00nReWfYPY03l4AABFFw -DCVZR20osRda9YGWOQHCWUHdLlY6abT7xyf9095eY3jYOikbx/29MgiE/iJh4Si6MbstviagUB4B -dJKAtYwHWaGi8reID0++SHy4wV8//NDvbQ46vRHMd26OwRQ1Hv7h0g8n+Jec/7LZPYV/N3Y/ARYv -zSzu9XfLxtLgdHjYeNbqtQ7KQWNjsFcOZqf/rcF/XG51u52DQevksNO2LXdgafONLdzpuYZpbI6a -82ONZxtzl2YmfyHO+KTWeoO6+KXVhU/Uxb7gxkH31MWUL9a6rfZhfRFAoxs92LRDakpduPZP+4fd -EtqvlYCjye1d051B2esBkXXKeerCNt7jNmHLF+Xg186wfThyHVqCCTvlLrj9ow7sErTvlUBKPei9 -yXsPxBLt/BemoLGZz+KLBTt1+DlpoBcT39pk+I9S/BMoII20sfj4w9IAzlK3pO/XO7tw/j8sb0NL -82ELmAYtcO/DWqv3n1rPGUQBK4moAn4H2Q3/QcZS0P/b/0yb6B8adyMgdhoW1DgQVA30MqMu2bBv -P77WwPF6Ba11LkVjg0YWX2HkiCXQ0LxSyiWTMKIF//drjV1fdTXwBfb5SxLkJD4C84p4DvwuG/ON -571e67jca6TzDfcfxNF8g7ZJNKpHQsL/BD//QRSeMbGKw9Vmxa3gg4ao5oc8IafrY4z9dAXPQN0F -5dByyDMmj45r/P+p0/+SGzGB/7o1x7zXrvuH8lf3TUPHvwox/jvvVoK7ReuetmVflvgK/gvs5/bo -9245vDT/tNf/tUe/gDI2s7hb9vb2/v1/jo/LwWnvYLYx/wNsKOgq84ugs/1Suobzy1bTW+t0YVn4 -6fJhq9NrcAOGzvIibZNboEPPv+gMO6ADYYfjPWyPWu2jz+hhqTXstMPPB/2j8uLfS/pDd2NgP4Qe -Q80Ult4fbZXtPmhDe/hHbmYx0DjzFR6msbLP8BJ8hkdN9qDvcr9xp3GpMRP0g1rgnQYN27hzqTG/ -2RqMJqxsud/bO+2MLrKosJc/v0e1eSC6PuPradj0+vHzYbn6S9nb2NtDZNCOVCjeOPux4ziaBaJ5 -adc/dV06hC2ecSwJBMBepwStXcOk8clCQTeuLNnQiVrYkdy/S/jScmkp2Dg/twtuG/OSnUGrN9zv -D45nqeszkDXjmzVbnRM4d3zUoXFpT2DVAjgQcJ7G/E5ndCFEoy3UmDnu/1I++rA5ItMDBZyFD8ES -Kl98AJurDeiZ9MdH4R/9nwb9UWtUftgCtMK87J/ILOqdHi/3TzqlA/JGw+z3+sedf5Wz9Vm9cLOq -2pb7XWBxr2cZTdFE11rtUX+Af9F2vBNg4v2Onx53Qa3XOz2ehagtqOqkvp6V8mBQ1qfO03kVAUdu -OyxnH36jim9U8SepYu30X//6Hezw4dF0sqB2q/voJDmTMF6WnfZh2Xja6o3Ki5KF9OvGDTy96OSX -uqA1fCHBME+dVSIAn39dTAJUjLrq4owFfJOS50pJ0LXnrJzEPK74kB03A69tvsnJbxzxm5z8RhX/ -w3LybyYm1UX5/zcZ+bkyUmDCDry3FGeJSsOSMuECJjLPJJuUBYjMb6LyG1P8+kzx6zEWrBilzTfe -8h/Xv0Vu9W+ubqCMIAcxljZQ37jKN67yz+cq2Teu8p/nKjrSVRQWYUeuojBi7Utzla/rFcI3VF+R -fv4Ou19dBgoswfi55HDxnfuLNvFr2CyX5ld/K9unOAf6A307fst5gNebe+WobKy2j8peQ1IIwFe8 -7ZwocbcoVm65P+iVg+F0qUtNz5S4vIjWLi3qwhLXW7GDqVbsP/6idutP1z9dvPjhw2Z/zXXvN5b1 -38yyev3ePyoa48sQ8V/BKpIkNWT25gVxh6LQyCaShB4fYxomiRXA8UmAyKipVALfqJ/FJiRSyJ/n -El9JCFlMbezvD8vRdCHETbnlmbLoab83Oh38UmIa6065e9o7uN1YB2KdQz/s0SgQTp+5WTOPe8MO -BpTjrnV6B42Zlc7wpNv6nX+dPWM/Y550Fidv5p+/ST4HeIgXN8m/gYqcfrOwvryFNcmsynmmc0UV -uvdPMKMwC8XXtcL/a3ZYJDzVr7LD/1y0aPkF0fK/qNI1RGH+F9S6/3ndZeN09EeUF7BbC4EXipgW -AJ8+E63iOzdFz2t0ojA/L74ZlspMVkEFUDQXdmmYz1Nwkr+1guM4zjf15msLv/SbfvONSD7jlqGY -DYyuilrwbeDfnV7M1w2k+Udu96Q9luKbQjyJ9L/pw39YH344aO13joajTvcfpRF/e3b2N+NxfxRB -f+I8sjWyMuifNLYPW3v9X6fbItiQ251piWy3D7utAzBDMGbjwjd63iN5Oiw3t9eXuqeDyFF50uoM -JrO/ixbM/FOXOsN2l0NZmlQQzt4+7lazpCiZ3W5vj1slvk3/pNWuRfzstQZHYTBPezigJnNx72DM -jSaAD/uDf1GHqcvlQVvIscKN1d9OWsBgl8r9/qDE9AzDDl6//A1MngS1lP88bwYUlYPWEfDixqvG -8KRTHpTdf9Z91Dc2/Y1N/6/FOYr/cJyj+spxjuIfFuf4FyrT663T3Q8Pt7+x6G8s+s+YxURHs43Q -l8P/eZuQc22OPWxz0mDG2RwTVCqjsY6Tke8aJ18xAOhvgBwsBSqwqCsWNcwTScEhIsfqa7RuLO+Y -kub8LRTqC7K2TeA7YDudDr7xt384f/s7u8/FRTmkJ8eJbDJwAv5n+eHXfL3xT/WRXw== - - - dE8xQbmtyQL/g6VUMfxPYKEsun0VWYrPV9ylK2YdT2VmvrH5L8nmxzxg3xj9P5bR/9W62jeX8B9x -Cf+5NxNfwlGszvcRA9ut+YjNmItY1FzEsu4clv98t/BfwqMpd0ajtXtQ7mPG3FGVRuMbs/5HMeup -S/iPPEb4K+z3L3PnbwRllJQS4+0FVTFIFCYAyczXSgDyV+e/EZ7FTk2A8x+IAOWYRMyuezgdFdwQ -252Jic391l7DB39+1jtKkhWfeqMT+9zf+tK7nWMEZF5E7Q9Hf2N/1hc6D9qGC2o6CvLLhsL8r4nY -tyDeeqDI7L0LpOp25/ik66XqGU8v/8PPXlmF24QzPQpWQzmnV3t7Vcbpc9NWb7a6JWi4tKTN3YuH -Jsy83WwNh+XgGLS+svdulrDx6l+X6vBLm+1xdCEb/Pf/y3/704ovquq/tkCT5O6SRGAtMCxmlaqi -oGmnWZayV6EgP0NewE8EkQqLD8EPmTY8teVnr582tvoj1502qaGcPM08z7FWIXubC3yWSNEB7puH -ZXfXfpSrPMXYRizmRLlksO6UomeMmOMxp7diTVyAoheNlKe96mjw7/+v55BjsBhoghXtsGiaxSQW -e+Z525X4b5d/b7lPYf38YLJITUavK5OcssAnMKuEcFhIYXBjhXtkifVKZdDdUrd16vGas/e9yISm -aSeKqivjfGgWRLxNDT2JYDXPWgfAp1q2F5EZoxmBiZa0t0lmaJtgBzhdfcL+HSw9Q4H31NU9YRrP -7uHd7Ot7RdJ4Cj/6ialUScJFkWS4nFxTXZ9wYk1J6e91ltjV3YMjhR29vpcb6C1xhIqVnzEBGRZF -FFhrnsgGCy3i0bDEp5j4IirkHnOaX9Ajfp0XFOyaCKOZHhURB7JrEb1xNdA9Ngk6NNghLjuco9J5 -wu9lDdU9S5jKsXgxJg3Ned7hvGDD4pViGVus2MSEnbrdKxxNFs7tlsBmaGO7wj3wO+B6gkOVFPxo -VyeSUZfmuKfYd4YoousZ6setTSa2q2htWMfYnraiwJ/tQdGKEIUVqlIcSQDhpoWb1OSugFlpm0cV -OpKK5yMlnfQEy0jTdREWY4P+bV+ZmdhXDt8bYkRpgdUUafuUxiLfNEG3gZIfKXNfOVOs78xTLHC3 -JMGhlURfJlFAgVWLsV6uoFOBBynYwoL20NOWqtYIFMA7l8HuG+IQTeHwldq16vrqspASslxZfiAS -TQdPwyHOeEONtqlo+dU1Po2hRzB2iYlbog57BBI0gk8ckISyTEu6F9ypJYVmbt/R8OSoL6TTCPM6 -I+6ggUITPjUit0v2wkBrnB0iIaQLwj+dn6pD4jxKwvroHjHThKcES4vTfN0hEniGZI0j0j4+wy14 -fc/EXeZUABE5Q5JweXGZ8+EWWHuDfsDJEvdgSNgl8zWJnUrjWb/OmV/AhHOWVhJLshLLK4hVstMc -8VqfalaxyxCdIE61lY6moKdOOF/Dp1eSTPSnTTguZ0IuZ5KJHcssEwUxDtAV6AcJ8iKhw5rBUaRn -U4BSQfN3LErxgeaeVTVlZSIOn2dY85REWkHiEIiC5ywySVSZ+64dKaiQFEQ1aROzUiy+x1tfUM1K -YsuGMOSZvCezSk7f84QgIyyIlHI3N6VSrMsAMhRVPwDEJyznXDWWzL0rS2WlfSARPMN/4EiFPSun -lqki41wJWuU0gsgzSYDULd04itDh+dI4ZW0cChyZaan9oU/o0IKEpmOqNH5fdYO1mHG/6E8BKoyX -U2kSEjBQmiZKAAwn1HGWQ9cNpkLaR+XEjZeoWLtWusNhEBcp9YwEoSuJY/cKWFJiGIWFIJYCB1kY -RrNVbuBgpgkdlpBzEZq179pvoHYEZljnzZTkoyZSqmmJzI0UHKqmagVtoIQhl7bYyCIiVqC/0TYZ -OA68/1mhCR0gPID/058s+1HuB8kYDw4IoSOPEA3oNSzTJBAFKbipUTltagKymESAU3kr3YWpJCCO -1DjRrnzXmVH8IhJYFjFE0CYTwquRpqBt1SxAkehRDuImsGwNus4qMWi8rE+pLife7FEpapRESUpi -MBO4FJKHVlR4NpoE+iWTXuYxknHXbCqTjXGr4ayDwAaZZB7Xm1f9bAxavYPyFuv35/USNa76eFEO -drtoF/X2wDR+1Dk+Lrvn9jXxI9unM6n4Udh2eTho/MqupSEvoGy86Bz00LQ7d5zpX+OAZwcm1W7c -uSF98lk39UFrFqn4PuV0ODrdR8eQaGwefAlDkbm2k1xAjU5dSRVplaAPC/oGjTYaKKHqtQFlBz9w -FpqxbgsvaguVUQobODcpPRvWaEES5xC5ogGU49TVD6wljPeb+36VMMx/FNWdRRbO7ENmoL7TYXfS -ULvp2h/Gu838aaRKy6idsJXo/legYklM21q8Yz+Md5p6pgcalmKuA/yYxTLilPYlSVLip06oBD8E -WnTYr2cdqDiTySHBEmKxkaXMerTJidt7M6j+w3i3Xq5IZqMgUQ1rLiAMWD9MsagsFbtlvlz/YbxT -r6OLDPRxsrJBs5esCarERJYlyEXmpfUfxvuVlVZEZcDRkgOFmH4ARlpYO87tUf2HSefAuzpEwRqQ -Etb2Uyl0yR2y7Tz+w4Rdcmd4ezRoHaJnrmystQa7wVH+006ae5VCF1tqoM6yagBtqWIxz95Zs8pZ -kGDp4B5UnWVmvC/Qt8kwStApVnA/CeXCpIBDa9pGM6qb/yDaMqtNC5ULthxBlAlGq3HKoAx1wLrl -WBkacCZ5+pnJeM9B9DNReRvUZSkwoZJmTZe62p7CGknbRWVdMLLQhKEu8XPiqt5yC4kyndDh5pL3 -ODrH4udUyFvvDEeRE3bsHv7sdJJnPZs5+950vABfFcldC3yM3kuOuVlp2n+kQKBI+E9cgxi7+X9g -4EH/X+VwOChxqu0jKvt6NOrjRcnx6ehfOMnyFP8GOBg2WOFgRPU6oxH7XRs/zRRzVHr5p1n42xLg -E9cACgVgaLZe/JgHPnsBdpdW+u3T47I3WmmNWlhc2v3euE2/Bf79oFL1ZjkYnpR0L43lnz9sdQ4O -R5vdVq+ku4FOtzP6fRufHqDj/uoHsOtEA4Rjypc49LCBakrX+1kv97mb5bLbXf1tZN9n4MBzxr3g -OPvzrbLbwl/Wuv3+4EWr1xkewnL9O4qrH/IUuHouCnFuT7igiT3gx7nWF+uB1rLUH436x/U+suJz -+tjpnwQdnLUNtO6zt+GszzzWJ3514dn6buLJXhhj/vsxhGWZuNi2YReTdu1Muj3tloONQQfOwWM4 -eL9tl+0+R8Vc/Ju1zmDolgq2+oXovNrYOqFnJiwGD6wNGSQYCoPWEPjg3C8lvvFp7Lbg23ZVSt3X -SQ8/aXc7J1ROvlv+1hjAWe/3htO/OBmUw3LwS9no/1IOTpBb2A9mSH4kiRUk8H9BFp7tFw8/vOiU -vy71f3v1mtur6rUVMJrGxuno5HTU2KI1dP5FpeAbW+Ww36WbOzspE33zrBwenvPFzNtnwBthN8rG -4ul+99//dwjsHu/4nvc67f5e6SYIqxQNz9e2y9HpCRYtt4vexEWPGngvOI4dwX9GnOyUv40aq3ud -UYuPiG2cqlz5LXODLLd6v7SG2/ZhWEVLy4P+yeKgbHFID/9tfnEwaP0+kd36mTweglTa7YP0XCn3 -W6fdUTRbRtrm4paFgEU/J2QeECEwxp3+FhMtE2R/2MGR6K+Sv0vRQ33Rb4SleDRrJp3M4JTEKLA5 -m57ZuDLYxTdkpPZgA/8PGqtifAOr1d6x/844fBDy/AiMiaVuWe4xK+ieOiy/erb+A3Q5Ec/Q32/H -3R78eQ6oYtDZPR2Vw/Hd+cpdfIH+g1btw053j95UYhsXX+H+iv+Mfj+xyJm51ht++KU1GN4JjnXY -9JcKkQQfntGuF2yUa3jrH4+d3U5vD5YqLoAcQCLwF6bX8zEUtv5CRPpH19jr986acri+bh+U872L -rM21/ELb/ydXL6au/kL72mntdsuLEP65O/lPOui3f7nwUcemfzEV4/Lap0PQHv9aTvb16PD2sIUK -HVprwDouSo5f/VxsY+7Iv81U/htO6XD/17+xNP6Lj8EQnSD/8F3GS0mvsp610N8vsrW//9V8N9dF -ft5KfrsQkf7VK5lTMkvT89bya2cPY6XPX49t+E9Y02GJPomLLMq1/GtX5W3jsxa0S14kNAOdGXr+ -0sa/+RvIfGJ22/3TQbtcwvD/v1zog2j6q6dwXI5ae6AD/dl5FH9yHlf2rMfnItQVNEaIlpWrb8w/ -td7qHZy2DsrGZv/k9MQSW+18KX+oT1p71tRydRLbfX7+R24RPiitbsdSLbnnqNneSadZa3ZMT2Ki -IzY86Y/8KhYfNxZPR33vmytr39Nq3EobRz2wA/uno8bBoO/WcUbTzrDfbY3Kxi5eLtqE3lHjonHS -OikHjWHn+LTbqvyAFR6LBmUwOmnBBrd/hzE7e9DaTdFIjV4x37ZlfUgNQFZ/0JAw8mkZ0+d4swt2 -hQmXehdsO3B2cmqMMme3FMEEz20aTODctn4COmhlPaRI0j+ettDj2Vgvf8EIlxhBQn7YBvpYxr6e -9ffKeE/qnu+dSv9JMZTy7BuJhwEG8yIXU5ouRWiZ0nDLLbTyNU9qtnHSansPr8mntg2d9yhos2l3 -M378c1tWS9Imz87CEzUNEHXmTKmhy/F/zqrociK+fJs8+vNeZ+So4dyrmW1MUmZbm7OGxtuQZZBx -Dp9nzhEbVu51efZ2PuoDi+r3HgU6y9mNGUvh5l/48govzJxv3O7b2RjG2xI4Vyud4ai6xtFnYgXJ -bK3VLhd7B91zG9Pu1VqfvWJqHa04ZBYTW3sannoZXNHvNFKnpgH9zgjdTJrh5dLR4uO1027XSUf7 -YBz+OlkkxvrYdtl91BrBtxRE8HhlGNwWnNFyvQ90ih7ZsG3YYAd9DDjquCwLr9JA5HU7vbIxKn/z -Z2R6yyE9Th6Od5sGjVu9UacBzLhl23kyeNjt77a6W+XJaXfoBWN8I7LTPwkuROK/2fvuM/7KF7LB -n+/gn/1Dv9o21N8BVsJi+8XDtX5vhDiupLeq/rg5KNudavpVcTD408H+bL2v1eNdmDmpITWswR93 -yuOTrr/E1tVXFNwRiKGgv167Xyke4eio8DwtB7WrLLucqq9w/FeturZDk/pttNHjl7a1nlZ2VgLl -zN2Dgn60urndwJyPjcXBqIG5E+gicurtqf3qoXtOil/xfWrtq3gs2QDd7nOHoo/OH8lt2zpsl7u7 -e7wydjK2W7+Uz067o85Jt3TNhovA60tMVunVlM/VnGfePiv3OqfHwbTeBUzGLWPKpXB8IFEHxpO4 -iCdxnGJcf8t4A79sb+C3zrqBp6Yblgtsn8UFolY7nquE8QLUhM/D/AsOFFiqBQpETTfqt/znYonp -6sJowsbn4Il7vBiiuO25mIqbTUYVt7kYrmx/NWSJ0C7p9auQiUanR1EXeFvu7vUuxg== - - - Hucr78L8p/5uk7prdbtOXx/WrdKxD4DsOXLsQv1ObzU86pzsAkaOYlZVbzYo8bl/iasZTG8J1kbF -kGfGb/nr7d2ygxXd8UEZiOgn/d3Hvf1+o5LG5yD6rJntdkbHLQx+iViWt67D5icHx0dNDMcb9vf3 -m+wvqonMsea7KEPrzSehPW59OiyBtZEA9gc0fMk+BX2I6pIwEQWKjA04HHWbexxRQkThtvCc3cHP -bPvKwXGRb072juHP3d6FJ3Wyd+HO+d7PfzFpr09OBrbZlD2ANnZ0q2IVZuLOQrvAvZtreVar0F8q -8rNadY7h8Da7oOpfrOWob2WbybLpLQfV8PjGcmrbkEJnVk4H7cPGzqDs7AKvB6R0eh3K9jRtK7Cv -XigPxodr13B3dqMQdVO66geG7ZTOas3Gtp5akQNpOiPbK4edg94EB9RERrtrfbbTeqSG/f1Ot7xA -u9aQ+dWFGWm0HeOr7g6ax95vc1aLdr+Hmb7QEzhlLdjSC0IQHm0rPDI5sfVgrwnmOSYSGIU2y8SG -+6AiuNRiZPdMbHViY9Sm0QKNetCcSny2jY1oCJLH11tR7nXX17ntfpm+xmH7pNv+/WzexG3aveE0 -IoY2o0439J+esT7Yp27r5Hw82HZT5k5yq+yhZTyN+2KroUvnci7lUnPm1tXBuMg3QPIjzDlkZ4Jl -G8+SzMDTUEmMedEUsXxOm/agf3JOE1TROqC0nNMMw3WHwIqmnwseFB2vu63BcMo+xopFJWQu0HgU -LOm8toGkuYDyVE3jAo39NC7QNpjGpFO03xs197rTWR63ORns93vT+B02G57uuvOoJu3REBbq3eYT -d3EICiiqrFOb9MqDVhU0fEYjtDtAjR1OpxpsB+KmN72vrsDeWt4VO3n/h83hYQtU/3IKlrBROUIn -Vg/m5jdmYm9xq2wSn//tpBnFv1e3WbVWg7pGT56LSS0P6i3PaGelYOTUm9KOXXrTW3Y7U1gGNECP -Xysyn89o2D9pT+Eq1GA4Zcepwd6p9wRcKLK+1sF0RQMsueH5UoJa7Z/22lPohNtYL4SjlXNEA33T -6vXcTeZkK5panWcitI8DhWlmuQ9G7V5jbePh1qLMGj/NPN7eaAiZ6mxO3pZJon+aPcdYOwa9LlD+ -JmmT0AbNLX+5cMa03AOO9vHvU8z2oGF/dOhsvti2XnTNA+s69MWYyu222fmt7G6WA0yRGA9ad/fz -swd693Ihe33m7eNu95TkYH8w9yds38ksApe7ubL24Vm/128fDvrHYG//2uMAy7P8l5O+d09Egsni -OxGY2XLrhF+LdMppHO2z3vpM+lriNPiVDV519EabTFCIgc1+t9OuP5GYtIzA1X827bjGO4POMb7h -cIdvBVbYAB6119jroG+OtwoIFpYCaOkdlL3Gbtk5bmyf0HOPQa8Bf4TFhju8AicJrzeGDRhibqV/ -RATWxDTBv+LDyF5jGzqHAYZlYzXs+lYD/u7/2uB+hif//j+Ys7DTCwdp9PcYhsMfN9Zbv/cBb4P+ -waB1fNz4Fb7+V7Ox1Gw87q2QmQezxlfOHcBq4/jf/5eeaHIf+F+Y1cG//7/uqHNQulnChIb/+rVs -H1EDnsppb7c8AuaDQQ6j5nT6ddsJw7cHnZNzSZAaO4Sfv3FrQFwvy128gpzaWPIut05OAm59DvGs -T1cso7Zb51j2UeMdp/2dgQNNCLPOsvNxsFUeBLQ7reUmqOuPvbo+jQl4//D5TRlXnWmeVdeUoiqW -vHI/rddlq/stDqZYh25jnw9L4vM7gaWYTun84aD1+3KlXD7tuFeSUp7B0PxnFU99jC6mxd2+U2In -q2OTv/0cfhx/GczVTFsioRrwUVZxBdPRDc0DlNgYmfNRWf/worik7/4oMmsffw42a59W0526BSjI -gPXtHJ4e7/Zana6LPDuHgMHECV9XnqPTxZIPc3WT8+cxSIpRZ7/jNJtpjNMKyse9NkgICuCopOUF -R99iW/33H6Zqv+dc5J8xxwu/5z1zgZbc0B75g+urYff8IUFygfJj42Gq4c4wIS/+QPgCKthie9Df -bY1AqpeDi9kF52hPF96diWEcwVV5fckXed98YUXBoftcEmQVgCe9E4RKTv1oelzLH7EVazPBx/p0 -kxxgfKrGvn26C60x+GIL1z3FM+TFM/n+10DJB/n4a39wFCq6n/flzlRX1JmfbZ3jHDvzw0CrOsOX -WVfKX8bXJlNODd3h4SdVEIueKkjQVpoihD7TyqqEib7Ql5PF5lSxjsrAxaV6Tc+JhDoYyNCECXXZ -G5nWkF3qOCN2cXv58ePcrJRI/PhH/WDj2tubd1/eu36n9erWE3VtY25pYfDw+PD2Qe/yk7XLt2au -L3dazeH36fNHq+mV2wvPH95/ph/cXv/p+rOFwWk7W1uVz/KrQusrSTJc+bRycCv5fuHO++aNhbu3 -ToYLw6dy/tLVhTvrlweu0ZPR0sGjH9cX7upye7lz7357pdm8fjA21PreaxgvW1m7ejt783C08und -kn4zd2vxuL8+XHy8PTq8eT+9crq2or9/ufSpe/3lpasr+8mT3YmdfZ8V+9mLH9/+tLiz3Hxx9qBh -u9vvFu4erb1buD1sHt9cuXX1dG3m4d7+pauErLWPwJNX9t+9zJa6C91Xt/eXDkfLh9kbEaHj47WV -tlj/eeHug+svuR+Y8nD5/cH7Pvx07eeVx3uPLy/N5Z++X9yeu9LjObxq7Z1eulp8mrnZXm2bH2eW -D/WHO3cXr6prN5d+uPXx5sLy9edry+Xpjfsvnlw5vNNut47wp87N1f31Qx5ZJPOtbND5/uPtzvsn -e0vdqw+uzw1u/nS6uL597Wec/+zCnSeH6tLV9M6LdwuLvfb145v3nt2Zz45/utfJsvnhvloctB+L -m0e3he+xvfJk+ALQll0vs5cq2bvdWZ5vwf6KZ/dm5m6VS91s85hX8Hr96sLy47tXXq7eKswQ9uXx -2/TK/Wy5//7m3Rd7b2/L3SvvqNv7vauwoPvpjSu4JW/Tl+mPPcTT/aWj2XTOkuaLvfVEvLvybGW+ -dffa2uWbbwY4Sop/eE+9UJNLV5Pd7x5r+vnm/bW79qe7L1efcvPlW6sfuTP5Wj4G0n2V3Lx/f/WW -XHlwcM/28/Le3Tt7n354TzvpJwz9bSwZOwo0WnriJ/CumoCYubeFjUpNMHN5aeUDoRok4AOdvkk/ -tRd3Vj7dXNmff/rzaqt1/fuldPf5j8Xm1VfPFzeWlzZX9rc7Py/8/O72waWrS/r1zgdG5pt0783q -B3HzxZJ+tbixtvLp5Yflzqd0/s7+8dWDteX9GwIQeP9jlm3t9avx8u2fj58ubqzfeLq2Mrv3lHHj -EM20D7s/Omn+ePPBi9bPvKD7ad5auLMz+m5x58nodHxpNcwGeHAb8Wpw2XW1DSdnY3l06erqm72r -B/Lj3QcrydpPC4pI4O7Hu2srQB03bt1c6hfv63sVYzbcWLcRTDkPDoenhCVYS4inJ+sPFz88EUQx -d2+e3Pm4NrP/tLmY3N15I2e/f3+XJxKjIz3dKsq1mWsns8uH6dbR6s315lpFqXAAXveRw2yv7iGF -LsChOr4GS7syu3xwuDrM7rSfby1mb+TL+h5sPuq+iPr+7uHq3K3dYtKWFEfl0+VLVxd3nu3dBA5z -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 -KQqkvAgIKQqkvAgIqQukuQgIKQqkvAgIKQqkvAgIqQukuQgIKQqkvAgIKQqkvAgIKQqkvAgIqQuk -uQgIKQqkvAgIKQqkvAgIKQqkvAgIqQukuQgIKQqkvAgIKQqkvAgIKQqkvAgIKQqkvAgIKQqkvAgI -qQukvAgIKQqkuQgIKQqkvAgIKQqkvAgIKQqkvAgIKQqkvAgIKQqkvAgIKQqkvAgIKQqkvAgIqQuk -vAgIKQqkvAgIKQqkvAgIKQqkvAgIqQjksEJASFEg5UVASFEg5UVASFEg5UVASFEg5UVASFEg5UVA -SFGga3AQEFIUSHkREFIUSHkREFIR/6VC6OYgICDwBVJeBIQUBVJeBIQUBVJeBIQUxX8jIHSLEBAQ -eAEpLwJCKiJSc5HyIiCkBJDyIiCkKBiVF+kvAsKRD6S8CAipCDbNRcqLgHCEAykvAkKKgkN5kf4i -IBzJQMqLgJCK4NZcpLwICEcsoiov0l8EhCMTSHkREFIRfDQXKS8CwhEInsqL9BcB4UgDUl4EhFQE -f81F+ouAcEQBKS8CQioiVs1FyouAcIQgDuVF+ouAcCQAKS8CQioiPs1F+ouAIDiQ8iIgpCIS0Vyk -vwgIAgIpLwJCKiJxzUX6i4AgCJKlvEh/ERB+SiRRc5HyIiD8lEiu8iL9RUD4aZB0zUX6i4DwE+Aw -aS5SXgSEw43Dp7xIfxEQDh8Oq+Yi/UVAOEz4CTQX6S8CQtLxk2kuUl4EhOTip1RepL8ICMnCT6y5 -SH8REJICQTQX6S8CQoIQUHOR/iIgJAKhdRdCaA4QEFIPQmttGEIzgYCQShBaX+kQmg8EhNSA0JrK -DKFZQUA40iG0jnJBaG4QEI5cCK2d0SE0QwgIRyKE1ku+EJonBIQjC0JrZGwQmi0EhCMFQutiPBCa -MwQE4SG0FsYPoZlDQBASQutfEiA0hQgIPzWE1rlkQmguERB+OgitbcmH0IwiIPwUEFrPDiOEphYB -4XBBaN36KSA0xwgIyYfQWvWTQmiyERCSBqGVSRgIzToCQkIQWoGEh9A9gIAQM4RWmiMLQvcGAgJf -CK0rRyiE7hYEBC4IrR+pAaF7CQGBAqEVIvUgdI8hICC1TRRCdyDCMQehRf4ohNBdinCUQ2gBP1Yg -dD8jHD0QWpaPdQjd/wipB6FlFiEKhBYQhCMLQssjAsJRBaSwCAhHE5CeIiAgICAgIBwhSO6yAuEo -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 -w723XXfpOVMDbdX2PE32KUsXgGEPThoJsvf/CAWeecLcBUtXZsr1BY4qb8/IqedcdOWN6+558NF/ -PPviq1ve2vb+9p0ffbL708/27N27d5/A+Pzz/fsPHGRQCPzV94C5bwB1Bz7fs/vjHe9t3fzSMxvX -/+XW6y4799Q1XfXuIoM8Y8ViTHETGvZI/OECOGf+wmWrJAp9gb2yqXvIf+baS6++8ba7H3josSee -eeGl115/c+s77773/gfbAXYIip07P/zo411MCgFfHTr05RcH9u/bs/uTnR9se2vzS8/+Y8MDd958 -zSXnnjrc3VRRmqvKgqKXFMUl8/cLYMKIAIGnAALzrBX17X2j079ee8mV1998+933/23D3//x1LPP -b3rp5VdeffXV1wTF5s2vb3njrbe3vcegEODlns8+3f3JRzve37Z1y6ubnnl8wwN33XrDFReeNQ3I -81gtGql4+aL5c2YlSXEpBM6YOVt04qJlqzJztOYSV3VT5+rRqTPP/e1lV1530y233/WX+x9c//CG -Rx997LHH/i4kNm58/Iknn37uhZdeff0NqkLAfz94/71339n6xuZXNj37xGPr77vzlg== - - - G668+LzTJwc7Gz22PF1OxoolJ82dDUQvSYob5i9IIJDAlRlStTHfWlbT1NE7ND59xtlrL7zs91df -94c//umWW29bt27d7ULiz3fcedc99z3wtw2PPf7kM1SFAC9eefmlTc8/8+TGR9bff/e6P153xUXn -nREY7mmtKy+16OSZq5YumHcCPuollT2MwP8JErhgyQpxllxjKrC6Kuu9HT0DIxNTv/zV2eed/9sL -L7r4kksuFRSX/e7y3191zfU3/Wndnffc/1egEI8EFQL88+gjGx5e/9f77rnjtj9ef+WlF5xzun+4 -t72h0lmUq8nJWLV0IaG3yWePTOCceSctXrYyHTBotBTZ3J7axpYOX9/AmpGxiUm/PyAkpqamp0/7 -5Zm/PmfthZdeftV1f7jp5rBCgH9uu/WWm2/6w7VXXX7x+eecMTU22NPWUOUqydMrpekrAXkiYK0c -DtGjEAhm4RPmzl+w+OSVYolMqTWaC0psrvLKmvrGZm9La2ubkGhv7+jo6u7tHxwZD5x25lnnrb2A -pBCXXHLxRRdesPbcs8481T862NvRXOdxluQZ1LLMU5YvWQDIm3lY9JZCIDYLQxGEDC5dfkq6RCpX -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 -3324ufz+b9/9P0DKJ1L8s8/+5Xe/ufhvBohm1g== - - - diff --git a/shell_integration/icons/SVG/Sync.svg b/shell_integration/icons/SVG/Sync.svg deleted file mode 100644 index fb696c4462d..00000000000 --- a/shell_integration/icons/SVG/Sync.svg +++ /dev/null @@ -1,3837 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eJzsvemS3ciVJvgEeIc7P8pMaitGweHuWDRjbXbXKnVrM6Vqm5q2NIoZymQXk0wjmVJLLzMPMy82 -5zubu+MiFiZD3SV1hFuQN3ABB+DLWb6z/c3/8asvXuy/evfb2xfxpt91f/M3x/e3Lz++e/+THR/d -/fTNm+8/fHyPQz/69Y93Ybzp6aT9T+cv9cR/un3/4fW7tz/hr/jLC67+0f7VN6+//fHuRz+mI795 -/fHNLR375X+9+fD7r39sd6HLTi8/0hc9XTrehLQL6ScRXRxfvv39yw8fXv/pFv3GOdKxw7vv3371 -+u3Xh3f/gw7muHsRYtiFJe9ezAt9/w+vf337YX3STRgyTuxvwpjp5MT/0wX4gi46vXv1/be3bz/+ -6v27V7cfPhzfvXn3/sNPdsc/vny7+/nLr+mbl7t/vX3z5t0fdoc3L1/9e0evnr+8vH5zS2/57cuP -uzDgnfc/DcOXh+9fv/nqF99/+9tbev9xHnA4fsld/uMH6ou6xWccnr786bd05Ivbjx/pYemGeOlf -//2hfgw6yO1H//YrGopbutv7f799+99+rN2+f/cdjtC1Y6I3HGZ6u2HXy7e/uf32uzc0tDIMw3yT -+RT6r/5Dz6WX4fNehBzwXUppl/qJPsa4yDllmG5///r2Dz/Z/eLd21sZi/37j1/IPKXU9/KvfPPr -79/cvv/Ht68/4hlxaJHB+Pm7r27f0Pl+/eXNSx4DbqH8Kyf85uX7r28/0ty+e/P9R15ps92BBvtn -L/94ixkLcoNffnf79jfv/omf8cUQp5txpPmegvwfd0PuFxqDTHeY+SZhGHZDyH5za709H3pEf3aj -iWbqVzR3v3z/+uvXb3+ijzl9+ffvX39V5nPWxu9xM1e/i/3KA9O7f/x4+1ZfgFbR8efVquhvfv4F -3e/89qvju28xAR+wEW7pxrRS3rz7Wr7zz/wNXf79d/Ls/PeXNFe/ev/6LfrsfsHfzF/+6s339NXf -v3/3/Xc/ffu7d92PZLf/0+0r2tI0nV/tfvnb/05/0Ab+xctX33x9+6fb16++eXv78fbD7kDLnI77 -uTcvX3/343u7pZd8f7uTL+lK/tP+f/jq0+3vaK+Vy+XoP/w/NL1v3ty+ffF/f//h5cc/ffvuq+/f -0Lfnt7+/ffPuu9vdy7df7f755fvvHr7Br968fPvy/Y6PW/91r7ufvv14+/7lv398/fvdty/plnTS -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 -xTNo/GK30X7WZMhon/I9j6rNbdM+7lvjtfby3txB9Uqr8zpol21icYYpO3bjlHz0rVG79nCc7+yM -hloBBgl+bttnC8DwUOr2RRgTg8BtjgZqzx5WbNgmTGqz0sJ+aTKUljpIMMTX85rtHBUX0x4TFhM1 -wALdSMOKSapKChtYN8BYJeXWj1+kkYZkA3TVTTlwLlqgnYa9p8Lj28wOg5DaWCnvtBvpvt7XMHHC -vqSoWSAraYcKhYK6L0Uvwl6lMlaulFu095Q3XFO3esG4WKojOIZpjFCrN4xnIUmwM4aJreLXNLAN -YwD4OZ3khrqV4N6Gklvz/M5MqVv8TVgo9+CES0CNq0rNwnFoAAVG+4p7F+AgyJAK96Pd/1iavZUB -uYM5eDWbieHKUJ8rHZDeDgW6lkUKr0hvcaW1bVgVPOxtpYRzxZieJ1UE7oJxmbO49eN+GgeV836B -cOR0Zfbu9Pd0nXstuPHqfqF68Mti1O/aRQg/y14OD1OKPNrQwhFV8TyoR+mr0pR/L9u1+7RCIQFS -66Nb6PJ9QYfQcKZ/TA3uCp1a+tHU8yaDfbrbjSwvSK/hkfNKm4Y4PHcK5GNAw79vqghaW48MUOKr -9p0uoQqRvXOKFeRjZNyBsuLKx8j4RCM2WD3LnXGMflzEOvq1UjLdM6X5bBFf6c8fcZjpLT1a08ci -Ajo5XBH3mUc14kN99COG1L9QxJrmLxkxqf7FI27VZ0XEt+bJE3GwPskiVtbnYcTU5vkasbec1xmf -y6mfcbxpgWS8L9dRhgRjpUXgcFyNEV3sa9YDkLmsM045rf6MZ6aSyJhn6pGMjU7aJkKoqZMyyppq -K6Oxk3LLqG2qwIzsppbMCPCgSjNOnPo2Y8mpkjPmPGnujE2nfs/4de4AGefOneLDGA/vO0rEzXPn -+a2HexVf7ztUxOBjD8tQ/bTTZUg/98MM+/c9M9ID894aWYS+B3uioe/SIZg28xDmnh95jW4XRP5j -th8iT9LtjMilfEIymSxdGqZN5G+6+RN5ntlMinxQPy5yRt3kytzSZJpFDqpbcJGn6lZe5LNmazDy -Xt1qzNxYWpaZQ5ssUE+1paGa2bhuzHrSbjJ5M7eXlnHm/9J27nnC0cTOdGKa4plyTGs9U5OTUZ8p -zLT9M8sZ3kEmQycfIpOm7mhkWjU9kcy+Tg5LZmnTr4lEbno+me+dHKTMC6cblZnjcLQywTx5Y5mH -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 -Im70YJ3cezu1ujGu2Rycbey611wa6AcyoGmiuGJM51xrcI297cMbJOviGtEGs9GeDXvj29MUkRzh -uZk/65BOtdnqRNz4/jq593RqdW1Ei9GfvVEwUYq8OEh99l6/o23M3dN37yi6DBDJgmm6UjI1bpID -rru51L1O/1gjt1OmwIcl6z40HaPQ99B+z1L3o4mgKFc4CiNVKfze3i1SYmlTeKCKYtNp/j4THkqm -aItREIhrTTRikVjn9LnvTAYAEyu9YpOserHW9faG3vc4DfPkQMg8TKdJWZcfP27+Tw825EZFElNe -k+Ey/YOE3knOqPMKIjz+wjxYNBjnyLcW24FE2gJnun4xDFFG0FSWous3NNOo6jYbQicSkcBni4qI -qmWw5GOxiIhY9HbLDo806WZDksWJaWNIjYjSETWsD6kRndzhsDEkLgoLDm0Mie1SfZMh+TY4IxMj -vDYoEjMU47VBbevvznvyzdpDBPHLP9xc/+n24vru4vrroyMhU6lY/8PBH77FX8ZB/vT5F7/9zcVl -a+bgl/3Hw/928Mu//X71h5sX5/jxVxdndxc316e3//qhP3xy+PN/Xl1etz8dtW7dXg== - - - fPX67vzVLw7/8+CXn97ens6eOPvm4vLF7fk1/u4Of/m767vpb/jX3b++Pcfffv7p77789Kuvz19e -np59c3f+P06v785/f3H3+dk3GPr1l+MvDn/51+uLs/bK5+2b119vNvPd6eVraefiRXty2yPQr/BE -6xye+c/3PcAjN/xs10H864cfXBvKvx75SP6580j++QFGMobdR/L9xYu7b3YejT79qEf0zfnF19/c -7Twke/w/d+j6Y5EqYde5eHX34lfn312cokM7z8jmS+99rT+/eX17dv7p5bffnO4sHXcf3MWHGNJX -l69vdx3L7fmr15e7s689vgv7vqHr4w90ffuhM/X25flvT1+/enVxev3ZvWOcev0DC/BYd9KL3UX+ -iw8h8x/CYo99u+y+KLtrFC8+hEpx8/Llq/O7+3fG0939f+QIn96+f07bJe6sDby+fXl6dv752enl -+e7awMZL731wr749P/vj63s47B3sn/c8rEsonTBzz24ub27/2/ffXNzdsyTrSt2/HrJ+8vT7V9mH -ncfTlvj15entr//57c31+fXuq7d88f2P8qGDPLm5fnV3+haDnF58SpZKM6rb/3adpH/vPCv//hAO -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 -fPH3I6/HpXzy9yNn8lI+VfdXpnMpHz+qAJFyyiJFSJQoNVj+Vr/0z4ZqzMTZUCrv9VN1NlTa9/rx -upaBs6Hk7vVLbp9Sqvf6Kd/ql9LZUBL3+ilHhaRn4snf6yctfIpnQ6Vwr19iSc7EvX7KO0O4yFWa -9/olyuTKUEBX8VY/jS6pgK7svX7KXRPFLlK+10+ASNytfincACJ5r59yh6R2qKZyr59kOqLSqebq -CaM6U1byLOgU7vVTPvmLWxNP814/xYGuDo1iGbjXTzksnNQJVAr3+ilPdSV3Qadwr59UmmRM9dK7 -jy8DOe9J3eunXAuM82fiXj/lBRWN6nrSSXwXZ6Smeq+fhKvJu9WPfzsDlca9fiI1pIS3+gnO7Eo6 -PYXi7vVLmPeakXv9lG/14yQ5zXv9lDdc8zJS07rXL5a/JSWLonOuUr7XT5mAyIPNwL1+CroWnbNl -6D6+EuVaVN/Hl+A6OdX38aV1rx9Xi6T6JJvNEXevn0LeQ8LdtuFk7/VT9uThiQeZuNdPbgzviV9J -TC1/S4U7z64kJlLDRPf6Ka83C3cRpH6vn5Da4tliqvfxJbdJQ/4+vgyoD3erX/r38alJYkp0ekP8 -Xo3kruITZ6Smd6+fILAdd6sfzhhK/14/LlVK6V7RtO/1U3ZzZGQs6Xv9lG/1S/vUJnKvX5qxPpX3 -+qnIe83AvX7Kt/olfR9fSlm4ImsZf69f6snwvFv9eK2kda9ffCiZf6uf3Clnyd7rp7y9SzSKpXyv -n/JGJ1FMKeV7/ST4wrvVT/lcOPX3+qUetxRSLPmcJ5l5ZRr3+sUMl9StfinvHhTd66coEbwbQNK7 -10/ZMcSz1/Tv9VNOiCUjctr3+gk7Kb7VL84fU32SlfBeP3knCPswCU6yUnmvnyofJu17/ficjF/A -UciuldFP6Xv9lAcHxSzOjCXE4nHfKjE89Cv5aK3d0js+xOdKTeMTFUVjJSiVD+0qb7wXpS0KpImV -sdjqTWXFMj8PHsemSFN6wQhpWYoRhrK2hp0cPVsEM9FgFOa+MdFpLRhWGrXGgZZCyhqac+GXYD6V -trC5ssFC6Re11omBZbstMt3or5wf8rR3l+jNC4bi2pVaqn6qpaZgZa3WqR0cm7SBT6PVOoe7rL6o -uXWlw7Ey1zeg0bkcFvuEu9kfsjRZIlpHMGAfre2Y666a7Gx29k53hVscKz368PbC3hqD3Tub39XR -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 -vv71888796tfOeuML34+sjA9MdTbEfRVeWxmff7Jxx21vshiZYKIbj36+FMKS2hXpa+5a2BsenH1 -9DPPOf/C71586Y8uv+InV1511VU/BQD+d+VPrrj8R5de/N0Lzz/nzNNXF6fHBrqafZUuuqTwlOOP -3kqQXQcF4yF6wqk6o620trGjf3x2ZceXv/qN71xy2ZU//+W1v7nu+htuvOnmm2/+AwDwv5tuvOH6 -635z7S9/fuVll3znG1/98o6V2fH+jsbaUptRd+oJPGQzj+gBB0LWH3+qzmQv9zb3jEwv7zjrvG99 -/7Krrtl53Y233HbHXffce9/9DzzwwIMIwIf777v3nrvuuO2WG6/bec1Vl33/W+edtWN5eqSn2Vtu -N+lOPR6KwYEHZBhZiCkwT1sAoqdARH0tofG5tTO+9q1LrvjFtdfd/Mc777n/oYcfefTxJ5586mkA -zwCA/3/qyScef/SRhx+6/547/3jzddf+4opLvvW1M9bmxkMtPojsKQDZLcB0ZRJXzPtDD9t23MmF -Rlu5r7VvYvELZ3/jost/vvP6W2+/54G/PPLYk888/+LLr/xj16uvvrqbAPi46x+vvPzi8888+dgj -f3ngnttvvX7nzy+/6Btnf2Fxoq/VV24zFp583LbDDsVSkBlEMUk1W485Kb+EKfO2AER3fOWbl1z5 -H9fdcsefH/rbY089++LLu3a//s8339qz520A7yCAn/bseevNf76+e9fLLz771GN/e+jPd9xy3X9c -eck3v7IDINviLWNK8k86ZqsmY4QFmP7bAQcdevhRx+cVU57a5hBE9MJLf7rzxtvufvBvjz/zwsu7 -Xvvnm3veefe99z/44EMEHwHAnz744P333n1nz5v/fG3Xyy888/jfHrz7tht3/vTSCyGyoeZaD1Wc -d/xRhx960AH/lgFcEfMP3gJIWmhyVDV2j81/ASD6s1/fdPu9f3n06edfefV1gCbA8sOPPv7Xv/71 -iQhA0ccffQjwBei+/uorzz/96F/uvf2mX/8MIPuF+bHuxiqHqRAQdsvBGRACiOmBhxy27fg8PV3m -bx+a/fzZ3wSI3nzHfQ8//uxLu15/8+133/8AYvnJp59++t8SAIo/gfh+8P67b7/5+q6Xnn384fvu -uBkg+82zPz871O4vo/V5x2877BAkBGliesCBgPknFhgd1cFQOPLlr19y1c6b7rjvr0889/LuN/a8 -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 - - - diff --git a/shell_integration/icons/SVG/Sync_Shared.svg b/shell_integration/icons/SVG/Sync_Shared.svg deleted file mode 100644 index cce9305b801..00000000000 --- a/shell_integration/icons/SVG/Sync_Shared.svg +++ /dev/null @@ -1,4802 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 -aPEPQgS6SxEQnAi0NgQDAt2HCAhcCLR+XJEIdKchIPBFoHXlykCgewkBwUsEWnWGLgLdMwgIfkCg -1WhoIdC9gYDgZwRapQKPQPcAAsLlQqB1K5AINPcICJcdgVay3xqB5hsB4bdDoLXtt0OgmUZA+K0R -aJ277Ag0wQgIgUSg9e9yIdC8IiAEHoHWQv8j0IwiIAwVBFoX/YZAE4mAMBQRaL30FYHmDwFh6CLQ -2uk9As0cAsJQR6B11BsEmjMEhCsDgdZUDxBoqhAQrjAEWmV5IdAkISBckQi04rpBoOlBQLiCEWj1 -ZUWgiUFAuOIRaCVmQKApQUAIEgRalSkINBkICEGFQCs0gUDTgIAQhAi0WkMEmgMEhCBEoNUa6TUC -wuUC0msEhGAF0msEhGAF0msEhKAEUm0EhGAF0msEhGAF0msEhGAF0msEhKAEUm0EhGAF0msEhGAF -0msEhKAEUm0EhGAF0msEhKAEUm0EhGAF0msEhKAEUm0EhGAF0msEhKAEUm0EhGAF0msEhKAEUm0E -hGAF0msEhKAEUm0EhGAFUm0EhKAE0msEhKAEUm0EhGAFUm0EhKAE0msEhKAEUm0EhA== - - - YAVSbQSEoATSawSEoARSbQSEYAVSbQSEoATSawSEoARSbQSEoARSbQSEYAVSbQSEoATSawSEoARS -bQSEoARSbQSEYAVSbQSEoARSbQSEoATSawSEoARSbQSEoARSbQSEoARSbQSEoARSbQSEYAVSbQSE -oARSbQSEoARSbQSEoATSawSEoARSbQSEoARSbQSEoARSbQSEoARSbQSEoARSbQSEoARSbQSEoARS -bQSEoARSbQSEoARSbQSEoARSbQSEoARSbQSEoAQKD0FACEog1UZACEog1UZACEog1UZACEog1UZA -CD78HxWBbg4CAoJ/gFQbASEogVQbASEo8X8uCHSLEBAQ/ACk2ggIQQmk2ggIQQmk2ggIwQdXvUba -jYAQBECqjYAQlECqjYAQlECqjYAQfGDTa6TdCAhXNJBqIyAEJZBqIyAEH7j1Gmk3AsIVCqTaCAhB -CaTaCAjBBz56jbQbAeGKA1JtBISgBFJtBITgA3+9RtqNgHAFAak2AkJQAqk2AkLwwVO9RtqNgHBF -AKk2AkJQwjvVRtqNgDCU4bVeI9VGQBjK8EW1kXYjIAxN+KjXSLUREIYmfFdtpN0ICEMNftFrpNoI -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 - - - diff --git a/shell_integration/icons/SVG/Warning.svg b/shell_integration/icons/SVG/Warning.svg deleted file mode 100644 index 4f761579d47..00000000000 --- a/shell_integration/icons/SVG/Warning.svg +++ /dev/null @@ -1,3787 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eJzsvemSHcl1JvgE8Q53fsiMbBNS4eHusXDGZHZXiTPiYiyKkkbTVgaiklVooYAyAEU2+TLzMPNi -c76zuXvcyAWFZLfIznRL4GbcCI8IX87yne1v/rdffvFi/9W7396+iDf9rvubvzm+v3358d37n+z4 -6O6nb958/+Hjexz60a9+vAvjTU8n7X86f6kn/ub2/YfX797+hL/iLy+4+kf7V9+8/vbHux/9mI78 -+vXHN7d07F9evn/7+u3XX37xzcv3t1/dfPj91z+2O1IXp5cf6aSeuhlvQtqF9JNhwdcv3/7+5YcP -r/90i3vEOdKxw7vv335FPR3e/Xc6mOPuRYhhF5a8ezHjmn98/avbD+uTbsKQcWJ/E8ZMJyf+ny7A -F3TR6d2r77+9ffvxl+/fvbr98OH47s279x9+sjv+8eXb3c9efk3fvNz92+2bN+/+sDu8efnqPzoa -hvzl5fWbW3rjb19+3IUB77//aRi+PHz/+s1XP//+29/e0liM84DD8Uvu8p8/UF/ULT7j8PTlT7+l -I1/cfvxID0s3xBj+6h8O9WPQQW4/+vdf0lDc0t3e/8ft2//6Y+32/bvvcISuHRO94TDT2w27Xr79 -9e23372hoZVhGOabzKfQf/Ufei69DJ/3IuSA71JKu9RP9DHGRc4pw3T7+9e3f/jJ7ufv3t7KWOzf -f/xC5imlvpd/5Ztfff/m9v0/v339Ec+IQ4sMxs/efXX7hs736y9vXvIYcAvlXznh1y/ff337keb2 -3ZvvP/Kqm+0ONNj/9PKPt5ixIDf4xXe3b3/97jf8jC+GON2MI833FOT/uBtyv9AYZLrDzDcJw7Ab -Qvabh/Kv3AM9oj+70UQz9Uuau1+8f/3167c/0cecvvyH96+/KvM5a+P3uJmr38V+5YHp3T9+vH2r -L0Cr6PizalX0Nz/7gu53fvvV8d23mIAP2Ai3dGNaKW/efS3f+Wf+hi7//jt5dv77S5qrX75//RZ9 -dj/nb+Yvf/nme/rqH96/+/67n7793bvuR7Lzf3P7irY3TedXu1/89r/RH7SZf/7y1Tdf3/7p9vWr -b97efrz9sDvQMqfjfu7Ny9ff/fjebukl39/u5Eu6kv+0/x+++nT7O9pr5XI5+o//D03vmze3b1/8 -399/ePnxT9++++r7N/Tt+e3vb9+8++529/LtVzsiPt89fINfvnn59uX7HR+3/utedz99+/H2/cv/ -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 -setBojeo/ud3xbP3A7u5TtT9uR19yvz9y+3r/+///dvdF6+++cPL93+imVsduCr41+bnPlzVkMlN -9ZaSvKdY0z1gtHMX2rUD7dHzIdWV1MZ1LTW3o2s9tc6zcde5CM4ld2GVfbspWHyVC0kI4NitChYX -Omi1z6PboEe3EmvRYgvuqGsWd2zks6LFll+rlDDfa5YtL1rsCS6L4W/U0oNcfLBzkusE9yrdZalG -aPUIOeHlulZP0ozddYam7UoGxX3awoQtSLjvNkrm7auclucqZKi4To/u299WNKcV0K2WwFZi9nUw -0DrlQBMB1K2j0XVZeHoKT5S19+wUbT52WyL3LhINA2oCgUoo0HqhNKWtO7cFmzW4zX5aFsjFsp/y -4qB33JpTm9HDA7O5vyPgO3Ybs3kaTj6Xq5oU2/NYZ9jvVnO59y29nsm6Fv3o8VzrPPvnrgm/ic02 -b+fz4GEY2/OpJRW7Kqxrr94tMD2X2WyrlT+4+buqYnlUSK2uWn69+a1eef/nsHXem4ugDs5ok7dN -VSBGm2rgVBK59aGu6FUqn7S1TyqasU0yujvDLCoeUREHW0x1nc2oid+wZDhHnnCCqMlrF10OEthn -Xht7SVPLs4xMcDM7aiAXHDLSZnXNOC7njndf1BrTC1PhM1eYTpzVcOE8hhfOWJjZV2JvJazXFb7+ -hxVrtHKN8TEFGzuvhvREBRu7O/yqf3C22jqfWZ3N7CnzmT02X+3znD7P6RMXVX3UjB767mln9DF5 -B/+Cc0q3aMCU6ASuxJToRaFZjcMiERZziJmT8YUQuZJ19eG+LAhP16VU5P7BdRLv1ALpwJc/f/f2 -l+9fv/34+u3XL15UymH9Rffz7/BNlG9++fLjx9v3b0lr/KeX3/+WdEX+bxfSeJNJbtqFMN/Qytjl -JSLdQ9zFab4hfpv5Mff877/+gf77nj8GffJ//SP/+X/Sx/9GB/9Ab7772e7f/2u/+0ou+RUgSLuJ -9bn7lo5e3Wn3T/VRe6B/qjvYPNhc/5Z+/27//uPp9auPr9+9ffn+j7ufMAr6d4d3797sfrT/qY7E -l+evXn989/7Lw8tX/0Gj9eWvX7+5/fJXt68+/nj3t3TB/06//ra/kKWxkIgaGQeKc9YaXBGSLuJ3 -oNZPljNRUj3+60u80IDMjmPehXm6QRwPv3tEukh6n3mgbvOyI+HohtYQHZkSfUMrEEdIGJ93YUw3 -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 -05jWw1tpc+zhj6PSrX8fdijZS2MQczjusM26X+to1uEy3NOLbCKGYI8mJAHqUPPxqWzVdpqveW/I -e9v6a9pwHI5L2ocMXDiO60N7NIsPxNDaF9g9Btw/0r7PeQZ8RTXVRHDPPnkeFpOiXykmz3xHn2P5 -YDkN8+Fzuk4vGbM6xyLnfY5XXx/juOY6yvHvay2+0WtNhrXxHQ7H+sW//cm33/7nWyv91V//7De/ -+frXv/zqr/7hq7/5+ldf/+w3X/8cDTVppA658R3Xw2e+dxapxPGif/nzb37z7a+/+ot//M23/+uv -fvbxm1/+/Vf/3q68ffFv/+rWZO9j//rXX//2m69/99XffPu7f7BDvYnv/M7L/+03tzL9919/8/f/ -yR/7j3ic/83vcUF6tW+//x7/+zc//81/+jfuk71GcLj6v/v1t7/7vuP+h29+8dWHb3/1zdf/8NX/ -/PU/fd/RP/36P/7mq29++dX/+Otvf/mb7zv4b7/91ePY7x3uD9/+Zx/u9l2HMj1+8w2f/au//adf -fe0XXr5zyHjBv/761x/vHau/4Xdenf/97P++B/kek3/66qfffuznPGbhX/7ov/mLv6pf/eUvf+6X -sN8/+frvv/mlS/77L/7bv/72F7/62c+//cdf/5sfDf9WIccK/956Vg8tYdojERfnLdFe+Rf67//x -ux8ZzEzspbfon/Tzf7r/+f/cwt99sS5f/Icv/s//a/ni53bK34TbNV7TcaUed/rip6M0n+in4xU+ -LZ2u8Ms/eJH7WP/kZx//Xya/JvVt88yrnff97+yl/xeZCXW59dQl4+K2sbAyjuNqt7IUN+x2rfrT -bSXwjxr/uK0HanoMyZs64p3Gsd26EH7hwluxdmEzh/JA0PZySbCt96Z27fIIPv7IaFlP0Bypgxb/ -OfVuqxqU6KWzvhAI/RT+rFZEpzOpnKR1YSUMKdZBJO0yiXS2HaOANLk/P4+SPopvqRVUCRYI1/W0 -owyiHy9mPexKLbr8KEe6N5Vd269oFiDIvIpaE3ZDmSZYzwZNc+Ma7IV0o9ErzFXF1QnnfQW1CFDv -1bbea7/M5sgMgOwObGxtU2r5xSzYtSsd5s4YUy+YM7enIxJnnlQYhZXoOVI4RrV7lc24b87dQ5jC -xkQily3O3MphZzr4lEiGiyRqF7SdsNo9vQyRbW9fVe27WekgG9y6K62GPaWmwLpsJX7rLOgwmRFx -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 -wn7KNwLTLVQiwDRF27borvX3WT0b6+CVhs/m+vPPtn5axHmBpgTaunnzZ4yTC5gmqCn9SePkAqaJ -1fo8hc7h0thkjmzf90eVhZJ3m4NHdg+eXz2y+tGrJ0ukxlZ4qrr677IsyJB29tSjn1aeLqXXYYDZ -3aUfF97eWPrx6rsRqcPJk4cim4Cn0BYhsr+fPm2uPP6UtSq0gWWBiLPoX+w9yl5013cWN54/BXKH -08nEWU3kLKOC8Xk/TW/4A/BwqZsevCXt/9LVSv8fn2i5sfLw6GoJx+vHu+jUaMNPt0YPT77bLYqb -d/QPcbfrZxgpd68zh3m4uN1duAlfH/ws2lsPNuTC88uFtVR3bpSArCN9jtEv3xxdWT786fJtmPCH -D2733zVhuU04+0tHz26CIbK5r/0BuY2GwfulI3nvcvUHT+zJ7b3jboLb9CSykdOltburPzV9L9na -+3c3rqyt9k7hVMqrxXHOlHXr+/m3edZef7L04+bCppx9ffyDnejp66uLP/6w/QJtbfeHl5+UxXH/ -WpIdne5uJusnze7D4UY5qI1sT2Vt7C898qWrniTXFgaDw+f69rOXD2iAO8mDOx/Rug== - - - W072i+XHQd8bJ0uPlvTpUiJuPR9ejvbg7fvF4s7TOf+HHf1zs7NIttjqrYXBd08erd2YvaWBXb0E -++bWXg+k09ba2ofZy4e4UrVwd/d9b+Hljzev3T68ud0B4Vd8IjoPuPOYA+PR3MqTwY2PTGPjFHN5 -9T639Oc36uxip1emi9dIAqIf5rzzqyqn4/TTG0wETy+Ygv395c6wk6JXYe4t6A+rPy/XeryAlR90 -++NlkNzu9CrxcPlpl5rcFvnlH+Dsz77eHC0+fS32eBvvPf1hb2XvuLjliG/mO7DTHyQry4+usDCa -61++32RHAHvPXt4zt8uP10qLp0eb80vd5paATrd7pHZcusoj/vA8f7f64f3VtzD5g8sLPy+9n/ES -69DTHXDd9PurgIniMcqcg9Xd+TedWIchjv2WbHcrr9cPQFPKXj472Y5lN7fM7qrVcvX9xu4H0E3u -IMPZn11t30x+WtQzxY/A/l7mrAvY4dWVhbsPnl9beXzcebtkXrxqLv54Y/NgcWvn5hqcl6sP32VL -R2JvEKoqLFDuza88frt+A5WNLWoC3Pnu9qTJ5zvXvt9e+3Dl2uuVfcDXUjf//tbKk5nZGVJu/Nkn -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 - - - diff --git a/shell_integration/icons/SVG/Warning_Shared.svg b/shell_integration/icons/SVG/Warning_Shared.svg deleted file mode 100644 index 90e88b6a05c..00000000000 --- a/shell_integration/icons/SVG/Warning_Shared.svg +++ /dev/null @@ -1,4730 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eJzsvemSHcl1JvgE8Q53fsiMbBNS4eHusXDGZHZXiTPiYiyKkkbTVgaiklVooYAyAEU2+TLzMPNi -c76zuXvcyAWFZLfIznRL4GbcCI8IX87yne1v/rdffvFi/9W7396+iDf9rvubvzm+v3358d37n+z4 -6O6nb958/+Hjexz60a9+vAvjTU8n7X86f6kn/ub2/YfX797+hL/iLy+4+kf7V9+8/vbHux/9mI78 -+vXHN7d07Pz+/bv3X37xzcv3t1/dfPj91z+2+1EHp5cf6ZSeOhlvQtqF9JNhwdcv3/7+5YcPr/90 -izvEOdKxw7vv3371+u3Xh3f/nQ6GafcixLALS969mHHNP77+1e2H9Uk3OU8jzuxvwpjp7HRDRwdc -chOGTJed3r36/tvbtx9/+f7dq9sPH47v3rx7/+Enu+MfX77d/ezl1/TNy92/3b558+4Pu8Obl6/+ -o6NhyF9eXr+5pTf+9uXHXRjw/vufhuHLw/ev33z18++//e0tjcU4Dzgcv+Qu//kD9UXd4jMOT1/+ -9Fs68sXtx4/0uHRDjOGv/uFQPwYd5Pajf/8lDcYt3e39f9y+/a8/1m7fv/sOR+jaMdErDjO93rDr -5dtf33773RsaXBmIYb7JfAr9V/+h59LL8HkvQg74LqW0Sz0N3i7GRc4pw3T7+9e3f/jJ7ufv3t7K -WOzff/xCZiqlvpd/5Ztfff/m9v0/v339Ec+IQ4sMxs/efXX7hs736y9vXvIYcAvlXznh1y/ff337 -kWb33ZvvP/Kqm+0ONNj/9PKPt5ixIDf4xXe3b3/97jf8jC+GON2MI038FOT/uBtyv9AYZLrDzDcJ -w7AbQvabh/Kv3AM9oj+70UQz9Uuau1+8f/3167c/0cecvvyH96+/KvM5a+P3uJmr38V+5YHp3T9+ -vH2rL0Cr6PizalX0Nz/7gu53fvvV8d23mIAP2Aq3dGNaKW/efS3f+Wf+hi7//jt5dv77S5qrX75/ -/RZ9dj/nb+Yvf/nme/rqH96/+/67n7793bvuR7Lzf3P7irY3TedXu1/89r/RH7SZf/7y1Tdf3/7p -9vWrb97efrz9sDvQMqfjfu7Ny9ff/fjebukl39/u5Eu6kv+0/x+++nT7O9pr5XI5+o//D03vmze3 -b1/8399/ePnxT9++++r7N/Tt+e3vb9+8++529/LtV7t/efn+u4dv8Ms3L9++fL/j49Z/3evup28/ -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 -k2iJLLHrQaI3qP7nd8Wz9wO7uU7U/bkdfcr8/cvt6//v//3b3RevvvnDy/d/oplbHbgq+Nfm5z5c -1ZDJTfWWkrynWNM9YLRzF9q1A+3R8yHVldTGdS01t6NrPbXOs3HXuQjOJXdhlX27KVh8lQtJCODY -rQoWFzpotc+j26BHtxJr0WIL7qhrFnds5LOixZZfq5Qw32uWLS9a7Akui+Fv1NKDXHywc5LrBPcq -3WWpRmj1CDnh5bpWT9KM3XWGpu1KBsV92sKELUi47zZK5u2rnJbnKmSouE6P7tvfVjSnFdCtlsBW -YvZ1MNA65UATAdSto9F1WXh6Ck+UtffsFG0+dlsi9y4SDQNqAoFKKNB6oTSlrTu3BZs1uM1+WhbI -xbKf8uKgd9yaU5vRwwOzub8j4Dt2G7N5Gk4+l6uaFNvzWGfY71ZzufctvZ7Juhb96PFc6zz7564J -v4nNNm/n8+BhGNvzqSUVuyqsa6/eLTA9l9lsq5U/uPm7qmJ5VEitrlp+vfmtXnn/57B13puLoA7O -aJO3TVUgRptq4FQSufWhruhVKp+0tU8qmrFNMro7wywqHlERB1tMdZ3NqInfsGQ4R55wgqjJaxdd -DhLYZ14be0lTy7OMTHAzO2ogFxwy0mZ1zTgu5453X9Qa0wtT4TNXmE6c1XDhPIYXzliY2VdibyWs -1xW+/ocVa7RyjfExBRs7r4b0RAUbuzv8qn9wtto6n1mdzewp85k9Nl/t85w+z+kTF1V91Iwe+u5p -Z/QxeQf/gnNKt2jAlOgErsSU6EWhWY3DIhEWc4iZk/GFELmSdfXhviwIT9elVOT+wXUS79QC6cCX -P3/39pfvX7/9+Prt1y9eVMph/UX38+/wTZRvfvny48fb929Ja/ynl9//lnRF/m8X0niTSW7ahTDf -0MrY5SUi3UPcxWm+IX6b+TH3/O+//oH++54/Bn3yf/0j//l/0sf/Rgf/QG+++9nu3/9rv/tKLvkV -IEi7ifW5+5aOXt1p90/1UXugf6o72DzYXP+Wfv9u//7j6fWrj6/fvX35/o+7nzAK+neHd+/e7H60 -/6mOxJfnr15/fPf+y8PLV/9Bo/Xlr1+/uf3yV7evPv5497d0wf9Ov/62v5ClsZCIGhkHinPWGlwR -ki7id6DWT5YzUVI9/utLvNCAzI5j3oV5ukEcD797RLpIep95oG7zsiPh6IbWEB2ZEn1DKxBHSBif -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 -Nxq9aUzr4a20Ofbwx1Hp1r8PO5TspTGIORx32Gbdr3U063AZ7ulFNhFDsEcTkgB1qPn4VLZqO83X -vDfkvW39NW04Dscl7UMGLhzH9aE9msUHYmjtC+weA+4fad/nPAO+oppqIrhnnzwPi0nRrxSTZ76j -z7F8sJyG+fA5XaeXjFmdY5HzPserr49xXHMd5fj3tRbf6LUmw9r4Dodj/eLf/uTbb//zrZX+6q9/ -9pvffP3rX371V//w1d98/auvf/abr3+Ohpo0Uofc+I7r4TPfO4tU4njRv/z5N7/59tdf/cU//ubb -//VXP/v4zS///qt/b1fevvi3f3Vrsvexf/3rr3/7zde/++pvvv3dP9ih3sR3fufl//abW5n++6+/ -+fv/5I/9RzzO/+b3uCC92rfff4///Zuf/+Y//Rv3yV4jOFz93/36299933H/wze/+OrDt7/65ut/ -+Op//vqfvu/on379H3/z1Te//Op//PW3v/zN9x38t9/+6nHs9w73h2//sw93+65DmR6/+YbP/tXf -/tOvvvYLL985ZLzgX3/964/3jtXf8Duvzv9+9n/fg3yPyT999dNvP/ZzHrPwL3/03/zFX9Wv/vKX -P/dL2O+ffP333/zSJf/9F//tX3/7i1/97Off/uOv/82Phn+rkGOFf289q4eWMO2RiIvzlmiv/Av9 -9//43Y8MZib20lv0T/r5P93//H9u4e++WJcv/sMX/+f/tXzxczvlb8LtGq/puFKPO33x01GaT/TT -8Qqflk5X+OUfvMh9rH/ys4//L5Nfk/q2eebVzvv+d/bS/4vMhLrceuqScXHbWFgZx3G1W1mKG3a7 -Vv3pthL4R41/3NYDNT2G5E0d8U7j2G5dCL9w4a1Yu7CZQ3kgaHu5JNjWe1O7dnkEH39ktKwnaI7U -QYv/nHq3VQ1K9NJZXwiEfgp/Viui05lUTtK6sBKGFOsgknaZRDrbjlFAmtyfn0dJH8W31AqqBAuE -63raUQbRjxezHnalFl1+lCPdm8qu7Vc0CxBkXkWtCbuhTBOsZ4OmuXEN9kK60egV5qri6oTzvoJa -BKj3alvvtV9mc2QGQHYHNra2KbX8Yhbs2pUOc2eMqRfMmdvTEYkzTyqMwkr0HCkco9q9ymbcN+fu -IUxhYyKRyxZnbuWwMx18SiTDRRK1C9pOWO2eXobItrevqvbdrHSQDW7dlVbDnlJTYF22Er91FnSY -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 -W43UD5wj6QGX5BpzcCvTEvD06hTpVp1htE7mUtkUQLF1jelKtqPijG20wCZb8Kx5B+WgvDFDUQVJ -7QA/WprkSMY69Og1fGWgfrNi0ATfMxHrWFpratzdjS0Op9KqcLtdFY3WgCd0zKwb6M2xoH3PoFOj -lfDAZwt1DcboGITjWBSuwRgdg5LoXIMxOgYlsqhcgzE6BqFXQaBrcOKMAkmuRAa3Kbtc4/3ULUqJ -TAbeqRuYnpmEXzB32QcGh8e9PacO+CT6FOI/HfgP/sy2pegN1hSD2Qx+McN3vb0Sed2p432KFO+4 -RNalLfZNO4f7pocnxnt8p6bkwLeaK7wNpc6UnBTyi7kpctAOXRf4KnhfAXdEu0DbuiS6lGLwf/Mp -khlQsU6js5L1Np8K3i4DL9eAX05J0etSKlLaOnQp/fDbtUSj3cOjAzmU9tf2+KfBA8gV8O89Eq1z -4OThvoFajz2luVLSBko3mFDp6IVen60xm7MtKWqrTaM3mFM6Uox68KjZ8Jmbh4R8H34W+JvAv9YU -I2QvG1FnSkEFALZR+5w90z05KXqL0WLWSZpL5/wXAwMDAwMj0fh/MUHsVmNgHFmITQ== - - - T7FGH8Y42CKBkYwQWyoxmBBbIjCSEmKL7ZELsXse4/CB2LJ8REDsTsY4zCG2gB+GELtLMY44iC3y -SQ+xOxDjSIfYGpB8ELvHMDCYEFsnDnWI3T8YGBEgtoocihC7TzAwooDY6nKoQOx+wMCIBWLrjcgQ -m34MjHghtg6JALEpx8BIGMRWpp8UYpONgZF4iK1VBx1iE4yBcRAhtnodRIhNLQbGTwGx9SzxEJtR -DIyfDmJrW8IgNpEYGCJAbLVLAMSmEANDNIitfHFBbPIwMMSH2FoYNcQmDAPjEILY6hgFxKYKA+OQ -g9hKKQhik4SBcYhCbNWMALHpwcA4pCG2gnJCbGIwMJIAYqspC8SmBAMjaSC2stIgNhkYGEkGsVWW -hNg0YGAkJcRWXKy5GBixA2suBkaSAisvBkbyAmsuBkbyAmsuBkbyAmsuBkaSAisvBkbyAmsuBkby -AmsuBkbyAmsuBkaSAisvBkbyAmsuBkbyAmsuBkaSAisvBkbyAmsuBkaSAisvBkbyAmsuBkaSAisv -BkbyAmsuBkaSAisvBkbyAmsuBkaSAisvBkaSAisvBkbyAmsuBkaSAisvBkbyAmsuBkaSAisvBkaS -AisvBkbyAmsuBkaSAisvBkaSAisvBkbyAmsuBkaSAisvBkaSAisvBkaSAisvBkbyAmsuBkaSAisv -BkaSAisvBkaSAisvBkbyAmsuBkaSAisvBkaSAisvBkaSAisvBkaSAisvBkaSAisvBkbyAisvBkaS -AmsuBkaSAisvBkaSAisvBkaSAisvBkaSAisvBkaSAisvBkaSAisvBkaSAisvBkbyAisvBkaSAisv -BkaSAisvBkaSAisvBkYyAjusMDCSFFh5MTCSFFh5MTCSFFh5MTCSFFh5MTCSFFh5MTCSFPgaHAyM -JAVWXgyMJAVWXgyMZMR/6RC7ORgYGEKBlRcDI0mBlRcDI0mBlRcDI0nx3zCI3SIMDAxBwMqLgZGM -CNdcrLwYGEkBrLwYGEkKVuXF+ouBcegDKy8GRjKCS3Ox8mJgHOLAyouBkaTgUV6svxgYhzKw8mJg -JCP4NRcrLwbGIYuIyov1FwPj0ARWXgyMZIQQzcXKi4FxCEKg8mL9xcA41ICVFwMjGSFcc7H+YmAc -UsDKi4GRjIhWc7HyYmAcIohBebH+YmAcCsDKi4GRjIhNc7H+YmCIDqy8GBjJiHg0F+svBoaIwMqL -gZGMiF9zsf5iYIiCRCkv1l8MjJ8SCdRcrLwYGD8lEqu8WH8xMH4aJFxzsf5iYPwEOEiai5UXA+Ng -4+ApL9ZfDIyDh4OquVh/MTAOEn4CzcX6i4GRcPxkmouVFwMjsfgplRfrLwZGovATay7WXwyMhEAU -zcX6i4ERJ0TUXKy/GBjxQGzdhRCbAwyM5IPYWhuC2ExgYCQTxNZXJsTmAwMjOSC2prJDbFYwMA51 -iK2jfBCbGwyMQxdia2dkiM0QBsahCLH1UijE5gkD49CC2BoZHcRmCwPjUIHYuhgLxOYMA0N8iK2F -sUNs5jAwxITY+pcAiE0hBsZPDbF1LpEQm0sMjJ8OYmtb4iE2oxgYPwXE1rODCLGpxcA4WBBbt34K -iM0xBkbiIbZW/aQQm2wMjIRBbGUSB2KzjoERF8RWIPEhdg9gYEQNsZXm0ILYvYGBIRRi68ohCrG7 -BQODD2LrR3JA7F7CwKBBbIVIPojdYxgYWG3jhdgdiHHEQWyRPwwhdpdiHOYQW8CPFIjdzxiHD8SW -5SMdYvc/RvJBbJnFiACxBQTj0ILY8oiBcVgBKywGxuEErKcYGBgYGBgYhwgSu6zAOMzwszCI3aLk -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 - - - diff --git a/shell_integration/nautilus/CMakeLists.txt b/shell_integration/nautilus/CMakeLists.txt deleted file mode 100644 index 3d9a7f6d1c2..00000000000 --- a/shell_integration/nautilus/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -# copy the python nautilus plugin to build dir as it is going to -# be changed. -if( UNIX AND NOT APPLE ) - - configure_file(syncstate.py syncstate.py COPYONLY) - - # Call the setupappname.sh script to set the custom app name. - set (cmd "${CMAKE_CURRENT_SOURCE_DIR}/setappname.sh") - execute_process(COMMAND ${cmd} ${APPLICATION_SHORTNAME} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ERROR_VARIABLE errors OUTPUT_VARIABLE out) - - # Create a nemo plugin script from the nautilus one. - # nemocmd copies the syncstate.py and performs string replacement. - set (nemocmd "${CMAKE_CURRENT_SOURCE_DIR}/createnemoplugin.sh") - execute_process(COMMAND ${nemocmd} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ERROR_VARIABLE errors OUTPUT_VARIABLE out) - - # Create a caja plugin script from the nautilus one. - # cajacmd copies the syncstate.py and performs string replacement. - set (cajacmd "${CMAKE_CURRENT_SOURCE_DIR}/createcajaplugin.sh") - execute_process(COMMAND ${cajacmd} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ERROR_VARIABLE errors OUTPUT_VARIABLE out) - - - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/syncstate.py DESTINATION ${DATA_INSTALL_DIR}/nautilus-python/extensions RENAME syncstate-${APPLICATION_SHORTNAME}.py) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/syncstate_nemo.py DESTINATION ${DATA_INSTALL_DIR}/nemo-python/extensions RENAME syncstate-${APPLICATION_SHORTNAME}.py) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/syncstate_caja.py DESTINATION ${DATA_INSTALL_DIR}/caja-python/extensions RENAME syncstate-${APPLICATION_SHORTNAME}.py) - - - -endif() - diff --git a/shell_integration/nautilus/createcajaplugin.sh b/shell_integration/nautilus/createcajaplugin.sh deleted file mode 100755 index 8912d4a3f39..00000000000 --- a/shell_integration/nautilus/createcajaplugin.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -# this script creates a plugin for caja, just by replacing -# all occurences of Nautilus with Caja (case sensitive). - -cp syncstate.py syncstate_caja.py -sed -i.org -e 's/Nautilus/Caja/g' syncstate_caja.py -sed -i.org -e 's/nautilus/caja/g' syncstate_caja.py diff --git a/shell_integration/nautilus/createnemoplugin.sh b/shell_integration/nautilus/createnemoplugin.sh deleted file mode 100755 index 0e08affc49f..00000000000 --- a/shell_integration/nautilus/createnemoplugin.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -# this script creates a plugin for nemo, just be replacing -# all occurences of Nautilus with Nemo. - -cp syncstate.py syncstate_nemo.py -sed -i.org -e 's/autilus/emo/g' syncstate_nemo.py diff --git a/shell_integration/nautilus/setappname.sh b/shell_integration/nautilus/setappname.sh deleted file mode 100755 index ed01328bd3c..00000000000 --- a/shell_integration/nautilus/setappname.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -# this script replaces the line -# appname = 'ownCloud' -# with the correct branding name in the syncstate.py script -# It also replaces the occurences in the class name so several -# branding can be loaded (see #6524) -sed -i.org -e "s/ownCloud/$1/g" syncstate.py diff --git a/shell_integration/nautilus/syncstate.py b/shell_integration/nautilus/syncstate.py deleted file mode 100644 index 2b7fa8334aa..00000000000 --- a/shell_integration/nautilus/syncstate.py +++ /dev/null @@ -1,496 +0,0 @@ -# -# Copyright (C) by Klaas Freitag -# -# This program is the core of OwnCloud integration to Nautilus -# It will be installed on /usr/share/nautilus-python/extensions/ with the paquet owncloud-client-nautilus -# (https://github.com/owncloud/client/edit/master/shell_integration/nautilus/syncstate.py) -# -# 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. - -import sys -python3 = sys.version_info[0] >= 3 - -import os -import urllib -if python3: - import urllib.parse -import socket -import tempfile -import time - -from gi.repository import GObject, Nautilus - -# Note: setappname.sh will search and replace 'ownCloud' on this file to update this line and other -# occurrences of the name -appname = 'ownCloud' - -print("Initializing "+appname+"-client-nautilus extension") -print("Using python version {}".format(sys.version_info)) - -def get_local_path(url): - if url[0:7] == 'file://': - url = url[7:] - if python3: - return urllib.parse.unquote(url) - else: - return urllib.unquote(url).decode('utf-8') - -def get_runtime_dir(): - """Returns the value of $XDG_RUNTIME_DIR, a directory path. - - If the value is not set, returns the same default as in Qt5 - """ - try: - return os.environ['XDG_RUNTIME_DIR'] - except KeyError: - fallback = os.path.join(tempfile.gettempdir(), 'runtime-' + os.environ['USER']) - return fallback - - -class SocketConnect(GObject.GObject): - def __init__(self): - GObject.GObject.__init__(self) - self.connected = False - self.registered_paths = {} - self._watch_id = 0 - self._sock = None - self._listeners = [self._update_registered_paths, self._get_version] - self._remainder = ''.encode('utf-8') - self.protocolVersion = '1.0' - self.nautilusVFSFile_table = {} # not needed in this object actually but shared - # all over the other objects. - - # returns true when one should try again! - if self._connectToSocketServer(): - GObject.timeout_add(5000, self._connectToSocketServer) - - def reconnect(self): - self._sock.close() - self.connected = False - GObject.source_remove(self._watch_id) - GObject.timeout_add(5000, self._connectToSocketServer) - - def sendCommand(self, cmd): - # print("Server command: " + cmd) - if self.connected: - try: - self._sock.send(cmd.encode('utf-8')) - except: - print("Sending failed.") - self.reconnect() - else: - print("Cannot send, not connected!") - - def addListener(self, listener): - self._listeners.append(listener) - - def _connectToSocketServer(self): - try: - self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock_file = os.path.join(get_runtime_dir(), appname, "socket") - try: - self._sock.connect(sock_file) # fails if sock_file doesn't exist - self.connected = True - self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify) - - self.sendCommand('VERSION:\n') - self.sendCommand('GET_STRINGS:\n') - - return False # Don't run again - except Exception as e: - print("Could not connect to unix socket " + sock_file + ". " + str(e)) - except Exception as e: # Bad habbit - print("Connect could not be established, try again later.") - self._sock.close() - - return True # Run again, if enabled via timeout_add() - - # Reads data that becomes available. - # New responses can be accessed with get_available_responses(). - # Returns false if no data was received within timeout - def read_socket_data_with_timeout(self, timeout): - self._sock.settimeout(timeout) - try: - self._remainder += self._sock.recv(1024) - except socket.timeout: - return False - else: - return True - finally: - self._sock.settimeout(None) - - # Parses response lines out of collected data, returns list of strings - def get_available_responses(self): - end = self._remainder.rfind(b'\n') - if end == -1: - return [] - data = self._remainder[:end] - self._remainder = self._remainder[end+1:] - return data.decode('utf-8').split('\n') - - # Notify is the raw answer from the socket - def _handle_notify(self, source, condition): - # Blocking is ok since we're notified of available data - self._remainder += self._sock.recv(1024) - - if len(self._remainder) == 0: - return False - - for line in self.get_available_responses(): - self.handle_server_response(line) - - return True # Run again - - def handle_server_response(self, line): - # print("Server response: " + line) - parts = line.split(':') - action = parts[0] - args = parts[1:] - - for listener in self._listeners: - listener(action, args) - - def _update_registered_paths(self, action, args): - if action == 'REGISTER_PATH': - self.registered_paths[args[0]] = 1 - elif action == 'UNREGISTER_PATH': - del self.registered_paths[args[0]] - - # Check if there are no paths left. If so, its usual - # that mirall went away. Try reconnecting. - if not self.registered_paths: - self.reconnect() - - def _get_version(self, action, args): - if action == 'VERSION': - self.protocolVersion = args[1] - -socketConnect = SocketConnect() - - -class MenuExtension_ownCloud(GObject.GObject, Nautilus.MenuProvider): - def __init__(self): - GObject.GObject.__init__(self) - - self.strings = {} - socketConnect.addListener(self.handle_commands) - - def handle_commands(self, action, args): - if action == 'STRING': - self.strings[args[0]] = ':'.join(args[1:]) - - def check_registered_paths(self, filename): - topLevelFolder = False - internalFile = False - for reg_path in socketConnect.registered_paths: - if filename == reg_path: - topLevelFolder = True - break - if filename.startswith(reg_path): - internalFile = True - # you can't have a registered path below another so it is save to break here - break - return (topLevelFolder, internalFile) - - def get_file_items(self, window, files): - # Show the menu extension to share a file or folder - - # Get usable file paths from the uris - all_internal_files = True - for i, file_uri in enumerate(files): - filename = get_local_path(file_uri.get_uri()) - - # Check if its a folder (ends with an /), if yes add a "/" - # otherwise it will not find the entry in the table - isDir = os.path.isdir(filename + os.sep) - if isDir: - filename += os.sep - - # Check if toplevel folder, we need to ignore those as they cannot be shared - topLevelFolder, internalFile = self.check_registered_paths(filename) - if not internalFile: - all_internal_files = False - - files[i] = filename - - # Don't show a context menu if some selected files aren't in a sync folder - if not all_internal_files: - return [] - - if socketConnect.protocolVersion >= '1.1': # lexicographic! - return self.ask_for_menu_items(files) - else: - return self.legacy_menu_items(files) - - def ask_for_menu_items(self, files): - record_separator = '\x1e' - filesstring = record_separator.join(files) - socketConnect.sendCommand(u'GET_MENU_ITEMS:{}\n'.format(filesstring)) - - done = False - start = time.time() - # timeout is specified in seconds - timeout = 0.5 - menu_items = [] - while not done: - dt = time.time() - start - - if dt >= timeout: - break - - if not socketConnect.read_socket_data_with_timeout(timeout - dt): - break - - for line in socketConnect.get_available_responses(): - # if we're done with the menu items, we don't have to try to parse lines any more - if not done: - # using a bool to keep track of this eliminates duplicate code when parsing succeeded - line_handled = False - - if line == 'GET_MENU_ITEMS:END': - done = True - line_handled = True - - elif line.startswith('MENU_ITEM:'): - args = line.split(':') - if len(args) >= 4: - action = args[1] - label = ':'.join(args[3:]) - enabled = 'd' not in args[2] - - item = Nautilus.MenuItem(name=action, label=label, sensitive=enabled) - item.connect("activate", self.context_menu_action, action, filesstring) - - menu_items.append(item) - - line_handled = True - - if line_handled: - # we don't have to have call the handler any more below - continue - - # original comment: Process lines we don't care about - socketConnect.handle_server_response(line) - - if not done: - return self.legacy_menu_items(files) - - # if there are no items for the submenu, we don't have to create it at all - if not menu_items: - return [] - - # Set up the 'ownCloud...' submenu - item_owncloud = Nautilus.MenuItem( - name='IntegrationMenu', label=self.strings.get('CONTEXT_MENU_TITLE', appname)) - - submenu = Nautilus.Menu() - item_owncloud.set_submenu(submenu) - - for item in menu_items: - submenu.append_item(item) - - return [item_owncloud] - - - def legacy_menu_items(self, files): - # No legacy menu for a selection of several files - if len(files) != 1: - return [] - filename = files[0] - - entry = socketConnect.nautilusVFSFile_table.get(filename) - if not entry: - return [] - - # Currently 'sharable' also controls access to private link actions, - # and we definitely don't want to show them for IGNORED. - shareable = False - state = entry['state'] - state_ok = state and state.startswith('OK') - state_sync = state and state.startswith('SYNC') - if state_ok: - shareable = True - elif state_sync and isDir: - # some file below is OK or SYNC - for key, value in socketConnect.nautilusVFSFile_table.items(): - if key != filename and key.startswith(filename): - state = value['state'] - if state.startswith('OK') or state.startswith('SYNC'): - shareable = True - break - - if not shareable: - return [] - - # Set up the 'ownCloud...' submenu - item_owncloud = Nautilus.MenuItem( - name='IntegrationMenu', label=self.strings.get('CONTEXT_MENU_TITLE', appname)) - menu = Nautilus.Menu() - item_owncloud.set_submenu(menu) - - # Add share menu option - item = Nautilus.MenuItem( - name='NautilusPython::ShareItem', - label=self.strings.get('SHARE_MENU_TITLE', 'Share...')) - item.connect("activate", self.context_menu_action, 'SHARE', filename) - menu.append_item(item) - - # Add permalink menu options, but hide these options for older clients - # that don't have these actions. - if 'COPY_PRIVATE_LINK_MENU_TITLE' in self.strings: - item_copyprivatelink = Nautilus.MenuItem( - name='CopyPrivateLink', label=self.strings.get('COPY_PRIVATE_LINK_MENU_TITLE', 'Copy private link to clipboard')) - item_copyprivatelink.connect("activate", self.context_menu_action, 'COPY_PRIVATE_LINK', filename) - menu.append_item(item_copyprivatelink) - - if 'EMAIL_PRIVATE_LINK_MENU_TITLE' in self.strings: - item_emailprivatelink = Nautilus.MenuItem( - name='EmailPrivateLink', label=self.strings.get('EMAIL_PRIVATE_LINK_MENU_TITLE', 'Send private link by email...')) - item_emailprivatelink.connect("activate", self.context_menu_action, 'EMAIL_PRIVATE_LINK', filename) - menu.append_item(item_emailprivatelink) - - return [item_owncloud] - - - def context_menu_action(self, menu, action, filename): - # print("Context menu: " + action + ' ' + filename) - socketConnect.sendCommand(action + ":" + filename + "\n") - - -class SyncStateExtension_ownCloud(GObject.GObject, Nautilus.InfoProvider): - def __init__(self): - GObject.GObject.__init__(self) - - socketConnect.nautilusVFSFile_table = {} - socketConnect.addListener(self.handle_commands) - - def find_item_for_file(self, path): - if path in socketConnect.nautilusVFSFile_table: - return socketConnect.nautilusVFSFile_table[path] - else: - return None - - def askForOverlay(self, file): - # print("Asking for overlay for "+file) # For debug only - if os.path.isdir(file): - folderStatus = socketConnect.sendCommand("RETRIEVE_FOLDER_STATUS:"+file+"\n"); - - if os.path.isfile(file): - fileStatus = socketConnect.sendCommand("RETRIEVE_FILE_STATUS:"+file+"\n"); - - def invalidate_items_underneath(self, path): - update_items = [] - if not socketConnect.nautilusVFSFile_table: - self.askForOverlay(path) - else: - for p in socketConnect.nautilusVFSFile_table: - if p == path or p.startswith(path): - item = socketConnect.nautilusVFSFile_table[p]['item'] - update_items.append(p) - - for path1 in update_items: - socketConnect.nautilusVFSFile_table[path1]['item'].invalidate_extension_info() - - # Handles a single line of server response and sets the emblem - def handle_commands(self, action, args): - # file = args[0] # For debug only - # print("Action for " + file + ": " + args[0]) # For debug only - if action == 'STATUS': - newState = args[0] - filename = ':'.join(args[1:]) - - itemStore = self.find_item_for_file(filename) - if itemStore: - if( not itemStore['state'] or newState != itemStore['state'] ): - item = itemStore['item'] - - # print("Setting emblem on " + filename + "<>" + emblem + "<>") # For debug only - - # If an emblem is already set for this item, we need to - # clear the existing extension info before setting a new one. - # - # That will also trigger a new call to - # update_file_info for this item! That's why we set - # skipNextUpdate to True: we don't want to pull the - # current data from the client after getting a push - # notification. - invalidate = itemStore['state'] != None - if invalidate: - item.invalidate_extension_info() - self.set_emblem(item, newState) - - socketConnect.nautilusVFSFile_table[filename] = { - 'item': item, - 'state': newState, - 'skipNextUpdate': invalidate } - - elif action == 'UPDATE_VIEW': - # Search all items underneath this path and invalidate them - if args[0] in socketConnect.registered_paths: - self.invalidate_items_underneath(args[0]) - - elif action == 'REGISTER_PATH': - self.invalidate_items_underneath(args[0]) - elif action == 'UNREGISTER_PATH': - self.invalidate_items_underneath(args[0]) - - def set_emblem(self, item, state): - Emblems = { 'OK' : appname +'_ok', - 'SYNC' : appname +'_sync', - 'NEW' : appname +'_sync', - 'IGNORE' : appname +'_warn', - 'ERROR' : appname +'_error', - 'OK+SWM' : appname +'_ok_shared', - 'SYNC+SWM' : appname +'_sync_shared', - 'NEW+SWM' : appname +'_sync_shared', - 'IGNORE+SWM': appname +'_warn_shared', - 'ERROR+SWM' : appname +'_error_shared', - 'NOP' : '' - } - - emblem = 'NOP' # Show nothing if no emblem is defined. - if state in Emblems: - emblem = Emblems[state] - item.add_emblem(emblem) - - def update_file_info(self, item): - if item.get_uri_scheme() != 'file': - return - - filename = get_local_path(item.get_uri()) - if item.is_directory(): - filename += os.sep - - inScope = False - for reg_path in socketConnect.registered_paths: - if filename.startswith(reg_path): - inScope = True - break - - if not inScope: - return - - # Ask for the current state from the client -- unless this update was - # triggered by receiving a STATUS message from the client in the first - # place. - itemStore = self.find_item_for_file(filename) - if itemStore and itemStore['skipNextUpdate'] and itemStore['state']: - itemStore['skipNextUpdate'] = False - itemStore['item'] = item - self.set_emblem(item, itemStore['state']) - else: - socketConnect.nautilusVFSFile_table[filename] = { - 'item': item, - 'state': None, - 'skipNextUpdate': False } - - # item.add_string_attribute('share_state', "share state") # ? - self.askForOverlay(filename) diff --git a/shell_integration/windows/3rdparty/nlohmann-json/LICENSE.MIT b/shell_integration/windows/3rdparty/nlohmann-json/LICENSE.MIT deleted file mode 100644 index f0622d6dc24..00000000000 --- a/shell_integration/windows/3rdparty/nlohmann-json/LICENSE.MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2013-2021 Niels Lohmann - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/shell_integration/windows/3rdparty/nlohmann-json/json.hpp b/shell_integration/windows/3rdparty/nlohmann-json/json.hpp deleted file mode 100644 index a70aaf8cbcc..00000000000 --- a/shell_integration/windows/3rdparty/nlohmann-json/json.hpp +++ /dev/null @@ -1,25447 +0,0 @@ -/* - __ _____ _____ _____ - __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 3.9.1 -|_____|_____|_____|_|___| https://github.com/nlohmann/json - -Licensed under the MIT License . -SPDX-License-Identifier: MIT -Copyright (c) 2013-2019 Niels Lohmann . - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#ifndef INCLUDE_NLOHMANN_JSON_HPP_ -#define INCLUDE_NLOHMANN_JSON_HPP_ - -#define NLOHMANN_JSON_VERSION_MAJOR 3 -#define NLOHMANN_JSON_VERSION_MINOR 9 -#define NLOHMANN_JSON_VERSION_PATCH 1 - -#include // all_of, find, for_each -#include // nullptr_t, ptrdiff_t, size_t -#include // hash, less -#include // initializer_list -#include // istream, ostream -#include // random_access_iterator_tag -#include // unique_ptr -#include // accumulate -#include // string, stoi, to_string -#include // declval, forward, move, pair, swap -#include // vector - -// #include - - -#include - -// #include - - -#include // transform -#include // array -#include // forward_list -#include // inserter, front_inserter, end -#include // map -#include // string -#include // tuple, make_tuple -#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible -#include // unordered_map -#include // pair, declval -#include // valarray - -// #include - - -#include // exception -#include // runtime_error -#include // to_string - -// #include - - -#include // size_t - -namespace nlohmann -{ -namespace detail -{ -/// struct to capture the start position of the current token -struct position_t -{ - /// the total number of characters read - std::size_t chars_read_total = 0; - /// the number of characters read in the current line - std::size_t chars_read_current_line = 0; - /// the number of lines read - std::size_t lines_read = 0; - - /// conversion to size_t to preserve SAX interface - constexpr operator size_t() const - { - return chars_read_total; - } -}; - -} // namespace detail -} // namespace nlohmann - -// #include - - -#include // pair -// #include -/* Hedley - https://nemequ.github.io/hedley - * Created by Evan Nemerson - * - * To the extent possible under law, the author(s) have dedicated all - * copyright and related and neighboring rights to this software to - * the public domain worldwide. This software is distributed without - * any warranty. - * - * For details, see . - * SPDX-License-Identifier: CC0-1.0 - */ - -#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 13) -#if defined(JSON_HEDLEY_VERSION) - #undef JSON_HEDLEY_VERSION -#endif -#define JSON_HEDLEY_VERSION 13 - -#if defined(JSON_HEDLEY_STRINGIFY_EX) - #undef JSON_HEDLEY_STRINGIFY_EX -#endif -#define JSON_HEDLEY_STRINGIFY_EX(x) #x - -#if defined(JSON_HEDLEY_STRINGIFY) - #undef JSON_HEDLEY_STRINGIFY -#endif -#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) - -#if defined(JSON_HEDLEY_CONCAT_EX) - #undef JSON_HEDLEY_CONCAT_EX -#endif -#define JSON_HEDLEY_CONCAT_EX(a,b) a##b - -#if defined(JSON_HEDLEY_CONCAT) - #undef JSON_HEDLEY_CONCAT -#endif -#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) - -#if defined(JSON_HEDLEY_CONCAT3_EX) - #undef JSON_HEDLEY_CONCAT3_EX -#endif -#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c - -#if defined(JSON_HEDLEY_CONCAT3) - #undef JSON_HEDLEY_CONCAT3 -#endif -#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) - -#if defined(JSON_HEDLEY_VERSION_ENCODE) - #undef JSON_HEDLEY_VERSION_ENCODE -#endif -#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) - -#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) - #undef JSON_HEDLEY_VERSION_DECODE_MAJOR -#endif -#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) - -#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) - #undef JSON_HEDLEY_VERSION_DECODE_MINOR -#endif -#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) - -#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) - #undef JSON_HEDLEY_VERSION_DECODE_REVISION -#endif -#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) - -#if defined(JSON_HEDLEY_GNUC_VERSION) - #undef JSON_HEDLEY_GNUC_VERSION -#endif -#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) - #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#elif defined(__GNUC__) - #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) -#endif - -#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) - #undef JSON_HEDLEY_GNUC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_GNUC_VERSION) - #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_MSVC_VERSION) - #undef JSON_HEDLEY_MSVC_VERSION -#endif -#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) -#elif defined(_MSC_FULL_VER) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) -#elif defined(_MSC_VER) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) -#endif - -#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) - #undef JSON_HEDLEY_MSVC_VERSION_CHECK -#endif -#if !defined(_MSC_VER) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) -#elif defined(_MSC_VER) && (_MSC_VER >= 1400) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) -#elif defined(_MSC_VER) && (_MSC_VER >= 1200) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) -#else - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) -#endif - -#if defined(JSON_HEDLEY_INTEL_VERSION) - #undef JSON_HEDLEY_INTEL_VERSION -#endif -#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) - #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) -#elif defined(__INTEL_COMPILER) - #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) -#endif - -#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) - #undef JSON_HEDLEY_INTEL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_INTEL_VERSION) - #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_PGI_VERSION) - #undef JSON_HEDLEY_PGI_VERSION -#endif -#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) - #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) -#endif - -#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) - #undef JSON_HEDLEY_PGI_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_PGI_VERSION) - #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_SUNPRO_VERSION) - #undef JSON_HEDLEY_SUNPRO_VERSION -#endif -#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) -#elif defined(__SUNPRO_C) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) -#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) -#elif defined(__SUNPRO_CC) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) -#endif - -#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) - #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_SUNPRO_VERSION) - #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) - #undef JSON_HEDLEY_EMSCRIPTEN_VERSION -#endif -#if defined(__EMSCRIPTEN__) - #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) -#endif - -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) - #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) - #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_ARM_VERSION) - #undef JSON_HEDLEY_ARM_VERSION -#endif -#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) - #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) -#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) - #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) -#endif - -#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) - #undef JSON_HEDLEY_ARM_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_ARM_VERSION) - #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_IBM_VERSION) - #undef JSON_HEDLEY_IBM_VERSION -#endif -#if defined(__ibmxl__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) -#elif defined(__xlC__) && defined(__xlC_ver__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) -#elif defined(__xlC__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) -#endif - -#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) - #undef JSON_HEDLEY_IBM_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_IBM_VERSION) - #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_VERSION) - #undef JSON_HEDLEY_TI_VERSION -#endif -#if \ - defined(__TI_COMPILER_VERSION__) && \ - ( \ - defined(__TMS470__) || defined(__TI_ARM__) || \ - defined(__MSP430__) || \ - defined(__TMS320C2000__) \ - ) -#if (__TI_COMPILER_VERSION__ >= 16000000) - #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif -#endif - -#if defined(JSON_HEDLEY_TI_VERSION_CHECK) - #undef JSON_HEDLEY_TI_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_VERSION) - #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL2000_VERSION) - #undef JSON_HEDLEY_TI_CL2000_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) - #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL2000_VERSION) - #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL430_VERSION) - #undef JSON_HEDLEY_TI_CL430_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) - #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL430_VERSION) - #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) - #undef JSON_HEDLEY_TI_ARMCL_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) - #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) - #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) - #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL6X_VERSION) - #undef JSON_HEDLEY_TI_CL6X_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) - #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL6X_VERSION) - #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL7X_VERSION) - #undef JSON_HEDLEY_TI_CL7X_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) - #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL7X_VERSION) - #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) - #undef JSON_HEDLEY_TI_CLPRU_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) - #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) - #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_CRAY_VERSION) - #undef JSON_HEDLEY_CRAY_VERSION -#endif -#if defined(_CRAYC) - #if defined(_RELEASE_PATCHLEVEL) - #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) - #else - #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) - #endif -#endif - -#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) - #undef JSON_HEDLEY_CRAY_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_CRAY_VERSION) - #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_IAR_VERSION) - #undef JSON_HEDLEY_IAR_VERSION -#endif -#if defined(__IAR_SYSTEMS_ICC__) - #if __VER__ > 1000 - #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) - #else - #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0) - #endif -#endif - -#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) - #undef JSON_HEDLEY_IAR_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_IAR_VERSION) - #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TINYC_VERSION) - #undef JSON_HEDLEY_TINYC_VERSION -#endif -#if defined(__TINYC__) - #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) -#endif - -#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) - #undef JSON_HEDLEY_TINYC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TINYC_VERSION) - #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_DMC_VERSION) - #undef JSON_HEDLEY_DMC_VERSION -#endif -#if defined(__DMC__) - #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) -#endif - -#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) - #undef JSON_HEDLEY_DMC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_DMC_VERSION) - #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_COMPCERT_VERSION) - #undef JSON_HEDLEY_COMPCERT_VERSION -#endif -#if defined(__COMPCERT_VERSION__) - #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) -#endif - -#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) - #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_COMPCERT_VERSION) - #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_PELLES_VERSION) - #undef JSON_HEDLEY_PELLES_VERSION -#endif -#if defined(__POCC__) - #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) -#endif - -#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) - #undef JSON_HEDLEY_PELLES_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_PELLES_VERSION) - #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_GCC_VERSION) - #undef JSON_HEDLEY_GCC_VERSION -#endif -#if \ - defined(JSON_HEDLEY_GNUC_VERSION) && \ - !defined(__clang__) && \ - !defined(JSON_HEDLEY_INTEL_VERSION) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_ARM_VERSION) && \ - !defined(JSON_HEDLEY_TI_VERSION) && \ - !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ - !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ - !defined(__COMPCERT__) - #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION -#endif - -#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) - #undef JSON_HEDLEY_GCC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_GCC_VERSION) - #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_ATTRIBUTE -#endif -#if defined(__has_attribute) - #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) -#else - #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE -#endif -#if defined(__has_attribute) - #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE -#endif -#if defined(__has_attribute) - #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE -#endif -#if \ - defined(__has_cpp_attribute) && \ - defined(__cplusplus) && \ - (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) - #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS -#endif -#if !defined(__cplusplus) || !defined(__has_cpp_attribute) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) -#elif \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_IAR_VERSION) && \ - (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ - (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) -#else - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE -#endif -#if defined(__has_cpp_attribute) && defined(__cplusplus) - #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE -#endif -#if defined(__has_cpp_attribute) && defined(__cplusplus) - #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_BUILTIN) - #undef JSON_HEDLEY_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) -#else - #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) - #undef JSON_HEDLEY_GNUC_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) -#else - #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) - #undef JSON_HEDLEY_GCC_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) -#else - #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_FEATURE) - #undef JSON_HEDLEY_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) -#else - #define JSON_HEDLEY_HAS_FEATURE(feature) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) - #undef JSON_HEDLEY_GNUC_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) -#else - #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) - #undef JSON_HEDLEY_GCC_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) -#else - #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_EXTENSION) - #undef JSON_HEDLEY_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) -#else - #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) - #undef JSON_HEDLEY_GNUC_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) -#else - #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) - #undef JSON_HEDLEY_GCC_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) -#else - #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_WARNING) - #undef JSON_HEDLEY_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) -#else - #define JSON_HEDLEY_HAS_WARNING(warning) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) - #undef JSON_HEDLEY_GNUC_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) -#else - #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_WARNING) - #undef JSON_HEDLEY_GCC_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) -#else - #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for - HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ -#endif -#if defined(__cplusplus) -# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") -# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# endif -# endif -#endif -#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x -#endif - -#if defined(JSON_HEDLEY_CONST_CAST) - #undef JSON_HEDLEY_CONST_CAST -#endif -#if defined(__cplusplus) -# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) -#elif \ - JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ - ((T) (expr)); \ - JSON_HEDLEY_DIAGNOSTIC_POP \ - })) -#else -# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_REINTERPRET_CAST) - #undef JSON_HEDLEY_REINTERPRET_CAST -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) -#else - #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_STATIC_CAST) - #undef JSON_HEDLEY_STATIC_CAST -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) -#else - #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_CPP_CAST) - #undef JSON_HEDLEY_CPP_CAST -#endif -#if defined(__cplusplus) -# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") -# define JSON_HEDLEY_CPP_CAST(T, expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ - ((T) (expr)) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) -# define JSON_HEDLEY_CPP_CAST(T, expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("diag_suppress=Pe137") \ - JSON_HEDLEY_DIAGNOSTIC_POP \ -# else -# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) -# endif -#else -# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) -#endif - -#if \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ - defined(__clang__) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ - (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) - #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_PRAGMA(value) __pragma(value) -#else - #define JSON_HEDLEY_PRAGMA(value) -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) - #undef JSON_HEDLEY_DIAGNOSTIC_PUSH -#endif -#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) - #undef JSON_HEDLEY_DIAGNOSTIC_POP -#endif -#if defined(__clang__) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) - #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) -#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#else - #define JSON_HEDLEY_DIAGNOSTIC_PUSH - #define JSON_HEDLEY_DIAGNOSTIC_POP -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL -#endif - -#if defined(JSON_HEDLEY_DEPRECATED) - #undef JSON_HEDLEY_DEPRECATED -#endif -#if defined(JSON_HEDLEY_DEPRECATED_FOR) - #undef JSON_HEDLEY_DEPRECATED_FOR -#endif -#if JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) - #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) -#elif defined(__cplusplus) && (__cplusplus >= 201402L) - #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) -#elif \ - JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) - #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) - #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") -#else - #define JSON_HEDLEY_DEPRECATED(since) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) -#endif - -#if defined(JSON_HEDLEY_UNAVAILABLE) - #undef JSON_HEDLEY_UNAVAILABLE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) -#else - #define JSON_HEDLEY_UNAVAILABLE(available_since) -#endif - -#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) - #undef JSON_HEDLEY_WARN_UNUSED_RESULT -#endif -#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) - #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG -#endif -#if (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) -#elif defined(_Check_return_) /* SAL */ - #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ -#else - #define JSON_HEDLEY_WARN_UNUSED_RESULT - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) -#endif - -#if defined(JSON_HEDLEY_SENTINEL) - #undef JSON_HEDLEY_SENTINEL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) - #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) -#else - #define JSON_HEDLEY_SENTINEL(position) -#endif - -#if defined(JSON_HEDLEY_NO_RETURN) - #undef JSON_HEDLEY_NO_RETURN -#endif -#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_NO_RETURN __noreturn -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) -#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L - #define JSON_HEDLEY_NO_RETURN _Noreturn -#elif defined(__cplusplus) && (__cplusplus >= 201103L) - #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) - #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) - #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") -#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) - #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) - #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) -#else - #define JSON_HEDLEY_NO_RETURN -#endif - -#if defined(JSON_HEDLEY_NO_ESCAPE) - #undef JSON_HEDLEY_NO_ESCAPE -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) - #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) -#else - #define JSON_HEDLEY_NO_ESCAPE -#endif - -#if defined(JSON_HEDLEY_UNREACHABLE) - #undef JSON_HEDLEY_UNREACHABLE -#endif -#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) - #undef JSON_HEDLEY_UNREACHABLE_RETURN -#endif -#if defined(JSON_HEDLEY_ASSUME) - #undef JSON_HEDLEY_ASSUME -#endif -#if \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_ASSUME(expr) __assume(expr) -#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) - #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) -#elif \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) - #if defined(__cplusplus) - #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) - #else - #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) - #endif -#endif -#if \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) - #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() -#elif defined(JSON_HEDLEY_ASSUME) - #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) -#endif -#if !defined(JSON_HEDLEY_ASSUME) - #if defined(JSON_HEDLEY_UNREACHABLE) - #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) - #else - #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) - #endif -#endif -#if defined(JSON_HEDLEY_UNREACHABLE) - #if \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) - #else - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() - #endif -#else - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) -#endif -#if !defined(JSON_HEDLEY_UNREACHABLE) - #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) -#endif - -JSON_HEDLEY_DIAGNOSTIC_PUSH -#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") - #pragma clang diagnostic ignored "-Wpedantic" -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) - #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" -#endif -#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) - #if defined(__clang__) - #pragma clang diagnostic ignored "-Wvariadic-macros" - #elif defined(JSON_HEDLEY_GCC_VERSION) - #pragma GCC diagnostic ignored "-Wvariadic-macros" - #endif -#endif -#if defined(JSON_HEDLEY_NON_NULL) - #undef JSON_HEDLEY_NON_NULL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) - #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) -#else - #define JSON_HEDLEY_NON_NULL(...) -#endif -JSON_HEDLEY_DIAGNOSTIC_POP - -#if defined(JSON_HEDLEY_PRINTF_FORMAT) - #undef JSON_HEDLEY_PRINTF_FORMAT -#endif -#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) -#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) -#else - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) -#endif - -#if defined(JSON_HEDLEY_CONSTEXPR) - #undef JSON_HEDLEY_CONSTEXPR -#endif -#if defined(__cplusplus) - #if __cplusplus >= 201103L - #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) - #endif -#endif -#if !defined(JSON_HEDLEY_CONSTEXPR) - #define JSON_HEDLEY_CONSTEXPR -#endif - -#if defined(JSON_HEDLEY_PREDICT) - #undef JSON_HEDLEY_PREDICT -#endif -#if defined(JSON_HEDLEY_LIKELY) - #undef JSON_HEDLEY_LIKELY -#endif -#if defined(JSON_HEDLEY_UNLIKELY) - #undef JSON_HEDLEY_UNLIKELY -#endif -#if defined(JSON_HEDLEY_UNPREDICTABLE) - #undef JSON_HEDLEY_UNPREDICTABLE -#endif -#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) - #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) -#endif -#if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) -# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) -# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) -# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) -#elif \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) -# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ - (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ - (__extension__ ({ \ - double hedley_probability_ = (probability); \ - ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ - })) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ - (__extension__ ({ \ - double hedley_probability_ = (probability); \ - ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ - })) -# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) -# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) -#else -# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) -# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) -# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) -#endif -#if !defined(JSON_HEDLEY_UNPREDICTABLE) - #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) -#endif - -#if defined(JSON_HEDLEY_MALLOC) - #undef JSON_HEDLEY_MALLOC -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(14, 0, 0) - #define JSON_HEDLEY_MALLOC __declspec(restrict) -#else - #define JSON_HEDLEY_MALLOC -#endif - -#if defined(JSON_HEDLEY_PURE) - #undef JSON_HEDLEY_PURE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) -# define JSON_HEDLEY_PURE __attribute__((__pure__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) -# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") -#elif defined(__cplusplus) && \ - ( \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ - ) -# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") -#else -# define JSON_HEDLEY_PURE -#endif - -#if defined(JSON_HEDLEY_CONST) - #undef JSON_HEDLEY_CONST -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_CONST __attribute__((__const__)) -#elif \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_CONST _Pragma("no_side_effect") -#else - #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE -#endif - -#if defined(JSON_HEDLEY_RESTRICT) - #undef JSON_HEDLEY_RESTRICT -#endif -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) - #define JSON_HEDLEY_RESTRICT restrict -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - defined(__clang__) - #define JSON_HEDLEY_RESTRICT __restrict -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) - #define JSON_HEDLEY_RESTRICT _Restrict -#else - #define JSON_HEDLEY_RESTRICT -#endif - -#if defined(JSON_HEDLEY_INLINE) - #undef JSON_HEDLEY_INLINE -#endif -#if \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ - (defined(__cplusplus) && (__cplusplus >= 199711L)) - #define JSON_HEDLEY_INLINE inline -#elif \ - defined(JSON_HEDLEY_GCC_VERSION) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) - #define JSON_HEDLEY_INLINE __inline__ -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_INLINE __inline -#else - #define JSON_HEDLEY_INLINE -#endif - -#if defined(JSON_HEDLEY_ALWAYS_INLINE) - #undef JSON_HEDLEY_ALWAYS_INLINE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) -# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) -# define JSON_HEDLEY_ALWAYS_INLINE __forceinline -#elif defined(__cplusplus) && \ - ( \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ - ) -# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) -# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") -#else -# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE -#endif - -#if defined(JSON_HEDLEY_NEVER_INLINE) - #undef JSON_HEDLEY_NEVER_INLINE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) - #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") -#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) - #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) - #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) -#else - #define JSON_HEDLEY_NEVER_INLINE -#endif - -#if defined(JSON_HEDLEY_PRIVATE) - #undef JSON_HEDLEY_PRIVATE -#endif -#if defined(JSON_HEDLEY_PUBLIC) - #undef JSON_HEDLEY_PUBLIC -#endif -#if defined(JSON_HEDLEY_IMPORT) - #undef JSON_HEDLEY_IMPORT -#endif -#if defined(_WIN32) || defined(__CYGWIN__) -# define JSON_HEDLEY_PRIVATE -# define JSON_HEDLEY_PUBLIC __declspec(dllexport) -# define JSON_HEDLEY_IMPORT __declspec(dllimport) -#else -# if \ - JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - ( \ - defined(__TI_EABI__) && \ - ( \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ - ) \ - ) -# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) -# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) -# else -# define JSON_HEDLEY_PRIVATE -# define JSON_HEDLEY_PUBLIC -# endif -# define JSON_HEDLEY_IMPORT extern -#endif - -#if defined(JSON_HEDLEY_NO_THROW) - #undef JSON_HEDLEY_NO_THROW -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) - #define JSON_HEDLEY_NO_THROW __declspec(nothrow) -#else - #define JSON_HEDLEY_NO_THROW -#endif - -#if defined(JSON_HEDLEY_FALL_THROUGH) - #undef JSON_HEDLEY_FALL_THROUGH -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) - #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) - #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) - #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) -#elif defined(__fallthrough) /* SAL */ - #define JSON_HEDLEY_FALL_THROUGH __fallthrough -#else - #define JSON_HEDLEY_FALL_THROUGH -#endif - -#if defined(JSON_HEDLEY_RETURNS_NON_NULL) - #undef JSON_HEDLEY_RETURNS_NON_NULL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) - #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) -#elif defined(_Ret_notnull_) /* SAL */ - #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ -#else - #define JSON_HEDLEY_RETURNS_NON_NULL -#endif - -#if defined(JSON_HEDLEY_ARRAY_PARAM) - #undef JSON_HEDLEY_ARRAY_PARAM -#endif -#if \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__STDC_NO_VLA__) && \ - !defined(__cplusplus) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_TINYC_VERSION) - #define JSON_HEDLEY_ARRAY_PARAM(name) (name) -#else - #define JSON_HEDLEY_ARRAY_PARAM(name) -#endif - -#if defined(JSON_HEDLEY_IS_CONSTANT) - #undef JSON_HEDLEY_IS_CONSTANT -#endif -#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) - #undef JSON_HEDLEY_REQUIRE_CONSTEXPR -#endif -/* JSON_HEDLEY_IS_CONSTEXPR_ is for - HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ -#if defined(JSON_HEDLEY_IS_CONSTEXPR_) - #undef JSON_HEDLEY_IS_CONSTEXPR_ -#endif -#if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) - #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) -#endif -#if !defined(__cplusplus) -# if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) -#if defined(__INTPTR_TYPE__) - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) -#else - #include - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) -#endif -# elif \ - ( \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ - !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_IAR_VERSION)) || \ - JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) -#if defined(__INTPTR_TYPE__) - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) -#else - #include - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) -#endif -# elif \ - defined(JSON_HEDLEY_GCC_VERSION) || \ - defined(JSON_HEDLEY_INTEL_VERSION) || \ - defined(JSON_HEDLEY_TINYC_VERSION) || \ - defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ - defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ - defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ - defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ - defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ - defined(__clang__) -# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ - sizeof(void) != \ - sizeof(*( \ - 1 ? \ - ((void*) ((expr) * 0L) ) : \ -((struct { char v[sizeof(void) * 2]; } *) 1) \ - ) \ - ) \ - ) -# endif -#endif -#if defined(JSON_HEDLEY_IS_CONSTEXPR_) - #if !defined(JSON_HEDLEY_IS_CONSTANT) - #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) - #endif - #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) -#else - #if !defined(JSON_HEDLEY_IS_CONSTANT) - #define JSON_HEDLEY_IS_CONSTANT(expr) (0) - #endif - #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) -#endif - -#if defined(JSON_HEDLEY_BEGIN_C_DECLS) - #undef JSON_HEDLEY_BEGIN_C_DECLS -#endif -#if defined(JSON_HEDLEY_END_C_DECLS) - #undef JSON_HEDLEY_END_C_DECLS -#endif -#if defined(JSON_HEDLEY_C_DECL) - #undef JSON_HEDLEY_C_DECL -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { - #define JSON_HEDLEY_END_C_DECLS } - #define JSON_HEDLEY_C_DECL extern "C" -#else - #define JSON_HEDLEY_BEGIN_C_DECLS - #define JSON_HEDLEY_END_C_DECLS - #define JSON_HEDLEY_C_DECL -#endif - -#if defined(JSON_HEDLEY_STATIC_ASSERT) - #undef JSON_HEDLEY_STATIC_ASSERT -#endif -#if \ - !defined(__cplusplus) && ( \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ - JSON_HEDLEY_HAS_FEATURE(c_static_assert) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - defined(_Static_assert) \ - ) -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) -#elif \ - (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ - JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) -#else -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) -#endif - -#if defined(JSON_HEDLEY_NULL) - #undef JSON_HEDLEY_NULL -#endif -#if defined(__cplusplus) - #if __cplusplus >= 201103L - #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) - #elif defined(NULL) - #define JSON_HEDLEY_NULL NULL - #else - #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) - #endif -#elif defined(NULL) - #define JSON_HEDLEY_NULL NULL -#else - #define JSON_HEDLEY_NULL ((void*) 0) -#endif - -#if defined(JSON_HEDLEY_MESSAGE) - #undef JSON_HEDLEY_MESSAGE -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") -# define JSON_HEDLEY_MESSAGE(msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ - JSON_HEDLEY_PRAGMA(message msg) \ - JSON_HEDLEY_DIAGNOSTIC_POP -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) -#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#else -# define JSON_HEDLEY_MESSAGE(msg) -#endif - -#if defined(JSON_HEDLEY_WARNING) - #undef JSON_HEDLEY_WARNING -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") -# define JSON_HEDLEY_WARNING(msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ - JSON_HEDLEY_PRAGMA(clang warning msg) \ - JSON_HEDLEY_DIAGNOSTIC_POP -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#else -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) -#endif - -#if defined(JSON_HEDLEY_REQUIRE) - #undef JSON_HEDLEY_REQUIRE -#endif -#if defined(JSON_HEDLEY_REQUIRE_MSG) - #undef JSON_HEDLEY_REQUIRE_MSG -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) -# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") -# define JSON_HEDLEY_REQUIRE(expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ - __attribute__((diagnose_if(!(expr), #expr, "error"))) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ - __attribute__((diagnose_if(!(expr), msg, "error"))) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) -# endif -#else -# define JSON_HEDLEY_REQUIRE(expr) -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) -#endif - -#if defined(JSON_HEDLEY_FLAGS) - #undef JSON_HEDLEY_FLAGS -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) - #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) -#endif - -#if defined(JSON_HEDLEY_FLAGS_CAST) - #undef JSON_HEDLEY_FLAGS_CAST -#endif -#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) -# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("warning(disable:188)") \ - ((T) (expr)); \ - JSON_HEDLEY_DIAGNOSTIC_POP \ - })) -#else -# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) -#endif - -#if defined(JSON_HEDLEY_EMPTY_BASES) - #undef JSON_HEDLEY_EMPTY_BASES -#endif -#if JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0) - #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) -#else - #define JSON_HEDLEY_EMPTY_BASES -#endif - -/* Remaining macros are deprecated. */ - -#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) - #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK -#endif -#if defined(__clang__) - #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) -#else - #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) - #undef JSON_HEDLEY_CLANG_HAS_BUILTIN -#endif -#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) - -#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) - #undef JSON_HEDLEY_CLANG_HAS_FEATURE -#endif -#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) - -#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) - #undef JSON_HEDLEY_CLANG_HAS_EXTENSION -#endif -#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) - -#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) - #undef JSON_HEDLEY_CLANG_HAS_WARNING -#endif -#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) - -#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ - - -// This file contains all internal macro definitions -// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them - -// exclude unsupported compilers -#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) - #if defined(__clang__) - #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 - #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" - #endif - #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) - #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 - #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" - #endif - #endif -#endif - -// C++ language standard detection -#if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) - #define JSON_HAS_CPP_20 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 -#elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 -#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) - #define JSON_HAS_CPP_14 -#endif - -// disable float-equal warnings on GCC/clang -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wfloat-equal" -#endif - -// disable documentation warnings on clang -#if defined(__clang__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdocumentation" -#endif - -// allow to disable exceptions -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) - #define JSON_THROW(exception) throw exception - #define JSON_TRY try - #define JSON_CATCH(exception) catch(exception) - #define JSON_INTERNAL_CATCH(exception) catch(exception) -#else - #include - #define JSON_THROW(exception) std::abort() - #define JSON_TRY if(true) - #define JSON_CATCH(exception) if(false) - #define JSON_INTERNAL_CATCH(exception) if(false) -#endif - -// override exception macros -#if defined(JSON_THROW_USER) - #undef JSON_THROW - #define JSON_THROW JSON_THROW_USER -#endif -#if defined(JSON_TRY_USER) - #undef JSON_TRY - #define JSON_TRY JSON_TRY_USER -#endif -#if defined(JSON_CATCH_USER) - #undef JSON_CATCH - #define JSON_CATCH JSON_CATCH_USER - #undef JSON_INTERNAL_CATCH - #define JSON_INTERNAL_CATCH JSON_CATCH_USER -#endif -#if defined(JSON_INTERNAL_CATCH_USER) - #undef JSON_INTERNAL_CATCH - #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER -#endif - -// allow to override assert -#if !defined(JSON_ASSERT) - #include // assert - #define JSON_ASSERT(x) assert(x) -#endif - -/*! -@brief macro to briefly define a mapping between an enum and JSON -@def NLOHMANN_JSON_SERIALIZE_ENUM -@since version 3.4.0 -*/ -#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ - template \ - inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ - { \ - static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - static const std::pair m[] = __VA_ARGS__; \ - auto it = std::find_if(std::begin(m), std::end(m), \ - [e](const std::pair& ej_pair) -> bool \ - { \ - return ej_pair.first == e; \ - }); \ - j = ((it != std::end(m)) ? it : std::begin(m))->second; \ - } \ - template \ - inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ - { \ - static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - static const std::pair m[] = __VA_ARGS__; \ - auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ - { \ - return ej_pair.second == j; \ - }); \ - e = ((it != std::end(m)) ? it : std::begin(m))->first; \ - } - -// Ugly macros to avoid uglier copy-paste when specializing basic_json. They -// may be removed in the future once the class is split. - -#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ - template class ObjectType, \ - template class ArrayType, \ - class StringType, class BooleanType, class NumberIntegerType, \ - class NumberUnsignedType, class NumberFloatType, \ - template class AllocatorType, \ - template class JSONSerializer, \ - class BinaryType> - -#define NLOHMANN_BASIC_JSON_TPL \ - basic_json - -// Macros to simplify conversion from/to types - -#define NLOHMANN_JSON_EXPAND( x ) x -#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME -#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ - NLOHMANN_JSON_PASTE64, \ - NLOHMANN_JSON_PASTE63, \ - NLOHMANN_JSON_PASTE62, \ - NLOHMANN_JSON_PASTE61, \ - NLOHMANN_JSON_PASTE60, \ - NLOHMANN_JSON_PASTE59, \ - NLOHMANN_JSON_PASTE58, \ - NLOHMANN_JSON_PASTE57, \ - NLOHMANN_JSON_PASTE56, \ - NLOHMANN_JSON_PASTE55, \ - NLOHMANN_JSON_PASTE54, \ - NLOHMANN_JSON_PASTE53, \ - NLOHMANN_JSON_PASTE52, \ - NLOHMANN_JSON_PASTE51, \ - NLOHMANN_JSON_PASTE50, \ - NLOHMANN_JSON_PASTE49, \ - NLOHMANN_JSON_PASTE48, \ - NLOHMANN_JSON_PASTE47, \ - NLOHMANN_JSON_PASTE46, \ - NLOHMANN_JSON_PASTE45, \ - NLOHMANN_JSON_PASTE44, \ - NLOHMANN_JSON_PASTE43, \ - NLOHMANN_JSON_PASTE42, \ - NLOHMANN_JSON_PASTE41, \ - NLOHMANN_JSON_PASTE40, \ - NLOHMANN_JSON_PASTE39, \ - NLOHMANN_JSON_PASTE38, \ - NLOHMANN_JSON_PASTE37, \ - NLOHMANN_JSON_PASTE36, \ - NLOHMANN_JSON_PASTE35, \ - NLOHMANN_JSON_PASTE34, \ - NLOHMANN_JSON_PASTE33, \ - NLOHMANN_JSON_PASTE32, \ - NLOHMANN_JSON_PASTE31, \ - NLOHMANN_JSON_PASTE30, \ - NLOHMANN_JSON_PASTE29, \ - NLOHMANN_JSON_PASTE28, \ - NLOHMANN_JSON_PASTE27, \ - NLOHMANN_JSON_PASTE26, \ - NLOHMANN_JSON_PASTE25, \ - NLOHMANN_JSON_PASTE24, \ - NLOHMANN_JSON_PASTE23, \ - NLOHMANN_JSON_PASTE22, \ - NLOHMANN_JSON_PASTE21, \ - NLOHMANN_JSON_PASTE20, \ - NLOHMANN_JSON_PASTE19, \ - NLOHMANN_JSON_PASTE18, \ - NLOHMANN_JSON_PASTE17, \ - NLOHMANN_JSON_PASTE16, \ - NLOHMANN_JSON_PASTE15, \ - NLOHMANN_JSON_PASTE14, \ - NLOHMANN_JSON_PASTE13, \ - NLOHMANN_JSON_PASTE12, \ - NLOHMANN_JSON_PASTE11, \ - NLOHMANN_JSON_PASTE10, \ - NLOHMANN_JSON_PASTE9, \ - NLOHMANN_JSON_PASTE8, \ - NLOHMANN_JSON_PASTE7, \ - NLOHMANN_JSON_PASTE6, \ - NLOHMANN_JSON_PASTE5, \ - NLOHMANN_JSON_PASTE4, \ - NLOHMANN_JSON_PASTE3, \ - NLOHMANN_JSON_PASTE2, \ - NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) -#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) -#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) -#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) -#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) -#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) -#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) -#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) -#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) -#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) -#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) -#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) -#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) -#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) -#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) -#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) -#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) -#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) -#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) -#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) -#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) -#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) -#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) -#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) -#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) -#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) -#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) -#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) -#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) -#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) -#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) -#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) -#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) -#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) -#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) -#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) -#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) -#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) -#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) -#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) -#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) -#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) -#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) -#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) -#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) -#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) -#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) -#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) -#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) -#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) -#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) -#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) -#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) -#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) -#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) -#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) -#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) -#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) -#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) -#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) -#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) -#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) -#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) - -#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; -#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_INTRUSIVE -@since version 3.9.0 -*/ -#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ - friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE -@since version 3.9.0 -*/ -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ - inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -#ifndef JSON_USE_IMPLICIT_CONVERSIONS - #define JSON_USE_IMPLICIT_CONVERSIONS 1 -#endif - -#if JSON_USE_IMPLICIT_CONVERSIONS - #define JSON_EXPLICIT -#else - #define JSON_EXPLICIT explicit -#endif - - -namespace nlohmann -{ -namespace detail -{ -//////////////// -// exceptions // -//////////////// - -/*! -@brief general exception of the @ref basic_json class - -This class is an extension of `std::exception` objects with a member @a id for -exception ids. It is used as the base class for all exceptions thrown by the -@ref basic_json class. This class can hence be used as "wildcard" to catch -exceptions. - -Subclasses: -- @ref parse_error for exceptions indicating a parse error -- @ref invalid_iterator for exceptions indicating errors with iterators -- @ref type_error for exceptions indicating executing a member function with - a wrong type -- @ref out_of_range for exceptions indicating access out of the defined range -- @ref other_error for exceptions indicating other library errors - -@internal -@note To have nothrow-copy-constructible exceptions, we internally use - `std::runtime_error` which can cope with arbitrary-length error messages. - Intermediate strings are built with static functions and then passed to - the actual constructor. -@endinternal - -@liveexample{The following code shows how arbitrary library exceptions can be -caught.,exception} - -@since version 3.0.0 -*/ -class exception : public std::exception -{ - public: - /// returns the explanatory string - JSON_HEDLEY_RETURNS_NON_NULL - const char* what() const noexcept override - { - return m.what(); - } - - /// the id of the exception - const int id; - - protected: - JSON_HEDLEY_NON_NULL(3) - exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} - - static std::string name(const std::string& ename, int id_) - { - return "[json.exception." + ename + "." + std::to_string(id_) + "] "; - } - - private: - /// an exception object as storage for error messages - std::runtime_error m; -}; - -/*! -@brief exception indicating a parse error - -This exception is thrown by the library when a parse error occurs. Parse errors -can occur during the deserialization of JSON text, CBOR, MessagePack, as well -as when using JSON Patch. - -Member @a byte holds the byte index of the last read character in the input -file. - -Exceptions have ids 1xx. - -name / id | example message | description ------------------------------- | --------------- | ------------------------- -json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. -json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. -json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. -json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. -json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. -json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. -json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. -json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. -json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. -json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. -json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. -json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. -json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). -json.exception.parse_error.115 | parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A | A UBJSON high-precision number could not be parsed. - -@note For an input with n bytes, 1 is the index of the first character and n+1 - is the index of the terminating null byte or the end of file. This also - holds true when reading a byte vector (CBOR or MessagePack). - -@liveexample{The following code shows how a `parse_error` exception can be -caught.,parse_error} - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref invalid_iterator for exceptions indicating errors with iterators -@sa - @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa - @ref out_of_range for exceptions indicating access out of the defined range -@sa - @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class parse_error : public exception -{ - public: - /*! - @brief create a parse error exception - @param[in] id_ the id of the exception - @param[in] pos the position where the error occurred (or with - chars_read_total=0 if the position cannot be - determined) - @param[in] what_arg the explanatory string - @return parse_error object - */ - static parse_error create(int id_, const position_t& pos, const std::string& what_arg) - { - std::string w = exception::name("parse_error", id_) + "parse error" + - position_string(pos) + ": " + what_arg; - return parse_error(id_, pos.chars_read_total, w.c_str()); - } - - static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) - { - std::string w = exception::name("parse_error", id_) + "parse error" + - (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + - ": " + what_arg; - return parse_error(id_, byte_, w.c_str()); - } - - /*! - @brief byte index of the parse error - - The byte index of the last read character in the input file. - - @note For an input with n bytes, 1 is the index of the first character and - n+1 is the index of the terminating null byte or the end of file. - This also holds true when reading a byte vector (CBOR or MessagePack). - */ - const std::size_t byte; - - private: - parse_error(int id_, std::size_t byte_, const char* what_arg) - : exception(id_, what_arg), byte(byte_) {} - - static std::string position_string(const position_t& pos) - { - return " at line " + std::to_string(pos.lines_read + 1) + - ", column " + std::to_string(pos.chars_read_current_line); - } -}; - -/*! -@brief exception indicating errors with iterators - -This exception is thrown if iterators passed to a library function do not match -the expected semantics. - -Exceptions have ids 2xx. - -name / id | example message | description ------------------------------------ | --------------- | ------------------------- -json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. -json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. -json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. -json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. -json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. -json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. -json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. -json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. -json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. -json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. -json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. -json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. -json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. -json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). - -@liveexample{The following code shows how an `invalid_iterator` exception can be -caught.,invalid_iterator} - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref parse_error for exceptions indicating a parse error -@sa - @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa - @ref out_of_range for exceptions indicating access out of the defined range -@sa - @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class invalid_iterator : public exception -{ - public: - static invalid_iterator create(int id_, const std::string& what_arg) - { - std::string w = exception::name("invalid_iterator", id_) + what_arg; - return invalid_iterator(id_, w.c_str()); - } - - private: - JSON_HEDLEY_NON_NULL(3) - invalid_iterator(int id_, const char* what_arg) - : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating executing a member function with a wrong type - -This exception is thrown in case of a type error; that is, a library function is -executed on a JSON value whose type does not match the expected semantics. - -Exceptions have ids 3xx. - -name / id | example message | description ------------------------------ | --------------- | ------------------------- -json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. -json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. -json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t &. -json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. -json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. -json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. -json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. -json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. -json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. -json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. -json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. -json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. -json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. -json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. -json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. -json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | -json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) | - -@liveexample{The following code shows how a `type_error` exception can be -caught.,type_error} - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref parse_error for exceptions indicating a parse error -@sa - @ref invalid_iterator for exceptions indicating errors with iterators -@sa - @ref out_of_range for exceptions indicating access out of the defined range -@sa - @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class type_error : public exception -{ - public: - static type_error create(int id_, const std::string& what_arg) - { - std::string w = exception::name("type_error", id_) + what_arg; - return type_error(id_, w.c_str()); - } - - private: - JSON_HEDLEY_NON_NULL(3) - type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating access out of the defined range - -This exception is thrown in case a library function is called on an input -parameter that exceeds the expected range, for instance in case of array -indices or nonexisting object keys. - -Exceptions have ids 4xx. - -name / id | example message | description -------------------------------- | --------------- | ------------------------- -json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. -json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. -json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. -json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. -json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. -json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. -json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. (until version 3.8.0) | -json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | -json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | - -@liveexample{The following code shows how an `out_of_range` exception can be -caught.,out_of_range} - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref parse_error for exceptions indicating a parse error -@sa - @ref invalid_iterator for exceptions indicating errors with iterators -@sa - @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa - @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class out_of_range : public exception -{ - public: - static out_of_range create(int id_, const std::string& what_arg) - { - std::string w = exception::name("out_of_range", id_) + what_arg; - return out_of_range(id_, w.c_str()); - } - - private: - JSON_HEDLEY_NON_NULL(3) - out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating other library errors - -This exception is thrown in case of errors that cannot be classified with the -other exception types. - -Exceptions have ids 5xx. - -name / id | example message | description ------------------------------- | --------------- | ------------------------- -json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. - -@sa - @ref exception for the base class of the library exceptions -@sa - @ref parse_error for exceptions indicating a parse error -@sa - @ref invalid_iterator for exceptions indicating errors with iterators -@sa - @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa - @ref out_of_range for exceptions indicating access out of the defined range - -@liveexample{The following code shows how an `other_error` exception can be -caught.,other_error} - -@since version 3.0.0 -*/ -class other_error : public exception -{ - public: - static other_error create(int id_, const std::string& what_arg) - { - std::string w = exception::name("other_error", id_) + what_arg; - return other_error(id_, w.c_str()); - } - - private: - JSON_HEDLEY_NON_NULL(3) - other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - - -#include // size_t -#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type - -namespace nlohmann -{ -namespace detail -{ -// alias templates to reduce boilerplate -template -using enable_if_t = typename std::enable_if::type; - -template -using uncvref_t = typename std::remove_cv::type>::type; - -// implementation of C++14 index_sequence and affiliates -// source: https://stackoverflow.com/a/32223343 -template -struct index_sequence -{ - using type = index_sequence; - using value_type = std::size_t; - static constexpr std::size_t size() noexcept - { - return sizeof...(Ints); - } -}; - -template -struct merge_and_renumber; - -template -struct merge_and_renumber, index_sequence> - : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; - -template -struct make_index_sequence - : merge_and_renumber < typename make_index_sequence < N / 2 >::type, - typename make_index_sequence < N - N / 2 >::type > {}; - -template<> struct make_index_sequence<0> : index_sequence<> {}; -template<> struct make_index_sequence<1> : index_sequence<0> {}; - -template -using index_sequence_for = make_index_sequence; - -// dispatch utility (taken from ranges-v3) -template struct priority_tag : priority_tag < N - 1 > {}; -template<> struct priority_tag<0> {}; - -// taken from ranges-v3 -template -struct static_const -{ - static constexpr T value{}; -}; - -template -constexpr T static_const::value; -} // namespace detail -} // namespace nlohmann - -// #include - - -#include // numeric_limits -#include // false_type, is_constructible, is_integral, is_same, true_type -#include // declval - -// #include - - -#include // random_access_iterator_tag - -// #include - - -namespace nlohmann -{ -namespace detail -{ -template struct make_void -{ - using type = void; -}; -template using void_t = typename make_void::type; -} // namespace detail -} // namespace nlohmann - -// #include - - -namespace nlohmann -{ -namespace detail -{ -template -struct iterator_types {}; - -template -struct iterator_types < - It, - void_t> -{ - using difference_type = typename It::difference_type; - using value_type = typename It::value_type; - using pointer = typename It::pointer; - using reference = typename It::reference; - using iterator_category = typename It::iterator_category; -}; - -// This is required as some compilers implement std::iterator_traits in a way that -// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. -template -struct iterator_traits -{ -}; - -template -struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> - : iterator_types -{ -}; - -template -struct iterator_traits::value>> -{ - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using difference_type = ptrdiff_t; - using pointer = T*; - using reference = T&; -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - -// #include - - -#include - -// #include - - -// https://en.cppreference.com/w/cpp/experimental/is_detected -namespace nlohmann -{ -namespace detail -{ -struct nonesuch -{ - nonesuch() = delete; - ~nonesuch() = delete; - nonesuch(nonesuch const&) = delete; - nonesuch(nonesuch const&&) = delete; - void operator=(nonesuch const&) = delete; - void operator=(nonesuch&&) = delete; -}; - -template class Op, - class... Args> -struct detector -{ - using value_t = std::false_type; - using type = Default; -}; - -template class Op, class... Args> -struct detector>, Op, Args...> -{ - using value_t = std::true_type; - using type = Op; -}; - -template class Op, class... Args> -using is_detected = typename detector::value_t; - -template class Op, class... Args> -using detected_t = typename detector::type; - -template class Op, class... Args> -using detected_or = detector; - -template class Op, class... Args> -using detected_or_t = typename detected_or::type; - -template class Op, class... Args> -using is_detected_exact = std::is_same>; - -template class Op, class... Args> -using is_detected_convertible = - std::is_convertible, To>; -} // namespace detail -} // namespace nlohmann - -// #include -#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ -#define INCLUDE_NLOHMANN_JSON_FWD_HPP_ - -#include // int64_t, uint64_t -#include // map -#include // allocator -#include // string -#include // vector - -/*! -@brief namespace for Niels Lohmann -@see https://github.com/nlohmann -@since version 1.0.0 -*/ -namespace nlohmann -{ -/*! -@brief default JSONSerializer template argument - -This serializer ignores the template arguments and uses ADL -([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) -for serialization. -*/ -template -struct adl_serializer; - -template class ObjectType = - std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = - adl_serializer, - class BinaryType = std::vector> -class basic_json; - -/*! -@brief JSON Pointer - -A JSON pointer defines a string syntax for identifying a specific value -within a JSON document. It can be used with functions `at` and -`operator[]`. Furthermore, JSON pointers are the base for JSON patches. - -@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) - -@since version 2.0.0 -*/ -template -class json_pointer; - -/*! -@brief default JSON class - -This type is the default specialization of the @ref basic_json class which -uses the standard template types. - -@since version 1.0.0 -*/ -using json = basic_json<>; - -template -struct ordered_map; - -/*! -@brief ordered JSON class - -This type preserves the insertion order of object keys. - -@since version 3.9.0 -*/ -using ordered_json = basic_json; - -} // namespace nlohmann - -#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ - - -namespace nlohmann -{ -/*! -@brief detail namespace with internal helper functions - -This namespace collects functions that should not be exposed, -implementations of some @ref basic_json methods, and meta-programming helpers. - -@since version 2.1.0 -*/ -namespace detail -{ -///////////// -// helpers // -///////////// - -// Note to maintainers: -// -// Every trait in this file expects a non CV-qualified type. -// The only exceptions are in the 'aliases for detected' section -// (i.e. those of the form: decltype(T::member_function(std::declval()))) -// -// In this case, T has to be properly CV-qualified to constraint the function arguments -// (e.g. to_json(BasicJsonType&, const T&)) - -template struct is_basic_json : std::false_type {}; - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -struct is_basic_json : std::true_type {}; - -////////////////////// -// json_ref helpers // -////////////////////// - -template -class json_ref; - -template -struct is_json_ref : std::false_type {}; - -template -struct is_json_ref> : std::true_type {}; - -////////////////////////// -// aliases for detected // -////////////////////////// - -template -using mapped_type_t = typename T::mapped_type; - -template -using key_type_t = typename T::key_type; - -template -using value_type_t = typename T::value_type; - -template -using difference_type_t = typename T::difference_type; - -template -using pointer_t = typename T::pointer; - -template -using reference_t = typename T::reference; - -template -using iterator_category_t = typename T::iterator_category; - -template -using iterator_t = typename T::iterator; - -template -using to_json_function = decltype(T::to_json(std::declval()...)); - -template -using from_json_function = decltype(T::from_json(std::declval()...)); - -template -using get_template_function = decltype(std::declval().template get()); - -// trait checking if JSONSerializer::from_json(json const&, udt&) exists -template -struct has_from_json : std::false_type {}; - -// trait checking if j.get is valid -// use this trait instead of std::is_constructible or std::is_convertible, -// both rely on, or make use of implicit conversions, and thus fail when T -// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) -template -struct is_getable -{ - static constexpr bool value = is_detected::value; -}; - -template -struct has_from_json < BasicJsonType, T, - enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if JSONSerializer::from_json(json const&) exists -// this overload is used for non-default-constructible user-defined-types -template -struct has_non_default_from_json : std::false_type {}; - -template -struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if BasicJsonType::json_serializer::to_json exists -// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. -template -struct has_to_json : std::false_type {}; - -template -struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - - -/////////////////// -// is_ functions // -/////////////////// - -template -struct is_iterator_traits : std::false_type {}; - -template -struct is_iterator_traits> -{ - private: - using traits = iterator_traits; - - public: - static constexpr auto value = - is_detected::value && - is_detected::value && - is_detected::value && - is_detected::value && - is_detected::value; -}; - -// source: https://stackoverflow.com/a/37193089/4116453 - -template -struct is_complete_type : std::false_type {}; - -template -struct is_complete_type : std::true_type {}; - -template -struct is_compatible_object_type_impl : std::false_type {}; - -template -struct is_compatible_object_type_impl < - BasicJsonType, CompatibleObjectType, - enable_if_t < is_detected::value&& - is_detected::value >> -{ - - using object_t = typename BasicJsonType::object_t; - - // macOS's is_constructible does not play well with nonesuch... - static constexpr bool value = - std::is_constructible::value && - std::is_constructible::value; -}; - -template -struct is_compatible_object_type - : is_compatible_object_type_impl {}; - -template -struct is_constructible_object_type_impl : std::false_type {}; - -template -struct is_constructible_object_type_impl < - BasicJsonType, ConstructibleObjectType, - enable_if_t < is_detected::value&& - is_detected::value >> -{ - using object_t = typename BasicJsonType::object_t; - - static constexpr bool value = - (std::is_default_constructible::value && - (std::is_move_assignable::value || - std::is_copy_assignable::value) && - (std::is_constructible::value && - std::is_same < - typename object_t::mapped_type, - typename ConstructibleObjectType::mapped_type >::value)) || - (has_from_json::value || - has_non_default_from_json < - BasicJsonType, - typename ConstructibleObjectType::mapped_type >::value); -}; - -template -struct is_constructible_object_type - : is_constructible_object_type_impl {}; - -template -struct is_compatible_string_type_impl : std::false_type {}; - -template -struct is_compatible_string_type_impl < - BasicJsonType, CompatibleStringType, - enable_if_t::value >> -{ - static constexpr auto value = - std::is_constructible::value; -}; - -template -struct is_compatible_string_type - : is_compatible_string_type_impl {}; - -template -struct is_constructible_string_type_impl : std::false_type {}; - -template -struct is_constructible_string_type_impl < - BasicJsonType, ConstructibleStringType, - enable_if_t::value >> -{ - static constexpr auto value = - std::is_constructible::value; -}; - -template -struct is_constructible_string_type - : is_constructible_string_type_impl {}; - -template -struct is_compatible_array_type_impl : std::false_type {}; - -template -struct is_compatible_array_type_impl < - BasicJsonType, CompatibleArrayType, - enable_if_t < is_detected::value&& - is_detected::value&& -// This is needed because json_reverse_iterator has a ::iterator type... -// Therefore it is detected as a CompatibleArrayType. -// The real fix would be to have an Iterable concept. - !is_iterator_traits < - iterator_traits>::value >> -{ - static constexpr bool value = - std::is_constructible::value; -}; - -template -struct is_compatible_array_type - : is_compatible_array_type_impl {}; - -template -struct is_constructible_array_type_impl : std::false_type {}; - -template -struct is_constructible_array_type_impl < - BasicJsonType, ConstructibleArrayType, - enable_if_t::value >> - : std::true_type {}; - -template -struct is_constructible_array_type_impl < - BasicJsonType, ConstructibleArrayType, - enable_if_t < !std::is_same::value&& - std::is_default_constructible::value&& -(std::is_move_assignable::value || - std::is_copy_assignable::value)&& -is_detected::value&& -is_detected::value&& -is_complete_type < -detected_t>::value >> -{ - static constexpr bool value = - // This is needed because json_reverse_iterator has a ::iterator type, - // furthermore, std::back_insert_iterator (and other iterators) have a - // base class `iterator`... Therefore it is detected as a - // ConstructibleArrayType. The real fix would be to have an Iterable - // concept. - !is_iterator_traits>::value && - - (std::is_same::value || - has_from_json::value || - has_non_default_from_json < - BasicJsonType, typename ConstructibleArrayType::value_type >::value); -}; - -template -struct is_constructible_array_type - : is_constructible_array_type_impl {}; - -template -struct is_compatible_integer_type_impl : std::false_type {}; - -template -struct is_compatible_integer_type_impl < - RealIntegerType, CompatibleNumberIntegerType, - enable_if_t < std::is_integral::value&& - std::is_integral::value&& - !std::is_same::value >> -{ - // is there an assert somewhere on overflows? - using RealLimits = std::numeric_limits; - using CompatibleLimits = std::numeric_limits; - - static constexpr auto value = - std::is_constructible::value && - CompatibleLimits::is_integer && - RealLimits::is_signed == CompatibleLimits::is_signed; -}; - -template -struct is_compatible_integer_type - : is_compatible_integer_type_impl {}; - -template -struct is_compatible_type_impl: std::false_type {}; - -template -struct is_compatible_type_impl < - BasicJsonType, CompatibleType, - enable_if_t::value >> -{ - static constexpr bool value = - has_to_json::value; -}; - -template -struct is_compatible_type - : is_compatible_type_impl {}; - -// https://en.cppreference.com/w/cpp/types/conjunction -template struct conjunction : std::true_type { }; -template struct conjunction : B1 { }; -template -struct conjunction -: std::conditional, B1>::type {}; - -template -struct is_constructible_tuple : std::false_type {}; - -template -struct is_constructible_tuple> : conjunction...> {}; -} // namespace detail -} // namespace nlohmann - -// #include - - -#include // array -#include // size_t -#include // uint8_t -#include // string - -namespace nlohmann -{ -namespace detail -{ -/////////////////////////// -// JSON type enumeration // -/////////////////////////// - -/*! -@brief the JSON type enumeration - -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. - -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. - -@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type - -@since version 1.0.0 -*/ -enum class value_t : std::uint8_t -{ - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - binary, ///< binary array (ordered collection of bytes) - discarded ///< discarded by the parser callback function -}; - -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string < binary -- furthermore, each type is not smaller than itself -- discarded values are not comparable -- binary is represented as a b"" string in python and directly comparable to a - string; however, making a binary array directly comparable with a string would - be surprising behavior in a JSON file. - -@since version 1.0.0 -*/ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept -{ - static constexpr std::array order = {{ - 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, - 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, - 6 /* binary */ - } - }; - - const auto l_index = static_cast(lhs); - const auto r_index = static_cast(rhs); - return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; -} -} // namespace detail -} // namespace nlohmann - - -namespace nlohmann -{ -namespace detail -{ -template -void from_json(const BasicJsonType& j, typename std::nullptr_t& n) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_null())) - { - JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name()))); - } - n = nullptr; -} - -// overloads for basic_json template parameters -template < typename BasicJsonType, typename ArithmeticType, - enable_if_t < std::is_arithmetic::value&& - !std::is_same::value, - int > = 0 > -void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) -{ - switch (static_cast(j)) - { - case value_t::number_unsigned: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_integer: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_float: - { - val = static_cast(*j.template get_ptr()); - break; - } - - default: - JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); - } -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) - { - JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); - } - b = *j.template get_ptr(); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_string())) - { - JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); - } - s = *j.template get_ptr(); -} - -template < - typename BasicJsonType, typename ConstructibleStringType, - enable_if_t < - is_constructible_string_type::value&& - !std::is_same::value, - int > = 0 > -void from_json(const BasicJsonType& j, ConstructibleStringType& s) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_string())) - { - JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); - } - - s = *j.template get_ptr(); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) -{ - get_arithmetic_value(j, val); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) -{ - get_arithmetic_value(j, val); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) -{ - get_arithmetic_value(j, val); -} - -template::value, int> = 0> -void from_json(const BasicJsonType& j, EnumType& e) -{ - typename std::underlying_type::type val; - get_arithmetic_value(j, val); - e = static_cast(val); -} - -// forward_list doesn't have an insert method -template::value, int> = 0> -void from_json(const BasicJsonType& j, std::forward_list& l) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - l.clear(); - std::transform(j.rbegin(), j.rend(), - std::front_inserter(l), [](const BasicJsonType & i) - { - return i.template get(); - }); -} - -// valarray doesn't have an insert method -template::value, int> = 0> -void from_json(const BasicJsonType& j, std::valarray& l) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - l.resize(j.size()); - std::transform(j.begin(), j.end(), std::begin(l), - [](const BasicJsonType & elem) - { - return elem.template get(); - }); -} - -template -auto from_json(const BasicJsonType& j, T (&arr)[N]) --> decltype(j.template get(), void()) -{ - for (std::size_t i = 0; i < N; ++i) - { - arr[i] = j.at(i).template get(); - } -} - -template -void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) -{ - arr = *j.template get_ptr(); -} - -template -auto from_json_array_impl(const BasicJsonType& j, std::array& arr, - priority_tag<2> /*unused*/) --> decltype(j.template get(), void()) -{ - for (std::size_t i = 0; i < N; ++i) - { - arr[i] = j.at(i).template get(); - } -} - -template -auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) --> decltype( - arr.reserve(std::declval()), - j.template get(), - void()) -{ - using std::end; - - ConstructibleArrayType ret; - ret.reserve(j.size()); - std::transform(j.begin(), j.end(), - std::inserter(ret, end(ret)), [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); - arr = std::move(ret); -} - -template -void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, - priority_tag<0> /*unused*/) -{ - using std::end; - - ConstructibleArrayType ret; - std::transform( - j.begin(), j.end(), std::inserter(ret, end(ret)), - [](const BasicJsonType & i) - { - // get() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get(); - }); - arr = std::move(ret); -} - -template < typename BasicJsonType, typename ConstructibleArrayType, - enable_if_t < - is_constructible_array_type::value&& - !is_constructible_object_type::value&& - !is_constructible_string_type::value&& - !std::is_same::value&& - !is_basic_json::value, - int > = 0 > -auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) --> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), -j.template get(), -void()) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + - std::string(j.type_name()))); - } - - from_json_array_impl(j, arr, priority_tag<3> {}); -} - -template -void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) - { - JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(j.type_name()))); - } - - bin = *j.template get_ptr(); -} - -template::value, int> = 0> -void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_object())) - { - JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); - } - - ConstructibleObjectType ret; - auto inner_object = j.template get_ptr(); - using value_type = typename ConstructibleObjectType::value_type; - std::transform( - inner_object->begin(), inner_object->end(), - std::inserter(ret, ret.begin()), - [](typename BasicJsonType::object_t::value_type const & p) - { - return value_type(p.first, p.second.template get()); - }); - obj = std::move(ret); -} - -// overload for arithmetic types, not chosen for basic_json template arguments -// (BooleanType, etc..); note: Is it really necessary to provide explicit -// overloads for boolean_t etc. in case of a custom BooleanType which is not -// an arithmetic type? -template < typename BasicJsonType, typename ArithmeticType, - enable_if_t < - std::is_arithmetic::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value, - int > = 0 > -void from_json(const BasicJsonType& j, ArithmeticType& val) -{ - switch (static_cast(j)) - { - case value_t::number_unsigned: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_integer: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::number_float: - { - val = static_cast(*j.template get_ptr()); - break; - } - case value_t::boolean: - { - val = static_cast(*j.template get_ptr()); - break; - } - - default: - JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); - } -} - -template -void from_json(const BasicJsonType& j, std::pair& p) -{ - p = {j.at(0).template get(), j.at(1).template get()}; -} - -template -void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence /*unused*/) -{ - t = std::make_tuple(j.at(Idx).template get::type>()...); -} - -template -void from_json(const BasicJsonType& j, std::tuple& t) -{ - from_json_tuple_impl(j, t, index_sequence_for {}); -} - -template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, - typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::map& m) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - m.clear(); - for (const auto& p : j) - { - if (JSON_HEDLEY_UNLIKELY(!p.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); - } - m.emplace(p.at(0).template get(), p.at(1).template get()); - } -} - -template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, - typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::unordered_map& m) -{ - if (JSON_HEDLEY_UNLIKELY(!j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - m.clear(); - for (const auto& p : j) - { - if (JSON_HEDLEY_UNLIKELY(!p.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name()))); - } - m.emplace(p.at(0).template get(), p.at(1).template get()); - } -} - -struct from_json_fn -{ - template - auto operator()(const BasicJsonType& j, T& val) const - noexcept(noexcept(from_json(j, val))) - -> decltype(from_json(j, val), void()) - { - return from_json(j, val); - } -}; -} // namespace detail - -/// namespace to hold default `from_json` function -/// to see why this is required: -/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html -namespace -{ -constexpr const auto& from_json = detail::static_const::value; -} // namespace -} // namespace nlohmann - -// #include - - -#include // copy -#include // begin, end -#include // string -#include // tuple, get -#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type -#include // move, forward, declval, pair -#include // valarray -#include // vector - -// #include - - -#include // size_t -#include // input_iterator_tag -#include // string, to_string -#include // tuple_size, get, tuple_element - -// #include - -// #include - - -namespace nlohmann -{ -namespace detail -{ -template -void int_to_string( string_type& target, std::size_t value ) -{ - // For ADL - using std::to_string; - target = to_string(value); -} -template class iteration_proxy_value -{ - public: - using difference_type = std::ptrdiff_t; - using value_type = iteration_proxy_value; - using pointer = value_type * ; - using reference = value_type & ; - using iterator_category = std::input_iterator_tag; - using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; - - private: - /// the iterator - IteratorType anchor; - /// an index for arrays (used to create key names) - std::size_t array_index = 0; - /// last stringified array index - mutable std::size_t array_index_last = 0; - /// a string representation of the array index - mutable string_type array_index_str = "0"; - /// an empty string (to return a reference for primitive values) - const string_type empty_str = ""; - - public: - explicit iteration_proxy_value(IteratorType it) noexcept : anchor(it) {} - - /// dereference operator (needed for range-based for) - iteration_proxy_value& operator*() - { - return *this; - } - - /// increment operator (needed for range-based for) - iteration_proxy_value& operator++() - { - ++anchor; - ++array_index; - - return *this; - } - - /// equality operator (needed for InputIterator) - bool operator==(const iteration_proxy_value& o) const - { - return anchor == o.anchor; - } - - /// inequality operator (needed for range-based for) - bool operator!=(const iteration_proxy_value& o) const - { - return anchor != o.anchor; - } - - /// return key of the iterator - const string_type& key() const - { - JSON_ASSERT(anchor.m_object != nullptr); - - switch (anchor.m_object->type()) - { - // use integer array index as key - case value_t::array: - { - if (array_index != array_index_last) - { - int_to_string( array_index_str, array_index ); - array_index_last = array_index; - } - return array_index_str; - } - - // use key from the object - case value_t::object: - return anchor.key(); - - // use an empty key for all primitive types - default: - return empty_str; - } - } - - /// return value of the iterator - typename IteratorType::reference value() const - { - return anchor.value(); - } -}; - -/// proxy class for the items() function -template class iteration_proxy -{ - private: - /// the container to iterate - typename IteratorType::reference container; - - public: - /// construct iteration proxy from a container - explicit iteration_proxy(typename IteratorType::reference cont) noexcept - : container(cont) {} - - /// return iterator begin (needed for range-based for) - iteration_proxy_value begin() noexcept - { - return iteration_proxy_value(container.begin()); - } - - /// return iterator end (needed for range-based for) - iteration_proxy_value end() noexcept - { - return iteration_proxy_value(container.end()); - } -}; -// Structured Bindings Support -// For further reference see https://blog.tartanllama.xyz/structured-bindings/ -// And see https://github.com/nlohmann/json/pull/1391 -template = 0> -auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.key()) -{ - return i.key(); -} -// Structured Bindings Support -// For further reference see https://blog.tartanllama.xyz/structured-bindings/ -// And see https://github.com/nlohmann/json/pull/1391 -template = 0> -auto get(const nlohmann::detail::iteration_proxy_value& i) -> decltype(i.value()) -{ - return i.value(); -} -} // namespace detail -} // namespace nlohmann - -// The Addition to the STD Namespace is required to add -// Structured Bindings Support to the iteration_proxy_value class -// For further reference see https://blog.tartanllama.xyz/structured-bindings/ -// And see https://github.com/nlohmann/json/pull/1391 -namespace std -{ -#if defined(__clang__) - // Fix: https://github.com/nlohmann/json/issues/1401 - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wmismatched-tags" -#endif -template -class tuple_size<::nlohmann::detail::iteration_proxy_value> - : public std::integral_constant {}; - -template -class tuple_element> -{ - public: - using type = decltype( - get(std::declval < - ::nlohmann::detail::iteration_proxy_value> ())); -}; -#if defined(__clang__) - #pragma clang diagnostic pop -#endif -} // namespace std - -// #include - -// #include - -// #include - - -namespace nlohmann -{ -namespace detail -{ -////////////////// -// constructors // -////////////////// - -template struct external_constructor; - -template<> -struct external_constructor -{ - template - static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept - { - j.m_type = value_t::boolean; - j.m_value = b; - j.assert_invariant(); - } -}; - -template<> -struct external_constructor -{ - template - static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) - { - j.m_type = value_t::string; - j.m_value = s; - j.assert_invariant(); - } - - template - static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) - { - j.m_type = value_t::string; - j.m_value = std::move(s); - j.assert_invariant(); - } - - template < typename BasicJsonType, typename CompatibleStringType, - enable_if_t < !std::is_same::value, - int > = 0 > - static void construct(BasicJsonType& j, const CompatibleStringType& str) - { - j.m_type = value_t::string; - j.m_value.string = j.template create(str); - j.assert_invariant(); - } -}; - -template<> -struct external_constructor -{ - template - static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) - { - j.m_type = value_t::binary; - typename BasicJsonType::binary_t value{b}; - j.m_value = value; - j.assert_invariant(); - } - - template - static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) - { - j.m_type = value_t::binary; - typename BasicJsonType::binary_t value{std::move(b)}; - j.m_value = value; - j.assert_invariant(); - } -}; - -template<> -struct external_constructor -{ - template - static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept - { - j.m_type = value_t::number_float; - j.m_value = val; - j.assert_invariant(); - } -}; - -template<> -struct external_constructor -{ - template - static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept - { - j.m_type = value_t::number_unsigned; - j.m_value = val; - j.assert_invariant(); - } -}; - -template<> -struct external_constructor -{ - template - static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept - { - j.m_type = value_t::number_integer; - j.m_value = val; - j.assert_invariant(); - } -}; - -template<> -struct external_constructor -{ - template - static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) - { - j.m_type = value_t::array; - j.m_value = arr; - j.assert_invariant(); - } - - template - static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) - { - j.m_type = value_t::array; - j.m_value = std::move(arr); - j.assert_invariant(); - } - - template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < !std::is_same::value, - int > = 0 > - static void construct(BasicJsonType& j, const CompatibleArrayType& arr) - { - using std::begin; - using std::end; - j.m_type = value_t::array; - j.m_value.array = j.template create(begin(arr), end(arr)); - j.assert_invariant(); - } - - template - static void construct(BasicJsonType& j, const std::vector& arr) - { - j.m_type = value_t::array; - j.m_value = value_t::array; - j.m_value.array->reserve(arr.size()); - for (const bool x : arr) - { - j.m_value.array->push_back(x); - } - j.assert_invariant(); - } - - template::value, int> = 0> - static void construct(BasicJsonType& j, const std::valarray& arr) - { - j.m_type = value_t::array; - j.m_value = value_t::array; - j.m_value.array->resize(arr.size()); - if (arr.size() > 0) - { - std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); - } - j.assert_invariant(); - } -}; - -template<> -struct external_constructor -{ - template - static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) - { - j.m_type = value_t::object; - j.m_value = obj; - j.assert_invariant(); - } - - template - static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) - { - j.m_type = value_t::object; - j.m_value = std::move(obj); - j.assert_invariant(); - } - - template < typename BasicJsonType, typename CompatibleObjectType, - enable_if_t < !std::is_same::value, int > = 0 > - static void construct(BasicJsonType& j, const CompatibleObjectType& obj) - { - using std::begin; - using std::end; - - j.m_type = value_t::object; - j.m_value.object = j.template create(begin(obj), end(obj)); - j.assert_invariant(); - } -}; - -///////////// -// to_json // -///////////// - -template::value, int> = 0> -void to_json(BasicJsonType& j, T b) noexcept -{ - external_constructor::construct(j, b); -} - -template::value, int> = 0> -void to_json(BasicJsonType& j, const CompatibleString& s) -{ - external_constructor::construct(j, s); -} - -template -void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) -{ - external_constructor::construct(j, std::move(s)); -} - -template::value, int> = 0> -void to_json(BasicJsonType& j, FloatType val) noexcept -{ - external_constructor::construct(j, static_cast(val)); -} - -template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept -{ - external_constructor::construct(j, static_cast(val)); -} - -template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept -{ - external_constructor::construct(j, static_cast(val)); -} - -template::value, int> = 0> -void to_json(BasicJsonType& j, EnumType e) noexcept -{ - using underlying_type = typename std::underlying_type::type; - external_constructor::construct(j, static_cast(e)); -} - -template -void to_json(BasicJsonType& j, const std::vector& e) -{ - external_constructor::construct(j, e); -} - -template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < is_compatible_array_type::value&& - !is_compatible_object_type::value&& - !is_compatible_string_type::value&& - !std::is_same::value&& - !is_basic_json::value, - int > = 0 > -void to_json(BasicJsonType& j, const CompatibleArrayType& arr) -{ - external_constructor::construct(j, arr); -} - -template -void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) -{ - external_constructor::construct(j, bin); -} - -template::value, int> = 0> -void to_json(BasicJsonType& j, const std::valarray& arr) -{ - external_constructor::construct(j, std::move(arr)); -} - -template -void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) -{ - external_constructor::construct(j, std::move(arr)); -} - -template < typename BasicJsonType, typename CompatibleObjectType, - enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > -void to_json(BasicJsonType& j, const CompatibleObjectType& obj) -{ - external_constructor::construct(j, obj); -} - -template -void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) -{ - external_constructor::construct(j, std::move(obj)); -} - -template < - typename BasicJsonType, typename T, std::size_t N, - enable_if_t < !std::is_constructible::value, - int > = 0 > -void to_json(BasicJsonType& j, const T(&arr)[N]) -{ - external_constructor::construct(j, arr); -} - -template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > -void to_json(BasicJsonType& j, const std::pair& p) -{ - j = { p.first, p.second }; -} - -// for https://github.com/nlohmann/json/pull/1134 -template>::value, int> = 0> -void to_json(BasicJsonType& j, const T& b) -{ - j = { {b.key(), b.value()} }; -} - -template -void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) -{ - j = { std::get(t)... }; -} - -template::value, int > = 0> -void to_json(BasicJsonType& j, const T& t) -{ - to_json_tuple_impl(j, t, make_index_sequence::value> {}); -} - -struct to_json_fn -{ - template - auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward(val)))) - -> decltype(to_json(j, std::forward(val)), void()) - { - return to_json(j, std::forward(val)); - } -}; -} // namespace detail - -/// namespace to hold default `to_json` function -namespace -{ -constexpr const auto& to_json = detail::static_const::value; -} // namespace -} // namespace nlohmann - - -namespace nlohmann -{ - -template -struct adl_serializer -{ - /*! - @brief convert a JSON value to any value type - - This function is usually called by the `get()` function of the - @ref basic_json class (either explicit or via conversion operators). - - @param[in] j JSON value to read from - @param[in,out] val value to write to - */ - template - static auto from_json(BasicJsonType&& j, ValueType& val) noexcept( - noexcept(::nlohmann::from_json(std::forward(j), val))) - -> decltype(::nlohmann::from_json(std::forward(j), val), void()) - { - ::nlohmann::from_json(std::forward(j), val); - } - - /*! - @brief convert any value type to a JSON value - - This function is usually called by the constructors of the @ref basic_json - class. - - @param[in,out] j JSON value to write to - @param[in] val value to read from - */ - template - static auto to_json(BasicJsonType& j, ValueType&& val) noexcept( - noexcept(::nlohmann::to_json(j, std::forward(val)))) - -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) - { - ::nlohmann::to_json(j, std::forward(val)); - } -}; - -} // namespace nlohmann - -// #include - - -#include // uint8_t -#include // tie -#include // move - -namespace nlohmann -{ - -/*! -@brief an internal type for a backed binary type - -This type extends the template parameter @a BinaryType provided to `basic_json` -with a subtype used by BSON and MessagePack. This type exists so that the user -does not have to specify a type themselves with a specific naming scheme in -order to override the binary type. - -@tparam BinaryType container to store bytes (`std::vector` by - default) - -@since version 3.8.0 -*/ -template -class byte_container_with_subtype : public BinaryType -{ - public: - /// the type of the underlying container - using container_type = BinaryType; - - byte_container_with_subtype() noexcept(noexcept(container_type())) - : container_type() - {} - - byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b))) - : container_type(b) - {} - - byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b)))) - : container_type(std::move(b)) - {} - - byte_container_with_subtype(const container_type& b, std::uint8_t subtype) noexcept(noexcept(container_type(b))) - : container_type(b) - , m_subtype(subtype) - , m_has_subtype(true) - {} - - byte_container_with_subtype(container_type&& b, std::uint8_t subtype) noexcept(noexcept(container_type(std::move(b)))) - : container_type(std::move(b)) - , m_subtype(subtype) - , m_has_subtype(true) - {} - - bool operator==(const byte_container_with_subtype& rhs) const - { - return std::tie(static_cast(*this), m_subtype, m_has_subtype) == - std::tie(static_cast(rhs), rhs.m_subtype, rhs.m_has_subtype); - } - - bool operator!=(const byte_container_with_subtype& rhs) const - { - return !(rhs == *this); - } - - /*! - @brief sets the binary subtype - - Sets the binary subtype of the value, also flags a binary JSON value as - having a subtype, which has implications for serialization. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @sa @ref subtype() -- return the binary subtype - @sa @ref clear_subtype() -- clears the binary subtype - @sa @ref has_subtype() -- returns whether or not the binary value has a - subtype - - @since version 3.8.0 - */ - void set_subtype(std::uint8_t subtype) noexcept - { - m_subtype = subtype; - m_has_subtype = true; - } - - /*! - @brief return the binary subtype - - Returns the numerical subtype of the value if it has a subtype. If it does - not have a subtype, this function will return size_t(-1) as a sentinel - value. - - @return the numerical subtype of the binary value - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @sa @ref set_subtype() -- sets the binary subtype - @sa @ref clear_subtype() -- clears the binary subtype - @sa @ref has_subtype() -- returns whether or not the binary value has a - subtype - - @since version 3.8.0 - */ - constexpr std::uint8_t subtype() const noexcept - { - return m_subtype; - } - - /*! - @brief return whether the value has a subtype - - @return whether the value has a subtype - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @sa @ref subtype() -- return the binary subtype - @sa @ref set_subtype() -- sets the binary subtype - @sa @ref clear_subtype() -- clears the binary subtype - - @since version 3.8.0 - */ - constexpr bool has_subtype() const noexcept - { - return m_has_subtype; - } - - /*! - @brief clears the binary subtype - - Clears the binary subtype and flags the value as not having a subtype, which - has implications for serialization; for instance MessagePack will prefer the - bin family over the ext family. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @sa @ref subtype() -- return the binary subtype - @sa @ref set_subtype() -- sets the binary subtype - @sa @ref has_subtype() -- returns whether or not the binary value has a - subtype - - @since version 3.8.0 - */ - void clear_subtype() noexcept - { - m_subtype = 0; - m_has_subtype = false; - } - - private: - std::uint8_t m_subtype = 0; - bool m_has_subtype = false; -}; - -} // namespace nlohmann - -// #include - -// #include - -// #include - -// #include - - -#include // size_t, uint8_t -#include // hash - -namespace nlohmann -{ -namespace detail -{ - -// boost::hash_combine -inline std::size_t combine(std::size_t seed, std::size_t h) noexcept -{ - seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U); - return seed; -} - -/*! -@brief hash a JSON value - -The hash function tries to rely on std::hash where possible. Furthermore, the -type of the JSON value is taken into account to have different hash values for -null, 0, 0U, and false, etc. - -@tparam BasicJsonType basic_json specialization -@param j JSON value to hash -@return hash value of j -*/ -template -std::size_t hash(const BasicJsonType& j) -{ - using string_t = typename BasicJsonType::string_t; - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - - const auto type = static_cast(j.type()); - switch (j.type()) - { - case BasicJsonType::value_t::null: - case BasicJsonType::value_t::discarded: - { - return combine(type, 0); - } - - case BasicJsonType::value_t::object: - { - auto seed = combine(type, j.size()); - for (const auto& element : j.items()) - { - const auto h = std::hash {}(element.key()); - seed = combine(seed, h); - seed = combine(seed, hash(element.value())); - } - return seed; - } - - case BasicJsonType::value_t::array: - { - auto seed = combine(type, j.size()); - for (const auto& element : j) - { - seed = combine(seed, hash(element)); - } - return seed; - } - - case BasicJsonType::value_t::string: - { - const auto h = std::hash {}(j.template get_ref()); - return combine(type, h); - } - - case BasicJsonType::value_t::boolean: - { - const auto h = std::hash {}(j.template get()); - return combine(type, h); - } - - case BasicJsonType::value_t::number_integer: - { - const auto h = std::hash {}(j.template get()); - return combine(type, h); - } - - case nlohmann::detail::value_t::number_unsigned: - { - const auto h = std::hash {}(j.template get()); - return combine(type, h); - } - - case nlohmann::detail::value_t::number_float: - { - const auto h = std::hash {}(j.template get()); - return combine(type, h); - } - - case nlohmann::detail::value_t::binary: - { - auto seed = combine(type, j.get_binary().size()); - const auto h = std::hash {}(j.get_binary().has_subtype()); - seed = combine(seed, h); - seed = combine(seed, j.get_binary().subtype()); - for (const auto byte : j.get_binary()) - { - seed = combine(seed, std::hash {}(byte)); - } - return seed; - } - - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } -} - -} // namespace detail -} // namespace nlohmann - -// #include - - -#include // generate_n -#include // array -#include // ldexp -#include // size_t -#include // uint8_t, uint16_t, uint32_t, uint64_t -#include // snprintf -#include // memcpy -#include // back_inserter -#include // numeric_limits -#include // char_traits, string -#include // make_pair, move - -// #include - -// #include - - -#include // array -#include // size_t -#include //FILE * -#include // strlen -#include // istream -#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next -#include // shared_ptr, make_shared, addressof -#include // accumulate -#include // string, char_traits -#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer -#include // pair, declval - -// #include - -// #include - - -namespace nlohmann -{ -namespace detail -{ -/// the supported input formats -enum class input_format_t { json, cbor, msgpack, ubjson, bson }; - -//////////////////// -// input adapters // -//////////////////// - -/*! -Input adapter for stdio file access. This adapter read only 1 byte and do not use any - buffer. This adapter is a very low level adapter. -*/ -class file_input_adapter -{ - public: - using char_type = char; - - JSON_HEDLEY_NON_NULL(2) - explicit file_input_adapter(std::FILE* f) noexcept - : m_file(f) - {} - - // make class move-only - file_input_adapter(const file_input_adapter&) = delete; - file_input_adapter(file_input_adapter&&) = default; - file_input_adapter& operator=(const file_input_adapter&) = delete; - file_input_adapter& operator=(file_input_adapter&&) = delete; - - std::char_traits::int_type get_character() noexcept - { - return std::fgetc(m_file); - } - - private: - /// the file pointer to read from - std::FILE* m_file; -}; - - -/*! -Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at -beginning of input. Does not support changing the underlying std::streambuf -in mid-input. Maintains underlying std::istream and std::streambuf to support -subsequent use of standard std::istream operations to process any input -characters following those used in parsing the JSON input. Clears the -std::istream flags; any input errors (e.g., EOF) will be detected by the first -subsequent call for input from the std::istream. -*/ -class input_stream_adapter -{ - public: - using char_type = char; - - ~input_stream_adapter() - { - // clear stream flags; we use underlying streambuf I/O, do not - // maintain ifstream flags, except eof - if (is != nullptr) - { - is->clear(is->rdstate() & std::ios::eofbit); - } - } - - explicit input_stream_adapter(std::istream& i) - : is(&i), sb(i.rdbuf()) - {} - - // delete because of pointer members - input_stream_adapter(const input_stream_adapter&) = delete; - input_stream_adapter& operator=(input_stream_adapter&) = delete; - input_stream_adapter& operator=(input_stream_adapter&& rhs) = delete; - - input_stream_adapter(input_stream_adapter&& rhs) noexcept : is(rhs.is), sb(rhs.sb) - { - rhs.is = nullptr; - rhs.sb = nullptr; - } - - // std::istream/std::streambuf use std::char_traits::to_int_type, to - // ensure that std::char_traits::eof() and the character 0xFF do not - // end up as the same value, eg. 0xFFFFFFFF. - std::char_traits::int_type get_character() - { - auto res = sb->sbumpc(); - // set eof manually, as we don't use the istream interface. - if (JSON_HEDLEY_UNLIKELY(res == EOF)) - { - is->clear(is->rdstate() | std::ios::eofbit); - } - return res; - } - - private: - /// the associated input stream - std::istream* is = nullptr; - std::streambuf* sb = nullptr; -}; - -// General-purpose iterator-based adapter. It might not be as fast as -// theoretically possible for some containers, but it is extremely versatile. -template -class iterator_input_adapter -{ - public: - using char_type = typename std::iterator_traits::value_type; - - iterator_input_adapter(IteratorType first, IteratorType last) - : current(std::move(first)), end(std::move(last)) {} - - typename std::char_traits::int_type get_character() - { - if (JSON_HEDLEY_LIKELY(current != end)) - { - auto result = std::char_traits::to_int_type(*current); - std::advance(current, 1); - return result; - } - else - { - return std::char_traits::eof(); - } - } - - private: - IteratorType current; - IteratorType end; - - template - friend struct wide_string_input_helper; - - bool empty() const - { - return current == end; - } - -}; - - -template -struct wide_string_input_helper; - -template -struct wide_string_input_helper -{ - // UTF-32 - static void fill_buffer(BaseInputAdapter& input, - std::array::int_type, 4>& utf8_bytes, - size_t& utf8_bytes_index, - size_t& utf8_bytes_filled) - { - utf8_bytes_index = 0; - - if (JSON_HEDLEY_UNLIKELY(input.empty())) - { - utf8_bytes[0] = std::char_traits::eof(); - utf8_bytes_filled = 1; - } - else - { - // get the current character - const auto wc = input.get_character(); - - // UTF-32 to UTF-8 encoding - if (wc < 0x80) - { - utf8_bytes[0] = static_cast::int_type>(wc); - utf8_bytes_filled = 1; - } - else if (wc <= 0x7FF) - { - utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u) & 0x1Fu)); - utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); - utf8_bytes_filled = 2; - } - else if (wc <= 0xFFFF) - { - utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u) & 0x0Fu)); - utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); - utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); - utf8_bytes_filled = 3; - } - else if (wc <= 0x10FFFF) - { - utf8_bytes[0] = static_cast::int_type>(0xF0u | ((static_cast(wc) >> 18u) & 0x07u)); - utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 12u) & 0x3Fu)); - utf8_bytes[2] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); - utf8_bytes[3] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); - utf8_bytes_filled = 4; - } - else - { - // unknown character - utf8_bytes[0] = static_cast::int_type>(wc); - utf8_bytes_filled = 1; - } - } - } -}; - -template -struct wide_string_input_helper -{ - // UTF-16 - static void fill_buffer(BaseInputAdapter& input, - std::array::int_type, 4>& utf8_bytes, - size_t& utf8_bytes_index, - size_t& utf8_bytes_filled) - { - utf8_bytes_index = 0; - - if (JSON_HEDLEY_UNLIKELY(input.empty())) - { - utf8_bytes[0] = std::char_traits::eof(); - utf8_bytes_filled = 1; - } - else - { - // get the current character - const auto wc = input.get_character(); - - // UTF-16 to UTF-8 encoding - if (wc < 0x80) - { - utf8_bytes[0] = static_cast::int_type>(wc); - utf8_bytes_filled = 1; - } - else if (wc <= 0x7FF) - { - utf8_bytes[0] = static_cast::int_type>(0xC0u | ((static_cast(wc) >> 6u))); - utf8_bytes[1] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); - utf8_bytes_filled = 2; - } - else if (0xD800 > wc || wc >= 0xE000) - { - utf8_bytes[0] = static_cast::int_type>(0xE0u | ((static_cast(wc) >> 12u))); - utf8_bytes[1] = static_cast::int_type>(0x80u | ((static_cast(wc) >> 6u) & 0x3Fu)); - utf8_bytes[2] = static_cast::int_type>(0x80u | (static_cast(wc) & 0x3Fu)); - utf8_bytes_filled = 3; - } - else - { - if (JSON_HEDLEY_UNLIKELY(!input.empty())) - { - const auto wc2 = static_cast(input.get_character()); - const auto charcode = 0x10000u + (((static_cast(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); - utf8_bytes[0] = static_cast::int_type>(0xF0u | (charcode >> 18u)); - utf8_bytes[1] = static_cast::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); - utf8_bytes[2] = static_cast::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); - utf8_bytes[3] = static_cast::int_type>(0x80u | (charcode & 0x3Fu)); - utf8_bytes_filled = 4; - } - else - { - utf8_bytes[0] = static_cast::int_type>(wc); - utf8_bytes_filled = 1; - } - } - } - } -}; - -// Wraps another input apdater to convert wide character types into individual bytes. -template -class wide_string_input_adapter -{ - public: - using char_type = char; - - wide_string_input_adapter(BaseInputAdapter base) - : base_adapter(base) {} - - typename std::char_traits::int_type get_character() noexcept - { - // check if buffer needs to be filled - if (utf8_bytes_index == utf8_bytes_filled) - { - fill_buffer(); - - JSON_ASSERT(utf8_bytes_filled > 0); - JSON_ASSERT(utf8_bytes_index == 0); - } - - // use buffer - JSON_ASSERT(utf8_bytes_filled > 0); - JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled); - return utf8_bytes[utf8_bytes_index++]; - } - - private: - BaseInputAdapter base_adapter; - - template - void fill_buffer() - { - wide_string_input_helper::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled); - } - - /// a buffer for UTF-8 bytes - std::array::int_type, 4> utf8_bytes = {{0, 0, 0, 0}}; - - /// index to the utf8_codes array for the next valid byte - std::size_t utf8_bytes_index = 0; - /// number of valid bytes in the utf8_codes array - std::size_t utf8_bytes_filled = 0; -}; - - -template -struct iterator_input_adapter_factory -{ - using iterator_type = IteratorType; - using char_type = typename std::iterator_traits::value_type; - using adapter_type = iterator_input_adapter; - - static adapter_type create(IteratorType first, IteratorType last) - { - return adapter_type(std::move(first), std::move(last)); - } -}; - -template -struct is_iterator_of_multibyte -{ - using value_type = typename std::iterator_traits::value_type; - enum - { - value = sizeof(value_type) > 1 - }; -}; - -template -struct iterator_input_adapter_factory::value>> -{ - using iterator_type = IteratorType; - using char_type = typename std::iterator_traits::value_type; - using base_adapter_type = iterator_input_adapter; - using adapter_type = wide_string_input_adapter; - - static adapter_type create(IteratorType first, IteratorType last) - { - return adapter_type(base_adapter_type(std::move(first), std::move(last))); - } -}; - -// General purpose iterator-based input -template -typename iterator_input_adapter_factory::adapter_type input_adapter(IteratorType first, IteratorType last) -{ - using factory_type = iterator_input_adapter_factory; - return factory_type::create(first, last); -} - -// Convenience shorthand from container to iterator -template -auto input_adapter(const ContainerType& container) -> decltype(input_adapter(begin(container), end(container))) -{ - // Enable ADL - using std::begin; - using std::end; - - return input_adapter(begin(container), end(container)); -} - -// Special cases with fast paths -inline file_input_adapter input_adapter(std::FILE* file) -{ - return file_input_adapter(file); -} - -inline input_stream_adapter input_adapter(std::istream& stream) -{ - return input_stream_adapter(stream); -} - -inline input_stream_adapter input_adapter(std::istream&& stream) -{ - return input_stream_adapter(stream); -} - -using contiguous_bytes_input_adapter = decltype(input_adapter(std::declval(), std::declval())); - -// Null-delimited strings, and the like. -template < typename CharT, - typename std::enable_if < - std::is_pointer::value&& - !std::is_array::value&& - std::is_integral::type>::value&& - sizeof(typename std::remove_pointer::type) == 1, - int >::type = 0 > -contiguous_bytes_input_adapter input_adapter(CharT b) -{ - auto length = std::strlen(reinterpret_cast(b)); - const auto* ptr = reinterpret_cast(b); - return input_adapter(ptr, ptr + length); -} - -template -auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) -{ - return input_adapter(array, array + N); -} - -// This class only handles inputs of input_buffer_adapter type. -// It's required so that expressions like {ptr, len} can be implicitely casted -// to the correct adapter. -class span_input_adapter -{ - public: - template < typename CharT, - typename std::enable_if < - std::is_pointer::value&& - std::is_integral::type>::value&& - sizeof(typename std::remove_pointer::type) == 1, - int >::type = 0 > - span_input_adapter(CharT b, std::size_t l) - : ia(reinterpret_cast(b), reinterpret_cast(b) + l) {} - - template::iterator_category, std::random_access_iterator_tag>::value, - int>::type = 0> - span_input_adapter(IteratorType first, IteratorType last) - : ia(input_adapter(first, last)) {} - - contiguous_bytes_input_adapter&& get() - { - return std::move(ia); - } - - private: - contiguous_bytes_input_adapter ia; -}; -} // namespace detail -} // namespace nlohmann - -// #include - - -#include -#include // string -#include // move -#include // vector - -// #include - -// #include - - -namespace nlohmann -{ - -/*! -@brief SAX interface - -This class describes the SAX interface used by @ref nlohmann::json::sax_parse. -Each function is called in different situations while the input is parsed. The -boolean return value informs the parser whether to continue processing the -input. -*/ -template -struct json_sax -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - /*! - @brief a null value was read - @return whether parsing should proceed - */ - virtual bool null() = 0; - - /*! - @brief a boolean value was read - @param[in] val boolean value - @return whether parsing should proceed - */ - virtual bool boolean(bool val) = 0; - - /*! - @brief an integer number was read - @param[in] val integer value - @return whether parsing should proceed - */ - virtual bool number_integer(number_integer_t val) = 0; - - /*! - @brief an unsigned integer number was read - @param[in] val unsigned integer value - @return whether parsing should proceed - */ - virtual bool number_unsigned(number_unsigned_t val) = 0; - - /*! - @brief an floating-point number was read - @param[in] val floating-point value - @param[in] s raw token value - @return whether parsing should proceed - */ - virtual bool number_float(number_float_t val, const string_t& s) = 0; - - /*! - @brief a string was read - @param[in] val string value - @return whether parsing should proceed - @note It is safe to move the passed string. - */ - virtual bool string(string_t& val) = 0; - - /*! - @brief a binary string was read - @param[in] val binary value - @return whether parsing should proceed - @note It is safe to move the passed binary. - */ - virtual bool binary(binary_t& val) = 0; - - /*! - @brief the beginning of an object was read - @param[in] elements number of object elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_object(std::size_t elements) = 0; - - /*! - @brief an object key was read - @param[in] val object key - @return whether parsing should proceed - @note It is safe to move the passed string. - */ - virtual bool key(string_t& val) = 0; - - /*! - @brief the end of an object was read - @return whether parsing should proceed - */ - virtual bool end_object() = 0; - - /*! - @brief the beginning of an array was read - @param[in] elements number of array elements or -1 if unknown - @return whether parsing should proceed - @note binary formats may report the number of elements - */ - virtual bool start_array(std::size_t elements) = 0; - - /*! - @brief the end of an array was read - @return whether parsing should proceed - */ - virtual bool end_array() = 0; - - /*! - @brief a parse error occurred - @param[in] position the position in the input where the error occurs - @param[in] last_token the last read token - @param[in] ex an exception object describing the error - @return whether parsing should proceed (must return false) - */ - virtual bool parse_error(std::size_t position, - const std::string& last_token, - const detail::exception& ex) = 0; - - virtual ~json_sax() = default; -}; - - -namespace detail -{ -/*! -@brief SAX implementation to create a JSON value from SAX events - -This class implements the @ref json_sax interface and processes the SAX events -to create a JSON value which makes it basically a DOM parser. The structure or -hierarchy of the JSON value is managed by the stack `ref_stack` which contains -a pointer to the respective array or object for each recursion depth. - -After successful parsing, the value that is passed by reference to the -constructor contains the parsed value. - -@tparam BasicJsonType the JSON type -*/ -template -class json_sax_dom_parser -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - /*! - @param[in, out] r reference to a JSON value that is manipulated while - parsing - @param[in] allow_exceptions_ whether parse errors yield exceptions - */ - explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true) - : root(r), allow_exceptions(allow_exceptions_) - {} - - // make class move-only - json_sax_dom_parser(const json_sax_dom_parser&) = delete; - json_sax_dom_parser(json_sax_dom_parser&&) = default; - json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete; - json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; - ~json_sax_dom_parser() = default; - - bool null() - { - handle_value(nullptr); - return true; - } - - bool boolean(bool val) - { - handle_value(val); - return true; - } - - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } - - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } - - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } - - bool string(string_t& val) - { - handle_value(val); - return true; - } - - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } - - bool start_object(std::size_t len) - { - ref_stack.push_back(handle_value(BasicJsonType::value_t::object)); - - if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, - "excessive object size: " + std::to_string(len))); - } - - return true; - } - - bool key(string_t& val) - { - // add null at given key and store the reference for later - object_element = &(ref_stack.back()->m_value.object->operator[](val)); - return true; - } - - bool end_object() - { - ref_stack.pop_back(); - return true; - } - - bool start_array(std::size_t len) - { - ref_stack.push_back(handle_value(BasicJsonType::value_t::array)); - - if (JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, - "excessive array size: " + std::to_string(len))); - } - - return true; - } - - bool end_array() - { - ref_stack.pop_back(); - return true; - } - - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } - - constexpr bool is_errored() const - { - return errored; - } - - private: - /*! - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements - */ - template - JSON_HEDLEY_RETURNS_NON_NULL - BasicJsonType* handle_value(Value&& v) - { - if (ref_stack.empty()) - { - root = BasicJsonType(std::forward(v)); - return &root; - } - - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); - - if (ref_stack.back()->is_array()) - { - ref_stack.back()->m_value.array->emplace_back(std::forward(v)); - return &(ref_stack.back()->m_value.array->back()); - } - - JSON_ASSERT(ref_stack.back()->is_object()); - JSON_ASSERT(object_element); - *object_element = BasicJsonType(std::forward(v)); - return object_element; - } - - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; -}; - -template -class json_sax_dom_callback_parser -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using parser_callback_t = typename BasicJsonType::parser_callback_t; - using parse_event_t = typename BasicJsonType::parse_event_t; - - json_sax_dom_callback_parser(BasicJsonType& r, - const parser_callback_t cb, - const bool allow_exceptions_ = true) - : root(r), callback(cb), allow_exceptions(allow_exceptions_) - { - keep_stack.push_back(true); - } - - // make class move-only - json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; - json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete; - json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; - ~json_sax_dom_callback_parser() = default; - - bool null() - { - handle_value(nullptr); - return true; - } - - bool boolean(bool val) - { - handle_value(val); - return true; - } - - bool number_integer(number_integer_t val) - { - handle_value(val); - return true; - } - - bool number_unsigned(number_unsigned_t val) - { - handle_value(val); - return true; - } - - bool number_float(number_float_t val, const string_t& /*unused*/) - { - handle_value(val); - return true; - } - - bool string(string_t& val) - { - handle_value(val); - return true; - } - - bool binary(binary_t& val) - { - handle_value(std::move(val)); - return true; - } - - bool start_object(std::size_t len) - { - // check callback for object start - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::object_start, discarded); - keep_stack.push_back(keep); - - auto val = handle_value(BasicJsonType::value_t::object, true); - ref_stack.push_back(val.second); - - // check object limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, "excessive object size: " + std::to_string(len))); - } - - return true; - } - - bool key(string_t& val) - { - BasicJsonType k = BasicJsonType(val); - - // check callback for key - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::key, k); - key_keep_stack.push_back(keep); - - // add discarded value at given key and store the reference for later - if (keep && ref_stack.back()) - { - object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded); - } - - return true; - } - - bool end_object() - { - if (ref_stack.back() && !callback(static_cast(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back())) - { - // discard object - *ref_stack.back() = discarded; - } - - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - - if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) - { - // remove discarded value - for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it) - { - if (it->is_discarded()) - { - ref_stack.back()->erase(it); - break; - } - } - } - - return true; - } - - bool start_array(std::size_t len) - { - const bool keep = callback(static_cast(ref_stack.size()), parse_event_t::array_start, discarded); - keep_stack.push_back(keep); - - auto val = handle_value(BasicJsonType::value_t::array, true); - ref_stack.push_back(val.second); - - // check array limit - if (ref_stack.back() && JSON_HEDLEY_UNLIKELY(len != std::size_t(-1) && len > ref_stack.back()->max_size())) - { - JSON_THROW(out_of_range::create(408, "excessive array size: " + std::to_string(len))); - } - - return true; - } - - bool end_array() - { - bool keep = true; - - if (ref_stack.back()) - { - keep = callback(static_cast(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); - if (!keep) - { - // discard array - *ref_stack.back() = discarded; - } - } - - JSON_ASSERT(!ref_stack.empty()); - JSON_ASSERT(!keep_stack.empty()); - ref_stack.pop_back(); - keep_stack.pop_back(); - - // remove discarded value - if (!keep && !ref_stack.empty() && ref_stack.back()->is_array()) - { - ref_stack.back()->m_value.array->pop_back(); - } - - return true; - } - - template - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, - const Exception& ex) - { - errored = true; - static_cast(ex); - if (allow_exceptions) - { - JSON_THROW(ex); - } - return false; - } - - constexpr bool is_errored() const - { - return errored; - } - - private: - /*! - @param[in] v value to add to the JSON value we build during parsing - @param[in] skip_callback whether we should skip calling the callback - function; this is required after start_array() and - start_object() SAX events, because otherwise we would call the - callback function with an empty array or object, respectively. - - @invariant If the ref stack is empty, then the passed value will be the new - root. - @invariant If the ref stack contains a value, then it is an array or an - object to which we can add elements - - @return pair of boolean (whether value should be kept) and pointer (to the - passed value in the ref_stack hierarchy; nullptr if not kept) - */ - template - std::pair handle_value(Value&& v, const bool skip_callback = false) - { - JSON_ASSERT(!keep_stack.empty()); - - // do not handle this value if we know it would be added to a discarded - // container - if (!keep_stack.back()) - { - return {false, nullptr}; - } - - // create value - auto value = BasicJsonType(std::forward(v)); - - // check callback - const bool keep = skip_callback || callback(static_cast(ref_stack.size()), parse_event_t::value, value); - - // do not handle this value if we just learnt it shall be discarded - if (!keep) - { - return {false, nullptr}; - } - - if (ref_stack.empty()) - { - root = std::move(value); - return {true, &root}; - } - - // skip this value if we already decided to skip the parent - // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360) - if (!ref_stack.back()) - { - return {false, nullptr}; - } - - // we now only expect arrays and objects - JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object()); - - // array - if (ref_stack.back()->is_array()) - { - ref_stack.back()->m_value.array->push_back(std::move(value)); - return {true, &(ref_stack.back()->m_value.array->back())}; - } - - // object - JSON_ASSERT(ref_stack.back()->is_object()); - // check if we should store an element for the current key - JSON_ASSERT(!key_keep_stack.empty()); - const bool store_element = key_keep_stack.back(); - key_keep_stack.pop_back(); - - if (!store_element) - { - return {false, nullptr}; - } - - JSON_ASSERT(object_element); - *object_element = std::move(value); - return {true, object_element}; - } - - /// the parsed JSON value - BasicJsonType& root; - /// stack to model hierarchy of values - std::vector ref_stack {}; - /// stack to manage which values to keep - std::vector keep_stack {}; - /// stack to manage which object keys to keep - std::vector key_keep_stack {}; - /// helper to hold the reference for the next object element - BasicJsonType* object_element = nullptr; - /// whether a syntax error occurred - bool errored = false; - /// callback function - const parser_callback_t callback = nullptr; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; - /// a discarded value for the callback - BasicJsonType discarded = BasicJsonType::value_t::discarded; -}; - -template -class json_sax_acceptor -{ - public: - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - - bool null() - { - return true; - } - - bool boolean(bool /*unused*/) - { - return true; - } - - bool number_integer(number_integer_t /*unused*/) - { - return true; - } - - bool number_unsigned(number_unsigned_t /*unused*/) - { - return true; - } - - bool number_float(number_float_t /*unused*/, const string_t& /*unused*/) - { - return true; - } - - bool string(string_t& /*unused*/) - { - return true; - } - - bool binary(binary_t& /*unused*/) - { - return true; - } - - bool start_object(std::size_t /*unused*/ = std::size_t(-1)) - { - return true; - } - - bool key(string_t& /*unused*/) - { - return true; - } - - bool end_object() - { - return true; - } - - bool start_array(std::size_t /*unused*/ = std::size_t(-1)) - { - return true; - } - - bool end_array() - { - return true; - } - - bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/) - { - return false; - } -}; -} // namespace detail - -} // namespace nlohmann - -// #include - - -#include // array -#include // localeconv -#include // size_t -#include // snprintf -#include // strtof, strtod, strtold, strtoll, strtoull -#include // initializer_list -#include // char_traits, string -#include // move -#include // vector - -// #include - -// #include - -// #include - - -namespace nlohmann -{ -namespace detail -{ -/////////// -// lexer // -/////////// - -template -class lexer_base -{ - public: - /// token types for the parser - enum class token_type - { - uninitialized, ///< indicating the scanner is uninitialized - literal_true, ///< the `true` literal - literal_false, ///< the `false` literal - literal_null, ///< the `null` literal - value_string, ///< a string -- use get_string() for actual value - value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value - value_integer, ///< a signed integer -- use get_number_integer() for actual value - value_float, ///< an floating point number -- use get_number_float() for actual value - begin_array, ///< the character for array begin `[` - begin_object, ///< the character for object begin `{` - end_array, ///< the character for array end `]` - end_object, ///< the character for object end `}` - name_separator, ///< the name separator `:` - value_separator, ///< the value separator `,` - parse_error, ///< indicating a parse error - end_of_input, ///< indicating the end of the input buffer - literal_or_value ///< a literal or the begin of a value (only for diagnostics) - }; - - /// return name of values of type token_type (only used for errors) - JSON_HEDLEY_RETURNS_NON_NULL - JSON_HEDLEY_CONST - static const char* token_type_name(const token_type t) noexcept - { - switch (t) - { - case token_type::uninitialized: - return ""; - case token_type::literal_true: - return "true literal"; - case token_type::literal_false: - return "false literal"; - case token_type::literal_null: - return "null literal"; - case token_type::value_string: - return "string literal"; - case token_type::value_unsigned: - case token_type::value_integer: - case token_type::value_float: - return "number literal"; - case token_type::begin_array: - return "'['"; - case token_type::begin_object: - return "'{'"; - case token_type::end_array: - return "']'"; - case token_type::end_object: - return "'}'"; - case token_type::name_separator: - return "':'"; - case token_type::value_separator: - return "','"; - case token_type::parse_error: - return ""; - case token_type::end_of_input: - return "end of input"; - case token_type::literal_or_value: - return "'[', '{', or a literal"; - // LCOV_EXCL_START - default: // catch non-enum values - return "unknown token"; - // LCOV_EXCL_STOP - } - } -}; -/*! -@brief lexical analysis - -This class organizes the lexical analysis during JSON deserialization. -*/ -template -class lexer : public lexer_base -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using char_type = typename InputAdapterType::char_type; - using char_int_type = typename std::char_traits::int_type; - - public: - using token_type = typename lexer_base::token_type; - - explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) - : ia(std::move(adapter)) - , ignore_comments(ignore_comments_) - , decimal_point_char(static_cast(get_decimal_point())) - {} - - // delete because of pointer members - lexer(const lexer&) = delete; - lexer(lexer&&) = default; - lexer& operator=(lexer&) = delete; - lexer& operator=(lexer&&) = default; - ~lexer() = default; - - private: - ///////////////////// - // locales - ///////////////////// - - /// return the locale-dependent decimal point - JSON_HEDLEY_PURE - static char get_decimal_point() noexcept - { - const auto* loc = localeconv(); - JSON_ASSERT(loc != nullptr); - return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); - } - - ///////////////////// - // scan functions - ///////////////////// - - /*! - @brief get codepoint from 4 hex characters following `\u` - - For input "\u c1 c2 c3 c4" the codepoint is: - (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 - = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) - - Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' - must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The - conversion is done by subtracting the offset (0x30, 0x37, and 0x57) - between the ASCII value of the character and the desired integer value. - - @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or - non-hex character) - */ - int get_codepoint() - { - // this function only makes sense after reading `\u` - JSON_ASSERT(current == 'u'); - int codepoint = 0; - - const auto factors = { 12u, 8u, 4u, 0u }; - for (const auto factor : factors) - { - get(); - - if (current >= '0' && current <= '9') - { - codepoint += static_cast((static_cast(current) - 0x30u) << factor); - } - else if (current >= 'A' && current <= 'F') - { - codepoint += static_cast((static_cast(current) - 0x37u) << factor); - } - else if (current >= 'a' && current <= 'f') - { - codepoint += static_cast((static_cast(current) - 0x57u) << factor); - } - else - { - return -1; - } - } - - JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF); - return codepoint; - } - - /*! - @brief check if the next byte(s) are inside a given range - - Adds the current byte and, for each passed range, reads a new byte and - checks if it is inside the range. If a violation was detected, set up an - error message and return false. Otherwise, return true. - - @param[in] ranges list of integers; interpreted as list of pairs of - inclusive lower and upper bound, respectively - - @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, - 1, 2, or 3 pairs. This precondition is enforced by an assertion. - - @return true if and only if no range violation was detected - */ - bool next_byte_in_range(std::initializer_list ranges) - { - JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6); - add(current); - - for (auto range = ranges.begin(); range != ranges.end(); ++range) - { - get(); - if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) - { - add(current); - } - else - { - error_message = "invalid string: ill-formed UTF-8 byte"; - return false; - } - } - - return true; - } - - /*! - @brief scan a string literal - - This function scans a string according to Sect. 7 of RFC 7159. While - scanning, bytes are escaped and copied into buffer token_buffer. Then the - function returns successfully, token_buffer is *not* null-terminated (as it - may contain \0 bytes), and token_buffer.size() is the number of bytes in the - string. - - @return token_type::value_string if string could be successfully scanned, - token_type::parse_error otherwise - - @note In case of errors, variable error_message contains a textual - description. - */ - token_type scan_string() - { - // reset token_buffer (ignore opening quote) - reset(); - - // we entered the function by reading an open quote - JSON_ASSERT(current == '\"'); - - while (true) - { - // get next character - switch (get()) - { - // end of file while parsing string - case std::char_traits::eof(): - { - error_message = "invalid string: missing closing quote"; - return token_type::parse_error; - } - - // closing quote - case '\"': - { - return token_type::value_string; - } - - // escapes - case '\\': - { - switch (get()) - { - // quotation mark - case '\"': - add('\"'); - break; - // reverse solidus - case '\\': - add('\\'); - break; - // solidus - case '/': - add('/'); - break; - // backspace - case 'b': - add('\b'); - break; - // form feed - case 'f': - add('\f'); - break; - // line feed - case 'n': - add('\n'); - break; - // carriage return - case 'r': - add('\r'); - break; - // tab - case 't': - add('\t'); - break; - - // unicode escapes - case 'u': - { - const int codepoint1 = get_codepoint(); - int codepoint = codepoint1; // start with codepoint1 - - if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1)) - { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } - - // check if code point is a high surrogate - if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF) - { - // expect next \uxxxx entry - if (JSON_HEDLEY_LIKELY(get() == '\\' && get() == 'u')) - { - const int codepoint2 = get_codepoint(); - - if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1)) - { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } - - // check if codepoint2 is a low surrogate - if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF)) - { - // overwrite codepoint - codepoint = static_cast( - // high surrogate occupies the most significant 22 bits - (static_cast(codepoint1) << 10u) - // low surrogate occupies the least significant 15 bits - + static_cast(codepoint2) - // there is still the 0xD800, 0xDC00 and 0x10000 noise - // in the result so we have to subtract with: - // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - - 0x35FDC00u); - } - else - { - error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } - else - { - error_message = "invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } - else - { - if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF)) - { - error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; - return token_type::parse_error; - } - } - - // result of the above calculation yields a proper codepoint - JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF); - - // translate codepoint into bytes - if (codepoint < 0x80) - { - // 1-byte characters: 0xxxxxxx (ASCII) - add(static_cast(codepoint)); - } - else if (codepoint <= 0x7FF) - { - // 2-byte characters: 110xxxxx 10xxxxxx - add(static_cast(0xC0u | (static_cast(codepoint) >> 6u))); - add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); - } - else if (codepoint <= 0xFFFF) - { - // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx - add(static_cast(0xE0u | (static_cast(codepoint) >> 12u))); - add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); - add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); - } - else - { - // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - add(static_cast(0xF0u | (static_cast(codepoint) >> 18u))); - add(static_cast(0x80u | ((static_cast(codepoint) >> 12u) & 0x3Fu))); - add(static_cast(0x80u | ((static_cast(codepoint) >> 6u) & 0x3Fu))); - add(static_cast(0x80u | (static_cast(codepoint) & 0x3Fu))); - } - - break; - } - - // other characters after escape - default: - error_message = "invalid string: forbidden character after backslash"; - return token_type::parse_error; - } - - break; - } - - // invalid control characters - case 0x00: - { - error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000"; - return token_type::parse_error; - } - - case 0x01: - { - error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001"; - return token_type::parse_error; - } - - case 0x02: - { - error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002"; - return token_type::parse_error; - } - - case 0x03: - { - error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003"; - return token_type::parse_error; - } - - case 0x04: - { - error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004"; - return token_type::parse_error; - } - - case 0x05: - { - error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005"; - return token_type::parse_error; - } - - case 0x06: - { - error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006"; - return token_type::parse_error; - } - - case 0x07: - { - error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007"; - return token_type::parse_error; - } - - case 0x08: - { - error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b"; - return token_type::parse_error; - } - - case 0x09: - { - error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t"; - return token_type::parse_error; - } - - case 0x0A: - { - error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n"; - return token_type::parse_error; - } - - case 0x0B: - { - error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B"; - return token_type::parse_error; - } - - case 0x0C: - { - error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f"; - return token_type::parse_error; - } - - case 0x0D: - { - error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r"; - return token_type::parse_error; - } - - case 0x0E: - { - error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E"; - return token_type::parse_error; - } - - case 0x0F: - { - error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F"; - return token_type::parse_error; - } - - case 0x10: - { - error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010"; - return token_type::parse_error; - } - - case 0x11: - { - error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011"; - return token_type::parse_error; - } - - case 0x12: - { - error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012"; - return token_type::parse_error; - } - - case 0x13: - { - error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013"; - return token_type::parse_error; - } - - case 0x14: - { - error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014"; - return token_type::parse_error; - } - - case 0x15: - { - error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015"; - return token_type::parse_error; - } - - case 0x16: - { - error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016"; - return token_type::parse_error; - } - - case 0x17: - { - error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017"; - return token_type::parse_error; - } - - case 0x18: - { - error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018"; - return token_type::parse_error; - } - - case 0x19: - { - error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019"; - return token_type::parse_error; - } - - case 0x1A: - { - error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A"; - return token_type::parse_error; - } - - case 0x1B: - { - error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B"; - return token_type::parse_error; - } - - case 0x1C: - { - error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C"; - return token_type::parse_error; - } - - case 0x1D: - { - error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D"; - return token_type::parse_error; - } - - case 0x1E: - { - error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E"; - return token_type::parse_error; - } - - case 0x1F: - { - error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F"; - return token_type::parse_error; - } - - // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) - case 0x20: - case 0x21: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2A: - case 0x2B: - case 0x2C: - case 0x2D: - case 0x2E: - case 0x2F: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5D: - case 0x5E: - case 0x5F: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7C: - case 0x7D: - case 0x7E: - case 0x7F: - { - add(current); - break; - } - - // U+0080..U+07FF: bytes C2..DF 80..BF - case 0xC2: - case 0xC3: - case 0xC4: - case 0xC5: - case 0xC6: - case 0xC7: - case 0xC8: - case 0xC9: - case 0xCA: - case 0xCB: - case 0xCC: - case 0xCD: - case 0xCE: - case 0xCF: - case 0xD0: - case 0xD1: - case 0xD2: - case 0xD3: - case 0xD4: - case 0xD5: - case 0xD6: - case 0xD7: - case 0xD8: - case 0xD9: - case 0xDA: - case 0xDB: - case 0xDC: - case 0xDD: - case 0xDE: - case 0xDF: - { - if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF}))) - { - return token_type::parse_error; - } - break; - } - - // U+0800..U+0FFF: bytes E0 A0..BF 80..BF - case 0xE0: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF - // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF - case 0xE1: - case 0xE2: - case 0xE3: - case 0xE4: - case 0xE5: - case 0xE6: - case 0xE7: - case 0xE8: - case 0xE9: - case 0xEA: - case 0xEB: - case 0xEC: - case 0xEE: - case 0xEF: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+D000..U+D7FF: bytes ED 80..9F 80..BF - case 0xED: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF - case 0xF0: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF - case 0xF1: - case 0xF2: - case 0xF3: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF - case 0xF4: - { - if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // remaining bytes (80..C1 and F5..FF) are ill-formed - default: - { - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; - } - } - } - } - - /*! - * @brief scan a comment - * @return whether comment could be scanned successfully - */ - bool scan_comment() - { - switch (get()) - { - // single-line comments skip input until a newline or EOF is read - case '/': - { - while (true) - { - switch (get()) - { - case '\n': - case '\r': - case std::char_traits::eof(): - case '\0': - return true; - - default: - break; - } - } - } - - // multi-line comments skip input until */ is read - case '*': - { - while (true) - { - switch (get()) - { - case std::char_traits::eof(): - case '\0': - { - error_message = "invalid comment; missing closing '*/'"; - return false; - } - - case '*': - { - switch (get()) - { - case '/': - return true; - - default: - { - unget(); - continue; - } - } - } - - default: - continue; - } - } - } - - // unexpected character after reading '/' - default: - { - error_message = "invalid comment; expecting '/' or '*' after '/'"; - return false; - } - } - } - - JSON_HEDLEY_NON_NULL(2) - static void strtof(float& f, const char* str, char** endptr) noexcept - { - f = std::strtof(str, endptr); - } - - JSON_HEDLEY_NON_NULL(2) - static void strtof(double& f, const char* str, char** endptr) noexcept - { - f = std::strtod(str, endptr); - } - - JSON_HEDLEY_NON_NULL(2) - static void strtof(long double& f, const char* str, char** endptr) noexcept - { - f = std::strtold(str, endptr); - } - - /*! - @brief scan a number literal - - This function scans a string according to Sect. 6 of RFC 7159. - - The function is realized with a deterministic finite state machine derived - from the grammar described in RFC 7159. Starting in state "init", the - input is read and used to determined the next state. Only state "done" - accepts the number. State "error" is a trap state to model errors. In the - table below, "anything" means any character but the ones listed before. - - state | 0 | 1-9 | e E | + | - | . | anything - ---------|----------|----------|----------|---------|---------|----------|----------- - init | zero | any1 | [error] | [error] | minus | [error] | [error] - minus | zero | any1 | [error] | [error] | [error] | [error] | [error] - zero | done | done | exponent | done | done | decimal1 | done - any1 | any1 | any1 | exponent | done | done | decimal1 | done - decimal1 | decimal2 | decimal2 | [error] | [error] | [error] | [error] | [error] - decimal2 | decimal2 | decimal2 | exponent | done | done | done | done - exponent | any2 | any2 | [error] | sign | sign | [error] | [error] - sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] - any2 | any2 | any2 | done | done | done | done | done - - The state machine is realized with one label per state (prefixed with - "scan_number_") and `goto` statements between them. The state machine - contains cycles, but any cycle can be left when EOF is read. Therefore, - the function is guaranteed to terminate. - - During scanning, the read bytes are stored in token_buffer. This string is - then converted to a signed integer, an unsigned integer, or a - floating-point number. - - @return token_type::value_unsigned, token_type::value_integer, or - token_type::value_float if number could be successfully scanned, - token_type::parse_error otherwise - - @note The scanner is independent of the current locale. Internally, the - locale's decimal point is used instead of `.` to work with the - locale-dependent converters. - */ - token_type scan_number() // lgtm [cpp/use-of-goto] - { - // reset token_buffer to store the number's bytes - reset(); - - // the type of the parsed number; initially set to unsigned; will be - // changed if minus sign, decimal point or exponent is read - token_type number_type = token_type::value_unsigned; - - // state (init): we just found out we need to scan a number - switch (current) - { - case '-': - { - add(current); - goto scan_number_minus; - } - - case '0': - { - add(current); - goto scan_number_zero; - } - - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } - - // all other characters are rejected outside scan_number() - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } - -scan_number_minus: - // state: we just parsed a leading minus sign - number_type = token_type::value_integer; - switch (get()) - { - case '0': - { - add(current); - goto scan_number_zero; - } - - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } - - default: - { - error_message = "invalid number; expected digit after '-'"; - return token_type::parse_error; - } - } - -scan_number_zero: - // state: we just parse a zero (maybe with a leading minus sign) - switch (get()) - { - case '.': - { - add(decimal_point_char); - goto scan_number_decimal1; - } - - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } - - default: - goto scan_number_done; - } - -scan_number_any1: - // state: we just parsed a number 0-9 (maybe with a leading minus sign) - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } - - case '.': - { - add(decimal_point_char); - goto scan_number_decimal1; - } - - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } - - default: - goto scan_number_done; - } - -scan_number_decimal1: - // state: we just parsed a decimal point - number_type = token_type::value_float; - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_decimal2; - } - - default: - { - error_message = "invalid number; expected digit after '.'"; - return token_type::parse_error; - } - } - -scan_number_decimal2: - // we just parsed at least one number after a decimal point - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_decimal2; - } - - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } - - default: - goto scan_number_done; - } - -scan_number_exponent: - // we just parsed an exponent - number_type = token_type::value_float; - switch (get()) - { - case '+': - case '-': - { - add(current); - goto scan_number_sign; - } - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any2; - } - - default: - { - error_message = - "invalid number; expected '+', '-', or digit after exponent"; - return token_type::parse_error; - } - } - -scan_number_sign: - // we just parsed an exponent sign - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any2; - } - - default: - { - error_message = "invalid number; expected digit after exponent sign"; - return token_type::parse_error; - } - } - -scan_number_any2: - // we just parsed a number after the exponent or exponent sign - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any2; - } - - default: - goto scan_number_done; - } - -scan_number_done: - // unget the character after the number (we only read it to know that - // we are done scanning a number) - unget(); - - char* endptr = nullptr; - errno = 0; - - // try to parse integers first and fall back to floats - if (number_type == token_type::value_unsigned) - { - const auto x = std::strtoull(token_buffer.data(), &endptr, 10); - - // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - - if (errno == 0) - { - value_unsigned = static_cast(x); - if (value_unsigned == x) - { - return token_type::value_unsigned; - } - } - } - else if (number_type == token_type::value_integer) - { - const auto x = std::strtoll(token_buffer.data(), &endptr, 10); - - // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - - if (errno == 0) - { - value_integer = static_cast(x); - if (value_integer == x) - { - return token_type::value_integer; - } - } - } - - // this code is reached if we parse a floating-point number or if an - // integer conversion above failed - strtof(value_float, token_buffer.data(), &endptr); - - // we checked the number format before - JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size()); - - return token_type::value_float; - } - - /*! - @param[in] literal_text the literal text to expect - @param[in] length the length of the passed literal text - @param[in] return_type the token type to return on success - */ - JSON_HEDLEY_NON_NULL(2) - token_type scan_literal(const char_type* literal_text, const std::size_t length, - token_type return_type) - { - JSON_ASSERT(std::char_traits::to_char_type(current) == literal_text[0]); - for (std::size_t i = 1; i < length; ++i) - { - if (JSON_HEDLEY_UNLIKELY(std::char_traits::to_char_type(get()) != literal_text[i])) - { - error_message = "invalid literal"; - return token_type::parse_error; - } - } - return return_type; - } - - ///////////////////// - // input management - ///////////////////// - - /// reset token_buffer; current character is beginning of token - void reset() noexcept - { - token_buffer.clear(); - token_string.clear(); - token_string.push_back(std::char_traits::to_char_type(current)); - } - - /* - @brief get next character from the input - - This function provides the interface to the used input adapter. It does - not throw in case the input reached EOF, but returns a - `std::char_traits::eof()` in that case. Stores the scanned characters - for use in error messages. - - @return character read from the input - */ - char_int_type get() - { - ++position.chars_read_total; - ++position.chars_read_current_line; - - if (next_unget) - { - // just reset the next_unget variable and work with current - next_unget = false; - } - else - { - current = ia.get_character(); - } - - if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) - { - token_string.push_back(std::char_traits::to_char_type(current)); - } - - if (current == '\n') - { - ++position.lines_read; - position.chars_read_current_line = 0; - } - - return current; - } - - /*! - @brief unget current character (read it again on next get) - - We implement unget by setting variable next_unget to true. The input is not - changed - we just simulate ungetting by modifying chars_read_total, - chars_read_current_line, and token_string. The next call to get() will - behave as if the unget character is read again. - */ - void unget() - { - next_unget = true; - - --position.chars_read_total; - - // in case we "unget" a newline, we have to also decrement the lines_read - if (position.chars_read_current_line == 0) - { - if (position.lines_read > 0) - { - --position.lines_read; - } - } - else - { - --position.chars_read_current_line; - } - - if (JSON_HEDLEY_LIKELY(current != std::char_traits::eof())) - { - JSON_ASSERT(!token_string.empty()); - token_string.pop_back(); - } - } - - /// add a character to token_buffer - void add(char_int_type c) - { - token_buffer.push_back(static_cast(c)); - } - - public: - ///////////////////// - // value getters - ///////////////////// - - /// return integer value - constexpr number_integer_t get_number_integer() const noexcept - { - return value_integer; - } - - /// return unsigned integer value - constexpr number_unsigned_t get_number_unsigned() const noexcept - { - return value_unsigned; - } - - /// return floating-point value - constexpr number_float_t get_number_float() const noexcept - { - return value_float; - } - - /// return current string value (implicitly resets the token; useful only once) - string_t& get_string() - { - return token_buffer; - } - - ///////////////////// - // diagnostics - ///////////////////// - - /// return position of last read token - constexpr position_t get_position() const noexcept - { - return position; - } - - /// return the last read token (for errors only). Will never contain EOF - /// (an arbitrary value that is not a valid char value, often -1), because - /// 255 may legitimately occur. May contain NUL, which should be escaped. - std::string get_token_string() const - { - // escape control characters - std::string result; - for (const auto c : token_string) - { - if (static_cast(c) <= '\x1F') - { - // escape control characters - std::array cs{{}}; - (std::snprintf)(cs.data(), cs.size(), "", static_cast(c)); - result += cs.data(); - } - else - { - // add character as is - result.push_back(static_cast(c)); - } - } - - return result; - } - - /// return syntax error message - JSON_HEDLEY_RETURNS_NON_NULL - constexpr const char* get_error_message() const noexcept - { - return error_message; - } - - ///////////////////// - // actual scanner - ///////////////////// - - /*! - @brief skip the UTF-8 byte order mark - @return true iff there is no BOM or the correct BOM has been skipped - */ - bool skip_bom() - { - if (get() == 0xEF) - { - // check if we completely parse the BOM - return get() == 0xBB && get() == 0xBF; - } - - // the first character is not the beginning of the BOM; unget it to - // process is later - unget(); - return true; - } - - void skip_whitespace() - { - do - { - get(); - } - while (current == ' ' || current == '\t' || current == '\n' || current == '\r'); - } - - token_type scan() - { - // initially, skip the BOM - if (position.chars_read_total == 0 && !skip_bom()) - { - error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given"; - return token_type::parse_error; - } - - // read next character and ignore whitespace - skip_whitespace(); - - // ignore comments - while (ignore_comments && current == '/') - { - if (!scan_comment()) - { - return token_type::parse_error; - } - - // skip following whitespace - skip_whitespace(); - } - - switch (current) - { - // structural characters - case '[': - return token_type::begin_array; - case ']': - return token_type::end_array; - case '{': - return token_type::begin_object; - case '}': - return token_type::end_object; - case ':': - return token_type::name_separator; - case ',': - return token_type::value_separator; - - // literals - case 't': - { - std::array true_literal = {{'t', 'r', 'u', 'e'}}; - return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true); - } - case 'f': - { - std::array false_literal = {{'f', 'a', 'l', 's', 'e'}}; - return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false); - } - case 'n': - { - std::array null_literal = {{'n', 'u', 'l', 'l'}}; - return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null); - } - - // string - case '\"': - return scan_string(); - - // number - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return scan_number(); - - // end of input (the null byte is needed when parsing from - // string literals) - case '\0': - case std::char_traits::eof(): - return token_type::end_of_input; - - // error - default: - error_message = "invalid literal"; - return token_type::parse_error; - } - } - - private: - /// input adapter - InputAdapterType ia; - - /// whether comments should be ignored (true) or signaled as errors (false) - const bool ignore_comments = false; - - /// the current character - char_int_type current = std::char_traits::eof(); - - /// whether the next get() call should just return current - bool next_unget = false; - - /// the start position of the current token - position_t position {}; - - /// raw input token string (for error messages) - std::vector token_string {}; - - /// buffer for variable-length tokens (numbers, strings) - string_t token_buffer {}; - - /// a description of occurred lexer errors - const char* error_message = ""; - - // number values - number_integer_t value_integer = 0; - number_unsigned_t value_unsigned = 0; - number_float_t value_float = 0; - - /// the decimal point - const char_int_type decimal_point_char = '.'; -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - - -#include // size_t -#include // declval -#include // string - -// #include - -// #include - - -namespace nlohmann -{ -namespace detail -{ -template -using null_function_t = decltype(std::declval().null()); - -template -using boolean_function_t = - decltype(std::declval().boolean(std::declval())); - -template -using number_integer_function_t = - decltype(std::declval().number_integer(std::declval())); - -template -using number_unsigned_function_t = - decltype(std::declval().number_unsigned(std::declval())); - -template -using number_float_function_t = decltype(std::declval().number_float( - std::declval(), std::declval())); - -template -using string_function_t = - decltype(std::declval().string(std::declval())); - -template -using binary_function_t = - decltype(std::declval().binary(std::declval())); - -template -using start_object_function_t = - decltype(std::declval().start_object(std::declval())); - -template -using key_function_t = - decltype(std::declval().key(std::declval())); - -template -using end_object_function_t = decltype(std::declval().end_object()); - -template -using start_array_function_t = - decltype(std::declval().start_array(std::declval())); - -template -using end_array_function_t = decltype(std::declval().end_array()); - -template -using parse_error_function_t = decltype(std::declval().parse_error( - std::declval(), std::declval(), - std::declval())); - -template -struct is_sax -{ - private: - static_assert(is_basic_json::value, - "BasicJsonType must be of type basic_json<...>"); - - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using exception_t = typename BasicJsonType::exception; - - public: - static constexpr bool value = - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value && - is_detected_exact::value; -}; - -template -struct is_sax_static_asserts -{ - private: - static_assert(is_basic_json::value, - "BasicJsonType must be of type basic_json<...>"); - - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using exception_t = typename BasicJsonType::exception; - - public: - static_assert(is_detected_exact::value, - "Missing/invalid function: bool null()"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool boolean(bool)"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool boolean(bool)"); - static_assert( - is_detected_exact::value, - "Missing/invalid function: bool number_integer(number_integer_t)"); - static_assert( - is_detected_exact::value, - "Missing/invalid function: bool number_unsigned(number_unsigned_t)"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool number_float(number_float_t, const string_t&)"); - static_assert( - is_detected_exact::value, - "Missing/invalid function: bool string(string_t&)"); - static_assert( - is_detected_exact::value, - "Missing/invalid function: bool binary(binary_t&)"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool start_object(std::size_t)"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool key(string_t&)"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool end_object()"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool start_array(std::size_t)"); - static_assert(is_detected_exact::value, - "Missing/invalid function: bool end_array()"); - static_assert( - is_detected_exact::value, - "Missing/invalid function: bool parse_error(std::size_t, const " - "std::string&, const exception&)"); -}; -} // namespace detail -} // namespace nlohmann - -// #include - - -namespace nlohmann -{ -namespace detail -{ - -/// how to treat CBOR tags -enum class cbor_tag_handler_t -{ - error, ///< throw a parse_error exception in case of a tag - ignore ///< ignore tags -}; - -/*! -@brief determine system byte order - -@return true if and only if system's byte order is little endian - -@note from https://stackoverflow.com/a/1001328/266378 -*/ -static inline bool little_endianess(int num = 1) noexcept -{ - return *reinterpret_cast(&num) == 1; -} - - -/////////////////// -// binary reader // -/////////////////// - -/*! -@brief deserialization of CBOR, MessagePack, and UBJSON values -*/ -template> -class binary_reader -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using json_sax_t = SAX; - using char_type = typename InputAdapterType::char_type; - using char_int_type = typename std::char_traits::int_type; - - public: - /*! - @brief create a binary reader - - @param[in] adapter input adapter to read from - */ - explicit binary_reader(InputAdapterType&& adapter) : ia(std::move(adapter)) - { - (void)detail::is_sax_static_asserts {}; - } - - // make class move-only - binary_reader(const binary_reader&) = delete; - binary_reader(binary_reader&&) = default; - binary_reader& operator=(const binary_reader&) = delete; - binary_reader& operator=(binary_reader&&) = default; - ~binary_reader() = default; - - /*! - @param[in] format the binary format to parse - @param[in] sax_ a SAX event processor - @param[in] strict whether to expect the input to be consumed completed - @param[in] tag_handler how to treat CBOR tags - - @return - */ - JSON_HEDLEY_NON_NULL(3) - bool sax_parse(const input_format_t format, - json_sax_t* sax_, - const bool strict = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) - { - sax = sax_; - bool result = false; - - switch (format) - { - case input_format_t::bson: - result = parse_bson_internal(); - break; - - case input_format_t::cbor: - result = parse_cbor_internal(true, tag_handler); - break; - - case input_format_t::msgpack: - result = parse_msgpack_internal(); - break; - - case input_format_t::ubjson: - result = parse_ubjson_internal(); - break; - - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } - - // strict mode: next byte must be EOF - if (result && strict) - { - if (format == input_format_t::ubjson) - { - get_ignore_noop(); - } - else - { - get(); - } - - if (JSON_HEDLEY_UNLIKELY(current != std::char_traits::eof())) - { - return sax->parse_error(chars_read, get_token_string(), - parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value"))); - } - } - - return result; - } - - private: - ////////// - // BSON // - ////////// - - /*! - @brief Reads in a BSON-object and passes it to the SAX-parser. - @return whether a valid BSON-value was passed to the SAX parser - */ - bool parse_bson_internal() - { - std::int32_t document_size{}; - get_number(input_format_t::bson, document_size); - - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) - { - return false; - } - - if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false))) - { - return false; - } - - return sax->end_object(); - } - - /*! - @brief Parses a C-style string from the BSON input. - @param[in, out] result A reference to the string variable where the read - string is to be stored. - @return `true` if the \x00-byte indicating the end of the string was - encountered before the EOF; false` indicates an unexpected EOF. - */ - bool get_bson_cstr(string_t& result) - { - auto out = std::back_inserter(result); - while (true) - { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "cstring"))) - { - return false; - } - if (current == 0x00) - { - return true; - } - *out++ = static_cast(current); - } - } - - /*! - @brief Parses a zero-terminated string of length @a len from the BSON - input. - @param[in] len The length (including the zero-byte at the end) of the - string to be read. - @param[in, out] result A reference to the string variable where the read - string is to be stored. - @tparam NumberType The type of the length @a len - @pre len >= 1 - @return `true` if the string was successfully parsed - */ - template - bool get_bson_string(const NumberType len, string_t& result) - { - if (JSON_HEDLEY_UNLIKELY(len < 1)) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string"))); - } - - return get_string(input_format_t::bson, len - static_cast(1), result) && get() != std::char_traits::eof(); - } - - /*! - @brief Parses a byte array input of length @a len from the BSON input. - @param[in] len The length of the byte array to be read. - @param[in, out] result A reference to the binary variable where the read - array is to be stored. - @tparam NumberType The type of the length @a len - @pre len >= 0 - @return `true` if the byte array was successfully parsed - */ - template - bool get_bson_binary(const NumberType len, binary_t& result) - { - if (JSON_HEDLEY_UNLIKELY(len < 0)) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "byte array length cannot be negative, is " + std::to_string(len), "binary"))); - } - - // All BSON binary values have a subtype - std::uint8_t subtype{}; - get_number(input_format_t::bson, subtype); - result.set_subtype(subtype); - - return get_binary(input_format_t::bson, len, result); - } - - /*! - @brief Read a BSON document element of the given @a element_type. - @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html - @param[in] element_type_parse_position The position in the input stream, - where the `element_type` was read. - @warning Not all BSON element types are supported yet. An unsupported - @a element_type will give rise to a parse_error.114: - Unsupported BSON record type 0x... - @return whether a valid BSON-object/array was passed to the SAX parser - */ - bool parse_bson_element_internal(const char_int_type element_type, - const std::size_t element_type_parse_position) - { - switch (element_type) - { - case 0x01: // double - { - double number{}; - return get_number(input_format_t::bson, number) && sax->number_float(static_cast(number), ""); - } - - case 0x02: // string - { - std::int32_t len{}; - string_t value; - return get_number(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value); - } - - case 0x03: // object - { - return parse_bson_internal(); - } - - case 0x04: // array - { - return parse_bson_array(); - } - - case 0x05: // binary - { - std::int32_t len{}; - binary_t value; - return get_number(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value); - } - - case 0x08: // boolean - { - return sax->boolean(get() != 0); - } - - case 0x0A: // null - { - return sax->null(); - } - - case 0x10: // int32 - { - std::int32_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); - } - - case 0x12: // int64 - { - std::int64_t value{}; - return get_number(input_format_t::bson, value) && sax->number_integer(value); - } - - default: // anything else not supported (yet) - { - std::array cr{{}}; - (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(element_type)); - return sax->parse_error(element_type_parse_position, std::string(cr.data()), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr.data()))); - } - } - } - - /*! - @brief Read a BSON element list (as specified in the BSON-spec) - - The same binary layout is used for objects and arrays, hence it must be - indicated with the argument @a is_array which one is expected - (true --> array, false --> object). - - @param[in] is_array Determines if the element list being read is to be - treated as an object (@a is_array == false), or as an - array (@a is_array == true). - @return whether a valid BSON-object/array was passed to the SAX parser - */ - bool parse_bson_element_list(const bool is_array) - { - string_t key; - - while (auto element_type = get()) - { - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, "element list"))) - { - return false; - } - - const std::size_t element_type_parse_position = chars_read; - if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key))) - { - return false; - } - - if (!is_array && !sax->key(key)) - { - return false; - } - - if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position))) - { - return false; - } - - // get_bson_cstr only appends - key.clear(); - } - - return true; - } - - /*! - @brief Reads an array from the BSON input and passes it to the SAX-parser. - @return whether a valid BSON-array was passed to the SAX parser - */ - bool parse_bson_array() - { - std::int32_t document_size{}; - get_number(input_format_t::bson, document_size); - - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) - { - return false; - } - - if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true))) - { - return false; - } - - return sax->end_array(); - } - - ////////// - // CBOR // - ////////// - - /*! - @param[in] get_char whether a new character should be retrieved from the - input (true) or whether the last read character should - be considered instead (false) - @param[in] tag_handler how CBOR tags should be treated - - @return whether a valid CBOR value was passed to the SAX parser - */ - bool parse_cbor_internal(const bool get_char, - const cbor_tag_handler_t tag_handler) - { - switch (get_char ? get() : current) - { - // EOF - case std::char_traits::eof(): - return unexpect_eof(input_format_t::cbor, "value"); - - // Integer 0x00..0x17 (0..23) - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0A: - case 0x0B: - case 0x0C: - case 0x0D: - case 0x0E: - case 0x0F: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - return sax->number_unsigned(static_cast(current)); - - case 0x18: // Unsigned integer (one-byte uint8_t follows) - { - std::uint8_t number{}; - return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); - } - - case 0x19: // Unsigned integer (two-byte uint16_t follows) - { - std::uint16_t number{}; - return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); - } - - case 0x1A: // Unsigned integer (four-byte uint32_t follows) - { - std::uint32_t number{}; - return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); - } - - case 0x1B: // Unsigned integer (eight-byte uint64_t follows) - { - std::uint64_t number{}; - return get_number(input_format_t::cbor, number) && sax->number_unsigned(number); - } - - // Negative integer -1-0x00..-1-0x17 (-1..-24) - case 0x20: - case 0x21: - case 0x22: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2A: - case 0x2B: - case 0x2C: - case 0x2D: - case 0x2E: - case 0x2F: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - return sax->number_integer(static_cast(0x20 - 1 - current)); - - case 0x38: // Negative integer (one-byte uint8_t follows) - { - std::uint8_t number{}; - return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); - } - - case 0x39: // Negative integer -1-n (two-byte uint16_t follows) - { - std::uint16_t number{}; - return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); - } - - case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) - { - std::uint32_t number{}; - return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - number); - } - - case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) - { - std::uint64_t number{}; - return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast(-1) - - static_cast(number)); - } - - // Binary data (0x00..0x17 bytes follow) - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: // Binary data (one-byte uint8_t for n follows) - case 0x59: // Binary data (two-byte uint16_t for n follow) - case 0x5A: // Binary data (four-byte uint32_t for n follow) - case 0x5B: // Binary data (eight-byte uint64_t for n follow) - case 0x5F: // Binary data (indefinite length) - { - binary_t b; - return get_cbor_binary(b) && sax->binary(b); - } - - // UTF-8 string (0x00..0x17 bytes follow) - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: // UTF-8 string (one-byte uint8_t for n follows) - case 0x79: // UTF-8 string (two-byte uint16_t for n follow) - case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) - case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) - case 0x7F: // UTF-8 string (indefinite length) - { - string_t s; - return get_cbor_string(s) && sax->string(s); - } - - // array (0x00..0x17 data items follow) - case 0x80: - case 0x81: - case 0x82: - case 0x83: - case 0x84: - case 0x85: - case 0x86: - case 0x87: - case 0x88: - case 0x89: - case 0x8A: - case 0x8B: - case 0x8C: - case 0x8D: - case 0x8E: - case 0x8F: - case 0x90: - case 0x91: - case 0x92: - case 0x93: - case 0x94: - case 0x95: - case 0x96: - case 0x97: - return get_cbor_array(static_cast(static_cast(current) & 0x1Fu), tag_handler); - - case 0x98: // array (one-byte uint8_t for n follows) - { - std::uint8_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); - } - - case 0x99: // array (two-byte uint16_t for n follow) - { - std::uint16_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); - } - - case 0x9A: // array (four-byte uint32_t for n follow) - { - std::uint32_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); - } - - case 0x9B: // array (eight-byte uint64_t for n follow) - { - std::uint64_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast(len), tag_handler); - } - - case 0x9F: // array (indefinite length) - return get_cbor_array(std::size_t(-1), tag_handler); - - // map (0x00..0x17 pairs of data items follow) - case 0xA0: - case 0xA1: - case 0xA2: - case 0xA3: - case 0xA4: - case 0xA5: - case 0xA6: - case 0xA7: - case 0xA8: - case 0xA9: - case 0xAA: - case 0xAB: - case 0xAC: - case 0xAD: - case 0xAE: - case 0xAF: - case 0xB0: - case 0xB1: - case 0xB2: - case 0xB3: - case 0xB4: - case 0xB5: - case 0xB6: - case 0xB7: - return get_cbor_object(static_cast(static_cast(current) & 0x1Fu), tag_handler); - - case 0xB8: // map (one-byte uint8_t for n follows) - { - std::uint8_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); - } - - case 0xB9: // map (two-byte uint16_t for n follow) - { - std::uint16_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); - } - - case 0xBA: // map (four-byte uint32_t for n follow) - { - std::uint32_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); - } - - case 0xBB: // map (eight-byte uint64_t for n follow) - { - std::uint64_t len{}; - return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast(len), tag_handler); - } - - case 0xBF: // map (indefinite length) - return get_cbor_object(std::size_t(-1), tag_handler); - - case 0xC6: // tagged item - case 0xC7: - case 0xC8: - case 0xC9: - case 0xCA: - case 0xCB: - case 0xCC: - case 0xCD: - case 0xCE: - case 0xCF: - case 0xD0: - case 0xD1: - case 0xD2: - case 0xD3: - case 0xD4: - case 0xD8: // tagged item (1 bytes follow) - case 0xD9: // tagged item (2 bytes follow) - case 0xDA: // tagged item (4 bytes follow) - case 0xDB: // tagged item (8 bytes follow) - { - switch (tag_handler) - { - case cbor_tag_handler_t::error: - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); - } - - case cbor_tag_handler_t::ignore: - { - switch (current) - { - case 0xD8: - { - std::uint8_t len{}; - get_number(input_format_t::cbor, len); - break; - } - case 0xD9: - { - std::uint16_t len{}; - get_number(input_format_t::cbor, len); - break; - } - case 0xDA: - { - std::uint32_t len{}; - get_number(input_format_t::cbor, len); - break; - } - case 0xDB: - { - std::uint64_t len{}; - get_number(input_format_t::cbor, len); - break; - } - default: - break; - } - return parse_cbor_internal(true, tag_handler); - } - - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } - } - - case 0xF4: // false - return sax->boolean(false); - - case 0xF5: // true - return sax->boolean(true); - - case 0xF6: // null - return sax->null(); - - case 0xF9: // Half-Precision Float (two-byte IEEE 754) - { - const auto byte1_raw = get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) - { - return false; - } - const auto byte2_raw = get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "number"))) - { - return false; - } - - const auto byte1 = static_cast(byte1_raw); - const auto byte2 = static_cast(byte2_raw); - - // code from RFC 7049, Appendix D, Figure 3: - // As half-precision floating-point numbers were only added - // to IEEE 754 in 2008, today's programming platforms often - // still only have limited support for them. It is very - // easy to include at least decoding support for them even - // without such support. An example of a small decoder for - // half-precision floating-point numbers in the C language - // is shown in Fig. 3. - const auto half = static_cast((byte1 << 8u) + byte2); - const double val = [&half] - { - const int exp = (half >> 10u) & 0x1Fu; - const unsigned int mant = half & 0x3FFu; - JSON_ASSERT(0 <= exp&& exp <= 32); - JSON_ASSERT(mant <= 1024); - switch (exp) - { - case 0: - return std::ldexp(mant, -24); - case 31: - return (mant == 0) - ? std::numeric_limits::infinity() - : std::numeric_limits::quiet_NaN(); - default: - return std::ldexp(mant + 1024, exp - 25); - } - }(); - return sax->number_float((half & 0x8000u) != 0 - ? static_cast(-val) - : static_cast(val), ""); - } - - case 0xFA: // Single-Precision Float (four-byte IEEE 754) - { - float number{}; - return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); - } - - case 0xFB: // Double-Precision Float (eight-byte IEEE 754) - { - double number{}; - return get_number(input_format_t::cbor, number) && sax->number_float(static_cast(number), ""); - } - - default: // anything else (0xFF is handled inside the other types) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value"))); - } - } - } - - /*! - @brief reads a CBOR string - - This function first reads starting bytes to determine the expected - string length and then copies this number of bytes into a string. - Additionally, CBOR's strings with indefinite lengths are supported. - - @param[out] result created string - - @return whether string creation completed - */ - bool get_cbor_string(string_t& result) - { - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "string"))) - { - return false; - } - - switch (current) - { - // UTF-8 string (0x00..0x17 bytes follow) - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - { - return get_string(input_format_t::cbor, static_cast(current) & 0x1Fu, result); - } - - case 0x78: // UTF-8 string (one-byte uint8_t for n follows) - { - std::uint8_t len{}; - return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); - } - - case 0x79: // UTF-8 string (two-byte uint16_t for n follow) - { - std::uint16_t len{}; - return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); - } - - case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) - { - std::uint32_t len{}; - return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); - } - - case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) - { - std::uint64_t len{}; - return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result); - } - - case 0x7F: // UTF-8 string (indefinite length) - { - while (get() != 0xFF) - { - string_t chunk; - if (!get_cbor_string(chunk)) - { - return false; - } - result.append(chunk); - } - return true; - } - - default: - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string"))); - } - } - } - - /*! - @brief reads a CBOR byte array - - This function first reads starting bytes to determine the expected - byte array length and then copies this number of bytes into the byte array. - Additionally, CBOR's byte arrays with indefinite lengths are supported. - - @param[out] result created byte array - - @return whether byte array creation completed - */ - bool get_cbor_binary(binary_t& result) - { - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, "binary"))) - { - return false; - } - - switch (current) - { - // Binary data (0x00..0x17 bytes follow) - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - { - return get_binary(input_format_t::cbor, static_cast(current) & 0x1Fu, result); - } - - case 0x58: // Binary data (one-byte uint8_t for n follows) - { - std::uint8_t len{}; - return get_number(input_format_t::cbor, len) && - get_binary(input_format_t::cbor, len, result); - } - - case 0x59: // Binary data (two-byte uint16_t for n follow) - { - std::uint16_t len{}; - return get_number(input_format_t::cbor, len) && - get_binary(input_format_t::cbor, len, result); - } - - case 0x5A: // Binary data (four-byte uint32_t for n follow) - { - std::uint32_t len{}; - return get_number(input_format_t::cbor, len) && - get_binary(input_format_t::cbor, len, result); - } - - case 0x5B: // Binary data (eight-byte uint64_t for n follow) - { - std::uint64_t len{}; - return get_number(input_format_t::cbor, len) && - get_binary(input_format_t::cbor, len, result); - } - - case 0x5F: // Binary data (indefinite length) - { - while (get() != 0xFF) - { - binary_t chunk; - if (!get_cbor_binary(chunk)) - { - return false; - } - result.insert(result.end(), chunk.begin(), chunk.end()); - } - return true; - } - - default: - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x" + last_token, "binary"))); - } - } - } - - /*! - @param[in] len the length of the array or std::size_t(-1) for an - array of indefinite size - @param[in] tag_handler how CBOR tags should be treated - @return whether array creation completed - */ - bool get_cbor_array(const std::size_t len, - const cbor_tag_handler_t tag_handler) - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) - { - return false; - } - - if (len != std::size_t(-1)) - { - for (std::size_t i = 0; i < len; ++i) - { - if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) - { - return false; - } - } - } - else - { - while (get() != 0xFF) - { - if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler))) - { - return false; - } - } - } - - return sax->end_array(); - } - - /*! - @param[in] len the length of the object or std::size_t(-1) for an - object of indefinite size - @param[in] tag_handler how CBOR tags should be treated - @return whether object creation completed - */ - bool get_cbor_object(const std::size_t len, - const cbor_tag_handler_t tag_handler) - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) - { - return false; - } - - string_t key; - if (len != std::size_t(-1)) - { - for (std::size_t i = 0; i < len; ++i) - { - get(); - if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) - { - return false; - } - - if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) - { - return false; - } - key.clear(); - } - } - else - { - while (get() != 0xFF) - { - if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key))) - { - return false; - } - - if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler))) - { - return false; - } - key.clear(); - } - } - - return sax->end_object(); - } - - ///////////// - // MsgPack // - ///////////// - - /*! - @return whether a valid MessagePack value was passed to the SAX parser - */ - bool parse_msgpack_internal() - { - switch (get()) - { - // EOF - case std::char_traits::eof(): - return unexpect_eof(input_format_t::msgpack, "value"); - - // positive fixint - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0A: - case 0x0B: - case 0x0C: - case 0x0D: - case 0x0E: - case 0x0F: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - case 0x18: - case 0x19: - case 0x1A: - case 0x1B: - case 0x1C: - case 0x1D: - case 0x1E: - case 0x1F: - case 0x20: - case 0x21: - case 0x22: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2A: - case 0x2B: - case 0x2C: - case 0x2D: - case 0x2E: - case 0x2F: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4A: - case 0x4B: - case 0x4C: - case 0x4D: - case 0x4E: - case 0x4F: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5A: - case 0x5B: - case 0x5C: - case 0x5D: - case 0x5E: - case 0x5F: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6A: - case 0x6B: - case 0x6C: - case 0x6D: - case 0x6E: - case 0x6F: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7A: - case 0x7B: - case 0x7C: - case 0x7D: - case 0x7E: - case 0x7F: - return sax->number_unsigned(static_cast(current)); - - // fixmap - case 0x80: - case 0x81: - case 0x82: - case 0x83: - case 0x84: - case 0x85: - case 0x86: - case 0x87: - case 0x88: - case 0x89: - case 0x8A: - case 0x8B: - case 0x8C: - case 0x8D: - case 0x8E: - case 0x8F: - return get_msgpack_object(static_cast(static_cast(current) & 0x0Fu)); - - // fixarray - case 0x90: - case 0x91: - case 0x92: - case 0x93: - case 0x94: - case 0x95: - case 0x96: - case 0x97: - case 0x98: - case 0x99: - case 0x9A: - case 0x9B: - case 0x9C: - case 0x9D: - case 0x9E: - case 0x9F: - return get_msgpack_array(static_cast(static_cast(current) & 0x0Fu)); - - // fixstr - case 0xA0: - case 0xA1: - case 0xA2: - case 0xA3: - case 0xA4: - case 0xA5: - case 0xA6: - case 0xA7: - case 0xA8: - case 0xA9: - case 0xAA: - case 0xAB: - case 0xAC: - case 0xAD: - case 0xAE: - case 0xAF: - case 0xB0: - case 0xB1: - case 0xB2: - case 0xB3: - case 0xB4: - case 0xB5: - case 0xB6: - case 0xB7: - case 0xB8: - case 0xB9: - case 0xBA: - case 0xBB: - case 0xBC: - case 0xBD: - case 0xBE: - case 0xBF: - case 0xD9: // str 8 - case 0xDA: // str 16 - case 0xDB: // str 32 - { - string_t s; - return get_msgpack_string(s) && sax->string(s); - } - - case 0xC0: // nil - return sax->null(); - - case 0xC2: // false - return sax->boolean(false); - - case 0xC3: // true - return sax->boolean(true); - - case 0xC4: // bin 8 - case 0xC5: // bin 16 - case 0xC6: // bin 32 - case 0xC7: // ext 8 - case 0xC8: // ext 16 - case 0xC9: // ext 32 - case 0xD4: // fixext 1 - case 0xD5: // fixext 2 - case 0xD6: // fixext 4 - case 0xD7: // fixext 8 - case 0xD8: // fixext 16 - { - binary_t b; - return get_msgpack_binary(b) && sax->binary(b); - } - - case 0xCA: // float 32 - { - float number{}; - return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); - } - - case 0xCB: // float 64 - { - double number{}; - return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast(number), ""); - } - - case 0xCC: // uint 8 - { - std::uint8_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); - } - - case 0xCD: // uint 16 - { - std::uint16_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); - } - - case 0xCE: // uint 32 - { - std::uint32_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); - } - - case 0xCF: // uint 64 - { - std::uint64_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number); - } - - case 0xD0: // int 8 - { - std::int8_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); - } - - case 0xD1: // int 16 - { - std::int16_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); - } - - case 0xD2: // int 32 - { - std::int32_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); - } - - case 0xD3: // int 64 - { - std::int64_t number{}; - return get_number(input_format_t::msgpack, number) && sax->number_integer(number); - } - - case 0xDC: // array 16 - { - std::uint16_t len{}; - return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); - } - - case 0xDD: // array 32 - { - std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast(len)); - } - - case 0xDE: // map 16 - { - std::uint16_t len{}; - return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); - } - - case 0xDF: // map 32 - { - std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast(len)); - } - - // negative fixint - case 0xE0: - case 0xE1: - case 0xE2: - case 0xE3: - case 0xE4: - case 0xE5: - case 0xE6: - case 0xE7: - case 0xE8: - case 0xE9: - case 0xEA: - case 0xEB: - case 0xEC: - case 0xED: - case 0xEE: - case 0xEF: - case 0xF0: - case 0xF1: - case 0xF2: - case 0xF3: - case 0xF4: - case 0xF5: - case 0xF6: - case 0xF7: - case 0xF8: - case 0xF9: - case 0xFA: - case 0xFB: - case 0xFC: - case 0xFD: - case 0xFE: - case 0xFF: - return sax->number_integer(static_cast(current)); - - default: // anything else - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value"))); - } - } - } - - /*! - @brief reads a MessagePack string - - This function first reads starting bytes to determine the expected - string length and then copies this number of bytes into a string. - - @param[out] result created string - - @return whether string creation completed - */ - bool get_msgpack_string(string_t& result) - { - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, "string"))) - { - return false; - } - - switch (current) - { - // fixstr - case 0xA0: - case 0xA1: - case 0xA2: - case 0xA3: - case 0xA4: - case 0xA5: - case 0xA6: - case 0xA7: - case 0xA8: - case 0xA9: - case 0xAA: - case 0xAB: - case 0xAC: - case 0xAD: - case 0xAE: - case 0xAF: - case 0xB0: - case 0xB1: - case 0xB2: - case 0xB3: - case 0xB4: - case 0xB5: - case 0xB6: - case 0xB7: - case 0xB8: - case 0xB9: - case 0xBA: - case 0xBB: - case 0xBC: - case 0xBD: - case 0xBE: - case 0xBF: - { - return get_string(input_format_t::msgpack, static_cast(current) & 0x1Fu, result); - } - - case 0xD9: // str 8 - { - std::uint8_t len{}; - return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); - } - - case 0xDA: // str 16 - { - std::uint16_t len{}; - return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); - } - - case 0xDB: // str 32 - { - std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result); - } - - default: - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string"))); - } - } - } - - /*! - @brief reads a MessagePack byte array - - This function first reads starting bytes to determine the expected - byte array length and then copies this number of bytes into a byte array. - - @param[out] result created byte array - - @return whether byte array creation completed - */ - bool get_msgpack_binary(binary_t& result) - { - // helper function to set the subtype - auto assign_and_return_true = [&result](std::int8_t subtype) - { - result.set_subtype(static_cast(subtype)); - return true; - }; - - switch (current) - { - case 0xC4: // bin 8 - { - std::uint8_t len{}; - return get_number(input_format_t::msgpack, len) && - get_binary(input_format_t::msgpack, len, result); - } - - case 0xC5: // bin 16 - { - std::uint16_t len{}; - return get_number(input_format_t::msgpack, len) && - get_binary(input_format_t::msgpack, len, result); - } - - case 0xC6: // bin 32 - { - std::uint32_t len{}; - return get_number(input_format_t::msgpack, len) && - get_binary(input_format_t::msgpack, len, result); - } - - case 0xC7: // ext 8 - { - std::uint8_t len{}; - std::int8_t subtype{}; - return get_number(input_format_t::msgpack, len) && - get_number(input_format_t::msgpack, subtype) && - get_binary(input_format_t::msgpack, len, result) && - assign_and_return_true(subtype); - } - - case 0xC8: // ext 16 - { - std::uint16_t len{}; - std::int8_t subtype{}; - return get_number(input_format_t::msgpack, len) && - get_number(input_format_t::msgpack, subtype) && - get_binary(input_format_t::msgpack, len, result) && - assign_and_return_true(subtype); - } - - case 0xC9: // ext 32 - { - std::uint32_t len{}; - std::int8_t subtype{}; - return get_number(input_format_t::msgpack, len) && - get_number(input_format_t::msgpack, subtype) && - get_binary(input_format_t::msgpack, len, result) && - assign_and_return_true(subtype); - } - - case 0xD4: // fixext 1 - { - std::int8_t subtype{}; - return get_number(input_format_t::msgpack, subtype) && - get_binary(input_format_t::msgpack, 1, result) && - assign_and_return_true(subtype); - } - - case 0xD5: // fixext 2 - { - std::int8_t subtype{}; - return get_number(input_format_t::msgpack, subtype) && - get_binary(input_format_t::msgpack, 2, result) && - assign_and_return_true(subtype); - } - - case 0xD6: // fixext 4 - { - std::int8_t subtype{}; - return get_number(input_format_t::msgpack, subtype) && - get_binary(input_format_t::msgpack, 4, result) && - assign_and_return_true(subtype); - } - - case 0xD7: // fixext 8 - { - std::int8_t subtype{}; - return get_number(input_format_t::msgpack, subtype) && - get_binary(input_format_t::msgpack, 8, result) && - assign_and_return_true(subtype); - } - - case 0xD8: // fixext 16 - { - std::int8_t subtype{}; - return get_number(input_format_t::msgpack, subtype) && - get_binary(input_format_t::msgpack, 16, result) && - assign_and_return_true(subtype); - } - - default: // LCOV_EXCL_LINE - return false; // LCOV_EXCL_LINE - } - } - - /*! - @param[in] len the length of the array - @return whether array creation completed - */ - bool get_msgpack_array(const std::size_t len) - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len))) - { - return false; - } - - for (std::size_t i = 0; i < len; ++i) - { - if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) - { - return false; - } - } - - return sax->end_array(); - } - - /*! - @param[in] len the length of the object - @return whether object creation completed - */ - bool get_msgpack_object(const std::size_t len) - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len))) - { - return false; - } - - string_t key; - for (std::size_t i = 0; i < len; ++i) - { - get(); - if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key))) - { - return false; - } - - if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal())) - { - return false; - } - key.clear(); - } - - return sax->end_object(); - } - - //////////// - // UBJSON // - //////////// - - /*! - @param[in] get_char whether a new character should be retrieved from the - input (true, default) or whether the last read - character should be considered instead - - @return whether a valid UBJSON value was passed to the SAX parser - */ - bool parse_ubjson_internal(const bool get_char = true) - { - return get_ubjson_value(get_char ? get_ignore_noop() : current); - } - - /*! - @brief reads a UBJSON string - - This function is either called after reading the 'S' byte explicitly - indicating a string, or in case of an object key where the 'S' byte can be - left out. - - @param[out] result created string - @param[in] get_char whether a new character should be retrieved from the - input (true, default) or whether the last read - character should be considered instead - - @return whether string creation completed - */ - bool get_ubjson_string(string_t& result, const bool get_char = true) - { - if (get_char) - { - get(); // TODO(niels): may we ignore N here? - } - - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) - { - return false; - } - - switch (current) - { - case 'U': - { - std::uint8_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); - } - - case 'i': - { - std::int8_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); - } - - case 'I': - { - std::int16_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); - } - - case 'l': - { - std::int32_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); - } - - case 'L': - { - std::int64_t len{}; - return get_number(input_format_t::ubjson, len) && get_string(input_format_t::ubjson, len, result); - } - - default: - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string"))); - } - } - - /*! - @param[out] result determined size - @return whether size determination completed - */ - bool get_ubjson_size_value(std::size_t& result) - { - switch (get_ignore_noop()) - { - case 'U': - { - std::uint8_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) - { - return false; - } - result = static_cast(number); - return true; - } - - case 'i': - { - std::int8_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) - { - return false; - } - result = static_cast(number); - return true; - } - - case 'I': - { - std::int16_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) - { - return false; - } - result = static_cast(number); - return true; - } - - case 'l': - { - std::int32_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) - { - return false; - } - result = static_cast(number); - return true; - } - - case 'L': - { - std::int64_t number{}; - if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::ubjson, number))) - { - return false; - } - result = static_cast(number); - return true; - } - - default: - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size"))); - } - } - } - - /*! - @brief determine the type and size for a container - - In the optimized UBJSON format, a type and a size can be provided to allow - for a more compact representation. - - @param[out] result pair of the size and the type - - @return whether pair creation completed - */ - bool get_ubjson_size_type(std::pair& result) - { - result.first = string_t::npos; // size - result.second = 0; // type - - get_ignore_noop(); - - if (current == '$') - { - result.second = get(); // must not ignore 'N', because 'N' maybe the type - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "type"))) - { - return false; - } - - get_ignore_noop(); - if (JSON_HEDLEY_UNLIKELY(current != '#')) - { - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "value"))) - { - return false; - } - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size"))); - } - - return get_ubjson_size_value(result.first); - } - - if (current == '#') - { - return get_ubjson_size_value(result.first); - } - - return true; - } - - /*! - @param prefix the previously read or set type prefix - @return whether value creation completed - */ - bool get_ubjson_value(const char_int_type prefix) - { - switch (prefix) - { - case std::char_traits::eof(): // EOF - return unexpect_eof(input_format_t::ubjson, "value"); - - case 'T': // true - return sax->boolean(true); - case 'F': // false - return sax->boolean(false); - - case 'Z': // null - return sax->null(); - - case 'U': - { - std::uint8_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_unsigned(number); - } - - case 'i': - { - std::int8_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); - } - - case 'I': - { - std::int16_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); - } - - case 'l': - { - std::int32_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); - } - - case 'L': - { - std::int64_t number{}; - return get_number(input_format_t::ubjson, number) && sax->number_integer(number); - } - - case 'd': - { - float number{}; - return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); - } - - case 'D': - { - double number{}; - return get_number(input_format_t::ubjson, number) && sax->number_float(static_cast(number), ""); - } - - case 'H': - { - return get_ubjson_high_precision_number(); - } - - case 'C': // char - { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "char"))) - { - return false; - } - if (JSON_HEDLEY_UNLIKELY(current > 127)) - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char"))); - } - string_t s(1, static_cast(current)); - return sax->string(s); - } - - case 'S': // string - { - string_t s; - return get_ubjson_string(s) && sax->string(s); - } - - case '[': // array - return get_ubjson_array(); - - case '{': // object - return get_ubjson_object(); - - default: // anything else - { - auto last_token = get_token_string(); - return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value"))); - } - } - } - - /*! - @return whether array creation completed - */ - bool get_ubjson_array() - { - std::pair size_and_type; - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) - { - return false; - } - - if (size_and_type.first != string_t::npos) - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first))) - { - return false; - } - - if (size_and_type.second != 0) - { - if (size_and_type.second != 'N') - { - for (std::size_t i = 0; i < size_and_type.first; ++i) - { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) - { - return false; - } - } - } - } - else - { - for (std::size_t i = 0; i < size_and_type.first; ++i) - { - if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) - { - return false; - } - } - } - } - else - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) - { - return false; - } - - while (current != ']') - { - if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false))) - { - return false; - } - get_ignore_noop(); - } - } - - return sax->end_array(); - } - - /*! - @return whether object creation completed - */ - bool get_ubjson_object() - { - std::pair size_and_type; - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type))) - { - return false; - } - - string_t key; - if (size_and_type.first != string_t::npos) - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first))) - { - return false; - } - - if (size_and_type.second != 0) - { - for (std::size_t i = 0; i < size_and_type.first; ++i) - { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) - { - return false; - } - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second))) - { - return false; - } - key.clear(); - } - } - else - { - for (std::size_t i = 0; i < size_and_type.first; ++i) - { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key))) - { - return false; - } - if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) - { - return false; - } - key.clear(); - } - } - } - else - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) - { - return false; - } - - while (current != '}') - { - if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key))) - { - return false; - } - if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal())) - { - return false; - } - get_ignore_noop(); - key.clear(); - } - } - - return sax->end_object(); - } - - // Note, no reader for UBJSON binary types is implemented because they do - // not exist - - bool get_ubjson_high_precision_number() - { - // get size of following number string - std::size_t size{}; - auto res = get_ubjson_size_value(size); - if (JSON_HEDLEY_UNLIKELY(!res)) - { - return res; - } - - // get number string - std::vector number_vector; - for (std::size_t i = 0; i < size; ++i) - { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::ubjson, "number"))) - { - return false; - } - number_vector.push_back(static_cast(current)); - } - - // parse number string - auto number_ia = detail::input_adapter(std::forward(number_vector)); - auto number_lexer = detail::lexer(std::move(number_ia), false); - const auto result_number = number_lexer.scan(); - const auto number_string = number_lexer.get_token_string(); - const auto result_remainder = number_lexer.scan(); - - using token_type = typename detail::lexer_base::token_type; - - if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input)) - { - return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); - } - - switch (result_number) - { - case token_type::value_integer: - return sax->number_integer(number_lexer.get_number_integer()); - case token_type::value_unsigned: - return sax->number_unsigned(number_lexer.get_number_unsigned()); - case token_type::value_float: - return sax->number_float(number_lexer.get_number_float(), std::move(number_string)); - default: - return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read, exception_message(input_format_t::ubjson, "invalid number text: " + number_lexer.get_token_string(), "high-precision number"))); - } - } - - /////////////////////// - // Utility functions // - /////////////////////// - - /*! - @brief get next character from the input - - This function provides the interface to the used input adapter. It does - not throw in case the input reached EOF, but returns a -'ve valued - `std::char_traits::eof()` in that case. - - @return character read from the input - */ - char_int_type get() - { - ++chars_read; - return current = ia.get_character(); - } - - /*! - @return character read from the input after ignoring all 'N' entries - */ - char_int_type get_ignore_noop() - { - do - { - get(); - } - while (current == 'N'); - - return current; - } - - /* - @brief read a number from the input - - @tparam NumberType the type of the number - @param[in] format the current format (for diagnostics) - @param[out] result number of type @a NumberType - - @return whether conversion completed - - @note This function needs to respect the system's endianess, because - bytes in CBOR, MessagePack, and UBJSON are stored in network order - (big endian) and therefore need reordering on little endian systems. - */ - template - bool get_number(const input_format_t format, NumberType& result) - { - // step 1: read input into array with system's byte order - std::array vec; - for (std::size_t i = 0; i < sizeof(NumberType); ++i) - { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "number"))) - { - return false; - } - - // reverse byte order prior to conversion if necessary - if (is_little_endian != InputIsLittleEndian) - { - vec[sizeof(NumberType) - i - 1] = static_cast(current); - } - else - { - vec[i] = static_cast(current); // LCOV_EXCL_LINE - } - } - - // step 2: convert array into number of type T and return - std::memcpy(&result, vec.data(), sizeof(NumberType)); - return true; - } - - /*! - @brief create a string by reading characters from the input - - @tparam NumberType the type of the number - @param[in] format the current format (for diagnostics) - @param[in] len number of characters to read - @param[out] result string created by reading @a len bytes - - @return whether string creation completed - - @note We can not reserve @a len bytes for the result, because @a len - may be too large. Usually, @ref unexpect_eof() detects the end of - the input before we run out of string memory. - */ - template - bool get_string(const input_format_t format, - const NumberType len, - string_t& result) - { - bool success = true; - for (NumberType i = 0; i < len; i++) - { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string"))) - { - success = false; - break; - } - result.push_back(static_cast(current)); - }; - return success; - } - - /*! - @brief create a byte array by reading bytes from the input - - @tparam NumberType the type of the number - @param[in] format the current format (for diagnostics) - @param[in] len number of bytes to read - @param[out] result byte array created by reading @a len bytes - - @return whether byte array creation completed - - @note We can not reserve @a len bytes for the result, because @a len - may be too large. Usually, @ref unexpect_eof() detects the end of - the input before we run out of memory. - */ - template - bool get_binary(const input_format_t format, - const NumberType len, - binary_t& result) - { - bool success = true; - for (NumberType i = 0; i < len; i++) - { - get(); - if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary"))) - { - success = false; - break; - } - result.push_back(static_cast(current)); - } - return success; - } - - /*! - @param[in] format the current format (for diagnostics) - @param[in] context further context information (for diagnostics) - @return whether the last read character is not EOF - */ - JSON_HEDLEY_NON_NULL(3) - bool unexpect_eof(const input_format_t format, const char* context) const - { - if (JSON_HEDLEY_UNLIKELY(current == std::char_traits::eof())) - { - return sax->parse_error(chars_read, "", - parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context))); - } - return true; - } - - /*! - @return a string representation of the last read byte - */ - std::string get_token_string() const - { - std::array cr{{}}; - (std::snprintf)(cr.data(), cr.size(), "%.2hhX", static_cast(current)); - return std::string{cr.data()}; - } - - /*! - @param[in] format the current format - @param[in] detail a detailed error message - @param[in] context further context information - @return a message string to use in the parse_error exceptions - */ - std::string exception_message(const input_format_t format, - const std::string& detail, - const std::string& context) const - { - std::string error_msg = "syntax error while parsing "; - - switch (format) - { - case input_format_t::cbor: - error_msg += "CBOR"; - break; - - case input_format_t::msgpack: - error_msg += "MessagePack"; - break; - - case input_format_t::ubjson: - error_msg += "UBJSON"; - break; - - case input_format_t::bson: - error_msg += "BSON"; - break; - - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } - - return error_msg + " " + context + ": " + detail; - } - - private: - /// input adapter - InputAdapterType ia; - - /// the current character - char_int_type current = std::char_traits::eof(); - - /// the number of characters read - std::size_t chars_read = 0; - - /// whether we can assume little endianess - const bool is_little_endian = little_endianess(); - - /// the SAX parser - json_sax_t* sax = nullptr; -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - -// #include - - -#include // isfinite -#include // uint8_t -#include // function -#include // string -#include // move -#include // vector - -// #include - -// #include - -// #include - -// #include - -// #include - -// #include - -// #include - - -namespace nlohmann -{ -namespace detail -{ -//////////// -// parser // -//////////// - -enum class parse_event_t : uint8_t -{ - /// the parser read `{` and started to process a JSON object - object_start, - /// the parser read `}` and finished processing a JSON object - object_end, - /// the parser read `[` and started to process a JSON array - array_start, - /// the parser read `]` and finished processing a JSON array - array_end, - /// the parser read a key of a value in an object - key, - /// the parser finished reading a JSON value - value -}; - -template -using parser_callback_t = - std::function; - -/*! -@brief syntax analysis - -This class implements a recursive descent parser. -*/ -template -class parser -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using string_t = typename BasicJsonType::string_t; - using lexer_t = lexer; - using token_type = typename lexer_t::token_type; - - public: - /// a parser reading from an input adapter - explicit parser(InputAdapterType&& adapter, - const parser_callback_t cb = nullptr, - const bool allow_exceptions_ = true, - const bool skip_comments = false) - : callback(cb) - , m_lexer(std::move(adapter), skip_comments) - , allow_exceptions(allow_exceptions_) - { - // read first token - get_token(); - } - - /*! - @brief public parser interface - - @param[in] strict whether to expect the last token to be EOF - @param[in,out] result parsed JSON value - - @throw parse_error.101 in case of an unexpected token - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - */ - void parse(const bool strict, BasicJsonType& result) - { - if (callback) - { - json_sax_dom_callback_parser sdp(result, callback, allow_exceptions); - sax_parse_internal(&sdp); - result.assert_invariant(); - - // in strict mode, input must be completely read - if (strict && (get_token() != token_type::end_of_input)) - { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_of_input, "value"))); - } - - // in case of an error, return discarded value - if (sdp.is_errored()) - { - result = value_t::discarded; - return; - } - - // set top-level value to null if it was discarded by the callback - // function - if (result.is_discarded()) - { - result = nullptr; - } - } - else - { - json_sax_dom_parser sdp(result, allow_exceptions); - sax_parse_internal(&sdp); - result.assert_invariant(); - - // in strict mode, input must be completely read - if (strict && (get_token() != token_type::end_of_input)) - { - sdp.parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_of_input, "value"))); - } - - // in case of an error, return discarded value - if (sdp.is_errored()) - { - result = value_t::discarded; - return; - } - } - } - - /*! - @brief public accept interface - - @param[in] strict whether to expect the last token to be EOF - @return whether the input is a proper JSON text - */ - bool accept(const bool strict = true) - { - json_sax_acceptor sax_acceptor; - return sax_parse(&sax_acceptor, strict); - } - - template - JSON_HEDLEY_NON_NULL(2) - bool sax_parse(SAX* sax, const bool strict = true) - { - (void)detail::is_sax_static_asserts {}; - const bool result = sax_parse_internal(sax); - - // strict mode: next byte must be EOF - if (result && strict && (get_token() != token_type::end_of_input)) - { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_of_input, "value"))); - } - - return result; - } - - private: - template - JSON_HEDLEY_NON_NULL(2) - bool sax_parse_internal(SAX* sax) - { - // stack to remember the hierarchy of structured values we are parsing - // true = array; false = object - std::vector states; - // value to avoid a goto (see comment where set to true) - bool skip_to_state_evaluation = false; - - while (true) - { - if (!skip_to_state_evaluation) - { - // invariant: get_token() was called before each iteration - switch (last_token) - { - case token_type::begin_object: - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_object(std::size_t(-1)))) - { - return false; - } - - // closing } -> we are done - if (get_token() == token_type::end_object) - { - if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) - { - return false; - } - break; - } - - // parse key - if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) - { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::value_string, "object key"))); - } - if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) - { - return false; - } - - // parse separator (:) - if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) - { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::name_separator, "object separator"))); - } - - // remember we are now inside an object - states.push_back(false); - - // parse values - get_token(); - continue; - } - - case token_type::begin_array: - { - if (JSON_HEDLEY_UNLIKELY(!sax->start_array(std::size_t(-1)))) - { - return false; - } - - // closing ] -> we are done - if (get_token() == token_type::end_array) - { - if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) - { - return false; - } - break; - } - - // remember we are now inside an array - states.push_back(true); - - // parse values (no need to call get_token) - continue; - } - - case token_type::value_float: - { - const auto res = m_lexer.get_number_float(); - - if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) - { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'")); - } - - if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) - { - return false; - } - - break; - } - - case token_type::literal_false: - { - if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) - { - return false; - } - break; - } - - case token_type::literal_null: - { - if (JSON_HEDLEY_UNLIKELY(!sax->null())) - { - return false; - } - break; - } - - case token_type::literal_true: - { - if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) - { - return false; - } - break; - } - - case token_type::value_integer: - { - if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer()))) - { - return false; - } - break; - } - - case token_type::value_string: - { - if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) - { - return false; - } - break; - } - - case token_type::value_unsigned: - { - if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned()))) - { - return false; - } - break; - } - - case token_type::parse_error: - { - // using "uninitialized" to avoid "expected" message - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::uninitialized, "value"))); - } - - default: // the last token was unexpected - { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::literal_or_value, "value"))); - } - } - } - else - { - skip_to_state_evaluation = false; - } - - // we reached this line after we successfully parsed a value - if (states.empty()) - { - // empty stack: we reached the end of the hierarchy: done - return true; - } - - if (states.back()) // array - { - // comma -> next value - if (get_token() == token_type::value_separator) - { - // parse a new value - get_token(); - continue; - } - - // closing ] - if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) - { - if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) - { - return false; - } - - // We are done with this array. Before we can parse a - // new value, we need to evaluate the new state first. - // By setting skip_to_state_evaluation to false, we - // are effectively jumping to the beginning of this if. - JSON_ASSERT(!states.empty()); - states.pop_back(); - skip_to_state_evaluation = true; - continue; - } - - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_array, "array"))); - } - else // object - { - // comma -> next value - if (get_token() == token_type::value_separator) - { - // parse key - if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) - { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::value_string, "object key"))); - } - - if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) - { - return false; - } - - // parse separator (:) - if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) - { - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::name_separator, "object separator"))); - } - - // parse values - get_token(); - continue; - } - - // closing } - if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) - { - if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) - { - return false; - } - - // We are done with this object. Before we can parse a - // new value, we need to evaluate the new state first. - // By setting skip_to_state_evaluation to false, we - // are effectively jumping to the beginning of this if. - JSON_ASSERT(!states.empty()); - states.pop_back(); - skip_to_state_evaluation = true; - continue; - } - - return sax->parse_error(m_lexer.get_position(), - m_lexer.get_token_string(), - parse_error::create(101, m_lexer.get_position(), - exception_message(token_type::end_object, "object"))); - } - } - } - - /// get next token from lexer - token_type get_token() - { - return last_token = m_lexer.scan(); - } - - std::string exception_message(const token_type expected, const std::string& context) - { - std::string error_msg = "syntax error "; - - if (!context.empty()) - { - error_msg += "while parsing " + context + " "; - } - - error_msg += "- "; - - if (last_token == token_type::parse_error) - { - error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + - m_lexer.get_token_string() + "'"; - } - else - { - error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); - } - - if (expected != token_type::uninitialized) - { - error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); - } - - return error_msg; - } - - private: - /// callback function - const parser_callback_t callback = nullptr; - /// the type of the last read token - token_type last_token = token_type::uninitialized; - /// the lexer - lexer_t m_lexer; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; -}; -} // namespace detail -} // namespace nlohmann - -// #include - - -// #include - - -#include // ptrdiff_t -#include // numeric_limits - -namespace nlohmann -{ -namespace detail -{ -/* -@brief an iterator for primitive JSON types - -This class models an iterator for primitive JSON types (boolean, number, -string). It's only purpose is to allow the iterator/const_iterator classes -to "iterate" over primitive values. Internally, the iterator is modeled by -a `difference_type` variable. Value begin_value (`0`) models the begin, -end_value (`1`) models past the end. -*/ -class primitive_iterator_t -{ - private: - using difference_type = std::ptrdiff_t; - static constexpr difference_type begin_value = 0; - static constexpr difference_type end_value = begin_value + 1; - - /// iterator as signed integer type - difference_type m_it = (std::numeric_limits::min)(); - - public: - constexpr difference_type get_value() const noexcept - { - return m_it; - } - - /// set iterator to a defined beginning - void set_begin() noexcept - { - m_it = begin_value; - } - - /// set iterator to a defined past the end - void set_end() noexcept - { - m_it = end_value; - } - - /// return whether the iterator can be dereferenced - constexpr bool is_begin() const noexcept - { - return m_it == begin_value; - } - - /// return whether the iterator is at end - constexpr bool is_end() const noexcept - { - return m_it == end_value; - } - - friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it == rhs.m_it; - } - - friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it < rhs.m_it; - } - - primitive_iterator_t operator+(difference_type n) noexcept - { - auto result = *this; - result += n; - return result; - } - - friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it - rhs.m_it; - } - - primitive_iterator_t& operator++() noexcept - { - ++m_it; - return *this; - } - - primitive_iterator_t const operator++(int) noexcept - { - auto result = *this; - ++m_it; - return result; - } - - primitive_iterator_t& operator--() noexcept - { - --m_it; - return *this; - } - - primitive_iterator_t const operator--(int) noexcept - { - auto result = *this; - --m_it; - return result; - } - - primitive_iterator_t& operator+=(difference_type n) noexcept - { - m_it += n; - return *this; - } - - primitive_iterator_t& operator-=(difference_type n) noexcept - { - m_it -= n; - return *this; - } -}; -} // namespace detail -} // namespace nlohmann - - -namespace nlohmann -{ -namespace detail -{ -/*! -@brief an iterator value - -@note This structure could easily be a union, but MSVC currently does not allow -unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. -*/ -template struct internal_iterator -{ - /// iterator for JSON objects - typename BasicJsonType::object_t::iterator object_iterator {}; - /// iterator for JSON arrays - typename BasicJsonType::array_t::iterator array_iterator {}; - /// generic iterator for all other types - primitive_iterator_t primitive_iterator {}; -}; -} // namespace detail -} // namespace nlohmann - -// #include - - -#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next -#include // conditional, is_const, remove_const - -// #include - -// #include - -// #include - -// #include - -// #include - -// #include - -// #include - - -namespace nlohmann -{ -namespace detail -{ -// forward declare, to be able to friend it later on -template class iteration_proxy; -template class iteration_proxy_value; - -/*! -@brief a template for a bidirectional iterator for the @ref basic_json class -This class implements a both iterators (iterator and const_iterator) for the -@ref basic_json class. -@note An iterator is called *initialized* when a pointer to a JSON value has - been set (e.g., by a constructor or a copy assignment). If the iterator is - default-constructed, it is *uninitialized* and most methods are undefined. - **The library uses assertions to detect calls on uninitialized iterators.** -@requirement The class satisfies the following concept requirements: -- -[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): - The iterator that can be moved can be moved in both directions (i.e. - incremented and decremented). -@since version 1.0.0, simplified in version 2.0.9, change to bidirectional - iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) -*/ -template -class iter_impl -{ - /// allow basic_json to access private members - friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; - friend BasicJsonType; - friend iteration_proxy; - friend iteration_proxy_value; - - using object_t = typename BasicJsonType::object_t; - using array_t = typename BasicJsonType::array_t; - // make sure BasicJsonType is basic_json or const basic_json - static_assert(is_basic_json::type>::value, - "iter_impl only accepts (const) basic_json"); - - public: - - /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. - /// The C++ Standard has never required user-defined iterators to derive from std::iterator. - /// A user-defined iterator should provide publicly accessible typedefs named - /// iterator_category, value_type, difference_type, pointer, and reference. - /// Note that value_type is required to be non-const, even for constant iterators. - using iterator_category = std::bidirectional_iterator_tag; - - /// the type of the values when the iterator is dereferenced - using value_type = typename BasicJsonType::value_type; - /// a type to represent differences between iterators - using difference_type = typename BasicJsonType::difference_type; - /// defines a pointer to the type iterated over (value_type) - using pointer = typename std::conditional::value, - typename BasicJsonType::const_pointer, - typename BasicJsonType::pointer>::type; - /// defines a reference to the type iterated over (value_type) - using reference = - typename std::conditional::value, - typename BasicJsonType::const_reference, - typename BasicJsonType::reference>::type; - - /// default constructor - iter_impl() = default; - - /*! - @brief constructor for a given JSON instance - @param[in] object pointer to a JSON object for this iterator - @pre object != nullptr - @post The iterator is initialized; i.e. `m_object != nullptr`. - */ - explicit iter_impl(pointer object) noexcept : m_object(object) - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - m_it.object_iterator = typename object_t::iterator(); - break; - } - - case value_t::array: - { - m_it.array_iterator = typename array_t::iterator(); - break; - } - - default: - { - m_it.primitive_iterator = primitive_iterator_t(); - break; - } - } - } - - /*! - @note The conventional copy constructor and copy assignment are implicitly - defined. Combined with the following converting constructor and - assignment, they support: (1) copy from iterator to iterator, (2) - copy from const iterator to const iterator, and (3) conversion from - iterator to const iterator. However conversion from const iterator - to iterator is not defined. - */ - - /*! - @brief const copy constructor - @param[in] other const iterator to copy from - @note This copy constructor had to be defined explicitly to circumvent a bug - occurring on msvc v19.0 compiler (VS 2015) debug build. For more - information refer to: https://github.com/nlohmann/json/issues/1608 - */ - iter_impl(const iter_impl& other) noexcept - : m_object(other.m_object), m_it(other.m_it) - {} - - /*! - @brief converting assignment - @param[in] other const iterator to copy from - @return const/non-const iterator - @note It is not checked whether @a other is initialized. - */ - iter_impl& operator=(const iter_impl& other) noexcept - { - m_object = other.m_object; - m_it = other.m_it; - return *this; - } - - /*! - @brief converting constructor - @param[in] other non-const iterator to copy from - @note It is not checked whether @a other is initialized. - */ - iter_impl(const iter_impl::type>& other) noexcept - : m_object(other.m_object), m_it(other.m_it) - {} - - /*! - @brief converting assignment - @param[in] other non-const iterator to copy from - @return const/non-const iterator - @note It is not checked whether @a other is initialized. - */ - iter_impl& operator=(const iter_impl::type>& other) noexcept - { - m_object = other.m_object; - m_it = other.m_it; - return *this; - } - - private: - /*! - @brief set the iterator to the first value - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - void set_begin() noexcept - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - m_it.object_iterator = m_object->m_value.object->begin(); - break; - } - - case value_t::array: - { - m_it.array_iterator = m_object->m_value.array->begin(); - break; - } - - case value_t::null: - { - // set to end so begin()==end() is true: null is empty - m_it.primitive_iterator.set_end(); - break; - } - - default: - { - m_it.primitive_iterator.set_begin(); - break; - } - } - } - - /*! - @brief set the iterator past the last value - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - void set_end() noexcept - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - m_it.object_iterator = m_object->m_value.object->end(); - break; - } - - case value_t::array: - { - m_it.array_iterator = m_object->m_value.array->end(); - break; - } - - default: - { - m_it.primitive_iterator.set_end(); - break; - } - } - } - - public: - /*! - @brief return a reference to the value pointed to by the iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference operator*() const - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); - return m_it.object_iterator->second; - } - - case value_t::array: - { - JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); - return *m_it.array_iterator; - } - - case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - - default: - { - if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) - { - return *m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } - - /*! - @brief dereference the iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - pointer operator->() const - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - JSON_ASSERT(m_it.object_iterator != m_object->m_value.object->end()); - return &(m_it.object_iterator->second); - } - - case value_t::array: - { - JSON_ASSERT(m_it.array_iterator != m_object->m_value.array->end()); - return &*m_it.array_iterator; - } - - default: - { - if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin())) - { - return m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } - - /*! - @brief post-increment (it++) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl const operator++(int) - { - auto result = *this; - ++(*this); - return result; - } - - /*! - @brief pre-increment (++it) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator++() - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - std::advance(m_it.object_iterator, 1); - break; - } - - case value_t::array: - { - std::advance(m_it.array_iterator, 1); - break; - } - - default: - { - ++m_it.primitive_iterator; - break; - } - } - - return *this; - } - - /*! - @brief post-decrement (it--) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl const operator--(int) - { - auto result = *this; - --(*this); - return result; - } - - /*! - @brief pre-decrement (--it) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator--() - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - std::advance(m_it.object_iterator, -1); - break; - } - - case value_t::array: - { - std::advance(m_it.array_iterator, -1); - break; - } - - default: - { - --m_it.primitive_iterator; - break; - } - } - - return *this; - } - - /*! - @brief comparison: equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator==(const iter_impl& other) const - { - // if objects are not the same, the comparison is undefined - if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) - { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); - } - - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - return (m_it.object_iterator == other.m_it.object_iterator); - - case value_t::array: - return (m_it.array_iterator == other.m_it.array_iterator); - - default: - return (m_it.primitive_iterator == other.m_it.primitive_iterator); - } - } - - /*! - @brief comparison: not equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator!=(const iter_impl& other) const - { - return !operator==(other); - } - - /*! - @brief comparison: smaller - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator<(const iter_impl& other) const - { - // if objects are not the same, the comparison is undefined - if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object)) - { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); - } - - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); - - case value_t::array: - return (m_it.array_iterator < other.m_it.array_iterator); - - default: - return (m_it.primitive_iterator < other.m_it.primitive_iterator); - } - } - - /*! - @brief comparison: less than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator<=(const iter_impl& other) const - { - return !other.operator < (*this); - } - - /*! - @brief comparison: greater than - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator>(const iter_impl& other) const - { - return !operator<=(other); - } - - /*! - @brief comparison: greater than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator>=(const iter_impl& other) const - { - return !operator<(other); - } - - /*! - @brief add to iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator+=(difference_type i) - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); - - case value_t::array: - { - std::advance(m_it.array_iterator, i); - break; - } - - default: - { - m_it.primitive_iterator += i; - break; - } - } - - return *this; - } - - /*! - @brief subtract from iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator-=(difference_type i) - { - return operator+=(-i); - } - - /*! - @brief add to iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator+(difference_type i) const - { - auto result = *this; - result += i; - return result; - } - - /*! - @brief addition of distance and iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - friend iter_impl operator+(difference_type i, const iter_impl& it) - { - auto result = it; - result += i; - return result; - } - - /*! - @brief subtract from iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator-(difference_type i) const - { - auto result = *this; - result -= i; - return result; - } - - /*! - @brief return difference - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - difference_type operator-(const iter_impl& other) const - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); - - case value_t::array: - return m_it.array_iterator - other.m_it.array_iterator; - - default: - return m_it.primitive_iterator - other.m_it.primitive_iterator; - } - } - - /*! - @brief access to successor - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference operator[](difference_type n) const - { - JSON_ASSERT(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); - - case value_t::array: - return *std::next(m_it.array_iterator, n); - - case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - - default: - { - if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n)) - { - return *m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } - - /*! - @brief return the key of an object iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - const typename object_t::key_type& key() const - { - JSON_ASSERT(m_object != nullptr); - - if (JSON_HEDLEY_LIKELY(m_object->is_object())) - { - return m_it.object_iterator->first; - } - - JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); - } - - /*! - @brief return the value of an iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference value() const - { - return operator*(); - } - - private: - /// associated JSON instance - pointer m_object = nullptr; - /// the actual iterator of the associated instance - internal_iterator::type> m_it {}; -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - - -#include // ptrdiff_t -#include // reverse_iterator -#include // declval - -namespace nlohmann -{ -namespace detail -{ -////////////////////// -// reverse_iterator // -////////////////////// - -/*! -@brief a template for a reverse iterator class - -@tparam Base the base iterator type to reverse. Valid types are @ref -iterator (to create @ref reverse_iterator) and @ref const_iterator (to -create @ref const_reverse_iterator). - -@requirement The class satisfies the following concept requirements: -- -[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator): - The iterator that can be moved can be moved in both directions (i.e. - incremented and decremented). -- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator): - It is possible to write to the pointed-to element (only if @a Base is - @ref iterator). - -@since version 1.0.0 -*/ -template -class json_reverse_iterator : public std::reverse_iterator -{ - public: - using difference_type = std::ptrdiff_t; - /// shortcut to the reverse iterator adapter - using base_iterator = std::reverse_iterator; - /// the reference type for the pointed-to element - using reference = typename Base::reference; - - /// create reverse iterator from iterator - explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept - : base_iterator(it) {} - - /// create reverse iterator from base class - explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} - - /// post-increment (it++) - json_reverse_iterator const operator++(int) - { - return static_cast(base_iterator::operator++(1)); - } - - /// pre-increment (++it) - json_reverse_iterator& operator++() - { - return static_cast(base_iterator::operator++()); - } - - /// post-decrement (it--) - json_reverse_iterator const operator--(int) - { - return static_cast(base_iterator::operator--(1)); - } - - /// pre-decrement (--it) - json_reverse_iterator& operator--() - { - return static_cast(base_iterator::operator--()); - } - - /// add to iterator - json_reverse_iterator& operator+=(difference_type i) - { - return static_cast(base_iterator::operator+=(i)); - } - - /// add to iterator - json_reverse_iterator operator+(difference_type i) const - { - return static_cast(base_iterator::operator+(i)); - } - - /// subtract from iterator - json_reverse_iterator operator-(difference_type i) const - { - return static_cast(base_iterator::operator-(i)); - } - - /// return difference - difference_type operator-(const json_reverse_iterator& other) const - { - return base_iterator(*this) - base_iterator(other); - } - - /// access to successor - reference operator[](difference_type n) const - { - return *(this->operator+(n)); - } - - /// return the key of an object iterator - auto key() const -> decltype(std::declval().key()) - { - auto it = --this->base(); - return it.key(); - } - - /// return the value of an iterator - reference value() const - { - auto it = --this->base(); - return it.operator * (); - } -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - - -#include // all_of -#include // isdigit -#include // max -#include // accumulate -#include // string -#include // move -#include // vector - -// #include - -// #include - -// #include - - -namespace nlohmann -{ -template -class json_pointer -{ - // allow basic_json to access private members - NLOHMANN_BASIC_JSON_TPL_DECLARATION - friend class basic_json; - - public: - /*! - @brief create JSON pointer - - Create a JSON pointer according to the syntax described in - [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). - - @param[in] s string representing the JSON pointer; if omitted, the empty - string is assumed which references the whole JSON value - - @throw parse_error.107 if the given JSON pointer @a s is nonempty and does - not begin with a slash (`/`); see example below - - @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is - not followed by `0` (representing `~`) or `1` (representing `/`); see - example below - - @liveexample{The example shows the construction several valid JSON pointers - as well as the exceptional behavior.,json_pointer} - - @since version 2.0.0 - */ - explicit json_pointer(const std::string& s = "") - : reference_tokens(split(s)) - {} - - /*! - @brief return a string representation of the JSON pointer - - @invariant For each JSON pointer `ptr`, it holds: - @code {.cpp} - ptr == json_pointer(ptr.to_string()); - @endcode - - @return a string representation of the JSON pointer - - @liveexample{The example shows the result of `to_string`.,json_pointer__to_string} - - @since version 2.0.0 - */ - std::string to_string() const - { - return std::accumulate(reference_tokens.begin(), reference_tokens.end(), - std::string{}, - [](const std::string & a, const std::string & b) - { - return a + "/" + escape(b); - }); - } - - /// @copydoc to_string() - operator std::string() const - { - return to_string(); - } - - /*! - @brief append another JSON pointer at the end of this JSON pointer - - @param[in] ptr JSON pointer to append - @return JSON pointer with @a ptr appended - - @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} - - @complexity Linear in the length of @a ptr. - - @sa @ref operator/=(std::string) to append a reference token - @sa @ref operator/=(std::size_t) to append an array index - @sa @ref operator/(const json_pointer&, const json_pointer&) for a binary operator - - @since version 3.6.0 - */ - json_pointer& operator/=(const json_pointer& ptr) - { - reference_tokens.insert(reference_tokens.end(), - ptr.reference_tokens.begin(), - ptr.reference_tokens.end()); - return *this; - } - - /*! - @brief append an unescaped reference token at the end of this JSON pointer - - @param[in] token reference token to append - @return JSON pointer with @a token appended without escaping @a token - - @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} - - @complexity Amortized constant. - - @sa @ref operator/=(const json_pointer&) to append a JSON pointer - @sa @ref operator/=(std::size_t) to append an array index - @sa @ref operator/(const json_pointer&, std::size_t) for a binary operator - - @since version 3.6.0 - */ - json_pointer& operator/=(std::string token) - { - push_back(std::move(token)); - return *this; - } - - /*! - @brief append an array index at the end of this JSON pointer - - @param[in] array_idx array index to append - @return JSON pointer with @a array_idx appended - - @liveexample{The example shows the usage of `operator/=`.,json_pointer__operator_add} - - @complexity Amortized constant. - - @sa @ref operator/=(const json_pointer&) to append a JSON pointer - @sa @ref operator/=(std::string) to append a reference token - @sa @ref operator/(const json_pointer&, std::string) for a binary operator - - @since version 3.6.0 - */ - json_pointer& operator/=(std::size_t array_idx) - { - return *this /= std::to_string(array_idx); - } - - /*! - @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer - - @param[in] lhs JSON pointer - @param[in] rhs JSON pointer - @return a new JSON pointer with @a rhs appended to @a lhs - - @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} - - @complexity Linear in the length of @a lhs and @a rhs. - - @sa @ref operator/=(const json_pointer&) to append a JSON pointer - - @since version 3.6.0 - */ - friend json_pointer operator/(const json_pointer& lhs, - const json_pointer& rhs) - { - return json_pointer(lhs) /= rhs; - } - - /*! - @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer - - @param[in] ptr JSON pointer - @param[in] token reference token - @return a new JSON pointer with unescaped @a token appended to @a ptr - - @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} - - @complexity Linear in the length of @a ptr. - - @sa @ref operator/=(std::string) to append a reference token - - @since version 3.6.0 - */ - friend json_pointer operator/(const json_pointer& ptr, std::string token) - { - return json_pointer(ptr) /= std::move(token); - } - - /*! - @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer - - @param[in] ptr JSON pointer - @param[in] array_idx array index - @return a new JSON pointer with @a array_idx appended to @a ptr - - @liveexample{The example shows the usage of `operator/`.,json_pointer__operator_add_binary} - - @complexity Linear in the length of @a ptr. - - @sa @ref operator/=(std::size_t) to append an array index - - @since version 3.6.0 - */ - friend json_pointer operator/(const json_pointer& ptr, std::size_t array_idx) - { - return json_pointer(ptr) /= array_idx; - } - - /*! - @brief returns the parent of this JSON pointer - - @return parent of this JSON pointer; in case this JSON pointer is the root, - the root itself is returned - - @complexity Linear in the length of the JSON pointer. - - @liveexample{The example shows the result of `parent_pointer` for different - JSON Pointers.,json_pointer__parent_pointer} - - @since version 3.6.0 - */ - json_pointer parent_pointer() const - { - if (empty()) - { - return *this; - } - - json_pointer res = *this; - res.pop_back(); - return res; - } - - /*! - @brief remove last reference token - - @pre not `empty()` - - @liveexample{The example shows the usage of `pop_back`.,json_pointer__pop_back} - - @complexity Constant. - - @throw out_of_range.405 if JSON pointer has no parent - - @since version 3.6.0 - */ - void pop_back() - { - if (JSON_HEDLEY_UNLIKELY(empty())) - { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); - } - - reference_tokens.pop_back(); - } - - /*! - @brief return last reference token - - @pre not `empty()` - @return last reference token - - @liveexample{The example shows the usage of `back`.,json_pointer__back} - - @complexity Constant. - - @throw out_of_range.405 if JSON pointer has no parent - - @since version 3.6.0 - */ - const std::string& back() const - { - if (JSON_HEDLEY_UNLIKELY(empty())) - { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); - } - - return reference_tokens.back(); - } - - /*! - @brief append an unescaped token at the end of the reference pointer - - @param[in] token token to add - - @complexity Amortized constant. - - @liveexample{The example shows the result of `push_back` for different - JSON Pointers.,json_pointer__push_back} - - @since version 3.6.0 - */ - void push_back(const std::string& token) - { - reference_tokens.push_back(token); - } - - /// @copydoc push_back(const std::string&) - void push_back(std::string&& token) - { - reference_tokens.push_back(std::move(token)); - } - - /*! - @brief return whether pointer points to the root document - - @return true iff the JSON pointer points to the root document - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example shows the result of `empty` for different JSON - Pointers.,json_pointer__empty} - - @since version 3.6.0 - */ - bool empty() const noexcept - { - return reference_tokens.empty(); - } - - private: - /*! - @param[in] s reference token to be converted into an array index - - @return integer representation of @a s - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index begins not with a digit - @throw out_of_range.404 if string @a s could not be converted to an integer - @throw out_of_range.410 if an array index exceeds size_type - */ - static typename BasicJsonType::size_type array_index(const std::string& s) - { - using size_type = typename BasicJsonType::size_type; - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + s + - "' must not begin with '0'")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9'))) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + s + "' is not a number")); - } - - std::size_t processed_chars = 0; - unsigned long long res = 0; - JSON_TRY - { - res = std::stoull(s, &processed_chars); - } - JSON_CATCH(std::out_of_range&) - { - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); - } - - // check if the string was completely read - if (JSON_HEDLEY_UNLIKELY(processed_chars != s.size())) - { - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); - } - - // only triggered on special platforms (like 32bit), see also - // https://github.com/nlohmann/json/pull/2203 - if (res >= static_cast((std::numeric_limits::max)())) - { - JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type")); // LCOV_EXCL_LINE - } - - return static_cast(res); - } - - json_pointer top() const - { - if (JSON_HEDLEY_UNLIKELY(empty())) - { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); - } - - json_pointer result = *this; - result.reference_tokens = {reference_tokens[0]}; - return result; - } - - /*! - @brief create and return a reference to the pointed to value - - @complexity Linear in the number of reference tokens. - - @throw parse_error.109 if array index is not a number - @throw type_error.313 if value cannot be unflattened - */ - BasicJsonType& get_and_create(BasicJsonType& j) const - { - auto result = &j; - - // in case no reference tokens exist, return a reference to the JSON value - // j which will be overwritten by a primitive value - for (const auto& reference_token : reference_tokens) - { - switch (result->type()) - { - case detail::value_t::null: - { - if (reference_token == "0") - { - // start a new array if reference token is 0 - result = &result->operator[](0); - } - else - { - // start a new object otherwise - result = &result->operator[](reference_token); - } - break; - } - - case detail::value_t::object: - { - // create an entry in the object - result = &result->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - // create an entry in the array - result = &result->operator[](array_index(reference_token)); - break; - } - - /* - The following code is only reached if there exists a reference - token _and_ the current value is primitive. In this case, we have - an error situation, because primitive values may only occur as - single value; that is, with an empty list of reference tokens. - */ - default: - JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); - } - } - - return *result; - } - - /*! - @brief return a reference to the pointed to value - - @note This version does not throw if a value is not present, but tries to - create nested values instead. For instance, calling this function - with pointer `"/this/that"` on a null value is equivalent to calling - `operator[]("this").operator[]("that")` on that value, effectively - changing the null value to an object. - - @param[in] ptr a JSON value - - @return reference to the JSON value pointed to by the JSON pointer - - @complexity Linear in the length of the JSON pointer. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - BasicJsonType& get_unchecked(BasicJsonType* ptr) const - { - for (const auto& reference_token : reference_tokens) - { - // convert null values to arrays or objects before continuing - if (ptr->is_null()) - { - // check if reference token is a number - const bool nums = - std::all_of(reference_token.begin(), reference_token.end(), - [](const unsigned char x) - { - return std::isdigit(x); - }); - - // change value to array for numbers or "-" or to object otherwise - *ptr = (nums || reference_token == "-") - ? detail::value_t::array - : detail::value_t::object; - } - - switch (ptr->type()) - { - case detail::value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - if (reference_token == "-") - { - // explicitly treat "-" as index beyond the end - ptr = &ptr->operator[](ptr->m_value.array->size()); - } - else - { - // convert array index to number; unchecked access - ptr = &ptr->operator[](array_index(reference_token)); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; - } - - /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - BasicJsonType& get_checked(BasicJsonType* ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->type()) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // note: at performs range check - ptr = &ptr->at(array_index(reference_token)); - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; - } - - /*! - @brief return a const reference to the pointed to value - - @param[in] ptr a JSON value - - @return const reference to the JSON value pointed to by the JSON - pointer - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->type()) - { - case detail::value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) - { - // "-" cannot be used for const access - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // use unchecked array access - ptr = &ptr->operator[](array_index(reference_token)); - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; - } - - /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - const BasicJsonType& get_checked(const BasicJsonType* ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->type()) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // note: at performs range check - ptr = &ptr->at(array_index(reference_token)); - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; - } - - /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - */ - bool contains(const BasicJsonType* ptr) const - { - for (const auto& reference_token : reference_tokens) - { - switch (ptr->type()) - { - case detail::value_t::object: - { - if (!ptr->contains(reference_token)) - { - // we did not find the key in the object - return false; - } - - ptr = &ptr->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_HEDLEY_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - return false; - } - if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !("0" <= reference_token && reference_token <= "9"))) - { - // invalid char - return false; - } - if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1)) - { - if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9'))) - { - // first char should be between '1' and '9' - return false; - } - for (std::size_t i = 1; i < reference_token.size(); i++) - { - if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9'))) - { - // other char should be between '0' and '9' - return false; - } - } - } - - const auto idx = array_index(reference_token); - if (idx >= ptr->size()) - { - // index out of range - return false; - } - - ptr = &ptr->operator[](idx); - break; - } - - default: - { - // we do not expect primitive values if there is still a - // reference token to process - return false; - } - } - } - - // no reference token left means we found a primitive value - return true; - } - - /*! - @brief split the string input to reference tokens - - @note This function is only called by the json_pointer constructor. - All exceptions below are documented there. - - @throw parse_error.107 if the pointer is not empty or begins with '/' - @throw parse_error.108 if character '~' is not followed by '0' or '1' - */ - static std::vector split(const std::string& reference_string) - { - std::vector result; - - // special case: empty reference string -> no reference tokens - if (reference_string.empty()) - { - return result; - } - - // check if nonempty reference string begins with slash - if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/')) - { - JSON_THROW(detail::parse_error::create(107, 1, - "JSON pointer must be empty or begin with '/' - was: '" + - reference_string + "'")); - } - - // extract the reference tokens: - // - slash: position of the last read slash (or end of string) - // - start: position after the previous slash - for ( - // search for the first slash after the first character - std::size_t slash = reference_string.find_first_of('/', 1), - // set the beginning of the first reference token - start = 1; - // we can stop if start == 0 (if slash == std::string::npos) - start != 0; - // set the beginning of the next reference token - // (will eventually be 0 if slash == std::string::npos) - start = (slash == std::string::npos) ? 0 : slash + 1, - // find next slash - slash = reference_string.find_first_of('/', start)) - { - // use the text between the beginning of the reference token - // (start) and the last slash (slash). - auto reference_token = reference_string.substr(start, slash - start); - - // check reference tokens are properly escaped - for (std::size_t pos = reference_token.find_first_of('~'); - pos != std::string::npos; - pos = reference_token.find_first_of('~', pos + 1)) - { - JSON_ASSERT(reference_token[pos] == '~'); - - // ~ must be followed by 0 or 1 - if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 || - (reference_token[pos + 1] != '0' && - reference_token[pos + 1] != '1'))) - { - JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); - } - } - - // finally, store the reference token - unescape(reference_token); - result.push_back(reference_token); - } - - return result; - } - - /*! - @brief replace all occurrences of a substring by another string - - @param[in,out] s the string to manipulate; changed so that all - occurrences of @a f are replaced with @a t - @param[in] f the substring to replace with @a t - @param[in] t the string to replace @a f - - @pre The search string @a f must not be empty. **This precondition is - enforced with an assertion.** - - @since version 2.0.0 - */ - static void replace_substring(std::string& s, const std::string& f, - const std::string& t) - { - JSON_ASSERT(!f.empty()); - for (auto pos = s.find(f); // find first occurrence of f - pos != std::string::npos; // make sure f was found - s.replace(pos, f.size(), t), // replace with t, and - pos = s.find(f, pos + t.size())) // find next occurrence of f - {} - } - - /// escape "~" to "~0" and "/" to "~1" - static std::string escape(std::string s) - { - replace_substring(s, "~", "~0"); - replace_substring(s, "/", "~1"); - return s; - } - - /// unescape "~1" to tilde and "~0" to slash (order is important!) - static void unescape(std::string& s) - { - replace_substring(s, "~1", "/"); - replace_substring(s, "~0", "~"); - } - - /*! - @param[in] reference_string the reference string to the current value - @param[in] value the value to consider - @param[in,out] result the result object to insert values to - - @note Empty objects or arrays are flattened to `null`. - */ - static void flatten(const std::string& reference_string, - const BasicJsonType& value, - BasicJsonType& result) - { - switch (value.type()) - { - case detail::value_t::array: - { - if (value.m_value.array->empty()) - { - // flatten empty array as null - result[reference_string] = nullptr; - } - else - { - // iterate array and use index as reference string - for (std::size_t i = 0; i < value.m_value.array->size(); ++i) - { - flatten(reference_string + "/" + std::to_string(i), - value.m_value.array->operator[](i), result); - } - } - break; - } - - case detail::value_t::object: - { - if (value.m_value.object->empty()) - { - // flatten empty object as null - result[reference_string] = nullptr; - } - else - { - // iterate object and use keys as reference string - for (const auto& element : *value.m_value.object) - { - flatten(reference_string + "/" + escape(element.first), element.second, result); - } - } - break; - } - - default: - { - // add primitive value with its reference string - result[reference_string] = value; - break; - } - } - } - - /*! - @param[in] value flattened JSON - - @return unflattened JSON - - @throw parse_error.109 if array index is not a number - @throw type_error.314 if value is not an object - @throw type_error.315 if object values are not primitive - @throw type_error.313 if value cannot be unflattened - */ - static BasicJsonType - unflatten(const BasicJsonType& value) - { - if (JSON_HEDLEY_UNLIKELY(!value.is_object())) - { - JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); - } - - BasicJsonType result; - - // iterate the JSON object values - for (const auto& element : *value.m_value.object) - { - if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive())) - { - JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); - } - - // assign value to reference pointed to by JSON pointer; Note that if - // the JSON pointer is "" (i.e., points to the whole value), function - // get_and_create returns a reference to result itself. An assignment - // will then create a primitive value. - json_pointer(element.first).get_and_create(result) = element.second; - } - - return result; - } - - /*! - @brief compares two JSON pointers for equality - - @param[in] lhs JSON pointer to compare - @param[in] rhs JSON pointer to compare - @return whether @a lhs is equal to @a rhs - - @complexity Linear in the length of the JSON pointer - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - */ - friend bool operator==(json_pointer const& lhs, - json_pointer const& rhs) noexcept - { - return lhs.reference_tokens == rhs.reference_tokens; - } - - /*! - @brief compares two JSON pointers for inequality - - @param[in] lhs JSON pointer to compare - @param[in] rhs JSON pointer to compare - @return whether @a lhs is not equal @a rhs - - @complexity Linear in the length of the JSON pointer - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - */ - friend bool operator!=(json_pointer const& lhs, - json_pointer const& rhs) noexcept - { - return !(lhs == rhs); - } - - /// the reference tokens - std::vector reference_tokens; -}; -} // namespace nlohmann - -// #include - - -#include -#include - -// #include - - -namespace nlohmann -{ -namespace detail -{ -template -class json_ref -{ - public: - using value_type = BasicJsonType; - - json_ref(value_type&& value) - : owned_value(std::move(value)) - , value_ref(&owned_value) - , is_rvalue(true) - {} - - json_ref(const value_type& value) - : value_ref(const_cast(&value)) - , is_rvalue(false) - {} - - json_ref(std::initializer_list init) - : owned_value(init) - , value_ref(&owned_value) - , is_rvalue(true) - {} - - template < - class... Args, - enable_if_t::value, int> = 0 > - json_ref(Args && ... args) - : owned_value(std::forward(args)...) - , value_ref(&owned_value) - , is_rvalue(true) - {} - - // class should be movable only - json_ref(json_ref&&) = default; - json_ref(const json_ref&) = delete; - json_ref& operator=(const json_ref&) = delete; - json_ref& operator=(json_ref&&) = delete; - ~json_ref() = default; - - value_type moved_or_copied() const - { - if (is_rvalue) - { - return std::move(*value_ref); - } - return *value_ref; - } - - value_type const& operator*() const - { - return *static_cast(value_ref); - } - - value_type const* operator->() const - { - return static_cast(value_ref); - } - - private: - mutable value_type owned_value = nullptr; - value_type* value_ref = nullptr; - const bool is_rvalue = true; -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - -// #include - -// #include - - -#include // reverse -#include // array -#include // uint8_t, uint16_t, uint32_t, uint64_t -#include // memcpy -#include // numeric_limits -#include // string -#include // isnan, isinf - -// #include - -// #include - -// #include - - -#include // copy -#include // size_t -#include // streamsize -#include // back_inserter -#include // shared_ptr, make_shared -#include // basic_ostream -#include // basic_string -#include // vector -// #include - - -namespace nlohmann -{ -namespace detail -{ -/// abstract output adapter interface -template struct output_adapter_protocol -{ - virtual void write_character(CharType c) = 0; - virtual void write_characters(const CharType* s, std::size_t length) = 0; - virtual ~output_adapter_protocol() = default; -}; - -/// a type to simplify interfaces -template -using output_adapter_t = std::shared_ptr>; - -/// output adapter for byte vectors -template -class output_vector_adapter : public output_adapter_protocol -{ - public: - explicit output_vector_adapter(std::vector& vec) noexcept - : v(vec) - {} - - void write_character(CharType c) override - { - v.push_back(c); - } - - JSON_HEDLEY_NON_NULL(2) - void write_characters(const CharType* s, std::size_t length) override - { - std::copy(s, s + length, std::back_inserter(v)); - } - - private: - std::vector& v; -}; - -/// output adapter for output streams -template -class output_stream_adapter : public output_adapter_protocol -{ - public: - explicit output_stream_adapter(std::basic_ostream& s) noexcept - : stream(s) - {} - - void write_character(CharType c) override - { - stream.put(c); - } - - JSON_HEDLEY_NON_NULL(2) - void write_characters(const CharType* s, std::size_t length) override - { - stream.write(s, static_cast(length)); - } - - private: - std::basic_ostream& stream; -}; - -/// output adapter for basic_string -template> -class output_string_adapter : public output_adapter_protocol -{ - public: - explicit output_string_adapter(StringType& s) noexcept - : str(s) - {} - - void write_character(CharType c) override - { - str.push_back(c); - } - - JSON_HEDLEY_NON_NULL(2) - void write_characters(const CharType* s, std::size_t length) override - { - str.append(s, length); - } - - private: - StringType& str; -}; - -template> -class output_adapter -{ - public: - output_adapter(std::vector& vec) - : oa(std::make_shared>(vec)) {} - - output_adapter(std::basic_ostream& s) - : oa(std::make_shared>(s)) {} - - output_adapter(StringType& s) - : oa(std::make_shared>(s)) {} - - operator output_adapter_t() - { - return oa; - } - - private: - output_adapter_t oa = nullptr; -}; -} // namespace detail -} // namespace nlohmann - - -namespace nlohmann -{ -namespace detail -{ -/////////////////// -// binary writer // -/////////////////// - -/*! -@brief serialization to CBOR and MessagePack values -*/ -template -class binary_writer -{ - using string_t = typename BasicJsonType::string_t; - using binary_t = typename BasicJsonType::binary_t; - using number_float_t = typename BasicJsonType::number_float_t; - - public: - /*! - @brief create a binary writer - - @param[in] adapter output adapter to write to - */ - explicit binary_writer(output_adapter_t adapter) : oa(adapter) - { - JSON_ASSERT(oa); - } - - /*! - @param[in] j JSON value to serialize - @pre j.type() == value_t::object - */ - void write_bson(const BasicJsonType& j) - { - switch (j.type()) - { - case value_t::object: - { - write_bson_object(*j.m_value.object); - break; - } - - default: - { - JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name()))); - } - } - } - - /*! - @param[in] j JSON value to serialize - */ - void write_cbor(const BasicJsonType& j) - { - switch (j.type()) - { - case value_t::null: - { - oa->write_character(to_char_type(0xF6)); - break; - } - - case value_t::boolean: - { - oa->write_character(j.m_value.boolean - ? to_char_type(0xF5) - : to_char_type(0xF4)); - break; - } - - case value_t::number_integer: - { - if (j.m_value.number_integer >= 0) - { - // CBOR does not differentiate between positive signed - // integers and unsigned integers. Therefore, we used the - // code from the value_t::number_unsigned case here. - if (j.m_value.number_integer <= 0x17) - { - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_integer <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x18)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_integer <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x19)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_integer <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x1A)); - write_number(static_cast(j.m_value.number_integer)); - } - else - { - oa->write_character(to_char_type(0x1B)); - write_number(static_cast(j.m_value.number_integer)); - } - } - else - { - // The conversions below encode the sign in the first - // byte, and the value is converted to a positive number. - const auto positive_number = -1 - j.m_value.number_integer; - if (j.m_value.number_integer >= -24) - { - write_number(static_cast(0x20 + positive_number)); - } - else if (positive_number <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x38)); - write_number(static_cast(positive_number)); - } - else if (positive_number <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x39)); - write_number(static_cast(positive_number)); - } - else if (positive_number <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x3A)); - write_number(static_cast(positive_number)); - } - else - { - oa->write_character(to_char_type(0x3B)); - write_number(static_cast(positive_number)); - } - } - break; - } - - case value_t::number_unsigned: - { - if (j.m_value.number_unsigned <= 0x17) - { - write_number(static_cast(j.m_value.number_unsigned)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x18)); - write_number(static_cast(j.m_value.number_unsigned)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x19)); - write_number(static_cast(j.m_value.number_unsigned)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x1A)); - write_number(static_cast(j.m_value.number_unsigned)); - } - else - { - oa->write_character(to_char_type(0x1B)); - write_number(static_cast(j.m_value.number_unsigned)); - } - break; - } - - case value_t::number_float: - { - if (std::isnan(j.m_value.number_float)) - { - // NaN is 0xf97e00 in CBOR - oa->write_character(to_char_type(0xF9)); - oa->write_character(to_char_type(0x7E)); - oa->write_character(to_char_type(0x00)); - } - else if (std::isinf(j.m_value.number_float)) - { - // Infinity is 0xf97c00, -Infinity is 0xf9fc00 - oa->write_character(to_char_type(0xf9)); - oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); - oa->write_character(to_char_type(0x00)); - } - else - { - write_compact_float(j.m_value.number_float, detail::input_format_t::cbor); - } - break; - } - - case value_t::string: - { - // step 1: write control byte and the string length - const auto N = j.m_value.string->size(); - if (N <= 0x17) - { - write_number(static_cast(0x60 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x78)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x79)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x7A)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x7B)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP - - // step 2: write the string - oa->write_characters( - reinterpret_cast(j.m_value.string->c_str()), - j.m_value.string->size()); - break; - } - - case value_t::array: - { - // step 1: write control byte and the array size - const auto N = j.m_value.array->size(); - if (N <= 0x17) - { - write_number(static_cast(0x80 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x98)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x99)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x9A)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x9B)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP - - // step 2: write each element - for (const auto& el : *j.m_value.array) - { - write_cbor(el); - } - break; - } - - case value_t::binary: - { - if (j.m_value.binary->has_subtype()) - { - write_number(static_cast(0xd8)); - write_number(j.m_value.binary->subtype()); - } - - // step 1: write control byte and the binary array size - const auto N = j.m_value.binary->size(); - if (N <= 0x17) - { - write_number(static_cast(0x40 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x58)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x59)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x5A)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0x5B)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP - - // step 2: write each element - oa->write_characters( - reinterpret_cast(j.m_value.binary->data()), - N); - - break; - } - - case value_t::object: - { - // step 1: write control byte and the object size - const auto N = j.m_value.object->size(); - if (N <= 0x17) - { - write_number(static_cast(0xA0 + N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0xB8)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0xB9)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0xBA)); - write_number(static_cast(N)); - } - // LCOV_EXCL_START - else if (N <= (std::numeric_limits::max)()) - { - oa->write_character(to_char_type(0xBB)); - write_number(static_cast(N)); - } - // LCOV_EXCL_STOP - - // step 2: write each element - for (const auto& el : *j.m_value.object) - { - write_cbor(el.first); - write_cbor(el.second); - } - break; - } - - default: - break; - } - } - - /*! - @param[in] j JSON value to serialize - */ - void write_msgpack(const BasicJsonType& j) - { - switch (j.type()) - { - case value_t::null: // nil - { - oa->write_character(to_char_type(0xC0)); - break; - } - - case value_t::boolean: // true and false - { - oa->write_character(j.m_value.boolean - ? to_char_type(0xC3) - : to_char_type(0xC2)); - break; - } - - case value_t::number_integer: - { - if (j.m_value.number_integer >= 0) - { - // MessagePack does not differentiate between positive - // signed integers and unsigned integers. Therefore, we used - // the code from the value_t::number_unsigned case here. - if (j.m_value.number_unsigned < 128) - { - // positive fixnum - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - // uint 8 - oa->write_character(to_char_type(0xCC)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - // uint 16 - oa->write_character(to_char_type(0xCD)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - // uint 32 - oa->write_character(to_char_type(0xCE)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - // uint 64 - oa->write_character(to_char_type(0xCF)); - write_number(static_cast(j.m_value.number_integer)); - } - } - else - { - if (j.m_value.number_integer >= -32) - { - // negative fixnum - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_integer >= (std::numeric_limits::min)() && - j.m_value.number_integer <= (std::numeric_limits::max)()) - { - // int 8 - oa->write_character(to_char_type(0xD0)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_integer >= (std::numeric_limits::min)() && - j.m_value.number_integer <= (std::numeric_limits::max)()) - { - // int 16 - oa->write_character(to_char_type(0xD1)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_integer >= (std::numeric_limits::min)() && - j.m_value.number_integer <= (std::numeric_limits::max)()) - { - // int 32 - oa->write_character(to_char_type(0xD2)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_integer >= (std::numeric_limits::min)() && - j.m_value.number_integer <= (std::numeric_limits::max)()) - { - // int 64 - oa->write_character(to_char_type(0xD3)); - write_number(static_cast(j.m_value.number_integer)); - } - } - break; - } - - case value_t::number_unsigned: - { - if (j.m_value.number_unsigned < 128) - { - // positive fixnum - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - // uint 8 - oa->write_character(to_char_type(0xCC)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - // uint 16 - oa->write_character(to_char_type(0xCD)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - // uint 32 - oa->write_character(to_char_type(0xCE)); - write_number(static_cast(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) - { - // uint 64 - oa->write_character(to_char_type(0xCF)); - write_number(static_cast(j.m_value.number_integer)); - } - break; - } - - case value_t::number_float: - { - write_compact_float(j.m_value.number_float, detail::input_format_t::msgpack); - break; - } - - case value_t::string: - { - // step 1: write control byte and the string length - const auto N = j.m_value.string->size(); - if (N <= 31) - { - // fixstr - write_number(static_cast(0xA0 | N)); - } - else if (N <= (std::numeric_limits::max)()) - { - // str 8 - oa->write_character(to_char_type(0xD9)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - // str 16 - oa->write_character(to_char_type(0xDA)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - // str 32 - oa->write_character(to_char_type(0xDB)); - write_number(static_cast(N)); - } - - // step 2: write the string - oa->write_characters( - reinterpret_cast(j.m_value.string->c_str()), - j.m_value.string->size()); - break; - } - - case value_t::array: - { - // step 1: write control byte and the array size - const auto N = j.m_value.array->size(); - if (N <= 15) - { - // fixarray - write_number(static_cast(0x90 | N)); - } - else if (N <= (std::numeric_limits::max)()) - { - // array 16 - oa->write_character(to_char_type(0xDC)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - // array 32 - oa->write_character(to_char_type(0xDD)); - write_number(static_cast(N)); - } - - // step 2: write each element - for (const auto& el : *j.m_value.array) - { - write_msgpack(el); - } - break; - } - - case value_t::binary: - { - // step 0: determine if the binary type has a set subtype to - // determine whether or not to use the ext or fixext types - const bool use_ext = j.m_value.binary->has_subtype(); - - // step 1: write control byte and the byte string length - const auto N = j.m_value.binary->size(); - if (N <= (std::numeric_limits::max)()) - { - std::uint8_t output_type{}; - bool fixed = true; - if (use_ext) - { - switch (N) - { - case 1: - output_type = 0xD4; // fixext 1 - break; - case 2: - output_type = 0xD5; // fixext 2 - break; - case 4: - output_type = 0xD6; // fixext 4 - break; - case 8: - output_type = 0xD7; // fixext 8 - break; - case 16: - output_type = 0xD8; // fixext 16 - break; - default: - output_type = 0xC7; // ext 8 - fixed = false; - break; - } - - } - else - { - output_type = 0xC4; // bin 8 - fixed = false; - } - - oa->write_character(to_char_type(output_type)); - if (!fixed) - { - write_number(static_cast(N)); - } - } - else if (N <= (std::numeric_limits::max)()) - { - std::uint8_t output_type = use_ext - ? 0xC8 // ext 16 - : 0xC5; // bin 16 - - oa->write_character(to_char_type(output_type)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - std::uint8_t output_type = use_ext - ? 0xC9 // ext 32 - : 0xC6; // bin 32 - - oa->write_character(to_char_type(output_type)); - write_number(static_cast(N)); - } - - // step 1.5: if this is an ext type, write the subtype - if (use_ext) - { - write_number(static_cast(j.m_value.binary->subtype())); - } - - // step 2: write the byte string - oa->write_characters( - reinterpret_cast(j.m_value.binary->data()), - N); - - break; - } - - case value_t::object: - { - // step 1: write control byte and the object size - const auto N = j.m_value.object->size(); - if (N <= 15) - { - // fixmap - write_number(static_cast(0x80 | (N & 0xF))); - } - else if (N <= (std::numeric_limits::max)()) - { - // map 16 - oa->write_character(to_char_type(0xDE)); - write_number(static_cast(N)); - } - else if (N <= (std::numeric_limits::max)()) - { - // map 32 - oa->write_character(to_char_type(0xDF)); - write_number(static_cast(N)); - } - - // step 2: write each element - for (const auto& el : *j.m_value.object) - { - write_msgpack(el.first); - write_msgpack(el.second); - } - break; - } - - default: - break; - } - } - - /*! - @param[in] j JSON value to serialize - @param[in] use_count whether to use '#' prefixes (optimized format) - @param[in] use_type whether to use '$' prefixes (optimized format) - @param[in] add_prefix whether prefixes need to be used for this value - */ - void write_ubjson(const BasicJsonType& j, const bool use_count, - const bool use_type, const bool add_prefix = true) - { - switch (j.type()) - { - case value_t::null: - { - if (add_prefix) - { - oa->write_character(to_char_type('Z')); - } - break; - } - - case value_t::boolean: - { - if (add_prefix) - { - oa->write_character(j.m_value.boolean - ? to_char_type('T') - : to_char_type('F')); - } - break; - } - - case value_t::number_integer: - { - write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); - break; - } - - case value_t::number_unsigned: - { - write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); - break; - } - - case value_t::number_float: - { - write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); - break; - } - - case value_t::string: - { - if (add_prefix) - { - oa->write_character(to_char_type('S')); - } - write_number_with_ubjson_prefix(j.m_value.string->size(), true); - oa->write_characters( - reinterpret_cast(j.m_value.string->c_str()), - j.m_value.string->size()); - break; - } - - case value_t::array: - { - if (add_prefix) - { - oa->write_character(to_char_type('[')); - } - - bool prefix_required = true; - if (use_type && !j.m_value.array->empty()) - { - JSON_ASSERT(use_count); - const CharType first_prefix = ubjson_prefix(j.front()); - const bool same_prefix = std::all_of(j.begin() + 1, j.end(), - [this, first_prefix](const BasicJsonType & v) - { - return ubjson_prefix(v) == first_prefix; - }); - - if (same_prefix) - { - prefix_required = false; - oa->write_character(to_char_type('$')); - oa->write_character(first_prefix); - } - } - - if (use_count) - { - oa->write_character(to_char_type('#')); - write_number_with_ubjson_prefix(j.m_value.array->size(), true); - } - - for (const auto& el : *j.m_value.array) - { - write_ubjson(el, use_count, use_type, prefix_required); - } - - if (!use_count) - { - oa->write_character(to_char_type(']')); - } - - break; - } - - case value_t::binary: - { - if (add_prefix) - { - oa->write_character(to_char_type('[')); - } - - if (use_type && !j.m_value.binary->empty()) - { - JSON_ASSERT(use_count); - oa->write_character(to_char_type('$')); - oa->write_character('U'); - } - - if (use_count) - { - oa->write_character(to_char_type('#')); - write_number_with_ubjson_prefix(j.m_value.binary->size(), true); - } - - if (use_type) - { - oa->write_characters( - reinterpret_cast(j.m_value.binary->data()), - j.m_value.binary->size()); - } - else - { - for (size_t i = 0; i < j.m_value.binary->size(); ++i) - { - oa->write_character(to_char_type('U')); - oa->write_character(j.m_value.binary->data()[i]); - } - } - - if (!use_count) - { - oa->write_character(to_char_type(']')); - } - - break; - } - - case value_t::object: - { - if (add_prefix) - { - oa->write_character(to_char_type('{')); - } - - bool prefix_required = true; - if (use_type && !j.m_value.object->empty()) - { - JSON_ASSERT(use_count); - const CharType first_prefix = ubjson_prefix(j.front()); - const bool same_prefix = std::all_of(j.begin(), j.end(), - [this, first_prefix](const BasicJsonType & v) - { - return ubjson_prefix(v) == first_prefix; - }); - - if (same_prefix) - { - prefix_required = false; - oa->write_character(to_char_type('$')); - oa->write_character(first_prefix); - } - } - - if (use_count) - { - oa->write_character(to_char_type('#')); - write_number_with_ubjson_prefix(j.m_value.object->size(), true); - } - - for (const auto& el : *j.m_value.object) - { - write_number_with_ubjson_prefix(el.first.size(), true); - oa->write_characters( - reinterpret_cast(el.first.c_str()), - el.first.size()); - write_ubjson(el.second, use_count, use_type, prefix_required); - } - - if (!use_count) - { - oa->write_character(to_char_type('}')); - } - - break; - } - - default: - break; - } - } - - private: - ////////// - // BSON // - ////////// - - /*! - @return The size of a BSON document entry header, including the id marker - and the entry name size (and its null-terminator). - */ - static std::size_t calc_bson_entry_header_size(const string_t& name) - { - const auto it = name.find(static_cast(0)); - if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos)) - { - JSON_THROW(out_of_range::create(409, - "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")")); - } - - return /*id*/ 1ul + name.size() + /*zero-terminator*/1u; - } - - /*! - @brief Writes the given @a element_type and @a name to the output adapter - */ - void write_bson_entry_header(const string_t& name, - const std::uint8_t element_type) - { - oa->write_character(to_char_type(element_type)); // boolean - oa->write_characters( - reinterpret_cast(name.c_str()), - name.size() + 1u); - } - - /*! - @brief Writes a BSON element with key @a name and boolean value @a value - */ - void write_bson_boolean(const string_t& name, - const bool value) - { - write_bson_entry_header(name, 0x08); - oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00)); - } - - /*! - @brief Writes a BSON element with key @a name and double value @a value - */ - void write_bson_double(const string_t& name, - const double value) - { - write_bson_entry_header(name, 0x01); - write_number(value); - } - - /*! - @return The size of the BSON-encoded string in @a value - */ - static std::size_t calc_bson_string_size(const string_t& value) - { - return sizeof(std::int32_t) + value.size() + 1ul; - } - - /*! - @brief Writes a BSON element with key @a name and string value @a value - */ - void write_bson_string(const string_t& name, - const string_t& value) - { - write_bson_entry_header(name, 0x02); - - write_number(static_cast(value.size() + 1ul)); - oa->write_characters( - reinterpret_cast(value.c_str()), - value.size() + 1); - } - - /*! - @brief Writes a BSON element with key @a name and null value - */ - void write_bson_null(const string_t& name) - { - write_bson_entry_header(name, 0x0A); - } - - /*! - @return The size of the BSON-encoded integer @a value - */ - static std::size_t calc_bson_integer_size(const std::int64_t value) - { - return (std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)() - ? sizeof(std::int32_t) - : sizeof(std::int64_t); - } - - /*! - @brief Writes a BSON element with key @a name and integer @a value - */ - void write_bson_integer(const string_t& name, - const std::int64_t value) - { - if ((std::numeric_limits::min)() <= value && value <= (std::numeric_limits::max)()) - { - write_bson_entry_header(name, 0x10); // int32 - write_number(static_cast(value)); - } - else - { - write_bson_entry_header(name, 0x12); // int64 - write_number(static_cast(value)); - } - } - - /*! - @return The size of the BSON-encoded unsigned integer in @a j - */ - static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept - { - return (value <= static_cast((std::numeric_limits::max)())) - ? sizeof(std::int32_t) - : sizeof(std::int64_t); - } - - /*! - @brief Writes a BSON element with key @a name and unsigned @a value - */ - void write_bson_unsigned(const string_t& name, - const std::uint64_t value) - { - if (value <= static_cast((std::numeric_limits::max)())) - { - write_bson_entry_header(name, 0x10 /* int32 */); - write_number(static_cast(value)); - } - else if (value <= static_cast((std::numeric_limits::max)())) - { - write_bson_entry_header(name, 0x12 /* int64 */); - write_number(static_cast(value)); - } - else - { - JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(value) + " cannot be represented by BSON as it does not fit int64")); - } - } - - /*! - @brief Writes a BSON element with key @a name and object @a value - */ - void write_bson_object_entry(const string_t& name, - const typename BasicJsonType::object_t& value) - { - write_bson_entry_header(name, 0x03); // object - write_bson_object(value); - } - - /*! - @return The size of the BSON-encoded array @a value - */ - static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value) - { - std::size_t array_index = 0ul; - - const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), std::size_t(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el) - { - return result + calc_bson_element_size(std::to_string(array_index++), el); - }); - - return sizeof(std::int32_t) + embedded_document_size + 1ul; - } - - /*! - @return The size of the BSON-encoded binary array @a value - */ - static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value) - { - return sizeof(std::int32_t) + value.size() + 1ul; - } - - /*! - @brief Writes a BSON element with key @a name and array @a value - */ - void write_bson_array(const string_t& name, - const typename BasicJsonType::array_t& value) - { - write_bson_entry_header(name, 0x04); // array - write_number(static_cast(calc_bson_array_size(value))); - - std::size_t array_index = 0ul; - - for (const auto& el : value) - { - write_bson_element(std::to_string(array_index++), el); - } - - oa->write_character(to_char_type(0x00)); - } - - /*! - @brief Writes a BSON element with key @a name and binary value @a value - */ - void write_bson_binary(const string_t& name, - const binary_t& value) - { - write_bson_entry_header(name, 0x05); - - write_number(static_cast(value.size())); - write_number(value.has_subtype() ? value.subtype() : std::uint8_t(0x00)); - - oa->write_characters(reinterpret_cast(value.data()), value.size()); - } - - /*! - @brief Calculates the size necessary to serialize the JSON value @a j with its @a name - @return The calculated size for the BSON document entry for @a j with the given @a name. - */ - static std::size_t calc_bson_element_size(const string_t& name, - const BasicJsonType& j) - { - const auto header_size = calc_bson_entry_header_size(name); - switch (j.type()) - { - case value_t::object: - return header_size + calc_bson_object_size(*j.m_value.object); - - case value_t::array: - return header_size + calc_bson_array_size(*j.m_value.array); - - case value_t::binary: - return header_size + calc_bson_binary_size(*j.m_value.binary); - - case value_t::boolean: - return header_size + 1ul; - - case value_t::number_float: - return header_size + 8ul; - - case value_t::number_integer: - return header_size + calc_bson_integer_size(j.m_value.number_integer); - - case value_t::number_unsigned: - return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned); - - case value_t::string: - return header_size + calc_bson_string_size(*j.m_value.string); - - case value_t::null: - return header_size + 0ul; - - // LCOV_EXCL_START - default: - JSON_ASSERT(false); - return 0ul; - // LCOV_EXCL_STOP - } - } - - /*! - @brief Serializes the JSON value @a j to BSON and associates it with the - key @a name. - @param name The name to associate with the JSON entity @a j within the - current BSON document - @return The size of the BSON entry - */ - void write_bson_element(const string_t& name, - const BasicJsonType& j) - { - switch (j.type()) - { - case value_t::object: - return write_bson_object_entry(name, *j.m_value.object); - - case value_t::array: - return write_bson_array(name, *j.m_value.array); - - case value_t::binary: - return write_bson_binary(name, *j.m_value.binary); - - case value_t::boolean: - return write_bson_boolean(name, j.m_value.boolean); - - case value_t::number_float: - return write_bson_double(name, j.m_value.number_float); - - case value_t::number_integer: - return write_bson_integer(name, j.m_value.number_integer); - - case value_t::number_unsigned: - return write_bson_unsigned(name, j.m_value.number_unsigned); - - case value_t::string: - return write_bson_string(name, *j.m_value.string); - - case value_t::null: - return write_bson_null(name); - - // LCOV_EXCL_START - default: - JSON_ASSERT(false); - return; - // LCOV_EXCL_STOP - } - } - - /*! - @brief Calculates the size of the BSON serialization of the given - JSON-object @a j. - @param[in] j JSON value to serialize - @pre j.type() == value_t::object - */ - static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value) - { - std::size_t document_size = std::accumulate(value.begin(), value.end(), std::size_t(0), - [](size_t result, const typename BasicJsonType::object_t::value_type & el) - { - return result += calc_bson_element_size(el.first, el.second); - }); - - return sizeof(std::int32_t) + document_size + 1ul; - } - - /*! - @param[in] j JSON value to serialize - @pre j.type() == value_t::object - */ - void write_bson_object(const typename BasicJsonType::object_t& value) - { - write_number(static_cast(calc_bson_object_size(value))); - - for (const auto& el : value) - { - write_bson_element(el.first, el.second); - } - - oa->write_character(to_char_type(0x00)); - } - - ////////// - // CBOR // - ////////// - - static constexpr CharType get_cbor_float_prefix(float /*unused*/) - { - return to_char_type(0xFA); // Single-Precision Float - } - - static constexpr CharType get_cbor_float_prefix(double /*unused*/) - { - return to_char_type(0xFB); // Double-Precision Float - } - - ///////////// - // MsgPack // - ///////////// - - static constexpr CharType get_msgpack_float_prefix(float /*unused*/) - { - return to_char_type(0xCA); // float 32 - } - - static constexpr CharType get_msgpack_float_prefix(double /*unused*/) - { - return to_char_type(0xCB); // float 64 - } - - //////////// - // UBJSON // - //////////// - - // UBJSON: write number (floating point) - template::value, int>::type = 0> - void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) - { - if (add_prefix) - { - oa->write_character(get_ubjson_float_prefix(n)); - } - write_number(n); - } - - // UBJSON: write number (unsigned integer) - template::value, int>::type = 0> - void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) - { - if (n <= static_cast((std::numeric_limits::max)())) - { - if (add_prefix) - { - oa->write_character(to_char_type('i')); // int8 - } - write_number(static_cast(n)); - } - else if (n <= (std::numeric_limits::max)()) - { - if (add_prefix) - { - oa->write_character(to_char_type('U')); // uint8 - } - write_number(static_cast(n)); - } - else if (n <= static_cast((std::numeric_limits::max)())) - { - if (add_prefix) - { - oa->write_character(to_char_type('I')); // int16 - } - write_number(static_cast(n)); - } - else if (n <= static_cast((std::numeric_limits::max)())) - { - if (add_prefix) - { - oa->write_character(to_char_type('l')); // int32 - } - write_number(static_cast(n)); - } - else if (n <= static_cast((std::numeric_limits::max)())) - { - if (add_prefix) - { - oa->write_character(to_char_type('L')); // int64 - } - write_number(static_cast(n)); - } - else - { - if (add_prefix) - { - oa->write_character(to_char_type('H')); // high-precision number - } - - const auto number = BasicJsonType(n).dump(); - write_number_with_ubjson_prefix(number.size(), true); - for (std::size_t i = 0; i < number.size(); ++i) - { - oa->write_character(to_char_type(static_cast(number[i]))); - } - } - } - - // UBJSON: write number (signed integer) - template < typename NumberType, typename std::enable_if < - std::is_signed::value&& - !std::is_floating_point::value, int >::type = 0 > - void write_number_with_ubjson_prefix(const NumberType n, - const bool add_prefix) - { - if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) - { - if (add_prefix) - { - oa->write_character(to_char_type('i')); // int8 - } - write_number(static_cast(n)); - } - else if (static_cast((std::numeric_limits::min)()) <= n && n <= static_cast((std::numeric_limits::max)())) - { - if (add_prefix) - { - oa->write_character(to_char_type('U')); // uint8 - } - write_number(static_cast(n)); - } - else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) - { - if (add_prefix) - { - oa->write_character(to_char_type('I')); // int16 - } - write_number(static_cast(n)); - } - else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) - { - if (add_prefix) - { - oa->write_character(to_char_type('l')); // int32 - } - write_number(static_cast(n)); - } - else if ((std::numeric_limits::min)() <= n && n <= (std::numeric_limits::max)()) - { - if (add_prefix) - { - oa->write_character(to_char_type('L')); // int64 - } - write_number(static_cast(n)); - } - // LCOV_EXCL_START - else - { - if (add_prefix) - { - oa->write_character(to_char_type('H')); // high-precision number - } - - const auto number = BasicJsonType(n).dump(); - write_number_with_ubjson_prefix(number.size(), true); - for (std::size_t i = 0; i < number.size(); ++i) - { - oa->write_character(to_char_type(static_cast(number[i]))); - } - } - // LCOV_EXCL_STOP - } - - /*! - @brief determine the type prefix of container values - */ - CharType ubjson_prefix(const BasicJsonType& j) const noexcept - { - switch (j.type()) - { - case value_t::null: - return 'Z'; - - case value_t::boolean: - return j.m_value.boolean ? 'T' : 'F'; - - case value_t::number_integer: - { - if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) - { - return 'i'; - } - if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) - { - return 'U'; - } - if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) - { - return 'I'; - } - if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) - { - return 'l'; - } - if ((std::numeric_limits::min)() <= j.m_value.number_integer && j.m_value.number_integer <= (std::numeric_limits::max)()) - { - return 'L'; - } - // anything else is treated as high-precision number - return 'H'; // LCOV_EXCL_LINE - } - - case value_t::number_unsigned: - { - if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) - { - return 'i'; - } - if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) - { - return 'U'; - } - if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) - { - return 'I'; - } - if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) - { - return 'l'; - } - if (j.m_value.number_unsigned <= static_cast((std::numeric_limits::max)())) - { - return 'L'; - } - // anything else is treated as high-precision number - return 'H'; // LCOV_EXCL_LINE - } - - case value_t::number_float: - return get_ubjson_float_prefix(j.m_value.number_float); - - case value_t::string: - return 'S'; - - case value_t::array: // fallthrough - case value_t::binary: - return '['; - - case value_t::object: - return '{'; - - default: // discarded values - return 'N'; - } - } - - static constexpr CharType get_ubjson_float_prefix(float /*unused*/) - { - return 'd'; // float 32 - } - - static constexpr CharType get_ubjson_float_prefix(double /*unused*/) - { - return 'D'; // float 64 - } - - /////////////////////// - // Utility functions // - /////////////////////// - - /* - @brief write a number to output input - @param[in] n number of type @a NumberType - @tparam NumberType the type of the number - @tparam OutputIsLittleEndian Set to true if output data is - required to be little endian - - @note This function needs to respect the system's endianess, because bytes - in CBOR, MessagePack, and UBJSON are stored in network order (big - endian) and therefore need reordering on little endian systems. - */ - template - void write_number(const NumberType n) - { - // step 1: write number to array of length NumberType - std::array vec; - std::memcpy(vec.data(), &n, sizeof(NumberType)); - - // step 2: write array to output (with possible reordering) - if (is_little_endian != OutputIsLittleEndian) - { - // reverse byte order prior to conversion if necessary - std::reverse(vec.begin(), vec.end()); - } - - oa->write_characters(vec.data(), sizeof(NumberType)); - } - - void write_compact_float(const number_float_t n, detail::input_format_t format) - { - if (static_cast(n) >= static_cast(std::numeric_limits::lowest()) && - static_cast(n) <= static_cast((std::numeric_limits::max)()) && - static_cast(static_cast(n)) == static_cast(n)) - { - oa->write_character(format == detail::input_format_t::cbor - ? get_cbor_float_prefix(static_cast(n)) - : get_msgpack_float_prefix(static_cast(n))); - write_number(static_cast(n)); - } - else - { - oa->write_character(format == detail::input_format_t::cbor - ? get_cbor_float_prefix(n) - : get_msgpack_float_prefix(n)); - write_number(n); - } - } - - public: - // The following to_char_type functions are implement the conversion - // between uint8_t and CharType. In case CharType is not unsigned, - // such a conversion is required to allow values greater than 128. - // See for a discussion. - template < typename C = CharType, - enable_if_t < std::is_signed::value && std::is_signed::value > * = nullptr > - static constexpr CharType to_char_type(std::uint8_t x) noexcept - { - return *reinterpret_cast(&x); - } - - template < typename C = CharType, - enable_if_t < std::is_signed::value && std::is_unsigned::value > * = nullptr > - static CharType to_char_type(std::uint8_t x) noexcept - { - static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t"); - static_assert(std::is_trivial::value, "CharType must be trivial"); - CharType result; - std::memcpy(&result, &x, sizeof(x)); - return result; - } - - template::value>* = nullptr> - static constexpr CharType to_char_type(std::uint8_t x) noexcept - { - return x; - } - - template < typename InputCharType, typename C = CharType, - enable_if_t < - std::is_signed::value && - std::is_signed::value && - std::is_same::type>::value - > * = nullptr > - static constexpr CharType to_char_type(InputCharType x) noexcept - { - return x; - } - - private: - /// whether we can assume little endianess - const bool is_little_endian = little_endianess(); - - /// the output - output_adapter_t oa = nullptr; -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - - -#include // reverse, remove, fill, find, none_of -#include // array -#include // localeconv, lconv -#include // labs, isfinite, isnan, signbit -#include // size_t, ptrdiff_t -#include // uint8_t -#include // snprintf -#include // numeric_limits -#include // string, char_traits -#include // is_same -#include // move - -// #include - - -#include // array -#include // signbit, isfinite -#include // intN_t, uintN_t -#include // memcpy, memmove -#include // numeric_limits -#include // conditional - -// #include - - -namespace nlohmann -{ -namespace detail -{ - -/*! -@brief implements the Grisu2 algorithm for binary to decimal floating-point -conversion. - -This implementation is a slightly modified version of the reference -implementation which may be obtained from -http://florian.loitsch.com/publications (bench.tar.gz). - -The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. - -For a detailed description of the algorithm see: - -[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with - Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming - Language Design and Implementation, PLDI 2010 -[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", - Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language - Design and Implementation, PLDI 1996 -*/ -namespace dtoa_impl -{ - -template -Target reinterpret_bits(const Source source) -{ - static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); - - Target target; - std::memcpy(&target, &source, sizeof(Source)); - return target; -} - -struct diyfp // f * 2^e -{ - static constexpr int kPrecision = 64; // = q - - std::uint64_t f = 0; - int e = 0; - - constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {} - - /*! - @brief returns x - y - @pre x.e == y.e and x.f >= y.f - */ - static diyfp sub(const diyfp& x, const diyfp& y) noexcept - { - JSON_ASSERT(x.e == y.e); - JSON_ASSERT(x.f >= y.f); - - return {x.f - y.f, x.e}; - } - - /*! - @brief returns x * y - @note The result is rounded. (Only the upper q bits are returned.) - */ - static diyfp mul(const diyfp& x, const diyfp& y) noexcept - { - static_assert(kPrecision == 64, "internal error"); - - // Computes: - // f = round((x.f * y.f) / 2^q) - // e = x.e + y.e + q - - // Emulate the 64-bit * 64-bit multiplication: - // - // p = u * v - // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) - // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) - // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) - // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) - // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) - // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) - // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) - // - // (Since Q might be larger than 2^32 - 1) - // - // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) - // - // (Q_hi + H does not overflow a 64-bit int) - // - // = p_lo + 2^64 p_hi - - const std::uint64_t u_lo = x.f & 0xFFFFFFFFu; - const std::uint64_t u_hi = x.f >> 32u; - const std::uint64_t v_lo = y.f & 0xFFFFFFFFu; - const std::uint64_t v_hi = y.f >> 32u; - - const std::uint64_t p0 = u_lo * v_lo; - const std::uint64_t p1 = u_lo * v_hi; - const std::uint64_t p2 = u_hi * v_lo; - const std::uint64_t p3 = u_hi * v_hi; - - const std::uint64_t p0_hi = p0 >> 32u; - const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu; - const std::uint64_t p1_hi = p1 >> 32u; - const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu; - const std::uint64_t p2_hi = p2 >> 32u; - - std::uint64_t Q = p0_hi + p1_lo + p2_lo; - - // The full product might now be computed as - // - // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) - // p_lo = p0_lo + (Q << 32) - // - // But in this particular case here, the full p_lo is not required. - // Effectively we only need to add the highest bit in p_lo to p_hi (and - // Q_hi + 1 does not overflow). - - Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up - - const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u); - - return {h, x.e + y.e + 64}; - } - - /*! - @brief normalize x such that the significand is >= 2^(q-1) - @pre x.f != 0 - */ - static diyfp normalize(diyfp x) noexcept - { - JSON_ASSERT(x.f != 0); - - while ((x.f >> 63u) == 0) - { - x.f <<= 1u; - x.e--; - } - - return x; - } - - /*! - @brief normalize x such that the result has the exponent E - @pre e >= x.e and the upper e - x.e bits of x.f must be zero. - */ - static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept - { - const int delta = x.e - target_exponent; - - JSON_ASSERT(delta >= 0); - JSON_ASSERT(((x.f << delta) >> delta) == x.f); - - return {x.f << delta, target_exponent}; - } -}; - -struct boundaries -{ - diyfp w; - diyfp minus; - diyfp plus; -}; - -/*! -Compute the (normalized) diyfp representing the input number 'value' and its -boundaries. - -@pre value must be finite and positive -*/ -template -boundaries compute_boundaries(FloatType value) -{ - JSON_ASSERT(std::isfinite(value)); - JSON_ASSERT(value > 0); - - // Convert the IEEE representation into a diyfp. - // - // If v is denormal: - // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) - // If v is normalized: - // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) - - static_assert(std::numeric_limits::is_iec559, - "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); - - constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) - constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); - constexpr int kMinExp = 1 - kBias; - constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1) - - using bits_type = typename std::conditional::type; - - const std::uint64_t bits = reinterpret_bits(value); - const std::uint64_t E = bits >> (kPrecision - 1); - const std::uint64_t F = bits & (kHiddenBit - 1); - - const bool is_denormal = E == 0; - const diyfp v = is_denormal - ? diyfp(F, kMinExp) - : diyfp(F + kHiddenBit, static_cast(E) - kBias); - - // Compute the boundaries m- and m+ of the floating-point value - // v = f * 2^e. - // - // Determine v- and v+, the floating-point predecessor and successor if v, - // respectively. - // - // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) - // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) - // - // v+ = v + 2^e - // - // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ - // between m- and m+ round to v, regardless of how the input rounding - // algorithm breaks ties. - // - // ---+-------------+-------------+-------------+-------------+--- (A) - // v- m- v m+ v+ - // - // -----------------+------+------+-------------+-------------+--- (B) - // v- m- v m+ v+ - - const bool lower_boundary_is_closer = F == 0 && E > 1; - const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); - const diyfp m_minus = lower_boundary_is_closer - ? diyfp(4 * v.f - 1, v.e - 2) // (B) - : diyfp(2 * v.f - 1, v.e - 1); // (A) - - // Determine the normalized w+ = m+. - const diyfp w_plus = diyfp::normalize(m_plus); - - // Determine w- = m- such that e_(w-) = e_(w+). - const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); - - return {diyfp::normalize(v), w_minus, w_plus}; -} - -// Given normalized diyfp w, Grisu needs to find a (normalized) cached -// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies -// within a certain range [alpha, gamma] (Definition 3.2 from [1]) -// -// alpha <= e = e_c + e_w + q <= gamma -// -// or -// -// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q -// <= f_c * f_w * 2^gamma -// -// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies -// -// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma -// -// or -// -// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) -// -// The choice of (alpha,gamma) determines the size of the table and the form of -// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well -// in practice: -// -// The idea is to cut the number c * w = f * 2^e into two parts, which can be -// processed independently: An integral part p1, and a fractional part p2: -// -// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e -// = (f div 2^-e) + (f mod 2^-e) * 2^e -// = p1 + p2 * 2^e -// -// The conversion of p1 into decimal form requires a series of divisions and -// modulos by (a power of) 10. These operations are faster for 32-bit than for -// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be -// achieved by choosing -// -// -e >= 32 or e <= -32 := gamma -// -// In order to convert the fractional part -// -// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... -// -// into decimal form, the fraction is repeatedly multiplied by 10 and the digits -// d[-i] are extracted in order: -// -// (10 * p2) div 2^-e = d[-1] -// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... -// -// The multiplication by 10 must not overflow. It is sufficient to choose -// -// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. -// -// Since p2 = f mod 2^-e < 2^-e, -// -// -e <= 60 or e >= -60 := alpha - -constexpr int kAlpha = -60; -constexpr int kGamma = -32; - -struct cached_power // c = f * 2^e ~= 10^k -{ - std::uint64_t f; - int e; - int k; -}; - -/*! -For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached -power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c -satisfies (Definition 3.2 from [1]) - - alpha <= e_c + e + q <= gamma. -*/ -inline cached_power get_cached_power_for_binary_exponent(int e) -{ - // Now - // - // alpha <= e_c + e + q <= gamma (1) - // ==> f_c * 2^alpha <= c * 2^e * 2^q - // - // and since the c's are normalized, 2^(q-1) <= f_c, - // - // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) - // ==> 2^(alpha - e - 1) <= c - // - // If c were an exact power of ten, i.e. c = 10^k, one may determine k as - // - // k = ceil( log_10( 2^(alpha - e - 1) ) ) - // = ceil( (alpha - e - 1) * log_10(2) ) - // - // From the paper: - // "In theory the result of the procedure could be wrong since c is rounded, - // and the computation itself is approximated [...]. In practice, however, - // this simple function is sufficient." - // - // For IEEE double precision floating-point numbers converted into - // normalized diyfp's w = f * 2^e, with q = 64, - // - // e >= -1022 (min IEEE exponent) - // -52 (p - 1) - // -52 (p - 1, possibly normalize denormal IEEE numbers) - // -11 (normalize the diyfp) - // = -1137 - // - // and - // - // e <= +1023 (max IEEE exponent) - // -52 (p - 1) - // -11 (normalize the diyfp) - // = 960 - // - // This binary exponent range [-1137,960] results in a decimal exponent - // range [-307,324]. One does not need to store a cached power for each - // k in this range. For each such k it suffices to find a cached power - // such that the exponent of the product lies in [alpha,gamma]. - // This implies that the difference of the decimal exponents of adjacent - // table entries must be less than or equal to - // - // floor( (gamma - alpha) * log_10(2) ) = 8. - // - // (A smaller distance gamma-alpha would require a larger table.) - - // NB: - // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. - - constexpr int kCachedPowersMinDecExp = -300; - constexpr int kCachedPowersDecStep = 8; - - static constexpr std::array kCachedPowers = - { - { - { 0xAB70FE17C79AC6CA, -1060, -300 }, - { 0xFF77B1FCBEBCDC4F, -1034, -292 }, - { 0xBE5691EF416BD60C, -1007, -284 }, - { 0x8DD01FAD907FFC3C, -980, -276 }, - { 0xD3515C2831559A83, -954, -268 }, - { 0x9D71AC8FADA6C9B5, -927, -260 }, - { 0xEA9C227723EE8BCB, -901, -252 }, - { 0xAECC49914078536D, -874, -244 }, - { 0x823C12795DB6CE57, -847, -236 }, - { 0xC21094364DFB5637, -821, -228 }, - { 0x9096EA6F3848984F, -794, -220 }, - { 0xD77485CB25823AC7, -768, -212 }, - { 0xA086CFCD97BF97F4, -741, -204 }, - { 0xEF340A98172AACE5, -715, -196 }, - { 0xB23867FB2A35B28E, -688, -188 }, - { 0x84C8D4DFD2C63F3B, -661, -180 }, - { 0xC5DD44271AD3CDBA, -635, -172 }, - { 0x936B9FCEBB25C996, -608, -164 }, - { 0xDBAC6C247D62A584, -582, -156 }, - { 0xA3AB66580D5FDAF6, -555, -148 }, - { 0xF3E2F893DEC3F126, -529, -140 }, - { 0xB5B5ADA8AAFF80B8, -502, -132 }, - { 0x87625F056C7C4A8B, -475, -124 }, - { 0xC9BCFF6034C13053, -449, -116 }, - { 0x964E858C91BA2655, -422, -108 }, - { 0xDFF9772470297EBD, -396, -100 }, - { 0xA6DFBD9FB8E5B88F, -369, -92 }, - { 0xF8A95FCF88747D94, -343, -84 }, - { 0xB94470938FA89BCF, -316, -76 }, - { 0x8A08F0F8BF0F156B, -289, -68 }, - { 0xCDB02555653131B6, -263, -60 }, - { 0x993FE2C6D07B7FAC, -236, -52 }, - { 0xE45C10C42A2B3B06, -210, -44 }, - { 0xAA242499697392D3, -183, -36 }, - { 0xFD87B5F28300CA0E, -157, -28 }, - { 0xBCE5086492111AEB, -130, -20 }, - { 0x8CBCCC096F5088CC, -103, -12 }, - { 0xD1B71758E219652C, -77, -4 }, - { 0x9C40000000000000, -50, 4 }, - { 0xE8D4A51000000000, -24, 12 }, - { 0xAD78EBC5AC620000, 3, 20 }, - { 0x813F3978F8940984, 30, 28 }, - { 0xC097CE7BC90715B3, 56, 36 }, - { 0x8F7E32CE7BEA5C70, 83, 44 }, - { 0xD5D238A4ABE98068, 109, 52 }, - { 0x9F4F2726179A2245, 136, 60 }, - { 0xED63A231D4C4FB27, 162, 68 }, - { 0xB0DE65388CC8ADA8, 189, 76 }, - { 0x83C7088E1AAB65DB, 216, 84 }, - { 0xC45D1DF942711D9A, 242, 92 }, - { 0x924D692CA61BE758, 269, 100 }, - { 0xDA01EE641A708DEA, 295, 108 }, - { 0xA26DA3999AEF774A, 322, 116 }, - { 0xF209787BB47D6B85, 348, 124 }, - { 0xB454E4A179DD1877, 375, 132 }, - { 0x865B86925B9BC5C2, 402, 140 }, - { 0xC83553C5C8965D3D, 428, 148 }, - { 0x952AB45CFA97A0B3, 455, 156 }, - { 0xDE469FBD99A05FE3, 481, 164 }, - { 0xA59BC234DB398C25, 508, 172 }, - { 0xF6C69A72A3989F5C, 534, 180 }, - { 0xB7DCBF5354E9BECE, 561, 188 }, - { 0x88FCF317F22241E2, 588, 196 }, - { 0xCC20CE9BD35C78A5, 614, 204 }, - { 0x98165AF37B2153DF, 641, 212 }, - { 0xE2A0B5DC971F303A, 667, 220 }, - { 0xA8D9D1535CE3B396, 694, 228 }, - { 0xFB9B7CD9A4A7443C, 720, 236 }, - { 0xBB764C4CA7A44410, 747, 244 }, - { 0x8BAB8EEFB6409C1A, 774, 252 }, - { 0xD01FEF10A657842C, 800, 260 }, - { 0x9B10A4E5E9913129, 827, 268 }, - { 0xE7109BFBA19C0C9D, 853, 276 }, - { 0xAC2820D9623BF429, 880, 284 }, - { 0x80444B5E7AA7CF85, 907, 292 }, - { 0xBF21E44003ACDD2D, 933, 300 }, - { 0x8E679C2F5E44FF8F, 960, 308 }, - { 0xD433179D9C8CB841, 986, 316 }, - { 0x9E19DB92B4E31BA9, 1013, 324 }, - } - }; - - // This computation gives exactly the same results for k as - // k = ceil((kAlpha - e - 1) * 0.30102999566398114) - // for |e| <= 1500, but doesn't require floating-point operations. - // NB: log_10(2) ~= 78913 / 2^18 - JSON_ASSERT(e >= -1500); - JSON_ASSERT(e <= 1500); - const int f = kAlpha - e - 1; - const int k = (f * 78913) / (1 << 18) + static_cast(f > 0); - - const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; - JSON_ASSERT(index >= 0); - JSON_ASSERT(static_cast(index) < kCachedPowers.size()); - - const cached_power cached = kCachedPowers[static_cast(index)]; - JSON_ASSERT(kAlpha <= cached.e + e + 64); - JSON_ASSERT(kGamma >= cached.e + e + 64); - - return cached; -} - -/*! -For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. -For n == 0, returns 1 and sets pow10 := 1. -*/ -inline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10) -{ - // LCOV_EXCL_START - if (n >= 1000000000) - { - pow10 = 1000000000; - return 10; - } - // LCOV_EXCL_STOP - else if (n >= 100000000) - { - pow10 = 100000000; - return 9; - } - else if (n >= 10000000) - { - pow10 = 10000000; - return 8; - } - else if (n >= 1000000) - { - pow10 = 1000000; - return 7; - } - else if (n >= 100000) - { - pow10 = 100000; - return 6; - } - else if (n >= 10000) - { - pow10 = 10000; - return 5; - } - else if (n >= 1000) - { - pow10 = 1000; - return 4; - } - else if (n >= 100) - { - pow10 = 100; - return 3; - } - else if (n >= 10) - { - pow10 = 10; - return 2; - } - else - { - pow10 = 1; - return 1; - } -} - -inline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta, - std::uint64_t rest, std::uint64_t ten_k) -{ - JSON_ASSERT(len >= 1); - JSON_ASSERT(dist <= delta); - JSON_ASSERT(rest <= delta); - JSON_ASSERT(ten_k > 0); - - // <--------------------------- delta ----> - // <---- dist ---------> - // --------------[------------------+-------------------]-------------- - // M- w M+ - // - // ten_k - // <------> - // <---- rest ----> - // --------------[------------------+----+--------------]-------------- - // w V - // = buf * 10^k - // - // ten_k represents a unit-in-the-last-place in the decimal representation - // stored in buf. - // Decrement buf by ten_k while this takes buf closer to w. - - // The tests are written in this order to avoid overflow in unsigned - // integer arithmetic. - - while (rest < dist - && delta - rest >= ten_k - && (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) - { - JSON_ASSERT(buf[len - 1] != '0'); - buf[len - 1]--; - rest += ten_k; - } -} - -/*! -Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. -M- and M+ must be normalized and share the same exponent -60 <= e <= -32. -*/ -inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, - diyfp M_minus, diyfp w, diyfp M_plus) -{ - static_assert(kAlpha >= -60, "internal error"); - static_assert(kGamma <= -32, "internal error"); - - // Generates the digits (and the exponent) of a decimal floating-point - // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's - // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. - // - // <--------------------------- delta ----> - // <---- dist ---------> - // --------------[------------------+-------------------]-------------- - // M- w M+ - // - // Grisu2 generates the digits of M+ from left to right and stops as soon as - // V is in [M-,M+]. - - JSON_ASSERT(M_plus.e >= kAlpha); - JSON_ASSERT(M_plus.e <= kGamma); - - std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) - std::uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) - - // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): - // - // M+ = f * 2^e - // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e - // = ((p1 ) * 2^-e + (p2 )) * 2^e - // = p1 + p2 * 2^e - - const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e); - - auto p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) - std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e - - // 1) - // - // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] - - JSON_ASSERT(p1 > 0); - - std::uint32_t pow10; - const int k = find_largest_pow10(p1, pow10); - - // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) - // - // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) - // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) - // - // M+ = p1 + p2 * 2^e - // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e - // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e - // = d[k-1] * 10^(k-1) + ( rest) * 2^e - // - // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) - // - // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] - // - // but stop as soon as - // - // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e - - int n = k; - while (n > 0) - { - // Invariants: - // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) - // pow10 = 10^(n-1) <= p1 < 10^n - // - const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) - const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) - // - // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e - // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) - // - JSON_ASSERT(d <= 9); - buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d - // - // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) - // - p1 = r; - n--; - // - // M+ = buffer * 10^n + (p1 + p2 * 2^e) - // pow10 = 10^n - // - - // Now check if enough digits have been generated. - // Compute - // - // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e - // - // Note: - // Since rest and delta share the same exponent e, it suffices to - // compare the significands. - const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2; - if (rest <= delta) - { - // V = buffer * 10^n, with M- <= V <= M+. - - decimal_exponent += n; - - // We may now just stop. But instead look if the buffer could be - // decremented to bring V closer to w. - // - // pow10 = 10^n is now 1 ulp in the decimal representation V. - // The rounding procedure works with diyfp's with an implicit - // exponent of e. - // - // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e - // - const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e; - grisu2_round(buffer, length, dist, delta, rest, ten_n); - - return; - } - - pow10 /= 10; - // - // pow10 = 10^(n-1) <= p1 < 10^n - // Invariants restored. - } - - // 2) - // - // The digits of the integral part have been generated: - // - // M+ = d[k-1]...d[1]d[0] + p2 * 2^e - // = buffer + p2 * 2^e - // - // Now generate the digits of the fractional part p2 * 2^e. - // - // Note: - // No decimal point is generated: the exponent is adjusted instead. - // - // p2 actually represents the fraction - // - // p2 * 2^e - // = p2 / 2^-e - // = d[-1] / 10^1 + d[-2] / 10^2 + ... - // - // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) - // - // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m - // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) - // - // using - // - // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) - // = ( d) * 2^-e + ( r) - // - // or - // 10^m * p2 * 2^e = d + r * 2^e - // - // i.e. - // - // M+ = buffer + p2 * 2^e - // = buffer + 10^-m * (d + r * 2^e) - // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e - // - // and stop as soon as 10^-m * r * 2^e <= delta * 2^e - - JSON_ASSERT(p2 > delta); - - int m = 0; - for (;;) - { - // Invariant: - // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e - // = buffer * 10^-m + 10^-m * (p2 ) * 2^e - // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e - // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e - // - JSON_ASSERT(p2 <= (std::numeric_limits::max)() / 10); - p2 *= 10; - const std::uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e - const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e - // - // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e - // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) - // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e - // - JSON_ASSERT(d <= 9); - buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d - // - // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e - // - p2 = r; - m++; - // - // M+ = buffer * 10^-m + 10^-m * p2 * 2^e - // Invariant restored. - - // Check if enough digits have been generated. - // - // 10^-m * p2 * 2^e <= delta * 2^e - // p2 * 2^e <= 10^m * delta * 2^e - // p2 <= 10^m * delta - delta *= 10; - dist *= 10; - if (p2 <= delta) - { - break; - } - } - - // V = buffer * 10^-m, with M- <= V <= M+. - - decimal_exponent -= m; - - // 1 ulp in the decimal representation is now 10^-m. - // Since delta and dist are now scaled by 10^m, we need to do the - // same with ulp in order to keep the units in sync. - // - // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e - // - const std::uint64_t ten_m = one.f; - grisu2_round(buffer, length, dist, delta, p2, ten_m); - - // By construction this algorithm generates the shortest possible decimal - // number (Loitsch, Theorem 6.2) which rounds back to w. - // For an input number of precision p, at least - // - // N = 1 + ceil(p * log_10(2)) - // - // decimal digits are sufficient to identify all binary floating-point - // numbers (Matula, "In-and-Out conversions"). - // This implies that the algorithm does not produce more than N decimal - // digits. - // - // N = 17 for p = 53 (IEEE double precision) - // N = 9 for p = 24 (IEEE single precision) -} - -/*! -v = buf * 10^decimal_exponent -len is the length of the buffer (number of decimal digits) -The buffer must be large enough, i.e. >= max_digits10. -*/ -JSON_HEDLEY_NON_NULL(1) -inline void grisu2(char* buf, int& len, int& decimal_exponent, - diyfp m_minus, diyfp v, diyfp m_plus) -{ - JSON_ASSERT(m_plus.e == m_minus.e); - JSON_ASSERT(m_plus.e == v.e); - - // --------(-----------------------+-----------------------)-------- (A) - // m- v m+ - // - // --------------------(-----------+-----------------------)-------- (B) - // m- v m+ - // - // First scale v (and m- and m+) such that the exponent is in the range - // [alpha, gamma]. - - const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); - - const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k - - // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] - const diyfp w = diyfp::mul(v, c_minus_k); - const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); - const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); - - // ----(---+---)---------------(---+---)---------------(---+---)---- - // w- w w+ - // = c*m- = c*v = c*m+ - // - // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and - // w+ are now off by a small amount. - // In fact: - // - // w - v * 10^k < 1 ulp - // - // To account for this inaccuracy, add resp. subtract 1 ulp. - // - // --------+---[---------------(---+---)---------------]---+-------- - // w- M- w M+ w+ - // - // Now any number in [M-, M+] (bounds included) will round to w when input, - // regardless of how the input rounding algorithm breaks ties. - // - // And digit_gen generates the shortest possible such number in [M-, M+]. - // Note that this does not mean that Grisu2 always generates the shortest - // possible number in the interval (m-, m+). - const diyfp M_minus(w_minus.f + 1, w_minus.e); - const diyfp M_plus (w_plus.f - 1, w_plus.e ); - - decimal_exponent = -cached.k; // = -(-k) = k - - grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); -} - -/*! -v = buf * 10^decimal_exponent -len is the length of the buffer (number of decimal digits) -The buffer must be large enough, i.e. >= max_digits10. -*/ -template -JSON_HEDLEY_NON_NULL(1) -void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) -{ - static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, - "internal error: not enough precision"); - - JSON_ASSERT(std::isfinite(value)); - JSON_ASSERT(value > 0); - - // If the neighbors (and boundaries) of 'value' are always computed for double-precision - // numbers, all float's can be recovered using strtod (and strtof). However, the resulting - // decimal representations are not exactly "short". - // - // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars) - // says "value is converted to a string as if by std::sprintf in the default ("C") locale" - // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' - // does. - // On the other hand, the documentation for 'std::to_chars' requires that "parsing the - // representation using the corresponding std::from_chars function recovers value exactly". That - // indicates that single precision floating-point numbers should be recovered using - // 'std::strtof'. - // - // NB: If the neighbors are computed for single-precision numbers, there is a single float - // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision - // value is off by 1 ulp. -#if 0 - const boundaries w = compute_boundaries(static_cast(value)); -#else - const boundaries w = compute_boundaries(value); -#endif - - grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); -} - -/*! -@brief appends a decimal representation of e to buf -@return a pointer to the element following the exponent. -@pre -1000 < e < 1000 -*/ -JSON_HEDLEY_NON_NULL(1) -JSON_HEDLEY_RETURNS_NON_NULL -inline char* append_exponent(char* buf, int e) -{ - JSON_ASSERT(e > -1000); - JSON_ASSERT(e < 1000); - - if (e < 0) - { - e = -e; - *buf++ = '-'; - } - else - { - *buf++ = '+'; - } - - auto k = static_cast(e); - if (k < 10) - { - // Always print at least two digits in the exponent. - // This is for compatibility with printf("%g"). - *buf++ = '0'; - *buf++ = static_cast('0' + k); - } - else if (k < 100) - { - *buf++ = static_cast('0' + k / 10); - k %= 10; - *buf++ = static_cast('0' + k); - } - else - { - *buf++ = static_cast('0' + k / 100); - k %= 100; - *buf++ = static_cast('0' + k / 10); - k %= 10; - *buf++ = static_cast('0' + k); - } - - return buf; -} - -/*! -@brief prettify v = buf * 10^decimal_exponent - -If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point -notation. Otherwise it will be printed in exponential notation. - -@pre min_exp < 0 -@pre max_exp > 0 -*/ -JSON_HEDLEY_NON_NULL(1) -JSON_HEDLEY_RETURNS_NON_NULL -inline char* format_buffer(char* buf, int len, int decimal_exponent, - int min_exp, int max_exp) -{ - JSON_ASSERT(min_exp < 0); - JSON_ASSERT(max_exp > 0); - - const int k = len; - const int n = len + decimal_exponent; - - // v = buf * 10^(n-k) - // k is the length of the buffer (number of decimal digits) - // n is the position of the decimal point relative to the start of the buffer. - - if (k <= n && n <= max_exp) - { - // digits[000] - // len <= max_exp + 2 - - std::memset(buf + k, '0', static_cast(n) - static_cast(k)); - // Make it look like a floating-point number (#362, #378) - buf[n + 0] = '.'; - buf[n + 1] = '0'; - return buf + (static_cast(n) + 2); - } - - if (0 < n && n <= max_exp) - { - // dig.its - // len <= max_digits10 + 1 - - JSON_ASSERT(k > n); - - std::memmove(buf + (static_cast(n) + 1), buf + n, static_cast(k) - static_cast(n)); - buf[n] = '.'; - return buf + (static_cast(k) + 1U); - } - - if (min_exp < n && n <= 0) - { - // 0.[000]digits - // len <= 2 + (-min_exp - 1) + max_digits10 - - std::memmove(buf + (2 + static_cast(-n)), buf, static_cast(k)); - buf[0] = '0'; - buf[1] = '.'; - std::memset(buf + 2, '0', static_cast(-n)); - return buf + (2U + static_cast(-n) + static_cast(k)); - } - - if (k == 1) - { - // dE+123 - // len <= 1 + 5 - - buf += 1; - } - else - { - // d.igitsE+123 - // len <= max_digits10 + 1 + 5 - - std::memmove(buf + 2, buf + 1, static_cast(k) - 1); - buf[1] = '.'; - buf += 1 + static_cast(k); - } - - *buf++ = 'e'; - return append_exponent(buf, n - 1); -} - -} // namespace dtoa_impl - -/*! -@brief generates a decimal representation of the floating-point number value in [first, last). - -The format of the resulting decimal representation is similar to printf's %g -format. Returns an iterator pointing past-the-end of the decimal representation. - -@note The input number must be finite, i.e. NaN's and Inf's are not supported. -@note The buffer must be large enough. -@note The result is NOT null-terminated. -*/ -template -JSON_HEDLEY_NON_NULL(1, 2) -JSON_HEDLEY_RETURNS_NON_NULL -char* to_chars(char* first, const char* last, FloatType value) -{ - static_cast(last); // maybe unused - fix warning - JSON_ASSERT(std::isfinite(value)); - - // Use signbit(value) instead of (value < 0) since signbit works for -0. - if (std::signbit(value)) - { - value = -value; - *first++ = '-'; - } - - if (value == 0) // +-0 - { - *first++ = '0'; - // Make it look like a floating-point number (#362, #378) - *first++ = '.'; - *first++ = '0'; - return first; - } - - JSON_ASSERT(last - first >= std::numeric_limits::max_digits10); - - // Compute v = buffer * 10^decimal_exponent. - // The decimal digits are stored in the buffer, which needs to be interpreted - // as an unsigned decimal integer. - // len is the length of the buffer, i.e. the number of decimal digits. - int len = 0; - int decimal_exponent = 0; - dtoa_impl::grisu2(first, len, decimal_exponent, value); - - JSON_ASSERT(len <= std::numeric_limits::max_digits10); - - // Format the buffer like printf("%.*g", prec, value) - constexpr int kMinExp = -4; - // Use digits10 here to increase compatibility with version 2. - constexpr int kMaxExp = std::numeric_limits::digits10; - - JSON_ASSERT(last - first >= kMaxExp + 2); - JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); - JSON_ASSERT(last - first >= std::numeric_limits::max_digits10 + 6); - - return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); -} - -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - -// #include - -// #include - -// #include - -// #include - - -namespace nlohmann -{ -namespace detail -{ -/////////////////// -// serialization // -/////////////////// - -/// how to treat decoding errors -enum class error_handler_t -{ - strict, ///< throw a type_error exception in case of invalid UTF-8 - replace, ///< replace invalid UTF-8 sequences with U+FFFD - ignore ///< ignore invalid UTF-8 sequences -}; - -template -class serializer -{ - using string_t = typename BasicJsonType::string_t; - using number_float_t = typename BasicJsonType::number_float_t; - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using binary_char_t = typename BasicJsonType::binary_t::value_type; - static constexpr std::uint8_t UTF8_ACCEPT = 0; - static constexpr std::uint8_t UTF8_REJECT = 1; - - public: - /*! - @param[in] s output stream to serialize to - @param[in] ichar indentation character to use - @param[in] error_handler_ how to react on decoding errors - */ - serializer(output_adapter_t s, const char ichar, - error_handler_t error_handler_ = error_handler_t::strict) - : o(std::move(s)) - , loc(std::localeconv()) - , thousands_sep(loc->thousands_sep == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->thousands_sep))) - , decimal_point(loc->decimal_point == nullptr ? '\0' : std::char_traits::to_char_type(* (loc->decimal_point))) - , indent_char(ichar) - , indent_string(512, indent_char) - , error_handler(error_handler_) - {} - - // delete because of pointer members - serializer(const serializer&) = delete; - serializer& operator=(const serializer&) = delete; - serializer(serializer&&) = delete; - serializer& operator=(serializer&&) = delete; - ~serializer() = default; - - /*! - @brief internal implementation of the serialization function - - This function is called by the public member function dump and organizes - the serialization internally. The indentation level is propagated as - additional parameter. In case of arrays and objects, the function is - called recursively. - - - strings and object keys are escaped using `escape_string()` - - integer numbers are converted implicitly via `operator<<` - - floating-point numbers are converted to a string using `"%g"` format - - binary values are serialized as objects containing the subtype and the - byte array - - @param[in] val value to serialize - @param[in] pretty_print whether the output shall be pretty-printed - @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters - in the output are escaped with `\uXXXX` sequences, and the result consists - of ASCII characters only. - @param[in] indent_step the indent level - @param[in] current_indent the current indent level (only used internally) - */ - void dump(const BasicJsonType& val, - const bool pretty_print, - const bool ensure_ascii, - const unsigned int indent_step, - const unsigned int current_indent = 0) - { - switch (val.m_type) - { - case value_t::object: - { - if (val.m_value.object->empty()) - { - o->write_characters("{}", 2); - return; - } - - if (pretty_print) - { - o->write_characters("{\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; - if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); - o->write_characters(",\n", 2); - } - - // last element - JSON_ASSERT(i != val.m_value.object->cend()); - JSON_ASSERT(std::next(i) == val.m_value.object->cend()); - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character('}'); - } - else - { - o->write_character('{'); - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); - o->write_character(','); - } - - // last element - JSON_ASSERT(i != val.m_value.object->cend()); - JSON_ASSERT(std::next(i) == val.m_value.object->cend()); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); - - o->write_character('}'); - } - - return; - } - - case value_t::array: - { - if (val.m_value.array->empty()) - { - o->write_characters("[]", 2); - return; - } - - if (pretty_print) - { - o->write_characters("[\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; - if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - dump(*i, true, ensure_ascii, indent_step, new_indent); - o->write_characters(",\n", 2); - } - - // last element - JSON_ASSERT(!val.m_value.array->empty()); - o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character(']'); - } - else - { - o->write_character('['); - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - dump(*i, false, ensure_ascii, indent_step, current_indent); - o->write_character(','); - } - - // last element - JSON_ASSERT(!val.m_value.array->empty()); - dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); - - o->write_character(']'); - } - - return; - } - - case value_t::string: - { - o->write_character('\"'); - dump_escaped(*val.m_value.string, ensure_ascii); - o->write_character('\"'); - return; - } - - case value_t::binary: - { - if (pretty_print) - { - o->write_characters("{\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; - if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - o->write_characters(indent_string.c_str(), new_indent); - - o->write_characters("\"bytes\": [", 10); - - if (!val.m_value.binary->empty()) - { - for (auto i = val.m_value.binary->cbegin(); - i != val.m_value.binary->cend() - 1; ++i) - { - dump_integer(*i); - o->write_characters(", ", 2); - } - dump_integer(val.m_value.binary->back()); - } - - o->write_characters("],\n", 3); - o->write_characters(indent_string.c_str(), new_indent); - - o->write_characters("\"subtype\": ", 11); - if (val.m_value.binary->has_subtype()) - { - dump_integer(val.m_value.binary->subtype()); - } - else - { - o->write_characters("null", 4); - } - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character('}'); - } - else - { - o->write_characters("{\"bytes\":[", 10); - - if (!val.m_value.binary->empty()) - { - for (auto i = val.m_value.binary->cbegin(); - i != val.m_value.binary->cend() - 1; ++i) - { - dump_integer(*i); - o->write_character(','); - } - dump_integer(val.m_value.binary->back()); - } - - o->write_characters("],\"subtype\":", 12); - if (val.m_value.binary->has_subtype()) - { - dump_integer(val.m_value.binary->subtype()); - o->write_character('}'); - } - else - { - o->write_characters("null}", 5); - } - } - return; - } - - case value_t::boolean: - { - if (val.m_value.boolean) - { - o->write_characters("true", 4); - } - else - { - o->write_characters("false", 5); - } - return; - } - - case value_t::number_integer: - { - dump_integer(val.m_value.number_integer); - return; - } - - case value_t::number_unsigned: - { - dump_integer(val.m_value.number_unsigned); - return; - } - - case value_t::number_float: - { - dump_float(val.m_value.number_float); - return; - } - - case value_t::discarded: - { - o->write_characters("", 11); - return; - } - - case value_t::null: - { - o->write_characters("null", 4); - return; - } - - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } - } - - private: - /*! - @brief dump escaped string - - Escape a string by replacing certain special characters by a sequence of an - escape character (backslash) and another character and other control - characters by a sequence of "\u" followed by a four-digit hex - representation. The escaped string is written to output stream @a o. - - @param[in] s the string to escape - @param[in] ensure_ascii whether to escape non-ASCII characters with - \uXXXX sequences - - @complexity Linear in the length of string @a s. - */ - void dump_escaped(const string_t& s, const bool ensure_ascii) - { - std::uint32_t codepoint; - std::uint8_t state = UTF8_ACCEPT; - std::size_t bytes = 0; // number of bytes written to string_buffer - - // number of bytes written at the point of the last valid byte - std::size_t bytes_after_last_accept = 0; - std::size_t undumped_chars = 0; - - for (std::size_t i = 0; i < s.size(); ++i) - { - const auto byte = static_cast(s[i]); - - switch (decode(state, codepoint, byte)) - { - case UTF8_ACCEPT: // decode found a new code point - { - switch (codepoint) - { - case 0x08: // backspace - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'b'; - break; - } - - case 0x09: // horizontal tab - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 't'; - break; - } - - case 0x0A: // newline - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'n'; - break; - } - - case 0x0C: // formfeed - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'f'; - break; - } - - case 0x0D: // carriage return - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'r'; - break; - } - - case 0x22: // quotation mark - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\"'; - break; - } - - case 0x5C: // reverse solidus - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = '\\'; - break; - } - - default: - { - // escape control characters (0x00..0x1F) or, if - // ensure_ascii parameter is used, non-ASCII characters - if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F))) - { - if (codepoint <= 0xFFFF) - { - (std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x", - static_cast(codepoint)); - bytes += 6; - } - else - { - (std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", - static_cast(0xD7C0u + (codepoint >> 10u)), - static_cast(0xDC00u + (codepoint & 0x3FFu))); - bytes += 12; - } - } - else - { - // copy byte to buffer (all previous bytes - // been copied have in default case above) - string_buffer[bytes++] = s[i]; - } - break; - } - } - - // write buffer and reset index; there must be 13 bytes - // left, as this is the maximal number of bytes to be - // written ("\uxxxx\uxxxx\0") for one code point - if (string_buffer.size() - bytes < 13) - { - o->write_characters(string_buffer.data(), bytes); - bytes = 0; - } - - // remember the byte position of this accept - bytes_after_last_accept = bytes; - undumped_chars = 0; - break; - } - - case UTF8_REJECT: // decode found invalid UTF-8 byte - { - switch (error_handler) - { - case error_handler_t::strict: - { - std::string sn(3, '\0'); - (std::snprintf)(&sn[0], sn.size(), "%.2X", byte); - JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn)); - } - - case error_handler_t::ignore: - case error_handler_t::replace: - { - // in case we saw this character the first time, we - // would like to read it again, because the byte - // may be OK for itself, but just not OK for the - // previous sequence - if (undumped_chars > 0) - { - --i; - } - - // reset length buffer to the last accepted index; - // thus removing/ignoring the invalid characters - bytes = bytes_after_last_accept; - - if (error_handler == error_handler_t::replace) - { - // add a replacement character - if (ensure_ascii) - { - string_buffer[bytes++] = '\\'; - string_buffer[bytes++] = 'u'; - string_buffer[bytes++] = 'f'; - string_buffer[bytes++] = 'f'; - string_buffer[bytes++] = 'f'; - string_buffer[bytes++] = 'd'; - } - else - { - string_buffer[bytes++] = detail::binary_writer::to_char_type('\xEF'); - string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBF'); - string_buffer[bytes++] = detail::binary_writer::to_char_type('\xBD'); - } - - // write buffer and reset index; there must be 13 bytes - // left, as this is the maximal number of bytes to be - // written ("\uxxxx\uxxxx\0") for one code point - if (string_buffer.size() - bytes < 13) - { - o->write_characters(string_buffer.data(), bytes); - bytes = 0; - } - - bytes_after_last_accept = bytes; - } - - undumped_chars = 0; - - // continue processing the string - state = UTF8_ACCEPT; - break; - } - - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } - break; - } - - default: // decode found yet incomplete multi-byte code point - { - if (!ensure_ascii) - { - // code point will not be escaped - copy byte to buffer - string_buffer[bytes++] = s[i]; - } - ++undumped_chars; - break; - } - } - } - - // we finished processing the string - if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT)) - { - // write buffer - if (bytes > 0) - { - o->write_characters(string_buffer.data(), bytes); - } - } - else - { - // we finish reading, but do not accept: string was incomplete - switch (error_handler) - { - case error_handler_t::strict: - { - std::string sn(3, '\0'); - (std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast(s.back())); - JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn)); - } - - case error_handler_t::ignore: - { - // write all accepted bytes - o->write_characters(string_buffer.data(), bytes_after_last_accept); - break; - } - - case error_handler_t::replace: - { - // write all accepted bytes - o->write_characters(string_buffer.data(), bytes_after_last_accept); - // add a replacement character - if (ensure_ascii) - { - o->write_characters("\\ufffd", 6); - } - else - { - o->write_characters("\xEF\xBF\xBD", 3); - } - break; - } - - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } - } - } - - /*! - @brief count digits - - Count the number of decimal (base 10) digits for an input unsigned integer. - - @param[in] x unsigned integer number to count its digits - @return number of decimal digits - */ - inline unsigned int count_digits(number_unsigned_t x) noexcept - { - unsigned int n_digits = 1; - for (;;) - { - if (x < 10) - { - return n_digits; - } - if (x < 100) - { - return n_digits + 1; - } - if (x < 1000) - { - return n_digits + 2; - } - if (x < 10000) - { - return n_digits + 3; - } - x = x / 10000u; - n_digits += 4; - } - } - - /*! - @brief dump an integer - - Dump a given integer to output stream @a o. Works internally with - @a number_buffer. - - @param[in] x integer number (signed or unsigned) to dump - @tparam NumberType either @a number_integer_t or @a number_unsigned_t - */ - template < typename NumberType, detail::enable_if_t < - std::is_same::value || - std::is_same::value || - std::is_same::value, - int > = 0 > - void dump_integer(NumberType x) - { - static constexpr std::array, 100> digits_to_99 - { - { - {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}}, - {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}}, - {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}}, - {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}}, - {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}}, - {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}}, - {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}}, - {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}}, - {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}}, - {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}}, - } - }; - - // special case for "0" - if (x == 0) - { - o->write_character('0'); - return; - } - - // use a pointer to fill the buffer - auto buffer_ptr = number_buffer.begin(); - - const bool is_negative = std::is_same::value && !(x >= 0); // see issue #755 - number_unsigned_t abs_value; - - unsigned int n_chars; - - if (is_negative) - { - *buffer_ptr = '-'; - abs_value = remove_sign(static_cast(x)); - - // account one more byte for the minus sign - n_chars = 1 + count_digits(abs_value); - } - else - { - abs_value = static_cast(x); - n_chars = count_digits(abs_value); - } - - // spare 1 byte for '\0' - JSON_ASSERT(n_chars < number_buffer.size() - 1); - - // jump to the end to generate the string from backward - // so we later avoid reversing the result - buffer_ptr += n_chars; - - // Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu - // See: https://www.youtube.com/watch?v=o4-CwDo2zpg - while (abs_value >= 100) - { - const auto digits_index = static_cast((abs_value % 100)); - abs_value /= 100; - *(--buffer_ptr) = digits_to_99[digits_index][1]; - *(--buffer_ptr) = digits_to_99[digits_index][0]; - } - - if (abs_value >= 10) - { - const auto digits_index = static_cast(abs_value); - *(--buffer_ptr) = digits_to_99[digits_index][1]; - *(--buffer_ptr) = digits_to_99[digits_index][0]; - } - else - { - *(--buffer_ptr) = static_cast('0' + abs_value); - } - - o->write_characters(number_buffer.data(), n_chars); - } - - /*! - @brief dump a floating-point number - - Dump a given floating-point number to output stream @a o. Works internally - with @a number_buffer. - - @param[in] x floating-point number to dump - */ - void dump_float(number_float_t x) - { - // NaN / inf - if (!std::isfinite(x)) - { - o->write_characters("null", 4); - return; - } - - // If number_float_t is an IEEE-754 single or double precision number, - // use the Grisu2 algorithm to produce short numbers which are - // guaranteed to round-trip, using strtof and strtod, resp. - // - // NB: The test below works if == . - static constexpr bool is_ieee_single_or_double - = (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 24 && std::numeric_limits::max_exponent == 128) || - (std::numeric_limits::is_iec559 && std::numeric_limits::digits == 53 && std::numeric_limits::max_exponent == 1024); - - dump_float(x, std::integral_constant()); - } - - void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) - { - char* begin = number_buffer.data(); - char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); - - o->write_characters(begin, static_cast(end - begin)); - } - - void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) - { - // get number of digits for a float -> text -> float round-trip - static constexpr auto d = std::numeric_limits::max_digits10; - - // the actual conversion - std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x); - - // negative value indicates an error - JSON_ASSERT(len > 0); - // check if buffer was large enough - JSON_ASSERT(static_cast(len) < number_buffer.size()); - - // erase thousands separator - if (thousands_sep != '\0') - { - const auto end = std::remove(number_buffer.begin(), - number_buffer.begin() + len, thousands_sep); - std::fill(end, number_buffer.end(), '\0'); - JSON_ASSERT((end - number_buffer.begin()) <= len); - len = (end - number_buffer.begin()); - } - - // convert decimal point to '.' - if (decimal_point != '\0' && decimal_point != '.') - { - const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); - if (dec_pos != number_buffer.end()) - { - *dec_pos = '.'; - } - } - - o->write_characters(number_buffer.data(), static_cast(len)); - - // determine if need to append ".0" - const bool value_is_int_like = - std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, - [](char c) - { - return c == '.' || c == 'e'; - }); - - if (value_is_int_like) - { - o->write_characters(".0", 2); - } - } - - /*! - @brief check whether a string is UTF-8 encoded - - The function checks each byte of a string whether it is UTF-8 encoded. The - result of the check is stored in the @a state parameter. The function must - be called initially with state 0 (accept). State 1 means the string must - be rejected, because the current byte is not allowed. If the string is - completely processed, but the state is non-zero, the string ended - prematurely; that is, the last byte indicated more bytes should have - followed. - - @param[in,out] state the state of the decoding - @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) - @param[in] byte next byte to decode - @return new state - - @note The function has been edited: a std::array is used. - - @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann - @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ - */ - static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept - { - static const std::array utf8d = - { - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF - 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF - 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF - 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF - 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 - 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 - 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 - 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 - } - }; - - const std::uint8_t type = utf8d[byte]; - - codep = (state != UTF8_ACCEPT) - ? (byte & 0x3fu) | (codep << 6u) - : (0xFFu >> type) & (byte); - - std::size_t index = 256u + static_cast(state) * 16u + static_cast(type); - JSON_ASSERT(index < 400); - state = utf8d[index]; - return state; - } - - /* - * Overload to make the compiler happy while it is instantiating - * dump_integer for number_unsigned_t. - * Must never be called. - */ - number_unsigned_t remove_sign(number_unsigned_t x) - { - JSON_ASSERT(false); // LCOV_EXCL_LINE - return x; // LCOV_EXCL_LINE - } - - /* - * Helper function for dump_integer - * - * This function takes a negative signed integer and returns its absolute - * value as unsigned integer. The plus/minus shuffling is necessary as we can - * not directly remove the sign of an arbitrary signed integer as the - * absolute values of INT_MIN and INT_MAX are usually not the same. See - * #1708 for details. - */ - inline number_unsigned_t remove_sign(number_integer_t x) noexcept - { - JSON_ASSERT(x < 0 && x < (std::numeric_limits::max)()); - return static_cast(-(x + 1)) + 1; - } - - private: - /// the output of the serializer - output_adapter_t o = nullptr; - - /// a (hopefully) large enough character buffer - std::array number_buffer{{}}; - - /// the locale - const std::lconv* loc = nullptr; - /// the locale's thousand separator character - const char thousands_sep = '\0'; - /// the locale's decimal point character - const char decimal_point = '\0'; - - /// string buffer - std::array string_buffer{{}}; - - /// the indentation character - const char indent_char; - /// the indentation string - string_t indent_string; - - /// error_handler how to react on decoding errors - const error_handler_t error_handler; -}; -} // namespace detail -} // namespace nlohmann - -// #include - -// #include - -// #include - - -#include // less -#include // allocator -#include // pair -#include // vector - -namespace nlohmann -{ - -/// ordered_map: a minimal map-like container that preserves insertion order -/// for use within nlohmann::basic_json -template , - class Allocator = std::allocator>> - struct ordered_map : std::vector, Allocator> -{ - using key_type = Key; - using mapped_type = T; - using Container = std::vector, Allocator>; - using typename Container::iterator; - using typename Container::const_iterator; - using typename Container::size_type; - using typename Container::value_type; - - // Explicit constructors instead of `using Container::Container` - // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4) - ordered_map(const Allocator& alloc = Allocator()) : Container{alloc} {} - template - ordered_map(It first, It last, const Allocator& alloc = Allocator()) - : Container{first, last, alloc} {} - ordered_map(std::initializer_list init, const Allocator& alloc = Allocator() ) - : Container{init, alloc} {} - - std::pair emplace(const key_type& key, T&& t) - { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (it->first == key) - { - return {it, false}; - } - } - Container::emplace_back(key, t); - return {--this->end(), true}; - } - - T& operator[](const Key& key) - { - return emplace(key, T{}).first->second; - } - - const T& operator[](const Key& key) const - { - return at(key); - } - - T& at(const Key& key) - { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (it->first == key) - { - return it->second; - } - } - - throw std::out_of_range("key not found"); - } - - const T& at(const Key& key) const - { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (it->first == key) - { - return it->second; - } - } - - throw std::out_of_range("key not found"); - } - - size_type erase(const Key& key) - { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (it->first == key) - { - // Since we cannot move const Keys, re-construct them in place - for (auto next = it; ++next != this->end(); ++it) - { - it->~value_type(); // Destroy but keep allocation - new (&*it) value_type{std::move(*next)}; - } - Container::pop_back(); - return 1; - } - } - return 0; - } - - iterator erase(iterator pos) - { - auto it = pos; - - // Since we cannot move const Keys, re-construct them in place - for (auto next = it; ++next != this->end(); ++it) - { - it->~value_type(); // Destroy but keep allocation - new (&*it) value_type{std::move(*next)}; - } - Container::pop_back(); - return pos; - } - - size_type count(const Key& key) const - { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (it->first == key) - { - return 1; - } - } - return 0; - } - - iterator find(const Key& key) - { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (it->first == key) - { - return it; - } - } - return Container::end(); - } - - const_iterator find(const Key& key) const - { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (it->first == key) - { - return it; - } - } - return Container::end(); - } - - std::pair insert( value_type&& value ) - { - return emplace(value.first, std::move(value.second)); - } - - std::pair insert( const value_type& value ) - { - for (auto it = this->begin(); it != this->end(); ++it) - { - if (it->first == value.first) - { - return {it, false}; - } - } - Container::push_back(value); - return {--this->end(), true}; - } -}; - -} // namespace nlohmann - - -/*! -@brief namespace for Niels Lohmann -@see https://github.com/nlohmann -@since version 1.0.0 -*/ -namespace nlohmann -{ - -/*! -@brief a class to store JSON values - -@tparam ObjectType type for JSON objects (`std::map` by default; will be used -in @ref object_t) -@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used -in @ref array_t) -@tparam StringType type for JSON strings and object keys (`std::string` by -default; will be used in @ref string_t) -@tparam BooleanType type for JSON booleans (`bool` by default; will be used -in @ref boolean_t) -@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by -default; will be used in @ref number_integer_t) -@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c -`uint64_t` by default; will be used in @ref number_unsigned_t) -@tparam NumberFloatType type for JSON floating-point numbers (`double` by -default; will be used in @ref number_float_t) -@tparam BinaryType type for packed binary data for compatibility with binary -serialization formats (`std::vector` by default; will be used in -@ref binary_t) -@tparam AllocatorType type of the allocator to use (`std::allocator` by -default) -@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` -and `from_json()` (@ref adl_serializer by default) - -@requirement The class satisfies the following concept requirements: -- Basic - - [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible): - JSON values can be default constructed. The result will be a JSON null - value. - - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible): - A JSON value can be constructed from an rvalue argument. - - [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible): - A JSON value can be copy-constructed from an lvalue expression. - - [MoveAssignable](https://en.cppreference.com/w/cpp/named_req/MoveAssignable): - A JSON value van be assigned from an rvalue argument. - - [CopyAssignable](https://en.cppreference.com/w/cpp/named_req/CopyAssignable): - A JSON value can be copy-assigned from an lvalue expression. - - [Destructible](https://en.cppreference.com/w/cpp/named_req/Destructible): - JSON values can be destructed. -- Layout - - [StandardLayoutType](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType): - JSON values have - [standard layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout): - All non-static data members are private and standard layout types, the - class has no virtual functions or (virtual) base classes. -- Library-wide - - [EqualityComparable](https://en.cppreference.com/w/cpp/named_req/EqualityComparable): - JSON values can be compared with `==`, see @ref - operator==(const_reference,const_reference). - - [LessThanComparable](https://en.cppreference.com/w/cpp/named_req/LessThanComparable): - JSON values can be compared with `<`, see @ref - operator<(const_reference,const_reference). - - [Swappable](https://en.cppreference.com/w/cpp/named_req/Swappable): - Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of - other compatible types, using unqualified function call @ref swap(). - - [NullablePointer](https://en.cppreference.com/w/cpp/named_req/NullablePointer): - JSON values can be compared against `std::nullptr_t` objects which are used - to model the `null` value. -- Container - - [Container](https://en.cppreference.com/w/cpp/named_req/Container): - JSON values can be used like STL containers and provide iterator access. - - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer); - JSON values can be used like STL containers and provide reverse iterator - access. - -@invariant The member variables @a m_value and @a m_type have the following -relationship: -- If `m_type == value_t::object`, then `m_value.object != nullptr`. -- If `m_type == value_t::array`, then `m_value.array != nullptr`. -- If `m_type == value_t::string`, then `m_value.string != nullptr`. -The invariants are checked by member function assert_invariant(). - -@internal -@note ObjectType trick from https://stackoverflow.com/a/9860911 -@endinternal - -@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange -Format](http://rfc7159.net/rfc7159) - -@since version 1.0.0 - -@nosubgrouping -*/ -NLOHMANN_BASIC_JSON_TPL_DECLARATION -class basic_json -{ - private: - template friend struct detail::external_constructor; - friend ::nlohmann::json_pointer; - - template - friend class ::nlohmann::detail::parser; - friend ::nlohmann::detail::serializer; - template - friend class ::nlohmann::detail::iter_impl; - template - friend class ::nlohmann::detail::binary_writer; - template - friend class ::nlohmann::detail::binary_reader; - template - friend class ::nlohmann::detail::json_sax_dom_parser; - template - friend class ::nlohmann::detail::json_sax_dom_callback_parser; - - /// workaround type for MSVC - using basic_json_t = NLOHMANN_BASIC_JSON_TPL; - - // convenience aliases for types residing in namespace detail; - using lexer = ::nlohmann::detail::lexer_base; - - template - static ::nlohmann::detail::parser parser( - InputAdapterType adapter, - detail::parser_callback_tcb = nullptr, - const bool allow_exceptions = true, - const bool ignore_comments = false - ) - { - return ::nlohmann::detail::parser(std::move(adapter), - std::move(cb), allow_exceptions, ignore_comments); - } - - using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; - template - using internal_iterator = ::nlohmann::detail::internal_iterator; - template - using iter_impl = ::nlohmann::detail::iter_impl; - template - using iteration_proxy = ::nlohmann::detail::iteration_proxy; - template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; - - template - using output_adapter_t = ::nlohmann::detail::output_adapter_t; - - template - using binary_reader = ::nlohmann::detail::binary_reader; - template using binary_writer = ::nlohmann::detail::binary_writer; - - using serializer = ::nlohmann::detail::serializer; - - public: - using value_t = detail::value_t; - /// JSON Pointer, see @ref nlohmann::json_pointer - using json_pointer = ::nlohmann::json_pointer; - template - using json_serializer = JSONSerializer; - /// how to treat decoding errors - using error_handler_t = detail::error_handler_t; - /// how to treat CBOR tags - using cbor_tag_handler_t = detail::cbor_tag_handler_t; - /// helper type for initializer lists of basic_json values - using initializer_list_t = std::initializer_list>; - - using input_format_t = detail::input_format_t; - /// SAX interface type, see @ref nlohmann::json_sax - using json_sax_t = json_sax; - - //////////////// - // exceptions // - //////////////// - - /// @name exceptions - /// Classes to implement user-defined exceptions. - /// @{ - - /// @copydoc detail::exception - using exception = detail::exception; - /// @copydoc detail::parse_error - using parse_error = detail::parse_error; - /// @copydoc detail::invalid_iterator - using invalid_iterator = detail::invalid_iterator; - /// @copydoc detail::type_error - using type_error = detail::type_error; - /// @copydoc detail::out_of_range - using out_of_range = detail::out_of_range; - /// @copydoc detail::other_error - using other_error = detail::other_error; - - /// @} - - - ///////////////////// - // container types // - ///////////////////// - - /// @name container types - /// The canonic container types to use @ref basic_json like any other STL - /// container. - /// @{ - - /// the type of elements in a basic_json container - using value_type = basic_json; - - /// the type of an element reference - using reference = value_type&; - /// the type of an element const reference - using const_reference = const value_type&; - - /// a type to represent differences between iterators - using difference_type = std::ptrdiff_t; - /// a type to represent container sizes - using size_type = std::size_t; - - /// the allocator type - using allocator_type = AllocatorType; - - /// the type of an element pointer - using pointer = typename std::allocator_traits::pointer; - /// the type of an element const pointer - using const_pointer = typename std::allocator_traits::const_pointer; - - /// an iterator for a basic_json container - using iterator = iter_impl; - /// a const iterator for a basic_json container - using const_iterator = iter_impl; - /// a reverse iterator for a basic_json container - using reverse_iterator = json_reverse_iterator; - /// a const reverse iterator for a basic_json container - using const_reverse_iterator = json_reverse_iterator; - - /// @} - - - /*! - @brief returns the allocator associated with the container - */ - static allocator_type get_allocator() - { - return allocator_type(); - } - - /*! - @brief returns version information on the library - - This function returns a JSON object with information about the library, - including the version number and information on the platform and compiler. - - @return JSON object holding version information - key | description - ----------- | --------------- - `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). - `copyright` | The copyright line for the library as string. - `name` | The name of the library as string. - `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. - `url` | The URL of the project as string. - `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). - - @liveexample{The following code shows an example output of the `meta()` - function.,meta} - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @complexity Constant. - - @since 2.1.0 - */ - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json meta() - { - basic_json result; - - result["copyright"] = "(C) 2013-2020 Niels Lohmann"; - result["name"] = "JSON for Modern C++"; - result["url"] = "https://github.com/nlohmann/json"; - result["version"]["string"] = - std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + - std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + - std::to_string(NLOHMANN_JSON_VERSION_PATCH); - result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; - result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; - result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; - -#ifdef _WIN32 - result["platform"] = "win32"; -#elif defined __linux__ - result["platform"] = "linux"; -#elif defined __APPLE__ - result["platform"] = "apple"; -#elif defined __unix__ - result["platform"] = "unix"; -#else - result["platform"] = "unknown"; -#endif - -#if defined(__ICC) || defined(__INTEL_COMPILER) - result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; -#elif defined(__clang__) - result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; -#elif defined(__GNUC__) || defined(__GNUG__) - result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; -#elif defined(__HP_cc) || defined(__HP_aCC) - result["compiler"] = "hp" -#elif defined(__IBMCPP__) - result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; -#elif defined(_MSC_VER) - result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; -#elif defined(__PGI) - result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; -#elif defined(__SUNPRO_CC) - result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; -#else - result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; -#endif - -#ifdef __cplusplus - result["compiler"]["c++"] = std::to_string(__cplusplus); -#else - result["compiler"]["c++"] = "unknown"; -#endif - return result; - } - - - /////////////////////////// - // JSON value data types // - /////////////////////////// - - /// @name JSON value data types - /// The data types to store a JSON value. These types are derived from - /// the template arguments passed to class @ref basic_json. - /// @{ - -#if defined(JSON_HAS_CPP_14) - // Use transparent comparator if possible, combined with perfect forwarding - // on find() and count() calls prevents unnecessary string construction. - using object_comparator_t = std::less<>; -#else - using object_comparator_t = std::less; -#endif - - /*! - @brief a type for an object - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: - > An object is an unordered collection of zero or more name/value pairs, - > where a name is a string and a value is a string, number, boolean, null, - > object, or array. - - To store objects in C++, a type is defined by the template parameters - described below. - - @tparam ObjectType the container to store objects (e.g., `std::map` or - `std::unordered_map`) - @tparam StringType the type of the keys or names (e.g., `std::string`). - The comparison function `std::less` is used to order elements - inside the container. - @tparam AllocatorType the allocator to use for objects (e.g., - `std::allocator`) - - #### Default type - - With the default values for @a ObjectType (`std::map`), @a StringType - (`std::string`), and @a AllocatorType (`std::allocator`), the default - value for @a object_t is: - - @code {.cpp} - std::map< - std::string, // key_type - basic_json, // value_type - std::less, // key_compare - std::allocator> // allocator_type - > - @endcode - - #### Behavior - - The choice of @a object_t influences the behavior of the JSON class. With - the default type, objects have the following behavior: - - - When all names are unique, objects will be interoperable in the sense - that all software implementations receiving that object will agree on - the name-value mappings. - - When the names within an object are not unique, it is unspecified which - one of the values for a given key will be chosen. For instance, - `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or - `{"key": 2}`. - - Internally, name/value pairs are stored in lexicographical order of the - names. Objects will also be serialized (see @ref dump) in this order. - For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored - and serialized as `{"a": 2, "b": 1}`. - - When comparing objects, the order of the name/value pairs is irrelevant. - This makes objects interoperable in the sense that they will not be - affected by these differences. For instance, `{"b": 1, "a": 2}` and - `{"a": 2, "b": 1}` will be treated as equal. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the maximum depth of nesting. - - In this class, the object's limit of nesting is not explicitly constrained. - However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the - @ref max_size function of a JSON object. - - #### Storage - - Objects are stored as pointers in a @ref basic_json type. That is, for any - access to object values, a pointer of type `object_t*` must be - dereferenced. - - @sa @ref array_t -- type for an array value - - @since version 1.0.0 - - @note The order name/value pairs are added to the object is *not* - preserved by the library. Therefore, iterating an object may return - name/value pairs in a different order than they were originally stored. In - fact, keys will be traversed in alphabetical order as `std::map` with - `std::less` is used by default. Please note this behavior conforms to [RFC - 7159](http://rfc7159.net/rfc7159), because any order implements the - specified "unordered" nature of JSON objects. - */ - using object_t = ObjectType>>; - - /*! - @brief a type for an array - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: - > An array is an ordered sequence of zero or more values. - - To store objects in C++, a type is defined by the template parameters - explained below. - - @tparam ArrayType container type to store arrays (e.g., `std::vector` or - `std::list`) - @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) - - #### Default type - - With the default values for @a ArrayType (`std::vector`) and @a - AllocatorType (`std::allocator`), the default value for @a array_t is: - - @code {.cpp} - std::vector< - basic_json, // value_type - std::allocator // allocator_type - > - @endcode - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the maximum depth of nesting. - - In this class, the array's limit of nesting is not explicitly constrained. - However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the - @ref max_size function of a JSON array. - - #### Storage - - Arrays are stored as pointers in a @ref basic_json type. That is, for any - access to array values, a pointer of type `array_t*` must be dereferenced. - - @sa @ref object_t -- type for an object value - - @since version 1.0.0 - */ - using array_t = ArrayType>; - - /*! - @brief a type for a string - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: - > A string is a sequence of zero or more Unicode characters. - - To store objects in C++, a type is defined by the template parameter - described below. Unicode values are split by the JSON class into - byte-sized characters during deserialization. - - @tparam StringType the container to store strings (e.g., `std::string`). - Note this container is used for keys/names in objects, see @ref object_t. - - #### Default type - - With the default values for @a StringType (`std::string`), the default - value for @a string_t is: - - @code {.cpp} - std::string - @endcode - - #### Encoding - - Strings are stored in UTF-8 encoding. Therefore, functions like - `std::string::size()` or `std::string::length()` return the number of - bytes in the string rather than the number of characters or glyphs. - - #### String comparison - - [RFC 7159](http://rfc7159.net/rfc7159) states: - > Software implementations are typically required to test names of object - > members for equality. Implementations that transform the textual - > representation into sequences of Unicode code units and then perform the - > comparison numerically, code unit by code unit, are interoperable in the - > sense that implementations will agree in all cases on equality or - > inequality of two strings. For example, implementations that compare - > strings with escaped characters unconverted may incorrectly find that - > `"a\\b"` and `"a\u005Cb"` are not equal. - - This implementation is interoperable as it does compare strings code unit - by code unit. - - #### Storage - - String values are stored as pointers in a @ref basic_json type. That is, - for any access to string values, a pointer of type `string_t*` must be - dereferenced. - - @since version 1.0.0 - */ - using string_t = StringType; - - /*! - @brief a type for a boolean - - [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a - type which differentiates the two literals `true` and `false`. - - To store objects in C++, a type is defined by the template parameter @a - BooleanType which chooses the type to use. - - #### Default type - - With the default values for @a BooleanType (`bool`), the default value for - @a boolean_t is: - - @code {.cpp} - bool - @endcode - - #### Storage - - Boolean values are stored directly inside a @ref basic_json type. - - @since version 1.0.0 - */ - using boolean_t = BooleanType; - - /*! - @brief a type for a number (integer) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store integer numbers in C++, a type is defined by the template - parameter @a NumberIntegerType which chooses the type to use. - - #### Default type - - With the default values for @a NumberIntegerType (`int64_t`), the default - value for @a number_integer_t is: - - @code {.cpp} - int64_t - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. - - When the default type is used, the maximal integer number that can be - stored is `9223372036854775807` (INT64_MAX) and the minimal integer number - that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers - that are out of range will yield over/underflow when used in a - constructor. During deserialization, too large or small integer numbers - will be automatically be stored as @ref number_unsigned_t or @ref - number_float_t. - - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. - - As this range is a subrange of the exactly supported range [INT64_MIN, - INT64_MAX], this class's integer type is interoperable. - - #### Storage - - Integer number values are stored directly inside a @ref basic_json type. - - @sa @ref number_float_t -- type for number values (floating-point) - - @sa @ref number_unsigned_t -- type for number values (unsigned integer) - - @since version 1.0.0 - */ - using number_integer_t = NumberIntegerType; - - /*! - @brief a type for a number (unsigned) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store unsigned integer numbers in C++, a type is defined by the - template parameter @a NumberUnsignedType which chooses the type to use. - - #### Default type - - With the default values for @a NumberUnsignedType (`uint64_t`), the - default value for @a number_unsigned_t is: - - @code {.cpp} - uint64_t - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. - - When the default type is used, the maximal integer number that can be - stored is `18446744073709551615` (UINT64_MAX) and the minimal integer - number that can be stored is `0`. Integer numbers that are out of range - will yield over/underflow when used in a constructor. During - deserialization, too large or small integer numbers will be automatically - be stored as @ref number_integer_t or @ref number_float_t. - - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. - - As this range is a subrange (when considered in conjunction with the - number_integer_t type) of the exactly supported range [0, UINT64_MAX], - this class's integer type is interoperable. - - #### Storage - - Integer number values are stored directly inside a @ref basic_json type. - - @sa @ref number_float_t -- type for number values (floating-point) - @sa @ref number_integer_t -- type for number values (integer) - - @since version 2.0.0 - */ - using number_unsigned_t = NumberUnsignedType; - - /*! - @brief a type for a number (floating-point) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store floating-point numbers in C++, a type is defined by the template - parameter @a NumberFloatType which chooses the type to use. - - #### Default type - - With the default values for @a NumberFloatType (`double`), the default - value for @a number_float_t is: - - @code {.cpp} - double - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in floating-point literals will be ignored. Internally, - the value will be stored as decimal number. For instance, the C++ - floating-point literal `01.2` will be serialized to `1.2`. During - deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) states: - > This specification allows implementations to set limits on the range and - > precision of numbers accepted. Since software that implements IEEE - > 754-2008 binary64 (double precision) numbers is generally available and - > widely used, good interoperability can be achieved by implementations - > that expect no more precision or range than these provide, in the sense - > that implementations will approximate JSON numbers within the expected - > precision. - - This implementation does exactly follow this approach, as it uses double - precision floating-point numbers. Note values smaller than - `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` - will be stored as NaN internally and be serialized to `null`. - - #### Storage - - Floating-point number values are stored directly inside a @ref basic_json - type. - - @sa @ref number_integer_t -- type for number values (integer) - - @sa @ref number_unsigned_t -- type for number values (unsigned integer) - - @since version 1.0.0 - */ - using number_float_t = NumberFloatType; - - /*! - @brief a type for a packed binary type - - This type is a type designed to carry binary data that appears in various - serialized formats, such as CBOR's Major Type 2, MessagePack's bin, and - BSON's generic binary subtype. This type is NOT a part of standard JSON and - exists solely for compatibility with these binary types. As such, it is - simply defined as an ordered sequence of zero or more byte values. - - Additionally, as an implementation detail, the subtype of the binary data is - carried around as a `std::uint8_t`, which is compatible with both of the - binary data formats that use binary subtyping, (though the specific - numbering is incompatible with each other, and it is up to the user to - translate between them). - - [CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type - as: - > Major type 2: a byte string. The string's length in bytes is represented - > following the rules for positive integers (major type 0). - - [MessagePack's documentation on the bin type - family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) - describes this type as: - > Bin format family stores an byte array in 2, 3, or 5 bytes of extra bytes - > in addition to the size of the byte array. - - [BSON's specifications](http://bsonspec.org/spec.html) describe several - binary types; however, this type is intended to represent the generic binary - type which has the description: - > Generic binary subtype - This is the most commonly used binary subtype and - > should be the 'default' for drivers and tools. - - None of these impose any limitations on the internal representation other - than the basic unit of storage be some type of array whose parts are - decomposable into bytes. - - The default representation of this binary format is a - `std::vector`, which is a very common way to represent a byte - array in modern C++. - - #### Default type - - The default values for @a BinaryType is `std::vector` - - #### Storage - - Binary Arrays are stored as pointers in a @ref basic_json type. That is, - for any access to array values, a pointer of the type `binary_t*` must be - dereferenced. - - #### Notes on subtypes - - - CBOR - - Binary values are represented as byte strings. No subtypes are - supported and will be ignored when CBOR is written. - - MessagePack - - If a subtype is given and the binary array contains exactly 1, 2, 4, 8, - or 16 elements, the fixext family (fixext1, fixext2, fixext4, fixext8) - is used. For other sizes, the ext family (ext8, ext16, ext32) is used. - The subtype is then added as singed 8-bit integer. - - If no subtype is given, the bin family (bin8, bin16, bin32) is used. - - BSON - - If a subtype is given, it is used and added as unsigned 8-bit integer. - - If no subtype is given, the generic binary subtype 0x00 is used. - - @sa @ref binary -- create a binary array - - @since version 3.8.0 - */ - using binary_t = nlohmann::byte_container_with_subtype; - /// @} - - private: - - /// helper for exception-safe object creation - template - JSON_HEDLEY_RETURNS_NON_NULL - static T* create(Args&& ... args) - { - AllocatorType alloc; - using AllocatorTraits = std::allocator_traits>; - - auto deleter = [&](T * object) - { - AllocatorTraits::deallocate(alloc, object, 1); - }; - std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); - AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); - JSON_ASSERT(object != nullptr); - return object.release(); - } - - //////////////////////// - // JSON value storage // - //////////////////////// - - /*! - @brief a JSON value - - The actual storage for a JSON value of the @ref basic_json class. This - union combines the different storage types for the JSON value types - defined in @ref value_t. - - JSON type | value_t type | used type - --------- | --------------- | ------------------------ - object | object | pointer to @ref object_t - array | array | pointer to @ref array_t - string | string | pointer to @ref string_t - boolean | boolean | @ref boolean_t - number | number_integer | @ref number_integer_t - number | number_unsigned | @ref number_unsigned_t - number | number_float | @ref number_float_t - binary | binary | pointer to @ref binary_t - null | null | *no value is stored* - - @note Variable-length types (objects, arrays, and strings) are stored as - pointers. The size of the union should not exceed 64 bits if the default - value types are used. - - @since version 1.0.0 - */ - union json_value - { - /// object (stored with pointer to save storage) - object_t* object; - /// array (stored with pointer to save storage) - array_t* array; - /// string (stored with pointer to save storage) - string_t* string; - /// binary (stored with pointer to save storage) - binary_t* binary; - /// boolean - boolean_t boolean; - /// number (integer) - number_integer_t number_integer; - /// number (unsigned integer) - number_unsigned_t number_unsigned; - /// number (floating-point) - number_float_t number_float; - - /// default constructor (for null values) - json_value() = default; - /// constructor for booleans - json_value(boolean_t v) noexcept : boolean(v) {} - /// constructor for numbers (integer) - json_value(number_integer_t v) noexcept : number_integer(v) {} - /// constructor for numbers (unsigned) - json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} - /// constructor for numbers (floating-point) - json_value(number_float_t v) noexcept : number_float(v) {} - /// constructor for empty values of a given type - json_value(value_t t) - { - switch (t) - { - case value_t::object: - { - object = create(); - break; - } - - case value_t::array: - { - array = create(); - break; - } - - case value_t::string: - { - string = create(""); - break; - } - - case value_t::binary: - { - binary = create(); - break; - } - - case value_t::boolean: - { - boolean = boolean_t(false); - break; - } - - case value_t::number_integer: - { - number_integer = number_integer_t(0); - break; - } - - case value_t::number_unsigned: - { - number_unsigned = number_unsigned_t(0); - break; - } - - case value_t::number_float: - { - number_float = number_float_t(0.0); - break; - } - - case value_t::null: - { - object = nullptr; // silence warning, see #821 - break; - } - - default: - { - object = nullptr; // silence warning, see #821 - if (JSON_HEDLEY_UNLIKELY(t == value_t::null)) - { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.9.1")); // LCOV_EXCL_LINE - } - break; - } - } - } - - /// constructor for strings - json_value(const string_t& value) - { - string = create(value); - } - - /// constructor for rvalue strings - json_value(string_t&& value) - { - string = create(std::move(value)); - } - - /// constructor for objects - json_value(const object_t& value) - { - object = create(value); - } - - /// constructor for rvalue objects - json_value(object_t&& value) - { - object = create(std::move(value)); - } - - /// constructor for arrays - json_value(const array_t& value) - { - array = create(value); - } - - /// constructor for rvalue arrays - json_value(array_t&& value) - { - array = create(std::move(value)); - } - - /// constructor for binary arrays - json_value(const typename binary_t::container_type& value) - { - binary = create(value); - } - - /// constructor for rvalue binary arrays - json_value(typename binary_t::container_type&& value) - { - binary = create(std::move(value)); - } - - /// constructor for binary arrays (internal type) - json_value(const binary_t& value) - { - binary = create(value); - } - - /// constructor for rvalue binary arrays (internal type) - json_value(binary_t&& value) - { - binary = create(std::move(value)); - } - - void destroy(value_t t) noexcept - { - // flatten the current json_value to a heap-allocated stack - std::vector stack; - - // move the top-level items to stack - if (t == value_t::array) - { - stack.reserve(array->size()); - std::move(array->begin(), array->end(), std::back_inserter(stack)); - } - else if (t == value_t::object) - { - stack.reserve(object->size()); - for (auto&& it : *object) - { - stack.push_back(std::move(it.second)); - } - } - - while (!stack.empty()) - { - // move the last item to local variable to be processed - basic_json current_item(std::move(stack.back())); - stack.pop_back(); - - // if current_item is array/object, move - // its children to the stack to be processed later - if (current_item.is_array()) - { - std::move(current_item.m_value.array->begin(), current_item.m_value.array->end(), - std::back_inserter(stack)); - - current_item.m_value.array->clear(); - } - else if (current_item.is_object()) - { - for (auto&& it : *current_item.m_value.object) - { - stack.push_back(std::move(it.second)); - } - - current_item.m_value.object->clear(); - } - - // it's now safe that current_item get destructed - // since it doesn't have any children - } - - switch (t) - { - case value_t::object: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, object); - std::allocator_traits::deallocate(alloc, object, 1); - break; - } - - case value_t::array: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, array); - std::allocator_traits::deallocate(alloc, array, 1); - break; - } - - case value_t::string: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, string); - std::allocator_traits::deallocate(alloc, string, 1); - break; - } - - case value_t::binary: - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, binary); - std::allocator_traits::deallocate(alloc, binary, 1); - break; - } - - default: - { - break; - } - } - } - }; - - /*! - @brief checks the class invariants - - This function asserts the class invariants. It needs to be called at the - end of every constructor to make sure that created objects respect the - invariant. Furthermore, it has to be called each time the type of a JSON - value is changed, because the invariant expresses a relationship between - @a m_type and @a m_value. - */ - void assert_invariant() const noexcept - { - JSON_ASSERT(m_type != value_t::object || m_value.object != nullptr); - JSON_ASSERT(m_type != value_t::array || m_value.array != nullptr); - JSON_ASSERT(m_type != value_t::string || m_value.string != nullptr); - JSON_ASSERT(m_type != value_t::binary || m_value.binary != nullptr); - } - - public: - ////////////////////////// - // JSON parser callback // - ////////////////////////// - - /*! - @brief parser event types - - The parser callback distinguishes the following events: - - `object_start`: the parser read `{` and started to process a JSON object - - `key`: the parser read a key of a value in an object - - `object_end`: the parser read `}` and finished processing a JSON object - - `array_start`: the parser read `[` and started to process a JSON array - - `array_end`: the parser read `]` and finished processing a JSON array - - `value`: the parser finished reading a JSON value - - @image html callback_events.png "Example when certain parse events are triggered" - - @sa @ref parser_callback_t for more information and examples - */ - using parse_event_t = detail::parse_event_t; - - /*! - @brief per-element parser callback type - - With a parser callback function, the result of parsing a JSON text can be - influenced. When passed to @ref parse, it is called on certain events - (passed as @ref parse_event_t via parameter @a event) with a set recursion - depth @a depth and context JSON value @a parsed. The return value of the - callback function is a boolean indicating whether the element that emitted - the callback shall be kept or not. - - We distinguish six scenarios (determined by the event type) in which the - callback function can be called. The following table describes the values - of the parameters @a depth, @a event, and @a parsed. - - parameter @a event | description | parameter @a depth | parameter @a parsed - ------------------ | ----------- | ------------------ | ------------------- - parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded - parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key - parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object - parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded - parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array - parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value - - @image html callback_events.png "Example when certain parse events are triggered" - - Discarding a value (i.e., returning `false`) has different effects - depending on the context in which function was called: - - - Discarded values in structured types are skipped. That is, the parser - will behave as if the discarded value was never read. - - In case a value outside a structured type is skipped, it is replaced - with `null`. This case happens if the top-level element is skipped. - - @param[in] depth the depth of the recursion during parsing - - @param[in] event an event of type parse_event_t indicating the context in - the callback function has been called - - @param[in,out] parsed the current intermediate parse result; note that - writing to this value has no effect for parse_event_t::key events - - @return Whether the JSON value which called the function during parsing - should be kept (`true`) or not (`false`). In the latter case, it is either - skipped completely or replaced by an empty discarded object. - - @sa @ref parse for examples - - @since version 1.0.0 - */ - using parser_callback_t = detail::parser_callback_t; - - ////////////////// - // constructors // - ////////////////// - - /// @name constructors and destructors - /// Constructors of class @ref basic_json, copy/move constructor, copy - /// assignment, static functions creating objects, and the destructor. - /// @{ - - /*! - @brief create an empty value with a given type - - Create an empty JSON value with a given type. The value will be default - initialized with an empty value which depends on the type: - - Value type | initial value - ----------- | ------------- - null | `null` - boolean | `false` - string | `""` - number | `0` - object | `{}` - array | `[]` - binary | empty array - - @param[in] v the type of the value to create - - @complexity Constant. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The following code shows the constructor for different @ref - value_t values,basic_json__value_t} - - @sa @ref clear() -- restores the postcondition of this constructor - - @since version 1.0.0 - */ - basic_json(const value_t v) - : m_type(v), m_value(v) - { - assert_invariant(); - } - - /*! - @brief create a null object - - Create a `null` JSON value. It either takes a null pointer as parameter - (explicitly creating `null`) or no parameter (implicitly creating `null`). - The passed null pointer itself is not read -- it is only used to choose - the right constructor. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this constructor never throws - exceptions. - - @liveexample{The following code shows the constructor with and without a - null pointer parameter.,basic_json__nullptr_t} - - @since version 1.0.0 - */ - basic_json(std::nullptr_t = nullptr) noexcept - : basic_json(value_t::null) - { - assert_invariant(); - } - - /*! - @brief create a JSON value - - This is a "catch all" constructor for all compatible JSON types; that is, - types for which a `to_json()` method exists. The constructor forwards the - parameter @a val to that method (to `json_serializer::to_json` method - with `U = uncvref_t`, to be exact). - - Template type @a CompatibleType includes, but is not limited to, the - following types: - - **arrays**: @ref array_t and all kinds of compatible containers such as - `std::vector`, `std::deque`, `std::list`, `std::forward_list`, - `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, - `std::multiset`, and `std::unordered_multiset` with a `value_type` from - which a @ref basic_json value can be constructed. - - **objects**: @ref object_t and all kinds of compatible associative - containers such as `std::map`, `std::unordered_map`, `std::multimap`, - and `std::unordered_multimap` with a `key_type` compatible to - @ref string_t and a `value_type` from which a @ref basic_json value can - be constructed. - - **strings**: @ref string_t, string literals, and all compatible string - containers can be used. - - **numbers**: @ref number_integer_t, @ref number_unsigned_t, - @ref number_float_t, and all convertible number types such as `int`, - `size_t`, `int64_t`, `float` or `double` can be used. - - **boolean**: @ref boolean_t / `bool` can be used. - - **binary**: @ref binary_t / `std::vector` may be used, - unfortunately because string literals cannot be distinguished from binary - character arrays by the C++ type system, all types compatible with `const - char*` will be directed to the string constructor instead. This is both - for backwards compatibility, and due to the fact that a binary type is not - a standard JSON type. - - See the examples below. - - @tparam CompatibleType a type such that: - - @a CompatibleType is not derived from `std::istream`, - - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move - constructors), - - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) - - @a CompatibleType is not a @ref basic_json nested type (e.g., - @ref json_pointer, @ref iterator, etc ...) - - @ref @ref json_serializer has a - `to_json(basic_json_t&, CompatibleType&&)` method - - @tparam U = `uncvref_t` - - @param[in] val the value to be forwarded to the respective constructor - - @complexity Usually linear in the size of the passed @a val, also - depending on the implementation of the called `to_json()` - method. - - @exceptionsafety Depends on the called constructor. For types directly - supported by the library (i.e., all types for which no `to_json()` function - was provided), strong guarantee holds: if an exception is thrown, there are - no changes to any JSON value. - - @liveexample{The following code shows the constructor with several - compatible types.,basic_json__CompatibleType} - - @since version 2.1.0 - */ - template < typename CompatibleType, - typename U = detail::uncvref_t, - detail::enable_if_t < - !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > - basic_json(CompatibleType && val) noexcept(noexcept( - JSONSerializer::to_json(std::declval(), - std::forward(val)))) - { - JSONSerializer::to_json(*this, std::forward(val)); - assert_invariant(); - } - - /*! - @brief create a JSON value from an existing one - - This is a constructor for existing @ref basic_json types. - It does not hijack copy/move constructors, since the parameter has different - template arguments than the current ones. - - The constructor tries to convert the internal @ref m_value of the parameter. - - @tparam BasicJsonType a type such that: - - @a BasicJsonType is a @ref basic_json type. - - @a BasicJsonType has different template arguments than @ref basic_json_t. - - @param[in] val the @ref basic_json value to be converted. - - @complexity Usually linear in the size of the passed @a val, also - depending on the implementation of the called `to_json()` - method. - - @exceptionsafety Depends on the called constructor. For types directly - supported by the library (i.e., all types for which no `to_json()` function - was provided), strong guarantee holds: if an exception is thrown, there are - no changes to any JSON value. - - @since version 3.2.0 - */ - template < typename BasicJsonType, - detail::enable_if_t < - detail::is_basic_json::value&& !std::is_same::value, int > = 0 > - basic_json(const BasicJsonType& val) - { - using other_boolean_t = typename BasicJsonType::boolean_t; - using other_number_float_t = typename BasicJsonType::number_float_t; - using other_number_integer_t = typename BasicJsonType::number_integer_t; - using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using other_string_t = typename BasicJsonType::string_t; - using other_object_t = typename BasicJsonType::object_t; - using other_array_t = typename BasicJsonType::array_t; - using other_binary_t = typename BasicJsonType::binary_t; - - switch (val.type()) - { - case value_t::boolean: - JSONSerializer::to_json(*this, val.template get()); - break; - case value_t::number_float: - JSONSerializer::to_json(*this, val.template get()); - break; - case value_t::number_integer: - JSONSerializer::to_json(*this, val.template get()); - break; - case value_t::number_unsigned: - JSONSerializer::to_json(*this, val.template get()); - break; - case value_t::string: - JSONSerializer::to_json(*this, val.template get_ref()); - break; - case value_t::object: - JSONSerializer::to_json(*this, val.template get_ref()); - break; - case value_t::array: - JSONSerializer::to_json(*this, val.template get_ref()); - break; - case value_t::binary: - JSONSerializer::to_json(*this, val.template get_ref()); - break; - case value_t::null: - *this = nullptr; - break; - case value_t::discarded: - m_type = value_t::discarded; - break; - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } - assert_invariant(); - } - - /*! - @brief create a container (array or object) from an initializer list - - Creates a JSON value of type array or object from the passed initializer - list @a init. In case @a type_deduction is `true` (default), the type of - the JSON value to be created is deducted from the initializer list @a init - according to the following rules: - - 1. If the list is empty, an empty JSON object value `{}` is created. - 2. If the list consists of pairs whose first element is a string, a JSON - object value is created where the first elements of the pairs are - treated as keys and the second elements are as values. - 3. In all other cases, an array is created. - - The rules aim to create the best fit between a C++ initializer list and - JSON values. The rationale is as follows: - - 1. The empty initializer list is written as `{}` which is exactly an empty - JSON object. - 2. C++ has no way of describing mapped types other than to list a list of - pairs. As JSON requires that keys must be of type string, rule 2 is the - weakest constraint one can pose on initializer lists to interpret them - as an object. - 3. In all other cases, the initializer list could not be interpreted as - JSON object type, so interpreting it as JSON array type is safe. - - With the rules described above, the following JSON values cannot be - expressed by an initializer list: - - - the empty array (`[]`): use @ref array(initializer_list_t) - with an empty initializer list in this case - - arrays whose elements satisfy rule 2: use @ref - array(initializer_list_t) with the same initializer list - in this case - - @note When used without parentheses around an empty initializer list, @ref - basic_json() is called instead of this function, yielding the JSON null - value. - - @param[in] init initializer list with JSON values - - @param[in] type_deduction internal parameter; when set to `true`, the type - of the JSON value is deducted from the initializer list @a init; when set - to `false`, the type provided via @a manual_type is forced. This mode is - used by the functions @ref array(initializer_list_t) and - @ref object(initializer_list_t). - - @param[in] manual_type internal parameter; when @a type_deduction is set - to `false`, the created JSON value will use the provided type (only @ref - value_t::array and @ref value_t::object are valid); when @a type_deduction - is set to `true`, this parameter has no effect - - @throw type_error.301 if @a type_deduction is `false`, @a manual_type is - `value_t::object`, but @a init contains an element which is not a pair - whose first element is a string. In this case, the constructor could not - create an object. If @a type_deduction would have be `true`, an array - would have been created. See @ref object(initializer_list_t) - for an example. - - @complexity Linear in the size of the initializer list @a init. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The example below shows how JSON values are created from - initializer lists.,basic_json__list_init_t} - - @sa @ref array(initializer_list_t) -- create a JSON array - value from an initializer list - @sa @ref object(initializer_list_t) -- create a JSON object - value from an initializer list - - @since version 1.0.0 - */ - basic_json(initializer_list_t init, - bool type_deduction = true, - value_t manual_type = value_t::array) - { - // check if each element is an array with two elements whose first - // element is a string - bool is_an_object = std::all_of(init.begin(), init.end(), - [](const detail::json_ref& element_ref) - { - return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[0].is_string(); - }); - - // adjust type if type deduction is not wanted - if (!type_deduction) - { - // if array is wanted, do not create an object though possible - if (manual_type == value_t::array) - { - is_an_object = false; - } - - // if object is wanted but impossible, throw an exception - if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object)) - { - JSON_THROW(type_error::create(301, "cannot create object from initializer list")); - } - } - - if (is_an_object) - { - // the initializer list is a list of pairs -> create object - m_type = value_t::object; - m_value = value_t::object; - - std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) - { - auto element = element_ref.moved_or_copied(); - m_value.object->emplace( - std::move(*((*element.m_value.array)[0].m_value.string)), - std::move((*element.m_value.array)[1])); - }); - } - else - { - // the initializer list describes an array -> create array - m_type = value_t::array; - m_value.array = create(init.begin(), init.end()); - } - - assert_invariant(); - } - - /*! - @brief explicitly create a binary array (without subtype) - - Creates a JSON binary array value from a given binary container. Binary - values are part of various binary formats, such as CBOR, MessagePack, and - BSON. This constructor is used to create a value for serialization to those - formats. - - @note Note, this function exists because of the difficulty in correctly - specifying the correct template overload in the standard value ctor, as both - JSON arrays and JSON binary arrays are backed with some form of a - `std::vector`. Because JSON binary arrays are a non-standard extension it - was decided that it would be best to prevent automatic initialization of a - binary array type, for backwards compatibility and so it does not happen on - accident. - - @param[in] init container containing bytes to use as binary type - - @return JSON binary array value - - @complexity Linear in the size of @a init. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @since version 3.8.0 - */ - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json binary(const typename binary_t::container_type& init) - { - auto res = basic_json(); - res.m_type = value_t::binary; - res.m_value = init; - return res; - } - - /*! - @brief explicitly create a binary array (with subtype) - - Creates a JSON binary array value from a given binary container. Binary - values are part of various binary formats, such as CBOR, MessagePack, and - BSON. This constructor is used to create a value for serialization to those - formats. - - @note Note, this function exists because of the difficulty in correctly - specifying the correct template overload in the standard value ctor, as both - JSON arrays and JSON binary arrays are backed with some form of a - `std::vector`. Because JSON binary arrays are a non-standard extension it - was decided that it would be best to prevent automatic initialization of a - binary array type, for backwards compatibility and so it does not happen on - accident. - - @param[in] init container containing bytes to use as binary type - @param[in] subtype subtype to use in MessagePack and BSON - - @return JSON binary array value - - @complexity Linear in the size of @a init. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @since version 3.8.0 - */ - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json binary(const typename binary_t::container_type& init, std::uint8_t subtype) - { - auto res = basic_json(); - res.m_type = value_t::binary; - res.m_value = binary_t(init, subtype); - return res; - } - - /// @copydoc binary(const typename binary_t::container_type&) - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json binary(typename binary_t::container_type&& init) - { - auto res = basic_json(); - res.m_type = value_t::binary; - res.m_value = std::move(init); - return res; - } - - /// @copydoc binary(const typename binary_t::container_type&, std::uint8_t) - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json binary(typename binary_t::container_type&& init, std::uint8_t subtype) - { - auto res = basic_json(); - res.m_type = value_t::binary; - res.m_value = binary_t(std::move(init), subtype); - return res; - } - - /*! - @brief explicitly create an array from an initializer list - - Creates a JSON array value from a given initializer list. That is, given a - list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the - initializer list is empty, the empty array `[]` is created. - - @note This function is only needed to express two edge cases that cannot - be realized with the initializer list constructor (@ref - basic_json(initializer_list_t, bool, value_t)). These cases - are: - 1. creating an array whose elements are all pairs whose first element is a - string -- in this case, the initializer list constructor would create an - object, taking the first elements as keys - 2. creating an empty array -- passing the empty initializer list to the - initializer list constructor yields an empty object - - @param[in] init initializer list with JSON values to create an array from - (optional) - - @return JSON array value - - @complexity Linear in the size of @a init. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The following code shows an example for the `array` - function.,array} - - @sa @ref basic_json(initializer_list_t, bool, value_t) -- - create a JSON value from an initializer list - @sa @ref object(initializer_list_t) -- create a JSON object - value from an initializer list - - @since version 1.0.0 - */ - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json array(initializer_list_t init = {}) - { - return basic_json(init, false, value_t::array); - } - - /*! - @brief explicitly create an object from an initializer list - - Creates a JSON object value from a given initializer list. The initializer - lists elements must be pairs, and their first elements must be strings. If - the initializer list is empty, the empty object `{}` is created. - - @note This function is only added for symmetry reasons. In contrast to the - related function @ref array(initializer_list_t), there are - no cases which can only be expressed by this function. That is, any - initializer list @a init can also be passed to the initializer list - constructor @ref basic_json(initializer_list_t, bool, value_t). - - @param[in] init initializer list to create an object from (optional) - - @return JSON object value - - @throw type_error.301 if @a init is not a list of pairs whose first - elements are strings. In this case, no object can be created. When such a - value is passed to @ref basic_json(initializer_list_t, bool, value_t), - an array would have been created from the passed initializer list @a init. - See example below. - - @complexity Linear in the size of @a init. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The following code shows an example for the `object` - function.,object} - - @sa @ref basic_json(initializer_list_t, bool, value_t) -- - create a JSON value from an initializer list - @sa @ref array(initializer_list_t) -- create a JSON array - value from an initializer list - - @since version 1.0.0 - */ - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json object(initializer_list_t init = {}) - { - return basic_json(init, false, value_t::object); - } - - /*! - @brief construct an array with count copies of given value - - Constructs a JSON array value by creating @a cnt copies of a passed value. - In case @a cnt is `0`, an empty array is created. - - @param[in] cnt the number of JSON copies of @a val to create - @param[in] val the JSON value to copy - - @post `std::distance(begin(),end()) == cnt` holds. - - @complexity Linear in @a cnt. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The following code shows examples for the @ref - basic_json(size_type\, const basic_json&) - constructor.,basic_json__size_type_basic_json} - - @since version 1.0.0 - */ - basic_json(size_type cnt, const basic_json& val) - : m_type(value_t::array) - { - m_value.array = create(cnt, val); - assert_invariant(); - } - - /*! - @brief construct a JSON container given an iterator range - - Constructs the JSON value with the contents of the range `[first, last)`. - The semantics depends on the different types a JSON value can have: - - In case of a null type, invalid_iterator.206 is thrown. - - In case of other primitive types (number, boolean, or string), @a first - must be `begin()` and @a last must be `end()`. In this case, the value is - copied. Otherwise, invalid_iterator.204 is thrown. - - In case of structured types (array, object), the constructor behaves as - similar versions for `std::vector` or `std::map`; that is, a JSON array - or object is constructed from the values in the range. - - @tparam InputIT an input iterator type (@ref iterator or @ref - const_iterator) - - @param[in] first begin of the range to copy from (included) - @param[in] last end of the range to copy from (excluded) - - @pre Iterators @a first and @a last must be initialized. **This - precondition is enforced with an assertion (see warning).** If - assertions are switched off, a violation of this precondition yields - undefined behavior. - - @pre Range `[first, last)` is valid. Usually, this precondition cannot be - checked efficiently. Only certain edge cases are detected; see the - description of the exceptions below. A violation of this precondition - yields undefined behavior. - - @warning A precondition is enforced with a runtime assertion that will - result in calling `std::abort` if this precondition is not met. - Assertions can be disabled by defining `NDEBUG` at compile time. - See https://en.cppreference.com/w/cpp/error/assert for more - information. - - @throw invalid_iterator.201 if iterators @a first and @a last are not - compatible (i.e., do not belong to the same JSON value). In this case, - the range `[first, last)` is undefined. - @throw invalid_iterator.204 if iterators @a first and @a last belong to a - primitive type (number, boolean, or string), but @a first does not point - to the first element any more. In this case, the range `[first, last)` is - undefined. See example code below. - @throw invalid_iterator.206 if iterators @a first and @a last belong to a - null value. In this case, the range `[first, last)` is undefined. - - @complexity Linear in distance between @a first and @a last. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The example below shows several ways to create JSON values by - specifying a subrange with iterators.,basic_json__InputIt_InputIt} - - @since version 1.0.0 - */ - template < class InputIT, typename std::enable_if < - std::is_same::value || - std::is_same::value, int >::type = 0 > - basic_json(InputIT first, InputIT last) - { - JSON_ASSERT(first.m_object != nullptr); - JSON_ASSERT(last.m_object != nullptr); - - // make sure iterator fits the current value - if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) - { - JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); - } - - // copy type from first iterator - m_type = first.m_object->m_type; - - // check if iterator range is complete for primitive values - switch (m_type) - { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: - { - if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin() - || !last.m_it.primitive_iterator.is_end())) - { - JSON_THROW(invalid_iterator::create(204, "iterators out of range")); - } - break; - } - - default: - break; - } - - switch (m_type) - { - case value_t::number_integer: - { - m_value.number_integer = first.m_object->m_value.number_integer; - break; - } - - case value_t::number_unsigned: - { - m_value.number_unsigned = first.m_object->m_value.number_unsigned; - break; - } - - case value_t::number_float: - { - m_value.number_float = first.m_object->m_value.number_float; - break; - } - - case value_t::boolean: - { - m_value.boolean = first.m_object->m_value.boolean; - break; - } - - case value_t::string: - { - m_value = *first.m_object->m_value.string; - break; - } - - case value_t::object: - { - m_value.object = create(first.m_it.object_iterator, - last.m_it.object_iterator); - break; - } - - case value_t::array: - { - m_value.array = create(first.m_it.array_iterator, - last.m_it.array_iterator); - break; - } - - case value_t::binary: - { - m_value = *first.m_object->m_value.binary; - break; - } - - default: - JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + - std::string(first.m_object->type_name()))); - } - - assert_invariant(); - } - - - /////////////////////////////////////// - // other constructors and destructor // - /////////////////////////////////////// - - template, - std::is_same>::value, int> = 0 > - basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {} - - /*! - @brief copy constructor - - Creates a copy of a given JSON value. - - @param[in] other the JSON value to copy - - @post `*this == other` - - @complexity Linear in the size of @a other. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is linear. - - As postcondition, it holds: `other == basic_json(other)`. - - @liveexample{The following code shows an example for the copy - constructor.,basic_json__basic_json} - - @since version 1.0.0 - */ - basic_json(const basic_json& other) - : m_type(other.m_type) - { - // check of passed value is valid - other.assert_invariant(); - - switch (m_type) - { - case value_t::object: - { - m_value = *other.m_value.object; - break; - } - - case value_t::array: - { - m_value = *other.m_value.array; - break; - } - - case value_t::string: - { - m_value = *other.m_value.string; - break; - } - - case value_t::boolean: - { - m_value = other.m_value.boolean; - break; - } - - case value_t::number_integer: - { - m_value = other.m_value.number_integer; - break; - } - - case value_t::number_unsigned: - { - m_value = other.m_value.number_unsigned; - break; - } - - case value_t::number_float: - { - m_value = other.m_value.number_float; - break; - } - - case value_t::binary: - { - m_value = *other.m_value.binary; - break; - } - - default: - break; - } - - assert_invariant(); - } - - /*! - @brief move constructor - - Move constructor. Constructs a JSON value with the contents of the given - value @a other using move semantics. It "steals" the resources from @a - other and leaves it as JSON null value. - - @param[in,out] other value to move to this object - - @post `*this` has the same value as @a other before the call. - @post @a other is a JSON null value. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this constructor never throws - exceptions. - - @requirement This function helps `basic_json` satisfying the - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible) - requirements. - - @liveexample{The code below shows the move constructor explicitly called - via std::move.,basic_json__moveconstructor} - - @since version 1.0.0 - */ - basic_json(basic_json&& other) noexcept - : m_type(std::move(other.m_type)), - m_value(std::move(other.m_value)) - { - // check that passed value is valid - other.assert_invariant(); - - // invalidate payload - other.m_type = value_t::null; - other.m_value = {}; - - assert_invariant(); - } - - /*! - @brief copy assignment - - Copy assignment operator. Copies a JSON value via the "copy and swap" - strategy: It is expressed in terms of the copy constructor, destructor, - and the `swap()` member function. - - @param[in] other value to copy from - - @complexity Linear. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is linear. - - @liveexample{The code below shows and example for the copy assignment. It - creates a copy of value `a` which is then swapped with `b`. Finally\, the - copy of `a` (which is the null value after the swap) is - destroyed.,basic_json__copyassignment} - - @since version 1.0.0 - */ - basic_json& operator=(basic_json other) noexcept ( - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value&& - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value - ) - { - // check that passed value is valid - other.assert_invariant(); - - using std::swap; - swap(m_type, other.m_type); - swap(m_value, other.m_value); - - assert_invariant(); - return *this; - } - - /*! - @brief destructor - - Destroys the JSON value and frees all allocated memory. - - @complexity Linear. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is linear. - - All stored elements are destroyed and all memory is freed. - - @since version 1.0.0 - */ - ~basic_json() noexcept - { - assert_invariant(); - m_value.destroy(m_type); - } - - /// @} - - public: - /////////////////////// - // object inspection // - /////////////////////// - - /// @name object inspection - /// Functions to inspect the type of a JSON value. - /// @{ - - /*! - @brief serialization - - Serialization function for JSON values. The function tries to mimic - Python's `json.dumps()` function, and currently supports its @a indent - and @a ensure_ascii parameters. - - @param[in] indent If indent is nonnegative, then array elements and object - members will be pretty-printed with that indent level. An indent level of - `0` will only insert newlines. `-1` (the default) selects the most compact - representation. - @param[in] indent_char The character to use for indentation if @a indent is - greater than `0`. The default is ` ` (space). - @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters - in the output are escaped with `\uXXXX` sequences, and the result consists - of ASCII characters only. - @param[in] error_handler how to react on decoding errors; there are three - possible values: `strict` (throws and exception in case a decoding error - occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD), - and `ignore` (ignore invalid UTF-8 sequences during serialization; all - bytes are copied to the output unchanged). - - @return string containing the serialization of the JSON value - - @throw type_error.316 if a string stored inside the JSON value is not - UTF-8 encoded and @a error_handler is set to strict - - @note Binary values are serialized as object containing two keys: - - "bytes": an array of bytes as integers - - "subtype": the subtype as integer or "null" if the binary has no subtype - - @complexity Linear. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @liveexample{The following example shows the effect of different @a indent\, - @a indent_char\, and @a ensure_ascii parameters to the result of the - serialization.,dump} - - @see https://docs.python.org/2/library/json.html#json.dump - - @since version 1.0.0; indentation character @a indent_char, option - @a ensure_ascii and exceptions added in version 3.0.0; error - handlers added in version 3.4.0; serialization of binary values added - in version 3.8.0. - */ - string_t dump(const int indent = -1, - const char indent_char = ' ', - const bool ensure_ascii = false, - const error_handler_t error_handler = error_handler_t::strict) const - { - string_t result; - serializer s(detail::output_adapter(result), indent_char, error_handler); - - if (indent >= 0) - { - s.dump(*this, true, ensure_ascii, static_cast(indent)); - } - else - { - s.dump(*this, false, ensure_ascii, 0); - } - - return result; - } - - /*! - @brief return the type of the JSON value (explicit) - - Return the type of the JSON value as a value from the @ref value_t - enumeration. - - @return the type of the JSON value - Value type | return value - ------------------------- | ------------------------- - null | value_t::null - boolean | value_t::boolean - string | value_t::string - number (integer) | value_t::number_integer - number (unsigned integer) | value_t::number_unsigned - number (floating-point) | value_t::number_float - object | value_t::object - array | value_t::array - binary | value_t::binary - discarded | value_t::discarded - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `type()` for all JSON - types.,type} - - @sa @ref operator value_t() -- return the type of the JSON value (implicit) - @sa @ref type_name() -- return the type as string - - @since version 1.0.0 - */ - constexpr value_t type() const noexcept - { - return m_type; - } - - /*! - @brief return whether type is primitive - - This function returns true if and only if the JSON type is primitive - (string, number, boolean, or null). - - @return `true` if type is primitive (string, number, boolean, or null), - `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_primitive()` for all JSON - types.,is_primitive} - - @sa @ref is_structured() -- returns whether JSON value is structured - @sa @ref is_null() -- returns whether JSON value is `null` - @sa @ref is_string() -- returns whether JSON value is a string - @sa @ref is_boolean() -- returns whether JSON value is a boolean - @sa @ref is_number() -- returns whether JSON value is a number - @sa @ref is_binary() -- returns whether JSON value is a binary array - - @since version 1.0.0 - */ - constexpr bool is_primitive() const noexcept - { - return is_null() || is_string() || is_boolean() || is_number() || is_binary(); - } - - /*! - @brief return whether type is structured - - This function returns true if and only if the JSON type is structured - (array or object). - - @return `true` if type is structured (array or object), `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_structured()` for all JSON - types.,is_structured} - - @sa @ref is_primitive() -- returns whether value is primitive - @sa @ref is_array() -- returns whether value is an array - @sa @ref is_object() -- returns whether value is an object - - @since version 1.0.0 - */ - constexpr bool is_structured() const noexcept - { - return is_array() || is_object(); - } - - /*! - @brief return whether value is null - - This function returns true if and only if the JSON value is null. - - @return `true` if type is null, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_null()` for all JSON - types.,is_null} - - @since version 1.0.0 - */ - constexpr bool is_null() const noexcept - { - return m_type == value_t::null; - } - - /*! - @brief return whether value is a boolean - - This function returns true if and only if the JSON value is a boolean. - - @return `true` if type is boolean, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_boolean()` for all JSON - types.,is_boolean} - - @since version 1.0.0 - */ - constexpr bool is_boolean() const noexcept - { - return m_type == value_t::boolean; - } - - /*! - @brief return whether value is a number - - This function returns true if and only if the JSON value is a number. This - includes both integer (signed and unsigned) and floating-point values. - - @return `true` if type is number (regardless whether integer, unsigned - integer or floating-type), `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number()` for all JSON - types.,is_number} - - @sa @ref is_number_integer() -- check if value is an integer or unsigned - integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer - number - @sa @ref is_number_float() -- check if value is a floating-point number - - @since version 1.0.0 - */ - constexpr bool is_number() const noexcept - { - return is_number_integer() || is_number_float(); - } - - /*! - @brief return whether value is an integer number - - This function returns true if and only if the JSON value is a signed or - unsigned integer number. This excludes floating-point values. - - @return `true` if type is an integer or unsigned integer number, `false` - otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number_integer()` for all - JSON types.,is_number_integer} - - @sa @ref is_number() -- check if value is a number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer - number - @sa @ref is_number_float() -- check if value is a floating-point number - - @since version 1.0.0 - */ - constexpr bool is_number_integer() const noexcept - { - return m_type == value_t::number_integer || m_type == value_t::number_unsigned; - } - - /*! - @brief return whether value is an unsigned integer number - - This function returns true if and only if the JSON value is an unsigned - integer number. This excludes floating-point and signed integer values. - - @return `true` if type is an unsigned integer number, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number_unsigned()` for all - JSON types.,is_number_unsigned} - - @sa @ref is_number() -- check if value is a number - @sa @ref is_number_integer() -- check if value is an integer or unsigned - integer number - @sa @ref is_number_float() -- check if value is a floating-point number - - @since version 2.0.0 - */ - constexpr bool is_number_unsigned() const noexcept - { - return m_type == value_t::number_unsigned; - } - - /*! - @brief return whether value is a floating-point number - - This function returns true if and only if the JSON value is a - floating-point number. This excludes signed and unsigned integer values. - - @return `true` if type is a floating-point number, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number_float()` for all - JSON types.,is_number_float} - - @sa @ref is_number() -- check if value is number - @sa @ref is_number_integer() -- check if value is an integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer - number - - @since version 1.0.0 - */ - constexpr bool is_number_float() const noexcept - { - return m_type == value_t::number_float; - } - - /*! - @brief return whether value is an object - - This function returns true if and only if the JSON value is an object. - - @return `true` if type is object, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_object()` for all JSON - types.,is_object} - - @since version 1.0.0 - */ - constexpr bool is_object() const noexcept - { - return m_type == value_t::object; - } - - /*! - @brief return whether value is an array - - This function returns true if and only if the JSON value is an array. - - @return `true` if type is array, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_array()` for all JSON - types.,is_array} - - @since version 1.0.0 - */ - constexpr bool is_array() const noexcept - { - return m_type == value_t::array; - } - - /*! - @brief return whether value is a string - - This function returns true if and only if the JSON value is a string. - - @return `true` if type is string, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_string()` for all JSON - types.,is_string} - - @since version 1.0.0 - */ - constexpr bool is_string() const noexcept - { - return m_type == value_t::string; - } - - /*! - @brief return whether value is a binary array - - This function returns true if and only if the JSON value is a binary array. - - @return `true` if type is binary array, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_binary()` for all JSON - types.,is_binary} - - @since version 3.8.0 - */ - constexpr bool is_binary() const noexcept - { - return m_type == value_t::binary; - } - - /*! - @brief return whether value is discarded - - This function returns true if and only if the JSON value was discarded - during parsing with a callback function (see @ref parser_callback_t). - - @note This function will always be `false` for JSON values after parsing. - That is, discarded values can only occur during parsing, but will be - removed when inside a structured value or replaced by null in other cases. - - @return `true` if type is discarded, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_discarded()` for all JSON - types.,is_discarded} - - @since version 1.0.0 - */ - constexpr bool is_discarded() const noexcept - { - return m_type == value_t::discarded; - } - - /*! - @brief return the type of the JSON value (implicit) - - Implicitly return the type of the JSON value as a value from the @ref - value_t enumeration. - - @return the type of the JSON value - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies the @ref value_t operator for - all JSON types.,operator__value_t} - - @sa @ref type() -- return the type of the JSON value (explicit) - @sa @ref type_name() -- return the type as string - - @since version 1.0.0 - */ - constexpr operator value_t() const noexcept - { - return m_type; - } - - /// @} - - private: - ////////////////// - // value access // - ////////////////// - - /// get a boolean (explicit) - boolean_t get_impl(boolean_t* /*unused*/) const - { - if (JSON_HEDLEY_LIKELY(is_boolean())) - { - return m_value.boolean; - } - - JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); - } - - /// get a pointer to the value (object) - object_t* get_impl_ptr(object_t* /*unused*/) noexcept - { - return is_object() ? m_value.object : nullptr; - } - - /// get a pointer to the value (object) - constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept - { - return is_object() ? m_value.object : nullptr; - } - - /// get a pointer to the value (array) - array_t* get_impl_ptr(array_t* /*unused*/) noexcept - { - return is_array() ? m_value.array : nullptr; - } - - /// get a pointer to the value (array) - constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept - { - return is_array() ? m_value.array : nullptr; - } - - /// get a pointer to the value (string) - string_t* get_impl_ptr(string_t* /*unused*/) noexcept - { - return is_string() ? m_value.string : nullptr; - } - - /// get a pointer to the value (string) - constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept - { - return is_string() ? m_value.string : nullptr; - } - - /// get a pointer to the value (boolean) - boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept - { - return is_boolean() ? &m_value.boolean : nullptr; - } - - /// get a pointer to the value (boolean) - constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept - { - return is_boolean() ? &m_value.boolean : nullptr; - } - - /// get a pointer to the value (integer number) - number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept - { - return is_number_integer() ? &m_value.number_integer : nullptr; - } - - /// get a pointer to the value (integer number) - constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept - { - return is_number_integer() ? &m_value.number_integer : nullptr; - } - - /// get a pointer to the value (unsigned number) - number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept - { - return is_number_unsigned() ? &m_value.number_unsigned : nullptr; - } - - /// get a pointer to the value (unsigned number) - constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept - { - return is_number_unsigned() ? &m_value.number_unsigned : nullptr; - } - - /// get a pointer to the value (floating-point number) - number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept - { - return is_number_float() ? &m_value.number_float : nullptr; - } - - /// get a pointer to the value (floating-point number) - constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept - { - return is_number_float() ? &m_value.number_float : nullptr; - } - - /// get a pointer to the value (binary) - binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept - { - return is_binary() ? m_value.binary : nullptr; - } - - /// get a pointer to the value (binary) - constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept - { - return is_binary() ? m_value.binary : nullptr; - } - - /*! - @brief helper function to implement get_ref() - - This function helps to implement get_ref() without code duplication for - const and non-const overloads - - @tparam ThisType will be deduced as `basic_json` or `const basic_json` - - @throw type_error.303 if ReferenceType does not match underlying value - type of the current JSON - */ - template - static ReferenceType get_ref_impl(ThisType& obj) - { - // delegate the call to get_ptr<>() - auto ptr = obj.template get_ptr::type>(); - - if (JSON_HEDLEY_LIKELY(ptr != nullptr)) - { - return *ptr; - } - - JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); - } - - public: - /// @name value access - /// Direct access to the stored value of a JSON value. - /// @{ - - /*! - @brief get special-case overload - - This overloads avoids a lot of template boilerplate, it can be seen as the - identity method - - @tparam BasicJsonType == @ref basic_json - - @return a copy of *this - - @complexity Constant. - - @since version 2.1.0 - */ - template::type, basic_json_t>::value, - int> = 0> - basic_json get() const - { - return *this; - } - - /*! - @brief get special-case overload - - This overloads converts the current @ref basic_json in a different - @ref basic_json type - - @tparam BasicJsonType == @ref basic_json - - @return a copy of *this, converted into @tparam BasicJsonType - - @complexity Depending on the implementation of the called `from_json()` - method. - - @since version 3.2.0 - */ - template < typename BasicJsonType, detail::enable_if_t < - !std::is_same::value&& - detail::is_basic_json::value, int > = 0 > - BasicJsonType get() const - { - return *this; - } - - /*! - @brief get a value (explicit) - - Explicit type conversion between the JSON value and a compatible value - which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) - and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). - The value is converted by calling the @ref json_serializer - `from_json()` method. - - The function is equivalent to executing - @code {.cpp} - ValueType ret; - JSONSerializer::from_json(*this, ret); - return ret; - @endcode - - This overloads is chosen if: - - @a ValueType is not @ref basic_json, - - @ref json_serializer has a `from_json()` method of the form - `void from_json(const basic_json&, ValueType&)`, and - - @ref json_serializer does not have a `from_json()` method of - the form `ValueType from_json(const basic_json&)` - - @tparam ValueTypeCV the provided value type - @tparam ValueType the returned value type - - @return copy of the JSON value, converted to @a ValueType - - @throw what @ref json_serializer `from_json()` method throws - - @liveexample{The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,get__ValueType_const} - - @since version 2.1.0 - */ - template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, - detail::enable_if_t < - !detail::is_basic_json::value && - detail::has_from_json::value && - !detail::has_non_default_from_json::value, - int > = 0 > - ValueType get() const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), std::declval()))) - { - // we cannot static_assert on ValueTypeCV being non-const, because - // there is support for get(), which is why we - // still need the uncvref - static_assert(!std::is_reference::value, - "get() cannot be used with reference types, you might want to use get_ref()"); - static_assert(std::is_default_constructible::value, - "types must be DefaultConstructible when used with get()"); - - ValueType ret; - JSONSerializer::from_json(*this, ret); - return ret; - } - - /*! - @brief get a value (explicit); special case - - Explicit type conversion between the JSON value and a compatible value - which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible) - and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). - The value is converted by calling the @ref json_serializer - `from_json()` method. - - The function is equivalent to executing - @code {.cpp} - return JSONSerializer::from_json(*this); - @endcode - - This overloads is chosen if: - - @a ValueType is not @ref basic_json and - - @ref json_serializer has a `from_json()` method of the form - `ValueType from_json(const basic_json&)` - - @note If @ref json_serializer has both overloads of - `from_json()`, this one is chosen. - - @tparam ValueTypeCV the provided value type - @tparam ValueType the returned value type - - @return copy of the JSON value, converted to @a ValueType - - @throw what @ref json_serializer `from_json()` method throws - - @since version 2.1.0 - */ - template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, - detail::enable_if_t < !std::is_same::value && - detail::has_non_default_from_json::value, - int > = 0 > - ValueType get() const noexcept(noexcept( - JSONSerializer::from_json(std::declval()))) - { - static_assert(!std::is_reference::value, - "get() cannot be used with reference types, you might want to use get_ref()"); - return JSONSerializer::from_json(*this); - } - - /*! - @brief get a value (explicit) - - Explicit type conversion between the JSON value and a compatible value. - The value is filled into the input parameter by calling the @ref json_serializer - `from_json()` method. - - The function is equivalent to executing - @code {.cpp} - ValueType v; - JSONSerializer::from_json(*this, v); - @endcode - - This overloads is chosen if: - - @a ValueType is not @ref basic_json, - - @ref json_serializer has a `from_json()` method of the form - `void from_json(const basic_json&, ValueType&)`, and - - @tparam ValueType the input parameter type. - - @return the input parameter, allowing chaining calls. - - @throw what @ref json_serializer `from_json()` method throws - - @liveexample{The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,get_to} - - @since version 3.3.0 - */ - template < typename ValueType, - detail::enable_if_t < - !detail::is_basic_json::value&& - detail::has_from_json::value, - int > = 0 > - ValueType & get_to(ValueType& v) const noexcept(noexcept( - JSONSerializer::from_json(std::declval(), v))) - { - JSONSerializer::from_json(*this, v); - return v; - } - - // specialization to allow to call get_to with a basic_json value - // see https://github.com/nlohmann/json/issues/2175 - template::value, - int> = 0> - ValueType & get_to(ValueType& v) const - { - v = *this; - return v; - } - - template < - typename T, std::size_t N, - typename Array = T (&)[N], - detail::enable_if_t < - detail::has_from_json::value, int > = 0 > - Array get_to(T (&v)[N]) const - noexcept(noexcept(JSONSerializer::from_json( - std::declval(), v))) - { - JSONSerializer::from_json(*this, v); - return v; - } - - - /*! - @brief get a pointer value (implicit) - - Implicit pointer access to the internally stored JSON value. No copies are - made. - - @warning Writing data to the pointee of the result yields an undefined - state. - - @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, - @ref number_unsigned_t, or @ref number_float_t. Enforced by a static - assertion. - - @return pointer to the internally stored JSON value if the requested - pointer type @a PointerType fits to the JSON value; `nullptr` otherwise - - @complexity Constant. - - @liveexample{The example below shows how pointers to internal values of a - JSON value can be requested. Note that no type conversions are made and a - `nullptr` is returned if the value and the requested pointer type does not - match.,get_ptr} - - @since version 1.0.0 - */ - template::value, int>::type = 0> - auto get_ptr() noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) - { - // delegate the call to get_impl_ptr<>() - return get_impl_ptr(static_cast(nullptr)); - } - - /*! - @brief get a pointer value (implicit) - @copydoc get_ptr() - */ - template < typename PointerType, typename std::enable_if < - std::is_pointer::value&& - std::is_const::type>::value, int >::type = 0 > - constexpr auto get_ptr() const noexcept -> decltype(std::declval().get_impl_ptr(std::declval())) - { - // delegate the call to get_impl_ptr<>() const - return get_impl_ptr(static_cast(nullptr)); - } - - /*! - @brief get a pointer value (explicit) - - Explicit pointer access to the internally stored JSON value. No copies are - made. - - @warning The pointer becomes invalid if the underlying JSON object - changes. - - @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, - @ref number_unsigned_t, or @ref number_float_t. - - @return pointer to the internally stored JSON value if the requested - pointer type @a PointerType fits to the JSON value; `nullptr` otherwise - - @complexity Constant. - - @liveexample{The example below shows how pointers to internal values of a - JSON value can be requested. Note that no type conversions are made and a - `nullptr` is returned if the value and the requested pointer type does not - match.,get__PointerType} - - @sa @ref get_ptr() for explicit pointer-member access - - @since version 1.0.0 - */ - template::value, int>::type = 0> - auto get() noexcept -> decltype(std::declval().template get_ptr()) - { - // delegate the call to get_ptr - return get_ptr(); - } - - /*! - @brief get a pointer value (explicit) - @copydoc get() - */ - template::value, int>::type = 0> - constexpr auto get() const noexcept -> decltype(std::declval().template get_ptr()) - { - // delegate the call to get_ptr - return get_ptr(); - } - - /*! - @brief get a reference value (implicit) - - Implicit reference access to the internally stored JSON value. No copies - are made. - - @warning Writing data to the referee of the result yields an undefined - state. - - @tparam ReferenceType reference type; must be a reference to @ref array_t, - @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or - @ref number_float_t. Enforced by static assertion. - - @return reference to the internally stored JSON value if the requested - reference type @a ReferenceType fits to the JSON value; throws - type_error.303 otherwise - - @throw type_error.303 in case passed type @a ReferenceType is incompatible - with the stored JSON value; see example below - - @complexity Constant. - - @liveexample{The example shows several calls to `get_ref()`.,get_ref} - - @since version 1.1.0 - */ - template::value, int>::type = 0> - ReferenceType get_ref() - { - // delegate call to get_ref_impl - return get_ref_impl(*this); - } - - /*! - @brief get a reference value (implicit) - @copydoc get_ref() - */ - template < typename ReferenceType, typename std::enable_if < - std::is_reference::value&& - std::is_const::type>::value, int >::type = 0 > - ReferenceType get_ref() const - { - // delegate call to get_ref_impl - return get_ref_impl(*this); - } - - /*! - @brief get a value (implicit) - - Implicit type conversion between the JSON value and a compatible value. - The call is realized by calling @ref get() const. - - @tparam ValueType non-pointer type compatible to the JSON value, for - instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays. The character type of @ref string_t - as well as an initializer list of this type is excluded to avoid - ambiguities as these types implicitly convert to `std::string`. - - @return copy of the JSON value, converted to type @a ValueType - - @throw type_error.302 in case passed type @a ValueType is incompatible - to the JSON value type (e.g., the JSON value is of type boolean, but a - string is requested); see example below - - @complexity Linear in the size of the JSON value. - - @liveexample{The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map`.,operator__ValueType} - - @since version 1.0.0 - */ - template < typename ValueType, typename std::enable_if < - !std::is_pointer::value&& - !std::is_same>::value&& - !std::is_same::value&& - !detail::is_basic_json::value - && !std::is_same>::value -#if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) - && !std::is_same::value -#endif - && detail::is_detected::value - , int >::type = 0 > - JSON_EXPLICIT operator ValueType() const - { - // delegate the call to get<>() const - return get(); - } - - /*! - @return reference to the binary value - - @throw type_error.302 if the value is not binary - - @sa @ref is_binary() to check if the value is binary - - @since version 3.8.0 - */ - binary_t& get_binary() - { - if (!is_binary()) - { - JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); - } - - return *get_ptr(); - } - - /// @copydoc get_binary() - const binary_t& get_binary() const - { - if (!is_binary()) - { - JSON_THROW(type_error::create(302, "type must be binary, but is " + std::string(type_name()))); - } - - return *get_ptr(); - } - - /// @} - - - //////////////////// - // element access // - //////////////////// - - /// @name element access - /// Access to the JSON value. - /// @{ - - /*! - @brief access specified array element with bounds checking - - Returns a reference to the element at specified location @a idx, with - bounds checking. - - @param[in] idx index of the element to access - - @return reference to the element at index @a idx - - @throw type_error.304 if the JSON value is not an array; in this case, - calling `at` with an index makes no sense. See example below. - @throw out_of_range.401 if the index @a idx is out of range of the array; - that is, `idx >= size()`. See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @since version 1.0.0 - - @liveexample{The example below shows how array elements can be read and - written using `at()`. It also demonstrates the different exceptions that - can be thrown.,at__size_type} - */ - reference at(size_type idx) - { - // at only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) - { - JSON_TRY - { - return m_value.array->at(idx); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); - } - } - - /*! - @brief access specified array element with bounds checking - - Returns a const reference to the element at specified location @a idx, - with bounds checking. - - @param[in] idx index of the element to access - - @return const reference to the element at index @a idx - - @throw type_error.304 if the JSON value is not an array; in this case, - calling `at` with an index makes no sense. See example below. - @throw out_of_range.401 if the index @a idx is out of range of the array; - that is, `idx >= size()`. See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @since version 1.0.0 - - @liveexample{The example below shows how array elements can be read using - `at()`. It also demonstrates the different exceptions that can be thrown., - at__size_type_const} - */ - const_reference at(size_type idx) const - { - // at only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) - { - JSON_TRY - { - return m_value.array->at(idx); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); - } - } - - /*! - @brief access specified object element with bounds checking - - Returns a reference to the element at with specified key @a key, with - bounds checking. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw type_error.304 if the JSON value is not an object; in this case, - calling `at` with a key makes no sense. See example below. - @throw out_of_range.403 if the key @a key is is not stored in the object; - that is, `find(key) == end()`. See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Logarithmic in the size of the container. - - @sa @ref operator[](const typename object_t::key_type&) for unchecked - access by reference - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - - @liveexample{The example below shows how object elements can be read and - written using `at()`. It also demonstrates the different exceptions that - can be thrown.,at__object_t_key_type} - */ - reference at(const typename object_t::key_type& key) - { - // at only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - JSON_TRY - { - return m_value.object->at(key); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); - } - } - - /*! - @brief access specified object element with bounds checking - - Returns a const reference to the element at with specified key @a key, - with bounds checking. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @throw type_error.304 if the JSON value is not an object; in this case, - calling `at` with a key makes no sense. See example below. - @throw out_of_range.403 if the key @a key is is not stored in the object; - that is, `find(key) == end()`. See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Logarithmic in the size of the container. - - @sa @ref operator[](const typename object_t::key_type&) for unchecked - access by reference - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - - @liveexample{The example below shows how object elements can be read using - `at()`. It also demonstrates the different exceptions that can be thrown., - at__object_t_key_type_const} - */ - const_reference at(const typename object_t::key_type& key) const - { - // at only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - JSON_TRY - { - return m_value.object->at(key); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); - } - } - - /*! - @brief access specified array element - - Returns a reference to the element at specified location @a idx. - - @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), - then the array is silently filled up with `null` values to make `idx` a - valid reference to the last stored element. - - @param[in] idx index of the element to access - - @return reference to the element at index @a idx - - @throw type_error.305 if the JSON value is not an array or null; in that - cases, using the [] operator with an index makes no sense. - - @complexity Constant if @a idx is in the range of the array. Otherwise - linear in `idx - size()`. - - @liveexample{The example below shows how array elements can be read and - written using `[]` operator. Note the addition of `null` - values.,operatorarray__size_type} - - @since version 1.0.0 - */ - reference operator[](size_type idx) - { - // implicitly convert null value to an empty array - if (is_null()) - { - m_type = value_t::array; - m_value.array = create(); - assert_invariant(); - } - - // operator[] only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) - { - // fill up array with null values if given idx is outside range - if (idx >= m_value.array->size()) - { - m_value.array->insert(m_value.array->end(), - idx - m_value.array->size() + 1, - basic_json()); - } - - return m_value.array->operator[](idx); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); - } - - /*! - @brief access specified array element - - Returns a const reference to the element at specified location @a idx. - - @param[in] idx index of the element to access - - @return const reference to the element at index @a idx - - @throw type_error.305 if the JSON value is not an array; in that case, - using the [] operator with an index makes no sense. - - @complexity Constant. - - @liveexample{The example below shows how array elements can be read using - the `[]` operator.,operatorarray__size_type_const} - - @since version 1.0.0 - */ - const_reference operator[](size_type idx) const - { - // const operator[] only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) - { - return m_value.array->operator[](idx); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name()))); - } - - /*! - @brief access specified object element - - Returns a reference to the element at with specified key @a key. - - @note If @a key is not found in the object, then it is silently added to - the object and filled with a `null` value to make `key` a valid reference. - In case the value was `null` before, it is converted to an object. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw type_error.305 if the JSON value is not an object or null; in that - cases, using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read and - written using the `[]` operator.,operatorarray__key_type} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - reference operator[](const typename object_t::key_type& key) - { - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create(); - assert_invariant(); - } - - // operator[] only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - return m_value.object->operator[](key); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); - } - - /*! - @brief read-only access specified object element - - Returns a const reference to the element at with specified key @a key. No - bounds checking is performed. - - @warning If the element with key @a key does not exist, the behavior is - undefined. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @pre The element with key @a key must exist. **This precondition is - enforced with an assertion.** - - @throw type_error.305 if the JSON value is not an object; in that case, - using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read using - the `[]` operator.,operatorarray__key_type_const} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - const_reference operator[](const typename object_t::key_type& key) const - { - // const operator[] only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); - } - - /*! - @brief access specified object element - - Returns a reference to the element at with specified key @a key. - - @note If @a key is not found in the object, then it is silently added to - the object and filled with a `null` value to make `key` a valid reference. - In case the value was `null` before, it is converted to an object. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw type_error.305 if the JSON value is not an object or null; in that - cases, using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read and - written using the `[]` operator.,operatorarray__key_type} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.1.0 - */ - template - JSON_HEDLEY_NON_NULL(2) - reference operator[](T* key) - { - // implicitly convert null to object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // at only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - return m_value.object->operator[](key); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); - } - - /*! - @brief read-only access specified object element - - Returns a const reference to the element at with specified key @a key. No - bounds checking is performed. - - @warning If the element with key @a key does not exist, the behavior is - undefined. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @pre The element with key @a key must exist. **This precondition is - enforced with an assertion.** - - @throw type_error.305 if the JSON value is not an object; in that case, - using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read using - the `[]` operator.,operatorarray__key_type_const} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.1.0 - */ - template - JSON_HEDLEY_NON_NULL(2) - const_reference operator[](T* key) const - { - // at only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - JSON_ASSERT(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name()))); - } - - /*! - @brief access specified object element with default value - - Returns either a copy of an object's element at the specified key @a key - or a given default value if no element with key @a key exists. - - The function is basically equivalent to executing - @code {.cpp} - try { - return at(key); - } catch(out_of_range) { - return default_value; - } - @endcode - - @note Unlike @ref at(const typename object_t::key_type&), this function - does not throw if the given key @a key was not found. - - @note Unlike @ref operator[](const typename object_t::key_type& key), this - function does not implicitly add an element to the position defined by @a - key. This function is furthermore also applicable to const objects. - - @param[in] key key of the element to access - @param[in] default_value the value to return if @a key is not found - - @tparam ValueType type compatible to JSON values, for instance `int` for - JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for - JSON arrays. Note the type of the expected value at @a key and the default - value @a default_value must be compatible. - - @return copy of the element at key @a key or @a default_value if @a key - is not found - - @throw type_error.302 if @a default_value does not match the type of the - value at @a key - @throw type_error.306 if the JSON value is not an object; in that case, - using `value()` with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be queried - with a default value.,basic_json__value} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref operator[](const typename object_t::key_type&) for unchecked - access by reference - - @since version 1.0.0 - */ - // using std::is_convertible in a std::enable_if will fail when using explicit conversions - template < class ValueType, typename std::enable_if < - detail::is_getable::value - && !std::is_same::value, int >::type = 0 > - ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const - { - // at only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // if key is found, return value and given default value otherwise - const auto it = find(key); - if (it != end()) - { - return it->template get(); - } - - return default_value; - } - - JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); - } - - /*! - @brief overload for a default value of type const char* - @copydoc basic_json::value(const typename object_t::key_type&, const ValueType&) const - */ - string_t value(const typename object_t::key_type& key, const char* default_value) const - { - return value(key, string_t(default_value)); - } - - /*! - @brief access specified object element via JSON Pointer with default value - - Returns either a copy of an object's element at the specified key @a key - or a given default value if no element with key @a key exists. - - The function is basically equivalent to executing - @code {.cpp} - try { - return at(ptr); - } catch(out_of_range) { - return default_value; - } - @endcode - - @note Unlike @ref at(const json_pointer&), this function does not throw - if the given key @a key was not found. - - @param[in] ptr a JSON pointer to the element to access - @param[in] default_value the value to return if @a ptr found no value - - @tparam ValueType type compatible to JSON values, for instance `int` for - JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for - JSON arrays. Note the type of the expected value at @a key and the default - value @a default_value must be compatible. - - @return copy of the element at key @a key or @a default_value if @a key - is not found - - @throw type_error.302 if @a default_value does not match the type of the - value at @a ptr - @throw type_error.306 if the JSON value is not an object; in that case, - using `value()` with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be queried - with a default value.,basic_json__value_ptr} - - @sa @ref operator[](const json_pointer&) for unchecked access by reference - - @since version 2.0.2 - */ - template::value, int>::type = 0> - ValueType value(const json_pointer& ptr, const ValueType& default_value) const - { - // at only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - // if pointer resolves a value, return it or use default value - JSON_TRY - { - return ptr.get_checked(this).template get(); - } - JSON_INTERNAL_CATCH (out_of_range&) - { - return default_value; - } - } - - JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); - } - - /*! - @brief overload for a default value of type const char* - @copydoc basic_json::value(const json_pointer&, ValueType) const - */ - JSON_HEDLEY_NON_NULL(3) - string_t value(const json_pointer& ptr, const char* default_value) const - { - return value(ptr, string_t(default_value)); - } - - /*! - @brief access the first element - - Returns a reference to the first element in the container. For a JSON - container `c`, the expression `c.front()` is equivalent to `*c.begin()`. - - @return In case of a structured type (array or object), a reference to the - first element is returned. In case of number, string, boolean, or binary - values, a reference to the value is returned. - - @complexity Constant. - - @pre The JSON value must not be `null` (would throw `std::out_of_range`) - or an empty array or object (undefined behavior, **guarded by - assertions**). - @post The JSON value remains unchanged. - - @throw invalid_iterator.214 when called on `null` value - - @liveexample{The following code shows an example for `front()`.,front} - - @sa @ref back() -- access the last element - - @since version 1.0.0 - */ - reference front() - { - return *begin(); - } - - /*! - @copydoc basic_json::front() - */ - const_reference front() const - { - return *cbegin(); - } - - /*! - @brief access the last element - - Returns a reference to the last element in the container. For a JSON - container `c`, the expression `c.back()` is equivalent to - @code {.cpp} - auto tmp = c.end(); - --tmp; - return *tmp; - @endcode - - @return In case of a structured type (array or object), a reference to the - last element is returned. In case of number, string, boolean, or binary - values, a reference to the value is returned. - - @complexity Constant. - - @pre The JSON value must not be `null` (would throw `std::out_of_range`) - or an empty array or object (undefined behavior, **guarded by - assertions**). - @post The JSON value remains unchanged. - - @throw invalid_iterator.214 when called on a `null` value. See example - below. - - @liveexample{The following code shows an example for `back()`.,back} - - @sa @ref front() -- access the first element - - @since version 1.0.0 - */ - reference back() - { - auto tmp = end(); - --tmp; - return *tmp; - } - - /*! - @copydoc basic_json::back() - */ - const_reference back() const - { - auto tmp = cend(); - --tmp; - return *tmp; - } - - /*! - @brief remove element given an iterator - - Removes the element specified by iterator @a pos. The iterator @a pos must - be valid and dereferenceable. Thus the `end()` iterator (which is valid, - but is not dereferenceable) cannot be used as a value for @a pos. - - If called on a primitive type other than `null`, the resulting JSON value - will be `null`. - - @param[in] pos iterator to the element to remove - @return Iterator following the last removed element. If the iterator @a - pos refers to the last element, the `end()` iterator is returned. - - @tparam IteratorType an @ref iterator or @ref const_iterator - - @post Invalidates iterators and references at or after the point of the - erase, including the `end()` iterator. - - @throw type_error.307 if called on a `null` value; example: `"cannot use - erase() with null"` - @throw invalid_iterator.202 if called on an iterator which does not belong - to the current JSON value; example: `"iterator does not fit current - value"` - @throw invalid_iterator.205 if called on a primitive type with invalid - iterator (i.e., any iterator which is not `begin()`); example: `"iterator - out of range"` - - @complexity The complexity depends on the type: - - objects: amortized constant - - arrays: linear in distance between @a pos and the end of the container - - strings and binary: linear in the length of the member - - other types: constant - - @liveexample{The example shows the result of `erase()` for different JSON - types.,erase__IteratorType} - - @sa @ref erase(IteratorType, IteratorType) -- removes the elements in - the given range - @sa @ref erase(const typename object_t::key_type&) -- removes the element - from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at - the given index - - @since version 1.0.0 - */ - template < class IteratorType, typename std::enable_if < - std::is_same::value || - std::is_same::value, int >::type - = 0 > - IteratorType erase(IteratorType pos) - { - // make sure iterator fits the current value - if (JSON_HEDLEY_UNLIKELY(this != pos.m_object)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - IteratorType result = end(); - - switch (m_type) - { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: - case value_t::binary: - { - if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin())) - { - JSON_THROW(invalid_iterator::create(205, "iterator out of range")); - } - - if (is_string()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_value.string); - std::allocator_traits::deallocate(alloc, m_value.string, 1); - m_value.string = nullptr; - } - else if (is_binary()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_value.binary); - std::allocator_traits::deallocate(alloc, m_value.binary, 1); - m_value.binary = nullptr; - } - - m_type = value_t::null; - assert_invariant(); - break; - } - - case value_t::object: - { - result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); - break; - } - - case value_t::array: - { - result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); - break; - } - - default: - JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); - } - - return result; - } - - /*! - @brief remove elements given an iterator range - - Removes the element specified by the range `[first; last)`. The iterator - @a first does not need to be dereferenceable if `first == last`: erasing - an empty range is a no-op. - - If called on a primitive type other than `null`, the resulting JSON value - will be `null`. - - @param[in] first iterator to the beginning of the range to remove - @param[in] last iterator past the end of the range to remove - @return Iterator following the last removed element. If the iterator @a - second refers to the last element, the `end()` iterator is returned. - - @tparam IteratorType an @ref iterator or @ref const_iterator - - @post Invalidates iterators and references at or after the point of the - erase, including the `end()` iterator. - - @throw type_error.307 if called on a `null` value; example: `"cannot use - erase() with null"` - @throw invalid_iterator.203 if called on iterators which does not belong - to the current JSON value; example: `"iterators do not fit current value"` - @throw invalid_iterator.204 if called on a primitive type with invalid - iterators (i.e., if `first != begin()` and `last != end()`); example: - `"iterators out of range"` - - @complexity The complexity depends on the type: - - objects: `log(size()) + std::distance(first, last)` - - arrays: linear in the distance between @a first and @a last, plus linear - in the distance between @a last and end of the container - - strings and binary: linear in the length of the member - - other types: constant - - @liveexample{The example shows the result of `erase()` for different JSON - types.,erase__IteratorType_IteratorType} - - @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(const typename object_t::key_type&) -- removes the element - from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at - the given index - - @since version 1.0.0 - */ - template < class IteratorType, typename std::enable_if < - std::is_same::value || - std::is_same::value, int >::type - = 0 > - IteratorType erase(IteratorType first, IteratorType last) - { - // make sure iterator fits the current value - if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object)) - { - JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); - } - - IteratorType result = end(); - - switch (m_type) - { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: - case value_t::binary: - { - if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin() - || !last.m_it.primitive_iterator.is_end())) - { - JSON_THROW(invalid_iterator::create(204, "iterators out of range")); - } - - if (is_string()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_value.string); - std::allocator_traits::deallocate(alloc, m_value.string, 1); - m_value.string = nullptr; - } - else if (is_binary()) - { - AllocatorType alloc; - std::allocator_traits::destroy(alloc, m_value.binary); - std::allocator_traits::deallocate(alloc, m_value.binary, 1); - m_value.binary = nullptr; - } - - m_type = value_t::null; - assert_invariant(); - break; - } - - case value_t::object: - { - result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, - last.m_it.object_iterator); - break; - } - - case value_t::array: - { - result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, - last.m_it.array_iterator); - break; - } - - default: - JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); - } - - return result; - } - - /*! - @brief remove element from a JSON object given a key - - Removes elements from a JSON object with the key value @a key. - - @param[in] key value of the elements to remove - - @return Number of elements removed. If @a ObjectType is the default - `std::map` type, the return value will always be `0` (@a key was not - found) or `1` (@a key was found). - - @post References and iterators to the erased elements are invalidated. - Other references and iterators are not affected. - - @throw type_error.307 when called on a type other than JSON object; - example: `"cannot use erase() with null"` - - @complexity `log(size()) + count(key)` - - @liveexample{The example shows the effect of `erase()`.,erase__key_type} - - @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(IteratorType, IteratorType) -- removes the elements in - the given range - @sa @ref erase(const size_type) -- removes the element from an array at - the given index - - @since version 1.0.0 - */ - size_type erase(const typename object_t::key_type& key) - { - // this erase only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - return m_value.object->erase(key); - } - - JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); - } - - /*! - @brief remove element from a JSON array given an index - - Removes element from a JSON array at the index @a idx. - - @param[in] idx index of the element to remove - - @throw type_error.307 when called on a type other than JSON object; - example: `"cannot use erase() with null"` - @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 - is out of range"` - - @complexity Linear in distance between @a idx and the end of the container. - - @liveexample{The example shows the effect of `erase()`.,erase__size_type} - - @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(IteratorType, IteratorType) -- removes the elements in - the given range - @sa @ref erase(const typename object_t::key_type&) -- removes the element - from an object at the given key - - @since version 1.0.0 - */ - void erase(const size_type idx) - { - // this erase only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) - { - if (JSON_HEDLEY_UNLIKELY(idx >= size())) - { - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - - m_value.array->erase(m_value.array->begin() + static_cast(idx)); - } - else - { - JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); - } - } - - /// @} - - - //////////// - // lookup // - //////////// - - /// @name lookup - /// @{ - - /*! - @brief find an element in a JSON object - - Finds an element in a JSON object with key equivalent to @a key. If the - element is not found or the JSON value is not an object, end() is - returned. - - @note This method always returns @ref end() when executed on a JSON type - that is not an object. - - @param[in] key key value of the element to search for. - - @return Iterator to an element with key equivalent to @a key. If no such - element is found or the JSON value is not an object, past-the-end (see - @ref end()) iterator is returned. - - @complexity Logarithmic in the size of the JSON object. - - @liveexample{The example shows how `find()` is used.,find__key_type} - - @sa @ref contains(KeyT&&) const -- checks whether a key exists - - @since version 1.0.0 - */ - template - iterator find(KeyT&& key) - { - auto result = end(); - - if (is_object()) - { - result.m_it.object_iterator = m_value.object->find(std::forward(key)); - } - - return result; - } - - /*! - @brief find an element in a JSON object - @copydoc find(KeyT&&) - */ - template - const_iterator find(KeyT&& key) const - { - auto result = cend(); - - if (is_object()) - { - result.m_it.object_iterator = m_value.object->find(std::forward(key)); - } - - return result; - } - - /*! - @brief returns the number of occurrences of a key in a JSON object - - Returns the number of elements with key @a key. If ObjectType is the - default `std::map` type, the return value will always be `0` (@a key was - not found) or `1` (@a key was found). - - @note This method always returns `0` when executed on a JSON type that is - not an object. - - @param[in] key key value of the element to count - - @return Number of elements with key @a key. If the JSON value is not an - object, the return value will be `0`. - - @complexity Logarithmic in the size of the JSON object. - - @liveexample{The example shows how `count()` is used.,count} - - @since version 1.0.0 - */ - template - size_type count(KeyT&& key) const - { - // return 0 for all nonobject types - return is_object() ? m_value.object->count(std::forward(key)) : 0; - } - - /*! - @brief check the existence of an element in a JSON object - - Check whether an element exists in a JSON object with key equivalent to - @a key. If the element is not found or the JSON value is not an object, - false is returned. - - @note This method always returns false when executed on a JSON type - that is not an object. - - @param[in] key key value to check its existence. - - @return true if an element with specified @a key exists. If no such - element with such key is found or the JSON value is not an object, - false is returned. - - @complexity Logarithmic in the size of the JSON object. - - @liveexample{The following code shows an example for `contains()`.,contains} - - @sa @ref find(KeyT&&) -- returns an iterator to an object element - @sa @ref contains(const json_pointer&) const -- checks the existence for a JSON pointer - - @since version 3.6.0 - */ - template < typename KeyT, typename std::enable_if < - !std::is_same::type, json_pointer>::value, int >::type = 0 > - bool contains(KeyT && key) const - { - return is_object() && m_value.object->find(std::forward(key)) != m_value.object->end(); - } - - /*! - @brief check the existence of an element in a JSON object given a JSON pointer - - Check whether the given JSON pointer @a ptr can be resolved in the current - JSON value. - - @note This method can be executed on any JSON value type. - - @param[in] ptr JSON pointer to check its existence. - - @return true if the JSON pointer can be resolved to a stored value, false - otherwise. - - @post If `j.contains(ptr)` returns true, it is safe to call `j[ptr]`. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - - @complexity Logarithmic in the size of the JSON object. - - @liveexample{The following code shows an example for `contains()`.,contains_json_pointer} - - @sa @ref contains(KeyT &&) const -- checks the existence of a key - - @since version 3.7.0 - */ - bool contains(const json_pointer& ptr) const - { - return ptr.contains(this); - } - - /// @} - - - /////////////// - // iterators // - /////////////// - - /// @name iterators - /// @{ - - /*! - @brief returns an iterator to the first element - - Returns an iterator to the first element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return iterator to the first element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is constant. - - @liveexample{The following code shows an example for `begin()`.,begin} - - @sa @ref cbegin() -- returns a const iterator to the beginning - @sa @ref end() -- returns an iterator to the end - @sa @ref cend() -- returns a const iterator to the end - - @since version 1.0.0 - */ - iterator begin() noexcept - { - iterator result(this); - result.set_begin(); - return result; - } - - /*! - @copydoc basic_json::cbegin() - */ - const_iterator begin() const noexcept - { - return cbegin(); - } - - /*! - @brief returns a const iterator to the first element - - Returns a const iterator to the first element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return const iterator to the first element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast(*this).begin()`. - - @liveexample{The following code shows an example for `cbegin()`.,cbegin} - - @sa @ref begin() -- returns an iterator to the beginning - @sa @ref end() -- returns an iterator to the end - @sa @ref cend() -- returns a const iterator to the end - - @since version 1.0.0 - */ - const_iterator cbegin() const noexcept - { - const_iterator result(this); - result.set_begin(); - return result; - } - - /*! - @brief returns an iterator to one past the last element - - Returns an iterator to one past the last element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return iterator one past the last element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is constant. - - @liveexample{The following code shows an example for `end()`.,end} - - @sa @ref cend() -- returns a const iterator to the end - @sa @ref begin() -- returns an iterator to the beginning - @sa @ref cbegin() -- returns a const iterator to the beginning - - @since version 1.0.0 - */ - iterator end() noexcept - { - iterator result(this); - result.set_end(); - return result; - } - - /*! - @copydoc basic_json::cend() - */ - const_iterator end() const noexcept - { - return cend(); - } - - /*! - @brief returns a const iterator to one past the last element - - Returns a const iterator to one past the last element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return const iterator one past the last element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast(*this).end()`. - - @liveexample{The following code shows an example for `cend()`.,cend} - - @sa @ref end() -- returns an iterator to the end - @sa @ref begin() -- returns an iterator to the beginning - @sa @ref cbegin() -- returns a const iterator to the beginning - - @since version 1.0.0 - */ - const_iterator cend() const noexcept - { - const_iterator result(this); - result.set_end(); - return result; - } - - /*! - @brief returns an iterator to the reverse-beginning - - Returns an iterator to the reverse-beginning; that is, the last element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `reverse_iterator(end())`. - - @liveexample{The following code shows an example for `rbegin()`.,rbegin} - - @sa @ref crbegin() -- returns a const reverse iterator to the beginning - @sa @ref rend() -- returns a reverse iterator to the end - @sa @ref crend() -- returns a const reverse iterator to the end - - @since version 1.0.0 - */ - reverse_iterator rbegin() noexcept - { - return reverse_iterator(end()); - } - - /*! - @copydoc basic_json::crbegin() - */ - const_reverse_iterator rbegin() const noexcept - { - return crbegin(); - } - - /*! - @brief returns an iterator to the reverse-end - - Returns an iterator to the reverse-end; that is, one before the first - element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `reverse_iterator(begin())`. - - @liveexample{The following code shows an example for `rend()`.,rend} - - @sa @ref crend() -- returns a const reverse iterator to the end - @sa @ref rbegin() -- returns a reverse iterator to the beginning - @sa @ref crbegin() -- returns a const reverse iterator to the beginning - - @since version 1.0.0 - */ - reverse_iterator rend() noexcept - { - return reverse_iterator(begin()); - } - - /*! - @copydoc basic_json::crend() - */ - const_reverse_iterator rend() const noexcept - { - return crend(); - } - - /*! - @brief returns a const reverse iterator to the last element - - Returns a const iterator to the reverse-beginning; that is, the last - element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast(*this).rbegin()`. - - @liveexample{The following code shows an example for `crbegin()`.,crbegin} - - @sa @ref rbegin() -- returns a reverse iterator to the beginning - @sa @ref rend() -- returns a reverse iterator to the end - @sa @ref crend() -- returns a const reverse iterator to the end - - @since version 1.0.0 - */ - const_reverse_iterator crbegin() const noexcept - { - return const_reverse_iterator(cend()); - } - - /*! - @brief returns a const reverse iterator to one before the first - - Returns a const reverse iterator to the reverse-end; that is, one before - the first element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast(*this).rend()`. - - @liveexample{The following code shows an example for `crend()`.,crend} - - @sa @ref rend() -- returns a reverse iterator to the end - @sa @ref rbegin() -- returns a reverse iterator to the beginning - @sa @ref crbegin() -- returns a const reverse iterator to the beginning - - @since version 1.0.0 - */ - const_reverse_iterator crend() const noexcept - { - return const_reverse_iterator(cbegin()); - } - - public: - /*! - @brief wrapper to access iterator member functions in range-based for - - This function allows to access @ref iterator::key() and @ref - iterator::value() during range-based for loops. In these loops, a - reference to the JSON values is returned, so there is no access to the - underlying iterator. - - For loop without iterator_wrapper: - - @code{cpp} - for (auto it = j_object.begin(); it != j_object.end(); ++it) - { - std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; - } - @endcode - - Range-based for loop without iterator proxy: - - @code{cpp} - for (auto it : j_object) - { - // "it" is of type json::reference and has no key() member - std::cout << "value: " << it << '\n'; - } - @endcode - - Range-based for loop with iterator proxy: - - @code{cpp} - for (auto it : json::iterator_wrapper(j_object)) - { - std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; - } - @endcode - - @note When iterating over an array, `key()` will return the index of the - element as string (see example). - - @param[in] ref reference to a JSON value - @return iteration proxy object wrapping @a ref with an interface to use in - range-based for loops - - @liveexample{The following code shows how the wrapper is used,iterator_wrapper} - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @note The name of this function is not yet final and may change in the - future. - - @deprecated This stream operator is deprecated and will be removed in - future 4.0.0 of the library. Please use @ref items() instead; - that is, replace `json::iterator_wrapper(j)` with `j.items()`. - */ - JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) - static iteration_proxy iterator_wrapper(reference ref) noexcept - { - return ref.items(); - } - - /*! - @copydoc iterator_wrapper(reference) - */ - JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items()) - static iteration_proxy iterator_wrapper(const_reference ref) noexcept - { - return ref.items(); - } - - /*! - @brief helper to access iterator member functions in range-based for - - This function allows to access @ref iterator::key() and @ref - iterator::value() during range-based for loops. In these loops, a - reference to the JSON values is returned, so there is no access to the - underlying iterator. - - For loop without `items()` function: - - @code{cpp} - for (auto it = j_object.begin(); it != j_object.end(); ++it) - { - std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; - } - @endcode - - Range-based for loop without `items()` function: - - @code{cpp} - for (auto it : j_object) - { - // "it" is of type json::reference and has no key() member - std::cout << "value: " << it << '\n'; - } - @endcode - - Range-based for loop with `items()` function: - - @code{cpp} - for (auto& el : j_object.items()) - { - std::cout << "key: " << el.key() << ", value:" << el.value() << '\n'; - } - @endcode - - The `items()` function also allows to use - [structured bindings](https://en.cppreference.com/w/cpp/language/structured_binding) - (C++17): - - @code{cpp} - for (auto& [key, val] : j_object.items()) - { - std::cout << "key: " << key << ", value:" << val << '\n'; - } - @endcode - - @note When iterating over an array, `key()` will return the index of the - element as string (see example). For primitive types (e.g., numbers), - `key()` returns an empty string. - - @warning Using `items()` on temporary objects is dangerous. Make sure the - object's lifetime exeeds the iteration. See - for more - information. - - @return iteration proxy object wrapping @a ref with an interface to use in - range-based for loops - - @liveexample{The following code shows how the function is used.,items} - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @since version 3.1.0, structured bindings support since 3.5.0. - */ - iteration_proxy items() noexcept - { - return iteration_proxy(*this); - } - - /*! - @copydoc items() - */ - iteration_proxy items() const noexcept - { - return iteration_proxy(*this); - } - - /// @} - - - ////////////// - // capacity // - ////////////// - - /// @name capacity - /// @{ - - /*! - @brief checks whether the container is empty. - - Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). - - @return The return value depends on the different types and is - defined as follows: - Value type | return value - ----------- | ------------- - null | `true` - boolean | `false` - string | `false` - number | `false` - binary | `false` - object | result of function `object_t::empty()` - array | result of function `array_t::empty()` - - @liveexample{The following code uses `empty()` to check if a JSON - object contains any elements.,empty} - - @complexity Constant, as long as @ref array_t and @ref object_t satisfy - the Container concept; that is, their `empty()` functions have constant - complexity. - - @iterators No changes. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @note This function does not return whether a string stored as JSON value - is empty - it returns whether the JSON container itself is empty which is - false in the case of a string. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is constant. - - Has the semantics of `begin() == end()`. - - @sa @ref size() -- returns the number of elements - - @since version 1.0.0 - */ - bool empty() const noexcept - { - switch (m_type) - { - case value_t::null: - { - // null values are empty - return true; - } - - case value_t::array: - { - // delegate call to array_t::empty() - return m_value.array->empty(); - } - - case value_t::object: - { - // delegate call to object_t::empty() - return m_value.object->empty(); - } - - default: - { - // all other types are nonempty - return false; - } - } - } - - /*! - @brief returns the number of elements - - Returns the number of elements in a JSON value. - - @return The return value depends on the different types and is - defined as follows: - Value type | return value - ----------- | ------------- - null | `0` - boolean | `1` - string | `1` - number | `1` - binary | `1` - object | result of function object_t::size() - array | result of function array_t::size() - - @liveexample{The following code calls `size()` on the different value - types.,size} - - @complexity Constant, as long as @ref array_t and @ref object_t satisfy - the Container concept; that is, their size() functions have constant - complexity. - - @iterators No changes. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @note This function does not return the length of a string stored as JSON - value - it returns the number of elements in the JSON value which is 1 in - the case of a string. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is constant. - - Has the semantics of `std::distance(begin(), end())`. - - @sa @ref empty() -- checks whether the container is empty - @sa @ref max_size() -- returns the maximal number of elements - - @since version 1.0.0 - */ - size_type size() const noexcept - { - switch (m_type) - { - case value_t::null: - { - // null values are empty - return 0; - } - - case value_t::array: - { - // delegate call to array_t::size() - return m_value.array->size(); - } - - case value_t::object: - { - // delegate call to object_t::size() - return m_value.object->size(); - } - - default: - { - // all other types have size 1 - return 1; - } - } - } - - /*! - @brief returns the maximum possible number of elements - - Returns the maximum number of elements a JSON value is able to hold due to - system or library implementation limitations, i.e. `std::distance(begin(), - end())` for the JSON value. - - @return The return value depends on the different types and is - defined as follows: - Value type | return value - ----------- | ------------- - null | `0` (same as `size()`) - boolean | `1` (same as `size()`) - string | `1` (same as `size()`) - number | `1` (same as `size()`) - binary | `1` (same as `size()`) - object | result of function `object_t::max_size()` - array | result of function `array_t::max_size()` - - @liveexample{The following code calls `max_size()` on the different value - types. Note the output is implementation specific.,max_size} - - @complexity Constant, as long as @ref array_t and @ref object_t satisfy - the Container concept; that is, their `max_size()` functions have constant - complexity. - - @iterators No changes. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @requirement This function helps `basic_json` satisfying the - [Container](https://en.cppreference.com/w/cpp/named_req/Container) - requirements: - - The complexity is constant. - - Has the semantics of returning `b.size()` where `b` is the largest - possible JSON value. - - @sa @ref size() -- returns the number of elements - - @since version 1.0.0 - */ - size_type max_size() const noexcept - { - switch (m_type) - { - case value_t::array: - { - // delegate call to array_t::max_size() - return m_value.array->max_size(); - } - - case value_t::object: - { - // delegate call to object_t::max_size() - return m_value.object->max_size(); - } - - default: - { - // all other types have max_size() == size() - return size(); - } - } - } - - /// @} - - - /////////////// - // modifiers // - /////////////// - - /// @name modifiers - /// @{ - - /*! - @brief clears the contents - - Clears the content of a JSON value and resets it to the default value as - if @ref basic_json(value_t) would have been called with the current value - type from @ref type(): - - Value type | initial value - ----------- | ------------- - null | `null` - boolean | `false` - string | `""` - number | `0` - binary | An empty byte vector - object | `{}` - array | `[]` - - @post Has the same effect as calling - @code {.cpp} - *this = basic_json(type()); - @endcode - - @liveexample{The example below shows the effect of `clear()` to different - JSON types.,clear} - - @complexity Linear in the size of the JSON value. - - @iterators All iterators, pointers and references related to this container - are invalidated. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @sa @ref basic_json(value_t) -- constructor that creates an object with the - same value than calling `clear()` - - @since version 1.0.0 - */ - void clear() noexcept - { - switch (m_type) - { - case value_t::number_integer: - { - m_value.number_integer = 0; - break; - } - - case value_t::number_unsigned: - { - m_value.number_unsigned = 0; - break; - } - - case value_t::number_float: - { - m_value.number_float = 0.0; - break; - } - - case value_t::boolean: - { - m_value.boolean = false; - break; - } - - case value_t::string: - { - m_value.string->clear(); - break; - } - - case value_t::binary: - { - m_value.binary->clear(); - break; - } - - case value_t::array: - { - m_value.array->clear(); - break; - } - - case value_t::object: - { - m_value.object->clear(); - break; - } - - default: - break; - } - } - - /*! - @brief add an object to an array - - Appends the given element @a val to the end of the JSON value. If the - function is called on a JSON null value, an empty array is created before - appending @a val. - - @param[in] val the value to add to the JSON array - - @throw type_error.308 when called on a type other than JSON array or - null; example: `"cannot use push_back() with number"` - - @complexity Amortized constant. - - @liveexample{The example shows how `push_back()` and `+=` can be used to - add elements to a JSON array. Note how the `null` value was silently - converted to a JSON array.,push_back} - - @since version 1.0.0 - */ - void push_back(basic_json&& val) - { - // push_back only works for null objects or arrays - if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) - { - JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); - } - - // transform null object into an array - if (is_null()) - { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); - } - - // add element to array (move semantics) - m_value.array->push_back(std::move(val)); - // if val is moved from, basic_json move constructor marks it null so we do not call the destructor - } - - /*! - @brief add an object to an array - @copydoc push_back(basic_json&&) - */ - reference operator+=(basic_json&& val) - { - push_back(std::move(val)); - return *this; - } - - /*! - @brief add an object to an array - @copydoc push_back(basic_json&&) - */ - void push_back(const basic_json& val) - { - // push_back only works for null objects or arrays - if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) - { - JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); - } - - // transform null object into an array - if (is_null()) - { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); - } - - // add element to array - m_value.array->push_back(val); - } - - /*! - @brief add an object to an array - @copydoc push_back(basic_json&&) - */ - reference operator+=(const basic_json& val) - { - push_back(val); - return *this; - } - - /*! - @brief add an object to an object - - Inserts the given element @a val to the JSON object. If the function is - called on a JSON null value, an empty object is created before inserting - @a val. - - @param[in] val the value to add to the JSON object - - @throw type_error.308 when called on a type other than JSON object or - null; example: `"cannot use push_back() with number"` - - @complexity Logarithmic in the size of the container, O(log(`size()`)). - - @liveexample{The example shows how `push_back()` and `+=` can be used to - add elements to a JSON object. Note how the `null` value was silently - converted to a JSON object.,push_back__object_t__value} - - @since version 1.0.0 - */ - void push_back(const typename object_t::value_type& val) - { - // push_back only works for null objects or objects - if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) - { - JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); - } - - // transform null object into an object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // add element to array - m_value.object->insert(val); - } - - /*! - @brief add an object to an object - @copydoc push_back(const typename object_t::value_type&) - */ - reference operator+=(const typename object_t::value_type& val) - { - push_back(val); - return *this; - } - - /*! - @brief add an object to an object - - This function allows to use `push_back` with an initializer list. In case - - 1. the current value is an object, - 2. the initializer list @a init contains only two elements, and - 3. the first element of @a init is a string, - - @a init is converted into an object element and added using - @ref push_back(const typename object_t::value_type&). Otherwise, @a init - is converted to a JSON value and added using @ref push_back(basic_json&&). - - @param[in] init an initializer list - - @complexity Linear in the size of the initializer list @a init. - - @note This function is required to resolve an ambiguous overload error, - because pairs like `{"key", "value"}` can be both interpreted as - `object_t::value_type` or `std::initializer_list`, see - https://github.com/nlohmann/json/issues/235 for more information. - - @liveexample{The example shows how initializer lists are treated as - objects when possible.,push_back__initializer_list} - */ - void push_back(initializer_list_t init) - { - if (is_object() && init.size() == 2 && (*init.begin())->is_string()) - { - basic_json&& key = init.begin()->moved_or_copied(); - push_back(typename object_t::value_type( - std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); - } - else - { - push_back(basic_json(init)); - } - } - - /*! - @brief add an object to an object - @copydoc push_back(initializer_list_t) - */ - reference operator+=(initializer_list_t init) - { - push_back(init); - return *this; - } - - /*! - @brief add an object to an array - - Creates a JSON value from the passed parameters @a args to the end of the - JSON value. If the function is called on a JSON null value, an empty array - is created before appending the value created from @a args. - - @param[in] args arguments to forward to a constructor of @ref basic_json - @tparam Args compatible types to create a @ref basic_json object - - @return reference to the inserted element - - @throw type_error.311 when called on a type other than JSON array or - null; example: `"cannot use emplace_back() with number"` - - @complexity Amortized constant. - - @liveexample{The example shows how `push_back()` can be used to add - elements to a JSON array. Note how the `null` value was silently converted - to a JSON array.,emplace_back} - - @since version 2.0.8, returns reference since 3.7.0 - */ - template - reference emplace_back(Args&& ... args) - { - // emplace_back only works for null objects or arrays - if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array()))) - { - JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); - } - - // transform null object into an array - if (is_null()) - { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); - } - - // add element to array (perfect forwarding) -#ifdef JSON_HAS_CPP_17 - return m_value.array->emplace_back(std::forward(args)...); -#else - m_value.array->emplace_back(std::forward(args)...); - return m_value.array->back(); -#endif - } - - /*! - @brief add an object to an object if key does not exist - - Inserts a new element into a JSON object constructed in-place with the - given @a args if there is no element with the key in the container. If the - function is called on a JSON null value, an empty object is created before - appending the value created from @a args. - - @param[in] args arguments to forward to a constructor of @ref basic_json - @tparam Args compatible types to create a @ref basic_json object - - @return a pair consisting of an iterator to the inserted element, or the - already-existing element if no insertion happened, and a bool - denoting whether the insertion took place. - - @throw type_error.311 when called on a type other than JSON object or - null; example: `"cannot use emplace() with number"` - - @complexity Logarithmic in the size of the container, O(log(`size()`)). - - @liveexample{The example shows how `emplace()` can be used to add elements - to a JSON object. Note how the `null` value was silently converted to a - JSON object. Further note how no value is added if there was already one - value stored with the same key.,emplace} - - @since version 2.0.8 - */ - template - std::pair emplace(Args&& ... args) - { - // emplace only works for null objects or arrays - if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) - { - JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); - } - - // transform null object into an object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // add element to array (perfect forwarding) - auto res = m_value.object->emplace(std::forward(args)...); - // create result iterator and set iterator to the result of emplace - auto it = begin(); - it.m_it.object_iterator = res.first; - - // return pair of iterator and boolean - return {it, res.second}; - } - - /// Helper for insertion of an iterator - /// @note: This uses std::distance to support GCC 4.8, - /// see https://github.com/nlohmann/json/pull/1257 - template - iterator insert_iterator(const_iterator pos, Args&& ... args) - { - iterator result(this); - JSON_ASSERT(m_value.array != nullptr); - - auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator); - m_value.array->insert(pos.m_it.array_iterator, std::forward(args)...); - result.m_it.array_iterator = m_value.array->begin() + insert_pos; - - // This could have been written as: - // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); - // but the return value of insert is missing in GCC 4.8, so it is written this way instead. - - return result; - } - - /*! - @brief inserts element - - Inserts element @a val before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] val element to insert - @return iterator pointing to the inserted @a val. - - @throw type_error.309 if called on JSON values other than arrays; - example: `"cannot use insert() with string"` - @throw invalid_iterator.202 if @a pos is not an iterator of *this; - example: `"iterator does not fit current value"` - - @complexity Constant plus linear in the distance between @a pos and end of - the container. - - @liveexample{The example shows how `insert()` is used.,insert} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, const basic_json& val) - { - // insert only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) - { - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - // insert to array and return iterator - return insert_iterator(pos, val); - } - - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - /*! - @brief inserts element - @copydoc insert(const_iterator, const basic_json&) - */ - iterator insert(const_iterator pos, basic_json&& val) - { - return insert(pos, val); - } - - /*! - @brief inserts elements - - Inserts @a cnt copies of @a val before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] cnt number of copies of @a val to insert - @param[in] val element to insert - @return iterator pointing to the first element inserted, or @a pos if - `cnt==0` - - @throw type_error.309 if called on JSON values other than arrays; example: - `"cannot use insert() with string"` - @throw invalid_iterator.202 if @a pos is not an iterator of *this; - example: `"iterator does not fit current value"` - - @complexity Linear in @a cnt plus linear in the distance between @a pos - and end of the container. - - @liveexample{The example shows how `insert()` is used.,insert__count} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, size_type cnt, const basic_json& val) - { - // insert only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) - { - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - // insert to array and return iterator - return insert_iterator(pos, cnt, val); - } - - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - /*! - @brief inserts elements - - Inserts elements from range `[first, last)` before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] first begin of the range of elements to insert - @param[in] last end of the range of elements to insert - - @throw type_error.309 if called on JSON values other than arrays; example: - `"cannot use insert() with string"` - @throw invalid_iterator.202 if @a pos is not an iterator of *this; - example: `"iterator does not fit current value"` - @throw invalid_iterator.210 if @a first and @a last do not belong to the - same JSON value; example: `"iterators do not fit"` - @throw invalid_iterator.211 if @a first or @a last are iterators into - container for which insert is called; example: `"passed iterators may not - belong to container"` - - @return iterator pointing to the first element inserted, or @a pos if - `first==last` - - @complexity Linear in `std::distance(first, last)` plus linear in the - distance between @a pos and end of the container. - - @liveexample{The example shows how `insert()` is used.,insert__range} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, const_iterator first, const_iterator last) - { - // insert only works for arrays - if (JSON_HEDLEY_UNLIKELY(!is_array())) - { - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - // check if range iterators belong to the same JSON object - if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) - { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); - } - - if (JSON_HEDLEY_UNLIKELY(first.m_object == this)) - { - JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); - } - - // insert to array and return iterator - return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator); - } - - /*! - @brief inserts elements - - Inserts elements from initializer list @a ilist before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] ilist initializer list to insert the values from - - @throw type_error.309 if called on JSON values other than arrays; example: - `"cannot use insert() with string"` - @throw invalid_iterator.202 if @a pos is not an iterator of *this; - example: `"iterator does not fit current value"` - - @return iterator pointing to the first element inserted, or @a pos if - `ilist` is empty - - @complexity Linear in `ilist.size()` plus linear in the distance between - @a pos and end of the container. - - @liveexample{The example shows how `insert()` is used.,insert__ilist} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, initializer_list_t ilist) - { - // insert only works for arrays - if (JSON_HEDLEY_UNLIKELY(!is_array())) - { - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - // check if iterator pos fits to this JSON value - if (JSON_HEDLEY_UNLIKELY(pos.m_object != this)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - // insert to array and return iterator - return insert_iterator(pos, ilist.begin(), ilist.end()); - } - - /*! - @brief inserts elements - - Inserts elements from range `[first, last)`. - - @param[in] first begin of the range of elements to insert - @param[in] last end of the range of elements to insert - - @throw type_error.309 if called on JSON values other than objects; example: - `"cannot use insert() with string"` - @throw invalid_iterator.202 if iterator @a first or @a last does does not - point to an object; example: `"iterators first and last must point to - objects"` - @throw invalid_iterator.210 if @a first and @a last do not belong to the - same JSON value; example: `"iterators do not fit"` - - @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number - of elements to insert. - - @liveexample{The example shows how `insert()` is used.,insert__range_object} - - @since version 3.0.0 - */ - void insert(const_iterator first, const_iterator last) - { - // insert only works for objects - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - // check if range iterators belong to the same JSON object - if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) - { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); - } - - // passed iterators must belong to objects - if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object())) - { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); - } - - m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); - } - - /*! - @brief updates a JSON object from another object, overwriting existing keys - - Inserts all values from JSON object @a j and overwrites existing keys. - - @param[in] j JSON object to read values from - - @throw type_error.312 if called on JSON values other than objects; example: - `"cannot use update() with string"` - - @complexity O(N*log(size() + N)), where N is the number of elements to - insert. - - @liveexample{The example shows how `update()` is used.,update} - - @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update - - @since version 3.0.0 - */ - void update(const_reference j) - { - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create(); - assert_invariant(); - } - - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); - } - if (JSON_HEDLEY_UNLIKELY(!j.is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); - } - - for (auto it = j.cbegin(); it != j.cend(); ++it) - { - m_value.object->operator[](it.key()) = it.value(); - } - } - - /*! - @brief updates a JSON object from another object, overwriting existing keys - - Inserts all values from from range `[first, last)` and overwrites existing - keys. - - @param[in] first begin of the range of elements to insert - @param[in] last end of the range of elements to insert - - @throw type_error.312 if called on JSON values other than objects; example: - `"cannot use update() with string"` - @throw invalid_iterator.202 if iterator @a first or @a last does does not - point to an object; example: `"iterators first and last must point to - objects"` - @throw invalid_iterator.210 if @a first and @a last do not belong to the - same JSON value; example: `"iterators do not fit"` - - @complexity O(N*log(size() + N)), where N is the number of elements to - insert. - - @liveexample{The example shows how `update()` is used__range.,update} - - @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update - - @since version 3.0.0 - */ - void update(const_iterator first, const_iterator last) - { - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create(); - assert_invariant(); - } - - if (JSON_HEDLEY_UNLIKELY(!is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); - } - - // check if range iterators belong to the same JSON object - if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object)) - { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); - } - - // passed iterators must belong to objects - if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object() - || !last.m_object->is_object())) - { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); - } - - for (auto it = first; it != last; ++it) - { - m_value.object->operator[](it.key()) = it.value(); - } - } - - /*! - @brief exchanges the values - - Exchanges the contents of the JSON value with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other JSON value to exchange the contents with - - @complexity Constant. - - @liveexample{The example below shows how JSON values can be swapped with - `swap()`.,swap__reference} - - @since version 1.0.0 - */ - void swap(reference other) noexcept ( - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value&& - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value - ) - { - std::swap(m_type, other.m_type); - std::swap(m_value, other.m_value); - assert_invariant(); - } - - /*! - @brief exchanges the values - - Exchanges the contents of the JSON value from @a left with those of @a right. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. implemented as a friend function callable via ADL. - - @param[in,out] left JSON value to exchange the contents with - @param[in,out] right JSON value to exchange the contents with - - @complexity Constant. - - @liveexample{The example below shows how JSON values can be swapped with - `swap()`.,swap__reference} - - @since version 1.0.0 - */ - friend void swap(reference left, reference right) noexcept ( - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value&& - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value - ) - { - left.swap(right); - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON array with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other array to exchange the contents with - - @throw type_error.310 when JSON value is not an array; example: `"cannot - use swap() with string"` - - @complexity Constant. - - @liveexample{The example below shows how arrays can be swapped with - `swap()`.,swap__array_t} - - @since version 1.0.0 - */ - void swap(array_t& other) - { - // swap only works for arrays - if (JSON_HEDLEY_LIKELY(is_array())) - { - std::swap(*(m_value.array), other); - } - else - { - JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); - } - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON object with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other object to exchange the contents with - - @throw type_error.310 when JSON value is not an object; example: - `"cannot use swap() with string"` - - @complexity Constant. - - @liveexample{The example below shows how objects can be swapped with - `swap()`.,swap__object_t} - - @since version 1.0.0 - */ - void swap(object_t& other) - { - // swap only works for objects - if (JSON_HEDLEY_LIKELY(is_object())) - { - std::swap(*(m_value.object), other); - } - else - { - JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); - } - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON string with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other string to exchange the contents with - - @throw type_error.310 when JSON value is not a string; example: `"cannot - use swap() with boolean"` - - @complexity Constant. - - @liveexample{The example below shows how strings can be swapped with - `swap()`.,swap__string_t} - - @since version 1.0.0 - */ - void swap(string_t& other) - { - // swap only works for strings - if (JSON_HEDLEY_LIKELY(is_string())) - { - std::swap(*(m_value.string), other); - } - else - { - JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); - } - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON string with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other binary to exchange the contents with - - @throw type_error.310 when JSON value is not a string; example: `"cannot - use swap() with boolean"` - - @complexity Constant. - - @liveexample{The example below shows how strings can be swapped with - `swap()`.,swap__binary_t} - - @since version 3.8.0 - */ - void swap(binary_t& other) - { - // swap only works for strings - if (JSON_HEDLEY_LIKELY(is_binary())) - { - std::swap(*(m_value.binary), other); - } - else - { - JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); - } - } - - /// @copydoc swap(binary_t) - void swap(typename binary_t::container_type& other) - { - // swap only works for strings - if (JSON_HEDLEY_LIKELY(is_binary())) - { - std::swap(*(m_value.binary), other); - } - else - { - JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); - } - } - - /// @} - - public: - ////////////////////////////////////////// - // lexicographical comparison operators // - ////////////////////////////////////////// - - /// @name lexicographical comparison operators - /// @{ - - /*! - @brief comparison: equal - - Compares two JSON values for equality according to the following rules: - - Two JSON values are equal if (1) they are from the same type and (2) - their stored values are the same according to their respective - `operator==`. - - Integer and floating-point numbers are automatically converted before - comparison. Note that two NaN values are always treated as unequal. - - Two JSON null values are equal. - - @note Floating-point inside JSON values numbers are compared with - `json::number_float_t::operator==` which is `double::operator==` by - default. To compare floating-point while respecting an epsilon, an alternative - [comparison function](https://github.com/mariokonrad/marnav/blob/master/include/marnav/math/floatingpoint.hpp#L34-#L39) - could be used, for instance - @code {.cpp} - template::value, T>::type> - inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept - { - return std::abs(a - b) <= epsilon; - } - @endcode - Or you can self-defined operator equal function like this: - @code {.cpp} - bool my_equal(const_reference lhs, const_reference rhs) { - const auto lhs_type lhs.type(); - const auto rhs_type rhs.type(); - if (lhs_type == rhs_type) { - switch(lhs_type) - // self_defined case - case value_t::number_float: - return std::abs(lhs - rhs) <= std::numeric_limits::epsilon(); - // other cases remain the same with the original - ... - } - ... - } - @endcode - - @note NaN values never compare equal to themselves or to other NaN values. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether the values @a lhs and @a rhs are equal - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @complexity Linear. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__equal} - - @since version 1.0.0 - */ - friend bool operator==(const_reference lhs, const_reference rhs) noexcept - { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - return *lhs.m_value.array == *rhs.m_value.array; - - case value_t::object: - return *lhs.m_value.object == *rhs.m_value.object; - - case value_t::null: - return true; - - case value_t::string: - return *lhs.m_value.string == *rhs.m_value.string; - - case value_t::boolean: - return lhs.m_value.boolean == rhs.m_value.boolean; - - case value_t::number_integer: - return lhs.m_value.number_integer == rhs.m_value.number_integer; - - case value_t::number_unsigned: - return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; - - case value_t::number_float: - return lhs.m_value.number_float == rhs.m_value.number_float; - - case value_t::binary: - return *lhs.m_value.binary == *rhs.m_value.binary; - - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); - } - - return false; - } - - /*! - @brief comparison: equal - @copydoc operator==(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept - { - return lhs == basic_json(rhs); - } - - /*! - @brief comparison: equal - @copydoc operator==(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept - { - return basic_json(lhs) == rhs; - } - - /*! - @brief comparison: not equal - - Compares two JSON values for inequality by calculating `not (lhs == rhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether the values @a lhs and @a rhs are not equal - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__notequal} - - @since version 1.0.0 - */ - friend bool operator!=(const_reference lhs, const_reference rhs) noexcept - { - return !(lhs == rhs); - } - - /*! - @brief comparison: not equal - @copydoc operator!=(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept - { - return lhs != basic_json(rhs); - } - - /*! - @brief comparison: not equal - @copydoc operator!=(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept - { - return basic_json(lhs) != rhs; - } - - /*! - @brief comparison: less than - - Compares whether one JSON value @a lhs is less than another JSON value @a - rhs according to the following rules: - - If @a lhs and @a rhs have the same type, the values are compared using - the default `<` operator. - - Integer and floating-point numbers are automatically converted before - comparison - - In case @a lhs and @a rhs have different types, the values are ignored - and the order of the types is considered, see - @ref operator<(const value_t, const value_t). - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is less than @a rhs - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__less} - - @since version 1.0.0 - */ - friend bool operator<(const_reference lhs, const_reference rhs) noexcept - { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - // note parentheses are necessary, see - // https://github.com/nlohmann/json/issues/1530 - return (*lhs.m_value.array) < (*rhs.m_value.array); - - case value_t::object: - return (*lhs.m_value.object) < (*rhs.m_value.object); - - case value_t::null: - return false; - - case value_t::string: - return (*lhs.m_value.string) < (*rhs.m_value.string); - - case value_t::boolean: - return (lhs.m_value.boolean) < (rhs.m_value.boolean); - - case value_t::number_integer: - return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); - - case value_t::number_unsigned: - return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); - - case value_t::number_float: - return (lhs.m_value.number_float) < (rhs.m_value.number_float); - - case value_t::binary: - return (*lhs.m_value.binary) < (*rhs.m_value.binary); - - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, - // we compare types. Note we have to call the operator explicitly, - // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); - } - - /*! - @brief comparison: less than - @copydoc operator<(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept - { - return lhs < basic_json(rhs); - } - - /*! - @brief comparison: less than - @copydoc operator<(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept - { - return basic_json(lhs) < rhs; - } - - /*! - @brief comparison: less than or equal - - Compares whether one JSON value @a lhs is less than or equal to another - JSON value by calculating `not (rhs < lhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is less than or equal to @a rhs - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__greater} - - @since version 1.0.0 - */ - friend bool operator<=(const_reference lhs, const_reference rhs) noexcept - { - return !(rhs < lhs); - } - - /*! - @brief comparison: less than or equal - @copydoc operator<=(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept - { - return lhs <= basic_json(rhs); - } - - /*! - @brief comparison: less than or equal - @copydoc operator<=(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept - { - return basic_json(lhs) <= rhs; - } - - /*! - @brief comparison: greater than - - Compares whether one JSON value @a lhs is greater than another - JSON value by calculating `not (lhs <= rhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is greater than to @a rhs - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__lessequal} - - @since version 1.0.0 - */ - friend bool operator>(const_reference lhs, const_reference rhs) noexcept - { - return !(lhs <= rhs); - } - - /*! - @brief comparison: greater than - @copydoc operator>(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept - { - return lhs > basic_json(rhs); - } - - /*! - @brief comparison: greater than - @copydoc operator>(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept - { - return basic_json(lhs) > rhs; - } - - /*! - @brief comparison: greater than or equal - - Compares whether one JSON value @a lhs is greater than or equal to another - JSON value by calculating `not (lhs < rhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is greater than or equal to @a rhs - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__greaterequal} - - @since version 1.0.0 - */ - friend bool operator>=(const_reference lhs, const_reference rhs) noexcept - { - return !(lhs < rhs); - } - - /*! - @brief comparison: greater than or equal - @copydoc operator>=(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept - { - return lhs >= basic_json(rhs); - } - - /*! - @brief comparison: greater than or equal - @copydoc operator>=(const_reference, const_reference) - */ - template::value, int>::type = 0> - friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept - { - return basic_json(lhs) >= rhs; - } - - /// @} - - /////////////////// - // serialization // - /////////////////// - - /// @name serialization - /// @{ - - /*! - @brief serialize to stream - - Serialize the given JSON value @a j to the output stream @a o. The JSON - value will be serialized using the @ref dump member function. - - - The indentation of the output can be controlled with the member variable - `width` of the output stream @a o. For instance, using the manipulator - `std::setw(4)` on @a o sets the indentation level to `4` and the - serialization result is the same as calling `dump(4)`. - - - The indentation character can be controlled with the member variable - `fill` of the output stream @a o. For instance, the manipulator - `std::setfill('\\t')` sets indentation to use a tab character rather than - the default space character. - - @param[in,out] o stream to serialize to - @param[in] j JSON value to serialize - - @return the stream @a o - - @throw type_error.316 if a string stored inside the JSON value is not - UTF-8 encoded - - @complexity Linear. - - @liveexample{The example below shows the serialization with different - parameters to `width` to adjust the indentation level.,operator_serialize} - - @since version 1.0.0; indentation character added in version 3.0.0 - */ - friend std::ostream& operator<<(std::ostream& o, const basic_json& j) - { - // read width member and use it as indentation parameter if nonzero - const bool pretty_print = o.width() > 0; - const auto indentation = pretty_print ? o.width() : 0; - - // reset width to 0 for subsequent calls to this stream - o.width(0); - - // do the actual serialization - serializer s(detail::output_adapter(o), o.fill()); - s.dump(j, pretty_print, false, static_cast(indentation)); - return o; - } - - /*! - @brief serialize to stream - @deprecated This stream operator is deprecated and will be removed in - future 4.0.0 of the library. Please use - @ref operator<<(std::ostream&, const basic_json&) - instead; that is, replace calls like `j >> o;` with `o << j;`. - @since version 1.0.0; deprecated since version 3.0.0 - */ - JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&)) - friend std::ostream& operator>>(const basic_json& j, std::ostream& o) - { - return o << j; - } - - /// @} - - - ///////////////////// - // deserialization // - ///////////////////// - - /// @name deserialization - /// @{ - - /*! - @brief deserialize from a compatible input - - @tparam InputType A compatible input, for instance - - an std::istream object - - a FILE pointer - - a C-style array of characters - - a pointer to a null-terminated string of single byte characters - - an object obj for which begin(obj) and end(obj) produces a valid pair of - iterators. - - @param[in] i input to read from - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - @param[in] allow_exceptions whether to throw exceptions in case of a - parse error (optional, true by default) - @param[in] ignore_comments whether comments should be ignored and treated - like whitespace (true) or yield a parse error (true); (optional, false by - default) - - @return deserialized JSON value; in case of a parse error and - @a allow_exceptions set to `false`, the return value will be - value_t::discarded. - - @throw parse_error.101 if a parse error occurs; example: `""unexpected end - of input; expected string literal""` - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb or reading from the input @a i has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `parse()` function reading - from an array.,parse__array__parser_callback_t} - - @liveexample{The example below demonstrates the `parse()` function with - and without callback function.,parse__string__parser_callback_t} - - @liveexample{The example below demonstrates the `parse()` function with - and without callback function.,parse__istream__parser_callback_t} - - @liveexample{The example below demonstrates the `parse()` function reading - from a contiguous container.,parse__contiguouscontainer__parser_callback_t} - - @since version 2.0.3 (contiguous containers); version 3.9.0 allowed to - ignore comments. - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json parse(InputType&& i, - const parser_callback_t cb = nullptr, - const bool allow_exceptions = true, - const bool ignore_comments = false) - { - basic_json result; - parser(detail::input_adapter(std::forward(i)), cb, allow_exceptions, ignore_comments).parse(true, result); - return result; - } - - /*! - @brief deserialize from a pair of character iterators - - The value_type of the iterator must be a integral type with size of 1, 2 or - 4 bytes, which will be interpreted respectively as UTF-8, UTF-16 and UTF-32. - - @param[in] first iterator to start of character range - @param[in] last iterator to end of character range - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - @param[in] allow_exceptions whether to throw exceptions in case of a - parse error (optional, true by default) - @param[in] ignore_comments whether comments should be ignored and treated - like whitespace (true) or yield a parse error (true); (optional, false by - default) - - @return deserialized JSON value; in case of a parse error and - @a allow_exceptions set to `false`, the return value will be - value_t::discarded. - - @throw parse_error.101 if a parse error occurs; example: `""unexpected end - of input; expected string literal""` - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json parse(IteratorType first, - IteratorType last, - const parser_callback_t cb = nullptr, - const bool allow_exceptions = true, - const bool ignore_comments = false) - { - basic_json result; - parser(detail::input_adapter(std::move(first), std::move(last)), cb, allow_exceptions, ignore_comments).parse(true, result); - return result; - } - - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len)) - static basic_json parse(detail::span_input_adapter&& i, - const parser_callback_t cb = nullptr, - const bool allow_exceptions = true, - const bool ignore_comments = false) - { - basic_json result; - parser(i.get(), cb, allow_exceptions, ignore_comments).parse(true, result); - return result; - } - - /*! - @brief check if the input is valid JSON - - Unlike the @ref parse(InputType&&, const parser_callback_t,const bool) - function, this function neither throws an exception in case of invalid JSON - input (i.e., a parse error) nor creates diagnostic information. - - @tparam InputType A compatible input, for instance - - an std::istream object - - a FILE pointer - - a C-style array of characters - - a pointer to a null-terminated string of single byte characters - - an object obj for which begin(obj) and end(obj) produces a valid pair of - iterators. - - @param[in] i input to read from - @param[in] ignore_comments whether comments should be ignored and treated - like whitespace (true) or yield a parse error (true); (optional, false by - default) - - @return Whether the input read from @a i is valid JSON. - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `accept()` function reading - from a string.,accept__string} - */ - template - static bool accept(InputType&& i, - const bool ignore_comments = false) - { - return parser(detail::input_adapter(std::forward(i)), nullptr, false, ignore_comments).accept(true); - } - - template - static bool accept(IteratorType first, IteratorType last, - const bool ignore_comments = false) - { - return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true); - } - - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len)) - static bool accept(detail::span_input_adapter&& i, - const bool ignore_comments = false) - { - return parser(i.get(), nullptr, false, ignore_comments).accept(true); - } - - /*! - @brief generate SAX events - - The SAX event lister must follow the interface of @ref json_sax. - - This function reads from a compatible input. Examples are: - - an std::istream object - - a FILE pointer - - a C-style array of characters - - a pointer to a null-terminated string of single byte characters - - an object obj for which begin(obj) and end(obj) produces a valid pair of - iterators. - - @param[in] i input to read from - @param[in,out] sax SAX event listener - @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON) - @param[in] strict whether the input has to be consumed completely - @param[in] ignore_comments whether comments should be ignored and treated - like whitespace (true) or yield a parse error (true); (optional, false by - default); only applies to the JSON file format. - - @return return value of the last processed SAX event - - @throw parse_error.101 if a parse error occurs; example: `""unexpected end - of input; expected string literal""` - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the SAX consumer @a sax has - a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `sax_parse()` function - reading from string and processing the events with a user-defined SAX - event consumer.,sax_parse} - - @since version 3.2.0 - */ - template - JSON_HEDLEY_NON_NULL(2) - static bool sax_parse(InputType&& i, SAX* sax, - input_format_t format = input_format_t::json, - const bool strict = true, - const bool ignore_comments = false) - { - auto ia = detail::input_adapter(std::forward(i)); - return format == input_format_t::json - ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); - } - - template - JSON_HEDLEY_NON_NULL(3) - static bool sax_parse(IteratorType first, IteratorType last, SAX* sax, - input_format_t format = input_format_t::json, - const bool strict = true, - const bool ignore_comments = false) - { - auto ia = detail::input_adapter(std::move(first), std::move(last)); - return format == input_format_t::json - ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); - } - - template - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...)) - JSON_HEDLEY_NON_NULL(2) - static bool sax_parse(detail::span_input_adapter&& i, SAX* sax, - input_format_t format = input_format_t::json, - const bool strict = true, - const bool ignore_comments = false) - { - auto ia = i.get(); - return format == input_format_t::json - ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict) - : detail::binary_reader(std::move(ia)).sax_parse(format, sax, strict); - } - - /*! - @brief deserialize from stream - @deprecated This stream operator is deprecated and will be removed in - version 4.0.0 of the library. Please use - @ref operator>>(std::istream&, basic_json&) - instead; that is, replace calls like `j << i;` with `i >> j;`. - @since version 1.0.0; deprecated since version 3.0.0 - */ - JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&)) - friend std::istream& operator<<(basic_json& j, std::istream& i) - { - return operator>>(i, j); - } - - /*! - @brief deserialize from stream - - Deserializes an input stream to a JSON value. - - @param[in,out] i input stream to read a serialized JSON value from - @param[in,out] j JSON value to write the deserialized input to - - @throw parse_error.101 in case of an unexpected token - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below shows how a JSON value is constructed by - reading a serialization from a stream.,operator_deserialize} - - @sa parse(std::istream&, const parser_callback_t) for a variant with a - parser callback function to filter values while parsing - - @since version 1.0.0 - */ - friend std::istream& operator>>(std::istream& i, basic_json& j) - { - parser(detail::input_adapter(i)).parse(false, j); - return i; - } - - /// @} - - /////////////////////////// - // convenience functions // - /////////////////////////// - - /*! - @brief return the type as string - - Returns the type name as string to be used in error messages - usually to - indicate that a function was called on a wrong JSON type. - - @return a string representation of a the @a m_type member: - Value type | return value - ----------- | ------------- - null | `"null"` - boolean | `"boolean"` - string | `"string"` - number | `"number"` (for all number types) - object | `"object"` - array | `"array"` - binary | `"binary"` - discarded | `"discarded"` - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @complexity Constant. - - @liveexample{The following code exemplifies `type_name()` for all JSON - types.,type_name} - - @sa @ref type() -- return the type of the JSON value - @sa @ref operator value_t() -- return the type of the JSON value (implicit) - - @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` - since 3.0.0 - */ - JSON_HEDLEY_RETURNS_NON_NULL - const char* type_name() const noexcept - { - { - switch (m_type) - { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::binary: - return "binary"; - case value_t::discarded: - return "discarded"; - default: - return "number"; - } - } - } - - - private: - ////////////////////// - // member variables // - ////////////////////// - - /// the type of the current element - value_t m_type = value_t::null; - - /// the value of the current element - json_value m_value = {}; - - ////////////////////////////////////////// - // binary serialization/deserialization // - ////////////////////////////////////////// - - /// @name binary serialization/deserialization support - /// @{ - - public: - /*! - @brief create a CBOR serialization of a given JSON value - - Serializes a given JSON value @a j to a byte vector using the CBOR (Concise - Binary Object Representation) serialization format. CBOR is a binary - serialization format which aims to be more compact than JSON itself, yet - more efficient to parse. - - The library uses the following mapping from JSON values types to - CBOR types according to the CBOR specification (RFC 7049): - - JSON value type | value/range | CBOR type | first byte - --------------- | ------------------------------------------ | ---------------------------------- | --------------- - null | `null` | Null | 0xF6 - boolean | `true` | True | 0xF5 - boolean | `false` | False | 0xF4 - number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B - number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A - number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 - number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 - number_integer | -24..-1 | Negative integer | 0x20..0x37 - number_integer | 0..23 | Integer | 0x00..0x17 - number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 - number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 - number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A - number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B - number_unsigned | 0..23 | Integer | 0x00..0x17 - number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 - number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 - number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A - number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B - number_float | *any value representable by a float* | Single-Precision Float | 0xFA - number_float | *any value NOT representable by a float* | Double-Precision Float | 0xFB - string | *length*: 0..23 | UTF-8 string | 0x60..0x77 - string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 - string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 - string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A - string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B - array | *size*: 0..23 | array | 0x80..0x97 - array | *size*: 23..255 | array (1 byte follow) | 0x98 - array | *size*: 256..65535 | array (2 bytes follow) | 0x99 - array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A - array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B - object | *size*: 0..23 | map | 0xA0..0xB7 - object | *size*: 23..255 | map (1 byte follow) | 0xB8 - object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 - object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA - object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB - binary | *size*: 0..23 | byte string | 0x40..0x57 - binary | *size*: 23..255 | byte string (1 byte follow) | 0x58 - binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 - binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A - binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B - - @note The mapping is **complete** in the sense that any JSON value type - can be converted to a CBOR value. - - @note If NaN or Infinity are stored inside a JSON number, they are - serialized properly. This behavior differs from the @ref dump() - function which serializes NaN or Infinity to `null`. - - @note The following CBOR types are not used in the conversion: - - UTF-8 strings terminated by "break" (0x7F) - - arrays terminated by "break" (0x9F) - - maps terminated by "break" (0xBF) - - byte strings terminated by "break" (0x5F) - - date/time (0xC0..0xC1) - - bignum (0xC2..0xC3) - - decimal fraction (0xC4) - - bigfloat (0xC5) - - expected conversions (0xD5..0xD7) - - simple values (0xE0..0xF3, 0xF8) - - undefined (0xF7) - - half-precision floats (0xF9) - - break (0xFF) - - @param[in] j JSON value to serialize - @return CBOR serialization as byte vector - - @complexity Linear in the size of the JSON value @a j. - - @liveexample{The example shows the serialization of a JSON value to a byte - vector in CBOR format.,to_cbor} - - @sa http://cbor.io - @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the - analogous deserialization - @sa @ref to_msgpack(const basic_json&) for the related MessagePack format - @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the - related UBJSON format - - @since version 2.0.9; compact representation of floating-point numbers - since version 3.8.0 - */ - static std::vector to_cbor(const basic_json& j) - { - std::vector result; - to_cbor(j, result); - return result; - } - - static void to_cbor(const basic_json& j, detail::output_adapter o) - { - binary_writer(o).write_cbor(j); - } - - static void to_cbor(const basic_json& j, detail::output_adapter o) - { - binary_writer(o).write_cbor(j); - } - - /*! - @brief create a MessagePack serialization of a given JSON value - - Serializes a given JSON value @a j to a byte vector using the MessagePack - serialization format. MessagePack is a binary serialization format which - aims to be more compact than JSON itself, yet more efficient to parse. - - The library uses the following mapping from JSON values types to - MessagePack types according to the MessagePack specification: - - JSON value type | value/range | MessagePack type | first byte - --------------- | --------------------------------- | ---------------- | ---------- - null | `null` | nil | 0xC0 - boolean | `true` | true | 0xC3 - boolean | `false` | false | 0xC2 - number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 - number_integer | -2147483648..-32769 | int32 | 0xD2 - number_integer | -32768..-129 | int16 | 0xD1 - number_integer | -128..-33 | int8 | 0xD0 - number_integer | -32..-1 | negative fixint | 0xE0..0xFF - number_integer | 0..127 | positive fixint | 0x00..0x7F - number_integer | 128..255 | uint 8 | 0xCC - number_integer | 256..65535 | uint 16 | 0xCD - number_integer | 65536..4294967295 | uint 32 | 0xCE - number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF - number_unsigned | 0..127 | positive fixint | 0x00..0x7F - number_unsigned | 128..255 | uint 8 | 0xCC - number_unsigned | 256..65535 | uint 16 | 0xCD - number_unsigned | 65536..4294967295 | uint 32 | 0xCE - number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF - number_float | *any value representable by a float* | float 32 | 0xCA - number_float | *any value NOT representable by a float* | float 64 | 0xCB - string | *length*: 0..31 | fixstr | 0xA0..0xBF - string | *length*: 32..255 | str 8 | 0xD9 - string | *length*: 256..65535 | str 16 | 0xDA - string | *length*: 65536..4294967295 | str 32 | 0xDB - array | *size*: 0..15 | fixarray | 0x90..0x9F - array | *size*: 16..65535 | array 16 | 0xDC - array | *size*: 65536..4294967295 | array 32 | 0xDD - object | *size*: 0..15 | fix map | 0x80..0x8F - object | *size*: 16..65535 | map 16 | 0xDE - object | *size*: 65536..4294967295 | map 32 | 0xDF - binary | *size*: 0..255 | bin 8 | 0xC4 - binary | *size*: 256..65535 | bin 16 | 0xC5 - binary | *size*: 65536..4294967295 | bin 32 | 0xC6 - - @note The mapping is **complete** in the sense that any JSON value type - can be converted to a MessagePack value. - - @note The following values can **not** be converted to a MessagePack value: - - strings with more than 4294967295 bytes - - byte strings with more than 4294967295 bytes - - arrays with more than 4294967295 elements - - objects with more than 4294967295 elements - - @note Any MessagePack output created @ref to_msgpack can be successfully - parsed by @ref from_msgpack. - - @note If NaN or Infinity are stored inside a JSON number, they are - serialized properly. This behavior differs from the @ref dump() - function which serializes NaN or Infinity to `null`. - - @param[in] j JSON value to serialize - @return MessagePack serialization as byte vector - - @complexity Linear in the size of the JSON value @a j. - - @liveexample{The example shows the serialization of a JSON value to a byte - vector in MessagePack format.,to_msgpack} - - @sa http://msgpack.org - @sa @ref from_msgpack for the analogous deserialization - @sa @ref to_cbor(const basic_json& for the related CBOR format - @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the - related UBJSON format - - @since version 2.0.9 - */ - static std::vector to_msgpack(const basic_json& j) - { - std::vector result; - to_msgpack(j, result); - return result; - } - - static void to_msgpack(const basic_json& j, detail::output_adapter o) - { - binary_writer(o).write_msgpack(j); - } - - static void to_msgpack(const basic_json& j, detail::output_adapter o) - { - binary_writer(o).write_msgpack(j); - } - - /*! - @brief create a UBJSON serialization of a given JSON value - - Serializes a given JSON value @a j to a byte vector using the UBJSON - (Universal Binary JSON) serialization format. UBJSON aims to be more compact - than JSON itself, yet more efficient to parse. - - The library uses the following mapping from JSON values types to - UBJSON types according to the UBJSON specification: - - JSON value type | value/range | UBJSON type | marker - --------------- | --------------------------------- | ----------- | ------ - null | `null` | null | `Z` - boolean | `true` | true | `T` - boolean | `false` | false | `F` - number_integer | -9223372036854775808..-2147483649 | int64 | `L` - number_integer | -2147483648..-32769 | int32 | `l` - number_integer | -32768..-129 | int16 | `I` - number_integer | -128..127 | int8 | `i` - number_integer | 128..255 | uint8 | `U` - number_integer | 256..32767 | int16 | `I` - number_integer | 32768..2147483647 | int32 | `l` - number_integer | 2147483648..9223372036854775807 | int64 | `L` - number_unsigned | 0..127 | int8 | `i` - number_unsigned | 128..255 | uint8 | `U` - number_unsigned | 256..32767 | int16 | `I` - number_unsigned | 32768..2147483647 | int32 | `l` - number_unsigned | 2147483648..9223372036854775807 | int64 | `L` - number_unsigned | 2147483649..18446744073709551615 | high-precision | `H` - number_float | *any value* | float64 | `D` - string | *with shortest length indicator* | string | `S` - array | *see notes on optimized format* | array | `[` - object | *see notes on optimized format* | map | `{` - - @note The mapping is **complete** in the sense that any JSON value type - can be converted to a UBJSON value. - - @note The following values can **not** be converted to a UBJSON value: - - strings with more than 9223372036854775807 bytes (theoretical) - - @note The following markers are not used in the conversion: - - `Z`: no-op values are not created. - - `C`: single-byte strings are serialized with `S` markers. - - @note Any UBJSON output created @ref to_ubjson can be successfully parsed - by @ref from_ubjson. - - @note If NaN or Infinity are stored inside a JSON number, they are - serialized properly. This behavior differs from the @ref dump() - function which serializes NaN or Infinity to `null`. - - @note The optimized formats for containers are supported: Parameter - @a use_size adds size information to the beginning of a container and - removes the closing marker. Parameter @a use_type further checks - whether all elements of a container have the same type and adds the - type marker to the beginning of the container. The @a use_type - parameter must only be used together with @a use_size = true. Note - that @a use_size = true alone may result in larger representations - - the benefit of this parameter is that the receiving side is - immediately informed on the number of elements of the container. - - @note If the JSON data contains the binary type, the value stored is a list - of integers, as suggested by the UBJSON documentation. In particular, - this means that serialization and the deserialization of a JSON - containing binary values into UBJSON and back will result in a - different JSON object. - - @param[in] j JSON value to serialize - @param[in] use_size whether to add size annotations to container types - @param[in] use_type whether to add type annotations to container types - (must be combined with @a use_size = true) - @return UBJSON serialization as byte vector - - @complexity Linear in the size of the JSON value @a j. - - @liveexample{The example shows the serialization of a JSON value to a byte - vector in UBJSON format.,to_ubjson} - - @sa http://ubjson.org - @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the - analogous deserialization - @sa @ref to_cbor(const basic_json& for the related CBOR format - @sa @ref to_msgpack(const basic_json&) for the related MessagePack format - - @since version 3.1.0 - */ - static std::vector to_ubjson(const basic_json& j, - const bool use_size = false, - const bool use_type = false) - { - std::vector result; - to_ubjson(j, result, use_size, use_type); - return result; - } - - static void to_ubjson(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) - { - binary_writer(o).write_ubjson(j, use_size, use_type); - } - - static void to_ubjson(const basic_json& j, detail::output_adapter o, - const bool use_size = false, const bool use_type = false) - { - binary_writer(o).write_ubjson(j, use_size, use_type); - } - - - /*! - @brief Serializes the given JSON object `j` to BSON and returns a vector - containing the corresponding BSON-representation. - - BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are - stored as a single entity (a so-called document). - - The library uses the following mapping from JSON values types to BSON types: - - JSON value type | value/range | BSON type | marker - --------------- | --------------------------------- | ----------- | ------ - null | `null` | null | 0x0A - boolean | `true`, `false` | boolean | 0x08 - number_integer | -9223372036854775808..-2147483649 | int64 | 0x12 - number_integer | -2147483648..2147483647 | int32 | 0x10 - number_integer | 2147483648..9223372036854775807 | int64 | 0x12 - number_unsigned | 0..2147483647 | int32 | 0x10 - number_unsigned | 2147483648..9223372036854775807 | int64 | 0x12 - number_unsigned | 9223372036854775808..18446744073709551615| -- | -- - number_float | *any value* | double | 0x01 - string | *any value* | string | 0x02 - array | *any value* | document | 0x04 - object | *any value* | document | 0x03 - binary | *any value* | binary | 0x05 - - @warning The mapping is **incomplete**, since only JSON-objects (and things - contained therein) can be serialized to BSON. - Also, integers larger than 9223372036854775807 cannot be serialized to BSON, - and the keys may not contain U+0000, since they are serialized a - zero-terminated c-strings. - - @throw out_of_range.407 if `j.is_number_unsigned() && j.get() > 9223372036854775807` - @throw out_of_range.409 if a key in `j` contains a NULL (U+0000) - @throw type_error.317 if `!j.is_object()` - - @pre The input `j` is required to be an object: `j.is_object() == true`. - - @note Any BSON output created via @ref to_bson can be successfully parsed - by @ref from_bson. - - @param[in] j JSON value to serialize - @return BSON serialization as byte vector - - @complexity Linear in the size of the JSON value @a j. - - @liveexample{The example shows the serialization of a JSON value to a byte - vector in BSON format.,to_bson} - - @sa http://bsonspec.org/spec.html - @sa @ref from_bson(detail::input_adapter&&, const bool strict) for the - analogous deserialization - @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the - related UBJSON format - @sa @ref to_cbor(const basic_json&) for the related CBOR format - @sa @ref to_msgpack(const basic_json&) for the related MessagePack format - */ - static std::vector to_bson(const basic_json& j) - { - std::vector result; - to_bson(j, result); - return result; - } - - /*! - @brief Serializes the given JSON object `j` to BSON and forwards the - corresponding BSON-representation to the given output_adapter `o`. - @param j The JSON object to convert to BSON. - @param o The output adapter that receives the binary BSON representation. - @pre The input `j` shall be an object: `j.is_object() == true` - @sa @ref to_bson(const basic_json&) - */ - static void to_bson(const basic_json& j, detail::output_adapter o) - { - binary_writer(o).write_bson(j); - } - - /*! - @copydoc to_bson(const basic_json&, detail::output_adapter) - */ - static void to_bson(const basic_json& j, detail::output_adapter o) - { - binary_writer(o).write_bson(j); - } - - - /*! - @brief create a JSON value from an input in CBOR format - - Deserializes a given input @a i to a JSON value using the CBOR (Concise - Binary Object Representation) serialization format. - - The library maps CBOR types to JSON value types as follows: - - CBOR type | JSON value type | first byte - ---------------------- | --------------- | ---------- - Integer | number_unsigned | 0x00..0x17 - Unsigned integer | number_unsigned | 0x18 - Unsigned integer | number_unsigned | 0x19 - Unsigned integer | number_unsigned | 0x1A - Unsigned integer | number_unsigned | 0x1B - Negative integer | number_integer | 0x20..0x37 - Negative integer | number_integer | 0x38 - Negative integer | number_integer | 0x39 - Negative integer | number_integer | 0x3A - Negative integer | number_integer | 0x3B - Byte string | binary | 0x40..0x57 - Byte string | binary | 0x58 - Byte string | binary | 0x59 - Byte string | binary | 0x5A - Byte string | binary | 0x5B - UTF-8 string | string | 0x60..0x77 - UTF-8 string | string | 0x78 - UTF-8 string | string | 0x79 - UTF-8 string | string | 0x7A - UTF-8 string | string | 0x7B - UTF-8 string | string | 0x7F - array | array | 0x80..0x97 - array | array | 0x98 - array | array | 0x99 - array | array | 0x9A - array | array | 0x9B - array | array | 0x9F - map | object | 0xA0..0xB7 - map | object | 0xB8 - map | object | 0xB9 - map | object | 0xBA - map | object | 0xBB - map | object | 0xBF - False | `false` | 0xF4 - True | `true` | 0xF5 - Null | `null` | 0xF6 - Half-Precision Float | number_float | 0xF9 - Single-Precision Float | number_float | 0xFA - Double-Precision Float | number_float | 0xFB - - @warning The mapping is **incomplete** in the sense that not all CBOR - types can be converted to a JSON value. The following CBOR types - are not supported and will yield parse errors (parse_error.112): - - date/time (0xC0..0xC1) - - bignum (0xC2..0xC3) - - decimal fraction (0xC4) - - bigfloat (0xC5) - - expected conversions (0xD5..0xD7) - - simple values (0xE0..0xF3, 0xF8) - - undefined (0xF7) - - @warning CBOR allows map keys of any type, whereas JSON only allows - strings as keys in object values. Therefore, CBOR maps with keys - other than UTF-8 strings are rejected (parse_error.113). - - @note Any CBOR output created @ref to_cbor can be successfully parsed by - @ref from_cbor. - - @param[in] i an input in CBOR format convertible to an input adapter - @param[in] strict whether to expect the input to be consumed until EOF - (true by default) - @param[in] allow_exceptions whether to throw exceptions in case of a - parse error (optional, true by default) - @param[in] tag_handler how to treat CBOR tags (optional, error by default) - - @return deserialized JSON value; in case of a parse error and - @a allow_exceptions set to `false`, the return value will be - value_t::discarded. - - @throw parse_error.110 if the given input ends prematurely or the end of - file was not reached when @a strict was set to true - @throw parse_error.112 if unsupported features from CBOR were - used in the given input @a v or if the input is not valid CBOR - @throw parse_error.113 if a string was expected as map key, but not found - - @complexity Linear in the size of the input @a i. - - @liveexample{The example shows the deserialization of a byte vector in CBOR - format to a JSON value.,from_cbor} - - @sa http://cbor.io - @sa @ref to_cbor(const basic_json&) for the analogous serialization - @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the - related MessagePack format - @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the - related UBJSON format - - @since version 2.0.9; parameter @a start_index since 2.1.1; changed to - consume input adapters, removed start_index parameter, and added - @a strict parameter since 3.0.0; added @a allow_exceptions parameter - since 3.2.0; added @a tag_handler parameter since 3.9.0. - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_cbor(InputType&& i, - const bool strict = true, - const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); - return res ? result : basic_json(value_t::discarded); - } - - /*! - @copydoc from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_cbor(IteratorType first, IteratorType last, - const bool strict = true, - const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); - return res ? result : basic_json(value_t::discarded); - } - - template - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) - static basic_json from_cbor(const T* ptr, std::size_t len, - const bool strict = true, - const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) - { - return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler); - } - - - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len)) - static basic_json from_cbor(detail::span_input_adapter&& i, - const bool strict = true, - const bool allow_exceptions = true, - const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = i.get(); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); - return res ? result : basic_json(value_t::discarded); - } - - /*! - @brief create a JSON value from an input in MessagePack format - - Deserializes a given input @a i to a JSON value using the MessagePack - serialization format. - - The library maps MessagePack types to JSON value types as follows: - - MessagePack type | JSON value type | first byte - ---------------- | --------------- | ---------- - positive fixint | number_unsigned | 0x00..0x7F - fixmap | object | 0x80..0x8F - fixarray | array | 0x90..0x9F - fixstr | string | 0xA0..0xBF - nil | `null` | 0xC0 - false | `false` | 0xC2 - true | `true` | 0xC3 - float 32 | number_float | 0xCA - float 64 | number_float | 0xCB - uint 8 | number_unsigned | 0xCC - uint 16 | number_unsigned | 0xCD - uint 32 | number_unsigned | 0xCE - uint 64 | number_unsigned | 0xCF - int 8 | number_integer | 0xD0 - int 16 | number_integer | 0xD1 - int 32 | number_integer | 0xD2 - int 64 | number_integer | 0xD3 - str 8 | string | 0xD9 - str 16 | string | 0xDA - str 32 | string | 0xDB - array 16 | array | 0xDC - array 32 | array | 0xDD - map 16 | object | 0xDE - map 32 | object | 0xDF - bin 8 | binary | 0xC4 - bin 16 | binary | 0xC5 - bin 32 | binary | 0xC6 - ext 8 | binary | 0xC7 - ext 16 | binary | 0xC8 - ext 32 | binary | 0xC9 - fixext 1 | binary | 0xD4 - fixext 2 | binary | 0xD5 - fixext 4 | binary | 0xD6 - fixext 8 | binary | 0xD7 - fixext 16 | binary | 0xD8 - negative fixint | number_integer | 0xE0-0xFF - - @note Any MessagePack output created @ref to_msgpack can be successfully - parsed by @ref from_msgpack. - - @param[in] i an input in MessagePack format convertible to an input - adapter - @param[in] strict whether to expect the input to be consumed until EOF - (true by default) - @param[in] allow_exceptions whether to throw exceptions in case of a - parse error (optional, true by default) - - @return deserialized JSON value; in case of a parse error and - @a allow_exceptions set to `false`, the return value will be - value_t::discarded. - - @throw parse_error.110 if the given input ends prematurely or the end of - file was not reached when @a strict was set to true - @throw parse_error.112 if unsupported features from MessagePack were - used in the given input @a i or if the input is not valid MessagePack - @throw parse_error.113 if a string was expected as map key, but not found - - @complexity Linear in the size of the input @a i. - - @liveexample{The example shows the deserialization of a byte vector in - MessagePack format to a JSON value.,from_msgpack} - - @sa http://msgpack.org - @sa @ref to_msgpack(const basic_json&) for the analogous serialization - @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the - related CBOR format - @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for - the related UBJSON format - @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for - the related BSON format - - @since version 2.0.9; parameter @a start_index since 2.1.1; changed to - consume input adapters, removed start_index parameter, and added - @a strict parameter since 3.0.0; added @a allow_exceptions parameter - since 3.2.0 - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_msgpack(InputType&& i, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - /*! - @copydoc from_msgpack(detail::input_adapter&&, const bool, const bool) - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_msgpack(IteratorType first, IteratorType last, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - - template - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) - static basic_json from_msgpack(const T* ptr, std::size_t len, - const bool strict = true, - const bool allow_exceptions = true) - { - return from_msgpack(ptr, ptr + len, strict, allow_exceptions); - } - - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len)) - static basic_json from_msgpack(detail::span_input_adapter&& i, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = i.get(); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::msgpack, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - - /*! - @brief create a JSON value from an input in UBJSON format - - Deserializes a given input @a i to a JSON value using the UBJSON (Universal - Binary JSON) serialization format. - - The library maps UBJSON types to JSON value types as follows: - - UBJSON type | JSON value type | marker - ----------- | --------------------------------------- | ------ - no-op | *no value, next value is read* | `N` - null | `null` | `Z` - false | `false` | `F` - true | `true` | `T` - float32 | number_float | `d` - float64 | number_float | `D` - uint8 | number_unsigned | `U` - int8 | number_integer | `i` - int16 | number_integer | `I` - int32 | number_integer | `l` - int64 | number_integer | `L` - high-precision number | number_integer, number_unsigned, or number_float - depends on number string | 'H' - string | string | `S` - char | string | `C` - array | array (optimized values are supported) | `[` - object | object (optimized values are supported) | `{` - - @note The mapping is **complete** in the sense that any UBJSON value can - be converted to a JSON value. - - @param[in] i an input in UBJSON format convertible to an input adapter - @param[in] strict whether to expect the input to be consumed until EOF - (true by default) - @param[in] allow_exceptions whether to throw exceptions in case of a - parse error (optional, true by default) - - @return deserialized JSON value; in case of a parse error and - @a allow_exceptions set to `false`, the return value will be - value_t::discarded. - - @throw parse_error.110 if the given input ends prematurely or the end of - file was not reached when @a strict was set to true - @throw parse_error.112 if a parse error occurs - @throw parse_error.113 if a string could not be parsed successfully - - @complexity Linear in the size of the input @a i. - - @liveexample{The example shows the deserialization of a byte vector in - UBJSON format to a JSON value.,from_ubjson} - - @sa http://ubjson.org - @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the - analogous serialization - @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the - related CBOR format - @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for - the related MessagePack format - @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for - the related BSON format - - @since version 3.1.0; added @a allow_exceptions parameter since 3.2.0 - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_ubjson(InputType&& i, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - /*! - @copydoc from_ubjson(detail::input_adapter&&, const bool, const bool) - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_ubjson(IteratorType first, IteratorType last, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - template - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) - static basic_json from_ubjson(const T* ptr, std::size_t len, - const bool strict = true, - const bool allow_exceptions = true) - { - return from_ubjson(ptr, ptr + len, strict, allow_exceptions); - } - - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len)) - static basic_json from_ubjson(detail::span_input_adapter&& i, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = i.get(); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::ubjson, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - - /*! - @brief Create a JSON value from an input in BSON format - - Deserializes a given input @a i to a JSON value using the BSON (Binary JSON) - serialization format. - - The library maps BSON record types to JSON value types as follows: - - BSON type | BSON marker byte | JSON value type - --------------- | ---------------- | --------------------------- - double | 0x01 | number_float - string | 0x02 | string - document | 0x03 | object - array | 0x04 | array - binary | 0x05 | still unsupported - undefined | 0x06 | still unsupported - ObjectId | 0x07 | still unsupported - boolean | 0x08 | boolean - UTC Date-Time | 0x09 | still unsupported - null | 0x0A | null - Regular Expr. | 0x0B | still unsupported - DB Pointer | 0x0C | still unsupported - JavaScript Code | 0x0D | still unsupported - Symbol | 0x0E | still unsupported - JavaScript Code | 0x0F | still unsupported - int32 | 0x10 | number_integer - Timestamp | 0x11 | still unsupported - 128-bit decimal float | 0x13 | still unsupported - Max Key | 0x7F | still unsupported - Min Key | 0xFF | still unsupported - - @warning The mapping is **incomplete**. The unsupported mappings - are indicated in the table above. - - @param[in] i an input in BSON format convertible to an input adapter - @param[in] strict whether to expect the input to be consumed until EOF - (true by default) - @param[in] allow_exceptions whether to throw exceptions in case of a - parse error (optional, true by default) - - @return deserialized JSON value; in case of a parse error and - @a allow_exceptions set to `false`, the return value will be - value_t::discarded. - - @throw parse_error.114 if an unsupported BSON record type is encountered - - @complexity Linear in the size of the input @a i. - - @liveexample{The example shows the deserialization of a byte vector in - BSON format to a JSON value.,from_bson} - - @sa http://bsonspec.org/spec.html - @sa @ref to_bson(const basic_json&) for the analogous serialization - @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool, const cbor_tag_handler_t) for the - related CBOR format - @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for - the related MessagePack format - @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the - related UBJSON format - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bson(InputType&& i, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = detail::input_adapter(std::forward(i)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - /*! - @copydoc from_bson(detail::input_adapter&&, const bool, const bool) - */ - template - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json from_bson(IteratorType first, IteratorType last, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = detail::input_adapter(std::move(first), std::move(last)); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - - template - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) - static basic_json from_bson(const T* ptr, std::size_t len, - const bool strict = true, - const bool allow_exceptions = true) - { - return from_bson(ptr, ptr + len, strict, allow_exceptions); - } - - JSON_HEDLEY_WARN_UNUSED_RESULT - JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len)) - static basic_json from_bson(detail::span_input_adapter&& i, - const bool strict = true, - const bool allow_exceptions = true) - { - basic_json result; - detail::json_sax_dom_parser sdp(result, allow_exceptions); - auto ia = i.get(); - const bool res = binary_reader(std::move(ia)).sax_parse(input_format_t::bson, &sdp, strict); - return res ? result : basic_json(value_t::discarded); - } - /// @} - - ////////////////////////// - // JSON Pointer support // - ////////////////////////// - - /// @name JSON Pointer functions - /// @{ - - /*! - @brief access specified element via JSON Pointer - - Uses a JSON pointer to retrieve a reference to the respective JSON value. - No bound checking is performed. Similar to @ref operator[](const typename - object_t::key_type&), `null` values are created in arrays and objects if - necessary. - - In particular: - - If the JSON pointer points to an object key that does not exist, it - is created an filled with a `null` value before a reference to it - is returned. - - If the JSON pointer points to an array index that does not exist, it - is created an filled with a `null` value before a reference to it - is returned. All indices between the current maximum and the given - index are also filled with `null`. - - The special value `-` is treated as a synonym for the index past the - end. - - @param[in] ptr a JSON pointer - - @return reference to the element pointed to by @a ptr - - @complexity Constant. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.404 if the JSON pointer can not be resolved - - @liveexample{The behavior is shown in the example.,operatorjson_pointer} - - @since version 2.0.0 - */ - reference operator[](const json_pointer& ptr) - { - return ptr.get_unchecked(this); - } - - /*! - @brief access specified element via JSON Pointer - - Uses a JSON pointer to retrieve a reference to the respective JSON value. - No bound checking is performed. The function does not change the JSON - value; no `null` values are created. In particular, the special value - `-` yields an exception. - - @param[in] ptr JSON pointer to the desired element - - @return const reference to the element pointed to by @a ptr - - @complexity Constant. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - - @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} - - @since version 2.0.0 - */ - const_reference operator[](const json_pointer& ptr) const - { - return ptr.get_unchecked(this); - } - - /*! - @brief access specified element via JSON Pointer - - Returns a reference to the element at with specified JSON pointer @a ptr, - with bounds checking. - - @param[in] ptr JSON pointer to the desired element - - @return reference to the element pointed to by @a ptr - - @throw parse_error.106 if an array index in the passed JSON pointer @a ptr - begins with '0'. See example below. - - @throw parse_error.109 if an array index in the passed JSON pointer @a ptr - is not a number. See example below. - - @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr - is out of range. See example below. - - @throw out_of_range.402 if the array index '-' is used in the passed JSON - pointer @a ptr. As `at` provides checked access (and no elements are - implicitly inserted), the index '-' is always invalid. See example below. - - @throw out_of_range.403 if the JSON pointer describes a key of an object - which cannot be found. See example below. - - @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. - See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @since version 2.0.0 - - @liveexample{The behavior is shown in the example.,at_json_pointer} - */ - reference at(const json_pointer& ptr) - { - return ptr.get_checked(this); - } - - /*! - @brief access specified element via JSON Pointer - - Returns a const reference to the element at with specified JSON pointer @a - ptr, with bounds checking. - - @param[in] ptr JSON pointer to the desired element - - @return reference to the element pointed to by @a ptr - - @throw parse_error.106 if an array index in the passed JSON pointer @a ptr - begins with '0'. See example below. - - @throw parse_error.109 if an array index in the passed JSON pointer @a ptr - is not a number. See example below. - - @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr - is out of range. See example below. - - @throw out_of_range.402 if the array index '-' is used in the passed JSON - pointer @a ptr. As `at` provides checked access (and no elements are - implicitly inserted), the index '-' is always invalid. See example below. - - @throw out_of_range.403 if the JSON pointer describes a key of an object - which cannot be found. See example below. - - @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. - See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @since version 2.0.0 - - @liveexample{The behavior is shown in the example.,at_json_pointer_const} - */ - const_reference at(const json_pointer& ptr) const - { - return ptr.get_checked(this); - } - - /*! - @brief return flattened JSON value - - The function creates a JSON object whose keys are JSON pointers (see [RFC - 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all - primitive. The original JSON value can be restored using the @ref - unflatten() function. - - @return an object that maps JSON pointers to primitive values - - @note Empty objects and arrays are flattened to `null` and will not be - reconstructed correctly by the @ref unflatten() function. - - @complexity Linear in the size the JSON value. - - @liveexample{The following code shows how a JSON object is flattened to an - object whose keys consist of JSON pointers.,flatten} - - @sa @ref unflatten() for the reverse function - - @since version 2.0.0 - */ - basic_json flatten() const - { - basic_json result(value_t::object); - json_pointer::flatten("", *this, result); - return result; - } - - /*! - @brief unflatten a previously flattened JSON value - - The function restores the arbitrary nesting of a JSON value that has been - flattened before using the @ref flatten() function. The JSON value must - meet certain constraints: - 1. The value must be an object. - 2. The keys must be JSON pointers (see - [RFC 6901](https://tools.ietf.org/html/rfc6901)) - 3. The mapped values must be primitive JSON types. - - @return the original JSON from a flattened version - - @note Empty objects and arrays are flattened by @ref flatten() to `null` - values and can not unflattened to their original type. Apart from - this example, for a JSON value `j`, the following is always true: - `j == j.flatten().unflatten()`. - - @complexity Linear in the size the JSON value. - - @throw type_error.314 if value is not an object - @throw type_error.315 if object values are not primitive - - @liveexample{The following code shows how a flattened JSON object is - unflattened into the original nested JSON object.,unflatten} - - @sa @ref flatten() for the reverse function - - @since version 2.0.0 - */ - basic_json unflatten() const - { - return json_pointer::unflatten(*this); - } - - /// @} - - ////////////////////////// - // JSON Patch functions // - ////////////////////////// - - /// @name JSON Patch functions - /// @{ - - /*! - @brief applies a JSON patch - - [JSON Patch](http://jsonpatch.com) defines a JSON document structure for - expressing a sequence of operations to apply to a JSON) document. With - this function, a JSON Patch is applied to the current JSON value by - executing all operations from the patch. - - @param[in] json_patch JSON patch document - @return patched document - - @note The application of a patch is atomic: Either all operations succeed - and the patched document is returned or an exception is thrown. In - any case, the original value is not changed: the patch is applied - to a copy of the value. - - @throw parse_error.104 if the JSON patch does not consist of an array of - objects - - @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory - attributes are missing); example: `"operation add must have member path"` - - @throw out_of_range.401 if an array index is out of range. - - @throw out_of_range.403 if a JSON pointer inside the patch could not be - resolved successfully in the current JSON value; example: `"key baz not - found"` - - @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", - "move") - - @throw other_error.501 if "test" operation was unsuccessful - - @complexity Linear in the size of the JSON value and the length of the - JSON patch. As usually only a fraction of the JSON value is affected by - the patch, the complexity can usually be neglected. - - @liveexample{The following code shows how a JSON patch is applied to a - value.,patch} - - @sa @ref diff -- create a JSON patch by comparing two JSON values - - @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) - @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) - - @since version 2.0.0 - */ - basic_json patch(const basic_json& json_patch) const - { - // make a working copy to apply the patch to - basic_json result = *this; - - // the valid JSON Patch operations - enum class patch_operations {add, remove, replace, move, copy, test, invalid}; - - const auto get_op = [](const std::string & op) - { - if (op == "add") - { - return patch_operations::add; - } - if (op == "remove") - { - return patch_operations::remove; - } - if (op == "replace") - { - return patch_operations::replace; - } - if (op == "move") - { - return patch_operations::move; - } - if (op == "copy") - { - return patch_operations::copy; - } - if (op == "test") - { - return patch_operations::test; - } - - return patch_operations::invalid; - }; - - // wrapper for "add" operation; add value at ptr - const auto operation_add = [&result](json_pointer & ptr, basic_json val) - { - // adding to the root of the target document means replacing it - if (ptr.empty()) - { - result = val; - return; - } - - // make sure the top element of the pointer exists - json_pointer top_pointer = ptr.top(); - if (top_pointer != ptr) - { - result.at(top_pointer); - } - - // get reference to parent of JSON pointer ptr - const auto last_path = ptr.back(); - ptr.pop_back(); - basic_json& parent = result[ptr]; - - switch (parent.m_type) - { - case value_t::null: - case value_t::object: - { - // use operator[] to add value - parent[last_path] = val; - break; - } - - case value_t::array: - { - if (last_path == "-") - { - // special case: append to back - parent.push_back(val); - } - else - { - const auto idx = json_pointer::array_index(last_path); - if (JSON_HEDLEY_UNLIKELY(idx > parent.size())) - { - // avoid undefined behavior - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - - // default case: insert add offset - parent.insert(parent.begin() + static_cast(idx), val); - } - break; - } - - // if there exists a parent it cannot be primitive - default: // LCOV_EXCL_LINE - JSON_ASSERT(false); // LCOV_EXCL_LINE - } - }; - - // wrapper for "remove" operation; remove value at ptr - const auto operation_remove = [&result](json_pointer & ptr) - { - // get reference to parent of JSON pointer ptr - const auto last_path = ptr.back(); - ptr.pop_back(); - basic_json& parent = result.at(ptr); - - // remove child - if (parent.is_object()) - { - // perform range check - auto it = parent.find(last_path); - if (JSON_HEDLEY_LIKELY(it != parent.end())) - { - parent.erase(it); - } - else - { - JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); - } - } - else if (parent.is_array()) - { - // note erase performs range check - parent.erase(json_pointer::array_index(last_path)); - } - }; - - // type check: top level value must be an array - if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array())) - { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); - } - - // iterate and apply the operations - for (const auto& val : json_patch) - { - // wrapper to get a value for an operation - const auto get_value = [&val](const std::string & op, - const std::string & member, - bool string_type) -> basic_json & - { - // find value - auto it = val.m_value.object->find(member); - - // context-sensitive error message - const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; - - // check if desired value is present - if (JSON_HEDLEY_UNLIKELY(it == val.m_value.object->end())) - { - JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); - } - - // check if result is of type string - if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string())) - { - JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); - } - - // no error: return value - return it->second; - }; - - // type check: every element of the array must be an object - if (JSON_HEDLEY_UNLIKELY(!val.is_object())) - { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); - } - - // collect mandatory members - const auto op = get_value("op", "op", true).template get(); - const auto path = get_value(op, "path", true).template get(); - json_pointer ptr(path); - - switch (get_op(op)) - { - case patch_operations::add: - { - operation_add(ptr, get_value("add", "value", false)); - break; - } - - case patch_operations::remove: - { - operation_remove(ptr); - break; - } - - case patch_operations::replace: - { - // the "path" location must exist - use at() - result.at(ptr) = get_value("replace", "value", false); - break; - } - - case patch_operations::move: - { - const auto from_path = get_value("move", "from", true).template get(); - json_pointer from_ptr(from_path); - - // the "from" location must exist - use at() - basic_json v = result.at(from_ptr); - - // The move operation is functionally identical to a - // "remove" operation on the "from" location, followed - // immediately by an "add" operation at the target - // location with the value that was just removed. - operation_remove(from_ptr); - operation_add(ptr, v); - break; - } - - case patch_operations::copy: - { - const auto from_path = get_value("copy", "from", true).template get(); - const json_pointer from_ptr(from_path); - - // the "from" location must exist - use at() - basic_json v = result.at(from_ptr); - - // The copy is functionally identical to an "add" - // operation at the target location using the value - // specified in the "from" member. - operation_add(ptr, v); - break; - } - - case patch_operations::test: - { - bool success = false; - JSON_TRY - { - // check if "value" matches the one at "path" - // the "path" location must exist - use at() - success = (result.at(ptr) == get_value("test", "value", false)); - } - JSON_INTERNAL_CATCH (out_of_range&) - { - // ignore out of range errors: success remains false - } - - // throw an exception if test fails - if (JSON_HEDLEY_UNLIKELY(!success)) - { - JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); - } - - break; - } - - default: - { - // op must be "add", "remove", "replace", "move", "copy", or - // "test" - JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); - } - } - } - - return result; - } - - /*! - @brief creates a diff as a JSON patch - - Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can - be changed into the value @a target by calling @ref patch function. - - @invariant For two JSON values @a source and @a target, the following code - yields always `true`: - @code {.cpp} - source.patch(diff(source, target)) == target; - @endcode - - @note Currently, only `remove`, `add`, and `replace` operations are - generated. - - @param[in] source JSON value to compare from - @param[in] target JSON value to compare against - @param[in] path helper value to create JSON pointers - - @return a JSON patch to convert the @a source to @a target - - @complexity Linear in the lengths of @a source and @a target. - - @liveexample{The following code shows how a JSON patch is created as a - diff for two JSON values.,diff} - - @sa @ref patch -- apply a JSON patch - @sa @ref merge_patch -- apply a JSON Merge Patch - - @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) - - @since version 2.0.0 - */ - JSON_HEDLEY_WARN_UNUSED_RESULT - static basic_json diff(const basic_json& source, const basic_json& target, - const std::string& path = "") - { - // the patch - basic_json result(value_t::array); - - // if the values are the same, return empty patch - if (source == target) - { - return result; - } - - if (source.type() != target.type()) - { - // different types: replace value - result.push_back( - { - {"op", "replace"}, {"path", path}, {"value", target} - }); - return result; - } - - switch (source.type()) - { - case value_t::array: - { - // first pass: traverse common elements - std::size_t i = 0; - while (i < source.size() && i < target.size()) - { - // recursive call to compare array values at index i - auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); - result.insert(result.end(), temp_diff.begin(), temp_diff.end()); - ++i; - } - - // i now reached the end of at least one array - // in a second pass, traverse the remaining elements - - // remove my remaining elements - const auto end_index = static_cast(result.size()); - while (i < source.size()) - { - // add operations in reverse order to avoid invalid - // indices - result.insert(result.begin() + end_index, object( - { - {"op", "remove"}, - {"path", path + "/" + std::to_string(i)} - })); - ++i; - } - - // add other remaining elements - while (i < target.size()) - { - result.push_back( - { - {"op", "add"}, - {"path", path + "/-"}, - {"value", target[i]} - }); - ++i; - } - - break; - } - - case value_t::object: - { - // first pass: traverse this object's elements - for (auto it = source.cbegin(); it != source.cend(); ++it) - { - // escape the key name to be used in a JSON patch - const auto key = json_pointer::escape(it.key()); - - if (target.find(it.key()) != target.end()) - { - // recursive call to compare object values at key it - auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); - result.insert(result.end(), temp_diff.begin(), temp_diff.end()); - } - else - { - // found a key that is not in o -> remove it - result.push_back(object( - { - {"op", "remove"}, {"path", path + "/" + key} - })); - } - } - - // second pass: traverse other object's elements - for (auto it = target.cbegin(); it != target.cend(); ++it) - { - if (source.find(it.key()) == source.end()) - { - // found a key that is not in this -> add it - const auto key = json_pointer::escape(it.key()); - result.push_back( - { - {"op", "add"}, {"path", path + "/" + key}, - {"value", it.value()} - }); - } - } - - break; - } - - default: - { - // both primitive type: replace value - result.push_back( - { - {"op", "replace"}, {"path", path}, {"value", target} - }); - break; - } - } - - return result; - } - - /// @} - - //////////////////////////////// - // JSON Merge Patch functions // - //////////////////////////////// - - /// @name JSON Merge Patch functions - /// @{ - - /*! - @brief applies a JSON Merge Patch - - The merge patch format is primarily intended for use with the HTTP PATCH - method as a means of describing a set of modifications to a target - resource's content. This function applies a merge patch to the current - JSON value. - - The function implements the following algorithm from Section 2 of - [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): - - ``` - define MergePatch(Target, Patch): - if Patch is an Object: - if Target is not an Object: - Target = {} // Ignore the contents and set it to an empty Object - for each Name/Value pair in Patch: - if Value is null: - if Name exists in Target: - remove the Name/Value pair from Target - else: - Target[Name] = MergePatch(Target[Name], Value) - return Target - else: - return Patch - ``` - - Thereby, `Target` is the current object; that is, the patch is applied to - the current value. - - @param[in] apply_patch the patch to apply - - @complexity Linear in the lengths of @a patch. - - @liveexample{The following code shows how a JSON Merge Patch is applied to - a JSON document.,merge_patch} - - @sa @ref patch -- apply a JSON patch - @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) - - @since version 3.0.0 - */ - void merge_patch(const basic_json& apply_patch) - { - if (apply_patch.is_object()) - { - if (!is_object()) - { - *this = object(); - } - for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it) - { - if (it.value().is_null()) - { - erase(it.key()); - } - else - { - operator[](it.key()).merge_patch(it.value()); - } - } - } - else - { - *this = apply_patch; - } - } - - /// @} -}; - -/*! -@brief user-defined to_string function for JSON values - -This function implements a user-defined to_string for JSON objects. - -@param[in] j a JSON object -@return a std::string object -*/ - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -std::string to_string(const NLOHMANN_BASIC_JSON_TPL& j) -{ - return j.dump(); -} -} // namespace nlohmann - -/////////////////////// -// nonmember support // -/////////////////////// - -// specialization of std::swap, and std::hash -namespace std -{ - -/// hash value for JSON objects -template<> -struct hash -{ - /*! - @brief return a hash value for a JSON object - - @since version 1.0.0 - */ - std::size_t operator()(const nlohmann::json& j) const - { - return nlohmann::detail::hash(j); - } -}; - -/// specialization for std::less -/// @note: do not remove the space after '<', -/// see https://github.com/nlohmann/json/pull/679 -template<> -struct less<::nlohmann::detail::value_t> -{ - /*! - @brief compare two value_t enum values - @since version 3.0.0 - */ - bool operator()(nlohmann::detail::value_t lhs, - nlohmann::detail::value_t rhs) const noexcept - { - return nlohmann::detail::operator<(lhs, rhs); - } -}; - -// C++20 prohibit function specialization in the std namespace. -#ifndef JSON_HAS_CPP_20 - -/*! -@brief exchanges the values of two JSON objects - -@since version 1.0.0 -*/ -template<> -inline void swap(nlohmann::json& j1, nlohmann::json& j2) noexcept( - is_nothrow_move_constructible::value&& - is_nothrow_move_assignable::value - ) -{ - j1.swap(j2); -} - -#endif - -} // namespace std - -/*! -@brief user-defined string literal for JSON values - -This operator implements a user-defined string literal for JSON objects. It -can be used by adding `"_json"` to a string literal and returns a JSON object -if no parse error occurred. - -@param[in] s a string representation of a JSON object -@param[in] n the length of string @a s -@return a JSON object - -@since version 1.0.0 -*/ -JSON_HEDLEY_NON_NULL(1) -inline nlohmann::json operator "" _json(const char* s, std::size_t n) -{ - return nlohmann::json::parse(s, s + n); -} - -/*! -@brief user-defined string literal for JSON pointer - -This operator implements a user-defined string literal for JSON Pointers. It -can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer -object if no parse error occurred. - -@param[in] s a string representation of a JSON Pointer -@param[in] n the length of string @a s -@return a JSON pointer object - -@since version 2.0.0 -*/ -JSON_HEDLEY_NON_NULL(1) -inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) -{ - return nlohmann::json::json_pointer(std::string(s, n)); -} - -// #include - - -// restore GCC/clang diagnostic settings -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #pragma GCC diagnostic pop -#endif -#if defined(__clang__) - #pragma GCC diagnostic pop -#endif - -// clean up -#undef JSON_ASSERT -#undef JSON_INTERNAL_CATCH -#undef JSON_CATCH -#undef JSON_THROW -#undef JSON_TRY -#undef JSON_HAS_CPP_14 -#undef JSON_HAS_CPP_17 -#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION -#undef NLOHMANN_BASIC_JSON_TPL -#undef JSON_EXPLICIT - -// #include -#undef JSON_HEDLEY_ALWAYS_INLINE -#undef JSON_HEDLEY_ARM_VERSION -#undef JSON_HEDLEY_ARM_VERSION_CHECK -#undef JSON_HEDLEY_ARRAY_PARAM -#undef JSON_HEDLEY_ASSUME -#undef JSON_HEDLEY_BEGIN_C_DECLS -#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE -#undef JSON_HEDLEY_CLANG_HAS_BUILTIN -#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE -#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE -#undef JSON_HEDLEY_CLANG_HAS_EXTENSION -#undef JSON_HEDLEY_CLANG_HAS_FEATURE -#undef JSON_HEDLEY_CLANG_HAS_WARNING -#undef JSON_HEDLEY_COMPCERT_VERSION -#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK -#undef JSON_HEDLEY_CONCAT -#undef JSON_HEDLEY_CONCAT3 -#undef JSON_HEDLEY_CONCAT3_EX -#undef JSON_HEDLEY_CONCAT_EX -#undef JSON_HEDLEY_CONST -#undef JSON_HEDLEY_CONSTEXPR -#undef JSON_HEDLEY_CONST_CAST -#undef JSON_HEDLEY_CPP_CAST -#undef JSON_HEDLEY_CRAY_VERSION -#undef JSON_HEDLEY_CRAY_VERSION_CHECK -#undef JSON_HEDLEY_C_DECL -#undef JSON_HEDLEY_DEPRECATED -#undef JSON_HEDLEY_DEPRECATED_FOR -#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL -#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ -#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED -#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES -#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS -#undef JSON_HEDLEY_DIAGNOSTIC_POP -#undef JSON_HEDLEY_DIAGNOSTIC_PUSH -#undef JSON_HEDLEY_DMC_VERSION -#undef JSON_HEDLEY_DMC_VERSION_CHECK -#undef JSON_HEDLEY_EMPTY_BASES -#undef JSON_HEDLEY_EMSCRIPTEN_VERSION -#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK -#undef JSON_HEDLEY_END_C_DECLS -#undef JSON_HEDLEY_FLAGS -#undef JSON_HEDLEY_FLAGS_CAST -#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE -#undef JSON_HEDLEY_GCC_HAS_BUILTIN -#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE -#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE -#undef JSON_HEDLEY_GCC_HAS_EXTENSION -#undef JSON_HEDLEY_GCC_HAS_FEATURE -#undef JSON_HEDLEY_GCC_HAS_WARNING -#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK -#undef JSON_HEDLEY_GCC_VERSION -#undef JSON_HEDLEY_GCC_VERSION_CHECK -#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE -#undef JSON_HEDLEY_GNUC_HAS_BUILTIN -#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE -#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE -#undef JSON_HEDLEY_GNUC_HAS_EXTENSION -#undef JSON_HEDLEY_GNUC_HAS_FEATURE -#undef JSON_HEDLEY_GNUC_HAS_WARNING -#undef JSON_HEDLEY_GNUC_VERSION -#undef JSON_HEDLEY_GNUC_VERSION_CHECK -#undef JSON_HEDLEY_HAS_ATTRIBUTE -#undef JSON_HEDLEY_HAS_BUILTIN -#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE -#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS -#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE -#undef JSON_HEDLEY_HAS_EXTENSION -#undef JSON_HEDLEY_HAS_FEATURE -#undef JSON_HEDLEY_HAS_WARNING -#undef JSON_HEDLEY_IAR_VERSION -#undef JSON_HEDLEY_IAR_VERSION_CHECK -#undef JSON_HEDLEY_IBM_VERSION -#undef JSON_HEDLEY_IBM_VERSION_CHECK -#undef JSON_HEDLEY_IMPORT -#undef JSON_HEDLEY_INLINE -#undef JSON_HEDLEY_INTEL_VERSION -#undef JSON_HEDLEY_INTEL_VERSION_CHECK -#undef JSON_HEDLEY_IS_CONSTANT -#undef JSON_HEDLEY_IS_CONSTEXPR_ -#undef JSON_HEDLEY_LIKELY -#undef JSON_HEDLEY_MALLOC -#undef JSON_HEDLEY_MESSAGE -#undef JSON_HEDLEY_MSVC_VERSION -#undef JSON_HEDLEY_MSVC_VERSION_CHECK -#undef JSON_HEDLEY_NEVER_INLINE -#undef JSON_HEDLEY_NON_NULL -#undef JSON_HEDLEY_NO_ESCAPE -#undef JSON_HEDLEY_NO_RETURN -#undef JSON_HEDLEY_NO_THROW -#undef JSON_HEDLEY_NULL -#undef JSON_HEDLEY_PELLES_VERSION -#undef JSON_HEDLEY_PELLES_VERSION_CHECK -#undef JSON_HEDLEY_PGI_VERSION -#undef JSON_HEDLEY_PGI_VERSION_CHECK -#undef JSON_HEDLEY_PREDICT -#undef JSON_HEDLEY_PRINTF_FORMAT -#undef JSON_HEDLEY_PRIVATE -#undef JSON_HEDLEY_PUBLIC -#undef JSON_HEDLEY_PURE -#undef JSON_HEDLEY_REINTERPRET_CAST -#undef JSON_HEDLEY_REQUIRE -#undef JSON_HEDLEY_REQUIRE_CONSTEXPR -#undef JSON_HEDLEY_REQUIRE_MSG -#undef JSON_HEDLEY_RESTRICT -#undef JSON_HEDLEY_RETURNS_NON_NULL -#undef JSON_HEDLEY_SENTINEL -#undef JSON_HEDLEY_STATIC_ASSERT -#undef JSON_HEDLEY_STATIC_CAST -#undef JSON_HEDLEY_STRINGIFY -#undef JSON_HEDLEY_STRINGIFY_EX -#undef JSON_HEDLEY_SUNPRO_VERSION -#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK -#undef JSON_HEDLEY_TINYC_VERSION -#undef JSON_HEDLEY_TINYC_VERSION_CHECK -#undef JSON_HEDLEY_TI_ARMCL_VERSION -#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK -#undef JSON_HEDLEY_TI_CL2000_VERSION -#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK -#undef JSON_HEDLEY_TI_CL430_VERSION -#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK -#undef JSON_HEDLEY_TI_CL6X_VERSION -#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK -#undef JSON_HEDLEY_TI_CL7X_VERSION -#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK -#undef JSON_HEDLEY_TI_CLPRU_VERSION -#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK -#undef JSON_HEDLEY_TI_VERSION -#undef JSON_HEDLEY_TI_VERSION_CHECK -#undef JSON_HEDLEY_UNAVAILABLE -#undef JSON_HEDLEY_UNLIKELY -#undef JSON_HEDLEY_UNPREDICTABLE -#undef JSON_HEDLEY_UNREACHABLE -#undef JSON_HEDLEY_UNREACHABLE_RETURN -#undef JSON_HEDLEY_VERSION -#undef JSON_HEDLEY_VERSION_DECODE_MAJOR -#undef JSON_HEDLEY_VERSION_DECODE_MINOR -#undef JSON_HEDLEY_VERSION_DECODE_REVISION -#undef JSON_HEDLEY_VERSION_ENCODE -#undef JSON_HEDLEY_WARNING -#undef JSON_HEDLEY_WARN_UNUSED_RESULT -#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG -#undef JSON_HEDLEY_FALL_THROUGH - - - -#endif // INCLUDE_NLOHMANN_JSON_HPP_ diff --git a/shell_integration/windows/CMakeLists.txt b/shell_integration/windows/CMakeLists.txt index 2c004fd6728..1f194f24151 100644 --- a/shell_integration/windows/CMakeLists.txt +++ b/shell_integration/windows/CMakeLists.txt @@ -1,9 +1,14 @@ -# Use static runtime for all subdirectories -foreach(buildType "" "_DEBUG" "_MINSIZEREL" "_RELEASE" "_RELWITHDEBINFO") - string(REPLACE "/MD" "/MT" "CMAKE_CXX_FLAGS${buildType}" "${CMAKE_CXX_FLAGS${buildType}}") -endforeach() +find_package(nlohmann_json 3 QUIET) -add_subdirectory(OCContextMenu) -add_subdirectory(OCOverlays) -add_subdirectory(OCUtil) +option(WITH_WINDOWS_SHELL_EXTENSIONS "Whether to build the Windows shell extensions" ${nlohmann_json_FOUND}) +add_feature_info("Windows shell extensions" WITH_WINDOWS_SHELL_EXTENSIONS "") +if (WITH_WINDOWS_SHELL_EXTENSIONS) + find_package(nlohmann_json 3 REQUIRED) + # Use static runtime for all subdirectories + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + + add_subdirectory(OCContextMenu) + add_subdirectory(OCOverlays) + add_subdirectory(OCUtil) +endif() diff --git a/shell_integration/windows/OCContextMenu/CMakeLists.txt b/shell_integration/windows/OCContextMenu/CMakeLists.txt index f84ed47214d..f76ce37ae72 100644 --- a/shell_integration/windows/OCContextMenu/CMakeLists.txt +++ b/shell_integration/windows/OCContextMenu/CMakeLists.txt @@ -4,15 +4,17 @@ add_library(OCContextMenu MODULE OCContextMenu.cpp OCContextMenuFactory.cpp OCContextMenuRegHandler.cpp - OCContextMenu.rc OCContextMenu.def ) target_link_libraries(OCContextMenu OCUtil crypt32 - gdiplus) + gdiplus + nlohmann_json::nlohmann_json +) target_compile_definitions(OCContextMenu PRIVATE JSON_NOEXCEPTION) +add_windows_version_info(OCContextMenu) install(TARGETS OCContextMenu RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/shell_integration/windows/OCContextMenu/OCClientInterface.cpp b/shell_integration/windows/OCContextMenu/OCClientInterface.cpp index 5bf7d125568..ec493dae296 100644 --- a/shell_integration/windows/OCContextMenu/OCClientInterface.cpp +++ b/shell_integration/windows/OCContextMenu/OCClientInterface.cpp @@ -15,6 +15,7 @@ #include "OCClientInterface.h" #include "CommunicationSocket.h" +#include "Log.h" #include "StringUtil.h" #include @@ -35,12 +36,11 @@ using std::min; #include using namespace std; -#include #include #include #include -#include "../3rdparty/nlohmann-json/json.hpp" +#include "nlohmann/json.hpp" using Microsoft::WRL::ComPtr; @@ -48,29 +48,14 @@ using Microsoft::WRL::ComPtr; namespace { -template -void log(const wstring &msg, const T &error = {}) -{ - wstringstream tmp; - tmp << L"ownCloud: " << msg; - if (!error.empty()) { - tmp << L" " << error.data(); - } - OutputDebugStringW(tmp.str().data()); -} -void logWinError(const wstring &msg, const DWORD &error = GetLastError()) -{ - log(msg, wstring(_com_error(error).ErrorMessage())); -} - -void sendV2(const CommunicationSocket &socket, const wstring &command, const nlohmann::json &j) +bool sendV2(const CommunicationSocket &socket, const wstring &command, const nlohmann::json &j) { static int messageId = 0; const nlohmann::json json { { "id", to_string(messageId++) }, { "arguments", j } }; const auto data = json.dump(); wstringstream tmp; tmp << command << L":" << StringUtil::toUtf16(data.data(), data.size()) << L"\n"; - socket.SendMsg(tmp.str()); + return socket.SendMsg(tmp.str()); } pair parseV2(const wstring &data) @@ -91,19 +76,19 @@ std::shared_ptr saveImage(const string &data) std::vector buf(size, 0); DWORD skipped; if (!CryptStringToBinaryA(data.data(), 0, CRYPT_STRING_BASE64, buf.data(), &size, &skipped, nullptr)) { - logWinError(L"Failed to decode icon"); + OCShell::logWinError(L"Failed to decode icon"); return {}; } ComPtr stream = SHCreateMemStream(buf.data(), size); if (!stream) { - log(L"Failed to create stream"); + OCShell::log(L"Failed to create stream"); return {}; }; HBITMAP result; Gdiplus::Bitmap bitmap(stream.Get(), true); const auto status = bitmap.GetHBITMAP(0, &result); if (status != Gdiplus::Ok) { - log(L"Failed to get HBITMAP", to_wstring(status)); + OCShell::log(L"Failed to get HBITMAP", to_wstring(status)); return {}; } return std::shared_ptr { new HBITMAP(result), [gdiplusToken](auto o) { @@ -119,16 +104,29 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo(const std::wstri CommunicationSocket socket; if (!WaitNamedPipe(pipename.data(), PIPE_TIMEOUT)) { + OCShell::logWinError(L"OCClientInterface::FetchInfo: Failed to connect to " + pipename); return {}; } if (!socket.Connect(pipename)) { + OCShell::log(L"OCClientInterface::FetchInfo: Failed to connect to " + pipename); + return {}; + } + bool ok = sendV2(socket, L"V2/GET_CLIENT_ICON", { { "size", 16 } }) + && socket.SendMsg(L"GET_STRINGS:CONTEXT_MENU_TITLE\n") + && socket.SendMsg(L"GET_MENU_ITEMS:" + files + L"\n"); + + if (!ok) { + socket.Close(); + OCShell::log(L"OCClientInterface::FetchInfo: Failed to request the context menu"); return {}; } - sendV2(socket, L"V2/GET_CLIENT_ICON", { { "size", 16 } }); - socket.SendMsg(L"GET_STRINGS:CONTEXT_MENU_TITLE\n"); - socket.SendMsg(L"GET_MENU_ITEMS:" + files + L"\n"); + ContextMenuInfo info; + bool endReceived = false; + bool iconReceived = false; + auto ready = [&] { return endReceived && iconReceived; }; + std::wstring response; int sleptCount = 0; while (sleptCount < 5) { @@ -137,8 +135,9 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo(const std::wstri const auto msg = parseV2(response); const auto &arguments = msg.second["arguments"]; if (msg.first == L"V2/GET_CLIENT_ICON_RESULT") { + iconReceived = true; if (arguments.contains("error")) { - log(L"V2/GET_CLIENT_ICON failed", arguments["error"].get()); + OCShell::log(L"V2/GET_CLIENT_ICON failed", arguments["error"].get()); } else { info.icon = saveImage(arguments["png"].get()); } @@ -149,17 +148,23 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo(const std::wstri info.watchedDirectories.push_back(responsePath); } else if (StringUtil::begins_with(response, wstring(L"STRING:"))) { wstring stringName, stringValue; - if (!StringUtil::extractChunks(response, stringName, stringValue)) + if (!StringUtil::extractChunks(response, stringName, stringValue)) { continue; - if (stringName == L"CONTEXT_MENU_TITLE") - info.contextMenuTitle = move(stringValue); + } + if (stringName == L"CONTEXT_MENU_TITLE") { + info.contextMenuTitle = std::move(stringValue); + } } else if (StringUtil::begins_with(response, wstring(L"MENU_ITEM:"))) { wstring commandName, flags, title; - if (!StringUtil::extractChunks(response, commandName, flags, title)) + if (!StringUtil::extractChunks(response, commandName, flags, title)) { continue; + } info.menuItems.push_back({ commandName, flags, title }); } else if (StringUtil::begins_with(response, wstring(L"GET_MENU_ITEMS:END"))) { - break; // Stop once we completely received the last sent request + endReceived = true; + } + if (ready()) { + return info; } } else { @@ -167,20 +172,30 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo(const std::wstri ++sleptCount; } } - return info; + if (endReceived && !iconReceived) { + OCShell::log(L"OCClientInterface::FetchInfo: received a menu but no icon"); + return info; + } + + if (!endReceived && iconReceived) { + OCShell::log(L"OCClientInterface::FetchInfo: received a icon but no menu"); + return {}; + } + OCShell::log(L"OCClientInterface::FetchInfo: timeout"); + return {}; } -void OCClientInterface::SendRequest(const wstring &verb, const std::wstring &path) +bool OCClientInterface::SendRequest(const wstring &verb, const std::wstring &path) { auto pipename = CommunicationSocket::DefaultPipePath(); CommunicationSocket socket; if (!WaitNamedPipe(pipename.data(), PIPE_TIMEOUT)) { - return; + return false; } if (!socket.Connect(pipename)) { - return; + return false; } - socket.SendMsg(verb + L":" + path + L"\n"); + return socket.SendMsg(verb + L":" + path + L"\n"); } diff --git a/shell_integration/windows/OCContextMenu/OCClientInterface.h b/shell_integration/windows/OCContextMenu/OCClientInterface.h index e779303cdcd..66a9009eefb 100644 --- a/shell_integration/windows/OCContextMenu/OCClientInterface.h +++ b/shell_integration/windows/OCContextMenu/OCClientInterface.h @@ -48,7 +48,7 @@ class OCClientInterface struct ContextMenuInfo { std::vector watchedDirectories; std::wstring contextMenuTitle; - std::shared_ptr icon; + std::shared_ptr icon = {}; struct MenuItem { std::wstring command, flags, title; @@ -56,7 +56,7 @@ class OCClientInterface std::vector menuItems; }; static ContextMenuInfo FetchInfo(const std::wstring &files); - static void SendRequest(const std::wstring &verb, const std::wstring &path); + [[nodiscard]] static bool SendRequest(const std::wstring &verb, const std::wstring &path); }; #endif //ABSTRACTSOCKETHANDLER_H diff --git a/shell_integration/windows/OCContextMenu/OCContextMenu.cpp b/shell_integration/windows/OCContextMenu/OCContextMenu.cpp index 5d06f42b621..8b8cb70927e 100644 --- a/shell_integration/windows/OCContextMenu/OCContextMenu.cpp +++ b/shell_integration/windows/OCContextMenu/OCContextMenu.cpp @@ -39,11 +39,10 @@ OCContextMenu::~OCContextMenu(void) // Query to the interface the component supported. IFACEMETHODIMP OCContextMenu::QueryInterface(REFIID riid, void **ppv) { - static const QITAB qit[] = - { + static const QITAB qit[] = { QITABENT(OCContextMenu, IContextMenu), QITABENT(OCContextMenu, IShellExtInit), - { 0 }, + {nullptr}, }; return QISearch(this, qit, riid, ppv); } @@ -80,14 +79,14 @@ IFACEMETHODIMP OCContextMenu::Initialize( return E_INVALIDARG; } - FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + FORMATETC fe = {CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; STGMEDIUM stm; if (SUCCEEDED(pDataObj->GetData(&fe, &stm))) { // Get an HDROP handle. HDROP hDrop = static_cast(GlobalLock(stm.hGlobal)); if (hDrop) { - UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0); + UINT nFiles = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0); for (UINT i = 0; i < nFiles; ++i) { // Get the path of the file. wchar_t buffer[MAX_PATH]; @@ -219,7 +218,7 @@ IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici) return E_FAIL; } - OCClientInterface::SendRequest(command, m_selectedFiles); + std::ignore = OCClientInterface::SendRequest(command, m_selectedFiles); return S_OK; } diff --git a/shell_integration/windows/OCContextMenu/OCContextMenu.rc b/shell_integration/windows/OCContextMenu/OCContextMenu.rc deleted file mode 100644 index 2d1826e232f..00000000000 Binary files a/shell_integration/windows/OCContextMenu/OCContextMenu.rc and /dev/null differ diff --git a/shell_integration/windows/OCContextMenu/OCContextMenuFactory.cpp b/shell_integration/windows/OCContextMenu/OCContextMenuFactory.cpp index 80c68bc37dc..b8d7f5aa588 100644 --- a/shell_integration/windows/OCContextMenu/OCContextMenuFactory.cpp +++ b/shell_integration/windows/OCContextMenu/OCContextMenuFactory.cpp @@ -37,7 +37,10 @@ OCContextMenuFactory::~OCContextMenuFactory() IFACEMETHODIMP OCContextMenuFactory::QueryInterface(REFIID riid, void **ppv) { - static const QITAB qit[] = { QITABENT(OCContextMenuFactory, IClassFactory), { 0 }, }; + static const QITAB qit[] = { + QITABENT(OCContextMenuFactory, IClassFactory), + {nullptr}, + }; return QISearch(this, qit, riid, ppv); } @@ -63,7 +66,7 @@ IFACEMETHODIMP OCContextMenuFactory::CreateInstance(IUnknown *pUnkOuter, REFIID HRESULT hr = CLASS_E_NOAGGREGATION; // pUnkOuter is used for aggregation. We do not support it in the sample. - if (pUnkOuter == NULL) { + if (pUnkOuter == nullptr) { hr = E_OUTOFMEMORY; // Create the COM component. diff --git a/shell_integration/windows/OCContextMenu/OCContextMenuRegHandler.cpp b/shell_integration/windows/OCContextMenu/OCContextMenuRegHandler.cpp index 4b3f482803c..6b4df708d7b 100644 --- a/shell_integration/windows/OCContextMenu/OCContextMenuRegHandler.cpp +++ b/shell_integration/windows/OCContextMenu/OCContextMenuRegHandler.cpp @@ -22,28 +22,24 @@ namespace { HRESULT SetHKCRRegistryKeyAndValue(PCWSTR pszSubKey, PCWSTR pszValueName, PCWSTR pszData) { HRESULT hr; - HKEY hKey = NULL; + HKEY hKey = nullptr; - // Creates the specified registry key. If the key already exists, the - // function opens it. - hr = HRESULT_FROM_WIN32(RegCreateKeyEx(HKEY_CLASSES_ROOT, pszSubKey, 0, - NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL)); + // Creates the specified registry key. If the key already exists, the + // function opens it. + hr = HRESULT_FROM_WIN32(RegCreateKeyEx(HKEY_CLASSES_ROOT, pszSubKey, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &hKey, nullptr)); if (SUCCEEDED(hr)) { DWORD cbData; const BYTE * lpData; - if (pszData != NULL) - { + if (pszData != nullptr) { // Set the specified value of the key. cbData = lstrlen(pszData) * sizeof(*pszData); lpData = reinterpret_cast(pszData); - } - else - { + } else { cbData = 0; - lpData = NULL; + lpData = nullptr; } hr = HRESULT_FROM_WIN32(RegSetValueEx(hKey, pszValueName, 0, @@ -58,7 +54,7 @@ HRESULT SetHKCRRegistryKeyAndValue(PCWSTR pszSubKey, PCWSTR pszValueName, PCWSTR HRESULT GetHKCRRegistryKeyAndValue(PCWSTR pszSubKey, PCWSTR pszValueName, PWSTR pszData, DWORD cbData) { HRESULT hr; - HKEY hKey = NULL; + HKEY hKey = nullptr; // Try to open the specified registry key. hr = HRESULT_FROM_WIN32(RegOpenKeyEx(HKEY_CLASSES_ROOT, pszSubKey, 0, @@ -67,8 +63,7 @@ HRESULT GetHKCRRegistryKeyAndValue(PCWSTR pszSubKey, PCWSTR pszValueName, PWSTR if (SUCCEEDED(hr)) { // Get the data for the specified value name. - hr = HRESULT_FROM_WIN32(RegQueryValueEx(hKey, pszValueName, NULL, - NULL, reinterpret_cast(pszData), &cbData)); + hr = HRESULT_FROM_WIN32(RegQueryValueEx(hKey, pszValueName, nullptr, nullptr, reinterpret_cast(pszData), &cbData)); RegCloseKey(hKey); } @@ -80,8 +75,7 @@ HRESULT GetHKCRRegistryKeyAndValue(PCWSTR pszSubKey, PCWSTR pszValueName, PWSTR HRESULT OCContextMenuRegHandler::RegisterInprocServer(PCWSTR pszModule, const CLSID& clsid, PCWSTR pszFriendlyName, PCWSTR pszThreadModel) { - if (pszModule == NULL || pszThreadModel == NULL) - { + if (pszModule == nullptr || pszThreadModel == nullptr) { return E_INVALIDARG; } @@ -96,11 +90,11 @@ HRESULT OCContextMenuRegHandler::RegisterInprocServer(PCWSTR pszModule, const CL hr = StringCchPrintf(szSubkey, ARRAYSIZE(szSubkey), L"CLSID\\%s", szCLSID); if (SUCCEEDED(hr)) { - hr = SetHKCRRegistryKeyAndValue(szSubkey, NULL, pszFriendlyName); + hr = SetHKCRRegistryKeyAndValue(szSubkey, nullptr, pszFriendlyName); // Create the HKCR\CLSID\{}\ContextMenuOptIn subkey. if (SUCCEEDED(hr)) { - hr = SetHKCRRegistryKeyAndValue(szSubkey, L"ContextMenuOptIn", NULL); + hr = SetHKCRRegistryKeyAndValue(szSubkey, L"ContextMenuOptIn", nullptr); } // Create the HKCR\CLSID\{}\InprocServer32 key. @@ -112,7 +106,7 @@ HRESULT OCContextMenuRegHandler::RegisterInprocServer(PCWSTR pszModule, const CL { // Set the default value of the InprocServer32 key to the // path of the COM module. - hr = SetHKCRRegistryKeyAndValue(szSubkey, NULL, pszModule); + hr = SetHKCRRegistryKeyAndValue(szSubkey, nullptr, pszModule); if (SUCCEEDED(hr)) { // Set the threading model of the component. @@ -149,8 +143,7 @@ HRESULT OCContextMenuRegHandler::UnregisterInprocServer(const CLSID& clsid) HRESULT OCContextMenuRegHandler::RegisterShellExtContextMenuHandler( PCWSTR pszFileType, const CLSID& clsid, PCWSTR pszFriendlyName) { - if (pszFileType == NULL) - { + if (pszFileType == nullptr) { return E_INVALIDARG; } @@ -167,8 +160,7 @@ HRESULT OCContextMenuRegHandler::RegisterShellExtContextMenuHandler( if (*pszFileType == L'.') { wchar_t szDefaultVal[260]; - hr = GetHKCRRegistryKeyAndValue(pszFileType, NULL, szDefaultVal, - sizeof(szDefaultVal)); + hr = GetHKCRRegistryKeyAndValue(pszFileType, nullptr, szDefaultVal, sizeof(szDefaultVal)); // If the key exists and its default value is not empty, use the // ProgID as the file type. @@ -184,7 +176,7 @@ HRESULT OCContextMenuRegHandler::RegisterShellExtContextMenuHandler( if (SUCCEEDED(hr)) { // Set the default value of the key. - hr = SetHKCRRegistryKeyAndValue(szSubkey, NULL, szCLSID); + hr = SetHKCRRegistryKeyAndValue(szSubkey, nullptr, szCLSID); } return hr; @@ -193,8 +185,7 @@ HRESULT OCContextMenuRegHandler::RegisterShellExtContextMenuHandler( HRESULT OCContextMenuRegHandler::UnregisterShellExtContextMenuHandler( PCWSTR pszFileType, PCWSTR pszFriendlyName) { - if (pszFileType == NULL) - { + if (pszFileType == nullptr) { return E_INVALIDARG; } @@ -208,8 +199,7 @@ HRESULT OCContextMenuRegHandler::UnregisterShellExtContextMenuHandler( if (*pszFileType == L'.') { wchar_t szDefaultVal[260]; - hr = GetHKCRRegistryKeyAndValue(pszFileType, NULL, szDefaultVal, - sizeof(szDefaultVal)); + hr = GetHKCRRegistryKeyAndValue(pszFileType, nullptr, szDefaultVal, sizeof(szDefaultVal)); // If the key exists and its default value is not empty, use the // ProgID as the file type. diff --git a/shell_integration/windows/OCContextMenu/RegDelnode.h b/shell_integration/windows/OCContextMenu/RegDelnode.h index 633745b6294..c8838114544 100644 --- a/shell_integration/windows/OCContextMenu/RegDelnode.h +++ b/shell_integration/windows/OCContextMenu/RegDelnode.h @@ -1,8 +1,8 @@ #pragma once -#include #include #include +#include // Stolen from the "Deleting a Key with Subkeys" example to replace // RegDeleteTree which isn't available on WinXP. @@ -24,70 +24,65 @@ HRESULT RegDelnodeRecurse(HKEY hKeyRoot, LPTSTR lpSubKey) { - LPTSTR lpEnd; - LONG lResult; - DWORD dwSize; - TCHAR szName[MAX_PATH]; - HKEY hKey; - FILETIME ftWrite; + LPTSTR lpEnd; + LONG lResult; + DWORD dwSize; + TCHAR szName[MAX_PATH]; + HKEY hKey; + FILETIME ftWrite; - // First, see if we can delete the key without having - // to recurse. + // First, see if we can delete the key without having + // to recurse. - lResult = RegDeleteKey(hKeyRoot, lpSubKey); + lResult = RegDeleteKey(hKeyRoot, lpSubKey); - if (lResult == ERROR_SUCCESS) - return lResult; + if (lResult == ERROR_SUCCESS) + return lResult; - lResult = RegOpenKeyEx(hKeyRoot, lpSubKey, 0, KEY_READ, &hKey); + lResult = RegOpenKeyEx(hKeyRoot, lpSubKey, 0, KEY_READ, &hKey); - if (lResult != ERROR_SUCCESS) - return lResult; + if (lResult != ERROR_SUCCESS) + return lResult; - // Check for an ending slash and add one if it is missing. + // Check for an ending slash and add one if it is missing. - lpEnd = lpSubKey + lstrlen(lpSubKey); + lpEnd = lpSubKey + lstrlen(lpSubKey); - if (*(lpEnd - 1) != TEXT('\\')) - { - *lpEnd = TEXT('\\'); - lpEnd++; - *lpEnd = TEXT('\0'); - } + if (*(lpEnd - 1) != TEXT('\\')) { + *lpEnd = TEXT('\\'); + lpEnd++; + *lpEnd = TEXT('\0'); + } - // Enumerate the keys + // Enumerate the keys - dwSize = MAX_PATH; - lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL, - NULL, NULL, &ftWrite); + dwSize = MAX_PATH; + lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, nullptr, nullptr, nullptr, &ftWrite); - if (lResult == ERROR_SUCCESS) - { - do { + if (lResult == ERROR_SUCCESS) { + do { + StringCchCopy(lpEnd, MAX_PATH * 2, szName); - StringCchCopy(lpEnd, MAX_PATH * 2, szName); + if (RegDelnodeRecurse(hKeyRoot, lpSubKey) != ERROR_SUCCESS) { + break; + } - if (RegDelnodeRecurse(hKeyRoot, lpSubKey) != ERROR_SUCCESS) { - break; - } + dwSize = MAX_PATH; - dwSize = MAX_PATH; + lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, nullptr, nullptr, nullptr, &ftWrite); - lResult = RegEnumKeyEx(hKey, 0, szName, &dwSize, NULL, - NULL, NULL, &ftWrite); + } while (lResult == ERROR_SUCCESS); + } - } while (lResult == ERROR_SUCCESS); - } + lpEnd--; + *lpEnd = TEXT('\0'); - lpEnd--; - *lpEnd = TEXT('\0'); + RegCloseKey(hKey); - RegCloseKey(hKey); + // Try again to delete the key. - // Try again to delete the key. - - lResult = RegDeleteKey(hKeyRoot, lpSubKey); - return lResult; + lResult = RegDeleteKey(hKeyRoot, lpSubKey); + return lResult; } //************************************************************* @@ -106,9 +101,8 @@ HRESULT RegDelnodeRecurse(HKEY hKeyRoot, LPTSTR lpSubKey) HRESULT RegDelnode(HKEY hKeyRoot, LPTSTR lpSubKey) { - TCHAR szDelKey[MAX_PATH * 2]; - - StringCchCopy(szDelKey, MAX_PATH * 2, lpSubKey); - return RegDelnodeRecurse(hKeyRoot, szDelKey); + TCHAR szDelKey[MAX_PATH * 2]; + StringCchCopy(szDelKey, MAX_PATH * 2, lpSubKey); + return RegDelnodeRecurse(hKeyRoot, szDelKey); } diff --git a/shell_integration/windows/OCContextMenu/dllmain.cpp b/shell_integration/windows/OCContextMenu/dllmain.cpp index 1b5c86f1832..cbec822b436 100644 --- a/shell_integration/windows/OCContextMenu/dllmain.cpp +++ b/shell_integration/windows/OCContextMenu/dllmain.cpp @@ -22,7 +22,7 @@ // {841A0AAD-AA11-4B50-84D9-7F8E727D77D7} static const GUID CLSID_FileContextMenuExt = { 0x841a0aad, 0xaa11, 0x4b50, { 0x84, 0xd9, 0x7f, 0x8e, 0x72, 0x7d, 0x77, 0xd7 } }; -HINSTANCE g_hInst = NULL; +HINSTANCE g_hInst = nullptr; long g_cDllRef = 0; BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) diff --git a/shell_integration/windows/OCOverlays/CMakeLists.txt b/shell_integration/windows/OCOverlays/CMakeLists.txt index 60d4a37480b..c0385b95525 100644 --- a/shell_integration/windows/OCOverlays/CMakeLists.txt +++ b/shell_integration/windows/OCOverlays/CMakeLists.txt @@ -10,6 +10,8 @@ add_library(OCOverlays MODULE target_link_libraries(OCOverlays OCUtil) +add_windows_version_info(OCOverlays) + install(TARGETS OCOverlays RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/shell_integration/windows/OCOverlays/DllMain.cpp b/shell_integration/windows/OCOverlays/DllMain.cpp index 52808c7357d..c52f367f575 100644 --- a/shell_integration/windows/OCOverlays/DllMain.cpp +++ b/shell_integration/windows/OCOverlays/DllMain.cpp @@ -16,7 +16,7 @@ #include "OCOverlayFactory.h" #include "OverlayConstants.h" -HINSTANCE instanceHandle = NULL; +HINSTANCE instanceHandle = nullptr; long dllReferenceCount = 0; diff --git a/shell_integration/windows/OCOverlays/OCOverlay.cpp b/shell_integration/windows/OCOverlays/OCOverlay.cpp index bd26a50d8a3..985faec54ca 100644 --- a/shell_integration/windows/OCOverlays/OCOverlay.cpp +++ b/shell_integration/windows/OCOverlays/OCOverlay.cpp @@ -77,7 +77,7 @@ IFACEMETHODIMP OCOverlay::QueryInterface(REFIID riid, void **ppv) else { hr = E_NOINTERFACE; - *ppv = NULL; + *ppv = nullptr; } if (*ppv) diff --git a/shell_integration/windows/OCOverlays/OCOverlay.rc b/shell_integration/windows/OCOverlays/OCOverlay.rc index 5f03bdf418c..19955ec6de0 100644 Binary files a/shell_integration/windows/OCOverlays/OCOverlay.rc and b/shell_integration/windows/OCOverlays/OCOverlay.rc differ diff --git a/shell_integration/windows/OCOverlays/OCOverlayFactory.cpp b/shell_integration/windows/OCOverlays/OCOverlayFactory.cpp index 66e240f9f94..749bfa2099d 100644 --- a/shell_integration/windows/OCOverlays/OCOverlayFactory.cpp +++ b/shell_integration/windows/OCOverlays/OCOverlayFactory.cpp @@ -44,7 +44,7 @@ IFACEMETHODIMP OCOverlayFactory::QueryInterface(REFIID riid, void **ppv) else { hResult = E_NOINTERFACE; - *ppv = NULL; + *ppv = nullptr; } return hResult; @@ -71,7 +71,9 @@ IFACEMETHODIMP OCOverlayFactory::CreateInstance( { HRESULT hResult = CLASS_E_NOAGGREGATION; - if (pUnkOuter != NULL) { return hResult; } + if (pUnkOuter != nullptr) { + return hResult; + } hResult = E_OUTOFMEMORY; OCOverlay *lrOverlay = new (std::nothrow) OCOverlay(_state); diff --git a/shell_integration/windows/OCOverlays/OCOverlayRegistrationHandler.cpp b/shell_integration/windows/OCOverlays/OCOverlayRegistrationHandler.cpp index e830091c704..f95ad27f5f1 100644 --- a/shell_integration/windows/OCOverlays/OCOverlayRegistrationHandler.cpp +++ b/shell_integration/windows/OCOverlays/OCOverlayRegistrationHandler.cpp @@ -25,9 +25,10 @@ using namespace std; HRESULT OCOverlayRegistrationHandler::MakeRegistryEntries(const CLSID& clsid, PCWSTR friendlyName) { HRESULT hResult; - HKEY shellOverlayKey = NULL; + HKEY shellOverlayKey = nullptr; // the key may not exist yet - hResult = HRESULT_FROM_WIN32(RegCreateKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_OVERLAY_KEY, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &shellOverlayKey, NULL)); + hResult = HRESULT_FROM_WIN32( + RegCreateKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_OVERLAY_KEY, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &shellOverlayKey, nullptr)); if (!SUCCEEDED(hResult)) { hResult = RegCreateKey(HKEY_LOCAL_MACHINE, REGISTRY_OVERLAY_KEY, &shellOverlayKey); if(!SUCCEEDED(hResult)) { @@ -35,8 +36,9 @@ HRESULT OCOverlayRegistrationHandler::MakeRegistryEntries(const CLSID& clsid, PC } } - HKEY syncExOverlayKey = NULL; - hResult = HRESULT_FROM_WIN32(RegCreateKeyEx(shellOverlayKey, friendlyName, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &syncExOverlayKey, NULL)); + HKEY syncExOverlayKey = nullptr; + hResult = + HRESULT_FROM_WIN32(RegCreateKeyEx(shellOverlayKey, friendlyName, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &syncExOverlayKey, nullptr)); if (!SUCCEEDED(hResult)) { return hResult; @@ -45,7 +47,7 @@ HRESULT OCOverlayRegistrationHandler::MakeRegistryEntries(const CLSID& clsid, PC wchar_t stringCLSID[MAX_PATH]; StringFromGUID2(clsid, stringCLSID, ARRAYSIZE(stringCLSID)); LPCTSTR value = stringCLSID; - hResult = RegSetValueEx(syncExOverlayKey, NULL, 0, REG_SZ, (LPBYTE)value, (DWORD)((wcslen(value)+1) * sizeof(TCHAR))); + hResult = RegSetValueEx(syncExOverlayKey, nullptr, 0, REG_SZ, (LPBYTE)value, (DWORD)((wcslen(value) + 1) * sizeof(TCHAR))); if (!SUCCEEDED(hResult)) { return hResult; } @@ -56,14 +58,14 @@ HRESULT OCOverlayRegistrationHandler::MakeRegistryEntries(const CLSID& clsid, PC HRESULT OCOverlayRegistrationHandler::RemoveRegistryEntries(PCWSTR friendlyName) { HRESULT hResult; - HKEY shellOverlayKey = NULL; + HKEY shellOverlayKey = nullptr; hResult = HRESULT_FROM_WIN32(RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_OVERLAY_KEY, 0, KEY_WRITE, &shellOverlayKey)); if (!SUCCEEDED(hResult)) { return hResult; } - HKEY syncExOverlayKey = NULL; + HKEY syncExOverlayKey = nullptr; hResult = HRESULT_FROM_WIN32(RegDeleteKey(shellOverlayKey, friendlyName)); if (!SUCCEEDED(hResult)) { return hResult; @@ -74,35 +76,36 @@ HRESULT OCOverlayRegistrationHandler::RemoveRegistryEntries(PCWSTR friendlyName) HRESULT OCOverlayRegistrationHandler::RegisterCOMObject(PCWSTR modulePath, PCWSTR friendlyName, const CLSID& clsid) { - if (modulePath == NULL) { + if (modulePath == nullptr) { return E_FAIL; } wchar_t stringCLSID[MAX_PATH]; StringFromGUID2(clsid, stringCLSID, ARRAYSIZE(stringCLSID)); HRESULT hResult; - HKEY hKey = NULL; + HKEY hKey = nullptr; hResult = HRESULT_FROM_WIN32(RegOpenKeyEx(HKEY_CLASSES_ROOT, REGISTRY_CLSID, 0, KEY_WRITE, &hKey)); if (!SUCCEEDED(hResult)) { return hResult; } - HKEY clsidKey = NULL; - hResult = HRESULT_FROM_WIN32(RegCreateKeyEx(hKey, stringCLSID, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &clsidKey, NULL)); + HKEY clsidKey = nullptr; + hResult = HRESULT_FROM_WIN32(RegCreateKeyEx(hKey, stringCLSID, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &clsidKey, nullptr)); if(!SUCCEEDED(hResult)) { return hResult; } - hResult = HRESULT_FROM_WIN32(RegSetValue(clsidKey, NULL, REG_SZ, friendlyName, (DWORD) wcslen(friendlyName))); + hResult = HRESULT_FROM_WIN32(RegSetValue(clsidKey, nullptr, REG_SZ, friendlyName, (DWORD)wcslen(friendlyName))); - HKEY inprocessKey = NULL; - hResult = HRESULT_FROM_WIN32(RegCreateKeyEx(clsidKey, REGISTRY_IN_PROCESS, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &inprocessKey, NULL)); + HKEY inprocessKey = nullptr; + hResult = + HRESULT_FROM_WIN32(RegCreateKeyEx(clsidKey, REGISTRY_IN_PROCESS, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &inprocessKey, nullptr)); if(!SUCCEEDED(hResult)) { return hResult; } - hResult = HRESULT_FROM_WIN32(RegSetValue(inprocessKey, NULL, REG_SZ, modulePath, (DWORD) wcslen(modulePath))); + hResult = HRESULT_FROM_WIN32(RegSetValue(inprocessKey, nullptr, REG_SZ, modulePath, (DWORD)wcslen(modulePath))); if(!SUCCEEDED(hResult)) { return hResult; @@ -127,13 +130,13 @@ HRESULT OCOverlayRegistrationHandler::UnregisterCOMObject(const CLSID& clsid) StringFromGUID2(clsid, stringCLSID, ARRAYSIZE(stringCLSID)); HRESULT hResult; - HKEY hKey = NULL; + HKEY hKey = nullptr; hResult = HRESULT_FROM_WIN32(RegOpenKeyEx(HKEY_CLASSES_ROOT, REGISTRY_CLSID, 0, DELETE, &hKey)); if (!SUCCEEDED(hResult)) { return hResult; } - HKEY clsidKey = NULL; + HKEY clsidKey = nullptr; hResult = HRESULT_FROM_WIN32(RegOpenKeyEx(hKey, stringCLSID, 0, DELETE, &clsidKey)); if(!SUCCEEDED(hResult)) { return hResult; diff --git a/shell_integration/windows/OCUtil/CMakeLists.txt b/shell_integration/windows/OCUtil/CMakeLists.txt index 13a9f6f5389..5af991a4ce3 100644 --- a/shell_integration/windows/OCUtil/CMakeLists.txt +++ b/shell_integration/windows/OCUtil/CMakeLists.txt @@ -2,8 +2,7 @@ add_library(OCUtil STATIC CommunicationSocket.cpp RemotePathChecker.cpp StringUtil.cpp - OCUtil.rc -) + ) target_include_directories(OCUtil PUBLIC diff --git a/shell_integration/windows/OCUtil/CommunicationSocket.cpp b/shell_integration/windows/OCUtil/CommunicationSocket.cpp index d66b03bdec4..d2a26099d03 100644 --- a/shell_integration/windows/OCUtil/CommunicationSocket.cpp +++ b/shell_integration/windows/OCUtil/CommunicationSocket.cpp @@ -13,8 +13,9 @@ */ #include "CommunicationSocket.h" -#include "UtilConstants.h" +#include "Log.h" #include "StringUtil.h" +#include "UtilConstants.h" #include #include @@ -28,6 +29,8 @@ using namespace std; namespace { +constexpr DWORD timeoutC = 100; + std::wstring getUserName() { DWORD len = DEFAULT_BUFLEN; TCHAR buf[DEFAULT_BUFLEN]; @@ -51,6 +54,7 @@ std::wstring CommunicationSocket::DefaultPipePath() CommunicationSocket::CommunicationSocket() : _pipe(INVALID_HANDLE_VALUE) { + _overlapped.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); } CommunicationSocket::~CommunicationSocket() @@ -71,7 +75,7 @@ bool CommunicationSocket::Close() bool CommunicationSocket::Connect(const std::wstring &pipename) { - _pipe = CreateFile(pipename.data(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + _pipe = CreateFile(pipename.data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr); if (_pipe == INVALID_HANDLE_VALUE) { return false; @@ -85,23 +89,25 @@ bool CommunicationSocket::SendMsg(const wstring &message) const auto utf8_msg = StringUtil::toUtf8(message.data(), message.size()); DWORD numBytesWritten = 0; - auto result = WriteFile(_pipe, utf8_msg.c_str(), static_cast(utf8_msg.size()), &numBytesWritten, NULL); - if (result) { - return true; - } else { - const_cast(this)->Close(); - - return false; + if (!WriteFile(_pipe, utf8_msg.c_str(), static_cast(utf8_msg.size()), &numBytesWritten, &_overlapped)) { + if (GetLastError() == ERROR_IO_PENDING) { + if (WaitForSingleObject(_overlapped.hEvent, timeoutC) != WAIT_OBJECT_0) { + OCShell::logWinError(L"SendMsg timed out"); + return false; + } + if (!GetOverlappedResult(_pipe, &_overlapped, &numBytesWritten, FALSE)) { + OCShell::logWinError(L"GetOverlappedResult failed"); + return false; + } + } } + return true; } -bool CommunicationSocket::ReadLine(wstring* response) +bool CommunicationSocket::ReadLine(wstring *response) const { - if (!response) { - return false; - } - + assert(response); response->clear(); if (_pipe == INVALID_HANDLE_VALUE) { @@ -121,17 +127,25 @@ bool CommunicationSocket::ReadLine(wstring* response) DWORD numBytesRead = 0; DWORD totalBytesAvailable = 0; - if (!PeekNamedPipe(_pipe, NULL, 0, 0, &totalBytesAvailable, 0)) { - Close(); + if (!PeekNamedPipe(_pipe, nullptr, 0, nullptr, &totalBytesAvailable, nullptr)) { return false; } if (totalBytesAvailable == 0) { - return false; + return true; } - if (!ReadFile(_pipe, resp_utf8.data(), DWORD(resp_utf8.size()), &numBytesRead, NULL)) { - Close(); - return false; + if (!ReadFile(_pipe, resp_utf8.data(), DWORD(resp_utf8.size()), &numBytesRead, &_overlapped)) { + if (GetLastError() == ERROR_IO_PENDING) { + if (WaitForSingleObject(_overlapped.hEvent, timeoutC) != WAIT_OBJECT_0) { + OCShell::logWinError(L"ReadLine timed out"); + } + if (!GetOverlappedResult(_pipe, &_overlapped, &numBytesRead, FALSE)) { + OCShell::logWinError(L"GetOverlappedResult failed"); + return false; + } + } else { + return false; + } } if (numBytesRead <= 0) { return false; diff --git a/shell_integration/windows/OCUtil/CommunicationSocket.h b/shell_integration/windows/OCUtil/CommunicationSocket.h index 7df8fe73069..4ac7526669d 100644 --- a/shell_integration/windows/OCUtil/CommunicationSocket.h +++ b/shell_integration/windows/OCUtil/CommunicationSocket.h @@ -34,15 +34,17 @@ class __declspec(dllexport) CommunicationSocket bool Connect(const std::wstring& pipename); bool Close(); - bool SendMsg(const std::wstring &) const; - bool ReadLine(std::wstring*); + [[nodiscard]] bool SendMsg(const std::wstring &) const; + [[nodiscard]] bool ReadLine(std::wstring *) const; HANDLE Event() { return _pipe; } private: HANDLE _pipe; - std::vector _buffer; + mutable std::vector _buffer; bool _connected; + + mutable OVERLAPPED _overlapped = {}; }; #endif diff --git a/shell_integration/windows/OCUtil/Log.h b/shell_integration/windows/OCUtil/Log.h new file mode 100644 index 00000000000..f08974dbec3 --- /dev/null +++ b/shell_integration/windows/OCUtil/Log.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2022 Hannah von Reth . All rights reserved. + * + * 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. + */ + +#pragma once +#include +#include + +namespace OCShell { + +template +void log(const std::wstring &msg, const T &error = {}) +{ + std::wstringstream tmp; + tmp << L"ownCloud ShellExtension: " << msg; + if (!error.empty()) { + tmp << L" " << error.data(); + } + OutputDebugStringW(tmp.str().data()); +} +inline void logWinError(const std::wstring &msg, const DWORD error = GetLastError()) +{ + log(msg, std::wstring(_com_error(error).ErrorMessage())); +} + +} \ No newline at end of file diff --git a/shell_integration/windows/OCUtil/OCUtil.rc b/shell_integration/windows/OCUtil/OCUtil.rc deleted file mode 100644 index fac68a02e0a..00000000000 Binary files a/shell_integration/windows/OCUtil/OCUtil.rc and /dev/null differ diff --git a/shell_integration/windows/OCUtil/RemotePathChecker.cpp b/shell_integration/windows/OCUtil/RemotePathChecker.cpp index 868a0982608..cdbc0153153 100644 --- a/shell_integration/windows/OCUtil/RemotePathChecker.cpp +++ b/shell_integration/windows/OCUtil/RemotePathChecker.cpp @@ -62,14 +62,24 @@ void RemotePathChecker::workerThreadLoop() lock.unlock(); if (!asked.count(filePath)) { asked.insert(filePath); - socket.SendMsg(L"RETRIEVE_FILE_STATUS:" + filePath + L'\n'); + if (!socket.SendMsg(L"RETRIEVE_FILE_STATUS:" + filePath + L'\n')) { + _stop = true; + break; + } } lock.lock(); } } std::wstring response; - while (!_stop && socket.ReadLine(&response)) { + while (!_stop) { + if (!socket.ReadLine(&response)) { + socket.Close(); + break; + } + if (response.empty()) { + break; + } if (StringUtil::begins_with(response, wstring(L"REGISTER_PATH:"))) { wstring responsePath = response.substr(14); // length of REGISTER_PATH: @@ -81,7 +91,7 @@ void RemotePathChecker::workerThreadLoop() // We don't keep track of all files and can't know which file is currently visible // to the user, but at least reload the root dir so that any shortcut to the root // is updated without the user needing to refresh. - SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH | SHCNF_FLUSHNOWAIT, responsePath.data(), NULL); + SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH | SHCNF_FLUSHNOWAIT, responsePath.data(), nullptr); } else if (StringUtil::begins_with(response, wstring(L"UNREGISTER_PATH:"))) { wstring responsePath = response.substr(16); // length of UNREGISTER_PATH: @@ -97,7 +107,7 @@ void RemotePathChecker::workerThreadLoop() // Remove any item from the cache for (auto it = _cache.begin(); it != _cache.end() ; ) { if (StringUtil::isDescendantOf(it->first, responsePath)) { - removedPaths.emplace_back(move(it->first)); + removedPaths.emplace_back(std::move(it->first)); it = _cache.erase(it); } else { ++it; @@ -105,7 +115,7 @@ void RemotePathChecker::workerThreadLoop() } } for (auto& path : removedPaths) - SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, path.data(), NULL); + SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, path.data(), nullptr); } else if (StringUtil::begins_with(response, wstring(L"STATUS:")) || StringUtil::begins_with(response, wstring(L"BROADCAST:"))) { @@ -133,7 +143,7 @@ void RemotePathChecker::workerThreadLoop() it->second = state; } if (updateView) { - SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, responsePath.data(), NULL); + SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, responsePath.data(), nullptr); } } } @@ -149,11 +159,13 @@ void RemotePathChecker::workerThreadLoop() lock.unlock(); // Let explorer know about each invalidated cache entry that needs to get its icon removed. for (auto it = cache.begin(); it != cache.end(); ++it) { - SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, it->first.data(), NULL); + SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH | SHCNF_FLUSHNOWAIT, it->first.data(), nullptr); } } - if (_stop) return; + if (_stop) { + return; + } HANDLE handles[2] = { _newQueries, socket.Event() }; WaitForMultipleObjects(2, handles, false, 0); @@ -161,13 +173,12 @@ void RemotePathChecker::workerThreadLoop() } - RemotePathChecker::RemotePathChecker() : _stop(false) , _watchedDirectories(make_shared>()) , _connected(false) - , _newQueries(CreateEvent(NULL, FALSE, FALSE, NULL)) - , _thread([this]{ this->workerThreadLoop(); }) + , _newQueries(CreateEvent(nullptr, FALSE, FALSE, nullptr)) + , _thread([this] { this->workerThreadLoop(); }) { } diff --git a/shell_integration/windows/OCUtil/Version.h b/shell_integration/windows/OCUtil/Version.h deleted file mode 100644 index d2c83b060ae..00000000000 --- a/shell_integration/windows/OCUtil/Version.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -// This is the number that will end up in the version window of the DLLs. -// Increment this version before committing a new build if you are today's shell_integration build master. -#define OCEXT_BUILD_NUM 46 - -#define STRINGIZE2(s) #s -#define STRINGIZE(s) STRINGIZE2(s) - -#define OCEXT_VERSION 1,0,0,OCEXT_BUILD_NUM -#define OCEXT_VERSION_STRING STRINGIZE(OCEXT_VERSION) diff --git a/src/3rdparty/QProgressIndicator/QProgressIndicator.h b/src/3rdparty/QProgressIndicator/QProgressIndicator.h index 9b128d06690..41d3a8856c7 100644 --- a/src/3rdparty/QProgressIndicator/QProgressIndicator.h +++ b/src/3rdparty/QProgressIndicator/QProgressIndicator.h @@ -69,7 +69,7 @@ class QProgressIndicator : public QWidget QSize sizeHint() const override; int heightForWidth(int w) const override; -public slots: +public Q_SLOTS: /*! Starts the spin animation. \sa stopAnimation isAnimated */ diff --git a/src/3rdparty/libcrashreporter-qt b/src/3rdparty/libcrashreporter-qt deleted file mode 160000 index 47dbf7629f0..00000000000 --- a/src/3rdparty/libcrashreporter-qt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 47dbf7629f07608416a0476b82c98004f04fbdde diff --git a/src/3rdparty/qtlockedfile/README.txt b/src/3rdparty/qtlockedfile/README.txt deleted file mode 100644 index b89401295b1..00000000000 --- a/src/3rdparty/qtlockedfile/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -This is the src directory of the QtLockedFile -solution integrated over from Qt's Qt Creator. - -It is required by the QtSingleApplication solution. - diff --git a/src/3rdparty/qtlockedfile/qtlockedfile.cpp b/src/3rdparty/qtlockedfile/qtlockedfile.cpp deleted file mode 100644 index f833ca0d321..00000000000 --- a/src/3rdparty/qtlockedfile/qtlockedfile.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include "qtlockedfile.h" - -namespace SharedTools { - -/*! - \class QtLockedFile - - \brief The QtLockedFile class extends QFile with advisory locking functions. - - A file may be locked in read or write mode. Multiple instances of - \e QtLockedFile, created in multiple processes running on the same - machine, may have a file locked in read mode. Exactly one instance - may have it locked in write mode. A read and a write lock cannot - exist simultaneously on the same file. - - The file locks are advisory. This means that nothing prevents - another process from manipulating a locked file using QFile or - file system functions offered by the OS. Serialization is only - guaranteed if all processes that access the file use - QtLockedFile. Also, while holding a lock on a file, a process - must not open the same file again (through any API), or locks - can be unexpectedly lost. - - The lock provided by an instance of \e QtLockedFile is released - whenever the program terminates. This is true even when the - program crashes and no destructors are called. -*/ - -/*! \enum QtLockedFile::LockMode - - This enum describes the available lock modes. - - \value ReadLock A read lock. - \value WriteLock A write lock. - \value NoLock Neither a read lock nor a write lock. -*/ - -/*! - Constructs an unlocked \e QtLockedFile object. This constructor behaves in the same way - as \e QFile::QFile(). - - \sa QFile::QFile() -*/ -QtLockedFile::QtLockedFile() - : QFile() -{ -#ifdef Q_OS_WIN - m_semaphore_hnd = 0; - m_mutex_hnd = 0; -#endif - m_lock_mode = NoLock; -} - -/*! - Constructs an unlocked QtLockedFile object with file \a name. This constructor behaves in - the same way as \e QFile::QFile(const QString&). - - \sa QFile::QFile() -*/ -QtLockedFile::QtLockedFile(const QString &name) - : QFile(name) -{ -#ifdef Q_OS_WIN - m_semaphore_hnd = 0; - m_mutex_hnd = 0; -#endif - m_lock_mode = NoLock; -} - -/*! - Returns \e true if this object has a in read or write lock; - otherwise returns \e false. - - \sa lockMode() -*/ -bool QtLockedFile::isLocked() const -{ - return m_lock_mode != NoLock; -} - -/*! - Returns the type of lock currently held by this object, or \e QtLockedFile::NoLock. - - \sa isLocked() -*/ -QtLockedFile::LockMode QtLockedFile::lockMode() const -{ - return m_lock_mode; -} - -/*! - \fn bool QtLockedFile::lock(LockMode mode, bool block = true) - - Obtains a lock of type \a mode. - - If \a block is true, this - function will block until the lock is acquired. If \a block is - false, this function returns \e false immediately if the lock cannot - be acquired. - - If this object already has a lock of type \a mode, this function returns \e true immediately. If this object has a lock of a different type than \a mode, the lock - is first released and then a new lock is obtained. - - This function returns \e true if, after it executes, the file is locked by this object, - and \e false otherwise. - - \sa unlock(), isLocked(), lockMode() -*/ - -/*! - \fn bool QtLockedFile::unlock() - - Releases a lock. - - If the object has no lock, this function returns immediately. - - This function returns \e true if, after it executes, the file is not locked by - this object, and \e false otherwise. - - \sa lock(), isLocked(), lockMode() -*/ - -/*! - \fn QtLockedFile::~QtLockedFile() - - Destroys the \e QtLockedFile object. If any locks were held, they are released. -*/ - -} // namespace SharedTools diff --git a/src/3rdparty/qtlockedfile/qtlockedfile.h b/src/3rdparty/qtlockedfile/qtlockedfile.h deleted file mode 100644 index 128cbf0ff90..00000000000 --- a/src/3rdparty/qtlockedfile/qtlockedfile.h +++ /dev/null @@ -1,77 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#ifndef QTLOCKEDFILE_H -#define QTLOCKEDFILE_H - -#include - -#if defined(Q_OS_WIN) -# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) -# define QT_QTLOCKEDFILE_EXPORT -# elif defined(QT_QTLOCKEDFILE_IMPORT) -# if defined(QT_QTLOCKEDFILE_EXPORT) -# undef QT_QTLOCKEDFILE_EXPORT -# endif -# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) -# elif defined(QT_QTLOCKEDFILE_EXPORT) -# undef QT_QTLOCKEDFILE_EXPORT -# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) -# endif -#else -# define QT_QTLOCKEDFILE_EXPORT -#endif - -namespace SharedTools { - -class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile -{ -public: - enum LockMode { NoLock = 0, ReadLock, WriteLock }; - - QtLockedFile(); - QtLockedFile(const QString &name); - ~QtLockedFile() override; - - bool lock(LockMode mode, bool block = true); - bool unlock(); - bool isLocked() const; - LockMode lockMode() const; - -private: -#ifdef Q_OS_WIN - Qt::HANDLE m_semaphore_hnd; - Qt::HANDLE m_mutex_hnd; -#endif - LockMode m_lock_mode; -}; - -} // namespace SharedTools - -#endif // QTLOCKEDFILE_H diff --git a/src/3rdparty/qtlockedfile/qtlockedfile.pri b/src/3rdparty/qtlockedfile/qtlockedfile.pri deleted file mode 100644 index ad0c2fbabfb..00000000000 --- a/src/3rdparty/qtlockedfile/qtlockedfile.pri +++ /dev/null @@ -1,11 +0,0 @@ -INCLUDEPATH += $$PWD -DEPENDPATH += $$PWD -HEADERS += $$PWD/qtlockedfile.h -SOURCES += $$PWD/qtlockedfile.cpp - -unix:SOURCES += $$PWD/qtlockedfile_unix.cpp -win32:SOURCES += $$PWD/qtlockedfile_win.cpp - -win32:contains(TEMPLATE, lib):contains(CONFIG, shared) { - DEFINES += QT_QTLOCKEDFILE_EXPORT=__declspec(dllexport) -} diff --git a/src/3rdparty/qtlockedfile/qtlockedfile_unix.cpp b/src/3rdparty/qtlockedfile/qtlockedfile_unix.cpp deleted file mode 100644 index 171178b5f11..00000000000 --- a/src/3rdparty/qtlockedfile/qtlockedfile_unix.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include "qtlockedfile.h" - -#include -#include -#include -#include - -namespace SharedTools { - -bool QtLockedFile::lock(LockMode mode, bool block) -{ - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } - - if (mode == NoLock) - return unlock(); - - if (mode == m_lock_mode) - return true; - - if (m_lock_mode != NoLock) - unlock(); - - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; - int cmd = block ? F_SETLKW : F_SETLK; - int ret = fcntl(handle(), cmd, &fl); - - if (ret == -1) { - if (errno != EINTR && errno != EAGAIN) - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } - - - m_lock_mode = mode; - return true; -} - - -bool QtLockedFile::unlock() -{ - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - struct flock fl; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 0; - fl.l_type = F_UNLCK; - int ret = fcntl(handle(), F_SETLKW, &fl); - - if (ret == -1) { - qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); - return false; - } - - m_lock_mode = NoLock; - remove(); - return true; -} - -QtLockedFile::~QtLockedFile() -{ - if (isOpen()) - unlock(); -} - -} // namespace SharedTools diff --git a/src/3rdparty/qtlockedfile/qtlockedfile_win.cpp b/src/3rdparty/qtlockedfile/qtlockedfile_win.cpp deleted file mode 100644 index ae6286fce6c..00000000000 --- a/src/3rdparty/qtlockedfile/qtlockedfile_win.cpp +++ /dev/null @@ -1,196 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include "qtlockedfile.h" - -#include -#include - -namespace SharedTools { - -#define SEMAPHORE_PREFIX "QtLockedFile semaphore " -#define MUTEX_PREFIX "QtLockedFile mutex " -#define SEMAPHORE_MAX 100 - -static QString errorCodeToString(DWORD errorCode) -{ - QString result; - char *data = 0; - FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - 0, errorCode, 0, - (char*)&data, 0, 0); - result = QString::fromLocal8Bit(data); - if (data != 0) - LocalFree(data); - - if (result.endsWith(QLatin1Char('\n'))) - result.truncate(result.length() - 1); - - return result; -} - -bool QtLockedFile::lock(LockMode mode, bool block) -{ - if (!isOpen()) { - qWarning("QtLockedFile::lock(): file is not opened"); - return false; - } - - if (mode == m_lock_mode) - return true; - - if (m_lock_mode != 0) - unlock(); - - if (m_semaphore_hnd == 0) { - QFileInfo fi(*this); - QString sem_name = QString::fromLatin1(SEMAPHORE_PREFIX) - + fi.absoluteFilePath().toLower(); - - m_semaphore_hnd = CreateSemaphoreW(0, SEMAPHORE_MAX, SEMAPHORE_MAX, - (TCHAR*)sem_name.utf16()); - - if (m_semaphore_hnd == 0) { - qWarning("QtLockedFile::lock(): CreateSemaphore: %s", - errorCodeToString(GetLastError()).toLatin1().constData()); - return false; - } - } - - bool gotMutex = false; - int decrement; - if (mode == ReadLock) { - decrement = 1; - } else { - decrement = SEMAPHORE_MAX; - if (m_mutex_hnd == 0) { - QFileInfo fi(*this); - QString mut_name = QString::fromLatin1(MUTEX_PREFIX) - + fi.absoluteFilePath().toLower(); - - m_mutex_hnd = CreateMutexW(NULL, FALSE, (TCHAR*)mut_name.utf16()); - - if (m_mutex_hnd == 0) { - qWarning("QtLockedFile::lock(): CreateMutex: %s", - errorCodeToString(GetLastError()).toLatin1().constData()); - return false; - } - } - DWORD res = WaitForSingleObject(m_mutex_hnd, block ? INFINITE : 0); - if (res == WAIT_TIMEOUT) - return false; - if (res == WAIT_FAILED) { - qWarning("QtLockedFile::lock(): WaitForSingleObject (mutex): %s", - errorCodeToString(GetLastError()).toLatin1().constData()); - return false; - } - gotMutex = true; - } - - for (int i = 0; i < decrement; ++i) { - DWORD res = WaitForSingleObject(m_semaphore_hnd, block ? INFINITE : 0); - if (res == WAIT_TIMEOUT) { - if (i) { - // A failed nonblocking rw locking. Undo changes to semaphore. - if (ReleaseSemaphore(m_semaphore_hnd, i, NULL) == 0) { - qWarning("QtLockedFile::unlock(): ReleaseSemaphore: %s", - errorCodeToString(GetLastError()).toLatin1().constData()); - // Fall through - } - } - if (gotMutex) - ReleaseMutex(m_mutex_hnd); - return false; - } - if (res != WAIT_OBJECT_0) { - if (gotMutex) - ReleaseMutex(m_mutex_hnd); - qWarning("QtLockedFile::lock(): WaitForSingleObject (semaphore): %s", - errorCodeToString(GetLastError()).toLatin1().constData()); - return false; - } - } - - m_lock_mode = mode; - if (gotMutex) - ReleaseMutex(m_mutex_hnd); - return true; -} - -bool QtLockedFile::unlock() -{ - if (!isOpen()) { - qWarning("QtLockedFile::unlock(): file is not opened"); - return false; - } - - if (!isLocked()) - return true; - - int increment; - if (m_lock_mode == ReadLock) - increment = 1; - else - increment = SEMAPHORE_MAX; - - DWORD ret = ReleaseSemaphore(m_semaphore_hnd, increment, 0); - if (ret == 0) { - qWarning("QtLockedFile::unlock(): ReleaseSemaphore: %s", - errorCodeToString(GetLastError()).toLatin1().constData()); - return false; - } - - m_lock_mode = QtLockedFile::NoLock; - remove(); - return true; -} - -QtLockedFile::~QtLockedFile() -{ - if (isOpen()) - unlock(); - if (m_mutex_hnd != 0) { - DWORD ret = CloseHandle(m_mutex_hnd); - if (ret == 0) { - qWarning("QtLockedFile::~QtLockedFile(): CloseHandle (mutex): %s", - errorCodeToString(GetLastError()).toLatin1().constData()); - } - m_mutex_hnd = 0; - } - if (m_semaphore_hnd != 0) { - DWORD ret = CloseHandle(m_semaphore_hnd); - if (ret == 0) { - qWarning("QtLockedFile::~QtLockedFile(): CloseHandle (semaphore): %s", - errorCodeToString(GetLastError()).toLatin1().constData()); - } - m_semaphore_hnd = 0; - } -} - -} // namespace SharedTools diff --git a/src/3rdparty/qtokenizer/qtokenizer.h b/src/3rdparty/qtokenizer/qtokenizer.h deleted file mode 100644 index 2c4b082b444..00000000000 --- a/src/3rdparty/qtokenizer/qtokenizer.h +++ /dev/null @@ -1,264 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Daniel Molkentin -** Contact: http://www.qt-project.org/legal -** -** This file is part of the QtNetwork module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef TOKENIZER_H -#define TOKENIZER_H - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -template -struct QTokenizerPrivate { - typedef typename T::value_type char_type; - - struct State { - bool inQuote; - bool inEscape; - char_type quoteChar; - State() : inQuote(false), inEscape(false), quoteChar('\0') {} - }; - - QTokenizerPrivate(const T& _string, const T& _delims) : - string(_string) - , begin(string.begin()) - , end(string.end()) - , tokenBegin(end) - , tokenEnd(begin) - , delimiters(_delims) - , isDelim(false) - , returnDelimiters(false) - , returnQuotes(false) - { - } - - bool isDelimiter(char_type c) const { - return delimiters.contains(c); - } - - bool isQuote(char_type c) const { - return quotes.contains(c); - } - - // Returns true if a delimiter was not hit - bool nextChar(State* state, char_type c) { - if (state->inQuote) { - if (state->inEscape) { - state->inEscape = false; - } else if (c == '\\') { - state->inEscape = true; - } else if (c == state->quoteChar) { - state->inQuote = false; - } - } else { - if (isDelimiter(c)) - return false; - state->inQuote = isQuote(state->quoteChar = c); - } - return true; - } - - T string; - // ### copies begin and end for performance, premature optimization? - const_iterator begin; - const_iterator end; - const_iterator tokenBegin; - const_iterator tokenEnd; - T delimiters; - T quotes; - bool isDelim; - bool returnDelimiters; - bool returnQuotes; -}; - -template -class QTokenizer { -public: - typedef typename T::value_type char_type; - - /*! - \class QTokenizer - \inmodule QtNetwork - \brief QTokenizer tokenizes Strings on QString, QByteArray, - std::string or std::wstring - - Example Usage: - - \code - QString str = ...; - QByteArrayTokenizer tokenizer(str, "; "); - tokenizer.setQuoteCharacters("\"'"); - tokenizer.setReturnDelimiters(true); - while (tokenizer.hasNext()) { - QByteArray token = tokenizer.next(); - bool isDelimiter = tokenizer.isDelimiter(); - ... - } - \endcode - - \param string The string to tokenize - \param delimiters A string containing delimiters - - \sa QStringTokenizer, QByteArrayTokenizer, StringTokenizer, WStringTokenizer - */ - QTokenizer(const T& string, const T& delimiters) - : d(new QTokenizerPrivate(string, delimiters)) - { } - - /*! - Whether or not to return delimiters as tokens - \see setQuoteCharacters - */ - void setReturnDelimiters(bool enable) { d->returnDelimiters = enable; } - - - /*! - Sets characters that are considered to start and end quotes. - - When between two characters considered a quote, delimiters will - be ignored. - - When between quotes, blackslash characters will cause the QTokenizer - to skip the next character. - - \param quotes Characters that delimit quotes. - */ - void setQuoteCharacters(const T& quotes) { d->quotes = quotes; } - - - /*! - Whether or not to return delimiters as tokens - \see setQuoteCharacters - */ - void setReturnQuoteCharacters(bool enable) { d->returnQuotes = enable; } - - - /*! - Retrieve next token. - - Returns true if there are more tokens, false otherwise. - - \sa next() - */ - bool hasNext() - { - typename QTokenizerPrivate::State state; - d->isDelim = false; - for (;;) { - d->tokenBegin = d->tokenEnd; - if (d->tokenEnd == d->end) - return false; - d->tokenEnd++; - if (d->nextChar(&state, *d->tokenBegin)) - break; - if (d->returnDelimiters) { - d->isDelim = true; - return true; - } - } - while (d->tokenEnd != d->end && d->nextChar(&state, *d->tokenEnd)) { - d->tokenEnd++; - } - return true; - } - - /*! - Resets the tokenizer to the starting position. - */ - void reset() { - d->tokenEnd = d->begin; - } - - /*! - Returns true if the current token is a delimiter, - if one more more delimiting characters have been set. - */ - bool isDelimiter() const { return d->isDelim; } - - /*! - Returns the current token. - - Use \c hasNext() to fetch the next token. - */ - T next() const { - int len = d->tokenEnd-d->tokenBegin; - const_iterator tmpStart = d->tokenBegin; - if (!d->returnQuotes && len > 1 && d->isQuote(*d->tokenBegin)) { - tmpStart++; - len -= 2; - } - return T(tmpStart, len); - } - -private: - friend class QStringTokenizer; - QSharedPointer > d; -}; - -class QStringTokenizer : public QTokenizer { -public: - QStringTokenizer(const QString &string, const QString &delim) : - QTokenizer(string, delim) {} - /** - * @brief Like \see next(), but returns a lightweight string reference - * @return A reference to the token within the string - */ - QStringRef stringRef() { - int begin = d->tokenBegin-d->begin; - int end = d->tokenEnd-d->tokenBegin; - if (!d->returnQuotes && d->isQuote(*d->tokenBegin)) { - begin++; - end -= 2; - } - return QStringRef(&d->string, begin, end); - } -}; - -typedef QTokenizer QByteArrayTokenizer; -typedef QTokenizer StringTokenizer; -typedef QTokenizer WStringTokenizer; - -QT_END_NAMESPACE - -#endif // TOKENIZER_H - diff --git a/src/3rdparty/qtokenizer/qtokenizer.pro b/src/3rdparty/qtokenizer/qtokenizer.pro deleted file mode 100644 index 4dcd7002811..00000000000 --- a/src/3rdparty/qtokenizer/qtokenizer.pro +++ /dev/null @@ -1,2 +0,0 @@ -TEMPLATE = subdirs -SUBDIRS = test diff --git a/src/3rdparty/qtokenizer/test/test.pro b/src/3rdparty/qtokenizer/test/test.pro deleted file mode 100644 index 269fcf6b028..00000000000 --- a/src/3rdparty/qtokenizer/test/test.pro +++ /dev/null @@ -1,8 +0,0 @@ -TEMPLATE = app -QT += testlib -CONFIG += testlib -TARGET = test -INCLUDEPATH += . .. - -# Input -SOURCES += tst_qtokenizer.cpp diff --git a/src/3rdparty/qtokenizer/test/tst_qtokenizer.cpp b/src/3rdparty/qtokenizer/test/tst_qtokenizer.cpp deleted file mode 100644 index 537439ccf4c..00000000000 --- a/src/3rdparty/qtokenizer/test/tst_qtokenizer.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include - -#include "qtokenizer.h" - -namespace { - const QString simple = QLatin1String("A simple tokenizer test"); - const QString quoted = QLatin1String("\"Wait for me!\" he shouted"); -} - -class TestTokenizer : public QObject -{ - Q_OBJECT -private slots: - void tokenizeQStringSimple() { - QStringTokenizer tokenizer(simple, " "); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("A")); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("simple")); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("tokenizer")); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("test")); - - QCOMPARE(tokenizer.hasNext(), false); - } - - void tokenizeQStringSimpleRef() { - QStringTokenizer tokenizer(simple, " "); - - QCOMPARE(tokenizer.hasNext(), true); - QVERIFY(tokenizer.stringRef() == QLatin1String("A")); - - QCOMPARE(tokenizer.hasNext(), true); - QVERIFY(tokenizer.stringRef() == QLatin1String("simple")); - - QCOMPARE(tokenizer.hasNext(), true); - QVERIFY(tokenizer.stringRef() == QLatin1String("tokenizer")); - - QCOMPARE(tokenizer.hasNext(), true); - QVERIFY(tokenizer.stringRef() == QLatin1String("test")); - - QCOMPARE(tokenizer.hasNext(), false); - } - - void tokenizeQStringQuoted() { - const QString multiquote(QLatin1String("\"'Billy - the Kid' is dead!\"")); - QStringTokenizer tokenizer(multiquote, " -"); - tokenizer.setQuoteCharacters("\""); - tokenizer.setReturnQuoteCharacters(true); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("\"'Billy - the Kid' is dead!\"")); - - QCOMPARE(tokenizer.hasNext(), false); - } - - void tokenizeQStringSkipQuotes() { - const QString multiquote(QLatin1String("\"'Billy - the Kid' is dead!\"")); - QStringTokenizer tokenizer(multiquote, " "); - tokenizer.setQuoteCharacters("\""); - tokenizer.setReturnQuoteCharacters(false); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("'Billy - the Kid' is dead!")); - QCOMPARE(tokenizer.stringRef().toString(), QLatin1String("'Billy - the Kid' is dead!")); - - QCOMPARE(tokenizer.hasNext(), false); - } - - - void tokenizeQStringWithDelims() { - const QString delims(QLatin1String("I;Insist,On/a-Delimiter")); - QStringTokenizer tokenizer(delims, ";,/-"); - tokenizer.setReturnDelimiters(true); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.isDelimiter(), false); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.isDelimiter(), true); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.isDelimiter(), false); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.isDelimiter(), true); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.isDelimiter(), false); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.isDelimiter(), true); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.isDelimiter(), false); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.isDelimiter(), true); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.isDelimiter(), false); - - QCOMPARE(tokenizer.hasNext(), false); - } - - void resetTokenizer() { - for (int i = 0; i < 2; i++) { - QStringTokenizer tokenizer(simple, " "); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("A")); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("simple")); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("tokenizer")); - - QCOMPARE(tokenizer.hasNext(), true); - QCOMPARE(tokenizer.next(), QLatin1String("test")); - - QCOMPARE(tokenizer.hasNext(), false); - - tokenizer.reset(); - } - } - - // ### QByteArray, other types -}; - -QTEST_APPLESS_MAIN(TestTokenizer) - -#include "tst_qtokenizer.moc" - diff --git a/src/3rdparty/qtsingleapplication/README.txt b/src/3rdparty/qtsingleapplication/README.txt deleted file mode 100644 index b5d88694810..00000000000 --- a/src/3rdparty/qtsingleapplication/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -This is the src directory of the QtSingleApplication solution -integrated over from Qt's Qt Creator project. - -It additionally requires the QtLockedFile solution. - diff --git a/src/3rdparty/qtsingleapplication/qtlocalpeer.cpp b/src/3rdparty/qtsingleapplication/qtlocalpeer.cpp deleted file mode 100644 index 1575b37420b..00000000000 --- a/src/3rdparty/qtsingleapplication/qtlocalpeer.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include "qtlocalpeer.h" - -#include -#include -#include - -#if defined(Q_OS_WIN) -#include -#include -typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); -static PProcessIdToSessionId pProcessIdToSessionId = 0; -#endif - -#if defined(Q_OS_UNIX) -#include -#include -#endif - -namespace SharedTools { - -static const char ack[] = "ack"; - -QString QtLocalPeer::appSessionId(const QString &appId) -{ - QByteArray idc = appId.toUtf8(); - quint16 idNum = qChecksum(idc.constData(), idc.size()); - //### could do: two 16bit checksums over separate halves of id, for a 32bit result - improved uniqeness probability. Every-other-char split would be best. - - QString res = QLatin1String("qtsingleapplication-") - + QString::number(idNum, 16); -#if defined(Q_OS_WIN) - if (!pProcessIdToSessionId) { - QLibrary lib(QLatin1String("kernel32")); - pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); - } - if (pProcessIdToSessionId) { - DWORD sessionId = 0; - pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); - res += QLatin1Char('-') + QString::number(sessionId, 16); - } -#else - res += QLatin1Char('-') + QString::number(::getuid(), 16); -#endif - return res; -} - -QtLocalPeer::QtLocalPeer(QObject *parent, const QString &appId) - : QObject(parent), id(appId) -{ - if (id.isEmpty()) - id = QCoreApplication::applicationFilePath(); //### On win, check if this returns .../argv[0] without casefolding; .\MYAPP == .\myapp on Win - - socketName = appSessionId(id); - server = new QLocalServer(this); - QString lockName = QDir(QDir::tempPath()).absolutePath() - + QLatin1Char('/') + socketName - + QLatin1String("-lockfile"); - lockFile.setFileName(lockName); - lockFile.open(QIODevice::ReadWrite); -} - -bool QtLocalPeer::isClient() -{ - if (lockFile.isLocked()) - return false; - - if (!lockFile.lock(QtLockedFile::WriteLock, false)) - return true; - - if (!QLocalServer::removeServer(socketName)) - qWarning("QtSingleCoreApplication: could not cleanup socket"); - bool res = server->listen(socketName); - if (!res) - qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qUtf8Printable(server->errorString())); - QObject::connect(server, &QLocalServer::newConnection, this, &QtLocalPeer::receiveConnection); - return false; -} - -bool QtLocalPeer::sendMessage(const QString &message, int timeout, bool block) -{ - if (!isClient()) - return false; - - QLocalSocket socket; - bool connOk = false; - for (int i = 0; i < 2; i++) { - // Try twice, in case the other instance is just starting up - socket.connectToServer(socketName); - connOk = socket.waitForConnected(timeout/2); - if (connOk || i) - break; - int ms = 250; -#if defined(Q_OS_WIN) - Sleep(DWORD(ms)); -#else - struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; - nanosleep(&ts, nullptr); -#endif - } - if (!connOk) - return false; - - QByteArray uMsg(message.toUtf8()); - QDataStream ds(&socket); - ds.writeBytes(uMsg.constData(), uMsg.size()); - bool res = socket.waitForBytesWritten(timeout); - res &= socket.waitForReadyRead(timeout); // wait for ack - res &= (socket.read(qstrlen(ack)) == ack); - if (block) // block until peer disconnects - socket.waitForDisconnected(-1); - return res; -} - -void QtLocalPeer::receiveConnection() -{ - QLocalSocket* socket = server->nextPendingConnection(); - if (!socket) - return; - - // Why doesn't Qt have a blocking stream that takes care of this shait??? - while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { - if (!socket->isValid()) // stale request - return; - socket->waitForReadyRead(1000); - } - QDataStream ds(socket); - QByteArray uMsg; - quint32 remaining; - ds >> remaining; - uMsg.resize(remaining); - int got = 0; - char* uMsgBuf = uMsg.data(); - //qDebug() << "RCV: remaining" << remaining; - do { - got = ds.readRawData(uMsgBuf, remaining); - remaining -= got; - uMsgBuf += got; - //qDebug() << "RCV: got" << got << "remaining" << remaining; - } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); - //### error check: got<0 - if (got < 0) { - qWarning() << "QtLocalPeer: Message reception failed" << socket->errorString(); - delete socket; - return; - } - // ### async this - QString message = QString::fromUtf8(uMsg.constData(), uMsg.size()); - socket->write(ack, qstrlen(ack)); - socket->waitForBytesWritten(1000); - emit messageReceived(message, socket); // ##(might take a long time to return) -} - -} // namespace SharedTools diff --git a/src/3rdparty/qtsingleapplication/qtlocalpeer.h b/src/3rdparty/qtsingleapplication/qtlocalpeer.h deleted file mode 100644 index ce93a5e0ae9..00000000000 --- a/src/3rdparty/qtsingleapplication/qtlocalpeer.h +++ /dev/null @@ -1,63 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include - -#include -#include -#include - -namespace SharedTools { - -class QtLocalPeer : public QObject -{ - Q_OBJECT - -public: - explicit QtLocalPeer(QObject *parent = nullptr, const QString &appId = QString()); - bool isClient(); - bool sendMessage(const QString &message, int timeout, bool block); - QString applicationId() const - { return id; } - static QString appSessionId(const QString &appId); - -Q_SIGNALS: - void messageReceived(const QString &message, QObject *socket); - -protected Q_SLOTS: - void receiveConnection(); - -protected: - QString id; - QString socketName; - QLocalServer* server; - QtLockedFile lockFile; -}; - -} // namespace SharedTools diff --git a/src/3rdparty/qtsingleapplication/qtsingleapplication.cpp b/src/3rdparty/qtsingleapplication/qtsingleapplication.cpp deleted file mode 100644 index 5934d1cb38d..00000000000 --- a/src/3rdparty/qtsingleapplication/qtsingleapplication.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include "qtsingleapplication.h" -#include "qtlocalpeer.h" - -#include - -#include -#include -#include -#include - -namespace SharedTools { - -static const int instancesSize = 1024; - -static QString instancesLockFilename(const QString &appSessionId) -{ - const QChar slash(QLatin1Char('/')); - QString res = QDir::tempPath(); - if (!res.endsWith(slash)) - res += slash; - return res + appSessionId + QLatin1String("-instances"); -} - -QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) - : QApplication(argc, argv), - firstPeer(-1), - pidPeer(nullptr) -{ - this->appId = appId; - - const QString appSessionId = QtLocalPeer::appSessionId(appId); - - // This shared memory holds a zero-terminated array of active (or crashed) instances - instances = new QSharedMemory(appSessionId, this); - actWin = nullptr; - block = false; - - // First instance creates the shared memory, later instances attach to it - const bool created = instances->create(instancesSize); - if (!created) { - if (!instances->attach()) { - qWarning() << "Failed to initialize instances shared memory: " - << instances->errorString(); - delete instances; - instances = nullptr; - return; - } - } - - // QtLockedFile is used to workaround QTBUG-10364 - QtLockedFile lockfile(instancesLockFilename(appSessionId)); - - lockfile.open(QtLockedFile::ReadWrite); - lockfile.lock(QtLockedFile::WriteLock); - qint64 *pids = static_cast(instances->data()); - if (!created) { - // Find the first instance that it still running - // The whole list needs to be iterated in order to append to it - for (; *pids; ++pids) { - if (firstPeer == -1 && isRunning(*pids)) - firstPeer = *pids; - } - } - // Add current pid to list and terminate it - *pids++ = QCoreApplication::applicationPid(); - *pids = 0; - pidPeer = new QtLocalPeer(this, appId + QLatin1Char('-') + - QString::number(QCoreApplication::applicationPid())); - connect(pidPeer, &QtLocalPeer::messageReceived, this, &QtSingleApplication::messageReceived); - pidPeer->isClient(); - lockfile.unlock(); -} - -QtSingleApplication::~QtSingleApplication() -{ - if (!instances) - return; - const qint64 appPid = QCoreApplication::applicationPid(); - QtLockedFile lockfile(instancesLockFilename(QtLocalPeer::appSessionId(appId))); - lockfile.open(QtLockedFile::ReadWrite); - lockfile.lock(QtLockedFile::WriteLock); - // Rewrite array, removing current pid and previously crashed ones - qint64 *pids = static_cast(instances->data()); - qint64 *newpids = pids; - for (; *pids; ++pids) { - if (*pids != appPid && isRunning(*pids)) - *newpids++ = *pids; - } - *newpids = 0; - lockfile.unlock(); -} - -bool QtSingleApplication::event(QEvent *event) -{ - if (event->type() == QEvent::FileOpen) { - QFileOpenEvent *foe = static_cast(event); - emit fileOpenRequest(foe->file()); - return true; - } - return QApplication::event(event); -} - -bool QtSingleApplication::isRunning(qint64 pid) -{ - if (pid == -1) { - pid = firstPeer; - if (pid == -1) - return false; - } - - QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10)); - return peer.isClient(); -} - -bool QtSingleApplication::sendMessage(const QString &message, int timeout, qint64 pid) -{ - if (pid == -1) { - pid = firstPeer; - if (pid == -1) - return false; - } - - QtLocalPeer peer(this, appId + QLatin1Char('-') + QString::number(pid, 10)); - return peer.sendMessage(message, timeout, block); -} - -QString QtSingleApplication::applicationId() const -{ - return appId; -} - -void QtSingleApplication::setBlock(bool value) -{ - block = value; -} - -void QtSingleApplication::setActivationWindow(QWidget *aw, bool activateOnMessage) -{ - actWin = aw; - if (!pidPeer) - return; - if (activateOnMessage) - connect(pidPeer, &QtLocalPeer::messageReceived, this, &QtSingleApplication::activateWindow); - else - disconnect(pidPeer, &QtLocalPeer::messageReceived, this, &QtSingleApplication::activateWindow); -} - - -QWidget* QtSingleApplication::activationWindow() const -{ - return actWin; -} - - -void QtSingleApplication::activateWindow() -{ - if (actWin) { - actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); - actWin->raise(); - actWin->activateWindow(); - } -} - -} // namespace SharedTools diff --git a/src/3rdparty/qtsingleapplication/qtsingleapplication.h b/src/3rdparty/qtsingleapplication/qtsingleapplication.h deleted file mode 100644 index 674649e4cf0..00000000000 --- a/src/3rdparty/qtsingleapplication/qtsingleapplication.h +++ /dev/null @@ -1,79 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#ifndef QTSINGLEAPPLICATION_H -#define QTSINGLEAPPLICATION_H - -#include - -QT_FORWARD_DECLARE_CLASS(QSharedMemory) - -namespace SharedTools { - -class QtLocalPeer; - -class QtSingleApplication : public QApplication -{ - Q_OBJECT - -public: - QtSingleApplication(const QString &id, int &argc, char **argv); - ~QtSingleApplication() override; - - bool isRunning(qint64 pid = -1); - - void setActivationWindow(QWidget* aw, bool activateOnMessage = true); - QWidget* activationWindow() const; - bool event(QEvent *event) override; - - QString applicationId() const; - void setBlock(bool value); - -public Q_SLOTS: - bool sendMessage(const QString &message, int timeout = 5000, qint64 pid = -1); - void activateWindow(); - -Q_SIGNALS: - void messageReceived(const QString &message, QObject *socket); - void fileOpenRequest(const QString &file); - -private: - QString instancesFileName(const QString &appId); - - qint64 firstPeer; - QSharedMemory *instances; - QtLocalPeer *pidPeer; - QWidget *actWin; - QString appId; - bool block; -}; - -} // namespace SharedTools - -#endif // QTSINGLEAPPLICATION_H diff --git a/src/3rdparty/qtsingleapplication/qtsingleapplication.pri b/src/3rdparty/qtsingleapplication/qtsingleapplication.pri deleted file mode 100644 index c807763dce0..00000000000 --- a/src/3rdparty/qtsingleapplication/qtsingleapplication.pri +++ /dev/null @@ -1,15 +0,0 @@ -INCLUDEPATH += $$PWD -DEPENDPATH += $$PWD -HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h -SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp - -QT *= network -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -gotqtlockedfile = $$find(HEADERS, .*qtlockedfile.h) -isEmpty(gotqtlockedfile):include(../qtlockedfile/qtlockedfile.pri) - - -win32:contains(TEMPLATE, lib):contains(CONFIG, shared) { - DEFINES += QT_QTSINGLEAPPLICATION_EXPORT=__declspec(dllexport) -} diff --git a/src/3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp b/src/3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp deleted file mode 100644 index e34fd6328ba..00000000000 --- a/src/3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include "qtsinglecoreapplication.h" -#include "qtlocalpeer.h" - -namespace SharedTools { - -QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) - : QCoreApplication(argc, argv) -{ - peer = new QtLocalPeer(this); - block = false; - connect(peer, &QtLocalPeer::messageReceived, this, &QtSingleCoreApplication::messageReceived); -} - - -QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) - : QCoreApplication(argc, argv) -{ - peer = new QtLocalPeer(this, appId); - connect(peer, &QtLocalPeer::messageReceived, this, &QtSingleCoreApplication::messageReceived); -} - - -bool QtSingleCoreApplication::isRunning() -{ - return peer->isClient(); -} - - -bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) -{ - return peer->sendMessage(message, timeout, block); -} - - -QString QtSingleCoreApplication::id() const -{ - return peer->applicationId(); -} - -void QtSingleCoreApplication::setBlock(bool value) -{ - block = value; -} - -} // namespace SharedTools diff --git a/src/3rdparty/qtsingleapplication/qtsinglecoreapplication.h b/src/3rdparty/qtsingleapplication/qtsinglecoreapplication.h deleted file mode 100644 index 15750321215..00000000000 --- a/src/3rdparty/qtsingleapplication/qtsinglecoreapplication.h +++ /dev/null @@ -1,61 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of Qt Creator. -** -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -****************************************************************************/ - -#include - -namespace SharedTools { - -class QtLocalPeer; - -class QtSingleCoreApplication : public QCoreApplication -{ - Q_OBJECT - -public: - QtSingleCoreApplication(int &argc, char **argv); - QtSingleCoreApplication(const QString &id, int &argc, char **argv); - - bool isRunning(); - QString id() const; - void setBlock(bool value); - -public Q_SLOTS: - bool sendMessage(const QString &message, int timeout = 5000); - - -Q_SIGNALS: - void messageReceived(const QString &message); - - -private: - QtLocalPeer* peer; - bool block; -}; - -} // namespace SharedTools diff --git a/src/3rdparty/qtsingleapplication/qtsinglecoreapplication.pri b/src/3rdparty/qtsingleapplication/qtsinglecoreapplication.pri deleted file mode 100644 index 3d6301b29c4..00000000000 --- a/src/3rdparty/qtsingleapplication/qtsinglecoreapplication.pri +++ /dev/null @@ -1,14 +0,0 @@ -INCLUDEPATH += $$PWD -DEPENDPATH += $$PWD -HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h -SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp - -QT *= network - -gotqtlockedfile = $$find(HEADERS, .*qtlockedfile.h) -isEmpty(gotqtlockedfile):include(../qtlockedfile/qtlockedfile.pri) - - -win32:contains(TEMPLATE, lib):contains(CONFIG, shared) { - DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport) -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f4cbecdbece..ca9fc5f68cf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,12 +1,9 @@ -find_package(Qt5 5.15 COMPONENTS Core Network Xml Concurrent REQUIRED) -get_target_property (QT_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION) -message(STATUS "Using Qt ${Qt5Core_VERSION} (${QT_QMAKE_EXECUTABLE})") - - -if(NOT TOKEN_AUTH_ONLY) - find_package(Qt5Keychain REQUIRED) +if(WITH_CRASHREPORTER) + find_package(CrashReporterQt REQUIRED) endif() +find_package(Qt6Keychain 0.13 REQUIRED) + # TODO: Mingw64 7.3 might also need to be excluded here as it seems to not automatically link libssp if(NOT WIN32) if(NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "^(alpha|parisc|hppa)") AND NOT CMAKE_CROSSCOMPILING) @@ -37,16 +34,23 @@ elseif(UNIX AND NOT APPLE) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now") endif() +add_subdirectory(resources) add_subdirectory(csync) add_subdirectory(libsync) -if (NOT BUILD_LIBRARIES_ONLY) - if (BUILD_GUI) - add_subdirectory(gui) - endif() - add_subdirectory(cmd) +add_subdirectory(gui) - if (WITH_CRASHREPORTER) - add_subdirectory(3rdparty/libcrashreporter-qt EXCLUDE_FROM_ALL) - add_subdirectory(crashreporter) - endif() -endif(NOT BUILD_LIBRARIES_ONLY) +if (WITH_CRASHREPORTER) + add_subdirectory(crashreporter) +endif() + +add_subdirectory(plugins) + +install(EXPORT ${APPLICATION_SHORTNAME}Config DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/${APPLICATION_SHORTNAME}" NAMESPACE ownCloud::) + +ecm_setup_version(PROJECT + VARIABLE_PREFIX ${APPLICATION_SHORTNAME} + PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_SHORTNAME}ConfigVersion.cmake" + SOVERSION ${MIRALL_SOVERSION} +) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_SHORTNAME}ConfigVersion.cmake" DESTINATION "${KDE_INSTALL_CMAKEPACKAGEDIR}/${APPLICATION_SHORTNAME}") diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt deleted file mode 100644 index 38cb61e8093..00000000000 --- a/src/cmd/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -if(NOT BUILD_LIBRARIES_ONLY) - add_executable(cmd - cmd.cpp - simplesslerrorhandler.cpp - netrcparser.cpp - ) - set_target_properties(cmd PROPERTIES OUTPUT_NAME "${APPLICATION_EXECUTABLE}cmd") - ecm_mark_nongui_executable(cmd) - - target_link_libraries(cmd csync libsync Qt5::Core Qt5::Network) - - # Need tokenizer for netrc parser - target_include_directories(cmd PRIVATE ${CMAKE_SOURCE_DIR}/src/3rdparty/qtokenizer) - - if(APPLE) - install(TARGETS cmd RUNTIME DESTINATION "${KDE_INSTALL_BUNDLEDIR}/${APPLICATION_EXECUTABLE}.app/Contents/MacOS/") - else() - install(TARGETS cmd ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) - endif() -endif() - diff --git a/src/cmd/cmd.cpp b/src/cmd/cmd.cpp deleted file mode 100644 index 8281af70aea..00000000000 --- a/src/cmd/cmd.cpp +++ /dev/null @@ -1,577 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * Copyright (C) by Klaas Freitag - * Copyright (C) by Daniel Heule - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "account.h" -#include "configfile.h" // ONLY ACCESS THE STATIC FUNCTIONS! -#ifdef TOKEN_AUTH_ONLY -# include "creds/tokencredentials.h" -#else -# include "creds/httpcredentials.h" -#endif -#include "simplesslerrorhandler.h" -#include "syncengine.h" -#include "common/syncjournaldb.h" -#include "config.h" -#include "csync_exclude.h" - -#include "theme.h" -#include "netrcparser.h" -#include "libsync/logger.h" - -#include "config.h" - -#ifdef Q_OS_WIN32 -#include -#else -#include -#include -#endif - -using namespace OCC; - -namespace { - -struct CmdOptions -{ - QString source_dir; - QString target_url; - QString config_directory; - QString user; - QString password; - QString proxy; - bool silent = false; - bool trustSSL = false; - bool useNetrc = false; - bool interactive = true; - bool ignoreHiddenFiles = true; - QString exclude; - QString unsyncedfolders; - QString davPath; - int restartTimes = 3; - int downlimit = 0; - int uplimit = 0; - bool deltasync; - qint64 deltasyncminfilesize; -}; - -struct SyncCTX -{ - explicit SyncCTX(const CmdOptions &cmdOptions) - : options { cmdOptions } - { - } - CmdOptions options; - QUrl credentialFreeUrl; - QString folder; - AccountPtr account; - QString user; -}; - - -/* If the selective sync list is different from before, we need to disable the read from db - (The normal client does it in SelectiveSyncDialog::accept*) - */ -void selectiveSyncFixup(OCC::SyncJournalDb *journal, const QStringList &newList) -{ - SqlDatabase db; - if (!db.openOrCreateReadWrite(journal->databaseFilePath())) { - return; - } - - bool ok; - - auto oldBlackListSet = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok).toSet(); - if (ok) { - auto blackListSet = newList.toSet(); - const auto changes = (oldBlackListSet - blackListSet) + (blackListSet - oldBlackListSet); - for (const auto &it : changes) { - journal->schedulePathForRemoteDiscovery(it); - } - - journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, newList); - } -} - - -void sync(const SyncCTX &ctx) -{ - QStringList selectiveSyncList; - if (!ctx.options.unsyncedfolders.isEmpty()) { - QFile f(ctx.options.unsyncedfolders); - if (!f.open(QFile::ReadOnly)) { - qCritical() << "Could not open file containing the list of unsynced folders: " << ctx.options.unsyncedfolders; - } else { - // filter out empty lines and comments - selectiveSyncList = QString::fromUtf8(f.readAll()).split('\n').filter(QRegExp("\\S+")).filter(QRegExp("^[^#]")); - - for (int i = 0; i < selectiveSyncList.count(); ++i) { - if (!selectiveSyncList.at(i).endsWith(QLatin1Char('/'))) { - selectiveSyncList[i].append(QLatin1Char('/')); - } - } - } - } - - const QString dbPath = ctx.options.source_dir + SyncJournalDb::makeDbName(ctx.options.source_dir); - auto db = new SyncJournalDb(dbPath, qApp); - if (!selectiveSyncList.empty()) { - selectiveSyncFixup(db, selectiveSyncList); - } - - SyncOptions opt; - opt.fillFromEnvironmentVariables(); - opt.verifyChunkSizes(); - auto engine = new SyncEngine(ctx.account, ctx.options.source_dir, ctx.folder, db); - engine->setParent(db); - - QObject::connect(engine, &SyncEngine::finished, engine, [engine, ctx, restartCount = std::make_shared(0)](bool result) { - if (!result) { - qWarning() << "Failed to sync"; - qApp->exit(EXIT_FAILURE); - } else { - if (engine->isAnotherSyncNeeded() != NoFollowUpSync) { - if (*restartCount < ctx.options.restartTimes) { - (*restartCount)++; - qDebug() << "Restarting Sync, because another sync is needed" << *restartCount; - engine->startSync(); - return; - } - qWarning() << "Another sync is needed, but not done because restart count is exceeded" << *restartCount; - } else { - qApp->quit(); - } - } - }); - QObject::connect(engine, &SyncEngine::aboutToRemoveAllFiles, engine, [ctx](OCC::SyncFileItem::Direction dir, std::function abort) { - if (!ctx.options.interactive) { - abort(false); - } else { - std::cout << (dir == SyncFileItem::Down ? "All files in the sync folder '%1' folder were deleted on the server.\n" - "These deletes will be synchronized to your local sync folder, making such files " - "unavailable unless you have a right to restore. \n" - "If you decide to keep the files, they will be re-synced with the server if you have rights to do so.\n" - "If you decide to delete the files, they will be unavailable to you, unless you are the owner." - : "All the files in your local sync folder '%1' were deleted. These deletes will be " - "synchronized with your server, making such files unavailable unless restored.\n" - "Are you sure you want to sync those actions with the server?\n" - "If this was an accident and you decide to keep your files, they will be re-synced from the server.") - << std::endl; - std::string s; - while (true) { - std::cout << "Remove all files?[y,n]"; - std::getline(std::cin, s); - if (s == "y") { - abort(false); - } else if (s == "n") { - abort(true); - } else { - continue; - } - return; - } - } - }); - QObject::connect(engine, &SyncEngine::syncError, engine, - [](const QString &error) { qWarning() << "Sync error:" << error; }); - engine->setSyncOptions(opt); - engine->setIgnoreHiddenFiles(ctx.options.ignoreHiddenFiles); - engine->setNetworkLimits(ctx.options.uplimit, ctx.options.downlimit); - - - // Exclude lists - - bool hasUserExcludeFile = !ctx.options.exclude.isEmpty(); - QString systemExcludeFile = ConfigFile::excludeFileFromSystem(); - - // Always try to load the user-provided exclude list if one is specified - if (hasUserExcludeFile) { - engine->excludedFiles().addExcludeFilePath(ctx.options.exclude); - } - // Load the system list if available, or if there's no user-provided list - if (!hasUserExcludeFile || QFile::exists(systemExcludeFile)) { - engine->excludedFiles().addExcludeFilePath(systemExcludeFile); - } - - if (!engine->excludedFiles().reloadExcludeFiles()) { - qFatal("Cannot load system exclude list or list supplied via --exclude"); - } - engine->startSync(); -} - -} - -class EchoDisabler -{ -public: - EchoDisabler() - { -#ifdef Q_OS_WIN - hStdin = GetStdHandle(STD_INPUT_HANDLE); - GetConsoleMode(hStdin, &mode); - SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT)); -#else - tcgetattr(STDIN_FILENO, &tios); - termios tios_new = tios; - tios_new.c_lflag &= ~ECHO; - tcsetattr(STDIN_FILENO, TCSANOW, &tios_new); -#endif - } - - ~EchoDisabler() - { -#ifdef Q_OS_WIN - SetConsoleMode(hStdin, mode); -#else - tcsetattr(STDIN_FILENO, TCSANOW, &tios); -#endif - } - -private: -#ifdef Q_OS_WIN - DWORD mode = 0; - HANDLE hStdin; -#else - termios tios; -#endif -}; - -QString queryPassword(const QString &user) -{ - EchoDisabler disabler; - std::cout << "Password for user " << qUtf8Printable(user) << ": "; - std::string s; - std::getline(std::cin, s); - return QString::fromStdString(s); -} - -#ifndef TOKEN_AUTH_ONLY -class HttpCredentialsText : public HttpCredentials -{ -public: - HttpCredentialsText(const QString &user, const QString &password) - : HttpCredentials(DetermineAuthTypeJob::AuthType::Basic, user, password) - , _sslTrusted(false) - { - } - - void askFromUser() override - { - _password = ::queryPassword(user()); - _ready = true; - persist(); - emit asked(); - } - - void setSSLTrusted(bool isTrusted) - { - _sslTrusted = isTrusted; - } - - bool sslIsTrusted() override - { - return _sslTrusted; - } - -private: - bool _sslTrusted; -}; -#endif /* TOKEN_AUTH_ONLY */ - -void help() -{ - const char *binaryName = APPLICATION_EXECUTABLE "cmd"; - - std::cout << binaryName << " - command line " APPLICATION_NAME " client tool" << std::endl; - std::cout << "" << std::endl; - std::cout << "Usage: " << binaryName << " [OPTION] " << std::endl; - std::cout << "" << std::endl; - std::cout << "A proxy can either be set manually using --httpproxy." << std::endl; - std::cout << "Otherwise, the setting from a configured sync client will be used." << std::endl; - std::cout << std::endl; - std::cout << "Options:" << std::endl; - std::cout << " --silent, -s Don't be so verbose" << std::endl; - std::cout << " --httpproxy [proxy] Specify a http proxy to use." << std::endl; - std::cout << " Proxy is http://server:port" << std::endl; - std::cout << " --trust Trust the SSL certification." << std::endl; - std::cout << " --exclude [file] Exclude list file" << std::endl; - std::cout << " --unsyncedfolders [file] File containing the list of unsynced remote folders (selective sync)" << std::endl; - std::cout << " --user, -u [name] Use [name] as the login name" << std::endl; - std::cout << " --password, -p [pass] Use [pass] as password" << std::endl; - std::cout << " -n Use netrc (5) for login" << std::endl; - std::cout << " --non-interactive Do not block execution with interaction" << std::endl; - std::cout << " --davpath [path] Custom themed dav path" << std::endl; - std::cout << " --max-sync-retries [n] Retries maximum n times (default to 3)" << std::endl; - std::cout << " --uplimit [n] Limit the upload speed of files to n KB/s" << std::endl; - std::cout << " --downlimit [n] Limit the download speed of files to n KB/s" << std::endl; - std::cout << " -h Sync hidden files,do not ignore them" << std::endl; - std::cout << " --version, -v Display version and exit" << std::endl; - std::cout << " --logdebug More verbose logging" << std::endl; - std::cout << "" << std::endl; - exit(0); -} - -void showVersion() -{ - std::cout << qUtf8Printable(Theme::instance()->versionSwitchOutput()); - exit(0); -} - -CmdOptions parseOptions(const QStringList &app_args) -{ - CmdOptions options; - QStringList args(app_args); - - int argCount = args.count(); - - if (argCount < 3) { - if (argCount >= 2) { - const QString option = args.at(1); - if (option == "-v" || option == "--version") { - showVersion(); - } - } - help(); - } - - options.target_url = args.takeLast(); - // check if the webDAV path was added to the url and append if not. - if (!options.target_url.endsWith("/")) { - options.target_url.append("/"); - } - - options.source_dir = args.takeLast(); - if (!options.source_dir.endsWith('/')) { - options.source_dir.append('/'); - } - QFileInfo fi(options.source_dir); - if (!fi.exists()) { - std::cerr << "Source dir '" << qUtf8Printable(options.source_dir) << "' does not exist." << std::endl; - exit(1); - } - options.source_dir = fi.absoluteFilePath(); - - QStringListIterator it(args); - // skip file name; - if (it.hasNext()) - it.next(); - - while (it.hasNext()) { - const QString option = it.next(); - - if (option == "--httpproxy" && !it.peekNext().startsWith("-")) { - options.proxy = it.next(); - } else if (option == "-s" || option == "--silent") { - options.silent = true; - } else if (option == "--trust") { - options.trustSSL = true; - } else if (option == "-n") { - options.useNetrc = true; - } else if (option == "-h") { - options.ignoreHiddenFiles = false; - } else if (option == "--non-interactive") { - options.interactive = false; - } else if ((option == "-u" || option == "--user") && !it.peekNext().startsWith("-")) { - options.user = it.next(); - } else if ((option == "-p" || option == "--password") && !it.peekNext().startsWith("-")) { - options.password = it.next(); - } else if (option == "--exclude" && !it.peekNext().startsWith("-")) { - options.exclude = it.next(); - } else if (option == "--unsyncedfolders" && !it.peekNext().startsWith("-")) { - options.unsyncedfolders = it.next(); - } else if (option == "--davpath" && !it.peekNext().startsWith("-")) { - options.davPath = it.next(); - } else if (option == "--max-sync-retries" && !it.peekNext().startsWith("-")) { - options.restartTimes = it.next().toInt(); - } else if (option == "--uplimit" && !it.peekNext().startsWith("-")) { - options.uplimit = it.next().toInt() * 1000; - } else if (option == "--downlimit" && !it.peekNext().startsWith("-")) { - options.downlimit = it.next().toInt() * 1000; - } else if (option == "--logdebug") { - Logger::instance()->setLogFile("-"); - Logger::instance()->setLogDebug(true); - } else { - help(); - } - } - - if (options.target_url.isEmpty() || options.source_dir.isEmpty()) { - help(); - } - return options; -} - -int main(int argc, char **argv) -{ - QCoreApplication app(argc, argv); - -#ifdef Q_OS_WIN - // Ensure OpenSSL config file is only loaded from app directory - QString opensslConf = QCoreApplication::applicationDirPath() + QString("/openssl.cnf"); - qputenv("OPENSSL_CONF", opensslConf.toLocal8Bit()); -#endif - SyncCTX ctx { parseOptions(app.arguments()) }; - - if (ctx.options.silent) { - qInstallMessageHandler([](QtMsgType, const QMessageLogContext &, const QString &) {}); - } else { - qSetMessagePattern(Logger::loggerPattern()); - } - - ctx.account = Account::create(); - - if (!ctx.account) { - qFatal("Could not initialize account!"); - } - - if (!ctx.options.davPath.isEmpty()) { - ctx.account->setDavPath(ctx.options.davPath); - } - - if (!ctx.options.target_url.contains(ctx.account->davPath())) { - ctx.options.target_url.append(ctx.account->davPath()); - } - - const QUrl url = [&ctx] { - auto tmp = QUrl::fromUserInput(ctx.options.target_url); - // Find the folder and the original owncloud url - QStringList splitted = tmp.path().split("/" + ctx.account->davPath()); - tmp.setPath(splitted.value(0)); - tmp.setScheme(tmp.scheme().replace("owncloud", "http")); - - // Remote folders typically start with a / and don't end with one - ctx.folder = "/" + splitted.value(1); - if (ctx.folder.endsWith("/") && ctx.folder != "/") { - ctx.folder.chop(1); - } - return tmp; - }(); - ctx.credentialFreeUrl = url.adjusted(QUrl::RemoveUserInfo); - - // Order of retrieval attempt (later attempts override earlier ones): - // 1. From URL - // 2. From options - // 3. From netrc (if enabled) - // 4. From prompt (if interactive) - - ctx.user = url.userName(); - QString password = url.password(); - - if (!ctx.options.user.isEmpty()) { - ctx.user = ctx.options.user; - } - - if (!ctx.options.password.isEmpty()) { - password = ctx.options.password; - } - - if (ctx.options.useNetrc) { - NetrcParser parser; - if (parser.parse()) { - NetrcParser::LoginPair pair = parser.find(url.host()); - ctx.user = pair.first; - password = pair.second; - } - } - - if (ctx.options.interactive) { - if (ctx.user.isEmpty()) { - std::cout << "Please enter user name: "; - std::string s; - std::getline(std::cin, s); - ctx.user = QString::fromStdString(s); - } - if (password.isEmpty()) { - password = queryPassword(ctx.user); - } - } - - if (!ctx.options.proxy.isNull()) { - QString host; - int port = 0; - bool ok; - - QStringList pList = ctx.options.proxy.split(':'); - if (pList.count() == 3) { - // http: //192.168.178.23 : 8080 - // 0 1 2 - host = pList.at(1); - if (host.startsWith("//")) - host.remove(0, 2); - - port = pList.at(2).toInt(&ok); - - QNetworkProxyFactory::setUseSystemConfiguration(false); - QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::HttpProxy, host, port)); - } else { - qFatal("Could not read httpproxy. The proxy should have the format \"http://hostname:port\"."); - } - } - - SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler; - -#ifdef TOKEN_AUTH_ONLY - TokenCredentials *cred = new TokenCredentials(ctx.user, password, ""); - account->setCredentials(cred); -#else - HttpCredentialsText *cred = new HttpCredentialsText(ctx.user, password); - ctx.account->setCredentials(cred); - if (ctx.options.trustSSL) { - cred->setSSLTrusted(true); - } -#endif - - ctx.account->setUrl(ctx.credentialFreeUrl); - ctx.account->setSslErrorHandler(sslErrorHandler); - - // Perform a call to get the capabilities. - auto capabilitiesJob = new JsonApiJob(ctx.account, QLatin1String("ocs/v1.php/cloud/capabilities")); - QObject::connect(capabilitiesJob, &JsonApiJob::jsonReceived, qApp, [capabilitiesJob, ctx](const QJsonDocument &json) { - auto caps = json.object().value("ocs").toObject().value("data").toObject().value("capabilities").toObject(); - qDebug() << "Server capabilities" << caps; - ctx.account->setCapabilities(caps.toVariantMap()); - ctx.account->setServerVersion(caps["core"].toObject()["status"].toObject()["version"].toString()); - - if (capabilitiesJob->reply()->error() != QNetworkReply::NoError) { - qFatal("Error connecting to server"); - } - - auto userJob = new JsonApiJob(ctx.account, QLatin1String("ocs/v1.php/cloud/user")); - QObject::connect(userJob, &JsonApiJob::jsonReceived, qApp, [ctx](const QJsonDocument &json) { - const QJsonObject data = json.object().value("ocs").toObject().value("data").toObject(); - ctx.account->setDavUser(data.value("id").toString()); - ctx.account->setDavDisplayName(data.value("display-name").toString()); - - // much lower age than the default since this utility is usually made to be run right after a change in the tests - SyncEngine::minimumFileAgeForUpload = std::chrono::milliseconds(0); - sync(ctx); - }); - userJob->start(); - }); - capabilitiesJob->start(); - return app.exec(); -} diff --git a/src/cmd/netrcparser.cpp b/src/cmd/netrcparser.cpp deleted file mode 100644 index 4a893609139..00000000000 --- a/src/cmd/netrcparser.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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 -#include -#include - -#include - -#include - -#include "netrcparser.h" - -namespace OCC { - -namespace { - QString defaultKeyword = QLatin1String("default"); - QString machineKeyword = QLatin1String("machine"); - QString loginKeyword = QLatin1String("login"); - QString passwordKeyword = QLatin1String("password"); -} - -NetrcParser::NetrcParser(const QString &file) -{ - _netrcLocation = file; - if (_netrcLocation.isEmpty()) { - _netrcLocation = QDir::homePath() + QLatin1String("/.netrc"); - } -} - -void NetrcParser::tryAddEntryAndClear(QString &machine, LoginPair &pair, bool &isDefault) -{ - if (isDefault) { - _default = pair; - } else if (!machine.isEmpty() && !pair.first.isEmpty()) { - _entries.insert(machine, pair); - } - pair = qMakePair(QString(), QString()); - machine.clear(); - isDefault = false; -} - -bool NetrcParser::parse() -{ - QFile netrc(_netrcLocation); - if (!netrc.open(QIODevice::ReadOnly)) { - return false; - } - QString content = netrc.readAll(); - - QStringTokenizer tokenizer(content, " \n\t"); - tokenizer.setQuoteCharacters("\"'"); - - LoginPair pair; - QString machine; - bool isDefault = false; - while (tokenizer.hasNext()) { - QString key = tokenizer.next(); - if (key == defaultKeyword) { - tryAddEntryAndClear(machine, pair, isDefault); - isDefault = true; - continue; // don't read a value - } - - if (!tokenizer.hasNext()) { - qDebug() << "error fetching value for" << key; - return false; - } - QString value = tokenizer.next(); - - if (key == machineKeyword) { - tryAddEntryAndClear(machine, pair, isDefault); - machine = value; - } else if (key == loginKeyword) { - pair.first = value; - } else if (key == passwordKeyword) { - pair.second = value; - } // ignore unsupported tokens - } - tryAddEntryAndClear(machine, pair, isDefault); - - if (!_entries.isEmpty() || _default != qMakePair(QString(), QString())) { - return true; - } else { - return false; - } -} - -NetrcParser::LoginPair NetrcParser::find(const QString &machine) const -{ - return _entries.value(machine, _default); -} - -} // namespace OCC diff --git a/src/cmd/netrcparser.h b/src/cmd/netrcparser.h deleted file mode 100644 index cf3702b42d4..00000000000 --- a/src/cmd/netrcparser.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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. - */ - -#ifndef NETRCPARSER_H -#define NETRCPARSER_H - -#include -#include - -namespace OCC { - -/** - * @brief Parser for netrc files - * @ingroup cmd - */ -class NetrcParser -{ -public: - typedef QPair LoginPair; - - NetrcParser(const QString &file = QString()); - bool parse(); - LoginPair find(const QString &machine) const; - -private: - void tryAddEntryAndClear(QString &machine, LoginPair &pair, bool &isDefault); - QHash _entries; - LoginPair _default; - QString _netrcLocation; -}; - -} // namespace OCC - -#endif // NETRCPARSER_H diff --git a/src/cmd/simplesslerrorhandler.cpp b/src/cmd/simplesslerrorhandler.cpp deleted file mode 100644 index 8a578ea967a..00000000000 --- a/src/cmd/simplesslerrorhandler.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 "common/utility.h" -#include "account.h" -#include "simplesslerrorhandler.h" - -namespace OCC { - -bool SimpleSslErrorHandler::handleErrors(const QList &errors, const QSslConfiguration &, QList *certs, OCC::AccountPtr) -{ - Q_ASSERT(certs); - for (const auto &error : qAsConst(errors)) { - certs->append(error.certificate()); - } - return true; -} -} diff --git a/src/cmd/simplesslerrorhandler.h b/src/cmd/simplesslerrorhandler.h deleted file mode 100644 index 74e473e8d47..00000000000 --- a/src/cmd/simplesslerrorhandler.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ -#ifndef SIMPLESSLERRORHANDLER_H -#define SIMPLESSLERRORHANDLER_H - -#include "accountfwd.h" - -class QSslError; -class QSslCertificate; - -namespace OCC { - -/** - * @brief The SimpleSslErrorHandler class - * @ingroup cmd - */ -class SimpleSslErrorHandler : public OCC::AbstractSslErrorHandler -{ -public: - bool handleErrors(const QList &errors, const QSslConfiguration &conf, QList *certs, OCC::AccountPtr) override; -}; -} - -#endif // SIMPLESSLERRORHANDLER_H diff --git a/src/common/asserts.h b/src/common/asserts.h index 9f43c532a6d..c9fc17b9634 100644 --- a/src/common/asserts.h +++ b/src/common/asserts.h @@ -3,13 +3,6 @@ #include -#if 0 && defined(Q_CC_MSVC) -// requires c++2017 -#define OC_REQUIRED_RESULT [[nodiscard]] -#else -#define OC_REQUIRED_RESULT Q_REQUIRED_RESULT -#endif - #if defined(QT_FORCE_ASSERTS) || !defined(QT_NO_DEBUG) #define OC_ASSERT_MSG qFatal #else @@ -44,7 +37,7 @@ } else { \ } -inline OC_REQUIRED_RESULT bool __OC_ENSURE(bool condition, const char *cond, const char *file, int line, const char *info) +[[nodiscard]] inline bool __OC_ENSURE(bool condition, const char *cond, const char *file, int line, const char *info) { if (Q_UNLIKELY(!condition)) { OC_ASSERT_MSG("ENSURE: \"%s\" in file %s, line %d %s", cond, file, line, info); @@ -53,6 +46,7 @@ inline OC_REQUIRED_RESULT bool __OC_ENSURE(bool condition, const char *cond, con return true; } #define OC_ENSURE(cond) Q_LIKELY(__OC_ENSURE(cond, #cond, __FILE__, __LINE__, Q_FUNC_INFO)) +#define OC_ENSURE_NOT(cond) OC_ENSURE(!cond) // An assert that is only present in debug builds: typically used for // asserts that are too expensive for release mode. // diff --git a/src/common/c_jhash.h b/src/common/c_jhash.h index 038a62b672f..07a063fe90c 100644 --- a/src/common/c_jhash.h +++ b/src/common/c_jhash.h @@ -26,51 +26,6 @@ #include #include -#ifndef Q_FALLTHROUGH -#define Q_FALLTHROUGH() // Was added in Qt 5.8 -#endif - -#define c_hashsize(n) ((uint8_t) 1 << (n)) -#define c_hashmask(n) (xhashsize(n) - 1) - -/** - * _c_mix -- Mix 3 32-bit values reversibly. - * - * For every delta with one or two bit set, and the deltas of all three - * high bits or all three low bits, whether the original value of a,b,c - * is almost all zero or is uniformly distributed, - * If _c_mix() is run forward or backward, at least 32 bits in a,b,c - * have at least 1/4 probability of changing. - * If _c_mix() is run forward, every bit of c will change between 1/3 and - * 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) - * _c_mix() was built out of 36 single-cycle latency instructions in a - * structure that could supported 2x parallelism, like so: - * a -= b; - * a -= c; x = (c>>13); - * b -= c; a ^= x; - * b -= a; x = (a<<8); - * c -= a; b ^= x; - * c -= b; x = (b>>13); - * ... - * - * Unfortunately, superscalar Pentiums and Sparcs can't take advantage - * of that parallelism. They've also turned some of those single-cycle - * latency instructions into multi-cycle latency instructions. Still, - * this is the fastest good hash I could find. There were about 2^^68 - * to choose from. I only looked at a billion or so. - */ -#define _c_mix(a,b,c) \ -{ \ - a -= b; a -= c; a ^= (c>>13); \ - b -= c; b -= a; b ^= (a<<8); \ - c -= a; c -= b; c ^= (b>>13); \ - a -= b; a -= c; a ^= (c>>12); \ - b -= c; b -= a; b ^= (a<<16); \ - c -= a; c -= b; c ^= (b>>5); \ - a -= b; a -= c; a ^= (c>>3); \ - b -= c; b -= a; b ^= (a<<10); \ - c -= a; c -= b; c ^= (b>>15); \ -} /** * _c_mix64 -- Mix 3 64-bit values reversibly. @@ -106,67 +61,6 @@ c -= a; c -= b; c ^= (b>>22); \ } -/** - * @brief hash a variable-length key into a 32-bit value - * - * The best hash table sizes are powers of 2. There is no need to do - * mod a prime (mod is sooo slow!). If you need less than 32 bits, - * use a bitmask. For example, if you need only 10 bits, do - * h = (h & hashmask(10)); - * In which case, the hash table should have hashsize(10) elements. - * - * Use for hash table lookup, or anything where one collision in 2^32 is - * acceptable. Do NOT use for cryptographic purposes. - * - * @param k The key (the unaligned variable-length array of bytes). - * - * @param length The length of the key, counting by bytes. - * - * @param initval Initial value, can be any 4-byte value. - * - * @return Returns a 32-bit value. Every bit of the key affects every bit - * of the return value. Every 1-bit and 2-bit delta achieves - * avalanche. About 36+6len instructions. - */ -static inline uint32_t c_jhash(const uint8_t *k, uint32_t length, uint32_t initval) { - uint32_t a,b,c,len; - - /* Set up the internal state */ - len = length; - a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ - c = initval; /* the previous hash value */ - - while (len >= 12) { - a += (k[0] +((uint32_t)k[1]<<8) +((uint32_t)k[2]<<16) +((uint32_t)k[3]<<24)); - b += (k[4] +((uint32_t)k[5]<<8) +((uint32_t)k[6]<<16) +((uint32_t)k[7]<<24)); - c += (k[8] +((uint32_t)k[9]<<8) +((uint32_t)k[10]<<16)+((uint32_t)k[11]<<24)); - _c_mix(a,b,c); - k += 12; len -= 12; - } - - /* handle the last 11 bytes */ - c += length; - /* all the case statements fall through */ - switch(len) { - case 11: c+=((uint32_t)k[10]<<24); - case 10: c+=((uint32_t)k[9]<<16); - case 9 : c+=((uint32_t)k[8]<<8); - /* the first byte of c is reserved for the length */ - case 8 : b+=((uint32_t)k[7]<<24); - case 7 : b+=((uint32_t)k[6]<<16); - case 6 : b+=((uint32_t)k[5]<<8); - case 5 : b+=k[4]; - case 4 : a+=((uint32_t)k[3]<<24); - case 3 : a+=((uint32_t)k[2]<<16); - case 2 : a+=((uint32_t)k[1]<<8); - case 1 : a+=k[0]; - /* case 0: nothing left to add */ - } - _c_mix(a,b,c); - - return c; -} - /** * @brief hash a variable-length key into a 64-bit value * diff --git a/src/common/checksumalgorithms.cpp b/src/common/checksumalgorithms.cpp new file mode 100644 index 00000000000..b163a561659 --- /dev/null +++ b/src/common/checksumalgorithms.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "checksumalgorithms.h" + +using namespace OCC; +CheckSums::Algorithm CheckSums::fromByteArray(const QByteArray &s) +{ + // assert to ensure that all keys are uppercase + Q_ASSERT([] { + // ensure to run the check only once + static bool once = [] { + for (const auto &a : All) { + const QString s = Utility::enumToString(a.first); + if (s != s.toUpper()) { + return false; + } + } + return true; + }(); + return once; + }()); + return fromName(s.toUpper().constData()); +} + +template <> +QString Utility::enumToString(CheckSums::Algorithm algo) +{ + const auto n = toString(algo); + return QString::fromUtf8(n.data(), static_cast(n.size())); +} + +#include "moc_checksumalgorithms.cpp" diff --git a/src/common/checksumalgorithms.h b/src/common/checksumalgorithms.h new file mode 100644 index 00000000000..c5ef3953c9f --- /dev/null +++ b/src/common/checksumalgorithms.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 + */ +#pragma once + +#include "constexpr_list.h" +#include "ocsynclib.h" +#include "utility.h" + +#include +#include + + +namespace OCC { +namespace CheckSums { + OCSYNC_EXPORT Q_NAMESPACE; + + enum class Algorithm { + NONE, + SHA3_256 = QCryptographicHash::Sha3_256, + SHA256 = QCryptographicHash::Sha256, + SHA1 = QCryptographicHash::Sha1, + MD5 = QCryptographicHash::Md5, + ADLER32 = 100, + DUMMY_FOR_TESTS, + PARSE_ERROR + }; + Q_ENUM_NS(Algorithm); + + constexpr std::string_view toString(Algorithm algo) + { + switch (algo) { + case Algorithm::SHA3_256: + return "SHA3-256"; + case Algorithm::SHA256: + return "SHA256"; + case Algorithm::SHA1: + return "SHA1"; + case Algorithm::MD5: + return "MD5"; + case Algorithm::ADLER32: + return "ADLER32"; + case Algorithm::DUMMY_FOR_TESTS: + return "DUMMY_FOR_TESTS"; + case Algorithm::PARSE_ERROR: + break; + case Algorithm::NONE: + // while none is a valid enum value, it is not valid when used to create a checksum string + Q_UNREACHABLE(); + break; + } + return "ERROR"; + } + + inline QString toQString(Algorithm algo) + { + const auto n = toString(algo); + return QString::fromUtf8(n.data(), n.size()); + } + + constexpr auto pair(Algorithm a) + { + return std::make_pair(a, toString(a)); + } + + constexpr_list auto All = { + // Sorted by priority + pair(Algorithm::SHA3_256), + pair(Algorithm::SHA256), + pair(Algorithm::SHA1), + pair(Algorithm::MD5), + pair(Algorithm::ADLER32), + pair(Algorithm::DUMMY_FOR_TESTS) + }; + + constexpr_list auto UnsafeAlgorithms = { + pair(Algorithm::ADLER32), + pair(Algorithm::DUMMY_FOR_TESTS) + }; + + constexpr_list auto SafeAlgorithms = { + pair(Algorithm::SHA3_256), + pair(Algorithm::SHA256), + pair(Algorithm::SHA1), + pair(Algorithm::MD5) + }; + + inline Algorithm fromName(std::string_view s) + { + auto it = std::find_if(All.begin(), All.end(), [s](const auto &it) { + return it.second == s; + }); + if (it != All.end()) { + return it->first; + } + return Algorithm::PARSE_ERROR; + } + + OCSYNC_EXPORT Algorithm fromByteArray(const QByteArray &s); +} // namespace CheckSums + +template <> +OCSYNC_EXPORT QString Utility::enumToString(CheckSums::Algorithm algo); + +} // namespace OCC diff --git a/src/common/checksums.cpp b/src/common/checksums.cpp index d4b22ecb2a3..2bb63625b34 100644 --- a/src/common/checksums.cpp +++ b/src/common/checksums.cpp @@ -15,14 +15,17 @@ * 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 "config.h" -#include "filesystembase.h" #include "common/checksums.h" #include "asserts.h" +#include "common/chronoelapsedtimer.h" +#include "common/utility.h" +#include "filesystembase.h" -#include -#include +#include #include +#include +#include +#include #include @@ -82,37 +85,13 @@ * */ -namespace OCC { - -Q_LOGGING_CATEGORY(lcChecksums, "sync.checksums", QtInfoMsg) - -#define BUFSIZE qint64(500 * 1024) // 500 KiB - -static QByteArray calcCryptoHash(QIODevice *device, QCryptographicHash::Algorithm algo) -{ - QByteArray arr; - QCryptographicHash crypto( algo ); - - if (crypto.addData(device)) { - arr = crypto.result().toHex(); - } - return arr; - } - -QByteArray calcMd5(QIODevice *device) -{ - return calcCryptoHash(device, QCryptographicHash::Md5); -} -QByteArray calcSha1(QIODevice *device) -{ - return calcCryptoHash(device, QCryptographicHash::Sha1); -} +namespace { QByteArray calcAdler32(QIODevice *device) { - if (device->size() == 0) - { + const qint64 BUFSIZE(500 * 1024); // 500 KiB + if (device->size() == 0) { return QByteArray(); } QByteArray buf(BUFSIZE, Qt::Uninitialized); @@ -127,15 +106,77 @@ QByteArray calcAdler32(QIODevice *device) return QByteArray::number(adler, 16); } +} -QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum) +namespace OCC { + +Q_LOGGING_CATEGORY(lcChecksums, "sync.checksums", QtInfoMsg) +Q_LOGGING_CATEGORY(lcChecksumsHeader, "sync.checksums.header", QtInfoMsg) + +ChecksumHeader ChecksumHeader::parseChecksumHeader(const QByteArray &header) { - if (checksumType.isEmpty() || checksum.isEmpty()) - return QByteArray(); - QByteArray header = checksumType; - header.append(':'); - header.append(checksum); - return header; + if (header.isEmpty()) { + return {}; + } + ChecksumHeader out; + const auto idx = header.indexOf(':'); + if (idx < 0) { + out._error = QCoreApplication::translate("ChecksumHeader", "The checksum header is malformed: %1").arg(QString::fromUtf8(header)); + } else { + out._checksumType = CheckSums::fromByteArray(header.left(idx)); + if (out._checksumType == CheckSums::Algorithm::PARSE_ERROR) { + out._error = QCoreApplication::translate("ChecksumHeader", "The checksum header contained an unknown checksum type '%1'").arg(QString::fromUtf8(header.left(idx))); + } + out._checksum = header.mid(idx + 1); + } + if (!out.isValid()) { + qCDebug(lcChecksumsHeader) << "Failed to parse" << header << out.error(); + } + return out; +} + +ChecksumHeader::ChecksumHeader(CheckSums::Algorithm type, const QByteArray &checksum) + : _checksumType(type) + , _checksum(checksum) +{ +} + +QByteArray ChecksumHeader::makeChecksumHeader() const +{ + if (!isValid()) { + return {}; + } + return Utility::enumToString(_checksumType).toUtf8() + ':' + _checksum; +} + +CheckSums::Algorithm ChecksumHeader::type() const +{ + return _checksumType; +} + +QByteArray ChecksumHeader::checksum() const +{ + return _checksum; +} + +bool ChecksumHeader::isValid() const +{ + return _checksumType != CheckSums::Algorithm::NONE && _checksumType != CheckSums::Algorithm::PARSE_ERROR && !_checksum.isEmpty(); +} + +QString ChecksumHeader::error() const +{ + return _error; +} + +bool ChecksumHeader::operator==(const ChecksumHeader &other) const +{ + return _checksumType == other._checksumType && _checksum == other._checksum; +} + +bool ChecksumHeader::operator!=(const ChecksumHeader &other) const +{ + return !(*this == other); } QByteArray findBestChecksum(const QByteArray &_checksums) @@ -143,14 +184,19 @@ QByteArray findBestChecksum(const QByteArray &_checksums) if (_checksums.isEmpty()) { return {}; } - const auto checksums = QString::fromUtf8(_checksums); - int i = 0; + // The order of the searches here defines the preference ordering. - if (-1 != (i = checksums.indexOf(QLatin1String("SHA3-256:"), 0, Qt::CaseInsensitive)) - || -1 != (i = checksums.indexOf(QLatin1String("SHA256:"), 0, Qt::CaseInsensitive)) - || -1 != (i = checksums.indexOf(QLatin1String("SHA1:"), 0, Qt::CaseInsensitive)) - || -1 != (i = checksums.indexOf(QLatin1String("MD5:"), 0, Qt::CaseInsensitive)) - || -1 != (i = checksums.indexOf(QLatin1String("ADLER32:"), 0, Qt::CaseInsensitive))) { + // we assume _checksums to be utf-8 so toUpper is valid here + const auto i = [checksums = _checksums.toUpper()]() -> qsizetype { + for (const auto &algo : CheckSums::All) { + auto i = checksums.indexOf(algo.second.data()); + if (i != -1) { + return i; + } + } + return -1; + }(); + if (i != -1) { // Now i is the start of the best checksum // Grab it until the next space or end of xml or end of string. int end = _checksums.indexOf(' ', i); @@ -164,46 +210,12 @@ QByteArray findBestChecksum(const QByteArray &_checksums) return {}; } -bool parseChecksumHeader(const QByteArray &header, QByteArray *type, QByteArray *checksum) -{ - if (header.isEmpty()) { - type->clear(); - checksum->clear(); - return true; - } - - const auto idx = header.indexOf(':'); - if (idx < 0) { - return false; - } - - *type = header.left(idx); - *checksum = header.mid(idx + 1); - return true; -} - - -QByteArray parseChecksumHeaderType(const QByteArray &header) -{ - const auto idx = header.indexOf(':'); - if (idx < 0) { - return QByteArray(); - } - return header.left(idx); -} - bool uploadChecksumEnabled() { static bool enabled = qEnvironmentVariableIsEmpty("OWNCLOUD_DISABLE_CHECKSUM_UPLOAD"); return enabled; } -static bool checksumComputationEnabled() -{ - static bool enabled = qEnvironmentVariableIsEmpty("OWNCLOUD_DISABLE_CHECKSUM_COMPUTATIONS"); - return enabled; -} - ComputeChecksum::ComputeChecksum(QObject *parent) : QObject(parent) { @@ -213,12 +225,13 @@ ComputeChecksum::~ComputeChecksum() { } -void ComputeChecksum::setChecksumType(const QByteArray &type) +void ComputeChecksum::setChecksumType(CheckSums::Algorithm type) { + Q_ASSERT(type != CheckSums::Algorithm::NONE && type != CheckSums::Algorithm::PARSE_ERROR); _checksumType = type; } -QByteArray ComputeChecksum::checksumType() const +CheckSums::Algorithm ComputeChecksum::checksumType() const { return _checksumType; } @@ -267,7 +280,7 @@ void ComputeChecksum::startImpl(std::unique_ptr device) })); } -QByteArray ComputeChecksum::computeNowOnFile(const QString &filePath, const QByteArray &checksumType) +QByteArray ComputeChecksum::computeNowOnFile(const QString &filePath, CheckSums::Algorithm checksumType) { QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { @@ -278,39 +291,50 @@ QByteArray ComputeChecksum::computeNowOnFile(const QString &filePath, const QByt return computeNow(&file, checksumType); } -QByteArray ComputeChecksum::computeNow(QIODevice *device, const QByteArray &checksumType) +QByteArray ComputeChecksum::computeNow(QIODevice *device, CheckSums::Algorithm algorithm) { - if (!checksumComputationEnabled()) { - qCWarning(lcChecksums) << "Checksum computation disabled by environment variable"; - return QByteArray(); - } - - if (checksumType == checkSumMD5C) { - return calcMd5(device); - } else if (checksumType == checkSumSHA1C) { - return calcSha1(device); - } else if (checksumType == checkSumSHA2C) { - return calcCryptoHash(device, QCryptographicHash::Sha256); - } else if (checksumType == checkSumSHA3C) { - return calcCryptoHash(device, QCryptographicHash::Sha3_256); + // const cast to prevent stream to "device" + const auto log = qScopeGuard([device, algorithm, timer = Utility::ChronoElapsedTimer()] { + if (auto file = qobject_cast(device)) { + qCDebug(lcChecksums) << "Finished" << algorithm << "computation for" << file->fileName() << timer.duration(); + } else { + qCDebug(lcChecksums) << "Finished" << algorithm << "computation for" << device << timer.duration(); + } + }); + switch (algorithm) { + case CheckSums::Algorithm::SHA3_256: + [[fallthrough]]; + case CheckSums::Algorithm::SHA256: + [[fallthrough]]; + case CheckSums::Algorithm::SHA1: + [[fallthrough]]; + case CheckSums::Algorithm::MD5: { + QCryptographicHash crypto(static_cast(algorithm)); + if (crypto.addData(device)) { + return crypto.result().toHex(); + } + qCWarning(lcChecksums) << "Failed to compoute checksum" << Utility::enumToString(algorithm); + return {}; } - else if (checksumType == checkSumAdlerC) { + case CheckSums::Algorithm::ADLER32: return calcAdler32(device); + case CheckSums::Algorithm::DUMMY_FOR_TESTS: + return QByteArrayLiteral("0x1"); + case CheckSums::Algorithm::PARSE_ERROR: + return {}; + case CheckSums::Algorithm::NONE: + Q_UNREACHABLE(); } - // for an unknown checksum or no checksum, we're done right now - if (!checksumType.isEmpty()) { - qCWarning(lcChecksums) << "Unknown checksum type:" << checksumType; - } - return QByteArray(); + Q_UNREACHABLE(); } void ComputeChecksum::slotCalculationDone() { QByteArray checksum = _watcher.future().result(); if (!checksum.isNull()) { - emit done(_checksumType, checksum); + Q_EMIT done(_checksumType, checksum); } else { - emit done(QByteArray(), QByteArray()); + Q_EMIT done(CheckSums::Algorithm::PARSE_ERROR, QByteArray()); } } @@ -324,18 +348,18 @@ ComputeChecksum *ValidateChecksumHeader::prepareStart(const QByteArray &checksum { // If the incoming header is empty no validation can happen. Just continue. if (checksumHeader.isEmpty()) { - emit validated(QByteArray(), QByteArray()); + Q_EMIT validated(CheckSums::Algorithm::PARSE_ERROR, QByteArray()); return nullptr; } - - if (!parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum)) { + _expectedChecksum = ChecksumHeader::parseChecksumHeader(checksumHeader); + if (!_expectedChecksum.isValid()) { qCWarning(lcChecksums) << "Checksum header malformed:" << checksumHeader; - emit validationFailed(tr("The checksum header is malformed.")); + Q_EMIT validationFailed(_expectedChecksum.error()); return nullptr; } auto calculator = new ComputeChecksum(this); - calculator->setChecksumType(_expectedChecksumType); + calculator->setChecksumType(_expectedChecksum.type()); connect(calculator, &ComputeChecksum::done, this, &ValidateChecksumHeader::slotChecksumCalculated); return calculator; @@ -353,38 +377,18 @@ void ValidateChecksumHeader::start(std::unique_ptr device, const QByt calculator->start(std::move(device)); } -void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumType, +void ValidateChecksumHeader::slotChecksumCalculated(CheckSums::Algorithm checksumType, const QByteArray &checksum) { - if (checksumType != _expectedChecksumType) { - emit validationFailed(tr("The checksum header contained an unknown checksum type '%1'").arg(QString::fromLatin1(_expectedChecksumType))); + if (_expectedChecksum.type() == CheckSums::Algorithm::PARSE_ERROR) { + Q_EMIT validationFailed(_expectedChecksum.error()); return; } - if (checksum != _expectedChecksum) { - emit validationFailed(tr("The downloaded file does not match the checksum, it will be resumed. '%1' != '%2'").arg(QString::fromUtf8(_expectedChecksum), QString::fromUtf8(checksum))); + if (checksum != _expectedChecksum.checksum()) { + Q_EMIT validationFailed(tr("The downloaded file does not match the checksum, it will be resumed. '%1' != '%2'") + .arg(QString::fromUtf8(_expectedChecksum.checksum()), QString::fromUtf8(checksum))); return; } - emit validated(checksumType, checksum); -} - -CSyncChecksumHook::CSyncChecksumHook() -{ + Q_EMIT validated(checksumType, checksum); } - -QByteArray CSyncChecksumHook::hook(const QByteArray &path, const QByteArray &otherChecksumHeader, void * /*this_obj*/) -{ - QByteArray type = parseChecksumHeaderType(QByteArray(otherChecksumHeader)); - if (type.isEmpty()) - return nullptr; - - qCInfo(lcChecksums) << "Computing" << type << "checksum of" << path << "in the csync hook"; - QByteArray checksum = ComputeChecksum::computeNowOnFile(QString::fromUtf8(path), type); - if (checksum.isNull()) { - qCWarning(lcChecksums) << "Failed to compute checksum" << type << "for" << path; - return nullptr; - } - - return makeChecksumHeader(type, checksum); -} - } diff --git a/src/common/checksums.h b/src/common/checksums.h index f0b2af02211..b303bcae531 100644 --- a/src/common/checksums.h +++ b/src/common/checksums.h @@ -18,12 +18,13 @@ #pragma once +#include "checksumalgorithms.h" #include "ocsynclib.h" -#include "config.h" -#include #include +#include #include +#include #include @@ -35,11 +36,6 @@ namespace OCC { * Tags for checksum headers values. * They are here for being shared between Upload- and Download Job */ -static const char checkSumMD5C[] = "MD5"; -static const char checkSumSHA1C[] = "SHA1"; -static const char checkSumSHA2C[] = "SHA256"; -static const char checkSumSHA3C[] = "SHA3-256"; -static const char checkSumAdlerC[] = "Adler32"; class SyncJournalDb; @@ -52,24 +48,38 @@ class SyncJournalDb; */ OCSYNC_EXPORT QByteArray findBestChecksum(const QByteArray &checksums); +class OCSYNC_EXPORT ChecksumHeader +{ +public: + static ChecksumHeader parseChecksumHeader(const QByteArray &header); + + ChecksumHeader() = default; + ChecksumHeader(CheckSums::Algorithm type, const QByteArray &checksum); + + QByteArray makeChecksumHeader() const; + -/// Creates a checksum header from type and value. -OCSYNC_EXPORT QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum); + CheckSums::Algorithm type() const; -/// Parses a checksum header -OCSYNC_EXPORT bool parseChecksumHeader(const QByteArray &header, QByteArray *type, QByteArray *checksum); + QByteArray checksum() const; -/// Convenience for getting the type from a checksum header, null if none -OCSYNC_EXPORT QByteArray parseChecksumHeaderType(const QByteArray &header); + bool isValid() const; + + QString error() const; + + bool operator==(const ChecksumHeader &other) const; + + bool operator!=(const ChecksumHeader &other) const; + +private: + CheckSums::Algorithm _checksumType = CheckSums::Algorithm::NONE; + QByteArray _checksum; + QString _error; +}; /// Checks OWNCLOUD_DISABLE_CHECKSUM_UPLOAD OCSYNC_EXPORT bool uploadChecksumEnabled(); -// Exported functions for the tests. -QByteArray OCSYNC_EXPORT calcMd5(QIODevice *device); -QByteArray OCSYNC_EXPORT calcSha1(QIODevice *device); -QByteArray OCSYNC_EXPORT calcAdler32(QIODevice *device); - /** * Computes the checksum of a file. * \ingroup libsync @@ -82,11 +92,11 @@ class OCSYNC_EXPORT ComputeChecksum : public QObject ~ComputeChecksum() override; /** - * Sets the checksum type to be used. The default is empty. + * Sets the checksum type to be used. */ - void setChecksumType(const QByteArray &type); + void setChecksumType(CheckSums::Algorithm type); - QByteArray checksumType() const; + CheckSums::Algorithm checksumType() const; /** * Computes the checksum for the given file path. @@ -108,23 +118,23 @@ class OCSYNC_EXPORT ComputeChecksum : public QObject /** * Computes the checksum synchronously. */ - static QByteArray computeNow(QIODevice *device, const QByteArray &checksumType); + static QByteArray computeNow(QIODevice *device, CheckSums::Algorithm algo); /** * Computes the checksum synchronously on file. Convenience wrapper for computeNow(). */ - static QByteArray computeNowOnFile(const QString &filePath, const QByteArray &checksumType); + static QByteArray computeNowOnFile(const QString &filePath, CheckSums::Algorithm checksumType); -signals: - void done(const QByteArray &checksumType, const QByteArray &checksum); +Q_SIGNALS: + void done(CheckSums::Algorithm checksumType, const QByteArray &checksum); -private slots: +private Q_SLOTS: void slotCalculationDone(); private: void startImpl(std::unique_ptr device); - QByteArray _checksumType; + CheckSums::Algorithm _checksumType; // watcher for the checksum calculation thread QFutureWatcher _watcher; @@ -159,37 +169,16 @@ class OCSYNC_EXPORT ValidateChecksumHeader : public QObject */ void start(std::unique_ptr device, const QByteArray &checksumHeader); -signals: - void validated(const QByteArray &checksumType, const QByteArray &checksum); +Q_SIGNALS: + void validated(CheckSums::Algorithm checksumType, const QByteArray &checksum); void validationFailed(const QString &errMsg); -private slots: - void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum); +private Q_SLOTS: + void slotChecksumCalculated(CheckSums::Algorithm checksumType, const QByteArray &checksum); private: ComputeChecksum *prepareStart(const QByteArray &checksumHeader); - QByteArray _expectedChecksumType; - QByteArray _expectedChecksum; -}; - -/** - * Hooks checksum computations into csync. - * @ingroup libsync - */ -class OCSYNC_EXPORT CSyncChecksumHook : public QObject -{ - Q_OBJECT -public: - explicit CSyncChecksumHook(); - - /** - * Returns the checksum value for \a path that is comparable to \a otherChecksum. - * - * Called from csync, where a instance of CSyncChecksumHook has - * to be set as userdata. - * The return value will be owned by csync. - */ - static QByteArray hook(const QByteArray &path, const QByteArray &otherChecksumHeader, void *this_obj); + ChecksumHeader _expectedChecksum; }; } diff --git a/src/common/chronoelapsedtimer.cpp b/src/common/chronoelapsedtimer.cpp new file mode 100644 index 00000000000..463842ef098 --- /dev/null +++ b/src/common/chronoelapsedtimer.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "chronoelapsedtimer.h" + +#include + +using namespace OCC::Utility; + +ChronoElapsedTimer::ChronoElapsedTimer() + : _start(std::chrono::steady_clock::now()) +{ +} + +void ChronoElapsedTimer::reset() +{ + _start = std::chrono::steady_clock::now(); + _end = {}; +} + +void ChronoElapsedTimer::stop() +{ + Q_ASSERT(_end == std::chrono::steady_clock::time_point {}); + _end = std::chrono::steady_clock::now(); +} + +std::chrono::nanoseconds ChronoElapsedTimer::duration() const +{ + if (_end != std::chrono::steady_clock::time_point {}) { + return _end - _start; + } + return std::chrono::steady_clock::now() - _start; +} diff --git a/src/common/chronoelapsedtimer.h b/src/common/chronoelapsedtimer.h new file mode 100644 index 00000000000..22296a3b4f0 --- /dev/null +++ b/src/common/chronoelapsedtimer.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 + */ +#pragma once +#include "ocsynclib.h" + +#include + +namespace OCC::Utility { + +/** + * Meassure time using std::chrono::steady_clock + */ +class OCSYNC_EXPORT ChronoElapsedTimer +{ +public: + ChronoElapsedTimer(); + + /** + * Resets the timer + */ + void reset(); + /** + * Stops the timer + */ + void stop(); + /** + * Returns the elapsed time. + * If the timer is stopped it is the time between start and stop of the timer. + */ + std::chrono::nanoseconds duration() const; + +private: + std::chrono::steady_clock::time_point _start = {}; + std::chrono::steady_clock::time_point _end = {}; +}; + +} diff --git a/src/common/common.cmake b/src/common/common.cmake index 52fc5de54eb..225884f8f87 100644 --- a/src/common/common.cmake +++ b/src/common/common.cmake @@ -2,8 +2,11 @@ # Essentially they could be in the same directory but are separate to # help keep track of the different code licenses. configure_file(${CMAKE_CURRENT_LIST_DIR}/version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) +configure_file(${CMAKE_CURRENT_LIST_DIR}/version-defines.h.in ${CMAKE_CURRENT_BINARY_DIR}/version-defines.h @ONLY) set(common_SOURCES ${CMAKE_CURRENT_LIST_DIR}/checksums.cpp + ${CMAKE_CURRENT_LIST_DIR}/checksumalgorithms.cpp + ${CMAKE_CURRENT_LIST_DIR}/chronoelapsedtimer.cpp ${CMAKE_CURRENT_LIST_DIR}/filesystembase.cpp ${CMAKE_CURRENT_LIST_DIR}/ownsql.cpp ${CMAKE_CURRENT_LIST_DIR}/preparedsqlquerymanager.cpp @@ -14,12 +17,11 @@ set(common_SOURCES ${CMAKE_CURRENT_LIST_DIR}/vfs.cpp ${CMAKE_CURRENT_LIST_DIR}/pinstate.cpp ${CMAKE_CURRENT_LIST_DIR}/plugin.cpp + ${CMAKE_CURRENT_LIST_DIR}/restartmanager.cpp ${CMAKE_CURRENT_LIST_DIR}/syncfilestatus.cpp ${CMAKE_CURRENT_BINARY_DIR}/version.cpp ) - - if(WIN32) list(APPEND common_SOURCES ${CMAKE_CURRENT_LIST_DIR}/utility_win.cpp diff --git a/src/common/constexpr_list.h b/src/common/constexpr_list.h new file mode 100644 index 00000000000..c3ca32810a0 --- /dev/null +++ b/src/common/constexpr_list.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 + */ +#pragma once + +#include + +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102921 +// we hope it will be fixed in GCC >= 13 +#if !defined(Q_CC_GNU) || Q_CC_GNU >= 1300 +#define constexpr_list constexpr +#else +#define constexpr_list inline +#endif \ No newline at end of file diff --git a/src/common/filesystembase.cpp b/src/common/filesystembase.cpp index d349d31de87..aae61843b79 100644 --- a/src/common/filesystembase.cpp +++ b/src/common/filesystembase.cpp @@ -20,33 +20,70 @@ #include "utility.h" #include "common/asserts.h" +#include #include -#include -#include #include -#include +#include +#include + +#include #include #include #ifdef Q_OS_WIN -#include -#include -#include +#include "utility_win.h" #include #include #endif +namespace { +// Regarding +// https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits +// the file name limit appears to be about 255 chars +// -10 for safety +constexpr qsizetype fileNameMaxC = 255 - 10; + +constexpr auto replacementCharC = QLatin1Char('_'); +} + namespace OCC { Q_LOGGING_CATEGORY(lcFileSystem, "sync.filesystem", QtInfoMsg) +QByteArray FileSystem::encodeFileName(const QString &fileName) +{ + return fileName.toLocal8Bit(); +} + +QString FileSystem::decodeFileName(const char *localFileName) +{ + return QString::fromLocal8Bit(localFileName); +} + QString FileSystem::longWinPath(const QString &inpath) { -#ifdef Q_OS_WIN - return pathtoUNC(inpath); -#else +#ifndef Q_OS_WIN return inpath; +#else + Q_ASSERT(QFileInfo(inpath).isAbsolute()); + if (inpath.isEmpty()) { + return inpath; + } + const QString str = QDir::toNativeSeparators(inpath); + const QLatin1Char sep('\\'); + + // we already have a unc path + if (str.startsWith(sep + sep)) { + return str; + } + // prepend \\?\ and to support long names + + if (str.at(0) == sep) { + // should not happen as we require the path to be absolute + return QStringLiteral("\\\\?") + str; + } + return QStringLiteral("\\\\?\\") + str; #endif } @@ -104,18 +141,6 @@ void FileSystem::setFileReadOnly(const QString &filename, bool readonly) file.setPermissions(permissions); } -void FileSystem::setFolderMinimumPermissions(const QString &filename) -{ -#ifdef Q_OS_MAC - QFile::Permissions perm = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; - QFile file(filename); - file.setPermissions(perm); -#else - Q_UNUSED(filename); -#endif -} - - void FileSystem::setFileReadOnlyWeak(const QString &filename, bool readonly) { QFile file(filename); @@ -135,28 +160,33 @@ bool FileSystem::rename(const QString &originFileName, bool success = false; QString error; #ifdef Q_OS_WIN - const QString orig = longWinPath(originFileName); + const QString originalFileNameLong = longWinPath(originFileName); const QString dest = longWinPath(destinationFileName); if (FileSystem::isFileLocked(dest, FileSystem::LockMode::Exclusive)) { error = QCoreApplication::translate("FileSystem", "Can't rename %1, the file is currently in use").arg(destinationFileName); - } else if (FileSystem::isFileLocked(orig, FileSystem::LockMode::Exclusive)) { + } else if (FileSystem::isFileLocked(originalFileNameLong, FileSystem::LockMode::Exclusive)) { error = QCoreApplication::translate("FileSystem", "Can't rename %1, the file is currently in use").arg(originFileName); } else if (isLnkFile(originFileName) || isLnkFile(destinationFileName)) { - success = MoveFileEx((wchar_t *)orig.utf16(), - (wchar_t *)dest.utf16(), - MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH); + success = MoveFileEx((wchar_t *)originalFileNameLong.utf16(), (wchar_t *)dest.utf16(), MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH); if (!success) { error = Utility::formatWinError(GetLastError()); } - } else -#endif - { + } else { QFile orig(originFileName); success = orig.rename(destinationFileName); if (!success) { error = orig.errorString(); } } +#else + std::error_code err; + std::filesystem::rename(originFileName.toStdString(), destinationFileName.toStdString(), err); + if (err) { + error = QString::fromStdString(err.message()); + } else { + success = true; + } +#endif if (!success) { qCWarning(lcFileSystem) << "Error renaming file" << originFileName @@ -174,28 +204,15 @@ bool FileSystem::uncheckedRenameReplace(const QString &originFileName, QString *errorString) { Q_ASSERT(errorString); + #ifndef Q_OS_WIN - bool success; - QFile orig(originFileName); - // We want a rename that also overwites. QFile::rename does not overwite. - // Qt 5.1 has QSaveFile::renameOverwrite we could use. - // ### FIXME - success = true; - bool destExists = fileExists(destinationFileName); - if (destExists && !QFile::remove(destinationFileName)) { - *errorString = orig.errorString(); - qCWarning(lcFileSystem) << "Target file could not be removed."; - success = false; - } - if (success) { - success = orig.rename(destinationFileName); - } - if (!success) { - *errorString = orig.errorString(); + std::error_code err; + std::filesystem::rename(originFileName.toStdString(), destinationFileName.toStdString(), err); + if (err) { + *errorString = QString::fromStdString(err.message()); qCWarning(lcFileSystem) << "Renaming temp file to final failed: " << *errorString; return false; } - #else //Q_OS_WIN // You can not overwrite a read-only file on windows. @@ -216,7 +233,7 @@ bool FileSystem::uncheckedRenameReplace(const QString &originFileName, } const BOOL ok = MoveFileEx(reinterpret_cast(orig.utf16()), reinterpret_cast(dest.utf16()), - MOVEFILE_REPLACE_EXISTING + MOVEFILE_COPY_ALLOWED + MOVEFILE_WRITE_THROUGH); + MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH); if (!ok) { *errorString = Utility::formatWinError(GetLastError()); qCWarning(lcFileSystem) << "Renaming temp file to final failed: " << *errorString; @@ -246,17 +263,10 @@ bool FileSystem::openAndSeekFileSharedRead(QFile *file, QString *errorOrNull, qi DWORD creationDisp = OPEN_EXISTING; // Create the file handle. - SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE }; + SECURITY_ATTRIBUTES securityAtts = {sizeof(SECURITY_ATTRIBUTES), nullptr, FALSE}; QString fName = longWinPath(file->fileName()); - HANDLE fileHandle = CreateFileW( - (const wchar_t *)fName.utf16(), - accessRights, - shareMode, - &securityAtts, - creationDisp, - FILE_ATTRIBUTE_NORMAL, - NULL); + HANDLE fileHandle = CreateFileW((const wchar_t *)fName.utf16(), accessRights, shareMode, &securityAtts, creationDisp, FILE_ATTRIBUTE_NORMAL, nullptr); // Bail out on error. if (fileHandle == INVALID_HANDLE_VALUE) { @@ -327,35 +337,62 @@ bool FileSystem::fileExists(const QString &filename, const QFileInfo &fileInfo) #endif bool re = fileInfo.exists(); // if the filename is different from the filename in fileInfo, the fileInfo is - // not valid. There needs to be one initialised here. Otherwise the incoming + // not valid. There needs to be one initialised here. Otherwise, the incoming // fileInfo is re-used. if (fileInfo.filePath() != filename) { - QFileInfo myFI(filename); - re = myFI.exists(); + re = QFileInfo::exists(filename); } return re; } +bool FileSystem::mkpath(const QString &parent, const QString &newDir) +{ #ifdef Q_OS_WIN + return QDir(parent).mkpath(newDir); +#else // POSIX + std::error_code err; + QString fullPath = parent; + if (!fullPath.endsWith(u'/')) { + fullPath += u'/'; + } + fullPath += newDir; + std::filesystem::create_directories(fullPath.toStdString(), err); + return err.value() == 0; +#endif +} + QString FileSystem::fileSystemForPath(const QString &path) { - // See also QStorageInfo (Qt >=5.4) and GetVolumeInformationByHandleW (>= Vista) - QString drive = path.left(2); - if (!drive.endsWith(QLatin1Char(':'))) - return QString(); - drive.append(QLatin1Char('\\')); - - const size_t fileSystemBufferSize = 4096; - TCHAR fileSystemBuffer[fileSystemBufferSize]; - - if (!GetVolumeInformationW( - reinterpret_cast(drive.utf16()), - NULL, 0, - NULL, NULL, NULL, - fileSystemBuffer, fileSystemBufferSize)) { - return QString(); - } - return QString::fromUtf16(reinterpret_cast(fileSystemBuffer)); + QString p = path; + while (true) { + if (!fileExists(p)) { + QFileInfo file(p); + p = file.absolutePath(); + continue; + } + const QStorageInfo storage(p); + if (!storage.isValid() || !storage.isReady()) { + return {}; + } + + return QString::fromUtf8(storage.fileSystemType()); + } +} + +#ifdef Q_OS_WIN +bool FileSystem::longPathsEnabledOnWindows() +{ + static std::optional longPathsEnabledCached = {}; + + if (!longPathsEnabledCached.has_value()) { + // https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry#enable-long-paths-in-windows-10-version-1607-and-later + QSettings fsSettings(QStringLiteral("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\FileSystem"), QSettings::NativeFormat); + QVariant longPathsEnabled = fsSettings.value(QStringLiteral("LongPathsEnabled")); + qCDebug(lcFileSystem) << "LongPathsEnabled:" << longPathsEnabled; + longPathsEnabledCached = longPathsEnabled.value() == 1; + } + + return longPathsEnabledCached.value(); } #endif @@ -380,110 +417,66 @@ bool FileSystem::remove(const QString &fileName, QString *errorString) return true; } -bool FileSystem::moveToTrash(const QString &fileName, QString *errorString) -{ - // TODO: Qt 5.15 bool QFile::moveToTrash() -#if defined Q_OS_UNIX && !defined Q_OS_MAC - QString trashPath, trashFilePath, trashInfoPath; - QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME")); - if (xdgDataHome.isEmpty()) { - trashPath = QDir::homePath() + QStringLiteral("/.local/share/Trash/"); // trash path that should exist - } else { - trashPath = xdgDataHome + QStringLiteral("/Trash/"); - } - - trashFilePath = trashPath + QStringLiteral("files/"); // trash file path contain delete files - trashInfoPath = trashPath + QStringLiteral("info/"); // trash info path contain delete files information - - if (!(QDir().mkpath(trashFilePath) && QDir().mkpath(trashInfoPath))) { - *errorString = QCoreApplication::translate("FileSystem", "Could not make directories in trash"); - return false; //mkpath will return true if path exists - } - - QFileInfo f(fileName); +#ifdef Q_OS_WIN - QDir file; - int suffix_number = 1; - if (file.exists(trashFilePath + f.fileName())) { //file in trash already exists, move to "filename.1" - QString path = trashFilePath + f.fileName() + QLatin1Char('.'); - while (file.exists(path + QString::number(suffix_number))) { //or to "filename.2" if "filename.1" exists, etc - suffix_number++; - } - if (!file.rename(f.absoluteFilePath(), path + QString::number(suffix_number))) { // rename(file old path, file trash path) - *errorString = QCoreApplication::translate("FileSystem", "Could not move '%1' to '%2'") - .arg(f.absoluteFilePath(), path + QString::number(suffix_number)); - return false; +namespace { + /** + * This function creates a file handle with the desired LockMode + */ + Utility::Handle lockFile(const QString &fileName, FileSystem::LockMode mode) + { + const QString fName = FileSystem::longWinPath(fileName); + int shareMode = 0; + int accessMode = GENERIC_READ | GENERIC_WRITE; + switch (mode) { + case FileSystem::LockMode::Exclusive: + shareMode = 0; + break; + case FileSystem::LockMode::Shared: + shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + break; + case FileSystem::LockMode::SharedRead: + shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + accessMode = GENERIC_READ; + break; } - } else { - if (!file.rename(f.absoluteFilePath(), trashFilePath + f.fileName())) { // rename(file old path, file trash path) - *errorString = QCoreApplication::translate("FileSystem", "Could not move '%1' to '%2'") - .arg(f.absoluteFilePath(), trashFilePath + f.fileName()); - return false; + // Check if file exists + DWORD attr = GetFileAttributesW(reinterpret_cast(fName.utf16())); + if (attr != INVALID_FILE_ATTRIBUTES) { + // Try to open the file with as much access as possible.. + auto out = Utility::Handle{CreateFileW(reinterpret_cast(fName.utf16()), accessMode, shareMode, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr)}; + + if (out) { + LARGE_INTEGER start; + start.QuadPart = 0; + LARGE_INTEGER end; + end.QuadPart = -1; + if (LockFile(out.handle(), start.LowPart, start.HighPart, end.LowPart, end.HighPart)) { + OC_ENFORCE(UnlockFile(out.handle(), start.LowPart, start.HighPart, end.LowPart, end.HighPart)); + return out; + } else { + return {}; + } + } + return out; } + return {}; } - - // create file format for trash info file----- START - QFile infoFile; - if (file.exists(trashInfoPath + f.fileName() + QStringLiteral(".trashinfo"))) { //TrashInfo file already exists, create "filename.1.trashinfo" - QString filename = trashInfoPath + f.fileName() + QLatin1Char('.') + QString::number(suffix_number) + QStringLiteral(".trashinfo"); - infoFile.setFileName(filename); //filename+.trashinfo // create file information file in /.local/share/Trash/info/ folder - } else { - QString filename = trashInfoPath + f.fileName() + QStringLiteral(".trashinfo"); - infoFile.setFileName(filename); //filename+.trashinfo // create file information file in /.local/share/Trash/info/ folder - } - - infoFile.open(QIODevice::ReadWrite); - - QTextStream stream(&infoFile); // for write data on open file - - stream << "[Trash Info]\n" - << "Path=" - << QUrl::toPercentEncoding(f.absoluteFilePath(), "~_-./") - << "\n" - << "DeletionDate=" - << QDateTime::currentDateTime().toString(Qt::ISODate) - << '\n'; - infoFile.close(); - - // create info file format of trash file----- END - - return true; -#else - Q_UNUSED(fileName) - *errorString = QCoreApplication::translate("FileSystem", "Moving to the trash is not implemented on this platform"); - return false; -#endif } +#endif + bool FileSystem::isFileLocked(const QString &fileName, LockMode mode) { #ifdef Q_OS_WIN - // Check if file exists - const QString fName = longWinPath(fileName); - auto accessMode = mode == LockMode::Exclusive ? 0 : FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; - DWORD attr = GetFileAttributesW(reinterpret_cast(fName.utf16())); - if (attr != INVALID_FILE_ATTRIBUTES) { - // Try to open the file with as much access as possible.. - HANDLE win_h = CreateFileW( - reinterpret_cast(fName.utf16()), - GENERIC_READ | GENERIC_WRITE, - accessMode, - NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, - NULL); - - if (win_h == INVALID_HANDLE_VALUE) { - if (GetLastError() == ERROR_SHARING_VIOLATION) { - return true; - } - return false; - } else { - CloseHandle(win_h); - } - } else { + const auto handle = lockFile(fileName, mode); + if (!handle) { const auto error = GetLastError(); - if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND) { - qCWarning(lcFileSystem()) << "GetFileAttributesW" << Utility::formatWinError(error); + if (error == ERROR_SHARING_VIOLATION || error == ERROR_LOCK_VIOLATION) { + return true; + } else if (error != ERROR_FILE_NOT_FOUND && error != ERROR_PATH_NOT_FOUND) { + qCWarning(lcFileSystem()) << Q_FUNC_INFO << Utility::formatWinError(error); } } #else @@ -502,7 +495,8 @@ bool FileSystem::isJunction(const QString &filename) { #ifdef Q_OS_WIN WIN32_FIND_DATA findData; - HANDLE hFind = FindFirstFileEx(reinterpret_cast(longWinPath(filename).utf16()), FindExInfoBasic, &findData, FindExSearchNameMatch, NULL, 0); + HANDLE hFind = + FindFirstFileEx(reinterpret_cast(longWinPath(filename).utf16()), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, 0); if (hFind != INVALID_HANDLE_VALUE) { FindClose(hFind); return false; @@ -516,38 +510,88 @@ bool FileSystem::isJunction(const QString &filename) #endif } -#ifdef Q_OS_WIN -QString FileSystem::pathtoUNC(const QString &_str) +bool FileSystem::isChildPathOf(QStringView child, QStringView parent) { - Q_ASSERT(QFileInfo(_str).isAbsolute()); - if (_str.isEmpty()) { - return _str; + // if it is a relative path assume a local file, resolve it based on root + const auto sensitivity = Utility::fsCaseSensitivity(); + + // Fast-path the 3 common cases in this if-else statement: + if (parent.isEmpty()) { + // The empty parent is often used as the sync root, as (child) items do not start with a `/` + return true; } - const QString str = QDir::toNativeSeparators(_str); - const QLatin1Char sep('\\'); - // we already have a unc path - if (str.startsWith(sep + sep)) { - return str; + const auto isSeparator = [](QChar c) { return c == QLatin1Char('/') || (Utility::isWindows() && c == QLatin1Char('\\')); }; + + if (isSeparator(parent.back())) { + // Here we can do a normal prefix check, because the parent is "terminated" with a slash, + // and we can't walk into the case in the else below. + if (child.startsWith(parent, sensitivity)) { + return true; + } + // else: do the `cleanPath` version below + } else { + // Here we fast-path for the case the child is "Documents/a" and the parent is "Documents", + // and *not* the case where the child is "Documents/a" and the parent is "Document". In + // the latter case, the does start with the name of the parent, but there is an extra 's', + // and not a slash. + + // Prevent a string concatenation like in `child.startsWith(parent + QLatin1Char('/'))`: + // first check if the child string starts with the parent string + if (child.startsWith(parent, sensitivity)) { + // ok, now check if the character after the parent is a '/' + if (child.length() >= parent.length() + 1 && isSeparator(child.at(parent.length()))) { + return true; + } + // else: do the `cleanPath` version below + } } - // prepend \\?\ and to support long names - if (str.at(0) == sep) { - // should not happen as we require the path to be absolute - return QStringLiteral("\\\\?") + str; + // Slow path (`QDir::cleanPath` does lots of string operations): + const QString cleanParent = QDir::cleanPath(parent.toString()); + const QString cleanChild = QDir::cleanPath(child.toString()); + // cleanPath removes trailing slashes, add one to parent to be sure we handle a child path + // /root/foo/bar is not a child of /root/fo, therefor the trailing slash is important + if (cleanChild.startsWith(cleanParent + QLatin1Char('/'), sensitivity)) { + return true; } - return QStringLiteral("\\\\?\\") + str; + // both paths are the same /root/foo == /root/foo + if (cleanChild.compare(cleanParent, sensitivity) == 0) { + return true; + } + return false; } -#endif -bool FileSystem::isChildPathOf(const QString &child, const QString &parent) +QString FileSystem::createPortableFileName(const QString &path, const QString &fileName, qsizetype reservedSize) { - // if it is a relative path assume a local file, resolve it based on root - const auto sensitivity = Utility::fsCaseSensitivity(); - return (child.startsWith(parent.endsWith(QLatin1Char('/')) ? parent : parent + QLatin1Char('/'), sensitivity) - // clear trailing slashes etc - || QString::compare(QDir::cleanPath(parent), QDir::cleanPath(child), sensitivity) == 0); + // remove leading and trailing whitespace + QString tmp = pathEscape(fileName); + // limit size to fileNameMaxC + tmp.resize(std::min(tmp.size(), fileNameMaxC - reservedSize)); + // remove eventual trailing whitespace after the resize + tmp = tmp.trimmed(); + + return QDir::cleanPath(path + QLatin1Char('/') + tmp); +} + +QString FileSystem::pathEscape(const QString &s) +{ + QString tmp = s; + tmp.replace(QLatin1String("../"), QString(replacementCharC)); +#ifdef Q_OS_WIN + tmp.replace(QLatin1String("..\\"), QString(replacementCharC)); +#endif + + // escape the folder separator, "\" is handled in IllegalFilenameCharsWindows + tmp.replace(QLatin1Char('/'), replacementCharC); + + // replace non portable characters + for (auto c : IllegalFilenameCharsWindows) { + tmp.replace(c, replacementCharC); + } + return tmp.trimmed(); } + } // namespace OCC #include "moc_filesystembase.cpp" diff --git a/src/common/filesystembase.h b/src/common/filesystembase.h index b4ace754cdc..259d3266644 100644 --- a/src/common/filesystembase.h +++ b/src/common/filesystembase.h @@ -18,14 +18,17 @@ #pragma once -#include "config.h" +#include "constexpr_list.h" +#include "ocsynclib.h" +#include "utility.h" -#include -#include #include #include +#include + +#include +#include -#include class QFile; @@ -44,6 +47,15 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcFileSystem) namespace FileSystem { OCSYNC_EXPORT Q_NAMESPACE; + QByteArray OCSYNC_EXPORT encodeFileName(const QString &fileName); + QString OCSYNC_EXPORT decodeFileName(const char *localFileName); + + /** + * List of characters not allowd in filenames on Windows + */ + constexpr_list auto IllegalFilenameCharsWindows = { + QLatin1Char('\\'), QLatin1Char(':'), QLatin1Char('?'), QLatin1Char('*'), QLatin1Char('"'), QLatin1Char('>'), QLatin1Char('<'), QLatin1Char('|')}; + /** * @brief Mark the file as hidden (only has effects on windows) */ @@ -68,13 +80,18 @@ namespace FileSystem { */ void OCSYNC_EXPORT setFileReadOnlyWeak(const QString &filename, bool readonly); - /** - * @brief Try to set permissions so that other users on the local machine can not - * go into the folder. - */ - void OCSYNC_EXPORT setFolderMinimumPermissions(const QString &filename); - /** convert a "normal" windows path into a path that can be 32k chars long. */ + /* + * This function takes a path and converts it to a UNC representation of the + * string. That means that it prepends a \\?\ (unless already UNC) and converts + * all slashes to backslashes. + * + * Note the following: + * - The string must be absolute. + * - it needs to contain a drive character to be a valid UNC + * - A conversion is only done if the path len is larger than 245. Otherwise + * the windows API functions work with the normal "unixoid" representation too. + */ QString OCSYNC_EXPORT longWinPath(const QString &inpath); /** @@ -85,6 +102,8 @@ namespace FileSystem { */ bool OCSYNC_EXPORT fileExists(const QString &filename, const QFileInfo & = QFileInfo()); + bool OCSYNC_EXPORT mkpath(const QString &parent, const QString &newDir); + /** * @brief Rename the file \a originFileName to \a destinationFileName. * @@ -110,11 +129,6 @@ namespace FileSystem { */ bool OCSYNC_EXPORT remove(const QString &fileName, QString *errorString = nullptr); - /** - * Move the specified file or folder to the trash. (Only implemented on linux) - */ - bool OCSYNC_EXPORT moveToTrash(const QString &filename, QString *errorString); - /** * Replacement for QFile::open(ReadOnly) followed by a seek(). * This version sets a more permissive sharing mode on Windows. @@ -122,31 +136,12 @@ namespace FileSystem { * Warning: The resulting file may have an empty fileName and be unsuitable for use * with QFileInfo! Calling seek() on the QFile with >32bit signed values will fail! */ - bool OCSYNC_EXPORT openAndSeekFileSharedRead(QFile *file, QString *error, qint64 seek); - -#ifdef Q_OS_WIN - /** - * Returns the file system used at the given path. - */ - QString fileSystemForPath(const QString &path); - - /* - * This function takes a path and converts it to a UNC representation of the - * string. That means that it prepends a \\?\ (unless already UNC) and converts - * all slashes to backslashes. - * - * Note the following: - * - The string must be absolute. - * - it needs to contain a drive character to be a valid UNC - * - A conversion is only done if the path len is larger than 245. Otherwise - * the windows API functions work with the normal "unixoid" representation too. - */ - QString OCSYNC_EXPORT pathtoUNC(const QString &str); -#endif + bool OCSYNC_EXPORT openAndSeekFileSharedRead(QFile * file, QString * error, qint64 seek); enum class LockMode { Shared, - Exclusive + Exclusive, + SharedRead }; Q_ENUM_NS(LockMode); /** @@ -154,6 +149,16 @@ namespace FileSystem { */ bool OCSYNC_EXPORT isFileLocked(const QString &fileName, LockMode mode); +#ifdef Q_OS_WIN + + bool OCSYNC_EXPORT longPathsEnabledOnWindows(); + +#endif + /** + * Returns the file system used at the given path. + */ + QString OCSYNC_EXPORT fileSystemForPath(const QString &path); + /** * Returns whether the file is a shortcut file (ends with .lnk) */ @@ -167,7 +172,58 @@ namespace FileSystem { /** * Returns whether a Path is a child of another */ - bool OCSYNC_EXPORT isChildPathOf(const QString &child, const QString &parent); + bool OCSYNC_EXPORT isChildPathOf(QStringView child, QStringView parent); + + /** + * Ensures the file name length is allowed on all platforms and the file name does not contain illegal characters + * reservedSize: The resulting path will be reservedSize < MAX and allows appending. + */ + QString OCSYNC_EXPORT createPortableFileName(const QString &path, const QString &fileName, qsizetype reservedSize = 0); + + /* + * Replace path navigation elements from the string + */ + QString OCSYNC_EXPORT pathEscape(const QString &s); + + namespace SizeLiterals { + constexpr auto BinaryBase = 1024; + constexpr auto DecimalBase = 1000; + + constexpr unsigned long long operator"" _B(unsigned long long sz) + { + return sz; + } + + constexpr unsigned long long operator"" _KiB(unsigned long long sz) + { + return operator"" _B(sz) * BinaryBase; + } + + constexpr unsigned long long operator"" _MiB(unsigned long long sz) + { + return operator"" _KiB(sz) * BinaryBase; + } + + constexpr unsigned long long operator"" _GiB(unsigned long long sz) + { + return operator"" _MiB(sz) * BinaryBase; + } + + constexpr unsigned long long operator"" _kB(unsigned long long sz) + { + return operator"" _B(sz) * DecimalBase; + } + + constexpr unsigned long long operator"" _MB(unsigned long long sz) + { + return operator"" _kB(sz) * DecimalBase; + } + + constexpr unsigned long long operator"" _GB(unsigned long long sz) + { + return operator"" _MB(sz) * DecimalBase; + } + } } /** @} */ diff --git a/src/common/ownsql.cpp b/src/common/ownsql.cpp index 0877b2787fe..bdce12a5f91 100644 --- a/src/common/ownsql.cpp +++ b/src/common/ownsql.cpp @@ -17,26 +17,34 @@ */ #include -#include -#include +#include #include #include -#include +#include -#include "ownsql.h" -#include "common/utility.h" #include "common/asserts.h" +#include "common/filesystembase.h" +#include "common/utility.h" + +#include "ownsql.h" + #include -#define SQLITE_SLEEP_TIME_USEC 100000 -#define SQLITE_REPEAT_COUNT 20 +#include + +using namespace std::chrono_literals; + +namespace { +constexpr int SQLITE_REPEAT_COUNT = 20; -#define SQLITE_DO(A) \ - if (1) { \ - _errId = (A); \ - if (_errId != SQLITE_OK && _errId != SQLITE_DONE && _errId != SQLITE_ROW) { \ - _error = QString::fromUtf8(sqlite3_errmsg(_db)); \ - } \ +} + +#define SQLITE_DO(A) \ + if (1) { \ + _errId = (A); \ + if (_errId != SQLITE_OK && _errId != SQLITE_DONE && _errId != SQLITE_ROW) { \ + _error = QString::fromUtf8(sqlite3_errmsg(_db)); \ + } \ } namespace OCC { @@ -55,7 +63,7 @@ SqlDatabase::~SqlDatabase() } -bool SqlDatabase::isOpen() +bool SqlDatabase::isOpen() const { return _db != nullptr; } @@ -68,7 +76,14 @@ bool SqlDatabase::openHelper(const QString &filename, int sqliteFlags) sqliteFlags |= SQLITE_OPEN_NOMUTEX; - SQLITE_DO(sqlite3_open_v2(filename.toUtf8().constData(), &_db, sqliteFlags, nullptr)); +#ifdef Q_OS_WIN + // allow file paths > 260 + // https://www.sqlite.org/vfs.html#standard_windows_vfses + const char *vfs = "win32-longpath"; +#else + char *vfs = nullptr; +#endif + SQLITE_DO(sqlite3_open_v2(FileSystem::longWinPath(filename).toUtf8().constData(), &_db, sqliteFlags, vfs)); if (_errId != SQLITE_OK) { qCWarning(lcSql) << "Error:" << _error << "for" << filename; @@ -132,28 +147,29 @@ bool SqlDatabase::openOrCreateReadWrite(const QString &filename) auto checkResult = checkDb(); if (checkResult != CheckDbResult::Ok) { + close(); if (checkResult == CheckDbResult::CantPrepare) { // When disk space is low, preparing may fail even though the db is fine. // Typically CANTOPEN or IOERR. qint64 freeSpace = Utility::freeDiskSpace(QFileInfo(filename).dir().absolutePath()); if (freeSpace != -1 && freeSpace < 1000000) { qCWarning(lcSql) << "Can't prepare consistency check and disk space is low:" << freeSpace; - close(); - return false; - } - - // Even when there's enough disk space, it might very well be that the - // file is on a read-only filesystem and can't be opened because of that. - if (_errId == SQLITE_CANTOPEN) { + } else if (_errId == SQLITE_CANTOPEN) { + // Even when there's enough disk space, it might very well be that the + // file is on a read-only filesystem and can't be opened because of that. qCWarning(lcSql) << "Can't open db to prepare consistency check, aborting"; - close(); - return false; + } else if (_errId == SQLITE_LOCKED || _errId == SQLITE_BUSY) { + qCWarning(lcSql) << "Can't open db to prepare consistency check, the db is locked aborting" << _errId << _error; } + return false; } qCCritical(lcSql) << "Consistency check failed, removing broken db" << filename; - close(); - QFile::remove(filename); + QFile fileToRemove(filename); + if (!fileToRemove.remove()) { + qCCritical(lcSql) << "Failed to remove broken db" << filename << ":" << fileToRemove.errorString(); + return false; + } return openHelper(filename, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); } @@ -254,24 +270,32 @@ int SqlQuery::prepare(const QByteArray &sql, bool allow_failure) finish(); } if (!_sql.isEmpty()) { - int n = 0; - int rc; - do { - rc = sqlite3_prepare_v2(_db, _sql.constData(), -1, &_stmt, nullptr); - if ((rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED)) { - n++; - OCC::Utility::usleep(SQLITE_SLEEP_TIME_USEC); + for (int n = 0; n < SQLITE_REPEAT_COUNT; ++n) { + qCDebug(lcSql) << "SQL prepare" << _sql << "Try:" << n; + _errId = sqlite3_prepare_v2(_db, _sql.constData(), -1, &_stmt, nullptr); + if (_errId != SQLITE_OK) { + qCWarning(lcSql) << "SQL prepare failed" << _sql << QString::fromUtf8(sqlite3_errmsg(_db)); + if ((_errId == SQLITE_BUSY) || (_errId == SQLITE_LOCKED)) { + continue; + } } - } while ((n < SQLITE_REPEAT_COUNT) && ((rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED))); - _errId = rc; + break; + } if (_errId != SQLITE_OK) { _error = QString::fromUtf8(sqlite3_errmsg(_db)); - qCWarning(lcSql) << "Sqlite prepare statement error:" << _error << "in" << _sql; + qCWarning(lcSql) << "Sqlite prepare statement error:" << _errId << _error << "in" << _sql; OC_ENFORCE_X(allow_failure, "SQLITE Prepare error"); } else { OC_ASSERT(_stmt); _sqldb->_queries.insert(this); + + if (lcSql().isDebugEnabled()) { + _boundValues.resize(sqlite3_bind_parameter_count(_stmt)); + for (int i = 1; i <= _boundValues.size(); ++i) { + _boundValues[i - 1].name = QString::fromUtf8(sqlite3_bind_parameter_name(_stmt, i)); + } + } } } return _errId; @@ -298,28 +322,37 @@ bool SqlQuery::isPragma() bool SqlQuery::exec() { - qCDebug(lcSql) << "SQL exec" << _sql; - if (!_stmt) { qCWarning(lcSql) << "Can't exec query, statement unprepared."; return false; } - // Don't do anything for selects, that is how we use the lib :-| if (!isSelect() && !isPragma()) { - int rc, n = 0; - do { - rc = sqlite3_step(_stmt); - if (rc == SQLITE_LOCKED) { - rc = sqlite3_reset(_stmt); /* This will also return SQLITE_LOCKED */ - n++; - OCC::Utility::usleep(SQLITE_SLEEP_TIME_USEC); - } else if (rc == SQLITE_BUSY) { - OCC::Utility::usleep(SQLITE_SLEEP_TIME_USEC); - n++; + for (int n = 0; n < SQLITE_REPEAT_COUNT; ++n) { + if (lcSql().isDebugEnabled()) { + if (!_boundValues.isEmpty()) { + // Try to create a pretty printed version of the sql query + // the result can be inaccurate + QString query = QString::fromUtf8(_sql); + for (const auto &v : _boundValues) { + query.replace(query.indexOf(v.name), v.name.size(), v.value); + } + char *actualQuery = sqlite3_expanded_sql(_stmt); + qCDebug(lcSql) << "SQL exec: Estimated query:" << query << "Actual query:" << QString::fromUtf8(actualQuery) << "Try:" << n; + sqlite3_free(actualQuery); + } else { + qCDebug(lcSql) << "SQL exec:" << _sql << "Try:" << n; + } } - } while ((n < SQLITE_REPEAT_COUNT) && ((rc == SQLITE_BUSY) || (rc == SQLITE_LOCKED))); - _errId = rc; + _errId = sqlite3_step(_stmt); + if (_errId != SQLITE_DONE && _errId != SQLITE_ROW) { + qCWarning(lcSql) << "SQL exec failed" << _sql << QString::fromUtf8(sqlite3_errmsg(_db)); + if (_errId == SQLITE_LOCKED || _errId == SQLITE_BUSY) { + continue; + } + } + break; + } if (_errId != SQLITE_DONE && _errId != SQLITE_ROW) { _error = QString::fromUtf8(sqlite3_errmsg(_db)); @@ -343,14 +376,12 @@ auto SqlQuery::next() -> NextResult { const bool firstStep = !sqlite3_stmt_busy(_stmt); - int n = 0; - forever { + for (int n = 0; n < SQLITE_REPEAT_COUNT; ++n) { _errId = sqlite3_step(_stmt); - if (n < SQLITE_REPEAT_COUNT && firstStep && (_errId == SQLITE_LOCKED || _errId == SQLITE_BUSY)) { - sqlite3_reset(_stmt); // not necessary after sqlite version 3.6.23.1 - n++; - OCC::Utility::usleep(SQLITE_SLEEP_TIME_USEC); - } else { + if (!(firstStep && (_errId == SQLITE_LOCKED || _errId == SQLITE_BUSY))) { + // For the first step: retry if the database is locked or busy. + // For any other iteration: the first one succeeded, so subsequent steps should also + // succeed. If not, bail out and report an error (done below). break; } } @@ -374,45 +405,46 @@ void SqlQuery::bindValueInternal(int pos, const QVariant &value) return; } - switch (value.type()) { - case QVariant::Int: - case QVariant::Bool: + const auto bindUtf16 = [this](int pos, const QString &str) { + return sqlite3_bind_text16(_stmt, pos, str.utf16(), static_cast(str.size() * sizeof(ushort)), SQLITE_TRANSIENT); + }; + + switch (value.typeId()) { + case QMetaType::Int: + case QMetaType::Bool: res = sqlite3_bind_int(_stmt, pos, value.toInt()); break; - case QVariant::Double: + case QMetaType::Double: res = sqlite3_bind_double(_stmt, pos, value.toDouble()); break; - case QVariant::UInt: - case QVariant::LongLong: - case QVariant::ULongLong: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: res = sqlite3_bind_int64(_stmt, pos, value.toLongLong()); break; - case QVariant::DateTime: { + case QMetaType::QDateTime: { const QDateTime dateTime = value.toDateTime(); const QString str = dateTime.toString(QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz")); - res = sqlite3_bind_text16(_stmt, pos, str.utf16(), - str.size() * sizeof(ushort), SQLITE_TRANSIENT); + res = bindUtf16(pos, str); break; } - case QVariant::Time: { + case QMetaType::QTime: { const QTime time = value.toTime(); const QString str = time.toString(QStringLiteral("hh:mm:ss.zzz")); - res = sqlite3_bind_text16(_stmt, pos, str.utf16(), - str.size() * sizeof(ushort), SQLITE_TRANSIENT); + res = bindUtf16(pos, str); break; } - case QVariant::String: { + case QMetaType::QString: { if (!value.toString().isNull()) { // lifetime of string == lifetime of its qvariant const QString *str = static_cast(value.constData()); - res = sqlite3_bind_text16(_stmt, pos, str->utf16(), - (str->size()) * sizeof(QChar), SQLITE_TRANSIENT); + res = bindUtf16(pos, *str); } else { res = sqlite3_bind_null(_stmt, pos); } break; } - case QVariant::ByteArray: { + case QMetaType::QByteArray: { auto ba = value.toByteArray(); res = sqlite3_bind_text(_stmt, pos, ba.constData(), ba.size(), SQLITE_TRANSIENT); break; @@ -420,8 +452,7 @@ void SqlQuery::bindValueInternal(int pos, const QVariant &value) default: { QString str = value.toString(); // SQLITE_TRANSIENT makes sure that sqlite buffers the data - res = sqlite3_bind_text16(_stmt, pos, str.utf16(), - (str.size()) * sizeof(QChar), SQLITE_TRANSIENT); + res = sqlite3_bind_text16(_stmt, pos, str.utf16(), static_cast(str.size() * sizeof(ushort)), SQLITE_TRANSIENT); break; } } @@ -438,7 +469,7 @@ bool SqlQuery::nullValue(int index) QString SqlQuery::stringValue(int index) { - return QString::fromUtf16(static_cast(sqlite3_column_text16(_stmt, index))); + return QString::fromUtf16(static_cast(sqlite3_column_text16(_stmt, index))); } int SqlQuery::intValue(int index) diff --git a/src/common/ownsql.h b/src/common/ownsql.h index e544fcd00b0..b510eee5987 100644 --- a/src/common/ownsql.h +++ b/src/common/ownsql.h @@ -45,7 +45,7 @@ class OCSYNC_EXPORT SqlDatabase explicit SqlDatabase(); ~SqlDatabase(); - bool isOpen(); + bool isOpen() const; bool openOrCreateReadWrite(const QString &filename); bool openReadOnly(const QString &filename); bool transaction(); @@ -133,18 +133,20 @@ class OCSYNC_EXPORT SqlQuery }; NextResult next(); - template::value, int>::type = 0> + template void bindValue(int pos, const T &value) { - qCDebug(lcSql) << "SQL bind" << pos << value; - bindValueInternal(pos, static_cast(value)); - } - - template::value, int>::type = 0> - void bindValue(int pos, const T &value) - { - qCDebug(lcSql) << "SQL bind" << pos << value; - bindValueInternal(pos, value); + const auto converted = convertValue(value); + if (lcSql().isDebugEnabled() && !_boundValues.isEmpty()) { + QString sqlValue; + auto stream = QDebug(&sqlValue).noquote().nospace() << '\'' << value; + if (typeid(converted) != typeid(value)) { + stream << " [" << converted << ']'; + } + stream << '\''; + _boundValues[pos - 1].value = sqlValue; + } + bindValueInternal(pos, converted); } const QByteArray &lastQuery() const; @@ -152,6 +154,18 @@ class OCSYNC_EXPORT SqlQuery void reset_and_clear_bindings(); private: + template ::value, int>::type = 0> + auto convertValue(const T &value) + { + return static_cast(value); + } + + template ::value, int>::type = 0> + auto convertValue(const T &value) + { + return value; + } + void bindValueInternal(int pos, const QVariant &value); void finish(); @@ -161,6 +175,12 @@ class OCSYNC_EXPORT SqlQuery QString _error; int _errId; QByteArray _sql; + struct Binding + { + QString name; + QString value; + }; + QVector _boundValues; friend class SqlDatabase; friend class PreparedSqlQueryManager; diff --git a/src/common/pinstate.cpp b/src/common/pinstate.cpp index bb3e4e165bb..6d3570298b6 100644 --- a/src/common/pinstate.cpp +++ b/src/common/pinstate.cpp @@ -14,3 +14,25 @@ #include "pinstate.h" #include "moc_pinstate.cpp" + +#include + +using namespace OCC; + +template <> +QString Utility::enumToDisplayName(VfsItemAvailability availability) +{ + switch (availability) { + case VfsItemAvailability::AlwaysLocal: + return QCoreApplication::translate("pinstate", "Always available locally"); + case VfsItemAvailability::AllHydrated: + return QCoreApplication::translate("pinstate", "Currently available locally"); + case VfsItemAvailability::Mixed: + return QCoreApplication::translate("pinstate", "Some available online only"); + case VfsItemAvailability::AllDehydrated: + [[fallthrough]]; + case VfsItemAvailability::OnlineOnly: + return QCoreApplication::translate("pinstate", "Available online only"); + } + Q_UNREACHABLE(); +} diff --git a/src/common/pinstate.h b/src/common/pinstate.h index a50ed7eab26..fe08a7b9139 100644 --- a/src/common/pinstate.h +++ b/src/common/pinstate.h @@ -16,6 +16,7 @@ #define PINSTATE_H #include "ocsynclib.h" +#include "utility.h" #include @@ -129,6 +130,8 @@ Q_ENUM_NS(VfsItemAvailability) } using namespace PinStateEnums; +template <> +OCSYNC_EXPORT QString Utility::enumToDisplayName(VfsItemAvailability availability); } #endif diff --git a/src/common/plugin.cpp b/src/common/plugin.cpp index 7e705d94e74..ec3a3f15752 100644 --- a/src/common/plugin.cpp +++ b/src/common/plugin.cpp @@ -18,16 +18,13 @@ #include "plugin.h" -#include "config.h" - namespace OCC { PluginFactory::~PluginFactory() = default; QString pluginFileName(const QString &type, const QString &name) { - return QStringLiteral("%1sync_%2_%3") - .arg(QStringLiteral(APPLICATION_EXECUTABLE), type, name); + return QStringLiteral("ownCloud_%2_%3").arg(type, name); } } diff --git a/src/common/preparedsqlquerymanager.h b/src/common/preparedsqlquerymanager.h index 6fe264f54bd..90a3799fff0 100644 --- a/src/common/preparedsqlquerymanager.h +++ b/src/common/preparedsqlquerymanager.h @@ -29,7 +29,7 @@ class OCSYNC_EXPORT PreparedSqlQuery public: ~PreparedSqlQuery(); - explicit operator bool() const { return _ok; } + operator bool() const { return _ok; } SqlQuery *operator->() const { @@ -71,6 +71,7 @@ class OCSYNC_EXPORT PreparedSqlQueryManager SetDownloadInfoQuery, DeleteDownloadInfoQuery, GetUploadInfoQuery, + GetAllUploadInfoQuery, SetUploadInfoQuery, DeleteUploadInfoQuery, DeleteFileRecordPhash, @@ -94,6 +95,8 @@ class OCSYNC_EXPORT PreparedSqlQueryManager SetPinStateQuery, WipePinStateQuery, + GetFileReocrdsWithDirtyPlaceholdersQuery, + PreparedQueryCount }; PreparedSqlQueryManager() = default; @@ -108,7 +111,7 @@ class OCSYNC_EXPORT PreparedSqlQueryManager private: SqlQuery _queries[PreparedQueryCount]; - Q_DISABLE_COPY(PreparedSqlQueryManager); + Q_DISABLE_COPY(PreparedSqlQueryManager) }; } diff --git a/src/common/restartmanager.cpp b/src/common/restartmanager.cpp new file mode 100644 index 00000000000..307102adb99 --- /dev/null +++ b/src/common/restartmanager.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "restartmanager.h" + +#include "utility.h" + +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(lcRestart, "sync.restartmanager", QtInfoMsg) + +using namespace OCC; + +RestartManager *RestartManager::_instance = nullptr; + +RestartManager::RestartManager(std::function &&main) + : _main(main) +{ + Q_ASSERT(!_instance); + if (!_instance) + _instance = this; +} + +RestartManager::~RestartManager() +{ + if (!_applicationToRestart.isEmpty()) { + QProcess process; + process.setProgram(_applicationToRestart); + process.setArguments(_args); + qint64 pid; + qCDebug(lcRestart) << "Detaching" << _applicationToRestart << _args; + if (process.startDetached(&pid)) { + qCDebug(lcRestart) << "Successfully restarted. New process PID" << pid; + } else { + qCCritical(lcRestart) << "Failed to restart" << process.error() << process.errorString(); + } + } +} + +int RestartManager::exec(int argc, char **argv) const +{ + return _main(argc, argv); +} + +void RestartManager::requestRestart() +{ + Q_ASSERT(_instance); + if (!_instance) + return; + + qCInfo(lcRestart) << "Restarting application with PID" << QCoreApplication::applicationPid(); + + QString pathToLaunch = QCoreApplication::applicationFilePath(); +#ifdef Q_OS_LINUX + if (Utility::runningInAppImage()) { + pathToLaunch = Utility::appImageLocation(); + } +#endif + _instance->_applicationToRestart = QFileInfo(pathToLaunch).absoluteFilePath(); + // remove arg0 + _instance->_args = QCoreApplication::arguments().sliced(1); + QTimer::singleShot(0, QCoreApplication::instance(), &QCoreApplication::quit); +} diff --git a/src/common/restartmanager.h b/src/common/restartmanager.h new file mode 100644 index 00000000000..fc40a74928d --- /dev/null +++ b/src/common/restartmanager.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "ocsynclib.h" + +#include + +#include + +namespace OCC { +class OCSYNC_EXPORT RestartManager +{ +public: + RestartManager(std::function &&main); + + ~RestartManager(); + + int exec(int argc, char **argv) const; + + static void requestRestart(); + +private: + static RestartManager *_instance; + + std::function _main; + + QString _applicationToRestart; + QStringList _args; +}; + +} diff --git a/src/common/syncfilestatus.cpp b/src/common/syncfilestatus.cpp index fe9d4fc2afe..2fdfee4268d 100644 --- a/src/common/syncfilestatus.cpp +++ b/src/common/syncfilestatus.cpp @@ -14,6 +14,8 @@ #include "syncfilestatus.h" +#include + namespace OCC { SyncFileStatus::SyncFileStatus() : _tag(StatusNone) @@ -82,3 +84,12 @@ QString SyncFileStatus::toSocketAPIString() const return statusString; } } + + +QDebug &operator<<(QDebug &debug, const OCC::SyncFileStatus &item) +{ + QDebugStateSaver saver(debug); + debug.setAutoInsertSpaces(false); + debug << "OCC::SyncFileStatus(shared=" << item.shared() << ", tag=" << item.tag() << ")"; + return debug; +} diff --git a/src/common/syncfilestatus.h b/src/common/syncfilestatus.h index 9e9c1f5390f..a0f5930bb33 100644 --- a/src/common/syncfilestatus.h +++ b/src/common/syncfilestatus.h @@ -67,6 +67,7 @@ inline bool operator!=(const SyncFileStatus &a, const SyncFileStatus &b) return !(a == b); } } +OCSYNC_EXPORT QDebug &operator<<(QDebug &debug, const OCC::SyncFileStatus &item); Q_DECLARE_METATYPE(OCC::SyncFileStatus) diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index 59b7ea1e43c..91f90874e4f 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -16,25 +16,27 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include +#include "common/syncjournaldb.h" + +#include "common/asserts.h" +#include "common/c_jhash.h" +#include "common/checksums.h" +#include "common/filesystembase.h" +#include "common/preparedsqlquerymanager.h" +#include "common/version.h" + +#include +#include #include #include #include -#include #include -#include + #include -#include -#include "common/asserts.h" -#include "common/checksums.h" -#include "common/preparedsqlquerymanager.h" -#include "common/syncjournaldb.h" -#include "common/version.h" -#include "filesystembase.h" -#include "version.h" +#include -#include "common/c_jhash.h" +Q_LOGGING_CATEGORY(lcDb, "sync.database", QtInfoMsg) // SQL expression to check whether path.startswith(prefix + '/') // Note: '/' + 1 == '0' @@ -43,17 +45,16 @@ #define IS_PREFIX_PATH_OR_EQUAL(prefix, path) \ "(" path " == " prefix " OR " IS_PREFIX_PATH_OF(prefix, path) ")" -namespace OCC { +namespace { +// base query used to select file record objects, used in combination with WHERE statements. +const auto getFileRecordQueryC = QByteArrayLiteral("SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," + " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum," + " hasDirtyPlaceholder" + " FROM metadata" + " LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id "); -Q_LOGGING_CATEGORY(lcDb, "sync.database", QtInfoMsg) - -#define GET_FILE_RECORD_QUERY \ - "SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \ - " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum" \ - " FROM metadata" \ - " LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id" -static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &query) +void fillFileRecordFromGetQuery(OCC::SyncJournalFileRecord &rec, OCC::SqlQuery &query) { rec._path = query.baValue(0); rec._inode = query.int64Value(1); @@ -61,18 +62,18 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que rec._type = static_cast(query.intValue(3)); rec._etag = query.baValue(4); rec._fileId = query.baValue(5); - rec._remotePerm = RemotePermissions::fromDbValue(query.baValue(6)); + rec._remotePerm = OCC::RemotePermissions::fromDbValue(query.baValue(6)); rec._fileSize = query.int64Value(7); rec._serverHasIgnoredFiles = (query.intValue(8) > 0); rec._checksumHeader = query.baValue(9); } -static QByteArray defaultJournalMode(const QString &dbPath) +QByteArray defaultJournalMode(const QString &dbPath) { #if defined(Q_OS_WIN) // See #2693: Some exFAT file systems seem unable to cope with the // WAL journaling mode. They work fine with DELETE. - QString fileSystem = FileSystem::fileSystemForPath(dbPath); + QString fileSystem = OCC::FileSystem::fileSystemForPath(dbPath); qCInfo(lcDb) << "Detected filesystem" << fileSystem << "for" << dbPath; if (fileSystem.contains(QLatin1String("FAT"))) { qCInfo(lcDb) << "Filesystem contains FAT - using DELETE journal mode"; @@ -88,11 +89,13 @@ static QByteArray defaultJournalMode(const QString &dbPath) #endif return "WAL"; } +} + +namespace OCC { SyncJournalDb::SyncJournalDb(const QString &dbFilePath, QObject *parent) : QObject(parent) , _dbFile(dbFilePath) - , _mutex(QMutex::Recursive) , _transaction(0) , _metadataTableIsEmpty(false) { @@ -128,65 +131,25 @@ QString SyncJournalDb::makeDbName(const QString &localPath, return journalPath; } -bool SyncJournalDb::maybeMigrateDb(const QString &localPath, const QString &absoluteJournalPath) +bool SyncJournalDb::dbIsTooNewForClient(const QString &dbFilePath) { - const QString oldDbName = localPath + QLatin1String(".csync_journal.db"); - if (!FileSystem::fileExists(oldDbName)) { - return true; - } - const QString oldDbNameShm = oldDbName + QStringLiteral("-shm"); - const QString oldDbNameWal = oldDbName + QStringLiteral("-wal"); - - const QString newDbName = absoluteJournalPath; - const QString newDbNameShm = newDbName + QStringLiteral("-shm"); - const QString newDbNameWal = newDbName + QStringLiteral("-wal"); - - // Whenever there is an old db file, migrate it to the new db path. - // This is done to make switching from older versions to newer versions - // work correctly even if the user had previously used a new version - // and therefore already has an (outdated) new-style db file. - QString error; - - if (FileSystem::fileExists(newDbName)) { - if (!FileSystem::remove(newDbName, &error)) { - qCWarning(lcDb) << "Database migration: Could not remove db file" << newDbName - << "due to" << error; - return false; - } - } - if (FileSystem::fileExists(newDbNameWal)) { - if (!FileSystem::remove(newDbNameWal, &error)) { - qCWarning(lcDb) << "Database migration: Could not remove db WAL file" << newDbNameWal - << "due to" << error; - return false; - } - } - if (FileSystem::fileExists(newDbNameShm)) { - if (!FileSystem::remove(newDbNameShm, &error)) { - qCWarning(lcDb) << "Database migration: Could not remove db SHM file" << newDbNameShm - << "due to" << error; - return false; - } - } - - if (!FileSystem::rename(oldDbName, newDbName, &error)) { - qCWarning(lcDb) << "Database migration: could not rename" << oldDbName - << "to" << newDbName << ":" << error; - return false; - } - if (!FileSystem::rename(oldDbNameWal, newDbNameWal, &error)) { - qCWarning(lcDb) << "Database migration: could not rename" << oldDbNameWal - << "to" << newDbNameWal << ":" << error; + SqlDatabase db; + if (!db.openReadOnly(dbFilePath)) { + // No DB, we're not downgrading, so we're fine return false; } - if (!FileSystem::rename(oldDbNameShm, newDbNameShm, &error)) { - qCWarning(lcDb) << "Database migration: could not rename" << oldDbNameShm - << "to" << newDbNameShm << ":" << error; + + SqlQuery versionQuery("SELECT major, minor, patch FROM version;", db); + if (!versionQuery.next().hasData) { + // Broken DB? Assume we're not upgrading. return false; } - qCInfo(lcDb) << "Journal successfully migrated from" << oldDbName << "to" << newDbName; - return true; + int dbMajor = versionQuery.intValue(0); + // int minor = versionQuery.intValue(1); + // int patch = versionQuery.intValue(2); + + return dbMajor > OCC::Version::versionWithBuildNumber().majorVersion(); } bool SyncJournalDb::exists() @@ -251,6 +214,10 @@ bool SyncJournalDb::sqlFail(const QString &log, const SqlQuery &query) bool SyncJournalDb::checkConnect() { + if (_closed) { + qCWarning(lcDb) << Q_FUNC_INFO << "after the db was closed"; + return false; + } if (autotestFailCounter >= 0) { if (!autotestFailCounter--) { qCInfo(lcDb) << "Error Simulated"; @@ -259,13 +226,6 @@ bool SyncJournalDb::checkConnect() } if (_db.isOpen()) { - // Unfortunately the sqlite isOpen check can return true even when the underlying storage - // has become unavailable - and then some operations may cause crashes. See #6049 - if (!QFile::exists(_dbFile)) { - qCWarning(lcDb) << "Database open, but file" << _dbFile << "does not exist"; - close(); - return false; - } return true; } @@ -386,6 +346,7 @@ bool SyncJournalDb::checkConnect() // ignoredChildrenRemote // contentChecksum // contentChecksumTypeId + // hasDirtyPlaceholder "PRIMARY KEY(phash)" ");"); @@ -423,19 +384,9 @@ bool SyncJournalDb::checkConnect() return sqlFail(QStringLiteral("Create table downloadinfo"), createQuery); } - createQuery.prepare("CREATE TABLE IF NOT EXISTS uploadinfo(" - "path VARCHAR(4096)," - "chunk INTEGER," - "transferid INTEGER," - "errorcount INTEGER," - "size INTEGER(8)," - "modtime INTEGER(8)," - "contentChecksum TEXT," - "PRIMARY KEY(path)" - ");"); - if (!createQuery.exec()) { - return sqlFail(QStringLiteral("Create table uploadinfo"), createQuery); + if (!createUploadInfo()) { + return false; } // create the blacklist table. @@ -591,8 +542,8 @@ bool SyncJournalDb::checkConnect() QByteArray sql("SELECT lastTryEtag, lastTryModtime, retrycount, errorstring, lastTryTime, ignoreDuration, renameTarget, errorCategory, requestId " "FROM blacklist WHERE path=?1"); - if (Utility::fsCasePreserving()) { - // if the file system is case preserving we have to check the blacklist + if (Utility::fsCasePreservingButCaseInsensitive()) { + // if the file system is case insensitive we have to check the blacklist // case insensitively sql += " COLLATE NOCASE"; } @@ -626,8 +577,14 @@ void SyncJournalDb::close() _db.close(); clearEtagStorageFilter(); _metadataTableIsEmpty = false; + _closed = true; } +void SyncJournalDb::allowReopen() +{ + Q_ASSERT(_closed); + _closed = false; +} bool SyncJournalDb::updateDatabaseStructure() { @@ -682,7 +639,7 @@ bool SyncJournalDb::updateMetadataTableStructure() commitInternal(QStringLiteral("update database structure: add filesize col")); } - if (1) { + { SqlQuery query(_db); query.prepare("CREATE INDEX IF NOT EXISTS metadata_inode ON metadata(inode);"); if (!query.exec()) { @@ -692,7 +649,7 @@ bool SyncJournalDb::updateMetadataTableStructure() commitInternal(QStringLiteral("update database structure: add inode index")); } - if (1) { + { SqlQuery query(_db); query.prepare("CREATE INDEX IF NOT EXISTS metadata_path ON metadata(path);"); if (!query.exec()) { @@ -702,7 +659,7 @@ bool SyncJournalDb::updateMetadataTableStructure() commitInternal(QStringLiteral("update database structure: add path index")); } - if (1) { + { SqlQuery query(_db); query.prepare("CREATE INDEX IF NOT EXISTS metadata_parent ON metadata(parent_hash(path));"); if (!query.exec()) { @@ -741,6 +698,22 @@ bool SyncJournalDb::updateMetadataTableStructure() commitInternal(QStringLiteral("update database structure: add contentChecksumTypeId col")); } + if (columns.indexOf("hasDirtyPlaceholder") == -1) { + SqlQuery addDirtyQuery(_db); + addDirtyQuery.prepare("ALTER TABLE metadata ADD COLUMN hasDirtyPlaceholder BOOLEAN;"); + if (!addDirtyQuery.exec()) { + sqlFail(QStringLiteral("updateMetadataTableStructure: add hasDirtyPlaceholder column"), addDirtyQuery); + re = false; + } + SqlQuery dirtyIndex(_db); + dirtyIndex.prepare("CREATE INDEX hasDirtyPlaceholderIndex ON metadata(path) WHERE hasDirtyPlaceholder=TRUE;"); + if (!dirtyIndex.exec()) { + sqlFail(QStringLiteral("updateMetadataTableStructure: add index on hasDirtyPlaceholder column"), dirtyIndex); + re = false; + } + commitInternal(QStringLiteral("update database structure: add hasDirtyPlaceholder col")); + } + auto uploadInfoColumns = tableColumns("uploadinfo"); if (uploadInfoColumns.isEmpty()) return false; @@ -866,7 +839,7 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & if (!_etagStorageFilter.isEmpty()) { // If we are a directory that should not be read from db next time, don't write the etag QByteArray prefix = record._path + "/"; - for (const auto &it : qAsConst(_etagStorageFilter)) { + for (const auto &it : std::as_const(_etagStorageFilter)) { if (it.startsWith(prefix)) { qCInfo(lcDb) << "Filtered writing the etag of" << prefix << "because it is a prefix of" << it; record._etag = "_invalid_"; @@ -878,7 +851,7 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & qCInfo(lcDb) << "Updating file record for path:" << record._path << "inode:" << record._inode << "modtime:" << record._modtime << "type:" << record._type << "etag:" << record._etag << "fileId:" << record._fileId << "remotePerm:" << record._remotePerm.toString() - << "fileSize:" << record._fileSize << "checksum:" << record._checksumHeader; + << "fileSize:" << record._fileSize << "checksum:" << record._checksumHeader << "hasDirtyPlaceholder:" << record._hasDirtyPlaceholder; const qint64 phash = getPHash(record._path); if (checkConnect()) { @@ -891,12 +864,12 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & if (fileId.isEmpty()) fileId = ""; QByteArray remotePerm = record._remotePerm.toDbValue(); - QByteArray checksumType, checksum; - parseChecksumHeader(record._checksumHeader, &checksumType, &checksum); - int contentChecksumTypeId = mapChecksumType(checksumType); + + const auto checksumHeader = ChecksumHeader::parseChecksumHeader(record._checksumHeader); + int contentChecksumTypeId = mapChecksumType(checksumHeader.type()); const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata " - "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId) " - "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16);"), + "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId, hasDirtyPlaceholder) " + "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17);"), _db); if (!query) { return query->error(); @@ -916,8 +889,9 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & query->bindValue(12, remotePerm); query->bindValue(13, record._fileSize); query->bindValue(14, record._serverHasIgnoredFiles ? 1 : 0); - query->bindValue(15, checksum); + query->bindValue(15, checksumHeader.checksum()); query->bindValue(16, contentChecksumTypeId); + query->bindValue(17, record._hasDirtyPlaceholder); if (!query->exec()) { return query->error(); @@ -989,7 +963,7 @@ bool SyncJournalDb::getFileRecord(const QByteArray &filename, SyncJournalFileRec return false; if (!filename.isEmpty()) { - const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE phash=?1"), _db); + const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQuery, getFileRecordQueryC + QByteArrayLiteral("WHERE phash=?1"), _db); if (!query) { return false; } @@ -1029,7 +1003,7 @@ bool SyncJournalDb::getFileRecordByInode(quint64 inode, SyncJournalFileRecord *r if (!checkConnect()) return false; - const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQueryByInode, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE inode=?1"), _db); + const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQueryByInode, getFileRecordQueryC + QByteArrayLiteral("WHERE inode=?1"), _db); if (!query) return false; @@ -1057,7 +1031,7 @@ bool SyncJournalDb::getFileRecordsByFileId(const QByteArray &fileId, const std:: if (!checkConnect()) return false; - const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQueryByFileId, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE fileid=?1"), _db); + const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileRecordQueryByFileId, getFileRecordQueryC + QByteArrayLiteral("WHERE fileid=?1"), _db); if (!query) { return false; } @@ -1067,7 +1041,7 @@ bool SyncJournalDb::getFileRecordsByFileId(const QByteArray &fileId, const std:: if (!query->exec()) return false; - forever { + while (true) { auto next = query->next(); if (!next.ok) return false; @@ -1097,7 +1071,7 @@ bool SyncJournalDb::getFilesBelowPath(const QByteArray &path, const std::functio return false; } - forever { + while (true) { auto next = query.next(); if (!next.ok) return false; @@ -1117,7 +1091,7 @@ bool SyncJournalDb::getFilesBelowPath(const QByteArray &path, const std::functio // and find nothing. So, unfortunately, we have to use a different query for // retrieving the whole tree. - const auto query = _queryManager.get(PreparedSqlQueryManager::GetAllFilesQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " ORDER BY path||'/' ASC"), _db); + const auto query = _queryManager.get(PreparedSqlQueryManager::GetAllFilesQuery, getFileRecordQueryC + QByteArrayLiteral("ORDER BY path||'/' ASC"), _db); if (!query) { return false; } @@ -1125,7 +1099,7 @@ bool SyncJournalDb::getFilesBelowPath(const QByteArray &path, const std::functio } else { // This query is used to skip discovery and fill the tree from the // database instead - const auto query = _queryManager.get(PreparedSqlQueryManager::GetFilesBelowPathQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE " IS_PREFIX_PATH_OF("?1", "path") + const auto query = _queryManager.get(PreparedSqlQueryManager::GetFilesBelowPathQuery, getFileRecordQueryC + QByteArrayLiteral("WHERE " IS_PREFIX_PATH_OF("?1", "path") // We want to ensure that the contents of a directory are sorted // directly behind the directory itself. Without this ORDER BY // an ordering like foo, foo-2, foo/file would be returned. @@ -1152,7 +1126,7 @@ bool SyncJournalDb::listFilesInPath(const QByteArray& path, if (!checkConnect()) return false; - const auto query = _queryManager.get(PreparedSqlQueryManager::ListFilesInPathQuery, QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE parent_hash(path) = ?1 ORDER BY path||'/' ASC"), _db); + const auto query = _queryManager.get(PreparedSqlQueryManager::ListFilesInPathQuery, getFileRecordQueryC + QByteArrayLiteral("WHERE parent_hash(path) = ?1 ORDER BY path||'/' ASC"), _db); if (!query) { return false; } @@ -1161,7 +1135,7 @@ bool SyncJournalDb::listFilesInPath(const QByteArray& path, if (!query->exec()) return false; - forever { + while (true) { auto next = query->next(); if (!next.ok) return false; @@ -1201,7 +1175,7 @@ int SyncJournalDb::getFileRecordCount() bool SyncJournalDb::updateFileRecordChecksum(const QString &filename, const QByteArray &contentChecksum, - const QByteArray &contentChecksumType) + CheckSums::Algorithm contentChecksumType) { QMutexLocker locker(&_mutex); @@ -1246,7 +1220,7 @@ Optional SyncJournalDb::hasHydratedOrDehyd return {}; HasHydratedDehydrated result; - forever { + while (true) { auto next = query->next(); if (!next.ok) return {}; @@ -1352,27 +1326,27 @@ QVector SyncJournalDb::getAndDeleteStaleDownloadInf return empty_result; } - SqlQuery query(_db); - // The selected values *must* match the ones expected by toDownloadInfo(). - query.prepare("SELECT tmpfile, etag, errorcount, path FROM downloadinfo"); - - if (!query.exec()) { - return empty_result; - } - QStringList superfluousPaths; QVector deleted_entries; + { + SqlQuery query(_db); + // The selected values *must* match the ones expected by toDownloadInfo(). + query.prepare("SELECT tmpfile, etag, errorcount, path FROM downloadinfo"); - while (query.next().hasData) { - const QString file = query.stringValue(3); // path - if (!keep.contains(file)) { - superfluousPaths.append(file); - DownloadInfo info; - toDownloadInfo(query, &info); - deleted_entries.append(info); + if (!query.exec()) { + return empty_result; } - } + while (query.next().hasData) { + const QString file = query.stringValue(3); // path + if (!keep.contains(file)) { + superfluousPaths.append(file); + DownloadInfo info; + toDownloadInfo(query, &info); + deleted_entries.append(info); + } + } + } { const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteDownloadInfoQuery); if (!deleteBatch(*query, superfluousPaths, QStringLiteral("downloadinfo"))) { @@ -1405,31 +1379,66 @@ SyncJournalDb::UploadInfo SyncJournalDb::getUploadInfo(const QString &file) { QMutexLocker locker(&_mutex); + + if (!checkConnect()) { + return {}; + } + + const auto query = _queryManager.get(PreparedSqlQueryManager::GetUploadInfoQuery, + QByteArrayLiteral("SELECT chunk, transferid, errorcount, size, modtime, contentChecksum, url FROM uploadinfo WHERE path=?1"), + _db); + if (!query) { + return {}; + } + query->bindValue(1, file); + + if (!query->exec()) { + return {}; + } + UploadInfo res; + if (query->next().hasData) { + res._path = file; + res._chunk = query->intValue(0); + res._transferid = query->intValue(1); + res._errorCount = query->intValue(2); + res._size = query->int64Value(3); + res._modtime = query->int64Value(4); + res._contentChecksum = query->baValue(5); + res._url = QUrl::fromEncoded(query->baValue(6)); + res._valid = true; + } + return res; +} - if (checkConnect()) { - const auto query = _queryManager.get(PreparedSqlQueryManager::GetUploadInfoQuery, QByteArrayLiteral("SELECT chunk, transferid, errorcount, size, modtime, contentChecksum FROM " - "uploadinfo WHERE path=?1"), - _db); - if (!query) { - return res; - } - query->bindValue(1, file); +std::vector SyncJournalDb::getUploadInfos() +{ + QMutexLocker locker(&_mutex); - if (!query->exec()) { - return res; - } + const auto query = _queryManager.get(PreparedSqlQueryManager::GetAllUploadInfoQuery, + QByteArrayLiteral("SELECT chunk, transferid, errorcount, size, modtime, contentChecksum, path, url FROM uploadinfo"), + _db); + if (!query) { + return {}; + } - if (query->next().hasData) { - bool ok = true; - res._chunk = query->intValue(0); - res._transferid = query->intValue(1); - res._errorCount = query->intValue(2); - res._size = query->int64Value(3); - res._modtime = query->int64Value(4); - res._contentChecksum = query->baValue(5); - res._valid = ok; - } + if (!query->exec()) { + return {}; + } + + std::vector res; + while (query->next().hasData) { + UploadInfo info; + info._chunk = query->intValue(0); + info._transferid = query->intValue(1); + info._errorCount = query->intValue(2); + info._size = query->int64Value(3); + info._modtime = query->int64Value(4); + info._contentChecksum = query->baValue(5); + info._path = query->stringValue(6); + info._url = QUrl::fromEncoded(query->baValue(7)); + info._valid = true; + res.push_back(std::move(info)); } return res; } @@ -1444,8 +1453,8 @@ void SyncJournalDb::setUploadInfo(const QString &file, const SyncJournalDb::Uplo if (i._valid) { const auto query = _queryManager.get(PreparedSqlQueryManager::SetUploadInfoQuery, QByteArrayLiteral("INSERT OR REPLACE INTO uploadinfo " - "(path, chunk, transferid, errorcount, size, modtime, contentChecksum) " - "VALUES ( ?1 , ?2, ?3 , ?4 , ?5, ?6 , ?7 )"), + "(path, chunk, transferid, errorcount, size, modtime, contentChecksum, url) " + "VALUES ( ?1 , ?2, ?3 , ?4 , ?5, ?6 , ?7, ?8 )"), _db); if (!query) { return; @@ -1458,6 +1467,7 @@ void SyncJournalDb::setUploadInfo(const QString &file, const SyncJournalDb::Uplo query->bindValue(5, i._size); query->bindValue(6, i._modtime); query->bindValue(7, i._contentChecksum); + query->bindValue(8, i._url.toEncoded()); if (!query->exec()) { return; @@ -1475,20 +1485,20 @@ void SyncJournalDb::setUploadInfo(const QString &file, const SyncJournalDb::Uplo QVector SyncJournalDb::deleteStaleUploadInfos(const QSet &keep) { QMutexLocker locker(&_mutex); - QVector ids; if (!checkConnect()) { - return ids; + return {}; } SqlQuery query(_db); query.prepare("SELECT path,transferid FROM uploadinfo"); if (!query.exec()) { - return ids; + return {}; } QStringList superfluousPaths; + QVector ids; while (query.next().hasData) { const QString file = query.stringValue(0); @@ -1497,7 +1507,7 @@ QVector SyncJournalDb::deleteStaleUploadInfos(const QSet &keep) ids.append(query.intValue(1)); } } - + qCDebug(lcDb) << Q_FUNC_INFO << "Keep:" << keep << "Removing Stale:" << superfluousPaths; const auto deleteUploadInfoQuery = _queryManager.get(PreparedSqlQueryManager::DeleteUploadInfoQuery); deleteBatch(*deleteUploadInfoQuery, superfluousPaths, QStringLiteral("uploadinfo")); return ids; @@ -1608,18 +1618,39 @@ int SyncJournalDb::wipeErrorBlacklist() return -1; } -void SyncJournalDb::wipeErrorBlacklistEntry(const QString &file) +void SyncJournalDb::wipeErrorBlacklistEntry(const QString &relativeFile) { - if (file.isEmpty()) { + if (relativeFile.isEmpty()) { return; } + Q_ASSERT(QFileInfo(relativeFile).isRelative()); QMutexLocker locker(&_mutex); if (checkConnect()) { SqlQuery query(_db); query.prepare("DELETE FROM blacklist WHERE path=?1"); - query.bindValue(1, file); + query.bindValue(1, relativeFile); + if (!query.exec()) { + sqlFail(QStringLiteral("Deletion of blacklist item failed."), query); + } + } +} + +void SyncJournalDb::wipeErrorBlacklistEntry(const QString &relativeFile, SyncJournalErrorBlacklistRecord::Category category) +{ + if (relativeFile.isEmpty()) { + return; + } + Q_ASSERT(QFileInfo(relativeFile).isRelative()); + + QMutexLocker locker(&_mutex); + if (checkConnect()) { + SqlQuery query(_db); + + query.prepare("DELETE FROM blacklist WHERE path=?1 AND errorCategory=?2"); + query.bindValue(1, relativeFile); + query.bindValue(2, category); if (!query.exec()) { sqlFail(QStringLiteral("Deletion of blacklist item failed."), query); } @@ -1674,32 +1705,31 @@ void SyncJournalDb::setErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord query->exec(); } -QStringList SyncJournalDb::getSelectiveSyncList(SyncJournalDb::SelectiveSyncListType type, bool *ok) +QSet SyncJournalDb::getSelectiveSyncList(SyncJournalDb::SelectiveSyncListType type, bool &ok) { - QStringList result; - OC_ASSERT(ok); + QSet result; QMutexLocker locker(&_mutex); if (!checkConnect()) { - *ok = false; + ok = false; return result; } const auto query = _queryManager.get(PreparedSqlQueryManager::GetSelectiveSyncListQuery, QByteArrayLiteral("SELECT path FROM selectivesync WHERE type=?1"), _db); if (!query) { - *ok = false; + ok = false; return result; } query->bindValue(1, int(type)); if (!query->exec()) { - *ok = false; + ok = false; return result; } - forever { + while (true) { auto next = query->next(); if (!next.ok) { - *ok = false; + ok = false; return result; } if (!next.hasData) @@ -1709,14 +1739,14 @@ QStringList SyncJournalDb::getSelectiveSyncList(SyncJournalDb::SelectiveSyncList if (!entry.endsWith(QLatin1Char('/'))) { entry.append(QLatin1Char('/')); } - result.append(entry); + result.insert(entry); } - *ok = true; + ok = true; return result; } -void SyncJournalDb::setSelectiveSyncList(SyncJournalDb::SelectiveSyncListType type, const QStringList &list) +void SyncJournalDb::setSelectiveSyncList(SyncJournalDb::SelectiveSyncListType type, const QSet &list) { QMutexLocker locker(&_mutex); if (!checkConnect()) { @@ -1838,23 +1868,26 @@ QByteArray SyncJournalDb::getChecksumType(int checksumTypeId) return query->baValue(0); } -int SyncJournalDb::mapChecksumType(const QByteArray &checksumType) +int SyncJournalDb::mapChecksumType(CheckSums::Algorithm checksumType) { - if (checksumType.isEmpty()) { + if (checksumType == CheckSums::Algorithm::PARSE_ERROR || checksumType == CheckSums::Algorithm::NONE) { return 0; } - auto it = _checksymTypeCache.find(checksumType); - if (it != _checksymTypeCache.end()) + auto it = _checksymTypeCache.constFind(checksumType); + if (it != _checksymTypeCache.cend()) { return *it; + } + // the type is mapped as utf8 + const auto typeName = Utility::enumToString(checksumType).toUtf8(); // Ensure the checksum type is in the db { const auto query = _queryManager.get(PreparedSqlQueryManager::InsertChecksumTypeQuery, QByteArrayLiteral("INSERT OR IGNORE INTO checksumtype (name) VALUES (?1)"), _db); if (!query) { return 0; } - query->bindValue(1, checksumType); + query->bindValue(1, typeName); if (!query->exec()) { return 0; } @@ -1862,11 +1895,11 @@ int SyncJournalDb::mapChecksumType(const QByteArray &checksumType) // Retrieve the id { - const auto query = _queryManager.get(PreparedSqlQueryManager::GetChecksumTypeIdQuery, QByteArrayLiteral("SELECT id FROM checksumtype WHERE name=?1"), _db); + const auto query = _queryManager.get(PreparedSqlQueryManager::GetChecksumTypeIdQuery, QByteArrayLiteral("SELECT id FROM checksumtype WHERE UPPER(name)=?1"), _db); if (!query) { return 0; } - query->bindValue(1, checksumType); + query->bindValue(1, typeName); if (!query->exec()) { return 0; } @@ -2080,9 +2113,10 @@ Optional SyncJournalDb::PinStateInterface::effectiveForPath(const QByt auto next = query->next(); if (!next.ok) return {}; - // If the root path has no setting, assume AlwaysLocal - if (!next.hasData) - return PinState::AlwaysLocal; + // If the root path has no setting, assume Unspecified + if (!next.hasData) { + return PinState::Unspecified; + } return static_cast(query->intValue(0)); } @@ -2109,7 +2143,7 @@ Optional SyncJournalDb::PinStateInterface::effectiveForPathRecursive(c query->exec(); // Check if they are all identical - forever { + while (true) { auto next = query->next(); if (!next.ok) return {}; @@ -2169,7 +2203,7 @@ SyncJournalDb::PinStateInterface::rawList() query.exec(); QVector> result; - forever { + while (true) { auto next = query.next(); if (!next.ok) return {}; @@ -2207,7 +2241,7 @@ bool SyncJournalDb::open() return checkConnect(); } -bool SyncJournalDb::isOpen() +bool SyncJournalDb::isOpen() const { QMutexLocker lock(&_mutex); return _db.isOpen(); @@ -2228,6 +2262,58 @@ SyncJournalDb::~SyncJournalDb() close(); } +const QVector SyncJournalDb::getFileRecordsWithDirtyPlaceholders() const +{ + QMutexLocker locker(&_mutex); + + if (OC_ENSURE(isOpen())) { + const auto query = _queryManager.get(PreparedSqlQueryManager::GetFileReocrdsWithDirtyPlaceholdersQuery, getFileRecordQueryC + QByteArrayLiteral("WHERE hasDirtyPlaceholder=TRUE"), const_cast(this)->_db); + if (!OC_ENSURE(query)) { + return {}; + } + if (!OC_ENSURE(query->exec())) { + return {}; + } + QVector out; + while (query->next().hasData) { + SyncJournalFileRecord rec; + fillFileRecordFromGetQuery(rec, *query); + out.append(rec); + } + return out; + } + return {}; +} + +bool SyncJournalDb::createUploadInfo() +{ + SqlQuery createQuery(QByteArrayLiteral("CREATE TABLE IF NOT EXISTS uploadinfo(" + "path VARCHAR(4096)," + "chunk INTEGER," + "transferid INTEGER," + "errorcount INTEGER," + "size INTEGER(8)," + "modtime INTEGER(8)," + "contentChecksum TEXT," + // uploadUrl TEXT + "PRIMARY KEY(path)" + ");"), + _db); + + if (!createQuery.exec()) { + return sqlFail(QStringLiteral("Create table uploadinfo"), createQuery); + } + + const auto columns = tableColumns("uploadinfo"); + + if (columns.indexOf(QByteArrayLiteral("url")) == -1) { + SqlQuery query(QByteArrayLiteral("ALTER TABLE uploadinfo ADD COLUMN url TEXT;"), _db); + if (!query.exec()) { + return sqlFail(QStringLiteral("Add column url"), query); + } + } + return true; +} bool operator==(const SyncJournalDb::DownloadInfo &lhs, const SyncJournalDb::DownloadInfo &rhs) @@ -2249,5 +2335,4 @@ bool operator==(const SyncJournalDb::UploadInfo &lhs, && lhs._transferid == rhs._transferid && lhs._contentChecksum == rhs._contentChecksum; } - } // namespace OCC diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h index 03a22230488..2a1f1bac33e 100644 --- a/src/common/syncjournaldb.h +++ b/src/common/syncjournaldb.h @@ -25,12 +25,13 @@ #include #include -#include "common/utility.h" +#include "common/checksumalgorithms.h" #include "common/ownsql.h" +#include "common/pinstate.h" #include "common/preparedsqlquerymanager.h" -#include "common/syncjournalfilerecord.h" #include "common/result.h" -#include "common/pinstate.h" +#include "common/syncjournalfilerecord.h" +#include "common/utility.h" namespace OCC { class SyncJournalFileRecord; @@ -45,15 +46,14 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject { Q_OBJECT public: - explicit SyncJournalDb(const QString &dbFilePath, QObject *parent = nullptr); + explicit SyncJournalDb(const QString &dbFilePath, QObject *parent); ~SyncJournalDb() override; /// Create a journal path for a specific configuration static QString makeDbName(const QString &localPath, const QString &infix = QStringLiteral("journal")); - /// Migrate a csync_journal to the new path, if necessary. Returns false on error - static bool maybeMigrateDb(const QString &localPath, const QString &absoluteJournalPath); + static bool dbIsTooNewForClient(const QString &dbFilePath); // To verify that the record could be found check with SyncJournalFileRecord::isValid() bool getFileRecord(const QString &filename, SyncJournalFileRecord *rec) { return getFileRecord(filename.toUtf8(), rec); } @@ -62,12 +62,13 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject bool getFileRecordsByFileId(const QByteArray &fileId, const std::function &rowCallback); bool getFilesBelowPath(const QByteArray &path, const std::function &rowCallback); bool listFilesInPath(const QByteArray &path, const std::function &rowCallback); + const QVector getFileRecordsWithDirtyPlaceholders() const; Result setFileRecord(const SyncJournalFileRecord &record); bool deleteFileRecord(const QString &filename, bool recursively = false); bool updateFileRecordChecksum(const QString &filename, const QByteArray &contentChecksum, - const QByteArray &contentChecksumType); + CheckSums::Algorithm contentChecksumType); /// Return value for hasHydratedOrDehydratedFiles() struct HasHydratedDehydrated @@ -87,7 +88,8 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject static qint64 getPHash(const QByteArray &); void setErrorBlacklistEntry(const SyncJournalErrorBlacklistRecord &item); - void wipeErrorBlacklistEntry(const QString &file); + void wipeErrorBlacklistEntry(const QString &relativeFile); + void wipeErrorBlacklistEntry(const QString &relativeFile, SyncJournalErrorBlacklistRecord::Category category); void wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::Category category); int wipeErrorBlacklist(); int errorBlackListEntryCount(); @@ -106,27 +108,28 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject }; struct UploadInfo { - UploadInfo() - : _chunk(0) - , _transferid(0) - , _size(0) - , _errorCount(0) - , _valid(false) - { - } - int _chunk; - uint _transferid; - qint64 _size; - qint64 _modtime; - int _errorCount; - bool _valid; + int _chunk = 0; + uint _transferid = 0; + qint64 _size = 0; + qint64 _modtime = 0; + int _errorCount = 0; + bool _valid = false; QByteArray _contentChecksum; + QString _path; // stored as utf16 + QUrl _url; // upload url (tus) /** * Returns true if this entry refers to a chunked upload that can be continued. * (As opposed to a small file transfer which is stored in the db so we can detect the case * when the upload succeeded, but the connection was dropped before we got the answer) */ bool isChunked() const { return _transferid != 0; } + + bool validate(qint64 size, qint64 modtime, const QByteArray &checksum) const + { + Q_ASSERT(!checksum.isEmpty()); + Q_ASSERT(!_valid || !_contentChecksum.isEmpty()); + return _valid && _size == size && _modtime == modtime && _contentChecksum == checksum; + } }; DownloadInfo getDownloadInfo(const QString &file); @@ -135,6 +138,8 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject int downloadInfoCount(); UploadInfo getUploadInfo(const QString &file); + std::vector getUploadInfos(); + void setUploadInfo(const QString &file, const UploadInfo &i); // Return the list of transfer ids that were removed. QVector deleteStaleUploadInfos(const QSet &keep); @@ -161,10 +166,15 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject * should notify about */ SelectiveSyncUndecidedList = 3 }; - /* return the specified list from the database */ - QStringList getSelectiveSyncList(SelectiveSyncListType type, bool *ok); + Q_ENUM(SelectiveSyncListType) + + /* return the specified list from the database + * ok param holds value indicating "success" of the operation. If the sync list cannot be retrieved ok will be false + */ + QSet getSelectiveSyncList(SelectiveSyncListType type, bool &ok); + /* Write the selective sync list (remove all other entries of that list */ - void setSelectiveSyncList(SelectiveSyncListType type, const QStringList &list); + void setSelectiveSyncList(SelectiveSyncListType type, const QSet &list); /** * Make sure that on the next sync fileName and its parents are discovered from the server. @@ -213,11 +223,17 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject bool open(); /** Returns whether the db is currently openend. */ - bool isOpen(); + bool isOpen() const; /** Close the database */ void close(); + /** + * allow to reopen/recreate the db after it was closed (unit tests) + * This is usually not allowed to prevent accidential recreation of db. + */ + void allowReopen(); + /** * Returns the checksum type for an id. */ @@ -374,18 +390,20 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject QVector tableColumns(const QByteArray &table); bool checkConnect(); + bool createUploadInfo(); + // Same as forceRemoteDiscoveryNextSync but without acquiring the lock void forceRemoteDiscoveryNextSyncLocked(); // Returns the integer id of the checksum type // // Returns 0 on failure and for empty checksum types. - int mapChecksumType(const QByteArray &checksumType); + int mapChecksumType(CheckSums::Algorithm checksumType); SqlDatabase _db; QString _dbFile; - QMutex _mutex; // Public functions are protected with the mutex. - QMap _checksymTypeCache; + mutable QRecursiveMutex _mutex; // Public functions are protected with the mutex. + QMap _checksymTypeCache; int _transaction; bool _metadataTableIsEmpty; @@ -411,7 +429,12 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject */ QByteArray _journalMode; - PreparedSqlQueryManager _queryManager; + mutable PreparedSqlQueryManager _queryManager; + + /** + * Whether the db was already closed, prevent recreation + */ + bool _closed = false; }; bool OCSYNC_EXPORT diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h index 5c88f91d472..5c3106d6084 100644 --- a/src/common/syncjournalfilerecord.h +++ b/src/common/syncjournalfilerecord.h @@ -58,6 +58,7 @@ class OCSYNC_EXPORT SyncJournalFileRecord qint64 _fileSize = 0; RemotePermissions _remotePerm; bool _serverHasIgnoredFiles = false; + bool _hasDirtyPlaceholder = false; QByteArray _checksumHeader; }; @@ -67,13 +68,16 @@ operator==(const SyncJournalFileRecord &lhs, class OCSYNC_EXPORT SyncJournalErrorBlacklistRecord { + Q_GADGET public: - enum Category { + enum class Category { /// Normal errors have no special behavior Normal = 0, /// These get a special summary message - InsufficientRemoteStorage + InsufficientRemoteStorage, + LocalSoftError }; + Q_ENUM(Category) SyncJournalErrorBlacklistRecord() : _retryCount(0) diff --git a/src/common/utility.cpp b/src/common/utility.cpp index c2c1467c444..88b40438df6 100644 --- a/src/common/utility.cpp +++ b/src/common/utility.cpp @@ -16,11 +16,11 @@ * 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 "config.h" -#include "common/utility.h" +#include "common/asserts.h" #include "common/filesystembase.h" -#include "version.h" +#include "common/utility.h" +#include "common/version.h" // Note: This file must compile without QtGui #include @@ -31,11 +31,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #ifdef Q_OS_UNIX @@ -43,75 +45,41 @@ #include #include #endif +#ifdef Q_OS_WIN +#include +#endif #include #include #include using namespace std::chrono; - -namespace OCC { - -Q_LOGGING_CATEGORY(lcUtility, "sync.utility") - -QString Utility::formatFingerprint(const QByteArray &fmhash, bool colonSeparated) +namespace { +auto RFC1123PatternC() { - QByteArray hash; - int steps = fmhash.length() / 2; - for (int i = 0; i < steps; i++) { - hash.append(fmhash[i * 2]); - hash.append(fmhash[i * 2 + 1]); - hash.append(' '); - } - - QString fp = QString::fromLatin1(hash.trimmed()); - if (colonSeparated) { - fp.replace(QLatin1Char(' '), QLatin1Char(':')); - } - - return fp; + return QStringLiteral("ddd, dd MMM yyyy HH:mm:ss 'GMT'"); +} } +namespace OCC { + +Q_LOGGING_CATEGORY(lcUtility, "sync.utility", QtMsgType::QtInfoMsg) QString Utility::octetsToString(qint64 octets) { -#define THE_FACTOR 1024 - static const qint64 kb = THE_FACTOR; - static const qint64 mb = THE_FACTOR * kb; - static const qint64 gb = THE_FACTOR * mb; - - QString s; - qreal value = octets; - - // Whether we care about decimals: only for GB/MB and only - // if it's less than 10 units. - bool round = true; - - // do not display terra byte with the current units, as when - // the MB, GB and KB units were made, there was no TB, - // see the JEDEC standard - // https://en.wikipedia.org/wiki/JEDEC_memory_standards - if (octets >= gb) { - s = QCoreApplication::translate("Utility", "%L1 GB"); - value /= gb; - round = false; - } else if (octets >= mb) { - s = QCoreApplication::translate("Utility", "%L1 MB"); - value /= mb; - round = false; - } else if (octets >= kb) { - s = QCoreApplication::translate("Utility", "%L1 KB"); - value /= kb; - } else { - s = QCoreApplication::translate("Utility", "%L1 B"); - } + OC_ASSERT(octets >= 0) - if (value > 9.95) - round = true; + using namespace FileSystem::SizeLiterals; - if (round) - return s.arg(qRound(value)); + // We do what macOS 10.8 and above do: 0 fraction digits for bytes and KB; 1 fraction digits for MB; 2 for GB and above. + // See also https://developer.apple.com/documentation/foundation/nsbytecountformatter/1417887-adaptive + int precision = 0; + if (quint64(octets) >= 1_GiB) { + precision = 2; + } else if (quint64(octets) >= 1_MiB) { + precision = 1; + } - return s.arg(value, 0, 'g', 2); + return QLocale().formattedDataSize(octets, precision, QLocale::DataSizeTraditionalFormat); } // Qtified version of get_platforms() in csync_owncloud.c @@ -141,15 +109,10 @@ static QLatin1String platform() QByteArray Utility::userAgentString() { return QStringLiteral("Mozilla/5.0 (%1) mirall/%2 (%3, %4-%5 ClientArchitecture: %6 OsArchitecture: %7)") - .arg(platform(), - OCC::Version::displayString(), + .arg(platform(), OCC::Version::displayString(), // accessing the theme to fetch the string is rather difficult // since this is only needed server-side to identify clients, the app name (as of 2.9, the short name) is good enough - qApp->applicationName(), - QSysInfo::productType(), - QSysInfo::kernelVersion(), - QSysInfo::buildCpuArchitecture(), - QSysInfo::currentCpuArchitecture()) + qApp->applicationName(), QSysInfo::productType(), QSysInfo::kernelVersion(), QSysInfo::buildCpuArchitecture(), Utility::currentCpuArch()) .toLatin1(); } @@ -168,7 +131,7 @@ qint64 Utility::freeDiskSpace(const QString &path) #elif defined(Q_OS_WIN) ULARGE_INTEGER freeBytes; freeBytes.QuadPart = 0L; - if (GetDiskFreeSpaceEx(reinterpret_cast(FileSystem::longWinPath(path).utf16()), &freeBytes, NULL, NULL)) { + if (GetDiskFreeSpaceEx(reinterpret_cast(FileSystem::longWinPath(path).utf16()), &freeBytes, nullptr, nullptr)) { return freeBytes.QuadPart; } #endif @@ -178,7 +141,7 @@ qint64 Utility::freeDiskSpace(const QString &path) QString Utility::compactFormatDouble(double value, int prec, const QString &unit) { QLocale locale = QLocale::system(); - QChar decPoint = locale.decimalPoint(); + const QString decPoint = locale.decimalPoint(); QString str = locale.toString(value, 'f', prec); while (str.endsWith(QLatin1Char('0')) || str.endsWith(decPoint)) { if (str.endsWith(decPoint)) { @@ -197,16 +160,6 @@ QString Utility::escape(const QString &in) return in.toHtmlEscaped(); } -void Utility::sleep(int sec) -{ - QThread::sleep(sec); -} - -void Utility::usleep(int usec) -{ - QThread::usleep(usec); -} - // This can be overriden from the tests OCSYNC_EXPORT bool fsCasePreserving_override = []() -> bool { static bool ok = false; @@ -217,7 +170,7 @@ OCSYNC_EXPORT bool fsCasePreserving_override = []() -> bool { return Utility::isWindows() || Utility::isMac(); }(); -bool Utility::fsCasePreserving() +bool Utility::fsCasePreservingButCaseInsensitive() { return fsCasePreserving_override; } @@ -231,7 +184,7 @@ bool Utility::fileNamesEqual(const QString &fn1, const QString &fn2) // ONLY use this function with existing pathes. const QString a = fd1.canonicalPath(); const QString b = fd2.canonicalPath(); - bool re = !a.isEmpty() && QString::compare(a, b, fsCasePreserving() ? Qt::CaseInsensitive : Qt::CaseSensitive) == 0; + bool re = !a.isEmpty() && QString::compare(a, b, fsCaseSensitivity()) == 0; return re; } @@ -246,46 +199,36 @@ qint64 Utility::qDateTimeToTime_t(const QDateTime &t) } namespace { - struct Period + constexpr struct { - const char *name; - quint64 msec; - - QString description(quint64 value) const - { - return QCoreApplication::translate("Utility", name, nullptr, value); - } - }; -// QTBUG-3945 and issue #4855: QT_TRANSLATE_NOOP does not work with plural form because lupdate -// limitation unless we fake more arguments -// (it must be in the form ("context", "source", "comment", n) -#undef QT_TRANSLATE_NOOP -#define QT_TRANSLATE_NOOP(ctx, str, ...) str - Q_DECL_CONSTEXPR Period periods[] = { - { QT_TRANSLATE_NOOP("Utility", "%n year(s)", 0, _), 365 * 24 * 3600 * 1000LL }, - { QT_TRANSLATE_NOOP("Utility", "%n month(s)", 0, _), 30 * 24 * 3600 * 1000LL }, - { QT_TRANSLATE_NOOP("Utility", "%n day(s)", 0, _), 24 * 3600 * 1000LL }, - { QT_TRANSLATE_NOOP("Utility", "%n hour(s)", 0, _), 3600 * 1000LL }, - { QT_TRANSLATE_NOOP("Utility", "%n minute(s)", 0, _), 60 * 1000LL }, - { QT_TRANSLATE_NOOP("Utility", "%n second(s)", 0, _), 1000LL }, - { nullptr, 0 } + const char *const name; + const std::chrono::milliseconds msec; + QString description(int value) const { return QCoreApplication::translate("Utility", name, nullptr, value); } + } periods[] = { + {QT_TRANSLATE_N_NOOP("Utility", "%n year(s)"), 24h * 365}, + {QT_TRANSLATE_N_NOOP("Utility", "%n month(s)"), 24h * 30}, + {QT_TRANSLATE_N_NOOP("Utility", "%n day(s)"), 24h}, + {QT_TRANSLATE_N_NOOP("Utility", "%n hour(s)"), 1h}, + {QT_TRANSLATE_N_NOOP("Utility", "%n minute(s)"), 1min}, + {QT_TRANSLATE_N_NOOP("Utility", "%n second(s)"), 1s}, + {nullptr, {}}, }; } // anonymous namespace -QString Utility::durationToDescriptiveString2(quint64 msecs) +QString Utility::durationToDescriptiveString2(std::chrono::milliseconds msecs) { int p = 0; while (periods[p + 1].name && msecs < periods[p].msec) { p++; } - auto firstPart = periods[p].description(int(msecs / periods[p].msec)); + const QString firstPart = periods[p].description(static_cast(msecs / periods[p].msec)); if (!periods[p + 1].name) { return firstPart; } - quint64 secondPartNum = qRound(double(msecs % periods[p].msec) / periods[p + 1].msec); + const quint64 secondPartNum = qRound(static_cast(msecs.count() % periods[p].msec.count()) / periods[p + 1].msec.count()); if (secondPartNum == 0) { return firstPart; @@ -294,15 +237,13 @@ QString Utility::durationToDescriptiveString2(quint64 msecs) return QCoreApplication::translate("Utility", "%1 %2").arg(firstPart, periods[p + 1].description(secondPartNum)); } -QString Utility::durationToDescriptiveString1(quint64 msecs) +QString Utility::durationToDescriptiveString1(std::chrono::milliseconds msecs) { int p = 0; while (periods[p + 1].name && msecs < periods[p].msec) { p++; } - - quint64 amount = qRound(double(msecs) / periods[p].msec); - return periods[p].description(amount); + return periods[p].description(qRound(static_cast(msecs.count()) / periods[p].msec.count())); } QString Utility::fileNameForGuiUse(const QString &fName) @@ -314,24 +255,34 @@ QString Utility::fileNameForGuiUse(const QString &fName) return fName; } -QByteArray Utility::normalizeEtag(QByteArray etag) +QString Utility::normalizeEtag(QStringView etag) { - /* strip "XXXX-gzip" */ - if(etag.startsWith('"') && etag.endsWith("-gzip\"")) { - etag.chop(6); - etag.remove(0, 1); + if (etag.isEmpty()) { + return {}; } - /* strip trailing -gzip */ - if(etag.endsWith("-gzip")) { - etag.chop(5); + + const auto unQuote = [&] { + if (etag.startsWith(QLatin1Char('"')) && etag.endsWith(QLatin1Char('"'))) { + etag = etag.mid(1); + etag.chop(1); + } + }; + + // Weak E-Tags can appear when gzip compression is on, see #3946 + if (etag.startsWith(QLatin1String("W/"))) { + etag = etag.mid(2); } - /* strip normal quotes */ - if (etag.startsWith('"') && etag.endsWith('"')) { - etag.chop(1); - etag.remove(0, 1); + /* strip normal quotes and quotes around "XXXX-gzip" */ + unQuote(); + + // https://github.com/owncloud/client/issues/1195 + const QLatin1String gzipSuffix("-gzip"); + if (etag.endsWith(gzipSuffix)) { + etag.chop(gzipSuffix.size()); } - etag.squeeze(); - return etag; + /* strip normal quotes */ + unQuote(); + return etag.toString(); } QString Utility::platformName() @@ -345,123 +296,34 @@ void Utility::crash() *a = 1; } -// read the output of the owncloud --version command from the owncloud -// version that is on disk. This works for most versions of the client, -// because clients that do not yet know the --version flag return the -// version in the first line of the help output :-) -// -// This version only delivers output on linux, as Mac and Win get their -// restarting from the installer. -QByteArray Utility::versionOfInstalledBinary(const QString &command) -{ - QByteArray re; - if (isLinux()) { - QString binary(command); - if (binary.isEmpty()) { - binary = qApp->arguments()[0]; - } - QStringList params; - params << QStringLiteral("--version"); - QProcess process; - process.start(binary, params); - process.waitForFinished(); // sets current thread to sleep and waits for pingProcess end - re = process.readAllStandardOutput(); - int newline = re.indexOf('\n'); - if (newline > 0) { - re.truncate(newline); - } - } - return re; -} - QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from) { - QDateTime now = QDateTime::currentDateTimeUtc(); - - if (from.isValid()) { - now = from; - } + const QDateTime now = from.isValid() ? from : QDateTime::currentDateTimeUtc(); if (dt.daysTo(now) > 0) { - int dtn = dt.daysTo(now); - return QObject::tr("%n day(s) ago", "", dtn); - } else { - qint64 secs = dt.secsTo(now); - if (secs < 0) { - return QObject::tr("in the future"); - } - if (floor(secs / 3600.0) > 0) { - int hours = floor(secs / 3600.0); - return (QObject::tr("%n hour(s) ago", "", hours)); - } else { - int minutes = qRound(secs / 60.0); - if (minutes == 0) { - if (secs < 5) { - return QObject::tr("now"); - } else { - return QObject::tr("Less than a minute ago"); - } - } - return (QObject::tr("%n minute(s) ago", "", minutes)); - } + return QObject::tr("%n day(s) ago", "", dt.daysTo(now)); } - return QObject::tr("Some time ago"); -} - -/* --------------------------------------------------------------------------- */ - -static const char STOPWATCH_END_TAG[] = "_STOPWATCH_END"; - -void Utility::StopWatch::start() -{ - _startTime = QDateTime::currentDateTimeUtc(); - _timer.start(); -} - -quint64 Utility::StopWatch::stop() -{ - addLapTime(QLatin1String(STOPWATCH_END_TAG)); - quint64 duration = _timer.elapsed(); - _timer.invalidate(); - return duration; -} -void Utility::StopWatch::reset() -{ - _timer.invalidate(); - _startTime.setMSecsSinceEpoch(0); - _lapTimes.clear(); -} - -quint64 Utility::StopWatch::addLapTime(const QString &lapName) -{ - if (!_timer.isValid()) { - start(); + const qint64 secs = dt.secsTo(now); + if (secs < 0) { + return QObject::tr("in the future"); } - quint64 re = _timer.elapsed(); - _lapTimes[lapName] = re; - return re; -} -QDateTime Utility::StopWatch::startTime() const -{ - return _startTime; -} + if (floor(secs / 3600.0) > 0) { + const int hours = floor(secs / 3600.0); + return (QObject::tr("%n hour(s) ago", "", hours)); + } -QDateTime Utility::StopWatch::timeOfLap(const QString &lapName) const -{ - quint64 t = durationOfLap(lapName); - if (t) { - QDateTime re(_startTime); - return re.addMSecs(t); + const int minutes = qRound(secs / 60.0); + if (minutes == 0) { + if (secs < 5) { + return QObject::tr("now"); + } else { + return QObject::tr("less than a minute ago"); + } } - return QDateTime(); -} - -quint64 Utility::StopWatch::durationOfLap(const QString &lapName) const -{ - return _lapTimes.value(lapName, 0); + return (QObject::tr("%n minute(s) ago", "", minutes)); } void Utility::sortFilenames(QStringList &fileNames) @@ -472,6 +334,16 @@ void Utility::sortFilenames(QStringList &fileNames) std::sort(fileNames.begin(), fileNames.end(), collator); } +QString Utility::concatUrlPathItems(QStringList &&items, const QLatin1Char delimiter) +{ + for (QString &item : items) { + while (item.endsWith(QLatin1Char('/'))) { + item.chop(1); + } + } + return items.join(delimiter); +} + QUrl Utility::concatUrlPath(const QUrl &url, const QString &concatPath, const QUrlQuery &queryItems) { @@ -486,15 +358,29 @@ QUrl Utility::concatUrlPath(const QUrl &url, const QString &concatPath, } path += concatPath; // put the complete path together } - + Q_ASSERT(!path.contains(QStringLiteral("//"))); + Q_ASSERT(url.query().isEmpty()); QUrl tmpUrl = url; tmpUrl.setPath(path); tmpUrl.setQuery(queryItems); return tmpUrl; } -QString Utility::makeConflictFileName( - const QString &fn, const QDateTime &dt, const QString &user) +bool Utility::urlEqual(QUrl url1, QUrl url2) +{ + // ensure https://demo.owncloud.org/ matches https://demo.owncloud.org + // the empty path was the legacy formating before 2.9 + if (url1.path().isEmpty()) { + url1.setPath(QStringLiteral("/")); + } + if (url2.path().isEmpty()) { + url2.setPath(QStringLiteral("/")); + } + + return url1.matches(url2, QUrl::StripTrailingSlash | QUrl::NormalizePathSegments); +} + +QString Utility::makeConflictFileName(const QString &fn, const QDateTime &dt) { QString conflictFileName(fn); // Add conflict tag before the extension. @@ -505,19 +391,13 @@ QString Utility::makeConflictFileName( } QString conflictMarker = QStringLiteral(" (conflicted copy "); - if (!user.isEmpty()) { - // Don't allow parens in the user name, to ensure - // we can find the beginning and end of the conflict tag. - const auto userName = sanitizeForFileName(user).replace(QLatin1Char('('), QLatin1Char('_')).replace(QLatin1Char(')'), QLatin1Char('_'));; - conflictMarker += userName + QLatin1Char(' '); - } conflictMarker += dt.toString(QStringLiteral("yyyy-MM-dd hhmmss")) + QLatin1Char(')'); conflictFileName.insert(dotLocation, conflictMarker); return conflictFileName; } -bool Utility::isConflictFile(const QStringRef &name) +bool Utility::isConflictFile(QStringView name) { auto bname = name.mid(name.lastIndexOf(QLatin1Char('/')) + 1); @@ -566,16 +446,59 @@ QString Utility::sanitizeForFileName(const QString &name) QString result; result.reserve(name.size()); for (const auto &c : name) { - if (!invalid.contains(c) - && c.category() != QChar::Other_Control - && c.category() != QChar::Other_Format) { + if (!invalid.contains(c) && c.category() != QChar::Other_Control && c.category() != QChar::Other_Format) { result.append(c); } } return result; } -QDebug &operator<<(QDebug &debug, nanoseconds in) +QString Utility::appImageLocation() +{ +#ifdef Q_OS_LINUX + Q_ASSERT(Utility::runningInAppImage()); + static const auto value = qEnvironmentVariable("APPIMAGE"); + return value; +#else + Q_UNREACHABLE(); +#endif +} + +bool Utility::runningInAppImage() +{ +#ifdef Q_OS_LINUX + return qEnvironmentVariableIsSet("APPIMAGE"); +#else + return false; +#endif +} + +QDateTime Utility::parseRFC1123Date(const QString &date) +{ + if (!date.isEmpty()) { + auto out = QDateTime::fromString(date, RFC1123PatternC()); + Q_ASSERT(out.isValid()); + out.setTimeZone(QTimeZone::utc()); + return out; + } + return {}; +} + +QString Utility::formatRFC1123Date(const QDateTime &date) +{ + return date.toUTC().toString(RFC1123PatternC()); +} + +#ifndef Q_OS_MAC +QString Utility::currentCpuArch() +{ + return QSysInfo::currentCpuArchitecture(); +} +#endif + +} // namespace OCC + +QDebug operator<<(QDebug debug, nanoseconds in) { QDebugStateSaver save(debug); debug.nospace(); @@ -583,11 +506,9 @@ QDebug &operator<<(QDebug &debug, nanoseconds in) const auto min = duration_cast(in -= h); const auto s = duration_cast(in -= min); const auto ms = duration_cast(in -= s); - return debug << "std::chrono::duration(" + return debug << "duration(" << h.count() << "h, " << min.count() << "min, " << s.count() << "s, " << ms.count() << "ms)"; } - -} // namespace OCC diff --git a/src/common/utility.h b/src/common/utility.h index 753d4279194..8a9286e13a0 100644 --- a/src/common/utility.h +++ b/src/common/utility.h @@ -17,10 +17,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef UTILITY_H -#define UTILITY_H +#pragma once #include "ocsynclib.h" + #include #include #include @@ -30,14 +30,13 @@ #include #include #include + #include #include - -#ifdef Q_OS_WIN -#include -#endif +#include class QSettings; +class QQuickWidget; namespace OCC { @@ -50,12 +49,15 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) */ namespace Utility { - OCSYNC_EXPORT void sleep(int sec); - OCSYNC_EXPORT void usleep(int usec); - OCSYNC_EXPORT QString formatFingerprint(const QByteArray &, bool colonSeparated = true); + /** + * @brief setupFavLink is a mac only impl that is defined in utility_mac.mm + * It's a noop on windows. + * @param folder is the folder path you want to add the fav link to + */ OCSYNC_EXPORT void setupFavLink(const QString &folder); OCSYNC_EXPORT QString octetsToString(qint64 octets); OCSYNC_EXPORT QByteArray userAgentString(); + /** * @brief Return whether launch on startup is enabled system wide. * @@ -65,6 +67,11 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) * Currently only implemented on Windows. */ OCSYNC_EXPORT bool hasSystemLaunchOnStartup(const QString &appName); + + /** + * @brief Return if launch on startup is enabled for the current user. + * @param appName the name of the application (because of branding) + */ OCSYNC_EXPORT bool hasLaunchOnStartup(const QString &appName); OCSYNC_EXPORT void setLaunchOnStartup(const QString &appName, const QString &guiName, bool launch); @@ -94,7 +101,7 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) /** * @brief Convert milliseconds duration to human readable string. - * @param quint64 msecs the milliseconds to convert to string. + * @param msecs the milliseconds to convert to string. * @return an HMS representation of the milliseconds value. * * durationToDescriptiveString1 describes the duration in a single @@ -103,10 +110,9 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) * durationToDescriptiveString2 uses two units where possible, so * "5 minutes 43 seconds" or "1 month 3 days". */ - OCSYNC_EXPORT QString durationToDescriptiveString1(quint64 msecs); - OCSYNC_EXPORT QString durationToDescriptiveString2(quint64 msecs); + OCSYNC_EXPORT QString durationToDescriptiveString1(std::chrono::milliseconds msecs); + OCSYNC_EXPORT QString durationToDescriptiveString2(std::chrono::milliseconds msecs); -#ifndef TOKEN_AUTH_ONLY /** * @brief hasDarkSystray - determines whether the systray is dark or light. * @@ -118,14 +124,13 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) * @return bool which is true for systems with dark systray. */ OCSYNC_EXPORT bool hasDarkSystray(); -#endif // convenience OS detection methods - inline bool isWindows(); - inline bool isMac(); - inline bool isUnix(); - inline bool isLinux(); // use with care - inline bool isBSD(); // use with care, does not match OS X + constexpr bool isWindows(); + constexpr bool isMac(); + constexpr bool isUnix(); + constexpr bool isLinux(); // use with care + constexpr bool isBSD(); // use with care, does not match OS X OCSYNC_EXPORT QString platformName(); // crash helper for --debug @@ -135,10 +140,10 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) // if this function returns true, the file system is case preserving, // that means "test" means the same as "TEST" for filenames. // if false, the two cases are two different files. - OCSYNC_EXPORT bool fsCasePreserving(); + OCSYNC_EXPORT bool fsCasePreservingButCaseInsensitive(); inline auto fsCaseSensitivity() { - return fsCasePreserving() ? Qt::CaseInsensitive : Qt::CaseSensitive; + return fsCasePreservingButCaseInsensitive() ? Qt::CaseInsensitive : Qt::CaseSensitive; } // Check if two pathes that MUST exist are equal. This function @@ -154,16 +159,24 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) return s.toString(); } + inline QString ensureTrailingSlash(QStringView s) + { + if (!s.endsWith(QLatin1Char('/'))) { + return s.toString() + QLatin1Char('/'); + } + return s.toString(); + } + // Call the given command with the switch --version and rerun the first line // of the output. // If command is empty, the function calls the running application which, on // Linux, might have changed while this one is running. // For Mac and Windows, it returns QString() - OCSYNC_EXPORT QByteArray versionOfInstalledBinary(const QString &command = QString()); + OCSYNC_EXPORT QString versionOfInstalledBinary(const QString &command = QString()); OCSYNC_EXPORT QString fileNameForGuiUse(const QString &fName); - OCSYNC_EXPORT QByteArray normalizeEtag(QByteArray etag); + OCSYNC_EXPORT QString normalizeEtag(QStringView etag); /** * @brief timeAgoInWords - human readable time span @@ -175,35 +188,24 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) */ OCSYNC_EXPORT QString timeAgoInWords(const QDateTime &dt, const QDateTime &from = QDateTime()); - class OCSYNC_EXPORT StopWatch - { - private: - QMap _lapTimes; - QDateTime _startTime; - QElapsedTimer _timer; - - public: - void start(); - quint64 stop(); - quint64 addLapTime(const QString &lapName); - void reset(); - - // out helpers, return the measured times. - QDateTime startTime() const; - QDateTime timeOfLap(const QString &lapName) const; - quint64 durationOfLap(const QString &lapName) const; - }; - /** * @brief Sort a QStringList in a way that's appropriate for filenames */ OCSYNC_EXPORT void sortFilenames(QStringList &fileNames); + /** + * Concatenate parts of a URL path with a given delimiter, eliminating duplicate + */ + OCSYNC_EXPORT QString concatUrlPathItems(QStringList && items, const QLatin1Char delimiter = QLatin1Char('/')); + /** Appends concatPath and queryItems to the url */ OCSYNC_EXPORT QUrl concatUrlPath( const QUrl &url, const QString &concatPath, const QUrlQuery &queryItems = {}); + /** Compares two urls and ignores whether thei end wit / */ + OCSYNC_EXPORT bool urlEqual(QUrl a, QUrl b); + /** Returns a new settings pre-set in a specific group. The Settings will be created with the given parent. If no parent is specified, the caller must destroy the settings */ OCSYNC_EXPORT std::unique_ptr settingsWithGroup(const QString &group, QObject *parent = nullptr); @@ -224,16 +226,11 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) /** Returns a file name based on \a fn that's suitable for a conflict. */ - OCSYNC_EXPORT QString makeConflictFileName( - const QString &fn, const QDateTime &dt, const QString &user); + OCSYNC_EXPORT QString makeConflictFileName(const QString &fn, const QDateTime &dt); /** Returns whether a file name indicates a conflict file */ - OCSYNC_EXPORT bool isConflictFile(const QStringRef &name); - inline auto isConflictFile(const QString &name) - { - return isConflictFile(&name); - } + OCSYNC_EXPORT bool isConflictFile(QStringView name); /** Find the base name for a conflict file name, using name pattern only * @@ -244,41 +241,13 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) */ OCSYNC_EXPORT QByteArray conflictFileBaseNameFromPattern(const QByteArray &conflictName); -#ifdef Q_OS_WIN - OCSYNC_EXPORT QVariant registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName); - OCSYNC_EXPORT bool registrySetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName, DWORD type, const QVariant &value); - OCSYNC_EXPORT bool registryDeleteKeyTree(HKEY hRootKey, const QString &subKey); - OCSYNC_EXPORT bool registryDeleteKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName); - OCSYNC_EXPORT bool registryWalkSubKeys(HKEY hRootKey, const QString &subKey, const std::function &callback); - - // Possibly refactor to share code with UnixTimevalToFileTime in c_time.c - OCSYNC_EXPORT void UnixTimeToFiletime(time_t t, FILETIME *filetime); - OCSYNC_EXPORT void FiletimeToLargeIntegerFiletime(FILETIME *filetime, LARGE_INTEGER *hundredNSecs); - OCSYNC_EXPORT void UnixTimeToLargeIntegerFiletime(time_t t, LARGE_INTEGER *hundredNSecs); - - OCSYNC_EXPORT QString formatWinError(long error); - - class OCSYNC_EXPORT NtfsPermissionLookupRAII - { - public: - /** - * NTFS permissions lookup is diabled by default for performance reasons - * Enable it and disable it again once we leave the scope - * https://doc.qt.io/Qt-5/qfileinfo.html#ntfs-permissions - */ - NtfsPermissionLookupRAII(); - ~NtfsPermissionLookupRAII(); - - private: - Q_DISABLE_COPY(NtfsPermissionLookupRAII); - }; - -#endif - template E stringToEnum(const char *key) { - return static_cast(QMetaEnum::fromType().keyToValue(key)); + bool ok; + auto out = static_cast(QMetaEnum::fromType().keyToValue(key, &ok)); + Q_ASSERT(ok); + return out; } template @@ -290,18 +259,59 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcUtility) template QString enumToString(E value) { - return QMetaEnum::fromType().valueToKeys(value); + return QString::fromUtf8(QMetaEnum::fromType().valueToKeys(static_cast(value))); } + template QString enumToDisplayName(E) { static_assert(std::is_same::value, "Not implemented"); Q_UNREACHABLE(); } -} + + template + class asKeyValueRange + { + // https://www.kdab.com/qt-range-based-for-loops-and-structured-bindings/ + public: + asKeyValueRange(const T &data) + : _data{data} + { + } + + auto begin() { return _data.constKeyValueBegin(); } + + auto end() { return _data.constKeyValueEnd(); } + + private: + const T &_data; + }; + + /** + * Perform a const find on a Qt container and returns an std::optional + * This allows performant access to the container with in a simple if condition. + * if (auto it = optionalFind("key")) + */ + template + auto optionalFind(const T &container, const QString &key) + { + auto it = container.constFind(key); + return it == container.cend() ? std::nullopt : std::make_optional(it); + } + + + OCSYNC_EXPORT QString appImageLocation(); + OCSYNC_EXPORT bool runningInAppImage(); + + OCSYNC_EXPORT QDateTime parseRFC1123Date(const QString &date); + OCSYNC_EXPORT QString formatRFC1123Date(const QDateTime &date); + + // replacement for QSysInfo::currentCpuArchitecture() that respects macOS's rosetta2 + OCSYNC_EXPORT QString currentCpuArch(); +} // Utility namespace /** @} */ // \addtogroup -inline bool Utility::isWindows() +constexpr bool Utility::isWindows() { #ifdef Q_OS_WIN return true; @@ -310,7 +320,7 @@ inline bool Utility::isWindows() #endif } -inline bool Utility::isMac() +constexpr bool Utility::isMac() { #ifdef Q_OS_MAC return true; @@ -319,7 +329,7 @@ inline bool Utility::isMac() #endif } -inline bool Utility::isUnix() +constexpr bool Utility::isUnix() { #ifdef Q_OS_UNIX return true; @@ -328,7 +338,7 @@ inline bool Utility::isUnix() #endif } -inline bool Utility::isLinux() +constexpr bool Utility::isLinux() { #if defined(Q_OS_LINUX) return true; @@ -337,7 +347,7 @@ inline bool Utility::isLinux() #endif } -inline bool Utility::isBSD() +constexpr bool Utility::isBSD() { #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) return true; @@ -345,7 +355,6 @@ inline bool Utility::isBSD() return false; #endif } +} // OCC namespace -OCSYNC_EXPORT QDebug &operator<<(QDebug &debug, std::chrono::nanoseconds in); -} -#endif // UTILITY_H +OCSYNC_EXPORT QDebug operator<<(QDebug debug, std::chrono::nanoseconds in); diff --git a/src/common/utility_mac.mm b/src/common/utility_mac.mm index 16105143ef6..d3f76256733 100644 --- a/src/common/utility_mac.mm +++ b/src/common/utility_mac.mm @@ -22,13 +22,18 @@ #include #include #include + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include +#endif #include #include #import #import +#include + namespace OCC { void Utility::setupFavLink(const QString &folder) @@ -61,6 +66,26 @@ static Result writePlistToFile(NSString *plistFile, NSDictionary *plist) { NSError *error = nil; + + // Check if the directory exists. On a fresh installation for example, the directory does not + // exist, so writing the plist file below will fail. + QDir plistDir = QFileInfo(QString::fromNSString(plistFile)).dir(); + if (!plistDir.exists()) { + if (!QDir().mkpath(plistDir.path())) { + return QString(QStringLiteral("Failed to create directory '%1'")).arg(plistDir.path()); + } + + // Permissions always seem to be 0700, so set that. + // Note: the Permission enum documentation states that on unix the owner permissions are + // returned, but: "This behavior might change in a future Qt version." So we play it safe, + // and set both the user and the owner permissions to rwx. + if (!QFile(plistDir.path()).setPermissions(QFileDevice::ReadOwner | QFileDevice::ReadUser | QFileDevice::WriteOwner | QFileDevice::WriteUser | QFileDevice::ExeOwner | QFileDevice::ExeUser)) { + qCInfo(lcUtility()) << "Failed to set directory permmissions for" << plistDir.path(); + } + } + + // Now write the file. + qCInfo(lcUtility()) << "Writing plist file" << QString::fromNSString(plistFile); // Especially for branded clients: log the file name, so it can be found when debugging. if (![plist writeToURL:[NSURL fileURLWithPath:plistFile isDirectory:NO] error:&error]) { return QString::fromNSString(error.localizedDescription); } @@ -103,7 +128,8 @@ // ... and yes, it's the correct app-id... if (id program = plist[@"Program"]) { // .. and there is a program mentioned ... - if ([QCoreApplication::applicationFilePath().toNSString() isEqualToString:program]) { + // (Note: case insensitive compare, because most fs setups on mac are case insensitive) + if ([QCoreApplication::applicationFilePath().toNSString() compare:program options:NSCaseInsensitiveSearch] == NSOrderedSame) { // ... and it's our executable .. if (NSNumber *value = plist[@"RunAtLoad"]) { // yes, there is even a RunAtLoad key, so use it! @@ -124,10 +150,7 @@ { NSDictionary *plistTemplate = @{ @"Label" : QCoreApplication::organizationDomain().toNSString(), - @"KeepAlive" : @ { - @"Crashed" : @YES, - @"SuccessfulExit" : @NO - }, + @"KeepAlive" : @NO, @"Program" : fullPath, @"RunAtLoad" : enable ? @YES : @NO }; @@ -189,7 +212,7 @@ if (!result) { qCWarning(lcUtility) << result.error(); } - } else if ([fullPath isEqualToString:programValue]) { + } else if ([fullPath compare:programValue options:NSCaseInsensitiveSearch] == NSOrderedSame) { // (Note: case insensitive compare, because most fs setups on mac are case insensitive) // Wohoo, it's ours! Now carefully change only the RunAtLoad entry. If any value for // e.g. KeepAlive was changed, we leave it as-is. auto result = modifyPlist(plistFile, plist, enable); @@ -208,10 +231,14 @@ "so let's not touch the file."; } } else { - // plist doens't exist, write a new one. - auto result = writeNewPlistFile(plistFile, fullPath, enable); - if (!result) { - qCWarning(lcUtility) << result.error(); + if (enable) { + // plist doens't exist, write a new one. + auto result = writeNewPlistFile(plistFile, fullPath, enable); + if (!result) { + qCWarning(lcUtility) << result.error(); + } + } else { + // Do nothing: if the file doesn't exist, the client won't be auto-started. } } } @@ -230,4 +257,41 @@ } #endif +QString Utility::currentCpuArch() +{ + static const QString rv = []() { + int procTranslated = 0; + size_t size = sizeof(procTranslated); + + const auto rv = sysctlbyname("sysctl.proc_translated", &procTranslated, &size, nullptr, 0); + + // should be backed up if not evaluated immediately + const auto error = errno; + + qCDebug(lcUtility) << "proc_translated:" << procTranslated; + qCDebug(lcUtility) << "error:" << strerror(error); + + if (rv != -1) { + if (procTranslated == 1) { + qCInfo(lcUtility) << "process translated with rosetta2"; + return QStringLiteral("arm64"); + } else { + qCInfo(lcUtility) << "process not translated"; + } + } else { + if (error == ENOENT) { + qCDebug(lcUtility) << "not running on Apple Silicon"; + } else { + qCDebug(lcUtility) << "unknown error trying to detect whether proc is translated"; + } + } + + const auto qtArch = QSysInfo::currentCpuArchitecture(); + qCDebug(lcUtility) << "CPU arch reported by Qt:" << qtArch; + return qtArch; + }(); + + return rv; +} + } // namespace OCC diff --git a/src/common/utility_unix.cpp b/src/common/utility_unix.cpp index 7770465cd5f..f9eddf08776 100644 --- a/src/common/utility_unix.cpp +++ b/src/common/utility_unix.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -31,8 +32,8 @@ namespace OCC { void Utility::setupFavLink(const QString &folder) { // Nautilus: add to ~/.gtk-bookmarks - QFile gtkBookmarks(QDir::homePath() + QLatin1String("/.gtk-bookmarks")); - QByteArray folderUrl = "file://" + folder.toUtf8(); + QFile gtkBookmarks(QDir::homePath() + QLatin1String("/.config/gtk-3.0/bookmarks")); + const QByteArray folderUrl = QUrl::fromLocalFile(folder).toEncoded(QUrl::ComponentFormattingOption::EncodeSpaces); if (gtkBookmarks.open(QFile::ReadWrite)) { QByteArray places = gtkBookmarks.readAll(); if (!places.contains(folderUrl)) { @@ -78,19 +79,47 @@ void Utility::setLaunchOnStartup(const QString &appName, const QString &guiName, qCWarning(lcUtility) << "Could not write auto start entry" << desktopFileLocation; return; } + + auto autostartApplicationPath = []() -> QString { + // $APPIMAGE will be set to the AppImage's path by the AppImage runtime + // if it is set, we can assume to be run from within an AppImage + // in that case, the desktop file should point to the AppImage rather than the + // main binary, which will be in a temporary mount point + if (Utility::runningInAppImage()) { + return Utility::appImageLocation(); + } + + // we need to launch the client via its provided AppRun script (or at least source the AppRun hooks) to make sure it is displayed correctly + // if installed as a native package generated by linuxdeploy-plugin-native_packages, a linuxdeploy.conf file will be available that points to the + // location of the installed AppDir + QString linuxdeployConfPath = qApp->applicationDirPath() + QStringLiteral("/linuxdeploy.conf"); + + if (QFile(linuxdeployConfPath).exists()) { + QSettings linuxdeployConf(linuxdeployConfPath, QSettings::IniFormat); + + const auto appdirInstallatedPath = linuxdeployConf.value(QStringLiteral("native_packages/appdir_installed_path")); + if (!appdirInstallatedPath.isNull()) { + return appdirInstallatedPath.toString() + QStringLiteral("/AppRun"); + } + } + + // otherwise, we just use the application binary's own path, which should work for distribution-packaged installations and also for development + return QCoreApplication::applicationFilePath(); + }(); + QTextStream ts(&iniFile); - ts.setCodec("UTF-8"); - ts << QLatin1String("[Desktop Entry]") << endl - << QLatin1String("Name=") << guiName << endl - << QLatin1String("GenericName=") << QLatin1String("File Synchronizer") << endl - << QLatin1String("Exec=") << QCoreApplication::applicationFilePath() << endl - << QLatin1String("Terminal=") << "false" << endl - << QLatin1String("Icon=") << appName.toLower() << endl // always use lowercase for icons - << QLatin1String("Categories=") << QLatin1String("Network") << endl - << QLatin1String("Type=") << QLatin1String("Application") << endl - << QLatin1String("StartupNotify=") << "false" << endl - << QLatin1String("X-GNOME-Autostart-enabled=") << "true" << endl - << QLatin1String("X-GNOME-Autostart-Delay=10") << endl; + ts.setEncoding(QStringConverter::Utf8); + ts << QLatin1String("[Desktop Entry]") << Qt::endl + << QLatin1String("Name=") << guiName << Qt::endl + << QLatin1String("GenericName=") << QLatin1String("File Synchronizer") << Qt::endl + << QLatin1String("Exec=") << autostartApplicationPath << Qt::endl + << QLatin1String("Terminal=") << "false" << Qt::endl + << QLatin1String("Icon=") << appName.toLower() << Qt::endl // always use lowercase for icons + << QLatin1String("Categories=") << QLatin1String("Network") << Qt::endl + << QLatin1String("Type=") << QLatin1String("Application") << Qt::endl + << QLatin1String("StartupNotify=") << "false" << Qt::endl + << QLatin1String("X-GNOME-Autostart-enabled=") << "true" << Qt::endl + << QLatin1String("X-GNOME-Autostart-Delay=10") << Qt::endl; } else { if (!QFile::remove(desktopFileLocation)) { qCWarning(lcUtility) << "Could not remove autostart desktop file"; @@ -98,11 +127,9 @@ void Utility::setLaunchOnStartup(const QString &appName, const QString &guiName, } } -#ifndef TOKEN_AUTH_ONLY bool Utility::hasDarkSystray() { return true; } -#endif } // namespace OCC diff --git a/src/common/utility_win.cpp b/src/common/utility_win.cpp index ad3dfc19dd1..95c40c22f33 100644 --- a/src/common/utility_win.cpp +++ b/src/common/utility_win.cpp @@ -16,16 +16,13 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "asserts.h" +#include "utility_win.h" #include "utility.h" #include +#include #include #include -#include -#include -#include -#include #include #include @@ -52,43 +49,9 @@ const QString systemThemesC() } namespace OCC { - -void Utility::setupFavLink(const QString &folder) +// important: this is mac only, hence the empty impl here +void Utility::setupFavLink(const QString &) { - // First create a Desktop.ini so that the folder and favorite link show our application's icon. - QFile desktopIni(folder + QLatin1String("/Desktop.ini")); - if (desktopIni.exists()) { - qCWarning(lcUtility) << desktopIni.fileName() << "already exists, not overwriting it to set the folder icon."; - } else { - qCInfo(lcUtility) << "Creating" << desktopIni.fileName() << "to set a folder icon in Explorer."; - desktopIni.open(QFile::WriteOnly); - desktopIni.write("[.ShellClassInfo]\r\nIconResource="); - desktopIni.write(QDir::toNativeSeparators(qApp->applicationFilePath()).toUtf8()); - desktopIni.write(",0\r\n"); - desktopIni.close(); - - // Set the folder as system and Desktop.ini as hidden+system for explorer to pick it. - // https://msdn.microsoft.com/en-us/library/windows/desktop/cc144102 - DWORD folderAttrs = GetFileAttributesW((wchar_t *)folder.utf16()); - SetFileAttributesW((wchar_t *)folder.utf16(), folderAttrs | FILE_ATTRIBUTE_SYSTEM); - SetFileAttributesW((wchar_t *)desktopIni.fileName().utf16(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM); - } - - // Windows Explorer: Place under "Favorites" (Links) - QString linkName; - QDir folderDir(QDir::fromNativeSeparators(folder)); - - /* Use new WINAPI functions */ - PWSTR path; - - if (SHGetKnownFolderPath(FOLDERID_Links, 0, NULL, &path) == S_OK) { - QString links = QDir::fromNativeSeparators(QString::fromWCharArray(path)); - linkName = QDir(links).filePath(folderDir.dirName() + QLatin1String(".lnk")); - CoTaskMemFree(path); - } - qCInfo(lcUtility) << "Creating favorite link from" << folder << "to" << linkName; - if (!QFile::link(folder, linkName)) - qCWarning(lcUtility) << "linking" << folder << "to" << linkName << "failed!"; } bool Utility::hasSystemLaunchOnStartup(const QString &appName) @@ -105,7 +68,7 @@ bool Utility::hasLaunchOnStartup(const QString &appName) void Utility::setLaunchOnStartup(const QString &appName, const QString &guiName, bool enable) { - Q_UNUSED(guiName); + Q_UNUSED(guiName) QSettings settings(runPathC(), QSettings::NativeFormat); if (enable) { settings.setValue(appName, QDir::toNativeSeparators(QCoreApplication::applicationFilePath())); @@ -114,186 +77,20 @@ void Utility::setLaunchOnStartup(const QString &appName, const QString &guiName, } } -#ifndef TOKEN_AUTH_ONLY bool Utility::hasDarkSystray() { const QSettings settings(systemThemesC(), QSettings::NativeFormat); return !settings.value(QStringLiteral("SystemUsesLightTheme"), false).toBool(); } -#endif - -QVariant Utility::registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName) -{ - QVariant value; - - HKEY hKey; - - REGSAM sam = KEY_READ | KEY_WOW64_64KEY; - LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast(subKey.utf16()), 0, sam, &hKey); - OC_ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND); - if (result != ERROR_SUCCESS) - return value; - - DWORD type = 0, sizeInBytes = 0; - result = RegQueryValueEx(hKey, reinterpret_cast(valueName.utf16()), 0, &type, nullptr, &sizeInBytes); - OC_ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND); - if (result == ERROR_SUCCESS) { - switch (type) { - case REG_DWORD: - DWORD dword; - Q_ASSERT(sizeInBytes == sizeof(dword)); - if (RegQueryValueEx(hKey, reinterpret_cast(valueName.utf16()), 0, &type, reinterpret_cast(&dword), &sizeInBytes) == ERROR_SUCCESS) { - value = int(dword); - } - break; - case REG_EXPAND_SZ: - case REG_SZ: { - QString string; - string.resize(sizeInBytes / sizeof(QChar)); - result = RegQueryValueEx(hKey, reinterpret_cast(valueName.utf16()), 0, &type, reinterpret_cast(string.data()), &sizeInBytes); - - if (result == ERROR_SUCCESS) { - int newCharSize = sizeInBytes / sizeof(QChar); - // From the doc: - // If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been stored with - // the proper terminating null characters. Therefore, even if the function returns ERROR_SUCCESS, - // the application should ensure that the string is properly terminated before using it; otherwise, it may overwrite a buffer. - if (string.at(newCharSize - 1) == QLatin1Char('\0')) - string.resize(newCharSize - 1); - value = string; - } - break; - } - default: - Q_UNREACHABLE(); - } - } - OC_ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND); - - RegCloseKey(hKey); - return value; -} - -bool Utility::registrySetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName, DWORD type, const QVariant &value) -{ - HKEY hKey; - // KEY_WOW64_64KEY is necessary because CLSIDs are "Redirected and reflected only for CLSIDs that do not specify InprocServer32 or InprocHandler32." - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384253%28v=vs.85%29.aspx#redirected__shared__and_reflected_keys_under_wow64 - // This shouldn't be an issue in our case since we use shell32.dll as InprocServer32, so we could write those registry keys for both 32 and 64bit. - // FIXME: Not doing so at the moment means that explorer will show the cloud provider, but 32bit processes' open dialogs (like the ownCloud client itself) won't show it. - REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY; - LONG result = RegCreateKeyEx(hRootKey, reinterpret_cast(subKey.utf16()), 0, nullptr, 0, sam, nullptr, &hKey, nullptr); - OC_ASSERT(result == ERROR_SUCCESS); - if (result != ERROR_SUCCESS) - return false; - - result = -1; - switch (type) { - case REG_DWORD: { - DWORD dword = value.toInt(); - result = RegSetValueEx(hKey, reinterpret_cast(valueName.utf16()), 0, type, reinterpret_cast(&dword), sizeof(dword)); - break; - } - case REG_EXPAND_SZ: - case REG_SZ: { - QString string = value.toString(); - result = RegSetValueEx(hKey, reinterpret_cast(valueName.utf16()), 0, type, reinterpret_cast(string.constData()), (string.size() + 1) * sizeof(QChar)); - break; - } - default: - Q_UNREACHABLE(); - } - OC_ASSERT(result == ERROR_SUCCESS); - - RegCloseKey(hKey); - return result == ERROR_SUCCESS; -} - -bool Utility::registryDeleteKeyTree(HKEY hRootKey, const QString &subKey) -{ - HKEY hKey; - REGSAM sam = DELETE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_WOW64_64KEY; - LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast(subKey.utf16()), 0, sam, &hKey); - OC_ASSERT(result == ERROR_SUCCESS); - if (result != ERROR_SUCCESS) - return false; - - result = RegDeleteTree(hKey, nullptr); - RegCloseKey(hKey); - OC_ASSERT(result == ERROR_SUCCESS); - - result |= RegDeleteKeyEx(hRootKey, reinterpret_cast(subKey.utf16()), sam, 0); - OC_ASSERT(result == ERROR_SUCCESS); - - return result == ERROR_SUCCESS; -} - -bool Utility::registryDeleteKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName) -{ - HKEY hKey; - REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY; - LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast(subKey.utf16()), 0, sam, &hKey); - OC_ASSERT(result == ERROR_SUCCESS); - if (result != ERROR_SUCCESS) - return false; - - result = RegDeleteValue(hKey, reinterpret_cast(valueName.utf16())); - OC_ASSERT(result == ERROR_SUCCESS); - - RegCloseKey(hKey); - return result == ERROR_SUCCESS; -} - -bool Utility::registryWalkSubKeys(HKEY hRootKey, const QString &subKey, const std::function &callback) -{ - HKEY hKey; - REGSAM sam = KEY_READ | KEY_WOW64_64KEY; - LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast(subKey.utf16()), 0, sam, &hKey); - OC_ASSERT(result == ERROR_SUCCESS); - if (result != ERROR_SUCCESS) - return false; - - DWORD maxSubKeyNameSize; - // Get the largest keyname size once instead of relying each call on ERROR_MORE_DATA. - result = RegQueryInfoKey(hKey, nullptr, nullptr, nullptr, nullptr, &maxSubKeyNameSize, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); - OC_ASSERT(result == ERROR_SUCCESS); - if (result != ERROR_SUCCESS) { - RegCloseKey(hKey); - return false; - } - - QString subKeyName; - subKeyName.reserve(maxSubKeyNameSize + 1); - - DWORD retCode = ERROR_SUCCESS; - for (DWORD i = 0; retCode == ERROR_SUCCESS; ++i) { - Q_ASSERT(unsigned(subKeyName.capacity()) > maxSubKeyNameSize); - // Make the previously reserved capacity official again. - subKeyName.resize(subKeyName.capacity()); - DWORD subKeyNameSize = subKeyName.size(); - retCode = RegEnumKeyEx(hKey, i, reinterpret_cast(subKeyName.data()), &subKeyNameSize, nullptr, nullptr, nullptr, nullptr); - - OC_ASSERT(result == ERROR_SUCCESS || retCode == ERROR_NO_MORE_ITEMS); - if (retCode == ERROR_SUCCESS) { - // subKeyNameSize excludes the trailing \0 - subKeyName.resize(subKeyNameSize); - // Pass only the sub keyname, not the full path. - callback(hKey, subKeyName); - } - } - - RegCloseKey(hKey); - return retCode != ERROR_NO_MORE_ITEMS; -} void Utility::UnixTimeToFiletime(time_t t, FILETIME *filetime) { - LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000; + LONGLONG ll = (t * 10000000LL) + 116444736000000000LL; filetime->dwLowDateTime = (DWORD) ll; filetime->dwHighDateTime = ll >>32; } -void Utility::FiletimeToLargeIntegerFiletime(FILETIME *filetime, LARGE_INTEGER *hundredNSecs) +void Utility::FiletimeToLargeIntegerFiletime(const FILETIME *filetime, LARGE_INTEGER *hundredNSecs) { hundredNSecs->LowPart = filetime->dwLowDateTime; hundredNSecs->HighPart = filetime->dwHighDateTime; @@ -301,9 +98,7 @@ void Utility::FiletimeToLargeIntegerFiletime(FILETIME *filetime, LARGE_INTEGER * void Utility::UnixTimeToLargeIntegerFiletime(time_t t, LARGE_INTEGER *hundredNSecs) { - LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000; - hundredNSecs->LowPart = (DWORD) ll; - hundredNSecs->HighPart = ll >>32; + hundredNSecs->QuadPart = (t * 10000000LL) + 116444736000000000LL; } @@ -312,15 +107,30 @@ QString Utility::formatWinError(long errorCode) return QStringLiteral("WindowsError: %1: %2").arg(QString::number(errorCode, 16), QString::fromWCharArray(_com_error(errorCode).ErrorMessage())); } +Utility::Handle::Handle(HANDLE h, std::function &&close) + : _handle(h) + , _close(std::move(close)) +{ +} + +Utility::Handle::Handle(HANDLE h) + : _handle(h) + , _close(&CloseHandle) +{ +} -Utility::NtfsPermissionLookupRAII::NtfsPermissionLookupRAII() +Utility::Handle::~Handle() { - qt_ntfs_permission_lookup++; + close(); } -Utility::NtfsPermissionLookupRAII::~NtfsPermissionLookupRAII() +void Utility::Handle::close() { - qt_ntfs_permission_lookup--; + if (_handle != INVALID_HANDLE_VALUE) { + _close(_handle); + _handle = INVALID_HANDLE_VALUE; + } } + } // namespace OCC diff --git a/src/common/utility_win.h b/src/common/utility_win.h new file mode 100644 index 00000000000..390c691f840 --- /dev/null +++ b/src/common/utility_win.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "ocsynclib.h" + +#include + +#include +#include + +namespace OCC { +namespace Utility { + class OCSYNC_EXPORT Handle + { + public: + /** + * A RAAI for Windows Handles + */ + Handle() = default; + explicit Handle(HANDLE h); + explicit Handle(HANDLE h, std::function &&close); + + Handle(const Handle &) = delete; + Handle &operator=(const Handle &) = delete; + + Handle(Handle &&other) + { + std::swap(_handle, other._handle); + std::swap(_close, other._close); + } + + Handle &operator=(Handle &&other) + { + if (this != &other) { + std::swap(_handle, other._handle); + std::swap(_close, other._close); + } + return *this; + } + + ~Handle(); + + HANDLE &handle() { return _handle; } + + void close(); + + explicit operator bool() const { return _handle != INVALID_HANDLE_VALUE; } + + operator HANDLE() const { return _handle; } + + private: + HANDLE _handle = INVALID_HANDLE_VALUE; + std::function _close; + }; + + // Possibly refactor to share code with UnixTimevalToFileTime in c_time.c + OCSYNC_EXPORT void UnixTimeToFiletime(time_t t, FILETIME *filetime); + OCSYNC_EXPORT void FiletimeToLargeIntegerFiletime(const FILETIME *filetime, LARGE_INTEGER *hundredNSecs); + OCSYNC_EXPORT void UnixTimeToLargeIntegerFiletime(time_t t, LARGE_INTEGER *hundredNSecs); + + OCSYNC_EXPORT QString formatWinError(long error); +} +} diff --git a/src/common/version-defines.h.in b/src/common/version-defines.h.in new file mode 100644 index 00000000000..8f6f0eb0a2d --- /dev/null +++ b/src/common/version-defines.h.in @@ -0,0 +1,7 @@ +#pragma once + +#define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@ +#define PROJECT_VERSION "@PROJECT_VERSION@" + diff --git a/src/common/version.cpp.in b/src/common/version.cpp.in index 8bfa00e4574..016c7bdf0d4 100644 --- a/src/common/version.cpp.in +++ b/src/common/version.cpp.in @@ -39,3 +39,13 @@ QString OCC::Version::suffix() { return QStringLiteral("@MIRALL_VERSION_SUFFIX@"); } + +QString OCC::Version::displayString() +{ + // ensure a well-defined version string + constexpr std::string_view versionString("@MIRALL_VERSION_STRING@"); + constexpr std::string_view versionSuffix("@MIRALL_VERSION_SUFFIX@"); + static_assert(versionString.find("@MIRALL_VERSION_MAJOR@.@MIRALL_VERSION_MINOR@.@MIRALL_VERSION_PATCH@") == 0); + static_assert(versionString.rfind("@MIRALL_VERSION_SUFFIX@") == versionString.size() - versionSuffix.size()); + return QStringLiteral("@MIRALL_VERSION_STRING@"); +} \ No newline at end of file diff --git a/src/common/version.h b/src/common/version.h index 2d93d31256f..7a8ecf24881 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -18,10 +18,12 @@ #pragma once #include "ocsynclib.h" +#include "version-defines.h" #include #include + namespace OCC::Version { OCSYNC_EXPORT const QVersionNumber &version(); @@ -43,8 +45,5 @@ OCSYNC_EXPORT QString suffix(); */ OCSYNC_EXPORT QString gitSha(); -inline auto displayString() -{ - return QStringLiteral("%1-%2").arg(versionWithBuildNumber().toString(), suffix()); -} +OCSYNC_EXPORT QString displayString(); } diff --git a/src/common/vfs.cpp b/src/common/vfs.cpp index f3c67920f3c..1d759309bda 100644 --- a/src/common/vfs.cpp +++ b/src/common/vfs.cpp @@ -17,58 +17,58 @@ */ #include "vfs.h" +#include "common/filesystembase.h" +#include "common/version.h" #include "plugin.h" -#include "version.h" #include "syncjournaldb.h" -#include "common/filesystembase.h" - +#include #include -#include #include +#include +#ifdef Q_OS_WIN +#include +#endif using namespace OCC; +Q_LOGGING_CATEGORY(lcVfs, "sync.vfs", QtInfoMsg) + + Vfs::Vfs(QObject* parent) : QObject(parent) { } -QString Vfs::underlyingFileName(const QString &fileName) const -{ - return fileName; -} - Vfs::~Vfs() = default; -QString Vfs::modeToString(Mode mode) +Vfs::Mode Vfs::modeFromString(const QString &str) { // Note: Strings are used for config and must be stable - switch (mode) { - case Off: - return QStringLiteral("off"); - case WithSuffix: - return QStringLiteral("suffix"); - case WindowsCfApi: - return QStringLiteral("wincfapi"); + // keep in sync with: QString Utility::enumToString(Vfs::Mode mode) + if (str == QLatin1String("off")) { + return Off; + } else if (str == QLatin1String("wincfapi")) { + return WindowsCfApi; } - return QStringLiteral("off"); + return Off; } -Optional Vfs::modeFromString(const QString &str) +template <> +QString Utility::enumToString(Vfs::Mode mode) { // Note: Strings are used for config and must be stable - if (str == QLatin1String("off")) { - return Off; - } else if (str == QLatin1String("suffix")) { - return WithSuffix; - } else if (str == QLatin1String("wincfapi")) { - return WindowsCfApi; + // keep in sync with: Optional Vfs::modeFromString(const QString &str) + switch (mode) { + case Vfs::Mode::WindowsCfApi: + return QStringLiteral("wincfapi"); + case Vfs::Mode::Off: + default: + return QStringLiteral("off"); } - return {}; } -Result Vfs::checkAvailability(const QString &path, Vfs::Mode mode) +QString Vfs::pathSupportDetail(const QString &path, Vfs::Mode mode) { #ifdef Q_OS_WIN if (mode == Mode::WindowsCfApi) { @@ -94,31 +94,31 @@ Result Vfs::checkAvailability(const QString &path, Vfs::Mode mode void Vfs::start(const VfsSetupParams ¶ms) { - _setupParams = params; - startImpl(params); + _setupParams = std::make_unique(params); + startImpl(this->params()); } bool Vfs::setPinStateInDb(const QString &folderPath, PinState state) { auto path = folderPath.toUtf8(); - _setupParams.journal->internalPinStates().wipeForPathAndBelow(path); + params().journal->internalPinStates().wipeForPathAndBelow(path); if (state != PinState::Inherited) - _setupParams.journal->internalPinStates().setForPath(path, state); + params().journal->internalPinStates().setForPath(path, state); return true; } Optional Vfs::pinStateInDb(const QString &folderPath) { - auto pin = _setupParams.journal->internalPinStates().effectiveForPath(folderPath.toUtf8()); + auto pin = params().journal->internalPinStates().effectiveForPath(folderPath.toUtf8()); return pin; } Vfs::AvailabilityResult Vfs::availabilityInDb(const QString &folderPath) { auto path = folderPath.toUtf8(); - auto pin = _setupParams.journal->internalPinStates().effectiveForPathRecursive(path); + auto pin = params().journal->internalPinStates().effectiveForPathRecursive(path); // not being able to retrieve the pin state isn't too bad - auto hydrationStatus = _setupParams.journal->hasHydratedOrDehydratedFiles(path); + auto hydrationStatus = params().journal->hasHydratedOrDehydratedFiles(path); if (!hydrationStatus) return AvailabilityError::DbError; @@ -138,87 +138,113 @@ Vfs::AvailabilityResult Vfs::availabilityInDb(const QString &folderPath) return AvailabilityError::NoSuchItem; } -VfsOff::VfsOff(QObject *parent) - : Vfs(parent) +void Vfs::wipeDehydratedVirtualFiles() { -} + if (mode() == Vfs::Mode::Off) { + // there are no placeholders + return; + } + _setupParams->journal->getFilesBelowPath(QByteArray(), [&](const SyncJournalFileRecord &rec) { + // only handle dehydrated files + if (rec._type != ItemTypeVirtualFile && rec._type != ItemTypeVirtualFileDownload) { + return; + } + const QString relativePath = QString::fromUtf8(rec._path); + qCDebug(lcVfs) << "Removing db record for dehydrated file" << relativePath; + _setupParams->journal->deleteFileRecord(relativePath); + + // If the local file is a dehydrated placeholder, wipe it too. + // Otherwise, leave it to allow the next sync to have a new-new conflict. + const QString absolutePath = _setupParams->filesystemPath + relativePath; + if (QFile::exists(absolutePath)) { + // according to our db this is a dehydrated file, check it to be sure + if (isDehydratedPlaceholder(absolutePath)) { + qCDebug(lcVfs) << "Removing local dehydrated placeholder" << relativePath; + FileSystem::remove(absolutePath); + } + } + }); -VfsOff::~VfsOff() = default; + _setupParams->journal->forceRemoteDiscoveryNextSync(); -static QString modeToPluginName(Vfs::Mode mode) -{ - if (mode == Vfs::WithSuffix) - return QStringLiteral("suffix"); - if (mode == Vfs::WindowsCfApi) - return QStringLiteral("win"); - return QString(); + // Postcondition: No ItemTypeVirtualFile / ItemTypeVirtualFileDownload left in the db. + // But hydrated placeholders may still be around. } -Q_LOGGING_CATEGORY(lcPlugin, "plugins", QtInfoMsg) +Q_LOGGING_CATEGORY(lcPlugin, "sync.plugins", QtInfoMsg) -bool OCC::isVfsPluginAvailable(Vfs::Mode mode) -{ - // TODO: cache plugins available? - if (mode == Vfs::Off) - return true; - auto name = modeToPluginName(mode); - if (name.isEmpty()) - return false; - auto pluginPath = pluginFileName(QStringLiteral("vfs"), name); - QPluginLoader loader(pluginPath); +OCC::VfsPluginManager *OCC::VfsPluginManager::_instance = nullptr; - auto basemeta = loader.metaData(); - if (basemeta.isEmpty() || !basemeta.contains(QStringLiteral("IID"))) { - qCDebug(lcPlugin) << "Plugin doesn't exist" << loader.fileName(); - return false; - } - if (basemeta[QStringLiteral("IID")].toString() != QLatin1String("org.owncloud.PluginFactory")) { - qCWarning(lcPlugin) << "Plugin has wrong IID" << loader.fileName() << basemeta[QStringLiteral("IID")]; - return false; +bool OCC::VfsPluginManager::isVfsPluginAvailable(Vfs::Mode mode) const +{ + { + auto result = _pluginCache.constFind(mode); + if (result != _pluginCache.cend()) { + return *result; + } } + const bool out = [mode] { + const QString name = Utility::enumToString(mode); + if (!OC_ENSURE_NOT(name.isEmpty())) { + return false; + } + auto pluginPath = pluginFileName(QStringLiteral("vfs"), name); + QPluginLoader loader(pluginPath); - auto metadata = basemeta[QStringLiteral("MetaData")].toObject(); - if (metadata[QStringLiteral("type")].toString() != QLatin1String("vfs")) { - qCWarning(lcPlugin) << "Plugin has wrong type" << loader.fileName() << metadata[QStringLiteral("type")]; - return false; - } - if (metadata[QStringLiteral("version")].toString() != OCC::Version::version().toString()) { - qCWarning(lcPlugin) << "Plugin has wrong version" << loader.fileName() << metadata[QStringLiteral("version")]; - return false; - } + auto baseMetaData = loader.metaData(); + if (baseMetaData.isEmpty() || !baseMetaData.contains(QStringLiteral("IID"))) { + qCDebug(lcPlugin) << "Plugin doesn't exist:" << loader.fileName() << "LibraryPath:" << QCoreApplication::libraryPaths(); + return false; + } + if (baseMetaData[QStringLiteral("IID")].toString() != QLatin1String("org.owncloud.PluginFactory")) { + qCWarning(lcPlugin) << "Plugin has wrong IID" << loader.fileName() << baseMetaData[QStringLiteral("IID")]; + return false; + } - // Attempting to load the plugin is essential as it could have dependencies that - // can't be resolved and thus not be available after all. - if (!loader.load()) { - qCWarning(lcPlugin) << "Plugin failed to load:" << loader.errorString(); - return false; - } + auto metadata = baseMetaData[QStringLiteral("MetaData")].toObject(); + if (metadata[QStringLiteral("type")].toString() != QLatin1String("vfs")) { + qCWarning(lcPlugin) << "Plugin has wrong type" << loader.fileName() << metadata[QStringLiteral("type")]; + return false; + } + if (metadata[QStringLiteral("version")].toString() != OCC::Version::version().toString()) { + qCWarning(lcPlugin) << "Plugin has wrong version" << loader.fileName() << metadata[QStringLiteral("version")]; + return false; + } - return true; + // Attempting to load the plugin is essential as it could have dependencies that + // can't be resolved and thus not be available after all. + if (!loader.load()) { + qCWarning(lcPlugin) << "Plugin failed to load:" << loader.errorString(); + return false; + } + + return true; + }(); + _pluginCache[mode] = out; + return out; } -Vfs::Mode OCC::bestAvailableVfsMode() +Vfs::Mode OCC::VfsPluginManager::bestAvailableVfsMode() const { + // todo: #54 if we keep some theming option to block use of vfs, currently indicated by Theme::showVirtualFilesOption == false, + // we should check that here and return Vfs::off + // we should also take care to block loading the vfs plugin, below, if branding doesn't want to allow using vfs - be really consistent here. if (isVfsPluginAvailable(Vfs::WindowsCfApi)) { return Vfs::WindowsCfApi; - } else if (isVfsPluginAvailable(Vfs::WithSuffix)) { - return Vfs::WithSuffix; } + return Vfs::Off; } -std::unique_ptr OCC::createVfsFromPlugin(Vfs::Mode mode) +Vfs *OCC::VfsPluginManager::createVfsFromPlugin(Vfs::Mode mode, QObject *parent) const { - if (mode == Vfs::Off) - return std::make_unique(); - - auto name = modeToPluginName(mode); + auto name = Utility::enumToString(mode); if (name.isEmpty()) return nullptr; auto pluginPath = pluginFileName(QStringLiteral("vfs"), name); if (!isVfsPluginAvailable(mode)) { - qCCritical(lcPlugin) << "Could not load plugin: not existant or bad metadata" << pluginPath; + qCCritical(lcPlugin) << "Could not load plugin: not existent or bad metadata" << pluginPath; return nullptr; } @@ -235,7 +261,7 @@ std::unique_ptr OCC::createVfsFromPlugin(Vfs::Mode mode) return nullptr; } - auto vfs = std::unique_ptr(qobject_cast(factory->create(nullptr))); + Vfs *vfs = qobject_cast(factory->create(parent)); if (!vfs) { qCCritical(lcPlugin) << "Plugin" << loader.fileName() << "does not create a Vfs instance"; return nullptr; @@ -244,3 +270,23 @@ std::unique_ptr OCC::createVfsFromPlugin(Vfs::Mode mode) qCInfo(lcPlugin) << "Created VFS instance from plugin" << pluginPath; return vfs; } + +const VfsPluginManager &VfsPluginManager::instance() +{ + if (!_instance) { + _instance = new VfsPluginManager(); + } + return *_instance; +} + +VfsSetupParams::VfsSetupParams(Account *account, const QUrl &baseUrl, SyncEngine *syncEngine) + : account(account) + , _baseUrl(baseUrl) + , _syncEngine(syncEngine) +{ +} + +SyncEngine *VfsSetupParams::syncEngine() const +{ + return _syncEngine; +} diff --git a/src/common/vfs.h b/src/common/vfs.h index 7efde8ad768..9bf906410c3 100644 --- a/src/common/vfs.h +++ b/src/common/vfs.h @@ -13,32 +13,37 @@ */ #pragma once -#include -#include -#include -#include - -#include - +// if this is included, to support a qpointer on the account, all hell breaks loose wrt endless "cannot open include file: 'owncloudlib.h' +// No such file or directory" which is fundamental to the libsync exports +// so unfortunately we can't make a qpointer for the account here - do the update described below asap to get rid of the dependency +// #include "libsync/account.h" #include "assert.h" +#include "csync/csync.h" #include "ocsynclib.h" +#include "pinstate.h" #include "result.h" #include "syncfilestatus.h" -#include "pinstate.h" +#include "utility.h" + +#include +#include +#include + +#include -typedef struct csync_file_stat_s csync_file_stat_t; namespace OCC { class Account; -typedef QSharedPointer AccountPtr; + class SyncJournalDb; -class VfsPrivate; class SyncFileItem; +class SyncEngine; /** Collection of parameters for initializing a Vfs instance. */ struct OCSYNC_EXPORT VfsSetupParams { + explicit VfsSetupParams(Account *account, const QUrl &baseUrl, SyncEngine *syncEngine); /** The full path to the folder on the local filesystem * * Always ends with /. @@ -51,8 +56,8 @@ struct OCSYNC_EXPORT VfsSetupParams */ QString remotePath; - /// Account url, credentials etc for network calls - AccountPtr account; + /// Account url, credentials etc. for network calls + Account *account; /** Access to the sync folder's database. * @@ -66,9 +71,25 @@ struct OCSYNC_EXPORT VfsSetupParams QVersionNumber providerVersion; /** when registering with the system we might use - * a different presentaton to identify the accounts + * a different presentation to identify the accounts */ bool multipleAccountsRegistered = false; + + const QUrl &baseUrl() const + { + return _baseUrl; + } + + // sync engine is only used by win vfs impl + SyncEngine *syncEngine() const; + +private: + QUrl _baseUrl; + // this should be a QPointer but when I try to make it so, I get compile errors because std::isConvertible fails + // no freaking idea but I'll figure it out later + // update: nope! due to our "creative" splitting into libs I can't actually include sync engine to make a qpointer to it possible :D + // too bad. See comment on include of account.h above - we have the same problem if we try to include SyncEngine + SyncEngine *_syncEngine; }; /** Interface describing how to deal with virtual/placeholder files. @@ -92,24 +113,27 @@ class OCSYNC_EXPORT Vfs : public QObject * * Currently plugins and modes are one-to-one but that's not required. */ - enum Mode - { - Off, - WithSuffix, - WindowsCfApi, - }; + enum Mode { Off, WindowsCfApi }; Q_ENUM(Mode) enum class ConvertToPlaceholderResult { - Error, Ok, Locked }; Q_ENUM(ConvertToPlaceholderResult) - static QString modeToString(Mode mode); - static Optional modeFromString(const QString &str); + static Mode modeFromString(const QString &str); - static Result checkAvailability(const QString &path, OCC::Vfs::Mode mode); + /** + * @brief pathSupportDetail - needs a better name but the concept is that this function checks to see if the path is supported for vfs. + * if it is not supported the return string contains the "reason" it's not supported. + * @param path to check to for support + * @param mode which we want to check - note that the only arg that makes sense here at all is Vfs::WindowsCfApi + * @return empty string if the mode is supported on the given path. If the string is not empty, it contains a string explaining the + * reason the mode is not supported. + * + * as of this writing, the possible root reasons are: path is a drive, path is on a network drive, path fs is not ntfs. + */ + static QString pathSupportDetail(const QString &path, OCC::Vfs::Mode mode); enum class AvailabilityError { @@ -121,24 +145,20 @@ class OCSYNC_EXPORT Vfs : public QObject using AvailabilityResult = Result; public: - explicit Vfs(QObject* parent = nullptr); + explicit Vfs(QObject *parent); ~Vfs() override; virtual Mode mode() const = 0; - /// For WithSuffix modes: the suffix (including the dot) - virtual QString fileSuffix() const = 0; - - /// The fileName without fileSuffix - /// TODO: better naming welcome - virtual QString underlyingFileName(const QString &fileName) const; - /// Access to the parameters the instance was start()ed with. - const VfsSetupParams ¶ms() const { return _setupParams; } + const VfsSetupParams ¶ms() const { return *_setupParams.get(); } /** Initializes interaction with the VFS provider. * * The plugin-specific work is done in startImpl(). + * + * \warning Please read the comments to \a startImpl() about the + * asynchronous behaviour! */ void start(const VfsSetupParams ¶ms); @@ -156,31 +176,18 @@ class OCSYNC_EXPORT Vfs : public QObject */ virtual bool socketApiPinStateActionsShown() const = 0; - /** Return true when download of a file's data is currently ongoing. - * - * See also the beginHydrating() and doneHydrating() signals. - */ - virtual bool isHydrating() const = 0; - /// Create a new dehydrated placeholder. Called from PropagateDownload. - virtual OC_REQUIRED_RESULT Result createPlaceholder(const SyncFileItem &item) = 0; - - /** Convert a hydrated placeholder to a dehydrated one. Called from PropagateDownlaod. - * - * This is different from delete+create because preserving some file metadata - * (like pin states) may be essential for some vfs plugins. - */ - virtual OC_REQUIRED_RESULT Result dehydratePlaceholder(const SyncFileItem &item) = 0; + [[nodiscard]] virtual Result createPlaceholder(const SyncFileItem &item) = 0; /** Discovery hook: even unchanged files may need UPDATE_METADATA. * * For instance cfapi vfs wants local hydrated non-placeholder files to * become hydrated placeholder files. */ - virtual OC_REQUIRED_RESULT bool needsMetadataUpdate(const SyncFileItem &item) = 0; + [[nodiscard]] virtual bool needsMetadataUpdate(const SyncFileItem &item) = 0; /// Determine whether the file at the given absolute path is a dehydrated placeholder. - virtual OC_REQUIRED_RESULT bool isDehydratedPlaceholder(const QString &filePath) = 0; + [[nodiscard]] virtual bool isDehydratedPlaceholder(const QString &filePath) = 0; /** Similar to isDehydratedPlaceholder() but used from sync discovery. * @@ -189,7 +196,7 @@ class OCSYNC_EXPORT Vfs : public QObject * * Returning true means that type was fully determined. */ - virtual OC_REQUIRED_RESULT bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) = 0; + [[nodiscard]] virtual bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) = 0; /** Sets the pin state for the item at a path. * @@ -198,20 +205,20 @@ class OCSYNC_EXPORT Vfs : public QObject * Usually this would forward to setting the pin state flag in the db table, * but some vfs plugins will store the pin state in file attributes instead. * - * folderPath is relative to the sync folder. Can be "" for root folder. + * relFilePath is relative to the sync folder. Can be "" for root folder. */ - virtual OC_REQUIRED_RESULT bool setPinState(const QString &folderPath, PinState state) = 0; + [[nodiscard]] virtual bool setPinState(const QString &relFilePath, PinState state) = 0; /** Returns the pin state of an item at a path. * * Usually backed by the db's effectivePinState() function but some vfs * plugins will override it to retrieve the state from elsewhere. * - * folderPath is relative to the sync folder. Can be "" for root folder. + * relFilePath is relative to the sync folder. Can be "" for root folder. * * Returns none on retrieval error. */ - virtual OC_REQUIRED_RESULT Optional pinState(const QString &folderPath) = 0; + [[nodiscard]] virtual Optional pinState(const QString &relFilePath) = 0; /** Returns availability status of an item at a path. * @@ -220,37 +227,43 @@ class OCSYNC_EXPORT Vfs : public QObject * * folderPath is relative to the sync folder. Can be "" for root folder. */ - virtual OC_REQUIRED_RESULT AvailabilityResult availability(const QString &folderPath) = 0; + [[nodiscard]] virtual AvailabilityResult availability(const QString &folderPath) = 0; + + /** Removes all virtual file db entries and dehydrated local placeholders. + * + * Particularly useful when switching off vfs mode or switching to a + * different kind of vfs. + */ + void wipeDehydratedVirtualFiles(); -public slots: +public Q_SLOTS: /** Update in-sync state based on SyncFileStatusTracker signal. * * For some vfs plugins the icons aren't based on SocketAPI but rather on data shared * via the vfs plugin. The connection to SyncFileStatusTracker allows both to be based * on the same data. */ - virtual void fileStatusChanged(const QString &systemFileName, SyncFileStatus fileStatus) = 0; + virtual void onFileStatusChanged(const QString &systemFileName, SyncFileStatus fileStatus) = 0; -signals: - /// Emitted when a user-initiated hydration starts - void beginHydrating(); - /// Emitted when the hydration ends - void doneHydrating(); +Q_SIGNALS: /// start complete void started(); /// we encountered an error void error(const QString &error); + /// The vfs plugin detected that the metadata are out of sync and requests a sync with the server + void needSync(); + protected: /** Update placeholder metadata during discovery. * * If the remote metadata changes, the local placeholder's metadata should possibly * change as well. */ - virtual OC_REQUIRED_RESULT Result updateMetadata(const SyncFileItem &item, const QString &filePath, const QString &replacesFile) = 0; + [[nodiscard]] virtual Result updateMetadata(const SyncFileItem &item, const QString &filePath, const QString &replacesFile) = 0; - /** Setup the plugin for the folder. + /** Set up the plugin for the folder. * * For example, the VFS provider might monitor files to be able to start a file * hydration (download of a file's remote contents) when the user wants to open @@ -258,6 +271,16 @@ public slots: * * Usually some registration needs to be done with the backend. This function * should take care of it if necessary. + * + * \note The implementation is allowed to run asynchronously, meaning the method + * can return before the start is fully complete. On completion, the + * \a started() signal is emitted. + * \warning For VFS implementations: the rest of the code in the client assumes + * that it is safe to do multiple calls to this method, EVEN IF A PREVIOUS + * START IS NOT FULLY FINISHED. If ANY synchronization needs to be done + * (e.g. reading/writing to the Windows registry, or searching for + * StorageProvider information), this has to be implemented in the + * plugin itself. */ virtual void startImpl(const VfsSetupParams ¶ms) = 0; @@ -266,57 +289,37 @@ public slots: Optional pinStateInDb(const QString &folderPath); AvailabilityResult availabilityInDb(const QString &folderPath); +private: // the parameters passed to start() - VfsSetupParams _setupParams; + std::unique_ptr _setupParams; friend class OwncloudPropagator; }; -/// Implementation of Vfs for Vfs::Off mode - does nothing -class OCSYNC_EXPORT VfsOff : public Vfs +class OCSYNC_EXPORT VfsPluginManager { - Q_OBJECT - public: - VfsOff(QObject* parent = nullptr); - ~VfsOff() override; - - Mode mode() const override { return Vfs::Off; } - - QString fileSuffix() const override { return QString(); } + /// Check whether the plugin for the mode is available. + bool isVfsPluginAvailable(Vfs::Mode mode) const; - void stop() override {} - void unregisterFolder() override {} + /// Return the best available VFS mode. + Vfs::Mode bestAvailableVfsMode() const; - bool socketApiPinStateActionsShown() const override { return false; } - bool isHydrating() const override { return false; } + /// Create a VFS instance for the mode, returns nullptr on failure. + Vfs *createVfsFromPlugin(Vfs::Mode mode, QObject *parent) const; - Result createPlaceholder(const SyncFileItem &) override { return {}; } - Result dehydratePlaceholder(const SyncFileItem &) override { return {}; } - - bool needsMetadataUpdate(const SyncFileItem &) override { return false; } - bool isDehydratedPlaceholder(const QString &) override { return false; } - bool statTypeVirtualFile(csync_file_stat_t *, void *) override { return false; } - - bool setPinState(const QString &, PinState) override { return true; } - Optional pinState(const QString &) override { return PinState::AlwaysLocal; } - AvailabilityResult availability(const QString &) override { return VfsItemAvailability::AlwaysLocal; } - -public slots: - void fileStatusChanged(const QString &, SyncFileStatus) override {} + static const VfsPluginManager &instance(); protected: - Result updateMetadata(const SyncFileItem &, const QString &, const QString &) override { return { ConvertToPlaceholderResult::Ok }; } - void startImpl(const VfsSetupParams &) override { Q_EMIT started(); } -}; + VfsPluginManager() = default; -/// Check whether the plugin for the mode is available. -OCSYNC_EXPORT bool isVfsPluginAvailable(Vfs::Mode mode); +private: + static VfsPluginManager *_instance; -/// Return the best available VFS mode. -OCSYNC_EXPORT Vfs::Mode bestAvailableVfsMode(); + mutable QMap _pluginCache; +}; -/// Create a VFS instance for the mode, returns nullptr on failure. -OCSYNC_EXPORT std::unique_ptr createVfsFromPlugin(Vfs::Mode mode); +template <> +OCSYNC_EXPORT QString Utility::enumToString(Vfs::Mode mode); } // namespace OCC diff --git a/src/crashreporter/CMakeLists.txt b/src/crashreporter/CMakeLists.txt index b0fd2bf1c04..8af1ef26a8d 100644 --- a/src/crashreporter/CMakeLists.txt +++ b/src/crashreporter/CMakeLists.txt @@ -32,32 +32,30 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CrashReporterConfig.h.in list(APPEND crashreporter_SOURCES main.cpp) list(APPEND crashreporter_RC "${CMAKE_CURRENT_BINARY_DIR}/resources.qrc") - -if(NOT BUILD_LIBRARIES_ONLY) - add_executable(${CRASHREPORTER_EXECUTABLE} - ${crashreporter_SOURCES} - ${crashreporter_HEADERS_MOC} - ${crashreporter_UI_HEADERS} - ${crashreporter_RC} - ) - - # This is a GUI Application without its own bundle - set_target_properties(${CRASHREPORTER_EXECUTABLE} - PROPERTIES - MACOSX_BUNDLE FALSE - ) - - find_package(Qt5 REQUIRED COMPONENTS Widgets) - - target_include_directories(${CRASHREPORTER_EXECUTABLE} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) - target_link_libraries(${CRASHREPORTER_EXECUTABLE} - crashreporter-gui - Qt5::Core Qt5::Widgets - ) - - if(APPLE) - install(TARGETS ${CRASHREPORTER_EXECUTABLE} RUNTIME DESTINATION "${KDE_INSTALL_BUNDLEDIR}/${APPLICATION_EXECUTABLE}.app/Contents/MacOS/") - else() - install(TARGETS ${CRASHREPORTER_EXECUTABLE} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) - endif() +add_executable(${CRASHREPORTER_EXECUTABLE} + ${crashreporter_SOURCES} + ${crashreporter_HEADERS_MOC} + ${crashreporter_UI_HEADERS} + ${crashreporter_RC} +) +apply_common_target_settings(${CRASHREPORTER_EXECUTABLE}) + +# This is a GUI Application without its own bundle +set_target_properties(${CRASHREPORTER_EXECUTABLE} + PROPERTIES + MACOSX_BUNDLE FALSE +) + +target_include_directories(${CRASHREPORTER_EXECUTABLE} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries(${CRASHREPORTER_EXECUTABLE} + PRIVATE + CrashReporterQt::Gui + Qt::Core + Qt::Widgets +) + +if(APPLE) + set_target_properties(${CRASHREPORTER_EXECUTABLE} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "$") +else() + install(TARGETS ${CRASHREPORTER_EXECUTABLE} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) endif() diff --git a/src/crashreporter/CrashReporterConfig.h.in b/src/crashreporter/CrashReporterConfig.h.in index d17edc7c688..597e86e6f29 100644 --- a/src/crashreporter/CrashReporterConfig.h.in +++ b/src/crashreporter/CrashReporterConfig.h.in @@ -5,7 +5,7 @@ #define CRASHREPORTER_RELEASE_CHANNEL "@CRASHREPORTER_RELEASE_CHANNEL@" -#define CRASHREPORTER_PRODUCT_NAME "@APPLICATION_NAME@" +#define CRASHREPORTER_PRODUCT_NAME "@APPLICATION_SHORTNAME@" #define CRASHREPORTER_VERSION_STRING "@MIRALL_VERSION_STRING@" diff --git a/src/crashreporter/main.cpp b/src/crashreporter/main.cpp index 9d057e05a88..911811a18db 100644 --- a/src/crashreporter/main.cpp +++ b/src/crashreporter/main.cpp @@ -24,20 +24,10 @@ int main(int argc, char *argv[]) { - QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); -#ifdef Q_OS_WIN - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); -#endif // !Q_OS_WIN QApplication app(argc, argv); #ifdef Q_OS_WIN - // The Windows style still has pixelated elements with Qt 5.6, - // it's recommended to use the Fusion style in this case, even - // though it looks slightly less native. Check here after the - // QApplication was constructed, but before any QWidget is - // constructed. - if (app.devicePixelRatio() > 1) - QApplication::setStyle(QStringLiteral("fusion")); + QApplication::setStyle(QStringLiteral("fusion")); #endif // Q_OS_WIN if (app.arguments().size() != 2) { @@ -46,19 +36,22 @@ int main(int argc, char *argv[]) } // TODO: install socorro .... - CrashReporter reporter(QUrl(CRASHREPORTER_SUBMIT_URL), app.arguments()); + CrashReporter reporter(QUrl(QStringLiteral(CRASHREPORTER_SUBMIT_URL)), app.arguments()); #ifdef CRASHREPORTER_ICON - reporter.setLogo(QPixmap(CRASHREPORTER_ICON)); + reporter.setLogo(QPixmap(QStringLiteral(CRASHREPORTER_ICON))); #endif - reporter.setWindowTitle(CRASHREPORTER_PRODUCT_NAME); - reporter.setText("

Sorry! " CRASHREPORTER_PRODUCT_NAME " crashed. Please tell us about it! " CRASHREPORTER_PRODUCT_NAME " has created an error report for you that can help improve the stability in the future. You can now send this report directly to the " CRASHREPORTER_PRODUCT_NAME " developers.

"); + reporter.setWindowTitle(QStringLiteral(CRASHREPORTER_PRODUCT_NAME)); + reporter.setText(QStringLiteral("

Sorry! " CRASHREPORTER_PRODUCT_NAME + " crashed. Please tell us about it! " CRASHREPORTER_PRODUCT_NAME + " has created an error report for you that can help improve the stability in the future. You can now send this report " + "directly to the " CRASHREPORTER_PRODUCT_NAME " developers.

")); const QFileInfo crashLog(QDir::tempPath() + QStringLiteral("/" CRASHREPORTER_PRODUCT_NAME "-crash.log")); if (crashLog.exists()) { QFile inFile(crashLog.filePath()); if (inFile.open(QFile::ReadOnly)) { - reporter.setComment(inFile.readAll()); + reporter.setComment(QString::fromUtf8(inFile.readAll())); } } diff --git a/src/csync/CMakeLists.txt b/src/csync/CMakeLists.txt index 218a2a7c028..aea5015f35e 100644 --- a/src/csync/CMakeLists.txt +++ b/src/csync/CMakeLists.txt @@ -1,25 +1,8 @@ -add_definitions(-DQT_NO_CAST_TO_ASCII - -DQT_NO_CAST_FROM_ASCII - -DQT_NO_URL_CAST_FROM_STRING - -DQT_NO_CAST_FROM_BYTEARRAY) - # global needed variables -set(APPLICATION_NAME "ocsync") - -set(LIBRARY_VERSION ${MIRALL_VERSION}) -set(LIBRARY_SOVERSION "0") - -# add definitions -option(MEM_NULL_TESTS "Enable NULL memory testing" OFF) - include(ConfigureChecks.cmake) include(../common/common.cmake) -if (MEM_NULL_TESTS) - add_definitions(-DCSYNC_MEM_NULL_TESTS) -endif (MEM_NULL_TESTS) - set(csync_SRCS csync.cpp csync_exclude.cpp @@ -41,41 +24,72 @@ endif() configure_file(csync_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/csync_version.h) add_library(csync SHARED ${common_SOURCES} ${csync_SRCS}) - -target_include_directories( - csync - PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/std +set_target_properties(csync PROPERTIES EXPORT_NAME CSync) + +target_include_directories(csync + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/std + PUBLIC + # include root source dir in order to make src/common available to the targets + $ + $ ) +apply_common_target_settings(csync) + +if(NO_MSG_HANDLER) + target_compile_definitions(csync PUBLIC -DNO_MSG_HANDLER=1) +endif() + +if(MINGWS) + target_compile_definitions(csync PUBLIC __USE_MINGW_ANSI_STDIO=1) +endif() + generate_export_header(csync EXPORT_MACRO_NAME OCSYNC_EXPORT EXPORT_FILE_NAME ocsynclib.h ) target_link_libraries(csync - ${CSYNC_REQUIRED_LIBRARIES} - SQLite::SQLite3 - ZLIB::ZLIB - Qt5::Core Qt5::Concurrent + PUBLIC + Qt::Core + PRIVATE + Qt::Concurrent + SQLite::SQLite3 + ZLIB::ZLIB ) # For src/common/utility_mac.cpp if (APPLE) find_library(FOUNDATION_LIBRARY NAMES Foundation) find_library(CORESERVICES_LIBRARY NAMES CoreServices) - target_link_libraries(csync ${FOUNDATION_LIBRARY} ${CORESERVICES_LIBRARY}) + target_link_libraries(csync + PRIVATE + ${FOUNDATION_LIBRARY} ${CORESERVICES_LIBRARY}) endif() set_target_properties( csync PROPERTIES OUTPUT_NAME - "${APPLICATION_EXECUTABLE}_csync" + "ownCloudCsync" VERSION - ${LIBRARY_VERSION} + ${MIRALL_VERSION} SOVERSION - ${LIBRARY_SOVERSION} + "0" ) -INSTALL(TARGETS csync ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) + +INSTALL( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/ocsynclib.h + ../common/filesystembase.h + ../common/utility.h + DESTINATION + ${KDE_INSTALL_INCLUDEDIR}/${APPLICATION_SHORTNAME}/common +) + +INSTALL(TARGETS csync EXPORT ${APPLICATION_SHORTNAME}Config ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) configure_file(config_csync.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_csync.h) + +configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) diff --git a/src/csync/ChangeLog b/src/csync/ChangeLog index a95a0758916..6acb7dfcfc0 100644 --- a/src/csync/ChangeLog +++ b/src/csync/ChangeLog @@ -83,7 +83,7 @@ version 0.90.0 (released 2013-09-04, ownCloud Client 1.4.0) * Many fixes found by a Coverity check. * Fix: use correct stat struct on all platforms * Fix: download resuming. - * ownCloud module: Bandwith limitation added. + * ownCloud module: Bandwidth limitation added. * Added ability to remove ignored files automatically. * Fix: Use int64_t and friends * Fix: Removed all compile warnings. @@ -210,7 +210,7 @@ version 0.50.9 (first beta version, released 2012-08-30) version 0.50.8 (released 2012-08-10) * Inode equivalent support for Win32 platforms to support rename - * ownCloud supports propagates renames from local to webdav MOVE + * ownCloud supports propagates renames from local to WebDAV MOVE * ownCloud module works with proxy, settings from mirall. * improved CMake modules (openSSL) * Fixed namespace for lastmodified propset. diff --git a/src/csync/ConfigureChecks.cmake b/src/csync/ConfigureChecks.cmake index 77f4c164275..32c5ed5e6fd 100644 --- a/src/csync/ConfigureChecks.cmake +++ b/src/csync/ConfigureChecks.cmake @@ -1,32 +1,13 @@ -include(CheckIncludeFile) -include(CheckSymbolExists) include(CheckFunctionExists) include(CheckLibraryExists) -include(CheckTypeSize) -include(CheckCXXSourceCompiles) -set(PACKAGE ${APPLICATION_NAME}) -set(VERSION ${APPLICATION_VERSION}) -set(DATADIR ${DATA_INSTALL_DIR}) -set(LIBDIR ${LIB_INSTALL_DIR}) -set(SYSCONFDIR ${SYSCONF_INSTALL_DIR}) - -# HEADER FILES -check_include_file(argp.h HAVE_ARGP_H) +set(SYSCONFDIR ${CMAKE_INSTALL_SYSCONFDIR}) # FUNCTIONS if (NOT LINUX) # librt check_library_exists(rt nanosleep "" HAVE_LIBRT) - - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ) endif (NOT LINUX) -if(WIN32) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32) -endif() - check_function_exists(utimes HAVE_UTIMES) check_function_exists(lstat HAVE_LSTAT) - -set(CSYNC_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} CACHE INTERNAL "csync required system libraries") diff --git a/src/csync/INSTALL b/src/csync/INSTALL index caf65c2bdc9..1fef7c0043f 100644 --- a/src/csync/INSTALL +++ b/src/csync/INSTALL @@ -28,7 +28,7 @@ build and run csync successfully with an older version, please let us know. First, you need to configure the compilation, using CMake. Go inside the `build` dir. Create it if it doesn't exist. -GNU/Linux and MacOS X: +GNU/Linux and macOS X: cmake -DCMAKE_BUILD_TYPE=Debug .. make @@ -38,7 +38,7 @@ Here is a list of the most interesting options provided out of the box by CMake. - CMAKE_BUILD_TYPE: The type of build (can be Debug Release MinSizeRel RelWithDebInfo) - CMAKE_INSTALL_PREFIX: The prefix to use when running make install (Default to - /usr/local on GNU/Linux and MacOS X) + /usr/local on GNU/Linux and macOS X) - CMAKE_C_COMPILER: The path to the C compiler - CMAKE_CXX_COMPILER: The path to the C++ compiler @@ -59,7 +59,7 @@ CMake options using `cmakesetup` (Windows) or `ccmake` (GNU/Linux and MacOS X). - Go to the build dir - On Windows: run `cmakesetup` -- On GNU/Linux and MacOS X: run `ccmake ..` +- On GNU/Linux and macOS X: run `ccmake ..` ## Installing diff --git a/src/csync/README b/src/csync/README index 2487e24159a..4093cbf5363 100644 --- a/src/csync/README +++ b/src/csync/README @@ -25,7 +25,7 @@ http://git.csync.org/ DOCUMENTATION ============= -As a user you can find a userguide which is shipped with this package or is +As a user you can find a user-guide which is shipped with this package or is available at the website. For developers there is doxygen documentation and comments in the source code itself. See diff --git a/src/csync/config.h.in b/src/csync/config.h.in new file mode 100644 index 00000000000..1346780ec2e --- /dev/null +++ b/src/csync/config.h.in @@ -0,0 +1,23 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#cmakedefine USE_INOTIFY 1 +#cmakedefine WITH_QTKEYCHAIN 1 +#cmakedefine WITH_CRASHREPORTER +#cmakedefine CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@" + +#cmakedefine APPLICATION_DOMAIN "@APPLICATION_DOMAIN@" +#cmakedefine THEME_CLASS @THEME_CLASS@ +#cmakedefine THEME_INCLUDE "@THEME_INCLUDE@" + +#cmakedefine APPLICATION_NAME "@APPLICATION_NAME@" +#cmakedefine APPLICATION_VENDOR "@APPLICATION_VENDOR@" +#cmakedefine APPLICATION_REV_DOMAIN "@APPLICATION_REV_DOMAIN@" +#cmakedefine APPLICATION_SHORTNAME "@APPLICATION_SHORTNAME@" +#cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@" +#cmakedefine APPLICATION_UPDATE_URL "@APPLICATION_UPDATE_URL@" +#cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@" + +#cmakedefine SYSCONFDIR "@SYSCONFDIR@" + +#endif diff --git a/src/csync/config_csync.h.cmake b/src/csync/config_csync.h.cmake index ca505f7f527..44bbae56754 100644 --- a/src/csync/config_csync.h.cmake +++ b/src/csync/config_csync.h.cmake @@ -1,13 +1,5 @@ #include "ocsynclib.h" -#cmakedefine PACKAGE "${APPLICATION_NAME}" -#cmakedefine VERSION "${APPLICATION_VERSION}" -#cmakedefine LOCALEDIR "${LOCALE_INSTALL_DIR}" -#cmakedefine LIBDIR "${LIBDIR}" #cmakedefine SYSCONFDIR "${SYSCONFDIR}" -#cmakedefine HAVE_ARGP_H 1 - #cmakedefine HAVE_UTIMES 1 #cmakedefine HAVE_LSTAT 1 - - diff --git a/src/csync/csync.cpp b/src/csync/csync.cpp index 3a95f059211..2cd346655a8 100644 --- a/src/csync/csync.cpp +++ b/src/csync/csync.cpp @@ -19,7 +19,6 @@ */ #include "csync.h" -#include "moc_csync.cpp" #include diff --git a/src/csync/csync.h b/src/csync/csync.h index a07fe0673bd..1c1134c79ef 100644 --- a/src/csync/csync.h +++ b/src/csync/csync.h @@ -32,7 +32,6 @@ #ifndef _CSYNC_H #define _CSYNC_H -#include "std/c_private.h" #include "ocsynclib.h" #include @@ -49,54 +48,44 @@ namespace OCC { class SyncJournalFileRecord; } -#if defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && !defined(Q_CC_CLANG) && (__GNUC__ * 100 + __GNUC_MINOR__ < 408) -// openSuse 12.3 didn't like enum bitfields. -#define BITFIELD(size) -#elif defined(Q_CC_MSVC) -// MSVC stores enum and bool as signed, so we need to add a bit for the sign -#define BITFIELD(size) :(size+1) -#else -#define BITFIELD(size) :size -#endif - namespace CSyncEnums { OCSYNC_EXPORT Q_NAMESPACE -enum csync_status_codes_e { - CSYNC_STATUS_OK = 0, - - CSYNC_STATUS_ERROR = 1024, /* don't use this code, - */ - CSYNC_STATUS_UNSUCCESSFUL, /* Unspecific problem happend */ - CSYNC_STATUS_STATEDB_LOAD_ERROR, /* Statedb can not be loaded. */ - CSYNC_STATUS_UPDATE_ERROR, /* general update or discovery error */ - CSYNC_STATUS_TIMEOUT, /* UNUSED */ - CSYNC_STATUS_HTTP_ERROR, /* UNUSED */ - CSYNC_STATUS_PERMISSION_DENIED, /* */ - CSYNC_STATUS_NOT_FOUND, - CSYNC_STATUS_FILE_EXISTS, - CSYNC_STATUS_OUT_OF_SPACE, - CSYNC_STATUS_SERVICE_UNAVAILABLE, - CSYNC_STATUS_STORAGE_UNAVAILABLE, - CSYNC_STATUS_FILE_SIZE_ERROR, - CSYNC_STATUS_OPENDIR_ERROR, - CSYNC_STATUS_READDIR_ERROR, - CSYNC_STATUS_OPEN_ERROR, - CSYNC_STATUS_ABORTED, - /* Codes for file individual status: */ - CSYNC_STATUS_INDIVIDUAL_IS_SYMLINK, - CSYNC_STATUS_INDIVIDUAL_IGNORE_LIST, - CSYNC_STATUS_INDIVIDUAL_IS_INVALID_CHARS, - CSYNC_STATUS_INDIVIDUAL_TRAILING_SPACE, - CSYNC_STATUS_INDIVIDUAL_EXCLUDE_LONG_FILENAME, - CSYNC_STATUS_INDIVIDUAL_EXCLUDE_HIDDEN, - CSYNC_STATUS_INVALID_CHARACTERS, - CSYNC_STATUS_INDIVIDUAL_STAT_FAILED, - CSYNC_STATUS_FORBIDDEN, - CSYNC_STATUS_INDIVIDUAL_TOO_DEEP, - CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE, - CSYNC_STATUS_INDIVIDUAL_CANNOT_ENCODE -}; + enum csync_status_codes_e : uint16_t { + CSYNC_STATUS_OK = 0, + + CSYNC_STATUS_ERROR = 1024, /* don't use this code, + */ + CSYNC_STATUS_UNSUCCESSFUL, /* Unspecific problem happend */ + CSYNC_STATUS_STATEDB_LOAD_ERROR, /* Statedb can not be loaded. */ + CSYNC_STATUS_UPDATE_ERROR, /* general update or discovery error */ + CSYNC_STATUS_TIMEOUT, /* UNUSED */ + CSYNC_STATUS_HTTP_ERROR, /* UNUSED */ + CSYNC_STATUS_PERMISSION_DENIED, /* */ + CSYNC_STATUS_NOT_FOUND, + CSYNC_STATUS_FILE_EXISTS, + CSYNC_STATUS_OUT_OF_SPACE, + CSYNC_STATUS_SERVICE_UNAVAILABLE, + CSYNC_STATUS_STORAGE_UNAVAILABLE, + CSYNC_STATUS_FILE_SIZE_ERROR, + CSYNC_STATUS_OPENDIR_ERROR, + CSYNC_STATUS_READDIR_ERROR, + CSYNC_STATUS_OPEN_ERROR, + CSYNC_STATUS_ABORTED, + /* Codes for file individual status: */ + CSYNC_STATUS_INDIVIDUAL_IS_SYMLINK, + CSYNC_STATUS_INDIVIDUAL_IGNORE_LIST, + CSYNC_STATUS_INDIVIDUAL_IS_INVALID_CHARS, + CSYNC_STATUS_INDIVIDUAL_TRAILING_SPACE, + CSYNC_STATUS_INDIVIDUAL_EXCLUDE_LONG_FILENAME, + CSYNC_STATUS_INDIVIDUAL_EXCLUDE_HIDDEN, + CSYNC_STATUS_INVALID_CHARACTERS, + CSYNC_STATUS_INDIVIDUAL_STAT_FAILED, + CSYNC_STATUS_FORBIDDEN, + CSYNC_STATUS_INDIVIDUAL_TOO_DEEP, + CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE, + CSYNC_STATUS_INDIVIDUAL_CANNOT_ENCODE + }; Q_ENUM_NS(csync_status_codes_e) /** @@ -104,25 +93,22 @@ Q_ENUM_NS(csync_status_codes_e) * the csync state of a file. */ // clang-format off -enum SyncInstruction { - CSYNC_INSTRUCTION_NONE = 0, /* Nothing to do (UPDATE|RECONCILE) */ - CSYNC_INSTRUCTION_REMOVE = 1 << 1, /* The file need to be removed (RECONCILE) */ - CSYNC_INSTRUCTION_RENAME = 1 << 2, /* The file need to be renamed (RECONCILE) */ - CSYNC_INSTRUCTION_NEW = 1 << 3, /* The file is new compared to the db (UPDATE) */ - CSYNC_INSTRUCTION_CONFLICT = 1 << 4, /* The file need to be downloaded because it is a conflict (RECONCILE) */ - CSYNC_INSTRUCTION_IGNORE = 1 << 5, /* The file is ignored (UPDATE|RECONCILE) */ - CSYNC_INSTRUCTION_SYNC = 1 << 6, /* The file need to be pushed to the other remote (RECONCILE) */ +enum SyncInstruction : uint16_t { + CSYNC_INSTRUCTION_NONE = 1 << 1, /* Nothing to do (UPDATE|RECONCILE) */ + CSYNC_INSTRUCTION_REMOVE = 1 << 2, /* The file need to be removed (RECONCILE) */ + CSYNC_INSTRUCTION_RENAME = 1 << 3, /* The file need to be renamed (RECONCILE) */ + CSYNC_INSTRUCTION_NEW = 1 << 4, /* The file is new compared to the db (UPDATE) */ + CSYNC_INSTRUCTION_CONFLICT = 1 << 5, /* The file need to be downloaded because it is a conflict (RECONCILE) */ + CSYNC_INSTRUCTION_IGNORE = 1 << 6, /* The file is ignored (UPDATE|RECONCILE) + * Identical to CSYNC_INSTRUCTION_NONE but logged to the user. + */ + CSYNC_INSTRUCTION_SYNC = 1 << 7, /* The file need to be pushed to the other remote (RECONCILE) */ CSYNC_INSTRUCTION_ERROR = 1 << 8, CSYNC_INSTRUCTION_TYPE_CHANGE = 1 << 9, /* Like NEW, but deletes the old entity first (RECONCILE) Used when the type of something changes from directory to file or back. */ CSYNC_INSTRUCTION_UPDATE_METADATA = 1 << 10, /* If the etag has been updated and need to be writen to the db, but without any propagation (UPDATE|RECONCILE) */ - - // TODO: - CSYNC_INSTRUCTION_EVAL_RENAME = 1 << 11, // DEPRECATED REMOVED - CSYNC_INSTRUCTION_STAT_ERROR = 1 << 7, // DEPRECATED REMOVED - CSYNC_INSTRUCTION_EVAL = 1 << 0, // DEPRECATED REMOVED }; // clang-format on Q_FLAG_NS(SyncInstruction) @@ -131,7 +117,7 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(SyncInstructions) // This enum is used with BITFIELD(3) and BITFIELD(4) in several places. // Also, this value is stored in the database, so beware of value changes. -enum ItemType { +enum ItemType : uint8_t { ItemTypeFile = 0, ItemTypeSoftLink = 1, ItemTypeDirectory = 2, @@ -169,49 +155,17 @@ Q_ENUM_NS(ItemType) } using namespace CSyncEnums; -using CSYNC_STATUS = CSyncEnums::csync_status_codes_e; -typedef struct csync_file_stat_s csync_file_stat_t; - -struct OCSYNC_EXPORT csync_file_stat_s { - time_t modtime; - int64_t size; - uint64_t inode; - - OCC::RemotePermissions remotePerm; - ItemType type BITFIELD(4); - bool child_modified BITFIELD(1); - bool has_ignored_files BITFIELD(1); // Specify that a directory, or child directory contains ignored files. - bool is_hidden BITFIELD(1); // Not saved in the DB, only used during discovery for local files. - - QByteArray path; - QByteArray rename_path; - QByteArray etag; - QByteArray file_id; - QByteArray directDownloadUrl; - QByteArray directDownloadCookies; - QByteArray original_path; // only set if locale conversion fails - - // In the local tree, this can hold a checksum and its type if it is - // computed during discovery for some reason. - // In the remote tree, this will have the server checksum, if available. - // In both cases, the format is "SHA1:baff". - QByteArray checksumHeader; - - CSYNC_STATUS error_status; - - SyncInstructions instruction; /* u32 */ - - csync_file_stat_s() - : modtime(0) - , size(0) - , inode(0) - , type(ItemTypeSkip) - , child_modified(false) - , has_ignored_files(false) - , is_hidden(false) - , error_status(CSYNC_STATUS_OK) - , instruction(CSYNC_INSTRUCTION_NONE) - { } + +struct OCSYNC_EXPORT csync_file_stat_t +{ + time_t modtime = 0; + int64_t size = 0; + uint64_t inode = 0; + + ItemType type = ItemTypeSkip; + bool is_hidden = false; // Not saved in the DB, only used during discovery for local files. + + QString path; }; OCSYNC_EXPORT QDebug operator<<(QDebug debug, const SyncInstructions &job); diff --git a/src/csync/csync_exclude.cpp b/src/csync/csync_exclude.cpp index fd4117ae596..e7f7d7fb138 100644 --- a/src/csync/csync_exclude.cpp +++ b/src/csync/csync_exclude.cpp @@ -19,25 +19,19 @@ */ #include "config.h" -#include "config_csync.h" #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif - -#include "c_lib.h" -#include "c_private.h" - #include "csync_exclude.h" #include "common/filesystembase.h" #include "common/utility.h" #include "common/version.h" -#include -#include #include -#include +#include +#include namespace { @@ -101,10 +95,10 @@ OCSYNC_EXPORT void csync_exclude_expand_escapes(QByteArray &input) /** * @brief Checks if filename is considered reserved by Windows - * @param file_name filename + * @param filename filename * @return true if file is reserved, false otherwise */ -OCSYNC_EXPORT bool csync_is_windows_reserved_word(const QStringRef &filename) +OCSYNC_EXPORT bool csync_is_windows_reserved_word(QStringView filename) { size_t len_filename = filename.size(); @@ -126,7 +120,7 @@ OCSYNC_EXPORT bool csync_is_windows_reserved_word(const QStringRef &filename) if (static_cast(word.size()) > len_filename) { break; } - // until windows 11, not only the device names where illegal file names + // until Windows 11, not only the device names where illegal file names // also COM9.png was illegal if ((static_cast(word.size()) == len_filename || filename.at(word.size()) == QLatin1Char('.')) && filename.startsWith(word, Qt::CaseInsensitive)) { return true; @@ -147,10 +141,10 @@ OCSYNC_EXPORT bool csync_is_windows_reserved_word(const QStringRef &filename) return false; } -static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QStringRef &path, bool excludeConflictFiles) +static CSYNC_EXCLUDE_TYPE _csync_excluded_common(QStringView path) { /* split up the path */ - QStringRef bname(path); + QStringView bname(path); int lastSlash = path.lastIndexOf(QLatin1Char('/')); if (lastSlash >= 0) { bname = path.mid(lastSlash + 1); @@ -171,10 +165,6 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QStringRef &path, bool ex } } - if (bname.endsWith(QLatin1String(APPLICATION_DOTVIRTUALFILE_SUFFIX), Qt::CaseInsensitive)) { // ".owncloud" placeholder - return CSYNC_FILE_EXCLUDE_RESERVED; - } - // check the strlen and ignore the file if its name is longer than 254 chars. // whenever changing this also check createDownloadTmpFileName if (blen > 254) { @@ -200,23 +190,16 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QStringRef &path, bool ex // Filter out characters not allowed in a filename on windows // see https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file - for (auto p : path) { - ushort c = p.unicode(); - if (c < 32) { + for (auto c : path) { + if (c.unicode() < 32) { return CSYNC_FILE_EXCLUDE_INVALID_CHAR; } - switch (c) { - case '\\': - case ':': - case '?': - case '*': - case '"': - case '>': - case '<': - case '|': + if (std::find_if( + OCC::FileSystem::IllegalFilenameCharsWindows.begin(), OCC::FileSystem::IllegalFilenameCharsWindows.end(), [c](const auto illegal) { + return c == illegal; + }) + != OCC::FileSystem::IllegalFilenameCharsWindows.end()) { return CSYNC_FILE_EXCLUDE_INVALID_CHAR; - default: - break; } } #endif @@ -228,7 +211,7 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QStringRef &path, bool ex } - if (excludeConflictFiles && OCC::Utility::isConflictFile(path)) { + if (OCC::Utility::isConflictFile(path)) { return CSYNC_FILE_EXCLUDE_CONFLICT; } return CSYNC_NOT_EXCLUDED; @@ -237,8 +220,9 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const QStringRef &path, bool ex using namespace OCC; -ExcludedFiles::ExcludedFiles() - : _clientVersion(OCC::Version::version()) +ExcludedFiles::ExcludedFiles(QObject *parent) + : QObject(parent) + , _clientVersion(OCC::Version::version()) { // Windows used to use PathMatchSpec which allows *foo to match abc/deffoo. _wildcardsMatchSlash = Utility::isWindows(); @@ -253,11 +237,6 @@ void ExcludedFiles::addExcludeFilePath(const QString &path) _excludeFiles.insert(path); } -void ExcludedFiles::setExcludeConflictFiles(bool onoff) -{ - _excludeConflictFiles = onoff; -} - void ExcludedFiles::addManualExclude(const QString &expr) { _manualExcludes.append(expr); @@ -277,7 +256,7 @@ void ExcludedFiles::setWildcardsMatchSlash(bool onoff) prepare(); } -void ExcludedFiles::setClientVersion(QVersionNumber version) +void ExcludedFiles::setClientVersion(const QVersionNumber &version) { _clientVersion = version; } @@ -286,7 +265,7 @@ bool ExcludedFiles::reloadExcludeFiles() { _allExcludes.clear(); bool success = true; - for (const auto &file : qAsConst(_excludeFiles)) { + for (const auto &file : std::as_const(_excludeFiles)) { QFile f(file); if (!f.open(QIODevice::ReadOnly)) { success = false; @@ -335,18 +314,19 @@ bool ExcludedFiles::versionDirectiveKeepNextLine(const QByteArray &directive) co return true; } -bool ExcludedFiles::isExcluded( - const QString &filePath, - const QString &basePath, - bool excludeHidden) const +bool ExcludedFiles::isExcluded(QStringView filePath, QStringView basePath, bool excludeHidden) const { if (!FileSystem::isChildPathOf(filePath, basePath)) { // Mark paths we're not responsible for as excluded... return true; } - const QFileInfo fileInfo(filePath); + const QFileInfo fileInfo(filePath.toString()); if (!fileInfo.exists()) { + if (excludeHidden && fileInfo.fileName().startsWith(QLatin1Char('.'))) { + return true; + } + // recursively call isExcluded with the path of non-existing file's parent directory return isExcluded(fileInfo.path(), basePath, excludeHidden); } @@ -360,7 +340,7 @@ bool ExcludedFiles::isExcluded( } // Get the parent path - fi = { fi.path() }; + fi = QFileInfo{fi.path()}; } } ItemType type = ItemTypeFile; @@ -370,14 +350,14 @@ bool ExcludedFiles::isExcluded( return isExcludedRemote(filePath, basePath, excludeHidden, type); } -bool ExcludedFiles::isExcludedRemote(const QString &filePath, const QString &basePath, bool excludeHidden, ItemType type) const +bool ExcludedFiles::isExcludedRemote(QStringView filePath, QStringView basePath, bool excludeHidden, ItemType type) const { if (!FileSystem::isChildPathOf(filePath, basePath)) { // Mark paths we're not responsible for as excluded... return true; } - auto relativePath = filePath.midRef(basePath.size()); + auto relativePath = filePath.mid(basePath.size()); if (relativePath.endsWith(QLatin1Char('/'))) { relativePath.chop(1); } @@ -393,9 +373,9 @@ bool ExcludedFiles::isExcludedRemote(const QString &filePath, const QString &bas return fullPatternMatch(relativePath, type) != CSYNC_NOT_EXCLUDED; } -CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const QStringRef &path, ItemType filetype) const +CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(QStringView path, ItemType filetype) const { - auto match = _csync_excluded_common(path, _excludeConflictFiles); + auto match = _csync_excluded_common(path); if (match != CSYNC_NOT_EXCLUDED) return match; if (_allExcludes.isEmpty()) @@ -403,47 +383,48 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const QStringRef &path, // Check the bname part of the path to see whether the full // regex should be run. - QStringRef bnameStr(path); + QStringView bnameStr(path); int lastSlash = path.lastIndexOf(QLatin1Char('/')); if (lastSlash >= 0) { bnameStr = path.mid(lastSlash + 1); } - QRegularExpressionMatch m; + QRegularExpressionMatch regularExpressionMatch; if (filetype == ItemTypeDirectory) { - m = _bnameTraversalRegexDir.match(bnameStr); + regularExpressionMatch = _bnameTraversalRegexDir.matchView(bnameStr); } else { - m = _bnameTraversalRegexFile.match(bnameStr); + regularExpressionMatch = _bnameTraversalRegexFile.matchView(bnameStr); } - if (!m.hasMatch()) + if (!regularExpressionMatch.hasMatch()) return CSYNC_NOT_EXCLUDED; - if (m.capturedStart(QStringLiteral("exclude")) != -1) { + if (regularExpressionMatch.capturedStart(QStringLiteral("exclude")) != -1) { return CSYNC_FILE_EXCLUDE_LIST; - } else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { + } else if (regularExpressionMatch.capturedStart(QStringLiteral("excluderemove")) != -1) { return CSYNC_FILE_EXCLUDE_AND_REMOVE; } // third capture: full path matching is triggered - QStringRef pathStr = path; + QStringView pathStr = path; if (filetype == ItemTypeDirectory) { - m = _fullTraversalRegexDir.match(pathStr); + regularExpressionMatch = _fullTraversalRegexDir.matchView(pathStr); } else { - m = _fullTraversalRegexFile.match(pathStr); + regularExpressionMatch = _fullTraversalRegexFile.matchView(pathStr); } - if (m.hasMatch()) { - if (m.capturedStart(QStringLiteral("exclude")) != -1) { + if (regularExpressionMatch.hasMatch()) { + if (regularExpressionMatch.capturedStart(QStringLiteral("exclude")) != -1) { return CSYNC_FILE_EXCLUDE_LIST; - } else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { + } + if (regularExpressionMatch.capturedStart(QStringLiteral("excluderemove")) != -1) { return CSYNC_FILE_EXCLUDE_AND_REMOVE; } } return CSYNC_NOT_EXCLUDED; } -CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const QStringRef &p, ItemType filetype) const +CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(QStringView p, ItemType filetype) const { - auto match = _csync_excluded_common(p, _excludeConflictFiles); + auto match = _csync_excluded_common(p); if (match != CSYNC_NOT_EXCLUDED) return match; if (_allExcludes.isEmpty()) @@ -451,9 +432,9 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const QStringRef &p, ItemType QRegularExpressionMatch m; if (filetype == ItemTypeDirectory) { - m = _fullRegexDir.match(p); + m = _fullRegexDir.matchView(p); } else { - m = _fullRegexFile.match(p); + m = _fullRegexFile.matchView(p); } if (m.hasMatch()) { if (m.capturedStart(QStringLiteral("exclude")) != -1) { @@ -639,7 +620,7 @@ void ExcludedFiles::prepare() pattern.append(appendMe); }; - for (auto exclude : qAsConst(_allExcludes)) { + for (auto exclude : std::as_const(_allExcludes)) { if (exclude[0] == QLatin1Char('\n')) continue; // empty line if (exclude[0] == QLatin1Char('\r')) @@ -752,7 +733,7 @@ void ExcludedFiles::prepare() .arg(fullFileDirKeep, fullDirKeep, bnameFileDirKeep, bnameDirKeep, fullFileDirRemove, fullDirRemove, bnameFileDirRemove, bnameDirRemove)); QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption; - if (OCC::Utility::fsCasePreserving()) + if (OCC::Utility::fsCasePreservingButCaseInsensitive()) patternOptions |= QRegularExpression::CaseInsensitiveOption; _bnameTraversalRegexFile.setPatternOptions(patternOptions); _bnameTraversalRegexFile.optimize(); diff --git a/src/csync/csync_exclude.h b/src/csync/csync_exclude.h index 7e7b98f7cac..15c5b51e99b 100644 --- a/src/csync/csync_exclude.h +++ b/src/csync/csync_exclude.h @@ -66,7 +66,9 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject { Q_OBJECT public: - ExcludedFiles(); + // need to allow nullptr as this is also used in the selective sync routine as a straight object instance, so no parent + // is needed + ExcludedFiles(QObject *parent = nullptr); ~ExcludedFiles() override; /** @@ -76,23 +78,13 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject */ void addExcludeFilePath(const QString &path); - /** - * Whether conflict files shall be excluded. - * - * Defaults to true. - */ - void setExcludeConflictFiles(bool onoff); - /** * Checks whether a file or directory should be excluded. * * @param filePath the absolute path to the file * @param basePath folder path from which to apply exclude rules, ends with a / */ - bool isExcluded( - const QString &filePath, - const QString &basePath, - bool excludeHidden) const; + bool isExcluded(QStringView filePath, QStringView basePath, bool excludeHidden) const; /** * Checks whether a remote file or directory should be excluded. @@ -100,8 +92,7 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject * @param filePath the absolute path to the file * @param basePath folder path from which to apply exclude rules, ends with a / */ - bool isExcludedRemote(const QString &filePath, - const QString &basePath, bool excludeHidden, ItemType type) const; + bool isExcludedRemote(QStringView filePath, QStringView basePath, bool excludeHidden, ItemType type) const; /** * Adds an exclude pattern. @@ -126,7 +117,7 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject /** * Sets the client version, only used for testing. */ - void setClientVersion(QVersionNumber version); + void setClientVersion(const QVersionNumber &version); /** * @brief Check if the given path should be excluded in a traversal situation. @@ -143,9 +134,9 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject * 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 QStringRef &path, ItemType filetype) const; + CSYNC_EXCLUDE_TYPE traversalPatternMatch(QStringView path, ItemType filetype) const; -public slots: +public Q_SLOTS: /** * Reloads the exclude patterns from the registered paths. */ @@ -177,7 +168,7 @@ public slots: * 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 fullPatternMatch(const QStringRef &path, ItemType filetype) const; + CSYNC_EXCLUDE_TYPE fullPatternMatch(QStringView path, ItemType filetype) const; /** * Generate optimized regular expressions for the exclude patterns. @@ -230,8 +221,6 @@ public slots: QRegularExpression _fullRegexFile; QRegularExpression _fullRegexDir; - bool _excludeConflictFiles = true; - /** * Whether * and ? in patterns can match a / * diff --git a/src/csync/std/c_lib.h b/src/csync/std/c_lib.h deleted file mode 100644 index dd116d76705..00000000000 --- a/src/csync/std/c_lib.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * cynapses libc functions - * - * Copyright (c) 2008-2013 by Andreas Schneider - * - * 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 -#include - -#include "c_private.h" diff --git a/src/csync/std/c_private.h b/src/csync/std/c_private.h deleted file mode 100644 index 7cb3594af6d..00000000000 --- a/src/csync/std/c_private.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * cynapses libc functions - * - * Copyright (c) 2008-2013 by Andreas Schneider - * Copyright (c) 2012-2013 by Dominik Schmidt - * Copyright (c) 2012-2013 by Klaas Freitag - * - * 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 _C_PRIVATE_H -#define _C_PRIVATE_H - -#include "config_csync.h" - -/* cross platform defines */ -#include "config_csync.h" -#include -#include - -#ifdef _WIN32 -#include -#include -#include -#include -#else -#include -#endif - -#include - -#ifdef __MINGW32__ -#ifndef S_IRGRP -#define S_IRGRP 0 -#endif -#ifndef S_IROTH -#define S_IROTH 0 -#endif -#ifndef S_IXGRP -#define S_IXGRP 0 -#endif -#ifndef S_IXOTH -#define S_IXOTH 0 -#endif - -#define S_IFSOCK 10000 /* dummy val on Win32 */ -#define S_IFLNK 10001 /* dummy val on Win32 */ - -#define O_NOFOLLOW 0 -#define O_NOCTTY 0 - -#define uid_t int -#define gid_t int -#define nlink_t int -#define getuid() 0 -#define geteuid() 0 -#elif defined(_WIN32) -#define mode_t int -#else -#include -#endif - - -#ifdef _WIN32 -typedef struct stat64 csync_stat_t; -#define _FILE_OFFSET_BITS 64 -#else -typedef struct stat csync_stat_t; -#endif - -#ifndef O_NOATIME -#define O_NOATIME 0 -#endif - -#ifndef HAVE_LSTAT -#define lstat _stat -#endif - -/* tchar definitions for clean win32 filenames */ -#ifndef _UNICODE -#define _UNICODE -#endif - -#if defined _WIN32 && defined _UNICODE -typedef wchar_t mbchar_t; -#define _topen _wopen -#define _tdirent _wdirent -#define _topendir _wopendir -#define _tclosedir _wclosedir -#define _treaddir _wreaddir -#define _trewinddir _wrewinddir -#define _ttelldir _wtelldir -#define _tseekdir _wseekdir -#define _tcreat _wcreat -#define _tstat _wstat64 -#define _tfstat _fstat64 -#define _tunlink _wunlink -#define _tmkdir(X,Y) _wmkdir(X) -#define _trmdir _wrmdir -#define _tchmod _wchmod -#define _trewinddir _wrewinddir -#define _tchown(X, Y, Z) 0 /* no chown on Win32 */ -#define _tchdir _wchdir -#define _tgetcwd _wgetcwd -#else -typedef char mbchar_t; -#define _tdirent dirent -#define _topen open -#define _topendir opendir -#define _tclosedir closedir -#define _treaddir readdir -#define _trewinddir rewinddir -#define _ttelldir telldir -#define _tseekdir seekdir -#define _tcreat creat -#define _tstat lstat -#define _tfstat fstat -#define _tunlink unlink -#define _tmkdir(X,Y) mkdir(X,Y) -#define _trmdir rmdir -#define _tchmod chmod -#define _trewinddir rewinddir -#define _tchown(X,Y,Z) chown(X,Y,Z) -#define _tchdir chdir -#define _tgetcwd getcwd -#endif - -/* FIXME: Implement TLS for OS X */ -#if defined(__GNUC__) && !defined(__APPLE__) -# define CSYNC_THREAD __thread -#elif defined(_MSC_VER) -# define CSYNC_THREAD __declspec(thread) -#else -# define CSYNC_THREAD -#endif - -#endif //_C_PRIVATE_H - -/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/src/csync/std/c_time.cpp b/src/csync/std/c_time.cpp index f944df5349c..439964b58da 100644 --- a/src/csync/std/c_time.cpp +++ b/src/csync/std/c_time.cpp @@ -19,31 +19,34 @@ */ #include "config_csync.h" -#include "c_private.h" #include "c_time.h" -#include +#include "common/filesystembase.h" #ifdef HAVE_UTIMES int c_utimes(const QString &uri, const struct timeval *times) { - int ret = utimes(QFile::encodeName(uri).constData(), times); + int ret = utimes(uri.toLocal8Bit().constData(), times); return ret; } #else // HAVE_UTIMES #ifdef _WIN32 +#include "common/utility_win.h" // implementation for utimes taken from KDE mingw headers #include #include #include + +Q_LOGGING_CATEGORY(lcCSyncCtime, "sync.csync.c_time", QtInfoMsg) + + #define CSYNC_SECONDS_SINCE_1601 11644473600LL #define CSYNC_USEC_IN_SEC 1000000LL //after Microsoft KB167296 static void UnixTimevalToFileTime(struct timeval t, LPFILETIME pft) { - LONGLONG ll; - ll = Int32x32To64(t.tv_sec, CSYNC_USEC_IN_SEC*10) + t.tv_usec*10 + CSYNC_SECONDS_SINCE_1601*CSYNC_USEC_IN_SEC*10; + LONGLONG ll = t.tv_sec * CSYNC_USEC_IN_SEC*10 + t.tv_usec*10 + CSYNC_SECONDS_SINCE_1601*CSYNC_USEC_IN_SEC*10; pft->dwLowDateTime = (DWORD)ll; pft->dwHighDateTime = ll >> 32; } @@ -53,7 +56,7 @@ int c_utimes(const QString &uri, const struct timeval *times) { FILETIME LastModificationTime; HANDLE hFile; - auto wuri = uri.toStdWString(); + const QString wuri = OCC::FileSystem::longWinPath(uri); if(times) { UnixTimevalToFileTime(times[0], &LastAccessTime); @@ -64,32 +67,36 @@ int c_utimes(const QString &uri, const struct timeval *times) { GetSystemTimeAsFileTime(&LastModificationTime); } - hFile=CreateFileW(wuri.data(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL+FILE_FLAG_BACKUP_SEMANTICS, NULL); + hFile = CreateFileW(reinterpret_cast(wuri.utf16()), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr); if(hFile==INVALID_HANDLE_VALUE) { - switch(GetLastError()) { - case ERROR_FILE_NOT_FOUND: - errno=ENOENT; - break; - case ERROR_PATH_NOT_FOUND: - case ERROR_INVALID_DRIVE: - errno=ENOTDIR; - break; - /*case ERROR_WRITE_PROTECT: //CreateFile sets ERROR_ACCESS_DENIED on read-only devices - * errno=EROFS; - * break;*/ - case ERROR_ACCESS_DENIED: - errno=EACCES; - break; - default: - errno=ENOENT; //what other errors can occur? + const auto error = GetLastError(); + qCWarning(lcCSyncCtime) << Q_FUNC_INFO << "for" << wuri << "failed with error:" << OCC::Utility::formatWinError(error); + switch (error) { + case ERROR_FILE_NOT_FOUND: + errno = ENOENT; + break; + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + errno = ENOTDIR; + break; + /*case ERROR_WRITE_PROTECT: //CreateFile sets ERROR_ACCESS_DENIED on read-only devices + * errno=EROFS; + * break;*/ + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + default: + errno = ENOENT; // what other errors can occur? } return -1; } - if(!SetFileTime(hFile, NULL, &LastAccessTime, &LastModificationTime)) { - //can this happen? + if (!SetFileTime(hFile, nullptr, &LastAccessTime, &LastModificationTime)) { + // can this happen? + const auto error = GetLastError(); + qCWarning(lcCSyncCtime) << Q_FUNC_INFO << "for" << wuri << "failed with error:" << OCC::Utility::formatWinError(error); errno=ENOENT; CloseHandle(hFile); return -1; diff --git a/src/csync/vio/csync_vio_local.h b/src/csync/vio/csync_vio_local.h index 04bd057ea4d..c0432ea8ad4 100644 --- a/src/csync/vio/csync_vio_local.h +++ b/src/csync/vio/csync_vio_local.h @@ -21,6 +21,8 @@ #ifndef _CSYNC_VIO_LOCAL_H #define _CSYNC_VIO_LOCAL_H +#include "ocsynclib.h" + #include struct csync_vio_handle_t; diff --git a/src/csync/vio/csync_vio_local_unix.cpp b/src/csync/vio/csync_vio_local_unix.cpp index 4a048ebcf09..e866cded635 100644 --- a/src/csync/vio/csync_vio_local_unix.cpp +++ b/src/csync/vio/csync_vio_local_unix.cpp @@ -26,15 +26,13 @@ #include #include -#include "c_private.h" -#include "c_lib.h" #include "csync.h" -#include "vio/csync_vio_local.h" +#include "common/filesystembase.h" #include "common/vfs.h" +#include "vio/csync_vio_local.h" #include -#include Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "sync.csync.vio_local", QtInfoMsg) @@ -44,50 +42,42 @@ Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "sync.csync.vio_local", QtInfoMsg) struct csync_vio_handle_t { DIR *dh; - QByteArray path; + QString path; }; -static int _csync_vio_local_stat_mb(const mbchar_t *wuri, csync_file_stat_t *buf); - csync_vio_handle_t *csync_vio_local_opendir(const QString &name) { - QScopedPointer handle(new csync_vio_handle_t{}); + std::unique_ptr handle(new csync_vio_handle_t{}); - auto dirname = QFile::encodeName(name); + auto dirname = OCC::FileSystem::encodeFileName(name); - handle->dh = _topendir(dirname.constData()); + handle->dh = opendir(dirname.constData()); if (!handle->dh) { return nullptr; } - handle->path = dirname; - return handle.take(); + handle->path = name; + return handle.release(); } int csync_vio_local_closedir(csync_vio_handle_t *dhandle) { Q_ASSERT(dhandle); - auto rc = _tclosedir(dhandle->dh); + auto rc = closedir(dhandle->dh); delete dhandle; return rc; } std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *handle, OCC::Vfs *vfs) { + struct dirent *dirent = nullptr; + std::unique_ptr file_stat; - struct _tdirent *dirent = nullptr; - std::unique_ptr file_stat; - - do { - dirent = _treaddir(handle->dh); - if (dirent == nullptr) - return {}; - } while (qstrcmp(dirent->d_name, ".") == 0 || qstrcmp(dirent->d_name, "..") == 0); + do { + dirent = readdir(handle->dh); + if (dirent == nullptr) + return {}; + } while (qstrcmp(dirent->d_name, ".") == 0 || qstrcmp(dirent->d_name, "..") == 0); file_stat.reset(new csync_file_stat_t); - file_stat->path = QFile::decodeName(dirent->d_name).toUtf8(); - QByteArray fullPath = handle->path % '/' % QByteArray() % const_cast(dirent->d_name); - if (file_stat->path.isNull()) { - file_stat->original_path = fullPath; - qCWarning(lcCSyncVIOLocal) << "Invalid characters in file/directory name, please rename:" << dirent->d_name << handle->path; - } + file_stat->path = OCC::FileSystem::decodeFileName(dirent->d_name); /* Check for availability of d_type, see manpage. */ #if defined(_DIRENT_HAVE_D_TYPE) || defined(__APPLE__) @@ -110,10 +100,7 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *h } #endif - if (file_stat->path.isNull()) - return file_stat; - - if (_csync_vio_local_stat_mb(fullPath.constData(), file_stat.get()) < 0) { + if (csync_vio_local_stat(handle->path + QLatin1Char('/') + file_stat->path, file_stat.get()) < 0) { // Will get excluded by _csync_detect_update. file_stat->type = ItemTypeSkip; } @@ -131,15 +118,10 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *h int csync_vio_local_stat(const QString &uri, csync_file_stat_t *buf) { - return _csync_vio_local_stat_mb(QFile::encodeName(uri).constData(), buf); -} - -static int _csync_vio_local_stat_mb(const mbchar_t *wuri, csync_file_stat_t *buf) -{ - csync_stat_t sb; + struct stat sb; - if (_tstat(wuri, &sb) < 0) { - return -1; + if (lstat(OCC::FileSystem::encodeFileName(uri).constData(), &sb) < 0) { + return -1; } switch (sb.st_mode & S_IFMT) { diff --git a/src/csync/vio/csync_vio_local_win.cpp b/src/csync/vio/csync_vio_local_win.cpp index d1ce9d23969..f299127f93a 100644 --- a/src/csync/vio/csync_vio_local_win.cpp +++ b/src/csync/vio/csync_vio_local_win.cpp @@ -20,19 +20,12 @@ */ #include -#include -#include -#include -#include -#include "windows.h" - -#include "c_private.h" -#include "c_lib.h" -#include "csync.h" -#include "vio/csync_vio_local.h" #include "common/filesystembase.h" #include "common/utility.h" +#include "common/utility_win.h" +#include "csync.h" +#include "vio/csync_vio_local.h" #include @@ -44,16 +37,17 @@ Q_LOGGING_CATEGORY(lcCSyncVIOLocal, "sync.csync.vio_local", QtInfoMsg) * directory functions */ -struct csync_vio_handle_t { - WIN32_FIND_DATA ffd; - HANDLE hFind; - int firstFind; - QString path; // Always ends with '\' +struct csync_vio_handle_t +{ + WIN32_FIND_DATA ffd; + HANDLE hFind; + int firstFind; + QString path; // Always ends with '\' }; -csync_vio_handle_t *csync_vio_local_opendir(const QString &name) { - - QScopedPointer handle(new csync_vio_handle_t{}); +csync_vio_handle_t *csync_vio_local_opendir(const QString &name) +{ + std::unique_ptr handle(new csync_vio_handle_t{}); // the file wildcard has to be attached QString dirname = OCC::FileSystem::longWinPath(name + QLatin1String("/*")); @@ -62,7 +56,7 @@ csync_vio_handle_t *csync_vio_local_opendir(const QString &name) { if (handle->hFind == INVALID_HANDLE_VALUE) { int retcode = GetLastError(); - if( retcode == ERROR_FILE_NOT_FOUND ) { + if (retcode == ERROR_FILE_NOT_FOUND) { errno = ENOENT; } else { errno = EACCES; @@ -74,15 +68,16 @@ csync_vio_handle_t *csync_vio_local_opendir(const QString &name) { dirname.chop(1); // remove the * handle->path = std::move(dirname); - return handle.take(); + return handle.release(); } -int csync_vio_local_closedir(csync_vio_handle_t *dhandle) { +int csync_vio_local_closedir(csync_vio_handle_t *dhandle) +{ Q_ASSERT(dhandle); int rc = -1; // FindClose returns non-zero on success - if( FindClose(dhandle->hFind) != 0 ) { + if (FindClose(dhandle->hFind) != 0) { rc = 0; } else { // error case, set errno @@ -99,81 +94,79 @@ static time_t FileTimeToUnixTime(FILETIME *filetime, DWORD *remainder) t <<= 32; t += (UINT32)filetime->dwLowDateTime; t -= 116444736000000000LL; - if (t < 0) - { - if (remainder) *remainder = 9999999 - (-t - 1) % 10000000; + if (t < 0) { + if (remainder) + *remainder = 9999999 - (-t - 1) % 10000000; return -1 - ((-t - 1) / 10000000); - } - else - { - if (remainder) *remainder = t % 10000000; + } else { + if (remainder) + *remainder = t % 10000000; return t / 10000000; } } -std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *handle, OCC::Vfs *vfs) { - - std::unique_ptr file_stat; - DWORD rem; - - errno = 0; - - // the win32 functions get the first valid entry with the opendir - // thus we must not jump to next entry if it was the first find. - if( handle->firstFind ) { - handle->firstFind = 0; - } else { - if( FindNextFile(handle->hFind, &(handle->ffd)) == 0 ) { - // might be error, check! - int dwError = GetLastError(); - if (dwError != ERROR_NO_MORE_FILES) { - qCWarning(lcCSyncVIOLocal, "FindNextFile error %d", dwError); - errno = EACCES; // no more files is fine. Otherwise EACCESS - } - return nullptr; - } - } - auto path = QString::fromWCharArray(handle->ffd.cFileName); - if (path == QLatin1String(".") || path == QLatin1String("..")) - return csync_vio_local_readdir(handle, vfs); - - file_stat.reset(new csync_file_stat_t); - file_stat->path = path.toUtf8(); +std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *handle, OCC::Vfs *vfs) +{ + std::unique_ptr file_stat; + DWORD rem; + + errno = 0; + + // the win32 functions get the first valid entry with the opendir + // thus we must not jump to next entry if it was the first find. + if (handle->firstFind) { + handle->firstFind = 0; + } else { + if (FindNextFile(handle->hFind, &(handle->ffd)) == 0) { + // might be error, check! + int dwError = GetLastError(); + if (dwError != ERROR_NO_MORE_FILES) { + qCWarning(lcCSyncVIOLocal, "FindNextFile error %d", dwError); + errno = EACCES; // no more files is fine. Otherwise EACCESS + } + return nullptr; + } + } + const QString path = QString::fromWCharArray(handle->ffd.cFileName); + if (path == QLatin1String(".") || path == QLatin1String("..")) + return csync_vio_local_readdir(handle, vfs); + + file_stat.reset(new csync_file_stat_t); + file_stat->path = path; if (vfs && vfs->statTypeVirtualFile(file_stat.get(), &handle->ffd)) { - // all good + // all good + } else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + file_stat->type = ItemTypeDirectory; } else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { - // Detect symlinks, and treat junctions as symlinks too. - if (handle->ffd.dwReserved0 == IO_REPARSE_TAG_SYMLINK - || handle->ffd.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT) { - file_stat->type = ItemTypeSoftLink; - } else { - // The SIS and DEDUP reparse points should be treated as - // regular files. We don't know about the other ones yet, - // but will also treat them normally for now. - file_stat->type = ItemTypeFile; - } + // Detect symlinks, and treat junctions as symlinks too. + if (handle->ffd.dwReserved0 == IO_REPARSE_TAG_SYMLINK + || handle->ffd.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT) { + file_stat->type = ItemTypeSoftLink; + } else { + // The SIS and DEDUP reparse points should be treated as + // regular files. We don't know about the other ones yet, + // but will also treat them normally for now. + file_stat->type = ItemTypeFile; + } } else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_DEVICE - || handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE - ) { + || handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) { file_stat->type = ItemTypeSkip; - } else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - file_stat->type = ItemTypeDirectory; } else { file_stat->type = ItemTypeFile; } /* Check for the hidden flag */ - if( handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN ) { + if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) { file_stat->is_hidden = true; } - file_stat->size = (handle->ffd.nFileSizeHigh * ((int64_t)(MAXDWORD)+1)) + handle->ffd.nFileSizeLow; + file_stat->size = (handle->ffd.nFileSizeHigh * ((int64_t)(MAXDWORD) + 1)) + handle->ffd.nFileSizeLow; file_stat->modtime = FileTimeToUnixTime(&handle->ffd.ftLastWriteTime, &rem); // path always ends with '\', by construction - if (csync_vio_local_stat(handle->path + QString::fromWCharArray(handle->ffd.cFileName), file_stat.get()) < 0) { + if (csync_vio_local_stat(handle->path + path, file_stat.get()) < 0) { // Will get excluded by _csync_detect_update. file_stat->type = ItemTypeSkip; } @@ -193,16 +186,14 @@ int csync_vio_local_stat(const QString &uri, csync_file_stat_t *buf) ULARGE_INTEGER FileIndex; h = CreateFileW(reinterpret_cast(OCC::FileSystem::longWinPath(uri).utf16()), 0, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, - NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, - NULL); - if( h == INVALID_HANDLE_VALUE ) { + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr); + if (h == INVALID_HANDLE_VALUE) { errno = GetLastError(); qCCritical(lcCSyncVIOLocal) << "CreateFileW failed on" << uri << OCC::Utility::formatWinError(errno); return -1; } - if(!GetFileInformationByHandle( h, &fileInfo ) ) { + if (!GetFileInformationByHandle(h, &fileInfo)) { errno = GetLastError(); qCCritical(lcCSyncVIOLocal) << "GetFileInformationByHandle failed on" << uri << OCC::Utility::formatWinError(errno); CloseHandle(h); @@ -215,7 +206,8 @@ int csync_vio_local_stat(const QString &uri, csync_file_stat_t *buf) FileIndex.QuadPart &= 0x0000FFFFFFFFFFFF; /* printf("Index: %I64i\n", FileIndex.QuadPart); */ buf->inode = FileIndex.QuadPart; - buf->size = (fileInfo.nFileSizeHigh * ((int64_t)(MAXDWORD)+1)) + fileInfo.nFileSizeLow; + + buf->size = ULARGE_INTEGER { { fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh } }.QuadPart; DWORD rem; buf->modtime = FileTimeToUnixTime(&fileInfo.ftLastWriteTime, &rem); diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index acf943987fa..259cea94431 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,124 +1,66 @@ include(ECMAddAppIcon) -find_package(Qt5 REQUIRED COMPONENTS Widgets) - -if (WIN32) - find_package(Qt5 REQUIRED COMPONENTS WinExtras) -endif() - -add_subdirectory(updater) +find_package(KDSingleApplication-qt6 1.0.0 REQUIRED) set(client_UI_SRCS - aboutdialog.ui - accountsettings.ui - folderwizardsourcepage.ui - folderwizardtargetpage.ui - generalsettings.ui ignorelisteditor.ui networksettings.ui - protocolwidget.ui - issueswidget.ui - activitywidget.ui - settingsdialog.ui - sharedialog.ui - sharelinkwidget.ui - shareusergroupwidget.ui - shareuserline.ui - sslerrordialog.ui - proxyauthdialog.ui - notificationwidget.ui + localactivitywidget.ui + syncerrorwidget.ui + tlserrordialog.ui logbrowser.ui - wizard/owncloudadvancedsetuppage.ui - wizard/owncloudhttpcredspage.ui - wizard/owncloudoauthcredspage.ui - wizard/owncloudsetuppage.ui ) set(client_SRCS - aboutdialog.cpp accountmanager.cpp - accountsettings.cpp + application.cpp + fetchserversettings.cpp clientproxy.cpp + commonstrings.cpp connectionvalidator.cpp folder.cpp folderman.cpp - folderstatusmodel.cpp - folderstatusdelegate.cpp folderwatcher.cpp - folderwizard.cpp - generalsettings.cpp ignorelisteditor.cpp lockwatcher.cpp logbrowser.cpp + networkinformation.cpp networksettings.cpp - ocsjob.cpp - ocssharejob.cpp - ocsshareejob.cpp openfilemanager.cpp - owncloudgui.cpp - owncloudsetupwizard.cpp - protocolwidget.cpp + traymenucontroller.cpp + localactivitywidget.cpp protocolitem.cpp - issueswidget.cpp - activitydata.cpp - activitywidget.cpp - selectivesyncdialog.cpp - settingsdialog.cpp - sharedialog.cpp - sharelinkwidget.cpp - sharemanager.cpp - shareusergroupwidget.cpp - sharee.cpp - sslbutton.cpp - sslerrordialog.cpp + syncerrorwidget.cpp + activitysettings.cpp + selectivesyncwidget.cpp + tlserrordialog.cpp syncrunfilelog.cpp - systray.cpp - thumbnailjob.cpp - quotainfo.cpp accountstate.cpp - authenticationdialog.cpp - platform.cpp - proxyauthhandler.cpp - proxyauthdialog.cpp - tooltipupdater.cpp - notificationwidget.cpp - notificationconfirmjob.cpp - servernotificationhandler.cpp guiutility.cpp elidedlabel.cpp translations.cpp - creds/httpcredentialsgui.cpp - models/activitylistmodel.cpp + creds/oauth.cpp + creds/credentials.cpp + creds/requestauthenticationwidget.cpp + creds/requestauthenticationcontroller.cpp + creds/oauthhtmlpage.cpp + models/expandingheaderview.cpp models/models.cpp models/protocolitemmodel.cpp - wizard/postfixlineedit.cpp - wizard/abstractcredswizardpage.cpp - wizard/owncloudadvancedsetuppage.cpp - wizard/owncloudhttpcredspage.cpp - wizard/owncloudoauthcredspage.cpp - wizard/owncloudsetuppage.cpp - wizard/owncloudwizardcommon.cpp - wizard/owncloudwizard.cpp + scheduling/syncscheduler.cpp + scheduling/etagwatcher.cpp + + qmlutils.cpp ) set(3rdparty_SRC ../3rdparty/QProgressIndicator/QProgressIndicator.cpp - ../3rdparty/qtlockedfile/qtlockedfile.cpp - ../3rdparty/qtsingleapplication/qtlocalpeer.cpp - ../3rdparty/qtsingleapplication/qtsingleapplication.cpp - ../3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp ) -if(NOT WIN32) - list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_unix.cpp) -else() - list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_win.cpp ) -endif() - set( final_src ${MIRALL_RC_SRC} ${client_SRCS} @@ -126,108 +68,89 @@ set( final_src ${3rdparty_SRC} ) -if(TARGET qt5keychain) - list(APPEND libsync_LINK_TARGETS qt5keychain) -endif() +add_subdirectory(folderwizard) +add_library(owncloudGui SHARED ${final_src}) + +set_target_properties(owncloudGui PROPERTIES AUTOUIC ON AUTORCC ON) +# for the generated qml module +target_include_directories(owncloudGui PRIVATE models spaces creds) +target_link_libraries(owncloudGui + PUBLIC + Qt::Widgets Qt::Network Qt::Xml Qt::Quick Qt::QuickWidgets Qt::QuickControls2 + folderwizard + libsync + Qt6Keychain::Qt6Keychain +) -add_library(owncloudCore STATIC ${final_src}) -set_target_properties(owncloudCore PROPERTIES AUTOUIC ON AUTORCC ON) -target_link_libraries(owncloudCore PUBLIC Qt5::Widgets Qt5::Network Qt5::Xml - libsync) +apply_common_target_settings(owncloudGui) +ecm_add_qml_module(owncloudGui + URI org.ownCloud.gui + VERSION 1.0 + NAMESPACE OCC + GENERATE_PLUGIN_SOURCE + QML_FILES + spaces/qml/SpaceDelegate.qml + spaces/qml/SpacesView.qml +) +generate_export_header(owncloudGui + EXPORT_MACRO_NAME OWNCLOUDGUI_EXPORT + EXPORT_FILE_NAME owncloudguilib.h + STATIC_DEFINE OWNCLOUD_BUILT_AS_STATIC +) + +add_subdirectory(networkadapters) +add_subdirectory(newaccountwizard) add_subdirectory(socketapi) +add_subdirectory(spaces) +add_subdirectory(FoldersGui) +add_subdirectory(mainwindow) +add_subdirectory(accountsgui) +add_subdirectory(foldermanagement) -target_include_directories(owncloudCore PUBLIC +target_include_directories(owncloudGui PUBLIC ${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator - ${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile - ${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication ${CMAKE_CURRENT_BINARY_DIR} ) - -target_sources(owncloudCore PRIVATE - updater/ocupdater.cpp - updater/updateinfo.cpp - updater/updater.cpp -) - IF( APPLE ) - find_package(Qt5 COMPONENTS MacExtras) - target_link_libraries(owncloudCore PUBLIC Qt5::MacExtras) - - target_sources(owncloudCore PRIVATE - platform_mac.mm - systray.mm + target_sources(owncloudGui PRIVATE settingsdialog_mac.mm guiutility_mac.mm folderwatcher_mac.cpp) - - if(SPARKLE_FOUND) - # Define this, we need to check in updater.cpp - target_compile_definitions(owncloudCore PUBLIC HAVE_SPARKLE) - target_sources(owncloudCore PRIVATE - updater/sparkleupdater_mac.mm - updater/sparkleupdater.h) - target_link_libraries(owncloudCore PRIVATE ${SPARKLE_LIBRARY}) - endif() + set_source_files_properties(guiutility_mac.mm PROPERTIES COMPILE_DEFINITIONS SOCKETAPI_TEAM_IDENTIFIER_PREFIX="${SOCKETAPI_TEAM_IDENTIFIER_PREFIX}") elseif( WIN32 ) - target_sources(owncloudCore PRIVATE - platform_win.cpp + target_sources(owncloudGui PRIVATE guiutility_win.cpp - folderwatcher_win.cpp - navigationpanehelper.cpp) - target_link_libraries(owncloudCore PUBLIC Qt5::WinExtras) -elseif(UNIX AND NOT APPLE ) + folderwatcher_win.cpp) +elseif(UNIX AND NOT APPLE) ## handle DBUS for Fdo notifications - find_package(Qt5 COMPONENTS DBus) - if (TARGET Qt5::DBus) - target_link_libraries(owncloudCore PUBLIC Qt5::DBus) - target_compile_definitions(owncloudCore PUBLIC "USE_FDO_NOTIFICATIONS") + if (TARGET Qt::DBus) + target_link_libraries(owncloudGui PUBLIC Qt::DBus) endif() - target_sources(owncloudCore PRIVATE + target_sources(owncloudGui PRIVATE folderwatcher_linux.cpp - platform_unix.cpp guiutility_unix.cpp) endif() - -if(WITH_CRASHREPORTER) - target_link_libraries(owncloudCore PUBLIC crashreporter-handler) - - if(UNIX AND NOT MAC) - find_package(Threads REQUIRED) - target_link_libraries(owncloudCore PUBLIC ${CMAKE_THREAD_LIBS_INIT}) - endif() +if(WITH_AUTO_UPDATER) + add_subdirectory(updater) + target_compile_definitions(owncloudGui PUBLIC $) endif() +find_package(Qt6 REQUIRED COMPONENTS Core) + add_executable(owncloud main.cpp) + set_target_properties(owncloud PROPERTIES OUTPUT_NAME "${APPLICATION_EXECUTABLE}" AUTOUIC ON AUTORCC ON ) -target_link_libraries(owncloud owncloudCore) +apply_common_target_settings(owncloud) +target_link_libraries(owncloud PUBLIC owncloudGui owncloudResources KDAB::kdsingleapplication ) +target_link_libraries(owncloud PRIVATE Qt6::Core) - -find_package(Qt5LinguistTools) -if(Qt5LinguistTools_FOUND) - # Handle Translations, pick all client_* files from trans directory. - file(GLOB client_translations ${CMAKE_SOURCE_DIR}/translations/client_*.ts) - qt5_add_translation(client_compiled_translations ${client_translations}) - target_sources(owncloud PRIVATE ${client_compiled_translations}) - add_resources_to_target( - TARGET owncloud - PREFIX translations - FILES "${client_compiled_translations}" - ) -endif() - -#TODO Move resources files -target_sources(owncloud PRIVATE - ${PROJECT_SOURCE_DIR}/client.qrc - ${PROJECT_SOURCE_DIR}/core_theme.qrc) - -generate_theme(owncloud OWNCLOUD_SIDEBAR_ICONS) MESSAGE(STATUS "OWNCLOUD_SIDEBAR_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_SIDEBAR_ICONS}") ecm_add_app_icon(appIcons ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APPLICATION_ICON_NAME}") @@ -235,20 +158,13 @@ target_sources(owncloud PRIVATE ${appIcons}) if(NOT APPLE) if(WIN32) - configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in - ${CMAKE_CURRENT_BINARY_DIR}/version.rc - @ONLY) - target_sources(owncloud PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/version.rc) - if(NOT MSVC) - target_sources(owncloud PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/manifest-mingw.rc) - endif() + target_sources(owncloud PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/owncloud.exe.manifest) else() file(GLOB _icons "${OEM_THEME_DIR}/theme/colored/*-${APPLICATION_ICON_NAME}-icon.png") foreach(_file ${_icons}) string(REPLACE "${OEM_THEME_DIR}/theme/colored/" "" _res ${_file}) string(REPLACE "-${APPLICATION_ICON_NAME}-icon.png" "" _res ${_res}) - install(FILES ${_file} RENAME ${APPLICATION_ICON_NAME}.png DESTINATION ${DATA_INSTALL_DIR}/icons/hicolor/${_res}x${_res}/apps) + install(FILES ${_file} RENAME ${APPLICATION_ICON_NAME}.png DESTINATION ${KDE_INSTALL_DATADIR}/icons/hicolor/${_res}x${_res}/apps) endforeach(_file) endif() @@ -265,20 +181,19 @@ else() set_target_properties(owncloud PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/MacOSXBundleInfo.plist) endif() -install(TARGETS owncloud ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) +install(TARGETS owncloud owncloudGui ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) +ecm_finalize_qml_module(owncloudGui DESTINATION ${KDE_INSTALL_QMLDIR}) if(UNIX AND NOT APPLE) - include(libcloudproviders/libcloudproviders.cmake) - - configure_file(${CMAKE_SOURCE_DIR}/mirall.desktop.in + configure_file(${CMAKE_SOURCE_DIR}/owncloud.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.desktop) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.desktop DESTINATION ${DATA_INSTALL_DIR}/applications ) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.desktop DESTINATION ${KDE_INSTALL_DATADIR}/applications ) configure_file(owncloud.xml.in ${APPLICATION_EXECUTABLE}.xml) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.xml DESTINATION ${DATA_INSTALL_DIR}/mime/packages ) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.xml DESTINATION ${KDE_INSTALL_DATADIR}/mime/packages ) find_package(SharedMimeInfo) if(SharedMimeInfo_FOUND) - update_xdg_mimetypes( ${DATA_INSTALL_DIR}/mime/packages ) + update_xdg_mimetypes( ${KDE_INSTALL_DATADIR}/mime/packages ) endif(SharedMimeInfo_FOUND) endif() diff --git a/src/gui/FoldersGui/CMakeLists.txt b/src/gui/FoldersGui/CMakeLists.txt new file mode 100644 index 00000000000..486b8817d18 --- /dev/null +++ b/src/gui/FoldersGui/CMakeLists.txt @@ -0,0 +1,19 @@ +target_sources(owncloudGui PRIVATE + folderitem.cpp + folderitem.h + folderitemdelegate.cpp + folderitemdelegate.h + foldererroritem.cpp + foldererroritem.h + foldermodelcontroller.cpp + foldermodelcontroller.h + accountfoldersview.cpp + accountfoldersview.h + folderitemupdater.cpp + folderitemupdater.h + accountfolderscontroller.cpp + accountfolderscontroller.h + buttondelegate.cpp + buttondelegate.h +) + diff --git a/src/gui/FoldersGui/accountfolderscontroller.cpp b/src/gui/FoldersGui/accountfolderscontroller.cpp new file mode 100644 index 00000000000..2803481f4e8 --- /dev/null +++ b/src/gui/FoldersGui/accountfolderscontroller.cpp @@ -0,0 +1,354 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "accountfolderscontroller.h" + +#include "accountfoldersview.h" +#include "accountmodalwidget.h" +#include "application.h" +#include "commonstrings.h" +#include "configfile.h" +#include "foldermodelcontroller.h" +#include "selectivesyncwidget.h" + +#include "guiutility.h" +#include "networkjobs.h" +#include "openfilemanager.h" + +#include "theme.h" + +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcAccountFoldersController, "gui.accountfolders.controller", QtInfoMsg) + +AccountFoldersController::AccountFoldersController(AccountState *state, AccountFoldersView *view, QObject *parent) + : QObject{parent} + , _accountState(state) + , _view(view) +{ + if (!_view || !_accountState || !_accountState->account()) + return; + + _accountId = _accountState->account()->uuid(); + + buildMenuActions(); + + FolderModelController *modelController = new FolderModelController(_accountId, this); + connect(modelController, &FolderModelController::currentFolderChanged, this, &AccountFoldersController::onFolderChanged); + + QStandardItemModel *model = modelController->itemModel(); + _view->setItemModels(model, modelController->selectionModel()); + + connect(_view, &AccountFoldersView::addFolderTriggered, this, &AccountFoldersController::onAddFolder); + + FolderMan *folderMan = FolderMan::instance(); + modelController->connectSignals(folderMan); + + connect(folderMan, &FolderMan::unsyncedSpaceCountChanged, this, &AccountFoldersController::onUnsyncedSpaceCountChanged); +} + +void AccountFoldersController::onUnsyncedSpaceCountChanged(const QUuid &accountId, int unsyncedSpaceCount, int totalSpaceCount) +{ + if (accountId != _accountId) + return; + + _view->enableAddFolder(unsyncedSpaceCount > 0); + // sometimes we have folders that are related to a space which has been removed server side. Because of this, + // sometimes the total "synced" count can be larger than the space count, so min the diff to get something reasonable + int reasonableCount = qMin(totalSpaceCount - unsyncedSpaceCount, totalSpaceCount); + _view->setSyncedFolderCount(reasonableCount, totalSpaceCount); +} + +void AccountFoldersController::onFolderChanged(OCC::Folder *folder) +{ + if (_currentFolder == folder) + return; + + _currentFolder = folder; +} + +void AccountFoldersController::onAddFolder() +{ + emit requestAddFolder(_accountId); +} + +void AccountFoldersController::buildMenuActions() +{ + QList itemActions; + + // show in finder + _showInSystemFolder = new QAction(CommonStrings::showInFileBrowser(), this); + _showInSystemFolder->setObjectName("showInFileSystemAction"); + itemActions.push_back(_showInSystemFolder); + connect(_showInSystemFolder, &QAction::triggered, this, &AccountFoldersController::onShowInSystemFolder); + + if (_accountState->account()->capabilities().privateLinkPropertyAvailable()) { + _showInBrowser = new QAction(CommonStrings::showInWebBrowser(), this); + _showInBrowser->setObjectName("showInBrowserAction"); + itemActions.push_back(_showInBrowser); + connect(_showInBrowser, &QAction::triggered, this, &AccountFoldersController::onShowInBrowser); + } + + QAction *separator = new QAction(this); + // I don't think this is particularly useful but shouldn't hurt + separator->setObjectName("sparatorAction"); + separator->setSeparator(true); + itemActions.push_back(separator); + + _forceSync = new QAction(tr("Sync now"), this); + _forceSync->setObjectName("forceSyncAction"); + itemActions.push_back(_forceSync); + connect(_forceSync, &QAction::triggered, this, &AccountFoldersController::onForceSync); + + _pauseSync = new QAction(this); + _pauseSync->setObjectName("pauseSyncAction"); + itemActions.push_back(_pauseSync); + connect(_pauseSync, &QAction::triggered, this, &AccountFoldersController::onTogglePauseSync); + + _chooseSync = new QAction(tr("Manage subfolder sync"), this); + _chooseSync->setObjectName("selectiveSyncAction"); + + Vfs::Mode mode = VfsPluginManager::instance().bestAvailableVfsMode(); + if (mode == Vfs::WindowsCfApi) { + // need extra checks: + if (Theme::instance()->forceVirtualFilesOption()) { + // get rid of choose sync action as it can never be enabled + // yes this is a slightly strange way to do this but think of the alternative code...blech. + delete _chooseSync; + _chooseSync = nullptr; + } else { + _enableVfs = new QAction(this); + _enableVfs->setObjectName("enableVfsAction"); + itemActions.push_back(_enableVfs); + connect(_enableVfs, &QAction::triggered, this, &AccountFoldersController::onEnableVfs); + } + } + + if (_chooseSync) { + itemActions.push_back(_chooseSync); + connect(_chooseSync, &QAction::triggered, this, &AccountFoldersController::onChooseSync); + } + + //: This shows as 'Remove folder sync' or 'Remove Space sync' + QString removeSyncString = tr("Remove %1 sync").arg(CommonStrings::space()); + _removeSync = new QAction(removeSyncString, this); + _removeSync->setObjectName("removeFolderSyncAction"); + itemActions.push_back(_removeSync); + connect(_removeSync, &QAction::triggered, this, &AccountFoldersController::onRemoveSync); + + connect(_view, &AccountFoldersView::requestActionsUpdate, this, &AccountFoldersController::updateActions); + _view->setFolderActions(itemActions); +} + + +void AccountFoldersController::onEnableVfs() +{ + if (!_currentFolder || VfsPluginManager::instance().bestAvailableVfsMode() != Vfs::WindowsCfApi) + return; + + if (!_currentFolder->virtualFilesEnabled()) { + qCInfo(lcAccountFoldersController) << "Enabling vfs support for folder" << _currentFolder->path(); + + // Change the folder vfs mode and load the plugin + _currentFolder->setVirtualFilesEnabled(true); + } else { + QMessageBox msgBox(QMessageBox::Question, tr("Disable virtual file support?"), + tr("This action will disable virtual file support. As a consequence contents of folders that " + "are currently marked as 'available online only' will be downloaded." + "\n\n" + "The only advantage of disabling virtual file support is that the selective sync feature " + "will become available again." + "\n\n" + "This action will abort any currently running synchronization."), + QMessageBox::Yes | QMessageBox::No, ocApp()->mainWindow()); + msgBox.setObjectName("confirmDisableVfsDialog"); + msgBox.button(QMessageBox::Yes)->setText(tr("Disable support")); + msgBox.button(QMessageBox::Yes)->setObjectName("disableVfsButton"); + msgBox.button(QMessageBox::No)->setText(tr("Cancel")); + msgBox.button(QMessageBox::No)->setObjectName("cancelDisableVfsButton"); + + int result = msgBox.exec(); + if (result == QDialog::Rejected) + return; + + qCInfo(lcAccountFoldersController) << "Disabling vfs support for folder" << _currentFolder->path(); + + // Also wipes virtual files, schedules remote discovery + _currentFolder->setVirtualFilesEnabled(false); + } +} + + +void AccountFoldersController::updateActions() +{ + // the checks for _currentFolder are twofold: + // obvs we don't want to try to access values that aren't there + // furthermore, we do need to disable if the current folder selection + // is empty so it's actually a key part of the evaluation for action state here + // so doing a general if(!_curentFolder) return; handling is not correct + _showInSystemFolder->setEnabled(_currentFolder && QFileInfo::exists(_currentFolder->path())); + + if (_showInBrowser) + _showInBrowser->setEnabled(_currentFolder && _currentFolder->isConnected() && _currentFolder->isAvailable()); + + _forceSync->setEnabled(_currentFolder && _currentFolder->canSync()); + _forceSync->setText(_currentFolder && _currentFolder->isSyncRunning() ? tr("Restart sync") : tr("Sync now")); + + _pauseSync->setText(_currentFolder && _currentFolder->syncPaused() ? tr("Resume sync") : tr("Pause sync")); + // this is a bit nuanced: we want to enable pause if the folder is disconnected as this is always a safe operation. if it *is* connected + // we only want to enabled the action if the folder is available. If the folder is not available pause makes no sense as there is nothing to sync to + // as the space is missing, server side. + _pauseSync->setEnabled(_currentFolder && (!_currentFolder->isConnected() || _currentFolder->isAvailable())); + + _removeSync->setEnabled(_currentFolder); + + if (_enableVfs) { + _enableVfs->setText(_currentFolder && _currentFolder->virtualFilesEnabled() ? tr("Deactivate virtual files") : tr("Activate virtual files")); + // we can't block existence of the action when the folder path doesn't support vfs, so disable it here just in case + // todo: DC-219 hopefully this check can go away + QString pathError = Vfs::pathSupportDetail(_currentFolder->path(), Vfs::WindowsCfApi); + _enableVfs->setEnabled(pathError.isEmpty() && _currentFolder->canSync()); + } + + if (_chooseSync) + _chooseSync->setEnabled(_currentFolder && _currentFolder->isConnected() && _currentFolder->isAvailable() && !_currentFolder->virtualFilesEnabled()); +} + +void OCC::AccountFoldersController::onShowInSystemFolder() +{ + if (!_currentFolder) + return; + + if (QFileInfo::exists(_currentFolder->path())) + showInFileManager(_currentFolder->path()); +} + +void AccountFoldersController::onShowInBrowser() +{ + if (!_currentFolder) + return; + + QString path = _currentFolder->remotePathTrailingSlash(); + QUrl davUrl = _currentFolder->webDavUrl(); + fetchPrivateLinkUrl(_accountState->account(), davUrl, path, this, [](const QUrl &url) { Utility::openBrowser(url, nullptr); }); +} + +void AccountFoldersController::onForceSync() +{ + if (!_currentFolder) + return; + + if (NetworkInformation::instance()->isMetered() && ConfigFile().pauseSyncWhenMetered()) { + QMessageBox messageBox(QMessageBox::Question, tr("Internet connection is metered"), + tr("Synchronization is paused because the Internet connection is a metered connection" + "

Do you really want to force a Synchronization now?"), + QMessageBox::Yes | QMessageBox::No, ocApp()->mainWindow()); + messageBox.setObjectName("confirmForceSyncWhenMeteredDialog"); + messageBox.button(QMessageBox::No)->setObjectName("cancelForceSyncWhenMeteredButton"); + messageBox.button(QMessageBox::Yes)->setObjectName("forceSyncWhenMeteredButton"); + int result = messageBox.exec(); + if (result == QMessageBox::No) + return; + } + + FolderMan::instance()->forceFolderSync(_currentFolder); +} + +void AccountFoldersController::onTogglePauseSync() +{ + if (!_currentFolder) + return; + + bool currentlyPaused = _currentFolder->syncPaused(); + + if (!currentlyPaused) { + if (_currentFolder->isSyncRunning()) { + QMessageBox msgbox(QMessageBox::Question, tr("Sync Running"), tr("The sync operation is running.
Do you want to stop it?"), + QMessageBox::Yes | QMessageBox::No, ocApp()->mainWindow()); + msgbox.setDefaultButton(QMessageBox::No); + msgbox.setObjectName("confirmPauseRunningSyncDialog"); + msgbox.button(QMessageBox::Yes)->setObjectName("pauseRunningSyncButton"); + msgbox.button(QMessageBox::No)->setObjectName("cancelPausRunningSyncButton"); + int result = msgbox.exec(); + if (result == QMessageBox::No) + return; + _currentFolder->slotTerminateSync(tr("Sync paused by user")); + } + } + + // todo: what is this really for? if we are pausing do we actually need it or only on resume? + // if we do need it, it should probably move to the setSyncPaused function so it never gets missed + _currentFolder->slotNextSyncFullLocalDiscovery(); // ensure we don't forget about local errors + _currentFolder->setSyncPaused(!currentlyPaused); +} + +void AccountFoldersController::onRemoveSync() +{ + if (!_currentFolder) + return; + + qCInfo(lcAccountFoldersController) << "Remove Folder " << _currentFolder->path(); + QString shortGuiLocalPath = _currentFolder->shortGuiLocalPath(); + + QMessageBox msgBox(QMessageBox::Question, tr("Confirm Folder Sync Connection Removal"), + tr("

Do you really want to stop syncing the folder %1?

" + "

Note: This will not delete any files.

") + .arg(shortGuiLocalPath), + QMessageBox::Yes | QMessageBox::No, ocApp()->mainWindow()); + msgBox.button(QMessageBox::Yes)->setText(tr("Remove Folder Sync Connection")); + msgBox.button(QMessageBox::No)->setText(tr("Cancel")); + msgBox.setObjectName("confirmRemoveFolderSyncDialog"); + msgBox.button(QMessageBox::Yes)->setObjectName("removeFolderSyncButton"); + msgBox.button(QMessageBox::No)->setObjectName("cancelRemoveFolderSyncButton"); + + int result = msgBox.exec(); + if (result == QMessageBox::No) + return; + // todo: make it a signal + FolderMan::instance()->removeFolderFromGui(_currentFolder); +} + +void AccountFoldersController::onChooseSync() +{ + if (!_currentFolder || !_accountState || !_accountState->account()) { + return; + } + + bool ok; + QSet selectiveSyncList = _currentFolder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, ok); + if (!ok) + return; + + // this widget gets reparented to the layout in the AccountModalWidget so should be cleaned up there + auto *selectiveSync = new SelectiveSyncWidget(_accountState->account(), nullptr); + selectiveSync->setDavUrl(_currentFolder->webDavUrl()); + selectiveSync->setFolderInfo(_currentFolder->remotePath(), _currentFolder->displayName(), selectiveSyncList); + + // the account modal widget is auto-deleted by the handler, currently the AccountView, when it emits "finished" + // as with so many of these async impls the details are way too complicated but that's a topic for a future refactoring + auto *modalWidget = new AccountModalWidget(tr("Choose what to sync"), selectiveSync, nullptr); + modalWidget->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + connect(modalWidget, &AccountModalWidget::accepted, this, [selectiveSync, this] { + if (!_currentFolder) + return; + _currentFolder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, selectiveSync->createBlackList()); + FolderMan::instance()->forceFolderSync(_currentFolder); + }); + + emit requestAccountModalWidget(modalWidget); +} +} diff --git a/src/gui/FoldersGui/accountfolderscontroller.h b/src/gui/FoldersGui/accountfolderscontroller.h new file mode 100644 index 00000000000..88ccfa9a1db --- /dev/null +++ b/src/gui/FoldersGui/accountfolderscontroller.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "folder.h" +#include "folderman.h" + +#include +#include +#include + +class QAction; + +namespace OCC { + +class AccountFoldersView; +class AccountState; +class FolderModelController; +class AccountModalWidget; + +class AccountFoldersController : public QObject +{ + Q_OBJECT +public: + explicit AccountFoldersController(AccountState *state, AccountFoldersView *view, QObject *parent); + +signals: + void removeFolderFromGui(OCC::Folder *f); + void requestAddFolder(QUuid accountId); + + void requestAccountModalWidget(OCC::AccountModalWidget *widget); + +protected: + void onUnsyncedSpaceCountChanged(const QUuid &accountId, int unsyncedSpaceCount, int totalSpaceCount); + void onAddFolder(); + +private: + QPointer _accountState; + QUuid _accountId; + QPointer _currentFolder = nullptr; + AccountFoldersView *_view = nullptr; + FolderModelController *_modelController = nullptr; + + void buildMenuActions(); + + void updateActions(); + + // menu actions: + QAction *_showInSystemFolder = nullptr; + QAction *_showInBrowser = nullptr; + QAction *_forceSync = nullptr; + QAction *_pauseSync = nullptr; + QAction *_removeSync = nullptr; + QAction *_chooseSync = nullptr; + QAction *_enableVfs = nullptr; + + void onFolderChanged(OCC::Folder *folder); + + void onShowInSystemFolder(); + void onShowInBrowser(); + void onForceSync(); + void onTogglePauseSync(); + void onRemoveSync(); + void onChooseSync(); + void onEnableVfs(); +}; +} diff --git a/src/gui/FoldersGui/accountfoldersview.cpp b/src/gui/FoldersGui/accountfoldersview.cpp new file mode 100644 index 00000000000..014f86e135d --- /dev/null +++ b/src/gui/FoldersGui/accountfoldersview.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "accountfoldersview.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "buttondelegate.h" +#include "commonstrings.h" +#include "folderitemdelegate.h" + +namespace OCC { + + +AccountFoldersView::AccountFoldersView(QWidget *parent) + : QWidget{parent} +{ + // important to know: the parent in this ctr is always null because the widget is instantiated by the ui impl. it leaves the parent + // null, presumably because it adds it to a stacked widget after ctr which takes ownership as parent. The issue with this is that you *can't* + // do anything relative to the parent in this construction setup because it is not known at time of construction. + + // possibly more important, we can't pass the model(s) via the ctr which is particularly unpleasant, because we have to declare a public interface + // do set them after ctr. The issue with this is that the models should be immutable once set, which is most cleanly accomplished by passing the + // deps via the ctr, NOT a setter. + + _itemMenu = new QMenu(this); + _itemMenu->setObjectName("folderOptionsMenu"); + _itemMenu->setAccessibleName(tr("Sync options menu")); + + buildView(); +} + +void AccountFoldersView::buildView() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(); + //: This shows as 'Folder sync' or 'Space sync' + QString titleString = tr("%1 sync").arg(CommonStrings::capSpace()); + QLabel *titleLabel = new QLabel(titleString, this); + QFont f = titleLabel->font(); + f.setBold(true); + titleLabel->setFont(f); + titleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + mainLayout->addWidget(titleLabel, 0, Qt::AlignLeft); + + QHBoxLayout *buttonLineLayout = new QHBoxLayout(); + QString descString = tr("Manage your synced %1.").arg(CommonStrings::spaces()); + QLabel *description = new QLabel(descString, this); + description->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + buttonLineLayout->addWidget(description, 0, Qt::AlignLeft); + + //: This shows as 'Add new folder sync…' or 'Add new Space sync…' + QString addSyncString = tr("Add new %1 sync…").arg(CommonStrings::space()); + _addFolderButton = new QPushButton(addSyncString, this); + _addFolderButton->setObjectName("addFolderSyncButton"); + _addFolderButton->setFocusPolicy(Qt::StrongFocus); + connect(_addFolderButton, &QPushButton::clicked, this, &AccountFoldersView::addFolderTriggered); + buttonLineLayout->addStretch(1); + buttonLineLayout->addWidget(_addFolderButton, 0, Qt::AlignRight); + mainLayout->addLayout(buttonLineLayout); + + _treeView = new QTreeView(this); + _treeView->setObjectName("accountFoldersTreeView"); + _treeView->setAccessibleName(tr("%1 list view").arg(CommonStrings::space())); + _treeView->setAccessibleDescription(tr("Navigate the %1 list using the up and down arrows").arg(CommonStrings::space())); + _treeView->setFocusPolicy(Qt::StrongFocus); + _treeView->installEventFilter(this); + + _treeView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _treeView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + // indentation is "required" to show the expanders on folder when there are errors + // I played around with this a lot and I think letting the default indent ride is the best choice, otherwise it can be hard + // to trigger the expander consistently, among other things + //_treeView->setIndentation(10); + + _treeView->setItemsExpandable(true); + _treeView->setExpandsOnDoubleClick(true); + _treeView->setSelectionMode(QAbstractItemView::SingleSelection); + _treeView->setSelectionBehavior(QAbstractItemView::SelectRows); + _treeView->setAllColumnsShowFocus(true); + + // this should allow error items to paint fully on multiple lines just using the default impl + //_treeView->setWordWrap(true); + // ha! no that does not work at all...google says it's been broken for at least 15 years. + // nice job guys. + + _treeView->header()->setSectionsMovable(false); + _treeView->setHeaderHidden(true); + + // this method of correcting the row selection color to match the app button highlight does not work when user has changed system settings (the color sticks + // with the color that matched the system settings on creation of the view, which is bad. this color correction has been moved to the item delegates for now + // because we can always grab the true current color on paint. + // TODO: #60 - if we really need to implement full palettes for both light and dark mode, my rec would be to use a QStyle sub but will + // make a decision when the time comes. + QStyleOptionButton buttonStyle; + QPalette treePalette = _treeView->palette(); + treePalette.setColor(QPalette::Highlight, buttonStyle.palette.color(QPalette::Highlight)); + _treeView->setPalette(treePalette); + + + FolderItemDelegate *delegate = new FolderItemDelegate(_treeView->indentation(), _treeView); + _treeView->setItemDelegateForColumn(0, delegate); + // note this is not the normal ellipses character, it's vertically centered instead of positioned at font baseline. This is better + // for this button than normal ellipses. We also have an elipses icon (core/more.svg) but it looks quite bad in the button so text it is + ButtonDelegate *buttonDel = new ButtonDelegate("⋯", _treeView); + buttonDel->setMenu(_itemMenu); + _treeView->setItemDelegateForColumn(1, buttonDel); + + _treeView->setEditTriggers(QAbstractItemView::CurrentChanged | QAbstractItemView::EditKeyPressed | QAbstractItemView::SelectedClicked); + + _treeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(_treeView, &QWidget::customContextMenuRequested, this, &AccountFoldersView::popItemMenu); + + mainLayout->addWidget(_treeView); + + _syncedFolderCountLabel = new QLabel("placeholder for sync count", this); + _syncedFolderCountLabel->setObjectName("syncedFolderCount"); + _syncedFolderCountLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + mainLayout->addWidget(_syncedFolderCountLabel, 0, Qt::AlignLeft); + + setLayout(mainLayout); +} + +bool AccountFoldersView::performBizarreSetupOnTreeView() +{ + if (_firstShowAfterCreation && _treeView->model()->rowCount(QModelIndex()) > 0) { + QModelIndex current = _treeView->currentIndex(); + // this condition matches case when using selective sync, after adding first folder + // no I can't explain why this "helps" - just trial and error with this mess + if (current.isValid()) + _treeView->selectionModel()->clear(); + + current = _treeView->model()->index(0, 1); + _treeView->setCurrentIndex(current); + + // clear the selection no matter what! on first show there should be no item selected, else the automatic tree edit mode using + // tab into tree or direct mouse click doesn't work for the "default" selected item + _treeView->selectionModel()->clear(); + _firstShowAfterCreation = false; + return true; + } + return false; +} + +// this really belongs in a tree view controller... +bool AccountFoldersView::eventFilter(QObject *obj, QEvent *ev) +{ + if (obj == _treeView) { + QModelIndex current = _treeView->currentIndex(); + // this has been the only way I've found so far to ensure the FocusIn behavior works correctly on first focus enter in the tree after show + // this should never be necessary but it's unclear why exactly first focus is so broken (using tab or direct mouse click) + if (ev->type() == QEvent::ShowToParent) { + return performBizarreSetupOnTreeView(); + } + + // from here out event handling is fairly comprehensible. + + // this ensures that if a tree item is in edit mode, but is currently scrolled out of view, hitting edit trigger scrolls to the item + // before popping the button menu + if (ev->type() == QEvent::ShortcutOverride) { + _treeView->scrollTo(current); + return true; + } + + // if I don't include showToParent in this, direct clicking the selected item on first show is fubar - menu pops but in a very strange location + if (ev->type() == QEvent::FocusIn) { + QFocusEvent *focus = dynamic_cast(ev); + if (!focus) + return false; + + if (focus->reason() == Qt::TabFocusReason) { + // cover case where user is tabbing into the tree when there is no selection yet + if (!current.isValid()) { + current = _treeView->model()->index(0, 1); + _treeView->setCurrentIndex(current); + } + _treeView->scrollTo(current); + if (current.flags() & Qt::ItemIsEditable) + _treeView->edit(current); + return true; + } + if (focus->reason() == Qt::MouseFocusReason) { + if (current.flags() & Qt::ItemIsEditable) { + _treeView->edit(current); + return true; + } else { + // this helps ensure no stray editor shows when clicking on an error row + _treeView->edit(QModelIndex()); + } + return false; + } + // this happens if you return the the client after switching to a different app, and the tree was focused before you left + if (focus->reason() == Qt::ActiveWindowFocusReason) { + _treeView->scrollTo(current); + if (current.flags() & Qt::ItemIsEditable) + _treeView->edit(current); + return true; + } + } + } + return false; +} + +void AccountFoldersView::setItemModels(QStandardItemModel *model, QItemSelectionModel *selectionModel) +{ + // order here is important, if you set the selection model before the main model the selection model for the view + // reverts to the "default" selectionModel. + // also the tree view doesn't manage the lifetime of the selection model so we delete the default here + // and no we can't just use the default selection model directly as it doesn't exist when the model controller + // is being set up :/ Thank you, .ui impl + _treeView->setModel(model); + QItemSelectionModel *origSelectionModel = _treeView->selectionModel(); + _treeView->setSelectionModel(selectionModel); + origSelectionModel->deleteLater(); + + // these settings can only work if/when the model has been added to the view because the header "data" comes from the model. + // without the header data the logical indexes do not exist so...we can't do it in the ctr unless we inject the models there. + // but we can't do that either as the view is currently created by the .ui impl, which can't take those params. + QHeaderView *header = _treeView->header(); + header->setStretchLastSection(false); + header->setSectionResizeMode(0, QHeaderView::Stretch); + // ResizeToContents is required to allow the button delegate size hint to work - else it defaults to 100px wide regardless + // of other settings. On windows the button is much wider, even with this setting. + header->setSectionResizeMode(1, QHeaderView::ResizeToContents); +} + +void AccountFoldersView::setSyncedFolderCount(int synced, int total) +{ + _syncedFolderCountLabel->setText(tr("%1 out of %2 %3 are synchronized").arg(QString::number(synced), QString::number(total), CommonStrings::spaces())); +} + +void AccountFoldersView::enableAddFolder(bool enableAdd) +{ + _addFolderButton->setEnabled(enableAdd); +} + +void AccountFoldersView::setFolderActions(QList actions) +{ + if (_itemMenu) { + _itemMenu->clear(); + _itemMenu->addActions(actions); + connect(_itemMenu, &QMenu::aboutToShow, this, &AccountFoldersView::refreshMenu); + } +} + +void AccountFoldersView::refreshMenu() +{ + emit requestActionsUpdate(); +} + +void AccountFoldersView::popItemMenu(const QPoint &pos) +{ + // only pop the menu on folder items. TODO: possibly work out a secondary menu to allow copy of any sync error(s) + // shown in the child items. the old folder list had no copy function that I know of so I see this as a nice to have, not a must + if (!_treeView->currentIndex().parent().isValid()) + _itemMenu->exec(_treeView->viewport()->mapToGlobal(pos)); +} +} diff --git a/src/gui/FoldersGui/accountfoldersview.h b/src/gui/FoldersGui/accountfoldersview.h new file mode 100644 index 00000000000..73a1058f278 --- /dev/null +++ b/src/gui/FoldersGui/accountfoldersview.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +class QStandardItemModel; +class QItemSelectionModel; +class QTreeView; +class QLabel; +class QPushButton; +class QMenu; + +namespace OCC { + +class AccountFoldersView : public QWidget +{ + Q_OBJECT +public: + explicit AccountFoldersView(QWidget *parent = nullptr); + + void setItemModels(QStandardItemModel *model, QItemSelectionModel *selectionModel); + void setFolderActions(QList actions); + void setSyncedFolderCount(int synced, int total); + void enableAddFolder(bool enableAdd); + void setMenuActions(QList actions); + +signals: + void addFolderTriggered(); + void requestActionsUpdate(); + +protected: + bool eventFilter(QObject *obj, QEvent *ev) override; + +private: + void buildView(); + void popItemMenu(const QPoint &pos); + void refreshMenu(); + bool performBizarreSetupOnTreeView(); + + QTreeView *_treeView = nullptr; + QLabel *_syncedFolderCountLabel = nullptr; + QPushButton *_addFolderButton = nullptr; + QMenu *_itemMenu = nullptr; + bool _firstShowAfterCreation = true; +}; +} diff --git a/src/gui/FoldersGui/buttondelegate.cpp b/src/gui/FoldersGui/buttondelegate.cpp new file mode 100644 index 00000000000..88db7ad3874 --- /dev/null +++ b/src/gui/FoldersGui/buttondelegate.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "buttondelegate.h" +#include "commonstrings.h" + +#include +#include +#include +#include +#include +#include + +namespace OCC { + +ButtonDelegate::ButtonDelegate(const QString &text, QAbstractItemView *parent) + : QItemDelegate{parent} + , _buttonText(text) +{ + // note we will update the widget parent in the first createEditor as that passes the correct parent for the pop + // we can't really get the "right" parent here, and reusing the button is simpler and I'd guess slightly more efficient + // than creating it over and over in create editor. + + _button = new QPushButton(_buttonText); + _button->setObjectName("buttonDelegateButton"); + _button->setFocusPolicy(Qt::StrongFocus); + _button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + _button->setStyleSheet("QPushButton::menu-indicator{width:0px;}"); + + _button->setAccessibleName(tr("%1 options button").arg(CommonStrings::capSpace())); + _button->setAccessibleDescription(tr("Menu button with %1 options. Use the space key to show the menu").arg(CommonStrings::space())); +} + +void ButtonDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt(option); + QStyleOptionButton buttonStyle; + opt.palette.setColor(QPalette::Highlight, buttonStyle.palette.color(QPalette::Highlight)); + drawBackground(painter, opt, index); + + painter->save(); + // this rigamarole is "needed" because if I set the button to autoFillBackground (which is super normal) there are weird artifacts of + // the window color above and below the button. No idea. This is on mac at least. The fix is to not paint the text dots if the button + // is showing on the current cell, as if we paint them there is bleed through visibility of the text under the button since we apparently + // autoFillBackground has to be off :/ + if (!index.parent().isValid() && (!_button->isVisible() || !option.rect.contains(_button->pos()))) { + QFont f = painter->font(); + f.setBold(true); + f.setPixelSize(18); + painter->setFont(f); + painter->drawText(option.rect, Qt::AlignCenter, _buttonText); + } + + painter->setPen(QPen(QBrush("#807F7F7F"), 1)); + QRect r = option.rect; + r.setRight(r.right() - 10); + painter->drawLine(r.bottomLeft(), r.bottomRight()); + painter->restore(); +} + +QSize ButtonDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QSize baseSize = QItemDelegate::sizeHint(option, index); + int width = _button->sizeHint().width(); + return QSize(width + 20, baseSize.height()); +} + +QWidget *ButtonDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(option); + Q_UNUSED(index); + if (_button->parentWidget() != parent) + // the parent is actually the scroll area viewport. don't ask :D + _button->setParent(parent); + return _button; +} + + +void ButtonDelegate::destroyEditor(QWidget *widget, const QModelIndex &index) const +{ + Q_UNUSED(widget); + Q_UNUSED(index); + // base impl calls delete later on the editor widget. + // but we don't really want or need to delete the button, just re-use it. it should go away naturally when the + // viewport is deleted, as we reparent to that in createEditor. + // this is all pretty weird but logical if you investigate it ;) +} + +void ButtonDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(index); + Q_ASSERT(editor == _button); + + int xOffset = (option.rect.width() - _button->width()) / 2; + int yOffset = (option.rect.height() - _button->height()) / 2; + _button->move(option.rect.left() + xOffset, option.rect.top() + yOffset); +} + +bool ButtonDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) +{ + // implement the menu auto-pop when clicking the delegate directly + // super important! we can't use MouseButtonPress or the menu doesn't pop when eg the tree is not already focused + // no idea why this is but release is the way to go, 100% + if (event->type() == QEvent::MouseButtonRelease) { + QAbstractItemView *view = qobject_cast(parent()); + if (view) { + QModelIndex current = view->currentIndex(); + if (current != index) + view->setCurrentIndex(index); + _button->setVisible(true); + _button->showMenu(); + return true; + } + } + return QItemDelegate::editorEvent(event, model, option, index); +} + +void ButtonDelegate::setMenu(QMenu *menu) +{ + _button->setMenu(menu); +} + +} diff --git a/src/gui/FoldersGui/buttondelegate.h b/src/gui/FoldersGui/buttondelegate.h new file mode 100644 index 00000000000..b4565c6faa6 --- /dev/null +++ b/src/gui/FoldersGui/buttondelegate.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +class QPushButton; +class QAbstractItemView; + +namespace OCC { + +class ButtonDelegate : public QItemDelegate +{ +public: + ButtonDelegate(const QString &text, QAbstractItemView *parent); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + // void closeEditor() + void destroyEditor(QWidget *editor, const QModelIndex &index) const override; + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; + void setMenu(QMenu *menu); + +private: + QPushButton *_button = nullptr; + QString _buttonText; + bool _clickThrough = false; +}; +} diff --git a/src/gui/FoldersGui/foldererroritem.cpp b/src/gui/FoldersGui/foldererroritem.cpp new file mode 100644 index 00000000000..530b22aef15 --- /dev/null +++ b/src/gui/FoldersGui/foldererroritem.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "foldererroritem.h" +#include "folderitem.h" + +namespace OCC { + +FolderErrorItem::FolderErrorItem(FolderItem *parent) + : QStandardItem() + , _parent(parent) +{ +} + +} diff --git a/src/gui/FoldersGui/foldererroritem.h b/src/gui/FoldersGui/foldererroritem.h new file mode 100644 index 00000000000..283226b65d0 --- /dev/null +++ b/src/gui/FoldersGui/foldererroritem.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +namespace OCC { + +class FolderItem; + +/** + * @brief The FolderErrorItem class will present any errors that arise on sync + * + * the error items will be children of the related FolderItem + * + */ +class FolderErrorItem : public QStandardItem +{ + FolderErrorItem(FolderItem *parent); + +private: + FolderItem *_parent; +}; +} diff --git a/src/gui/FoldersGui/folderitem.cpp b/src/gui/FoldersGui/folderitem.cpp new file mode 100644 index 00000000000..3a25690cdf3 --- /dev/null +++ b/src/gui/FoldersGui/folderitem.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "folderitem.h" + +#include "configfile.h" +#include "folderitemupdater.h" +#include "progressdispatcher.h" +#include "theme.h" + +namespace OCC { + + +FolderItem::FolderItem(Folder *folder) + : QStandardItem() + , _folder(folder) +{ + if (_folder == nullptr) + return; + + setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + + _updater = new FolderItemUpdater(this); + updateStatusString(); + updateImage(); +} + +FolderItem::~FolderItem() +{ + if (_updater) { + delete _updater; + _updater = nullptr; + } +} + +void FolderItem::refresh() +{ + updateStatusString(); + emitDataChanged(); +} + +void FolderItem::setProgress(const ProgressInfo &progress) +{ + if (progress.status() == ProgressInfo::Done) { + // this might be premature - if so use one of the completion states from folder sync result + _totalSize = 0; + _completedSize = 0; + _percentComplete = 0; + _estimatedDownBw = 0; + _estimatedUpBw = 0; + return; + } + + if (progress.totalSize() == 0) { + // nothing is going to happen, visually so ditch - with vfs this will always be the case? + return; + } + + // total size is literal - meaning if there was nothing to actually sync, total size = 0 + // this can happen if there are no files/folders in a space + // or of the space is already 100% up to date, so no diffs, + // or if there is nothing to "move" between server/client (move or delete) + _totalSize = progress.totalSize(); + _completedSize = progress.completedSize(); + // the old impl added extra "bytes" to the calculation to account for "contentless" operations (move, delete, possibly vfs placeholder creation) + // but I'm going with the simple calc because my strong gut feeling is that these tiny updates won't take long enough to substantially change the + // info in the gui. If someone complains we can make it more complicated again. + _percentComplete = qBound(0, qRound(double(_completedSize) / double(_totalSize) * 100.0), 100); + + _estimatedUpBw = 0; + _estimatedDownBw = 0; + + for (const auto &citm : progress._currentItems) { + // the idea here is the total available bandwidth will be "shared" among multiple items. So summing their individual bw consumption reveals + // the total bw up or down. + if (citm._item._direction == SyncFileItem::Up) { + _estimatedUpBw += progress.fileProgress(citm._item).estimatedBandwidth; + } else { + _estimatedDownBw += progress.fileProgress(citm._item).estimatedBandwidth; + } + } + + refresh(); +} + +Folder *FolderItem::folder() +{ + return _folder; +} + +QString FolderItem::statusIconName() const +{ + if (!_folder || !_folder->accountState()) + return {}; + SyncResult status = _folder->syncResult(); + if (!_folder->isConnected()) { + status.setStatus(SyncResult::Status::Offline); + } else if (_folder->syncPaused() || NetworkInformation::instance()->isBehindCaptivePortal() + || (NetworkInformation::instance()->isMetered() && ConfigFile().pauseSyncWhenMetered())) { + status.setStatus(SyncResult::Status::Paused); + } + return QString("states/%1").arg(Theme::instance()->syncStateIconName(status)); +} + +QString FolderItem::statusAsString() const +{ + if (!_folder || !_folder->accountState()) + return {}; + + if (!_folder->isConnected()) + return tr("Offline"); + + // this can get out of sorts wrt sync state if we were offline then go on - no sync is ever attempted if the folder is already + // marked unavailable, so hopefully this catches the corner cases + if (!_folder->isAvailable()) + return tr("Unavailable: the space is no longer available on the server"); + + // when reloading folders from config, no sync on paused folders is attempted, so we can't rely on the sync status + if (_folder->syncPaused()) + return tr("Sync paused"); + + SyncResult status = _folder->syncResult(); + + switch (status.status()) { + case SyncResult::SyncPrepare: + case SyncResult::Undefined: + case SyncResult::SyncAbortRequested: + return {}; + case SyncResult::Success: + return tr("Synced"); + case SyncResult::Unavailable: + return tr("Unavailable: the space is no longer available on the server"); + case SyncResult::Problem: + case SyncResult::Error: + case SyncResult::SetupError: + return tr("Sync failed"); // todo: I presume this collection of states needs refinement, will cover it when I get to the error handling + case SyncResult::Paused: + return tr("Sync paused"); + case SyncResult::Offline: + return tr("Offline"); + case SyncResult::NotYetStarted: + return tr("Sync pending"); + case SyncResult::SyncRunning: { + QString completedFormatted = Utility::octetsToString(_completedSize); + QString totalFormatted = Utility::octetsToString(_totalSize); + QString percentFormatted = QString::number(_percentComplete); + QString progress = tr("Syncing %1 of %2 (%3 %").arg(completedFormatted, totalFormatted, percentFormatted); + + if (_estimatedDownBw > 0) { + QString formattedDownBw = Utility::octetsToString(_estimatedDownBw); + progress.append(tr(", ⬇️ %1/s").arg(formattedDownBw)); + } + if (_estimatedUpBw > 0) { + QString formattedUpBw = Utility::octetsToString(_estimatedUpBw); + progress.append(tr(", ⬆️ %1/s").arg(formattedUpBw)); + } + progress.append(")"); + return progress; + } + }; + + return {}; +} + +void FolderItem::updateStatusString() +{ + // the idea here is that we only want to update the string if the current status is something we care to show. If + // a new status string is empty, just use the LAST status string until we get something new that we care about + QString newStatusString = statusAsString(); + if (!newStatusString.isEmpty()) + _statusString = newStatusString; +} + +void FolderItem::updateImage() +{ + QIcon spaceIcon; + if (_folder && _folder->space() && _folder->space()->image()) + spaceIcon = _folder->space()->image()->image(); + + if (spaceIcon.isNull()) + spaceIcon = Resources::getCoreIcon("defaultSpaceImage"); + + // sure I would like to see if they are equal before the set, but apparently there are no available ==/!= operators. + _image = spaceIcon; +} + +QVariant FolderItem::data(int role) const +{ + if (!_folder) + return QVariant(); + + switch (role) { + case Qt::DisplayRole: + return _folder->displayName(); + case Qt::AccessibleTextRole: { + //: Accessible text, read out by a screen reader. + //~ The first argument is the displayed name of the folder. + return tr("%1, sync status: %2").arg(_folder->displayName(), _statusString); + } + case Qt::DecorationRole: + return _image; + case FolderItemRoles::StatusIconRole: + return Resources::getCoreIcon(statusIconName()); + case FolderItemRoles::StatusStringRole: + return _statusString; + case FolderItemRoles::SortPriorityRole: + // everything will be sorted in descending order, multiply the priority by 100 and prefer A over Z by applying a negative factor + return QVariant::fromValue( + _folder->sortPriority() * 100 - (_folder->displayName().isEmpty() ? 0 : static_cast(_folder->displayName().at(0).toLower().unicode()))); + } + return QStandardItem::data(role); +} +} diff --git a/src/gui/FoldersGui/folderitem.h b/src/gui/FoldersGui/folderitem.h new file mode 100644 index 00000000000..901cfb99c4c --- /dev/null +++ b/src/gui/FoldersGui/folderitem.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "QStandardItem" + +// #include "foldererroritem.h" +#include "folder.h" +#include "progressdispatcher.h" + +#include +#include + +// #include "folderstatusupdater.h" + +namespace OCC { + +class FolderItemUpdater; + +enum FolderItemRoles { DataRole = Qt::UserRole, StatusIconRole, StatusStringRole, StatusInfoRole, SortPriorityRole }; + +class FolderItem : public QStandardItem +{ + Q_DECLARE_TR_FUNCTIONS(FolderItem) + +public: + FolderItem(Folder *folder); + ~FolderItem() override; + + QVariant data(int role) const override; + + void refresh(); + void setProgress(const ProgressInfo &progress); + void updateImage(); + + Folder *folder(); + +private: + QPointer _folder; + FolderItemUpdater *_updater = nullptr; + + quint64 _totalSize = 0; + quint64 _completedSize = 0; + quint64 _estimatedUpBw = 0; + quint64 _estimatedDownBw = 0; + int _percentComplete = 0; + + QString _statusString; + QIcon _image; + // ProgressInfo _progress; + // void updateProgress(OCC::Folder *folder, const OCC::ProgressInfo &progress); + + QString statusIconName() const; + QString statusAsString() const; + void updateStatusString(); +}; +} diff --git a/src/gui/FoldersGui/folderitemdelegate.cpp b/src/gui/FoldersGui/folderitemdelegate.cpp new file mode 100644 index 00000000000..206e05d2e4f --- /dev/null +++ b/src/gui/FoldersGui/folderitemdelegate.cpp @@ -0,0 +1,229 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "folderitemdelegate.h" + +#include "folderitem.h" +#include + +#include + + +namespace OCC { +FolderItemDelegate::FolderItemDelegate(int treeIndentation, QObject *parent) + : QItemDelegate{parent} + , _treeIndentation(treeIndentation) +{ +} + +void FolderItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + // this is hopefully temporary - the idea for now is if we are painting an error row, just incorporate a separate paint into this delegate + // if we want to make a dedicated delegate first check that setting the delegate per row overrides the column delegate per the docs. So far + // many QAbstractItemView settings do not actually work with a tree view - heck, some tree view functions don't work! + // so I am very wary about this and updating with a new error delegate is now much lower prio. + if (index.parent().isValid()) { + paintError(painter, option, index); + return; + } + + + QStyleOptionViewItem opt(option); + + // this paints the full row color just fine, but unfortunately it seems to erase the expander icon which is presumably drawn by the tree. + // this means we have to set the highlight color on the tree, too, but that looks icky when switching between dark and light modes + // after the app is running. At least this only affects the indent area for now, where the expander is. + // Until we have addressed todo #60 we simply have to live with this. + /* int treeIndent = 0; + const QTreeView *tree = qobject_cast(option.widget); + if (tree) + treeIndent = tree->indentation(); + opt.rect.setX(opt.rect.X() - treeIndent); +*/ + + QStyleOptionButton buttonStyle; + opt.palette.setColor(QPalette::Highlight, buttonStyle.palette.color(QPalette::Highlight)); + drawBackground(painter, opt, index); + + // calculate the line heights using current font - yes again, as we can't "store" the sizes calculated in sizeHint :/ + int firstLineHeight = 0; + int secondLineHeight = 0; + calculateLineHeights(option, firstLineHeight, secondLineHeight); + + int paintableHeight = firstLineHeight + secondLineHeight + _lineSpacing; + + // we only apply the cell border on the left side to avoid having a duplicated gap between the text and button delegate + QRect paintableArea(option.rect.left() + _cellBorder, option.rect.top() + _cellBorder, option.rect.width() - _cellBorder, paintableHeight); + // we always fit the icon into this area: + QRect iconAreaRect(paintableArea.topLeft(), QSize(paintableHeight, paintableHeight)); + + QIcon ico = index.data(Qt::DecorationRole).value(); + QPixmap decoration = ico.pixmap(paintableHeight, paintableHeight); + QSize actualIconSize = decoration.deviceIndependentSize().toSize(); + + int verticalOffset = 0; + int horizontalOffset = 0; + if (actualIconSize.height() != actualIconSize.width()) { + if (actualIconSize.height() < actualIconSize.width()) + verticalOffset = (iconAreaRect.height() - actualIconSize.height()) / 2; + else + horizontalOffset = (iconAreaRect.width() - actualIconSize.width()) / 2; + } + + QRect iconRect(iconAreaRect.left() + horizontalOffset, iconAreaRect.top() + verticalOffset, actualIconSize.width(), actualIconSize.height()); + // drawDecoration gives very very strange results on mac when the deviceIndependentSize is used - the paint location is way off, "up" and + // to the left - it looks like it may be adjusting location using dpr? Anyway it works better if we just paint it ourselves. + // drawDecoration(painter, option, iconRect, decoration); + + painter->drawPixmap(iconRect, decoration); + + painter->save(); + + QFont f = option.font; + f.setPixelSize(_namePixelSize); + painter->setFont(f); + + int nameWidth = paintableArea.width() - iconAreaRect.width() - _horizontalSpacing; + QFontMetrics metrics(f); + QString paintableString = metrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, nameWidth); + QRect firstLineRect(iconAreaRect.right() + _horizontalSpacing, paintableArea.top(), nameWidth, firstLineHeight); + + // important point -> painter::drawText, when using just a point to position it, the y position is used as baseline which does not work here + // use the function with rect arg instead for more consistent placement + painter->drawText(firstLineRect, Qt::TextSingleLine, paintableString); + + painter->setFont(option.font); + // I don't think we want to use the actual first line bounding rect because it may change depending on declenation of actual text + QRect statusIconRect(iconAreaRect.right() + _horizontalSpacing, firstLineRect.bottom() + _lineSpacing, secondLineHeight, secondLineHeight); + QIcon statusIcon = index.data(FolderItemRoles::StatusIconRole).value(); + painter->drawPixmap(statusIconRect, statusIcon.pixmap(statusIconRect.size())); + QRect secondLineRect(statusIconRect.right() + _horizontalSpacing, firstLineRect.bottom() + _lineSpacing, + paintableArea.width() - iconRect.width() - _horizontalSpacing - statusIconRect.width() - _cellBorder, secondLineHeight); + // the status strings are short enough that we should not need to elide them. + painter->drawText(secondLineRect, Qt::TextSingleLine, index.data(FolderItemRoles::StatusStringRole).toString(), nullptr); + + painter->setPen(QPen(QBrush(_separatorColor), _cellSeparatorWidth)); + painter->drawLine(option.rect.left(), option.rect.bottom(), option.rect.right(), option.rect.bottom()); + + painter->restore(); +} + +void FolderItemDelegate::paintError(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem rowOpt(option); + QStyleOptionButton buttonStyle; + rowOpt.palette.setColor(QPalette::Highlight, buttonStyle.palette.color(QPalette::Highlight)); + + + // this allows painting over the indent area which may be incorrect color after switching dark/light mode + // note we *can't* do this on the folder row because the expander is then invisible. + rowOpt.rect.setLeft(rowOpt.rect.left() - _treeIndentation * 2); + + drawBackground(painter, rowOpt, index); + + QStyleOptionViewItem opt(option); + QFontMetrics metrics(opt.font); + int lineHeight = metrics.height(); + QSize iconSize(lineHeight, lineHeight); + + int indent = calculateErrorIndent(opt); + // these placements align the error icon and text with the status icon and text of the parent folder + opt.rect.setTop(opt.rect.top() + _cellBorder); + opt.rect.setLeft(opt.rect.left() + indent); + painter->drawPixmap(QRect(opt.rect.topLeft(), iconSize), index.data(Qt::DecorationRole).value().pixmap(iconSize)); + + opt.rect.setLeft(opt.rect.left() + iconSize.width() + _horizontalSpacing); + QRect textRect = calculateErrorTextRect(opt, index); + painter->drawText(textRect, Qt::TextWordWrap, index.data(Qt::DisplayRole).toString()); + + painter->save(); + painter->setPen(QPen(QBrush(_separatorColor), _cellSeparatorWidth)); + + painter->drawLine(option.rect.left() - _treeIndentation, rowOpt.rect.bottom(), rowOpt.rect.right(), rowOpt.rect.bottom()); + + painter->restore(); +} + +QSize FolderItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + // note that for the error item the default size calculated here is nuts - the width appears to be the width of the screen? + // anyway it's really wrong. As I understand it this has something to do with the fact that the tree supports two columns + // but we only have one "column" in the model for the error rows. I tried adding an empty item to the row but that caused other + // issues so I'm just using brute force + // I will try to optimize this later but for the moment, everything looks quite good in action. + + + if (index.parent().isValid()) + return errorSizeHint(option, index); + + QSize defaultSize = QItemDelegate::sizeHint(option, index); + int firstLineHeight = 0; + int secondLineHeight = 0; + calculateLineHeights(option, firstLineHeight, secondLineHeight); + defaultSize.setHeight(firstLineHeight + secondLineHeight + _lineSpacing + (_cellBorder * 2) + _cellSeparatorWidth); + + return defaultSize; +} + +QSize FolderItemDelegate::errorSizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + int viewWidth = option.widget->width(); + + // need to compensate for the width of the button column too + int buttonColumnWidth = 0; + const QTreeView *tree = qobject_cast(option.widget); + if (tree) + buttonColumnWidth = tree->columnWidth(1); + int errorIndent = calculateErrorIndent(option); + int errorWidth = viewWidth - (errorIndent + _treeIndentation + buttonColumnWidth); + + QStyleOptionViewItem opt(option); + opt.rect.setWidth(errorWidth); + QRect textRect = calculateErrorTextRect(opt, index); + int totalHeight = _cellBorder * 2 + _cellSeparatorWidth + textRect.height(); + // weirdly, it does not seem to matter that I am not setting the correct width on the hint... + // todo: see what happens if I do. + return {viewWidth - buttonColumnWidth, totalHeight}; +} + +QRect FolderItemDelegate::calculateErrorTextRect(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QString error = index.data(Qt::DisplayRole).toString(); + QRect rect = option.fontMetrics.boundingRect(option.rect, Qt::TextWordWrap, error); + return rect; +} + +int FolderItemDelegate::calculateErrorIndent(const QStyleOptionViewItem &option) const +{ + int line1 = 0; + int line2 = 0; + calculateLineHeights(option, line1, line2); + int iconAreaWidth = line1 + line2 + _lineSpacing; + // we don't want to do anything special with the tree's indent as that offset should be baked into the option rect during paint + int totalOffset = iconAreaWidth; + return totalOffset; +} + +void FolderItemDelegate::calculateLineHeights(const QStyleOptionViewItem &option, int &firstLineHeight, int &secondLineHeight) const +{ + QFont f1 = option.font; + f1.setPixelSize(_namePixelSize); + QFontMetrics metrics1(f1); + firstLineHeight = metrics1.height(); + + QFont f2 = option.font; + QFontMetrics metrics2(f2); + secondLineHeight = metrics2.height(); +} +} diff --git a/src/gui/FoldersGui/folderitemdelegate.h b/src/gui/FoldersGui/folderitemdelegate.h new file mode 100644 index 00000000000..5150af4ddaf --- /dev/null +++ b/src/gui/FoldersGui/folderitemdelegate.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 +#include + +namespace OCC { + + +class FolderItemDelegate : public QItemDelegate +{ +public: + explicit FolderItemDelegate(int treeIndentation, QObject *parent = nullptr); + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +private: + const int _cellBorder = 10; + const int _horizontalSpacing = 10; + const int _cellSeparatorWidth = 1; + const int _namePixelSize = 14; + const int _lineSpacing = 5; + int _treeIndentation; + + // we need a gray that works with light and dark mode so this needs to be refined + const QColor _separatorColor = "#807F7F7F"; + void calculateLineHeights(const QStyleOptionViewItem &option, int &firstLineHeight, int &secondLineHeight) const; + void paintError(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + int calculateErrorIndent(const QStyleOptionViewItem &option) const; + /** + * @brief calculateErrorTextRect + * @param option for the current cell, wherin the rect has been set to the *actual* available width for the painted + * text as it will appear in the cell. Option height is irrelevant for the calculation so may be zero + * @param index of the item + * @return the bounding rectangle of the error text for the item with the option rectangle's given width, and the height is calculated + * based on the number of lines created when word wrap is applied (if necessary) + */ + QRect calculateErrorTextRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; + /** + * @brief errorSizeHint is a specialized size hint for error rows in the tree + * @param option the usual + * @param index + * @return + */ + QSize errorSizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; +} diff --git a/src/gui/FoldersGui/folderitemupdater.cpp b/src/gui/FoldersGui/folderitemupdater.cpp new file mode 100644 index 00000000000..5df63d8d137 --- /dev/null +++ b/src/gui/FoldersGui/folderitemupdater.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "folderitemupdater.h" + +#include "folder.h" +#include "folderitem.h" +#include "resources.h" +#include "space.h" + +namespace OCC { +FolderItemUpdater::FolderItemUpdater(FolderItem *item) + : QObject{nullptr} + , _item(item) +{ + if (_item && _item->folder()) { + connect(_item->folder(), &Folder::spaceChanged, this, &FolderItemUpdater::onSpaceChanged); + connect(_item->folder(), &Folder::syncStateChange, this, &FolderItemUpdater::onSyncStateChanged); + if (_item->folder()->accountState()) + connect(_item->folder()->accountState(), &AccountState::stateChanged, this, &FolderItemUpdater::onConnectedChanged); + } +} + +// space changed is emitted, basically, any time the etag changes on the drive. +// note that in spite of the fact that drive etag changes when the image is changed, we can't use this signal to trigger the image update +// because spaceChanged is emitted *before* the new image is retrieved in a second step as a consequence of updating the drive. +// This should be refactored after the folderWizard is gone and the goal should be to ensure that you can get any/all space changes, including +// an updated image, when spaceChanged is handled. +void FolderItemUpdater::onSpaceChanged() +{ + if (!_item->folder()) + return; + + // we need to possibly connect here to cover cases where a space/folder is added after start. + // in this scenario the account is already connected, so we have to use the first spaceChanged trigger + // which is associated with an emit from the folder constructor. + if (!_imageChangeConnection && _item->folder()->space()) { + _imageChangeConnection = connect(_item->folder()->space(), &GraphApi::Space::imageChanged, this, &FolderItemUpdater::onImageChanged); + onImageChanged(); + } + _item->refresh(); +} + +void FolderItemUpdater::onConnectedChanged(AccountState::State newState) +{ + if (!_item->folder()) + return; + + // we may possibly need to connect here as the account may not have been connected during folder construction (eg on loading an account + // from config) in which the space is not available (yet). So to cover that case, connect to the space (if not already connected) asap + // after connection. + if (newState == AccountState::Connected && !_imageChangeConnection && _item->folder()->space()) { + _imageChangeConnection = connect(_item->folder()->space(), &GraphApi::Space::imageChanged, this, &FolderItemUpdater::onImageChanged); + onImageChanged(); + } else { + // yes we need to drop this connection if the account is disconnected to ensure we fetch the "current" image on next connect + // in case it changed while the spaces/drives could not be updated + disconnect(_imageChangeConnection); + } + + if (newState == AccountState::Connected) + _item->updateImage(); + _item->refresh(); +} +void FolderItemUpdater::onSyncStateChanged() +{ + if (!_item->folder()) + return; + + SyncResult::Status status = _item->folder()->syncResult().status(); + if (status == SyncResult::SyncRunning) { + Q_ASSERT(!_progressInfoConnection); // ie it should not be connected already + _progressInfoConnection = connect(_item->folder(), &Folder::progressUpdate, this, &FolderItemUpdater::onProgressUpdated); + } else { + disconnect(_progressInfoConnection); + } + + _item->refresh(); + + if (status == SyncResult::Error || status == SyncResult::Problem || status == SyncResult::SetupError) { + QStringList errors = _item->folder()->syncResult().errorStrings(); + + if (_item->folder()->syncResult().hasUnresolvedConflicts()) + errors.append(tr("There are unresolved conflicts.")); + + for (const QString &error : std::as_const(errors)) { + QIcon errorIcon = Resources::getCoreIcon("states/warning"); + QStandardItem *errorItem = new QStandardItem(errorIcon, error); + errorItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); + + // just for testing to replace the normal error with something really long - will remove before merge + /* QString longError = + " I think trying to track overall total sizes using progress is just hopeless because a) when stuff is removed sync totals = 0. This means + we " "can't update a known total size using these progress values becausethey are never negative, to indicate a removal"; QStandardItem + *errorItem = new QStandardItem(errorIcon, longError); + */ + QString accessibleError = tr("Sync error: %1").arg(error); + errorItem->setData(accessibleError, Qt::AccessibleTextRole); + + QStandardItem *emptyEditorItem = new QStandardItem(); + emptyEditorItem->setFlags(Qt::NoItemFlags); + + _item->appendRow({errorItem, emptyEditorItem}); + } + + } else if (status == SyncResult::SyncPrepare && _item->hasChildren()) { + // I expect this check needs refinement - may want to wait until the sync has actually started before removing previous errors + _item->removeRows(0, _item->rowCount()); + } +} + +void FolderItemUpdater::onImageChanged() +{ + _item->updateImage(); +} + +void FolderItemUpdater::onProgressUpdated(const ProgressInfo &progress) +{ + // I think trying to track overall total sizes using progress is just hopeless because + // a) when stuff is removed sync totals = 0. This means we can't update a "known total size" using these progress values because + // they are never negative, to indicate a removal + // b) when discovery finds that nothing needs to be uploaded/downloaded, the totals are zero. I am not sure why a progress is even reported in this + // case as nothing is going to "progress" or happen at all! Maybe there is some other value in the progress that let's us identify that + // c) if sync totals are zero we don't know if it's because something was removed or there was no diff. I just don't see how this can + // work even if we try to maintain "last known totals" as these dual meaning "zeros" are a big problem. + // kw does not know total space size so we can't get it there + // calculating sizes "on the fly" by recursing a directory is expensive and would have to happen constantly, as far as I can tell, + // due to the problem of not knowing about deletion sizes (without vfs) and not knowing when user hydrates/dehydrates files (with vfs) + + // I don't think we need to worry about the other states - propagation is the state where progress data seems to be provided, but double check this + if (progress.status() == ProgressInfo::Propagation) { + // slow down the updates just a bit - the default is too incremental + if (std::chrono::steady_clock::now() - _lastProgressUpdated > ProgressUpdateTimeout) { + _item->setProgress(progress); + _lastProgressUpdated = std::chrono::steady_clock::now(); + } + } else + _lastProgressUpdated = {}; +} +} diff --git a/src/gui/FoldersGui/folderitemupdater.h b/src/gui/FoldersGui/folderitemupdater.h new file mode 100644 index 00000000000..0cbdd63721c --- /dev/null +++ b/src/gui/FoldersGui/folderitemupdater.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +#include "accountstate.h" +#include "progressdispatcher.h" +#include + +namespace OCC { + +class FolderItem; + +class FolderItemUpdater : public QObject +{ + Q_OBJECT +public: + explicit FolderItemUpdater(FolderItem *item); + + +private: + FolderItem *_item; + + void onSpaceChanged(); + void onConnectedChanged(AccountState::State newState); + void onSyncStateChanged(); + void onProgressUpdated(const ProgressInfo &progress); + void onImageChanged(); + + QMetaObject::Connection _progressInfoConnection; + QMetaObject::Connection _imageChangeConnection; + + inline static const auto ProgressUpdateTimeout = std::chrono::seconds(1); + std::chrono::steady_clock::time_point _lastProgressUpdated; +}; +} diff --git a/src/gui/FoldersGui/foldermodelcontroller.cpp b/src/gui/FoldersGui/foldermodelcontroller.cpp new file mode 100644 index 00000000000..8bad6c83650 --- /dev/null +++ b/src/gui/FoldersGui/foldermodelcontroller.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "foldermodelcontroller.h" + +#include "commonstrings.h" +#include "folder.h" +#include "folderitem.h" +#include "folderman.h" + +#include +#include + +namespace OCC { + + +FolderModelController::FolderModelController(const QUuid &accountId, QObject *parent) + : QObject(parent) + , _accountId(accountId) +{ + _model = new QStandardItemModel(this); + // first column is the real data, which is in a FolderItem, the second item is just a dumb QStandardItem that is basically + // a placeholder that can host the ButtonDelegate. + _model->setColumnCount(2); + // we have to add something to the model header to allow us to pretend we can manipulate the logical indexes before real data has been added + // to the model. Just setting the column count does nothing. Also note these get deleted on model clear so DON'T USE CLEAR + _model->setHorizontalHeaderItem(0, new QStandardItem("bling")); + _model->setHorizontalHeaderItem(1, new QStandardItem("boop")); + Q_ASSERT(_model->columnCount() == 2); + _model->setSortRole(FolderItemRoles::SortPriorityRole); + + // you can't get the selection model from the model. you have to get it from the view. this creates dependency ick so instead just create + // our own selection model and set it on the view via setModels + _selectionModel = new QItemSelectionModel(_model, this); + connect(_selectionModel, &QItemSelectionModel::currentColumnChanged, this, &FolderModelController::onCurrentChanged); + connect(_selectionModel, &QItemSelectionModel::currentRowChanged, this, &FolderModelController::onCurrentChanged); +} + +void FolderModelController::onCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + if (current.parent().isValid()) { + emit currentFolderChanged(nullptr); + return; + } + if (current.row() == previous.row()) { + _selectionModel->setCurrentIndex(_model->index(current.row(), 1), QItemSelectionModel::Current); + return; + } + // todo: don't bother with all this casting nonsense, just make a new type for folder items + QStandardItem *item = _model->item(current.row(), 0); + FolderItem *folderItem = dynamic_cast(item); + if (folderItem) { + if (current.column() != 1) + _selectionModel->setCurrentIndex(_model->index(current.row(), 1), QItemSelectionModel::Current); + emit currentFolderChanged(folderItem->folder()); + } else { + // at the moment we won't support a "current folder" value if a non-folder item is selected. + emit currentFolderChanged(nullptr); + } +} + +void FolderModelController::onFolderListChanged(const QUuid &accountId, const QList folders) +{ + // first check is just the identify whether new folder set is associated with the current account, which should not be null + if (!accountId.isNull() && _accountId != accountId) + return; + + // if the accountId is null it should mean that ALL folders in the system, regardless of account id, were removed, eg on shutdown + // so we still need to clear the model even if the accountId is null. In this case the whole model needs to be cleared + if (accountId.isNull()) { + Q_ASSERT(folders.isEmpty()); + _model->clear(); + return; + } + + // otherwise we *can't* reset the model or we lose the headers and the associated column sizing >:| + _model->invisibleRootItem()->removeRows(0, _model->rowCount()); + + _multiLoad = true; + for (const auto &f : folders) + onFolderAdded(accountId, f); + _multiLoad = false; + + _model->sort(0, Qt::DescendingOrder); + // be sure nothing is selected on first load. see the kooky handling in AccountFoldersView handleEvent - it is "necessary" to + // set and then clear the selection on ShowToParent so going in, selection should be empty. yes this needs improvement but + // the nature of always having current row in edit mode is super complicated + _selectionModel->clear(); +} + +void FolderModelController::onFolderAdded(const QUuid &accountId, Folder *folder) +{ + if (!folder || folder->id().isEmpty() || accountId != _accountId) + return; + + FolderItem *item = new FolderItem(folder); + QStandardItem *buttonItem = new QStandardItem(); + buttonItem->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); + buttonItem->setAccessibleText(tr("%1 options").arg(CommonStrings::space())); + buttonItem->setAccessibleDescription( + tr("Menu button with %1 options. Use the space key to pop the %2 options menu").arg(CommonStrings::space(), CommonStrings::space())); + + // we need a dummy item for the second column to host the button delegate for the row + _model->appendRow({item, buttonItem}); + _items.insert(folder->spaceId(), item); + + if (!_multiLoad) { + _model->sort(0, Qt::DescendingOrder); + _selectionModel->setCurrentIndex(item->index(), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); + } +} + +void FolderModelController::onFolderRemoved(const QUuid &accountId, Folder *folder) +{ + if (!folder || accountId != _accountId) + return; + + QString id = folder->spaceId(); + if (id.isEmpty()) + return; + + if (_items.contains(id)) { + QStandardItem *it = _items.value(folder->spaceId()); + QModelIndex index = it->index(); + QModelIndex previousIndex = _model->sibling(index.row() - 1, 0, index); + if (index.isValid()) + // note that removeRow(s) takes care of deleting the items in the row + _model->removeRow(it->index().row()); + _items.remove(id); + // should not need to sort on remove but it's helpful to update the selection to previous item: + _selectionModel->setCurrentIndex(previousIndex, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); + } +} + +void FolderModelController::connectSignals(FolderMan *folderMan) +{ + connect(folderMan, &FolderMan::folderListChanged, this, &FolderModelController::onFolderListChanged); + connect(folderMan, &FolderMan::folderAdded, this, &FolderModelController::onFolderAdded); + connect(folderMan, &FolderMan::folderRemoved, this, &FolderModelController::onFolderRemoved); + + // normally the folder list is updated after the views have been created but just in case it + // happened sooner: + // onFolderListChanged(_accountId, folderMan->foldersForAccount(_accountId)); + // so far I never see it happening sooner but may revisit this. +} +} diff --git a/src/gui/FoldersGui/foldermodelcontroller.h b/src/gui/FoldersGui/foldermodelcontroller.h new file mode 100644 index 00000000000..89d2ba217d2 --- /dev/null +++ b/src/gui/FoldersGui/foldermodelcontroller.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 +#include +#include +#include +#include + +class QStandardItem; +class QStandardItemModel; +class QItemSelectionModel; + + +namespace OCC { +//class FolderData; +class Folder; +class FolderMan; +class FolderItem; + +class FolderModelController : public QObject +{ + Q_OBJECT + +public: + explicit FolderModelController(const QUuid &accountId, QObject *parent); + + QStandardItemModel *itemModel() const { return _model; } + QItemSelectionModel *selectionModel() const { return _selectionModel; } + + // getting creative to allow connecting to folderman via dependency injection. its a bit weird but let's see how it is + // note we don't want to pass folderman to the ctr as we only need to connect to it, don't want it to be a member, + // and this separate connect function makes more sense it that respect + void connectSignals(FolderMan *folderman); + + signals: + void currentFolderChanged(OCC::Folder *folder); + + protected slots: + void onFolderListChanged(const QUuid &accountId, const QList folders); + void onFolderAdded(const QUuid &accountId, OCC::Folder *folder); + void onFolderRemoved(const QUuid &accountId, OCC::Folder *folder); + void onCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); + + private: + QUuid _accountId; + QHash _items; + QStandardItemModel *_model = nullptr; + QItemSelectionModel *_selectionModel = nullptr; + bool _multiLoad = false; +}; +} diff --git a/src/gui/MacOSXBundleInfo.plist b/src/gui/MacOSXBundleInfo.plist index 5d907094f20..8cfac655211 100644 --- a/src/gui/MacOSXBundleInfo.plist +++ b/src/gui/MacOSXBundleInfo.plist @@ -4,7 +4,7 @@ NSPrincipalClass NSApplication - CFBundleDevelopmentRegion + CFBundleDevelopmentRegion English CFBundleExecutable ${APPLICATION_EXECUTABLE} @@ -25,47 +25,18 @@ CFBundleShortVersionString ${MIRALL_VERSION_STRING} NSHumanReadableCopyright - (C) 2014-2021 ${APPLICATION_VENDOR} + (C) 2014-${MIRALL_VERSION_YEAR} ${APPLICATION_VENDOR} SUShowReleaseNotes SUPublicDSAKeyFile dsa_pub.pem - -UTExportedTypeDeclarations - - - UTTypeIdentifier - ${APPLICATION_REV_DOMAIN}.VIRTUALFILE - UTTypeTagSpecification - - public.filename-extension - ${APPLICATION_VIRTUALFILE_SUFFIX} - public.mime-type - application/octet-stream - - UTTypeConformsTo - - public.data - - - - -CFBundleDocumentTypes - - - CFBundleTypeName - ${APPLICATION_EXECUTABLE} Download Virtual File - CFBundleTypeRole - Editor - LSHandlerRank - Owner - LSItemContentTypes - - ${APPLICATION_REV_DOMAIN}.VIRTUALFILE - - - - - + SUPublicEDKey + F9ZGRXJE7XbQb2Kt36hwaBO4rYjkXYHiNS5hd+MkyKY= + SUAllowsAutomaticUpdates + NO + LSMinimumSystemVersion + ${CMAKE_OSX_DEPLOYMENT_TARGET} + CFBundleAllowMixedLocalizations + diff --git a/src/gui/aboutdialog.cpp b/src/gui/aboutdialog.cpp deleted file mode 100644 index c9bb2f65207..00000000000 --- a/src/gui/aboutdialog.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) by Hannah von Reth - * - * 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 "aboutdialog.h" -#include "ui_aboutdialog.h" - -#include "theme.h" -#include "guiutility.h" - -namespace OCC { - -AboutDialog::AboutDialog(QWidget *parent) - : QDialog(parent) - , ui(new Ui::AboutDialog) -{ - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - ui->setupUi(this); - setWindowTitle(tr("About %1").arg(Theme::instance()->appNameGUI())); - ui->aboutText->setText(Theme::instance()->about()); - ui->icon->setPixmap(Theme::instance()->aboutIcon().pixmap(256)); - ui->versionInfo->setText(Theme::instance()->aboutVersions(Theme::VersionFormat::RichText)); - - connect(ui->versionInfo, &QTextBrowser::anchorClicked, this, &AboutDialog::openBrowserFromUrl); - connect(ui->aboutText, &QLabel::linkActivated, this, &AboutDialog::openBrowser); -} - -AboutDialog::~AboutDialog() -{ - delete ui; -} - -void AboutDialog::openBrowser(const QString &s) -{ - Utility::openBrowser(s, this); -} - -void AboutDialog::openBrowserFromUrl(const QUrl &s) -{ - return openBrowser(s.toString()); -} - -} diff --git a/src/gui/aboutdialog.h b/src/gui/aboutdialog.h deleted file mode 100644 index 601291da684..00000000000 --- a/src/gui/aboutdialog.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) by Hannah von Reth - * - * 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. - */ -#ifndef ABOUTDIALOG_H -#define ABOUTDIALOG_H - -#include - - -namespace OCC { - -namespace Ui { - class AboutDialog; -} - -class AboutDialog : public QDialog -{ - Q_OBJECT - -public: - explicit AboutDialog(QWidget *parent = nullptr); - ~AboutDialog(); - -private: - void openBrowser(const QString &s); - void openBrowserFromUrl(const QUrl &s); - -private: - Ui::AboutDialog *ui; -}; - -} -#endif // ABOUTDIALOG_H diff --git a/src/gui/aboutdialog.ui b/src/gui/aboutdialog.ui deleted file mode 100644 index 833f1ffe02a..00000000000 --- a/src/gui/aboutdialog.ui +++ /dev/null @@ -1,162 +0,0 @@ - - - OCC::AboutDialog - - - - 0 - 0 - 751 - 391 - - - - About - - - - - - 0 - - - - About - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - 0 - - - - - 256 - 256 - - - - - - - :/client/ownCloud/theme/colored/owncloud-icon.svg - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - 0 - 0 - - - - TextLabel - - - - - - - - Versions - - - - - - false - - - false - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Ok - - - - - - - - - - - buttonBox - accepted() - OCC::AboutDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - OCC::AboutDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/gui/accountmanager.cpp b/src/gui/accountmanager.cpp index bae1a8df918..2aec3f25fdf 100644 --- a/src/gui/accountmanager.cpp +++ b/src/gui/accountmanager.cpp @@ -13,45 +13,62 @@ */ #include "accountmanager.h" +#include "account.h" #include "configfile.h" #include "creds/credentialmanager.h" -#include "sslerrordialog.h" -#include "proxyauthhandler.h" -#include "common/asserts.h" +#include "creds/credentials.h" +#include "guiutility.h" + #include -#include -#include -#include + +#ifdef Q_OS_WIN +#include "common/utility_win.h" +#endif + #include -#include +#include +// todo: move these to static const instances in the class once the tests are fixed to allow qstring from ascii namespace { -static const char urlC[] = "url"; -static const char userC[] = "user"; -static const char httpUserC[] = "http_user"; -const QString davUserC() +QString urlC() +{ + return "url"; +} + +QString defaultSyncRootC() +{ + return "default_sync_root"; +} + +QString davUserC() +{ + return "dav_user"; +} + +QString davUserDisplyNameC() { - return QStringLiteral("dav_user"); + return "display-name"; } -const QString davUserDisplyNameC() +QString userUUIDC() { - return QStringLiteral("display-name"); + return "uuid"; } -const QString userUUIDC() +QString caCertsKeyC() { - return QStringLiteral("uuid"); + return "CaCertificates"; } -static const char caCertsKeyC[] = "CaCertificates"; -static const char accountsC[] = "Accounts"; -static const char versionC[] = "version"; -static const char serverVersionC[] = "serverVersion"; +QString accountsC() +{ + return "Accounts"; +} -// The maximum versions that this client can read -static const int maxAccountsVersion = 2; -static const int maxAccountVersion = 1; +QString capabilitesC() +{ + return "capabilities"; +} } @@ -65,48 +82,35 @@ AccountManager *AccountManager::instance() return &instance; } -bool AccountManager::restore() +AccountManager *AccountManager::create(QQmlEngine *qmlEngine, QJSEngine *) { - QStringList skipSettingsKeys; - backwardMigrationSettingsKeys(&skipSettingsKeys, &skipSettingsKeys); + Q_ASSERT(qmlEngine->thread() == AccountManager::instance()->thread()); + QJSEngine::setObjectOwnership(AccountManager::instance(), QJSEngine::CppOwnership); + return instance(); +} - auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); +bool AccountManager::restore() +{ + auto settings = ConfigFile::settingsWithGroup(accountsC()); if (settings->status() != QSettings::NoError) { qCWarning(lcAccountManager) << "Could not read settings from" << settings->fileName() << settings->status(); return false; } - if (skipSettingsKeys.contains(settings->group())) { - // Should not happen: bad container keys should have been deleted - qCWarning(lcAccountManager) << "Accounts structure is too new, ignoring"; - return true; + if (settings->contains("version")) { + // Migration from pre-7.0: + settings->remove("version"); } - // If there are no accounts, check the old format. const auto &childGroups = settings->childGroups(); - if (childGroups.isEmpty() - && !settings->contains(QLatin1String(versionC))) { - restoreFromLegacySettings(); - return true; - } - - for (const auto &accountId : childGroups) { - settings->beginGroup(accountId); - if (!skipSettingsKeys.contains(settings->group())) { - if (auto acc = loadAccountHelper(*settings)) { - acc->_id = accountId; - if (auto accState = AccountState::loadFromSettings(acc, *settings)) { - auto jar = qobject_cast(acc->_am->cookieJar()); - OC_ASSERT(jar); - if (jar) - jar->restore(acc->cookieJarPath()); - addAccountState(accState); - } + for (const auto &accountIndex : childGroups) { + settings->beginGroup(accountIndex); + if (auto acc = loadAccountHelper(*settings)) { + acc->_groupIndex = accountIndex; + if (auto accState = AccountState::loadFromSettings(acc, *settings)) { + addAccountState(accState); } - } else { - qCInfo(lcAccountManager) << "Account" << accountId << "is too new, ignoring"; - _additionalBlockedAccountIds.insert(accountId); } settings->endGroup(); } @@ -114,334 +118,320 @@ bool AccountManager::restore() return true; } -void AccountManager::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys) +Account *AccountManager::createAccount(const NewAccountModel &model) { - auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); - const int accountsVersion = settings->value(QLatin1String(versionC)).toInt(); - if (accountsVersion <= maxAccountsVersion) { - const auto &childGroups = settings->childGroups(); - for (const auto &accountId : childGroups) { - settings->beginGroup(accountId); - const int accountVersion = settings->value(QLatin1String(versionC), 1).toInt(); - if (accountVersion > maxAccountVersion) { - ignoreKeys->append(settings->group()); - } - settings->endGroup(); - } - } else { - deleteKeys->append(settings->group()); - } -} - -bool AccountManager::restoreFromLegacySettings() -{ - static const auto logPrefix = QStringLiteral("Legacy settings migration: "); - - qCInfo(lcAccountManager) << logPrefix - << "restoreFromLegacySettings, checking settings group" - << Theme::instance()->appName(); - - // try to open the correctly themed settings - auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName()); + auto account = new Account(QUuid::createUuid(), model.davUser(), model.effectiveUserInfoUrl()); - // if the settings file could not be opened, the childKeys list is empty - // then try to load settings from a very old place - if (settings->childKeys().isEmpty()) { - // Now try to open the original ownCloud settings to see if they exist. - QString oCCfgFile = QDir::fromNativeSeparators(settings->fileName()); - // replace the last two segments with ownCloud/owncloud.cfg - oCCfgFile = oCCfgFile.left(oCCfgFile.lastIndexOf('/')); - oCCfgFile = oCCfgFile.left(oCCfgFile.lastIndexOf('/')); - oCCfgFile += QLatin1String("/ownCloud/owncloud.cfg"); + account->setDavDisplayName(model.displayName()); - qCInfo(lcAccountManager) << logPrefix - << "checking old config " << oCCfgFile; + Credentials *creds = new Credentials(model.authToken(), model.refreshToken(), account); + account->setCredentials(creds); -#ifdef Q_OS_WIN - Utility::NtfsPermissionLookupRAII ntfs_perm; -#endif - QFileInfo fi(oCCfgFile); - if (fi.isReadable()) { - auto oCSettings = std::make_unique(oCCfgFile, QSettings::IniFormat); - oCSettings->beginGroup(QLatin1String("ownCloud")); - - // Check the theme url to see if it is the same url that the oC config was for - QString overrideUrl = Theme::instance()->overrideServerUrlV2(); - if (!overrideUrl.isEmpty()) { - if (overrideUrl.endsWith('/')) { - overrideUrl.chop(1); - } - QString oCUrl = oCSettings->value(QLatin1String(urlC)).toString(); - if (oCUrl.endsWith('/')) { - oCUrl.chop(1); - } - - // in case the urls are equal reset the settings object to read from - // the ownCloud settings object - qCInfo(lcAccountManager) << logPrefix - << "Migrate oC config if " << oCUrl << " == " << overrideUrl << ":" - << (oCUrl == overrideUrl ? "Yes" : "No"); - if (oCUrl == overrideUrl) { - settings = std::move(oCSettings); - } - } + account->addApprovedCerts(model.trustedCertificates()); + QString syncRoot = model.defaultSyncRoot(); + if (!syncRoot.isEmpty()) { + account->setDefaultSyncRoot(syncRoot); + if (!QFileInfo::exists(syncRoot)) { + OC_ASSERT(QDir().mkpath(syncRoot)); } + Utility::markDirectoryAsSyncRoot(syncRoot, account->uuid()); } - // Try to load the single account. - if (!settings->childKeys().isEmpty()) { - if (auto acc = loadAccountHelper(*settings)) { - addAccount(acc); - return true; - } - } - return false; -} + account->setCapabilities(model.capabilities()); -void AccountManager::save(bool saveCredentials) -{ - auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); - settings->setValue(QLatin1String(versionC), maxAccountsVersion); - for (const auto &acc : qAsConst(_accounts)) { - settings->beginGroup(acc->account()->id()); - saveAccountHelper(acc->account().data(), *settings, saveCredentials); - acc->writeToSettings(*settings); - settings->endGroup(); - } - - settings->sync(); - qCInfo(lcAccountManager) << "Saved all account settings, status:" << settings->status(); + return account; } -void AccountManager::saveAccount(Account *a) +void AccountManager::save() { - qCDebug(lcAccountManager) << "Saving account" << a->url().toString(); - auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); - settings->beginGroup(a->id()); - saveAccountHelper(a, *settings, false); // don't save credentials they might not have been loaded yet - settings->endGroup(); + for (const auto &acc : std::as_const(_accounts)) { + saveAccount(acc->account()); + } - settings->sync(); - qCDebug(lcAccountManager) << "Saved account settings, status:" << settings->status(); + qCInfo(lcAccountManager) << "Saved all account settings"; } -void AccountManager::saveAccountState(AccountState *a) +void AccountManager::saveAccount(Account *account) { - qCDebug(lcAccountManager) << "Saving account state" << a->account()->url().toString(); - auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); - settings->beginGroup(a->account()->id()); - a->writeToSettings(*settings); - settings->endGroup(); + if (!account) + return; - settings->sync(); - qCDebug(lcAccountManager) << "Saved account state settings, status:" << settings->status(); -} + qCDebug(lcAccountManager) << "Saving account" << account->url().toString(); + auto settings = ConfigFile::settingsWithGroup(accountsC()); + settings->beginGroup(account->groupIndex()); -QStringList AccountManager::accountNames() const -{ - QStringList accounts; - accounts.reserve(AccountManager::instance()->accounts().size()); - for (const auto &a : AccountManager::instance()->accounts()) { - accounts << a->account()->displayName(); + settings->setValue(urlC(), account->_url.toString()); + settings->setValue(davUserC(), account->_davUser); + settings->setValue(davUserDisplyNameC(), account->_displayName); + settings->setValue(userUUIDC(), account->uuid()); + if (account->hasCapabilities()) { + settings->setValue(capabilitesC(), account->capabilities().raw()); } - std::sort(accounts.begin(), accounts.end()); - return accounts; -} - -void AccountManager::saveAccountHelper(Account *acc, QSettings &settings, bool saveCredentials) -{ - settings.setValue(QLatin1String(versionC), maxAccountVersion); - settings.setValue(QLatin1String(urlC), acc->_url.toString()); - settings.setValue(davUserC(), acc->_davUser); - settings.setValue(davUserDisplyNameC(), acc->_displayName); - settings.setValue(userUUIDC(), acc->uuid()); - settings.setValue(QLatin1String(serverVersionC), acc->_serverVersion); - if (acc->_credentials) { - if (saveCredentials) { - // Only persist the credentials if the parameter is set, on migration from 1.8.x - // we want to save the accounts but not overwrite the credentials - // (This is easier than asynchronously fetching the credentials from keychain and then - // re-persisting them) - acc->_credentials->persist(); - } - - for (auto it = acc->_settingsMap.constBegin(); it != acc->_settingsMap.constEnd(); ++it) { - settings.setValue(it.key(), it.value()); - } - - // HACK: Save http_user also as user - if (acc->_settingsMap.contains(httpUserC)) - settings.setValue(userC, acc->_settingsMap.value(httpUserC)); + if (account->hasDefaultSyncRoot()) { + settings->setValue(defaultSyncRootC(), account->defaultSyncRoot()); } // Save accepted certificates. - settings.beginGroup(QLatin1String("General")); - qCInfo(lcAccountManager) << "Saving " << acc->approvedCerts().count() << " unknown certs."; - const auto approvedCerts = acc->approvedCerts(); + settings->beginGroup("General"); + qCInfo(lcAccountManager) << "Saving " << account->approvedCerts().count() << " unknown certs."; + const auto approvedCerts = account->approvedCerts(); QByteArray certs; for (const auto &cert : approvedCerts) { certs += cert.toPem() + '\n'; } if (!certs.isEmpty()) { - settings.setValue(QLatin1String(caCertsKeyC), certs); + settings->setValue(caCertsKeyC(), certs); } - settings.endGroup(); + settings->endGroup(); + + // save the account state but only if it actually exists!!! + AccountState *state = accountState(account->uuid()); + if (state) { + state->writeToSettings(*settings); + } + settings->endGroup(); + + settings->sync(); + qCDebug(lcAccountManager) << "Saved account settings, status:" << settings->status(); +} + +QStringList AccountManager::accountNames() const +{ + QStringList accountNames; + accountNames.reserve(_accounts.count()); + for (const auto &a : _accounts) { + accountNames << a->account()->displayNameWithHost(); + } + accountNames.sort(); + return accountNames; +} - // Save cookies. - if (acc->_am) { - CookieJar *jar = qobject_cast(acc->_am->cookieJar()); - if (jar) { - qCInfo(lcAccountManager) << "Saving cookies." << acc->cookieJarPath(); - if (!jar->save(acc->cookieJarPath())) - { - qCWarning(lcAccountManager) << "Failed to save cookies to" << acc->cookieJarPath(); +bool AccountManager::accountForLoginExists(const QUrl &url, const QString &davUser) const +{ + for (const auto &state : _accounts) { + if (state && state->account()) { + Account *account = state->account(); + if (account->url() == url && account->davUser() == davUser) { + return true; } } } + return false; } -AccountPtr AccountManager::loadAccountHelper(QSettings &settings) +const QList AccountManager::accounts() const { - auto urlConfig = settings.value(QLatin1String(urlC)); + return _accounts.values(); +} + +Account *AccountManager::loadAccountHelper(QSettings &settings) +{ + QVariant urlConfig = settings.value(urlC()); if (!urlConfig.isValid()) { // No URL probably means a corrupted entry in the account settings qCWarning(lcAccountManager) << "No URL for account " << settings.group(); - return AccountPtr(); + return nullptr; } - auto acc = createAccount(); + QString user = settings.value(davUserC()).toString(); + if (user.isEmpty()) { + qCWarning(lcAccountManager) << "No user name provided for account " << settings.group(); + return nullptr; + } + QUrl url = urlConfig.toUrl(); + + QVariantMap capsValue = settings.value(capabilitesC()).value(); + Capabilities caps(url, capsValue); + QUuid uid = settings.value(userUUIDC(), QVariant::fromValue(QUuid::createUuid())).toUuid(); + // if spaces are not enabled, this is an oc10 account = nogo. + // if the starting caps are not even valid, forget all of it as well + if (!caps.isValid() || !caps.spacesSupport().enabled) { + // ignore this account and strip it from the config + qCWarning(lcAccountManager) << "The capabilities for this account " << urlConfig << " are not supported by this client"; + // this should remove all keys in the group which == the account index + settings.remove(""); + Q_ASSERT(settings.childKeys().isEmpty()); + + // also kill any credentials associated with this account. This makes me very unhappy but the creds are stored in a top level group + // and I don't want to mess up the group used for the actual account settings so just do a one off instance here. Yes it adds overhead + // but should happen so rarely I think it's worth the trade-off to safeguard the more important behavior of the account settings, which is + // ACTUALLY what we should be dealing with. + // todo: future: refactor the credentials manager to store the creds keys in the account's group! you can't create a credentialsManager without + // an account so that is where the settings belong to avoid this mess. + std::unique_ptr credsGroup = ConfigFile::settingsWithGroup("Credentials"); + QStringList credsKeys = credsGroup->allKeys(); + for (const QString &key : std::as_const(credsKeys)) { + if (key.contains(uid.toString(QUuid::WithoutBraces))) + credsGroup->remove(key); + } + return nullptr; + } - QString overrideUrl = Theme::instance()->overrideServerUrlV2(); - QString forceAuth = Theme::instance()->forceConfigAuthType(); - if (!forceAuth.isEmpty() && !overrideUrl.isEmpty()) { - // If forceAuth is set, this might also mean the overrideURL has changed. - // See enterprise issues #1126 - acc->setUrl(overrideUrl); - } else { - acc->setUrl(urlConfig.toUrl()); + // 7.0 change - this special handling can be removed in 8.0 + QString supportsSpacesKey = "supportsSpaces"; + if (settings.contains(supportsSpacesKey)) { + settings.remove(supportsSpacesKey); } - acc->_serverVersion = settings.value(QLatin1String(serverVersionC)).toString(); - acc->_davUser = settings.value(davUserC()).toString(); - acc->_displayName = settings.value(davUserDisplyNameC()).toString(); - acc->_uuid = settings.value(userUUIDC(), acc->_uuid).toUuid(); - - // We want to only restore settings for that auth type and the user value - acc->_settingsMap.insert(QLatin1String(userC), settings.value(userC)); - const QString authTypePrefix = QStringLiteral("http_"); - const auto childKeys = settings.childKeys(); - for (const auto &key : childKeys) { - if (!key.startsWith(authTypePrefix)) - continue; - acc->_settingsMap.insert(key, settings.value(key)); + if (settings.contains("version")) { + // Migration from pre-7.0: + settings.remove("version"); } - acc->setCredentials(new HttpCredentialsGui); + + + auto acc = new Account(uid, user, url); + + acc->setDavDisplayName(settings.value(davUserDisplyNameC()).toString()); + acc->setCapabilities(caps); + acc->setDefaultSyncRoot(settings.value(defaultSyncRootC()).toString()); + + acc->setCredentials(new Credentials(acc)); // now the server cert, it is in the general group - settings.beginGroup(QLatin1String("General")); - const auto certs = QSslCertificate::fromData(settings.value(caCertsKeyC).toByteArray()); + settings.beginGroup("General"); + const auto certs = QSslCertificate::fromData(settings.value(caCertsKeyC()).toByteArray()); qCInfo(lcAccountManager) << "Restored: " << certs.count() << " unknown certs."; - acc->setApprovedCerts(certs); + acc->setApprovedCerts({certs.begin(), certs.end()}); settings.endGroup(); + // 7.0 config settings maintenance: clean up legacy settings related to the old style credentials + // todo: remove this when we get to 8.0 + QString credsVersion = "http_CredentialVersion"; + if (settings.contains(credsVersion)) { + settings.remove("user"); + const QString authTypePrefix = "http_"; + const auto childKeys = settings.childKeys(); + for (const auto &key : childKeys) { + if (key.startsWith(authTypePrefix)) + settings.remove(key); + } + } + return acc; } -AccountStatePtr AccountManager::account(const QString &name) +AccountState *AccountManager::account(const QString &name) { - for (const auto &acc : qAsConst(_accounts)) { - if (acc->account()->displayName() == name) { + for (const auto &acc : std::as_const(_accounts)) { + if (acc->account()->displayNameWithHost() == name) { return acc; } } - return AccountStatePtr(); + return nullptr; } -AccountStatePtr AccountManager::account(const QUuid uuid) { +AccountState *AccountManager::accountState(const QUuid uuid) +{ return _accounts.value(uuid); } -AccountState *AccountManager::addAccount(const AccountPtr &newAccount) +AccountState *AccountManager::addAccount(Account *newAccount) { - auto id = newAccount->id(); - if (id.isEmpty() || !isAccountIdAvailable(id)) { - id = generateFreeAccountId(); + auto id = newAccount->groupIndex(); + if (id.isEmpty() || !isAccountIndexAvailable(id)) { + id = generateFreeAccountIndex(); } - newAccount->_id = id; + newAccount->_groupIndex = id; + - auto newAccountState = new AccountState(newAccount); - addAccountState(newAccountState); - return newAccountState; + return addAccountState(new AccountState(newAccount)); } void AccountManager::deleteAccount(AccountState *account) { - auto it = std::find(_accounts.begin(), _accounts.end(), account); - if (it == _accounts.end()) { + // do these notifications asap so anyone trying to use the account stops doing that + // unfortunately, if we call these early, the "button" for the account is never removed, and if it's the last + // account (so there will be none now) the wizard doesn't auto-pop to create a new one. Obviously something is out of + // whack with gui elements holding onto the pointers or who knows what. I leave this here as this should work! + // Q_EMIT accountRemoved(account); + // Q_EMIT accountsChanged(); + + if (!_accounts.contains(account->account()->uuid())) { + qDebug() << "why is the account we are trying to remove not in the accounts map?"; return; } - auto copy = *it; // keep a reference to the shared pointer so it does not delete it just yet - _accounts.erase(it); + _accounts.remove(account->account()->uuid()); + + if (account->account()->hasDefaultSyncRoot()) { + Utility::unmarkDirectoryAsSyncRoot(account->account()->defaultSyncRoot()); + } + + // todo: DC-150 try moving these to the account::cleanupForRemoval routine. I'm worried that calling these + // after the accountRemoved/accountsChanged signals could cause problems, as we had when I put those notifications + // at the start // Forget account credentials, cookies account->account()->credentials()->forgetSensitiveData(); account->account()->credentialManager()->clear(); - QFile::remove(account->account()->cookieJarPath()); - auto settings = ConfigFile::settingsWithGroup(QLatin1String(accountsC)); - settings->remove(account->account()->id()); + auto settings = ConfigFile::settingsWithGroup(accountsC()); + settings->remove(account->account()->groupIndex()); - emit accountRemoved(account); -} + // when called this way the gui stuff gets cleaned up eventually, though not as quickly as I would expect. Still it works + // better than having these calls at the start. + Q_EMIT accountRemoved(account); + Q_EMIT accountsChanged(); -AccountPtr AccountManager::createAccount() -{ - AccountPtr acc = Account::create(); - acc->setSslErrorHandler(new SslDialogErrorHandler); - connect(acc.data(), &Account::proxyAuthenticationRequired, - ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired); - return acc; + account->account()->cleanupForRemoval(); + + // it should be ok to hard delete the account state. if this starts causing trouble we should make sure the destructor + // does everything it should to ensure a clean exit. + // in this case we really don't want to put the cleanup in the hands of the event loop, as other activities can get in + // the way, eg the account wizard auto-run + delete account; + + if (_accounts.isEmpty()) + Q_EMIT lastAccountRemoved(); } void AccountManager::shutdown() { const auto accounts = std::move(_accounts); - for (const auto &acc : accounts) { - emit accountRemoved(acc.data()); + for (AccountState *acc : accounts) { + Q_EMIT accountRemoved(acc); + delete acc; } } -bool AccountManager::isAccountIdAvailable(const QString &id) const +bool AccountManager::isAccountIndexAvailable(const QString &index) const { for (const auto &acc : _accounts) { - if (acc->account()->id() == id) { + if (acc->account()->groupIndex() == index) { return false; } } - if (_additionalBlockedAccountIds.contains(id)) - return false; + return true; } -QString AccountManager::generateFreeAccountId() const +QString AccountManager::generateFreeAccountIndex() const { int i = 0; - forever { - QString id = QString::number(i); - if (isAccountIdAvailable(id)) { - return id; + while (true) { + QString index = QString::number(i); + if (isAccountIndexAvailable(index)) { + return index; } ++i; } } -void AccountManager::addAccountState(AccountState *accountState) +AccountState *AccountManager::addAccountState(AccountState *accountState) { - QObject::connect(accountState->account().data(), - &Account::wantsAccountSaved, - this, &AccountManager::saveAccount); + if (!accountState || !accountState->account()) // just bail. I have no idea why this is happening but fine. it's null and not usable + return nullptr; + + _accounts.insert(accountState->account()->uuid(), accountState); + + Account *acc = accountState->account(); + + // this slot can't be connected until the account state exists because saveAccount uses the state + connect(acc, &Account::wantsAccountSaved, this, [acc, this] { saveAccount(acc); }); + + Q_EMIT accountAdded(accountState); + Q_EMIT accountsChanged(); + accountState->checkConnectivity(); - _accounts.insert(accountState->account()->uuid(), AccountStatePtr{accountState}); - emit accountAdded(accountState); + return accountState; } } diff --git a/src/gui/accountmanager.h b/src/gui/accountmanager.h index 41232127e59..980ba1ab9f7 100644 --- a/src/gui/accountmanager.h +++ b/src/gui/accountmanager.h @@ -14,26 +14,46 @@ #pragma once +#include "gui/owncloudguilib.h" + #include "account.h" #include "accountstate.h" +#include "newaccountwizard/newaccountmodel.h" + +#include + +class QJSEngine; +class QQmlEngine; namespace OCC { /** @brief The AccountManager class @ingroup gui */ -class AccountManager : public QObject +class OWNCLOUDGUI_EXPORT AccountManager : public QObject { Q_OBJECT + Q_PROPERTY(QList accounts READ accounts() NOTIFY accountsChanged) + QML_SINGLETON + QML_ELEMENT public: static AccountManager *instance(); + + static AccountManager *create(QQmlEngine *qmlEngine, QJSEngine *); ~AccountManager() override {} /** * Saves the accounts to a given settings file */ - void save(bool saveCredentials = true); + void save(); + + /** + * @brief createAccount creates an account from scratch + * @param model contains the data used to set up the new account (usually collected from the new account wizard) + * @return the AccountPtr associated with the new account. for now. + */ + Account *createAccount(const NewAccountModel &model); /** * Creates account objects from a given settings file. @@ -47,7 +67,8 @@ class AccountManager : public QObject * Add this account in the list of saved accounts. * Typically called from the wizard */ - AccountState *addAccount(const AccountPtr &newAccount); + // todo: #28 - this should not be public and should not be called directly by any third party + AccountState *addAccount(Account *newAccount); /** * remove all accounts @@ -55,73 +76,59 @@ class AccountManager : public QObject void shutdown(); /** - * Return a map of all accounts. - * (this is a map of QSharedPointer for internal reasons, one should normally not keep a copy of them) + * Return a list of all accounts. */ - const QMap &accounts() { return _accounts; } + const QList accounts() const; /** * Return the account state pointer for an account identified by its display name */ - Q_DECL_DEPRECATED_X("Please use the uuid to specify the account") AccountStatePtr account(const QString &name); + Q_DECL_DEPRECATED_X("Please use the uuid to specify the account") AccountState *account(const QString &name); /** - * Return the account state pointer for an account identified by its display name + * Return the accountState state pointer for an accountState identified by its display name */ - AccountStatePtr account(const QUuid uuid); + AccountState *accountState(const QUuid uuid); /** * Delete the AccountState */ void deleteAccount(AccountState *account); - - /** - * Creates an account and sets up some basic handlers. - * Does *not* add the account to the account manager just yet. - */ - static AccountPtr createAccount(); - - /** - * Returns the list of settings keys that can't be read because - * they are from the future. - */ - static void backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys); - /** * Returns a sorted list of displayNames */ QStringList accountNames() const; -private: - // saving and loading Account to settings - void saveAccountHelper(Account *account, QSettings &settings, bool saveCredentials = true); - AccountPtr loadAccountHelper(QSettings &settings); - - bool restoreFromLegacySettings(); - - bool isAccountIdAvailable(const QString &id) const; - QString generateFreeAccountId() const; + bool accountForLoginExists(const QUrl &url, const QString &davUser) const; - // Adds an account to the tracked list, emitting accountAdded() - void addAccountState(AccountState *accountState); - -public slots: +public Q_SLOTS: /// Saves account data, not including the credentials - void saveAccount(Account *a); - - /// Saves account state data, not including the account - void saveAccountState(AccountState *a); - + void saveAccount(Account *account /*, bool saveCredentials*/); Q_SIGNALS: void accountAdded(AccountState *account); void accountRemoved(AccountState *account); + void lastAccountRemoved(); + + // this signal is not formally connected anywhere, but it's used on the Q_PROPERTY declared for "accounts" above + // the accountManager::accounts prop is used as a model in AccountsBar.qml so the accounts are reevaluated in that gui + // any time the accountsChanged signal is emitted. + void accountsChanged(); private: + // saving and loading Account to settings + void saveAccountHelper(Account *account, QSettings &settings, bool saveCredentials = true); + Account *loadAccountHelper(QSettings &settings); + + bool isAccountIndexAvailable(const QString &index) const; + QString generateFreeAccountIndex() const; + + // Adds an account to the tracked list, emitting accountAdded() + AccountState *addAccountState(AccountState *accountState); + AccountManager() {} - QMap _accounts; - /// Account ids from settings that weren't read - QSet _additionalBlockedAccountIds; + + QMap _accounts; }; } diff --git a/src/gui/accountmodalwidget.cpp b/src/gui/accountmodalwidget.cpp new file mode 100644 index 00000000000..1cbd622c4ff --- /dev/null +++ b/src/gui/accountmodalwidget.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "accountmodalwidget.h" +#include "ui_accountmodalwidget.h" + +#include "gui/qmlutils.h" + +#include + +namespace OCC { + +AccountModalWidget::AccountModalWidget(const QString &title, QWidget *widget, QWidget *parent) + : QWidget(parent) + , ui(new Ui::AccountModalWidget) +{ + ui->setupUi(this); + ui->groupBox->setTitle(title); + ui->groupBox->layout()->addWidget(widget); + + // the internal widget may be a dialog, in which case we want to block adding "standard buttons" and + // connect to the dialog accept/reject signals instead + QDialog *dialog = qobject_cast(widget); + if (dialog) { + _widgetHasButtons = true; + connect(dialog, &QDialog::accepted, this, &AccountModalWidget::accept); + connect(dialog, &QDialog::rejected, this, &AccountModalWidget::reject); + } else { + _widgetHasButtons = false; + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &AccountModalWidget::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &AccountModalWidget::reject); + } +} + + +AccountModalWidget::AccountModalWidget(const QString &title, const QUrl &qmlSource, QObject *qmlContext, QWidget *parent) + : AccountModalWidget( + title, + [&] { + auto *out = new QmlUtils::OCQuickWidget; + out->setOCContext(qmlSource, parent, qmlContext, QJSEngine::JavaScriptOwnership); + return out; + }(), + parent) +{ +} +void AccountModalWidget::setStandardButtons(QDialogButtonBox::StandardButtons buttons) +{ + if (!_widgetHasButtons) + ui->buttonBox->setStandardButtons(buttons); +} + +QPushButton *AccountModalWidget::addButton(const QString &text, QDialogButtonBox::ButtonRole role) +{ + if (!_widgetHasButtons) + return ui->buttonBox->addButton(text, role); + return nullptr; +} + +void AccountModalWidget::accept() +{ + Q_EMIT accepted(); + Q_EMIT finished(this); +} + +void AccountModalWidget::reject() +{ + Q_EMIT rejected(); + Q_EMIT finished(this); +} + +} // OCC diff --git a/src/gui/accountmodalwidget.h b/src/gui/accountmodalwidget.h new file mode 100644 index 00000000000..c30131ec9cb --- /dev/null +++ b/src/gui/accountmodalwidget.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "gui/qmlutils.h" + +#include + +namespace OCC { + +namespace Ui { + class AccountModalWidget; +} + +// this class is basically a "wrapper" that hosts a widget/panel that we want to run in a modal concept. +// basically it adds a title to the given widget, and provides buttons (normally ok/cancel) that control the lifetime of +// the "modality" +// the class is used in the AccountView with account related stuff +// it's basically a reproduction of a modal dialog but it's embedded in the account view of the main window. +// it's also possible to use it to wrap a QDialog, such as the FolderWizard, in which case the dialog's accept/reject +// signals are taken over for the modal widget result +class AccountModalWidget : public QWidget +{ + Q_OBJECT +public: + AccountModalWidget(const QString &title, QWidget *widget, QWidget *parent); + AccountModalWidget(const QString &title, const QUrl &qmlSource, QObject *qmlContext, QWidget *parent); + + // if the widget is a QDialog, these functions silently do nothing (because the dialog buttons already exist) + void setStandardButtons(QDialogButtonBox::StandardButtons buttons); + QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role); + +public Q_SLOTS: + void accept(); + void reject(); + +Q_SIGNALS: + void accepted(); + void rejected(); + void finished(OCC::AccountModalWidget *widget); + +private: + Ui::AccountModalWidget *ui; + + bool _widgetHasButtons = false; +}; + +} // OCC diff --git a/src/gui/accountmodalwidget.ui b/src/gui/accountmodalwidget.ui new file mode 100644 index 00000000000..e5e5aa004ee --- /dev/null +++ b/src/gui/accountmodalwidget.ui @@ -0,0 +1,42 @@ + + + OCC::AccountModalWidget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 25 + + + 25 + + + + + placeholder + + + + + + + + QDialogButtonBox::StandardButton::NoButton + + + + + + + + diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp deleted file mode 100644 index 2ac102159ee..00000000000 --- a/src/gui/accountsettings.cpp +++ /dev/null @@ -1,1091 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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 "accountsettings.h" -#include "ui_accountsettings.h" - -#include "theme.h" -#include "folderman.h" -#include "folderwizard.h" -#include "folderstatusmodel.h" -#include "folderstatusdelegate.h" -#include "common/utility.h" -#include "application.h" -#include "configfile.h" -#include "account.h" -#include "accountstate.h" -#include "quotainfo.h" -#include "accountmanager.h" -#include "owncloudsetupwizard.h" -#include "creds/abstractcredentials.h" -#include "creds/httpcredentialsgui.h" -#include "tooltipupdater.h" -#include "filesystem.h" -#include "wizard/owncloudwizard.h" -#include "guiutility.h" -#include "settingsdialog.h" - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "account.h" - -namespace OCC { - -Q_LOGGING_CATEGORY(lcAccountSettings, "gui.account.settings", QtInfoMsg) - -static const char progressBarStyleC[] = - "QProgressBar {" - "border: 1px solid grey;" - "border-radius: 5px;" - "text-align: center;" - "}" - "QProgressBar::chunk {" - "background-color: %1; width: 1px;" - "}"; - -/** - * Adjusts the mouse cursor based on the region it is on over the folder tree view. - * - * Used to show that one can click the red error list box by changing the cursor - * to the pointing hand. - */ -class MouseCursorChanger : public QObject -{ - Q_OBJECT -public: - MouseCursorChanger(QObject *parent) - : QObject(parent) - { - } - - QTreeView *folderList; - FolderStatusModel *model; - -protected: - bool eventFilter(QObject *watched, QEvent *event) override - { - if (event->type() == QEvent::HoverMove) { - Qt::CursorShape shape = Qt::ArrowCursor; - auto pos = folderList->mapFromGlobal(QCursor::pos()); - auto index = folderList->indexAt(pos); - if (model->classify(index) == FolderStatusModel::RootFolder - && (FolderStatusDelegate::errorsListRect(folderList->visualRect(index), index).contains(pos) - || FolderStatusDelegate::optionsButtonRect(folderList->visualRect(index),folderList->layoutDirection()).contains(pos))) { - shape = Qt::PointingHandCursor; - } - folderList->setCursor(shape); - } - return QObject::eventFilter(watched, event); - } -}; - -AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) - : QWidget(parent) - , ui(new Ui::AccountSettings) - , _wasDisabledBefore(false) - , _accountState(accountState) - , _quotaInfo(accountState) -{ - ui->setupUi(this); - - _model = new FolderStatusModel; - _model->setAccountState(_accountState); - _model->setParent(this); - FolderStatusDelegate *delegate = new FolderStatusDelegate; - delegate->setParent(this); - - ui->_folderList->header()->hide(); - ui->_folderList->setItemDelegate(delegate); - ui->_folderList->setModel(_model); -#if defined(Q_OS_MAC) - ui->_folderList->setMinimumWidth(400); -#else - ui->_folderList->setMinimumWidth(300); -#endif - new ToolTipUpdater(ui->_folderList); - - auto mouseCursorChanger = new MouseCursorChanger(this); - mouseCursorChanger->folderList = ui->_folderList; - mouseCursorChanger->model = _model; - ui->_folderList->setMouseTracking(true); - ui->_folderList->setAttribute(Qt::WA_Hover, true); - ui->_folderList->installEventFilter(mouseCursorChanger); - - ui->selectiveSyncStatus->hide(); - - createAccountToolbox(); - connect(ui->_folderList, &QWidget::customContextMenuRequested, - this, &AccountSettings::slotCustomContextMenuRequested); - connect(ui->_folderList, &QAbstractItemView::clicked, - this, &AccountSettings::slotFolderListClicked); - connect(ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus); - connect(ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus); - connect(ui->selectiveSyncNotification, &QLabel::linkActivated, - this, &AccountSettings::slotLinkActivated); - connect(_model, &FolderStatusModel::suggestExpand, ui->_folderList, &QTreeView::expand); - connect(_model, &FolderStatusModel::dirtyChanged, this, &AccountSettings::refreshSelectiveSyncStatus); - refreshSelectiveSyncStatus(); - connect(_model, &QAbstractItemModel::rowsInserted, - this, &AccountSettings::refreshSelectiveSyncStatus); - - QAction *syncNowAction = new QAction(this); - syncNowAction->setShortcut(QKeySequence(Qt::Key_F6)); - connect(syncNowAction, &QAction::triggered, this, &AccountSettings::slotScheduleCurrentFolder); - addAction(syncNowAction); - - QAction *syncNowWithRemoteDiscovery = new QAction(this); - syncNowWithRemoteDiscovery->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_F6)); - connect(syncNowWithRemoteDiscovery, &QAction::triggered, this, &AccountSettings::slotScheduleCurrentFolderForceFullDiscovery); - addAction(syncNowWithRemoteDiscovery); - - - connect(ui->selectiveSyncApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync); - connect(ui->selectiveSyncCancel, &QAbstractButton::clicked, _model, &FolderStatusModel::resetFolders); - connect(ui->bigFolderApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync); - connect(ui->bigFolderSyncAll, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncAllPendingBigFolders); - connect(ui->bigFolderSyncNone, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncNoPendingBigFolders); - - connect(FolderMan::instance(), &FolderMan::folderListChanged, _model, &FolderStatusModel::resetFolders); - connect(this, &AccountSettings::folderChanged, _model, &FolderStatusModel::resetFolders); - - - QColor color = palette().highlight().color(); - ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name())); - - ui->connectLabel->setText(tr("No account configured.")); - - connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged); - slotAccountStateChanged(); - - connect(&_quotaInfo, &QuotaInfo::quotaUpdated, - this, &AccountSettings::slotUpdateQuota); - - ui->openBrowserButton->setVisible(false); - connect(ui->openBrowserButton, &QToolButton::clicked, this, [this]{ - qobject_cast(_accountState->account()->credentials())->openBrowser(); - }); -} - - -void AccountSettings::createAccountToolbox() -{ - QMenu *menu = new QMenu(ui->_accountToolbox); - - _toggleSignInOutAction = new QAction(tr("Log out"), this); - connect(_toggleSignInOutAction, &QAction::triggered, this, &AccountSettings::slotToggleSignInState); - menu->addAction(_toggleSignInOutAction); - - QAction *action = new QAction(tr("Remove"), this); - menu->addAction(action); - connect(action, &QAction::triggered, this, &AccountSettings::slotDeleteAccount); - - ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' ')); - ui->_accountToolbox->setMenu(menu); - ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup); -} - -QString AccountSettings::selectedFolderAlias() const -{ - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); - if (!selected.isValid()) - return ""; - return _model->data(selected, FolderStatusDelegate::FolderAliasRole).toString(); -} - -void AccountSettings::slotToggleSignInState() -{ - if (_accountState->isSignedOut()) { - _accountState->account()->resetRejectedCertificates(); - _accountState->signIn(); - } else { - _accountState->signOutByUi(); - } -} - -void AccountSettings::doExpand() -{ - // Make sure at least the root items are expanded - for (int i = 0; i < _model->rowCount(); ++i) { - auto idx = _model->index(i); - if (!ui->_folderList->isExpanded(idx)) - ui->_folderList->setExpanded(idx, true); - } -} - -void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos) -{ - const auto removeFolderAction = [this](QMenu *menu) { - return menu->addAction(tr("Remove folder sync connection"), this, &AccountSettings::slotRemoveCurrentFolder); - }; - QTreeView *tv = ui->_folderList; - QModelIndex index = tv->indexAt(pos); - if (!index.isValid()) { - return; - } else if (!_model->data(index, FolderStatusDelegate::IsReady).toBool()) { - QMenu *menu = new QMenu(tv); - menu->setAttribute(Qt::WA_DeleteOnClose); - removeFolderAction(menu); - menu->popup(QCursor::pos()); - return; - } - - if (_model->classify(index) == FolderStatusModel::SubFolder) { - QMenu *menu = new QMenu(tv); - menu->setAttribute(Qt::WA_DeleteOnClose); - - QAction *ac = menu->addAction(tr("Open local folder")); - connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentLocalSubFolder); - - QString fileName = _model->data(index, FolderStatusDelegate::FolderPathRole).toString(); - if (!QFile::exists(fileName)) { - ac->setEnabled(false); - } - - ac = menu->addAction(tr("Open folder in browser")); - auto info = _model->infoForIndex(index); - OC_ASSERT(info); - QString path = info->_folder->remotePathTrailingSlash(); - path += info->_path; - connect(ac, &QAction::triggered, this, [this, path]{ - fetchPrivateLinkUrl(_accountState->account(), path, this, [](const QString &url) { - Utility::openBrowser(url, nullptr); - }); - }); - - - menu->popup(QCursor::pos()); - return; - } - - if (_model->classify(index) != FolderStatusModel::RootFolder) { - return; - } - - tv->setCurrentIndex(index); - QString alias = _model->data(index, FolderStatusDelegate::FolderAliasRole).toString(); - bool folderPaused = _model->data(index, FolderStatusDelegate::FolderSyncPaused).toBool(); - bool folderConnected = _model->data(index, FolderStatusDelegate::FolderAccountConnected).toBool(); - auto folderMan = FolderMan::instance(); - QPointer folder = folderMan->folder(alias); - if (!folder || !OC_ENSURE(folder->isReady())) - return; - - QMenu *menu = new QMenu(tv); - - menu->setAttribute(Qt::WA_DeleteOnClose); - - QAction *ac = menu->addAction(tr("Open folder")); - connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentFolder); - - if (!ui->_folderList->isExpanded(index) && folder->supportsSelectiveSync()) { - ac = menu->addAction(tr("Choose what to sync")); - ac->setEnabled(folderConnected); - connect(ac, &QAction::triggered, this, &AccountSettings::doExpand); - } - - if (!folderPaused) { - ac = menu->addAction(tr("Force sync now")); - if (folder && folder->isSyncRunning()) { - ac->setText(tr("Restart sync")); - } - ac->setEnabled(folderConnected); - connect(ac, &QAction::triggered, this, &AccountSettings::slotForceSyncCurrentFolder); - } - - ac = menu->addAction(folderPaused ? tr("Resume sync") : tr("Pause sync")); - connect(ac, &QAction::triggered, this, &AccountSettings::slotEnableCurrentFolder); - - removeFolderAction(menu); - - if (folder->virtualFilesEnabled()) { - auto availabilityMenu = menu->addMenu(tr("Availability")); - auto availability = folder->vfs().availability(QString()); - if (availability) { - ac = availabilityMenu->addAction(Utility::vfsCurrentAvailabilityText(*availability)); - ac->setEnabled(false); - } - - ac = availabilityMenu->addAction(Utility::vfsPinActionText()); - ac->setEnabled(!availability || *availability != VfsItemAvailability::AlwaysLocal); - connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::AlwaysLocal); }); - - ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText()); - ac->setEnabled(!availability - || !(*availability == VfsItemAvailability::OnlineOnly - || *availability == VfsItemAvailability::AllDehydrated)); - connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::OnlineOnly); }); - if (!Theme::instance()->forceVirtualFilesOption()) { - ac = menu->addAction(tr("Disable virtual file support...")); - connect(ac, &QAction::triggered, this, &AccountSettings::slotDisableVfsCurrentFolder); - } - } - - if (Theme::instance()->showVirtualFilesOption() - && !folder->virtualFilesEnabled() && FolderMan::instance()->checkVfsAvailability(folder->path())) { - const auto mode = bestAvailableVfsMode(); - if (mode == Vfs::WindowsCfApi || Theme::instance()->enableExperimentalFeatures()) { - ac = menu->addAction(tr("Enable virtual file support%1...").arg(mode == Vfs::WindowsCfApi ? QString() : tr(" (experimental)"))); - connect(ac, &QAction::triggered, this, &AccountSettings::slotEnableVfsCurrentFolder); - } - } - - - menu->popup(QCursor::pos()); -} - -void AccountSettings::slotFolderListClicked(const QModelIndex &indx) -{ - if (indx.data(FolderStatusDelegate::AddButton).toBool()) { - // "Add Folder Sync Connection" - QTreeView *tv = ui->_folderList; - auto pos = tv->mapFromGlobal(QCursor::pos()); - QStyleOptionViewItem opt; - opt.initFrom(tv); - auto btnRect = tv->visualRect(indx); - auto btnSize = tv->itemDelegate(indx)->sizeHint(opt, indx); - auto actual = QStyle::visualRect(opt.direction, btnRect, QRect(btnRect.topLeft(), btnSize)); - if (!actual.contains(pos)) - return; - - if (indx.flags() & Qt::ItemIsEnabled) { - slotAddFolder(); - } else { - QToolTip::showText( - QCursor::pos(), - _model->data(indx, Qt::ToolTipRole).toString(), - this); - } - return; - } - if (_model->classify(indx) == FolderStatusModel::RootFolder) { - // tries to find if we clicked on the '...' button. - QTreeView *tv = ui->_folderList; - auto pos = tv->mapFromGlobal(QCursor::pos()); - if (FolderStatusDelegate::optionsButtonRect(tv->visualRect(indx), layoutDirection()).contains(pos)) { - slotCustomContextMenuRequested(pos); - return; - } - if (FolderStatusDelegate::errorsListRect(tv->visualRect(indx), indx).contains(pos)) { - emit showIssuesList(_model->data(indx, FolderStatusDelegate::FolderAliasRole).toString()); - return; - } - - // Expand root items on single click - if (_accountState && _accountState->state() == AccountState::Connected) { - bool expanded = !(ui->_folderList->isExpanded(indx)); - ui->_folderList->setExpanded(indx, expanded); - } - } -} - -void AccountSettings::slotAddFolder() -{ - FolderMan *folderMan = FolderMan::instance(); - folderMan->setSyncEnabled(false); // do not start more syncs. - - FolderWizard *folderWizard = new FolderWizard(_accountState->account(), this); - folderWizard->setAttribute(Qt::WA_DeleteOnClose); - - connect(folderWizard, &QDialog::accepted, this, &AccountSettings::slotFolderWizardAccepted); - connect(folderWizard, &QDialog::rejected, this, &AccountSettings::slotFolderWizardRejected); - folderWizard->open(); -} - - -void AccountSettings::slotFolderWizardAccepted() -{ - FolderWizard *folderWizard = qobject_cast(sender()); - FolderMan *folderMan = FolderMan::instance(); - - qCInfo(lcAccountSettings) << "Folder wizard completed"; - - FolderDefinition definition; - definition.localPath = FolderDefinition::prepareLocalPath( - folderWizard->field(QLatin1String("sourceFolder")).toString()); - definition.targetPath = FolderDefinition::prepareTargetPath( - folderWizard->property("targetPath").toString()); - - if (folderWizard->property("useVirtualFiles").toBool()) { - definition.virtualFilesMode = bestAvailableVfsMode(); - } - - { - QDir dir(definition.localPath); - if (!dir.exists()) { - qCInfo(lcAccountSettings) << "Creating folder" << definition.localPath; - if (!dir.mkpath(".")) { - QMessageBox::warning(this, tr("Folder creation failed"), - tr("

Could not create local folder %1.") - .arg(QDir::toNativeSeparators(definition.localPath))); - return; - } - } - FileSystem::setFolderMinimumPermissions(definition.localPath); - Utility::setupFavLink(definition.localPath); - } - - /* take the value from the definition of already existing folders. All folders have - * the same setting so far. - * The default is to not sync hidden files - */ - definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles(); - -#ifdef Q_OS_WIN - if (folderMan->navigationPaneHelper().showInExplorerNavigationPane()) - definition.navigationPaneClsid = QUuid::createUuid(); -#endif - - auto selectiveSyncBlackList = folderWizard->property("selectiveSyncBlackList").toStringList(); - - folderMan->setSyncEnabled(true); - - Folder *f = folderMan->addFolder(_accountState, definition); - if (f) { - if (definition.virtualFilesMode != Vfs::Off && folderWizard->property("useVirtualFiles").toBool()) - f->setRootPinState(PinState::OnlineOnly); - - f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, selectiveSyncBlackList); - - // The user already accepted the selective sync dialog. everything is in the white list - f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, - QStringList() << QLatin1String("/")); - folderMan->scheduleAllFolders(); - emit folderChanged(); - } -} - -void AccountSettings::slotFolderWizardRejected() -{ - qCInfo(lcAccountSettings) << "Folder wizard cancelled"; - FolderMan *folderMan = FolderMan::instance(); - folderMan->setSyncEnabled(true); -} - -void AccountSettings::slotRemoveCurrentFolder() -{ - auto folder = FolderMan::instance()->folder(selectedFolderAlias()); - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); - if (selected.isValid() && folder) { - int row = selected.row(); - - qCInfo(lcAccountSettings) << "Remove Folder alias " << folder->alias(); - QString shortGuiLocalPath = folder->shortGuiLocalPath(); - - auto messageBox = new QMessageBox(QMessageBox::Question, - tr("Confirm Folder Sync Connection Removal"), - tr("

Do you really want to stop syncing the folder %1?

" - "

Note: This will not delete any files.

") - .arg(shortGuiLocalPath), - QMessageBox::NoButton, - ocApp()->gui()->settingsDialog()); - messageBox->setAttribute(Qt::WA_DeleteOnClose); - QPushButton *yesButton = - messageBox->addButton(tr("Remove Folder Sync Connection"), QMessageBox::YesRole); - messageBox->addButton(tr("Cancel"), QMessageBox::NoRole); - connect(messageBox, &QMessageBox::finished, this, [messageBox, yesButton, folder, row, this]{ - if (messageBox->clickedButton() == yesButton) { - FolderMan::instance()->removeFolder(folder); - _model->removeRow(row); - - // single folder fix to show add-button and hide remove-button - emit folderChanged(); - } - }); - messageBox->open(); - ownCloudGui::raiseDialog(messageBox); - } -} - -void AccountSettings::slotOpenCurrentFolder() -{ - auto alias = selectedFolderAlias(); - if (!alias.isEmpty()) { - emit openFolderAlias(alias); - } -} - -void AccountSettings::slotOpenCurrentLocalSubFolder() -{ - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); - if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder) - return; - QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString(); - QUrl url = QUrl::fromLocalFile(fileName); - QDesktopServices::openUrl(url); -} - -void AccountSettings::slotEnableVfsCurrentFolder() -{ - FolderMan *folderMan = FolderMan::instance(); - QPointer folder = folderMan->folder(selectedFolderAlias()); - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); - if (!selected.isValid() || !folder) - return; - - OwncloudWizard::askExperimentalVirtualFilesFeature(this, [folder, this](bool enable) { - if (!enable || !folder) - return; - -#ifdef Q_OS_WIN - // we might need to add or remove the panel entry as cfapi brings this feature out of the box - FolderMan::instance()->navigationPaneHelper().scheduleUpdateCloudStorageRegistry(); -#endif - - // It is unsafe to switch on vfs while a sync is running - wait if necessary. - auto connection = std::make_shared(); - auto switchVfsOn = [folder, connection, this]() { - if (*connection) - QObject::disconnect(*connection); - - qCInfo(lcAccountSettings) << "Enabling vfs support for folder" << folder->path(); - - // Wipe selective sync blacklist - bool ok = false; - const auto oldBlacklist = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok); - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {}); - - // Change the folder vfs mode and load the plugin - folder->setVirtualFilesEnabled(true); - folder->setVfsOnOffSwitchPending(false); - - // Setting to Unspecified retains existing data. - // Selective sync excluded folders become OnlineOnly. - folder->setRootPinState(PinState::Unspecified); - for (const auto &entry : oldBlacklist) { - folder->journalDb()->schedulePathForRemoteDiscovery(entry); - folder->vfs().setPinState(entry, PinState::OnlineOnly); - } - folder->slotNextSyncFullLocalDiscovery(); - - FolderMan::instance()->scheduleFolder(folder); - - ui->_folderList->doItemsLayout(); - ui->selectiveSyncStatus->setVisible(false); - }; - - if (folder->isSyncRunning()) { - *connection = connect(folder, &Folder::syncFinished, this, switchVfsOn); - folder->setVfsOnOffSwitchPending(true); - folder->slotTerminateSync(); - ui->_folderList->doItemsLayout(); - } else { - switchVfsOn(); - } - }); -} - -void AccountSettings::slotDisableVfsCurrentFolder() -{ - FolderMan *folderMan = FolderMan::instance(); - QPointer folder = folderMan->folder(selectedFolderAlias()); - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); - if (!selected.isValid() || !folder) - return; - - auto msgBox = new QMessageBox( - QMessageBox::Question, - tr("Disable virtual file support?"), - tr("This action will disable virtual file support. As a consequence contents of folders that " - "are currently marked as 'available online only' will be downloaded." - "\n\n" - "The only advantage of disabling virtual file support is that the selective sync feature " - "will become available again." - "\n\n" - "This action will abort any currently running synchronization.")); - auto acceptButton = msgBox->addButton(tr("Disable support"), QMessageBox::AcceptRole); - msgBox->addButton(tr("Cancel"), QMessageBox::RejectRole); - connect(msgBox, &QMessageBox::finished, msgBox, [this, msgBox, folder, acceptButton] { - msgBox->deleteLater(); - if (msgBox->clickedButton() != acceptButton|| !folder) - return; - -#ifdef Q_OS_WIN - // we might need to add or remove the panel entry as cfapi brings this feature out of the box - FolderMan::instance()->navigationPaneHelper().scheduleUpdateCloudStorageRegistry(); -#endif - - // It is unsafe to switch off vfs while a sync is running - wait if necessary. - auto connection = std::make_shared(); - auto switchVfsOff = [folder, connection, this]() { - if (*connection) - QObject::disconnect(*connection); - - qCInfo(lcAccountSettings) << "Disabling vfs support for folder" << folder->path(); - - // Also wipes virtual files, schedules remote discovery - folder->setVirtualFilesEnabled(false); - folder->setVfsOnOffSwitchPending(false); - - // Wipe pin states and selective sync db - folder->setRootPinState(PinState::AlwaysLocal); - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {}); - - // Prevent issues with missing local files - folder->slotNextSyncFullLocalDiscovery(); - - FolderMan::instance()->scheduleFolder(folder); - - ui->_folderList->doItemsLayout(); - }; - - if (folder->isSyncRunning()) { - *connection = connect(folder, &Folder::syncFinished, this, switchVfsOff); - folder->setVfsOnOffSwitchPending(true); - folder->slotTerminateSync(); - ui->_folderList->doItemsLayout(); - } else { - switchVfsOff(); - } - }); - msgBox->open(); -} - -void AccountSettings::slotSetCurrentFolderAvailability(PinState state) -{ - OC_ASSERT(state == PinState::OnlineOnly || state == PinState::AlwaysLocal); - - FolderMan *folderMan = FolderMan::instance(); - QPointer folder = folderMan->folder(selectedFolderAlias()); - QModelIndex selected = ui->_folderList->selectionModel()->currentIndex(); - if (!selected.isValid() || !folder) - return; - - // similar to socket api: sets pin state recursively and sync - folder->setRootPinState(state); - folder->scheduleThisFolderSoon(); -} - -void AccountSettings::showConnectionLabel(const QString &message, QStringList errors) -{ - const QString errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;" - "border-width: 1px; border-style: solid; border-color: #aaaaaa;" - "border-radius:5px;"); - if (errors.isEmpty()) { - ui->connectLabel->setText(message); - ui->connectLabel->setToolTip(QString()); - ui->connectLabel->setStyleSheet(QString()); - } else { - errors.prepend(message); - const QString msg = errors.join(QLatin1String("\n")); - qCDebug(lcAccountSettings) << msg; - ui->connectLabel->setText(msg); - ui->connectLabel->setToolTip(QString()); - ui->connectLabel->setStyleSheet(errStyle); - } - ui->accountStatus->setVisible(!message.isEmpty()); -} - -void AccountSettings::slotEnableCurrentFolder(bool terminate) -{ - auto alias = selectedFolderAlias(); - - if (!alias.isEmpty()) { - FolderMan *folderMan = FolderMan::instance(); - - qCInfo(lcAccountSettings) << "Application: enable folder with alias " << alias; - bool currentlyPaused = false; - - // this sets the folder status to disabled but does not interrupt it. - Folder *f = folderMan->folder(alias); - if (!f) { - return; - } - currentlyPaused = f->syncPaused(); - if (!currentlyPaused && !terminate) { - // check if a sync is still running and if so, ask if we should terminate. - if (f->isSyncRunning()) { // its still running - auto msgbox = new QMessageBox(QMessageBox::Question, tr("Sync Running"), - tr("The syncing operation is running.
Do you want to terminate it?"), - QMessageBox::Yes | QMessageBox::No, this); - msgbox->setAttribute(Qt::WA_DeleteOnClose); - msgbox->setDefaultButton(QMessageBox::Yes); - connect(msgbox, &QMessageBox::accepted, this, [this]{ - slotEnableCurrentFolder(true); - }); - msgbox->open(); - return; - } - } - - // message box can return at any time while the thread keeps running, - // so better check again after the user has responded. - if (f->isSyncRunning() && terminate) { - f->slotTerminateSync(); - } - f->setSyncPaused(!currentlyPaused); - - // keep state for the icon setting. - if (currentlyPaused) - _wasDisabledBefore = true; - - _model->slotUpdateFolderState(f); - } -} - -void AccountSettings::slotScheduleCurrentFolder() -{ - FolderMan *folderMan = FolderMan::instance(); - if (auto folder = folderMan->folder(selectedFolderAlias())) { - folderMan->scheduleFolder(folder); - } -} - -void AccountSettings::slotScheduleCurrentFolderForceFullDiscovery() -{ - FolderMan *folderMan = FolderMan::instance(); - if (auto folder = folderMan->folder(selectedFolderAlias())) { - folder->slotWipeErrorBlacklist(); - folder->slotNextSyncFullLocalDiscovery(); - folder->journalDb()->forceRemoteDiscoveryNextSync(); - folderMan->scheduleFolder(folder); - } -} - -void AccountSettings::slotForceSyncCurrentFolder() -{ - FolderMan *folderMan = FolderMan::instance(); - if (auto selectedFolder = folderMan->folder(selectedFolderAlias())) { - // Terminate and reschedule any running sync - for (auto f : folderMan->map()) { - if (f->isSyncRunning()) { - f->slotTerminateSync(); - folderMan->scheduleFolder(f); - } - } - - selectedFolder->slotWipeErrorBlacklist(); // issue #6757 - - // Insert the selected folder at the front of the queue - folderMan->scheduleFolderNext(selectedFolder); - } -} - -void AccountSettings::slotOpenOC() -{ - if (_OCUrl.isValid()) - QDesktopServices::openUrl(_OCUrl); -} - -void AccountSettings::slotUpdateQuota(qint64 total, qint64 used) -{ - if (total > 0) { - ui->quotaProgressBar->setVisible(true); - ui->quotaProgressBar->setEnabled(true); - // workaround the label only accepting ints (which may be only 32 bit wide) - const double percent = used / (double)total * 100; - const int percentInt = qMin(qRound(percent), 100); - ui->quotaProgressBar->setValue(percentInt); - QString usedStr = Utility::octetsToString(used); - QString totalStr = Utility::octetsToString(total); - QString percentStr = Utility::compactFormatDouble(percent, 1); - QString toolTip = tr("%1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits.").arg(usedStr, totalStr, percentStr); - ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr)); - ui->quotaInfoLabel->setToolTip(toolTip); - ui->quotaProgressBar->setToolTip(toolTip); - } else { - ui->quotaProgressBar->setVisible(false); - ui->quotaInfoLabel->setToolTip(QString()); - - /* -1 means not computed; -2 means unknown; -3 means unlimited (#3940)*/ - if (total == 0 || total == -1) { - ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available.")); - } else { - QString usedStr = Utility::octetsToString(used); - ui->quotaInfoLabel->setText(tr("%1 in use").arg(usedStr)); - } - } -} - -void AccountSettings::slotAccountStateChanged() -{ - const AccountState::State state = _accountState ? _accountState->state() : AccountState::Disconnected; - if (state != AccountState::Disconnected) { - ui->sslButton->updateAccountState(_accountState); - AccountPtr account = _accountState->account(); - QUrl safeUrl(account->url()); - safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI - FolderMan *folderMan = FolderMan::instance(); - for (Folder *folder : folderMan->map()) { - _model->slotUpdateFolderState(folder); - } - - const QString server = QString::fromLatin1("%2") - .arg(Utility::escape(account->url().toString()), - Utility::escape(safeUrl.toString())); - QString serverWithUser = server; - if (AbstractCredentials *cred = account->credentials()) { - QString user = account->davDisplayName(); - if (user.isEmpty()) { - user = cred->user(); - } - serverWithUser = tr("%1 as %2").arg(server, Utility::escape(user)); - } - - switch (state) { - case AccountState::Connected: { - QStringList errors; - if (account->serverVersionUnsupported()) { - errors << tr("The server version %1 is unsupported! Proceed at your own risk.").arg(account->serverVersion()); - } - showConnectionLabel(tr("Connected to %1.").arg(serverWithUser), errors); - ui->openBrowserButton->setVisible(false); - break; - } - case AccountState::ServiceUnavailable: - showConnectionLabel(tr("Server %1 is temporarily unavailable.").arg(server)); - break; - case AccountState::MaintenanceMode: - showConnectionLabel(tr("Server %1 is currently in maintenance mode.").arg(server)); - break; - case AccountState::SignedOut: - showConnectionLabel(tr("Signed out from %1.").arg(serverWithUser)); - break; - case AccountState::AskingCredentials: { - auto cred = qobject_cast(account->credentials()); - if (cred && cred->isUsingOAuth()) { - connect(cred, &HttpCredentialsGui::authorisationLinkChanged, - this, &AccountSettings::slotAccountStateChanged, Qt::UniqueConnection); - showConnectionLabel(tr("Obtaining authorization from the browser.")); - ui->openBrowserButton->setVisible(true); - } else { - showConnectionLabel(tr("Connecting to %1...").arg(serverWithUser)); - } - break; - } - case AccountState::NetworkError: - showConnectionLabel(tr("No connection to %1 at %2.") - .arg(Utility::escape(Theme::instance()->appNameGUI()), server), - _accountState->connectionErrors()); - break; - case AccountState::ConfigurationError: - showConnectionLabel(tr("Server configuration error: %1 at %2.") - .arg(Utility::escape(Theme::instance()->appNameGUI()), server), - _accountState->connectionErrors()); - break; - case AccountState::Disconnected: - // we can't end up here as the whole block is ifdeffed - Q_UNREACHABLE(); - break; - } - } else { - // ownCloud is not yet configured. - showConnectionLabel(tr("No %1 connection configured.") - .arg(Utility::escape(Theme::instance()->appNameGUI()))); - } - - /* Allow to expand the item if the account is connected. */ - ui->_folderList->setItemsExpandable(state == AccountState::Connected); - - if (state != AccountState::Connected) { - /* check if there are expanded root items, if so, close them */ - int i; - for (i = 0; i < _model->rowCount(); ++i) { - if (ui->_folderList->isExpanded(_model->index(i))) - ui->_folderList->setExpanded(_model->index(i), false); - } - } - - // Disabling expansion of folders might require hiding the selective - // sync user interface buttons. - refreshSelectiveSyncStatus(); - - /* set the correct label for the Account toolbox button */ - if (_accountState) { - if (_accountState->isSignedOut()) { - _toggleSignInOutAction->setText(tr("Log in")); - } else { - _toggleSignInOutAction->setText(tr("Log out")); - } - } -} - -void AccountSettings::slotLinkActivated(const QString &link) -{ - // Parse folder alias and filename from the link, calculate the index - // and select it if it exists. - const QStringList li = link.split(QLatin1String("?folder=")); - if (li.count() > 1) { - QString myFolder = li[0]; - const QString alias = li[1]; - if (myFolder.endsWith(QLatin1Char('/'))) - myFolder.chop(1); - - // Make sure the folder itself is expanded - Folder *f = FolderMan::instance()->folder(alias); - QModelIndex folderIndx = _model->indexForPath(f, QString()); - if (!ui->_folderList->isExpanded(folderIndx)) { - ui->_folderList->setExpanded(folderIndx, true); - } - - QModelIndex indx = _model->indexForPath(f, myFolder); - if (indx.isValid()) { - // make sure all the parents are expanded - for (auto i = indx.parent(); i.isValid(); i = i.parent()) { - if (!ui->_folderList->isExpanded(i)) { - ui->_folderList->setExpanded(i, true); - } - } - ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection); - ui->_folderList->setCurrentIndex(indx); - ui->_folderList->scrollTo(indx); - } else { - qCWarning(lcAccountSettings) << "Unable to find a valid index for " << myFolder; - } - } -} - -AccountSettings::~AccountSettings() -{ - delete ui; -} - -void AccountSettings::refreshSelectiveSyncStatus() -{ - QString msg; - int cnt = 0; - for (Folder *folder : FolderMan::instance()->map()) { - if (folder->accountState() != _accountState || !folder->isReady()) { - continue; - } - - bool ok; - const auto &undecidedList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, &ok); - for (const auto &it : undecidedList) { - // FIXME: add the folder alias in a hoover hint. - // folder->alias() + QLatin1String("/") - if (cnt++) { - msg += QLatin1String(", "); - } - QString myFolder = (it); - if (myFolder.endsWith('/')) { - myFolder.chop(1); - } - QModelIndex theIndx = _model->indexForPath(folder, myFolder); - if (theIndx.isValid()) { - msg += QString::fromLatin1("%1") - .arg(Utility::escape(myFolder), Utility::escape(folder->alias())); - } else { - msg += myFolder; // no link because we do not know the index yet. - } - } - } - - // Some selective sync ui (either normal editing or big folder) will show - // if this variable ends up true. - bool shouldBeVisible = false; - - if (msg.isEmpty()) { - // Show the ui if the model is dirty only - shouldBeVisible = _model->isDirty() && _accountState->isConnected(); - - ui->selectiveSyncButtons->setVisible(true); - ui->bigFolderUi->setVisible(false); - ui->selectiveSyncApply->setEnabled(_model->isDirty()); - } else { - // There's a reason the big folder ui should be shown - shouldBeVisible = _accountState->isConnected(); - - ConfigFile cfg; - QString info = !cfg.confirmExternalStorage() - ? tr("There are folders that were not synchronized because they are too big: ") - : !cfg.newBigFolderSizeLimit().first - ? tr("There are folders that were not synchronized because they are external storages: ") - : tr("There are folders that were not synchronized because they are too big or external storages: "); - - ui->selectiveSyncNotification->setText(info + msg); - ui->selectiveSyncButtons->setVisible(false); - ui->bigFolderUi->setVisible(true); - ui->bigFolderApply->setEnabled(_model->isDirty()); - } - - bool wasVisible = !ui->selectiveSyncStatus->isHidden(); - if (wasVisible != shouldBeVisible) { - QSize hint = ui->selectiveSyncStatus->sizeHint(); - if (shouldBeVisible) { - ui->selectiveSyncStatus->setMaximumHeight(0); - ui->selectiveSyncStatus->setVisible(true); - doExpand(); - } - auto anim = new QPropertyAnimation(ui->selectiveSyncStatus, "maximumHeight", ui->selectiveSyncStatus); - anim->setEndValue(shouldBeVisible ? hint.height() : 0); - anim->start(QAbstractAnimation::DeleteWhenStopped); - connect(anim, &QPropertyAnimation::finished, this, [this, shouldBeVisible]() { - ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX); - if (!shouldBeVisible) { - ui->selectiveSyncStatus->hide(); - } - }); - } -} - -void AccountSettings::slotDeleteAccount() -{ - // Deleting the account potentially deletes 'this', so - // the QMessageBox should be destroyed before that happens. - auto messageBox = new QMessageBox(QMessageBox::Question, - tr("Confirm Account Removal"), - tr("

Do you really want to remove the connection to the account %1?

" - "

Note: This will not delete any files.

") - .arg(_accountState->account()->displayName()), - QMessageBox::NoButton, - this); - auto yesButton = messageBox->addButton(tr("Remove connection"), QMessageBox::YesRole); - messageBox->addButton(tr("Cancel"), QMessageBox::NoRole); - messageBox->setAttribute(Qt::WA_DeleteOnClose); - connect(messageBox, &QMessageBox::finished, this, [this, messageBox, yesButton]{ - if (messageBox->clickedButton() == yesButton) { - // Else it might access during destruction. This should be better handled by it having a QSharedPointer - _model->setAccountState(nullptr); - - auto manager = AccountManager::instance(); - manager->deleteAccount(_accountState); - manager->save(); - } - }); - messageBox->open(); -} - -bool AccountSettings::event(QEvent *e) -{ - if (e->type() == QEvent::Hide || e->type() == QEvent::Show) { - _quotaInfo.setActive(isVisible()); - } - if (e->type() == QEvent::Show) { - // Expand the folder automatically only if there's only one, see #4283 - // The 2 is 1 folder + 1 'add folder' button - if (_model->rowCount() <= 2) { - ui->_folderList->setExpanded(_model->index(0, 0), true); - } - } - return QWidget::event(e); -} - -} // namespace OCC - -#include "accountsettings.moc" diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h deleted file mode 100644 index a791101c7db..00000000000 --- a/src/gui/accountsettings.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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. - */ - -#ifndef ACCOUNTSETTINGS_H -#define ACCOUNTSETTINGS_H - -#include -#include -#include -#include -#include - -#include "folder.h" -#include "quotainfo.h" -#include "progressdispatcher.h" -#include "owncloudgui.h" - -class QModelIndex; -class QNetworkReply; -class QListWidgetItem; -class QLabel; - -namespace OCC { - -namespace Ui { - class AccountSettings; -} - -class FolderMan; - -class Account; -class AccountState; -class FolderStatusModel; - -/** - * @brief The AccountSettings class - * @ingroup gui - */ -class AccountSettings : public QWidget -{ - Q_OBJECT - Q_PROPERTY(AccountState* accountState MEMBER _accountState) - -public: - explicit AccountSettings(AccountState *accountState, QWidget *parent = nullptr); - ~AccountSettings() override; - -signals: - void folderChanged(); - void openFolderAlias(const QString &); - void showIssuesList(const QString &folderAlias); - -public slots: - void slotOpenOC(); - void slotUpdateQuota(qint64, qint64); - void slotAccountStateChanged(); - - AccountState *accountsState() { return _accountState; } - -protected slots: - void slotAddFolder(); - void slotEnableCurrentFolder(bool terminate = false); - void slotScheduleCurrentFolder(); - void slotScheduleCurrentFolderForceFullDiscovery(); - void slotForceSyncCurrentFolder(); - void slotRemoveCurrentFolder(); - void slotOpenCurrentFolder(); // sync folder - void slotOpenCurrentLocalSubFolder(); // selected subfolder in sync folder - void slotEnableVfsCurrentFolder(); - void slotDisableVfsCurrentFolder(); - void slotSetCurrentFolderAvailability(PinState state); - void slotFolderWizardAccepted(); - void slotFolderWizardRejected(); - void slotDeleteAccount(); - void slotToggleSignInState(); - void refreshSelectiveSyncStatus(); - void slotCustomContextMenuRequested(const QPoint &); - void slotFolderListClicked(const QModelIndex &indx); - void doExpand(); - void slotLinkActivated(const QString &link); - -private: - void showConnectionLabel(const QString &message, - QStringList errors = QStringList()); - bool event(QEvent *) override; - void createAccountToolbox(); - - /// Returns the alias of the selected folder, empty string if none - QString selectedFolderAlias() const; - - Ui::AccountSettings *ui; - - FolderStatusModel *_model; - QUrl _OCUrl; - bool _wasDisabledBefore; - AccountState *_accountState; - QuotaInfo _quotaInfo; - QAction *_toggleSignInOutAction; - QAction *_addAccountAction; -}; - -} // namespace OCC - -#endif // ACCOUNTSETTINGS_H diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui deleted file mode 100644 index f37cc8139da..00000000000 --- a/src/gui/accountsettings.ui +++ /dev/null @@ -1,305 +0,0 @@ - - - OCC::AccountSettings - - - - 0 - 0 - 678 - 557 - - - - Form - - - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - Connected with <server> as <user> - - - Qt::RichText - - - true - - - true - - - - - - - Re-open Browser - - - - - - - ... - - - - - - - - - - - - - 0 - 0 - - - - - - - Storage space: ... - - - Qt::PlainText - - - false - - - - - - - false - - - - 0 - 0 - - - - - 16777215 - 7 - - - - 100 - - - -1 - - - false - - - - - - - - - - 0 - 5 - - - - Qt::CustomContextMenu - - - QAbstractItemView::NoEditTriggers - - - true - - - - - - - - - - - - - 0 - 0 - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - - - Qt::RichText - - - true - - - true - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - color: red - - - - - - true - - - - - - - - - Synchronize all - - - - - - - Synchronize none - - - - - - - Apply manual changes - - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - Cancel - - - - - - - - 0 - 0 - - - - Apply - - - - - - - - - - - - - - - - SslButton - QToolButton -
sslbutton.h
-
-
- - -
diff --git a/src/gui/accountsgui/CMakeLists.txt b/src/gui/accountsgui/CMakeLists.txt new file mode 100644 index 00000000000..fb018b30aee --- /dev/null +++ b/src/gui/accountsgui/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources(owncloudGui PRIVATE + accountsguicontroller.cpp + accountsguicontroller.h + accountview.cpp + accountview.h + accountview.ui + accountviewcontroller.cpp + accountviewcontroller.h + accountplaceholderwidget.h + accountplaceholderwidget.cpp + accountmodalwidget.cpp + accountmodalwidget.h + accountmodalwidget.ui +) + diff --git a/src/gui/accountsgui/accountmodalwidget.cpp b/src/gui/accountsgui/accountmodalwidget.cpp new file mode 100644 index 00000000000..c4b922d0160 --- /dev/null +++ b/src/gui/accountsgui/accountmodalwidget.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "accountmodalwidget.h" +#include "ui_accountmodalwidget.h" + +#include "gui/qmlutils.h" + +#include + +namespace OCC { + +AccountModalWidget::AccountModalWidget(const QString &title, QWidget *widget, QWidget *parent) + : QWidget(parent) + , ui(new Ui::AccountModalWidget) +{ + ui->setupUi(this); + ui->groupBox->setTitle(title); + ui->groupBox->layout()->addWidget(widget); + + // the internal widget may be a dialog, in which case we want to block adding "standard buttons" and + // connect to the dialog accept/reject signals instead + QDialog *dialog = qobject_cast(widget); + if (dialog) { + _widgetHasButtons = true; + connect(dialog, &QDialog::accepted, this, &AccountModalWidget::accept); + connect(dialog, &QDialog::rejected, this, &AccountModalWidget::reject); + } else { + _widgetHasButtons = false; + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &AccountModalWidget::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &AccountModalWidget::reject); + } +} + + +AccountModalWidget::AccountModalWidget(const QString &title, const QUrl &qmlSource, QObject *qmlContext, QWidget *parent) + : AccountModalWidget( + title, + [&] { + auto *out = new QmlUtils::OCQuickWidget; + out->setOCContext(qmlSource, parent, qmlContext, QJSEngine::JavaScriptOwnership); + return out; + }(), + parent) +{ +} +void AccountModalWidget::setStandardButtons(QDialogButtonBox::StandardButtons buttons) +{ + if (!_widgetHasButtons) + ui->buttonBox->setStandardButtons(buttons); +} + +QPushButton *AccountModalWidget::addButton(const QString &text, QDialogButtonBox::ButtonRole role) +{ + if (!_widgetHasButtons) + return ui->buttonBox->addButton(text, role); + return nullptr; +} + +void AccountModalWidget::accept() +{ + Q_EMIT accepted(); + Q_EMIT finished(this); +} + +void AccountModalWidget::reject() +{ + Q_EMIT rejected(); + Q_EMIT finished(this); +} + +} // OCC diff --git a/src/gui/accountsgui/accountmodalwidget.h b/src/gui/accountsgui/accountmodalwidget.h new file mode 100644 index 00000000000..aedbabc7f02 --- /dev/null +++ b/src/gui/accountsgui/accountmodalwidget.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "gui/qmlutils.h" + +#include + +namespace OCC { + +namespace Ui { + class AccountModalWidget; +} + +// this class is basically a "wrapper" that hosts a widget/panel that we want to run in a modal concept. +// basically it adds a title to the given widget, and provides buttons (normally ok/cancel) that control the lifetime of +// the "modality" +// the class is used in the AccountView with account related stuff +// it's basically a reproduction of a modal dialog but it's embedded in the account view of the main window. +// it's also possible to use it to wrap a QDialog, such as the FolderWizard, in which case the dialog's accept/reject +// signals are taken over for the modal widget result +class AccountModalWidget : public QWidget +{ + Q_OBJECT +public: + AccountModalWidget(const QString &title, QWidget *widget, QWidget *parent); + AccountModalWidget(const QString &title, const QUrl &qmlSource, QObject *qmlContext, QWidget *parent); + + // if the widget is a QDialog, these functions silently do nothing (because the dialog buttons already exist) + void setStandardButtons(QDialogButtonBox::StandardButtons buttons); + QPushButton *addButton(const QString &text, QDialogButtonBox::ButtonRole role); + +public Q_SLOTS: + void accept(); + void reject(); + +Q_SIGNALS: + void accepted(); + void rejected(); + void finished(OCC::AccountModalWidget *widget); + +private: + Ui::AccountModalWidget *ui; + + bool _widgetHasButtons = false; +}; + +} // OCC diff --git a/src/gui/accountsgui/accountmodalwidget.ui b/src/gui/accountsgui/accountmodalwidget.ui new file mode 100644 index 00000000000..e5e5aa004ee --- /dev/null +++ b/src/gui/accountsgui/accountmodalwidget.ui @@ -0,0 +1,42 @@ + + + OCC::AccountModalWidget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 25 + + + 25 + + + + + placeholder + + + + + + + + QDialogButtonBox::StandardButton::NoButton + + + + + + + + diff --git a/src/gui/accountsgui/accountplaceholderwidget.cpp b/src/gui/accountsgui/accountplaceholderwidget.cpp new file mode 100644 index 00000000000..7d6c949479a --- /dev/null +++ b/src/gui/accountsgui/accountplaceholderwidget.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "accountplaceholderwidget.h" + +#include +#include + +#include "theme.h" + +namespace OCC { + +AccountPlaceholderWidget::AccountPlaceholderWidget(QWidget *parent) + : QWidget{parent} +{ + setObjectName("accountPlaceholderWidget"); + QString appName = Theme::instance()->appNameGUI(); + + QLabel *logoLabel = new QLabel({}, this); + logoLabel->setPixmap(Theme::instance()->wizardHeaderLogo().pixmap(200, 100)); + logoLabel->setAlignment(Qt::AlignCenter); + logoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + logoLabel->setAccessibleName(tr("%1 logo").arg(appName)); + + QLabel *instructionLabel = new QLabel( + tr("You don't have any connected accounts yet.\n\nPlease create an account by selecting the \"Add account...\" option on the More button menu."), this); + instructionLabel->setWordWrap(true); + instructionLabel->setAlignment(Qt::AlignCenter); + instructionLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + QLabel *footerLogoLabel = nullptr; + if (!Theme::instance()->wizardFooterLogo().isNull()) { + footerLogoLabel = new QLabel({}, this); + footerLogoLabel->setPixmap(Theme::instance()->wizardFooterLogo().pixmap(100, 52)); + footerLogoLabel->setAlignment(Qt::AlignCenter); + footerLogoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + footerLogoLabel->setAccessibleName(tr("Additional logo defined by the organization")); + } + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(50, 0, 50, 0); + layout->setSpacing(12); + layout->addStretch(1); + layout->addWidget(logoLabel, Qt::AlignCenter); + layout->addSpacing(16); + layout->addWidget(instructionLabel, Qt::AlignCenter); + if (footerLogoLabel) + layout->addWidget(footerLogoLabel, Qt::AlignCenter); + layout->addStretch(1); + setLayout(layout); +} + +} diff --git a/src/gui/accountsgui/accountplaceholderwidget.h b/src/gui/accountsgui/accountplaceholderwidget.h new file mode 100644 index 00000000000..2a99a6114ba --- /dev/null +++ b/src/gui/accountsgui/accountplaceholderwidget.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +namespace OCC { +class AccountPlaceholderWidget : public QWidget +{ + Q_OBJECT +public: + explicit AccountPlaceholderWidget(QWidget *parent = nullptr); + +}; +} diff --git a/src/gui/accountsgui/accountsguicontroller.cpp b/src/gui/accountsgui/accountsguicontroller.cpp new file mode 100644 index 00000000000..ece1debbc5d --- /dev/null +++ b/src/gui/accountsgui/accountsguicontroller.cpp @@ -0,0 +1,276 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "accountsguicontroller.h" + +#include +#include +#include +#include + +#include "accountmanager.h" +#include "accountplaceholderwidget.h" +#include "accountsgui/accountviewcontroller.h" +#include "accountstate.h" +#include "accountview.h" +#include "application.h" +#include "creds/abstractcredentials.h" +#include "folderman.h" +#include "mainwindow/mainwindow.h" +#include "newaccountwizard/newaccountbuilder.h" +#include "newaccountwizard/newaccountwizard.h" +#include "newaccountwizard/newaccountwizardcontroller.h" +#include "resources.h" + +namespace OCC { + +AccountsGuiController::AccountsGuiController(AccountManager *accountMgr, MainWindow *window, QObject *parent) + : QObject(parent) + , _accountMgr(accountMgr) + , _window(window) +{ + Q_ASSERT(_accountMgr && _window); + // load any existing accounts from the manager. + const auto accounts = _accountMgr->accounts(); + for (const auto &accountState : accounts) { + onAccountAdded(accountState); + } + if (!accounts.isEmpty()) { + _actionForAccount.value(accounts.first()->account()->uuid(), nullptr)->setChecked(true); + } + + connect(_accountMgr, &AccountManager::accountAdded, this, &AccountsGuiController::onAccountAdded); + connect(_accountMgr, &AccountManager::accountRemoved, this, &AccountsGuiController::onAccountRemoved); + connect(_accountMgr, &AccountManager::lastAccountRemoved, this, &AccountsGuiController::onLastAccountRemoved); + + if (accounts.isEmpty()) { + // this is a genuinely reasonable use of timer to execute an operation after construction and any + // other event loop actions have finished + QTimer::singleShot(0, this, &AccountsGuiController::runAccountWizard); + } +} + +void AccountsGuiController::onAccountAdded(AccountState *state) +{ + if (!_window || !state || !state->account()) + return; + + Account *account = state->account(); + QUuid accountId = account->uuid(); + + connect(account, &Account::avatarChanged, this, &AccountsGuiController::onAccountAvatarChanged); + + auto accountView = new AccountView(nullptr); + // for both the view and the action, we create a unique objectName using the account uuid + // to support squish test object identification + accountView->setObjectName(QString("accountView_%1").arg(accountId.toString())); + + AccountViewController *viewController = new AccountViewController(accountView, state, this); + _viewControllerForAccount.insert(accountId, viewController); + + connect(account->credentials(), &AbstractCredentials::requestAccountModal, viewController, &AccountViewController::addAccountModalWidget); + + connect(viewController, &AccountViewController::accountBeginModal, this, &AccountsGuiController::startModal); + connect(viewController, &AccountViewController::accountEndModal, this, &AccountsGuiController::endModal); + + QAction *accountAction = new QAction(this); + accountAction->setObjectName(QString("accountAction_%1").arg(accountId.toString())); + _actionForAccount.insert(accountId, accountAction); + + accountAction->setIcon(account->avatar()); + // eventually make this elided + // problem is, the action has no idea what the size of the button should be. + // it's really dumb to have "long" text on any toolbar component in the first place. We hope to fix this someday + // by allowing the user to set an account alias which will show as the account name but that needs a major release + // to support the config change + accountAction->setText(account->hostName()); + + accountAction->setToolTip(QString("%1\n%2").arg(account->davDisplayName(), account->url().toDisplayString())); + accountAction->setData(QVariant::fromValue(accountView)); + accountAction->setCheckable(true); + + _window->addAccountAction(accountAction); + + accountAction->setChecked(true); + + if (_actionForAccount.contains(QUuid())) + removeAccountPlaceholder(); +} + +void AccountsGuiController::onAccountRemoved(AccountState *state) +{ + if (!_window || !state || !state->account()) + return; + + Account *acc = state->account(); + QUuid uid = acc->uuid(); + if (_actionForAccount.contains(uid)) { + QAction *action = _actionForAccount[uid]; + _window->removeAction(action); + _actionForAccount.remove(uid); + action->deleteLater(); + + if (!_accountMgr) + return; + + const QList accounts = _accountMgr->accounts(); + if (!accounts.isEmpty()) { + QUuid newuid = accounts.last()->account()->uuid(); + if (_actionForAccount.contains(newuid)) + _actionForAccount[newuid]->setChecked(true); + } + } +} + +void AccountsGuiController::onLastAccountRemoved() +{ + runAccountWizard(); +} + +void AccountsGuiController::onAccountAvatarChanged() +{ + Account *account = qobject_cast(sender()); + if (!account) + return; + QAction *action = _actionForAccount.value(account->uuid(), nullptr); + + if (action) { + action->setIcon(account->avatar()); + } +} + +void AccountsGuiController::runAccountWizard() +{ + if (!_window) + return; + NewAccountWizard wizard(_window); + NewAccountModel model(nullptr); + NewAccountWizardController wizardController(&model, &wizard, nullptr); + _window->ensureVisible(); + int result = wizard.exec(); + if (result == QDialog::Accepted) { + // the builder needs to be a pointer as it has to wait for the connection state to go to connected + // it will delete itself once it has completed its mission. + // pass this as parent only as a safeguard. + if (!model.isComplete()) { + QMessageBox::warning( + _window, tr("New account failure"), tr("The information required to create a new account is incomplete. Please run the wizard again.")); + } else { + NewAccountBuilder *builder = new NewAccountBuilder(model, this); + FolderMan *folderman = FolderMan::instance(); + connect(builder, &NewAccountBuilder::requestSetUpSyncFoldersForAccount, folderman, &FolderMan::setUpInitialSyncFolders); + connect(builder, &NewAccountBuilder::requestLoadSpacesOnly, folderman, &FolderMan::setUpInitialSpaces); + connect(builder, &NewAccountBuilder::requestFolderWizard, this, &AccountsGuiController::runFolderWizard); + connect(builder, &NewAccountBuilder::unableToCompleteAccountCreation, this, &AccountsGuiController::handleAccountSetupError); + builder->buildAccount(); + } + } else { + // if there are no accounts, add a placeholder "page" to the main window that informs the user how to create one + setupAccountPlaceholder(); + } +} + +void AccountsGuiController::setupAccountPlaceholder() +{ + if (!_window || !_accountMgr || !_accountMgr->accounts().isEmpty()) + return; + + QUuid uid; + QAction *placeholderAccountAction = nullptr; + if (_actionForAccount.contains(uid)) { + placeholderAccountAction = _actionForAccount[uid]; + } else { + placeholderAccountAction = new QAction(tr("Accounts")); + placeholderAccountAction->setObjectName("placeholderAccountAction"); + placeholderAccountAction->setCheckable(true); + placeholderAccountAction->setIcon(Resources::getCoreIcon("warning")); + // use null uuid for placeholder action since there IS no account for it + _actionForAccount.insert(QUuid(), placeholderAccountAction); + auto placeholderWidget = new AccountPlaceholderWidget(_window); + placeholderAccountAction->setData(QVariant::fromValue(placeholderWidget)); + _window->addAccountAction(placeholderAccountAction); + } + placeholderAccountAction->setChecked(true); +} + +void AccountsGuiController::removeAccountPlaceholder() +{ + if (!_window) + return; + QUuid uid; + if (_actionForAccount.contains(uid)) { + QAction *action = _actionForAccount[uid]; + _window->removeAction(action); + _actionForAccount.remove(uid); + action->deleteLater(); + } +} + +void AccountsGuiController::handleAccountSetupError(const QString &error) +{ + if (!_window) + return; + + QMessageBox::warning(_window, tr("New account failure"), + tr("The account could not be created due to an error:\n%1\nPlease check the server's availability then run the wizard again.").arg(error)); + + // note that the current behavior for the setup failure is that the account wizard auto-pops again because the + // accountState for the new account was already created and it is subsequently deleted by the account builder if the final + // setup fails for some reason. The accountRemoved handler then runs the wizard again if there are no other accounts. + // In case we change that in future, this will create the dummy account page if the wizard doesn't auto-run again + setupAccountPlaceholder(); +} + +void AccountsGuiController::runFolderWizard(QUuid accountId) +{ + QAction *action = _actionForAccount.value(accountId, nullptr); + if (!action) + return; + + action->setChecked(true); + + AccountViewController *controller = _viewControllerForAccount.value(accountId, nullptr); + if (controller) + controller->runFolderWizard(); +} + +void AccountsGuiController::startModal(QUuid accountId) +{ + QAction *action = _actionForAccount.value(accountId, nullptr); + if (!action) + return; + + action->setIcon(Resources::getCoreIcon("states/warning")); + action->setChecked(true); + ocApp()->ensureVisible(); +} + +void AccountsGuiController::endModal(QUuid accountId) +{ + if (!_accountMgr) + return; + + QAction *action = _actionForAccount.value(accountId, nullptr); + if (!action) + return; + // get the icon from the account again and put it back on the action + AccountState *state = _accountMgr->accountState(accountId); + if (state && state->account()) { + Account *account = _accountMgr->accountState(accountId)->account(); + action->setIcon(account->avatar()); + } + // I don't think we need to set the action checked again + Q_ASSERT(action->isChecked()); +} +} diff --git a/src/gui/accountsgui/accountsguicontroller.h b/src/gui/accountsgui/accountsguicontroller.h new file mode 100644 index 00000000000..0a8eafcbb9c --- /dev/null +++ b/src/gui/accountsgui/accountsguicontroller.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 +#include + +#include +#include + +class QToolBar; +class QAction; +class QActionGroup; + +namespace OCC { + +class AccountState; +class AccountManager; +class Account; +class MainWindow; +class AccountViewController; + +class AccountsGuiController : public QObject +{ + Q_OBJECT + +public: + AccountsGuiController(AccountManager *accountMgr, MainWindow *window, QObject *parent); + void runAccountWizard(); + + +private: + QPointer _accountMgr; + QPointer _window; + QHash _actionForAccount; + QHash _viewControllerForAccount; + + void onAccountAdded(AccountState *state); + void onAccountRemoved(AccountState *state); + void onLastAccountRemoved(); + + void onAccountAvatarChanged(); + + void runFolderWizard(QUuid accountId); + void handleAccountSetupError(const QString &error); + + void startModal(QUuid accountId); + void endModal(QUuid accountId); + + void setupAccountPlaceholder(); + void removeAccountPlaceholder(); +}; +} diff --git a/src/gui/accountsgui/accountview.cpp b/src/gui/accountsgui/accountview.cpp new file mode 100644 index 00000000000..3d92335e58b --- /dev/null +++ b/src/gui/accountsgui/accountview.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) by Lisa Reese + * + * 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 "accountview.h" +#include "ui_accountview.h" + +#include + +namespace OCC { + +AccountView::AccountView(QWidget *parent) + : QWidget(parent) + , _ui(new Ui::AccountView) +{ + _ui->setupUi(this); + _ui->connectionStatusLabel->clear(); + + connect(_ui->stackedWidget, &QStackedWidget::currentChanged, this, + [this] { _ui->manageAccountButton->setEnabled(_ui->stackedWidget->currentWidget() == _ui->accountFoldersView); }); + _ui->stackedWidget->setCurrentWidget(_ui->accountFoldersView); +} + +AccountView::~AccountView() +{ + delete _ui; +} + +void AccountView::setAccountMenuActions(QList actions) +{ + QMenu *menu = _ui->manageAccountButton->menu(); + if (!menu) { + menu = new QMenu(this); + menu->setObjectName("manageAccountMenu"); + menu->setAccessibleName(tr("Account options menu")); + connect(menu, &QMenu::aboutToShow, this, &AccountView::requestMenuActionUpdate); + } else { + menu->clear(); + } + + menu->addActions(actions); + if (_ui->manageAccountButton->menu() == nullptr) + _ui->manageAccountButton->setMenu(menu); +} + +void AccountView::setTopStackWidget(QWidget *widget) +{ + // should not contain this widget already + Q_ASSERT(_ui->stackedWidget->indexOf(widget) < 0); + + _ui->stackedWidget->addWidget(widget); + _ui->stackedWidget->setCurrentWidget(widget); +} + +void AccountView::removeStackWidget(QWidget *widget) +{ + _ui->stackedWidget->removeWidget(widget); +} + +AccountFoldersView *AccountView::foldersView() +{ + return _ui->accountFoldersView; +} + +void AccountView::accountSettingUpChanged(bool settingUp) +{ + if (settingUp) { + _ui->spinner->startAnimation(); + _ui->stackedWidget->setCurrentWidget(_ui->loadingPage); + } else { + _ui->spinner->stopAnimation(); + _ui->stackedWidget->setCurrentWidget(_ui->accountFoldersView); + } +} + +void AccountView::showEvent(QShowEvent *ev) +{ + Q_UNUSED(ev); + _ui->manageAccountButton->setFocus(); +} + +void AccountView::setConnectionLabel(const QString &message, const QIcon &icon, QStringList errors) +{ + // I really see no point in these but they existed previously...eval usefulness later + // oh wait...the "warning label" is actually the name of the STATUS ICON?!?! + // if so this makes more sense. for goodness sake, let's rename that item asap :D + _ui->warningLabel->setVisible(!icon.isNull()); + if (!icon.isNull()) { + _ui->warningLabel->setPixmap(icon.pixmap(_ui->warningLabel->size())); + } + + _ui->accountStatus->setVisible(!message.isEmpty()); + + if (errors.isEmpty()) { + _ui->connectionStatusLabel->setText(message); + _ui->connectionStatusLabel->setToolTip(QString()); + } else { + errors.prepend(message); + const QString msg = errors.join(QLatin1String("\n")); + _ui->connectionStatusLabel->setText(msg); + _ui->connectionStatusLabel->setToolTip(QString()); + } +} + +} // namespace OCC diff --git a/src/gui/accountsgui/accountview.h b/src/gui/accountsgui/accountview.h new file mode 100644 index 00000000000..ad2903d5e50 --- /dev/null +++ b/src/gui/accountsgui/accountview.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) by Lisa Reese + * + * 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 "gui/owncloudguilib.h" + +#include + +namespace OCC { +class AccountModalWidget; + +namespace Ui { + class AccountView; +} + +class FolderMan; + +class Account; +class AccountState; +class AccountFoldersView; + +/** + * @brief The AccountView class + * @ingroup gui + */ +class OWNCLOUDGUI_EXPORT AccountView : public QWidget +{ + Q_OBJECT + +public: + explicit AccountView(QWidget *parent); + ~AccountView() override; + + void setAccountMenuActions(QList actions); + void setConnectionLabel(const QString &message, const QIcon &icon, QStringList errors = QStringList()); + + // this is primarily used to run an account "modal" widget + void setTopStackWidget(QWidget *widget); + void removeStackWidget(QWidget *widget); + + // holding my nose for now - this should not be public, nor should the type be "embedded" in the view's ui. + // todo: replace the concrete folders view with a placeholder location so the controller can SET the folders view in the + // account view. + // open question: would this mess up the squish tests? we'll soon learn the answer + AccountFoldersView *foldersView(); + void accountSettingUpChanged(bool settingUp); + + +signals: + void requestMenuActionUpdate(); + +protected: + void showEvent(QShowEvent *ev) override; + +private: + Ui::AccountView *_ui; +}; + +} // namespace OCC + +Q_DECLARE_METATYPE(OCC::AccountView) diff --git a/src/gui/accountsgui/accountview.ui b/src/gui/accountsgui/accountview.ui new file mode 100644 index 00000000000..9b82c6ac2c8 --- /dev/null +++ b/src/gui/accountsgui/accountview.ui @@ -0,0 +1,175 @@ + + + OCC::AccountView + + + + 0 + 0 + 678 + 557 + + + + Form + + + + + + + 12 + + + 0 + + + 12 + + + 0 + + + + + true + + + + 0 + 0 + + + + + 32 + 32 + + + + + 32 + 32 + + + + + + + true + + + + + + + + 0 + 0 + + + + Connection Status + + + Qt::TextFormat::PlainText + + + true + + + + + + + Qt::FocusPolicy::StrongFocus + + + Manage Account + + + + + + + + + + + 0 + 5 + + + + 0 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + Preparing the account + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + Sync connections + + + + + + + + + QProgressIndicator + QWidget +
QProgressIndicator.h
+ 1 +
+ + OCC::AccountFoldersView + QWidget +
gui/FoldersGui/accountfoldersview.h
+ 1 +
+
+ + +
diff --git a/src/gui/accountsgui/accountviewcontroller.cpp b/src/gui/accountsgui/accountviewcontroller.cpp new file mode 100644 index 00000000000..864ac9b778a --- /dev/null +++ b/src/gui/accountsgui/accountviewcontroller.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "accountviewcontroller.h" + +#include "FoldersGui/accountfolderscontroller.h" +#include "accountmanager.h" +#include "accountmodalwidget.h" +#include "accountview.h" +#include "commonstrings.h" +#include "configfile.h" +#include "folderman.h" +#include "folderwizard.h" +#include "libsync/theme.h" + +#include +#include +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcAccountViewController, "gui.account.viewcontroller", QtInfoMsg) + +AccountViewController::AccountViewController(AccountView *view, AccountState *state, QObject *parent) + : QObject{parent} + , _view(view) + , _accountState(state) +{ + if (!_view || !_accountState || !_accountState->account()) + return; + + connect(_view, &AccountView::requestMenuActionUpdate, this, &AccountViewController::refreshAccountActions); + + AccountFoldersController *foldersController = new AccountFoldersController(_accountState, _view->foldersView(), this); + connect(foldersController, &AccountFoldersController::requestAddFolder, this, &AccountViewController::runFolderWizard); + connect(foldersController, &AccountFoldersController::requestAccountModalWidget, this, &AccountViewController::addAccountModalWidget); + + connect(_accountState, &AccountState::stateChanged, this, &AccountViewController::onAccountStateChanged); + connect(_accountState, &AccountState::isSettingUpChanged, _view, &AccountView::accountSettingUpChanged); + + buildManageAccountMenu(); + + onAccountStateChanged(_accountState->state()); +} + +void AccountViewController::onOpenAccountInBrowser() +{ + if (!_accountState) { + return; + } + + QUrl url = _accountState->account()->url(); + if (!Theme::instance()->overrideServerPath().isEmpty()) { + // There is an override for the WebDAV endpoint. Remove it for normal web browsing. + url.setPath({}); + } + QDesktopServices::openUrl(url); +} + +void AccountViewController::onToggleSignInState() +{ + if (!_accountState) { + return; + } + + if (_accountState->isSignedOut()) { + _accountState->signIn(); + } else { + _accountState->signOutByUi(); + } +} + +void AccountViewController::buildManageAccountMenu() +{ + if (!_view || !_accountState) + return; + + QList actions; + + _logInOut = new QAction(tr("Log in"), this); + _logInOut->setObjectName("logInOutAction"); + connect(_logInOut, &QAction::triggered, this, &AccountViewController::onToggleSignInState); + actions.push_back(_logInOut); + + _reconnect = new QAction(tr("Reconnect"), this); + _reconnect->setObjectName("reconnectAction"); + connect(_reconnect, &QAction::triggered, this, [this] { _accountState->checkConnectivity(true); }); + actions.push_back(_reconnect); + + _showInBrowser = new QAction(CommonStrings::showInWebBrowser(), this); + _showInBrowser->setObjectName("showInBrowserAction"); + connect(_showInBrowser, &QAction::triggered, this, &AccountViewController::onOpenAccountInBrowser); + actions.push_back(_showInBrowser); + + _remove = new QAction(tr("Remove"), this); + _remove->setObjectName("removeAction"); + connect(_remove, &QAction::triggered, this, &AccountViewController::onDeleteAccount); + actions.push_back(_remove); + + _view->setAccountMenuActions(actions); +} + +void AccountViewController::refreshAccountActions() +{ + Q_ASSERT(_logInOut && _reconnect && _showInBrowser && _remove); + + // no we don't need to set enabled to match account state existence, if account state is null it's NOT coming back. + if (!_accountState) { + _logInOut->setEnabled(false); + _reconnect->setEnabled(false); + _showInBrowser->setEnabled(false); + _remove->setEnabled(false); + return; + } + + _logInOut->setText(_accountState->isSignedOut() ? tr("Log in") : tr("Log out")); + _reconnect->setEnabled(!_accountState->isConnected() && !_accountState->isSignedOut()); +} + +void AccountViewController::addAccountModalWidget(AccountModalWidget *widget) +{ + if (!_accountState || !_accountState->account()) { + return; + } + + _view->setTopStackWidget(widget); + + connect(widget, &AccountModalWidget::finished, this, &AccountViewController::finishAccountModalWidget); + + emit accountBeginModal(_accountState->account()->uuid()); +} + +void AccountViewController::finishAccountModalWidget(AccountModalWidget *widget) +{ + if (!widget) + return; + + _view->removeStackWidget(widget); + widget->deleteLater(); + + Q_ASSERT(_accountState && _accountState->account()); + + emit accountEndModal(_accountState->account()->uuid()); +} + +void AccountViewController::onDeleteAccount() +{ + if (!_accountState) { + return; + } + + // Deleting the account potentially deletes 'this', so + // the QMessageBox should be destroyed before that happens. + // todo: this is an unnecessarily complicated def for the message box and should exec instead of open. + auto messageBox = new QMessageBox(QMessageBox::Question, tr("Confirm Account Removal"), + tr("

Do you really want to remove the connection to the account %1?

" + "

Note: This will not delete any files.

") + .arg(_accountState->account()->displayNameWithHost()), + QMessageBox::NoButton, _view); + messageBox->setObjectName("confirmRemoveAccountDialog"); + auto yesButton = messageBox->addButton(tr("Remove connection"), QMessageBox::YesRole); + yesButton->setObjectName("removeAccountButton"); + auto noButton = messageBox->addButton(tr("Cancel"), QMessageBox::NoRole); + noButton->setObjectName("cancelRemoveAccountButton"); + + messageBox->setAttribute(Qt::WA_DeleteOnClose); + connect(messageBox, &QMessageBox::finished, this, [this, messageBox, yesButton] { + if (messageBox->clickedButton() == yesButton) { + auto manager = AccountManager::instance(); + manager->deleteAccount(_accountState); + manager->save(); + } + }); + messageBox->open(); +} + +QIcon AccountViewController::lookupStatusIcon(StatusIcon status) +{ + QIcon icon; + switch (status) { + case StatusIcon::None: + break; + case StatusIcon::Connected: + icon = Resources::getCoreIcon(QStringLiteral("states/ok")); + break; + case StatusIcon::Disconnected: + icon = Resources::getCoreIcon(QStringLiteral("states/offline")); + break; + case StatusIcon::Info: + icon = Resources::getCoreIcon(QStringLiteral("states/information")); + break; + case StatusIcon::Warning: + icon = Resources::getCoreIcon(QStringLiteral("states/warning")); + break; + } + return icon; +} +void AccountViewController::onAccountStateChanged(AccountState::State state) +{ + if (!_accountState || !_accountState->account() || !_view) { + return; + } + + Account *account = _accountState->account(); + qCDebug(lcAccountViewController()) << "Account state changed to" << state << "for account" << account; + + QStringList errors; + QString text; + StatusIcon icon; + + switch (state) { + case AccountState::Connected: { + icon = StatusIcon::Connected; + if (account->serverSupportLevel() != Account::ServerSupportLevel::Supported) { + errors << tr("The server version %1 is unsupported! Proceed at your own risk.").arg(account->capabilities().status().versionString()); + icon = StatusIcon::Warning; + } + text = tr("Connected"); + break; + } + case AccountState::ServiceUnavailable: + text = tr("Server is temporarily unavailable."); + icon = StatusIcon::Disconnected; + break; + case AccountState::MaintenanceMode: + text = tr("Server is currently in maintenance mode."); + icon = StatusIcon::Disconnected; + break; + case AccountState::SignedOut: + text = tr("Signed out"); + icon = StatusIcon::Disconnected; + break; + case AccountState::AskingCredentials: + text = tr("Updating credentials…"); + icon = StatusIcon::Info; + break; + case AccountState::Connecting: + if (NetworkInformation::instance()->isBehindCaptivePortal()) { + text = tr("Captive portal prevents connections to the server."); + icon = StatusIcon::Disconnected; + } else if (NetworkInformation::instance()->isMetered() && ConfigFile().pauseSyncWhenMetered()) { + text = tr("Sync is paused due to metered internet connection."); + icon = StatusIcon::Disconnected; + } else { + text = tr("Connecting…"); + icon = StatusIcon::Info; + } + break; + case AccountState::ConfigurationError: + text = tr("Server configuration error"); + icon = StatusIcon::Warning; + errors = _accountState->connectionErrors(); + break; + case AccountState::NetworkError: + // don't display the error to the user, https://github.com/owncloud/client/issues/9790 + [[fallthrough]]; + case AccountState::Disconnected: + text = tr("Disconnected"); + icon = StatusIcon::Disconnected; + break; + default: + text = tr("Invalid connection status"); + icon = StatusIcon::None; + } + + _view->setConnectionLabel(text, lookupStatusIcon(icon), errors); +} + +void AccountViewController::runFolderWizard() +{ + if (!_accountState || !_accountState->account() || !_view) + return; + + + FolderWizard *folderWizard = new FolderWizard(_accountState->account(), _view); + + connect(folderWizard, &QDialog::accepted, this, &AccountViewController::onFolderWizardAccepted); + connect(folderWizard, &QDialog::rejected, this, [] { qCInfo(lcAccountViewController) << "Folder wizard cancelled"; }); + + // ignore clang analyzer warning about potential memory leak, please. + // the modal widget gets reparented to the stacked widget and is automatically deleted when the finished() signal is + // received + AccountModalWidget *widget = new AccountModalWidget({}, folderWizard, _view); + addAccountModalWidget(widget); +} + +void AccountViewController::onFolderWizardAccepted() +{ + if (!_accountState) + return; + + FolderWizard *folderWizard = qobject_cast(sender()); + if (!folderWizard) + return; + + auto config = folderWizard->result(); + + // The gui should not allow users to selectively choose any sync lists if vfs is enabled, but this kind of check was + // originally in play here so...keep it just in case. + if (config.useVirtualFiles && !config.selectiveSyncBlackList.empty()) + config.selectiveSyncBlackList.clear(); + + + // Refactoring todo: turn this into a signal/requestAddFolder + FolderMan::instance()->addFolderFromGui(_accountState, config); +} + +} diff --git a/src/gui/accountsgui/accountviewcontroller.h b/src/gui/accountsgui/accountviewcontroller.h new file mode 100644 index 00000000000..4f00f507347 --- /dev/null +++ b/src/gui/accountsgui/accountviewcontroller.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "accountstate.h" +#include +#include + +class QAction; + +namespace OCC { + +class AccountView; +class AccountModalWidget; + +enum class StatusIcon { None, Connected, Disconnected, Info, Warning }; + +class AccountViewController : public QObject +{ + Q_OBJECT +public: + explicit AccountViewController(AccountView *view, AccountState *state, QObject *parent); + + void addAccountModalWidget(AccountModalWidget *widget); + void runFolderWizard(); + +signals: + // these are sent when the account view starts and ends a "modal" operation + // at the moment I'm not blocking access to main window toolbar actions as there is really no need, imo, + // just because the account is in the middle of something. At least we will try it this way and see + // if it's preferred. So long as the account modal widget blocks *account* related activity I think we're good + void accountEndModal(QUuid accountId); + void accountBeginModal(QUuid accountId); + +protected: + void onAccountStateChanged(OCC::AccountState::State state); + void onDeleteAccount(); + void onOpenAccountInBrowser(); + void onToggleSignInState(); + + void onFolderWizardAccepted(); + + void finishAccountModalWidget(AccountModalWidget *widget); + +private: + QPointer _view = nullptr; + QPointer _accountState; + + QAction *_logInOut = nullptr; + QAction *_reconnect = nullptr; + QAction *_showInBrowser = nullptr; + QAction *_remove = nullptr; + + void buildManageAccountMenu(); + void refreshAccountActions(); + QIcon lookupStatusIcon(StatusIcon status); +}; + +} diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index c9ab14fe2cb..096410bf33d 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -12,15 +12,17 @@ * for more details. */ -#include "application.h" #include "accountstate.h" -#include "accountmanager.h" -#include "account.h" -#include "creds/abstractcredentials.h" -#include "creds/httpcredentials.h" -#include "logger.h" +#include "application.h" #include "configfile.h" -#include "settingsdialog.h" + +#include "fetchserversettings.h" + +#include "libsync/creds/abstractcredentials.h" + +#include "gui/tlserrordialog.h" + +#include "socketapi/socketapi.h" #include "theme.h" #include @@ -45,94 +47,82 @@ namespace OCC { Q_LOGGING_CATEGORY(lcAccountState, "gui.account.state", QtInfoMsg) -void AccountState::updateUrlDialog(const QUrl &newUrl) -{ - // guard to prevent multiple dialogs - if (_updateUrlDialog) { - return; - } - auto accept = [=] { - _account->setUrl(newUrl); - Q_EMIT _account->wantsAccountSaved(_account.data()); - Q_EMIT urlUpdated(); - }; - - auto matchUrl = [](QUrl url1, QUrl url2) { - // ensure https://demo.owncloud.org/ matches https://demo.owncloud.org - // the empty path was the legacy formating before 2.9 - if (url1.path().isEmpty()) { - url1.setPath(QStringLiteral("/")); - } - if (url2.path().isEmpty()) { - url2.setPath(QStringLiteral("/")); - } - return url1.matches(url2, QUrl::StripTrailingSlash | QUrl::NormalizePathSegments); - }; - - if (matchUrl(newUrl, _account->url())) { - accept(); - return; - } - - _updateUrlDialog = new QMessageBox(QMessageBox::Warning, tr("Url update requested for %1").arg(_account->displayName()), - tr("The url for %1 changed from %2 to %3, do you want to accept the changed url?").arg(_account->displayName(), _account->url().toString(), newUrl.toString()), - QMessageBox::NoButton, ocApp()->gui()->settingsDialog()); - _updateUrlDialog->setAttribute(Qt::WA_DeleteOnClose); - auto yes = _updateUrlDialog->addButton(tr("Change url permanently to %1").arg(newUrl.toString()), QMessageBox::AcceptRole); - _updateUrlDialog->addButton(tr("Reject"), QMessageBox::RejectRole); - connect(_updateUrlDialog, &QMessageBox::finished, _account.data(), [yes, accept, this] { - if (_updateUrlDialog->clickedButton() == yes) { - accept(); - } - }); - _updateUrlDialog->show(); -} - -AccountState::AccountState(AccountPtr account) +AccountState::AccountState(Account *account) : QObject() , _account(account) , _queueGuard(_account->jobQueue()) , _state(AccountState::Disconnected) , _connectionStatus(ConnectionValidator::Undefined) , _waitingForNewCredentials(false) + , _connectionValidator(nullptr) , _maintenanceToConnectedDelay(1min + minutes(QRandomGenerator::global()->generate() % 4)) // 1-5min delay { qRegisterMetaType("AccountState*"); - connect(account.data(), &Account::invalidCredentials, - this, &AccountState::slotInvalidCredentials); - connect(account.data(), &Account::credentialsFetched, - this, &AccountState::slotCredentialsFetched); - connect(account.data(), &Account::credentialsAsked, - this, &AccountState::slotCredentialsAsked); - connect(account.data(), &Account::unknownConnectionState, - this, [this] { - checkConnectivity(true); - }); - connect(account.data(), &Account::requestUrlUpdate, this, &AccountState::updateUrlDialog); - connect(this, &AccountState::urlUpdated, this, [this] { - checkConnectivity(false); - }); - connect(account.data(), &Account::requestUrlUpdate, this, &AccountState::updateUrlDialog, Qt::QueuedConnection); - connect( - this, &AccountState::urlUpdated, this, [this] { - checkConnectivity(false); - }, - Qt::QueuedConnection); + Q_ASSERT(_account); + + connectAccount(); + connectNetworkInformation(); + + if (NetworkInformation::instance()->isBehindCaptivePortal()) { + _queueGuard.block(); + } + + // as a fallback and to recover after server issues we also poll + auto timer = new QTimer(this); + timer->setInterval(ConnectionValidator::DefaultCallingInterval); + connect(timer, &QTimer::timeout, this, [this] { checkConnectivity(false); }); + timer->start(); + + connect(_account->credentials(), &AbstractCredentials::requestLogout, this, [this] { setState(State::SignedOut); }); + + // todo: no we should not directly register the account, but let accountAdded signal trigger it + if (FolderMan::instance()) { + FolderMan::instance()->socketApi()->registerAccount(_account.get()); + } } AccountState::~AccountState() { + resetConnectionValidator(); + // disconnect NetworkInformation + // do we also need to disconnect the account, since it's shared? no idea. I hate this stuff +} + +void AccountState::connectAccount() +{ + if (!_account) { + qCWarning(lcAccountState) << "Account pointer is null when trying to set up AccountState"; + return; + } + connect(_account.data(), &Account::invalidCredentials, this, &AccountState::slotInvalidCredentials); + connect(_account.data(), &Account::credentialsFetched, this, &AccountState::slotCredentialsFetched); + connect(_account.data(), &Account::unknownConnectionState, this, [this] { checkConnectivity(true); }); + + connect(_account.data(), &Account::appProviderErrorOccured, this, [](const QString &error) { + QMessageBox *msgBox = new QMessageBox(QMessageBox::Information, Theme::instance()->appNameGUI(), error, {}, ocApp()->mainWindow()); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + ocApp()->ensureVisible(); + msgBox->open(); + }); +} + +void AccountState::connectNetworkInformation() +{ + connect(NetworkInformation::instance(), &NetworkInformation::reachabilityChanged, this, &AccountState::onNetworkReachabilityChanged); + connect(NetworkInformation::instance(), &NetworkInformation::isMeteredChanged, this, &AccountState::onNetworkMeteredChanged); + connect(NetworkInformation::instance(), &NetworkInformation::isBehindCaptivePortalChanged, this, &AccountState::onBehindCaptivePortalChanged); } -AccountState *AccountState::loadFromSettings(AccountPtr account, const QSettings &settings) +AccountState *AccountState::loadFromSettings(Account *account, const QSettings &settings) { auto accountState = new AccountState(account); const bool userExplicitlySignedOut = settings.value(userExplicitlySignedOutC(), false).toBool(); if (userExplicitlySignedOut) { // see writeToSettings below - accountState->_state = SignedOut; + accountState->setState(SignedOut); } + return accountState; } @@ -146,7 +136,7 @@ void AccountState::writeToSettings(QSettings &settings) const settings.setValue(userExplicitlySignedOutC(), _state == SignedOut); } -AccountPtr AccountState::account() const +Account *AccountState::account() const { return _account; } @@ -168,10 +158,12 @@ AccountState::State AccountState::state() const void AccountState::setState(State state) { + if (!_account) + return; + const State oldState = _state; if (_state != state) { - qCInfo(lcAccountState) << "AccountState state change: " - << stateString(_state) << "->" << stateString(state); + qCInfo(lcAccountState) << "AccountState state change: " << _state << "->" << state; _state = state; if (_state == SignedOut) { @@ -184,52 +176,38 @@ void AccountState::setState(State state) } else if (_state == ServiceUnavailable) { // Check if we are actually down for maintenance. // To do this we must clear the connection validator that just - // produced the 503. It's finished anyway and will delete itself. - _connectionValidator.clear(); + // produced the 503. + resetConnectionValidator(); checkConnectivity(); - } - if (oldState == Connected || _state == Connected) { - emit isConnectedChanged(); + } else if (_state == Connected) { + if ((NetworkInformation::instance()->isMetered() && ConfigFile().pauseSyncWhenMetered()) + || NetworkInformation::instance()->isBehindCaptivePortal()) { + _state = Connecting; + } } } // might not have changed but the underlying _connectionErrors might have if (_state == Connected) { - QTimer::singleShot(0, this, [this] { + QTimer::singleShot(0, this, [this, oldState] { // ensure the connection validator is done _queueGuard.unblock(); + // update capabilities and fetch relevant settings + _fetchCapabilitiesJob = new FetchServerSettingsJob(_account, this); + connect(_fetchCapabilitiesJob.get(), &FetchServerSettingsJob::finishedSignal, this, [oldState, this] { + // Lisa todo: I do not understand this logic at all - review it + if (oldState == Connected || _state == Connected) { + _fetchCapabilitiesJob.clear(); + Q_EMIT isConnectedChanged(); + } + }); + _fetchCapabilitiesJob->start(); }); - } else { - _queueGuard.clear(); } - // don't anounce a state change from connected to connected - // https://github.com/owncloud/client/commit/2c6c21d7532f0cbba4b768fde47810f6673ed931 - if (oldState != state || state != Connected) { - emit stateChanged(_state); - } -} -QString AccountState::stateString(State state) -{ - switch (state) { - case SignedOut: - return tr("Signed out"); - case Disconnected: - return tr("Disconnected"); - case Connected: - return tr("Connected"); - case ServiceUnavailable: - return tr("Service unavailable"); - case MaintenanceMode: - return tr("Maintenance mode"); - case NetworkError: - return tr("Network error"); - case ConfigurationError: - return tr("Configuration error"); - case AskingCredentials: - return tr("Asking Credentials"); + if (oldState != _state) { + Q_EMIT stateChanged(_state); } - return tr("Unknown account state"); } bool AccountState::isSignedOut() const @@ -239,9 +217,14 @@ bool AccountState::isSignedOut() const void AccountState::signOutByUi() { - account()->credentials()->forgetSensitiveData(); - account()->clearCookieJar(); + if (!_account) + return; + + _account->credentials()->forgetSensitiveData(); + _account->clearCookieJar(); setState(SignedOut); + // persist that we are signed out + Q_EMIT _account->wantsAccountSaved(_account); } void AccountState::freshConnectionAttempt() @@ -253,9 +236,13 @@ void AccountState::freshConnectionAttempt() void AccountState::signIn() { + if (!_account) + return; if (_state == SignedOut) { _waitingForNewCredentials = false; setState(Disconnected); + // persist that we are no longer signed out + Q_EMIT account()->wantsAccountSaved(_account); } } @@ -264,92 +251,209 @@ bool AccountState::isConnected() const return _state == Connected; } -void AccountState::tagLastSuccessfullETagRequest(const QDateTime &tp) +void AccountState::tagLastSuccessfulETagRequest(const QDateTime &tp) { _timeOfLastETagCheck = tp; } void AccountState::checkConnectivity(bool blockJobs) { - qCWarning(lcAccountState) << "checkConnectivity blocking:" << blockJobs; + // ======= These are all pre-checks that may effectively cancel the check if (isSignedOut() || _waitingForNewCredentials) { return; } - + qCInfo(lcAccountState) << "checkConnectivity blocking:" << blockJobs << account()->displayNameWithHost(); + if (_state != Connected) { + setState(Connecting); + } if (_connectionValidator && blockJobs && !_queueGuard.queue()->isBlocked()) { // abort already running non blocking validator - _connectionValidator->deleteLater(); - _connectionValidator.clear(); + resetConnectionValidator(); } if (_connectionValidator) { - qCWarning(lcAccountState) << "ConnectionValidator already running, ignoring" << account()->displayName(); + qCWarning(lcAccountState) << "ConnectionValidator already running, ignoring" << account()->displayNameWithHost() + << "Queue is blocked:" << _queueGuard.queue()->isBlocked(); return; } - // If we never fetched credentials, do that now - otherwise connection attempts + // ======= beginning here are pre-check updates (or so) + // If the credentials have never been fetched, try to fetch them - otherwise connection attempts // make little sense. - if (!account()->credentials()->wasFetched()) { + // todo: review this logic to see if that actually belongs here. I really don't understand why this is needed + + if (!account()->credentials()->wasEverFetched()) { + // I hope this is never called. ok. it is called on call to AccountState::checkConnectivity which is called during AccountManager::addAccountState + // so if we want to refresh the creds explicitly, do it there or prior _waitingForNewCredentials = true; account()->credentials()->fetchFromKeychain(); + // the fetch should be allowed to finish before we do anything more return; } - - // IF the account is connected the connection check can be skipped - // if the last successful etag check job is not so long ago. - const auto pta = account()->capabilities().remotePollInterval(); - const auto polltime = duration_cast(ConfigFile().remotePollInterval(pta)); - const auto elapsed = _timeOfLastETagCheck.secsTo(QDateTime::currentDateTimeUtc()); - if (!blockJobs && isConnected() && _timeOfLastETagCheck.isValid() - && elapsed <= polltime.count()) { - qCDebug(lcAccountState) << account()->displayName() << "The last ETag check succeeded within the last " << polltime.count() << "s (" << elapsed << "s). No connection check needed!"; - return; + if (account()->hasCapabilities()) { + // IF the account is connected the connection check can be skipped + // if the last successful etag check job is not so long ago. + // TODO: https://github.com/owncloud/client/issues/10935 + const auto pta = account()->capabilities().remotePollInterval(); + const auto polltime = duration_cast(ConfigFile().remotePollInterval(pta)); + const auto elapsed = _timeOfLastETagCheck.secsTo(QDateTime::currentDateTimeUtc()); + if (!blockJobs && isConnected() && _timeOfLastETagCheck.isValid() + && elapsed <= polltime.count()) { + qCDebug(lcAccountState) << account()->displayNameWithHost() << "The last ETag check succeeded within the last " << polltime.count() << "s (" + << elapsed << "s). No connection check needed!"; + return; + } } if (blockJobs) { _queueGuard.block(); } - _connectionValidator = new ConnectionValidator(account()); - connect(_connectionValidator, &ConnectionValidator::connectionResult, - this, &AccountState::slotConnectionValidatorResult); + + // ======= here we setup a new ConnectionValidator + + _connectionValidator = new ConnectionValidator(_account.get(), this); + connect(_connectionValidator, &ConnectionValidator::connectionResult, this, &AccountState::slotConnectionValidatorResult); + connect(_connectionValidator, &ConnectionValidator::sslErrors, this, + [blockJobs, this](const QList &errors) { handleSslConnectionErrors(errors, blockJobs); }); + + // ======= do some configuration related to the new validator + ConnectionValidator::ValidationMode mode = ConnectionValidator::ValidationMode::ValidateAuthAndUpdate; if (isConnected()) { // Use a small authed propfind as a minimal ping when we're // already connected. if (blockJobs) { - if (Theme::instance()->connectionValidatorClearCookies()) { - // clear the cookies directly before we try to validate - connect(_connectionValidator, &ConnectionValidator::aboutToStart, _account.get(), &Account::clearCookieJar, Qt::DirectConnection); - } - _connectionValidator->checkServer(); + _connectionValidator->setClearCookies(true); + mode = ConnectionValidator::ValidationMode::ValidateAuth; } else { - _connectionValidator->checkServerAndUpdate(); + mode = ConnectionValidator::ValidationMode::ValidateAuthAndUpdate; } } else { // Check the server and then the auth. + if (_waitingForNewCredentials) { + mode = ConnectionValidator::ValidationMode::ValidateServer; + } else { + _connectionValidator->setClearCookies(true); + mode = ConnectionValidator::ValidationMode::ValidateAuthAndUpdate; + } + } + // ======= and FINALLY we start the check + _connectionValidator->checkServer(mode); +} + +void AccountState::handleSslConnectionErrors(const QList &errors, bool jobsWereBlocked) +{ + if (NetworkInformation::instance()->isBehindCaptivePortal()) { + return; + } + // ignore errors for already accepted certificates + auto filteredErrors = _account->accessManager()->filterSslErrors(errors); + if (!filteredErrors.isEmpty()) { + QSet certs; + certs.reserve(filteredErrors.size()); + for (const auto &error : std::as_const(filteredErrors)) { + certs << error.certificate(); + } + TlsErrorDialog tlsDlg(filteredErrors, _account->url().host(), ocApp()->mainWindow()); + ocApp()->ensureVisible(); + int res = tlsDlg.exec(); + if (res == TlsErrorDialog::Accepted) { + _account->addApprovedCerts(certs); + // force a new _connectionValidator + if (_connectionValidator) { + resetConnectionValidator(); + } + checkConnectivity(jobsWereBlocked); + } + } +} - // Let's try this for all OS and see if it fixes the Qt issues we have on Linux #4720 #3888 #4051 - //#ifdef Q_OS_WIN - // There seems to be a bug in Qt on Windows where QNAM sometimes stops - // working correctly after the computer woke up from sleep. See #2895 #2899 - // and #2973. - // As an attempted workaround, reset the QNAM regularly if the account is - // disconnected. - account()->resetNetworkAccessManager(); - - // If we don't reset the ssl config a second CheckServerJob can produce a - // ssl config that does not have a sensible certificate chain. - account()->setSslConfiguration(QSslConfiguration()); - //#endif - _connectionValidator->checkServerAndUpdate(); +void AccountState::resetConnectionValidator() +{ + if (_connectionValidator) { + // we should look at this to be really sure deleteLater is required. + ConnectionValidator *soonToDie = _connectionValidator; + _connectionValidator = nullptr; + soonToDie->deleteLater(); } } +void AccountState::onNetworkReachabilityChanged(NetworkInformation::Reachability reachability) +{ + switch (reachability) { + case NetworkInformation::Reachability::Online: + case NetworkInformation::Reachability::Site: + case NetworkInformation::Reachability::Unknown: + // the connection might not yet be established + checkConnectivity(); + break; + case NetworkInformation::Reachability::Disconnected: + // explicitly set disconnected, this way a successful checkConnectivity will trigger a local discover + if (state() != State::SignedOut) { + setState(State::Disconnected); + } + break; + case NetworkInformation::Reachability::Local: + default: + break; + } +} + +void AccountState::onNetworkMeteredChanged(bool isMetered) +{ + if (ConfigFile().pauseSyncWhenMetered()) { + if (state() == State::Connected && isMetered) { + qCInfo(lcAccountState) << "Network switched to a metered connection, setting account state to PausedDueToMetered"; + setState(State::Connecting); + } else if (state() == State::Connecting && !isMetered) { + qCInfo(lcAccountState) << "Network switched to a NON-metered connection, setting account state to Connected"; + setState(State::Connected); + } + } +} +// todo: #12 +void AccountState::onBehindCaptivePortalChanged(bool isCaptive) +{ + if (isCaptive) { + // Block jobs from starting: they will fail because of the captive portal. + // Note: this includes the `Drives` jobs started periodically by the `SpacesManager`. + _queueGuard.block(); + } else { + // Empty the jobs queue before unblocking it. The client might have been behind a captive + // portal for hours, so a whole bunch of jobs might have queued up. If we wouldn't + // clear the queue, unleashing all those jobs might look like a DoS attack. Most of them + // are also not very useful anymore (e.g. `Drives` jobs), and the important ones (PUT jobs) + // will be rescheduled by a directory scan. + _account->jobQueue()->clear(); + _queueGuard.unblock(); + } + + // The call is also made for when we "go behind" a captive portal. That ensures that not + // only the status is set to `Connecting`, but also makes the UI show that syncing is paused. + // todo: #11, #12 + // todo: decide if the value to checkConnectivity should depend on value of isCaptive + // checkConnectivity does different things depending on whether behindCaptivePortal is true, including blocking the queue guard :/ + checkConnectivity(); +} + void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors) { + // the ConnectionValidator used to delete itself which was really not healthy. + // that was removed so now we are *fully* responsible and also able to safely clean the pointer up + resetConnectionValidator(); + if (isSignedOut()) { qCWarning(lcAccountState) << "Signed out, ignoring" << status << _account->url().toString(); return; } + + if (status == ConnectionValidator::Connected && !_account->hasCapabilities()) { + // this code should only be needed when upgrading from a < 3.0 release where capabilities where not cached + // The last check was _waitingForNewCredentials = true so we only checked ValidateServer + // now check again and fetch capabilities + checkConnectivity(); + return; + } + // Come online gradually from 503 or maintenance mode if (status == ConnectionValidator::Connected && (_connectionStatus == ConnectionValidator::ServiceUnavailable @@ -380,9 +484,12 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta setState(Connected); break; case ConnectionValidator::Undefined: + [[fallthrough]]; case ConnectionValidator::NotConfigured: setState(Disconnected); break; + case ConnectionValidator::ClientUnsupported: + [[fallthrough]]; case ConnectionValidator::ServerVersionMismatch: setState(ConfigurationError); break; @@ -393,11 +500,12 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta setState(NetworkError); break; case ConnectionValidator::CredentialsWrong: + [[fallthrough]]; case ConnectionValidator::CredentialsNotReady: slotInvalidCredentials(); break; case ConnectionValidator::SslError: - setState(SignedOut); + // handled with the tlsDialog break; case ConnectionValidator::ServiceUnavailable: _timeSinceMaintenanceOver.invalidate(); @@ -410,29 +518,36 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta case ConnectionValidator::Timeout: setState(NetworkError); break; + case ConnectionValidator::CaptivePortal: + setState(Connecting); + break; } } void AccountState::slotInvalidCredentials() { - if (!_waitingForNewCredentials) { - qCInfo(lcAccountState) << "Invalid credentials for" << _account->url().toString() - << "asking user"; + AbstractCredentials *creds = account()->credentials(); + if (!creds || _waitingForNewCredentials) + return; - _waitingForNewCredentials = true; - if (account()->credentials()->ready()) { - account()->credentials()->invalidateToken(); - } - if (auto creds = qobject_cast(account()->credentials())) { - if (creds->refreshAccessToken()) - return; - } - account()->credentials()->askFromUser(); - } + qCInfo(lcAccountState) << "Invalid credentials for" << _account->url().toString(); + + _waitingForNewCredentials = true; + if (creds->ready()) + creds->invalidateToken(); + + qCInfo(lcAccountState) << "refreshing oauth using refresh token"; + if (creds->refreshAccessToken()) + return; + + qCInfo(lcAccountState) << "refreshing oauth failed"; + qCInfo(lcAccountState) << "asking user"; + + creds->askFromUser(); setState(AskingCredentials); } -void AccountState::slotCredentialsFetched(AbstractCredentials *) +void AccountState::slotCredentialsFetched() { // Make a connection attempt, no matter whether the credentials are // ready or not - we want to check whether we can get an SSL connection @@ -443,34 +558,39 @@ void AccountState::slotCredentialsFetched(AbstractCredentials *) checkConnectivity(); } -void AccountState::slotCredentialsAsked(AbstractCredentials *credentials) + +Account *AccountState::accountForQml() const { - qCInfo(lcAccountState) << "Credentials asked for" << _account->url().toString() - << "are they ready?" << credentials->ready(); + return _account; +} - _waitingForNewCredentials = false; +std::unique_ptr AccountState::settings() +{ + auto s = ConfigFile::settingsWithGroup(QStringLiteral("Accounts")); + s->beginGroup(_account->groupIndex()); + return s; +} - if (!credentials->ready()) { - // User canceled the connection or did not give a password - setState(SignedOut); - return; - } +bool AccountState::isSettingUp() const +{ + return _settingUp; +} - if (_connectionValidator) { - // When new credentials become available we always want to restart the - // connection validation, even if it's currently running. - _connectionValidator->deleteLater(); - _connectionValidator = nullptr; +// Refactoring todo: We currently call this from owncloudgui to set the val true then false at a later point +// actual consumer is in the AccountSettings gui - it shows a spinny from the time it's set to +// true to when it switches to false. IMO this does NOT belong in the AccountState if it can't +// manage the value itself. It needs to go elsewhere, ideally the owncloudgui can just call +// into the account settings directly as any good controller would +void AccountState::setSettingUp(bool settingUp) +{ + if (_settingUp != settingUp) { + _settingUp = settingUp; + Q_EMIT isSettingUpChanged(settingUp); } - - checkConnectivity(); } - -std::unique_ptr AccountState::settings() +bool AccountState::readyForSync() const { - auto s = ConfigFile::settingsWithGroup(QLatin1String("Accounts")); - s->beginGroup(_account->id()); - return s; + return !_fetchCapabilitiesJob && isConnected(); } } // namespace OCC diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index 731165a5c41..3ab618247b3 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -13,34 +13,45 @@ */ -#ifndef ACCOUNTINFO_H -#define ACCOUNTINFO_H +#pragma once +#include "gui/networkinformation.h" +#include "gui/owncloudguilib.h" + +#include "connectionvalidator.h" #include "jobqueue.h" +#include "account.h" + #include #include #include -#include "connectionvalidator.h" -#include "creds/abstractcredentials.h" #include +#include -class QSettings; + +class QDialog; class QMessageBox; +class QSettings; namespace OCC { -class AccountState; -class Account; +class QuotaInfo; +class TlsErrorDialog; +class FetchServerSettingsJob; /** * @brief Extra info about an ownCloud server account. * @ingroup gui */ -class AccountState : public QObject, public QSharedData +class OWNCLOUDGUI_EXPORT AccountState : public QObject { Q_OBJECT - Q_PROPERTY(AccountPtr account MEMBER _account) + Q_PROPERTY(Account *account READ accountForQml CONSTANT) + Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged) + Q_PROPERTY(AccountState::State state READ state NOTIFY stateChanged) + QML_ELEMENT + QML_UNCREATABLE("Only created by AccountManager") public: enum State { @@ -71,21 +82,24 @@ class AccountState : public QObject, public QSharedData ConfigurationError, /// We are currently asking the user for credentials - AskingCredentials + AskingCredentials, + + Connecting }; + Q_ENUM(State) /// The actual current connectivity status. typedef ConnectionValidator::Status ConnectionStatus; /// Use the account as parent - explicit AccountState(AccountPtr account); + explicit AccountState(Account *account); ~AccountState() override; /** Creates an account state from settings and an Account object. * * Use from AccountManager with a prepared QSettings object only. */ - static AccountState *loadFromSettings(AccountPtr account, const QSettings &settings); + static AccountState *loadFromSettings(Account *account, const QSettings &settings); /** Writes account state information to settings. * @@ -93,16 +107,17 @@ class AccountState : public QObject, public QSharedData */ void writeToSettings(QSettings &settings) const; - AccountPtr account() const; + Account *account() const; ConnectionStatus connectionStatus() const; QStringList connectionErrors() const; State state() const; - static QString stateString(State state); bool isSignedOut() const; + [[nodiscard]] bool readyForSync() const; + /** A user-triggered sign out which disconnects, stops syncs * for the account and forgets the password. */ void signOutByUi(); @@ -131,39 +146,62 @@ class AccountState : public QObject, public QSharedData * the server to validate the connection if the last successful etag job * was not so long ago. */ - void tagLastSuccessfullETagRequest(const QDateTime &tp); - void updateUrlDialog(const QUrl &url); + void tagLastSuccessfulETagRequest(const QDateTime &tp); + + /*** + * The account is set up for the first time, this may take some time + */ + bool isSettingUp() const; + void setSettingUp(bool settingUp); -public slots: - /// Triggers a ping to the server to update state and - /// connection status and errors. - /// verifyServerState indicates that we must check the server - void checkConnectivity(bool verifyServerState = false); + /// Triggers a ping to the server to update state, connection status and errors. + /// blockJobs determines if we block the job queue while the connection is checked + void checkConnectivity(bool blockJobs = false); private: + void setState(State state); -signals: +Q_SIGNALS: void stateChanged(State state); void isConnectedChanged(); - void urlUpdated(); + void isSettingUpChanged(bool settingUp); protected Q_SLOTS: void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors); void slotInvalidCredentials(); - void slotCredentialsFetched(AbstractCredentials *creds); - void slotCredentialsAsked(AbstractCredentials *creds); + void slotCredentialsFetched(); private: - AccountPtr _account; + Account *accountForQml() const; + QPointer _account; JobQueueGuard _queueGuard; State _state; ConnectionStatus _connectionStatus; QStringList _connectionErrors; bool _waitingForNewCredentials; QDateTime _timeOfLastETagCheck; - QPointer _connectionValidator; - QPointer _updateUrlDialog; + bool _settingUp = false; + + ConnectionValidator *_connectionValidator; + void resetConnectionValidator(); + + void connectAccount(); + void connectNetworkInformation(); + + void onNetworkReachabilityChanged(NetworkInformation::Reachability reachability); + void onNetworkMeteredChanged(bool isMetered); + void onBehindCaptivePortalChanged(bool isCaptive); + + // this doesn't completely thrill me because it has dependencies on the rest of the gui but for now, it's + // ok and making it private is pretty sweet, too. + void confirmUrlUpdate(const QUrl &newUrl); + + // handles ssl errors from the ConnectionValidator + // if there are ssl errors it will pop a dialog asking user to accept new certificate. + // if that is accepted, we update the certificate on the account, then rerun checkConnection using jobsWereBlocked, + // which is what was in play during the connection check that produced the error(s) + void handleSslConnectionErrors(const QList &errors, bool jobsWereBlocked); /** * Starts counting when the server starts being back up after 503 or @@ -176,10 +214,11 @@ protected Q_SLOTS: * Milliseconds for which to delay reconnection after 503/maintenance. */ std::chrono::milliseconds _maintenanceToConnectedDelay; + + QuotaInfo *_quotaInfo = nullptr; + + QPointer _fetchCapabilitiesJob; }; } Q_DECLARE_METATYPE(OCC::AccountState *) -Q_DECLARE_METATYPE(OCC::AccountStatePtr) - -#endif //ACCOUNTINFO_H diff --git a/src/gui/activitydata.cpp b/src/gui/activitydata.cpp deleted file mode 100644 index 7d74a0d0c75..00000000000 --- a/src/gui/activitydata.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 - -#include "activitydata.h" - - -namespace OCC { - -Activity::Activity(Activity::Type type, qlonglong id, AccountPtr acc, const QString &subject, const QString &message, const QString &file, const QUrl &link, const QDateTime &dateTime, const QVector &&links) - : _type(type) - , _id(id) - , _accName(acc->displayName()) - , _uuid(acc->uuid()) - , _subject(subject) - , _message(message) - , _file(file) - , _link(link) - , _dateTime(dateTime) - , _links(links) -{ -} - -Activity::Type Activity::type() const -{ - return _type; -} - -Activity::Identifier Activity::id() const -{ - return _id; -} - -QString Activity::subject() const -{ - return _subject; -} - -QString Activity::message() const -{ - return _message; -} - -QString Activity::file() const -{ - return _file; -} - -QUrl Activity::link() const -{ - return _link; -} - -QDateTime Activity::dateTime() const -{ - return _dateTime; -} - -QString Activity::accName() const -{ - return _accName; -} - -QUuid Activity::uuid() const -{ - return _uuid; -} - -const QVector &Activity::links() const -{ - return _links; -} - -bool Activity::operator==(const Activity &lhs) const -{ - return (_type == lhs._type && _id == lhs._id && _uuid == lhs._uuid); -} -} diff --git a/src/gui/activitydata.h b/src/gui/activitydata.h deleted file mode 100644 index 2c66a299360..00000000000 --- a/src/gui/activitydata.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef ACTIVITYDATA_H -#define ACTIVITYDATA_H - -#include - -#include "account.h" - -namespace OCC { -/** - * @brief The ActivityLink class describes actions of an activity - * - * These are part of notifications which are mapped into activities. - */ - -class ActivityLink -{ -public: - QString _label; - QString _link; - QByteArray _verb; - bool _isPrimary; -}; - -/* ==================================================================== */ -/** - * @brief Activity Structure - * @ingroup gui - * - * contains all the information describing a single activity. - */ - -class Activity -{ -public: - using Identifier = qlonglong; - enum Type { - ActivityType, - NotificationType - }; - Activity() = default; - explicit Activity(Type type, Identifier id, AccountPtr acc, const QString &subject, const QString &message, const QString &file, const QUrl &link, const QDateTime &dateTime, const QVector &&links = {}); - - Type type() const; - - Identifier id() const; - - QString subject() const; - - QString message() const; - - QString file() const; - - QUrl link() const; - - QDateTime dateTime() const; - - QString accName() const; - - QUuid uuid() const; - - const QVector &links() const; - - bool operator==(const Activity &lhs) const; - -private: - Type _type; - Identifier _id; - QString _accName; /* display name of the account */ - QUuid _uuid; /* uuid of the account */ - QString _subject; - QString _message; - QString _file; - QUrl _link; - QDateTime _dateTime; - - QVector _links; /* These links are transformed into buttons that - * call links as reactions on the activity */ -}; - - -/* ==================================================================== */ -/** - * @brief The ActivityList - * @ingroup gui - * - * A QList based list of Activities - */ - -typedef QList ActivityList; -} - -#endif // ACTIVITYDATA_H diff --git a/src/gui/activitysettings.cpp b/src/gui/activitysettings.cpp new file mode 100644 index 00000000000..16048822937 --- /dev/null +++ b/src/gui/activitysettings.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 "activitysettings.h" +#include "localactivitywidget.h" +#include "resources.h" +#include "syncerrorwidget.h" + +#include +#include + + +namespace OCC { +ActivitySettings::ActivitySettings(QWidget *parent) + : QWidget(parent) +{ + QHBoxLayout *hbox = new QHBoxLayout(this); + + // create a tab widget for the activity views + _tab = new QTabWidget(this); + hbox->addWidget(_tab); + setLayout(hbox); + + auto _localActivityWidget = new LocalActivityWidget(this); + auto localActivityTabId = _tab->addTab(_localActivityWidget, Resources::getCoreIcon(QStringLiteral("states/sync")), tr("Local Activity")); + + auto _syncErrorWidget = new SyncErrorWidget(this); + _syncErrorTabId = _tab->addTab(_syncErrorWidget, Resources::getCoreIcon(QStringLiteral("states/warning")), QString()); + slotShowIssueItemCount(0); // to display the label. + connect(_syncErrorWidget, &SyncErrorWidget::issueCountUpdated, this, &ActivitySettings::slotShowIssueItemCount); + + // We want the local activity tab to be the default + _tab->setCurrentIndex(localActivityTabId); +} + +ActivitySettings::~ActivitySettings() +{ +} + +void ActivitySettings::slotShowIssueItemCount(const int cnt) +{ + QString cntText = tr("Not Synced"); + if (cnt) { + //: %1 is the number of not synced files. + cntText = tr("Not Synced (%1)").arg(cnt); + } + _tab->setTabText(_syncErrorTabId, cntText); +} +} \ No newline at end of file diff --git a/src/gui/activitysettings.h b/src/gui/activitysettings.h new file mode 100644 index 00000000000..ecac465bf1f --- /dev/null +++ b/src/gui/activitysettings.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 + +class QTabWidget; + +namespace OCC { + +/** + * @brief The ActivitySettings class + * @ingroup gui + * + * Implements a tab for the settings dialog, displaying lists of local activities and sync issues + */ +class ActivitySettings : public QWidget +{ + Q_OBJECT +public: + explicit ActivitySettings(QWidget *parent = nullptr); + ~ActivitySettings() override; + +private Q_SLOTS: + void slotShowIssueItemCount(int cnt); + +private: + QTabWidget *_tab = nullptr; + int _syncErrorTabId = -1; +}; +} diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp deleted file mode 100644 index 93a6c1bc992..00000000000 --- a/src/gui/activitywidget.cpp +++ /dev/null @@ -1,608 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 -#include - -#include "activitywidget.h" -#include "configfile.h" -#include "syncresult.h" -#include "logger.h" -#include "theme.h" -#include "folderman.h" -#include "syncfileitem.h" -#include "folder.h" -#include "openfilemanager.h" -#include "owncloudpropagator.h" -#include "account.h" -#include "accountstate.h" -#include "accountmanager.h" -#include "protocolwidget.h" -#include "issueswidget.h" -#include "QProgressIndicator.h" -#include "notificationwidget.h" -#include "notificationconfirmjob.h" -#include "servernotificationhandler.h" -#include "theme.h" -#include "ocsjob.h" - -#include "models/activitylistmodel.h" -#include "models/expandingheaderview.h" -#include "models/models.h" - -#include "ui_activitywidget.h" - -#include - -using namespace std::chrono; -using namespace std::chrono_literals; - -// time span in milliseconds which has to be between two -// refreshes of the notifications -#define NOTIFICATION_REQUEST_FREE_PERIOD 15000 - -namespace OCC { - -ActivityWidget::ActivityWidget(QWidget *parent) - : QWidget(parent) - , _ui(new Ui::ActivityWidget) - , _notificationRequestsRunning(0) -{ - _ui->setupUi(this); - - _model = new ActivityListModel(this); - _sortModel = new QSortFilterProxyModel(this); - _sortModel->setSourceModel(_model); - _ui->_activityList->setModel(_sortModel); - _sortModel->setSortRole(Models::UnderlyingDataRole); - auto header = new ExpandingHeaderView(QStringLiteral("ActivityListModelHeader"), _ui->_activityList); - _ui->_activityList->setHorizontalHeader(header); - header->hideSection(static_cast(ActivityListModel::ActivityRole::Path)); - header->setSectionResizeMode(QHeaderView::Interactive); - header->setSortIndicator(static_cast(ActivityListModel::ActivityRole::PointInTime), Qt::DescendingOrder); - - connect(_ui->_filterButton, &QAbstractButton::clicked, this, [this] { - ProtocolWidget::showFilterMenu(_ui->_filterButton, _sortModel); - }); - - _ui->_notifyLabel->hide(); - _ui->_notifyScroll->hide(); - - // Create a widget container for the notifications. The ui file defines - // a scroll area that get a widget with a layout as children - QWidget *w = new QWidget; - _notificationsLayout = new QVBoxLayout; - w->setLayout(_notificationsLayout); - _notificationsLayout->setAlignment(Qt::AlignTop); - _ui->_notifyScroll->setAlignment(Qt::AlignTop); - _ui->_notifyScroll->setWidget(w); - - showLabels(); - - connect(_model, &ActivityListModel::activityJobStatusCode, - this, &ActivityWidget::slotAccountActivityStatus); - - connect(AccountManager::instance(), &AccountManager::accountRemoved, this, [this](AccountState *ast) { - if (_accountsWithoutActivities.remove(ast->account()->displayName())) { - showLabels(); - } - - for (const auto widget : qAsConst(_widgetForNotifId)) { - if (widget->activity().uuid() == ast->account()->uuid()) { - scheduleWidgetToRemove(widget); - } - } - }); - - connect(_model, &QAbstractItemModel::modelReset, this, &ActivityWidget::dataChanged); - connect(_ui->_activityList, &QListView::customContextMenuRequested, this, &ActivityWidget::slotItemContextMenu); - header->setContextMenuPolicy(Qt::CustomContextMenu); - connect(header, &QListView::customContextMenuRequested, header, [header, this] { - auto menu = ProtocolWidget::showFilterMenu(header, _sortModel); - header->addResetActionToMenu(menu); - }); - - connect(&_removeTimer, &QTimer::timeout, this, &ActivityWidget::slotCheckToCleanWidgets); - _removeTimer.setInterval(1000); -} - -ActivityWidget::~ActivityWidget() -{ - delete _ui; -} - -void ActivityWidget::slotRefreshActivities(AccountState *ptr) -{ - _model->slotRefreshActivity(ptr); -} - -void ActivityWidget::slotRefreshNotifications(AccountState *ptr) -{ - // start a server notification handler if no notification requests - // are running - if (_notificationRequestsRunning == 0) { - ServerNotificationHandler *snh = new ServerNotificationHandler; - connect(snh, &ServerNotificationHandler::newNotificationList, - this, &ActivityWidget::slotBuildNotificationDisplay); - - snh->slotFetchNotifications(ptr); - } else { - qCWarning(lcActivity) << "Notification request counter not zero."; - } -} - -void ActivityWidget::slotRemoveAccount(AccountState *ptr) -{ - _model->slotRemoveAccount(ptr); -} - -void ActivityWidget::showLabels() -{ - QString t = tr("Server Activities"); - _ui->_headerLabel->setTextFormat(Qt::RichText); - _ui->_headerLabel->setText(t); - - _ui->_notifyLabel->setText(tr("Notifications")); - - t.clear(); - QSetIterator i(_accountsWithoutActivities); - while (i.hasNext()) { - t.append(tr("
Account %1 does not have activities enabled.").arg(i.next())); - } - _ui->_bottomLabel->setTextFormat(Qt::RichText); - _ui->_bottomLabel->setText(t); -} - -void ActivityWidget::slotAccountActivityStatus(AccountState *ast, int statusCode) -{ - if (!(ast && ast->account())) { - return; - } - if (statusCode == 999) { - _accountsWithoutActivities.insert(ast->account()->displayName()); - } else { - _accountsWithoutActivities.remove(ast->account()->displayName()); - } - - checkActivityTabVisibility(); - showLabels(); -} - -void ActivityWidget::checkActivityTabVisibility() -{ - int accountCount = AccountManager::instance()->accounts().count(); - bool hasAccountsWithActivity = - _accountsWithoutActivities.count() != accountCount; - bool hasNotifications = !_widgetForNotifId.isEmpty(); - - _ui->_headerLabel->setVisible(hasAccountsWithActivity); - _ui->_activityList->setVisible(hasAccountsWithActivity); - - _ui->_notifyLabel->setVisible(hasNotifications); - _ui->_notifyScroll->setVisible(hasNotifications); - - emit hideActivityTab(!hasAccountsWithActivity && !hasNotifications); -} - -// GUI: Display the notifications. -// All notifications in list are coming from the same account -// but in the _widgetForNotifId hash widgets for all accounts are -// collected. -void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list) -{ - if (list.empty()) { - return; - } - // compute the count to display later - QHash accNotified; - - // Whether a new notification widget was added to the notificationLayout. - bool newNotificationShown = false; - - for (const auto &activity : list) { - if (_blacklistedNotifications.contains(activity)) { - qCInfo(lcActivity) << "Activity in blacklist, skip"; - continue; - } - - NotificationWidget *widget = nullptr; - - if (_widgetForNotifId.contains(activity.id())) { - widget = _widgetForNotifId[activity.id()]; - } else { - widget = new NotificationWidget(this); - connect(widget, &NotificationWidget::sendNotificationRequest, - this, &ActivityWidget::slotSendNotificationRequest); - connect(widget, &NotificationWidget::requestCleanupAndBlacklist, - this, &ActivityWidget::slotRequestCleanupAndBlacklist); - - _notificationsLayout->addWidget(widget); - // _ui->_notifyScroll->setMinimumHeight( widget->height()); - _ui->_notifyScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow); - _widgetForNotifId[activity.id()] = widget; - newNotificationShown = true; - } - - widget->setActivity(activity); - - // handle gui logs. In order to NOT annoy the user with every fetching of the - // notifications the notification id is stored in a Set. Only if an id - // is not in the set, it qualifies for guiLog. - // Important: The _guiLoggedNotifications set must be wiped regularly which - // will repeat the gui log. - - // after one hour, clear the gui log notification store - if (_guiLogTimer.elapsed() > duration_cast(1h).count()) { - _guiLoggedNotifications.clear(); - } - if (!_guiLoggedNotifications.contains(activity.id())) { - // store the uui of the account that sends the notification to be - // able to add it to the tray notification - accNotified[activity.accName()]++; - _guiLoggedNotifications.insert(activity.id()); - } - } - - // check if there are widgets that have no corresponding activity from - // the server any more. Collect them in a list - - const auto accId = list.first().uuid(); - QList strayCats; - for (auto it = _widgetForNotifId.cbegin(); it != _widgetForNotifId.cend(); ++it) { - bool found = false; - // do not mark widgets of other accounts to delete. - if (it.value()->activity().uuid() != accId) { - continue; - } - - for (const auto &activity : list) { - if (activity.id() == it.key()) { - // found an activity - found = true; - break; - } - } - if (!found) { - // the activity does not exist any more. - strayCats.append(it.key()); - } - } - - // .. and now delete all these stray cat widgets. - for (const auto &strayCatId : strayCats) { - NotificationWidget *widgetToGo = _widgetForNotifId[strayCatId]; - scheduleWidgetToRemove(widgetToGo, 0); - } - - checkActivityTabVisibility(); - - const int newGuiLogCount = accNotified.count(); - - if (newGuiLogCount > 0) { - // restart the gui log timer now that we show a notification - _guiLogTimer.start(); - - // Assemble a tray notification - QString msg; - if (newGuiLogCount == 1) { - msg = tr("%n notifications(s) for %1.", "", accNotified.begin().value()).arg(accNotified.begin().key()); - } else if (newGuiLogCount >= 2) { - const auto acc1 = accNotified.begin(); - const auto acc2 = acc1 + 1; - if (newGuiLogCount == 2) { - const int notiCount = acc1.value() + acc2.value(); - msg = tr("%n notifications(s) for %1 and %2.", "", notiCount).arg(acc1.key(), acc2.key()); - } else { - msg = tr("New notifications for %1, %2 and other accounts.").arg(acc1.key(), acc2.key()); - } - } - const QString log = tr("Open the activity view for details."); - emit guiLog(msg, log); - } - - if (newNotificationShown) { - emit newNotification(); - } -} - -void ActivityWidget::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb) -{ - qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName; - NotificationWidget *theSender = qobject_cast(sender()); - - const QStringList validVerbs = { QStringLiteral("GET"), - QStringLiteral("PUT"), - QStringLiteral("POST"), - QStringLiteral("DELETE") }; - - if (validVerbs.contains(verb)) { - AccountStatePtr acc = AccountManager::instance()->account(accountName); - if (acc) { - NotificationConfirmJob *job = new NotificationConfirmJob(acc->account()); - QUrl l(link); - job->setLinkAndVerb(l, verb); - job->setWidget(theSender); - connect(job, &AbstractNetworkJob::networkError, - this, &ActivityWidget::slotNotifyNetworkError); - connect(job, &NotificationConfirmJob::jobFinished, - this, &ActivityWidget::slotNotifyServerFinished); - job->start(); - - // count the number of running notification requests. If this member var - // is larger than zero, no new fetching of notifications is started - _notificationRequestsRunning++; - } - } else { - qCWarning(lcActivity) << "Notification Links: Invalid verb:" << verb; - } -} - -void ActivityWidget::endNotificationRequest(NotificationWidget *widget, int replyCode) -{ - _notificationRequestsRunning--; - if (widget) { - widget->slotNotificationRequestFinished(replyCode); - } -} - -void ActivityWidget::slotNotifyNetworkError(QNetworkReply *reply) -{ - NotificationConfirmJob *job = qobject_cast(sender()); - if (!job) { - return; - } - - int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - endNotificationRequest(job->widget(), resultCode); - qCWarning(lcActivity) << "Server notify job failed with code " << resultCode; -} - -void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCode) -{ - NotificationConfirmJob *job = qobject_cast(sender()); - if (!job) { - return; - } - - endNotificationRequest(job->widget(), replyCode); - qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply; - - // if the notification was successful start a timer that triggers - // removal of the done widgets in a few seconds - // Add 200 millisecs to the predefined value to make sure that the timer in - // widget's method readyToClose() has elapsed. - if (replyCode == OCS_SUCCESS_STATUS_CODE || replyCode == OCS_SUCCESS_STATUS_CODE_V2) { - scheduleWidgetToRemove(job->widget()); - } -} - -// blacklist the activity coming in here. -void ActivityWidget::slotRequestCleanupAndBlacklist(const Activity &blacklistActivity) -{ - if (!_blacklistedNotifications.contains(blacklistActivity)) { - _blacklistedNotifications.append(blacklistActivity); - } - - NotificationWidget *widget = _widgetForNotifId[blacklistActivity.id()]; - scheduleWidgetToRemove(widget); -} - -void ActivityWidget::scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds) -{ - if (!widget) { - return; - } - // in five seconds from now, remove the widget. - QDateTime removeTime = QDateTime::currentDateTimeUtc().addMSecs(milliseconds); - QDateTime &it = _widgetsToRemove[widget]; - if (!it.isValid() || it > removeTime) { - it = removeTime; - } - if (!_removeTimer.isActive()) { - _removeTimer.start(); - } -} - -// Called every second to see if widgets need to be removed. -void ActivityWidget::slotCheckToCleanWidgets() -{ - auto currentTime = QDateTime::currentDateTimeUtc(); - auto it = _widgetsToRemove.begin(); - while (it != _widgetsToRemove.end()) { - // loop over all widgets in the to-remove queue - QDateTime t = it.value(); - NotificationWidget *widget = it.key(); - - if (currentTime > t) { - // found one to remove! - Activity::Identifier id = widget->activity().id(); - _widgetForNotifId.remove(id); - widget->deleteLater(); - it = _widgetsToRemove.erase(it); - } else { - ++it; - } - } - - if (_widgetsToRemove.isEmpty()) { - _removeTimer.stop(); - } - - // check to see if the whole notification pane should be hidden - if (_widgetForNotifId.isEmpty()) { - _ui->_notifyLabel->setHidden(true); - _ui->_notifyScroll->setHidden(true); - } -} - -void ActivityWidget::slotItemContextMenu() -{ - auto rows = _ui->_activityList->selectionModel()->selectedRows(); - auto menu = new QMenu(this); - menu->setAttribute(Qt::WA_DeleteOnClose); - - // keep in sync with ProtocolWidget::showContextMenu - menu->addAction(tr("Copy to clipboard"), this, [text = Models::formatSelection(rows, Models::UnderlyingDataRole)] { - QApplication::clipboard()->setText(text); - }); - - if (rows.size() == 1) { - // keep in sync with ProtocolWidget::showContextMenu - const auto localPath = rows.first().siblingAtColumn(static_cast(ActivityListModel::ActivityRole::Path)).data(Models::UnderlyingDataRole).toString(); - if (!localPath.isEmpty()) { - menu->addAction(tr("Show in file browser"), this, [localPath] { - if (QFileInfo::exists(localPath)) { - showInFileManager(localPath); - } - }); - } - } - menu->popup(QCursor::pos()); -} - - -/* ==================================================================== */ - -ActivitySettings::ActivitySettings(QWidget *parent) - : QWidget(parent) -{ - QHBoxLayout *hbox = new QHBoxLayout(this); - setLayout(hbox); - - // create a tab widget for the three activity views - _tab = new QTabWidget(this); - hbox->addWidget(_tab); - _activityWidget = new ActivityWidget(this); - _activityTabId = _tab->addTab(_activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity")); - connect(_activityWidget, &ActivityWidget::hideActivityTab, this, &ActivitySettings::setActivityTabHidden); - connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog); - connect(_activityWidget, &ActivityWidget::newNotification, this, &ActivitySettings::slotShowActivityTab); - - _protocolWidget = new ProtocolWidget(this); - _protocolTabId = _tab->addTab(_protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol")); - - _issuesWidget = new IssuesWidget(this); - _syncIssueTabId = _tab->addTab(_issuesWidget, Theme::instance()->syncStateIcon(SyncResult::Problem), QString()); - slotShowIssueItemCount(0); // to display the label. - connect(_issuesWidget, &IssuesWidget::issueCountUpdated, - this, &ActivitySettings::slotShowIssueItemCount); - - // Add a progress indicator to spin if the acitivity list is updated. - _progressIndicator = new QProgressIndicator(this); - _tab->setCornerWidget(_progressIndicator); - - connect(&_notificationCheckTimer, &QTimer::timeout, - this, &ActivitySettings::slotRegularNotificationCheck); - - // connect a model signal to stop the animation. - connect(_activityWidget, &ActivityWidget::dataChanged, _progressIndicator, &QProgressIndicator::stopAnimation); - - // We want the protocol be the default - _tab->setCurrentIndex(1); -} - -void ActivitySettings::setNotificationRefreshInterval(std::chrono::milliseconds interval) -{ - qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval"; - _notificationCheckTimer.start(interval.count()); -} - -void ActivitySettings::setActivityTabHidden(bool hidden) -{ - if (hidden && _activityTabId > -1) { - _tab->removeTab(_activityTabId); - _activityTabId = -1; - _protocolTabId -= 1; - _syncIssueTabId -= 1; - } - - if (!hidden && _activityTabId == -1) { - _activityTabId = _tab->insertTab(0, _activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity")); - _protocolTabId += 1; - _syncIssueTabId += 1; - } -} - -void ActivitySettings::slotShowIssueItemCount(int cnt) -{ - QString cntText = tr("Not Synced"); - if (cnt) { - //: %1 is the number of not synced files. - cntText = tr("Not Synced (%1)").arg(cnt); - } - _tab->setTabText(_syncIssueTabId, cntText); -} - -void ActivitySettings::slotShowActivityTab() -{ - if (_activityTabId != -1) { - _tab->setCurrentIndex(_activityTabId); - } -} - -void ActivitySettings::slotShowIssuesTab() -{ - if (_syncIssueTabId == -1) - return; - _tab->setCurrentIndex(_syncIssueTabId); -} - -void ActivitySettings::slotRemoveAccount(AccountState *ptr) -{ - _activityWidget->slotRemoveAccount(ptr); -} - -void ActivitySettings::slotRefresh(AccountState *ptr) -{ - // QElapsedTimer isn't actually constructed as invalid. - if (!_timeSinceLastCheck.contains(ptr)) { - _timeSinceLastCheck[ptr].invalidate(); - } - QElapsedTimer &timer = _timeSinceLastCheck[ptr]; - - // Fetch Activities only if visible and if last check is longer than 15 secs ago - if (timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD) { - qCDebug(lcActivity) << "Do not check as last check is only secs ago: " << timer.elapsed() / 1000; - return; - } - if (ptr && ptr->isConnected()) { - if (isVisible() || !timer.isValid()) { - _progressIndicator->startAnimation(); - _activityWidget->slotRefreshActivities(ptr); - } - _activityWidget->slotRefreshNotifications(ptr); - timer.start(); - } -} - -void ActivitySettings::slotRegularNotificationCheck() -{ - for (const auto &a : AccountManager::instance()->accounts()) { - slotRefresh(a.data()); - } -} - -bool ActivitySettings::event(QEvent *e) -{ - if (e->type() == QEvent::Show) { - slotRegularNotificationCheck(); - } - return QWidget::event(e); -} - -ActivitySettings::~ActivitySettings() -{ -} -} diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h deleted file mode 100644 index b4a4f554288..00000000000 --- a/src/gui/activitywidget.h +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef ACTIVITYWIDGET_H -#define ACTIVITYWIDGET_H - -#include -#include -#include -#include -#include - -#include "progressdispatcher.h" -#include "owncloudgui.h" -#include "account.h" -#include "activitydata.h" - -#include "ui_activitywidget.h" - -class QPushButton; -class QProgressIndicator; - -namespace OCC { - -class Account; -class AccountStatusPtr; -class ProtocolWidget; -class IssuesWidget; -class JsonApiJob; -class NotificationWidget; -class ActivityListModel; - -namespace Ui { - class ActivityWidget; -} -class Application; - -/** - * @brief The ActivityWidget class - * @ingroup gui - * - * The list widget to display the activities, contained in the - * subsequent ActivitySettings widget. - */ - -class ActivityWidget : public QWidget -{ - Q_OBJECT -public: - explicit ActivityWidget(QWidget *parent = nullptr); - ~ActivityWidget() override; - void storeActivityList(QTextStream &ts); - - /** - * Adjusts the activity tab's and some widgets' visibility - * - * Based on whether activities are enabled and whether notifications are - * available. - */ - void checkActivityTabVisibility(); - -public slots: - void slotRefreshActivities(AccountState *ptr); - void slotRefreshNotifications(AccountState *ptr); - void slotRemoveAccount(AccountState *ptr); - void slotAccountActivityStatus(AccountState *ast, int statusCode); - void slotRequestCleanupAndBlacklist(const Activity &blacklistActivity); - -signals: - void guiLog(const QString &, const QString &); - void dataChanged(); - void hideActivityTab(bool); - void newNotification(); - -private slots: - void slotBuildNotificationDisplay(const ActivityList &list); - void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb); - void slotNotifyNetworkError(QNetworkReply *); - void slotNotifyServerFinished(const QString &reply, int replyCode); - void endNotificationRequest(NotificationWidget *widget, int replyCode); - void scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds = 100); - void slotCheckToCleanWidgets(); - -private: - void slotItemContextMenu(); - void showLabels(); - QString timeString(QDateTime dt, QLocale::FormatType format) const; - Ui::ActivityWidget *_ui; - QPushButton *_copyBtn; - - QSet _accountsWithoutActivities; - QMap _widgetForNotifId; - QElapsedTimer _guiLogTimer; - QSet _guiLoggedNotifications; - ActivityList _blacklistedNotifications; - - QHash _widgetsToRemove; - QTimer _removeTimer; - - // number of currently running notification requests. If non zero, - // no query for notifications is started. - int _notificationRequestsRunning; - - ActivityListModel *_model; - QSortFilterProxyModel *_sortModel; - QVBoxLayout *_notificationsLayout; -}; - - -/** - * @brief The ActivitySettings class - * @ingroup gui - * - * Implements a tab for the settings dialog, displaying the three activity - * lists. - */ -class ActivitySettings : public QWidget -{ - Q_OBJECT -public: - explicit ActivitySettings(QWidget *parent = nullptr); - ~ActivitySettings() override; - -public slots: - void slotRefresh(AccountState *ptr); - void slotRemoveAccount(AccountState *ptr); - - void setNotificationRefreshInterval(std::chrono::milliseconds interval); - - void slotShowIssuesTab(); - -private slots: - void setActivityTabHidden(bool hidden); - void slotRegularNotificationCheck(); - void slotShowIssueItemCount(int cnt); - void slotShowActivityTab(); - -signals: - void guiLog(const QString &, const QString &); - -private: - bool event(QEvent *e) override; - - QTabWidget *_tab; - int _activityTabId; - int _protocolTabId; - int _syncIssueTabId; - - ActivityWidget *_activityWidget; - ProtocolWidget *_protocolWidget; - IssuesWidget *_issuesWidget; - QProgressIndicator *_progressIndicator; - QTimer _notificationCheckTimer; - QHash _timeSinceLastCheck; -}; -} -#endif // ActivityWIDGET_H diff --git a/src/gui/activitywidget.ui b/src/gui/activitywidget.ui deleted file mode 100644 index 0ba5f4f2be9..00000000000 --- a/src/gui/activitywidget.ui +++ /dev/null @@ -1,160 +0,0 @@ - - - OCC::ActivityWidget - - - - 0 - 0 - 693 - 556 - - - - Form - - - - - - - 0 - 0 - - - - TextLabel - - - Qt::PlainText - - - - - - - - 0 - 0 - - - - true - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - 0 - 0 - 673 - 68 - - - - true - - - - - - - - - - - 0 - 0 - - - - TextLabel - - - Qt::RichText - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter - - - - - - - - - - 0 - 0 - - - - Qt::CustomContextMenu - - - true - - - QAbstractItemView::SelectRows - - - false - - - true - - - false - - - false - - - - - - - - 0 - 0 - - - - TextLabel - - - Qt::RichText - - - - - - - - - - _activityList - _dialogButtonBox - - - - diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 16ec28bb61f..b71ac4c0252 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -16,658 +16,164 @@ #include "application.h" -#include -#include #include "account.h" #include "accountmanager.h" +#include "accountsgui/accountsguicontroller.h" #include "accountstate.h" -#include "clientproxy.h" -#include "common/asserts.h" -#include "common/version.h" #include "common/vfs.h" -#include "config.h" #include "configfile.h" -#include "connectionvalidator.h" -#include "creds/abstractcredentials.h" -#include "csync_exclude.h" #include "folder.h" #include "folderman.h" -#include "logbrowser.h" -#include "logger.h" -#include "owncloudsetupwizard.h" -#include "settingsdialog.h" -#include "sharedialog.h" +#include "mainwindow/mainwindow.h" +#include "mainwindow/mainwindowcontroller.h" #include "socketapi/socketapi.h" -#include "sslerrordialog.h" #include "theme.h" -#include "translations.h" -#include "updater/ocupdater.h" - -#include "config.h" +#include "traymenucontroller.h" -#if defined(Q_OS_WIN) -#include +#ifdef WITH_AUTO_UPDATER +#include "updater/ocupdater.h" #endif -#if defined(WITH_CRASHREPORTER) -#include +#if defined(Q_OS_WIN) +#include #endif -#include -#include -#include -#pragma push_macro("QT_DISABLE_DEPRECATED_BEFORE") -#undef QT_DISABLE_DEPRECATED_BEFORE -#define QT_DISABLE_DEPRECATED_BEFORE 0 +#include #include -#pragma pop_macro("QT_DISABLE_DEPRECATED_BEFORE") - - -class QSocket; - -namespace { - -void migrateConfigFile(const QCoreApplication *app) -{ - using namespace OCC; - if (!ConfigFile::exists()) { - // check whether an old config location must be migrated - // we support multiple locations from old versions - // these are worked on in-order to upgrade from version to version - // the algorithm is the same for all these locations, thus we can use a loop - // note that we try to migrate in descending order, i.e., we try to migrate from the last release, then from the release before, ... - // this is done in order to avoid porting old configu - const auto configLocationsToMigrate = [&app] { - QStringList out; - // note: this change is temporary to allow using QDesktopServices etc. to determine the paths - // the application name was changed to - auto scopeGuard = qScopeGuard([&app, oldApplicationName = app->applicationName()] { - // reset to original value - app->setApplicationName(oldApplicationName); - }); - - auto addLegacyLocation = [&out](const QString &path) { - if (QFileInfo(path).isDir()) { - // macOS 10.11.x does not like trailing slash for rename/move. - out.append(Utility::stripTrailingSlash(path)); - } - }; - - QCoreApplication::setApplicationName(Theme::instance()->appNameGUI()); - - // location used in versions from 2.5 to 2.8 - addLegacyLocation(QStandardPaths::writableLocation(Utility::isWindows() ? QStandardPaths::AppDataLocation : QStandardPaths::AppConfigLocation)); - - // location used in versions <= 2.4 - // We need to use the deprecated QDesktopServices::storageLocation because of its Qt4 behavior of adding "data" to the path - addLegacyLocation(QDesktopServices::storageLocation(QDesktopServices::DataLocation)); - return out; - }(); - - // macOS 10.11.x does not like trailing slash for rename/move. - const auto confDir = Utility::stripTrailingSlash(ConfigFile::configPath()); - for (auto &oldDir : configLocationsToMigrate) { - qCInfo(lcApplication) << Q_FUNC_INFO << "Migrating old config from" << oldDir << "to" << confDir; - - if (!QFile::rename(oldDir, confDir)) { - qCWarning(lcApplication) << Q_FUNC_INFO << "Failed to move the old config directory to its new location (" << oldDir << "to" << confDir << ")"; - - // Try to move the files one by one - if (QFileInfo(confDir).isDir() || QDir().mkpath(confDir)) { - const auto filesList = QDir(oldDir).entryInfoList(QDir::Files); - qCInfo(lcApplication) << Q_FUNC_INFO << "Will move the individual files" << filesList; - for (const auto &fileInfo : filesList) { - if (!QFile::rename(fileInfo.canonicalFilePath(), confDir + "/" + fileInfo.fileName())) { - qCWarning(lcApplication) << Q_FUNC_INFO << "Fallback move of " << fileInfo.fileName() << "also failed"; - } else { - // we found a suitable config directory to migrate, hence we can stop here - // if we continued to run, we would try to overwrite the working migration - break; - } - } - } - } else { -#ifndef Q_OS_WIN - // Create a symbolic link so a downgrade of the client would still find the config. - QFile::link(confDir, oldDir); -#endif - // we found a suitable config directory to migrate, hence we can stop here - // if we continued to run, we would try to overwrite the working migration - break; - } - } - } -} - - -} // namespace +#include namespace OCC { Q_LOGGING_CATEGORY(lcApplication, "gui.application", QtInfoMsg) -bool Application::configVersionMigration() -{ - QStringList deleteKeys, ignoreKeys; - AccountManager::backwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); - FolderMan::backwardMigrationSettingsKeys(&deleteKeys, &ignoreKeys); - - ConfigFile configFile; - - // Did the client version change? - // (The client version is adjusted further down) - const bool versionChanged = QVersionNumber::fromString(configFile.clientVersionString()) != OCC::Version::version(); - - // We want to message the user either for destructive changes, - // or if we're ignoring something and the client version changed. - bool warningMessage = !deleteKeys.isEmpty() || (!ignoreKeys.isEmpty() && versionChanged); - - if (!versionChanged && !warningMessage) - return true; - - const auto backupFile = configFile.backup(); - - if (warningMessage) { - QString boldMessage; - if (!deleteKeys.isEmpty()) { - boldMessage = tr("Continuing will mean deleting these settings."); - } else { - boldMessage = tr("Continuing will mean ignoring these settings."); - } - - QMessageBox box( - QMessageBox::Warning, - APPLICATION_SHORTNAME, - tr("Some settings were configured in newer versions of this client and " - "use features that are not available in this version.
" - "
" - "%1
" - "
" - "The current configuration file was already backed up to %2.") - .arg(boldMessage, backupFile)); - box.addButton(tr("Quit"), QMessageBox::AcceptRole); - auto continueBtn = box.addButton(tr("Continue"), QMessageBox::DestructiveRole); - - box.exec(); - if (box.clickedButton() != continueBtn) { - QTimer::singleShot(0, qApp, &QApplication::quit); - return false; - } - - auto settings = ConfigFile::settingsWithGroup("foo"); - settings->endGroup(); - - // Wipe confusing keys from the future, ignore the others - for (const auto &badKey : qAsConst(deleteKeys)) - settings->remove(badKey); - } - - configFile.setClientVersionString(OCC::Version::version().toString()); - return true; -} - QString Application::displayLanguage() const { return _displayLanguage; } -ownCloudGui *Application::gui() const +TrayMenuController *Application::tray() const { - return _gui; + return _trayController; } -Application::Application(int &argc, char **argv) - : SharedTools::QtSingleApplication(Theme::instance()->appName(), argc, argv) - , _gui(nullptr) - , _theme(Theme::instance()) - , _logExpire(0) - , _logFlush(false) - , _logDebug(false) - , _userTriggeredConnect(false) - , _debugMode(false) -{ -#ifdef Q_OS_WIN - // Ensure OpenSSL config file is only loaded from app directory - const QString opensslConf = QCoreApplication::applicationDirPath() + QString("/openssl.cnf"); - qputenv("OPENSSL_CONF", opensslConf.toLocal8Bit()); -#elif defined(Q_OS_LINUX) -#if defined(OC_PLUGIN_DIR) - addLibraryPath(QDir(QApplication::applicationDirPath()).filePath(QStringLiteral(OC_PLUGIN_DIR))); -#endif -#endif - - // TODO: Can't set this without breaking current config paths - // setOrganizationName(QLatin1String(APPLICATION_VENDOR)); - setOrganizationDomain(QLatin1String(APPLICATION_REV_DOMAIN)); - setApplicationName(_theme->appName()); - setWindowIcon(_theme->applicationIcon()); - - // migrate old configuration files if necessary - migrateConfigFile(this); - - // needed during commandline options parsing - setApplicationVersion(_theme->versionSwitchOutput()); - - parseOptions(arguments()); - - if (isRunning()) - return; +Application *Application::_instance = nullptr; -#if defined(WITH_CRASHREPORTER) - if (ConfigFile().crashReporter()) { - auto reporter = QStringLiteral(CRASHREPORTER_EXECUTABLE); -#ifdef Q_OS_WIN - if (!reporter.endsWith(QLatin1String(".exe"))) { - reporter.append(QLatin1String(".exe")); - } -#endif - _crashHandler.reset(new CrashReporter::Handler(QDir::tempPath(), true, reporter)); - } -#endif - - setupLogging(); - setupTranslations(); - - if (!configVersionMigration()) { - return; - } +Application::Application(Platform *platform, const QString &displayLanguage, bool debugMode) + : _debugMode(debugMode) + , _displayLanguage(displayLanguage) +{ + platform->migrate(); - ConfigFile cfg; - // The timeout is initialized with an environment variable, if not, override with the value from the config - if (!AbstractNetworkJob::httpTimeout.count()) { - AbstractNetworkJob::httpTimeout = cfg.timeout(); - } + qCInfo(lcApplication) << "Plugin search paths:" << qApp->libraryPaths(); // Check vfs plugins - if (Theme::instance()->showVirtualFilesOption() && bestAvailableVfsMode() == Vfs::Off) { - qCWarning(lcApplication) << "Theme wants to show vfs mode, but no vfs plugins are available"; + if (VfsPluginManager::instance().bestAvailableVfsMode() == Vfs::Off) { + qCWarning(lcApplication) << "No vfs plugins are available"; } - if (isVfsPluginAvailable(Vfs::WindowsCfApi)) + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) qCInfo(lcApplication) << "VFS windows plugin is available"; - if (isVfsPluginAvailable(Vfs::WithSuffix)) - qCInfo(lcApplication) << "VFS suffix plugin is available"; - - if (_quitInstance) { - QTimer::singleShot(0, qApp, &QApplication::quit); - return; - } - - _folderManager.reset(new FolderMan); - - connect(this, &SharedTools::QtSingleApplication::messageReceived, this, &Application::slotParseMessage); - - if (!AccountManager::instance()->restore()) { - // If there is an error reading the account settings, try again - // after a couple of seconds, if that fails, give up. - // (non-existence is not an error) - Utility::sleep(5); - if (!AccountManager::instance()->restore()) { - qCCritical(lcApplication) << "Could not read the account settings, quitting"; - QMessageBox::critical( - nullptr, - tr("Error accessing the configuration file"), - tr("There was an error while accessing the configuration " - "file at %1.") - .arg(ConfigFile::configFile()), - tr("Quit %1").arg(Theme::instance()->appNameGUI())); - QTimer::singleShot(0, qApp, &QApplication::quit); - return; - } - } - - FolderMan::instance()->setSyncEnabled(true); - - setQuitOnLastWindowClosed(false); - _theme->setSystrayUseMonoIcons(cfg.monoIcons()); - connect(_theme, &Theme::systrayUseMonoIconsChanged, this, &Application::slotUseMonoIconsChanged); - - // Setting up the gui class will allow tray notifications for the - // setup that follows, like folder setup - _gui = new ownCloudGui(this); - if (_showSettings) { - _gui->slotShowSettings(); - } - - FolderMan::instance()->setupFolders(); - _proxy.setupQtProxyFromConfig(); // folders have to be defined first, than we set up the Qt proxy. + ConfigFile cfg; - // Enable word wrapping of QInputDialog (#4197) - setStyleSheet("QInputDialog QLabel { qproperty-wordWrap:1; }"); + // this should be called once during application startup to make sure we don't miss any messages + cfg.configureHttpLogging(); - connect(AccountManager::instance(), &AccountManager::accountAdded, - this, &Application::slotAccountStateAdded); - connect(AccountManager::instance(), &AccountManager::accountRemoved, - this, &Application::slotAccountStateRemoved); - for (const auto &ai : AccountManager::instance()->accounts()) { - slotAccountStateAdded(ai.data()); + // The timeout is initialized with an environment variable, if not, override with the value from the config + if (AbstractNetworkJob::httpTimeout == AbstractNetworkJob::DefaultHttpTimeout) { + AbstractNetworkJob::httpTimeout = cfg.timeout(); } - connect(FolderMan::instance()->socketApi(), &SocketApi::shareCommandReceived, - _gui.data(), &ownCloudGui::slotShowShareDialog); + qApp->setQuitOnLastWindowClosed(false); - // startup procedure. - connect(&_checkConnectionTimer, &QTimer::timeout, this, &Application::slotCheckConnection); - _checkConnectionTimer.setInterval(ConnectionValidator::DefaultCallingInterval); - _checkConnectionTimer.start(); - // Also check immediately - QTimer::singleShot(0, this, &Application::slotCheckConnection); - - // Can't use onlineStateChanged because it is always true on modern systems because of many interfaces - connect(&_networkConfigurationManager, &QNetworkConfigurationManager::configurationChanged, - this, &Application::slotSystemOnlineConfigurationChanged); + // todo: dc-307 - there should be no setters and hence no notifications from theme + Theme::instance()->setSystrayUseMonoIcons(cfg.monoIcons()); + connect(Theme::instance(), &Theme::systrayUseMonoIconsChanged, this, &Application::slotUseMonoIconsChanged); +#ifdef WITH_AUTO_UPDATER // Update checks - UpdaterScheduler *updaterScheduler = new UpdaterScheduler(this); - connect(updaterScheduler, &UpdaterScheduler::updaterAnnouncement, - _gui.data(), [this](const QString &title, const QString &msg) { - _gui->slotShowTrayMessage(title, msg); - }); - connect(updaterScheduler, &UpdaterScheduler::requestRestart, - _folderManager.data(), &FolderMan::slotScheduleAppRestart); + UpdaterScheduler *updaterScheduler = new UpdaterScheduler(this, this); + // the updater scheduler takes care of connecting its GUI bits to other components + (void)updaterScheduler; +#endif // Cleanup at Quit. - connect(this, &QCoreApplication::aboutToQuit, this, &Application::slotCleanup); + connect(qApp, &QCoreApplication::aboutToQuit, this, &Application::slotCleanup); + qApp->installEventFilter(this); } Application::~Application() { - // Make sure all folders are gone, otherwise removing the - // accounts will remove the associated folders from the settings. - if (_folderManager) { - _folderManager->unloadAndDeleteAllFolders(); - } - - // Remove the account from the account manager so it can be deleted. - AccountManager::instance()->shutdown(); } -void Application::slotAccountStateRemoved(AccountState *accountState) +QMainWindow *Application::mainWindow() const { - if (_gui) { - disconnect(accountState, &AccountState::stateChanged, - _gui.data(), &ownCloudGui::slotAccountStateChanged); - disconnect(accountState->account().data(), &Account::serverVersionChanged, - _gui.data(), &ownCloudGui::slotTrayMessageIfServerUnsupported); - } - if (_folderManager) { - disconnect(accountState, &AccountState::stateChanged, - _folderManager.data(), &FolderMan::slotAccountStateChanged); - disconnect(accountState->account().data(), &Account::serverVersionChanged, - _folderManager.data(), &FolderMan::slotServerVersionChanged); - } - - // if there is no more account, show the wizard. - if (_gui && AccountManager::instance()->accounts().isEmpty()) { - // allow to add a new account if there is non any more. Always think - // about single account theming! - gui()->runNewAccountWizard(); - } + return _mainWin; } -void Application::slotAccountStateAdded(AccountState *accountState) +void Application::ensureVisible() const { - connect(accountState, &AccountState::stateChanged, - _gui.data(), &ownCloudGui::slotAccountStateChanged); - connect(accountState->account().data(), &Account::serverVersionChanged, - _gui.data(), &ownCloudGui::slotTrayMessageIfServerUnsupported); - connect(accountState, &AccountState::stateChanged, - _folderManager.data(), &FolderMan::slotAccountStateChanged); - connect(accountState->account().data(), &Account::serverVersionChanged, - _folderManager.data(), &FolderMan::slotServerVersionChanged); - - _gui->slotTrayMessageIfServerUnsupported(accountState->account().data()); + if (_mainWin) + _mainWin->ensureVisible(); } -void Application::slotCleanup() +void Application::showModalWidget(ModalWrapperWidget *wrapper) const { - AccountManager::instance()->save(); - FolderMan::instance()->unloadAndDeleteAllFolders(); - - _gui->slotShutdown(); - _gui->deleteLater(); + if (_mainWin) + _mainWin->showModalWidget(wrapper); } -// FIXME: This is not ideal yet since a ConnectionValidator might already be running and is in -// progress of timing out in some seconds. -// Maybe we need 2 validators, one triggered by timer, one by network configuration changes? -void Application::slotSystemOnlineConfigurationChanged(QNetworkConfiguration cnf) -{ - if (cnf.state() & QNetworkConfiguration::Active) { - QMetaObject::invokeMethod(this, "slotCheckConnection", Qt::QueuedConnection); - } -} -void Application::slotCheckConnection() +// move to owncloudgui or wherever we end up consolidating the tray meny/socketApi management +void Application::slotAccountStateAdded(AccountState *accountState) const { - const auto &list = AccountManager::instance()->accounts(); - for (const auto &accountState : list) { - AccountState::State state = accountState->state(); - - // Don't check if we're manually signed out or - // when the error is permanent. - if (state != AccountState::SignedOut - && state != AccountState::ConfigurationError - && state != AccountState::AskingCredentials) { - accountState->checkConnectivity(); - } - } - - if (list.isEmpty()) { - // let gui open the setup wizard - _gui->slotOpenSettingsDialog(); + if (!accountState || !accountState->account()) + return; + // Hook up the GUI slots to the account state's Q_SIGNALS: + Account *account = accountState->account(); - _checkConnectionTimer.stop(); // don't popup the wizard on interval; - } -} + connect(accountState, &AccountState::stateChanged, _trayController, &TrayMenuController::slotComputeOverallSyncStatus); + connect(account, &Account::serverVersionChanged, _trayController, &TrayMenuController::slotTrayMessageIfServerUnsupported); -void Application::slotCrash() -{ - Utility::crash(); + // todo: this does not belong here! This can be done in the folder man when it's given a "new" account + // (eg in load from config or load from new account). note that FolderMan does not receive accountAdded signals, but maybe it should? + // Hook up the folder manager slots to the account state's Q_SIGNALS: + connect(accountState, &AccountState::isConnectedChanged, FolderMan::instance(), &FolderMan::slotIsConnectedChanged); + connect(account, &Account::serverVersionChanged, FolderMan::instance(), [account] { FolderMan::instance()->slotServerVersionChanged(account); }); } -void Application::slotCrashEnforce() +void Application::slotCleanup() { - OC_ENFORCE(1 == 0); -} - + // by now the credentials are supposed to be persisted + // don't start async credentials jobs during shutdown + AccountManager::instance()->save(); -void Application::slotCrashFatal() -{ - qFatal("la Qt fatale"); -} + FolderMan::instance()->unloadAndDeleteAllFolders(); -void Application::slotShowGuiMessage(const QString &title, const QString &message) -{ - gui()->slotShowGuiMessage(title, message); + // Remove the account from the account manager so it can be deleted. + AccountManager::instance()->shutdown(); } - -void Application::slotownCloudWizardDone(int res) +void Application::updateAutoRun(bool firstRun) { - AccountManager *accountMan = AccountManager::instance(); - FolderMan *folderMan = FolderMan::instance(); - - // During the wizard, scheduling of new syncs is disabled - folderMan->setSyncEnabled(true); + if (!firstRun) + return; - if (res == QDialog::Accepted) { - // Check connectivity of the newly created account - _checkConnectionTimer.start(); - slotCheckConnection(); + bool shouldSetAutoStart = firstRun; - // If one account is configured: enable autostart - bool shouldSetAutoStart = (accountMan->accounts().size() == 1); -#ifdef Q_OS_MAC + if (Utility::isMac()) { // Don't auto start when not being 'installed' - shouldSetAutoStart = shouldSetAutoStart - && QCoreApplication::applicationDirPath().startsWith("/Applications/"); -#endif - if (shouldSetAutoStart) { - Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true); - } - - _gui->slotShowSettings(); + shouldSetAutoStart = shouldSetAutoStart && QCoreApplication::applicationDirPath().startsWith(QLatin1String("/Applications/")); } -} -void Application::setupLogging() -{ - // might be called from second instance - auto logger = Logger::instance(); - logger->setLogFile(_logFile); - logger->setLogDir(_logDir); - logger->setLogExpire(_logExpire); - logger->setLogFlush(_logFlush); - logger->setLogDebug(_logDebug); - logger->enterNextLogFile(); - - // Possibly configure logging from config file - LogBrowser::setupLoggingFromConfig(); - - qCInfo(lcApplication) << "##################" << _theme->appName() - << "locale:" << QLocale::system().name() - << "version:" << _theme->aboutVersions(Theme::VersionFormat::OneLiner); - qCInfo(lcApplication) << "Arguments:" << qApp->arguments(); + if (shouldSetAutoStart) { + Utility::setLaunchOnStartup(Theme::instance()->appName(), Theme::instance()->appNameGUI(), true); + } } void Application::slotUseMonoIconsChanged(bool) { - _gui->slotComputeOverallSyncStatus(); -} - -void Application::slotParseMessage(const QString &msg, QObject *) -{ - if (msg.startsWith(QLatin1String("MSG_PARSEOPTIONS:"))) { - const int lengthOfMsgPrefix = 17; - QStringList options = msg.mid(lengthOfMsgPrefix).split(QLatin1Char('|')); - _showSettings = false; - parseOptions(options); - setupLogging(); - if (_showSettings) { - _gui->slotShowSettings(); - } - if (_quitInstance) { - qApp->quit(); - } - } -} - -// Helpers for displaying messages. Note that there is probably no console on Windows. -static void displayHelpText(const QString &t, std::ostream &stream = std::cout) -{ - Logger::instance()->attacheToConsole(); - stream << qUtf8Printable(t) << std::endl; -#ifdef Q_OS_WIN - // No console on Windows. - QString spaces(80, ' '); // Add a line of non-wrapped space to make the messagebox wide enough. - QString text = QStringLiteral("
")
-        + t.toHtmlEscaped() + QStringLiteral("
") + spaces + QStringLiteral("
"); - QMessageBox::information(0, Theme::instance()->appNameGUI(), text); -#endif -} - -void Application::parseOptions(const QStringList &arguments) -{ - QCommandLineParser parser; - - QString descriptionText; - QTextStream descriptionTextStream(&descriptionText); - - descriptionTextStream << tr("%1 version %2\r\nFile synchronization desktop utility.").arg(_theme->appName(), OCC::Version::displayString()) << endl; - - if (_theme->appName() == QLatin1String("ownCloud")) { - descriptionTextStream << endl - << endl - << tr("For more information, see http://www.owncloud.org"); - } - - parser.setApplicationDescription(descriptionText); - - auto helpOption = parser.addHelpOption(); - auto versionOption = parser.addVersionOption(); - - // this little snippet saves a few lines below - auto addOption = [&parser](const QCommandLineOption &option) { - parser.addOption(option); - return option; - }; - - auto showSettingsOption = addOption({ { "s", "showsettings" }, tr("Show the settings dialog while starting.") }); - auto quitInstanceOption = addOption({ { "q", "quit" }, tr("Quit the running instance.") }); - auto logFileOption = addOption({ "logfile", tr("Write log to file (use - to write to stdout)."), "filename" }); - auto logDirOption = addOption({ "logdir", tr("Write each sync log output in a new file in folder."), "name" }); - auto logExpireOption = addOption({ "logexpire", tr("Remove logs older than hours (to be used with --logdir)."), "hours" }); - auto logFlushOption = addOption({ "logflush", tr("Flush the log file after every write.") }); - auto logDebugOption = addOption({ "logdebug", tr("Output debug-level messages in the log.") }); - auto languageOption = addOption({ "language", tr("Override UI language."), "language" }); - auto listLanguagesOption = addOption({ "list-languages", tr("Override UI language.") }); - auto confDirOption = addOption({ "confdir", tr("Use the given configuration folder."), "dirname" }); - auto debugOption = addOption({ "debug", tr("Enable debug mode.") }); - - // virtual file system parameters (optional) - parser.addPositionalArgument("vfs file", tr("Virtual file system file to be opened (optional)."), { tr("[]") }); - - parser.process(arguments); - - // TODO: rename this option (see #8234 for more information) - if (parser.isSet(showSettingsOption)) { - _showSettings = true; - } - if (parser.isSet(quitInstanceOption)) { - _quitInstance = true; - } - if (parser.isSet(logFileOption)) { - _logFile = parser.value(logFileOption); - } - if (parser.isSet(logDirOption)) { - _logDir = parser.value(logDirOption); - } - if (parser.isSet(logExpireOption)) { - _logExpire = std::chrono::hours(parser.value(logExpireOption).toInt()); - } - if (parser.isSet(logFlushOption)) { - _logFlush = true; - } - if (parser.isSet(logDebugOption)) { - _logDebug = true; - } - if (parser.isSet(confDirOption)) { - const auto confDir = parser.value(confDirOption); - if (!ConfigFile::setConfDir(confDir)) { - displayHelpText(tr("Invalid path passed to --confdir")); - std::exit(1); - } - } - if (parser.isSet(debugOption)) { - _logDebug = true; - _debugMode = true; - } - if (parser.isSet(languageOption)) { - const auto languageValue = parser.value(languageOption); - - // fail if the language is unknown - if (!Translations::listAvailableTranslations().contains(languageValue)) { - displayHelpText(tr("Error: unknown language \"%1\" (use --list-languages to get a complete list of supported translations)").arg(languageValue)); - std::exit(1); - } else { - _userEnforcedLanguage = languageValue; - } - } - if (parser.isSet(listLanguagesOption)) { - auto availableTranslations = Translations::listAvailableTranslations().toList(); - availableTranslations.sort(Qt::CaseInsensitive); - displayHelpText(tr("Available translations: %1").arg(availableTranslations.join(", "))); - std::exit(1); - } - - auto positionalArguments = parser.positionalArguments(); - - // ignore any positional arguments beyond the first one - if (!positionalArguments.empty()) { - QTimer::singleShot(0, this, [this, positionalArguments] { openVirtualFile(positionalArguments.front()); }); - } + _trayController->slotComputeOverallSyncStatus(); } bool Application::debugMode() @@ -675,172 +181,39 @@ bool Application::debugMode() return _debugMode; } -QString substLang(const QString &lang) +void Application::buildAppGuis() { - // Map the more appropriate script codes - // to country codes as used by Qt and - // transifex translation conventions. - - // Simplified Chinese - if (lang == QLatin1String("zh_Hans")) - return QLatin1String("zh_CN"); - // Traditional Chinese - if (lang == QLatin1String("zh_Hant")) - return QLatin1String("zh_TW"); - return lang; -} + Q_ASSERT(!_mainWin && !_trayController); + _mainWin = new MainWindow(); + _mainController = new MainWindowController(_mainWin, this); -void Application::setupTranslations() -{ - const auto trPath = Translations::translationsDirectoryPath(); - qCDebug(lcApplication) << "Translations directory path:" << trPath; - - QStringList uiLanguages = QLocale::system().uiLanguages(); - qCDebug(lcApplication) << "UI languages:" << uiLanguages; - - // the user can also set a locale in the settings, so we need to load the config file - ConfigFile cfg; + _accountsGuiController = new AccountsGuiController(AccountManager::instance(), _mainWin, _mainController); + connect(_mainController, &MainWindowController::requestAccountWizard, _accountsGuiController, &AccountsGuiController::runAccountWizard); - // allow user and theme to enforce a language via a commandline parameter - const auto themeEnforcedLocale = Theme::instance()->enforcedLocale(); - qCDebug(lcApplication) << "Theme-enforced locale:" << themeEnforcedLocale; - - // we need to track the enforced languages separately, since we need to distinguish between locale-provided - // and user-enforced ones below - QSet enforcedLanguages; - - // note that user-enforced languages are prioritized over the theme enforced one - // to make testing easier, --language overrides the setting from the config file - // as we are prepending to the list of languages, the list passed to the loop must be sorted with ascending priority - for (const auto &enforcedLocale : { themeEnforcedLocale, cfg.uiLanguage(), _userEnforcedLanguage }) { - if (!enforcedLocale.isEmpty()) { - enforcedLanguages.insert(enforcedLocale); - uiLanguages.prepend(enforcedLocale); - } - } - - qCDebug(lcApplication) << "Enforced languages:" << enforcedLanguages; - - QTranslator *translator = new QTranslator(this); - QTranslator *qtTranslator = new QTranslator(this); - QTranslator *qtkeychainTranslator = new QTranslator(this); - - for (QString lang : qAsConst(uiLanguages)) { - lang.replace(QLatin1Char('-'), QLatin1Char('_')); // work around QTBUG-25973 - lang = substLang(lang); - const QString trFile = Translations::translationsFilePrefix() + lang; - if (translator->load(trFile, trPath) || lang.startsWith(QLatin1String("en"))) { - // Permissive approach: Qt and keychain translations - // may be missing, but Qt translations must be there in order - // for us to accept the language. Otherwise, we try with the next. - // "en" is an exception as it is the default language and may not - // have a translation file provided. - qCInfo(lcApplication) << "Using" << lang << "translation"; - _displayLanguage = lang; - - const QString qtTrPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); - qCDebug(lcApplication) << "qtTrPath:" << qtTrPath; - const QString qtTrFile = QLatin1String("qt_") + lang; - qCDebug(lcApplication) << "qtTrFile:" << qtTrFile; - const QString qtBaseTrFile = QLatin1String("qtbase_") + lang; - qCDebug(lcApplication) << "qtBaseTrFile:" << qtBaseTrFile; - - if (!qtTranslator->load(qtTrFile, qtTrPath)) { - if (!qtTranslator->load(qtTrFile, trPath)) { - if (!qtTranslator->load(qtBaseTrFile, qtTrPath)) { - if (!qtTranslator->load(qtBaseTrFile, trPath)) { - qCCritical(lcApplication) << "Could not load Qt translations"; - } - } - } - } - - const QString qtkeychainTrFile = QLatin1String("qtkeychain_") + lang; - if (!qtkeychainTranslator->load(qtkeychainTrFile, qtTrPath)) { - if (!qtkeychainTranslator->load(qtkeychainTrFile, trPath)) { - qCCritical(lcApplication) << "Could not load qtkeychain translations"; - } - } - - if (!translator->isEmpty() && !installTranslator(translator)) { - qCCritical(lcApplication) << "Failed to install translator"; - } - if (!qtTranslator->isEmpty() && !installTranslator(qtTranslator)) { - qCCritical(lcApplication) << "Failed to install Qt translator"; - } - if (!qtkeychainTranslator->isEmpty() && !installTranslator(qtkeychainTranslator)) { - qCCritical(lcApplication) << "Failed to install qtkeychain translator"; - } - - // makes sure widgets with locale-dependent formatting, e.g., QDateEdit, display the correct formatting - // if the language is provided by the system locale anyway (i.e., coming from QLocale::system().uiLanguages()), we should - // not mess with the system locale, though - // if we did, we would enforce a locale for no apparent reason - // see https://github.com/owncloud/client/issues/8608 for more information - if (enforcedLanguages.contains(lang)) { - QLocale newLocale(lang); - qCDebug(lcApplication) << "language" << lang << "was enforced, changing default locale to" << newLocale; - QLocale::setDefault(newLocale); -#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) - // setting the layout direction directly only appears to be needed on mac - setLayoutDirection(newLocale.textDirection()); -#endif - } - - break; - } - } -} + // Setting up the gui class will allow tray notifications for the + // setup that follows, like folder setup + _trayController = new TrayMenuController(this); + connect(_trayController, &TrayMenuController::requestShowAbout, _mainController, &MainWindowController::onAbout); + connect(_trayController, &TrayMenuController::requestShowHelp, _mainController, &MainWindowController::onHelp); -void Application::showSettingsDialog() -{ - _gui->slotShowSettings(); + connect(FolderMan::instance()->socketApi(), &SocketApi::shareCommandReceived, _trayController, &TrayMenuController::slotShowShareInBrowser); } -void Application::openVirtualFile(const QString &filename) +void Application::setupManagers() { - QString virtualFileExt = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX); - if (!filename.endsWith(virtualFileExt)) { - qWarning(lcApplication) << "Can only handle file ending in .owncloud. Unable to open" << filename; - return; - } - QString relativePath; - auto folder = FolderMan::instance()->folderForPath(filename, &relativePath); - if (!folder) { - qWarning(lcApplication) << "Can't find sync folder for" << filename; - // TODO: show a QMessageBox for errors - return; + connect(AccountManager::instance(), &AccountManager::accountAdded, this, &Application::slotAccountStateAdded); + for (const auto &ai : AccountManager::instance()->accounts()) { + slotAccountStateAdded(ai); } - folder->implicitlyHydrateFile(relativePath); - QString normalName = filename.left(filename.size() - virtualFileExt.size()); - auto con = QSharedPointer::create(); - *con = connect(folder, &Folder::syncFinished, folder, [folder, con, normalName] { - folder->disconnect(*con); - if (QFile::exists(normalName)) { - QDesktopServices::openUrl(QUrl::fromLocalFile(normalName)); - } - }); } -void Application::tryTrayAgain() +std::unique_ptr Application::createInstance(Platform *platform, const QString &displayLanguage, bool debugMode) { - qCInfo(lcApplication) << "Trying tray icon, tray available:" << QSystemTrayIcon::isSystemTrayAvailable(); - if (!_gui->contextMenuVisible()) - _gui->hideAndShowTray(); -} - -bool Application::event(QEvent *event) -{ -#ifdef Q_OS_MAC - if (event->type() == QEvent::FileOpen) { - QFileOpenEvent *openEvent = static_cast(event); - qCDebug(lcApplication) << "QFileOpenEvent" << openEvent->file(); - // virtual file, open it after the Folder were created (if the app is not terminated) - QString fn = openEvent->file(); - QTimer::singleShot(0, this, [this, fn] { openVirtualFile(fn); }); - } -#endif - return SharedTools::QtSingleApplication::event(event); + Q_ASSERT(!_instance); + _instance = new Application(platform, displayLanguage, debugMode); + _instance->buildAppGuis(); + _instance->setupManagers(); + return std::unique_ptr(_instance); } } // namespace OCC diff --git a/src/gui/application.h b/src/gui/application.h index 9ca5bad051d..918c6e3238d 100644 --- a/src/gui/application.h +++ b/src/gui/application.h @@ -12,24 +12,15 @@ * for more details. */ -#ifndef APPLICATION_H -#define APPLICATION_H +#pragma once -#include -#include -#include -#include -#include - -#include "3rdparty/qtsingleapplication/qtsingleapplication.h" - -#include "syncresult.h" -#include "logbrowser.h" -#include "owncloudgui.h" -#include "connectionvalidator.h" -#include "progressdispatcher.h" -#include "clientproxy.h" #include "folderman.h" +#include "gui/owncloudguilib.h" +#include "platform.h" +#include "traymenucontroller.h" + +#include +#include class QMessageBox; class QSystemTrayIcon; @@ -45,104 +36,85 @@ Q_DECLARE_LOGGING_CATEGORY(lcApplication) class Theme; class Folder; -class SslErrorDialog; +class MainWindow; +class MainWindowController; +class AccountsGuiController; +class ModalWrapperWidget; + /** * @brief The Application class * @ingroup gui */ -class Application : public SharedTools::QtSingleApplication +class OWNCLOUDGUI_EXPORT Application : public QObject { Q_OBJECT public: - explicit Application(int &argc, char **argv); - ~Application() override; + static std::unique_ptr createInstance(Platform *platform, const QString &displayLanguage, bool debugMode); + ~Application(); bool debugMode(); - void showSettingsDialog(); + TrayMenuController *tray() const; + + // this is needed primarily to parent message boxes and other temporary views + // we return QMainWindow to protect access to public functions of the MainWindow that should only be used by true dependents! + // ie, if you need public functions of MainWindow, it should be injected as its concrete type + QMainWindow *mainWindow() const; - ownCloudGui *gui() const; + // redirect to MainWindow::ensureVisible -> protect access to main window interface + void ensureVisible() const; + + // hopefully temporary - this is only needed in the updater mess which has no reasonable structure I can currently use to pass the MainWindow + // again, redirect to MainWindow::showModalWidget to protect access to the rest of the main window interface + void showModalWidget(ModalWrapperWidget *wrapper) const; QString displayLanguage() const; -public slots: - // TODO: this should not be public - void slotownCloudWizardDone(int); - void slotCrash(); - void slotCrashEnforce(); - void slotCrashFatal(); - void slotShowGuiMessage(const QString &title, const QString &message); /** - * Will download a virtual file, and open the result. - * The argument is the filename of the virtual file (including the extension) + * @brief updateAutoRun will automatically turn on the autorun feature if appropriate + * @param firstRun indicates whether this is the first time the application is being run. + * + * This value is currently gleaned by checking whether the config file exists or not on start, if not, it's a first run. + * We can't check this inside the function, unfortunately, because the app startup writes the client version to the config + * "asap", before the Application is instantiated, so we have to capture the state and pass it to this function after the + * applicaiton exists. + * Once we have a real application builder this should be a simpler proccess but for now this is what we have. */ - void openVirtualFile(const QString &filename); - - /// Attempt to show() the tray icon again. Used if no systray was available initially. - void tryTrayAgain(); - -protected: - void parseOptions(const QStringList &); - void setupTranslations(); - void setupLogging(); - bool event(QEvent *event) override; - -signals: - void folderRemoved(); - void folderStateChanged(Folder *); + void updateAutoRun(bool firstRun); -protected slots: - void slotParseMessage(const QString &, QObject *); - void slotCheckConnection(); +protected Q_SLOTS: void slotUseMonoIconsChanged(bool); void slotCleanup(); - void slotAccountStateAdded(AccountState *accountState); - void slotAccountStateRemoved(AccountState *accountState); - void slotSystemOnlineConfigurationChanged(QNetworkConfiguration); + void slotAccountStateAdded(AccountState *accountState) const; private: - /** - * Maybe a newer version of the client was used with this config file: - * if so, backup, confirm with user and remove the config that can't be read. - */ - bool configVersionMigration(); - - QPointer _gui; - - Theme *_theme; - - // options from command line: - bool _showSettings = false; - bool _quitInstance = false; - QString _logFile; - QString _logDir; - std::chrono::hours _logExpire; - bool _logFlush; - bool _logDebug; - bool _userTriggeredConnect; - bool _debugMode; - QString _userEnforcedLanguage; - QString _displayLanguage; + // important! we can't set up the gui's in the ctr - it needs to be a separate step because owncloudgui depends on ocApp to parent + // it's actions + void buildAppGuis(); + + // this is currently fairly empty, but will be moving other manager init stuff in here. + void setupManagers(); - ClientProxy _proxy; +private: + explicit Application(Platform *platform, const QString &displayLanguage, bool debugMode); + + MainWindow *_mainWin = nullptr; + MainWindowController *_mainController = nullptr; + AccountsGuiController *_accountsGuiController = nullptr; + TrayMenuController *_trayController = nullptr; - QNetworkConfigurationManager _networkConfigurationManager; - QTimer _checkConnectionTimer; + const bool _debugMode = false; + QString _displayLanguage; -#if defined(WITH_CRASHREPORTER) - QScopedPointer _crashHandler; -#endif - QScopedPointer _folderManager; + static Application *_instance; + friend Application *ocApp(); }; inline Application *ocApp() { - auto instance = qobject_cast(qApp); - OC_ENFORCE(instance); - return instance; + OC_ENFORCE(Application::_instance); + return Application::_instance; } } // namespace OCC - -#endif // APPLICATION_H diff --git a/src/gui/authenticationdialog.cpp b/src/gui/authenticationdialog.cpp deleted file mode 100644 index 80fea6321c1..00000000000 --- a/src/gui/authenticationdialog.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2014 by Daniel Molkentin - * - * 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 "authenticationdialog.h" - -#include -#include -#include -#include -#include - -namespace OCC { - -AuthenticationDialog::AuthenticationDialog(const QString &realm, const QString &domain, QWidget *parent) - : QDialog(parent) - , _user(new QLineEdit) - , _password(new QLineEdit) -{ - setWindowTitle(tr("Authentication Required")); - QVBoxLayout *lay = new QVBoxLayout(this); - QLabel *label = new QLabel(tr("Enter username and password for '%1' at %2.").arg(realm, domain)); - label->setTextFormat(Qt::PlainText); - lay->addWidget(label); - - QFormLayout *form = new QFormLayout; - form->addRow(tr("&User:"), _user); - form->addRow(tr("&Password:"), _password); - lay->addLayout(form); - _password->setEchoMode(QLineEdit::Password); - - QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); - connect(box, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject); - lay->addWidget(box); -} - -QString AuthenticationDialog::user() const -{ - return _user->text(); -} - -QString AuthenticationDialog::password() const -{ - return _password->text(); -} - -} // namespace OCC diff --git a/src/gui/authenticationdialog.h b/src/gui/authenticationdialog.h deleted file mode 100644 index 3bd2183d785..00000000000 --- a/src/gui/authenticationdialog.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2014 by Daniel Molkentin - * - * 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. - */ - -#ifndef MIRALL_AUTHENTICATIONDIALOG_H -#define MIRALL_AUTHENTICATIONDIALOG_H - -#include - -class QLineEdit; - -namespace OCC { - -/** - * @brief Authenticate a user for a specific credential given his credentials - * @ingroup gui - */ -class AuthenticationDialog : public QDialog -{ - Q_OBJECT -public: - AuthenticationDialog(const QString &realm, const QString &domain, QWidget *parent = nullptr); - - QString user() const; - QString password() const; - -private: - QLineEdit *_user; - QLineEdit *_password; -}; - - -} // namespace OCC - -#endif // MIRALL_AUTHENTICATIONDIALOG_H diff --git a/src/gui/clientproxy.cpp b/src/gui/clientproxy.cpp index e87f04b7cd8..c7036a84c5b 100644 --- a/src/gui/clientproxy.cpp +++ b/src/gui/clientproxy.cpp @@ -23,25 +23,29 @@ namespace OCC { Q_LOGGING_CATEGORY(lcClientProxy, "sync.clientproxy", QtInfoMsg) -ClientProxy::ClientProxy(QObject *parent) - : QObject(parent) -{ +namespace { + + QNetworkProxy proxyFromConfig(const QString &password, const ConfigFile &cfg) + { + QNetworkProxy proxy; + + if (cfg.proxyHostName().isEmpty()) + return QNetworkProxy(); + + proxy.setHostName(cfg.proxyHostName()); + proxy.setPort(cfg.proxyPort()); + if (cfg.proxyNeedsAuth()) { + proxy.setUser(cfg.proxyUser()); + proxy.setPassword(password); + } + return proxy; + } + } -static QNetworkProxy proxyFromConfig(const ConfigFile &cfg) +QString ClientProxy::printQNetworkProxy(const QNetworkProxy &proxy) { - QNetworkProxy proxy; - - if (cfg.proxyHostName().isEmpty()) - return QNetworkProxy(); - - proxy.setHostName(cfg.proxyHostName()); - proxy.setPort(cfg.proxyPort()); - if (cfg.proxyNeedsAuth()) { - proxy.setUser(cfg.proxyUser()); - proxy.setPassword(cfg.proxyPassword()); - } - return proxy; + return QStringLiteral("%1://%2:%3").arg(proxy.type()).arg(proxy.hostName()).arg(proxy.port()); } bool ClientProxy::isUsingSystemDefault() @@ -56,12 +60,7 @@ bool ClientProxy::isUsingSystemDefault() return false; } -QString printQNetworkProxy(const QNetworkProxy &proxy) -{ - return QString("%1://%2:%3").arg(proxy.type()).arg(proxy.hostName()).arg(proxy.port()); -} - -void ClientProxy::setupQtProxyFromConfig() +void ClientProxy::setupQtProxyFromConfig(const QString &password) { OCC::ConfigFile cfg; int proxyType = QNetworkProxy::DefaultProxy; @@ -70,7 +69,7 @@ void ClientProxy::setupQtProxyFromConfig() // if there is no config file, default to system proxy. if (cfg.exists()) { proxyType = cfg.proxyType(); - proxy = proxyFromConfig(cfg); + proxy = proxyFromConfig(password, cfg); } switch (proxyType) { @@ -100,26 +99,6 @@ void ClientProxy::setupQtProxyFromConfig() } } -const char *ClientProxy::proxyTypeToCStr(QNetworkProxy::ProxyType type) -{ - switch (type) { - case QNetworkProxy::NoProxy: - return "NoProxy"; - case QNetworkProxy::DefaultProxy: - return "DefaultProxy"; - case QNetworkProxy::Socks5Proxy: - return "Socks5Proxy"; - case QNetworkProxy::HttpProxy: - return "HttpProxy"; - case QNetworkProxy::HttpCachingProxy: - return "HttpCachingProxy"; - case QNetworkProxy::FtpCachingProxy: - return "FtpCachingProxy"; - default: - return "NoProxy"; - } -} - void ClientProxy::lookupSystemProxyAsync(const QUrl &url, QObject *dst, const char *slot) { SystemProxyRunnable *runnable = new SystemProxyRunnable(url); @@ -140,9 +119,9 @@ void SystemProxyRunnable::run() QList proxies = QNetworkProxyFactory::systemProxyForQuery(QNetworkProxyQuery(_url)); if (proxies.isEmpty()) { - emit systemProxyLookedUp(QNetworkProxy(QNetworkProxy::NoProxy)); + Q_EMIT systemProxyLookedUp(QNetworkProxy(QNetworkProxy::NoProxy)); } else { - emit systemProxyLookedUp(proxies.first()); + Q_EMIT systemProxyLookedUp(proxies.first()); // FIXME Would we really ever return more? } } diff --git a/src/gui/clientproxy.h b/src/gui/clientproxy.h index 348eab99b51..31002914a1f 100644 --- a/src/gui/clientproxy.h +++ b/src/gui/clientproxy.h @@ -20,9 +20,8 @@ #include #include -#include #include "common/utility.h" -#include "owncloudlib.h" +#include "csync.h" namespace OCC { @@ -32,20 +31,12 @@ class ConfigFile; * @brief The ClientProxy class * @ingroup libsync */ -class ClientProxy : public QObject -{ - Q_OBJECT -public: - explicit ClientProxy(QObject *parent = nullptr); - - static bool isUsingSystemDefault(); - static void lookupSystemProxyAsync(const QUrl &url, QObject *dst, const char *slot); +namespace ClientProxy { + bool isUsingSystemDefault(); + void lookupSystemProxyAsync(const QUrl &url, QObject *dst, const char *slot); + void setupQtProxyFromConfig(const QString &password); -public slots: - void setupQtProxyFromConfig(); - -private: - const char *proxyTypeToCStr(QNetworkProxy::ProxyType type); + QString printQNetworkProxy(const QNetworkProxy &proxy); }; class SystemProxyRunnable : public QObject, public QRunnable @@ -54,14 +45,12 @@ class SystemProxyRunnable : public QObject, public QRunnable public: SystemProxyRunnable(const QUrl &url); void run() override; -signals: +Q_SIGNALS: void systemProxyLookedUp(const QNetworkProxy &url); private: QUrl _url; }; - -QString printQNetworkProxy(const QNetworkProxy &proxy); } #endif // CLIENTPROXY_H diff --git a/src/gui/commonstrings.cpp b/src/gui/commonstrings.cpp new file mode 100644 index 00000000000..7b79217b6bb --- /dev/null +++ b/src/gui/commonstrings.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "commonstrings.h" + +#include +#include + +using namespace OCC; + +QString CommonStrings::fileBrowser() +{ +#ifdef Q_OS_WIN + return QStringLiteral("Explorer"); +#elif defined(Q_OS_MAC) + return QStringLiteral("Finder"); +#else + return QCoreApplication::translate("CommonStrings", "file manager"); +#endif +} + +QString CommonStrings::showInFileBrowser() +{ + return QCoreApplication::translate("CommonStrings", "Show in %1").arg(fileBrowser()); +} + +QString CommonStrings::showInWebBrowser() +{ + return QCoreApplication::translate("CommonStrings", "Show in web browser"); +} + +QString CommonStrings::copyToClipBoard() +{ + return QCoreApplication::translate("CommonStrings", "Copy"); +} + +QString CommonStrings::filterButtonText(int filterCount) +{ + return QCoreApplication::translate("CommonStrings", "%n Filter(s)", nullptr, filterCount); +} + +QString CommonStrings::space() +{ + if (Theme::instance()->spacesAreCalledFolders()) + return QCoreApplication::translate("CommonStrings", "folder"); + else + return QCoreApplication::translate("CommonStrings", "space"); +} + +QString CommonStrings::spaces() +{ + if (Theme::instance()->spacesAreCalledFolders()) + return QCoreApplication::translate("CommonStrings", "folders"); + else + return QCoreApplication::translate("CommonStrings", "spaces"); +} + +QString CommonStrings::capSpace() +{ + if (Theme::instance()->spacesAreCalledFolders()) + return QCoreApplication::translate("CommonStrings", "Folder"); + else + return QCoreApplication::translate("CommonStrings", "Space"); +} diff --git a/src/gui/commonstrings.h b/src/gui/commonstrings.h new file mode 100644 index 00000000000..53246bb3c8f --- /dev/null +++ b/src/gui/commonstrings.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 + +namespace OCC { + +namespace CommonStrings { + QString fileBrowser(); + QString showInFileBrowser(); + QString showInWebBrowser(); + QString copyToClipBoard(); + QString filterButtonText(int filterCount); + QString space(); + QString spaces(); + QString capSpace(); +} +} diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index 3e492a1b6af..2b1bb3842c4 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -11,19 +11,20 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ +#include "gui/connectionvalidator.h" +#include "gui/clientproxy.h" +#include "gui/fetchserversettings.h" +#include "gui/networkinformation.h" +#include "libsync/account.h" +#include "libsync/creds/abstractcredentials.h" +#include "libsync/networkjobs.h" +#include "libsync/networkjobs/checkserverjobfactory.h" +#include "libsync/theme.h" -#include #include #include -#include #include -#include - -#include "connectionvalidator.h" -#include "account.h" -#include "networkjobs.h" -#include "clientproxy.h" -#include +#include using namespace std::chrono_literals; @@ -34,23 +35,41 @@ Q_LOGGING_CATEGORY(lcConnectionValidator, "sync.connectionvalidator", QtInfoMsg) // Make sure the timeout for this job is less than how often we get called // This makes sure we get tried often enough without "ConnectionValidator already running" namespace { - const auto timeoutToUse = ConnectionValidator::DefaultCallingInterval - 5s; + auto timeoutToUse() + { + return std::min(ConnectionValidator::DefaultCallingInterval - 5s, AbstractNetworkJob::httpTimeout); + }; } -ConnectionValidator::ConnectionValidator(AccountPtr account, QObject *parent) +ConnectionValidator::ConnectionValidator(Account *account, QObject *parent) : QObject(parent) , _account(account) + , _checkServerJob(nullptr) +{ + // TODO: 6.0 abort validator on 5min timeout + auto timer = new QTimer(this); + timer->setInterval(30s); + connect(timer, &QTimer::timeout, this, + [this] { qCInfo(lcConnectionValidator) << "ConnectionValidator" << _account->displayNameWithHost() << "still running after" << _duration.duration(); }); + timer->start(); +} + +ConnectionValidator::~ConnectionValidator() { + if (_checkServerJob) { + delete _checkServerJob; + _checkServerJob = nullptr; + } } -void ConnectionValidator::checkServer() +void ConnectionValidator::setClearCookies(bool clearCookies) { - _updateConfig = false; - checkServerAndUpdate(); + _clearCookies = clearCookies; } -void ConnectionValidator::checkServerAndUpdate() +void ConnectionValidator::checkServer(ConnectionValidator::ValidationMode mode) { + _mode = mode; if (!_account) { _errors << tr("No ownCloud account configured"); reportResult(NotConfigured); @@ -65,9 +84,10 @@ void ConnectionValidator::checkServerAndUpdate() this, SLOT(systemProxyLookupDone(QNetworkProxy))); } else { // We want to reset the QNAM proxy so that the global proxy settings are used (via ClientProxy settings) - _account->networkAccessManager()->setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy)); + _account->accessManager()->setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy)); + // todo: what is this trying to accomplish? // use a queued invocation so we're as asynchronous as with the other code path - QMetaObject::invokeMethod(this, "slotCheckServerAndAuth", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &ConnectionValidator::slotCheckServerAndAuth, Qt::QueuedConnection); } } @@ -79,11 +99,11 @@ void ConnectionValidator::systemProxyLookupDone(const QNetworkProxy &proxy) } if (proxy.type() != QNetworkProxy::NoProxy) { - qCInfo(lcConnectionValidator) << "Setting QNAM proxy to be system proxy" << printQNetworkProxy(proxy); + qCInfo(lcConnectionValidator) << "Setting QNAM proxy to be system proxy" << ClientProxy::printQNetworkProxy(proxy); } else { qCInfo(lcConnectionValidator) << "No system proxy set by OS"; } - _account->networkAccessManager()->setProxy(proxy); + _account->accessManager()->setProxy(proxy); slotCheckServerAndAuth(); } @@ -91,118 +111,162 @@ void ConnectionValidator::systemProxyLookupDone(const QNetworkProxy &proxy) // The actual check void ConnectionValidator::slotCheckServerAndAuth() { - CheckServerJob *checkJob = new CheckServerJob(_account, this); - checkJob->setTimeout(timeoutToUse); - connect(checkJob, &CheckServerJob::instanceFound, this, &ConnectionValidator::slotStatusFound); - connect(checkJob, &CheckServerJob::instanceNotFound, this, &ConnectionValidator::slotNoStatusFound); - connect(checkJob, &CheckServerJob::timeout, this, [checkJob, this] { - qCWarning(lcConnectionValidator) << checkJob; - _errors.append(tr("timeout")); - reportResult(Timeout); + if (!_account) { + qCWarning(lcConnectionValidator) << "Bailing out, Account had been deleted"; + reportResult(NotConfigured); + return; + } + + Q_ASSERT(_checkServerJob == nullptr); + auto checkServerFactory = CheckServerJobFactory::createFromAccount(_account, _clearCookies); + _checkServerJob = checkServerFactory.startJob(_account->url(), this); + + connect(_checkServerJob->reply()->manager(), &AccessManager::sslErrors, this, [this](QNetworkReply *reply, const QList &errors) { + Q_UNUSED(reply) + Q_EMIT sslErrors(errors); }); - Q_EMIT aboutToStart(); - checkJob->start(); + + connect(_checkServerJob, &CoreJob::finished, this, &ConnectionValidator::checkServerJobFinished); } -void ConnectionValidator::slotStatusFound(const QUrl &url, const QJsonObject &info) +void ConnectionValidator::checkServerJobFinished() { - // Newer servers don't disclose any version in status.php anymore - // https://github.com/owncloud/core/pull/27473/files - // so this string can be empty. - QString serverVersion = CheckServerJob::version(info); + if (!_account) { + qCWarning(lcConnectionValidator) << "Bailing out, Account had been deleted"; + reportResult(NotConfigured); + return; + } + + if (_checkServerJob->success()) { + const auto result = _checkServerJob->result().value(); + + // adopt the new cookies + _account->accessManager()->setCookieJar(_checkServerJob->reply()->manager()->cookieJar()); + + statusFound(result.serverUrl(), result.statusObject()); + } else { + switch (_checkServerJob->reply()->error()) { + case QNetworkReply::OperationCanceledError: + [[fallthrough]]; + case QNetworkReply::TimeoutError: + qCWarning(lcConnectionValidator) << _checkServerJob; + _errors.append(tr("timeout")); + reportResult(Timeout); + return; + case QNetworkReply::SslHandshakeFailedError: + reportResult(NetworkInformation::instance()->isBehindCaptivePortal() ? CaptivePortal : SslError); + return; + case QNetworkReply::TooManyRedirectsError: + reportResult(MaintenanceMode); + return; + case QNetworkReply::ContentAccessDenied: + reportResult(ClientUnsupported); + return; + default: + break; + } + + if (!_account->credentials()->stillValid(_checkServerJob->reply())) { + // Note: Why would this happen on a status.php request? + _errors.append(tr("Authentication error: Either username or password are wrong.")); + } else { + //_errors.append(tr("Unable to connect to %1").arg(_account->url().toString())); + _errors.append(_checkServerJob->errorMessage()); + } + reportResult(StatusNotFound); + } +} + +void ConnectionValidator::statusFound(const QUrl &url, const QJsonObject &info) +{ + if (!_account) { + qCWarning(lcConnectionValidator) << "Bailing out, Account had been deleted"; + reportResult(NotConfigured); + return; + } // status.php was found. qCInfo(lcConnectionValidator) << "** Application: ownCloud found: " << url << " with version " - << CheckServerJob::versionString(info) - << "(" << serverVersion << ")"; + << info.value(QLatin1String("versionstring")).toString(); - // Update server url in case of redirection + // this should never happen since we now use QNetworkRequest::ManualRedirectPolicy in the request - the result should be "not found" before we even + // get here, but leaving this in place as a safety net if (_account->url() != url) { - qCInfo(lcConnectionValidator()) << "status.php was redirected to" << url.toString() << "asking user to accept and abort for now"; - Q_EMIT _account->requestUrlUpdate(url); + qCWarning(lcConnectionValidator()) << "status.php was redirected to" << url.toString() << "redirects are not accepted"; reportResult(StatusNotFound); return; } - if (!serverVersion.isEmpty() && !setAndCheckServerVersion(serverVersion)) { - return; - } - // Check for maintenance mode: Servers send "true", so go through QVariant // to parse it correctly. - if (info["maintenance"].toVariant().toBool()) { + if (info[QStringLiteral("maintenance")].toVariant().toBool()) { reportResult(MaintenanceMode); return; } - // now check the authentication - QTimer::singleShot(0, this, &ConnectionValidator::checkAuthentication); -} - -// status.php could not be loaded (network or server issue!). -void ConnectionValidator::slotNoStatusFound(QNetworkReply *reply) -{ - auto job = qobject_cast(sender()); - qCWarning(lcConnectionValidator) << reply->error() << job << reply->peek(1024); - if (reply->error() == QNetworkReply::SslHandshakeFailedError) { - reportResult(SslError); - return; - } else if (reply->error() == QNetworkReply::TooManyRedirectsError) { - reportResult(MaintenanceMode); + AbstractCredentials *creds = _account->credentials(); + if (!creds->ready()) { + reportResult(CredentialsNotReady); return; - } else if (!_account->credentials()->stillValid(reply)) { - // Note: Why would this happen on a status.php request? - _errors.append(tr("Authentication error: Either username or password are wrong.")); + } + // now check the authentication + if (_mode != ConnectionValidator::ValidationMode::ValidateServer) { + checkAuthentication(); } else { - //_errors.append(tr("Unable to connect to %1").arg(_account->url().toString())); - _errors.append(job->errorString()); + reportResult(Connected); } - reportResult(StatusNotFound); } void ConnectionValidator::checkAuthentication() { - AbstractCredentials *creds = _account->credentials(); - - if (!creds->ready()) { - reportResult(CredentialsNotReady); + // simply GET the WebDAV root, will fail if credentials are wrong. + // continue in slotAuthCheck here :-) + if (!_account) { + _errors << tr("No account configured"); + reportResult(NotConfigured); return; } - // simply GET the webdav root, will fail if credentials are wrong. - // continue in slotAuthCheck here :-) - qCDebug(lcConnectionValidator) << "# Check whether authenticated propfind works."; - PropfindJob *job = new PropfindJob(_account, "/", this); + // we explicitly use a legacy dav path here - remote.php/webdav is available across all former, current and future server implementations (oc10, oCIS and + // Kiteworks PDN) + auto *job = new PropfindJob(_account, _account->url(), QString("remote.php/webdav/"), PropfindJob::Depth::Zero, this); + job->setAuthenticationJob(true); // don't retry - job->setTimeout(timeoutToUse); - job->setProperties(QList() << "getlastmodified"); + job->setTimeout(timeoutToUse()); + job->setProperties({ QByteArrayLiteral("getlastmodified") }); connect(job, &PropfindJob::finishedWithoutError, this, &ConnectionValidator::slotAuthSuccess); connect(job, &PropfindJob::finishedWithError, this, &ConnectionValidator::slotAuthFailed); job->start(); } -void ConnectionValidator::slotAuthFailed(QNetworkReply *reply) +// Lisa todo: I hit this once and nothing happened in the gui?! +void ConnectionValidator::slotAuthFailed() { + if (!_account) { + qCWarning(lcConnectionValidator) << "Bailing out, Account had been deleted"; + reportResult(NotConfigured); + return; + } + auto job = qobject_cast(sender()); Status stat = Timeout; - if (reply->error() == QNetworkReply::SslHandshakeFailedError) { + if (job->reply()->error() == QNetworkReply::SslHandshakeFailedError) { _errors << job->errorStringParsingBody(); - stat = SslError; - - } else if (reply->error() == QNetworkReply::AuthenticationRequiredError - || !_account->credentials()->stillValid(reply)) { - qCWarning(lcConnectionValidator) << "******** Password is wrong!" << reply->error() << job; + stat = NetworkInformation::instance()->isBehindCaptivePortal() ? CaptivePortal : SslError; + // dc-150 credentials::stillValid always returns true. the logic here needs fix + } else if (job->reply()->error() == QNetworkReply::AuthenticationRequiredError || !_account->credentials()->stillValid(job->reply())) { + qCWarning(lcConnectionValidator) << "******** Password is wrong!" << job->reply()->error() << job; _errors << tr("The provided credentials are not correct"); stat = CredentialsWrong; - - } else if (reply->error() != QNetworkReply::NoError) { + } else if (job->reply()->error() == QNetworkReply::ContentAccessDenied) { + stat = ClientUnsupported; + _errors << extractErrorMessage(job->reply()->readAll()); + } else if (job->reply()->error() != QNetworkReply::NoError) { _errors << job->errorStringParsingBody(); - const int httpStatus = - reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (httpStatus == 503) { + if (job->httpStatusCode() == 503) { _errors.clear(); stat = ServiceUnavailable; } @@ -213,115 +277,59 @@ void ConnectionValidator::slotAuthFailed(QNetworkReply *reply) void ConnectionValidator::slotAuthSuccess() { - _errors.clear(); - if (!_updateConfig) { - reportResult(Connected); + if (!_account) { + qCWarning(lcConnectionValidator) << "Bailing out, Account had been deleted"; + reportResult(NotConfigured); return; } - checkServerCapabilities(); -} - -void ConnectionValidator::checkServerCapabilities() -{ - // The main flow now needs the capabilities - JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/capabilities"), this); - job->setTimeout(timeoutToUse); - QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotCapabilitiesRecieved); - job->start(); -} - -void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json) -{ - auto caps = json.object().value("ocs").toObject().value("data").toObject().value("capabilities").toObject(); - qCInfo(lcConnectionValidator) << "Server capabilities" << caps; - _account->setCapabilities(caps.toVariantMap()); - - // New servers also report the version in the capabilities - QString serverVersion = caps["core"].toObject()["status"].toObject()["version"].toString(); - if (!serverVersion.isEmpty() && !setAndCheckServerVersion(serverVersion)) { + _errors.clear(); + if (_mode != ConnectionValidator::ValidationMode::ValidateAuth) { + auto *fetchSetting = new FetchServerSettingsJob(_account, this); + const auto unsupportedServerError = [this] { + _errors.append({tr("The configured server for this client is too old."), tr("Please update to the latest server and restart the client.")}); + }; + connect(fetchSetting, &FetchServerSettingsJob::finishedSignal, this, [unsupportedServerError, this](FetchServerSettingsJob::Result result) { + switch (result) { + case FetchServerSettingsJob::Result::UnsupportedServer: + unsupportedServerError(); + reportResult(ServerVersionMismatch); + break; + case FetchServerSettingsJob::Result::InvalidCredentials: + reportResult(CredentialsWrong); + break; + case FetchServerSettingsJob::Result::TimeOut: + reportResult(Timeout); + break; + case FetchServerSettingsJob::Result::Success: + if (_account->serverSupportLevel() == Account::ServerSupportLevel::Unknown) { + unsupportedServerError(); + } + reportResult(Connected); + break; + case FetchServerSettingsJob::Result::Undefined: + reportResult(Undefined); + break; + } + }); + + fetchSetting->start(); return; } - - fetchUser(); -} - -void ConnectionValidator::fetchUser() -{ - JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/user"), this); - job->setTimeout(timeoutToUse); - QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotUserFetched); - job->start(); -} - -bool ConnectionValidator::setAndCheckServerVersion(const QString &version) -{ - _account->setServerVersion(version); - - // We cannot deal with servers < 7.0.0 - if (_account->serverVersionInt() - && _account->serverVersionInt() < Account::makeServerVersion(7, 0, 0)) { - _errors.append(tr("The configured server for this client is too old")); - _errors.append(tr("Please update to the latest server and restart the client.")); - reportResult(ServerVersionMismatch); - return false; - } - // We attempt to work with servers >= 7.0.0 but warn users. - // Check usages of Account::serverVersionUnsupported() for details. - - // Record that the server supports HTTP/2 - // Actual decision if we should use HTTP/2 is done in AccessManager::createRequest - if (auto job = qobject_cast(sender())) { - if (auto reply = job->reply()) { - _account->setHttp2Supported( - reply->attribute(QNetworkRequest::Http2WasUsedAttribute).toBool()); - } - } - return true; -} - -void ConnectionValidator::slotUserFetched(const QJsonDocument &json) -{ - QString user = json.object().value("ocs").toObject().value("data").toObject().value("id").toString(); - if (!user.isEmpty()) { - _account->setDavUser(user); - } - QString displayName = json.object().value("ocs").toObject().value("data").toObject().value("display-name").toString(); - if (!displayName.isEmpty()) { - _account->setDavDisplayName(displayName); - } - - auto capabilities = _account->capabilities(); - // We should have received the capabilities by now. Check that assumption in a debug build. If - // it's not the case, the code below will assume that they are not available. - Q_ASSERT(capabilities.isValid()); - -#ifndef TOKEN_AUTH_ONLY - if (capabilities.isValid() && capabilities.avatarsAvailable()) { - AvatarJob *job = new AvatarJob(_account, _account->davUser(), 128, this); - job->setTimeout(20s); - QObject::connect(job, &AvatarJob::avatarPixmap, this, &ConnectionValidator::slotAvatarImage); - job->start(); - // reportResult will be called when the avatar has been received by `slotAvatarImage` - } else -#endif - { - reportResult(Connected); - } -} - -#ifndef TOKEN_AUTH_ONLY -void ConnectionValidator::slotAvatarImage(const QPixmap &img) -{ - _account->setAvatar(img); reportResult(Connected); } -#endif void ConnectionValidator::reportResult(Status status) { - emit connectionResult(status, _errors); - deleteLater(); + if (OC_ENSURE(!_finished)) { + _finished = true; + qCDebug(lcConnectionValidator) << status << _duration.duration(); + if (_checkServerJob) { + delete _checkServerJob; + _checkServerJob = nullptr; + } + Q_EMIT connectionResult(status, _errors); + } } } // namespace OCC diff --git a/src/gui/connectionvalidator.h b/src/gui/connectionvalidator.h index 7038b3eef4a..c14ce0cb5fd 100644 --- a/src/gui/connectionvalidator.h +++ b/src/gui/connectionvalidator.h @@ -12,16 +12,19 @@ * for more details. */ -#ifndef CONNECTIONVALIDATOR_H -#define CONNECTIONVALIDATOR_H +#pragma once -#include "accountfwd.h" -#include "owncloudlib.h" +#include "gui/owncloudguilib.h" + +#include "common/chronoelapsedtimer.h" +#include "libsync/account.h" #include #include +#include #include #include + #include namespace OCC { @@ -32,8 +35,9 @@ namespace OCC { * checkAuthentication is the quick version that only does the propfind * while checkServerAndAuth is doing the 4 calls. * - * We cannot use the capabilites call to test the login and the password because of + * We cannot use the capabilities call to test the login and the password because of * https://github.com/owncloud/core/issues/12930 + * TODO: that issue is apparently no longer an issue. Should we start using the capabilities in that case? * * Here follows the state machine @@ -61,28 +65,36 @@ namespace OCC { +---------------------------+ | +-> checkServerCapabilities - JsonApiJob (cloud/capabilities) - | - +-> slotCapabilitiesRecieved -+ - | - +-----------------------------------+ + JsonApiJob (cloud/capabilities) -> slotCapabilitiesRecieved -+ + | + +------------------------------------------------------------------+ | - +-> fetchUser - PropfindJob - | - +-> slotUserFetched - AvatarJob - | - +-> slotAvatarImage --> reportResult() + +-> fetchUser -+ + | + +-> AvatarJob + | + +-> slotAvatarImage --> reportResult() \endcode */ -class ConnectionValidator : public QObject + +class CoreJob; + +class OWNCLOUDGUI_EXPORT ConnectionValidator : public QObject { Q_OBJECT public: - explicit ConnectionValidator(AccountPtr account, QObject *parent = nullptr); + explicit ConnectionValidator(Account *account, QObject *parent); + ~ConnectionValidator() override; + enum class ValidationMode { + ValidateServer, + ValidateAuth, + ValidateAuthAndUpdate + }; + Q_ENUM(ValidationMode) + + // todo: should we add a status for attempted redirect? enum Status { Undefined, Connected, @@ -94,58 +106,61 @@ class ConnectionValidator : public QObject StatusNotFound, // Error retrieving status.php ServiceUnavailable, // 503 on authed request MaintenanceMode, // maintenance enabled in status.php - Timeout // actually also used for other errors on the authed request + Timeout, // actually also used for other errors on the authed request + ClientUnsupported, // The server blocks us as an unsupported client + CaptivePortal, // We're stuck behind a captive portal and (will) get SSL certificate problems }; - Q_ENUM(Status); + Q_ENUM(Status) // How often should the Application ask this object to check for the connection? static constexpr auto DefaultCallingInterval = std::chrono::seconds(62); -public slots: + + /** Whether to clear the cookies before we start the CheckServerJob job + */ + void setClearCookies(bool clearCookies); + +public Q_SLOTS: /// Checks the server and the authentication. - void checkServer(); - void checkServerAndUpdate(); + void checkServer(ConnectionValidator::ValidationMode mode = ConnectionValidator::ValidationMode::ValidateAuthAndUpdate); + void systemProxyLookupDone(const QNetworkProxy &proxy); -signals: - /** - * Emited before the actual validation starts - */ - void aboutToStart(); +Q_SIGNALS: void connectionResult(ConnectionValidator::Status status, const QStringList &errors); -protected slots: + void sslErrors(const QList &errors); + +protected Q_SLOTS: /// Checks authentication only. void checkAuthentication(); void slotCheckServerAndAuth(); - void slotStatusFound(const QUrl &url, const QJsonObject &info); - void slotNoStatusFound(QNetworkReply *reply); - - void slotAuthFailed(QNetworkReply *reply); + void slotAuthFailed(); void slotAuthSuccess(); - void slotCapabilitiesRecieved(const QJsonDocument &); - void slotUserFetched(const QJsonDocument &); -#ifndef TOKEN_AUTH_ONLY - void slotAvatarImage(const QPixmap &img); -#endif - private: + /** + * important update: this used to delete ITSELF after emitting connectionResult which was not only undocumented, but flies in the + * face of any sensible RAII impl = whoever creates the resource is responsible for cleaning it up, unless this responsibility + * is EXPLICITLY passed to a third party. So whoever instantiates the ConnectionValidator now has to clean it up in line with healthy + * memory management strategies. + */ void reportResult(Status status); - void checkServerCapabilities(); - void fetchUser(); - /** Sets the account's server version - * - * Returns false and reports ServerVersionMismatch for very old servers. - */ - bool setAndCheckServerVersion(const QString &version); + void statusFound(const QUrl &url, const QJsonObject &info); + void checkServerJobFinished(); + + QPointer _account; QStringList _errors; - AccountPtr _account; - bool _updateConfig = true; + bool _clearCookies = false; + + Utility::ChronoElapsedTimer _duration; + bool _finished = false; + + CoreJob *_checkServerJob; + + ConnectionValidator::ValidationMode _mode = ConnectionValidator::ValidationMode::ValidateAuthAndUpdate; }; } - -#endif // CONNECTIONVALIDATOR_H diff --git a/src/gui/creds/credentials.cpp b/src/gui/creds/credentials.cpp new file mode 100644 index 00000000000..13ffd5d82cf --- /dev/null +++ b/src/gui/creds/credentials.cpp @@ -0,0 +1,390 @@ +/* + * Copyright (C) by Lisa Reese + * + * 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 "credentials.h" + +#include "accessmanager.h" +#include "account.h" +#include "config/appconfig.h" +#include "creds/credentialmanager.h" +#include "oauth.h" +#include "requestauthenticationcontroller.h" +#include "requestauthenticationwidget.h" + +#include +#include +#include +#include +#include + +#include + +using namespace std::chrono_literals; + +Q_LOGGING_CATEGORY(lcCredentials, "gui.credentials", QtInfoMsg) + +namespace { +constexpr int TokenRefreshMaxRetries = 3; +constexpr std::chrono::seconds TokenRefreshDefaultTimeout = 30s; +const char authenticationFailedC[] = "owncloud-authentication-failed"; + +// when creating new keys we should probably stick with this idea related to a creds "type" in case we need to add or remove +// other cred types in future. all of the updates to transition to oauth only would have been so much easier if the last round had actually implmented +// separate http and oauth creds instead of rolling all of it into the httpCreds "type". +QString refreshTokenKeyC() +{ + return QString("http/oauthtoken"); +} +} + +namespace OCC { + +class CredentialsAccessManager : public AccessManager +{ + Q_OBJECT +public: + // this looks like trouble with the default nullptr on parent + CredentialsAccessManager(const Credentials *cred, QObject *parent = nullptr) + : AccessManager(parent) + , _cred(cred) + { + } + +protected: + QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) override + { + QNetworkRequest req(request); + if (!req.attribute(DontAddCredentialsAttribute).toBool()) { + if (_cred && !_cred->_accessToken.isEmpty()) { + req.setRawHeader("Authorization", "Bearer " + _cred->_accessToken.toUtf8()); + } + } + return AccessManager::createRequest(op, req, outgoingData); + } + +private: + // The credentials object dies along with the account, while the QNAM might + // outlive both. + // Lisa todo: that statement is very concerning. investigate -> In fact the nam is parented by the creds by default so no, it + // should not outlive the creds unless there are shenanigans wrt reparenting it. + QPointer _cred; +}; + +Credentials::Credentials(const QString &token, const QString &refreshToken, Account *account) + : AbstractCredentials(account, account) + , _accessToken(token) + , _refreshToken(refreshToken) + , _ready(false) + , _openIdConfig(AppConfig().openIdConfig()) +{ + if (!token.isEmpty() && !refreshToken.isEmpty()) + _ready = true; +} + +Credentials::Credentials(Account *account) + : Credentials({}, {}, account) +{ +} + +AccessManager *Credentials::createAccessManager() const +{ + AccessManager *am = new CredentialsAccessManager(this); + + connect(am, &QNetworkAccessManager::authenticationRequired, this, &Credentials::slotAuthentication); + + return am; +} + +bool Credentials::ready() const +{ + return _ready; +} + + +void Credentials::fetchFromKeychain() +{ + _wasEverFetched = true; + + if (!_ready && !_refreshToken.isEmpty()) { + // This happens if the credentials are still loaded from the keychain, bur we are called + // here because the auth is invalid, so this means we simply need to refresh the credentials + refreshAccessToken(); + return; + } + + if (_ready) { + Q_EMIT fetched(); + } else { + fetchCredentialsFromKeychain(); + } +} + +void Credentials::handleKeychainError(const QString &message) +{ + qCWarning(lcCredentials) << message; + + _fetchErrorString = message; + _accessToken.clear(); + _ready = false; + Q_EMIT fetched(); +} + +void Credentials::fetchCredentialsFromKeychain() +{ + auto job = _account->credentialManager()->get(refreshTokenKeyC()); + if (!job) { + handleKeychainError("get credentials job is null - most likely the key does not exist in the credentials manager"); + return; + } + connect(job, &CredentialJob::finished, this, [job, this] { + if (job->error() != QKeychain::NoError) { + handleKeychainError(job->errorString()); + return; + } + const auto data = job->data().toString(); + if (!data.isEmpty()) { + _refreshToken = data; + refreshAccessToken(); + } else { + handleKeychainError("get credentials data is empty"); + } + }); +} + +// Lisa todo: this is called periodically, find the trigger. it's coming mostly from the jobs :( +bool Credentials::stillValid(QNetworkReply *reply) +{ + // The function is called in order to determine whether we need to ask the user for a password + // since we are using OAuth, we already started a refresh in slotAuthentication, at least in theory, ensure the auth is started. + // If the refresh fails, we are going to Q_EMIT authenticationFailed ourselves + if (reply->error() == QNetworkReply::AuthenticationRequiredError) { + slotAuthentication(reply, nullptr); + } + return true; +} + +void Credentials::slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) +{ + qCDebug(lcCredentials) << Q_FUNC_INFO << reply; + if (!_ready) + return; + Q_UNUSED(authenticator) + // Because of issue #4326, we need to set the login and password manually at every requests + // Thus, if we reach this signal, those credentials were invalid and we terminate. + qCWarning(lcCredentials) << "Stop request: Authentication failed for " << reply->url().toString() << reply->request().rawHeader("Original-Request-ID"); + reply->setProperty(authenticationFailedC, true); + + if (!_oAuthJob) { + qCInfo(lcCredentials) << "Refreshing token"; + refreshAccessToken(); + } +} + +bool Credentials::refreshAccessToken() +{ + if (_refreshToken.isEmpty()) + return false; + if (_oAuthJob) + return true; + + refreshAccessTokenInternal(); + return true; +} + +void Credentials::handleRefreshError(QNetworkReply::NetworkError error, const QString &message) +{ + Q_UNUSED(message); + + _oAuthJob->deleteLater(); + _tokenRefreshRetriesCount++; + std::chrono::seconds timeout = {}; + + if (!networkAvailable()) { + _tokenRefreshRetriesCount = 0; + timeout = TokenRefreshDefaultTimeout; + } else { + switch (error) { + case QNetworkReply::ContentNotFoundError: + // 404: bigip f5? + timeout = 0s; + break; + case QNetworkReply::HostNotFoundError: + [[fallthrough]]; + case QNetworkReply::TimeoutError: + [[fallthrough]]; + // Qt reports OperationCanceledError if the request timed out + case QNetworkReply::OperationCanceledError: + [[fallthrough]]; + case QNetworkReply::TemporaryNetworkFailureError: + [[fallthrough]]; + // VPN not ready? + case QNetworkReply::ConnectionRefusedError: + _tokenRefreshRetriesCount = 0; + [[fallthrough]]; + default: + timeout = TokenRefreshDefaultTimeout; + } + } + + if (_tokenRefreshRetriesCount >= TokenRefreshMaxRetries) { + qCWarning(lcCredentials) << "Too many failed refreshes" << _tokenRefreshRetriesCount << "-> log out"; + finishFailedRefresh(); + _tokenRefreshRetriesCount = 0; + return; + } + QTimer::singleShot(timeout, this, [this] { refreshAccessTokenInternal(); }); + Q_EMIT authenticationFailed(); +} + +void Credentials::handleRefreshSuccess(const QString &accessToken, const QString &refreshToken) +{ + _oAuthJob->deleteLater(); + _tokenRefreshRetriesCount = 0; + if (refreshToken.isEmpty()) { + qCWarning(lcCredentials) << "Refresh job succeeded but refreshToken is empty -> log out"; + finishFailedRefresh(); + return; + } + _refreshToken = refreshToken; + if (!accessToken.isNull()) { + _ready = true; + _accessToken = accessToken; + persist(); + } + Q_EMIT fetched(); +} + +void Credentials::finishFailedRefresh() +{ + forgetSensitiveData(); + Q_EMIT authenticationFailed(); + Q_EMIT fetched(); +} + +bool Credentials::networkAvailable() +{ + auto qni = QNetworkInformation::instance(); + if (!qni) + return true; // this can allegedly happen on Linux + if (qni->reachability() == QNetworkInformation::Reachability::Disconnected || qni->reachability() == QNetworkInformation::Reachability::Unknown) + return false; + if (qni->isBehindCaptivePortal()) + return false; + return true; +} + +void Credentials::refreshAccessTokenInternal() +{ + if (!_account) + return; + + // parent with nam to ensure we reset when the nam is reset + // todo: #22 - the parenting here is highly questionable, as is the use of the shared account ptr + _oAuthJob = new AccountBasedOAuth(_account, _openIdConfig, this); + connect(_oAuthJob, &AccountBasedOAuth::refreshError, this, &Credentials::handleRefreshError); + connect(_oAuthJob, &AccountBasedOAuth::refreshFinished, this, &Credentials::handleRefreshSuccess); + + Q_EMIT authenticationStarted(); + _oAuthJob->refreshAuthentication(_refreshToken); +} + +void Credentials::askFromUser() +{ + // I think this can happen when the re-auth process has already quasi started and is waiting for user input, but + // we get a prompt to log in again. I am not quite sure as it's very hard to reproduce. + if (_requestAuth || !_account) { + // let the existing instance ride + return; + } + + _requestAuth = new RequestAuthenticationController(this); + connect(_requestAuth, &RequestAuthenticationController::requestAccountModal, this, &AbstractCredentials::requestAccountModal); + // we should not connect to the failed signal as we are forcing the user to make the decision using the gui. + // if they log in successfully, we get the success signal. + // if the auth fails the gui stays until the user gets it right or clicks the stay logged out button. + connect(_requestAuth, &RequestAuthenticationController::authenticationSucceeded, this, &Credentials::askFromUserSucceeded); + connect(_requestAuth, &RequestAuthenticationController::requestLogout, this, &Credentials::askFromUserLogout); + _requestAuth->startAuthentication(_account); +} + +void Credentials::askFromUserSucceeded(const QString &token, const QString &refreshToken) +{ + _accessToken = token; + _refreshToken = refreshToken; + _ready = true; + persist(); + Q_EMIT fetched(); + + if (_requestAuth) { + delete _requestAuth; + _requestAuth = nullptr; + } +} + +void Credentials::askFromUserLogout() +{ + Q_EMIT requestLogout(); + if (_requestAuth) { + delete _requestAuth; + _requestAuth = nullptr; + } +} + +void Credentials::invalidateToken() +{ + qCWarning(lcCredentials) << "Invalidating the credentials"; + + _accessToken.clear(); + _ready = false; + + // clear the session cookie. + _account->clearCookieJar(); + + if (!_refreshToken.isEmpty()) { + // Only invalidate the access_token but keep the _refreshToken in the keychain + // (when coming from forgetSensitiveData, the _refreshToken is cleared) + return; + } + + // i hoped we could change this to a new, more useful identifier but since the value is related to what is in the keychain, I don't + // think this can be changed easily so I'm leaving it for now. + _account->credentialManager()->clear("http"); + + // let QNAM forget about the previous credentials it may have been using + // This needs to be done later in the event loop because we might be called (directly or + // indirectly) from QNetworkAccessManagerPrivate::authenticationRequired, which itself + // is a called from a BlockingQueuedConnection from the Qt HTTP thread. And clearing the + // cache needs to synchronize again with the HTTP thread. + QTimer::singleShot(0, _account, &Account::clearAMCache); +} + +void Credentials::forgetSensitiveData() +{ + // need to be done before invalidateToken, so it actually deletes the refresh_token from the keychain + // to do, make that more explicit + _refreshToken.clear(); + invalidateToken(); +} + +void Credentials::persist() +{ + // write secrets to the keychain + if (!_refreshToken.isEmpty()) { + _account->credentialManager()->set(refreshTokenKeyC(), _refreshToken); + } +} + +} // namespace OCC + + +#include "credentials.moc" diff --git a/src/gui/creds/credentials.h b/src/gui/creds/credentials.h new file mode 100644 index 00000000000..d1f3e287fc6 --- /dev/null +++ b/src/gui/creds/credentials.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) by Lisa Reese + * + * 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 "creds/abstractcredentials.h" +#include "creds/credentialssupport.h" +#include "gui/owncloudguilib.h" +#include "oauth.h" + +#include +#include +#include +#include + +class QNetworkReply; +class QAuthenticator; + +namespace OCC { +class OAuth; +class RequestAuthenticationController; + +class OWNCLOUDGUI_EXPORT Credentials : public AbstractCredentials +{ + Q_OBJECT + // ick + friend class CredentialsAccessManager; + +public: + /// Don't add credentials if this is set on a QNetworkRequest + + explicit Credentials(const QString &token, const QString &refreshToken, Account *account); + + explicit Credentials(Account *account); + // this access manager is monitored for authenticationRequired signal + // it also becomes the access manager member in the account. Try to invert this so the account + // creates it and connects the creds slot to the _am. + // note this is used by the checkServerJobFactory where a new nam is "needed" for every job due to options to clear cookies + // and other factors that mean we don't want to use the account's nam directly. + AccessManager *createAccessManager() const override; + + // todo: DC-112 I think this needs a naming update. + bool ready() const override; + + // this is invoked by AccountState::checkConnectivity. Totally uncool. + // todo: DC-112 at least rename this to fetchCredentials() or something - also in base class. the "keychain" naming is an + // impl detail we don't need to expose + void fetchFromKeychain() override; + + void askFromUser() override; + bool stillValid(QNetworkReply *reply) override; + void persist() override; + + void invalidateToken() override; + void forgetSensitiveData() override; + + + /* If we don't have a valid refresh token, return false. + * otherwise: + * if the refresh routine is running, return true + * if it is not running, start the refresh and return true. + */ + bool refreshAccessToken() override; + +protected: + bool networkAvailable(); + + + void handleRefreshError(QNetworkReply::NetworkError error, const QString &message); + void handleRefreshSuccess(const QString &accessToken, const QString &refreshToken); + void finishFailedRefresh(); + void handleKeychainError(const QString &message); + + void slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator); + void fetchCredentialsFromKeychain(); + void refreshAccessTokenInternal(); + + void askFromUserSucceeded(const QString &token, const QString &refreshToken); + void askFromUserLogout(); + + QString _accessToken; + QString _refreshToken; + + QString _fetchErrorString; + bool _ready = false; + const OpenIdConfig _openIdConfig; + int _tokenRefreshRetriesCount = 0; + + QPointer _oAuthJob; + + RequestAuthenticationController *_requestAuth = nullptr; +}; + + +} // namespace OCC diff --git a/src/gui/creds/httpcredentialsgui.cpp b/src/gui/creds/httpcredentialsgui.cpp deleted file mode 100644 index ef69dd8dedb..00000000000 --- a/src/gui/creds/httpcredentialsgui.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * 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 "application.h" -#include "account.h" -#include "common/asserts.h" -#include "creds/httpcredentialsgui.h" -#include "networkjobs.h" -#include "settingsdialog.h" -#include "theme.h" - -#include -#include -#include -#include -#include -#include -#include - - -using namespace QKeychain; - -namespace OCC { - -Q_LOGGING_CATEGORY(lcHttpCredentialsGui, "sync.credentials.http.gui", QtInfoMsg) - -void HttpCredentialsGui::openBrowser() -{ - OC_ASSERT(isUsingOAuth()); - if (isUsingOAuth()) { - if (_asyncAuth) { - _asyncAuth->openBrowser(); - } else { - qCWarning(lcHttpCredentialsGui) << "There is no authentication job running, did the previous attempt fail?"; - askFromUserAsync(); - } - } -} - -void HttpCredentialsGui::askFromUser() -{ - // This function can be called from AccountState::slotInvalidCredentials, - // which (indirectly, through HttpCredentials::invalidateToken) schedules - // a cache wipe of the qnam. We can only execute a network job again once - // the cache has been cleared, otherwise we'd interfere with the job. - QTimer::singleShot(0, this, &HttpCredentialsGui::askFromUserAsync); -} - -void HttpCredentialsGui::askFromUserAsync() -{ - const auto updateOAuth = [this] { - _asyncAuth.reset(new OAuth(_account, this)); - connect(_asyncAuth.data(), &OAuth::result, - this, &HttpCredentialsGui::asyncAuthResult); - connect(_asyncAuth.data(), &OAuth::destroyed, - this, &HttpCredentialsGui::authorisationLinkChanged); - _asyncAuth->startAuthentication(); - emit authorisationLinkChanged(); - }; - if (isUsingOAuth()) { - updateOAuth(); - } else { - // First, we will check what kind of auth we need. - auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this); - QObject::connect(job, &DetermineAuthTypeJob::authType, this, [updateOAuth, this](DetermineAuthTypeJob::AuthType type) { - _authType = type; - if (type == DetermineAuthTypeJob::AuthType::OAuth) { - updateOAuth(); - } else if (type == DetermineAuthTypeJob::AuthType::Basic) { - showDialog(); - } else { - qCWarning(lcHttpCredentialsGui) << "Bad http auth type:" << type; - emit asked(); - } - }); - job->start(); - } -} - -void HttpCredentialsGui::asyncAuthResult(OAuth::Result r, const QString &user, - const QString &token, const QString &refreshToken) -{ - switch (r) { - case OAuth::NotSupported: - showDialog(); - _asyncAuth.reset(nullptr); - return; - case OAuth::Error: - _asyncAuth.reset(nullptr); - emit asked(); - return; - case OAuth::LoggedIn: - break; - } - - OC_ASSERT(_user == user); // ensured by _asyncAuth - - _password = token; - _refreshToken = refreshToken; - _ready = true; - persist(); - _asyncAuth.reset(nullptr); - emit asked(); -} - -void HttpCredentialsGui::showDialog() -{ - QString msg = tr("Please enter %1 password:
" - "
" - "User: %2
" - "Account: %3
") - .arg(Utility::escape(Theme::instance()->appNameGUI()), - Utility::escape(_user), - Utility::escape(_account->displayName())); - - QString reqTxt = requestAppPasswordText(_account); - if (!reqTxt.isEmpty()) { - msg += QLatin1String("
") + reqTxt + QLatin1String("
"); - } - if (!_fetchErrorString.isEmpty()) { - msg += QLatin1String("
") - + tr("Reading from keychain failed with error: '%1'") - .arg(Utility::escape(_fetchErrorString)) - + QLatin1String("
"); - } - - QInputDialog *dialog = new QInputDialog(ocApp()->gui()->settingsDialog()); - dialog->setAttribute(Qt::WA_DeleteOnClose, true); - dialog->setWindowTitle(tr("Enter Password")); - dialog->setLabelText(msg); - dialog->setTextValue(_previousPassword); - dialog->setTextEchoMode(QLineEdit::Password); - if (QLabel *dialogLabel = dialog->findChild()) { - dialogLabel->setOpenExternalLinks(true); - dialogLabel->setTextFormat(Qt::RichText); - } - - connect(dialog, &QDialog::finished, this, [this, dialog](int result) { - if (result == QDialog::Accepted) { - _password = dialog->textValue(); - _refreshToken.clear(); - _ready = true; - persist(); - } - emit asked(); - }); - dialog->open(); - ownCloudGui::raiseDialog(dialog); -} - -QString HttpCredentialsGui::requestAppPasswordText(const Account *account) -{ - int version = account->serverVersionInt(); - QString path; - - // Version may not be available before login on new servers! - if (!version || version >= Account::makeServerVersion(10, 0, 0)) { - path = QLatin1String("/index.php/settings/personal?sectionid=security#apppasswords"); - } else if (version >= Account::makeServerVersion(9, 1, 0)) { - path = QLatin1String("/index.php/settings/personal?section=apppasswords"); - } else { - // Older server than 9.1 does not have the feature to request App Password - return QString(); - } - - auto baseUrl = account->url().toString(); - if (baseUrl.endsWith('/')) - baseUrl.chop(1); - return tr("Click here to request an app password from the web interface.") - .arg(baseUrl + path); -} -} // namespace OCC diff --git a/src/gui/creds/httpcredentialsgui.h b/src/gui/creds/httpcredentialsgui.h deleted file mode 100644 index 4c84de8d2f3..00000000000 --- a/src/gui/creds/httpcredentialsgui.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * 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 "creds/httpcredentials.h" -#include "creds/oauth.h" -#include -#include - -namespace OCC { - -/** - * @brief The HttpCredentialsGui class - * @ingroup gui - */ -class HttpCredentialsGui : public HttpCredentials -{ - Q_OBJECT -public: - HttpCredentialsGui() - : HttpCredentials() - { - } - HttpCredentialsGui(const QString &user, const QString &password) - : HttpCredentials(DetermineAuthTypeJob::AuthType::Basic, user, password) - { - } - - HttpCredentialsGui(const QString &user, const QString &password, const QString &refreshToken) - : HttpCredentials(DetermineAuthTypeJob::AuthType::OAuth, user, password) - { - _refreshToken = refreshToken; - } - - void openBrowser(); - /** - * This will query the server and either uses OAuth via _asyncAuth->start() - * or call showDialog to ask the password - */ - void askFromUser() override; - - static QString requestAppPasswordText(const Account *account); -private slots: - void asyncAuthResult(OAuth::Result, const QString &user, const QString &accessToken, const QString &refreshToken); - void showDialog(); - void askFromUserAsync(); - -signals: - void authorisationLinkChanged(); - -private: - QScopedPointer> _asyncAuth; -}; - -} // namespace OCC diff --git a/src/gui/creds/oauth.cpp b/src/gui/creds/oauth.cpp new file mode 100644 index 00000000000..a0094d7e7b6 --- /dev/null +++ b/src/gui/creds/oauth.cpp @@ -0,0 +1,585 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "oauth.h" + +#include "accessmanager.h" +#include "creds/credentialssupport.h" +#include "gui/networkadapters/userinfoadapter.h" +#include "libsync/creds/credentialmanager.h" +#include "networkjobs/checkserverjobfactory.h" +#include "oauthhtmlpage.h" +#include "theme.h" + +#include +#include +#include +#include +#include +#include + +using namespace std::chrono; +using namespace std::chrono_literals; + +using namespace OCC; + +// todo: #24 - this is such a mess. it needs to parted out into clear classes (each in their own files), every impl needs to be reviewed. +// I think there is some duplication here as well - just sort it out as much as possible. +Q_LOGGING_CATEGORY(lcOauth, "sync.credentials.oauth", QtInfoMsg) + +namespace { + +const QString wellKnownPathC = QStringLiteral("/.well-known/openid-configuration"); + +OAuth::PromptValuesSupportedFlags defaultOauthPromptValue(const OpenIdConfig& config) +{ + static const auto promptValue = [config] { + auto prompt = config.prompt(); + OAuth::PromptValuesSupportedFlags out = OAuth::PromptValuesSupported::none; + // convert the legacy openIdConnectPrompt() to QFlags + for (const auto &x : prompt.split(QLatin1Char(' '))) { + out |= Utility::stringToEnum(x); + } + return out; + }(); + return promptValue; +} + +seconds defaultTimeout() +{ + // as the OAuth process can be interactive we don't want 5min of inactivity + return qMin(30s, OCC::AbstractNetworkJob::httpTimeout); +} + +int defaultTimeoutMs() +{ + return static_cast(duration_cast(defaultTimeout()).count()); +} + +QVariant getRequiredField(const QVariantMap &json, const QString &s, QString *error) +{ + const auto out = json.constFind(s); + if (out == json.constEnd()) { + error->append(QStringLiteral("\tError: Missing field %1\n").arg(s)); + return {}; + } + return *out; +} +} + +OAuth::OAuth(const QUrl &serverUrl, const QString &davUser, const OpenIdConfig& openIdConfig, QNetworkAccessManager *networkAccessManager, QObject *parent) + : QObject(parent) + , _serverUrl(serverUrl) + , _davUser(davUser) + , _openIdConfig(openIdConfig) + , _networkAccessManager(networkAccessManager) + , _redirectUrl(QString("http://localhost")) + , _supportedPromptValues(defaultOauthPromptValue(openIdConfig)) +{ +} + +OAuth::~OAuth() = default; + +void OAuth::startAuthentication() +{ + qCDebug(lcOauth) << "starting authentication"; + + // Listen on the socket to get a port which will be used in the redirect_uri + QList ports = _openIdConfig.ports(); + for (const auto port : std::as_const(ports)) { + if (_server.listen(QHostAddress::LocalHost, port)) { + break; + } + qCDebug(lcOauth) << "Creating local server Port:" << port << "failed. Error:" << _server.errorString(); + } + if (!_server.isListening()) { + qCDebug(lcOauth) << "server is not listening"; + Q_EMIT result(Error, {}); + return; + } + + _pkceCodeVerifier = generateRandomString(24); + OC_ASSERT(_pkceCodeVerifier.size() == 128) + _state = generateRandomString(8); + + connect(this, &OAuth::fetchWellKnownFinished, this, &OAuth::authorisationLinkChanged); + + fetchWellKnown(); + + QObject::connect(&_server, &QTcpServer::newConnection, this, &OAuth::handleTcpConnection); +} + +void OAuth::httpReplyAndClose(QPointer socket, bool success, const QString &code, const QString &title, const QString &body, const QStringList &additionalHeader) +{ + if (!socket) { + return; // socket can have been deleted if the browser was closed + } + + const QByteArray content = OAuthHtmlPage::buildPage(success, title, body.isEmpty() ? title : body).toUtf8(); + QString header = QStringLiteral("HTTP/1.1 %1\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "Connection: close\r\n" + "Content-Length: %2\r\n") + .arg(code, QString::number(content.length())); + + if (!additionalHeader.isEmpty()) { + const QString nl = QStringLiteral("\r\n"); + header += additionalHeader.join(nl) + nl; + } + + const QByteArray msg = header.toUtf8() + QByteArrayLiteral("\r\n") + content; + + qCDebug(lcOauth) << "replying with HTTP response and closing socket:" << msg; + + socket->write(msg); + socket->disconnectFromHost(); + // this is super important as we may delete the oauth/server before the socket has completed its work + // let the deleteLater on final disconnect take care of the cleanup + socket->setParent(nullptr); +} + +void OAuth::finalize(const QString &accessToken, const QString &refreshToken, const QUrl &messageUrl) +{ + const QString brand = Theme::instance()->appNameGUI(); + const QString loginSuccessfulTitle = tr("Successfully signed in"); + const QString loginSuccessfulHtml = tr("Now, explore %1 on desktop.").arg(brand); + + if (messageUrl.isValid()) { + httpReplyAndClose(_socket, true, QStringLiteral("303 See Other"), loginSuccessfulTitle, loginSuccessfulHtml, + {QStringLiteral("Location: %1").arg(QString::fromUtf8(messageUrl.toEncoded()))}); + } else { + httpReplyAndClose(_socket, true, QStringLiteral("200 OK"), loginSuccessfulTitle, loginSuccessfulHtml); + } + Q_EMIT result(LoggedIn, accessToken, refreshToken); +} + +// the tcp server handling has to be done async while we wait for a suitable socket with which to communiciate with the +// authentication web page. The async handling for tcp includes this function + the handleSocketReadyRead +void OAuth::handleTcpConnection() +{ + while (_server.hasPendingConnections()) { + auto socket = _server.nextPendingConnection(); + + qCDebug(lcOauth) << "accepted client connection from" << socket->peerAddress(); + + QObject::connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); + + QObject::connect(socket, &QIODevice::readyRead, this, &OAuth::handleSocketReadyRead); + } +} + +void OAuth::handleSocketReadyRead() +{ + // we already have the socket we want to use to complete the auth communications so ignore any other incoming + // sockets + if (!_server.isListening()) + return; + + QTcpSocket *connected = qobject_cast(sender()); + if (!connected) { + // something is really wrong + return; + } + QPointer candidateSocket(connected); + + Q_ASSERT(connected->state() == QAbstractSocket::ConnectedState); + + const QByteArray peek = candidateSocket->peek(qMin(connected->bytesAvailable(), 4000LL)); // The code should always be within the first 4K + + // wait until we find a \n + if (!peek.contains('\n')) { + return; + } + + qCDebug(lcOauth) << "Server provided:" << peek; + + const auto getPrefix = QByteArrayLiteral("GET /?"); + if (!peek.startsWith(getPrefix)) { + httpReplyAndClose(candidateSocket, false, QStringLiteral("404 Not Found"), QStringLiteral("404 Not Found")); + return; + } + const auto endOfUrl = peek.indexOf(' ', getPrefix.length()); + + _queryArgs = QUrlQuery(QUrl::fromPercentEncoding(peek.mid(getPrefix.length(), endOfUrl - getPrefix.length()))); + + if (_queryArgs.queryItemValue(QStringLiteral("state")).toUtf8() != _state) { + httpReplyAndClose(candidateSocket, false, QStringLiteral("400 Bad Request"), QStringLiteral("400 Bad Request")); + return; + } + + // basic checks passed, we now want to commit to this socket all other pending connections will effectively be + // ignored from here out. To ensure we get as few stray connections as possible, just close the server + _socket = candidateSocket; + + // server port cannot be queried any more after server has been closed, which we want to do as early as possible in the processing chain + // therefore we have to store it beforehand + _serverPort = _server.serverPort(); + _server.close(); + + getTokens(); +} + +// todo: #24 - this can be converted to an adapter +void OAuth::getTokens() +{ + if(_tokenEndpoint.isEmpty()) { + Q_EMIT result(Error); + return; + } + + auto postTokenReply = postTokenRequest({ + {QStringLiteral("grant_type"), QStringLiteral("authorization_code")}, + {QStringLiteral("code"), _queryArgs.queryItemValue(QStringLiteral("code"))}, + {QStringLiteral("redirect_uri"), QStringLiteral("%1:%2").arg(_redirectUrl, QString::number(_serverPort))}, + {QStringLiteral("code_verifier"), QString::fromUtf8(_pkceCodeVerifier)}, + }); + + connect(postTokenReply, &QNetworkReply::finished, this, [postTokenReply, this] { + const auto jsonData = postTokenReply->readAll(); + QJsonParseError jsonParseError; + const auto data = QJsonDocument::fromJson(jsonData, &jsonParseError).object().toVariantMap(); + QString fieldsError; + _accessToken = getRequiredField(data, QStringLiteral("access_token"), &fieldsError).toString(); + _refreshToken = getRequiredField(data, QStringLiteral("refresh_token"), &fieldsError).toString(); + const QString tokenType = getRequiredField(data, QStringLiteral("token_type"), &fieldsError).toString().toLower(); + _messageUrl = QUrl::fromEncoded(data[QStringLiteral("message_url")].toByteArray()); + + if (postTokenReply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError || !fieldsError.isEmpty() + || tokenType != QLatin1String("bearer")) { + // do we have error message suitable for users? + QString errorReason = data[QStringLiteral("error_description")].toString(); + if (errorReason.isEmpty()) { + // fall back to technical error + errorReason = data[QStringLiteral("error")].toString(); + } + if (!errorReason.isEmpty()) { + errorReason = tr("Error returned from the server: %1").arg(errorReason.toHtmlEscaped()); + } else if (postTokenReply->error() != QNetworkReply::NoError) { + errorReason = tr("There was an error accessing the 'token' endpoint:
%1").arg(postTokenReply->errorString().toHtmlEscaped()); + } else if (jsonParseError.error != QJsonParseError::NoError) { + errorReason = tr("Could not parse the JSON returned from the server:
%1").arg(jsonParseError.errorString()); + } else if (tokenType != QStringLiteral("bearer")) { + errorReason = tr("Unsupported token type: %1").arg(tokenType); + } else if (!fieldsError.isEmpty()) { + errorReason = tr("The reply from the server did not contain all expected fields\n:%1").arg(fieldsError); + } else { + errorReason = tr("Unknown Error"); + } + qCWarning(lcOauth) << "Error when getting the accessToken" << errorReason; + httpReplyAndClose(_socket, false, QStringLiteral("500 Internal Server Error"), tr("Login Error"), errorReason); + Q_EMIT result(Error); + return; + } + + // this check is important when reauthenticating an existing account, where a davUser is already defined. + // In this context, the check just validates that the user had not authenticated with a different username from the account + if (!_davUser.isEmpty()) { + checkUserInfo(); + } else { // davUser is empty so we are done: + finalize(_accessToken, _refreshToken, _messageUrl); + } + }); +} + +void OAuth::checkUserInfo() +{ + Q_ASSERT(!_davUser.isEmpty()); + + UserInfoAdapter infoAdapter(_networkAccessManager, _accessToken, _serverUrl); + const UserInfoResult infoResult = infoAdapter.getResult(); + + if (!infoResult.success()) { + httpReplyAndClose(_socket, false, QStringLiteral("500 Internal Server Error"), tr("Login Error"), infoResult.error); + Q_EMIT result(Error); + } else { + if (infoResult.userId.compare(_davUser, Qt::CaseInsensitive) != 0) { + // Connected with the wrong user + qCWarning(lcOauth) << "We expected the user" << _davUser << "but the server answered with user" << infoResult.userId; + const QString message = tr("You logged-in with user %1, but must login with user %2.
" + "Please return to the %3 client and restart the authentication.") + .arg(infoResult.userId, _davUser, Theme::instance()->appNameGUI()); + httpReplyAndClose(_socket, false, QStringLiteral("403 Forbidden"), tr("Wrong user"), message); + Q_EMIT result(Error); + } else { + finalize(_accessToken, _refreshToken, _messageUrl); + } + } +} + +// todo: #24 - I think this should also be an adapter. I also have a vague recollection that we have a job that does this already +QNetworkReply *OAuth::postTokenRequest(QUrlQuery &&queryItems) +{ + const QUrl requestTokenUrl = _tokenEndpoint; + QNetworkRequest req; + req.setTransferTimeout(defaultTimeoutMs()); + switch (_endpointAuthMethod) { + case TokenEndpointAuthMethods::client_secret_basic: + req.setRawHeader("Authorization", "Basic " + QStringLiteral("%1:%2").arg(_openIdConfig.clientId(), _openIdConfig.clientSecret()).toUtf8().toBase64()); + break; + case TokenEndpointAuthMethods::client_secret_post: + queryItems.addQueryItem(QStringLiteral("client_id"), _openIdConfig.clientId()); + queryItems.addQueryItem(QStringLiteral("client_secret"), _openIdConfig.clientSecret()); + break; + } + req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded; charset=UTF-8")); + req.setAttribute(DontAddCredentialsAttribute, true); + + queryItems.addQueryItem(QStringLiteral("scope"), QString::fromUtf8(QUrl::toPercentEncoding(_openIdConfig.scopes()))); + req.setUrl(requestTokenUrl); + return _networkAccessManager->post(req, queryItems.toString(QUrl::FullyEncoded).toUtf8()); +} + +QByteArray OAuth::generateRandomString(size_t size) const +{ + // TODO: do we need a variable size? + std::vector buffer(size, 0); + QRandomGenerator::global()->fillRange(buffer.data(), static_cast(size)); + return QByteArray(reinterpret_cast(buffer.data()), static_cast(size * sizeof(quint32))).toBase64(QByteArray::Base64UrlEncoding); +} + +QUrl OAuth::authorisationLink() const +{ + Q_ASSERT(_server.isListening()); + Q_ASSERT(_wellKnownFinished); + + if (!_authEndpoint.isValid()) { + return {}; // no auth endpoint, cannot continue + } + + const QByteArray code_challenge = + QCryptographicHash::hash(_pkceCodeVerifier, QCryptographicHash::Sha256).toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QUrlQuery query{{QStringLiteral("response_type"), QStringLiteral("code")}, {QStringLiteral("client_id"), _openIdConfig.clientId()}, + {QStringLiteral("redirect_uri"), QStringLiteral("%1:%2").arg(_redirectUrl, QString::number(_server.serverPort()))}, + {QStringLiteral("code_challenge"), QString::fromLatin1(code_challenge)}, {QStringLiteral("code_challenge_method"), QStringLiteral("S256")}, + {QStringLiteral("scope"), QString::fromUtf8(QUrl::toPercentEncoding(_openIdConfig.scopes()))}, + {QStringLiteral("prompt"), QString::fromUtf8(QUrl::toPercentEncoding(toString(_supportedPromptValues)))}, + {QStringLiteral("state"), QString::fromUtf8(_state)}}; + + if (!_davUser.isEmpty()) { + const QString davUser = QString::fromUtf8(QUrl::toPercentEncoding(_davUser)); // Issue #7762; + // open id connect + query.addQueryItem(QStringLiteral("login_hint"), davUser); + // todo: #20 oc10 as fallback! + query.addQueryItem(QStringLiteral("user"), davUser); + } + return Utility::concatUrlPath(_authEndpoint, {}, query); +} + +void OAuth::fetchWellKnown() +{ + qCDebug(lcOauth) << "fetching" << wellKnownPathC; + + QNetworkRequest req; + req.setAttribute(DontAddCredentialsAttribute, true); + req.setUrl(Utility::concatUrlPath(_serverUrl, wellKnownPathC)); + req.setTransferTimeout(defaultTimeoutMs()); + + auto reply = _networkAccessManager->get(req); + + QObject::connect(reply, &QNetworkReply::finished, this, [reply, this] { + _wellKnownFinished = true; + if (reply->error() != QNetworkReply::NoError) { + qCDebug(lcOauth) << "failed to fetch .well-known reply, error:" << reply->error(); + // Most likely the file does not exist, default to the normal endpoint + Q_EMIT fetchWellKnownFinished(); + return; + } + QJsonParseError err = {}; + QJsonObject data = QJsonDocument::fromJson(reply->readAll(), &err).object(); + if (err.error == QJsonParseError::NoError) { + _authEndpoint = QUrl::fromEncoded(data[QStringLiteral("authorization_endpoint")].toString().toUtf8()); + _tokenEndpoint = QUrl::fromEncoded(data[QStringLiteral("token_endpoint")].toString().toUtf8()); + _redirectUrl = QStringLiteral("http://127.0.0.1"); + + const auto authMethods = data.value(QStringLiteral("token_endpoint_auth_methods_supported")).toArray(); + if (authMethods.contains(QStringLiteral("client_secret_basic"))) { + _endpointAuthMethod = TokenEndpointAuthMethods::client_secret_basic; + } else if (authMethods.contains(QStringLiteral("client_secret_post"))) { + _endpointAuthMethod = TokenEndpointAuthMethods::client_secret_post; + } else { + OC_ASSERT_X(false, qPrintable(QStringLiteral("Unsupported token_endpoint_auth_methods_supported: %1").arg(QDebug::toString(authMethods)))); + } + const auto promptValuesSupported = data.value(QStringLiteral("prompt_values_supported")).toArray(); + if (!promptValuesSupported.isEmpty()) { + _supportedPromptValues = PromptValuesSupported::none; + for (const auto &x : promptValuesSupported) { + const auto flag = Utility::stringToEnum(x.toString()); + // only use flags present in _openIdConfig->openIdConnectPrompt() + if (flag & defaultOauthPromptValue(_openIdConfig)) + _supportedPromptValues |= flag; + } + } + + qCDebug(lcOauth) << "parsing .well-known reply successful, auth endpoint" << _authEndpoint << "and token endpoint" << _tokenEndpoint; + } else if (err.error == QJsonParseError::IllegalValue) { + qCDebug(lcOauth) << "failed to parse .well-known reply as JSON, server might not support OIDC"; + } else { + qCDebug(lcOauth) << "failed to parse .well-known reply, error:" << err.error; + } + Q_EMIT fetchWellKnownFinished(); + }); +} + +/** + * Checks whether a URL returned by the server is valid. + * @param url URL to validate + * @return true if validation is successful, false otherwise + */ +bool isUrlSchemeValid(const QUrl &url) +{ + qCDebug(lcOauth()) << "Checking URL scheme for validity:" << url; + + // todo: #21 - I am not sure this check has much of a point to begin with as + // we have already validated any URL via user input OR we take the url from the webfinger service, + // but if this is in fact "needed" it should check more than just the scheme imo + + // the following allow list contains URL schemes accepted as valid + // OAuth 2.0 URLs must be HTTPS to be in compliance with the specification + // for unit tests, we also permit the non existing oauthtest scheme + const QStringList allowedSchemes({QStringLiteral("https"), QStringLiteral("oauthtest")}); + return allowedSchemes.contains(url.scheme()); +} + +void OAuth::openBrowser() +{ + auto authorisationURL = authorisationLink(); + if (authorisationURL.isEmpty()) { + qCWarning(lcOauth) << "Authorization URL is unknown - well-known/openid-configuration endpoint did not return usable information"; + Q_EMIT result(ErrorIdPUnreachable, QString()); + return; + } + qCDebug(lcOauth) << "opening browser"; + + if (!isUrlSchemeValid(authorisationURL)) { + qCWarning(lcOauth) << "URL validation failed"; + Q_EMIT result(ErrorInsecureUrl, QString()); + return; + } + + if (!QDesktopServices::openUrl(authorisationURL)) { + qCWarning(lcOauth) << "QDesktopServices::openUrl Failed"; + // We cannot open the browser, then we claim we don't support OAuth. + Q_EMIT result(NotSupported, QString()); + } +} + +// todo: I was contemplating how we can make sure the passed account isn't null before we use it +// to seed the OAuth ctr, and really, I'm not sure this should be a subclass of oauth in the first place. Instead it could simply use an +// oauth instance to complete the tasks it can't do itself -> this could possibly be a "has a" not an "is a" impl +AccountBasedOAuth::AccountBasedOAuth(Account *account, const OpenIdConfig& openIdConfig, QObject *parent) + : OAuth(account->url(), account->davUser(), openIdConfig, account->accessManager(), parent) + , _account(account) +{ +} + +void AccountBasedOAuth::startAuthentication() +{ + OAuth::startAuthentication(); +} + +void AccountBasedOAuth::fetchWellKnown() +{ + if (!_account) { + qCWarning(lcOauth) << "Unable to fetch well known, account has been deleted"; + return; + } + + qCDebug(lcOauth) << "starting CheckServerJob before fetching" << wellKnownPathC; + + auto *checkServerJob = CheckServerJobFactory::createFromAccount(_account, true).startJob(_serverUrl, this); + + connect(checkServerJob, &CoreJob::finished, this, [checkServerJob, this]() { + if (checkServerJob->success()) { + qCDebug(lcOauth) << "CheckServerJob succeeded, fetching" << wellKnownPathC; + OAuth::fetchWellKnown(); + } else { + qCDebug(lcOauth) << "CheckServerJob failed, error:" << checkServerJob->errorMessage(); + if (_isRefreshingToken) { + Q_EMIT refreshError(checkServerJob->reply()->error(), checkServerJob->errorMessage()); + } else { + Q_EMIT result(Error); + } + } + }); +} + +void AccountBasedOAuth::refreshAuthentication(const QString &refreshToken) +{ + if (!OC_ENSURE(!_isRefreshingToken)) { + qCDebug(lcOauth) << "already refreshing token, aborting"; + return; + } + + // I don't see where this ever gets set to false - seems to rely on a one shot run before creating a new instance where the value + // is initialized to false. hm. + _isRefreshingToken = true; + + + // make this a real function - + auto refresh = [this, refreshToken] { + auto reply = postTokenRequest({{QStringLiteral("grant_type"), QStringLiteral("refresh_token")}, {QStringLiteral("refresh_token"), refreshToken}}); + connect(reply, &QNetworkReply::finished, this, [reply, refreshToken, this]() { + const auto jsonData = reply->readAll(); + QJsonParseError jsonParseError; + const auto data = QJsonDocument::fromJson(jsonData, &jsonParseError).object().toVariantMap(); + QString accessToken; + QString newRefreshToken = refreshToken; + // https://developer.okta.com/docs/reference/api/oidc/#response-properties-2 + const QString errorString = data.value(QStringLiteral("error")).toString(); + if (!errorString.isEmpty()) { + if (errorString == QLatin1String("invalid_grant") || errorString == QLatin1String("invalid_request")) { + newRefreshToken.clear(); + } else { + qCWarning(lcOauth) << "Error while refreshing the token:" << errorString << data.value(QStringLiteral("error_description")).toString(); + } + } else if (reply->error() != QNetworkReply::NoError) { + qCWarning(lcOauth) << "Error while refreshing the token:" << reply->error() << ":" << reply->errorString() + << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + Q_EMIT refreshError(reply->error(), reply->errorString()); + return; + } else { + if (jsonParseError.error != QJsonParseError::NoError || data.isEmpty()) { + // Invalid or empty JSON: Network error maybe? + qCWarning(lcOauth) << "Error while refreshing the token:" << jsonParseError.errorString(); + } else { + QString error; + accessToken = getRequiredField(data, QStringLiteral("access_token"), &error).toString(); + if (!error.isEmpty()) { + qCWarning(lcOauth) << "The reply from the server did not contain all expected fields:" << error; + } + + const auto refresh_token = data.find(QStringLiteral("refresh_token")); + if (refresh_token != data.constEnd()) { + newRefreshToken = refresh_token.value().toString(); + } + } + } + Q_EMIT refreshFinished(accessToken, newRefreshToken); + }); + }; + + connect(this, &OAuth::fetchWellKnownFinished, this, refresh); + + fetchWellKnown(); +} + +QString OCC::toString(OAuth::PromptValuesSupportedFlags s) +{ + QStringList out; + for (auto k : {OAuth::PromptValuesSupported::consent, OAuth::PromptValuesSupported::select_account}) + if (s & k) { + out += Utility::enumToString(k); + } + return out.join(QLatin1Char(' ')); +} diff --git a/src/gui/creds/oauth.h b/src/gui/creds/oauth.h new file mode 100644 index 00000000000..45661aa2314 --- /dev/null +++ b/src/gui/creds/oauth.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "owncloudlib.h" + +#include "account.h" +#include "config/openidconfig.h" + +#include +#include +#include +#include +#include + + +namespace OCC { +class JsonJob; + +/** + * Job that do the authorization grant and fetch the access token + * + * Normal workflow: + * + * --> start() + * | + * +----> fetchWellKnown() query the ".well-known/openid-configuration" endpoint + * | + * +----> openBrowser() open the browser after fetchWellKnown finished to the specified page + * | (or the default 'oauth2/authorize' if fetchWellKnown does not exist) + * | Then the browser will redirect to http://localhost:xxx + * | + * +----> _server starts listening on a TCP port waiting for an HTTP request with a 'code' + * | + * v + * request the access_token and the refresh_token via 'apps/oauth2/api/v1/token' + * | + * +-> Request the user_id is not present + * | | + * v v + * finalize(...): Q_EMIT result(...) + * + */ +class OAuth : public QObject +{ + Q_OBJECT +public: + enum Result { NotSupported, LoggedIn, Error, ErrorInsecureUrl, ErrorIdPUnreachable }; + Q_ENUM(Result) + enum class TokenEndpointAuthMethods : char { client_secret_basic, client_secret_post }; + Q_ENUM(TokenEndpointAuthMethods) + + enum class PromptValuesSupported : char { none = 0, consent = 1 << 0, select_account = 1 << 1, login = 1 << 2, create = 1 << 3 }; + Q_ENUM(PromptValuesSupported) + Q_DECLARE_FLAGS(PromptValuesSupportedFlags, PromptValuesSupported) + + OAuth(const QUrl &serverUrl, const QString &davUser, const OpenIdConfig& openIdConfig, QNetworkAccessManager *networkAccessManager, QObject *parent); + ~OAuth() override; + + virtual void startAuthentication(); + void openBrowser(); + QUrl authorisationLink() const; + +Q_SIGNALS: + /** + * The state has changed. + * when logged in, token has the value of the token. + */ + void result(OAuth::Result result, const QString &token = QString(), const QString &refreshToken = QString()); + + /** + * emitted when the call to the well-known endpoint is finished + */ + void authorisationLinkChanged(); + + void fetchWellKnownFinished(); + + +protected: + + QUrl _serverUrl; + QString _davUser; + OpenIdConfig _openIdConfig; + QNetworkAccessManager *_networkAccessManager; + bool _isRefreshingToken = false; + + virtual void fetchWellKnown(); + + QNetworkReply *postTokenRequest(QUrlQuery &&queryItems); + +protected Q_SLOTS: + void handleSocketReadyRead(); + +private: + void handleTcpConnection(); + void getTokens(); + void checkUserInfo(); + void finalize(const QString &accessToken, const QString &refreshToken, const QUrl &messageUrl); + void httpReplyAndClose( + QPointer socket, bool success, const QString &code, const QString &title, const QString &body = {}, const QStringList &additionalHeader = {}); + + QByteArray generateRandomString(size_t size) const; + + // The actual data we're collecting! + QString _accessToken; + QString _refreshToken; + QUrl _messageUrl; + + // peripheral deps + QTcpServer _server; + QPointer _socket; + QUrlQuery _queryArgs; + quint16 _serverPort; + + bool _wellKnownFinished = false; + + QUrl _authEndpoint; + QUrl _tokenEndpoint; + QString _redirectUrl; + QByteArray _pkceCodeVerifier; + QByteArray _state; + + TokenEndpointAuthMethods _endpointAuthMethod = TokenEndpointAuthMethods::client_secret_basic; + PromptValuesSupportedFlags _supportedPromptValues = {PromptValuesSupported::consent, PromptValuesSupported::select_account}; +}; + +/** + * This variant of OAuth uses an account's network access manager etc. + * Instead of relying on the user to provide a working server URL, a CheckServerJob is run upon start(), which also stores the fetched cookies in the account's state. + * Furthermore, it takes care of storing and loading the dynamic registration data in the account's credentials manager. + */ +class AccountBasedOAuth : public OAuth +{ + Q_OBJECT + +public: + explicit AccountBasedOAuth(Account *account, const OpenIdConfig& openIdConfig, QObject *parent); + + void startAuthentication() override; + + void refreshAuthentication(const QString &refreshToken); + +Q_SIGNALS: + // this can either come from refreshAuthentication or the fetchWellKnown's + // checkServerJob + void refreshError(QNetworkReply::NetworkError error, const QString &errorString); + void refreshFinished(const QString &accessToken, const QString &refreshToken); + +protected: + void fetchWellKnown() override; + +private: + QPointer _account; +}; + +QString toString(OAuth::PromptValuesSupportedFlags s); +Q_DECLARE_OPERATORS_FOR_FLAGS(OAuth::PromptValuesSupportedFlags) +} // namespace OCC diff --git a/src/gui/creds/oauthhtmlpage.cpp b/src/gui/creds/oauthhtmlpage.cpp new file mode 100644 index 00000000000..7e1a43ef345 --- /dev/null +++ b/src/gui/creds/oauthhtmlpage.cpp @@ -0,0 +1,22 @@ +// +// Created by deepdiver on 27/08/2025. +// + +#include "oauthhtmlpage.h" +#include "resources/template.h" + +#include + +namespace OCC { + +QString OAuthHtmlPage::buildPage(bool success, const QString &title, const QString &message) +{ + auto templateFile = success ? QStringLiteral(":/client/resources/oauth/success.html.in") : QStringLiteral(":/client/resources/oauth/error.html.in"); + QMap values = { + {QStringLiteral("TITLE"), title}, + {QStringLiteral("MESSAGE"), message}, + }; + return Resources::Template::renderTemplateFromFile(templateFile, values); +} + +} // OCC \ No newline at end of file diff --git a/src/gui/creds/oauthhtmlpage.h b/src/gui/creds/oauthhtmlpage.h new file mode 100644 index 00000000000..19aeeda1cc7 --- /dev/null +++ b/src/gui/creds/oauthhtmlpage.h @@ -0,0 +1,27 @@ +/* +* Copyright (C) by Olivier Goffart + * + * 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 + +namespace OCC { + +class OAuthHtmlPage +{ +public: + static QString buildPage(bool success, const QString &title, const QString &message); +}; + +} // OCC + diff --git a/src/gui/creds/requestauthenticationcontroller.cpp b/src/gui/creds/requestauthenticationcontroller.cpp new file mode 100644 index 00000000000..a4c1161f3b9 --- /dev/null +++ b/src/gui/creds/requestauthenticationcontroller.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "requestauthenticationcontroller.h" + +#include "accountsgui/accountview.h" +#include "application.h" +#include "requestauthenticationwidget.h" + +#include "accountmodalwidget.h" + +#include + +namespace OCC { + +/** + * @brief RequestAuthenticationController::RequestAuthenticationController + * @param widget - may be null, for example when using the "log in" function after user explicitly logged out + * @param parent - the usual qt parenting scheme + */ +RequestAuthenticationController::RequestAuthenticationController(QObject *parent) + : QObject{parent} +{ +} + +RequestAuthenticationController::~RequestAuthenticationController() +{ + // this should be enough to ensure the widgets get cleaned up, provided the account gui exists. + // if it does not, the widgets should have been deleted already since they are children of that gui + if (_modalWidget) + _modalWidget->reject(); +} + +void RequestAuthenticationController::handleSignIn() +{ + _oauth->openBrowser(); +} + +void RequestAuthenticationController::handleLogOut() +{ + if (_modalWidget) { + _modalWidget->reject(); + } + // this ultimately needs to be chained to creds::requestLogout signal + Q_EMIT requestLogout(); +} + +void RequestAuthenticationController::startAuthentication(Account *account) +{ + Q_ASSERT(account); + if (_oauth) { + delete _oauth; + _oauth = nullptr; + } + AppConfig systemConfig; + _account = account; + _oauth = new AccountBasedOAuth(_account, systemConfig.openIdConfig(), this); + connect(_oauth, &OAuth::authorisationLinkChanged, this, &RequestAuthenticationController::authUrlReady); + connect(_oauth, &OAuth::result, this, &RequestAuthenticationController::handleOAuthResult); + + // if there is an auth error, the widget already exists and is visible so just let it ride. + if (!_widget) { + // the widget is reparented to the AccountModalWidget then installed in the accounts gui. + // it will be cleaned up there - this is not a leak + _widget = new RequestAuthenticationWidget(nullptr); + connect(_widget, &RequestAuthenticationWidget::connectClicked, this, &RequestAuthenticationController::handleSignIn); + connect(_widget, &RequestAuthenticationWidget::stayLoggedOutClicked, this, &RequestAuthenticationController::handleLogOut); + + _modalWidget = new AccountModalWidget(QString(), _widget, nullptr); + emit requestAccountModal(_modalWidget); + } + Q_ASSERT(_widget && _modalWidget); + _oauth->startAuthentication(); +} + +void RequestAuthenticationController::authUrlReady() +{ + if (_widget) { + QString authUrl = _oauth->authorisationLink().toString(QUrl::FullyEncoded); + if (authUrl.isEmpty()) + _widget->setErrorMessage(tr("Unable to retrieve authorization link! Please contact your system administrator.")); + _widget->setAuthUrl(authUrl); + } else { + // just run with it + _oauth->openBrowser(); + } +} + +void RequestAuthenticationController::handleOAuthResult(OAuth::Result result, const QString &accessToken, const QString &refreshToken) +{ + QString errString; + + switch (result) { + case OAuth::ErrorIdPUnreachable: + errString = tr("IdP is unreachable. Contact your system administrator or try again later."); + break; + case OAuth::NotSupported: + // also should not happen after initial setup? + Q_ASSERT(false); + [[fallthrough]]; + case OAuth::ErrorInsecureUrl: + // should not happen after the initial setup + Q_ASSERT(false); + [[fallthrough]]; + case OAuth::Error: + errString = tr("Authentication failed."); + break; + case OAuth::LoggedIn: + errString.clear(); + break; + } + + + if (!errString.isEmpty() && _account) { + if (_widget) { + _widget->setErrorMessage(errString); + startAuthentication(_account); + } else + Q_EMIT authenticationFailed(errString); + } else { + if (_modalWidget) + _modalWidget->accept(); + Q_EMIT authenticationSucceeded(accessToken, refreshToken); + } + + ocApp()->ensureVisible(); +} +} diff --git a/src/gui/creds/requestauthenticationcontroller.h b/src/gui/creds/requestauthenticationcontroller.h new file mode 100644 index 00000000000..a0c1785379f --- /dev/null +++ b/src/gui/creds/requestauthenticationcontroller.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +#include "oauth.h" + +namespace OCC { + +class RequestAuthenticationWidget; +class AccountBasedOAuth; +class Account; +class AccountModalWidget; + +class RequestAuthenticationController : public QObject +{ + Q_OBJECT +public: + explicit RequestAuthenticationController(QObject *parent); + + ~RequestAuthenticationController() override; + + void startAuthentication(Account *account); + +Q_SIGNALS: + void requestAccountModal(OCC::AccountModalWidget *widget); + void requestLogout(); + void authenticationSucceeded(const QString &token, const QString &refreshToken); + // this is only emitted if the controller is running without a widget. + void authenticationFailed(const QString &errorMessage); + +private: + void handleSignIn(); + void handleLogOut(); + void authUrlReady(); + void handleOAuthResult(OAuth::Result, const QString &accessToken, const QString &refreshToken); + + AccountBasedOAuth *_oauth = nullptr; + QPointer _account = nullptr; + + // these will be cleaned up by the account gui automatically when the AccountModalWidget is "finished" + // that is handled internally when accept() or reject() is called + QPointer _widget = nullptr; + QPointer _modalWidget = nullptr; +}; +} diff --git a/src/gui/creds/requestauthenticationwidget.cpp b/src/gui/creds/requestauthenticationwidget.cpp new file mode 100644 index 00000000000..3cb43bcaba0 --- /dev/null +++ b/src/gui/creds/requestauthenticationwidget.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "requestauthenticationwidget.h" + +// #include "resources.h" +#include "template.h" +#include "theme.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OCC { + +RequestAuthenticationWidget::RequestAuthenticationWidget(QWidget *parent) + : QWidget{parent} +{ + updateColors(); + + QString appName = Theme::instance()->appNameGUI(); + + QLabel *logoLabel = new QLabel({}, this); + logoLabel->setPixmap(Theme::instance()->wizardHeaderLogo().pixmap(200, 100)); + logoLabel->setAlignment(Qt::AlignCenter); + logoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + logoLabel->setAccessibleName(tr("Application Name Logo %1").arg(appName)); + + QLabel *titleLabel = new QLabel(tr("Sign in required").arg(appName), this); + QFont welcomeFont = titleLabel->font(); + welcomeFont.setWeight(QFont::DemiBold); + welcomeFont.setPixelSize(20); + titleLabel->setFont(welcomeFont); + titleLabel->setAlignment(Qt::AlignCenter); + titleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + QLabel *instructionLabel = new QLabel(tr("You may have been automatically disconnected due to a server issue or time out. Please sign in again."), this); + QFont font = instructionLabel->font(); + font.setPixelSize(14); + instructionLabel->setFont(font); + instructionLabel->setWordWrap(true); + instructionLabel->setAlignment(Qt::AlignCenter); + instructionLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + _urlField = new QLabel(this); + _urlField->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _urlField->installEventFilter(this); + _urlField->setAccessibleName(tr("Sign in URL")); // Prevent screen readers from reading the full URL!!! + _urlField->setAccessibleDescription(tr("To copy the sign in URL to the clipboard, use the copy button")); + + _copyButton = new QPushButton(_copyIcon, QString(), this); + _copyButton->setFlat(true); + _copyButton->setContentsMargins(0, 0, 0, 0); + _copyButton->setFixedSize(_urlField->height(), _urlField->height()); + _copyButton->setAccessibleDescription(tr("Copy URL to sign in")); + _copyButton->installEventFilter(this); + connect(_copyButton, &QPushButton::clicked, this, &RequestAuthenticationWidget::onCopyUrl); + + _errorField = new QLabel(QString(), this); + QPalette errorPalette = _errorField->palette(); + errorPalette.setColor(QPalette::Text, Qt::red); + errorPalette.setColor(QPalette::WindowText, Qt::red); + _errorField->setPalette(errorPalette); + _errorField->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _errorField->setWordWrap(true); + _errorField->setAlignment(Qt::AlignLeft); + _errorField->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + + QLabel *footerLogoLabel = nullptr; + if (!Theme::instance()->wizardFooterLogo().isNull()) { + footerLogoLabel = new QLabel({}, this); + footerLogoLabel->setPixmap(Theme::instance()->wizardFooterLogo().pixmap(100, 52)); + footerLogoLabel->setAlignment(Qt::AlignCenter); + footerLogoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + footerLogoLabel->setAccessibleName(tr("Additional logo defined by the organization")); + } + + _cancelButton = new QPushButton(tr("Stay logged out"), this); + connect(_cancelButton, &QPushButton::clicked, this, &RequestAuthenticationWidget::stayLoggedOutClicked); + _signInButton = new QPushButton(tr("Sign in"), this); + connect(_signInButton, &QPushButton::clicked, this, &RequestAuthenticationWidget::connectClicked); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(50, 50, 50, 50); + layout->setSpacing(12); + layout->addStretch(1); + layout->addWidget(logoLabel, Qt::AlignCenter); + layout->addSpacing(16); + layout->addWidget(titleLabel); + layout->addWidget(instructionLabel, Qt::AlignCenter); + + QHBoxLayout *urlAreaLayout = new QHBoxLayout(); + urlAreaLayout->setContentsMargins(0, 0, 0, 0); + urlAreaLayout->setSpacing(0); + urlAreaLayout->addWidget(_urlField, Qt::AlignLeft); + urlAreaLayout->addWidget(_copyButton); + layout->addLayout(urlAreaLayout, Qt::AlignCenter); + layout->addWidget(_errorField, Qt::AlignLeft); + + if (footerLogoLabel) + layout->addWidget(footerLogoLabel, Qt::AlignCenter); + layout->addStretch(1); + + QHBoxLayout *buttonLayout = new QHBoxLayout(); + buttonLayout->setContentsMargins(0, 0, 0, 0); + buttonLayout->addStretch(1); + buttonLayout->addWidget(_cancelButton); + buttonLayout->addWidget(_signInButton); + layout->addLayout(buttonLayout, Qt::AlignRight); + + setLayout(layout); + + // disable buttons until the url has been set + _cancelButton->setEnabled(false); + _signInButton->setEnabled(false); +} + +bool RequestAuthenticationWidget::eventFilter(QObject *target, QEvent *event) +{ + if (target == _urlField && event->type() == QEvent::Resize) { + QResizeEvent *e = dynamic_cast(event); + if (e && e->size().width() != e->oldSize().width()) { + QString text = elidedUrl(e->size().width()); + _urlField->setText(text); + } + return false; // -> don't hijack the event as the target still needs to process it + } + return QObject::eventFilter(target, event); +} + +QString RequestAuthenticationWidget::elidedUrl(int targetWidth) +{ + if (_authUrl.isEmpty()) + return QString(); + + QFontMetrics metrics(_urlField->font()); + QString elidedText = metrics.elidedText(_authUrl, Qt::ElideRight, targetWidth); + return elidedText; +} + +void RequestAuthenticationWidget::setAuthUrl(const QString &url) +{ + if (url.isEmpty()) { + _cancelButton->setEnabled(true); + return; + } + if (_authUrl != url) { + _authUrl = url; + QString elidedText = elidedUrl(_urlField->width()); + _urlField->setText(elidedText); + // update the copy button tooltip + onClipboardChanged(); + + // and enable the buttons: + _cancelButton->setEnabled(true); + _signInButton->setEnabled(true); + } +} + +void RequestAuthenticationWidget::onClipboardChanged() +{ + QClipboard *clipboard = QGuiApplication::clipboard(); + if (clipboard->text() == _authUrl) + _copyButton->setToolTip(tr("URL copied")); + else + _copyButton->setToolTip(tr("Copy URL")); +} + +void RequestAuthenticationWidget::onCopyUrl() +{ + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(_authUrl); +} + +void RequestAuthenticationWidget::updateColors() +{ + QPalette palette = this->palette(); + QColor themeBackground = Theme::instance()->wizardHeaderBackgroundColor(); + QColor themeForeground = Theme::instance()->wizardHeaderTitleColor(); + if (themeBackground.isValid()) { + palette.setColor(QPalette::Window, themeBackground); + palette.setColor(QPalette::Base, themeBackground); + } + if (themeForeground.isValid()) { + palette.setColor(QPalette::WindowText, themeForeground); + palette.setColor(QPalette::Text, themeForeground); + } + + const QString iconPath = QStringLiteral(":/client/resources/core/copy.svg"); + const QString color = palette.color(QPalette::Text).name(); + QByteArray iconData = Resources::Template::renderTemplateFromFile(iconPath, {{QStringLiteral("color"), color}}).toUtf8(); + QBuffer buffer(&iconData); + QImageReader iconReader(&buffer, "svg"); + _copyIcon = QIcon(QPixmap::fromImageReader(&iconReader)); + + setPalette(palette); + setAutoFillBackground(true); +} + +void RequestAuthenticationWidget::setErrorMessage(const QString &error) +{ + _errorField->setText(error); +} + +} diff --git a/src/gui/creds/requestauthenticationwidget.h b/src/gui/creds/requestauthenticationwidget.h new file mode 100644 index 00000000000..c015e7ac017 --- /dev/null +++ b/src/gui/creds/requestauthenticationwidget.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 +#include + +class QLabel; +class QPushButton; + +namespace OCC { + + +class RequestAuthenticationWidget : public QWidget +{ + Q_OBJECT +public: + explicit RequestAuthenticationWidget(QWidget *parent = nullptr); + + void setAuthUrl(const QString &url); + void setErrorMessage(const QString &error); + +protected: + bool eventFilter(QObject *target, QEvent *event) override; + +signals: + void connectClicked(); + void stayLoggedOutClicked(); + +private: + void onClipboardChanged(); + void onCopyUrl(); + QString elidedUrl(int targetWidth); + + void updateColors(); + + QString _authUrl; + QLabel *_urlField = nullptr; + QLabel *_errorField = nullptr; + QPushButton *_copyButton = nullptr; + QIcon _copyIcon; + QPushButton *_cancelButton = nullptr; + QPushButton *_signInButton = nullptr; +}; +} diff --git a/src/gui/fetchserversettings.cpp b/src/gui/fetchserversettings.cpp new file mode 100644 index 00000000000..d1f46746317 --- /dev/null +++ b/src/gui/fetchserversettings.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "fetchserversettings.h" + +#include "gui/accountstate.h" +#include "gui/connectionvalidator.h" + +#include "libsync/networkjobs/jsonjob.h" + +using namespace std::chrono_literals; + +using namespace OCC; + + +Q_LOGGING_CATEGORY(lcfetchserversettings, "sync.fetchserversettings", QtInfoMsg) + +namespace { +auto fetchSettingsTimeout() +{ + return std::min(20s, AbstractNetworkJob::httpTimeout); +} +} + +// TODO: move to libsync? +FetchServerSettingsJob::FetchServerSettingsJob(OCC::Account *account, QObject *parent) + : QObject(parent) + , _account(account) +{ +} + + +void FetchServerSettingsJob::start() +{ + if (!_account) { + Q_EMIT finishedSignal(Result::Undefined); + return; + } + + // The main flow now needs the capabilities + auto *job = new JsonApiJob(_account, QStringLiteral("ocs/v2.php/cloud/capabilities"), {}, {}, this); + job->setAuthenticationJob(isAuthJob()); + job->setTimeout(fetchSettingsTimeout()); + + connect(job, &JsonApiJob::finishedSignal, this, [job, this] { + auto caps = + job->data().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toObject().value(QStringLiteral("capabilities")).toObject(); + qCInfo(lcfetchserversettings) << "Server capabilities" << caps; + if (job->ocsSuccess()) { + // todo: #23 - this comment is important relative to that review + // Record that the server supports HTTP/2 + // Actual decision if we should use HTTP/2 is done in AccessManager::createRequest + // TODO: http2 support is currently disabled in the client code + if (auto reply = job->reply()) { + _account->setHttp2Supported(reply->attribute(QNetworkRequest::Http2WasUsedAttribute).toBool()); + } + _account->setCapabilities({_account->url(), caps.toVariantMap()}); + // We cannot deal with servers < 10.0.0 + switch (_account->serverSupportLevel()) { + case Account::ServerSupportLevel::Unknown: + [[fallthrough]]; + case Account::ServerSupportLevel::Supported: + break; + case Account::ServerSupportLevel::Unsupported: + Q_EMIT finishedSignal(Result::UnsupportedServer); + return; + } + auto *userJob = new JsonApiJob(_account.get(), QStringLiteral("ocs/v2.php/cloud/user"), SimpleNetworkJob::UrlQuery{}, QNetworkRequest{}, this); + userJob->setAuthenticationJob(isAuthJob()); + userJob->setTimeout(fetchSettingsTimeout()); + connect(userJob, &JsonApiJob::finishedSignal, this, [userJob, this] { + if (userJob->timedOut()) { + Q_EMIT finishedSignal(Result::TimeOut); + } else if (userJob->httpStatusCode() == 401) { + Q_EMIT finishedSignal(Result::InvalidCredentials); + } else if (userJob->ocsSuccess()) { + const auto userData = userJob->data().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toObject(); + const QString user = userData.value(QStringLiteral("id")).toString(); + Q_ASSERT(!_account->davUser().isEmpty()); + // davUser should never change. + if (!user.isEmpty() && !_account->davUser().isEmpty() && user != _account->davUser()) { + Q_EMIT finishedSignal(Result::InvalidCredentials); + return; + } + const QString displayName = userData.value(QStringLiteral("display-name")).toString(); + if (!displayName.isEmpty()) { + _account->setDavDisplayName(displayName); + } + runAsyncUpdates(); + Q_EMIT finishedSignal(Result::Success); + } else { + Q_EMIT finishedSignal(Result::Undefined); + } + }); + userJob->start(); + } else { + if (job->timedOut()) { + Q_EMIT finishedSignal(Result::TimeOut); + } else if (job->httpStatusCode() == 401) { + Q_EMIT finishedSignal(Result::InvalidCredentials); + } else { + Q_EMIT finishedSignal(Result::Undefined); + } + } + }); + job->start(); +} + +void FetchServerSettingsJob::runAsyncUpdates() +{ + if (!_account) + return; + + // those jobs are: + // - never auth jobs + // - might get queued + // - have the default timeout + // - must not be parented by this object + + // ideally we would parent them to the account, but as things are messed up by the shared pointer stuff we can't at the moment + // so we just set them free + if (_account->capabilities().avatarsAvailable()) { + auto *avatarJob = new AvatarJob(_account, _account->davUser(), 128, nullptr); + connect(avatarJob, &AvatarJob::avatarPixmap, this, [this](const QPixmap &img) { _account->setAvatar(AvatarJob::makeCircularAvatar(img)); }); + avatarJob->start(); + }; + + if (_account->capabilities().appProviders().enabled) { + auto *jsonJob = new JsonJob(_account, _account->capabilities().appProviders().appsUrl, {}, "GET"); + connect(jsonJob, &JsonJob::finishedSignal, this, [jsonJob, this] { _account->setAppProvider(AppProvider{jsonJob->data()}); }); + jsonJob->start(); + } +} + +bool FetchServerSettingsJob::isAuthJob() const +{ + return qobject_cast(parent()); +} diff --git a/src/gui/fetchserversettings.h b/src/gui/fetchserversettings.h new file mode 100644 index 00000000000..9ea77521d26 --- /dev/null +++ b/src/gui/fetchserversettings.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "account.h" +#include +#include + +namespace OCC { +class Capabilities; + +class FetchServerSettingsJob : public QObject +{ + Q_OBJECT +public: + enum class Result { Success, TimeOut, InvalidCredentials, UnsupportedServer, Undefined }; + Q_ENUM(Result); + FetchServerSettingsJob(Account *account, QObject *parent); + + void start(); + +Q_SIGNALS: + void finishedSignal(Result); + +private: + void runAsyncUpdates(); + + // returns whether the started jobs should be excluded from the retry queue + bool isAuthJob() const; + + QPointer _account; +}; + +} diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 186d0d3c871..d14c0e3cb3f 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -13,300 +13,260 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ -#include "config.h" + +#include "folder.h" #include "account.h" #include "accountmanager.h" #include "accountstate.h" #include "application.h" -#include "clientproxy.h" +#include "common/checksums.h" +#include "common/filesystembase.h" #include "common/syncjournalfilerecord.h" #include "common/version.h" #include "common/vfs.h" #include "configfile.h" -#include "creds/abstractcredentials.h" -#include "csync_exclude.h" #include "filesystem.h" -#include "folder.h" #include "folderman.h" +#include "folderwatcher.h" +#include "libsync/graphapi/spacesmanager.h" #include "localdiscoverytracker.h" -#include "logger.h" -#include "networkjobs.h" -#include "settingsdialog.h" +#include "scheduling/syncscheduler.h" #include "socketapi/socketapi.h" #include "syncengine.h" #include "syncresult.h" #include "syncrunfilelog.h" #include "theme.h" +#ifdef Q_OS_WIN +#include "common/utility_win.h" +#endif + #include #include #include #include #include -#include -#include using namespace std::chrono_literals; namespace { - -/* - * [Accounts] - * 1\Folders\4\version=2 - * 1\FoldersWithPlaceholders\3\version=3 +/* How often to retry a sync + * Either due to _engine->isAnotherSyncNeeded or a sync error */ -auto versionC() +constexpr int retrySyncLimitC = 3; + +auto davUrlC() +{ + return QStringLiteral("davUrl"); +} + +auto spaceIdC() +{ + return QStringLiteral("spaceId"); +} + +auto displayNameC() +{ + return QLatin1String("displayString"); +} + +// todo #52 - eliminate this config value and scrub configs next major release +[[deprecated("deployed concept is no longer supported and will be removed in client 8.0")]] auto deployedC() { - return QStringLiteral("version"); + return QStringLiteral("deployed"); } -constexpr int WinVfsSettingsVersion = 4; -constexpr int SettingsVersion = 2; +auto priorityC() +{ + return QStringLiteral("priority"); +} } namespace OCC { +using namespace FileSystem::SizeLiterals; + Q_LOGGING_CATEGORY(lcFolder, "gui.folder", QtInfoMsg) -Folder::Folder(const FolderDefinition &definition, - AccountState *accountState, std::unique_ptr vfs, - QObject *parent) +Folder::Folder(const FolderDefinition &definition, AccountState *accountState, SyncJournalDb *journal, Vfs *vfs, SyncEngine *engine, QObject *parent) : QObject(parent) , _accountState(accountState) , _definition(definition) - , _lastSyncDuration(0) - , _consecutiveFailingSyncs(0) - , _consecutiveFollowUpSyncs(0) - , _journal(_definition.absoluteJournalPath()) , _fileLog(new SyncRunFileLog) - , _vfs(vfs.release()) { + // reparent the injected dependencies and set them as members + journal->setParent(this); + _journal = journal; + + vfs->setParent(this); + _vfs = vfs; + + engine->setParent(this); + _engine = engine; + + // the FolderBuilder should fail under all of the following conditions, so none of thes asserts should ever trigger + Q_ASSERT(_accountState && _accountState->account()); + Q_ASSERT(_vfs); + Q_ASSERT(_journal); + Q_ASSERT(_engine); + _timeSinceLastSyncStart.start(); _timeSinceLastSyncDone.start(); - _timeSinceLastEtagCheckDone.start(); SyncResult::Status status = SyncResult::NotYetStarted; - if (definition.paused) { + if (definition.paused()) { status = SyncResult::Paused; } - _syncResult.setStatus(status); - - // check if the local path exists - const auto folderOk = checkLocalPath(); + setSyncState(status); - _syncResult.setFolder(_definition.alias); + connect(_accountState, &AccountState::isConnectedChanged, this, &Folder::canSyncChanged); - _engine.reset(new SyncEngine(_accountState->account(), path(), remotePath(), &_journal)); - // pass the setting if hidden files are to be ignored, will be read in csync_update - _engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles); + // connect engine to folderman: + connect(_engine, &SyncEngine::seenLockedFile, FolderMan::instance(), &FolderMan::slotSyncOnceFileUnlocks); - ConfigFile::setupDefaultExcludeFilePaths(_engine->excludedFiles()); - if (!reloadExcludes()) - qCWarning(lcFolder, "Could not read system exclude file"); + // connect progress dispatcher to this + connect(ProgressDispatcher::instance(), &ProgressDispatcher::folderConflicts, this, &Folder::slotFolderConflicts); - connect(_accountState.data(), &AccountState::isConnectedChanged, this, &Folder::canSyncChanged); - connect(_engine.data(), &SyncEngine::rootEtag, this, &Folder::etagRetrievedFromSyncEngine); + // connect engine to this: + connect(_engine, &SyncEngine::started, this, &Folder::slotSyncStarted, Qt::QueuedConnection); + connect(_engine, &SyncEngine::finished, this, &Folder::slotSyncFinished, Qt::QueuedConnection); - connect(_engine.data(), &SyncEngine::started, this, &Folder::slotSyncStarted, Qt::QueuedConnection); - connect(_engine.data(), &SyncEngine::finished, this, &Folder::slotSyncFinished, Qt::QueuedConnection); + connect( + _engine, &SyncEngine::transmissionProgress, this, [this](const ProgressInfo &pi) { Q_EMIT ProgressDispatcher::instance()->progressInfo(this, pi); }); - connect(_engine.data(), &SyncEngine::aboutToRemoveAllFiles, - this, &Folder::slotAboutToRemoveAllFiles); - connect(_engine.data(), &SyncEngine::transmissionProgress, this, &Folder::slotTransmissionProgress); - connect(_engine.data(), &SyncEngine::itemCompleted, - this, &Folder::slotItemCompleted); - connect(_engine.data(), &SyncEngine::newBigFolder, - this, &Folder::slotNewBigFolderDiscovered); - connect(_engine.data(), &SyncEngine::seenLockedFile, FolderMan::instance(), &FolderMan::slotSyncOnceFileUnlocks); - connect(_engine.data(), &SyncEngine::aboutToPropagate, - this, &Folder::slotLogPropagationStart); - connect(_engine.data(), &SyncEngine::syncError, this, &Folder::slotSyncError); + connect(_engine, &SyncEngine::transmissionProgress, this, &Folder::progressUpdate); + connect(_engine, &SyncEngine::itemCompleted, this, &Folder::slotItemCompleted); + connect(_engine, &SyncEngine::aboutToPropagate, this, &Folder::slotLogPropagationStart); + connect(_engine, &SyncEngine::syncError, this, &Folder::slotSyncError); + connect(_engine, &SyncEngine::excluded, this, [this](const QString &path) { Q_EMIT ProgressDispatcher::instance()->excluded(this, path); }); - _scheduleSelfTimer.setSingleShot(true); - _scheduleSelfTimer.setInterval(SyncEngine::minimumFileAgeForUpload); - connect(&_scheduleSelfTimer, &QTimer::timeout, - this, &Folder::slotScheduleThisFolder); + // setup local discovery tracker + _localDiscoveryTracker = new LocalDiscoveryTracker(this); + connect(_engine, &SyncEngine::finished, _localDiscoveryTracker, &LocalDiscoveryTracker::slotSyncFinished); + connect(_engine, &SyncEngine::itemCompleted, _localDiscoveryTracker, &LocalDiscoveryTracker::slotItemCompleted); - connect(ProgressDispatcher::instance(), &ProgressDispatcher::folderConflicts, - this, &Folder::slotFolderConflicts); - connect(_engine.data(), &SyncEngine::excluded, this, [this](const QString &path, CSYNC_EXCLUDE_TYPE reason) { - Q_EMIT ProgressDispatcher::instance()->excluded(this, path, reason); + // this needs investigation as it looks sus. I would expect to get a signal like this from the space directly + connect(_accountState->account()->spacesManager(), &GraphApi::SpacesManager::spaceChanged, this, [this](GraphApi::Space *changedSpace) { + if (_definition.spaceId() == changedSpace->id()) { + emit spaceChanged(); + } }); - _localDiscoveryTracker.reset(new LocalDiscoveryTracker); - connect(_engine.data(), &SyncEngine::finished, - _localDiscoveryTracker.data(), &LocalDiscoveryTracker::slotSyncFinished); - connect(_engine.data(), &SyncEngine::itemCompleted, - _localDiscoveryTracker.data(), &LocalDiscoveryTracker::slotItemCompleted); - - // Potentially upgrade suffix vfs to windows vfs - OC_ENFORCE(_vfs); - if (_definition.virtualFilesMode == Vfs::WithSuffix - && _definition.upgradeVfsMode) { - if (isVfsPluginAvailable(Vfs::WindowsCfApi)) { - if (auto winvfs = createVfsFromPlugin(Vfs::WindowsCfApi)) { - // Wipe the existing suffix files from fs and journal - SyncEngine::wipeVirtualFiles(path(), _journal, *_vfs); - - // Then switch to winvfs mode - _vfs.reset(winvfs.release()); - _definition.virtualFilesMode = Vfs::WindowsCfApi; - } - } - saveToSettings(); - } + if (space()) + emit spaceChanged(); - if (folderOk) { - // Initialize the vfs plugin - startVfs(); - } + // Initialize the vfs plugin. Do this after the UI is running, so we can show a dialog when something goes wrong. + QTimer::singleShot(0, this, &Folder::startVfs); } Folder::~Folder() { - // If wipeForRemoval() was called the vfs has already shut down. + // If wipeForRemoval() was called vfs has already shut down, and the journal has been closed. + // any sync the engine was running should have been paused/aborted in folderman as prep to remove the folder. + // this feels very sketchy to me and I'm not really sure we should outsource all of that responsibility! + // I think the main question is whether the child cleanup routines can finish in time before the pointers are deleted. + // this is especially true for sync engine (if it's running on dtr) but any of them could get humg up, in theory + // discuss with cohorts. if (_vfs) _vfs->stop(); - - // Reset then engine first as it will abort and try to access members of the Folder - _engine.reset(); + if (_engine) + Q_ASSERT(!_engine->isSyncRunning()); + if (_journal) + _journal->close(); } -bool Folder::checkLocalPath() +GraphApi::Space *Folder::space() const { -#ifdef Q_OS_WIN - Utility::NtfsPermissionLookupRAII ntfs_perm; -#endif - const QFileInfo fi(_definition.localPath); - _canonicalLocalPath = fi.canonicalFilePath(); -#ifdef Q_OS_MAC - // Workaround QTBUG-55896 (Should be fixed in Qt 5.8) - _canonicalLocalPath = _canonicalLocalPath.normalized(QString::NormalizationForm_C); -#endif - if (_canonicalLocalPath.isEmpty()) { - qCWarning(lcFolder) << "Broken symlink:" << _definition.localPath; - _canonicalLocalPath = _definition.localPath; - } else if (!_canonicalLocalPath.endsWith('/')) { - _canonicalLocalPath.append('/'); + if (_accountState && _accountState->account() && _accountState->account()->spacesManager()) { + return _accountState->account()->spacesManager()->space(_definition.spaceId()); } - - if (fi.isDir() && fi.isReadable() && fi.isWritable()) { - qCDebug(lcFolder) << "Checked local path ok"; - } else { - QString error; - // Check directory again - if (!FileSystem::fileExists(_definition.localPath, fi)) { - error = tr("Local folder %1 does not exist.").arg(_definition.localPath); - } else if (!fi.isDir()) { - error = tr("%1 should be a folder but is not.").arg(_definition.localPath); - } else if (!fi.isReadable()) { - error = tr("%1 is not readable.").arg(_definition.localPath); - } else if (!fi.isWritable()) { - error = tr("%1 is not writable.").arg(_definition.localPath); - } - if (!error.isEmpty()) { - _syncResult.appendErrorString(error); - _syncResult.setStatus(SyncResult::SetupError); - return false; - } - } - return true; + return nullptr; } -QString Folder::shortGuiRemotePathOrAppName() const +SyncOptions Folder::loadSyncOptions() { - if (remotePath().length() > 0 && remotePath() != QLatin1String("/")) { - QString a = QFile(remotePath()).fileName(); - if (a.startsWith('/')) { - a = a.remove(0, 1); - } - return a; - } else { - return Theme::instance()->appNameGUI(); - } -} + if (!_accountState || !_accountState->account() || !_vfs) + return SyncOptions(); -QString Folder::alias() const -{ - return _definition.alias; + SyncOptions opt(_vfs); + opt._parallelNetworkJobs = _accountState->account()->isHttp2Supported() ? 20 : 6; + + // apparently the env can override the default + int maxParallel = qEnvironmentVariableIntValue("OWNCLOUD_MAX_PARALLEL"); + if (maxParallel > 0) + opt._parallelNetworkJobs = maxParallel; + + return opt; } -QString Folder::path() const +QString Folder::displayName() const { - return _canonicalLocalPath; + if (auto *s = space()) { + return s->displayName(); + } + return _definition.displayName(); } + QString Folder::shortGuiLocalPath() const { - QString p = _definition.localPath; + QString p = _definition.localPath(); QString home = QDir::homePath(); - if (!home.endsWith('/')) { - home.append('/'); + if (!home.endsWith(QLatin1Char('/'))) { + home.append(QLatin1Char('/')); } if (p.startsWith(home)) { p = p.mid(home.length()); } - if (p.length() > 1 && p.endsWith('/')) { + if (p.length() > 1 && p.endsWith(QLatin1Char('/'))) { p.chop(1); } return QDir::toNativeSeparators(p); } -bool Folder::ignoreHiddenFiles() -{ - bool re(_definition.ignoreHiddenFiles); - return re; -} - -void Folder::setIgnoreHiddenFiles(bool ignore) -{ - _definition.ignoreHiddenFiles = ignore; -} - QString Folder::cleanPath() const { - QString cleanedPath = QDir::cleanPath(_canonicalLocalPath); + QString cleanedPath = QDir::cleanPath(_definition.canonicalPath()); - if (cleanedPath.length() == 3 && cleanedPath.endsWith(":/")) + if (cleanedPath.length() == 3 && cleanedPath.endsWith(QLatin1String(":/"))) cleanedPath.remove(2, 1); return cleanedPath; } -bool Folder::isSyncRunning() const -{ - return _engine->isSyncRunning() || _vfs->isHydrating(); -} - -QString Folder::remotePath() const +QUrl Folder::webDavUrl() const { - return _definition.targetPath; + // this doesn't make sense to me - the definition should always carry the correct webdav url so I'm + // changing this to a reality check against the space instead of relying on the space value first, if it exists, + GraphApi::Space *sp = space(); + if (sp) { + QUrl spUrl = sp->webDavUrl(); + Q_ASSERT(spUrl == _definition.webDavUrl()); + } + return _definition.webDavUrl(); } QString Folder::remotePathTrailingSlash() const { - QString result = remotePath(); - if (!result.endsWith('/')) - result.append('/'); - return result; + const QString remote = remotePath(); + if (!remote.endsWith((QLatin1Char('/')))) + return remote + QLatin1Char('/'); + return remote; } -QUrl Folder::remoteUrl() const -{ - return Utility::concatUrlPath(_accountState->account()->davUrl(), remotePath()); -} - -bool Folder::syncPaused() const +bool Folder::isSyncRunning() const { - return _definition.paused; + return !hasSetupError() && _engine->isSyncRunning(); } bool Folder::canSync() const { - return !syncPaused() && accountState()->isConnected() && isReady(); + if (!_engine || !_accountState || !_accountState->account() || !_folderWatcher) + return false; + return isAvailable() && !syncPaused() && _accountState->readyForSync() && isReady() && _accountState->account()->hasCapabilities(); } bool Folder::isReady() const @@ -314,108 +274,55 @@ bool Folder::isReady() const return _vfsIsReady; } -bool Folder::dueToSync() const -{ - // conditions taken from previous folderman implementation - if (isSyncRunning() || etagJob() || !canSync()) { - return false; - } - - ConfigFile cfg; - // the default poll time of 30 seconds as it had been in the client forever. - // Now with https://github.com/owncloud/client/pull/8777 also the server capabilities are considered. - const auto pta = accountState()->account()->capabilities().remotePollInterval(); - const auto polltime = cfg.remotePollInterval(pta); - - const auto timeSinceLastSync = std::chrono::milliseconds(_timeSinceLastEtagCheckDone.elapsed()); - if (timeSinceLastSync >= polltime) { - return true; - } - return false; -} - void Folder::setSyncPaused(bool paused) { - if (paused == _definition.paused) { + if (hasSetupError()) { + return; + } + if (paused == _definition.paused()) { return; } - _definition.paused = paused; - saveToSettings(); + _definition.setPaused(paused); + Q_EMIT syncPausedChanged(this, paused); if (!paused) { setSyncState(SyncResult::NotYetStarted); } else { setSyncState(SyncResult::Paused); } - emit syncPausedChanged(this, paused); - emit syncStateChange(); - emit canSyncChanged(); -} - -void Folder::setSyncState(SyncResult::Status state) -{ - _syncResult.setStatus(state); -} - -SyncResult Folder::syncResult() const -{ - return _syncResult; -} - -void Folder::prepareToSync() -{ - _syncResult.reset(); - _syncResult.setStatus(SyncResult::NotYetStarted); + Q_EMIT canSyncChanged(); } -void Folder::slotRunEtagJob() +void Folder::setAvailable(bool available) { - qCInfo(lcFolder) << "Trying to check" << remoteUrl().toString() << "for changes via ETag check. (time since last sync:" << (_timeSinceLastSyncDone.elapsed() / 1000) << "s)"; - - AccountPtr account = _accountState->account(); - - if (_requestEtagJob) { - qCInfo(lcFolder) << remoteUrl().toString() << "has ETag job queued, not trying to sync"; + if (available != (space() != nullptr)) return; - } - if (!canSync()) { - qCInfo(lcFolder) << "Not syncing. :" << remoteUrl().toString() << _definition.paused << AccountState::stateString(_accountState->state()); - return; + _available = available; + _syncResult.reset(); + if (!_available) { + _syncResult.setStatus(SyncResult::Status::Unavailable); + _syncResult.appendErrorString(tr("The folder has been disabled or removed from the server")); + } else { + _syncResult.setStatus(SyncResult::Status::NotYetStarted); } - // Do the ordinary etag check for the root folder and schedule a - // sync if it's different. - - _requestEtagJob = new RequestEtagJob(account, remotePath(), this); - _requestEtagJob->setTimeout(60s); - // check if the etag is different when retrieved - QObject::connect(_requestEtagJob.data(), &RequestEtagJob::etagRetreived, this, &Folder::etagRetreived); - QObject::connect(_requestEtagJob.data(), &RequestEtagJob::finishedWithResult, this, [=](const HttpResult) { _timeSinceLastEtagCheckDone.start(); }); - FolderMan::instance()->slotScheduleETagJob(alias(), _requestEtagJob); - // The _requestEtagJob is auto deleting itself on finish. Our guard pointer _requestEtagJob will then be null. + emit syncStateChange(); + emit canSyncChanged(); } -void Folder::etagRetreived(const QByteArray &etag, const QDateTime &tp) +bool Folder::isConnected() { - // re-enable sync if it was disabled because network was down - FolderMan::instance()->setSyncEnabled(true); - - if (_lastEtag != etag) { - qCInfo(lcFolder) << "Compare etag with previous etag: last:" << _lastEtag << ", received:" << etag << "-> CHANGED"; - _lastEtag = etag; - slotScheduleThisFolder(); - } - - _accountState->tagLastSuccessfullETagRequest(tp); + return (_accountState && _accountState->isConnected()); } -void Folder::etagRetrievedFromSyncEngine(const QByteArray &etag, const QDateTime &time) +void Folder::setSyncState(SyncResult::Status state) { - qCInfo(lcFolder) << "Root etag from during sync:" << etag; - accountState()->tagLastSuccessfullETagRequest(time); - _lastEtag = etag; + if (state != _syncResult.status()) { + _syncResult.setStatus(state); + Q_EMIT syncStateChange(); + } } @@ -450,7 +357,7 @@ void Folder::showSyncResultPopup() createGuiLog(_syncResult.firstItemError()->_file, LogStatusError, errorCount); } - qCInfo(lcFolder) << "Folder" << _syncResult.folder() << "sync result: " << _syncResult.status(); + qCInfo(lcFolder) << "Folder" << path() << "sync result: " << _syncResult.status(); } void Folder::createGuiLog(const QString &filename, LogStatus status, int count, @@ -498,7 +405,7 @@ void Folder::createGuiLog(const QString &filename, LogStatus status, int count, break; case LogStatusConflict: if (count > 1) { - text = tr("%1 has and %n other file(s) have sync conflicts.", "", count - 1).arg(file); + text = tr("%1 and %n other file(s) have sync conflicts.", "", count - 1).arg(file); } else { text = tr("%1 has a sync conflict. Please check the conflict file!").arg(file); } @@ -513,149 +420,163 @@ void Folder::createGuiLog(const QString &filename, LogStatus status, int count, } if (!text.isEmpty()) { - ocApp()->gui()->slotShowOptionalTrayMessage(tr("Sync Activity"), text); + ocApp()->tray()->slotShowOptionalTrayMessage(tr("Sync Activity"), text); } } } void Folder::startVfs() { - OC_ENFORCE(_vfs); - OC_ENFORCE(_vfs->mode() == _definition.virtualFilesMode); + if (!_accountState || !_accountState->account()) + return; - const auto result = Vfs::checkAvailability(path(), _vfs->mode()); - if (!result) { - _syncResult.appendErrorString(result.error()); - _syncResult.setStatus(SyncResult::SetupError); + OC_ENFORCE(_vfs); + OC_ENFORCE(_vfs->mode() == _definition.virtualFilesMode()); + + // todo: DC-219 check to see if this sync error makes it to the gui or not. We need to more clearly inform the user that they should + // create a new sync connection to the remote folder, and use improved checks in the folder wizard to make sure vfs is supported + // on the chosen path. + // ideally we want a check like this before we even get here, eg in the area of load folder from config - but need to be sure there is + // a place to clearly instruct the user that the folder sync needs to be re-built with a valid local path + QString result = Vfs::pathSupportDetail(path(), _vfs->mode()); + if (!result.isEmpty()) { + _syncResult.appendErrorString(result); + setSyncState(SyncResult::SetupError); return; } - VfsSetupParams vfsParams; + VfsSetupParams vfsParams(_accountState->account(), webDavUrl(), _engine); vfsParams.filesystemPath = path(); vfsParams.remotePath = remotePathTrailingSlash(); - vfsParams.account = _accountState->account(); - vfsParams.journal = &_journal; + vfsParams.journal = _journal; vfsParams.providerDisplayName = Theme::instance()->appNameGUI(); vfsParams.providerName = Theme::instance()->appName(); vfsParams.providerVersion = Version::version(); vfsParams.multipleAccountsRegistered = AccountManager::instance()->accounts().size() > 1; - connect(_vfs.data(), &Vfs::beginHydrating, this, &Folder::slotHydrationStarts); - connect(_vfs.data(), &Vfs::doneHydrating, this, &Folder::slotHydrationDone); + connect(_engine, &SyncEngine::fileStatusChanged, _vfs, &Vfs::onFileStatusChanged); - connect(&_engine->syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged, - _vfs.data(), &Vfs::fileStatusChanged); - - - connect(_vfs.data(), &Vfs::started, this, [this] { + connect(_vfs, &Vfs::started, this, [this] { // Immediately mark the sqlite temporaries as excluded. They get recreated // on db-open and need to get marked again every time. - QString stateDbFile = _journal.databaseFilePath(); - _journal.open(); - _vfs->fileStatusChanged(stateDbFile + QStringLiteral("-wal"), SyncFileStatus::StatusExcluded); - _vfs->fileStatusChanged(stateDbFile + QStringLiteral("-shm"), SyncFileStatus::StatusExcluded); + QString stateDbFile = _journal->databaseFilePath(); + _vfs->onFileStatusChanged(stateDbFile + QStringLiteral("-wal"), SyncFileStatus::StatusExcluded); + _vfs->onFileStatusChanged(stateDbFile + QStringLiteral("-shm"), SyncFileStatus::StatusExcluded); + _engine->setSyncOptions(loadSyncOptions()); + registerFolderWatcher(); + + connect(_vfs, &Vfs::needSync, this, [this] { + if (canSync()) { + // the vfs plugin detected that its metadata is out of sync and requests a new sync + // the request has a high priority as it is probably issued after a user request + FolderMan::instance()->scheduler()->enqueueFolder(this, SyncScheduler::Priority::High); + } + }); _vfsIsReady = true; + // We are set up, schedule ourselves if we can. + // If not the scheduler will take care of it later. + if (canSync()) { + // Refactoring todo: also no. We need a general cleanup task to eval and fix all of these crossed triggers + // the only element that should emit FolderMan signals is FolderMan! same for any/all other classes + // I don't care if signals are now public: it is not correct for anyone but the owner to trigger them + FolderMan::instance()->scheduler()->enqueueFolder(this); + } }); - connect(_vfs.data(), &Vfs::error, this, [this](const QString &error) { + connect(_vfs, &Vfs::error, this, [this](const QString &error) { _syncResult.appendErrorString(error); - _syncResult.setStatus(SyncResult::SetupError); + setSyncState(SyncResult::SetupError); _vfsIsReady = false; }); + slotNextSyncFullLocalDiscovery(); _vfs->start(vfsParams); } -int Folder::slotDiscardDownloadProgress() +void Folder::slotDiscardDownloadProgress() { // Delete from journal and from filesystem. - QDir folderpath(_definition.localPath); + QDir folderpath(_definition.localPath()); QSet keep_nothing; - const QVector deleted_infos = - _journal.getAndDeleteStaleDownloadInfos(keep_nothing); + const QVector deleted_infos = _journal->getAndDeleteStaleDownloadInfos(keep_nothing); for (const auto &deleted_info : deleted_infos) { const QString tmppath = folderpath.filePath(deleted_info._tmpfile); qCInfo(lcFolder) << "Deleting temporary file: " << tmppath; FileSystem::remove(tmppath); } - return deleted_infos.size(); -} - -int Folder::downloadInfoCount() -{ - return _journal.downloadInfoCount(); -} - -int Folder::errorBlackListEntryCount() -{ - return _journal.errorBlackListEntryCount(); } int Folder::slotWipeErrorBlacklist() { - return _journal.wipeErrorBlacklist(); + return _journal->wipeErrorBlacklist(); } -void Folder::slotWatchedPathChanged(const QString &path, ChangeReason reason) +void Folder::slotWatchedPathsChanged(const QSet &paths, ChangeReason reason) { - if (!FileSystem::isChildPathOf(path, this->path())) { - qCDebug(lcFolder) << "Changed path is not contained in folder, ignoring:" << path; - return; - } - - auto relativePath = path.midRef(this->path().size()); - - // Add to list of locally modified paths - // - // We do this before checking for our own sync-related changes to make - // extra sure to not miss relevant changes. - auto relativePathBytes = relativePath.toUtf8(); - _localDiscoveryTracker->addTouchedPath(relativePathBytes); - -// The folder watcher fires a lot of bogus notifications during -// a sync operation, both for actual user files and the database -// and log. Therefore we check notifications against operations -// the sync is doing to filter out our own changes. -#ifdef Q_OS_MAC -// On OSX the folder watcher does not report changes done by our -// own process. Therefore nothing needs to be done here! -#else - // Use the path to figure out whether it was our own change - if (_engine->wasFileTouched(path)) { - qCDebug(lcFolder) << "Changed path was touched by SyncEngine, ignoring:" << path; + if (!isReady()) { + // we might be switching backend return; } -#endif - - - SyncJournalFileRecord record; - _journal.getFileRecord(relativePathBytes, &record); - if (reason != ChangeReason::UnLock) { - // Check that the mtime/size actually changed or there was - // an attribute change (pin state) that caused the notification - bool spurious = false; - if (record.isValid() - && !FileSystem::fileChanged(path, record._fileSize, record._modtime)) { - spurious = true; - - if (auto pinState = _vfs->pinState(relativePath.toString())) { - if (*pinState == PinState::AlwaysLocal && record.isVirtualFile()) - spurious = false; - if (*pinState == PinState::OnlineOnly && record.isFile()) - spurious = false; + bool needSync = false; + for (const auto &path : paths) { + Q_ASSERT(FileSystem::isChildPathOf(path, this->path())); + if (!FileSystem::isChildPathOf(path, this->path())) + continue; + const QString relativePath = path.mid(this->path().size()); + if (reason == ChangeReason::UnLock) { + journalDb()->wipeErrorBlacklistEntry(relativePath, SyncJournalErrorBlacklistRecord::Category::LocalSoftError); + + { + // horrible hack to compensate that we don't handle folder deletes on a per-file basis + qsizetype index = 0; + QString p = relativePath; + while ((index = p.lastIndexOf(QLatin1Char('/'))) != -1) { + p = p.left(index); + const auto rec = journalDb()->errorBlacklistEntry(p); + if (rec.isValid()) { + if (rec._errorCategory == SyncJournalErrorBlacklistRecord::Category::LocalSoftError) { + journalDb()->wipeErrorBlacklistEntry(p); + } + } + } } } - if (spurious) { - qCInfo(lcFolder) << "Ignoring spurious notification for file" << relativePath; - return; // probably a spurious notification + + // Add to list of locally modified paths + // + // We do this before checking for our own sync-related changes to make + // extra sure to not miss relevant changes. + _localDiscoveryTracker->addTouchedPath(relativePath); + + SyncJournalFileRecord record; + _journal->getFileRecord(relativePath.toUtf8(), &record); + if (reason != ChangeReason::UnLock) { + // Check that the mtime/size actually changed or there was + // an attribute change (pin state) that caused the notification + bool spurious = false; + if (record.isValid() && !FileSystem::fileChanged(QFileInfo{path}, record._fileSize, record._modtime, record._inode)) { + spurious = true; + + if (auto pinState = _vfs->pinState(relativePath)) { + if (*pinState == PinState::AlwaysLocal && record.isVirtualFile()) + spurious = false; + if (*pinState == PinState::OnlineOnly && record.isFile()) + spurious = false; + } + } + if (spurious) { + qCInfo(lcFolder) << "Ignoring spurious notification for file" << relativePath; + continue; // probably a spurious notification + } } - } - warnOnNewExcludedItem(record, relativePath); + warnOnNewExcludedItem(record, relativePath); - emit watchedFileChangedExternally(path); + _engine->pathTouched(path); - // Also schedule this folder for a sync, but only after some delay: - // The sync will not upload files that were changed too recently. - scheduleThisFolderSoon(); + needSync = true; + } + if (needSync && canSync()) { + FolderMan::instance()->scheduler()->enqueueFolder(this); + } } void Folder::implicitlyHydrateFile(const QString &relativepath) @@ -664,7 +585,7 @@ void Folder::implicitlyHydrateFile(const QString &relativepath) // Set in the database that we should download the file SyncJournalFileRecord record; - _journal.getFileRecord(relativepath.toUtf8(), &record); + _journal->getFileRecord(relativepath.toUtf8(), &record); if (!record.isValid()) { qCInfo(lcFolder) << "Did not find file in db"; return; @@ -674,158 +595,173 @@ void Folder::implicitlyHydrateFile(const QString &relativepath) return; } record._type = ItemTypeVirtualFileDownload; - _journal.setFileRecord(record); + _journal->setFileRecord(record); // Change the file's pin state if it's contradictory to being hydrated // (suffix-virtual file's pin state is stored at the hydrated path) const auto pin = _vfs->pinState(relativepath); if (pin && *pin == PinState::OnlineOnly) { - _vfs->setPinState(relativepath, PinState::Unspecified); + std::ignore = _vfs->setPinState(relativepath, PinState::Unspecified); } // Add to local discovery schedulePathForLocalDiscovery(relativepath); - slotScheduleThisFolder(); + FolderMan::instance()->scheduler()->enqueueFolder(this); } void Folder::setVirtualFilesEnabled(bool enabled) { - Vfs::Mode newMode = _definition.virtualFilesMode; - if (enabled && _definition.virtualFilesMode == Vfs::Off) { - newMode = bestAvailableVfsMode(); - } else if (!enabled && _definition.virtualFilesMode != Vfs::Off) { + Vfs::Mode newMode = _definition.virtualFilesMode(); + if (enabled && _definition.virtualFilesMode() == Vfs::Off) { + newMode = VfsPluginManager::instance().bestAvailableVfsMode(); + } else if (!enabled) { newMode = Vfs::Off; } - - if (newMode != _definition.virtualFilesMode) { - // TODO: Must wait for current sync to finish! - SyncEngine::wipeVirtualFiles(path(), _journal, *_vfs); - - _vfs->stop(); - _vfs->unregisterFolder(); - - disconnect(_vfs.data(), nullptr, this, nullptr); - disconnect(&_engine->syncFileStatusTracker(), nullptr, _vfs.data(), nullptr); - - _vfs.reset(createVfsFromPlugin(newMode).release()); - - _definition.virtualFilesMode = newMode; - startVfs(); - if (newMode != Vfs::Off) - _saveInFoldersWithPlaceholders = true; - saveToSettings(); + if (newMode == _definition.virtualFilesMode()) { + return; + } + if (isSyncRunning()) { + connect(this, &Folder::syncFinished, this, [this, newMode] { changeVfsMode(newMode); }, Qt::SingleShotConnection); + slotTerminateSync(tr("Switching VFS mode on folder '%1'").arg(displayName())); + } else { + changeVfsMode(newMode); } } -void Folder::setRootPinState(PinState state) +// Refactoring todo: this still causes THREE saves to config file which is potentially excessive for large +// sets. +// I think the best way to fix it is to avoid calling setSyncPaused as that emits a couple of changes, +// which may be overkill for this temp setting change (ie running to paused and back to running) +// needs a deeper look. For the moment most of the pain related to this is avoided because we now only +// connect the folder after it's set up, so the saves during setup are avoided = huge improvement already. +void Folder::changeVfsMode(Vfs::Mode newMode) { - _vfs->setPinState(QString(), state); + if (newMode == _definition.virtualFilesMode()) { + return; + } + // if we can't create the new mode, just ditch. + Vfs *newVfs = VfsPluginManager::instance().createVfsFromPlugin(newMode, this); + if (!newVfs) { + qCWarning(lcFolder) << "Unable to change vfs mode for Folder " << _definition.localPath() << " from " << _definition.virtualFilesMode() << " to " + << newMode << ". Leaving the original mode active."; + return; + } - // We don't actually need discovery, but it's important to recurse - // into all folders, so the changes can be applied. - slotNextSyncFullLocalDiscovery(); -} + const bool wasPaused = _definition.paused(); + if (!wasPaused) { + setSyncPaused(true); + } -bool Folder::supportsSelectiveSync() const -{ - return !virtualFilesEnabled() && !isVfsOnOffSwitchPending(); -} + // stash the previous blacklist + bool ok; + const auto oldBlacklist = journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, ok); -void Folder::saveToSettings() const -{ - // Remove first to make sure we don't get duplicates - removeFromSettings(); + if (!ok) { + qCWarning(lcFolder) << "Unable to retrieve previous selective sync blacklist for folder: " << _definition.localPath(); + return; + } + // clear previous blacklist + journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {}); - auto settings = _accountState->settings(); - QString settingsGroup = QStringLiteral("Multifolders"); - // True if the folder path appears in only one account - bool oneAccountOnly = true; - for (auto *other : FolderMan::instance()->map()) { - if (other != this && other->cleanPath() == this->cleanPath()) { - oneAccountOnly = false; - break; - } + // Wipe the dehydrated files from the DB, they will get downloaded on the next sync. We need to do this, otherwise the files + // are in the DB but not on disk, so the client assumes they are deleted, and removes them from the remote. + _vfs->wipeDehydratedVirtualFiles(); + + // Tear down and disconnect the VFS + _vfsIsReady = false; + _vfs->stop(); + _vfs->unregisterFolder(); + disconnect(_vfs, nullptr, this, nullptr); + disconnect(_engine, nullptr, _vfs, nullptr); + + Vfs *oldVfs = _vfs; + _vfs = newVfs; + oldVfs->deleteLater(); + + // Restart VFS. + _definition.setVirtualFilesMode(newMode); + + // note this is "on top of" started slot handling defined in startVfs + if (newMode != Vfs::Off) { + // schedule blacklisted folders for rediscovery + connect(_vfs, &Vfs::started, this, [oldBlacklist, this] { + for (const auto &entry : oldBlacklist) { + journalDb()->schedulePathForRemoteDiscovery(entry); + // Refactoring todo: from what I can see, in 98% of cases the return val of setPinState is ignored + // do we actually need that return value?! if so why aren't we using it? + std::ignore = vfs().setPinState(entry, PinState::OnlineOnly); + } + }); + } + if (!wasPaused) { + setSyncPaused(wasPaused); } + startVfs(); - if (virtualFilesEnabled() || _saveInFoldersWithPlaceholders) { - // If virtual files are enabled or even were enabled at some point, - // save the folder to a group that will not be read by older (<2.5.0) clients. - // The name is from when virtual files were called placeholders. - settingsGroup = QStringLiteral("FoldersWithPlaceholders"); - } else if (_saveBackwardsCompatible || oneAccountOnly) { - // The folder is saved to backwards-compatible "Folders" - // section only if it has the migrate flag set (i.e. was in - // there before) or if the folder is the only one for the - // given target path. - // This ensures that older clients will not read a configuration - // where two folders for different accounts point at the same - // local folders. - settingsGroup = QStringLiteral("Folders"); - } - - settings->beginGroup(settingsGroup); - // Note: Each of these groups might have a "version" tag, but that's - // currently unused. - settings->beginGroup(FolderMan::escapeAlias(_definition.alias)); - FolderDefinition::save(*settings, _definition); - - settings->sync(); - qCInfo(lcFolder) << "Saved folder" << _definition.alias << "to settings, status" << settings->status(); + Q_EMIT vfsModeChanged(this, newMode); } -void Folder::removeFromSettings() const +bool Folder::isDeployed() const { - auto settings = _accountState->settings(); - settings->beginGroup(QLatin1String("Folders")); - settings->remove(FolderMan::escapeAlias(_definition.alias)); - settings->endGroup(); - settings->beginGroup(QLatin1String("Multifolders")); - settings->remove(FolderMan::escapeAlias(_definition.alias)); - settings->endGroup(); - settings->beginGroup(QLatin1String("FoldersWithPlaceholders")); - settings->remove(FolderMan::escapeAlias(_definition.alias)); + return _definition.isDeployed(); } bool Folder::isFileExcludedAbsolute(const QString &fullPath) const { - return _engine->excludedFiles().isExcluded(fullPath, path(), _definition.ignoreHiddenFiles); + if (OC_ENSURE(_engine)) { + return _engine->isExcluded(fullPath); + } + return true; } bool Folder::isFileExcludedRelative(const QString &relativePath) const { - return _engine->excludedFiles().isExcluded(path() + relativePath, path(), _definition.ignoreHiddenFiles); + return isFileExcludedAbsolute(path() + relativePath); } -void Folder::slotTerminateSync() +void Folder::slotTerminateSync(const QString &reason) { - qCInfo(lcFolder) << "folder " << alias() << " Terminating!"; - - if (_engine->isSyncRunning()) { - _engine->abort(); - - setSyncState(SyncResult::SyncAbortRequested); + if (isReady()) { + qCInfo(lcFolder) << "folder " << path() << " Terminating!"; + if (_engine->isSyncRunning()) { + _engine->abort(reason); + setSyncState(SyncResult::SyncAbortRequested); + } } } void Folder::wipeForRemoval() { + // note we don't have to abort any running sync here as the folderman pauses the folder before this function is ever called. + Q_ASSERT(!isSyncRunning()); + + // I don't understand this logic so I'm removing it for now + // the setupError condition is related to failure to start vfs as far as I can tell. + // that doesn't mean the members don't exist, to the contrary! they are likely instantiated so let's + // let the wipe proceed. + // we can't acces those variables + // if (hasSetupError()) { + // return; + // } + // prevent interaction with the db etc _vfsIsReady = false; - // stop reacting to changes - // especially the upcoming deletion of the db - _folderWatcher.reset(); + _folderWatcher->disconnect(); // Delete files that have been partially downloaded. slotDiscardDownloadProgress(); // Unregister the socket API so it does not keep the .sync_journal file open - FolderMan::instance()->socketApi()->slotUnregisterPath(alias()); - _journal.close(); // close the sync journal + FolderMan::instance()->socketApi()->slotUnregisterPath(this); + _journal->close(); // close the sync journal // Remove db and temporaries - const QString stateDbFile = _engine->journal()->databaseFilePath(); + // what is this? the engine's journal IS THE SAME as the folder member journal!!!! + // const QString stateDbFile = _engine->journal()->databaseFilePath(); + const QString stateDbFile = _journal->databaseFilePath(); + QFile file(stateDbFile); if (file.exists()) { @@ -839,34 +775,48 @@ void Folder::wipeForRemoval() } // Also remove other db related files - QFile::remove(stateDbFile + ".ctmp"); - QFile::remove(stateDbFile + "-shm"); - QFile::remove(stateDbFile + "-wal"); - QFile::remove(stateDbFile + "-journal"); + QFile::remove(stateDbFile + QStringLiteral(".ctmp")); + QFile::remove(stateDbFile + QStringLiteral("-shm")); + QFile::remove(stateDbFile + QStringLiteral("-wal")); + QFile::remove(stateDbFile + QStringLiteral("-journal")); _vfs->stop(); _vfs->unregisterFolder(); - _vfs.reset(nullptr); // warning: folder now in an invalid state + + // don't kill vfs pointer, as it is a child of the Folder we should let it be auto-deleted by normal qobject destruction sequence. } bool Folder::reloadExcludes() { - return _engine->excludedFiles().reloadExcludeFiles(); + // reloadExcludes returns true *if the excludes were successfully reloaded*. + // If the engine is missing the excludes + // can't be reloaded so return here should be false? Erik agrees - caller never checks the value anyway + if (!_engine) { + return false; + } + return _engine->reloadExcludes(); } void Folder::startSync() { - Q_ASSERT(isReady()); - Q_ASSERT(_folderWatcher); + if (!isReady() || !_folderWatcher) { + qCWarning(lcFolder) << "Folder sync attempted before ready and/or without valid folder watcher"; + return; + } if (!OC_ENSURE(!isSyncRunning())) { - qCCritical(lcFolder) << "ERROR csync is still running and new sync requested."; + qCCritical(lcFolder) << "ERROR sync is still running and new sync requested."; + return; + } + + if (!OC_ENSURE(canSync())) { + qCCritical(lcFolder) << "ERROR folder is currently not sync able."; return; } _timeSinceLastSyncStart.start(); - _syncResult.setStatus(SyncResult::SyncPrepare); - emit syncStateChange(); + setSyncState(SyncResult::SyncPrepare); + _syncResult.reset(); qCInfo(lcFolder) << "*** Start syncing " << remoteUrl().toString() << "client version" << Theme::instance()->aboutVersions(Theme::VersionFormat::OneLiner); @@ -875,25 +825,20 @@ void Folder::startSync() if (!reloadExcludes()) { slotSyncError(tr("Could not read system exclude file")); - QMetaObject::invokeMethod(this, "slotSyncFinished", Qt::QueuedConnection, Q_ARG(bool, false)); + QMetaObject::invokeMethod( + this, [this] { slotSyncFinished(false); }, Qt::QueuedConnection); return; } - setDirtyNetworkLimits(); - setSyncOptions(); + // get the latest touched files + // this will enqueue this folder again, it doesn't matter + slotWatchedPathsChanged(_folderWatcher->popChangeSet(), Folder::ChangeReason::Other); - static std::chrono::milliseconds fullLocalDiscoveryInterval = []() { - auto interval = ConfigFile().fullLocalDiscoveryInterval(); - QByteArray env = qgetenv("OWNCLOUD_FULL_LOCAL_DISCOVERY_INTERVAL"); - if (!env.isEmpty()) { - interval = std::chrono::milliseconds(env.toLongLong()); - } - return interval; - }(); - bool hasDoneFullLocalDiscovery = _timeSinceLastFullLocalDiscovery.isValid(); - bool periodicFullLocalDiscoveryNow = - fullLocalDiscoveryInterval.count() >= 0 // negative means we don't require periodic full runs - && _timeSinceLastFullLocalDiscovery.hasExpired(fullLocalDiscoveryInterval.count()); + const std::chrono::milliseconds fullLocalDiscoveryInterval = ConfigFile().fullLocalDiscoveryInterval(); + const bool hasDoneFullLocalDiscovery = _timeSinceLastFullLocalDiscovery.isValid(); + // negative fullLocalDiscoveryInterval means we don't require periodic full runs + const bool periodicFullLocalDiscoveryNow = + fullLocalDiscoveryInterval.count() >= 0 && _timeSinceLastFullLocalDiscovery.hasExpired(fullLocalDiscoveryInterval.count()); if (_folderWatcher && _folderWatcher->isReliable() && hasDoneFullLocalDiscovery && !periodicFullLocalDiscoveryNow) { @@ -908,73 +853,35 @@ void Folder::startSync() _localDiscoveryTracker->startSyncFullDiscovery(); } - _engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles); + QMetaObject::invokeMethod(_engine, &SyncEngine::startSync, Qt::QueuedConnection); - QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection); - - emit syncStarted(); + Q_EMIT syncStarted(); } -void Folder::setSyncOptions() +void Folder::setMoveToTrash(bool trashIt) { - SyncOptions opt; - ConfigFile cfgFile; - - auto newFolderLimit = cfgFile.newBigFolderSizeLimit(); - opt._newBigFolderSizeLimit = newFolderLimit.first ? newFolderLimit.second * 1000LL * 1000LL : -1; // convert from MB to B - opt._confirmExternalStorage = cfgFile.confirmExternalStorage(); - opt._moveFilesToTrash = cfgFile.moveToTrash(); - opt._vfs = _vfs; - opt._parallelNetworkJobs = _accountState->account()->isHttp2Supported() ? 20 : 6; - - opt._initialChunkSize = cfgFile.chunkSize(); - opt._minChunkSize = cfgFile.minChunkSize(); - opt._maxChunkSize = cfgFile.maxChunkSize(); - opt._targetChunkUploadDuration = cfgFile.targetChunkUploadDuration(); - - opt.fillFromEnvironmentVariables(); - opt.verifyChunkSizes(); - - _engine->setSyncOptions(opt); -} - -void Folder::setDirtyNetworkLimits() -{ - ConfigFile cfg; - int downloadLimit = -75; // 75% - int useDownLimit = cfg.useDownloadLimit(); - if (useDownLimit >= 1) { - downloadLimit = cfg.downloadLimit() * 1000; - } else if (useDownLimit == 0) { - downloadLimit = 0; - } - - int uploadLimit = -75; // 75% - int useUpLimit = cfg.useUploadLimit(); - if (useUpLimit >= 1) { - uploadLimit = cfg.uploadLimit() * 1000; - } else if (useUpLimit == 0) { - uploadLimit = 0; - } - - _engine->setNetworkLimits(uploadLimit, downloadLimit); + _engine->setMoveToTrash(trashIt); } void Folder::slotSyncError(const QString &message, ErrorCategory category) { _syncResult.appendErrorString(message); - emit ProgressDispatcher::instance()->syncError(alias(), message, category); + Q_EMIT ProgressDispatcher::instance()->syncError(this, message, category); } void Folder::slotSyncStarted() { qCInfo(lcFolder) << "#### Propagation start ####################################################"; - _syncResult.setStatus(SyncResult::SyncRunning); - emit syncStateChange(); + setSyncState(SyncResult::SyncRunning); } void Folder::slotSyncFinished(bool success) { + if (!isReady()) { + // probably removing the folder + qCWarning(lcFolder) << "Folder not ready after sync finished"; + return; + } qCInfo(lcFolder) << "Client version" << Theme::instance()->aboutVersions(Theme::VersionFormat::OneLiner); bool syncError = !_syncResult.errorStrings().isEmpty(); @@ -986,58 +893,55 @@ void Folder::slotSyncFinished(bool success) _fileLog->finish(); showSyncResultPopup(); - auto anotherSyncNeeded = _engine->isAnotherSyncNeeded(); + auto anotherSyncNeeded = false; + + auto syncStatus = SyncResult::Status::Undefined; if (syncError) { - _syncResult.setStatus(SyncResult::Error); + syncStatus = SyncResult::Error; } else if (_syncResult.foundFilesNotSynced()) { - _syncResult.setStatus(SyncResult::Problem); - } else if (_definition.paused) { + syncStatus = SyncResult::Problem; + } else if (_definition.paused()) { // Maybe the sync was terminated because the user paused the folder - _syncResult.setStatus(SyncResult::Paused); + syncStatus = SyncResult::Paused; } else { - _syncResult.setStatus(SyncResult::Success); + syncStatus = SyncResult::Success; } // Count the number of syncs that have failed in a row. - if (_syncResult.status() == SyncResult::Success - || _syncResult.status() == SyncResult::Problem) { + if (syncStatus == SyncResult::Success || syncStatus == SyncResult::Problem) { _consecutiveFailingSyncs = 0; } else { _consecutiveFailingSyncs++; + anotherSyncNeeded |= _consecutiveFailingSyncs <= retrySyncLimitC; qCInfo(lcFolder) << "the last" << _consecutiveFailingSyncs << "syncs failed"; } - if (_syncResult.status() == SyncResult::Success && success) { + if (syncStatus == SyncResult::Success && success) { // Clear the white list as all the folders that should be on that list are sync-ed - journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList()); + journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, {}); } - if ((_syncResult.status() == SyncResult::Success - || _syncResult.status() == SyncResult::Problem) - && success) { + if ((syncStatus == SyncResult::Success || syncStatus == SyncResult::Problem) && success) { if (_engine->lastLocalDiscoveryStyle() == LocalDiscoveryStyle::FilesystemOnly) { _timeSinceLastFullLocalDiscovery.start(); } } + if (syncStatus != SyncResult::Undefined) { + setSyncState(syncStatus); + } - emit syncStateChange(); - - // The syncFinished result that is to be triggered here makes the folderman - // clear the current running sync folder marker. - // Lets wait a bit to do that because, as long as this marker is not cleared, - // file system change notifications are ignored for that folder. And it takes - // some time under certain conditions to make the file system notifications - // all come in. - QTimer::singleShot(200ms, this, &Folder::slotEmitFinishedDelayed); + // syncStateChange from setSyncState needs to be emitted first + QTimer::singleShot(0, this, [this] { Q_EMIT syncFinished(_syncResult); }); _lastSyncDuration = std::chrono::milliseconds(_timeSinceLastSyncStart.elapsed()); _timeSinceLastSyncDone.start(); // Increment the follow-up sync counter if necessary. - if (anotherSyncNeeded == ImmediateFollowUp) { + if (_engine->isAnotherSyncNeeded()) { _consecutiveFollowUpSyncs++; + anotherSyncNeeded |= _consecutiveFollowUpSyncs <= retrySyncLimitC; qCInfo(lcFolder) << "another sync was requested by the finished sync, this has" << "happened" << _consecutiveFollowUpSyncs << "times"; } else { @@ -1045,43 +949,18 @@ void Folder::slotSyncFinished(bool success) } // Maybe force a follow-up sync to take place, but only a couple of times. - if (anotherSyncNeeded == ImmediateFollowUp && _consecutiveFollowUpSyncs <= 3) { + if (anotherSyncNeeded && canSync()) { // Sometimes another sync is requested because a local file is still // changing, so wait at least a small amount of time before syncing // the folder again. - scheduleThisFolderSoon(); + QTimer::singleShot(SyncEngine::minimumFileAgeForUpload, this, [this] { FolderMan::instance()->scheduler()->enqueueFolder(this); }); } } -void Folder::slotEmitFinishedDelayed() -{ - emit syncFinished(_syncResult); - - // Immediately check the etag again if there was some sync activity. - if ((_syncResult.status() == SyncResult::Success - || _syncResult.status() == SyncResult::Problem) - && (_syncResult.firstItemDeleted() - || _syncResult.firstItemNew() - || _syncResult.firstItemRenamed() - || _syncResult.firstItemUpdated() - || _syncResult.firstNewConflictItem())) { - slotRunEtagJob(); - } -} - -// the progress comes without a folder and the valid path set. Add that here -// and hand the result over to the progress dispatcher. -void Folder::slotTransmissionProgress(const ProgressInfo &pi) -{ - emit progressInfo(pi); - emit ProgressDispatcher::instance()->progressInfo(alias(), pi); -} - -// a item is completed: count the errors and forward to the ProgressDispatcher +// an item is completed: count the errors and forward to the ProgressDispatcher void Folder::slotItemCompleted(const SyncFileItemPtr &item) { - if (item->_status == SyncFileItem::Success - && (item->_instruction & (CSYNC_INSTRUCTION_NONE | CSYNC_INSTRUCTION_UPDATE_METADATA))) { + if (item->_status == SyncFileItem::Success && (item->instruction() & (CSYNC_INSTRUCTION_NONE | CSYNC_INSTRUCTION_UPDATE_METADATA))) { // We only care about the updates that deserve to be shown in the UI return; } @@ -1089,52 +968,12 @@ void Folder::slotItemCompleted(const SyncFileItemPtr &item) _syncResult.processCompletedItem(item); _fileLog->logItem(*item); - emit ProgressDispatcher::instance()->itemCompleted(alias(), item); -} - -void Folder::slotNewBigFolderDiscovered(const QString &newF, bool isExternal) -{ - auto newFolder = newF; - if (!newFolder.endsWith(QLatin1Char('/'))) { - newFolder += QLatin1Char('/'); - } - auto journal = journalDb(); - - // Add the entry to the blacklist if it is neither in the blacklist or whitelist already - bool ok1, ok2; - auto blacklist = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok1); - auto whitelist = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, &ok2); - if (ok1 && ok2 && !blacklist.contains(newFolder) && !whitelist.contains(newFolder)) { - blacklist.append(newFolder); - journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blacklist); - } - - // And add the entry to the undecided list and signal the UI - auto undecidedList = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, &ok1); - if (ok1) { - if (!undecidedList.contains(newFolder)) { - undecidedList.append(newFolder); - journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, undecidedList); - emit newBigFolderDiscovered(newFolder); - } - QString message = !isExternal ? (tr("A new folder larger than %1 MB has been added: %2.\n") - .arg(ConfigFile().newBigFolderSizeLimit().second) - .arg(newF)) - : (tr("A folder from an external storage has been added.\n")); - message += tr("Please go in the settings to select it if you wish to download it."); - - ocApp()->gui()->slotShowOptionalTrayMessage(Theme::instance()->appNameGUI(), message); - } + Q_EMIT ProgressDispatcher::instance()->itemCompleted(this, item); } void Folder::slotLogPropagationStart() { - _fileLog->logLap("Propagation starts"); -} - -void Folder::slotScheduleThisFolder() -{ - FolderMan::instance()->scheduleFolder(this); + _fileLog->logLap(QStringLiteral("Propagation starts")); } void Folder::slotNextSyncFullLocalDiscovery() @@ -1144,12 +983,12 @@ void Folder::slotNextSyncFullLocalDiscovery() void Folder::schedulePathForLocalDiscovery(const QString &relativePath) { - _localDiscoveryTracker->addTouchedPath(relativePath.toUtf8()); + _localDiscoveryTracker->addTouchedPath(relativePath); } -void Folder::slotFolderConflicts(const QString &folder, const QStringList &conflictPaths) +void Folder::slotFolderConflicts(Folder *folder, const QStringList &conflictPaths) { - if (folder != _definition.alias) + if (folder != this) return; auto &r = _syncResult; @@ -1158,7 +997,7 @@ void Folder::slotFolderConflicts(const QString &folder, const QStringList &confl r.setNumOldConflictItems(conflictPaths.size() - r.numNewConflictItems()); } -void Folder::warnOnNewExcludedItem(const SyncJournalFileRecord &record, const QStringRef &path) +void Folder::warnOnNewExcludedItem(const SyncJournalFileRecord &record, QStringView path) { // Never warn for items in the database if (record.isValid()) @@ -1168,15 +1007,15 @@ void Folder::warnOnNewExcludedItem(const SyncJournalFileRecord &record, const QS // Note: This assumes we're getting file watcher notifications // for folders only on creation and deletion - if we got a notification // on content change that would create spurious warnings. - QFileInfo fi(_canonicalLocalPath + path); + QFileInfo fi(_definition.canonicalPath() + path); if (!fi.exists()) return; bool ok = false; - auto blacklist = _journal.getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok); + auto blacklist = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, ok); if (!ok) return; - if (!blacklist.contains(path + "/")) + if (!blacklist.contains(path + QLatin1Char('/'))) return; const auto message = fi.isDir() @@ -1187,207 +1026,176 @@ void Folder::warnOnNewExcludedItem(const SyncJournalFileRecord &record, const QS "It will not be synchronized.") .arg(fi.filePath()); - ocApp()->gui()->slotShowOptionalTrayMessage(Theme::instance()->appNameGUI(), message); + ocApp()->tray()->slotShowOptionalTrayMessage(Theme::instance()->appNameGUI(), message); } void Folder::slotWatcherUnreliable(const QString &message) { qCWarning(lcFolder) << "Folder watcher for" << path() << "became unreliable:" << message; - auto fullMessage = + + QMessageBox *msgBox = new QMessageBox(QMessageBox::Information, Theme::instance()->appNameGUI(), tr("Changes in synchronized folders could not be tracked reliably.\n" "\n" "This means that the synchronization client might not upload local changes " "immediately and will instead only scan for local changes and upload them " "occasionally (every two hours by default).\n" "\n" - "%1").arg(message); - ocApp()->gui()->slotShowGuiMessage(Theme::instance()->appNameGUI(), fullMessage); + "%1") + .arg(message), + {}, ocApp()->mainWindow()); + + msgBox->setAttribute(Qt::WA_DeleteOnClose); + ocApp()->ensureVisible(); + msgBox->open(); } -void Folder::slotHydrationStarts() +void Folder::registerFolderWatcher() { - // Abort any running full sync run and reschedule - if (_engine->isSyncRunning()) { - slotTerminateSync(); - scheduleThisFolderSoon(); - // TODO: This sets the sync state to AbortRequested on done, we don't want that + if (_folderWatcher) { + return; } - // Let everyone know we're syncing - _syncResult.reset(); - _syncResult.setStatus(SyncResult::SyncRunning); - emit syncStarted(); - emit syncStateChange(); + _folderWatcher = new FolderWatcher(this); + connect( + _folderWatcher, &FolderWatcher::pathChanged, this, [this](const QSet &paths) { slotWatchedPathsChanged(paths, Folder::ChangeReason::Other); }); + connect(_folderWatcher, &FolderWatcher::lostChanges, this, &Folder::slotNextSyncFullLocalDiscovery); + connect(_folderWatcher, &FolderWatcher::becameUnreliable, this, &Folder::slotWatcherUnreliable); + _folderWatcher->init(path()); + _folderWatcher->startNotificatonTest(path() + QLatin1String(".owncloudsync.log")); } -void Folder::slotHydrationDone() +bool Folder::virtualFilesEnabled() const { - // emit signal to update ui and reschedule normal syncs if necessary - _syncResult.setStatus(SyncResult::Success); - emit syncFinished(_syncResult); - emit syncStateChange(); + return _definition.virtualFilesMode() != Vfs::Off; } -void Folder::scheduleThisFolderSoon() +FolderDefinition::FolderDefinition(const QByteArray &id, const QUrl &davUrl, const QString &spaceId, const QString &displayName) + : _webDavUrl(davUrl) + , _spaceId(spaceId) + , _id(id) + , _displayName(displayName) { - if (!_scheduleSelfTimer.isActive()) { - _scheduleSelfTimer.start(); + if (_webDavUrl.path().endsWith(QLatin1Char('/'))) { + // this is related to folder wizard adding trailing separator. No other impl adds trailing sep to the webDavUrl + // so we want to clean these corner cases up so the paths are consistent. the folder wizard has been updated to + // no longer add the trailing sep so this just cleans up "legacy" paths from previously existing configs + QString path = _webDavUrl.path(); + path.truncate(path.lastIndexOf('/')); + _webDavUrl.setPath(path); + + // note if we want to add a trailing slash to the webDavUrl in future, it should be done in Space::webDavUrl since + // that is the ultimate source of the url. we should not be "fixing" separators all over the app. } } -void Folder::setSaveBackwardsCompatible(bool save) +FolderDefinition::FolderDefinition(const QUrl &davUrl, const QString &spaceId, const QString &displayName) + : FolderDefinition(QUuid::createUuid().toByteArray(QUuid::WithoutBraces), davUrl, spaceId, displayName) { - _saveBackwardsCompatible = save; } -void Folder::registerFolderWatcher() +void FolderDefinition::setPriority(uint32_t newPriority) { - if (!isReady()) { - return; - } - if (_folderWatcher) - return; - - _folderWatcher.reset(new FolderWatcher(this)); - connect(_folderWatcher.data(), &FolderWatcher::pathChanged, - this, [this](const QString &path) { slotWatchedPathChanged(path, Folder::ChangeReason::Other); }); - connect(_folderWatcher.data(), &FolderWatcher::lostChanges, - this, &Folder::slotNextSyncFullLocalDiscovery); - connect(_folderWatcher.data(), &FolderWatcher::becameUnreliable, - this, &Folder::slotWatcherUnreliable); - _folderWatcher->init(path()); - _folderWatcher->startNotificatonTest(path() + QLatin1String(".owncloudsync.log")); + _priority = newPriority; } -bool Folder::virtualFilesEnabled() const +uint32_t FolderDefinition::priority() const { - return _definition.virtualFilesMode != Vfs::Off && !isVfsOnOffSwitchPending(); + return _priority; } -void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction dir, std::function abort) +void FolderDefinition::save(QSettings &settings, const FolderDefinition &def) { - ConfigFile cfgFile; - if (!cfgFile.promptDeleteFiles()) { - abort(false); - return; + settings.setValue("localPath", def.localPath()); + settings.setValue("journalPath", def.journalPath()); + settings.setValue("targetPath", def.targetPath()); + if (!def.spaceId().isEmpty()) { + settings.setValue(spaceIdC(), def.spaceId()); } - const QString msg = dir == SyncFileItem::Down ? tr("All files in the sync folder '%1' folder were deleted on the server.\n" - "These deletes will be synchronized to your local sync folder, making such files " - "unavailable unless you have a right to restore. \n" - "If you decide to keep the files, they will be re-synced with the server if you have rights to do so.\n" - "If you decide to delete the files, they will be unavailable to you, unless you are the owner.") - : tr("All the files in your local sync folder '%1' were deleted. These deletes will be " - "synchronized with your server, making such files unavailable unless restored.\n" - "Are you sure you want to sync those actions with the server?\n" - "If this was an accident and you decide to keep your files, they will be re-synced from the server."); - auto msgBox = new QMessageBox(QMessageBox::Warning, tr("Remove All Files?"), - msg.arg(shortGuiLocalPath()), QMessageBox::NoButton, ocApp()->gui()->settingsDialog()); - msgBox->setAttribute(Qt::WA_DeleteOnClose); - msgBox->setWindowFlags(msgBox->windowFlags() | Qt::WindowStaysOnTopHint); - msgBox->addButton(tr("Remove all files"), QMessageBox::DestructiveRole); - QPushButton *keepBtn = msgBox->addButton(tr("Keep files"), QMessageBox::AcceptRole); - bool oldPaused = syncPaused(); - setSyncPaused(true); - connect(msgBox, &QMessageBox::finished, this, [msgBox, keepBtn, abort, oldPaused, this] { - const bool cancel = msgBox->clickedButton() == keepBtn; - abort(cancel); - if (cancel) { - FileSystem::setFolderMinimumPermissions(path()); - journalDb()->clearFileTable(); - _lastEtag.clear(); - slotScheduleThisFolder(); - } - setSyncPaused(oldPaused); - }); - connect(this, &Folder::destroyed, msgBox, &QMessageBox::deleteLater); - msgBox->open(); - ownCloudGui::raiseDialog(msgBox); -} + settings.setValue(davUrlC(), def.webDavUrl()); + settings.setValue(displayNameC(), def.displayName()); + settings.setValue("paused", def.paused()); + settings.setValue(deployedC(), def.isDeployed()); + settings.setValue(priorityC(), def.priority()); -void FolderDefinition::save(QSettings &settings, const FolderDefinition &folder) -{ - settings.setValue(QLatin1String("localPath"), folder.localPath); - settings.setValue(QLatin1String("journalPath"), folder.journalPath); - settings.setValue(QLatin1String("targetPath"), folder.targetPath); - settings.setValue(QLatin1String("paused"), folder.paused); - settings.setValue(QLatin1String("ignoreHiddenFiles"), folder.ignoreHiddenFiles); - - settings.setValue(QStringLiteral("virtualFilesMode"), Vfs::modeToString(folder.virtualFilesMode)); - - // Ensure new vfs modes won't be attempted by older clients - const int version = folder.virtualFilesMode == Vfs::WindowsCfApi ? WinVfsSettingsVersion : SettingsVersion; - Q_ASSERT(version <= maxSettingsVersion()); - settings.setValue(versionC(), version); - - // Happens only on Windows when the explorer integration is enabled. - if (!folder.navigationPaneClsid.isNull()) - settings.setValue(QLatin1String("navigationPaneClsid"), folder.navigationPaneClsid); - else - settings.remove(QLatin1String("navigationPaneClsid")); + settings.setValue("virtualFilesMode", Utility::enumToString(def.virtualFilesMode())); } -bool FolderDefinition::load(QSettings &settings, const QString &alias, - FolderDefinition *folder) +FolderDefinition FolderDefinition::load(QSettings &settings, const QByteArray &id) { - folder->alias = FolderMan::unescapeAlias(alias); - folder->localPath = settings.value(QLatin1String("localPath")).toString(); - folder->journalPath = settings.value(QLatin1String("journalPath")).toString(); - folder->targetPath = settings.value(QLatin1String("targetPath")).toString(); - folder->paused = settings.value(QLatin1String("paused")).toBool(); - folder->ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), QVariant(true)).toBool(); - folder->navigationPaneClsid = settings.value(QLatin1String("navigationPaneClsid")).toUuid(); - - folder->virtualFilesMode = Vfs::Off; + Q_ASSERT(!id.isEmpty()); + + FolderDefinition def{id, settings.value(davUrlC()).toUrl(), settings.value(spaceIdC()).toString(), settings.value(displayNameC()).toString()}; + def.setLocalPath(settings.value("localPath").toString()); + def.setTargetPath(settings.value("targetPath").toString()); + def._journalPath = settings.value("journalPath").toString(); + def._paused = settings.value("paused").toBool(); + def._deployed = settings.value(deployedC(), false).toBool(); + def._priority = settings.value(priorityC(), 0).toUInt(); + + def._virtualFilesMode = Vfs::Off; QString vfsModeString = settings.value(QStringLiteral("virtualFilesMode")).toString(); if (!vfsModeString.isEmpty()) { - if (auto mode = Vfs::modeFromString(vfsModeString)) { - folder->virtualFilesMode = *mode; - } else { - qCWarning(lcFolder) << "Unknown virtualFilesMode:" << vfsModeString << "assuming 'off'"; - } - } else { - if (settings.value(QLatin1String("usePlaceholders")).toBool()) { - folder->virtualFilesMode = Vfs::WithSuffix; - folder->upgradeVfsMode = true; // maybe winvfs is available? - } + def._virtualFilesMode = Vfs::modeFromString(vfsModeString); } - // Old settings can contain paths with native separators. In the rest of the - // code we assume /, so clean it up now. - folder->localPath = prepareLocalPath(folder->localPath); + if (settings.contains("version")) { + // Migration from pre-7.0: + settings.remove("version"); + } - // Target paths also have a convention - folder->targetPath = prepareTargetPath(folder->targetPath); + const QVariant ignoreHiddenFiles = settings.value("ignoreHiddenFiles"); + if (!ignoreHiddenFiles.isNull()) { + // Migration from pre-7.0: + settings.remove("ignoreHiddenFiles"); + FolderMan::instance()->setIgnoreHiddenFiles(ignoreHiddenFiles.toBool()); + } - return true; + return def; } -QString FolderDefinition::prepareLocalPath(const QString &path) +void FolderDefinition::setLocalPath(const QString &path) { - QString p = QDir::fromNativeSeparators(path); - if (!p.endsWith(QLatin1Char('/'))) { - p.append(QLatin1Char('/')); + _localPath = QDir::fromNativeSeparators(path); + if (!_localPath.endsWith(QLatin1Char('/'))) { + _localPath.append(QLatin1Char('/')); + } + + QFileInfo fi(_localPath); + _canonicalLocalPath = fi.canonicalFilePath(); + + if (_canonicalLocalPath.isEmpty()) { + qCWarning(lcFolder) << "Broken symlink:" << _localPath; + _canonicalLocalPath = _localPath; + } else if (!_canonicalLocalPath.endsWith(QLatin1Char('/'))) { + // the canonicalPath function strips off the trailing separator so we have to add it back, apparently + _canonicalLocalPath.append(QLatin1Char('/')); } - return p; } -QString FolderDefinition::prepareTargetPath(const QString &path) +void FolderDefinition::setTargetPath(const QString &path) { - QString p = path; - if (p.endsWith(QLatin1Char('/'))) { - p.chop(1); - } + _targetPath = Utility::stripTrailingSlash(path); // Doing this second ensures the empty string or "/" come // out as "/". - if (!p.startsWith(QLatin1Char('/'))) { - p.prepend(QLatin1Char('/')); + if (!_targetPath.startsWith(QLatin1Char('/'))) { + _targetPath.prepend(QLatin1Char('/')); } - return p; } -QString FolderDefinition::absoluteJournalPath() const +QString FolderDefinition::displayName() const { - return QDir(localPath).filePath(journalPath); + if (_displayName.isEmpty()) { + if (targetPath().length() > 0 && targetPath() != QLatin1String("/")) { + QString a = QFileInfo(targetPath()).fileName(); + if (a.startsWith(QLatin1Char('/'))) { + a = a.remove(0, 1); + } + return a; + } else { + return Theme::instance()->appNameGUI(); + } + } + return _displayName; } } // namespace OCC diff --git a/src/gui/folder.h b/src/gui/folder.h index adc64659ece..61dc411307a 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -14,23 +14,25 @@ * for more details. */ -#ifndef MIRALL_FOLDER_H -#define MIRALL_FOLDER_H +#pragma once -#include "syncresult.h" -#include "progressdispatcher.h" +#include "gui/owncloudguilib.h" + +#include "accountstate.h" #include "common/syncjournaldb.h" +#include "libsync/graphapi/space.h" #include "networkjobs.h" +#include "progressdispatcher.h" #include "syncoptions.h" +#include "syncresult.h" #include #include -#include #include +#include #include #include -#include class QThread; class QSettings; @@ -39,78 +41,138 @@ namespace OCC { class Vfs; class SyncEngine; -class AccountState; class SyncRunFileLog; class FolderWatcher; class LocalDiscoveryTracker; + /** * @brief The FolderDefinition class * @ingroup gui */ -class FolderDefinition + +class OWNCLOUDGUI_EXPORT FolderDefinition { public: - FolderDefinition() - : paused(false) - , ignoreHiddenFiles(true) - { - } + // use for reading from config + FolderDefinition(const QByteArray &id, const QUrl &davUrl, const QString &spaceId, const QString &displayName); - /// The name of the folder in the ui and internally - QString alias; - /// path on local machine (always trailing /) - QString localPath; - /// path to the journal, usually relative to localPath - QString journalPath; - /// path on remote (usually no trailing /, exception "/") - QString targetPath; - /// whether the folder is paused - bool paused; - /// whether the folder syncs hidden files - bool ignoreHiddenFiles; - /// Which virtual files setting the folder uses - Vfs::Mode virtualFilesMode = Vfs::Off; - /// The CLSID where this folder appears in registry for the Explorer navigation pane entry. - QUuid navigationPaneClsid; + // use when creating folder from scratch for the very first time + FolderDefinition(const QUrl &davUrl, const QString &spaceId, const QString &displayName = {}); - /// Whether the vfs mode shall silently be updated if possible - bool upgradeVfsMode = false; - /// Saves the folder definition into the current settings group. + /// Saves the folder definition into the given settings (group should be preconfigured) static void save(QSettings &settings, const FolderDefinition &folder); /// Reads a folder definition from the current settings group. - static bool load(QSettings &settings, const QString &alias, - FolderDefinition *folder); + static FolderDefinition load(QSettings &settings, const QByteArray &id); - /** The highest version in the settings that load() can read - * - * Version 1: initial version (default if value absent in settings) - * Version 2: introduction of metadata_parent hash in 2.6.0 - * (version remains readable by 2.5.1) - * Version 3: introduction of new windows vfs mode in 2.6.0 - * Version 4: until 2.9.1 windows vfs tried to unregister folders with a different id from windows. - */ - static int maxSettingsVersion() { return 4; } + /// path to the journal, usually relative to localPath + QString journalPath() const { return _journalPath; } + + void setJournalPath(const QString &newPath) + { + if (_journalPath != newPath) + _journalPath = newPath; + } + + /// whether the folder is paused + bool paused() const { return _paused; } + void setPaused(bool pause) { _paused = pause; } + + /// Which virtual files setting the folder uses + Vfs::Mode virtualFilesMode() const { return _virtualFilesMode; } + void setVirtualFilesMode(Vfs::Mode mode) { _virtualFilesMode = mode; } /// Ensure / as separator and trailing /. - static QString prepareLocalPath(const QString &path); + /// calling set here also ensures the canonical local path is properly created + void setLocalPath(const QString &path); + QString localPath() const { return _localPath; } + + QString canonicalPath() const { return _canonicalLocalPath; } + /// Remove ending /, then ensure starting '/': so "/foo/bar" and "/". - static QString prepareTargetPath(const QString &path); + void setTargetPath(const QString &path); + QString targetPath() const { return _targetPath; } /// journalPath relative to localPath. - QString absoluteJournalPath() const; + QString absoluteJournalPath() const { return QDir(localPath()).filePath(_journalPath); } + + + QUrl webDavUrl() const { return _webDavUrl; } + // could change in the case of spaces + void setWebDavUrl(const QUrl &url) { _webDavUrl = url; } + + + // when using spaces we don't store the dav URL but the space id + // this id is then used to look up the dav URL + QString spaceId() const { return _spaceId; } + void setSpaceId(const QString &spaceId) { _spaceId = spaceId; } + + const QByteArray &id() const { return _id; } + + QString displayName() const; + + /** + * The folder is deployed by an admin + * We will hide the remove option and the disable/enable vfs option. + */ + // todo #52 Release 8.0 = eliminate this OC10 property and remove prop from settings + [[deprecated("deployed concept is no longer supported and will be removed in client 8.0")]] bool isDeployed() const { return _deployed; } + + /** + * Higher values mean more important + * Used for sorting + */ + uint32_t priority() const; + void setPriority(uint32_t newPriority); + +private: + /// path to the journal, usually relative to localPath + QString _journalPath; + + /// whether the folder is paused + bool _paused = false; + /// Which virtual files setting the folder uses + Vfs::Mode _virtualFilesMode = Vfs::Off; + + // oc10 and as cache for ocis + QUrl _webDavUrl; + + QString _spaceId; + /// For legacy reasons this can be a string, new folder objects will use an uuid + QByteArray _id; + QString _displayName; + /// path on local machine (always trailing /) + QString _localPath; + /// canonical local path + QString _canonicalLocalPath; + /// path on remote (usually no trailing /, exception "/") + QString _targetPath; + bool _deployed = false; + + uint32_t _priority = 0; + }; /** * @brief The Folder class * @ingroup gui */ -class Folder : public QObject +// Refactoring todo: this class is overgrown and has too many responsibilities. Need to evaluate and refine it's core behaviors +// and split out some of the responsibilities to new dedicated classes. Eg I already see some need for a folder sync handler, and/or +// a folder scoped vfs controller. these could still be created/configured in the Folder (worst case!) but this class just does way too much +// Refactoring the impls to be more modular will also allow us to refactor the ctr sensibly to avoid the excessive setup routines which are +// invoked before the class is even fully built. +// we also have weird split responsibility around instantiating the vfs - on creation the FolderMan instantiates vfs and passes it to +// the ctr. But the folder resets the vfs with new instance sometimes as well. This is not healthy as responsibility is split/not clear +class OWNCLOUDGUI_EXPORT Folder : public QObject { Q_OBJECT + Q_PROPERTY(GraphApi::Space *space READ space NOTIFY spaceChanged) + QML_ELEMENT + QML_UNCREATABLE("Folders can only be created by the FolderManager") public: enum class ChangeReason { @@ -119,21 +181,46 @@ class Folder : public QObject }; Q_ENUM(ChangeReason) + /** Create a new Folder */ - Folder(const FolderDefinition &definition, AccountState *accountState, std::unique_ptr vfs, QObject *parent = nullptr); + Folder(const FolderDefinition &definition, AccountState *accountState, SyncJournalDb *journal, Vfs *vfs, SyncEngine *engine, QObject *parent); ~Folder() override; /** * The account the folder is configured on. */ - AccountState *accountState() const { return _accountState.data(); } + AccountState *accountState() const { return _accountState; } + + const FolderDefinition &definition() const { return _definition; } + + /** + * id is the id used to identify a folder/space in the config file + * + * this id is "legacy" from the days when not all supported servers (eg oc10) supported spaces. Ideally we should remove this in favor of always using + the space id but it would require changes to the config. + */ + QByteArray id() const { return _definition.id(); } + + /** + * @brief spaceId is the primary identifier for a folder, and it is guaranteed to be unique *within an account*. + * + * It is not universally unique because "shares" space has a hard coded spaceId which is always used regardless of where it lives. + * + * @return the space id for the folder + */ + QString spaceId() const { return _definition.spaceId(); } /** - * alias or nickname + * remote folder path, usually without trailing /, exception "/" */ - QString alias() const; - QString shortGuiRemotePathOrAppName() const; // since 2.0 we don't want to show aliases anymore, show the path instead + QString remotePath() const { return _definition.targetPath(); } + + // Normally this value comes from the space because it can change on the server side, but may come from the definition when the space is not available + QString displayName() const; + + // Normally this value comes from the space, but may come from the definition when the space is not available + QUrl webDavUrl() const; /** * short local path to display on the GUI (native separators) @@ -143,7 +230,7 @@ class Folder : public QObject /** * canonical local folder path, always ends with / */ - QString path() const; + QString path() const { return _definition.canonicalPath(); } /** * cleaned canonical folder path, like path() but never ends with a / @@ -153,31 +240,34 @@ class Folder : public QObject */ QString cleanPath() const; - /** - * remote folder path, usually without trailing /, exception "/" - */ - QString remotePath() const; /** * remote folder path, always with a trailing / */ QString remotePathTrailingSlash() const; - void setNavigationPaneClsid(const QUuid &clsid) { _definition.navigationPaneClsid = clsid; } - QUuid navigationPaneClsid() const { return _definition.navigationPaneClsid; } - /** - * remote folder path with server url + * remote folder path with server URL */ - QUrl remoteUrl() const; + QUrl remoteUrl() const { return Utility::concatUrlPath(webDavUrl(), remotePath()); } /** * switch sync on or off */ void setSyncPaused(bool); - bool syncPaused() const; + bool syncPaused() const { return _definition.paused(); } + /** a folder is unavailable if its space is missing from the spaces manager + * this happens when a folder has been added for an existing space, then that space is disabled + * or deleted on the server. + * we should not try to sync unavailable spaces! + */ + bool isAvailable() const { return _available; } + void setAvailable(bool available); + + // is the folder connected (via the accountState) + bool isConnected(); /** * Returns true when the folder may sync. */ @@ -185,16 +275,14 @@ class Folder : public QObject /** * Whether the folder is ready + * + * passthrough to _engine */ bool isReady() const; - /** - * Returns true if the folder needs sync poll interval wise, and can - * sync due to its internal state - */ - bool dueToSync() const; + bool hasSetupError() const { return _syncResult.status() == SyncResult::SetupError; } - void prepareToSync(); + void prepareToSync() { setSyncState(SyncResult::NotYetStarted); } /** True if the folder is currently synchronizing */ bool isSyncRunning() const; @@ -202,7 +290,7 @@ class Folder : public QObject /** * return the last sync result with error message and status */ - SyncResult syncResult() const; + SyncResult syncResult() const { return _syncResult; } /** * This is called when the sync folder definition is removed. Do cleanups here. @@ -215,35 +303,23 @@ class Folder : public QObject void setSyncState(SyncResult::Status state); - void setDirtyNetworkLimits(); + void setMoveToTrash(bool trashIt); - /** - * Ignore syncing of hidden files or not. This is defined in the - * folder definition - */ - bool ignoreHiddenFiles(); - void setIgnoreHiddenFiles(bool ignore); - // Used by the Socket API - SyncJournalDb *journalDb() { return &_journal; } - SyncEngine &syncEngine() { return *_engine; } + // TODO: don't expose + SyncJournalDb *journalDb() { return _journal; } + // TODO: don't expose + SyncEngine *syncEngine() { return _engine; } + Vfs &vfs() { OC_ENFORCE(_vfs); return *_vfs; } - RequestEtagJob *etagJob() const { return _requestEtagJob; } auto lastSyncTime() const { return QDateTime::currentDateTime().addMSecs(-msecSinceLastSync().count()); } std::chrono::milliseconds msecSinceLastSync() const { return std::chrono::milliseconds(_timeSinceLastSyncDone.elapsed()); } std::chrono::milliseconds msecLastSyncDuration() const { return _lastSyncDuration; } - int consecutiveFollowUpSyncs() const { return _consecutiveFollowUpSyncs; } - int consecutiveFailingSyncs() const { return _consecutiveFailingSyncs; } - - /// Saves the folder data in the account's settings. - void saveToSettings() const; - /// Removes the folder from the account's settings. - void removeFromSettings() const; /** * Returns whether a file inside this folder should be excluded. @@ -255,33 +331,6 @@ class Folder : public QObject */ bool isFileExcludedRelative(const QString &relativePath) const; - /** Calls schedules this folder on the FolderMan after a short delay. - * - * This should be used in situations where a sync should be triggered - * because a local file was modified. Syncs don't upload files that were - * modified too recently, and this delay ensures the modification is - * far enough in the past. - * - * The delay doesn't reset with subsequent calls. - */ - void scheduleThisFolderSoon(); - - /** - * Migration: When this flag is true, this folder will save to - * the backwards-compatible 'Folders' section in the config file. - */ - void setSaveBackwardsCompatible(bool save); - - /** Used to have placeholders: save in placeholder config section */ - void setSaveInFoldersWithPlaceholders() { _saveInFoldersWithPlaceholders = true; } - - /** - * Sets up this folder's folderWatcher if possible. - * - * May be called several times. - */ - void registerFolderWatcher(); - /** virtual files of some kind are enabled * * This is independent of whether new files will be virtual. It's possible to have this enabled @@ -291,41 +340,40 @@ class Folder : public QObject bool virtualFilesEnabled() const; void setVirtualFilesEnabled(bool enabled); - void setRootPinState(PinState state); + /** + * The folder is deployed by an admin + * We will hide the remove option and the disable/enable vfs option. + * + * see FolderDefinition isDeployed - this concept was oc10 related so it needs to go + */ + [[deprecated("see FolderDefinition::isDeployed")]] bool isDeployed() const; - /** Whether user desires a switch that couldn't be executed yet, see member */ - bool isVfsOnOffSwitchPending() const { return _vfsOnOffPending; } - void setVfsOnOffSwitchPending(bool pending) { _vfsOnOffPending = pending; } + uint32_t sortPriority() const { return _definition.priority(); } - /** Whether this folder should show selective sync ui */ - bool supportsSelectiveSync() const; + /** + * + * @return The corresponding space object or null + */ + GraphApi::Space *space() const; -signals: +Q_SIGNALS: void syncStateChange(); void syncStarted(); void syncFinished(const SyncResult &result); - void progressInfo(const ProgressInfo &progress); - void newBigFolderDiscovered(const QString &); // A new folder bigger than the threshold was discovered void syncPausedChanged(Folder *, bool paused); void canSyncChanged(); + void spaceChanged(); + void vfsModeChanged(Folder *f, Vfs::Mode newMode); + void displayNameChanged(); + void imageChanged(); // probably part of space changed + void progressUpdate(const ProgressInfo &progress); - /** - * Fires for each change inside this folder that wasn't caused - * by sync activity. - */ - void watchedFileChangedExternally(const QString &path); - -public slots: - - void slotRunEtagJob(); +public Q_SLOTS: /** * terminate the current sync run */ - void slotTerminateSync(); - - // connected to the corresponding signals in the SyncEngine - void slotAboutToRemoveAllFiles(SyncFileItem::Direction, std::function abort); + void slotTerminateSync(const QString &reason); /** * Starts a sync operation @@ -334,17 +382,15 @@ public slots: */ void startSync(); - int slotDiscardDownloadProgress(); - int downloadInfoCount(); + void slotDiscardDownloadProgress(); int slotWipeErrorBlacklist(); - int errorBlackListEntryCount(); /** * Triggered by the folder watcher when a file/dir in this folder * changes. Needs to check whether this change should trigger a new * sync run to be scheduled. */ - void slotWatchedPathChanged(const QString &path, ChangeReason reason); + void slotWatchedPathsChanged(const QSet &paths, ChangeReason reason); /** * Mark a virtual file as being requested for download, and start a sync. @@ -377,62 +423,47 @@ public slots: /// Reloads the excludes, used when changing the user-defined excludes after saving them to disk. bool reloadExcludes(); -private slots: +private Q_SLOTS: void slotSyncStarted(); void slotSyncFinished(bool); - /** Adds a error message that's not tied to a specific item. + /** Adds an error message that's not tied to a specific item. */ void slotSyncError(const QString &message, ErrorCategory category = ErrorCategory::Normal); - void slotTransmissionProgress(const ProgressInfo &pi); void slotItemCompleted(const SyncFileItemPtr &); - void etagRetreived(const QByteArray &, const QDateTime &tp); - void etagRetrievedFromSyncEngine(const QByteArray &, const QDateTime &time); - - void slotEmitFinishedDelayed(); - - void slotNewBigFolderDiscovered(const QString &, bool isExternal); - void slotLogPropagationStart(); - /** Adds this folder to the list of scheduled folders in the - * FolderMan. - */ - void slotScheduleThisFolder(); - - /** Adjust sync result based on conflict data from IssuesWidget. + /** Adjust sync result based on conflict data from SyncErrorWidget. * - * This is pretty awkward, but IssuesWidget just keeps better track + * This is pretty awkward, but SyncErrorWidget just keeps better track * of conflicts across partial local discovery. */ - void slotFolderConflicts(const QString &folder, const QStringList &conflictPaths); + void slotFolderConflicts(Folder *folder, const QStringList &conflictPaths); /** Warn users if they create a file or folder that is selective-sync excluded */ - void warnOnNewExcludedItem(const SyncJournalFileRecord &record, const QStringRef &path); + void warnOnNewExcludedItem(const SyncJournalFileRecord &record, QStringView path); /** Warn users about an unreliable folder watcher */ void slotWatcherUnreliable(const QString &message); - /** Aborts any running sync and blocks it until hydration is finished. - * - * Hydration circumvents the regular SyncEngine and both mustn't be running - * at the same time. - */ - void slotHydrationStarts(); - - /** Unblocks normal sync operation */ - void slotHydrationDone(); - private: - void connectSyncRoot(); - void showSyncResultPopup(); - bool checkLocalPath(); + SyncOptions loadSyncOptions(); - void setSyncOptions(); + /** + * Sets up this folder's folderWatcher if possible. + * + * May be called several times. + */ + // Refactoring todo: I would expect a "registration" function to take an arg for the thing that is supposed to be registered + // I also do not see evidence that this is called "several times" aside from the fact that it's called in relation to vfs::started() + // so if vfs is in play, any time you turn vfs on or off for an existing folder, yes it can be called again. Under normal circumstances + // I don't expect it to be very common for it to get called multiple times, though. Part of the todo is to evaluate whether it makes sense + // to re/build the watcher relative to vfs state as I don't see any connection conceptually (yet, at least). + void registerFolderWatcher(); enum LogStatus { LogStatusRemove, @@ -449,83 +480,60 @@ private slots: void startVfs(); - AccountStatePtr _accountState; + void changeVfsMode(Vfs::Mode newMode); + + QPointer _accountState; FolderDefinition _definition; - QString _canonicalLocalPath; // As returned with QFileInfo:canonicalFilePath. Always ends with "/" SyncResult _syncResult; - QScopedPointer _engine; - QPointer _requestEtagJob; - QByteArray _lastEtag; - QElapsedTimer _timeSinceLastEtagCheckDone; QElapsedTimer _timeSinceLastSyncDone; QElapsedTimer _timeSinceLastSyncStart; QElapsedTimer _timeSinceLastFullLocalDiscovery; - std::chrono::milliseconds _lastSyncDuration; + std::chrono::milliseconds _lastSyncDuration = {}; /// The number of syncs that failed in a row. /// Reset when a sync is successful. - int _consecutiveFailingSyncs; + int _consecutiveFailingSyncs = 0; /// The number of requested follow-up syncs. /// Reset when no follow-up is requested. - int _consecutiveFollowUpSyncs; + int _consecutiveFollowUpSyncs = 0; + + // the journal, vfs and engine are created in the builder, and reparented in the folder ctr. these should NEVER be null if the Folder still exists! + SyncJournalDb *_journal = nullptr; + SyncEngine *_engine = nullptr; - mutable SyncJournalDb _journal; + // When vfs is not in play, vfs_off is the impl used. + Vfs *_vfs = nullptr; + // ehhh...it's not a qobject so this actually makes sense. QScopedPointer _fileLog; QTimer _scheduleSelfTimer; /** - * When the same local path is synced to multiple accounts, only one - * of them can be stored in the settings in a way that's compatible - * with old clients that don't support it. This flag marks folders - * that shall be written in a backwards-compatible way, by being set - * on the *first* Folder instance that was configured for each local - * path. - */ - bool _saveBackwardsCompatible = false; - - /** Whether the folder should be saved in that settings group - * - * If it was read from there it had virtual files enabled at some - * point and might still have db entries or suffix-virtual files even - * if they are disabled right now. This flag ensures folders that - * were in that group once never go back. - */ - bool _saveInFoldersWithPlaceholders = false; - - /** Whether a vfs mode switch is pending - * - * When the user desires that vfs be switched on/off but it hasn't been - * executed yet (syncs are still running), some options should be hidden, - * disabled or different. - */ - bool _vfsOnOffPending = false; - - /** - * Setting up vfs is a async operation + * Setting up vfs can be an async operation which is triggered by vfs::startImpl + * this bool is set to true once vfs is fully started as notified via the started() signal + * when a "real" vfs impl is in play, started() is emitted after the async setup is complete + * when vfs is effectively "off", started() is emitted immediately by startImpl + * main point is that even when vfs is not actually in play we pretend it's "started" -> _vfsIsReady will be true */ bool _vfsIsReady = false; + // does the folder have a corresponding space on the server? folderman will update the value based on info we get + // from spaces manager. + bool _available = true; + /** * Watches this folder's local directory for changes. * - * Created by registerFolderWatcher(), triggers slotWatchedPathChanged() + * Created in registerFolderWatcher(), triggers slotWatchedPathsChanged() */ - QScopedPointer _folderWatcher; + FolderWatcher *_folderWatcher = nullptr; /** * Keeps track of locally dirty files so we can skip local discovery sometimes. */ - QScopedPointer _localDiscoveryTracker; - - /** - * The vfs mode instance (created by plugin) to use. Never null. - */ - QSharedPointer _vfs; + LocalDiscoveryTracker *_localDiscoveryTracker = nullptr; }; } - -#endif diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 65046642e57..0a298a7f5a5 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -13,67 +13,45 @@ */ #include "folderman.h" + +#include "accessmanager.h" +#include "account.h" +#include "accountmanager.h" +#include "accountstate.h" +#include "common/asserts.h" #include "configfile.h" #include "folder.h" +#include "foldermanagement/folderbuilder.h" +#include "foldermanagement/foldermanagementutils.h" +#include "gui/networkinformation.h" +#include "guiutility.h" +#include "libsync/syncengine.h" +#include "lockwatcher.h" +#include "scheduling/syncscheduler.h" +#include "socketapi/socketapi.h" +#include "spacesmanager.h" #include "syncresult.h" #include "theme.h" -#include "socketapi/socketapi.h" -#include "account.h" -#include "accountstate.h" -#include "accountmanager.h" -#include "filesystem.h" -#include "lockwatcher.h" -#include "common/asserts.h" -#include -#ifdef Q_OS_MAC -#include + +#ifdef Q_OS_WIN +#include "common/utility_win.h" #endif #include -#include #include -#include #include +#include using namespace std::chrono; using namespace std::chrono_literals; namespace { -/* - * [Accounts] - * 0\version=1 - */ -auto versionC() -{ - return QStringLiteral("version"); } -/* - * Folders with a version > maxFoldersVersion will be removed - * After the user was prompted for consent. - */ -constexpr int maxFoldersVersion = 1; - -int numberOfSyncJournals(const QString &path) -{ - return QDir(path).entryList({ QStringLiteral(".sync_*.db"), QStringLiteral("._sync_*.db") }, QDir::Hidden | QDir::Files).size(); -} +namespace OCC { -QString makeLegacyDbName(const OCC::FolderDefinition &def, const OCC::AccountPtr &account) -{ - // ensure https://demo.owncloud.org/ matches https://demo.owncloud.org - // the empty path was the legacy formating before 2.9 - auto legacyUrl = account->url(); - if (legacyUrl.path() == QLatin1String("/")) { - legacyUrl.setPath(QString()); - } - const QString key = QStringLiteral("%1@%2:%3").arg(account->credentials()->user(), legacyUrl.toString(), def.targetPath); - return OCC::SyncJournalDb::makeDbName(def.localPath, QString::fromUtf8(QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Md5).left(6).toHex())); -} -} -namespace OCC { Q_LOGGING_CATEGORY(lcFolderMan, "gui.folder.manager", QtInfoMsg) void TrayOverallStatusResult::addResult(Folder *f) @@ -87,42 +65,20 @@ void TrayOverallStatusResult::addResult(Folder *f) lastSyncDone = time; } - const auto status = f->syncPaused() ? SyncResult::Paused : f->syncResult().status(); - switch (status) { - case SyncResult::Paused: - Q_FALLTHROUGH(); - case SyncResult::SyncAbortRequested: - // Problem has a high enum value but real problems and errors - // take precedence - if (_overallStatus.status() < SyncResult::Success) { - _overallStatus.setStatus(status); - } - break; - case SyncResult::Success: - Q_FALLTHROUGH(); - case SyncResult::NotYetStarted: - Q_FALLTHROUGH(); - case SyncResult::SyncPrepare: - Q_FALLTHROUGH(); - case SyncResult::SyncRunning: - if (_overallStatus.status() < SyncResult::Problem) { - _overallStatus.setStatus(status); - } - break; - case SyncResult::Undefined: - if (_overallStatus.status() < SyncResult::Problem) { - _overallStatus.setStatus(SyncResult::Problem); - } - break; - case SyncResult::Problem: - Q_FALLTHROUGH(); - case SyncResult::Error: - Q_FALLTHROUGH(); - case SyncResult::SetupError: - if (_overallStatus.status() < status) { - _overallStatus.setStatus(status); - } - break; + // use status of the folder + auto status = f->syncPaused() || NetworkInformation::instance()->isBehindCaptivePortal() || NetworkInformation::instance()->isMetered() + ? SyncResult::Paused + : f->syncResult().status(); + // in case the linked account is not connected -> we are offline + if (!f->isConnected()) { + status = SyncResult::Offline; + } + // undefined state means we are in trouble + if (status == SyncResult::Undefined) { + status = SyncResult::Problem; + } + if (status > _overallStatus.status()) { + _overallStatus.setStatus(status); } } @@ -133,98 +89,92 @@ const SyncResult &TrayOverallStatusResult::overallStatus() const FolderMan *FolderMan::_instance = nullptr; -FolderMan::FolderMan(QObject *parent) - : QObject(parent) - , _currentSyncFolder(nullptr) - , _syncEnabled(true) - , _lockWatcher(new LockWatcher) -#ifdef Q_OS_WIN - , _navigationPaneHelper(this) -#endif - , _appRestartRequired(false) +FolderMan::FolderMan() + : _lockWatcher(new LockWatcher) + , _scheduler(new SyncScheduler(this)) + , _socketApi(new SocketApi) { - OC_ASSERT(!_instance); - _instance = this; - - _socketApi.reset(new SocketApi); - - // Set the remote poll interval fixed to 10 seconds. - // That does not mean that it polls every 10 seconds, but it checks every 10 secs - // if one of the folders is due to sync. - _etagPollTimer.setInterval(1000); - QObject::connect(&_etagPollTimer, &QTimer::timeout, this, &FolderMan::slotEtagPollTimerTimeout); - _etagPollTimer.start(); - - _startScheduledSyncTimer.setSingleShot(true); - connect(&_startScheduledSyncTimer, &QTimer::timeout, - this, &FolderMan::slotStartScheduledFolderSync); + auto settings = ConfigFile::makeQSettings(); + if (settings.contains(IgnoreHiddenFilesKey)) { + _ignoreHiddenFiles = settings.value(IgnoreHiddenFilesKey).toBool(); + } else { + settings.setValue(IgnoreHiddenFilesKey, _ignoreHiddenFiles); // defaults to true + } - _timeScheduler.setInterval(5000); - _timeScheduler.setSingleShot(false); - connect(&_timeScheduler, &QTimer::timeout, - this, &FolderMan::slotScheduleFolderByTime); - _timeScheduler.start(); + if (settings.contains(MoveToTrashKey)) { + _moveToTrash = settings.value(MoveToTrashKey).toBool(); + } else { + _moveToTrash = Theme::instance()->moveToTrashDefaultValue(); + settings.setValue(MoveToTrashKey, _moveToTrash); + } - connect(AccountManager::instance(), &AccountManager::accountRemoved, - this, &FolderMan::slotRemoveFoldersForAccount); + connect(AccountManager::instance(), &AccountManager::accountRemoved, this, &FolderMan::slotRemoveFoldersForAccount); - connect(_lockWatcher.data(), &LockWatcher::fileUnlocked, - this, &FolderMan::slotWatchedFileUnlocked); + connect(_lockWatcher.data(), &LockWatcher::fileUnlocked, this, [this](const QString &path, FileSystem::LockMode) { + if (Folder *f = folderForPath(path)) { + // Treat this equivalently to the file being reported by the file watcher + f->slotWatchedPathsChanged({path}, Folder::ChangeReason::UnLock); + } + }); } FolderMan *FolderMan::instance() { + Q_ASSERT(_instance); return _instance; } FolderMan::~FolderMan() { - qDeleteAll(_folderMap); + unloadAndDeleteAllFolders(); _instance = nullptr; } -const QMap &FolderMan::map() const +QList FolderMan::folders() const { - return _folderMap; + QList allFolders; + QList accountIds = _folders.keys(); + for (QUuid id : std::as_const(accountIds)) + allFolders.append(_folders[id].values()); + + return allFolders; } -void FolderMan::unloadFolder(Folder *f) +QList FolderMan::foldersForAccount(const QUuid &accountId) { - Q_ASSERT(f); - - _socketApi->slotUnregisterPath(f->alias()); - - _folderMap.remove(f->alias()); - - disconnect(f, &Folder::syncStarted, - this, &FolderMan::slotFolderSyncStarted); - disconnect(f, &Folder::syncFinished, - this, &FolderMan::slotFolderSyncFinished); - disconnect(f, &Folder::syncStateChange, - this, &FolderMan::slotForwardFolderSyncStateChange); - disconnect(f, &Folder::syncPausedChanged, - this, &FolderMan::slotFolderSyncPaused); - disconnect(&f->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged, - _socketApi.data(), &SocketApi::broadcastStatusPushMessage); - disconnect(f, &Folder::watchedFileChangedExternally, - &f->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::slotPathTouched); - - f->syncEngine().disconnect(f); + if (_folders.contains(accountId)) + return _folders[accountId].values(); + return {}; } void FolderMan::unloadAndDeleteAllFolders() { + if (_folders.isEmpty()) + return; + + // notify interested parties *before* the folders are actually deleted - this is important in eg etagwatcher and the activity + // tabs because they need the original folder pointers to update themselves. todo: review the use of the folder pointers in these + // classes + emit folderListChanged(QUuid(), {}); + + // save all folder definitions using a single settings instance to avoid file write/read churn + // the settings will be synced to file when the instance goes out of scope, at the latest + QSettings settings = ConfigFile::makeQSettings(); + settings.beginGroup("Accounts"); // clear the list of existing folders. - const auto folders = std::move(_folderMap); - for (auto *folder : folders) { - _socketApi->slotUnregisterPath(folder->alias()); - folder->deleteLater(); - } - _lastSyncFolder = nullptr; - _currentSyncFolder = nullptr; - _scheduledFolders.clear(); - emit folderListChanged(_folderMap); - emit scheduleQueueChanged(); + QList accountIds = _folders.keys(); + for (auto id : std::as_const(accountIds)) { + for (Folder *folder : std::as_const(_folders[id])) { + saveFolder(folder, settings); + _socketApi->slotUnregisterPath(folder); + folder->deleteLater(); + } + _folders[id].clear(); + emit unsyncedSpaceCountChanged(id, 0, 0); + } + _folders.clear(); + _unsyncedSpaces.clear(); + Q_ASSERT(_folders.isEmpty()); } void FolderMan::registerFolderWithSocketApi(Folder *folder) @@ -236,379 +186,332 @@ void FolderMan::registerFolderWithSocketApi(Folder *folder) // register the folder with the socket API if (folder->canSync()) - _socketApi->slotRegisterPath(folder->alias()); + _socketApi->slotRegisterPath(folder); } -int FolderMan::setupFolders() +std::optional FolderMan::setupFoldersFromConfig() { + setSyncEnabled(false); + + // todo: #9 unloadAndDeleteAllFolders(); - QStringList skipSettingsKeys, deleteSettingsKeys; - backwardMigrationSettingsKeys(&deleteSettingsKeys, &skipSettingsKeys); - // deleteKeys should already have been deleted on application startup. - // We ignore them here just in case. - skipSettingsKeys += deleteSettingsKeys; - - auto settings = ConfigFile::settingsWithGroup(QLatin1String("Accounts")); - const auto &accountsWithSettings = settings->childGroups(); - if (accountsWithSettings.isEmpty()) { - int r = setupFoldersMigration(); - if (r > 0) { - AccountManager::instance()->save(false); // don't save credentials, they had not been loaded from keychain - } - return r; - } + auto settings = ConfigFile::makeQSettings(); + settings.beginGroup("Accounts"); + const auto &accountsWithSettings = settings.childGroups(); qCInfo(lcFolderMan) << "Setup folders from settings file"; for (const auto &account : AccountManager::instance()->accounts()) { - const auto id = account->account()->id(); - if (!accountsWithSettings.contains(id)) { + const auto accountIndex = account->account()->groupIndex(); + Q_ASSERT(!accountIndex.isEmpty()); + if (!accountsWithSettings.contains(accountIndex)) { + qCWarning(lcFolderMan) << "Account id from account manager is missing from Config"; continue; } - settings->beginGroup(id); - - // The "backwardsCompatible" flag here is related to migrating old - // database locations - auto process = [&](const QString &groupName, bool backwardsCompatible, bool foldersWithPlaceholders) { - settings->beginGroup(groupName); - if (skipSettingsKeys.contains(settings->group())) { - // Should not happen: bad container keys should have been deleted - qCWarning(lcFolderMan) << "Folder structure" << groupName << "is too new, ignoring"; - } else { - setupFoldersHelper(*settings, account, skipSettingsKeys, backwardsCompatible, foldersWithPlaceholders); - } - settings->endGroup(); - }; - process(QStringLiteral("Folders"), true, false); - - // See Folder::saveToSettings for details about why these exists. - process(QStringLiteral("Multifolders"), false, false); - process(QStringLiteral("FoldersWithPlaceholders"), false, true); - - settings->endGroup(); // + // Process config for this account. + if (!addFoldersFromConfigByAccount(settings, account)) { + return {}; + } } + settings.endGroup(); // "Accounts" - emit folderListChanged(_folderMap); + if (!_folders.empty()) { + // couldn't this be "schedule new folders"? + scheduleAllFolders(); + setSyncEnabled(true); + } - return _folderMap.size(); + return _folders.size(); } -void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account, const QStringList &ignoreKeys, bool backwardsCompatible, bool foldersWithPlaceholders) +bool FolderMan::addFoldersFromConfigByAccount(QSettings &settings, AccountState *account) { + if (!account || !account->account() || !account->account()->spacesManager()) + return false; + + settings.beginGroup(QStringLiteral("%1/Folders").arg(account->account()->groupIndex())); + const auto &childGroups = settings.childGroups(); for (const auto &folderAlias : childGroups) { - // Skip folders with too-new version settings.beginGroup(folderAlias); - if (ignoreKeys.contains(settings.group())) { - qCInfo(lcFolderMan) << "Folder" << folderAlias << "is too new, ignoring"; - _additionalBlockedFolderAliases.insert(folderAlias); - settings.endGroup(); + + FolderDefinition folderDefinition = FolderDefinition::load(settings, folderAlias.toUtf8()); + // this should NEVER happen + Q_ASSERT(!folderDefinition.id().isEmpty()); + + // this can only happen when loading from config + // does not belong in general addFolder routine + if (SyncJournalDb::dbIsTooNewForClient(folderDefinition.absoluteJournalPath())) { + return false; + } + + Folder *folder = addFolder(account, folderDefinition); + if (!folder) { + // todo: decide if we should actively remove the folder data from the config! I think we should but let's see continue; } - settings.endGroup(); - FolderDefinition folderDefinition; - settings.beginGroup(folderAlias); - if (FolderDefinition::load(settings, folderAlias, &folderDefinition)) { - const auto defaultJournalPath = [&account, folderDefinition] { - // if we would have booth the 2.9.0 file name and the lagacy file - // with the md5 infix we prefer the 2.9.0 version - const QDir info(folderDefinition.localPath); - const QString defaultPath = SyncJournalDb::makeDbName(folderDefinition.localPath); - if (info.exists(defaultPath)) { - return defaultPath; - } - // 2.6 - QString legacyPath = makeLegacyDbName(folderDefinition, account->account()); - if (info.exists(legacyPath)) { - return legacyPath; - } - // pre 2.6 - legacyPath.replace(QLatin1String(".sync_"), QLatin1String("._sync_")); - if (info.exists(legacyPath)) { - return legacyPath; - } - return defaultPath; - }(); - - // Migration: Old settings don't have journalPath - if (folderDefinition.journalPath.isEmpty()) { - folderDefinition.journalPath = defaultJournalPath; - } + // this is checked before the spaces manager has loaded the spaces so it will always be null. + // this is a bit odd for now as in the gui it shows the unavailable state until after login. I think + // in most cases this will be ok as it's not a common workflow, we just have it on demo + // if we don't set this here a sync will be attempted, then it just shows as failed. + folder->setAvailable(folder->space() != nullptr); - // Migration: ._ files sometimes can't be created. - // So if the configured journalPath has a dot-underscore ("._sync_*.db") - // but the current default doesn't have the underscore, switch to the - // new default if no db exists yet. - if (folderDefinition.journalPath.startsWith("._sync_") - && defaultJournalPath.startsWith(".sync_") - && !QFile::exists(folderDefinition.absoluteJournalPath())) { - folderDefinition.journalPath = defaultJournalPath; - } + settings.endGroup(); // folderAlias + } + settings.endGroup(); // accountId\Folders - // Migration: If an old .csync_journal.db is found, move it to the new name. - if (backwardsCompatible) { - SyncJournalDb::maybeMigrateDb(folderDefinition.localPath, folderDefinition.absoluteJournalPath()); - } + GraphApi::SpacesManager *spaceMan = account->account()->spacesManager(); + _scheduler->connectSpacesManager(spaceMan); + connect(spaceMan, &GraphApi::SpacesManager::spacesAdded, this, &FolderMan::onSpacesAdded); + connect(spaceMan, &GraphApi::SpacesManager::spacesRemoved, this, &FolderMan::onSpacesRemoved); - auto vfs = createVfsFromPlugin(folderDefinition.virtualFilesMode); - if (!vfs) { - // TODO: Must do better error handling - qFatal("Could not load plugin"); - } + QUuid accountId = account->account()->uuid(); + emit folderListChanged(accountId, _folders[accountId].values()); + // at this stage the spaces manager tends to be empty + int spaceCount = spaceMan->spacesCount(); + int unsyncedCount = _unsyncedSpaces.contains(accountId) ? _unsyncedSpaces[accountId].count() : 0; + emit unsyncedSpaceCountChanged(accountId, unsyncedCount, spaceCount); - Folder *f = addFolderInternal(std::move(folderDefinition), account.data(), std::move(vfs)); - if (f) { - // Migrate the old "usePlaceholders" setting to the root folder pin state - if (settings.value(versionC(), 1).toInt() == 1 - && settings.value(QLatin1String("usePlaceholders"), false).toBool()) { - qCInfo(lcFolderMan) << "Migrate: From usePlaceholders to PinState::OnlineOnly"; - f->setRootPinState(PinState::OnlineOnly); - } - - // Migration: Mark folders that shall be saved in a backwards-compatible way - if (backwardsCompatible) - f->setSaveBackwardsCompatible(true); - if (foldersWithPlaceholders) - f->setSaveInFoldersWithPlaceholders(); - - // save possible changes from the migration - f->saveToSettings(); - - scheduleFolder(f); - emit folderSyncStateChange(f); - } - } - settings.endGroup(); + return true; +} + +void FolderMan::setUpInitialSyncFolders(AccountState *accountState, bool useVfs) +{ + if (accountState && accountState->account() && accountState->account()->spacesManager()) { + GraphApi::SpacesManager *spaceMan = accountState->account()->spacesManager(); + + // this replaces the old use of SpacesManager::checkReady which was overcomplicated. + // short explanation: the spaces manager has to wait for the account credentials to be valid before it can load its spaces + // folderman can't "force" that condiiton so it just has to wait for that setup to complete + // normally the spaces manager should always be ready at this point, but if for whatever reason it's not, catch it when it is + if (!spaceMan->isReady()) + connect(spaceMan, &GraphApi::SpacesManager::ready, this, [this, accountState, useVfs] { loadSpacesAndCreateFolders(accountState, useVfs); }); + else + loadSpacesAndCreateFolders(accountState, useVfs); } } -int FolderMan::setupFoldersMigration() +void FolderMan::loadSpacesAndCreateFolders(AccountState *accountState, bool useVfs) { - _folderConfigPath = ConfigFile::configPath() + QLatin1String("folders"); + if (!accountState || !accountState->account()) + return; + + GraphApi::SpacesManager *spacesMgr = accountState->account()->spacesManager(); + if (!spacesMgr) + return; - qCInfo(lcFolderMan) << "Setup folders from " << _folderConfigPath << "(migration)"; + auto spaces = spacesMgr->spaces(); - QDir dir(_folderConfigPath); - //We need to include hidden files just in case the alias starts with '.' - dir.setFilter(QDir::Files | QDir::Hidden); - const auto &list = dir.entryList(); + if (!spaces.isEmpty()) { + setSyncEnabled(false); - // Normally there should be only one account when migrating. - AccountState *accountState = AccountManager::instance()->accounts().value(0).data(); - for (const auto &alias : list) { - Folder *f = setupFolderFromOldConfigFile(alias, accountState); - if (f) { - scheduleFolder(f); - emit folderSyncStateChange(f); + QSettings settings = ConfigFile::makeQSettings(); + settings.beginGroup("Accounts"); + + // prepare the root - reality check this as I think the user can change this from default? Yes they can but not for auto-loaded + // folders eg on account creation, which is what is happening here. + const QString localDir(accountState->account()->defaultSyncRoot()); + if (!FolderManagementUtils::prepareFolder(localDir)) { + return; } - } - emit folderListChanged(_folderMap); + Utility::setupFavLink(localDir); - // return the number of valid folders. - return _folderMap.size(); -} + for (auto *space : std::as_const(spaces)) { + FolderDefinition folderDef(space->webDavUrl(), space->id(), space->displayName()); -void FolderMan::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys) -{ - auto settings = ConfigFile::settingsWithGroup(QLatin1String("Accounts")); - - auto processSubgroup = [&](const QString &name) { - settings->beginGroup(name); - const int foldersVersion = settings->value(versionC(), 1).toInt(); - if (foldersVersion <= maxFoldersVersion) { - const auto &childGroups = settings->childGroups(); - for (const auto &folderAlias : childGroups) { - settings->beginGroup(folderAlias); - const int folderVersion = settings->value(versionC(), 1).toInt(); - if (folderVersion > FolderDefinition::maxSettingsVersion()) { - ignoreKeys->append(settings->group()); - } - settings->endGroup(); + folderDef.setPriority(space->sortPriority()); + + QString localPath = findGoodPathForNewSyncFolder(localDir, folderDef.displayName(), NewFolderType::SpacesFolder, space->accountId()); + folderDef.setLocalPath(localPath); + folderDef.setTargetPath({}); + + Folder *folder = addFolderFromScratch(accountState, std::move(folderDef), useVfs); + if (folder) { + saveFolder(folder, settings); + // oof - todo #50 + _scheduler->enqueueFolder(folder, SyncScheduler::Priority::High); } - } else { - deleteKeys->append(settings->group()); } - settings->endGroup(); - }; + setSyncEnabled(true); + _scheduler->connectSpacesManager(spacesMgr); + // initial load is complete, now we just wait for spaces to be added or removed incrementally + connect(spacesMgr, &GraphApi::SpacesManager::spacesAdded, this, &FolderMan::onSpacesAdded); + connect(spacesMgr, &GraphApi::SpacesManager::spacesRemoved, this, &FolderMan::onSpacesRemoved); - const auto &childGroups = settings->childGroups(); - for (const auto &accountId : childGroups) { - settings->beginGroup(accountId); - processSubgroup("Folders"); - processSubgroup("Multifolders"); - processSubgroup("FoldersWithPlaceholders"); - settings->endGroup(); + QUuid id = accountState->account()->uuid(); + emit folderListChanged(id, _folders[id].values()); + int spaceCount = spacesMgr->spacesCount(); + emit unsyncedSpaceCountChanged(id, _unsyncedSpaces[id].count(), spaceCount); } } -bool FolderMan::ensureJournalGone(const QString &journalDbFile) +void FolderMan::setUpInitialSpaces(AccountState *accountState) { - // remove the old journal file - while (QFile::exists(journalDbFile) && !QFile::remove(journalDbFile)) { - qCWarning(lcFolderMan) << "Could not remove old db file at" << journalDbFile; - int ret = QMessageBox::warning(nullptr, tr("Could not reset folder state"), - tr("An old sync journal '%1' was found, " - "but could not be removed. Please make sure " - "that no application is currently using it.") - .arg(QDir::fromNativeSeparators(QDir::cleanPath(journalDbFile))), - QMessageBox::Retry | QMessageBox::Abort); - if (ret == QMessageBox::Abort) { - return false; - } + if (accountState && accountState->account() && accountState->account()->spacesManager()) { + GraphApi::SpacesManager *spaceMan = accountState->account()->spacesManager(); + + // this replaces the old use of SpacesManager::checkReady which was overcomplicated. + // short explanation: the spaces manager has to wait for the account credentials to be valid before it can load its spaces + // folderman can't "force" that condiiton so it just has to wait for that setup to complete + // normally the spaces manager should always be ready at this point, but if for whatever reason it's not, catch it when it is + if (!spaceMan->isReady()) + connect(spaceMan, &GraphApi::SpacesManager::ready, this, [this, accountState] { loadSpacesAlone(accountState); }); + else + loadSpacesAlone(accountState); } - return true; } -#define SLASH_TAG QLatin1String("__SLASH__") -#define BSLASH_TAG QLatin1String("__BSLASH__") -#define QMARK_TAG QLatin1String("__QMARK__") -#define PERCENT_TAG QLatin1String("__PERCENT__") -#define STAR_TAG QLatin1String("__STAR__") -#define COLON_TAG QLatin1String("__COLON__") -#define PIPE_TAG QLatin1String("__PIPE__") -#define QUOTE_TAG QLatin1String("__QUOTE__") -#define LT_TAG QLatin1String("__LESS_THAN__") -#define GT_TAG QLatin1String("__GREATER_THAN__") -#define PAR_O_TAG QLatin1String("__PAR_OPEN__") -#define PAR_C_TAG QLatin1String("__PAR_CLOSE__") - -QString FolderMan::escapeAlias(const QString &alias) +void FolderMan::loadSpacesAlone(AccountState *accountState) { - QString a(alias); - - a.replace(QLatin1Char('/'), SLASH_TAG); - a.replace(QLatin1Char('\\'), BSLASH_TAG); - a.replace(QLatin1Char('?'), QMARK_TAG); - a.replace(QLatin1Char('%'), PERCENT_TAG); - a.replace(QLatin1Char('*'), STAR_TAG); - a.replace(QLatin1Char(':'), COLON_TAG); - a.replace(QLatin1Char('|'), PIPE_TAG); - a.replace(QLatin1Char('"'), QUOTE_TAG); - a.replace(QLatin1Char('<'), LT_TAG); - a.replace(QLatin1Char('>'), GT_TAG); - a.replace(QLatin1Char('['), PAR_O_TAG); - a.replace(QLatin1Char(']'), PAR_C_TAG); - return a; -} + if (!accountState || !accountState->account()) + return; -SocketApi *FolderMan::socketApi() -{ - return this->_socketApi.data(); -} + GraphApi::SpacesManager *spacesMgr = accountState->account()->spacesManager(); + if (!spacesMgr) + return; -QString FolderMan::unescapeAlias(const QString &alias) -{ - QString a(alias); - - a.replace(SLASH_TAG, QLatin1String("/")); - a.replace(BSLASH_TAG, QLatin1String("\\")); - a.replace(QMARK_TAG, QLatin1String("?")); - a.replace(PERCENT_TAG, QLatin1String("%")); - a.replace(STAR_TAG, QLatin1String("*")); - a.replace(COLON_TAG, QLatin1String(":")); - a.replace(PIPE_TAG, QLatin1String("|")); - a.replace(QUOTE_TAG, QLatin1String("\"")); - a.replace(LT_TAG, QLatin1String("<")); - a.replace(GT_TAG, QLatin1String(">")); - a.replace(PAR_O_TAG, QLatin1String("[")); - a.replace(PAR_C_TAG, QLatin1String("]")); - - return a; + auto spaces = spacesMgr->spaces(); + + _scheduler->connectSpacesManager(spacesMgr); + + QUuid id = accountState->account()->uuid(); + int spaceCount = spacesMgr->spacesCount(); + onSpacesAdded(id, spaces, spaceCount); + + // initial load is complete, now we just wait for spaces to be added or removed incrementally + connect(spacesMgr, &GraphApi::SpacesManager::spacesAdded, this, &FolderMan::onSpacesAdded); + connect(spacesMgr, &GraphApi::SpacesManager::spacesRemoved, this, &FolderMan::onSpacesRemoved); } -// filename is the name of the file only, it does not include -// the configuration directory path -// WARNING: Do not remove this code, it is used for predefined/automated deployments (2016) -Folder *FolderMan::setupFolderFromOldConfigFile(const QString &file, AccountState *accountState) + +void FolderMan::onSpacesAdded(const QUuid &accountId, QList spaces, int totalSpaceCount) { - Folder *folder = nullptr; + // not a fan but the spaces manager doesn't have the account state, just the account. At the point that we correct the relationship + // so that account "owns" account state, the spacesManager can include the account pointer in the signal to eliminate + // this "need" for the singleton + AccountState *accountState = AccountManager::instance()->accountState(accountId); + if (!accountState || !accountState->account()) + return; - qCInfo(lcFolderMan) << " ` -> setting up:" << file; - QString escapedAlias(file); - // check the unescaped variant (for the case when the filename comes out - // of the directory listing). If the file does not exist, escape the - // file and try again. + QSet newUnsyncedSpaces(spaces.cbegin(), spaces.cend()); -#ifdef Q_OS_WIN - Utility::NtfsPermissionLookupRAII ntfs_perm; -#endif - QFileInfo cfgFile(_folderConfigPath, file); + if (!_unsyncedSpaces.contains(accountId)) { + _unsyncedSpaces.insert(accountId, {}); + } - if (!cfgFile.exists()) { - // try the escaped variant. - escapedAlias = escapeAlias(file); - cfgFile.setFile(_folderConfigPath, escapedAlias); + for (auto space : std::as_const(spaces)) { + // be sure to eliminate any spaces that are already in the unsynced list to prevent auto-adding spaces that the + // user explicitly removed from sync + if (_unsyncedSpaces.contains(accountId) && _unsyncedSpaces[accountId].contains(space->id())) + newUnsyncedSpaces.remove(space); + else if (_folders.contains(accountId) && _folders[accountId].contains(space->id())) { + newUnsyncedSpaces.remove(space); + _folders[accountId][space->id()]->setAvailable(true); + } } - if (!cfgFile.isReadable()) { - qCWarning(lcFolderMan) << "Cannot read folder definition for alias " << cfgFile.filePath(); - return folder; + + if (newUnsyncedSpaces.isEmpty()) { + // this normally happens when the folderman has loaded folders from config - the folders are created + // before the spaces manager is ready so space count at that stage is zero, we catch the change here instead + emit unsyncedSpaceCountChanged(accountId, _unsyncedSpaces[accountId].count(), totalSpaceCount); + return; } - QSettings settings(_folderConfigPath + QLatin1Char('/') + escapedAlias, QSettings::IniFormat); - qCInfo(lcFolderMan) << " -> file path: " << settings.fileName(); + if (Theme::instance()->syncNewlyDiscoveredSpaces()) { + // Refactoring todo: why was this scheduled for "later" on the main event loop? aren't we already there? + // where does this slot run if not on the main thread? + // what needs to be processed "before" this loading routine that requires scheduling it for later? + // if anything we should consider running the loading routines in a worker thread to avoid *blocking* the main + // event loop. + // QTimer::singleShot(0, this, [this, account, unsycnedSpaces]() { + for (GraphApi::Space *newSpace : newUnsyncedSpaces) { + qCInfo(lcFolderMan) << "Adding sync connection for newly discovered space" << newSpace->displayName(); + + const QString localDir(accountState->account()->defaultSyncRoot()); + const QString folderName = findGoodPathForNewSyncFolder(localDir, newSpace->displayName(), NewFolderType::SpacesFolder, accountId); + + // I'm recycling the routine used in the folder wizard to add a new space from gui for now. No I don't + // like it but I am out of steam trying to collate all these barely different methods of creating a folder + // into something sensible right now - I will come back to it + SyncConnectionDescription fwr; + fwr.davUrl = newSpace->webDavUrl(); + fwr.spaceId = newSpace->id(); + fwr.localPath = folderName; + fwr.displayName = newSpace->displayName(); + fwr.useVirtualFiles = Utility::isWindows() ? VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi) : false; + fwr.sortPriority = newSpace->sortPriority(); + + addFolderFromGui(accountState, fwr); + } + newUnsyncedSpaces.clear(); + } else { + for (auto *sp : std::as_const(newUnsyncedSpaces)) + _unsyncedSpaces[accountId].insert(sp->id(), sp); + } + + emit unsyncedSpaceCountChanged(accountId, _unsyncedSpaces[accountId].count(), totalSpaceCount); +} + +void FolderMan::onSpacesRemoved(const QUuid &accountId, QList spaceIds, int totalSpaceCount) +{ + if (!_unsyncedSpaces.contains(accountId) && !_folders.contains(accountId)) + return; - // Check if the filename is equal to the group setting. If not, use the group - // name as an alias. - QStringList groups = settings.childGroups(); + for (const QString &id : std::as_const(spaceIds)) { + _unsyncedSpaces[accountId].remove(id); - if (!groups.contains(escapedAlias) && groups.count() > 0) { - escapedAlias = groups.first(); + if (_folders[accountId].contains(id)) { + Folder *f = _folders[accountId][id]; + f->setAvailable(false); + } } - settings.beginGroup(escapedAlias); // read the group with the same name as the file which is the folder alias + emit unsyncedSpaceCountChanged(accountId, _unsyncedSpaces[accountId].count(), totalSpaceCount); +} - QString path = settings.value(QLatin1String("localPath")).toString(); - QString backend = settings.value(QLatin1String("backend")).toString(); - QString targetPath = settings.value(QLatin1String("targetPath")).toString(); - bool paused = settings.value(QLatin1String("paused"), false).toBool(); - // QString connection = settings.value( QLatin1String("connection") ).toString(); - QString alias = unescapeAlias(escapedAlias); - if (backend.isEmpty() || backend != QLatin1String("owncloud")) { - qCWarning(lcFolderMan) << "obsolete configuration of type" << backend; - return nullptr; +bool FolderMan::ensureJournalGone(const QString &journalDbFile) +{ + // remove the old journal file + while (QFile::exists(journalDbFile) && !QFile::remove(journalDbFile)) { + qCWarning(lcFolderMan) << "Could not remove old db file at" << journalDbFile; + int ret = QMessageBox::warning(nullptr, tr("Could not reset folder state"), + tr("An old sync journal '%1' was found, " + "but could not be removed. Please make sure " + "that no application is currently using it.") + .arg(QDir::fromNativeSeparators(QDir::cleanPath(journalDbFile))), + QMessageBox::Retry | QMessageBox::Abort); + if (ret == QMessageBox::Abort) { + return false; + } } + return true; +} - // cut off the leading slash, oCUrl always has a trailing. - if (targetPath.startsWith(QLatin1Char('/'))) { - targetPath.remove(0, 1); - } +bool FolderMan::ensureFilesystemSupported(const FolderDefinition &folderDefinition) +{ + if (Utility::isMac()) { + QString filesystemType = FileSystem::fileSystemForPath(folderDefinition.localPath()); + if (filesystemType != QStringLiteral("apfs")) { + QMessageBox::warning(nullptr, tr("Unsupported filesystem"), tr("On macOS, only the Apple File System is supported."), QMessageBox::Ok); - if (!accountState) { - qCCritical(lcFolderMan) << "can't create folder without an account"; - return nullptr; + return false; + } } - FolderDefinition folderDefinition; - folderDefinition.alias = alias; - folderDefinition.localPath = path; - folderDefinition.targetPath = targetPath; - folderDefinition.paused = paused; - folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles(); - - folder = addFolderInternal(folderDefinition, accountState, std::make_unique()); - if (folder) { - QStringList blackList = settings.value(QLatin1String("blackList")).toStringList(); - if (!blackList.empty()) { - //migrate settings - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList); - settings.remove(QLatin1String("blackList")); - // FIXME: If you remove this codepath, you need to provide another way to do - // this via theme.h or the normal FolderMan::setupFolders - } + return true; +} - folder->saveToSettings(); - } - qCInfo(lcFolderMan) << "Migrated!" << folder; - settings.sync(); - return folder; +SocketApi *FolderMan::socketApi() +{ + return _socketApi.get(); } -void FolderMan::slotFolderSyncPaused(Folder *f, bool paused) +void FolderMan::slotFolderSyncPauseChanged(Folder *f, bool paused) { if (!f) { qCCritical(lcFolderMan) << "slotFolderSyncPaused called with empty folder"; @@ -617,7 +520,9 @@ void FolderMan::slotFolderSyncPaused(Folder *f, bool paused) if (!paused) { _disabledFolders.remove(f); - scheduleFolder(f); + if (f->canSync()) { + scheduler()->enqueueFolder(f); + } } else { _disabledFolders.insert(f); } @@ -628,33 +533,37 @@ void FolderMan::slotFolderCanSyncChanged() Folder *f = qobject_cast(sender()); OC_ASSERT(f); if (f->canSync()) { - _socketApi->slotRegisterPath(f->alias()); + _socketApi->slotRegisterPath(f); } else { - _socketApi->slotUnregisterPath(f->alias()); + _socketApi->slotUnregisterPath(f); } } -Folder *FolderMan::folder(const QString &alias) +Folder *FolderMan::folder(const QUuid &accountId, const QString &spaceId) const { - if (!alias.isEmpty()) { - return _folderMap.value(alias); - } + if (_folders.contains(accountId) && _folders[accountId].contains(spaceId)) + return _folders[accountId][spaceId]; + return nullptr; } -void FolderMan::scheduleAllFolders() +// maybe add a new arg for prio level +void FolderMan::scheduleFoldersForAccount(const QUuid &accountId) { - for (auto *f : qAsConst(_folderMap)) { - if (f && f->canSync()) { - scheduleFolder(f); - } + if (!_folders.contains(accountId)) + return; + + for (Folder *f : std::as_const(_folders[accountId])) { + if (f && f->canSync()) + _scheduler->enqueueFolder(f); } } -void FolderMan::slotScheduleAppRestart() +void FolderMan::scheduleAllFolders() { - _appRestartRequired = true; - qCInfo(lcFolderMan) << "Application restart requested!"; + QList accountIds = _folders.keys(); + for (const QUuid &id : std::as_const(accountIds)) + scheduleFoldersForAccount(id); } void FolderMan::slotSyncOnceFileUnlocks(const QString &path, FileSystem::LockMode mode) @@ -662,134 +571,29 @@ void FolderMan::slotSyncOnceFileUnlocks(const QString &path, FileSystem::LockMod _lockWatcher->addFile(path, mode); } -/* - * if a folder wants to be synced, it calls this slot and is added - * to the queue. The slot to actually start a sync is called afterwards. - */ -void FolderMan::scheduleFolder(Folder *f) +void FolderMan::slotIsConnectedChanged() { - if (!OC_ENSURE(f)) { - return; - } - auto alias = f->alias(); - - qCInfo(lcFolderMan) << "Schedule folder " << alias << " to sync!"; + AccountState *accountState(qobject_cast(sender())); - if (!_scheduledFolders.contains(f)) { - if (!f->canSync()) { - qCInfo(lcFolderMan) << "Folder is not ready to sync, not scheduled!"; - _socketApi->slotUpdateFolderView(f); - return; - } - f->prepareToSync(); - emit folderSyncStateChange(f); - _scheduledFolders.enqueue(f); - emit scheduleQueueChanged(); - } else { - qCInfo(lcFolderMan) << "Sync for folder " << alias << " already scheduled, do not enqueue!"; - } - - startScheduledSyncSoon(); -} - -void FolderMan::scheduleFolderNext(Folder *f) -{ - auto alias = f->alias(); - qCInfo(lcFolderMan) << "Schedule folder " << alias << " to sync! Front-of-queue."; - - if (!f->canSync()) { - qCInfo(lcFolderMan) << "Folder is not ready to sync, not scheduled!"; + if (!accountState || !accountState->account()) return; - } - - _scheduledFolders.removeAll(f); - - f->prepareToSync(); - emit folderSyncStateChange(f); - _scheduledFolders.prepend(f); - emit scheduleQueueChanged(); - - startScheduledSyncSoon(); -} - -void FolderMan::slotScheduleETagJob(const QString & /*alias*/, RequestEtagJob *job) -{ - QObject::connect(job, &QObject::destroyed, this, &FolderMan::slotEtagJobDestroyed); - QMetaObject::invokeMethod(this, "slotRunOneEtagJob", Qt::QueuedConnection); - // maybe: add to queue -} - -void FolderMan::slotEtagJobDestroyed(QObject * /*o*/) -{ - // _currentEtagJob is automatically cleared - // maybe: remove from queue - QMetaObject::invokeMethod(this, "slotRunOneEtagJob", Qt::QueuedConnection); -} -void FolderMan::slotRunOneEtagJob() -{ - if (_currentEtagJob.isNull()) { - Folder *folder = nullptr; - for (auto *f : qAsConst(_folderMap)) { - if (f->etagJob()) { - // Caveat: always grabs the first folder with a job, but we think this is Ok for now and avoids us having a seperate queue. - _currentEtagJob = f->etagJob(); - folder = f; - break; - } - } - if (_currentEtagJob.isNull()) { - //qCDebug(lcFolderMan) << "No more remote ETag check jobs to schedule."; - - /* now it might be a good time to check for restarting... */ - if (!isAnySyncRunning() && _appRestartRequired) { - restartApplication(); - } - } else { - qCDebug(lcFolderMan) << "Scheduling" << folder->remoteUrl().toString() << "to check remote ETag"; - _currentEtagJob->start(); // on destroy/end it will continue the queue via slotEtagJobDestroyed - } - } -} - -void FolderMan::slotAccountStateChanged() -{ - AccountState *accountState = qobject_cast(sender()); - if (!accountState) { - return; - } - QString accountName = accountState->account()->displayName(); + QString accountName = accountState->account()->displayNameWithHost(); + QUuid accountId = accountState->account()->uuid(); if (accountState->isConnected()) { qCInfo(lcFolderMan) << "Account" << accountName << "connected, scheduling its folders"; - - for (auto *f : qAsConst(_folderMap)) { - if (f - && f->canSync() - && f->accountState() == accountState) { - scheduleFolder(f); - } - } + scheduleFoldersForAccount(accountId); } else { - qCInfo(lcFolderMan) << "Account" << accountName << "disconnected or paused, " - "terminating or descheduling sync folders"; - - for (auto *f : qAsConst(_folderMap)) { - if (f - && f->isSyncRunning() - && f->accountState() == accountState) { - f->slotTerminateSync(); - } - } + qCInfo(lcFolderMan) << "Account" << accountName + << "disconnected or paused, " + "terminating or descheduling sync folders"; - QMutableListIterator it(_scheduledFolders); - while (it.hasNext()) { - Folder *f = it.next(); - if (f->accountState() == accountState) { - it.remove(); + for (auto *f : std::as_const(_folders[accountId])) { + if (f && f->isSyncRunning()) { + f->slotTerminateSync(tr("Account disconnected or paused")); } } - emit scheduleQueueChanged(); } } @@ -797,218 +601,80 @@ void FolderMan::slotAccountStateChanged() // this is not the same as Pause and Resume of folders. void FolderMan::setSyncEnabled(bool enabled) { - if (!_syncEnabled && enabled && !_scheduledFolders.isEmpty()) { + if (enabled) { // We have things in our queue that were waiting for the connection to come back on. - startScheduledSyncSoon(); + scheduler()->start(); + } else { + scheduler()->stop(); } - _syncEnabled = enabled; + qCInfo(lcFolderMan) << Q_FUNC_INFO << enabled; // force a redraw in case the network connect status changed Q_EMIT folderSyncStateChange(nullptr); } -void FolderMan::startScheduledSyncSoon() +void FolderMan::slotRemoveFoldersForAccount(AccountState *accountState) { - if (_startScheduledSyncTimer.isActive()) { - return; - } - if (_scheduledFolders.empty()) { - return; - } - if (isAnySyncRunning()) { + if (!accountState || !accountState->account() || !_folders.contains(accountState->account()->uuid())) { return; } - seconds delay; - seconds sinceLastSync; + QSettings settings = ConfigFile::makeQSettings(); + QString accountGroup = QStringLiteral("Accounts/%1").arg(accountState->account()->groupIndex()); + settings.beginGroup(accountGroup); - // Require a pause based on the duration of the last sync run. - if (Folder *lastFolder = _lastSyncFolder) { - sinceLastSync = duration_cast(lastFolder->msecSinceLastSync()); + QUuid id = accountState->account()->uuid(); + // we always should emit before the folders have actually been deleted. consider renaming + emit folderListChanged(id, {}); - // 1s -> 1.5s pause - // 10s -> 5s pause - // 1min -> 12s pause - // 1h -> 90s pause - delay = seconds(static_cast(qSqrt(duration_cast(lastFolder->msecLastSyncDuration()).count()) / 20)); + for (const auto &f : std::as_const(_folders[id])) { + removeFolderSettings(f, settings); + deleteFolderSync(f); } - // Delays beyond one minute seem too big, particularly since there - // could be things later in the queue that shouldn't be punished by a - // long delay! - delay = qBound(1s, delay - sinceLastSync, 60s); - qCInfo(lcFolderMan) << "Starting the next scheduled sync in" << delay.count() << "seconds"; - _startScheduledSyncTimer.start(delay); + _folders.remove(id); + _unsyncedSpaces.remove(id); + // this is probably pointless but should not hurt + emit unsyncedSpaceCountChanged(id, 0, 0); } -/* - * slot to start folder syncs. - * It is either called from the slot where folders enqueue themselves for - * syncing or after a folder sync was finished. - */ -void FolderMan::slotStartScheduledFolderSync() +void FolderMan::removeFolderSettings(Folder *folder, QSettings &settings) { - if (isAnySyncRunning()) { - for (auto *f : qAsConst(_folderMap)) { - if (f->isSyncRunning()) - qCInfo(lcFolderMan) << "Currently folder " << f->remoteUrl().toString() << " is running, wait for finish!"; - } + if (!folder) { return; } - - if (!_syncEnabled) { - qCInfo(lcFolderMan) << "FolderMan: Syncing is disabled, no scheduling."; + disconnectAutoSave(folder); + QString id = QString::fromUtf8(folder->id()); + if (id.isEmpty()) return; - } - - qCDebug(lcFolderMan) << "folderQueue size: " << _scheduledFolders.count(); - if (_scheduledFolders.isEmpty()) { - return; - } - - // Find the first folder in the queue that can be synced. - Folder *folder = nullptr; - while (!_scheduledFolders.isEmpty()) { - Folder *g = _scheduledFolders.dequeue(); - if (g->canSync()) { - folder = g; - break; - } - } - - emit scheduleQueueChanged(); - - // Start syncing this folder! - if (folder) { - // Safe to call several times, and necessary to try again if - // the folder path didn't exist previously. - folder->registerFolderWatcher(); - registerFolderWithSocketApi(folder); - - _currentSyncFolder = folder; - folder->startSync(); - } -} - -void FolderMan::slotEtagPollTimerTimeout() -{ - for (auto *f : qAsConst(_folderMap)) { - if (!f) { - continue; - } - if (_scheduledFolders.contains(f)) { - continue; - } - if (_disabledFolders.contains(f)) { - continue; - } - if (f->dueToSync()) { - QMetaObject::invokeMethod(f, &Folder::slotRunEtagJob, Qt::QueuedConnection); - } - } -} - -void FolderMan::slotRemoveFoldersForAccount(AccountState *accountState) -{ - QList foldersToRemove; - // reserve a magic number - foldersToRemove.reserve(16); - for (auto *folder : qAsConst(_folderMap)) { - if (folder->accountState() == accountState) { - foldersToRemove.append(folder); - } - } - for (const auto &f : foldersToRemove) { - removeFolder(f); - } + settings.remove(QStringLiteral("Folders/%1").arg(id)); + settings.remove(QStringLiteral("Multifolders/%1").arg(id)); + settings.remove(QStringLiteral("FoldersWithPlaceholders/%1").arg(id)); } -void FolderMan::slotForwardFolderSyncStateChange() +void FolderMan::removeFolderSettings(Folder *folder) { - if (Folder *f = qobject_cast(sender())) { - emit folderSyncStateChange(f); - } + QSettings settings = ConfigFile::makeQSettings(); + QString accountGroup = QStringLiteral("Accounts/%1").arg(folder->accountState()->account()->groupIndex()); + settings.beginGroup(accountGroup); + removeFolderSettings(folder, settings); } void FolderMan::slotServerVersionChanged(Account *account) { + if (!account || !_folders.contains(account->uuid())) + return; + // Pause folders if the server version is unsupported - if (account->serverVersionUnsupported()) { - qCWarning(lcFolderMan) << "The server version is unsupported:" << account->serverVersion() + if (account->serverSupportLevel() == Account::ServerSupportLevel::Unsupported) { + qCWarning(lcFolderMan) << "The server version is unsupported:" << account->capabilities().status().versionString() << "pausing all folders on the account"; - for (auto &f : qAsConst(_folderMap)) { - if (f->accountState()->account().data() == account) { - f->setSyncPaused(true); - } - } - } -} - -void FolderMan::slotWatchedFileUnlocked(const QString &path) -{ - if (Folder *f = folderForPath(path)) { - // Treat this equivalently to the file being reported by the file watcher - f->slotWatchedPathChanged(path, Folder::ChangeReason::UnLock); - } -} - -void FolderMan::slotScheduleFolderByTime() -{ - for (const auto &f : qAsConst(_folderMap)) { - // Never schedule if syncing is disabled or when we're currently - // querying the server for etags - if (!f->canSync() || f->etagJob()) { - continue; - } - - auto msecsSinceSync = f->msecSinceLastSync(); - - // Possibly it's just time for a new sync run - const auto pta = f->accountState()->account()->capabilities().remotePollInterval(); - bool forceSyncIntervalExpired = msecsSinceSync > ConfigFile().forceSyncInterval(pta); - if (forceSyncIntervalExpired) { - qCInfo(lcFolderMan) << "Scheduling folder" << f->alias() - << "because it has been" << msecsSinceSync.count() << "ms " - << "since the last sync"; - - scheduleFolder(f); - continue; - } - - // Retry a couple of times after failure; or regularly if requested - bool syncAgain = - (f->consecutiveFailingSyncs() > 0 && f->consecutiveFailingSyncs() < 3) - || f->syncEngine().isAnotherSyncNeeded() == DelayedFollowUp; - auto syncAgainDelay = std::chrono::seconds(10); // 10s for the first retry-after-fail - if (f->consecutiveFailingSyncs() > 1) - syncAgainDelay = std::chrono::seconds(60); // 60s for each further attempt - if (syncAgain && msecsSinceSync > syncAgainDelay) { - qCInfo(lcFolderMan) << "Scheduling folder" << f->alias() - << ", the last" << f->consecutiveFailingSyncs() << "syncs failed" - << ", anotherSyncNeeded" << f->syncEngine().isAnotherSyncNeeded() - << ", last status:" << f->syncResult().statusString() - << ", time since last sync:" << msecsSinceSync.count(); - - scheduleFolder(f); - continue; + for (auto &f : _folders[account->uuid()]) { + f->setSyncPaused(true); } - - // Do we want to retry failing syncs or another-sync-needed runs more often? } } -bool FolderMan::isAnySyncRunning() const -{ - if (_currentSyncFolder) - return true; - - for (auto f : _folderMap) { - if (f->isSyncRunning()) - return true; - } - return false; -} - void FolderMan::slotFolderSyncStarted() { auto f = qobject_cast(sender()); @@ -1016,21 +682,16 @@ void FolderMan::slotFolderSyncStarted() if (!f) return; - qCInfo(lcFolderMan) << ">========== Sync started for folder [" - << f->shortGuiLocalPath() - << "] of account [" - << f->accountState()->account()->displayName() - << "] with remote [" - << f->remoteUrl().toDisplayString() - << "]"; + qCInfo(lcFolderMan) << ">========== Sync started for folder [" << f->shortGuiLocalPath() << "] of account [" + << f->accountState()->account()->displayNameWithHost() << "] with remote [" << f->remoteUrl().toDisplayString() << "]"; } /* - * a folder indicates that its syncing is finished. - * Start the next sync after the system had some milliseconds to breath. - * This delay is particularly useful to avoid late file change notifications - * (that we caused ourselves by syncing) from triggering another spurious sync. - */ + * a folder indicates that its syncing is finished. + * Start the next sync after the system had some milliseconds to breath. + * This delay is particularly useful to avoid late file change notifications + * (that we caused ourselves by syncing) from triggering another spurious sync. + */ void FolderMan::slotFolderSyncFinished(const SyncResult &) { auto f = qobject_cast(sender()); @@ -1038,105 +699,139 @@ void FolderMan::slotFolderSyncFinished(const SyncResult &) if (!f) return; - qCInfo(lcFolderMan) << "<========== Sync finished for folder [" - << f->shortGuiLocalPath() - << "] of account [" - << f->accountState()->account()->displayName() - << "] with remote [" - << f->remoteUrl().toDisplayString() - << "]"; - if (f == _currentSyncFolder) { - _lastSyncFolder = _currentSyncFolder; - _currentSyncFolder = nullptr; - } - if (!isAnySyncRunning()) - startScheduledSyncSoon(); + qCInfo(lcFolderMan) << "<========== Sync finished for folder [" << f->shortGuiLocalPath() << "] of account [" + << f->accountState()->account()->displayNameWithHost() << "] with remote [" << f->remoteUrl().toDisplayString() << "]"; } +// consider moving this to FolderBuilder so we can "make" tests use it someday +bool FolderMan::validateFolderDefinition(const FolderDefinition &folderDefinition) +{ + if (folderDefinition.id().isEmpty() || folderDefinition.webDavUrl().isEmpty() || folderDefinition.journalPath().isEmpty() + || !ensureFilesystemSupported(folderDefinition)) { + qCWarning(lcFolderMan) << "Folder definition is missing required value(s). Folder cannot be created from this definition."; + return false; + } + + QString pathCheck = FolderManagementUtils::validateFolderPath(folderDefinition.localPath()); + if (!pathCheck.isEmpty()) { + // does this warrant popping an error dialog? + qCWarning(lcFolderMan) << "Folder definition path check failed with error: " << pathCheck << ". Folder cannot be created using this path."; + return false; + } + return true; +} + + Folder *FolderMan::addFolder(AccountState *accountState, const FolderDefinition &folderDefinition) { - // Choose a db filename - auto definition = folderDefinition; - definition.journalPath = SyncJournalDb::makeDbName(folderDefinition.localPath); + if (!accountState || !accountState->account()) + return nullptr; - if (!ensureJournalGone(definition.absoluteJournalPath())) { + if (!validateFolderDefinition(folderDefinition)) { + qCWarning(lcFolderMan) << "Folder Definition validation failed for folder" << folderDefinition.localPath(); return nullptr; } - auto vfs = createVfsFromPlugin(folderDefinition.virtualFilesMode); - if (!vfs) { - qCWarning(lcFolderMan) << "Could not load plugin for mode" << folderDefinition.virtualFilesMode; + QUuid accountId = accountState->account()->uuid(); + + if (Folder *f = folder(accountId, folderDefinition.spaceId())) { + qCWarning(lcFolderMan) << "Trying to add new folder for " << folderDefinition.localPath() << "but it already exists in folder list"; + return f; + } + + FolderBuilder builder(folderDefinition); + auto folder = builder.buildFolder(accountState, _ignoreHiddenFiles, _moveToTrash, this); + + if (!folder) { + qCWarning(lcFolderMan) << "Unable to create Folder for " << folder->path() << " with spaceId " << folderDefinition.spaceId(); return nullptr; } - auto folder = addFolderInternal(definition, accountState, std::move(vfs)); + qCInfo(lcFolderMan) << "Adding folder to Folder Map " << folder << folder->path(); + QString spaceId = folder->spaceId(); + // always add the folder even if it had a setup error - future add special handling for incomplete folders if possible + _folders[accountId].insert(spaceId, folder); - // Migration: The first account that's configured for a local folder shall - // be saved in a backwards-compatible way. - bool oneAccountOnly = true; - for (auto *other : map()) { - if (other != folder && other->cleanPath() == folder->cleanPath()) { - oneAccountOnly = false; - break; - } + if (folder->syncPaused()) { + _disabledFolders.insert(folder); } - if (folder) { - folder->setSaveBackwardsCompatible(oneAccountOnly); - folder->saveToSettings(); - emit folderSyncStateChange(folder); - emit folderListChanged(_folderMap); + if (!folder->hasSetupError()) { + connectFolder(folder); + Q_EMIT folderSyncStateChange(folder); } -#ifdef Q_OS_WIN - _navigationPaneHelper.scheduleUpdateCloudStorageRegistry(); -#endif return folder; } -Folder *FolderMan::addFolderInternal( - FolderDefinition folderDefinition, - AccountState *accountState, - std::unique_ptr vfs) +void FolderMan::connectFolder(Folder *folder) { - auto alias = folderDefinition.alias; - int count = 0; - while (folderDefinition.alias.isEmpty() - || _folderMap.contains(folderDefinition.alias) - || _additionalBlockedFolderAliases.contains(folderDefinition.alias)) { - // There is already a folder configured with this name and folder names need to be unique - folderDefinition.alias = alias + QString::number(++count); + // See matching disconnects in disconnectFolder(). + if (folder && !folder->hasSetupError()) { + connect(folder, &Folder::syncStateChange, _socketApi.get(), [folder, this] { _socketApi->slotUpdateFolderView(folder); }); + connect(folder, &Folder::syncStarted, this, &FolderMan::slotFolderSyncStarted); + connect(folder, &Folder::syncFinished, this, &FolderMan::slotFolderSyncFinished); + connect(folder, &Folder::syncStateChange, this, [folder, this] { Q_EMIT folderSyncStateChange(folder); }); + connect(folder, &Folder::syncPausedChanged, this, &FolderMan::slotFolderSyncPauseChanged); + connect(folder, &Folder::canSyncChanged, this, &FolderMan::slotFolderCanSyncChanged); + // format wants to move the pointer "*" one space away from the type which = clazy not normalized sig warning + // clang-format off + connect(folder, SIGNAL(syncPausedChanged(Folder*,bool)), this, SLOT(saveFolder(Folder*))); + connect(folder, SIGNAL(vfsModeChanged(Folder*,Vfs::Mode)), this, SLOT(saveFolder(Folder*))); + // clang-format on + + connect( + folder->syncEngine(), &SyncEngine::fileStatusChanged, _socketApi.get(), &SocketApi::broadcastStatusPushMessage); + + registerFolderWithSocketApi(folder); } +} - auto folder = new Folder(folderDefinition, accountState, std::move(vfs), this); +void FolderMan::disconnectAutoSave(Folder *folder) +{ + // clang-format off + disconnect(folder, SIGNAL(syncPausedChanged(Folder*,bool)), this, SLOT(saveFolder(Folder*))); + disconnect(folder, SIGNAL(vfsModeChanged(Folder*,Vfs::Mode)), this, SLOT(saveFolder(Folder*))); + // clang-format on +} - qCInfo(lcFolderMan) << "Adding folder to Folder Map " << folder << folder->alias(); - _folderMap[folder->alias()] = folder; - if (folder->syncPaused()) { - _disabledFolders.insert(folder); +void FolderMan::disconnectFolder(Folder *folder) +{ + if (folder && !folder->hasSetupError()) { + _socketApi->slotUnregisterPath(folder); + + disconnect(folder, nullptr, _socketApi.get(), nullptr); + disconnect(folder, nullptr, this, nullptr); + disconnect(folder->syncEngine(), nullptr, folder, nullptr); + disconnect( + folder->syncEngine(), &SyncEngine::fileStatusChanged, _socketApi.get(), &SocketApi::broadcastStatusPushMessage); + disconnect(folder, nullptr, folder->syncEngine(), nullptr); } +} - // See matching disconnects in unloadFolder(). - connect(folder, &Folder::syncStarted, this, &FolderMan::slotFolderSyncStarted); - connect(folder, &Folder::syncFinished, this, &FolderMan::slotFolderSyncFinished); - connect(folder, &Folder::syncStateChange, this, &FolderMan::slotForwardFolderSyncStateChange); - connect(folder, &Folder::syncPausedChanged, this, &FolderMan::slotFolderSyncPaused); - connect(folder, &Folder::canSyncChanged, this, &FolderMan::slotFolderCanSyncChanged); - connect(&folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged, - _socketApi.data(), &SocketApi::broadcastStatusPushMessage); - connect(folder, &Folder::watchedFileChangedExternally, - &folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::slotPathTouched); +void FolderMan::saveFolder(Folder *folder, QSettings &settings) +{ + Q_ASSERT(settings.group() == QStringLiteral("Accounts")); - folder->registerFolderWatcher(); - registerFolderWithSocketApi(folder); - return folder; + auto strId = QString::fromUtf8(folder->id()); + QString targetGroup = QStringLiteral("%1/Folders/%2").arg(folder->accountState()->account()->groupIndex(), strId); + settings.beginGroup(targetGroup); + FolderDefinition::save(settings, folder->definition()); + settings.endGroup(); +} + +void FolderMan::saveFolder(Folder *folder) +{ + QSettings settings = ConfigFile::makeQSettings(); + settings.beginGroup("Accounts"); + saveFolder(folder, settings); } -Folder *FolderMan::folderForPath(const QString &path, QString *relativePath) +Folder *FolderMan::folderForPath(const QString &path, QString *relativePath) const { QString absolutePath = QDir::cleanPath(path) + QLatin1Char('/'); - for (auto *folder : map()) { + for (Folder *folder : folders()) { const QString folderPath = folder->cleanPath() + QLatin1Char('/'); if (absolutePath.startsWith(folderPath, (Utility::isWindows() || Utility::isMac()) ? Qt::CaseInsensitive : Qt::CaseSensitive)) { @@ -1153,76 +848,84 @@ Folder *FolderMan::folderForPath(const QString &path, QString *relativePath) return nullptr; } -QStringList FolderMan::findFileInLocalFolders(const QString &relPath, const AccountPtr acc) +void FolderMan::removeFolderFromGui(Folder *f) { - QStringList re; + Q_ASSERT(f->accountState() && f->accountState()->account()); - // We'll be comparing against Folder::remotePath which always starts with / - QString serverPath = relPath; - if (!serverPath.startsWith('/')) - serverPath.prepend('/'); - - for (auto *folder : map()) { - if (acc != nullptr && folder->accountState()->account() != acc) { - continue; - } - if (!serverPath.startsWith(folder->remotePath())) - continue; + QUuid accountId = f->accountState()->account()->uuid(); + emit folderRemoved(accountId, f); + if (f->isAvailable()) + { + _unsyncedSpaces[accountId].insert(f->spaceId(), f->space()); + emit unsyncedSpaceCountChanged(accountId, _unsyncedSpaces[accountId].count(), f->accountState()->account()->spacesManager()->spacesCount()); + } + removeFolderSettings(f); + deleteFolderSync(f); +} - QString path = folder->cleanPath() + '/'; - path += serverPath.midRef(folder->remotePathTrailingSlash().length()); - if (QFile::exists(path)) { - re.append(path); +void FolderMan::forceFolderSync(Folder *f) +{ + if (!f) + return; + // Prevent new sync starts + _scheduler->stop(); + + // Terminate and reschedule any running sync + // todo #50: evaluate whether it really makes sense to cancel folders that are ALREADY in sync - this makes no sense to me. + // also should this be responsibility of the sync scheduler? It knows which folder is currently syncing so it could + // cancel it directly without having to iterate ALL the folders. + for (auto *folder : folders()) { + if (folder->isSyncRunning()) { + folder->slotTerminateSync(tr("User triggered force sync")); + _scheduler->enqueueFolder(folder); } } - return re; + + f->slotWipeErrorBlacklist(); // issue #6757 + f->slotNextSyncFullLocalDiscovery(); // ensure we don't forget about local errors + + // Insert the selected folder at the front of the queue + // this should not be a direct call, just signal a request. When that time comes move the prio enum to an independent location to avoid + // caller deps on the scheduler + _scheduler->enqueueFolder(f, SyncScheduler::Priority::High); + + // Restart scheduler + _scheduler->start(); } -void FolderMan::removeFolder(Folder *f) +void FolderMan::deleteFolderSync(Folder *f) { - if (!OC_ENSURE(f)) { + if (!f) return; - } + + if (! (f->accountState() && f->accountState()->account() && _folders.contains(f->accountState()->account()->uuid()))) + return; + - qCInfo(lcFolderMan) << "Removing " << f->alias(); + qCInfo(lcFolderMan) << "Removing " << f->path(); const bool currentlyRunning = f->isSyncRunning(); if (currentlyRunning) { // abort the sync now - f->slotTerminateSync(); - } - - if (_scheduledFolders.removeAll(f) > 0) { - emit scheduleQueueChanged(); + f->slotTerminateSync(tr("Folder is about to be removed")); } + // this aborts any running sync so the sync engine should be idle f->setSyncPaused(true); + + // this function includes the stuff to remove the database files. f->wipeForRemoval(); - // remove the folder configuration - f->removeFromSettings(); + _folders[f->accountState()->account()->uuid()].remove(f->spaceId()); - unloadFolder(f); - if (currentlyRunning) { - // We want to schedule the next folder once this is done - connect(f, &Folder::syncFinished, - this, &FolderMan::slotFolderSyncFinished); - // Let the folder delete itself when done. - connect(f, &Folder::syncFinished, f, &QObject::deleteLater); - } else { - f->deleteLater(); - } + disconnectFolder(f); -#ifdef Q_OS_WIN - _navigationPaneHelper.scheduleUpdateCloudStorageRegistry(); -#endif - Q_EMIT folderRemoved(f); - emit folderListChanged(_folderMap); + f->deleteLater(); } QString FolderMan::getBackupName(QString fullPathName) const { - if (fullPathName.endsWith("/")) + if (fullPathName.endsWith(QLatin1String("/"))) fullPathName.chop(1); if (fullPathName.isEmpty()) @@ -1241,170 +944,18 @@ QString FolderMan::getBackupName(QString fullPathName) const return newName; } -bool FolderMan::startFromScratch(const QString &localFolder) -{ - if (localFolder.isEmpty()) { - return false; - } - - QFileInfo fi(localFolder); - QDir parentDir(fi.dir()); - QString folderName = fi.fileName(); - - // Adjust for case where localFolder ends with a / - if (fi.isDir()) { - folderName = parentDir.dirName(); - parentDir.cdUp(); - } - - if (fi.exists()) { - // It exists, but is empty -> just reuse it. - if (fi.isDir() && fi.dir().count() == 0) { - qCDebug(lcFolderMan) << "startFromScratch: Directory is empty!"; - return true; - } - // Disconnect the socket api from the database to avoid that locking of the - // db file does not allow to move this dir. - Folder *f = folderForPath(localFolder); - if (f) { - if (localFolder.startsWith(f->path())) { - _socketApi->slotUnregisterPath(f->alias()); - } - f->journalDb()->close(); - f->slotTerminateSync(); // Normally it should not be running, but viel hilft viel - } - - // Make a backup of the folder/file. - QString newName = getBackupName(parentDir.absoluteFilePath(folderName)); - QString renameError; - if (!FileSystem::rename(fi.absoluteFilePath(), newName, &renameError)) { - qCWarning(lcFolderMan) << "startFromScratch: Could not rename" << fi.absoluteFilePath() - << "to" << newName << "error:" << renameError; - return false; - } - } - - if (!parentDir.mkdir(fi.absoluteFilePath())) { - qCWarning(lcFolderMan) << "startFromScratch: Could not mkdir" << fi.absoluteFilePath(); - return false; - } - - return true; -} - void FolderMan::setDirtyProxy() { - for (auto *f : qAsConst(_folderMap)) { + for (auto *f : folders()) { if (f) { - if (f->accountState() && f->accountState()->account() - && f->accountState()->account()->networkAccessManager()) { + if (f->accountState() && f->accountState()->account() && f->accountState()->account()->accessManager()) { // Need to do this so we do not use the old determined system proxy - f->accountState()->account()->networkAccessManager()->setProxy( - QNetworkProxy(QNetworkProxy::DefaultProxy)); + f->accountState()->account()->accessManager()->setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy)); } } } } -void FolderMan::setDirtyNetworkLimits() -{ - for (auto *f : qAsConst(_folderMap)) { - // set only in busy folders. Otherwise they read the config anyway. - if (f && f->isSyncRunning()) { - f->setDirtyNetworkLimits(); - } - } -} - -TrayOverallStatusResult FolderMan::trayOverallStatus(const QList &folders) -{ - TrayOverallStatusResult result; - - // if one of them has an error -> show error - // if one is paused, but others ok, show ok - // - for (auto *folder : folders) { - result.addResult(folder); - } - return result; -} - -QString FolderMan::trayTooltipStatusString( - const SyncResult &result, bool paused) -{ - QString folderMessage; - switch (result.status()) { - case SyncResult::Undefined: - folderMessage = tr("Undefined State."); - break; - case SyncResult::NotYetStarted: - folderMessage = tr("Waiting to start syncing."); - break; - case SyncResult::SyncPrepare: - folderMessage = tr("Preparing for sync."); - break; - case SyncResult::SyncRunning: - folderMessage = tr("Sync is running."); - break; - case SyncResult::Success: - case SyncResult::Problem: - if (result.hasUnresolvedConflicts()) { - folderMessage = tr("Sync was successful, unresolved conflicts."); - } else { - folderMessage = tr("Last Sync was successful."); - } - break; - case SyncResult::Error: - break; - case SyncResult::SetupError: - folderMessage = tr("Setup Error."); - break; - case SyncResult::SyncAbortRequested: - folderMessage = tr("User Abort."); - break; - case SyncResult::Paused: - folderMessage = tr("Sync is paused."); - break; - // no default case on purpose, check compiler warnings - } - if (paused) { - // sync is disabled. - folderMessage = tr("%1 (Sync is paused)").arg(folderMessage); - } - return folderMessage; -} - -static QString checkPathValidityRecursive(const QString &path) -{ - if (path.isEmpty()) { - return FolderMan::tr("No valid folder selected!"); - } - -#ifdef Q_OS_WIN - Utility::NtfsPermissionLookupRAII ntfs_perm; -#endif - const QFileInfo selFile(path); - if (numberOfSyncJournals(selFile.filePath()) != 0) { - return FolderMan::tr("The folder %1 is used in a folder sync connection!").arg(QDir::toNativeSeparators(selFile.filePath())); - } - - if (!selFile.exists()) { - const QString parentPath = selFile.path(); - if (parentPath != path) - return checkPathValidityRecursive(parentPath); - return FolderMan::tr("The selected path does not exist!"); - } - - if (!selFile.isDir()) { - return FolderMan::tr("The selected path is not a folder!"); - } - - if (!selFile.isWritable()) { - return FolderMan::tr("You have no permission to write to the selected folder!"); - } - return QString(); -} - // QFileInfo::canonicalPath returns an empty string if the file does not exist. // This function also works with files that does not exist and resolve the symlinks in the // parent directories. @@ -1421,139 +972,291 @@ static QString canonicalPath(const QString &path) return path; } - return canonicalPath(parentPath) + '/' + selFile.fileName(); + return canonicalPath(parentPath) + QLatin1Char('/') + selFile.fileName(); } return selFile.canonicalFilePath(); } -QString FolderMan::checkPathValidityForNewFolder(const QString &path) const +static QString checkPathForSyncRootMarkingRecursive(const QString &path, FolderMan::NewFolderType folderType, const QUuid &accountUuid) { - // check if the local directory isn't used yet in another ownCloud sync - const auto cs = Utility::fsCaseSensitivity(); + // First do basic checks if this path is usable + const QFileInfo selectedPathInfo(path); + if (!selectedPathInfo.isDir()) { + return FolderMan::tr("The selected path is not a folder."); + } + + if (FolderManagementUtils::numberOfSyncJournals(selectedPathInfo.filePath()) != 0) { + return FolderMan::tr("The folder %1 is used in a folder sync connection.").arg(QDir::toNativeSeparators(selectedPathInfo.filePath())); + } + + // Check for sync root markings in this folder + std::pair existingTags = Utility::getDirectorySyncRootMarkings(path); + if (!existingTags.first.isEmpty()) { + if (existingTags.first != Theme::instance()->orgDomainName()) { + // another application uses this as spaces root folder + return FolderMan::tr("Folder '%1' is already in use by application %2.").arg(path, existingTags.first); + } + + // Looks good, it's our app, let's check the account tag: + switch (folderType) { + case FolderMan::NewFolderType::SpacesFolder: + if (existingTags.second == accountUuid) { + // Nice, that's what we like, the sync root for our account in our app. No error, and we don't need to check the parent folders. + return {}; + } + [[fallthrough]]; + case FolderMan::NewFolderType::SpacesSyncRoot: + // It's our application but we don't want to create a spaces folder, so it must be another space root + return FolderMan::tr("Folder '%1' is already in use by another account.").arg(path); + } + } + + // This folder is ok, but check the parent folders if they are used + + QString parent = QFileInfo(path).path(); + if (parent == path) { // root dir, stop recursing + return {}; + } - const QString userDir = QDir::cleanPath(canonicalPath(path)) + '/'; - for (auto i = _folderMap.constBegin(); i != _folderMap.constEnd(); ++i) { - Folder *f = static_cast(i.value()); - const QString folderDir = QDir::cleanPath(canonicalPath(f->path())) + '/'; + return checkPathForSyncRootMarkingRecursive(parent, folderType, accountUuid); +} +/* + * with spaces: + * - spaces sync root not in syncdb folder + * - spaces sync root not in another spaces sync root + * + * - space not in syncdb folder + * - space *can* be in sync root + * - space not in spaces sync root of other account (check with account uuid) + */ +QString FolderMan::checkPathValidity(const QString &path, NewFolderType folderType, const QUuid &accountUuid) const +{ + // Do checks against existing folders. This catches cases where: + // - the user deleted a folder from the filesystem, but there is still a folder sync connection in the configuration + // - check if this folder contains other folders for spaces + // If so, error out. + const auto cs = Utility::fsCaseSensitivity(); + const QString userDir = QDir::cleanPath(canonicalPath(path)) + QLatin1Char('/'); + for (auto f : folders()) { + const QString folderDir = QDir::cleanPath(canonicalPath(f->path())) + QLatin1Char('/'); if (QString::compare(folderDir, userDir, cs) == 0) { - return tr("There is already a sync from the server to this local folder. " - "Please pick another local folder!"); + return tr("There is already a sync from the server to this local folder."); } if (FileSystem::isChildPathOf(folderDir, userDir)) { - return tr("The local folder %1 already contains a folder used in a folder sync connection. " - "Please pick another one!") + return tr("The local folder %1 already contains a folder used in a folder sync connection.") .arg(QDir::toNativeSeparators(path)); } if (FileSystem::isChildPathOf(userDir, folderDir)) { - return tr("The local folder %1 is already contained in a folder used in a folder sync connection. " - "Please pick another one!") + return tr("The local folder %1 is already contained in a folder used in a folder sync connection.") .arg(QDir::toNativeSeparators(path)); } } - const auto result = checkPathValidityRecursive(path); - if (!result.isEmpty()) { - return tr("%1 Please pick another one!").arg(result); + + // Now run filesystem based checks + return findExistingFolderAndCheckValidity(path, folderType, accountUuid); +} + +QString FolderMan::findExistingFolderAndCheckValidity(const QString &path, NewFolderType folderType, const QUuid &accountUuid) +{ + if (path.isEmpty()) { + return tr("No valid folder selected."); } - return {}; + +#ifdef Q_OS_WIN + QNtfsPermissionCheckGuard ntfs_perm; +#endif + + auto pathLengthCheck = FolderManagementUtils::checkPathLength(path); + if (!pathLengthCheck.isEmpty()) { + return pathLengthCheck; + } + + // If this is a new folder, recurse up to the first parent that exists, to see if we can use that to create a new folder + const QFileInfo selectedPathInfo(path); + if (!selectedPathInfo.exists()) { + const QString parentPath = selectedPathInfo.path(); + if (parentPath != path) { + return findExistingFolderAndCheckValidity(parentPath, folderType, accountUuid); + } + return tr("The selected path does not exist."); + } + + // At this point we have an existing folder, so check if we can create files/folders here. + // Note: we don't need to write to any parent, so we don't need to check writablility when traversing the parents. + if (!selectedPathInfo.isWritable()) { + return tr("You have no permission to write to the selected folder."); + } + + // Now check if none of the parents is already used. + return checkPathForSyncRootMarkingRecursive(canonicalPath(path), folderType, accountUuid); } -QString FolderMan::findGoodPathForNewSyncFolder(const QString &basePath) const +QString FolderMan::findGoodPathForNewSyncFolder( + const QString &basePath, const QString &newFolder, FolderMan::NewFolderType folderType, const QUuid &accountUuid) const { - QString folder = basePath; + // reserve extra characters to allow appending of a number + const QString normalisedPath = FileSystem::createPortableFileName(basePath, FileSystem::pathEscape(newFolder), std::string_view(" (100)").size()); // If the parent folder is a sync folder or contained in one, we can't // possibly find a valid sync folder inside it. // Example: Someone syncs their home directory. Then ~/foobar is not // going to be an acceptable sync folder path for any value of foobar. - const QString parentFolder = QFileInfo(folder).canonicalPath(); - if (FolderMan::instance()->folderForPath(parentFolder)) { + if (folderForPath(QFileInfo(normalisedPath).canonicalPath())) { // Any path with that parent is going to be unacceptable, // so just keep it as-is. - return basePath; + return canonicalPath(normalisedPath); } // Count attempts and give up eventually - for (int attempt = 2; attempt < 100; ++attempt) { - if (!QFileInfo::exists(folder) - && FolderMan::instance()->checkPathValidityForNewFolder(folder).isEmpty()) { - return folder; + { + QString folder = normalisedPath; + for (int attempt = 2; attempt <= 100; ++attempt) { + if (!QFileInfo::exists(folder) && checkPathValidity(folder, folderType, accountUuid).isEmpty()) { + return canonicalPath(folder); + } + folder = normalisedPath + QStringLiteral(" (%1)").arg(attempt); } - folder = basePath + QString::number(attempt); } - return basePath; + // we failed to find a non existing path + return canonicalPath(normalisedPath); } bool FolderMan::ignoreHiddenFiles() const { - if (_folderMap.empty()) { - return true; - } - return _folderMap.begin().value()->ignoreHiddenFiles(); + return _ignoreHiddenFiles; } void FolderMan::setIgnoreHiddenFiles(bool ignore) { - // Note that the setting will revert to 'true' if all folders - // are deleted... - for (auto *folder : qAsConst(_folderMap)) { - folder->setIgnoreHiddenFiles(ignore); - folder->saveToSettings(); + if (ignore == _ignoreHiddenFiles) { + return; + } + + setSyncEnabled(false); + auto settings = ConfigFile::makeQSettings(); + settings.setValue(IgnoreHiddenFilesKey, ignore); + _ignoreHiddenFiles = ignore; + + // This should be done through a signal-slot connection. However, this has to wait until the + // engine is passed to the folder (so we can connect the signal/slot) instead of the folder + // creating it. See the todo in the Folder constructor. + for (Folder *folder : folders()) { + if (folder->canSync()) { + folder->syncEngine()->setIgnoreHiddenFiles(ignore); + } } + + setSyncEnabled(true); } -QQueue FolderMan::scheduleQueue() const +bool FolderMan::moveToTrash() const { - return _scheduledFolders; + return _moveToTrash; +} + +void FolderMan::updateMoveToTrash(bool trashIt) +{ + if (_moveToTrash == trashIt) + return; + + setSyncEnabled(false); + _moveToTrash = trashIt; + auto settings = ConfigFile::makeQSettings(); + settings.setValue(MoveToTrashKey, _moveToTrash); + + for (auto *f : folders()) { + if (f) { + f->setMoveToTrash(_moveToTrash); + } + } + setSyncEnabled(true); } -Folder *FolderMan::currentSyncFolder() const +bool FolderMan::isSpaceSynced(GraphApi::Space *space) const { - return _currentSyncFolder; + return (space && folder(space->accountId(), space->id()) != nullptr); } -void FolderMan::restartApplication() +Folder *FolderMan::addFolderFromScratch(AccountState *accountState, FolderDefinition &&folderDefinition, bool useVfs) { - if (Utility::isLinux()) { - // restart: - qCInfo(lcFolderMan) << "Restarting application NOW, PID" << qApp->applicationPid() << "is ending."; - qApp->quit(); - QStringList args = qApp->arguments(); - QString prg = args.takeFirst(); - - QProcess::startDetached(prg, args); + if (!FolderManagementUtils::prepareFolder(folderDefinition.localPath())) { + return nullptr; + } + + folderDefinition.setJournalPath(SyncJournalDb::makeDbName(folderDefinition.localPath())); + + // this is here because allegedly, old clients may not remove the old journal when the sync is removed. + // This is currently done in wipeForRemoval + if (!ensureJournalGone(folderDefinition.absoluteJournalPath())) { + return nullptr; + } + + if (useVfs) { + folderDefinition.setVirtualFilesMode(VfsPluginManager::instance().bestAvailableVfsMode()); + } + + auto newFolder = addFolder(accountState, folderDefinition); + + if (newFolder) { + if (newFolder->vfs().mode() != Vfs::WindowsCfApi) { + Utility::setupFavLink(folderDefinition.localPath()); + } + qCDebug(lcFolderMan) << "Local sync folder" << folderDefinition.localPath() << "successfully created"; } else { - qCDebug(lcFolderMan) << "On this platform we do not restart."; + qCWarning(lcFolderMan) << "Failed to create local sync folder: " << folderDefinition.localPath(); } + + // we should not emit any folder list change from this function because it can be called in bulk as well as individual operations + return newFolder; } -Result FolderMan::unsupportedConfiguration(const QString &path) const +// todo: #1 +void FolderMan::addFolderFromGui(AccountState *accountState, const SyncConnectionDescription &description) { - auto it = _unsupportedConfigurationError.find(path); - if (it == _unsupportedConfigurationError.end()) { - it = _unsupportedConfigurationError.insert(path, [&]() -> Result { - if (numberOfSyncJournals(path) > 1) { - const QString error = tr("Multiple accounts are sharing the folder %1.\n" - "This configuration is know to lead to dataloss and is no longer supported.\n" - "Please consider removing this folder from the account and adding it again.") - .arg(path); - if (Theme::instance()->warnOnMultipleDb()) { - qCWarning(lcFolderMan) << error; - return error; - } else { - qCWarning(lcFolderMan) << error << "this error is not displayed to the user as this is a branded" - << "client and the error itself might be a false positive caused by a previous broken migration"; - } - } - return {}; - }()); + if (!accountState || !accountState->account()) + return; + + setSyncEnabled(false); + + FolderDefinition definition(description.davUrl, description.spaceId, description.displayName); + definition.setLocalPath(description.localPath); + definition.setTargetPath(description.remotePath); + definition.setPriority(description.sortPriority); + auto f = addFolderFromScratch(accountState, std::move(definition), description.useVirtualFiles); + + + if (f) { + QUuid accountId = accountState->account()->uuid(); + saveFolder(f); + emit folderAdded(accountId, f); + + if (_unsyncedSpaces.contains(accountId) && _unsyncedSpaces[accountId].contains(f->spaceId())) + { + _unsyncedSpaces[accountId].remove(f->spaceId()); + emit unsyncedSpaceCountChanged(accountId, _unsyncedSpaces[accountId].count(), accountState->account()->spacesManager()->spacesCount()); + } + + f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, description.selectiveSyncBlackList); + f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, {QLatin1String("/")}); + + _scheduler->enqueueFolder(f, SyncScheduler::Priority::High); } - return *it; + setSyncEnabled(true); } -bool FolderMan::checkVfsAvailability(const QString &path, Vfs::Mode mode) const +QString FolderMan::suggestSyncFolder(NewFolderType folderType, const QUuid &accountUuid) { - return unsupportedConfiguration(path) && Vfs::checkAvailability(path, mode); + return FolderMan::instance()->findGoodPathForNewSyncFolder(QDir::homePath(), Theme::instance()->appName(), folderType, accountUuid); +} + + + +std::unique_ptr FolderMan::createInstance() +{ + OC_ASSERT(!_instance); + _instance = new FolderMan(); + return std::unique_ptr(_instance); } } // namespace OCC diff --git a/src/gui/folderman.h b/src/gui/folderman.h index 5c93110bc74..ccf60939a20 100644 --- a/src/gui/folderman.h +++ b/src/gui/folderman.h @@ -13,21 +13,19 @@ */ -#ifndef FOLDERMAN_H -#define FOLDERMAN_H +#pragma once -#include -#include -#include +#include "gui/owncloudguilib.h" #include "folder.h" -#include "folderwatcher.h" -#include "navigationpanehelper.h" -#include "syncfileitem.h" +#include "scheduling/syncscheduler.h" -class TestFolderMigration; +#include +#include namespace OCC { + +class FolderMan; namespace TestUtils { // prototype for test friend FolderMan *folderMan(); @@ -54,6 +52,7 @@ class TrayOverallStatusResult SyncResult _overallStatus; }; + /** * @brief The FolderMan class * @ingroup gui @@ -66,7 +65,7 @@ class TrayOverallStatusResult * (_timeScheduler and slotScheduleFolderByTime()) * * - A folder watcher receives a notification about a file change - * (_folderWatchers and Folder::slotWatchedPathChanged()) + * (_folderWatchers and Folder::slotWatchedPathsChanged()) * * - The folder etag on the server has changed * (_etagPollTimer) @@ -78,16 +77,124 @@ class TrayOverallStatusResult * (_timeScheduler and slotScheduleFolderByTime() * and Folder::slotSyncFinished()) */ -class FolderMan : public QObject +class OWNCLOUDGUI_EXPORT FolderMan : public QObject { Q_OBJECT public: + /** + * For a new folder, the type guides what kind of checks are done to ensure the new folder is not embedded in an existing one. + * Or in case of a space folder, that if the new folder is in a Space sync root, it is the sync root of the same account. + */ + enum class NewFolderType { + SpacesSyncRoot, + SpacesFolder, + }; + + // Refactoring todo: why does this duplicate most of the folder definition? + struct SyncConnectionDescription + { + /*** +- * The WebDAV URL for the sync connection. + */ + QUrl davUrl; + + /*** + * The id of the space or empty in case of ownCloud 10. + */ + QString spaceId; + + /*** + * The local folder used for the sync. + */ + QString localPath; + + /*** + * The relative remote path + */ + QString remotePath; + + /*** + * The Space name to display in the list of folders or an empty string. + */ + QString displayName; + + /*** + * Wether to use virtual files. + */ + bool useVirtualFiles; + + uint32_t sortPriority; + + QSet selectiveSyncBlackList; + }; + + static QString suggestSyncFolder(NewFolderType folderType, const QUuid &accountUuid); + + /** + * @brief Check a path for a new spaces sync root or spaces folder for validity. + * + * @param path The path to check + * @param folderType The kind of folder that is to be created (Folder or Spaces sync root) + * @param the account UUID for the which the folder is created + * @return an error string if there is a problem, or an null string if the path is valid + * + * Checks: + * - spaces sync root not in a syncdb folder (a space folder) + * - spaces sync root not in another spaces sync root + * - space folder not in a syncdb folder (another space folder) + * - space folder *can* be in sync root, if: + * - space folder not in a spaces sync root of other account (check with account uuid) or (possibly branded) client + */ + QString checkPathValidity(const QString &path, NewFolderType folderType, const QUuid &accountUuid) const; + + static std::unique_ptr createInstance(); + ~FolderMan() override; + + /** + * Helper to access the FolderMan instance + * Warning: may be null in unit tests + */ + // TODO: eliminate FolderMan as globally available resource. static FolderMan *instance(); - int setupFolders(); - int setupFoldersMigration(); + /** + * loads all folders from the config into the manager. + * + * returns empty if a downgrade of a folder was detected + * otherwise it will return the number of folders that were set up (this can be zero when no folders were previously configured). + * + * emits folderListChanged + */ + std::optional setupFoldersFromConfig(); + + /** + * + * Extremely important refactoring todo: addFolder should not be public! + * It is currently "required" for some tests which is not really cool, as it does not represent a complete/standalone impl. + * + * core step in any add folder routine. it validates the definition, instantiates vfs, instantiates the folder and validates whether + * it had setup errors. + * + * it is up to the caller to create the local sync folder (corresponding to the local path in the def) using the new FolderManagementUtils::prepareFolder, + * connect the folder, save it to settings, etc. + * + */ + Folder *addFolder(AccountState *accountState, const FolderDefinition &folderDefinition); + + /** + * sets up sync folders/spaces after adding a new account via the gui + */ + void setUpInitialSyncFolders(AccountState *accountState, bool useVfs); + + void setUpInitialSpaces(AccountState *accountState); + + + // Refactoring todo: this function actually just returns the internal vector of folders. I do not see any evidence of + // what is docced here, at least related to this specific function. + // Propose changing the container to a hash or similar anyway to allow fast retrieval of folder by id and other + // maintenance activities. /** Find folder setting keys that need to be ignored or deleted for being too new. * * The client has a maximum supported version for the folders lists (maxFoldersVersion @@ -105,16 +212,10 @@ class FolderMan : public QObject * future configurations (possibly with user confirmation for deletions) and in * FolderMan::setupFolders() to know which too-new folder configurations to skip. */ - static void backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys); - - const QMap &map() const; + QList folders() const; - /** Adds a folder for an account, ensures the journal is gone and saves it in the settings. - */ - Folder *addFolder(AccountState *accountState, const FolderDefinition &folderDefinition); + QList foldersForAccount(const QUuid &accountId); - /** Removes a folder */ - void removeFolder(Folder *); /** * Returns the folder which the file or directory stored in path is in @@ -122,23 +223,7 @@ class FolderMan : public QObject * Optionally, the path relative to the found folder is returned in * relativePath. */ - Folder *folderForPath(const QString &path, QString *relativePath = nullptr); - - /** - * returns a list of local files that exist on the local harddisk for an - * incoming relative server path. The method checks with all existing sync - * folders. - */ - QStringList findFileInLocalFolders(const QString &relPath, const AccountPtr acc); - - /** Returns the folder by alias or NULL if no folder with the alias exists. */ - Folder *folder(const QString &); - - /** - * Migrate accounts from owncloud < 2.0 - * Creates a folder for a specific configuration, identified by alias. - */ - Folder *setupFolderFromOldConfigFile(const QString &, AccountState *account); + Folder *folderForPath(const QString &path, QString *relativePath = nullptr) const; /** * Ensures that a given directory does not contain a sync journal file. @@ -146,36 +231,9 @@ class FolderMan : public QObject * @returns false if the journal could not be removed, true otherwise. */ static bool ensureJournalGone(const QString &journalDbFile); - - /** Creates a new and empty local directory. */ - bool startFromScratch(const QString &); - - /// Produce text for use in the tray tooltip - static QString trayTooltipStatusString(const SyncResult &result, bool paused); - - /** - * Compute status summarizing multiple folders - * @return tuple containing folders, status, unresolvedConflicts and lastSyncDone - */ - static TrayOverallStatusResult trayOverallStatus(const QList &folders); - - // Escaping of the alias which is used in QSettings AND the file - // system, thus need to be escaped. - static QString escapeAlias(const QString &); - static QString unescapeAlias(const QString &); + static bool ensureFilesystemSupported(const FolderDefinition &folderDefinition); SocketApi *socketApi(); -#ifdef Q_OS_WIN - NavigationPaneHelper &navigationPaneHelper() { return _navigationPaneHelper; } -#endif - - /** - * Check if @a path is a valid path for a new folder considering the already sync'ed items. - * Make sure that this folder, or any subfolder is not sync'ed already. - * - * @returns an empty string if it is allowed, or an error if it is not allowed - */ - QString checkPathValidityForNewFolder(const QString &path) const; /** * Attempts to find a non-existing, acceptable path for creating a new sync folder. @@ -186,41 +244,20 @@ class FolderMan : public QObject * subfolder of ~ would be a good candidate. When that happens \a basePath * is returned. */ - QString findGoodPathForNewSyncFolder(const QString &basePath) const; + QString findGoodPathForNewSyncFolder(const QString &basePath, const QString &newFolder, NewFolderType folderType, const QUuid &accountUuid) const; - /** - * While ignoring hidden files can theoretically be switched per folder, - * it's currently a global setting that users can only change for all folders - * at once. - * These helper functions can be removed once it's properly per-folder. - */ bool ignoreHiddenFiles() const; void setIgnoreHiddenFiles(bool ignore); - /** - * Access to the current queue of scheduled folders. - */ - QQueue scheduleQueue() const; + bool moveToTrash() const; + /// This slot will tell all sync engines to reload the sync options. + void updateMoveToTrash(bool trashIt); - /** - * Access to the currently syncing folder. + /** Simple save and remove all folders on shut down * - * Note: This is only the folder that's currently syncing *as-scheduled*. There - * may be externally-managed syncs such as from placeholder hydrations. - * - * See also isAnySyncRunning() - */ - Folder *currentSyncFolder() const; - - /** - * Returns true if any folder is currently syncing. + * emits folderListChanged * - * This might be a FolderMan-scheduled sync, or a externally - * managed sync like a placeholder hydration. */ - bool isAnySyncRunning() const; - - /** Removes all folders */ void unloadAndDeleteAllFolders(); /** @@ -229,24 +266,33 @@ class FolderMan : public QObject */ void setSyncEnabled(bool); - /** Queues a folder for syncing. */ - void scheduleFolder(Folder *); + SyncScheduler *scheduler() { return _scheduler; } - /** Puts a folder in the very front of the queue. */ - void scheduleFolderNext(Folder *); + void setDirtyProxy(); - /** Queues all folders for syncing. */ - void scheduleAllFolders(); + [[nodiscard]] bool isSpaceSynced(GraphApi::Space *space) const; - void setDirtyProxy(); - void setDirtyNetworkLimits(); + /** + * adds a folder from a gui operation. this is a "presetup" step that converts the SycnConnectionDescription to a FolderDefinition + * and does a few other things uniquely required by the gui workflow. + * + * this handler also saves the new folder def that is created by user request + * + * emits folderAdded(newFolder) + */ + // todo: #1 + // todo: #2 + void addFolderFromGui(AccountState *accountState, const SyncConnectionDescription &config); - /** Whether or not vfs is supported in the location. */ - bool checkVfsAvailability(const QString &path, Vfs::Mode mode = bestAvailableVfsMode()) const; + // todo: #3 + void removeFolderSettings(Folder *folder); - /** If the folder configuration is no longer supported this will return an error string */ - Result unsupportedConfiguration(const QString &path) const; -signals: + /** + * @brief folder retrieves the folder given the spaceId, or null if no folder exists for the account and space combo + */ + Folder *folder(const QUuid &accountId, const QString &spaceId) const; + +Q_SIGNALS: /** * signal to indicate a folder has changed its sync state. * @@ -255,28 +301,32 @@ class FolderMan : public QObject void folderSyncStateChange(Folder *); /** - * Indicates when the schedule queue changes. - */ - void scheduleQueueChanged(); - - /** - * Emitted whenever the list of configured folders changes. + * Emitted whenever the list of folders changes substantially. + * this includes after building the list for a new account or restore from config + * also when removing folders in bulk, on account removed or shutdown. */ - void folderListChanged(const QMap &); - void folderRemoved(Folder *folder); - -public slots: + void folderListChanged(const QUuid &accountId, const QList folders); + // emitted on incremental folder additions (eg when the user uses the folder wizard to create a new sync) + void folderAdded(const QUuid &accountId, Folder *folder); + // emitted on incremental folder removal (eg when the user deletes a sync connection via gui) + void folderRemoved(const QUuid &accountId, Folder *folder); + + // still working these out but generally this belongs in folderman more than anywhere else as it can cross ref + // existing folders and track unsynced spaces + // still need to figure out how to "track" synced folder count when a space had a folder but was deleted + // server side. we show these as "unavailable" in the folder list but really not sure how the effectively dead folder + // is "counted" + // void syncedSpaceCountChanged(QUuid accountId, int syncedCount); + // void unsyncedSpacesChanged(QUuid accountId, QSet unsyncedSpaces, int totalSpaceCount); + void unsyncedSpaceCountChanged(QUuid accountId, int unsyncedCount, int totalSpaces); + +public Q_SLOTS: /** * Schedules folders of newly connected accounts, terminates and * de-schedules folders of disconnected accounts. */ - void slotAccountStateChanged(); - - /** - * restart the client as soon as it is possible, ie. no folders syncing. - */ - void slotScheduleAppRestart(); + void slotIsConnectedChanged(); /** * Triggers a sync run once the lock on the given file is removed. @@ -286,113 +336,187 @@ public slots: */ void slotSyncOnceFileUnlocks(const QString &path, FileSystem::LockMode mode); - // slot to schedule an ETag job (from Folder only) - void slotScheduleETagJob(const QString &alias, RequestEtagJob *job); + // emits folderRemoved + void removeFolderFromGui(Folder *f); + void forceFolderSync(Folder *f); + + // this was private which then required Application be a friend of FolderMan... + // I am not sure why the Application seems to be the "master" minder of account manager signals + // but I think this will soon change + void slotServerVersionChanged(Account *account); + +private Q_SLOTS: -private slots: - void slotFolderSyncPaused(Folder *, bool paused); + void slotFolderSyncPauseChanged(Folder *, bool paused); void slotFolderCanSyncChanged(); void slotFolderSyncStarted(); void slotFolderSyncFinished(const SyncResult &); - void slotRunOneEtagJob(); - void slotEtagJobDestroyed(QObject *); + void slotRemoveFoldersForAccount(AccountState *accountState); - // slot to take the next folder from queue and start syncing. - void slotStartScheduledFolderSync(); - void slotEtagPollTimerTimeout(); + // saves folder using an internally created QSettings instance. This is used for "one off" persistence operations + // that should be synced immediately - eg when the user adds a new folder sync from the gui or as slot to eg persist a property + // change + // see second version of saveFolder, below, for use when persisting many folders in a row + void saveFolder(Folder *folder); - void slotRemoveFoldersForAccount(AccountState *accountState); - // Wraps the Folder::syncStateChange() signal into the - // FolderMan::folderSyncStateChange(Folder*) signal. - void slotForwardFolderSyncStateChange(); +private: + explicit FolderMan(); - void slotServerVersionChanged(Account *account); + /** + * Adds a folder "from scratch" as oppossd to from config, which requires less setup than when you create the folder + * from some dynamic operation (eg folders from new account or via the gui add folder sync operations). + */ + Folder *addFolderFromScratch(AccountState *accountState, FolderDefinition &&definition, bool useVfs); /** - * A file whose locks were being monitored has become unlocked. + * private handler connected to spacesManager::ready signal + * this is a bit weird as you have to ask the manager if it's ready then wait for the signal before actually loading + * the spaces. this function loads all the spaces into folders in the FolderMan and saves them in an efficient manner * - * This schedules the folder for synchronization that contains - * the file with the given path. + * emits folderListChanged + */ + void loadSpacesAndCreateFolders(AccountState *accountState, bool useVfs); + + /** + * similar to loadSpacesAndCreateFolders but it skips the folder creation and basically just calls onSpacesAdded to ensure the + * current spaces are known to the folderman when account is created using selective sync instead of sync all or use vfs */ - void slotWatchedFileUnlocked(const QString &path); + void loadSpacesAlone(AccountState *accountState); /** - * Schedules folders whose time to sync has come. + * @brief onSpacesAdded - handles notice from spaces manager that spaces were added + * @param accountId - account the new spaces live in + * @param spaces - the new spaces + * @param totalSpaceCount - total space count after new spaces added * - * Either because a long time has passed since the last sync or - * because of previous failures. + * important change: the spaces manager now filters out disabled spaces automatically so no one else should be trying to figure out + * if a space is disabled or not - they will not be part of the active space set from now on */ - void slotScheduleFolderByTime(); + void onSpacesAdded(const QUuid &accountId, QList spaces, int totalSpaceCount); -private: - /** Adds a new folder, does not add it to the account settings and - * does not set an account on the new folder. - */ - Folder *addFolderInternal(FolderDefinition folderDefinition, - AccountState *accountState, std::unique_ptr vfs); + /** + * @brief onSpacesRemoved - handles notice from spaces manager that spaces were removed + * @param accountId - account the spaces lived in + * @param spaceIds - these are the spoace id's of removed spaces since the pointers are already gone + * @param totalSpaceCount - the number of spaces available after removal + * + * important change: spaces that have been disabled will now appear in the "removed" collection because the spaces manager filters + * them out from the start, now. + */ + void onSpacesRemoved(const QUuid &accountId, QList spaceIds, int totalSpaceCount); + + /** + * reads the folder defs from the config for a single account. + * it is important to use a preconfigured settings instance here instead of using a series of one-off instances, to improve efficiecy + * by reducing file operations when loading many folders. + * + * The settings should be configured to group "Accounts" before it's passed to this function. + * + * returns false when a downgrade of the database is detected, true otherwise. + */ + bool addFoldersFromConfigByAccount(QSettings &settings, AccountState *account); + + // tests folder def for minimum reqs + bool validateFolderDefinition(const FolderDefinition &folderDefinition); + + /** Connects a folder instance, provided it has no setup errors + */ + void connectFolder(Folder *folder); + + /* disconnects a folder instance, provided it has no setup errors + */ + void disconnectFolder(Folder *folder); + + /* disconnects the signals that trigger autosave + * this is a bit dirty imo but we need to disconnect the autosave handling when a folder is removed from the settings + * because in the subsequent step where the folder is removed from the folder man, we will get a stray signal or two that causes it + * to be written back to the config again. + * we can't call removeFromSettings after removeFolderSync as the folder is deleted in that step + * in future when we have a better encapsulation on the folder manager this small detail will be easier to manage. + */ + void disconnectAutoSave(Folder *folder); + + /* saves folder using the given instance of config settings already set to group "Accounts" + * The main purpose of this version is to allow using a single settings instance to persist multiple folders + * in a row before syncing -> we save a lot of expensive file operations this way + */ + void saveFolder(Folder *folder, QSettings &settings); - /* unloads a folder object, does not delete it */ - void unloadFolder(Folder *); + // used to reduce file operation overhead when removing multiple folders + // impl detail: we also disconnect the folder from autosave here! + void removeFolderSettings(Folder *folder, QSettings &settings); - /** Will start a sync after a bit of delay. */ - void startScheduledSyncSoon(); + /** + * Removes a folder sync permanently and deletes the folder + * Not for general folder cleanup + * it is caller's responsibility to remove folder from settings if necessary. + */ + void deleteFolderSync(Folder *); + + /** Queues all folders for syncing. */ + void scheduleAllFolders(); // finds all folder configuration files // and create the folders QString getBackupName(QString fullPathName) const; // makes the folder known to the socket api + // pair this with _socketApi->slotUnregisterPath(folder); void registerFolderWithSocketApi(Folder *folder); - // restarts the application (Linux only) - void restartApplication(); + void scheduleFoldersForAccount(const QUuid &accountId); - void setupFoldersHelper(QSettings &settings, AccountStatePtr account, const QStringList &ignoreKeys, bool backwardsCompatible, bool foldersWithPlaceholders); + // Helper for `checkPathValidity`. It first checks if the folder `path` exists, and if not recusively checks its parent. When a folder + // is found, it checks for sync root markers. See the documentation of `checkPathValidity` for when a path is valid. If the path + // is valid, a null-string is returned. When a path is invalid, an error string is returned. + // + // For example: start with /x/y/z + // First check is if z exists. If not, retry with /x/y. + // Now if /x/y exists, check for markers wether this is used as a spaces folder, or a sync root for another account. If it is, return an error message. If + // not, check /x. When a path is valid, the null string is returned. + static QString findExistingFolderAndCheckValidity(const QString &path, NewFolderType folderType, const QUuid &accountUuid); - QSet _disabledFolders; - QMap _folderMap; QString _folderConfigPath; - Folder *_currentSyncFolder; - QPointer _lastSyncFolder; - bool _syncEnabled; + bool _ignoreHiddenFiles = true; + bool _moveToTrash = false; /// Folder aliases from the settings that weren't read QSet _additionalBlockedFolderAliases; - /// Starts regular etag query jobs - QTimer _etagPollTimer; - /// The currently running etag query - QPointer _currentEtagJob; - /// Watches files that couldn't be synced due to locks QScopedPointer _lockWatcher; - /// Occasionally schedules folders - QTimer _timeScheduler; - /// Scheduled folders that should be synced as soon as possible - QQueue _scheduledFolders; + SyncScheduler *_scheduler; - /// Picks the next scheduled folder and starts the sync - QTimer _startScheduledSyncTimer; - - QScopedPointer _socketApi; -#ifdef Q_OS_WIN - NavigationPaneHelper _navigationPaneHelper; -#endif - - bool _appRestartRequired; + std::unique_ptr _socketApi; mutable QMap> _unsupportedConfigurationError; static FolderMan *_instance; - explicit FolderMan(QObject *parent = nullptr); - friend class OCC::Application; - friend OCC::FolderMan *OCC::TestUtils::folderMan(); - friend class ::TestFolderMigration; + + // the inner hash contains the folder pointers hashed against their spaceId which makes any kind if retrieval *much* + // faster. the folders need to be split by account id for a variety of reasons, but a very important factor is that the "shares" space + // always has the same space id even across accounts. + // uuid is the account id, qstring is the folder's space id, folder is obvious I hope + QHash> _folders; + + + // similar to the _folders container, unsynced spaces need to be split by account id primarily because the "shares" space + // always has the same space id even across accounts. + // inner hash contains the unsynced spaces hashed by spaceid + // uuid is the account id, qstring is the space id, space is self explanatory + QHash> _unsyncedSpaces; + + // as far as I can tell these are paused folders, not to be confused with folders whose space is disabled. We add and remove folders to this + // set when they are paused/resumed but aside from that we don't really do anything with it. + QSet _disabledFolders; + + // the literal is needed to get the tests to build + inline static const QString IgnoreHiddenFilesKey = QStringLiteral("ignoreHiddenFiles"); + inline static const QString MoveToTrashKey = QStringLiteral("moveToTrash"); }; } // namespace OCC -#endif // FOLDERMAN_H diff --git a/src/gui/foldermanagement/CMakeLists.txt b/src/gui/foldermanagement/CMakeLists.txt new file mode 100644 index 00000000000..44e24991104 --- /dev/null +++ b/src/gui/foldermanagement/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(owncloudGui PRIVATE + foldermanagementutils.h + foldermanagementutils.cpp + folderbuilder.h + folderbuilder.cpp +) + diff --git a/src/gui/foldermanagement/folderbuilder.cpp b/src/gui/foldermanagement/folderbuilder.cpp new file mode 100644 index 00000000000..738d8f9cfb7 --- /dev/null +++ b/src/gui/foldermanagement/folderbuilder.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "folderbuilder.h" + +#include "common/syncjournaldb.h" +#include "common/vfs.h" +#include "folder.h" +#include "syncengine.h" + + +namespace OCC { + +Q_LOGGING_CATEGORY(lcFolderBuilder, "gui.folderbuilder", QtInfoMsg) + +FolderBuilder::FolderBuilder(const FolderDefinition &definition, QObject *parent) + : QObject(parent) + , _definition(definition) +{ +} + +Folder *FolderBuilder::buildFolder(AccountState *accountState, bool ignoreHiddenFiles, bool moveToTrash, QObject *parent) +{ + if (!accountState || !accountState->account()) + return nullptr; + + SyncJournalDb *db = buildJournal(); + Vfs *vfs = buildVfs(); + SyncEngine *engine = buildEngine(accountState->account(), db, ignoreHiddenFiles, moveToTrash); + if (db && vfs && engine) { + // for unknown reasons I am getting a warning on potential memory leak for the engine only - it's safe to ignore this as + // all pointers created in this class are parented by the builder (then transferred to folder) so even if + // the folder build fails, the pointers will be cleaned up when builder goes out of scope. + return new Folder(_definition, accountState, db, vfs, engine, parent); + } + return nullptr; +} + +SyncJournalDb *FolderBuilder::buildJournal() +{ + SyncJournalDb *journal = new SyncJournalDb(_definition.absoluteJournalPath(), this); + if (!journal->open()) { + qCWarning(lcFolderBuilder) << "Could not open database when creating new folder: " << _definition.absoluteJournalPath() << ". Aborting Folder build."; + return nullptr; + } + journal->close(); + journal->allowReopen(); + // those errors should not persist over sessions so kill them if there are any in an existing journal + journal->wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::Category::LocalSoftError); + return journal; +} + +Vfs *FolderBuilder::buildVfs() +{ + Vfs *vfs = VfsPluginManager::instance().createVfsFromPlugin(_definition.virtualFilesMode(), this); + if (vfs) + return vfs; + + qCWarning(lcFolderBuilder) << "Could not load vfs plugin for mode" << _definition.virtualFilesMode() << ". Aborting Folder build."; + return nullptr; +} + +SyncEngine *FolderBuilder::buildEngine(Account *account, SyncJournalDb *journal, bool ignoreHiddenFiles, bool moveToTrash) +{ + if (!account || !journal) + return nullptr; + SyncEngine *engine = new SyncEngine(account, _definition.webDavUrl(), _definition.canonicalPath(), _definition.targetPath(), journal, this); + if (!engine->loadDefaultExcludes()) { + qCWarning(lcFolderBuilder, "Engine could not read system exclude file. Aborting Folder build"); + return nullptr; + } + engine->setIgnoreHiddenFiles(ignoreHiddenFiles); + engine->setMoveToTrash(moveToTrash); + return engine; +} + + +} diff --git a/src/gui/foldermanagement/folderbuilder.h b/src/gui/foldermanagement/folderbuilder.h new file mode 100644 index 00000000000..d6c3040a3d0 --- /dev/null +++ b/src/gui/foldermanagement/folderbuilder.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +#include "folder.h" + +namespace OCC { + +class SyncJournalDb; +class SyncEngine; +class Vfs; + +class FolderBuilder : public QObject +{ + Q_OBJECT + +public: + FolderBuilder(const FolderDefinition &definition, QObject *parent = nullptr); + + Folder *buildFolder(AccountState *accountState, bool ignoreHiddenFiles, bool moveToTrash, QObject *parent); + + +private: + SyncJournalDb *buildJournal(); + Vfs *buildVfs(); + SyncEngine *buildEngine(Account *account, SyncJournalDb *journal, bool ignoreHiddenFiles, bool moveToTrash); + + FolderDefinition _definition; +}; +} diff --git a/src/gui/foldermanagement/foldermanagementutils.cpp b/src/gui/foldermanagement/foldermanagementutils.cpp new file mode 100644 index 00000000000..b751f1f9c83 --- /dev/null +++ b/src/gui/foldermanagement/foldermanagementutils.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "foldermanagementutils.h" + +#include "common/asserts.h" +#include "common/filesystembase.h" +#include "theme.h" + +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include "common/utility_win.h" +#endif + + +namespace OCC { + +Q_LOGGING_CATEGORY(lcFolderManagementUtils, "gui.foldermanagementutils", QtInfoMsg) + +bool FolderManagementUtils::prepareFolder(const QString &path) +{ + if (!QFileInfo::exists(path)) { + if (!OC_ENSURE(QDir().mkpath(path))) { + return false; + } +#ifdef Q_OS_WIN + // First create a Desktop.ini so that the folder and favorite link show our application's icon. + const QFileInfo desktopIniPath{QStringLiteral("%1/Desktop.ini").arg(path)}; + { + const QString updateIconKey = QStringLiteral("%1/UpdateIcon").arg(Theme::instance()->appName()); + QSettings desktopIni(desktopIniPath.absoluteFilePath(), QSettings::IniFormat); + if (desktopIni.value(updateIconKey, true).toBool()) { + qCInfo(lcFolderManagementUtils) << "Creating" << desktopIni.fileName() << "to set a folder icon in Explorer."; + desktopIni.setValue(QStringLiteral(".ShellClassInfo/IconResource"), QDir::toNativeSeparators(qApp->applicationFilePath())); + desktopIni.setValue(updateIconKey, true); + } else { + qCInfo(lcFolderManagementUtils) << "Skip icon update for" << desktopIni.fileName() << "," << updateIconKey << "is disabled"; + } + } + + const QString longFolderPath = FileSystem::longWinPath(path); + const QString longDesktopIniPath = FileSystem::longWinPath(desktopIniPath.absoluteFilePath()); + // Set the folder as system and Desktop.ini as hidden+system for explorer to pick it. + // https://msdn.microsoft.com/en-us/library/windows/desktop/cc144102 + const DWORD folderAttrs = GetFileAttributesW(reinterpret_cast(longFolderPath.utf16())); + if (!SetFileAttributesW(reinterpret_cast(longFolderPath.utf16()), folderAttrs | FILE_ATTRIBUTE_SYSTEM)) { + const auto error = GetLastError(); + qCWarning(lcFolderManagementUtils) << "SetFileAttributesW failed on" << longFolderPath << Utility::formatWinError(error); + return false; + } + if (!SetFileAttributesW(reinterpret_cast(longDesktopIniPath.utf16()), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) { + const auto error = GetLastError(); + qCWarning(lcFolderManagementUtils) << "SetFileAttributesW failed on" << longDesktopIniPath << Utility::formatWinError(error); + return false; + } +#else + QFile::Permissions perm = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; + QFile file(path); + if (!file.setPermissions(perm)) { + qCWarning(lcFolderManagementUtils) << "setPermissions failed on " << path; + return false; + } + +#endif + } + return true; +} + +qsizetype FolderManagementUtils::numberOfSyncJournals(const QString &path) +{ + return QDir(path).entryList({QStringLiteral(".sync_*.db"), QStringLiteral("._sync_*.db")}, QDir::Hidden | QDir::Files).size(); +} + +QString FolderManagementUtils::checkPathLength(const QString &path) +{ +#ifdef Q_OS_WIN + if (path.size() > MAX_PATH) { + if (!FileSystem::longPathsEnabledOnWindows()) { + return tr("The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder.").arg(path); + } + } +#else + Q_UNUSED(path) +#endif + return {}; +} + +QString FolderManagementUtils::validateFolderPath(const QString &path) +{ +#ifdef Q_OS_WIN + QNtfsPermissionCheckGuard ntfs_perm; +#endif + const QFileInfo fi(path); + QString error; + if (fi.isDir() && fi.isReadable() && fi.isWritable()) { + auto pathLengthCheck = FolderManagementUtils::checkPathLength(fi.canonicalFilePath()); + if (!pathLengthCheck.isEmpty()) { + error = pathLengthCheck; + } + } else { + // Check directory again + if (!FileSystem::fileExists(path, fi)) { + error = tr("Local folder %1 does not exist.").arg(path); + } else if (!fi.isDir()) { + error = tr("%1 is not a folder.").arg(path); + } else if (!fi.isReadable()) { + error = tr("%1 is not readable.").arg(path); + } else if (!fi.isWritable()) { + error = tr("%1 is not writable.").arg(path); + } + } + if (!error.isEmpty()) { + qCWarning(lcFolderManagementUtils) << error; + } + return error; +} + +} // OCC diff --git a/src/gui/foldermanagement/foldermanagementutils.h b/src/gui/foldermanagement/foldermanagementutils.h new file mode 100644 index 00000000000..69e9c3d417b --- /dev/null +++ b/src/gui/foldermanagement/foldermanagementutils.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 +#include + +namespace OCC { +/** + * @brief The FolderManagementUtils class is a "controlled" collection of general functions related to managing folders and their paths. + * + * indeed, this could be a namespace but I have seen too many abuses of "a namespace can be defined ANYWHERE" to be comfortable with it + * + * Declaring a final, non instantiable class prevents any creative "namespace" decls/defs + * + */ +class FolderManagementUtils final +{ + Q_DECLARE_TR_FUNCTIONS(FolderManagementUtils) + +public: + FolderManagementUtils() = delete; + ~FolderManagementUtils() = delete; + + /** + * @brief prepareFolder creates the folder from given path if it does not already exist, and configures the folder with mac and windows + * specific operations + * @param folder path + * @return true if the folder path exists and was successfully configured + */ + static bool prepareFolder(const QString &path); + + /** + * @brief numberOfSyncJournals counts sync journals by matching folder content with .sync_*.db or ._sync_*.db in the target folder + * @param path is the folder to check for number of sync journals + * @return the number of sync journals found in the given folder. + */ + static qsizetype numberOfSyncJournals(const QString &path); + + /** + * verifies that the length of the given path does not exceed system limits + * if the path fails the check, the return value will contain an error string + * if it passes, the return value will be empty + **/ + static QString checkPathLength(const QString &path); + + /** + * performs various checks on the folder path to ensure it exists and can be used as local sync destination. + * if the checks fail, the return value will contain the error + * if it passes, the return value will be empty. + **/ + static QString validateFolderPath(const QString &path); +}; +} diff --git a/src/gui/folderstatusdelegate.cpp b/src/gui/folderstatusdelegate.cpp deleted file mode 100644 index a970c4f25a3..00000000000 --- a/src/gui/folderstatusdelegate.cpp +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * - * 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 "folderstatusdelegate.h" -#include "folderstatusmodel.h" -#include "folderman.h" -#include "accountstate.h" -#include "theme.h" -#include "account.h" -#include "guiutility.h" - -#include -#include -#include - -inline static QFont makeAliasFont(const QFont &normalFont) -{ - QFont aliasFont = normalFont; - aliasFont.setBold(true); - aliasFont.setPointSize(normalFont.pointSize() + 2); - return aliasFont; -} - -namespace OCC { - -FolderStatusDelegate::FolderStatusDelegate() - : QStyledItemDelegate() -{ -} - -QString FolderStatusDelegate::addFolderText() -{ - return tr("Add Folder Sync Connection"); -} - -// allocate each item size in listview. -QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QFont aliasFont = makeAliasFont(option.font); - QFont font = option.font; - - QFontMetrics fm(font); - QFontMetrics aliasFm(aliasFont); - - auto classif = static_cast(index.model())->classify(index); - if (classif == FolderStatusModel::AddButton) { - const int margins = aliasFm.height(); // same as 2*aliasMargin of paint - QFontMetrics fm(qApp->font("QPushButton")); - QStyleOptionButton opt; - static_cast(opt) = option; - opt.text = addFolderText(); - return QApplication::style()->sizeFromContents( - QStyle::CT_PushButton, &opt, fm.size(Qt::TextSingleLine, opt.text)) - .expandedTo(QApplication::globalStrut()) - + QSize(0, margins); - } - - if (classif != FolderStatusModel::RootFolder) { - return QStyledItemDelegate::sizeHint(option, index); - } - - // calc height - int h = rootFolderHeightWithoutErrors(fm, aliasFm); - // this already includes the bottom margin - - // add some space for the message boxes. - int margin = fm.height() / 4; - for (auto role : {FolderConflictMsg, FolderErrorMsg, FolderInfoMsg}) { - auto msgs = qvariant_cast(index.data(role)); - if (!msgs.isEmpty()) { - h += margin + 2 * margin + msgs.count() * fm.height(); - } - } - - return QSize(0, h); -} - -int FolderStatusDelegate::rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm) -{ - const int aliasMargin = aliasFm.height() / 2; - const int margin = fm.height() / 4; - - int h = aliasMargin; // margin to top - h += aliasFm.height(); // alias - h += margin; // between alias and local path - h += fm.height(); // local path - h += margin; // between local and remote path - h += fm.height(); // remote path - h += margin; // bottom margin - return h; -} - -void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QStyledItemDelegate::paint(painter, option, index); - - auto textAlign = Qt::AlignLeft; - - QFont aliasFont = makeAliasFont(option.font); - QFont subFont = option.font; - QFont errorFont = subFont; - QFont progressFont = subFont; - - progressFont.setPointSize(subFont.pointSize() - 2); - - QFontMetrics subFm(subFont); - QFontMetrics aliasFm(aliasFont); - QFontMetrics progressFm(progressFont); - - int aliasMargin = aliasFm.height() / 2; - int margin = subFm.height() / 4; - - if (index.data(AddButton).toBool()) { - QSize hint = sizeHint(option, index); - QStyleOptionButton opt; - static_cast(opt) = option; - opt.state &= ~QStyle::State_Selected; - opt.state |= QStyle::State_Raised; - opt.text = addFolderText(); - opt.rect.setWidth(qMin(opt.rect.width(), hint.width())); - opt.rect.adjust(0, aliasMargin, 0, -aliasMargin); - opt.rect = QStyle::visualRect(option.direction, option.rect, opt.rect); - painter->save(); - painter->setFont(qApp->font("QPushButton")); - QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, option.widget); - painter->restore(); - return; - } - - if (static_cast(index.model())->classify(index) != FolderStatusModel::RootFolder) { - return; - } - painter->save(); - - const QIcon statusIcon = qvariant_cast(index.data(FolderStatusIconRole)); - const QString aliasText = qvariant_cast(index.data(HeaderRole)); - const QStringList conflictTexts = qvariant_cast(index.data(FolderConflictMsg)); - const QStringList errorTexts = qvariant_cast(index.data(FolderErrorMsg)); - const QStringList infoTexts = qvariant_cast(index.data(FolderInfoMsg)); - - const int overallPercent = qvariant_cast(index.data(SyncProgressOverallPercent)); - const QString overallString = qvariant_cast(index.data(SyncProgressOverallString)); - const QString itemString = qvariant_cast(index.data(SyncProgressItemString)); - const int warningCount = qvariant_cast(index.data(WarningCount)); - const bool syncOngoing = qvariant_cast(index.data(SyncRunning)); - const bool syncEnabled = qvariant_cast(index.data(FolderAccountConnected)); - const QString syncText = qvariant_cast(index.data(FolderSyncText)); - - QRect iconRect = option.rect; - QRect aliasRect = option.rect; - - iconRect.setLeft(option.rect.left() + aliasMargin); - iconRect.setTop(iconRect.top() + aliasMargin); // (iconRect.height()-iconsize.height())/2); - - // alias box - aliasRect.setTop(aliasRect.top() + aliasMargin); - aliasRect.setBottom(aliasRect.top() + aliasFm.height()); - aliasRect.setRight(aliasRect.right() - aliasMargin); - - // remote directory box - QRect remotePathRect = aliasRect; - remotePathRect.setTop(aliasRect.bottom() + margin); - remotePathRect.setBottom(remotePathRect.top() + subFm.height()); - - // local directory box - QRect localPathRect = remotePathRect; - localPathRect.setTop(remotePathRect.bottom() + margin); - localPathRect.setBottom(localPathRect.top() + subFm.height()); - - iconRect.setBottom(localPathRect.bottom()); - iconRect.setWidth(iconRect.height()); - - int nextToIcon = iconRect.right() + aliasMargin; - aliasRect.setLeft(nextToIcon); - localPathRect.setLeft(nextToIcon); - remotePathRect.setLeft(nextToIcon); - - int iconSize = iconRect.width(); - - auto optionsButtonVisualRect = optionsButtonRect(option.rect, option.direction); - - QPixmap pm = statusIcon.pixmap(iconSize, iconSize, syncEnabled ? QIcon::Normal : QIcon::Disabled); - painter->drawPixmap(QStyle::visualRect(option.direction, option.rect, iconRect).left(), - iconRect.top(), pm); - - // only show the warning icon if the sync is running. Otherwise its - // encoded in the status icon. - if (warningCount > 0 && syncOngoing) { - QRect warnRect; - warnRect.setLeft(iconRect.left()); - warnRect.setTop(iconRect.bottom() - 17); - warnRect.setWidth(16); - warnRect.setHeight(16); - - const QIcon warnIcon = Utility::getCoreIcon(QStringLiteral("warning")); - QPixmap pm = warnIcon.pixmap(16, 16, syncEnabled ? QIcon::Normal : QIcon::Disabled); - warnRect = QStyle::visualRect(option.direction, option.rect, warnRect); - painter->drawPixmap(QPoint(warnRect.left(), warnRect.top()), pm); - } - - auto palette = option.palette; - - if (qApp->style()->inherits("QWindowsVistaStyle")) { - // Hack: Windows Vista's light blue is not contrasting enough for white - - // (code from QWindowsVistaStyle::drawControl for CE_ItemViewItem) - palette.setColor(QPalette::All, QPalette::HighlightedText, palette.color(QPalette::Active, QPalette::Text)); - palette.setColor(QPalette::All, QPalette::Highlight, palette.base().color().darker(108)); - } - - - QPalette::ColorGroup cg = option.state & QStyle::State_Enabled - ? QPalette::Normal - : QPalette::Disabled; - if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) - cg = QPalette::Inactive; - - if (option.state & QStyle::State_Selected) { - painter->setPen(palette.color(cg, QPalette::HighlightedText)); - } else { - painter->setPen(palette.color(cg, QPalette::Text)); - } - QString elidedAlias = aliasFm.elidedText(aliasText, Qt::ElideRight, aliasRect.width()); - painter->setFont(aliasFont); - painter->drawText(QStyle::visualRect(option.direction, option.rect, aliasRect), textAlign, elidedAlias); - - const bool showProgess = !overallString.isEmpty() || !itemString.isEmpty(); - if (!showProgess) { - painter->setFont(subFont); - const QString elidedRemotePathText = subFm.elidedText( - syncText, - Qt::ElideRight, remotePathRect.width()); - painter->drawText(QStyle::visualRect(option.direction, option.rect, remotePathRect), - textAlign, elidedRemotePathText); - } - - int h = iconRect.bottom() + margin; - - // paint an error overlay if there is an error string or conflict string - auto drawTextBox = [&](const QStringList &texts, QColor color) { - QRect rect = localPathRect; - rect.setLeft(iconRect.left()); - rect.setTop(h); - rect.setHeight(texts.count() * subFm.height() + 2 * margin); - rect.setRight(option.rect.right() - margin); - - painter->save(); - painter->setBrush(color); - painter->setPen(QColor(0xaa, 0xaa, 0xaa)); - painter->drawRoundedRect(QStyle::visualRect(option.direction, option.rect, rect), - 4, 4); - painter->setPen(Qt::white); - painter->setFont(errorFont); - QRect textRect(rect.left() + margin, - rect.top() + margin, - rect.width() - 2 * margin, - subFm.height()); - - for (const auto &eText : texts) { - painter->drawText(QStyle::visualRect(option.direction, option.rect, textRect), textAlign, - subFm.elidedText(eText, Qt::ElideLeft, textRect.width())); - textRect.translate(0, textRect.height()); - } - painter->restore(); - - h = rect.bottom() + margin; - }; - - if (!conflictTexts.isEmpty()) - drawTextBox(conflictTexts, QColor(0xba, 0xba, 0x4d)); - if (!errorTexts.isEmpty()) - drawTextBox(errorTexts, QColor(0xbb, 0x4d, 0x4d)); - if (!infoTexts.isEmpty()) - drawTextBox(infoTexts, QColor(0x4d, 0x4d, 0xba)); - - // Sync File Progress Bar: Show it if syncFile is not empty. - if (showProgess) { - int fileNameTextHeight = subFm.boundingRect(tr("File")).height(); - int barHeight = 7; // same height as quota bar - int overallWidth = option.rect.right() - aliasMargin - optionsButtonVisualRect.width() - nextToIcon; - - painter->save(); - - // Overall Progress Bar. - QRect pBRect; - pBRect.setTop(remotePathRect.top()); - pBRect.setLeft(nextToIcon); - pBRect.setHeight(barHeight); - pBRect.setWidth(overallWidth - 2 * margin); - - QStyleOptionProgressBar pBarOpt; - - pBarOpt.state = option.state | QStyle::State_Horizontal; - pBarOpt.minimum = 0; - pBarOpt.maximum = 100; - pBarOpt.progress = overallPercent; - pBarOpt.orientation = Qt::Horizontal; - pBarOpt.rect = QStyle::visualRect(option.direction, option.rect, pBRect); - QApplication::style()->drawControl(QStyle::CE_ProgressBar, &pBarOpt, painter, option.widget); - - - // Overall Progress Text - QRect overallProgressRect; - overallProgressRect.setTop(pBRect.bottom() + margin); - overallProgressRect.setHeight(fileNameTextHeight); - overallProgressRect.setLeft(pBRect.left()); - overallProgressRect.setWidth(pBRect.width()); - painter->setFont(progressFont); - - painter->drawText(QStyle::visualRect(option.direction, option.rect, overallProgressRect), - Qt::AlignLeft | Qt::AlignVCenter, overallString); - // painter->drawRect(overallProgressRect); - - painter->restore(); - } - - painter->restore(); - - { - QStyleOptionToolButton btnOpt; - btnOpt.state = option.state; - btnOpt.state &= ~(QStyle::State_Selected | QStyle::State_HasFocus); - btnOpt.state |= QStyle::State_Raised; - btnOpt.arrowType = Qt::NoArrow; - btnOpt.subControls = QStyle::SC_ToolButton; - btnOpt.rect = optionsButtonVisualRect; - btnOpt.icon = Utility::getCoreIcon(QStringLiteral("more")); - int e = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); - btnOpt.iconSize = QSize(e,e); - QApplication::style()->drawComplexControl(QStyle::CC_ToolButton, &btnOpt, painter); - } -} - -QRect FolderStatusDelegate::optionsButtonRect(QRect within, Qt::LayoutDirection direction) -{ - QFont font = QFont(); - QFont aliasFont = makeAliasFont(font); - QFontMetrics fm(font); - QFontMetrics aliasFm(aliasFont); - within.setHeight(FolderStatusDelegate::rootFolderHeightWithoutErrors(fm, aliasFm)); - - QStyleOptionToolButton opt; - int e = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize); - opt.rect.setSize(QSize(e,e)); - QSize size = QApplication::style()->sizeFromContents(QStyle::CT_ToolButton, &opt, opt.rect.size()).expandedTo(QApplication::globalStrut()); - - int margin = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); - QRect r(QPoint(within.right() - size.width() - margin, - within.top() + within.height() / 2 - size.height() / 2), - size); - return QStyle::visualRect(direction, within, r); -} - -QRect FolderStatusDelegate::errorsListRect(QRect within, const QModelIndex &index) -{ - QFont font = QFont(); - QFont aliasFont = makeAliasFont(font); - QFontMetrics fm(font); - QFontMetrics aliasFm(aliasFont); - within.setTop(within.top() + FolderStatusDelegate::rootFolderHeightWithoutErrors(fm, aliasFm)); - int margin = fm.height() / 4; - int h = 0; - for (auto role : {FolderConflictMsg, FolderErrorMsg}) { - auto msgs = qvariant_cast(index.data(role)); - if (!msgs.isEmpty()) { - h += margin + 2 * margin + msgs.count() * fm.height(); - } - } - within.setHeight(h); - return within; -} - - -} // namespace OCC diff --git a/src/gui/folderstatusdelegate.h b/src/gui/folderstatusdelegate.h deleted file mode 100644 index ab207300fe2..00000000000 --- a/src/gui/folderstatusdelegate.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * 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 - -namespace OCC { - -/** - * @brief The FolderStatusDelegate class - * @ingroup gui - */ -class FolderStatusDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - FolderStatusDelegate(); - - enum datarole { FolderAliasRole = Qt::UserRole + 100, - HeaderRole, - FolderPathRole, // for a SubFolder it's the complete path - FolderSecondPathRole, - FolderConflictMsg, - FolderErrorMsg, - FolderInfoMsg, - FolderSyncPaused, - FolderStatusIconRole, - FolderAccountConnected, - - SyncProgressOverallPercent, - SyncProgressOverallString, - SyncProgressItemString, - WarningCount, - SyncRunning, - - AddButton, // 1 = enabled; 2 = disabled - FolderSyncText, - DataRoleCount, - - IsReady // boolean - }; - void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; - QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override; - - - /** - * return the position of the option button within the item - */ - static QRect optionsButtonRect(QRect within, Qt::LayoutDirection direction); - static QRect errorsListRect(QRect within, const QModelIndex &); - static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm); - -private: - static QString addFolderText(); -}; - -} // namespace OCC diff --git a/src/gui/folderstatusmodel.cpp b/src/gui/folderstatusmodel.cpp deleted file mode 100644 index 06f64118b76..00000000000 --- a/src/gui/folderstatusmodel.cpp +++ /dev/null @@ -1,1260 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 "folderstatusmodel.h" -#include "folderman.h" -#include "accountstate.h" -#include "common/asserts.h" -#include -#include -#include "folderstatusdelegate.h" - -#include -#include -#include - -using namespace std::chrono_literals; - -Q_DECLARE_METATYPE(QPersistentModelIndex) - -namespace OCC { - -Q_LOGGING_CATEGORY(lcFolderStatus, "gui.folder.model", QtInfoMsg) - -static const char propertyParentIndexC[] = "oc_parentIndex"; -static const char propertyPermissionMap[] = "oc_permissionMap"; - -static QString removeTrailingSlash(const QString &s) -{ - if (s.endsWith('/')) { - return s.left(s.size() - 1); - } - return s; -} - -FolderStatusModel::FolderStatusModel(QObject *parent) - : QAbstractItemModel(parent) - , _accountState(nullptr) - , _dirty(false) -{ -} - -FolderStatusModel::~FolderStatusModel() -{ -} - -static bool sortByFolderHeader(const FolderStatusModel::SubFolderInfo &lhs, const FolderStatusModel::SubFolderInfo &rhs) -{ - return QString::compare(lhs._folder->shortGuiRemotePathOrAppName(), - rhs._folder->shortGuiRemotePathOrAppName(), - Qt::CaseInsensitive) - < 0; -} - -void FolderStatusModel::setAccountState(const AccountState *accountState) -{ - beginResetModel(); - _dirty = false; - _folders.clear(); - _accountState = accountState; - - connect(FolderMan::instance(), &FolderMan::folderSyncStateChange, - this, &FolderStatusModel::slotFolderSyncStateChange, Qt::UniqueConnection); - connect(FolderMan::instance(), &FolderMan::scheduleQueueChanged, - this, &FolderStatusModel::slotFolderScheduleQueueChanged, Qt::UniqueConnection); - - for (const auto &f : FolderMan::instance()->map()) { - if (!accountState) - break; - if (f->accountState() != accountState) - continue; - SubFolderInfo info; - info._name = f->alias(); - info._path = "/"; - info._folder = f; - info._checked = Qt::PartiallyChecked; - _folders << info; - - connect(f, &Folder::progressInfo, this, &FolderStatusModel::slotSetProgress, Qt::UniqueConnection); - connect(f, &Folder::newBigFolderDiscovered, this, &FolderStatusModel::slotNewBigFolder, Qt::UniqueConnection); - } - - // Sort by header text - std::sort(_folders.begin(), _folders.end(), sortByFolderHeader); - - // Set the root _pathIdx after the sorting - for (int i = 0; i < _folders.size(); ++i) { - _folders[i]._pathIdx << i; - } - - endResetModel(); - emit dirtyChanged(); -} - - -Qt::ItemFlags FolderStatusModel::flags(const QModelIndex &index) const -{ - if (!_accountState) { - return nullptr; - } - - // Always enable the item. If it isn't enabled, it cannot be in the selection model, so all - // actions from the context menu and the pop-up menu will have some other model index than the - // one under the mouse cursor! - const auto flags = Qt::ItemIsEnabled; - - switch (classify(index)) { - case AddButton: { - Qt::ItemFlags ret; - ret = Qt::ItemNeverHasChildren; - if (!_accountState->isConnected()) { - return ret; - } - return flags | ret; - } - case FetchLabel: - return flags | Qt::ItemNeverHasChildren; - case RootFolder: - return flags; - case SubFolder: - return flags | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; - } - return Qt::NoItemFlags; -} - -QVariant FolderStatusModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid()) - return QVariant(); - - if (role == Qt::EditRole) - return QVariant(); - - switch (classify(index)) { - case AddButton: { - if (role == FolderStatusDelegate::AddButton) { - return QVariant(true); - } else if (role == Qt::ToolTipRole) { - if (!_accountState->isConnected()) { - return tr("You need to be connected to add a folder"); - } - return tr("Click this button to add a folder to synchronize."); - } - return QVariant(); - } - case SubFolder: { - const auto &x = static_cast(index.internalPointer())->_subs.at(index.row()); - switch (role) { - case Qt::DisplayRole: - //: Example text: "File.txt (23KB)" - return x._size < 0 ? x._name : tr("%1 (%2)").arg(x._name, Utility::octetsToString(x._size)); - case Qt::ToolTipRole: - return QString(QLatin1String("") + Utility::escape(x._size < 0 ? x._name : tr("%1 (%2)").arg(x._name, Utility::octetsToString(x._size))) + QLatin1String("")); - case Qt::CheckStateRole: - return x._checked; - case Qt::DecorationRole: - return QFileIconProvider().icon(x._isExternal ? QFileIconProvider::Network : QFileIconProvider::Folder); - case Qt::ForegroundRole: - if (x._isUndecided) { - return QColor(Qt::red); - } - break; - case FolderStatusDelegate::FolderPathRole: { - auto f = x._folder; - if (!f) - return QVariant(); - return QVariant(f->path() + x._path); - } - } - } - return QVariant(); - case FetchLabel: { - const auto x = static_cast(index.internalPointer()); - switch (role) { - case Qt::DisplayRole: - if (x->_hasError) { - return QVariant(tr("Error while loading the list of folders from the server.") - + QString("\n") + x->_lastErrorString); - } else { - return tr("Fetching folder list from server..."); - } - break; - default: - return QVariant(); - } - } - case RootFolder: - break; - } - - const SubFolderInfo &folderInfo = _folders.at(index.row()); - auto f = folderInfo._folder; - if (!f) - return QVariant(); - - const SubFolderInfo::Progress &progress = folderInfo._progress; - const bool accountConnected = _accountState->isConnected(); - - switch (role) { - case FolderStatusDelegate::FolderPathRole: - return f->shortGuiLocalPath(); - case FolderStatusDelegate::FolderSecondPathRole: - return f->remotePath(); - case FolderStatusDelegate::FolderConflictMsg: - return (f->syncResult().hasUnresolvedConflicts()) - ? QStringList(tr("There are unresolved conflicts. Click for details.")) - : QStringList(); - case FolderStatusDelegate::FolderErrorMsg: { - auto errors = f->syncResult().errorStrings(); - const auto legacyError = FolderMan::instance()->unsupportedConfiguration(f->path()); - if (!legacyError) { - // the error message might contain new lines, the delegate only expect multiple single line values - errors.append(legacyError.error().split(QLatin1Char('\n'))); - } - return errors; - } - case FolderStatusDelegate::FolderInfoMsg: - return f->isReady() && f->virtualFilesEnabled() && f->vfs().mode() != Vfs::Mode::WindowsCfApi - ? QStringList(tr("Virtual file support is enabled.")) - : QStringList(); - case FolderStatusDelegate::SyncRunning: - return f->syncResult().status() == SyncResult::SyncRunning; - case FolderStatusDelegate::HeaderRole: - return f->shortGuiRemotePathOrAppName(); - case FolderStatusDelegate::FolderAliasRole: - return f->alias(); - case FolderStatusDelegate::FolderSyncPaused: - return f->syncPaused(); - case FolderStatusDelegate::FolderAccountConnected: - return accountConnected; - case Qt::ToolTipRole: { - QString toolTip; - if (!progress.isNull()) { - return progress._progressString; - } - if (accountConnected) - toolTip = Theme::instance()->statusHeaderText(f->syncResult().status()); - else - toolTip = tr("Signed out"); - toolTip += "\n"; - toolTip += folderInfo._folder->path(); - return toolTip; - } - case FolderStatusDelegate::FolderStatusIconRole: - if (accountConnected) { - auto theme = Theme::instance(); - if (f->syncPaused()) { - return theme->folderDisabledIcon(); - } else { - return theme->syncStateIcon(f->syncResult().status()); - } - } else { - return Theme::instance()->folderOfflineIcon(); - } - case FolderStatusDelegate::SyncProgressItemString: - return progress._progressString; - case FolderStatusDelegate::WarningCount: - return progress._warningCount; - case FolderStatusDelegate::SyncProgressOverallPercent: - return progress._overallPercent; - case FolderStatusDelegate::SyncProgressOverallString: - return progress._overallSyncString; - case FolderStatusDelegate::FolderSyncText: - return tr("Local folder: %1").arg(f->shortGuiLocalPath()); - case FolderStatusDelegate::IsReady: - return f->isReady(); - } - return QVariant(); -} - -bool FolderStatusModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (role == Qt::CheckStateRole) { - auto info = infoForIndex(index); - Qt::CheckState checked = static_cast(value.toInt()); - - if (info && info->_checked != checked) { - info->_checked = checked; - if (checked == Qt::Checked) { - // If we are checked, check that we may need to check the parent as well if - // all the siblings are also checked - QModelIndex parent = index.parent(); - auto parentInfo = infoForIndex(parent); - if (parentInfo && parentInfo->_checked != Qt::Checked) { - bool hasUnchecked = false; - for (const auto &sub : qAsConst(parentInfo->_subs)) { - if (sub._checked != Qt::Checked) { - hasUnchecked = true; - break; - } - } - if (!hasUnchecked) { - setData(parent, Qt::Checked, Qt::CheckStateRole); - } else if (parentInfo->_checked == Qt::Unchecked) { - setData(parent, Qt::PartiallyChecked, Qt::CheckStateRole); - } - } - // also check all the children - for (int i = 0; i < info->_subs.count(); ++i) { - if (info->_subs.at(i)._checked != Qt::Checked) { - setData(this->index(i, 0, index), Qt::Checked, Qt::CheckStateRole); - } - } - } - - if (checked == Qt::Unchecked) { - QModelIndex parent = index.parent(); - auto parentInfo = infoForIndex(parent); - if (parentInfo && parentInfo->_checked == Qt::Checked) { - setData(parent, Qt::PartiallyChecked, Qt::CheckStateRole); - } - - // Uncheck all the children - for (int i = 0; i < info->_subs.count(); ++i) { - if (info->_subs.at(i)._checked != Qt::Unchecked) { - setData(this->index(i, 0, index), Qt::Unchecked, Qt::CheckStateRole); - } - } - } - - if (checked == Qt::PartiallyChecked) { - QModelIndex parent = index.parent(); - auto parentInfo = infoForIndex(parent); - if (parentInfo && parentInfo->_checked != Qt::PartiallyChecked) { - setData(parent, Qt::PartiallyChecked, Qt::CheckStateRole); - } - } - } - _dirty = true; - emit dirtyChanged(); - emit dataChanged(index, index, QVector() << role); - return true; - } - return QAbstractItemModel::setData(index, value, role); -} - - -int FolderStatusModel::columnCount(const QModelIndex &) const -{ - return 1; -} - -int FolderStatusModel::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) { - if (Theme::instance()->singleSyncFolder() && _folders.count() != 0) { - // "Add folder" button not visible in the singleSyncFolder configuration. - return _folders.count(); - } - return _folders.count() + 1; // +1 for the "add folder" button - } - auto info = infoForIndex(parent); - if (!info) - return 0; - if (info->hasLabel()) - return 1; - return info->_subs.count(); -} - -FolderStatusModel::ItemType FolderStatusModel::classify(const QModelIndex &index) const -{ - if (auto sub = static_cast(index.internalPointer())) { - if (sub->hasLabel()) { - return FetchLabel; - } else { - return SubFolder; - } - } - if (index.row() < _folders.count()) { - return RootFolder; - } - return AddButton; -} - -FolderStatusModel::SubFolderInfo *FolderStatusModel::infoForIndex(const QModelIndex &index) const -{ - if (!index.isValid()) - return nullptr; - if (auto parentInfo = static_cast(index.internalPointer())) { - if (parentInfo->hasLabel()) { - return nullptr; - } - if (index.row() >= parentInfo->_subs.size()) { - return nullptr; - } - return &parentInfo->_subs[index.row()]; - } else { - if (index.row() >= _folders.count()) { - // AddButton - return nullptr; - } - return const_cast(&_folders[index.row()]); - } -} - -QModelIndex FolderStatusModel::indexForPath(Folder *f, const QString &path) const -{ - if (!f) { - return QModelIndex(); - } - - int slashPos = path.lastIndexOf('/'); - if (slashPos == -1) { - // first level folder - for (int i = 0; i < _folders.size(); ++i) { - auto &info = _folders.at(i); - if (info._folder == f) { - if (path.isEmpty()) { // the folder object - return index(i, 0); - } - for (int j = 0; j < info._subs.size(); ++j) { - const QString subName = info._subs.at(j)._name; - if (subName == path) { - return index(j, 0, index(i)); - } - } - return QModelIndex(); - } - } - return QModelIndex(); - } - - auto parent = indexForPath(f, path.left(slashPos)); - if (!parent.isValid()) - return parent; - - if (slashPos == path.size() - 1) { - // The slash is the last part, we found our index - return parent; - } - - auto parentInfo = infoForIndex(parent); - if (!parentInfo) { - return QModelIndex(); - } - for (int i = 0; i < parentInfo->_subs.size(); ++i) { - if (parentInfo->_subs.at(i)._name == path.mid(slashPos + 1)) { - return index(i, 0, parent); - } - } - - return QModelIndex(); -} - -QModelIndex FolderStatusModel::index(int row, int column, const QModelIndex &parent) const -{ - if (!parent.isValid()) { - return createIndex(row, column /*, nullptr*/); - } - switch (classify(parent)) { - case AddButton: - case FetchLabel: - return QModelIndex(); - case RootFolder: - if (_folders.count() <= parent.row()) - return QModelIndex(); // should not happen - return createIndex(row, column, const_cast(&_folders[parent.row()])); - case SubFolder: { - auto pinfo = static_cast(parent.internalPointer()); - if (pinfo->_subs.count() <= parent.row()) - return QModelIndex(); // should not happen - auto &info = pinfo->_subs[parent.row()]; - if (!info.hasLabel() - && info._subs.count() <= row) - return QModelIndex(); // should not happen - return createIndex(row, column, &info); - } - } - return QModelIndex(); -} - -QModelIndex FolderStatusModel::parent(const QModelIndex &child) const -{ - if (!child.isValid()) { - return QModelIndex(); - } - switch (classify(child)) { - case RootFolder: - case AddButton: - return QModelIndex(); - case SubFolder: - case FetchLabel: - break; - } - auto pathIdx = static_cast(child.internalPointer())->_pathIdx; - int i = 1; - OC_ASSERT(pathIdx.at(0) < _folders.count()); - if (pathIdx.count() == 1) { - return createIndex(pathIdx.at(0), 0 /*, nullptr*/); - } - - const SubFolderInfo *info = &_folders[pathIdx.at(0)]; - while (i < pathIdx.count() - 1) { - OC_ASSERT(pathIdx.at(i) < info->_subs.count()); - info = &info->_subs.at(pathIdx.at(i)); - ++i; - } - return createIndex(pathIdx.at(i), 0, const_cast(info)); -} - -bool FolderStatusModel::hasChildren(const QModelIndex &parent) const -{ - if (!parent.isValid()) - return true; - - auto info = infoForIndex(parent); - if (!info) - return false; - - if (info->_folder && !info->_folder->supportsSelectiveSync()) - return false; - - if (!info->_fetched) - return true; - - if (info->_subs.isEmpty()) - return false; - - return true; -} - - -bool FolderStatusModel::canFetchMore(const QModelIndex &parent) const -{ - if (!_accountState) { - return false; - } - if (_accountState->state() != AccountState::Connected) { - return false; - } - auto info = infoForIndex(parent); - if (!info || info->_fetched || info->_fetchingJob) - return false; - if (info->_hasError) { - // Keep showing the error to the user, it will be hidden when the account reconnects - return false; - } - if (info->_folder && info->_folder->isReady() && !info->_folder->supportsSelectiveSync()) { - // Selective sync is hidden in that case - return false; - } - return true; -} - - -void FolderStatusModel::fetchMore(const QModelIndex &parent) -{ - auto info = infoForIndex(parent); - - if (!info || info->_fetched || info->_fetchingJob) - return; - info->resetSubs(this, parent); - QString path = info->_folder->remotePathTrailingSlash(); - if (info->_path != QLatin1String("/")) { - path += info->_path; - } - LsColJob *job = new LsColJob(_accountState->account(), path, this); - info->_fetchingJob = job; - job->setProperties(QList() << "resourcetype" - << "http://owncloud.org/ns:size" - << "http://owncloud.org/ns:permissions"); - job->setTimeout(60s); - connect(job, &LsColJob::directoryListingSubfolders, - this, &FolderStatusModel::slotUpdateDirectories); - connect(job, &LsColJob::finishedWithError, - this, &FolderStatusModel::slotLscolFinishedWithError); - connect(job, &LsColJob::directoryListingIterated, - this, &FolderStatusModel::slotGatherPermissions); - - job->start(); - - QPersistentModelIndex persistentIndex(parent); - job->setProperty(propertyParentIndexC, QVariant::fromValue(persistentIndex)); - - // Show 'fetching data...' hint after a while. - _fetchingItems[persistentIndex].start(); - QTimer::singleShot(1s, this, &FolderStatusModel::slotShowFetchProgress); -} - -void FolderStatusModel::resetAndFetch(const QModelIndex &parent) -{ - auto info = infoForIndex(parent); - info->resetSubs(this, parent); - fetchMore(parent); -} - -void FolderStatusModel::slotGatherPermissions(const QString &href, const QMap &map) -{ - auto it = map.find("permissions"); - if (it == map.end()) - return; - - auto job = sender(); - auto permissionMap = job->property(propertyPermissionMap).toMap(); - job->setProperty(propertyPermissionMap, QVariant()); // avoid a detach of the map while it is modified - OC_ASSERT_X(!href.endsWith(QLatin1Char('/')), "LsColXMLParser::parse should remove the trailing slash before calling us."); - permissionMap[href] = *it; - job->setProperty(propertyPermissionMap, permissionMap); -} - -void FolderStatusModel::slotUpdateDirectories(const QStringList &list) -{ - auto job = qobject_cast(sender()); - OC_ASSERT(job); - QModelIndex idx = qvariant_cast(job->property(propertyParentIndexC)); - auto parentInfo = infoForIndex(idx); - if (!parentInfo) { - return; - } - if (!parentInfo->_folder->supportsSelectiveSync()) { - return; - } - OC_ASSERT(parentInfo->_fetchingJob == job); - OC_ASSERT(parentInfo->_subs.isEmpty()); - - if (parentInfo->hasLabel()) { - beginRemoveRows(idx, 0, 0); - parentInfo->_hasError = false; - parentInfo->_fetchingLabel = false; - endRemoveRows(); - } - - parentInfo->_lastErrorString.clear(); - parentInfo->_fetchingJob = nullptr; - parentInfo->_fetched = true; - - QUrl url = parentInfo->_folder->remoteUrl(); - QString pathToRemove = url.path(); - if (!pathToRemove.endsWith('/')) - pathToRemove += '/'; - - QStringList selectiveSyncBlackList; - bool ok1 = true; - bool ok2 = true; - if (parentInfo->_checked == Qt::PartiallyChecked) { - selectiveSyncBlackList = parentInfo->_folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok1); - } - auto selectiveSyncUndecidedList = parentInfo->_folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, &ok2); - - if (!(ok1 && ok2)) { - qCWarning(lcFolderStatus) << "Could not retrieve selective sync info from journal"; - return; - } - - std::set selectiveSyncUndecidedSet; // not QSet because it's not sorted - for (const auto &str : qAsConst(selectiveSyncUndecidedList)) { - if (str.startsWith(parentInfo->_path) || parentInfo->_path == QLatin1String("/")) { - selectiveSyncUndecidedSet.insert(str); - } - } - const auto permissionMap = job->property(propertyPermissionMap).toMap(); - - QStringList sortedSubfolders = list; - if (!sortedSubfolders.isEmpty()) - sortedSubfolders.removeFirst(); // skip the parent item (first in the list) - Utility::sortFilenames(sortedSubfolders); - - QVarLengthArray undecidedIndexes; - - QVector newSubs; - newSubs.reserve(sortedSubfolders.size()); - for (const auto &path : qAsConst(sortedSubfolders)) { - auto relativePath = path.mid(pathToRemove.size()); - if (parentInfo->_folder->isFileExcludedRelative(relativePath)) { - continue; - } - - SubFolderInfo newInfo; - newInfo._folder = parentInfo->_folder; - newInfo._pathIdx = parentInfo->_pathIdx; - newInfo._pathIdx << newSubs.size(); - newInfo._size = job->sizes().value(path); - newInfo._isExternal = permissionMap.value(removeTrailingSlash(path)).toString().contains("M"); - newInfo._path = relativePath; - newInfo._name = removeTrailingSlash(relativePath).split('/').last(); - - if (relativePath.isEmpty()) - continue; - - if (parentInfo->_checked == Qt::Unchecked) { - newInfo._checked = Qt::Unchecked; - } else if (parentInfo->_checked == Qt::Checked) { - newInfo._checked = Qt::Checked; - } else { - for (const auto &str : qAsConst(selectiveSyncBlackList)) { - if (str == relativePath || str == QLatin1String("/")) { - newInfo._checked = Qt::Unchecked; - break; - } else if (str.startsWith(relativePath)) { - newInfo._checked = Qt::PartiallyChecked; - } - } - } - - auto it = selectiveSyncUndecidedSet.lower_bound(relativePath); - if (it != selectiveSyncUndecidedSet.end()) { - if (*it == relativePath) { - newInfo._isUndecided = true; - selectiveSyncUndecidedSet.erase(it); - } else if ((*it).startsWith(relativePath)) { - undecidedIndexes.append(newInfo._pathIdx.last()); - - // Remove all the items from the selectiveSyncUndecidedSet that starts with this path - QString relativePathNext = relativePath; - relativePathNext[relativePathNext.length() - 1].unicode()++; - auto it2 = selectiveSyncUndecidedSet.lower_bound(relativePathNext); - selectiveSyncUndecidedSet.erase(it, it2); - } - } - newSubs.append(newInfo); - } - - if (!newSubs.isEmpty()) { - beginInsertRows(idx, 0, newSubs.size() - 1); - parentInfo->_subs = std::move(newSubs); - endInsertRows(); - } - - for (auto it = undecidedIndexes.begin(); it != undecidedIndexes.end(); ++it) { - suggestExpand(index(*it, 0, idx)); - } - /* Try to remove the the undecided lists the items that are not on the server. */ - auto it = std::remove_if(selectiveSyncUndecidedList.begin(), selectiveSyncUndecidedList.end(), - [&](const QString &s) { return selectiveSyncUndecidedSet.count(s); }); - if (it != selectiveSyncUndecidedList.end()) { - selectiveSyncUndecidedList.erase(it, selectiveSyncUndecidedList.end()); - parentInfo->_folder->journalDb()->setSelectiveSyncList( - SyncJournalDb::SelectiveSyncUndecidedList, selectiveSyncUndecidedList); - emit dirtyChanged(); - } -} - -void FolderStatusModel::slotLscolFinishedWithError(QNetworkReply *r) -{ - auto job = qobject_cast(sender()); - OC_ASSERT(job); - QModelIndex idx = qvariant_cast(job->property(propertyParentIndexC)); - if (!idx.isValid()) { - return; - } - auto parentInfo = infoForIndex(idx); - if (parentInfo) { - qCDebug(lcFolderStatus) << r->errorString(); - parentInfo->_lastErrorString = r->errorString(); - auto error = r->error(); - - parentInfo->resetSubs(this, idx); - - if (error == QNetworkReply::ContentNotFoundError) { - parentInfo->_fetched = true; - } else { - OC_ASSERT(!parentInfo->hasLabel()); - beginInsertRows(idx, 0, 0); - parentInfo->_hasError = true; - endInsertRows(); - } - } -} - -QStringList FolderStatusModel::createBlackList(const FolderStatusModel::SubFolderInfo &root, - const QStringList &oldBlackList) const -{ - switch (root._checked) { - case Qt::Unchecked: - return QStringList(root._path); - case Qt::Checked: - return QStringList(); - case Qt::PartiallyChecked: - break; - } - - QStringList result; - if (root._fetched) { - for (int i = 0; i < root._subs.count(); ++i) { - result += createBlackList(root._subs.at(i), oldBlackList); - } - } else { - // We did not load from the server so we re-use the one from the old black list - const QString path = root._path; - for (const auto &it : oldBlackList) { - if (it.startsWith(path)) - result += it; - } - } - return result; -} - -void FolderStatusModel::slotUpdateFolderState(Folder *folder) -{ - if (!folder) - return; - for (int i = 0; i < _folders.count(); ++i) { - if (_folders.at(i)._folder == folder) { - emit dataChanged(index(i), index(i)); - } - } -} - -void FolderStatusModel::slotApplySelectiveSync() -{ - for (const auto &folderInfo : qAsConst(_folders)) { - if (!folderInfo._fetched) { - folderInfo._folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, QStringList()); - continue; - } - const auto folder = folderInfo._folder; - - bool ok; - auto oldBlackList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok); - if (!ok) { - qCWarning(lcFolderStatus) << "Could not read selective sync list from db."; - continue; - } - QStringList blackList = createBlackList(folderInfo, oldBlackList); - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList); - - auto blackListSet = blackList.toSet(); - auto oldBlackListSet = oldBlackList.toSet(); - - // The folders that were undecided or blacklisted and that are now checked should go on the white list. - // The user confirmed them already just now. - QStringList toAddToWhiteList = ((oldBlackListSet + folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, &ok).toSet()) - blackListSet).toList(); - - if (!toAddToWhiteList.isEmpty()) { - auto whiteList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, &ok); - if (ok) { - whiteList += toAddToWhiteList; - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, whiteList); - } - } - // clear the undecided list - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, QStringList()); - - // do the sync if there were changes - const auto changes = (oldBlackListSet - blackListSet) + (blackListSet - oldBlackListSet); - if (!changes.isEmpty()) { - if (folder->isSyncRunning()) { - folder->slotTerminateSync(); - } - //The part that changed should not be read from the DB on next sync because there might be new folders - // (the ones that are no longer in the blacklist) - for (const auto &it : changes) { - folder->journalDb()->schedulePathForRemoteDiscovery(it); - folder->schedulePathForLocalDiscovery(it); - } - // Also make sure we see the local file that had been ignored before - folder->slotNextSyncFullLocalDiscovery(); - FolderMan::instance()->scheduleFolder(folder); - } - } - - resetFolders(); -} - -void FolderStatusModel::slotSetProgress(const ProgressInfo &progress) -{ - auto par = qobject_cast(QObject::parent()); - if (!par->isVisible()) { - return; // for https://github.com/owncloud/client/issues/2648#issuecomment-71377909 - } - - Folder *f = qobject_cast(sender()); - if (!f) { - return; - } - - int folderIndex = -1; - for (int i = 0; i < _folders.count(); ++i) { - if (_folders.at(i)._folder == f) { - folderIndex = i; - break; - } - } - if (folderIndex < 0) { - return; - } - - auto *pi = &_folders[folderIndex]._progress; - - QVector roles; - roles << FolderStatusDelegate::SyncProgressItemString - << FolderStatusDelegate::WarningCount - << Qt::ToolTipRole; - - if (progress.status() == ProgressInfo::Discovery) { - if (!progress._currentDiscoveredRemoteFolder.isEmpty()) { - pi->_overallSyncString = tr("Checking for changes in remote '%1'").arg(progress._currentDiscoveredRemoteFolder); - emit dataChanged(index(folderIndex), index(folderIndex), roles); - return; - } else if (!progress._currentDiscoveredLocalFolder.isEmpty()) { - pi->_overallSyncString = tr("Checking for changes in local '%1'").arg(progress._currentDiscoveredLocalFolder); - emit dataChanged(index(folderIndex), index(folderIndex), roles); - return; - } - } - - if (progress.status() == ProgressInfo::Reconcile) { - pi->_overallSyncString = tr("Reconciling changes"); - emit dataChanged(index(folderIndex), index(folderIndex), roles); - return; - } - - // Status is Starting, Propagation or Done - - if (!progress._lastCompletedItem.isEmpty() - && Progress::isWarningKind(progress._lastCompletedItem._status)) { - pi->_warningCount++; - } - - // find the single item to display: This is going to be the bigger item, or the last completed - // item if no items are in progress. - SyncFileItem curItem = progress._lastCompletedItem; - qint64 curItemProgress = -1; // -1 means finished - qint64 biggerItemSize = 0; - quint64 estimatedUpBw = 0; - quint64 estimatedDownBw = 0; - QString allFilenames; - for (const auto &citm : progress._currentItems) { - if (curItemProgress == -1 || (ProgressInfo::isSizeDependent(citm._item) - && biggerItemSize < citm._item._size)) { - curItemProgress = citm._progress.completed(); - curItem = citm._item; - biggerItemSize = citm._item._size; - } - if (citm._item._direction != SyncFileItem::Up) { - estimatedDownBw += progress.fileProgress(citm._item).estimatedBandwidth; - } else { - estimatedUpBw += progress.fileProgress(citm._item).estimatedBandwidth; - } - auto fileName = QFileInfo(citm._item._file).fileName(); - if (allFilenames.length() > 0) { - //: Build a list of file names - allFilenames.append(tr(", '%1'").arg(fileName)); - } else { - //: Argument is a file name - allFilenames.append(tr("'%1'").arg(fileName)); - } - } - if (curItemProgress == -1) { - curItemProgress = curItem._size; - } - - QString itemFileName = curItem._file; - QString kindString = Progress::asActionString(curItem); - - QString fileProgressString; - if (ProgressInfo::isSizeDependent(curItem)) { - QString s1 = Utility::octetsToString(curItemProgress); - QString s2 = Utility::octetsToString(curItem._size); - //quint64 estimatedBw = progress.fileProgress(curItem).estimatedBandwidth; - if (estimatedUpBw || estimatedDownBw) { - /* - //: Example text: "uploading foobar.png (1MB of 2MB) time left 2 minutes at a rate of 24Kb/s" - fileProgressString = tr("%1 %2 (%3 of %4) %5 left at a rate of %6/s") - .arg(kindString, itemFileName, s1, s2, - Utility::durationToDescriptiveString(progress.fileProgress(curItem).estimatedEta), - Utility::octetsToString(estimatedBw) ); - */ - //: Example text: "Syncing 'foo.txt', 'bar.txt'" - fileProgressString = tr("Syncing %1").arg(allFilenames); - if (estimatedDownBw > 0) { - fileProgressString.append(tr(", ")); -// ifdefs: https://github.com/owncloud/client/issues/3095#issuecomment-128409294 -#ifdef Q_OS_WIN - //: Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - fileProgressString.append(tr("download %1/s").arg(Utility::octetsToString(estimatedDownBw))); -#else - fileProgressString.append(tr("\u2193 %1/s") - .arg(Utility::octetsToString(estimatedDownBw))); -#endif - } - if (estimatedUpBw > 0) { - fileProgressString.append(tr(", ")); -#ifdef Q_OS_WIN - //: Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - fileProgressString.append(tr("upload %1/s").arg(Utility::octetsToString(estimatedUpBw))); -#else - fileProgressString.append(tr("\u2191 %1/s") - .arg(Utility::octetsToString(estimatedUpBw))); -#endif - } - } else { - //: Example text: "uploading foobar.png (2MB of 2MB)" - fileProgressString = tr("%1 %2 (%3 of %4)").arg(kindString, itemFileName, s1, s2); - } - } else if (!kindString.isEmpty()) { - //: Example text: "uploading foobar.png" - fileProgressString = tr("%1 %2").arg(kindString, itemFileName); - } - pi->_progressString = fileProgressString; - - // overall progress - qint64 completedSize = progress.completedSize(); - qint64 completedFile = progress.completedFiles(); - qint64 currentFile = progress.currentFile(); - qint64 totalSize = qMax(completedSize, progress.totalSize()); - qint64 totalFileCount = qMax(currentFile, progress.totalFiles()); - QString overallSyncString; - if (totalSize > 0) { - QString s1 = Utility::octetsToString(completedSize); - QString s2 = Utility::octetsToString(totalSize); - - if (progress.trustEta()) { - //: Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - overallSyncString = tr("%5 left, %1 of %2, file %3 of %4") - .arg(s1, s2) - .arg(currentFile) - .arg(totalFileCount) - .arg(Utility::durationToDescriptiveString1(progress.totalProgress().estimatedEta)); - - } else { - //: Example text: "12 MB of 345 MB, file 6 of 7" - overallSyncString = tr("%1 of %2, file %3 of %4") - .arg(s1, s2) - .arg(currentFile) - .arg(totalFileCount); - } - } else if (totalFileCount > 0) { - // Don't attempt to estimate the time left if there is no kb to transfer. - overallSyncString = tr("file %1 of %2").arg(currentFile).arg(totalFileCount); - } - - pi->_overallSyncString = overallSyncString; - - int overallPercent = 0; - if (totalFileCount > 0) { - // Add one 'byte' for each file so the percentage is moving when deleting or renaming files - overallPercent = qRound(double(completedSize + completedFile) / double(totalSize + totalFileCount) * 100.0); - } - pi->_overallPercent = qBound(0, overallPercent, 100); - emit dataChanged(index(folderIndex), index(folderIndex), roles); -} - -void FolderStatusModel::slotFolderSyncStateChange(Folder *f) -{ - if (!f) { - return; - } - - int folderIndex = -1; - for (int i = 0; i < _folders.count(); ++i) { - if (_folders.at(i)._folder == f) { - folderIndex = i; - break; - } - } - if (folderIndex < 0) { - return; - } - - auto &pi = _folders[folderIndex]._progress; - - SyncResult::Status state = f->syncResult().status(); - if (!f->canSync()) { - // Reset progress info. - pi = SubFolderInfo::Progress(); - } else if (state == SyncResult::NotYetStarted) { - FolderMan *folderMan = FolderMan::instance(); - int pos = folderMan->scheduleQueue().indexOf(f); - for (auto other : folderMan->map()) { - if (other != f && other->isSyncRunning()) - pos += 1; - } - QString message; - if (pos <= 0) { - message = tr("Waiting..."); - } else { - message = tr("Waiting for %n other folder(s)...", "", pos); - } - pi = SubFolderInfo::Progress(); - pi._overallSyncString = message; - } else if (state == SyncResult::SyncPrepare) { - pi = SubFolderInfo::Progress(); - pi._overallSyncString = tr("Preparing to sync..."); - } else if (state == SyncResult::Problem || state == SyncResult::Success) { - // Reset the progress info after a sync. - pi = SubFolderInfo::Progress(); - } else if (state == SyncResult::Error) { - pi = SubFolderInfo::Progress(); - } - - // update the icon etc. now - slotUpdateFolderState(f); - - if (f->syncResult().folderStructureWasChanged() - && (state == SyncResult::Success || state == SyncResult::Problem)) { - // There is a new or a removed folder. reset all data - resetAndFetch(index(folderIndex)); - } -} - -void FolderStatusModel::slotFolderScheduleQueueChanged() -{ - // Update messages on waiting folders. - for (auto *f : FolderMan::instance()->map()) { - slotFolderSyncStateChange(f); - } -} - -void FolderStatusModel::resetFolders() -{ - setAccountState(_accountState); -} - -void FolderStatusModel::slotSyncAllPendingBigFolders() -{ - for (int i = 0; i < _folders.count(); ++i) { - if (!_folders[i]._fetched) { - _folders[i]._folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, QStringList()); - continue; - } - auto folder = _folders.at(i)._folder; - - bool ok; - const auto &undecidedList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, &ok); - if (!ok) { - qCWarning(lcFolderStatus) << "Could not read selective sync list from db."; - return; - } - - // If this folder had no undecided entries, skip it. - if (undecidedList.isEmpty()) { - continue; - } - - // Remove all undecided folders from the blacklist - auto blackList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok); - if (!ok) { - qCWarning(lcFolderStatus) << "Could not read selective sync list from db."; - return; - } - for (const auto &undecidedFolder : undecidedList) { - blackList.removeAll(undecidedFolder); - } - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList); - - // Add all undecided folders to the white list - auto whiteList = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, &ok); - if (!ok) { - qCWarning(lcFolderStatus) << "Could not read selective sync list from db."; - return; - } - whiteList += undecidedList; - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, whiteList); - - // Clear the undecided list - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, QStringList()); - - // Trigger a sync - if (folder->isSyncRunning()) { - folder->slotTerminateSync(); - } - // The part that changed should not be read from the DB on next sync because there might be new folders - // (the ones that are no longer in the blacklist) - for (const auto &it : undecidedList) { - folder->journalDb()->schedulePathForRemoteDiscovery(it); - folder->schedulePathForLocalDiscovery(it); - } - // Also make sure we see the local file that had been ignored before - folder->slotNextSyncFullLocalDiscovery(); - FolderMan::instance()->scheduleFolder(folder); - } - - resetFolders(); -} - -void FolderStatusModel::slotSyncNoPendingBigFolders() -{ - for (int i = 0; i < _folders.count(); ++i) { - auto folder = _folders.at(i)._folder; - - // clear the undecided list - folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncUndecidedList, QStringList()); - } - - resetFolders(); -} - -void FolderStatusModel::slotNewBigFolder() -{ - auto f = qobject_cast(sender()); - OC_ASSERT(f); - - int folderIndex = -1; - for (int i = 0; i < _folders.count(); ++i) { - if (_folders.at(i)._folder == f) { - folderIndex = i; - break; - } - } - if (folderIndex < 0) { - return; - } - - resetAndFetch(index(folderIndex)); - - emit suggestExpand(index(folderIndex)); - emit dirtyChanged(); -} - -void FolderStatusModel::slotShowFetchProgress() -{ - QMutableMapIterator it(_fetchingItems); - while (it.hasNext()) { - it.next(); - if (it.value().elapsed() > 800) { - auto idx = it.key(); - auto *info = infoForIndex(idx); - if (info && info->_fetchingJob) { - bool add = !info->hasLabel(); - if (add) { - beginInsertRows(idx, 0, 0); - } - info->_fetchingLabel = true; - if (add) { - endInsertRows(); - } - } - it.remove(); - } - } -} - -bool FolderStatusModel::SubFolderInfo::hasLabel() const -{ - return _hasError || _fetchingLabel; -} - -void FolderStatusModel::SubFolderInfo::resetSubs(FolderStatusModel *model, QModelIndex index) -{ - _fetched = false; - if (_fetchingJob) { - disconnect(_fetchingJob, nullptr, model, nullptr); - _fetchingJob->deleteLater(); - _fetchingJob.clear(); - } - if (hasLabel()) { - model->beginRemoveRows(index, 0, 0); - _fetchingLabel = false; - _hasError = false; - model->endRemoveRows(); - } else if (!_subs.isEmpty()) { - model->beginRemoveRows(index, 0, _subs.count() - 1); - _subs.clear(); - model->endRemoveRows(); - } -} - - -} // namespace OCC diff --git a/src/gui/folderstatusmodel.h b/src/gui/folderstatusmodel.h deleted file mode 100644 index ee5b87ec47e..00000000000 --- a/src/gui/folderstatusmodel.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef FOLDERSTATUSMODEL_H -#define FOLDERSTATUSMODEL_H - -#include -#include -#include -#include -#include -#include - -class QNetworkReply; -namespace OCC { - -Q_DECLARE_LOGGING_CATEGORY(lcFolderStatus) - -class Folder; -class ProgressInfo; -class LsColJob; - -/** - * @brief The FolderStatusModel class - * @ingroup gui - */ -class FolderStatusModel : public QAbstractItemModel -{ - Q_OBJECT -public: - FolderStatusModel(QObject *parent = nullptr); - ~FolderStatusModel() override; - void setAccountState(const AccountState *accountState); - - Qt::ItemFlags flags(const QModelIndex &) const override; - QVariant data(const QModelIndex &index, int role) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex &child) const override; - bool canFetchMore(const QModelIndex &parent) const override; - void fetchMore(const QModelIndex &parent) override; - void resetAndFetch(const QModelIndex &parent); - bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; - - struct SubFolderInfo - { - SubFolderInfo() - : _folder(nullptr) - , _size(0) - , _isExternal(false) - , _fetched(false) - , _hasError(false) - , _fetchingLabel(false) - , _isUndecided(false) - , _checked(Qt::Checked) - { - } - Folder *_folder; - QString _name; - QString _path; - QVector _pathIdx; - QVector _subs; - qint64 _size; - bool _isExternal; - - bool _fetched; // If we did the LSCOL for this folder already - QPointer _fetchingJob; // Currently running LsColJob - bool _hasError; // If the last fetching job ended in an error - QString _lastErrorString; - bool _fetchingLabel; // Whether a 'fetching in progress' label is shown. - - // undecided folders are the big folders that the user has not accepted yet - bool _isUndecided; - - Qt::CheckState _checked; - - // Whether this has a FetchLabel subrow - bool hasLabel() const; - - // Reset all subfolders and fetch status - void resetSubs(FolderStatusModel *model, QModelIndex index); - - struct Progress - { - Progress() - : _warningCount(0) - , _overallPercent(0) - { - } - bool isNull() const - { - return _progressString.isEmpty() && _warningCount == 0 && _overallSyncString.isEmpty(); - } - QString _progressString; - QString _overallSyncString; - int _warningCount; - int _overallPercent; - }; - Progress _progress; - }; - - QVector _folders; - - enum ItemType { RootFolder, - SubFolder, - AddButton, - FetchLabel }; - ItemType classify(const QModelIndex &index) const; - SubFolderInfo *infoForIndex(const QModelIndex &index) const; - - // If the selective sync check boxes were changed - bool isDirty() { return _dirty; } - - /** - * return a QModelIndex for the given path within the given folder. - * Note: this method returns an invalid index if the path was not fetched from the server before - */ - QModelIndex indexForPath(Folder *f, const QString &path) const; - -public slots: - void slotUpdateFolderState(Folder *); - void slotApplySelectiveSync(); - void resetFolders(); - void slotSyncAllPendingBigFolders(); - void slotSyncNoPendingBigFolders(); - void slotSetProgress(const ProgressInfo &progress); - -private slots: - void slotUpdateDirectories(const QStringList &); - void slotGatherPermissions(const QString &name, const QMap &properties); - void slotLscolFinishedWithError(QNetworkReply *r); - void slotFolderSyncStateChange(Folder *f); - void slotFolderScheduleQueueChanged(); - void slotNewBigFolder(); - - /** - * "In progress" labels for fetching data from the server are only - * added after some time to avoid popping. - */ - void slotShowFetchProgress(); - -private: - QStringList createBlackList(const OCC::FolderStatusModel::SubFolderInfo &root, - const QStringList &oldBlackList) const; - const AccountState *_accountState; - bool _dirty; // If the selective sync checkboxes were changed - - /** - * Keeps track of items that are fetching data from the server. - * - * See slotShowPendingFetchProgress() - */ - QMap _fetchingItems; - -signals: - void dirtyChanged(); - - // Tell the view that this item should be expanded because it has an undecided item - void suggestExpand(const QModelIndex &); - - friend struct SubFolderInfo; -}; - -} // namespace OCC - -#endif // FOLDERSTATUSMODEL_H diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 8e22afd9daa..476619c11c6 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -15,13 +15,9 @@ // event masks #include "folderwatcher.h" -#include +#include -#include #include -#include -#include -#include #include #if defined(Q_OS_WIN) @@ -37,6 +33,10 @@ using namespace std::chrono_literals; +namespace { +constexpr auto notificationTimeoutC = 10s; +} + namespace OCC { Q_LOGGING_CATEGORY(lcFolderWatcher, "gui.folderwatcher", QtInfoMsg) @@ -45,6 +45,14 @@ FolderWatcher::FolderWatcher(Folder *folder) : QObject(folder) , _folder(folder) { + _timer.setInterval(notificationTimeoutC); + connect(&_timer, &QTimer::timeout, this, [this] { + auto paths = popChangeSet(); + if (!paths.isEmpty()) { + qCInfo(lcFolderWatcher) << "Detected changes in paths:" << paths; + Q_EMIT pathChanged(paths); + } + }); } FolderWatcher::~FolderWatcher() @@ -54,15 +62,12 @@ FolderWatcher::~FolderWatcher() void FolderWatcher::init(const QString &root) { _d.reset(new FolderWatcherPrivate(this, root)); - _timer.start(); } -bool FolderWatcher::pathIsIgnored(const QString &path) +bool FolderWatcher::pathIsIgnored(const QString &path) const { - if (path.isEmpty()) - return true; - if (!_folder) - return false; + Q_ASSERT(!path.isEmpty()); + Q_ASSERT(_folder); if (_folder->isFileExcludedAbsolute(path) && !Utility::isConflictFile(path)) { qCDebug(lcFolderWatcher) << "* Ignoring file" << path; return true; @@ -85,7 +90,8 @@ void FolderWatcher::startNotificatonTest(const QString &path) return; #endif - OC_ASSERT(_testNotificationPath.isEmpty()); + Q_ASSERT(_testNotificationPath.isEmpty()); + Q_ASSERT(!path.isEmpty()); _testNotificationPath = path; // Don't do the local file modification immediately: @@ -95,23 +101,26 @@ void FolderWatcher::startNotificatonTest(const QString &path) void FolderWatcher::startNotificationTestWhenReady() { + if (!_testNotificationPath.isEmpty()) { + // we already received the notification + return; + } if (!_d->isReady()) { QTimer::singleShot(1s, this, &FolderWatcher::startNotificationTestWhenReady); return; } - auto path = _testNotificationPath; - if (QFile::exists(path)) { - auto mtime = FileSystem::getModTime(path); - FileSystem::setModTime(path, mtime + 1); + if (OC_ENSURE(QFile::exists(_testNotificationPath))) { + const auto mtime = FileSystem::getModTime(_testNotificationPath); + FileSystem::setModTime(_testNotificationPath, mtime + 1); } else { - QFile f(path); + QFile f(_testNotificationPath); f.open(QIODevice::WriteOnly | QIODevice::Append); } - QTimer::singleShot(5s, this, [this]() { + QTimer::singleShot(notificationTimeoutC + 5s, this, [this]() { if (!_testNotificationPath.isEmpty()) - emit becameUnreliable(tr("The watcher did not receive a test notification.")); + Q_EMIT becameUnreliable(tr("The watcher did not receive a test notification.")); _testNotificationPath.clear(); }); } @@ -126,51 +135,32 @@ int FolderWatcher::testLinuxWatchCount() const #endif } -void FolderWatcher::changeDetected(const QString &path) +void FolderWatcher::changeDetected(const QSet &paths) { - QStringList paths(path); - changeDetected(paths); + Q_ASSERT(thread() == QThread::currentThread()); + _changeSet.unite(paths); + if (!_timer.isActive()) { + _timer.start(); + } } -void FolderWatcher::changeDetected(const QStringList &paths) +QSet FolderWatcher::popChangeSet() { - // TODO: this shortcut doesn't look very reliable: - // - why is the timeout only 1 second? - // - what if there is more than one file being updated frequently? - // - why do we skip the file altogether instead of e.g. reducing the upload frequency? - - // Check if the same path was reported within the last second. - QSet pathsSet = paths.toSet(); - if (pathsSet == _lastPaths && _timer.elapsed() < 1000) { - // the same path was reported within the last second. Skip. - return; - } - _lastPaths = pathsSet; - _timer.restart(); - - QSet changedPaths; - + auto paths = std::move(_changeSet); // ------- handle ignores: - for (int i = 0; i < paths.size(); ++i) { - QString path = paths[i]; - if (!_testNotificationPath.isEmpty() - && Utility::fileNamesEqual(path, _testNotificationPath)) { + auto it = paths.cbegin(); + while (it != paths.cend()) { + // we cause a file change from time to time to check whether the folder watcher works as expected + if (!_testNotificationPath.isEmpty() && Utility::fileNamesEqual(*it, _testNotificationPath)) { _testNotificationPath.clear(); } - if (pathIsIgnored(path)) { - continue; + if (pathIsIgnored(*it)) { + it = paths.erase(it); + } else { + ++it; } - - changedPaths.insert(path); - } - if (changedPaths.isEmpty()) { - return; - } - - qCInfo(lcFolderWatcher) << "Detected changes in paths:" << changedPaths; - for (const auto &path : qAsConst(changedPaths)) { - emit pathChanged(path); } + return paths; } } // namespace OCC diff --git a/src/gui/folderwatcher.h b/src/gui/folderwatcher.h index 808a04b0b18..cbb76dbb649 100644 --- a/src/gui/folderwatcher.h +++ b/src/gui/folderwatcher.h @@ -12,22 +12,22 @@ * for more details. */ -#ifndef MIRALL_FOLDERWATCHER_H -#define MIRALL_FOLDERWATCHER_H +#pragma once -#include "config.h" +#include "gui/owncloudguilib.h" + +#include +#include #include #include #include -#include -#include -#include -#include #include #include +#include +#include +#include -class QTimer; namespace OCC { @@ -46,12 +46,12 @@ class Folder; * @ingroup gui */ -class FolderWatcher : public QObject +class OWNCLOUDGUI_EXPORT FolderWatcher : public QObject { Q_OBJECT public: // Construct, connect signals, call init() - explicit FolderWatcher(Folder *folder = nullptr); + explicit FolderWatcher(Folder *folder); ~FolderWatcher() override; /** @@ -60,7 +60,7 @@ class FolderWatcher : public QObject void init(const QString &root); /* Check if the path is ignored. */ - bool pathIsIgnored(const QString &path); + virtual bool pathIsIgnored(const QString &path) const; /** * Returns false if the folder watcher can't be trusted to capture all @@ -82,10 +82,14 @@ class FolderWatcher : public QObject /// For testing linux behavior only int testLinuxWatchCount() const; -signals: + // pop the accumulated changes + QSet popChangeSet(); + + +Q_SIGNALS: /** Emitted when one of the watched directories or one * of the contained files is changed. */ - void pathChanged(const QString &path); + void pathChanged(const QSet &path); /** * Emitted if some notifications were lost. @@ -103,21 +107,17 @@ class FolderWatcher : public QObject */ void becameUnreliable(const QString &message); -protected slots: +protected Q_SLOTS: // called from the implementations to indicate a change in path - void changeDetected(const QString &path); - void changeDetected(const QStringList &paths); + void changeDetected(const QSet &paths); -private slots: +private Q_SLOTS: void startNotificationTestWhenReady(); -protected: - QHash _pendingPathes; - private: QScopedPointer _d; - QElapsedTimer _timer; - QSet _lastPaths; + QTimer _timer; + QSet _changeSet; Folder *_folder; bool _isReliable = true; @@ -127,5 +127,3 @@ private slots: friend class FolderWatcherPrivate; }; } - -#endif diff --git a/src/gui/folderwatcher_linux.cpp b/src/gui/folderwatcher_linux.cpp index 4cdcc5afb3b..bba50406d3b 100644 --- a/src/gui/folderwatcher_linux.cpp +++ b/src/gui/folderwatcher_linux.cpp @@ -12,10 +12,9 @@ * for more details. */ -#include "config.h" - #include #include +#include #include "folder.h" #include "folderwatcher_linux.h" @@ -39,7 +38,7 @@ FolderWatcherPrivate::FolderWatcherPrivate(FolderWatcher *p, const QString &path qCWarning(lcFolderWatcher) << "notify_init() failed: " << strerror(errno); } - QMetaObject::invokeMethod(this, "slotAddFolderRecursive", Q_ARG(QString, path)); + QMetaObject::invokeMethod(this, [path, this] { slotAddFolderRecursive(path); }); } // attention: result list passed by reference! @@ -79,9 +78,8 @@ void FolderWatcherPrivate::inotifyRegisterPath(const QString &path) // If we're running out of memory or inotify watches, become unreliable. if (_parent->_isReliable && (errno == ENOMEM || errno == ENOSPC)) { _parent->_isReliable = false; - emit _parent->becameUnreliable( - tr("This problem usually happens when the inotify watches are exhausted. " - "Check the FAQ for details.")); + Q_EMIT _parent->becameUnreliable(tr("This problem usually happens when the inotify watches are exhausted. " + "Check the FAQ for details.")); } } } @@ -104,7 +102,7 @@ void FolderWatcherPrivate::slotAddFolderRecursive(const QString &path) << path << "'"; } - for (const auto &subfolder : allSubfolders) { + for (const auto &subfolder : std::as_const(allSubfolders)) { QDir folder(subfolder); if (folder.exists() && !_pathToWatch.contains(folder.absolutePath())) { ++subdirCount; @@ -130,7 +128,7 @@ void FolderWatcherPrivate::slotReceivedNotification(int fd) int len; QVarLengthArray buffer(2048); - forever { + while (true) { len = read(fd, buffer.data(), buffer.size()); auto error = errno; /** @@ -152,6 +150,7 @@ void FolderWatcherPrivate::slotReceivedNotification(int fd) } } + QSet paths; // iterate over events in buffer struct inotify_event *event = nullptr; for (size_t bytePosition = 0; // start at the beginning of the buffer @@ -180,7 +179,7 @@ void FolderWatcherPrivate::slotReceivedNotification(int fd) } const QString p = _watchToPath[event->wd] + QLatin1Char('/') + QString::fromUtf8(fileName); - _parent->changeDetected(p); + paths.insert(p); if ((event->mask & (IN_MOVED_TO | IN_CREATE)) && QFileInfo(p).isDir() @@ -191,6 +190,9 @@ void FolderWatcherPrivate::slotReceivedNotification(int fd) removeFoldersBelow(p); } } + if (!paths.isEmpty()) { + _parent->changeDetected(paths); + } } void FolderWatcherPrivate::removeFoldersBelow(const QString &path) @@ -199,7 +201,7 @@ void FolderWatcherPrivate::removeFoldersBelow(const QString &path) if (it == _pathToWatch.end()) return; - const QString pathSlash = path + '/'; + const QString pathSlash = path + QLatin1Char('/'); // Remove the entry and all subentries while (it != _pathToWatch.end()) { diff --git a/src/gui/folderwatcher_linux.h b/src/gui/folderwatcher_linux.h index 00e7a06442e..fb6e92fd808 100644 --- a/src/gui/folderwatcher_linux.h +++ b/src/gui/folderwatcher_linux.h @@ -12,8 +12,7 @@ * for more details. */ -#ifndef MIRALL_FOLDERWATCHER_LINUX_H -#define MIRALL_FOLDERWATCHER_LINUX_H +#pragma once #include #include @@ -31,7 +30,7 @@ namespace OCC { * @brief Linux (inotify) API implementation of FolderWatcher * @ingroup gui */ -class FolderWatcherPrivate : public QObject +class OWNCLOUDGUI_EXPORT FolderWatcherPrivate : public QObject { Q_OBJECT public: @@ -43,7 +42,7 @@ class FolderWatcherPrivate : public QObject /// On linux the watcher is ready when the ctor finished. constexpr bool isReady() const { return true; } -protected slots: +protected Q_SLOTS: void slotReceivedNotification(int fd); void slotAddFolderRecursive(const QString &path); @@ -62,5 +61,3 @@ protected slots: int _fd; }; } - -#endif diff --git a/src/gui/folderwatcher_mac.cpp b/src/gui/folderwatcher_mac.cpp index da06b7fd23e..6125013205b 100644 --- a/src/gui/folderwatcher_mac.cpp +++ b/src/gui/folderwatcher_mac.cpp @@ -11,9 +11,7 @@ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ -#include "config.h" -#include "folder.h" #include "folderwatcher.h" #include "folderwatcher_mac.h" @@ -59,9 +57,7 @@ static void callback( | kFSEventStreamEventFlagItemModified; // for content change // We ignore other flags, e.g. for owner change, xattr change, Finder label change etc - qCDebug(lcFolderWatcher) << "FolderWatcherPrivate::callback by OS X"; - - QStringList paths; + QSet paths; CFArrayRef eventPaths = static_cast(eventPathsVoid); for (CFIndex i = 0; i < static_cast(numEvents); ++i) { @@ -73,10 +69,12 @@ static void callback( continue; } - paths.append(qPath); + paths.insert(qPath); } - reinterpret_cast(clientCallBackInfo)->doNotifyParent(paths); + if (!paths.isEmpty()) { + reinterpret_cast(clientCallBackInfo)->doNotifyParent(paths); + } } void FolderWatcherPrivate::startWatching() @@ -105,7 +103,7 @@ void FolderWatcherPrivate::startWatching() FSEventStreamStart(_stream); } -void FolderWatcherPrivate::doNotifyParent(const QStringList &paths) +void FolderWatcherPrivate::doNotifyParent(const QSet &paths) { _parent->changeDetected(paths); } diff --git a/src/gui/folderwatcher_mac.h b/src/gui/folderwatcher_mac.h index 773feffdb54..7beaa34e480 100644 --- a/src/gui/folderwatcher_mac.h +++ b/src/gui/folderwatcher_mac.h @@ -36,7 +36,7 @@ class FolderWatcherPrivate ~FolderWatcherPrivate(); void startWatching(); - void doNotifyParent(const QStringList &); + void doNotifyParent(const QSet &); /// On OSX the watcher is ready when the ctor finished. constexpr bool isReady() const { return true; } diff --git a/src/gui/folderwatcher_win.cpp b/src/gui/folderwatcher_win.cpp index 01389a89c9d..033ca6e7526 100644 --- a/src/gui/folderwatcher_win.cpp +++ b/src/gui/folderwatcher_win.cpp @@ -18,6 +18,7 @@ #include "common/asserts.h" #include "common/utility.h" +#include "common/utility_win.h" #include "filesystem.h" #include "folderwatcher.h" #include "folderwatcher_win.h" @@ -30,19 +31,14 @@ namespace OCC { WatcherThread::WatchChanges WatcherThread::watchChanges(size_t fileNotifyBufferSize) { - _directory = CreateFileW( - reinterpret_cast(_longPath.utf16()), // QString stores UTF-16 internally, so use that here. - FILE_LIST_DIRECTORY, - FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, - NULL); + _directory = CreateFileW(reinterpret_cast(_longPath.utf16()), // QString stores UTF-16 internally, so use that here. + FILE_LIST_DIRECTORY, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + nullptr); if (_directory == INVALID_HANDLE_VALUE) { const auto error = GetLastError(); qCWarning(lcFolderWatcher) << "Failed to create handle for" << _path << ", error:" << Utility::formatWinError(error); - _directory = 0; + _directory = nullptr; return WatchChanges::Error; } @@ -74,7 +70,7 @@ WatcherThread::WatchChanges WatcherThread::watchChanges(size_t fileNotifyBufferS const DWORD errorCode = GetLastError(); if (errorCode == ERROR_NOTIFY_ENUM_DIR) { qCDebug(lcFolderWatcher) << "The buffer for changes overflowed! Triggering a generic change and resizing"; - emit changed(_path); + Q_EMIT changed({_path}); return WatchChanges::NeedBiggerBuffer; } else { qCWarning(lcFolderWatcher) << "ReadDirectoryChangesW error" << Utility::formatWinError(errorCode); @@ -82,7 +78,7 @@ WatcherThread::WatchChanges WatcherThread::watchChanges(size_t fileNotifyBufferS } } - emit ready(); + _parent->_ready = true; HANDLE handles[] = { _resultEvent, _stopEvent }; DWORD result = WaitForMultipleObjects( @@ -104,8 +100,8 @@ WatcherThread::WatchChanges WatcherThread::watchChanges(size_t fileNotifyBufferS const DWORD errorCode = GetLastError(); if (errorCode == ERROR_NOTIFY_ENUM_DIR) { qCDebug(lcFolderWatcher) << "The buffer for changes overflowed! Triggering a generic change and resizing"; - emit lostChanges(); - emit changed(_path); + Q_EMIT lostChanges(); + Q_EMIT changed({_path}); return WatchChanges::NeedBiggerBuffer; } else { qCWarning(lcFolderWatcher) << "GetOverlappedResult error" << Utility::formatWinError(errorCode); @@ -119,6 +115,7 @@ WatcherThread::WatchChanges WatcherThread::watchChanges(size_t fileNotifyBufferS void WatcherThread::processEntries(FILE_NOTIFY_INFORMATION *curEntry) { + QSet paths; while (curEntry) { const size_t fileNameBufferSize = 4096; TCHAR fileNameBuffer[fileNameBufferSize]; @@ -139,13 +136,6 @@ void WatcherThread::processEntries(FILE_NOTIFY_INFORMATION *curEntry) } } -#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - // The prefix is needed for native Windows functions before Windows 10, version 1607 - const bool hasLongPathPrefix = longPath.startsWith(QStringLiteral("\\\\?\\")); - if (hasLongPathPrefix) { - longfile.remove(0, 4); - } -#endif longfile = QDir::cleanPath(longfile); // Skip modifications of folders: One of these is triggered for changes @@ -154,29 +144,32 @@ void WatcherThread::processEntries(FILE_NOTIFY_INFORMATION *curEntry) const bool skip = curEntry->Action == FILE_ACTION_MODIFIED && QFileInfo(longfile).isDir(); if (!skip) { - emit changed(longfile); + paths.insert(longfile); } if (curEntry->NextEntryOffset == 0) { - return; + break; } // FILE_NOTIFY_INFORMATION has no fixed size and the offset is in bytes therefor we first need to cast to char curEntry = reinterpret_cast(reinterpret_cast(curEntry) + curEntry->NextEntryOffset); } + if (!paths.isEmpty()) { + Q_EMIT changed(paths); + } } void WatcherThread::closeHandle() { if (_directory) { CloseHandle(_directory); - _directory = NULL; + _directory = nullptr; } } void WatcherThread::run() { - _resultEvent = CreateEvent(NULL, true, false, NULL); - _stopEvent = CreateEvent(NULL, true, false, NULL); + _resultEvent = CreateEvent(nullptr, true, false, nullptr); + _stopEvent = CreateEvent(nullptr, true, false, nullptr); // If this buffer fills up before we've extracted its data we will lose // change information. Therefore start big. @@ -201,13 +194,14 @@ void WatcherThread::run() } } -WatcherThread::WatcherThread(const QString &path) +WatcherThread::WatcherThread(FolderWatcherPrivate *parent, const QString &path) : QThread() + , _parent(parent) , _path(path + (path.endsWith(QLatin1Char('/')) ? QString() : QStringLiteral("/"))) , _longPath(FileSystem::longWinPath(_path)) - , _directory(0) - , _resultEvent(0) - , _stopEvent(0) + , _directory(nullptr) + , _resultEvent(nullptr) + , _stopEvent(nullptr) { } @@ -224,10 +218,10 @@ void WatcherThread::stop() FolderWatcherPrivate::FolderWatcherPrivate(FolderWatcher *p, const QString &path) : _parent(p) { - _thread.reset(new WatcherThread(path)); - connect(_thread.get(), &WatcherThread::changed, _parent, qOverload(&FolderWatcher::changeDetected)); - connect(_thread.get(), &WatcherThread::lostChanges, _parent, &FolderWatcher::lostChanges); - connect(_thread.get(), &WatcherThread::ready, this, [this]() { _ready = 1; }); + _thread.reset(new WatcherThread(this, path)); + // we are using connects instead of directly emitting on p as we need to cross thread borders + connect(_thread.get(), &WatcherThread::changed, _parent, &FolderWatcher::changeDetected, Qt::QueuedConnection); + connect(_thread.get(), &WatcherThread::lostChanges, _parent, &FolderWatcher::lostChanges, Qt::QueuedConnection); _thread->start(); } diff --git a/src/gui/folderwatcher_win.h b/src/gui/folderwatcher_win.h index ad1126463dd..883ac2d9485 100644 --- a/src/gui/folderwatcher_win.h +++ b/src/gui/folderwatcher_win.h @@ -15,13 +15,14 @@ #ifndef MIRALL_FOLDERWATCHER_WIN_H #define MIRALL_FOLDERWATCHER_WIN_H -#include #include -#include +#include +#include namespace OCC { class FolderWatcher; +class FolderWatcherPrivate; /** * @brief The WatcherThread class @@ -31,7 +32,7 @@ class WatcherThread : public QThread { Q_OBJECT public: - WatcherThread(const QString &path); + WatcherThread(FolderWatcherPrivate *parent, const QString &path); ~WatcherThread() override; void stop(); @@ -48,12 +49,12 @@ class WatcherThread : public QThread void processEntries(FILE_NOTIFY_INFORMATION *curEntry); void closeHandle(); -signals: - void changed(const QString &path); +Q_SIGNALS: + void changed(const QSet &path); void lostChanges(); - void ready(); private: + FolderWatcherPrivate *_parent; const QString _path; const QString _longPath; HANDLE _directory; @@ -81,7 +82,8 @@ class FolderWatcherPrivate : public QObject private: FolderWatcher *_parent; QScopedPointer _thread; - QAtomicInt _ready; + bool _ready = false; + friend class WatcherThread; }; } diff --git a/src/gui/folderwizard.cpp b/src/gui/folderwizard.cpp deleted file mode 100644 index 7ce5be98768..00000000000 --- a/src/gui/folderwizard.cpp +++ /dev/null @@ -1,599 +0,0 @@ -/* - * Copyright (C) by Duncan Mac-Vicar P. - * - * 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 "folderwizard.h" -#include "folderman.h" -#include "configfile.h" -#include "theme.h" -#include "networkjobs.h" -#include "account.h" -#include "selectivesyncdialog.h" -#include "accountstate.h" -#include "creds/abstractcredentials.h" -#include "wizard/owncloudwizard.h" -#include "common/asserts.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace OCC { - -QString FormatWarningsWizardPage::formatWarnings(const QStringList &warnings) const -{ - QString ret; - if (warnings.count() == 1) { - ret = tr("Warning: %1").arg(warnings.first()); - } else if (warnings.count() > 1) { - ret = tr("Warning:") + "
    "; - for (const auto &warning : warnings) { - ret += QString::fromLatin1("
  • %1
  • ").arg(warning); - } - ret += "
"; - } - - return ret; -} - -FolderWizardLocalPath::FolderWizardLocalPath(const AccountPtr &account) - : FormatWarningsWizardPage() - , _account(account) -{ - _ui.setupUi(this); - registerField(QLatin1String("sourceFolder*"), _ui.localFolderLineEdit); - connect(_ui.localFolderChooseBtn, &QAbstractButton::clicked, this, &FolderWizardLocalPath::slotChooseLocalFolder); - _ui.localFolderChooseBtn->setToolTip(tr("Click to select a local folder to sync.")); - - QString defaultPath = QDir::homePath() + QLatin1Char('/') + Theme::instance()->appName(); - defaultPath = FolderMan::instance()->findGoodPathForNewSyncFolder(defaultPath); - _ui.localFolderLineEdit->setText(QDir::toNativeSeparators(defaultPath)); - _ui.localFolderLineEdit->setToolTip(tr("Enter the path to the local folder.")); - - _ui.warnLabel->setTextFormat(Qt::RichText); - _ui.warnLabel->hide(); -} - -FolderWizardLocalPath::~FolderWizardLocalPath() -{ -} - -void FolderWizardLocalPath::initializePage() -{ - _ui.warnLabel->hide(); -} - -void FolderWizardLocalPath::cleanupPage() -{ - _ui.warnLabel->hide(); -} - -bool FolderWizardLocalPath::isComplete() const -{ - QString errorStr = FolderMan::instance()->checkPathValidityForNewFolder( - QDir::fromNativeSeparators(_ui.localFolderLineEdit->text())); - - - bool isOk = errorStr.isEmpty(); - QStringList warnStrings; - if (!isOk) { - warnStrings << errorStr; - } - - _ui.warnLabel->setWordWrap(true); - if (isOk) { - _ui.warnLabel->hide(); - _ui.warnLabel->clear(); - } else { - _ui.warnLabel->show(); - QString warnings = formatWarnings(warnStrings); - _ui.warnLabel->setText(warnings); - } - return isOk; -} - -void FolderWizardLocalPath::slotChooseLocalFolder() -{ - QString sf = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); - QDir d(sf); - - // open the first entry of the home dir. Otherwise the dir picker comes - // up with the closed home dir icon, stupid Qt default... - QStringList dirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks, - QDir::DirsFirst | QDir::Name); - - if (dirs.count() > 0) - sf += "/" + dirs.at(0); // Take the first dir in home dir. - - QString dir = QFileDialog::getExistingDirectory(this, - tr("Select the source folder"), - sf); - if (!dir.isEmpty()) { - // set the last directory component name as alias - _ui.localFolderLineEdit->setText(QDir::toNativeSeparators(dir)); - } - emit completeChanged(); -} - -// ================================================================================= -FolderWizardRemotePath::FolderWizardRemotePath(const AccountPtr &account) - : FormatWarningsWizardPage() - , _warnWasVisible(false) - , _account(account) - -{ - _ui.setupUi(this); - _ui.warnFrame->hide(); - - _ui.folderTreeWidget->setSortingEnabled(true); - _ui.folderTreeWidget->sortByColumn(0, Qt::AscendingOrder); - - connect(_ui.addFolderButton, &QAbstractButton::clicked, this, &FolderWizardRemotePath::slotAddRemoteFolder); - connect(_ui.refreshButton, &QAbstractButton::clicked, this, &FolderWizardRemotePath::slotRefreshFolders); - connect(_ui.folderTreeWidget, &QTreeWidget::itemExpanded, this, &FolderWizardRemotePath::slotItemExpanded); - connect(_ui.folderTreeWidget, &QTreeWidget::currentItemChanged, this, &FolderWizardRemotePath::slotCurrentItemChanged); - connect(_ui.folderEntry, &QLineEdit::textEdited, this, &FolderWizardRemotePath::slotFolderEntryEdited); - - _lscolTimer.setInterval(500); - _lscolTimer.setSingleShot(true); - connect(&_lscolTimer, &QTimer::timeout, this, &FolderWizardRemotePath::slotLsColFolderEntry); - - _ui.folderTreeWidget->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - // Make sure that there will be a scrollbar when the contents is too wide - _ui.folderTreeWidget->header()->setStretchLastSection(false); -} - -void FolderWizardRemotePath::slotAddRemoteFolder() -{ - QTreeWidgetItem *current = _ui.folderTreeWidget->currentItem(); - - QString parent('/'); - if (current) { - parent = current->data(0, Qt::UserRole).toString(); - } - - QInputDialog *dlg = new QInputDialog(this); - - dlg->setWindowTitle(tr("Create Remote Folder")); - dlg->setLabelText(tr("Enter the name of the new folder to be created below '%1':") - .arg(parent)); - dlg->open(this, SLOT(slotCreateRemoteFolder(QString))); - dlg->setAttribute(Qt::WA_DeleteOnClose); -} - -void FolderWizardRemotePath::slotCreateRemoteFolder(const QString &folder) -{ - if (folder.isEmpty()) - return; - - QTreeWidgetItem *current = _ui.folderTreeWidget->currentItem(); - QString fullPath; - if (current) { - fullPath = current->data(0, Qt::UserRole).toString(); - } - fullPath += "/" + folder; - - MkColJob *job = new MkColJob(_account, fullPath, this); - /* check the owncloud configuration file and query the ownCloud */ - connect(job, &MkColJob::finishedWithoutError, - this, &FolderWizardRemotePath::slotCreateRemoteFolderFinished); - connect(job, &AbstractNetworkJob::networkError, this, &FolderWizardRemotePath::slotHandleMkdirNetworkError); - job->start(); -} - -void FolderWizardRemotePath::slotCreateRemoteFolderFinished() -{ - qCDebug(lcWizard) << "webdav mkdir request finished"; - showWarn(tr("Folder was successfully created on %1.").arg(Theme::instance()->appNameGUI())); - slotRefreshFolders(); - _ui.folderEntry->setText(static_cast(sender())->path()); - slotLsColFolderEntry(); -} - -void FolderWizardRemotePath::slotHandleMkdirNetworkError(QNetworkReply *reply) -{ - qCWarning(lcWizard) << "webdav mkdir request failed:" << reply->error(); - if (!_account->credentials()->stillValid(reply)) { - showWarn(tr("Authentication failed accessing %1").arg(Theme::instance()->appNameGUI())); - } else { - showWarn(tr("Failed to create the folder on %1. Please check manually.") - .arg(Theme::instance()->appNameGUI())); - } -} - -void FolderWizardRemotePath::slotHandleLsColNetworkError(QNetworkReply *reply) -{ - // Ignore 404s, otherwise users will get annoyed by error popups - // when not typing fast enough. It's still clear that a given path - // was not found, because the 'Next' button is disabled and no entry - // is selected in the tree view. - int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (httpCode == 404) { - showWarn(QString()); // hides the warning pane - return; - } - auto job = qobject_cast(sender()); - OC_ASSERT(job); - showWarn(tr("Failed to list a folder. Error: %1") - .arg(job->errorStringParsingBody())); -} - -static QTreeWidgetItem *findFirstChild(QTreeWidgetItem *parent, const QString &text) -{ - for (int i = 0; i < parent->childCount(); ++i) { - QTreeWidgetItem *child = parent->child(i); - if (child->text(0) == text) { - return child; - } - } - return nullptr; -} - -void FolderWizardRemotePath::recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path) -{ - if (pathTrail.isEmpty()) - return; - - const QString parentPath = parent->data(0, Qt::UserRole).toString(); - const QString folderName = pathTrail.first(); - QString folderPath; - if (parentPath == QLatin1String("/")) { - folderPath = folderName; - } else { - folderPath = parentPath + "/" + folderName; - } - QTreeWidgetItem *item = findFirstChild(parent, folderName); - if (!item) { - item = new QTreeWidgetItem(parent); - QFileIconProvider prov; - QIcon folderIcon = prov.icon(QFileIconProvider::Folder); - item->setIcon(0, folderIcon); - item->setText(0, folderName); - item->setData(0, Qt::UserRole, folderPath); - item->setToolTip(0, folderPath); - item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); - } - - pathTrail.removeFirst(); - recursiveInsert(item, pathTrail, path); -} - -bool FolderWizardRemotePath::selectByPath(QString path) -{ - if (path.startsWith(QLatin1Char('/'))) { - path = path.mid(1); - } - if (path.endsWith(QLatin1Char('/'))) { - path.chop(1); - } - - QTreeWidgetItem *it = _ui.folderTreeWidget->topLevelItem(0); - if (!path.isEmpty()) { - const QStringList pathTrail = path.split(QLatin1Char('/')); - for (const auto &path : pathTrail) { - if (!it) { - return false; - } - it = findFirstChild(it, path); - } - } - if (!it) { - return false; - } - - _ui.folderTreeWidget->setCurrentItem(it); - _ui.folderTreeWidget->scrollToItem(it); - return true; -} - -void FolderWizardRemotePath::slotUpdateDirectories(const QStringList &list) -{ - QString webdavFolder = QUrl(_account->davUrl()).path(); - - QTreeWidgetItem *root = _ui.folderTreeWidget->topLevelItem(0); - if (!root) { - root = new QTreeWidgetItem(_ui.folderTreeWidget); - root->setText(0, Theme::instance()->appNameGUI()); - root->setIcon(0, Theme::instance()->applicationIcon()); - root->setToolTip(0, tr("Choose this to sync the entire account")); - root->setData(0, Qt::UserRole, "/"); - } - QStringList sortedList = list; - Utility::sortFilenames(sortedList); - for (auto path : qAsConst(sortedList)) { - path.remove(webdavFolder); - QStringList paths = path.split('/'); - if (paths.last().isEmpty()) - paths.removeLast(); - recursiveInsert(root, paths, path); - } - root->setExpanded(true); -} - -void FolderWizardRemotePath::slotRefreshFolders() -{ - runLsColJob("/"); - _ui.folderTreeWidget->clear(); - _ui.folderEntry->clear(); -} - -void FolderWizardRemotePath::slotItemExpanded(QTreeWidgetItem *item) -{ - QString dir = item->data(0, Qt::UserRole).toString(); - if (!dir.startsWith(QLatin1Char('/'))) { - dir.prepend(QLatin1Char('/')); - } - runLsColJob(dir); -} - -void FolderWizardRemotePath::slotCurrentItemChanged(QTreeWidgetItem *item) -{ - if (item) { - QString dir = item->data(0, Qt::UserRole).toString(); - if (!dir.startsWith(QLatin1Char('/'))) { - dir.prepend(QLatin1Char('/')); - } - _ui.folderEntry->setText(dir); - } - - emit completeChanged(); -} - -void FolderWizardRemotePath::slotFolderEntryEdited(const QString &text) -{ - if (selectByPath(text)) { - _lscolTimer.stop(); - return; - } - - _ui.folderTreeWidget->setCurrentItem(nullptr); - _lscolTimer.start(); // avoid sending a request on each keystroke -} - -void FolderWizardRemotePath::slotLsColFolderEntry() -{ - QString path = _ui.folderEntry->text(); - - LsColJob *job = runLsColJob(path); - // No error handling, no updating, we do this manually - // because of extra logic in the typed-path case. - disconnect(job, nullptr, this, nullptr); - connect(job, &LsColJob::finishedWithError, - this, &FolderWizardRemotePath::slotHandleLsColNetworkError); - connect(job, &LsColJob::directoryListingSubfolders, - this, &FolderWizardRemotePath::slotTypedPathFound); -} - -void FolderWizardRemotePath::slotTypedPathFound(const QStringList &subpaths) -{ - slotUpdateDirectories(subpaths); - selectByPath(_ui.folderEntry->text()); -} - -LsColJob *FolderWizardRemotePath::runLsColJob(const QString &path) -{ - LsColJob *job = new LsColJob(_account, path, this); - job->setProperties(QList() << "resourcetype"); - connect(job, &LsColJob::directoryListingSubfolders, - this, &FolderWizardRemotePath::slotUpdateDirectories); - connect(job, &LsColJob::finishedWithError, - this, &FolderWizardRemotePath::slotHandleLsColNetworkError); - job->start(); - - return job; -} - -FolderWizardRemotePath::~FolderWizardRemotePath() -{ -} - -bool FolderWizardRemotePath::isComplete() const -{ - if (!_ui.folderTreeWidget->currentItem()) - return false; - - QStringList warnStrings; - QString dir = _ui.folderTreeWidget->currentItem()->data(0, Qt::UserRole).toString(); - if (!dir.startsWith(QLatin1Char('/'))) { - dir.prepend(QLatin1Char('/')); - } - wizard()->setProperty("targetPath", dir); - - for (auto *f : qAsConst(FolderMan::instance()->map())) { - if (f->accountState()->account() != _account) { - continue; - } - QString curDir = f->remotePathTrailingSlash(); - if (QDir::cleanPath(dir) == QDir::cleanPath(curDir)) { - warnStrings.append(tr("This folder is already being synced.")); - } else if (dir.startsWith(curDir)) { - warnStrings.append(tr("You are already syncing %1, which is a parent folder of %2.").arg(Utility::escape(curDir), Utility::escape(dir))); - } else if (curDir.startsWith(dir)) { - warnStrings.append(tr("You are already syncing %1, which is a subfolder of %2.").arg(Utility::escape(curDir), Utility::escape(dir))); - } - } - - showWarn(formatWarnings(warnStrings)); - return true; -} - -void FolderWizardRemotePath::cleanupPage() -{ - showWarn(); -} - -void FolderWizardRemotePath::initializePage() -{ - showWarn(); - slotRefreshFolders(); -} - -void FolderWizardRemotePath::showWarn(const QString &msg) const -{ - if (msg.isEmpty()) { - _ui.warnFrame->hide(); - - } else { - _ui.warnFrame->show(); - _ui.warnLabel->setText(msg); - } -} - -// ==================================================================================== - -FolderWizardSelectiveSync::FolderWizardSelectiveSync(const AccountPtr &account) -{ - QVBoxLayout *layout = new QVBoxLayout(this); - _selectiveSync = new SelectiveSyncWidget(account, this); - layout->addWidget(_selectiveSync); - - const auto vfsMode = bestAvailableVfsMode(); - if (Theme::instance()->forceVirtualFilesOption() && vfsMode == Vfs::WindowsCfApi) { - // using an else if to make the condition not even more complex... - } else if (Theme::instance()->showVirtualFilesOption() && vfsMode != Vfs::Off && (vfsMode == Vfs::WindowsCfApi || Theme::instance()->enableExperimentalFeatures())) { - _virtualFilesCheckBox = new QCheckBox(tr("Use virtual files instead of downloading content immediately%1").arg(vfsMode == Vfs::WindowsCfApi ? QString() : tr(" (experimental)"))); - connect(_virtualFilesCheckBox, &QCheckBox::clicked, this, &FolderWizardSelectiveSync::virtualFilesCheckboxClicked); - connect(_virtualFilesCheckBox, &QCheckBox::stateChanged, this, [this](int state) { - _selectiveSync->setEnabled(state == Qt::Unchecked); - }); - _virtualFilesCheckBox->setChecked(vfsMode == Vfs::WindowsCfApi); - layout->addWidget(_virtualFilesCheckBox); - } -} - -FolderWizardSelectiveSync::~FolderWizardSelectiveSync() -{ -} - - -void FolderWizardSelectiveSync::initializePage() -{ - QString targetPath = wizard()->property("targetPath").toString(); - QString alias = QFileInfo(targetPath).fileName(); - if (alias.isEmpty()) - alias = Theme::instance()->appName(); - _selectiveSync->setFolderInfo(targetPath, alias); - QWizardPage::initializePage(); -} - -bool FolderWizardSelectiveSync::validatePage() -{ - const auto mode = bestAvailableVfsMode(); - const bool useVirtualFiles = (Theme::instance()->forceVirtualFilesOption() && mode == Vfs::WindowsCfApi) || (_virtualFilesCheckBox && _virtualFilesCheckBox->isChecked()); - if (useVirtualFiles) { - const auto availability = Vfs::checkAvailability(wizard()->field(QStringLiteral("sourceFolder")).toString(), mode); - if (!availability) { - auto msg = new QMessageBox(QMessageBox::Warning, tr("Virtual files are not available for the selected folder"), availability.error(), QMessageBox::Ok, this); - msg->setAttribute(Qt::WA_DeleteOnClose); - msg->open(); - return false; - } - } - wizard()->setProperty("selectiveSyncBlackList", useVirtualFiles ? QVariant() : QVariant(_selectiveSync->createBlackList())); - wizard()->setProperty("useVirtualFiles", QVariant(useVirtualFiles)); - return true; -} - -void FolderWizardSelectiveSync::cleanupPage() -{ - QString targetPath = wizard()->property("targetPath").toString(); - QString alias = QFileInfo(targetPath).fileName(); - if (alias.isEmpty()) - alias = Theme::instance()->appName(); - _selectiveSync->setFolderInfo(targetPath, alias); - QWizardPage::cleanupPage(); -} - -void FolderWizardSelectiveSync::virtualFilesCheckboxClicked() -{ - // The click has already had an effect on the box, so if it's - // checked it was newly activated. - if (_virtualFilesCheckBox->isChecked()) { - OwncloudWizard::askExperimentalVirtualFilesFeature(this, [this](bool enable) { - if (!enable) - _virtualFilesCheckBox->setChecked(false); - }); - } -} - - -// ==================================================================================== - - -/** - * Folder wizard itself - */ - -FolderWizard::FolderWizard(AccountPtr account, QWidget *parent) - : QWizard(parent) - , _folderWizardSourcePage(new FolderWizardLocalPath(account)) - , _folderWizardTargetPage(nullptr) - , _folderWizardSelectiveSyncPage(new FolderWizardSelectiveSync(account)) -{ - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - setPage(Page_Source, _folderWizardSourcePage); - _folderWizardSourcePage->installEventFilter(this); - if (!Theme::instance()->singleSyncFolder()) { - _folderWizardTargetPage = new FolderWizardRemotePath(account); - setPage(Page_Target, _folderWizardTargetPage); - _folderWizardTargetPage->installEventFilter(this); - } - setPage(Page_SelectiveSync, _folderWizardSelectiveSyncPage); - - setWindowTitle(tr("Add Folder Sync Connection")); - setOptions(QWizard::CancelButtonOnLeft); - setButtonText(QWizard::FinishButton, tr("Add Sync Connection")); - setWizardStyle(QWizard::ModernStyle); -} - -FolderWizard::~FolderWizard() -{ -} - -bool FolderWizard::eventFilter(QObject *watched, QEvent *event) -{ - if (event->type() == QEvent::LayoutRequest) { - // Workaround QTBUG-3396: forces QWizardPrivate::updateLayout() - QTimer::singleShot(0, this, [this] { setTitleFormat(titleFormat()); }); - } - return QWizard::eventFilter(watched, event); -} - -void FolderWizard::resizeEvent(QResizeEvent *event) -{ - QWizard::resizeEvent(event); - - // workaround for QTBUG-22819: when the error label word wrap, the minimum height is not adjusted - if (auto page = currentPage()) { - int hfw = page->heightForWidth(page->width()); - if (page->height() < hfw) { - page->setMinimumSize(page->minimumSizeHint().width(), hfw); - setTitleFormat(titleFormat()); // And another workaround for QTBUG-3396 - } - } -} - -} // end namespace diff --git a/src/gui/folderwizard.h b/src/gui/folderwizard.h deleted file mode 100644 index f0b4e42e80e..00000000000 --- a/src/gui/folderwizard.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) by Duncan Mac-Vicar P. - * - * 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. - */ - -#ifndef MIRALL_FOLDERWIZARD_H -#define MIRALL_FOLDERWIZARD_H - -#include -#include -#include - -#include "folder.h" -#include "accountfwd.h" - -#include "ui_folderwizardsourcepage.h" -#include "ui_folderwizardtargetpage.h" - -class QCheckBox; - -namespace OCC { - -class SelectiveSyncWidget; - -class ownCloudInfo; - -/** - * @brief The FormatWarningsWizardPage class - * @ingroup gui - */ -class FormatWarningsWizardPage : public QWizardPage -{ - Q_OBJECT -protected: - QString formatWarnings(const QStringList &warnings) const; -}; - -/** - * @brief Page to ask for the local source folder - * @ingroup gui - */ -class FolderWizardLocalPath : public FormatWarningsWizardPage -{ - Q_OBJECT -public: - explicit FolderWizardLocalPath(const AccountPtr &account); - ~FolderWizardLocalPath() override; - - bool isComplete() const override; - void initializePage() override; - void cleanupPage() override; -protected slots: - void slotChooseLocalFolder(); - -private: - Ui_FolderWizardSourcePage _ui; - QMap _folderMap; - AccountPtr _account; -}; - - -/** - * @brief page to ask for the target folder - * @ingroup gui - */ - -class FolderWizardRemotePath : public FormatWarningsWizardPage -{ - Q_OBJECT -public: - explicit FolderWizardRemotePath(const AccountPtr &account); - ~FolderWizardRemotePath() override; - - bool isComplete() const override; - - void initializePage() override; - void cleanupPage() override; - -protected slots: - - void showWarn(const QString & = QString()) const; - void slotAddRemoteFolder(); - void slotCreateRemoteFolder(const QString &); - void slotCreateRemoteFolderFinished(); - void slotHandleMkdirNetworkError(QNetworkReply *); - void slotHandleLsColNetworkError(QNetworkReply *); - void slotUpdateDirectories(const QStringList &); - void slotRefreshFolders(); - void slotItemExpanded(QTreeWidgetItem *); - void slotCurrentItemChanged(QTreeWidgetItem *); - void slotFolderEntryEdited(const QString &text); - void slotLsColFolderEntry(); - void slotTypedPathFound(const QStringList &subpaths); - -private: - LsColJob *runLsColJob(const QString &path); - void recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path); - bool selectByPath(QString path); - Ui_FolderWizardTargetPage _ui; - bool _warnWasVisible; - AccountPtr _account; - QTimer _lscolTimer; -}; - -/** - * @brief The FolderWizardSelectiveSync class - * @ingroup gui - */ -class FolderWizardSelectiveSync : public QWizardPage -{ - Q_OBJECT -public: - explicit FolderWizardSelectiveSync(const AccountPtr &account); - ~FolderWizardSelectiveSync() override; - - bool validatePage() override; - - void initializePage() override; - void cleanupPage() override; - -private slots: - void virtualFilesCheckboxClicked(); - -private: - SelectiveSyncWidget *_selectiveSync; - QCheckBox *_virtualFilesCheckBox = nullptr; -}; - -/** - * @brief The FolderWizard class - * @ingroup gui - */ -class FolderWizard : public QWizard -{ - Q_OBJECT -public: - enum { - Page_Source, - Page_Target, - Page_SelectiveSync - }; - - explicit FolderWizard(AccountPtr account, QWidget *parent = nullptr); - ~FolderWizard() override; - - bool eventFilter(QObject *watched, QEvent *event) override; - void resizeEvent(QResizeEvent *event) override; - -private: - FolderWizardLocalPath *_folderWizardSourcePage; - FolderWizardRemotePath *_folderWizardTargetPage; - FolderWizardSelectiveSync *_folderWizardSelectiveSyncPage; -}; - - -} // namespace OCC - -#endif diff --git a/src/gui/folderwizard/CMakeLists.txt b/src/gui/folderwizard/CMakeLists.txt new file mode 100644 index 00000000000..7f5be4d0e1a --- /dev/null +++ b/src/gui/folderwizard/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(folderwizard STATIC + folderwizard.cpp + folderwizardlocalpath.cpp + folderwizardsourcepage.ui + + folderwizardtargetpage.ui + + folderwizardselectivesync.cpp + + spacespage.cpp + spacespage.ui +) + +target_link_libraries(folderwizard PUBLIC Qt::Widgets libsync owncloudResources) +set_target_properties(folderwizard PROPERTIES AUTOUIC ON AUTORCC ON) +apply_common_target_settings(folderwizard) diff --git a/src/gui/folderwizard/folderwizard.cpp b/src/gui/folderwizard/folderwizard.cpp new file mode 100644 index 00000000000..ba79a5ed36b --- /dev/null +++ b/src/gui/folderwizard/folderwizard.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) by Duncan Mac-Vicar P. + * + * 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 "folderwizard.h" +#include "folderwizard_p.h" + +#include "folderwizardlocalpath.h" +#include "folderwizardselectivesync.h" + +#include "spacespage.h" + +#include "account.h" +#include "common/asserts.h" +#include "common/vfs.h" +#include "gui/application.h" +#include "theme.h" + +#include "gui/folderman.h" + +#include "libsync/graphapi/space.h" + +#include +#include +#include +#include +#include + + +namespace OCC { + +Q_LOGGING_CATEGORY(lcFolderWizard, "gui.folderwizard", QtInfoMsg) + +QString FolderWizardPrivate::formatWarnings(const QStringList &warnings, bool isError) +{ + QString ret; + if (warnings.count() == 1) { + ret = isError ? QCoreApplication::translate("FolderWizard", "Error: %1").arg(warnings.first()) : QCoreApplication::translate("FolderWizard", "Warning: %1").arg(warnings.first()); + } else if (warnings.count() > 1) { + QStringList w2; + for (const auto &warning : warnings) { + w2.append(QStringLiteral("
  • %1
  • ").arg(warning)); + } + ret = isError ? QCoreApplication::translate("FolderWizard", "Error:
      %1
    ").arg(w2.join(QString())) + : QCoreApplication::translate("FolderWizard", "Warning:
      %1
    ").arg(w2.join(QString())); + } + + return ret; +} + +QString FolderWizardPrivate::defaultSyncRoot() const +{ + // this should never happen when we have set up the account using spaces - there is ALWAYS a default root when spaces are in play + // and they are always in play so this check is bogus. todo: #43 + if (!_account->hasDefaultSyncRoot()) { + const auto folderType = FolderMan::NewFolderType::SpacesSyncRoot; + return FolderMan::suggestSyncFolder(folderType, _account->uuid()); + } else { + return _account->defaultSyncRoot(); + } +} + +QUuid FolderWizardPrivate::uuid() const +{ + if (_account) + return _account->uuid(); + return {}; +} + +FolderWizardPrivate::FolderWizardPrivate(FolderWizard *q, Account *account) + : q_ptr(q) + , _account(account) + , _folderWizardSourcePage(new FolderWizardLocalPath(this)) + , _folderWizardSelectiveSyncPage(nullptr) +{ + if (_account) { + _spacesPage = new SpacesPage(account->spacesManager(), q); + q->setPage(FolderWizard::Page_Space, _spacesPage); + } + + q->setPage(FolderWizard::Page_Source, _folderWizardSourcePage); + + // When VFS is available (currently only with Windows' CFApi), and it is forced on, Spaces are meant to be synced as a whole. + const bool showPage = VfsPluginManager::instance().bestAvailableVfsMode() != Vfs::WindowsCfApi || !Theme::instance()->forceVirtualFilesOption(); + if (showPage) { + _folderWizardSelectiveSyncPage = new FolderWizardSelectiveSync(_account, this); + q->setPage(FolderWizard::Page_SelectiveSync, _folderWizardSelectiveSyncPage); + } +} + +QString FolderWizardPrivate::initialLocalPath() const +{ + return FolderMan::instance()->findGoodPathForNewSyncFolder( + defaultSyncRoot(), _spacesPage->currentSpace()->displayName(), FolderMan::NewFolderType::SpacesFolder, _account->uuid()); +} + +uint32_t FolderWizardPrivate::priority() const +{ + return _spacesPage->currentSpace()->sortPriority(); +} + +QUrl FolderWizardPrivate::davUrl() const +{ + auto url = _spacesPage->currentSpace()->webDavUrl(); + return url; +} + +QString FolderWizardPrivate::spaceId() const +{ + return _spacesPage->currentSpace()->id(); +} + +QString FolderWizardPrivate::displayName() const +{ + return _spacesPage->currentSpace()->displayName(); +} + +bool FolderWizardPrivate::useVirtualFiles() const +{ + // todo: see other todo's related to DC-219. Basically we should simply not allow user to pick a local path that is not supported + // for vfs if vfs is generally available. "Use virtual files" should not rely on the folder path check at all, as we should + // block use of unsupported paths from the start (generally via account wizard and folder wizard gui's) + const auto mode = VfsPluginManager::instance().bestAvailableVfsMode(); + const bool useVirtualFiles = (Theme::instance()->forceVirtualFilesOption() && mode == Vfs::WindowsCfApi) || (_folderWizardSelectiveSyncPage && _folderWizardSelectiveSyncPage->useVirtualFiles()); + if (useVirtualFiles) { + QString vfsSupported = Vfs::pathSupportDetail(initialLocalPath(), mode); + if (!vfsSupported.isEmpty()) { + auto msg = new QMessageBox(QMessageBox::Warning, FolderWizard::tr("Virtual files are not available for the selected folder"), vfsSupported, + QMessageBox::Ok, ocApp()->mainWindow()); + msg->setAttribute(Qt::WA_DeleteOnClose); + msg->open(); + return false; + } + } + return useVirtualFiles; +} + +FolderWizard::FolderWizard(Account *account, QWidget *parent) + : QWizard(parent) + , d_ptr(new FolderWizardPrivate(this, account)) +{ + setWindowTitle(tr("Add Folder Sync Connection")); + setObjectName("AddFolderSyncWizard"); + setOptions(QWizard::CancelButtonOnLeft); + setButtonText(QWizard::FinishButton, tr("Add Sync Connection")); + setWizardStyle(QWizard::ModernStyle); + + connect(this, &QWizard::accepted, this, &FolderWizard::sendResult); +} + +void FolderWizard::sendResult() +{ + emit folderWizardAccepted(result()); +} + +FolderMan::SyncConnectionDescription FolderWizard::result() +{ + Q_D(FolderWizard); + // DC-219 todo: + // check this local path to see if it supports vfs if vfs is in play - this check should happen before the wizard is completed, + // eg validate whatever page has the user choice of local path + const QString localPath = d->_folderWizardSourcePage->localPath(); + // leave this until we fix the weird handling in account wizard that leaves the sync root empty if selective sync is chosen + if (!d->_account->hasDefaultSyncRoot()) { + if (FileSystem::isChildPathOf(localPath, d->defaultSyncRoot())) { + d->_account->setDefaultSyncRoot(d->defaultSyncRoot()); + if (!QFileInfo::exists(d->defaultSyncRoot())) { + OC_ASSERT(QDir().mkpath(d->defaultSyncRoot())); + } + } + } + + // clang-format off + return { + d->davUrl(), + d->spaceId(), + localPath, + d->remotePath(), + d->displayName(), + d->useVirtualFiles(), + d->priority(), + d->_folderWizardSelectiveSyncPage ? d->_folderWizardSelectiveSyncPage->selectiveSyncBlackList() : QSet{} + }; + // clang-format on +} + +} // end namespace diff --git a/src/gui/folderwizard/folderwizard.h b/src/gui/folderwizard/folderwizard.h new file mode 100644 index 00000000000..ff126ef030e --- /dev/null +++ b/src/gui/folderwizard/folderwizard.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) by Duncan Mac-Vicar P. + * + * 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 +#include + +#include "gui/folderman.h" + +class QCheckBox; +class QTreeWidgetItem; + +class Ui_FolderWizardTargetPage; + +namespace OCC { + +class FolderWizardPrivate; + +/** + * @brief The FolderWizard class + * @ingroup gui + */ +class FolderWizard : public QWizard +{ + Q_OBJECT +public: + enum PageType { + Page_Space, + Page_Source, + Page_Target, + Page_SelectiveSync + }; + Q_ENUM(PageType) + + explicit FolderWizard(Account *account, QWidget *parent); + + FolderMan::SyncConnectionDescription result(); + Q_DECLARE_PRIVATE(FolderWizard) + +signals: + void folderWizardAccepted(OCC::FolderMan::SyncConnectionDescription result); + +private: + void sendResult(); + + +private: + QScopedPointer d_ptr; +}; + +} // namespace OCC diff --git a/src/gui/folderwizard/folderwizard_p.h b/src/gui/folderwizard/folderwizard_p.h new file mode 100644 index 00000000000..f7a450f9cb2 --- /dev/null +++ b/src/gui/folderwizard/folderwizard_p.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 + */ + +#pragma once + +#include "folderwizard.h" + +#include "libsync/account.h" + +#include +#include +#include +#include + +namespace OCC { +Q_DECLARE_LOGGING_CATEGORY(lcFolderWizard); + +class FolderWizardPrivate +{ +public: + FolderWizardPrivate(FolderWizard *q, Account *account); + static QString formatWarnings(const QStringList &warnings, bool isError = false); + + QString initialLocalPath() const; + + // todo: #44 this used to be calculated by the folderWizardRemotePath impl, which was oc10 specific and is not longer used + // However! the folder defition still wants this value so I'm leaving this dummy here for now (which is the value returned for non oc10 + // accounts), to be investigated later + QString remotePath() const { return QString(); } + + uint32_t priority() const; + + QString defaultSyncRoot() const; + + QUuid uuid() const; + QUrl davUrl() const; + QString spaceId() const; + bool useVirtualFiles() const; + QString displayName() const; + +private: + Q_DECLARE_PUBLIC(FolderWizard) + FolderWizard *q_ptr; + + QPointer _account; + class SpacesPage *_spacesPage = nullptr; + class FolderWizardLocalPath *_folderWizardSourcePage = nullptr; + class FolderWizardSelectiveSync *_folderWizardSelectiveSyncPage = nullptr; +}; + + +class FolderWizardPage : public QWizardPage +{ + Q_OBJECT +public: + FolderWizardPage(FolderWizardPrivate *parent) + : QWizardPage(nullptr) + , _parent(parent) + { + } + +protected: + inline FolderWizardPrivate *folderWizardPrivate() const + { + return _parent; + } + +private: + FolderWizardPrivate *_parent; +}; +} diff --git a/src/gui/folderwizard/folderwizardlocalpath.cpp b/src/gui/folderwizard/folderwizardlocalpath.cpp new file mode 100644 index 00000000000..7f56159d240 --- /dev/null +++ b/src/gui/folderwizard/folderwizardlocalpath.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) by Hannah von Reth + * Copyright (C) by Duncan Mac-Vicar P. + * + * 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 "folderwizardlocalpath.h" +#include "ui_folderwizardsourcepage.h" + +#include "folderwizard.h" +#include "folderwizard_p.h" + +#include "gui/folderman.h" + +#include +#include +#include + +using namespace OCC; + +FolderWizardLocalPath::FolderWizardLocalPath(FolderWizardPrivate *parent) + : FolderWizardPage(parent) + , _ui(new Ui_FolderWizardSourcePage) +{ + _ui->setupUi(this); + registerField(QStringLiteral("sourceFolder*"), _ui->localFolderLineEdit); + connect(_ui->localFolderChooseBtn, &QAbstractButton::clicked, this, &FolderWizardLocalPath::slotChooseLocalFolder); + _ui->localFolderChooseBtn->setToolTip(tr("Click to select a local folder to sync.")); + + _ui->localFolderLineEdit->setToolTip(tr("Enter the path to the local folder.")); + + _ui->warnLabel->setTextFormat(Qt::RichText); + _ui->warnLabel->hide(); +} + +FolderWizardLocalPath::~FolderWizardLocalPath() +{ + delete _ui; +} + +void FolderWizardLocalPath::initializePage() +{ + _ui->warnLabel->hide(); + _ui->localFolderLineEdit->setText(QDir::toNativeSeparators(folderWizardPrivate()->initialLocalPath())); +} + +QString FolderWizardLocalPath::localPath() const +{ + return QDir::fromNativeSeparators(_ui->localFolderLineEdit->text()); +} + +bool FolderWizardLocalPath::isComplete() const +{ + // todo: DC-219 we need a check here to see if the chosen local folder path supports vfs, provided vfs is generally available, + // and reject the path with reason it failed if it's no good. Regardless of whether the user currently wants to use vfs or not, + // we should not accept local paths that *can't* support vfs if they change their mind later. + // use Vfs::pathSupportDetail to get the reason vfs is not supported (path is supported if the return string is empty) + auto folderType = FolderMan::NewFolderType::SpacesFolder; + auto accountUuid = folderWizardPrivate()->uuid(); + QString errorStr = FolderMan::instance()->checkPathValidity(localPath(), folderType, accountUuid); + + bool isOk = errorStr.isEmpty(); + QStringList warnStrings; + if (!isOk) { + warnStrings << errorStr; + } + + _ui->warnLabel->setWordWrap(true); + if (isOk) { + _ui->warnLabel->hide(); + _ui->warnLabel->clear(); + } else { + _ui->warnLabel->show(); + QString warnings = FolderWizardPrivate::formatWarnings(warnStrings); + _ui->warnLabel->setText(warnings); + } + return isOk; +} + +void FolderWizardLocalPath::slotChooseLocalFolder() +{ + QString sf = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); + QDir d(sf); + + // open the first entry of the home dir. Otherwise the dir picker comes + // up with the closed home dir icon, stupid Qt default... + QStringList dirs = d.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks, + QDir::DirsFirst | QDir::Name); + + if (dirs.count() > 0) + sf += QLatin1Char('/') + dirs.at(0); // Take the first dir in home dir. + + QString dir = QFileDialog::getExistingDirectory(this, + tr("Select the local folder"), + sf); + if (!dir.isEmpty()) { + // set the last directory component name as alias + _ui->localFolderLineEdit->setText(QDir::toNativeSeparators(dir)); + } + Q_EMIT completeChanged(); +} diff --git a/src/gui/folderwizard/folderwizardlocalpath.h b/src/gui/folderwizard/folderwizardlocalpath.h new file mode 100644 index 00000000000..1686f9d6ad1 --- /dev/null +++ b/src/gui/folderwizard/folderwizardlocalpath.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) by Hannah von Reth + * Copyright (C) by Duncan Mac-Vicar P. + * + * 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 + */ +#pragma once + +#include "gui/folder.h" +#include "gui/folderwizard/folderwizard_p.h" + +class Ui_FolderWizardSourcePage; +namespace OCC { + + +/** + * @brief Page to ask for the local source folder + * @ingroup gui + */ +class FolderWizardLocalPath : public FolderWizardPage +{ + Q_OBJECT +public: + explicit FolderWizardLocalPath(FolderWizardPrivate *parent); + ~FolderWizardLocalPath() override; + + bool isComplete() const override; + void initializePage() override; + + QString localPath() const; +protected Q_SLOTS: + void slotChooseLocalFolder(); + +private: + Ui_FolderWizardSourcePage *_ui; + QMap _folderMap; +}; + +} diff --git a/src/gui/folderwizard/folderwizardselectivesync.cpp b/src/gui/folderwizard/folderwizardselectivesync.cpp new file mode 100644 index 00000000000..4aeb4bbe014 --- /dev/null +++ b/src/gui/folderwizard/folderwizardselectivesync.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) by Hannah von Reth + * Copyright (C) by Duncan Mac-Vicar P. + * + * 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 "folderwizardselectivesync.h" + +#include "folderwizard.h" +#include "folderwizard_p.h" + +#include "gui/application.h" +#include "gui/selectivesyncwidget.h" + +#include "libsync/theme.h" + +#include "common/vfs.h" + +#include +#include + + +using namespace OCC; + +FolderWizardSelectiveSync::FolderWizardSelectiveSync(Account *account, FolderWizardPrivate *parent) + : FolderWizardPage(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + _selectiveSync = new SelectiveSyncWidget(account, this); + layout->addWidget(_selectiveSync); + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + _virtualFilesCheckBox = new QCheckBox(tr("Use virtual files instead of downloading content immediately")); + connect(_virtualFilesCheckBox, &QCheckBox::checkStateChanged, this, &FolderWizardSelectiveSync::slotVfsStateChanged); + _virtualFilesCheckBox->setChecked(true); + layout->addWidget(_virtualFilesCheckBox); + _virtualFilesCheckBox->setDisabled(Theme::instance()->forceVirtualFilesOption()); + } +} + +FolderWizardSelectiveSync::~FolderWizardSelectiveSync() +{ +} + + +void FolderWizardSelectiveSync::initializePage() +{ + QString targetPath = static_cast(wizard())->d_func()->remotePath(); + QString alias = QFileInfo(targetPath).fileName(); + if (alias.isEmpty()) + alias = Theme::instance()->appName(); + _selectiveSync->setDavUrl(dynamic_cast(wizard())->d_func()->davUrl()); + _selectiveSync->setFolderInfo(targetPath, alias); + QWizardPage::initializePage(); +} + +bool FolderWizardSelectiveSync::validatePage() +{ + if (!useVirtualFiles()) { + _selectiveSyncBlackList = _selectiveSync->createBlackList(); + } + return true; +} + +bool FolderWizardSelectiveSync::useVirtualFiles() const +{ + return _virtualFilesCheckBox && _virtualFilesCheckBox->isChecked(); +} + +const QSet &FolderWizardSelectiveSync::selectiveSyncBlackList() const +{ + return _selectiveSyncBlackList; +} + +void FolderWizardSelectiveSync::slotVfsStateChanged(Qt::CheckState state) +{ + // Enable the selective sync widget when VFS is *not* used, disable it if VFS *is* used. + _selectiveSync->setEnabled(state == Qt::Unchecked); +} diff --git a/src/gui/folderwizard/folderwizardselectivesync.h b/src/gui/folderwizard/folderwizardselectivesync.h new file mode 100644 index 00000000000..ea85bd394d2 --- /dev/null +++ b/src/gui/folderwizard/folderwizardselectivesync.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) by Hannah von Reth + * Copyright (C) by Duncan Mac-Vicar P. + * + * 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 + */ + +#pragma once + +#include "gui/folderwizard/folderwizard_p.h" + +namespace OCC { + +class Account; +class SelectiveSyncWidget; + +/** + * @brief The FolderWizardSelectiveSync class + * @ingroup gui + */ +class FolderWizardSelectiveSync : public FolderWizardPage +{ + Q_OBJECT +public: + explicit FolderWizardSelectiveSync(Account *account, FolderWizardPrivate *parent); + ~FolderWizardSelectiveSync() override; + + bool validatePage() override; + + void initializePage() override; + bool useVirtualFiles() const; + + const QSet &selectiveSyncBlackList() const; + +private slots: + void slotVfsStateChanged(Qt::CheckState state); + +private: + SelectiveSyncWidget *_selectiveSync; + QCheckBox *_virtualFilesCheckBox = nullptr; + QSet _selectiveSyncBlackList; +}; + +} diff --git a/src/gui/folderwizard/folderwizardsourcepage.ui b/src/gui/folderwizard/folderwizardsourcepage.ui new file mode 100644 index 00000000000..ad9b69a0de1 --- /dev/null +++ b/src/gui/folderwizard/folderwizardsourcepage.ui @@ -0,0 +1,65 @@ + + + FolderWizardSourcePage + + + + 0 + 0 + 423 + 174 + + + + Form + + + + + + Pick a local folder on your computer to sync + + + + + + + + + + + + Browse… + + + + + + + + + + + + Qt::TextFormat::RichText + + + + + + + Qt::Orientation::Vertical + + + + 349 + 0 + + + + + + + + + diff --git a/src/gui/folderwizard/folderwizardtargetpage.ui b/src/gui/folderwizard/folderwizardtargetpage.ui new file mode 100644 index 00000000000..2a8d72c5c7f --- /dev/null +++ b/src/gui/folderwizard/folderwizardtargetpage.ui @@ -0,0 +1,169 @@ + + + FolderWizardTargetPage + + + + 0 + 0 + 520 + 350 + + + + Form + + + + + + Select a remote destination folder + + + true + + + false + + + + + + true + + + true + + + + Folders + + + + + + + + + + Create Folder + + + + + + + Refresh + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + + + true + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + false + + + QFrame::NoFrame + + + QFrame::Plain + + + Qt::AutoText + + + :/client/resources/light/warning.svg + + + true + + + false + + + 0 + + + + + + + + + + Qt::RichText + + + + + + + + + + + + + + + diff --git a/src/gui/folderwizard/spacespage.cpp b/src/gui/folderwizard/spacespage.cpp new file mode 100644 index 00000000000..ba4be7a5aef --- /dev/null +++ b/src/gui/folderwizard/spacespage.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "spacespage.h" +#include "ui_spacespage.h" + +#include "spacesmanager.h" + +#include "theme.h" + +using namespace OCC; + +SpacesPage::SpacesPage(GraphApi::SpacesManager *spacesMgr, QWidget *parent) + : QWizardPage(parent) + , ui(new Ui::SpacesPage) +{ + ui->setupUi(this); + + ui->widget->setSpacesManager(spacesMgr); + ui->label->setText( + Theme::instance()->spacesAreCalledFolders() ? tr("Select a folder to sync it to your computer.") : tr("Select a Space to sync it to your computer.")); + + connect(ui->widget, &Spaces::SpacesBrowser::currentSpaceChanged, this, &QWizardPage::completeChanged); +} + +SpacesPage::~SpacesPage() +{ + delete ui; +} + +bool OCC::SpacesPage::isComplete() const +{ + return ui->widget->currentSpace(); +} + +GraphApi::Space *OCC::SpacesPage::currentSpace() const +{ + return ui->widget->currentSpace(); +} diff --git a/src/gui/folderwizard/spacespage.h b/src/gui/folderwizard/spacespage.h new file mode 100644 index 00000000000..03e076c15fc --- /dev/null +++ b/src/gui/folderwizard/spacespage.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "gui/spaces/spacesmodel.h" + +#include + + +namespace Ui { +class SpacesPage; +} + +namespace OCC { + +namespace GraphApi { + class SpacesManager; +} + +class SpacesPage : public QWizardPage +{ + Q_OBJECT + +public: + explicit SpacesPage(GraphApi::SpacesManager *spacesMgr, QWidget *parent); + ~SpacesPage(); + + bool isComplete() const override; + + + GraphApi::Space *currentSpace() const; + +private: + Ui::SpacesPage *ui; +}; + +} diff --git a/src/gui/folderwizard/spacespage.ui b/src/gui/folderwizard/spacespage.ui new file mode 100644 index 00000000000..60027d2002e --- /dev/null +++ b/src/gui/folderwizard/spacespage.ui @@ -0,0 +1,52 @@ + + + SpacesPage + + + + 0 + 0 + 362 + 300 + + + + + + + placeholder text + + + Qt::TextFormat::PlainText + + + + + + + + 0 + 0 + + + + Qt::FocusPolicy::StrongFocus + + + Spaces list + + + + + + + + OCC::Spaces::SpacesBrowser + QWidget +
    gui/spaces/spacesbrowser.h
    + 1 +
    +
    + + +
    diff --git a/src/gui/folderwizardsourcepage.ui b/src/gui/folderwizardsourcepage.ui deleted file mode 100644 index 37ecc61e9ce..00000000000 --- a/src/gui/folderwizardsourcepage.ui +++ /dev/null @@ -1,136 +0,0 @@ - - - FolderWizardSourcePage - - - - 0 - 0 - 423 - 174 - - - - Form - - - - - - Pick a local folder on your computer to sync - - - - - - - - - &Choose... - - - - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 192 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 192 - - - - - - - - - 255 - 255 - 192 - - - - - - - 255 - 255 - 192 - - - - - - - - true - - - QFrame::StyledPanel - - - - - - Qt::RichText - - - 3 - - - - - - - Qt::Vertical - - - - 349 - 0 - - - - - - - - - diff --git a/src/gui/folderwizardtargetpage.ui b/src/gui/folderwizardtargetpage.ui deleted file mode 100644 index ec60fa5da1a..00000000000 --- a/src/gui/folderwizardtargetpage.ui +++ /dev/null @@ -1,189 +0,0 @@ - - - FolderWizardTargetPage - - - - 0 - 0 - 520 - 350 - - - - Form - - - - - - Select a remote destination folder - - - true - - - false - - - - - - true - - - true - - - - Folders - - - - - - - - - - Create Folder - - - - - - - Refresh - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 153 - - - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 153 - - - - - - - - - 255 - 255 - 153 - - - - - - - 255 - 255 - 153 - - - - - - - - true - - - QFrame::NoFrame - - - QFrame::Raised - - - - - - true - - - QFrame::NoFrame - - - QFrame::Plain - - - TextLabel - - - Qt::RichText - - - true - - - 3 - - - - - - - - - - - diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp deleted file mode 100644 index 73d202109b8..00000000000 --- a/src/gui/generalsettings.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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 "generalsettings.h" -#include "ui_generalsettings.h" - -#include "accountmanager.h" -#include "application.h" -#include "common/version.h" -#include "configfile.h" -#include "owncloudsetupwizard.h" -#include "theme.h" - -#include "updater/updater.h" -#include "updater/ocupdater.h" -#ifdef Q_OS_MAC -// FIXME We should unify those, but Sparkle does everything behind the scene transparently -#include "updater/sparkleupdater.h" -#endif -#include "ignorelisteditor.h" - -#include "config.h" -#include "translations.h" - -#include -#include -#include -#include -#include - -namespace OCC { - -GeneralSettings::GeneralSettings(QWidget *parent) - : QWidget(parent) - , _ui(new Ui::GeneralSettings) - , _currentlyLoading(false) -{ - _ui->setupUi(this); - - connect(_ui->desktopNotificationsCheckBox, &QAbstractButton::toggled, - this, &GeneralSettings::slotToggleOptionalDesktopNotifications); -#ifdef Q_OS_WIN - connect(_ui->showInExplorerNavigationPaneCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotShowInExplorerNavigationPane); -#endif - - reloadConfig(); - loadMiscSettings(); - slotUpdateInfo(); - - // misc - connect(_ui->monoIconsCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings); - connect(_ui->crashreporterCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings); - connect(_ui->newFolderLimitCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings); - connect(_ui->newFolderLimitSpinBox, static_cast(&QSpinBox::valueChanged), this, &GeneralSettings::saveMiscSettings); - connect(_ui->newExternalStorage, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings); - - connect(_ui->languageDropdown, QOverload::of(&QComboBox::activated), this, [this]() { - // first, store selected language in config file - saveMiscSettings(); - - // warn user that a language change requires a restart to take effect - QMessageBox::warning(this, tr("Warning"), tr("Language changes require a restart of this application to take effect."), QMessageBox::Ok); - }); - - /* 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. - */ - connect(_ui->syncHiddenFilesCheckBox, &QCheckBox::toggled, this,[](bool checked){ - FolderMan::instance()->setIgnoreHiddenFiles(!checked); - }); - -#ifndef WITH_CRASHREPORTER - _ui->crashreporterCheckBox->setVisible(false); -#endif - - // Hide on non-Windows, or WindowsVersion < 10. - // The condition should match the default value of ConfigFile::showInExplorerNavigationPane. - _ui->showInExplorerNavigationPaneCheckBox->setVisible(QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10); - - /* Set the left contents margin of the layout to zero to make the checkboxes - * align properly vertically , fixes bug #3758 - */ - int m0, m1, m2, m3; - _ui->horizontalLayout_3->getContentsMargins(&m0, &m1, &m2, &m3); - _ui->horizontalLayout_3->setContentsMargins(0, m1, m2, m3); - - // OEM themes are not obliged to ship mono icons, so there - // is no point in offering an option - _ui->monoIconsCheckBox->setVisible(Theme::instance()->monoIconsAvailable()); - - connect(_ui->ignoredFilesButton, &QAbstractButton::clicked, this, &GeneralSettings::slotIgnoreFilesEditor); - connect(_ui->logSettingsButton, &QPushButton::clicked, this, [] { - // only access occApp after things are set up - ocApp()->gui()->slotToggleLogBrowser(); - }); - - // accountAdded means the wizard was finished and the wizard might change some options. - connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings); - - // Only our standard brandings currently support beta channel - if (Theme::instance()->appName() != QLatin1String("testpilotcloud")) { -#ifdef Q_OS_MAC - // Because we don't have any statusString from the SparkleUpdater anyway we can hide the whole thing - _ui->updaterWidget->hide(); -#else - _ui->updateChannelLabel->hide(); - _ui->updateChannel->hide(); - if (ConfigFile().updateChannel() != QLatin1String("stable")) { - ConfigFile().setUpdateChannel(QStringLiteral("stable")); - } -#endif - } - connect(_ui->about_pushButton, &QPushButton::clicked, this, &GeneralSettings::showAbout); - - if (!Theme::instance()->aboutShowCopyright()) { - _ui->copyrightLabel->hide(); - } - if (Theme::instance()->forceVirtualFilesOption()) { - _ui->groupBox_non_vfs->hide(); - } -} - -GeneralSettings::~GeneralSettings() -{ - delete _ui; -} - -void GeneralSettings::loadMiscSettings() -{ - QScopedValueRollback scope(_currentlyLoading, true); - ConfigFile cfgFile; - _ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons()); - _ui->desktopNotificationsCheckBox->setChecked(cfgFile.optionalDesktopNotifications()); - _ui->showInExplorerNavigationPaneCheckBox->setChecked(cfgFile.showInExplorerNavigationPane()); - _ui->crashreporterCheckBox->setChecked(cfgFile.crashReporter()); - auto newFolderLimit = cfgFile.newBigFolderSizeLimit(); - _ui->newFolderLimitCheckBox->setChecked(newFolderLimit.first); - _ui->newFolderLimitSpinBox->setValue(newFolderLimit.second); - _ui->newExternalStorage->setChecked(cfgFile.confirmExternalStorage()); - _ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons()); - - // the dropdown has to be populated before we can can pick an entry below based on the stored setting - loadLanguageNamesIntoDropdown(); - - const auto &locale = cfgFile.uiLanguage(); - - // index 0 means "use default", which we use unless the loop below sets another entry - _ui->languageDropdown->setCurrentIndex(0); - - if (!locale.isEmpty()) { - const auto &language = QLocale(locale).nativeLanguageName(); - - // a simple linear search to find the right entry and choose it is sufficient for this application - // we can skip the "use default" entry by starting at index 1 - // note that if the loop below never breaks, the setting falls back to "use default" - // this is desired behavior, as it handles cases when the selected language no longer exists - for (int i = 1; i < _ui->languageDropdown->count(); ++i) { - const auto text = _ui->languageDropdown->itemText(i); - - if (text == language) { - _ui->languageDropdown->setCurrentIndex(i); - break; - } - } - } -} - -void GeneralSettings::showEvent(QShowEvent *) -{ - reloadConfig(); -} - -void GeneralSettings::slotUpdateInfo() -{ - if (ConfigFile().skipUpdateCheck() || !Updater::instance()) { - // updater disabled on compile - _ui->updaterWidget->setVisible(false); - return; - } - - // Note: the sparkle-updater is not an OCUpdater - OCUpdater *ocupdater = qobject_cast(Updater::instance()); - if (ocupdater) { - connect(ocupdater, &OCUpdater::downloadStateChanged, this, &GeneralSettings::slotUpdateInfo, Qt::UniqueConnection); - connect(_ui->restartButton, &QAbstractButton::clicked, ocupdater, &OCUpdater::slotStartInstaller, Qt::UniqueConnection); - connect(_ui->restartButton, &QAbstractButton::clicked, qApp, &QApplication::quit, Qt::UniqueConnection); - - _ui->updateStateLabel->setText(ocupdater->statusString()); - _ui->restartButton->setVisible(ocupdater->downloadState() == OCUpdater::DownloadComplete); - - } -#if defined(Q_OS_MAC) && defined(HAVE_SPARKLE) - else if (SparkleUpdater *sparkleUpdater = qobject_cast(Updater::instance())) { - _ui->updateStateLabel->setText(sparkleUpdater->statusString()); - _ui->restartButton->setVisible(false); - } -#endif - - // Channel selection - _ui->updateChannel->setCurrentIndex(ConfigFile().updateChannel() == "beta" ? 1 : 0); - connect(_ui->updateChannel, static_cast(&QComboBox::currentIndexChanged), - this, &GeneralSettings::slotUpdateChannelChanged, Qt::UniqueConnection); -} - -void GeneralSettings::slotUpdateChannelChanged(int index) -{ - QString channel = index == 0 ? QStringLiteral("stable") : QStringLiteral("beta"); - if (channel == ConfigFile().updateChannel()) - return; - - auto msgBox = new QMessageBox( - QMessageBox::Warning, - tr("Change update channel?"), - tr("The update channel determines which client updates will be offered " - "for installation. The \"stable\" channel contains only upgrades that " - "are considered reliable, while the versions in the \"beta\" channel " - "may contain newer features and bugfixes, but have not yet been tested " - "thoroughly." - "\n\n" - "Note that this selects only what pool upgrades are taken from, and that " - "there are no downgrades: So going back from the beta channel to " - "the stable channel usually cannot be done immediately and means waiting " - "for a stable version that is newer than the currently installed beta " - "version."), - QMessageBox::NoButton, - this); - auto acceptButton = msgBox->addButton(tr("Change update channel"), QMessageBox::AcceptRole); - msgBox->addButton(tr("Cancel"), QMessageBox::RejectRole); - connect(msgBox, &QMessageBox::finished, msgBox, [this, channel, msgBox, acceptButton] { - msgBox->deleteLater(); - if (msgBox->clickedButton() == acceptButton) { - ConfigFile().setUpdateChannel(channel); - if (OCUpdater *updater = qobject_cast(Updater::instance())) { - updater->setUpdateUrl(Updater::updateUrl()); - updater->checkForUpdate(); - } -#if defined(Q_OS_MAC) && defined(HAVE_SPARKLE) - else if (SparkleUpdater *updater = qobject_cast(Updater::instance())) { - updater->setUpdateUrl(Updater::updateUrl()); - updater->checkForUpdate(); - } -#endif - } else { - _ui->updateChannel->setCurrentText(ConfigFile().updateChannel()); - } - }); - msgBox->open(); -} - -void GeneralSettings::saveMiscSettings() -{ - if (_currentlyLoading) - return; - ConfigFile cfgFile; - bool isChecked = _ui->monoIconsCheckBox->isChecked(); - cfgFile.setMonoIcons(isChecked); - Theme::instance()->setSystrayUseMonoIcons(isChecked); - cfgFile.setCrashReporter(_ui->crashreporterCheckBox->isChecked()); - - cfgFile.setNewBigFolderSizeLimit(_ui->newFolderLimitCheckBox->isChecked(), - _ui->newFolderLimitSpinBox->value()); - cfgFile.setConfirmExternalStorage(_ui->newExternalStorage->isChecked()); - - const auto pickedLanguageIndex = _ui->languageDropdown->currentIndex(); - - // the first entry, identified by index 0, means "use default", which is a special case handled below - if (pickedLanguageIndex > 0) { - // for now, we use the locale names as labels in the dropdown - // therefore, we can store them directly in the config file - // in future versions, we will likely display nice names instead of locales to improve the user experience - const auto pickedLanguageName = _ui->languageDropdown->itemText(pickedLanguageIndex); - const auto pickedLanguageLocale = localesToLanguageNamesMap.key(pickedLanguageName); - - cfgFile.setUiLanguage(pickedLanguageLocale); - } else { - // empty string means "use system default" - cfgFile.setUiLanguage(""); - } -} - -void GeneralSettings::slotToggleLaunchOnStartup(bool enable) -{ - Theme *theme = Theme::instance(); - Utility::setLaunchOnStartup(theme->appName(), theme->appNameGUI(), enable); -} - -void GeneralSettings::slotToggleOptionalDesktopNotifications(bool enable) -{ - ConfigFile cfgFile; - cfgFile.setOptionalDesktopNotifications(enable); -} - -#ifdef Q_OS_WIN -void GeneralSettings::slotShowInExplorerNavigationPane(bool checked) -{ - ConfigFile cfgFile; - cfgFile.setShowInExplorerNavigationPane(checked); - // Now update the registry with the change. - FolderMan::instance()->navigationPaneHelper().setShowInExplorerNavigationPane(checked); -} -#endif - -void GeneralSettings::slotIgnoreFilesEditor() -{ - if (_ignoreEditor.isNull()) { - _ignoreEditor = new IgnoreListEditor(this); - _ignoreEditor->setAttribute(Qt::WA_DeleteOnClose, true); - _ignoreEditor->open(); - } else { - ownCloudGui::raiseDialog(_ignoreEditor); - } -} - -void GeneralSettings::reloadConfig() -{ - _ui->syncHiddenFilesCheckBox->setChecked(!FolderMan::instance()->ignoreHiddenFiles()); - if (Utility::hasSystemLaunchOnStartup(Theme::instance()->appName())) { - _ui->autostartCheckBox->setChecked(true); - _ui->autostartCheckBox->setDisabled(true); - _ui->autostartCheckBox->setToolTip(tr("You cannot disable autostart because system-wide autostart is enabled.")); - } else { - const bool hasAutoStart = Utility::hasLaunchOnStartup(Theme::instance()->appName()); - // make sure the binary location is correctly set - slotToggleLaunchOnStartup(hasAutoStart); - _ui->autostartCheckBox->setChecked(hasAutoStart); - connect(_ui->autostartCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotToggleLaunchOnStartup); - } -} - -void GeneralSettings::loadLanguageNamesIntoDropdown() -{ - // initialize map of locales to language names - const auto availableLocales = Translations::listAvailableTranslations(); - for (const auto &availableLocale : availableLocales) { - auto nativeLanguageName = QLocale(availableLocale).nativeLanguageName(); - - // fallback if there's a locale whose name Qt doesn't know - // this indicates a broken filename - if (nativeLanguageName.isEmpty()) { - qCDebug(lcApplication()) << "Warning: could not find native language name for locale" << availableLocale; - nativeLanguageName = tr("unknown (%1)").arg(availableLocale); - } - - localesToLanguageNamesMap.insert(availableLocale, nativeLanguageName); - } - - // allow method to be called more than once - _ui->languageDropdown->clear(); - - // if no option has been chosen explicitly by the user, the first entry shall be used - _ui->languageDropdown->addItem(tr("(use default)")); - - QStringList availableTranslations(localesToLanguageNamesMap.values()); - availableTranslations.sort(Qt::CaseInsensitive); - - for (const auto &i : qAsConst(availableTranslations)) { - _ui->languageDropdown->addItem(i); - } -} - -} // namespace OCC diff --git a/src/gui/generalsettings.h b/src/gui/generalsettings.h deleted file mode 100644 index 7a544f0e0c9..00000000000 --- a/src/gui/generalsettings.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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. - */ - -#ifndef MIRALL_GENERALSETTINGS_H -#define MIRALL_GENERALSETTINGS_H - -#include -#include -#include - -namespace OCC { -class IgnoreListEditor; -class SyncLogDialog; - -namespace Ui { - class GeneralSettings; -} - -/** - * @brief The GeneralSettings class - * @ingroup gui - */ -class GeneralSettings : public QWidget -{ - Q_OBJECT - -public: - explicit GeneralSettings(QWidget *parent = nullptr); - ~GeneralSettings() override; - -signals: - void showAbout(); - -private slots: - void saveMiscSettings(); - void slotToggleLaunchOnStartup(bool); - void slotToggleOptionalDesktopNotifications(bool); -#ifdef Q_OS_WIN - void slotShowInExplorerNavigationPane(bool); -#endif - void slotUpdateInfo(); - void slotUpdateChannelChanged(int index); - void slotIgnoreFilesEditor(); - void loadMiscSettings(); - -protected: - void showEvent(QShowEvent *event) override; - -private: - void reloadConfig(); - void loadLanguageNamesIntoDropdown(); - - Ui::GeneralSettings *_ui; - QPointer _ignoreEditor; - bool _currentlyLoading; - QMap localesToLanguageNamesMap; -}; - - -} // namespace OCC -#endif // MIRALL_GENERALSETTINGS_H diff --git a/src/gui/generalsettings.ui b/src/gui/generalsettings.ui deleted file mode 100644 index 6e0f3dd5516..00000000000 --- a/src/gui/generalsettings.ui +++ /dev/null @@ -1,422 +0,0 @@ - - - OCC::GeneralSettings - - - - 0 - 0 - 791 - 686 - - - - Form - - - - - - true - - - - - 0 - 0 - 771 - 637 - - - - - - - General Settings - - - - - - Use &Monochrome Icons in the system tray - - - - - - - Show &Desktop Notifications - - - - - - - &Launch on System Startup - - - - - - - - - - 0 - 0 - - - - - - - Language - - - - - - - - 0 - 0 - - - - - - - - - - - - - - - - Advanced - - - - - - - - Show sync folders in &Explorer's Navigation Pane - - - - - - - Sync hidden files - - - - - - - - 0 - 0 - - - - S&how crash reporter - - - - - - - - - Edit &Ignored Files - - - - - - - Log Settings - - - - - - - Qt::Horizontal - - - - 555 - 20 - - - - - - - - - - - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - - - - Ask for confirmation before synchronizing folders larger than - - - true - - - - - - - 999999 - - - 99 - - - - - - - MB - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Ask for confirmation before synchronizing external storages - - - - - - - - - - Network - - - - - - - - - - - - Updates - - - - - - - 0 - 0 - - - - &Update Channel - - - updateChannel - - - - - - - - 0 - 0 - - - - - stable - - - - - beta - - - - - - - - - - - true - - - true - - - - - - - - 0 - 0 - - - - &Restart && Update - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - ArrowCursor - - - Copyright ownCloud GmbH - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - About - - - - - - - - - - - NetworkSettings - QWidget -
    networksettings.h
    - 1 -
    -
    - - autostartCheckBox - desktopNotificationsCheckBox - monoIconsCheckBox - showInExplorerNavigationPaneCheckBox - syncHiddenFilesCheckBox - crashreporterCheckBox - ignoredFilesButton - logSettingsButton - newFolderLimitCheckBox - newFolderLimitSpinBox - newExternalStorage - updateChannel - restartButton - scrollArea - - - - - newFolderLimitCheckBox - toggled(bool) - newFolderLimitSpinBox - setEnabled(bool) - - - 245 - 139 - - - 412 - 141 - - - - -
    diff --git a/src/gui/guiutility.cpp b/src/gui/guiutility.cpp index 39077a0b71b..b92e105bde9 100644 --- a/src/gui/guiutility.cpp +++ b/src/gui/guiutility.cpp @@ -13,111 +13,34 @@ */ #include "guiutility.h" +#include "gui/application.h" +#include "libsync/filesystem.h" +#include "libsync/theme.h" -#include #include #include #include #include +#include #include -#include - -#ifdef Q_OS_WIN -#include - -#include -#include - -#include -#include - -using namespace std::chrono; -using namespace std::chrono_literals; -#endif - -#include "theme.h" - -#include "common/asserts.h" namespace OCC { Q_LOGGING_CATEGORY(lcGuiUtility, "gui.utility", QtInfoMsg) } -using namespace OCC; - namespace { - -#ifdef Q_OS_WIN -// TODO: 2.11 move to the new Platform class -struct +const QString dirTag() { - HANDLE windowMessageWatcherEvent = CreateEventW(nullptr, true, false, nullptr); - bool windowMessageWatcherRun = true; -} watchWMCtx; + return QStringLiteral("com.owncloud.spaces.app"); +} -void startShutdownWatcher() +const QString uuidTag() { - // Qt only receives window message if a window was displayed at least once - // create an invisible window to handle WM_ENDSESSION - // We also block a system shutdown until we are properly shutdown our selfs - // In the unlikely case that we require more than 5s Windows will require a fullscreen message - // with our icon, title and the reason why we are blocking the shutdown. - new std::thread([] { - WNDCLASS wc = {}; - wc.hInstance = GetModuleHandle(nullptr); - wc.lpszClassName = L"ocWindowMessageWatcher"; - if (qobject_cast(qApp)) { - wc.hIcon = QtWin::toHICON(Theme::instance()->applicationIcon().pixmap(64, 64)); - } - wc.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT { - // qDebug() << MSG { hwnd, msg, wParam, lParam, 0, {} }; - if (msg == WM_QUERYENDSESSION) { - qCDebug(OCC::lcUtility) << "Received WM_QUERYENDSESSION"; - return 1; - } else if (msg == WM_ENDSESSION) { - qCDebug(OCC::lcUtility) << "Received WM_ENDSESSION quitting"; - QMetaObject::invokeMethod(qApp, &QApplication::quit); - auto start = steady_clock::now(); - if (lParam == ENDSESSION_LOGOFF) { - // block the windows shutdown until we are done - const QString description = QApplication::translate("Utility", "Shutting down %1").arg(Theme::instance()->appNameGUI()); - OC_ASSERT(ShutdownBlockReasonCreate(hwnd, reinterpret_cast(description.utf16()))); - } - WaitForSingleObject(watchWMCtx.windowMessageWatcherEvent, INFINITE); - if (lParam == ENDSESSION_LOGOFF) { - OC_ASSERT(ShutdownBlockReasonDestroy(hwnd)); - } - qCInfo(OCC::lcUtility) << "WM_ENDSESSION successfully shut down" << (steady_clock::now() - start); - watchWMCtx.windowMessageWatcherRun = false; - return 0; - } - return DefWindowProc(hwnd, msg, wParam, lParam); - }; - OC_ASSERT(RegisterClass(&wc)); - - auto watcherWindow = CreateWindowW(wc.lpszClassName, reinterpret_cast(Theme::instance()->appNameGUI().utf16()), - WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, wc.hInstance, nullptr); - OC_ASSERT_X(watcherWindow, Utility::formatWinError(GetLastError()).toUtf8().constData()); - - MSG msg; - while (watchWMCtx.windowMessageWatcherRun) { - if (!PeekMessageW(&msg, watcherWindow, 0, 0, PM_REMOVE)) { - std::this_thread::sleep_for(100ms); - } else { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - }); - - qAddPostRoutine([] { - qCDebug(OCC::lcUtility) << "app closed"; - SetEvent(watchWMCtx.windowMessageWatcherEvent); - }); -} -Q_COREAPP_STARTUP_FUNCTION(startShutdownWatcher); -#endif + return QStringLiteral("com.owncloud.spaces.account-uuid"); } +} // anonymous namespace + +using namespace OCC; bool Utility::openBrowser(const QUrl &url, QWidget *errorWidgetParent) { @@ -139,7 +62,7 @@ bool Utility::openBrowser(const QUrl &url, QWidget *errorWidgetParent) bool Utility::openEmailComposer(const QString &subject, const QString &body, QWidget *errorWidgetParent) { - QUrl url(QLatin1String("mailto:")); + QUrl url(QStringLiteral("mailto:")); QUrlQuery query; query.setQueryItems({ { QLatin1String("subject"), subject }, { QLatin1String("body"), body } }); @@ -161,23 +84,6 @@ bool Utility::openEmailComposer(const QString &subject, const QString &body, QWi return true; } -QString Utility::vfsCurrentAvailabilityText(VfsItemAvailability availability) -{ - switch(availability) { - case VfsItemAvailability::AlwaysLocal: - return QCoreApplication::translate("utility", "Always available locally"); - case VfsItemAvailability::AllHydrated: - return QCoreApplication::translate("utility", "Currently available locally"); - case VfsItemAvailability::Mixed: - return QCoreApplication::translate("utility", "Some available online only"); - case VfsItemAvailability::AllDehydrated: - return QCoreApplication::translate("utility", "Available online only"); - case VfsItemAvailability::OnlineOnly: - return QCoreApplication::translate("utility", "Available online only"); - } - Q_UNREACHABLE(); -} - QString Utility::vfsPinActionText() { return QCoreApplication::translate("utility", "Make always available locally"); @@ -188,14 +94,50 @@ QString Utility::vfsFreeSpaceActionText() return QCoreApplication::translate("utility", "Free up local space"); } +void Utility::markDirectoryAsSyncRoot(const QString &path, const QUuid &accountUuid) +{ + Q_ASSERT(getDirectorySyncRootMarkings(path).first.isEmpty()); + Q_ASSERT(getDirectorySyncRootMarkings(path).second.isNull()); + + auto result1 = FileSystem::Tags::set(path, dirTag(), Theme::instance()->orgDomainName().toUtf8()); + if (!result1) { + qCWarning(lcGuiUtility) << QStringLiteral("Failed to set tag on '%1': %2").arg(path, result1.error()) +#ifdef Q_OS_WIN + << QStringLiteral("(filesystem %1)").arg(FileSystem::fileSystemForPath(path)) +#endif // Q_OS_WIN + ; + return; + } + + auto result2 = FileSystem::Tags::set(path, uuidTag(), accountUuid.toString().toUtf8()); + if (!result2) { + qCWarning(lcGuiUtility) << QStringLiteral("Failed to set tag on '%1': %2").arg(path, result2.error()) +#ifdef Q_OS_WIN + << QStringLiteral("(filesystem %1)").arg(FileSystem::fileSystemForPath(path)) +#endif // Q_OS_WIN + ; + return; + } +} + +std::pair Utility::getDirectorySyncRootMarkings(const QString &path) +{ + auto existingDirTag = FileSystem::Tags::get(path, dirTag()); + auto existingUuidTag = FileSystem::Tags::get(path, uuidTag()); -QIcon Utility::getCoreIcon(const QString &icon_name) + if (existingDirTag.has_value() && existingUuidTag.has_value()) { + return {QString::fromUtf8(existingDirTag.value()), QUuid::fromString(QString::fromUtf8(existingUuidTag.value()))}; + } + + return {}; +} + +void Utility::unmarkDirectoryAsSyncRoot(const QString &path) { - if (icon_name.isEmpty()) { - return {}; + if (!FileSystem::Tags::remove(path, dirTag())) { + qCWarning(lcGuiUtility) << "Failed to remove tag on" << path; + } + if (!FileSystem::Tags::remove(path, uuidTag())) { + qCWarning(lcGuiUtility) << "Failed to remove uuid tag on" << path; } - const QString path = Theme::instance()->isUsingDarkTheme() ? QStringLiteral("dark") : QStringLiteral("light"); - const QIcon icon(QStringLiteral(":/client/resources/%1/%2").arg(path, icon_name)); - Q_ASSERT(!icon.isNull()); - return icon; } diff --git a/src/gui/guiutility.h b/src/gui/guiutility.h index b7bdee53309..b4e87f33d60 100644 --- a/src/gui/guiutility.h +++ b/src/gui/guiutility.h @@ -12,23 +12,24 @@ * for more details. */ -#ifndef GUIUTILITY_H -#define GUIUTILITY_H +#pragma once + +#include "gui/owncloudguilib.h" + +#include "common/pinstate.h" #include #include #include #include -#include "common/pinstate.h" - namespace OCC { Q_DECLARE_LOGGING_CATEGORY(lcGuiUtility) namespace Utility { - /** Open an url in the browser. + /** Open a URL in the browser. * * If launching the browser fails, display a message. */ @@ -41,27 +42,18 @@ namespace Utility { bool openEmailComposer(const QString &subject, const QString &body, QWidget *errorWidgetParent); - /** Returns a translated string indicating the current availability. - * - * This will be used in context menus to describe the current state. - */ - QString vfsCurrentAvailabilityText(VfsItemAvailability availability); - /** Translated text for "making items always available locally" */ QString vfsPinActionText(); /** Translated text for "free up local space" (and unpinning the item) */ QString vfsFreeSpaceActionText(); - QIcon getCoreIcon(const QString &icon_name); - - void tweakUIStyle(); - void startShellIntegration(); QString socketApiSocketPath(); + OWNCLOUDGUI_EXPORT void markDirectoryAsSyncRoot(const QString &path, const QUuid &accountUuid); + std::pair getDirectorySyncRootMarkings(const QString &path); + void unmarkDirectoryAsSyncRoot(const QString &path); } // namespace Utility } // namespace OCC - -#endif diff --git a/src/gui/guiutility_mac.mm b/src/gui/guiutility_mac.mm index f5c880373a9..5adf137044e 100644 --- a/src/gui/guiutility_mac.mm +++ b/src/gui/guiutility_mac.mm @@ -16,16 +16,14 @@ #include "application.h" #include "guiutility.h" +#include "libsync/theme.h" + #include #import namespace OCC { -void Utility::tweakUIStyle() -{ -} - void Utility::startShellIntegration() { QString bundlePath = QUrl::fromNSURL([NSBundle mainBundle].bundleURL).path(); @@ -48,7 +46,8 @@ _system(QStringLiteral("pluginkit"), { QStringLiteral("-a"), QStringLiteral("%1Contents/PlugIns/FinderSyncExt.appex/").arg(bundlePath) }); // Tell Finder to use the Extension (checking it from System Preferences -> Extensions) - _system(QStringLiteral("pluginkit"), { QStringLiteral("-e"), QStringLiteral("use"), QStringLiteral("-i"), QStringLiteral(APPLICATION_REV_DOMAIN ".FinderSyncExt") }); + _system(QStringLiteral("pluginkit"), + {QStringLiteral("-e"), QStringLiteral("use"), QStringLiteral("-i"), Theme::instance()->orgDomainName() + QStringLiteral(".FinderSyncExt")}); } QString Utility::socketApiSocketPath() @@ -56,7 +55,7 @@ // This must match the code signing Team setting of the extension // Example for developer builds (with ad-hoc signing identity): "" "com.owncloud.desktopclient" ".socketApi" // Example for official signed packages: "9B5WD74GWJ." "com.owncloud.desktopclient" ".socketApi" - return QLatin1String(SOCKETAPI_TEAM_IDENTIFIER_PREFIX APPLICATION_REV_DOMAIN ".socketApi"); + return QStringLiteral("%1%2.socketApi").arg(QStringLiteral(SOCKETAPI_TEAM_IDENTIFIER_PREFIX), Theme::instance()->orgDomainName()); } } // namespace OCC diff --git a/src/gui/guiutility_unix.cpp b/src/gui/guiutility_unix.cpp index e3f409c3eed..db4210fe20f 100644 --- a/src/gui/guiutility_unix.cpp +++ b/src/gui/guiutility_unix.cpp @@ -19,18 +19,13 @@ namespace OCC { -void Utility::tweakUIStyle() -{ -} - void Utility::startShellIntegration() { } QString Utility::socketApiSocketPath() { - QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); - return runtimeDir + "/" + Theme::instance()->appName() + "/socket"; + return QStringLiteral("%1/ownCloud/socket").arg(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation)); } } // namespace OCC diff --git a/src/gui/guiutility_win.cpp b/src/gui/guiutility_win.cpp index 2d534f355ee..88ea7d2c510 100644 --- a/src/gui/guiutility_win.cpp +++ b/src/gui/guiutility_win.cpp @@ -19,29 +19,13 @@ namespace OCC { -void Utility::tweakUIStyle() -{ - // The Windows style still has pixelated elements with Qt 5.6, - // it's recommended to use the Fusion style in this case, even - // though it looks slightly less native. Check here after the - // QApplication was constructed, but before any QWidget is - // constructed. - if (qGuiApp->devicePixelRatio() > 1) { - QApplication::setStyle(QStringLiteral("fusion")); - } -} - void Utility::startShellIntegration() { } QString Utility::socketApiSocketPath() { - return QLatin1String("\\\\.\\pipe\\") + QLatin1String("ownCloud-") + qEnvironmentVariable("USERNAME"); - // TODO: once the windows extension supports multiple - // client connections, switch back to the theme name - // See issue #2388 - // + Theme::instance()->appName(); + return QStringLiteral(R"(\\.\pipe\ownCloud-%1)").arg(qEnvironmentVariable("USERNAME")); } } // namespace OCC diff --git a/src/gui/ignorelisteditor.cpp b/src/gui/ignorelisteditor.cpp index f6882e300d0..10a7c543f0b 100644 --- a/src/gui/ignorelisteditor.cpp +++ b/src/gui/ignorelisteditor.cpp @@ -12,18 +12,17 @@ * for more details. */ -#include "configfile.h" - #include "ignorelisteditor.h" -#include "folderman.h" #include "ui_ignorelisteditor.h" -#include +#include "configfile.h" +#include "gui/folderman.h" + #include -#include +#include +#include #include #include -#include namespace OCC { @@ -36,7 +35,6 @@ IgnoreListEditor::IgnoreListEditor(QWidget *parent) : QDialog(parent) , ui(new Ui::IgnoreListEditor) { - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->setupUi(this); ConfigFile cfgFile; @@ -44,9 +42,9 @@ IgnoreListEditor::IgnoreListEditor(QWidget *parent) "and cannot be modified in this view.") .arg(QDir::toNativeSeparators(cfgFile.excludeFile(ConfigFile::SystemScope))); - addPattern(".csync_journal.db*", /*deletable=*/false, /*readonly=*/true, /*global=*/true); - addPattern("._sync_*.db*", /*deletable=*/false, /*readonly=*/true, /*global=*/true); - addPattern(".sync_*.db*", /*deletable=*/false, /*readonly=*/true, /*global=*/true); + addPattern(QStringLiteral(".csync_journal.db*"), /*deletable=*/false, /*readonly=*/true, /*global=*/true); + addPattern(QStringLiteral("._sync_*.db*"), /*deletable=*/false, /*readonly=*/true, /*global=*/true); + addPattern(QStringLiteral(".sync_*.db*"), /*deletable=*/false, /*readonly=*/true, /*global=*/true); readIgnoreFile(cfgFile.excludeFile(ConfigFile::SystemScope), /*global=*/true); readIgnoreFile(cfgFile.excludeFile(ConfigFile::UserScope), /*global=*/false); @@ -103,7 +101,7 @@ void IgnoreListEditor::slotUpdateLocalIgnoreList() QByteArray prepend; if (deletableItem->checkState() == Qt::Checked) { prepend = "]"; - } else if (patternItem->text().startsWith('#')) { + } else if (patternItem->text().startsWith(QLatin1Char('#'))) { prepend = "\\"; } ignores.write(prepend + patternItem->text().toUtf8() + '\n'); @@ -119,11 +117,13 @@ void IgnoreListEditor::slotUpdateLocalIgnoreList() // 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) - for (auto *folder : folderMan->map()) { - folder->journalDb()->forceRemoteDiscoveryNextSync(); - folder->reloadExcludes(); - folder->slotNextSyncFullLocalDiscovery(); - folderMan->scheduleFolder(folder); + for (auto *folder : folderMan->folders()) { + if (folder->isReady()) { + folder->journalDb()->forceRemoteDiscoveryNextSync(); + folder->reloadExcludes(); + folder->slotNextSyncFullLocalDiscovery(); + folderMan->scheduler()->enqueueFolder(folder); + } } } @@ -155,16 +155,16 @@ void IgnoreListEditor::readIgnoreFile(const QString &file, bool global) line.chop(1); // Collect empty lines and comments, we want to preserve them - if (line.isEmpty() || line.startsWith("#")) { + if (line.isEmpty() || line.startsWith(QLatin1String("#"))) { skippedLines.append(line); // A directive that prohibits editing in the ui - if (line == "#!readonly") + if (line == QLatin1String("#!readonly")) readonly = true; continue; } bool deletable = false; - if (line.startsWith(']')) { + if (line.startsWith(QLatin1Char(']'))) { deletable = true; line = line.mid(1); } diff --git a/src/gui/ignorelisteditor.h b/src/gui/ignorelisteditor.h index b8ef563baea..ea0b19e3029 100644 --- a/src/gui/ignorelisteditor.h +++ b/src/gui/ignorelisteditor.h @@ -37,7 +37,7 @@ class IgnoreListEditor : public QDialog explicit IgnoreListEditor(QWidget *parent = nullptr); ~IgnoreListEditor() override; -private slots: +private Q_SLOTS: void slotItemSelectionChanged(); void slotRemoveCurrentItem(); void slotUpdateLocalIgnoreList(); diff --git a/src/gui/issueswidget.cpp b/src/gui/issueswidget.cpp deleted file mode 100644 index 3583ce40869..00000000000 --- a/src/gui/issueswidget.cpp +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 -#include - -#include "issueswidget.h" -#include "configfile.h" -#include "syncresult.h" -#include "syncengine.h" -#include "logger.h" -#include "theme.h" -#include "folderman.h" -#include "syncfileitem.h" -#include "folder.h" -#include "models/models.h" -#include "openfilemanager.h" -#include "protocolwidget.h" -#include "accountstate.h" -#include "account.h" -#include "accountmanager.h" -#include "common/syncjournalfilerecord.h" -#include "elidedlabel.h" - - -#include "ui_issueswidget.h" - -#include - -namespace { -bool persistsUntilLocalDiscovery(const OCC::ProtocolItem &data) -{ - return data.status() == OCC::SyncFileItem::Conflict - || (data.status() == OCC::SyncFileItem::FileIgnored && data.direction() == OCC::SyncFileItem::Up) - || data.status() == OCC::SyncFileItem::Excluded; -} - -} -namespace OCC { - -class SyncFileItemStatusSetSortFilterProxyModel : public QSortFilterProxyModel -{ -public: - using StatusSet = std::array; - - explicit SyncFileItemStatusSetSortFilterProxyModel(QObject *parent = nullptr) - : QSortFilterProxyModel(parent) - { - resetFilter(); - } - - ~SyncFileItemStatusSetSortFilterProxyModel() override - { - } - - StatusSet filter() const - { - return _filter; - } - - void setFilter(const StatusSet &newFilter) - { - if (_filter != newFilter) { - _filter = newFilter; - invalidateFilter(); - } - } - - void resetFilter() - { - StatusSet defaultFilter; - defaultFilter.fill(true); - defaultFilter[SyncFileItem::NoStatus] = false; - defaultFilter[SyncFileItem::Success] = false; - setFilter(defaultFilter); - } - - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override - { - QModelIndex idx = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent); - - bool ok = false; - int sourceData = sourceModel()->data(idx, filterRole()).toInt(&ok); - if (!ok) { - return false; - } - - return _filter[static_cast(sourceData)]; - } - -private: - StatusSet _filter; -}; - -/** - * If more issues are reported than this they will not show up - * to avoid performance issues around sorting this many issues. - */ - -IssuesWidget::IssuesWidget(QWidget *parent) - : QWidget(parent) - , _ui(new Ui::IssuesWidget) -{ - _ui->setupUi(this); - - connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo, - this, &IssuesWidget::slotProgressInfo); - connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted, - this, &IssuesWidget::slotItemCompleted); - connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError, - this, [this](const QString &folderAlias, const QString &message, ErrorCategory) { - auto item = SyncFileItemPtr::create(); - item->_status = SyncFileItem::NormalError; - item->_errorString = message; - _model->addProtocolItem(ProtocolItem { folderAlias, item }); - }); - - connect(ProgressDispatcher::instance(), &ProgressDispatcher::excluded, this, [this](Folder *f, const QString &file, CSYNC_EXCLUDE_TYPE reason) { - auto item = SyncFileItemPtr::create(); - item->_status = SyncFileItem::FileIgnored; - item->_file = file; - switch (reason) { - case CSYNC_FILE_EXCLUDE_RESERVED: - item->_errorString = tr("The file %1 was ignored as its name is reserved by %2").arg(file, Theme::instance()->appNameGUI()); - break; - default: - Q_UNREACHABLE(); - } - _model->addProtocolItem(ProtocolItem { f, item }); - }); - - _model = new ProtocolItemModel(20000, true, this); - _sortModel = new QSortFilterProxyModel(this); - _sortModel->setSourceModel(_model); - _statusSortModel = new SyncFileItemStatusSetSortFilterProxyModel(this); - _statusSortModel->setSourceModel(_sortModel); - _statusSortModel->setSortRole(Qt::DisplayRole); // Sorting should be done based on the text in the column cells, but... - _statusSortModel->setFilterRole(Models::UnderlyingDataRole); // ... filtering should be done on the underlying enum value. - _statusSortModel->setFilterKeyColumn(static_cast(ProtocolItemModel::ProtocolItemRole::Status)); - _ui->_tableView->setModel(_statusSortModel); - - auto header = new ExpandingHeaderView(QStringLiteral("ActivityErrorListHeaderV2"), _ui->_tableView); - _ui->_tableView->setHorizontalHeader(header); - header->setSectionResizeMode(QHeaderView::Interactive); - header->setExpandingColumn(static_cast(ProtocolItemModel::ProtocolItemRole::Action)); - header->setSortIndicator(static_cast(ProtocolItemModel::ProtocolItemRole::Time), Qt::DescendingOrder); - - connect(_ui->_tableView, &QTreeView::customContextMenuRequested, this, &IssuesWidget::slotItemContextMenu); - _ui->_tableView->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); - connect(header, &QHeaderView::customContextMenuRequested, [this, header]() { - auto menu = showFilterMenu(header); - menu->addAction(tr("Reset column sizes"), header, [header] { header->resizeColumns(true); }); - }); - - connect(_ui->_filterButton, &QAbstractButton::clicked, this, [this] { - showFilterMenu(_ui->_filterButton); - }); - - _ui->_tooManyIssuesWarning->hide(); - connect(_model, &ProtocolItemModel::rowsInserted, this, [this] { - Q_EMIT issueCountUpdated(_model->rowCount()); - _ui->_tooManyIssuesWarning->setVisible(_model->isModelFull()); - }); - connect(_model, &ProtocolItemModel::modelReset, this, [this] { - Q_EMIT issueCountUpdated(_model->rowCount()); - _ui->_tooManyIssuesWarning->setVisible(_model->isModelFull()); - }); - - _ui->_conflictHelp->hide(); - _ui->_conflictHelp->setText( - tr("There were conflicts. Check the documentation on how to resolve them.") - .arg(Theme::instance()->conflictHelpUrl())); - - connect(FolderMan::instance(), &FolderMan::folderRemoved, this, [this](Folder *f) { - _model->remove_if([f](const ProtocolItem &item) { - return item.folder() == f; - }); - }); -} - -IssuesWidget::~IssuesWidget() -{ - delete _ui; -} - -QMenu *IssuesWidget::showFilterMenu(QWidget *parent) -{ - auto menu = new QMenu(parent); - menu->setAttribute(Qt::WA_DeleteOnClose); - - auto accountFilterReset = Models::addFilterMenuItems(menu, AccountManager::instance()->accountNames(), _sortModel, static_cast(ProtocolItemModel::ProtocolItemRole::Account), tr("Account"), Qt::DisplayRole); - menu->addSeparator(); - auto statusFilterReset = addStatusFilter(menu); - menu->addSeparator(); - addResetFiltersAction(menu, { accountFilterReset, statusFilterReset }); - - QTimer::singleShot(0, menu, [menu] { - menu->popup(QCursor::pos()); - }); - - return menu; -} - -void IssuesWidget::addResetFiltersAction(QMenu *menu, const QList> &resetFunctions) -{ - menu->addAction(QCoreApplication::translate("OCC::Models", "Reset Filters"), [resetFunctions]() { - for (const auto &reset : resetFunctions) { - reset(); - } - }); -} - -void IssuesWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress) -{ - if (progress.status() == ProgressInfo::Reconcile) { - // Wipe all non-persistent entries - as well as the persistent ones - // in cases where a local discovery was done. - auto f = FolderMan::instance()->folder(folder); - if (!f) - return; - const auto &engine = f->syncEngine(); - const auto style = engine.lastLocalDiscoveryStyle(); - _model->remove_if([&](const ProtocolItem &item) { - if (item.folder() != f) { - return false; - } - if (item.direction() == SyncFileItem::None && item.status() != SyncFileItem::Excluded) { - // TODO: don't clear syncErrors and excludes for now. - // make them either unique or remove them on the next sync? - return false; - } - if (style == LocalDiscoveryStyle::FilesystemOnly) { - return true; - } - if (!persistsUntilLocalDiscovery(item)) { - return true; - } - // Definitely wipe the entry if the file no longer exists - if (!QFileInfo::exists(f->path() + item.path())) { - return true; - } - - auto path = QFileInfo(item.path()).dir().path(); - if (path == QLatin1Char('.')) - path.clear(); - - return engine.shouldDiscoverLocally(path); - }); - } - if (progress.status() == ProgressInfo::Done) { - // We keep track very well of pending conflicts. - // Inform other components about them. - QStringList conflicts; - for (const auto &data : _model->rawData()) { - if (data.folder()->path() == folder - && data.status() == SyncFileItem::Conflict) { - conflicts.append(data.path()); - } - } - emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts); - - _ui->_conflictHelp->setHidden(Theme::instance()->conflictHelpUrl().isEmpty() || conflicts.isEmpty()); - } -} - -void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item) -{ - if (!item->showInIssuesTab()) - return; - _model->addProtocolItem(ProtocolItem { folder, item }); -} - -void IssuesWidget::slotItemContextMenu() -{ - auto rows = _ui->_tableView->selectionModel()->selectedRows(); - for (int i = 0; i < rows.size(); ++i) { - rows[i] = _statusSortModel->mapToSource(rows[i]); - rows[i] = _sortModel->mapToSource(rows[i]); - } - ProtocolWidget::showContextMenu(this, _model, rows); -} - -std::function IssuesWidget::addStatusFilter(QMenu *menu) -{ - menu->addAction(QCoreApplication::translate("OCC::Models", "Status Filter:"))->setEnabled(false); - - // Use a QActionGroup to contain all status filter items, so we can find them back easily to reset. - auto statusFilterGroup = new QActionGroup(menu); - statusFilterGroup->setExclusive(false); - - const auto initialFilter = _statusSortModel->filter(); - - { // Add all errors under 1 action: - const std::array ErrorStatusItems = { - SyncFileItem::Status::FatalError, - SyncFileItem::Status::NormalError, - SyncFileItem::Status::SoftError, - SyncFileItem::Status::DetailError - }; - - auto action = menu->addAction(Utility::enumToDisplayName(SyncFileItem::NormalError), [this, ErrorStatusItems](bool checked) { - auto currentFilter = _statusSortModel->filter(); - for (const auto &item : ErrorStatusItems) { - currentFilter[item] = checked; - } - _statusSortModel->setFilter(currentFilter); - }); - action->setCheckable(true); - action->setChecked(initialFilter[ErrorStatusItems[0]]); - statusFilterGroup->addAction(action); - } - - // Add the other non-error items: - const std::array OtherStatusItems = { - SyncFileItem::Status::Conflict, - SyncFileItem::Status::FileIgnored, - SyncFileItem::Status::Restoration, - SyncFileItem::Status::BlacklistedError, - SyncFileItem::Status::Excluded - }; - for (const auto &item : OtherStatusItems) { - auto action = menu->addAction(Utility::enumToDisplayName(item), [this, item](bool checked) { - auto currentFilter = _statusSortModel->filter(); - currentFilter[item] = checked; - _statusSortModel->setFilter(currentFilter); - }); - action->setCheckable(true); - action->setChecked(initialFilter[item]); - statusFilterGroup->addAction(action); - } - - menu->addSeparator(); - - // Add action to reset all filters at once: - return [statusFilterGroup, this]() { - for (QAction *action : statusFilterGroup->actions()) { - action->setChecked(true); - } - _statusSortModel->resetFilter(); - }; -} -} diff --git a/src/gui/issueswidget.h b/src/gui/issueswidget.h deleted file mode 100644 index cda9830ed52..00000000000 --- a/src/gui/issueswidget.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef ISSUESWIDGET_H -#define ISSUESWIDGET_H - -#include -#include -#include -#include - -#include "models/expandingheaderview.h" -#include "models/protocolitemmodel.h" -#include "owncloudgui.h" -#include "progressdispatcher.h" - -#include "ui_issueswidget.h" - -class QSortFilterProxyModel; - -namespace OCC { -class SyncResult; -class SyncFileItemStatusSetSortFilterProxyModel; - -namespace Ui { - class ProtocolWidget; -} -class Application; - -/** - * @brief The ProtocolWidget class - * @ingroup gui - */ -class IssuesWidget : public QWidget -{ - Q_OBJECT -public: - explicit IssuesWidget(QWidget *parent = nullptr); - ~IssuesWidget() override; - -public slots: - void slotProgressInfo(const QString &folder, const ProgressInfo &progress); - void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); - -signals: - void issueCountUpdated(int); - -private slots: - QMenu *showFilterMenu(QWidget *parent); - void slotItemContextMenu(); - -private: - static void addResetFiltersAction(QMenu *menu, const QList> &resetFunctions); - std::function addStatusFilter(QMenu *menu); - - ProtocolItemModel *_model; - QSortFilterProxyModel *_sortModel; - SyncFileItemStatusSetSortFilterProxyModel *_statusSortModel; - - Ui::IssuesWidget *_ui; -}; -} - -#endif diff --git a/src/gui/issueswidget.ui b/src/gui/issueswidget.ui deleted file mode 100644 index db29c21ca8c..00000000000 --- a/src/gui/issueswidget.ui +++ /dev/null @@ -1,111 +0,0 @@ - - - OCC::IssuesWidget - - - - 0 - 0 - 580 - 578 - - - - Form - - - - - - - - List of issues - - - Qt::PlainText - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter - - - - - - - - - Qt::CustomContextMenu - - - true - - - QAbstractItemView::SelectRows - - - false - - - true - - - true - - - false - - - - - - - - - - - There were too many issues. Not all will be visible here. - - - true - - - - - - - There were conflicts. Check the documentation on how to resolve them. - - - true - - - true - - - - - - - - - - - - diff --git a/src/gui/libcloudproviders/cloud-provider.ini.in b/src/gui/libcloudproviders/cloud-provider.ini.in deleted file mode 100644 index 9809ce2191f..00000000000 --- a/src/gui/libcloudproviders/cloud-provider.ini.in +++ /dev/null @@ -1,4 +0,0 @@ -[Cloud Providers] -BusName=@APPLICATION_CLOUDPROVIDERS_DBUS_NAME@ -ObjectPath=@APPLICATION_CLOUDPROVIDERS_DBUS_PATH@ -Version=1 diff --git a/src/gui/libcloudproviders/libcloudproviders.cmake b/src/gui/libcloudproviders/libcloudproviders.cmake deleted file mode 100644 index 90a628da157..00000000000 --- a/src/gui/libcloudproviders/libcloudproviders.cmake +++ /dev/null @@ -1,41 +0,0 @@ -find_package(PkgConfig REQUIRED) -pkg_check_modules(CLOUDPROVIDERS cloudproviders>=0.3 IMPORTED_TARGET) - -if(CLOUDPROVIDERS_FOUND) - pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET) - pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET) -endif() - -set(LIBCLOUDPROVIDERS_POSSIBLE FALSE) -if (TARGET PkgConfig::CLOUDPROVIDERS) - set(LIBCLOUDPROVIDERS_POSSIBLE TRUE) -endif() - -option(WITH_LIBCLOUDPROVIDERS "Whether to bulid with libcloudproviders" ${LIBCLOUDPROVIDERS_POSSIBLE}) - -if(WITH_LIBCLOUDPROVIDERS AND NOT LIBCLOUDPROVIDERS_POSSIBLE) - message(FATAL_ERROR "Trying to enable libcloudproviders but dependencies are missing") -endif() - -if(WITH_LIBCLOUDPROVIDERS) - target_sources(owncloudCore PRIVATE - libcloudproviders/libcloudproviders.cpp - ) - target_link_libraries(owncloudCore PUBLIC - PkgConfig::CLOUDPROVIDERS - PkgConfig::GLIB2 - PkgConfig::GIO - ) - target_compile_definitions(owncloudCore PRIVATE WITH_LIBCLOUDPROVIDERS) - - configure_file(libcloudproviders/cloud-provider.ini.in ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_CLOUDPROVIDERS_DBUS_NAME}.ini) - install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_CLOUDPROVIDERS_DBUS_NAME}.ini - DESTINATION "${DATA_INSTALL_DIR}/cloud-providers") - - message("Building with libcloudproviders") -elseif(UNIX AND NOT APPLE) - message("Building without libcloudproviders") -endif() - -add_feature_info(Libcloudproviders WITH_LIBCLOUDPROVIDERS "Enable cloud provider integration") diff --git a/src/gui/libcloudproviders/libcloudproviders.cpp b/src/gui/libcloudproviders/libcloudproviders.cpp deleted file mode 100644 index 0d12a492f61..00000000000 --- a/src/gui/libcloudproviders/libcloudproviders.cpp +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) by Christian Kamm - * - * 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 -#include -#include -#include - -#include "libcloudproviders.h" -#include "libcloudproviders_p.h" - -#include -#include "folder.h" -#include "folderman.h" -#include "theme.h" -#include "accountstate.h" -#include "config.h" -#include "account.h" -#include "accountmanager.h" - -// These must match the name and path defined in a file in $DATADIR/cloud-providers/ -const gchar dbusName[] = APPLICATION_CLOUDPROVIDERS_DBUS_NAME; -const gchar dbusPath[] = APPLICATION_CLOUDPROVIDERS_DBUS_PATH; - -namespace OCC { - -LibCloudProvidersPrivate::~LibCloudProvidersPrivate() -{ - for (const auto folder : _folderExports.keys()) - unexportFolder(folder); - g_clear_object(&_exporter); - if (_busOwnerId) - g_bus_unown_name(_busOwnerId); -} - -static void onBusAcquired(GDBusConnection *connection, const gchar *, gpointer userData) -{ - auto d = static_cast(userData); - - d->_exporter = cloud_providers_provider_exporter_new(connection, dbusName, dbusPath); - cloud_providers_provider_exporter_set_name(d->_exporter, Theme::instance()->appNameGUI().toUtf8().constData()); - - d->updateExportedFolderList(); -} - -static void onNameLost(GDBusConnection *, const gchar *, gpointer userData) -{ - auto d = static_cast(userData); - - d->_folderExports.clear(); - g_clear_object(&d->_exporter); -} - -void LibCloudProvidersPrivate::start() -{ - _busOwnerId = g_bus_own_name( - G_BUS_TYPE_SESSION, dbusName, G_BUS_NAME_OWNER_FLAGS_NONE, - &onBusAcquired, - nullptr, // onNameAcquired - &onNameLost, - this, // user data - nullptr // user data free func - ); - - auto folderMan = FolderMan::instance(); - connect(folderMan, &FolderMan::folderListChanged, - this, &LibCloudProvidersPrivate::updateExportedFolderList); - connect(folderMan, &FolderMan::folderSyncStateChange, - this, &LibCloudProvidersPrivate::updateFolderExport); -} - -void LibCloudProvidersPrivate::updateExportedFolderList() -{ - const auto newFolders = FolderMan::instance()->map().values(); - const auto oldFolders = _folderExports.keys(); - - // Remove folders that are no longer exported - for (const auto old : oldFolders) { - if (!newFolders.contains(old)) - unexportFolder(old); - } - - // Add new folders - for (const auto n : newFolders) { - if (!oldFolders.contains(n)) - exportFolder(n); - } -} - -static void actionDispatcher(GSimpleAction *action, GVariant *, gpointer userData) -{ - gchar *strval; - g_object_get(action, "name", &strval, NULL); - QByteArray name(strval); - g_free(strval); - - auto d = static_cast(userData); - auto q = d->_q; - if (name == "settings") - q->showSettings(); - else - OC_ASSERT_X(false, "unknown action string"); -} - - -void LibCloudProvidersPrivate::exportFolder(Folder *folder) -{ - if (!_exporter) - return; - - GError *error = nullptr; - auto icon = g_icon_new_for_string(APPLICATION_ICON_NAME, &error); - if (error) { - qWarning() << "Could not create icon for" << APPLICATION_ICON_NAME << "error" << error->message; - g_error_free(error); - } - - auto exporter = cloud_providers_account_exporter_new(_exporter, folder->alias().toUtf8().constData()); - cloud_providers_account_exporter_set_path(exporter, folder->path().toUtf8().constData()); - cloud_providers_account_exporter_set_icon(exporter, icon); - cloud_providers_account_exporter_set_status(exporter, CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE); - - auto menu = g_menu_new(); - // The "cloudprovider" scope is hardcoded into the gtk code that uses this data. - // Different scopes will not work. - g_menu_append(menu, tr("Settings").toUtf8().constData(), "cloudprovider.settings"); - - auto actionGroup = g_simple_action_group_new(); - const GActionEntry entries[] = { - { "settings", actionDispatcher, nullptr, nullptr, nullptr, {}}, - }; - g_action_map_add_action_entries(G_ACTION_MAP(actionGroup), entries, G_N_ELEMENTS(entries), this); - - cloud_providers_account_exporter_set_menu_model(exporter, G_MENU_MODEL(menu)); - cloud_providers_account_exporter_set_action_group(exporter, G_ACTION_GROUP(actionGroup)); - - // Currently there's no reason for us to keep these around: no further modifications are done - g_clear_object(&menu); - g_clear_object(&actionGroup); - - _folderExports[folder] = FolderExport{folder, exporter}; - updateFolderExport(); -} - -void LibCloudProvidersPrivate::unexportFolder(Folder *folder) -{ - if (!_folderExports.contains(folder)) - return; - auto folderExporter = _folderExports[folder]._exporter; - cloud_providers_provider_exporter_remove_account(_exporter, folderExporter); - // the remove_account already calls _unref - _folderExports.remove(folder); -} - -void LibCloudProvidersPrivate::updateFolderExport() -{ - for (auto folderExport : _folderExports) { - if (!folderExport._folder) - continue; - Folder *folder = folderExport._folder; - - // Update the name, may change if accounts are added/removed - QString displayName = folder->shortGuiRemotePathOrAppName(); - if (AccountManager::instance()->accounts().size() > 1) { - displayName = QStringLiteral("%1 (%2)").arg( - displayName, folder->accountState()->account()->displayName()); - } - cloud_providers_account_exporter_set_name(folderExport._exporter, displayName.toUtf8().constData()); - - CloudProvidersAccountStatus status = CLOUD_PROVIDERS_ACCOUNT_STATUS_INVALID; - auto syncResult = folder->syncResult(); - switch (syncResult.status()) { - case SyncResult::Undefined: - status = CLOUD_PROVIDERS_ACCOUNT_STATUS_INVALID; - break; - case SyncResult::NotYetStarted: - case SyncResult::Success: - case SyncResult::Problem: - status = CLOUD_PROVIDERS_ACCOUNT_STATUS_IDLE; - break; - case SyncResult::SyncPrepare: - case SyncResult::SyncRunning: - case SyncResult::SyncAbortRequested: - status = CLOUD_PROVIDERS_ACCOUNT_STATUS_SYNCING; - break; - case SyncResult::Error: - case SyncResult::SetupError: - status = CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR; - break; - case SyncResult::Paused: - // There's no status that fits exactly. If our choice is only - // between IDLE And ERROR, let's go for ERROR to show that no - // syncing is happening. - status = CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR; - break; - } - - // Similarly to Paused: If disconnected, show something's wrong! - if (!folder->accountState()->isConnected()) - status = CLOUD_PROVIDERS_ACCOUNT_STATUS_ERROR; - - auto message = FolderMan::trayTooltipStatusString( - syncResult, - folder->syncPaused()); - - cloud_providers_account_exporter_set_status(folderExport._exporter, status); - cloud_providers_account_exporter_set_status_details(folderExport._exporter, message.toUtf8().constData()); - } -} - -LibCloudProviders::LibCloudProviders(QObject *parent) - : QObject(parent) - , d_ptr(new LibCloudProvidersPrivate) -{ - d_ptr->_q = this; -} - -void LibCloudProviders::start() -{ - d_ptr->start(); -} - -} // namespace OCC diff --git a/src/gui/libcloudproviders/libcloudproviders.h b/src/gui/libcloudproviders/libcloudproviders.h deleted file mode 100644 index 6f57b698530..00000000000 --- a/src/gui/libcloudproviders/libcloudproviders.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) by Christian Kamm - * - * 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 -#include - -namespace OCC { - -class LibCloudProvidersPrivate; - -/** Exports sync folders to dbus with libcloudproviders */ -class LibCloudProviders : public QObject -{ - Q_OBJECT - - QScopedPointer d_ptr; -public: - explicit LibCloudProviders(QObject *parent = nullptr); - - void start(); - -signals: - void showSettings(); -}; - -} // namespace OCC diff --git a/src/gui/libcloudproviders/libcloudproviders_p.h b/src/gui/libcloudproviders/libcloudproviders_p.h deleted file mode 100644 index cfd7db9ff2f..00000000000 --- a/src/gui/libcloudproviders/libcloudproviders_p.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) by Christian Kamm - * - * 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 -#include -#include -#include - -// Manual forward declares to avoid pulling in C headers that don't -// work well with moc (like due to use of "signals" as an identifier). -typedef struct _CloudProvidersProviderExporter CloudProvidersProviderExporter; -typedef struct _CloudProvidersAccountExporter CloudProvidersAccountExporter; - -// There's a naming mixup. -// What libcloudproviders calls "Provider" is not our Account, but "the ownCloud client" -// What libcloudproviders calls "Account" we call Folder - -namespace OCC { - -class LibCloudProviders; -class Folder; - -class LibCloudProvidersPrivate : public QObject -{ - Q_OBJECT -public: - ~LibCloudProvidersPrivate(); - - void start(); - - void exportFolder(Folder *folder); - void unexportFolder(Folder *folder); - - struct FolderExport - { - /** Can become zero when parent folder is removed, check before use */ - QPointer _folder; - - /** For updating exported folder information */ - CloudProvidersAccountExporter *_exporter; - }; - - /** DBus id so exporting can stop on error or destruction */ - uint _busOwnerId = 0; - - /** Exporter for the whole client. */ - CloudProvidersProviderExporter *_exporter = nullptr; - - /** Each folder's exporter for later updating. */ - QMap _folderExports; - - LibCloudProviders *_q = nullptr; - -public slots: - void updateExportedFolderList(); - void updateFolderExport(); -}; - -} // namespace OCC diff --git a/src/gui/localactivitywidget.cpp b/src/gui/localactivitywidget.cpp new file mode 100644 index 00000000000..44ff4d2a6c9 --- /dev/null +++ b/src/gui/localactivitywidget.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 "accountmanager.h" +#include "accountstate.h" +#include "commonstrings.h" +#include "folder.h" +#include "folderman.h" +#include "guiutility.h" +#include "openfilemanager.h" +#include "localactivitywidget.h" +#include "syncfileitem.h" + +#include "models/expandingheaderview.h" + +#include +#include +#include + +#include "ui_localactivitywidget.h" + +namespace OCC { + +LocalActivityWidget::LocalActivityWidget(QWidget *parent) + : QWidget(parent) + , _ui(new Ui::LocalActivityWidget) +{ + _ui->setupUi(this); + + connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted, + this, &LocalActivityWidget::slotItemCompleted); + + connect(_ui->_tableView, &QTableView::customContextMenuRequested, this, &LocalActivityWidget::slotItemContextMenu); + + // Build the model-view "stack": + // _model <- _sortModel <- _statusSortModel <- _tableView + _model = new ProtocolItemModel(2000, false, this); + _sortModel = new Models::SignalledQSortFilterProxyModel(this); + connect(_sortModel, &Models::SignalledQSortFilterProxyModel::filterChanged, this, &LocalActivityWidget::filterDidChange); + _sortModel->setSourceModel(_model); + _sortModel->setSortRole(Models::UnderlyingDataRole); + _ui->_tableView->setModel(_sortModel); + + auto header = new ExpandingHeaderView(QStringLiteral("ActivityListHeaderV2"), _ui->_tableView); + _ui->_tableView->setHorizontalHeader(header); + header->setSectionResizeMode(QHeaderView::Interactive); + header->setExpandingColumn(static_cast(ProtocolItemModel::ProtocolItemRole::File)); + header->setSortIndicator(static_cast(ProtocolItemModel::ProtocolItemRole::Time), Qt::DescendingOrder); + header->hideSection(static_cast(ProtocolItemModel::ProtocolItemRole::Status)); + header->setContextMenuPolicy(Qt::CustomContextMenu); + connect(header, &QHeaderView::customContextMenuRequested, header, [header, this] { + auto menu = showFilterMenu(header, _sortModel, static_cast(ProtocolItemModel::ProtocolItemRole::Account), tr("Account")); + menu->addSeparator(); + header->addResetActionToMenu(menu); + }); + + connect(_ui->_filterButton, &QAbstractButton::clicked, this, [this] { + showFilterMenu(_ui->_filterButton, _sortModel, static_cast(ProtocolItemModel::ProtocolItemRole::Account), tr("Account")); + }); + + connect(FolderMan::instance(), &FolderMan::folderRemoved, this, &LocalActivityWidget::onFolderRemoved); + connect(FolderMan::instance(), &FolderMan::folderListChanged, this, &LocalActivityWidget::onFolderListChanged); +} + +LocalActivityWidget::~LocalActivityWidget() +{ + delete _ui; +} + +void LocalActivityWidget::onFolderListChanged(const QUuid &accountId, const QList folders) +{ + _model->remove_if([accountId, folders](const ProtocolItem &item) { + return (item.folder()->accountState()->account()->uuid() == accountId && !folders.contains(item.folder())); + }); +} + +void LocalActivityWidget::onFolderRemoved(const QUuid &accountId, Folder *f) +{ + Q_UNUSED(accountId); + _model->remove_if([f](const ProtocolItem &item) { return item.folder() == f; }); +} +/** + * @brief Show a filter menu for the given model. + * + * @param parent Parent widget + * @param model The model that will do the filtering + * @param role the role (column number) to filter on + * @param columnName the name column on which the filter is done + * @return + */ +QMenu *LocalActivityWidget::showFilterMenu(QWidget *parent, Models::SignalledQSortFilterProxyModel *model, int role, const QString &columnName) +{ + auto menu = new QMenu(parent); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setAccessibleName(tr("Filter menu")); + Models::addFilterMenuItems(menu, AccountManager::instance()->accountNames(), model, role, columnName, Qt::DisplayRole); + QTimer::singleShot(0, menu, [menu] { + menu->popup(QCursor::pos()); + // accassability + menu->setFocus(); + }); + return menu; +} + +void LocalActivityWidget::showContextMenu(QWidget *parent, QTableView *table, Models::SignalledQSortFilterProxyModel *sortModel, ProtocolItemModel *itemModel, + const QModelIndexList &items, const QPoint &pos) +{ + auto menu = new QMenu(parent); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setAccessibleName(tr("Actions menu")); + + // keep in sync with ActivityWidget::slotItemContextMenu + menu->addAction(CommonStrings::copyToClipBoard(), parent, [text = Models::formatSelection(items)] { + QApplication::clipboard()->setText(text); + }); + + if (items.size() == 1) { + const auto &data = itemModel->protocolItem(items.first()); + + // Show in file browser action + const QString localPath = data.folder()->path() + data.path(); + // keep in sync with ActivityWidget::slotItemContextMenu + auto showInFileBrowserAction = menu->addAction(CommonStrings::showInFileBrowser(), parent, [localPath] { + // Double-check if the file still exists + if (QFileInfo::exists(localPath)) { + showInFileManager(localPath); + } + }); + if (!QFileInfo::exists(localPath)) { + showInFileBrowserAction->setEnabled(false); + } + + // "Open in Browser" action + auto showInWebBrowserAction = menu->addAction(CommonStrings::showInWebBrowser()); + showInWebBrowserAction->setEnabled(false); + fetchPrivateLinkUrl(data.folder()->accountState()->account(), data.folder()->webDavUrl(), data.folder()->remotePathTrailingSlash() + data.path(), + parent, [showInWebBrowserAction, parent, pos = menu->actions().size(), menu = QPointer(menu)](const QUrl &url) { + // as fetchPrivateLinkUrl is async we need to check the menu still exists + if (menu) { + connect(showInWebBrowserAction, &QAction::triggered, [url, parent] { Utility::openBrowser(url, parent); }); + showInWebBrowserAction->setEnabled(true); + } + }); + + menu->addSeparator(); + + // Sort actions + auto sortActions = new QActionGroup(menu); + int columnNr = table->columnAt(pos.x()); + QString columnName = itemModel->headerData(columnNr, Qt::Horizontal, Qt::DisplayRole).toString(); + auto ascendingAction = + menu->addAction(tr("Sort ascending by %1").arg(columnName), [table, columnNr]() { table->sortByColumn(columnNr, Qt::AscendingOrder); }); + ascendingAction->setCheckable(true); + sortActions->addAction(ascendingAction); + auto descendingAction = + menu->addAction(tr("Sort descending by %1").arg(columnName), [table, columnNr]() { table->sortByColumn(columnNr, Qt::DescendingOrder); }); + descendingAction->setCheckable(true); + sortActions->addAction(descendingAction); + if (columnNr == sortModel->sortColumn()) { + switch (sortModel->sortOrder()) { + case Qt::AscendingOrder: + ascendingAction->setChecked(true); + break; + case Qt::DescendingOrder: + descendingAction->setChecked(true); + break; + } + } + + // Retry action + switch (data.status()) { + case SyncFileItem::DetailError: + Q_FALLTHROUGH(); + case SyncFileItem::SoftError: + Q_FALLTHROUGH(); + case SyncFileItem::BlacklistedError: + menu->addSeparator(); + menu->addAction(tr("Retry sync"), parent, [data, folder = QPointer(data.folder())] { + if (folder) { + folder->journalDb()->wipeErrorBlacklistEntry(data.path()); + FolderMan::instance()->scheduler()->enqueueFolder(folder, SyncScheduler::Priority::Medium); + } + }); + break; + default: + break; + } + } + + menu->popup(table->mapToGlobal(pos)); + menu->setFocus(); // For accessibility +} + +void LocalActivityWidget::slotItemContextMenu(const QPoint &pos) +{ + auto rows = _ui->_tableView->selectionModel()->selectedRows(); + for (int i = 0; i < rows.size(); ++i) { + rows[i] = _sortModel->mapToSource(rows[i]); + } + showContextMenu(this, _ui->_tableView, _sortModel, _model, rows, pos); +} + +void LocalActivityWidget::slotItemCompleted(Folder *folder, const SyncFileItemPtr &item) +{ + if (!item->showInProtocolTab()) + return; + _model->addProtocolItem(ProtocolItem { folder, item }); +} + +void LocalActivityWidget::filterDidChange() +{ + _ui->_filterButton->setText(CommonStrings::filterButtonText(_sortModel->filterRegularExpression().pattern().isEmpty() ? 0 : 1)); +} + +} // OCC namespace diff --git a/src/gui/localactivitywidget.h b/src/gui/localactivitywidget.h new file mode 100644 index 00000000000..ea22f284552 --- /dev/null +++ b/src/gui/localactivitywidget.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 +#include + +// #include "owncloudgui.h" +#include "models/protocolitemmodel.h" + +#include "models/models.h" + +class QSortFilterProxyModel; +class QTableView; + +namespace OCC { + +namespace Ui { + class LocalActivityWidget; +} + +/** + * @brief The LocalActivityWidget class + * @ingroup gui + */ +class LocalActivityWidget : public QWidget +{ + Q_OBJECT +public: + explicit LocalActivityWidget(QWidget *parent = nullptr); + ~LocalActivityWidget() override; + + // todo: these absolutely do not belong here. move them to an independent impl which can be shared between the protocolWidget and the issuesWidget + static void showContextMenu(QWidget *parent, QTableView *table, Models::SignalledQSortFilterProxyModel *sortModel, ProtocolItemModel *itemModel, + const QModelIndexList &items, const QPoint &pos); + static QMenu *showFilterMenu(QWidget *parent, Models::SignalledQSortFilterProxyModel *model, int role, const QString &columnName); + +public Q_SLOTS: + void slotItemCompleted(Folder *folder, const SyncFileItemPtr &item); + void filterDidChange(); + +private Q_SLOTS: + void slotItemContextMenu(const QPoint &pos); + + void onFolderListChanged(const QUuid &accountId, const QList folders); + + void onFolderRemoved(const QUuid &accountId, Folder *f); + +private: + ProtocolItemModel *_model; + Models::SignalledQSortFilterProxyModel *_sortModel; + Ui::LocalActivityWidget *_ui; +}; +} diff --git a/src/gui/localactivitywidget.ui b/src/gui/localactivitywidget.ui new file mode 100644 index 00000000000..90f11c3a00a --- /dev/null +++ b/src/gui/localactivitywidget.ui @@ -0,0 +1,77 @@ + + + OCC::LocalActivityWidget + + + + 0 + 0 + 612 + 515 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Filter + + + + + + + + + Qt::CustomContextMenu + + + Local activity table + + + false + + + true + + + QAbstractItemView::SelectRows + + + false + + + true + + + false + + + false + + + + + + + _filterButton + _tableView + + + + diff --git a/src/gui/lockwatcher.cpp b/src/gui/lockwatcher.cpp index 24648593e1c..af6cfbb7b06 100644 --- a/src/gui/lockwatcher.cpp +++ b/src/gui/lockwatcher.cpp @@ -40,8 +40,8 @@ LockWatcher::LockWatcher(QObject *parent) void LockWatcher::addFile(const QString &path, FileSystem::LockMode mode) { - qCInfo(lcLockWatcher) << "Watching for lock of" << path << "being released"; - _watchedPaths.insert(path, mode); + qCInfo(lcLockWatcher) << "Watching for lock of" << path << mode << "being released"; + _watchedPaths.insert({ path, mode }); } void LockWatcher::setCheckInterval(std::chrono::milliseconds interval) @@ -49,24 +49,24 @@ void LockWatcher::setCheckInterval(std::chrono::milliseconds interval) _timer.start(interval.count()); } -bool LockWatcher::contains(const QString &path) +bool LockWatcher::contains(const QString &path, OCC::FileSystem::LockMode mode) const { - return _watchedPaths.contains(path); + return _watchedPaths.find({ path, mode }) != _watchedPaths.cend(); } void LockWatcher::checkFiles() { - // copy as emit fileUnlocked might trigger a new insert + // copy as Q_EMIT fileUnlocked might trigger a new insert const auto watchedPathsCopy = _watchedPaths; - QSet unlocked; - for (auto it = watchedPathsCopy.cbegin(); it != watchedPathsCopy.cend(); ++it) { - if (!FileSystem::isFileLocked(it.key(), it.value())) { - qCInfo(lcLockWatcher) << "Lock of" << it.key() << "was released"; - emit fileUnlocked(it.key()); - unlocked.insert(it.key()); + decltype(_watchedPaths) unlocked; + for (const auto &p : watchedPathsCopy) { + if (!FileSystem::isFileLocked(p.first, p.second)) { + qCInfo(lcLockWatcher) << "Lock of" << p.first << p.second << "was released"; + Q_EMIT fileUnlocked(p.first, p.second); + unlocked.insert(p); } } - for (const auto &removed : qAsConst(unlocked)) { - _watchedPaths.remove(removed); + for (const auto &removed : std::as_const(unlocked)) { + _watchedPaths.erase(removed); } } diff --git a/src/gui/lockwatcher.h b/src/gui/lockwatcher.h index 04eb4139dca..78cb81f76c5 100644 --- a/src/gui/lockwatcher.h +++ b/src/gui/lockwatcher.h @@ -14,7 +14,8 @@ #pragma once -#include "config.h" +#include "gui/owncloudguilib.h" + #include "filesystem.h" #include @@ -24,6 +25,7 @@ #include #include +#include namespace OCC { @@ -41,7 +43,7 @@ namespace OCC { * @ingroup gui */ -class LockWatcher : public QObject +class OWNCLOUDGUI_EXPORT LockWatcher : public QObject { Q_OBJECT public: @@ -58,18 +60,28 @@ class LockWatcher : public QObject void setCheckInterval(std::chrono::milliseconds interval); /** Whether the path is being watched for lock-changes */ - bool contains(const QString &path); + bool contains(const QString &path, OCC::FileSystem::LockMode mode) const; -signals: +Q_SIGNALS: /** Emitted when one of the watched files is no longer * being locked. */ - void fileUnlocked(const QString &path); + void fileUnlocked(const QString &path, OCC::FileSystem::LockMode mode); -private slots: +private Q_SLOTS: void checkFiles(); private: - QMap _watchedPaths; + using LockKey = std::pair; + + struct HashLockKey + { + size_t operator()(const LockKey &k) const + { + return std::hash {}(k.first) ^ std::hash {}(static_cast(k.second)); + } + }; + + std::unordered_set _watchedPaths; QTimer _timer; }; } diff --git a/src/gui/logbrowser.cpp b/src/gui/logbrowser.cpp index 14474ac1bc6..e90f8086035 100644 --- a/src/gui/logbrowser.cpp +++ b/src/gui/logbrowser.cpp @@ -17,50 +17,46 @@ #include "stdio.h" #include -#include -#include -#include -#include +#include #include -#include +#include #include -#include -#include -#include -#include +#include +#include #include "configfile.h" -#include "logger.h" #include "guiutility.h" +#include "logger.h" #include "ui_logbrowser.h" +#include "resources/resources.h" + namespace OCC { // ============================================================================== -const std::chrono::hours defaultExpireDuration(4); - LogBrowser::LogBrowser(QWidget *parent) : QDialog(parent) , ui(new Ui::LogBrowser) { ui->setupUi(this); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - ui->warningLabel->setPixmap(Utility::getCoreIcon(QStringLiteral("warning")).pixmap(ui->warningLabel->size())); + ui->warningIcon->setPixmap(Resources::getCoreIcon(QStringLiteral("warning")).pixmap(ui->warningIcon->size())); ui->locationLabel->setText(Logger::instance()->temporaryFolderLogDirPath()); ui->enableLoggingButton->setChecked(ConfigFile().automaticLogDir()); connect(ui->enableLoggingButton, &QCheckBox::toggled, this, &LogBrowser::togglePermanentLogging); ui->httpLogButton->setChecked(ConfigFile().logHttp()); - connect(ui->httpLogButton, &QCheckBox::toggled, this, [](bool b) { - ConfigFile().setLogHttp(b); + connect(ui->httpLogButton, &QCheckBox::toggled, this, [](bool enable) { + ConfigFile().configureHttpLogging(std::make_optional(enable)); }); - ui->deleteLogsButton->setText(tr("Delete logs older than %1 hours").arg(QString::number(defaultExpireDuration.count()))); - ui->deleteLogsButton->setChecked(bool(ConfigFile().automaticDeleteOldLogsAge())); - connect(ui->deleteLogsButton, &QCheckBox::toggled, this, &LogBrowser::toggleLogDeletion); + ui->spinBox_numberOflogsToKeep->setValue(ConfigFile().automaticDeleteOldLogs()); + connect(ui->spinBox_numberOflogsToKeep, qOverload(&QSpinBox::valueChanged), this, [](int i) { + ConfigFile().setAutomaticDeleteOldLogs(i); + Logger::instance()->setMaxLogFiles(i); + }); connect(ui->openFolderButton, &QPushButton::clicked, this, []() { @@ -89,12 +85,7 @@ void LogBrowser::setupLoggingFromConfig() return; logger->setupTemporaryFolderLogDir(); - if (auto deleteOldLogsHours = config.automaticDeleteOldLogsAge()) { - logger->setLogExpire(*deleteOldLogsHours); - } else { - logger->setLogExpire(std::chrono::hours(0)); - } - logger->enterNextLogFile(); + Logger::instance()->setMaxLogFiles(config.automaticDeleteOldLogs()); } else { logger->disableTemporaryFolderLogDir(); } @@ -107,18 +98,4 @@ void LogBrowser::togglePermanentLogging(bool enabled) setupLoggingFromConfig(); } -void LogBrowser::toggleLogDeletion(bool enabled) -{ - ConfigFile config; - auto logger = Logger::instance(); - - if (enabled) { - config.setAutomaticDeleteOldLogsAge(defaultExpireDuration); - logger->setLogExpire(defaultExpireDuration); - } else { - config.setAutomaticDeleteOldLogsAge({}); - logger->setLogExpire(std::chrono::hours(0)); - } -} - } // namespace diff --git a/src/gui/logbrowser.h b/src/gui/logbrowser.h index e3e47c24811..fead154170d 100644 --- a/src/gui/logbrowser.h +++ b/src/gui/logbrowser.h @@ -12,20 +12,11 @@ * for more details. */ -#ifndef LOGBROWSER_H -#define LOGBROWSER_H +#pragma once + +#include "gui/owncloudguilib.h" -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include namespace OCC { @@ -37,7 +28,7 @@ namespace Ui { * @brief The LogBrowser class * @ingroup gui */ -class LogBrowser : public QDialog +class OWNCLOUDGUI_EXPORT LogBrowser : public QDialog { Q_OBJECT public: @@ -51,14 +42,11 @@ class LogBrowser : public QDialog */ static void setupLoggingFromConfig(); -protected slots: +protected Q_SLOTS: void togglePermanentLogging(bool enabled); - void toggleLogDeletion(bool enabled); private: QScopedPointer ui; }; } // namespace - -#endif // LOGBROWSER_H diff --git a/src/gui/logbrowser.ui b/src/gui/logbrowser.ui index 24991739c15..d33c1e73314 100644 --- a/src/gui/logbrowser.ui +++ b/src/gui/logbrowser.ui @@ -7,7 +7,7 @@ 0 0 600 - 300 + 399 @@ -17,7 +17,7 @@ - + 0 @@ -40,7 +40,7 @@ - :/client/resources/light/warning.svg + :/client/resources/light/warning.svg true @@ -50,7 +50,7 @@ - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> true @@ -69,7 +69,7 @@ The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. +Since log files can get large, the client will start a new one for each sync run and compress older ones.
    true @@ -125,11 +125,35 @@ Since log files can get large, the client will start a new one for each sync run - - - Delete logs older than %1 hours - - + + + + + Log files to keep: + + + + + + + 5 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -168,7 +192,7 @@ Note that using any logging command line options will override the settings. - + diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 592696a17f4..92abc11f152 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -14,110 +14,477 @@ */ #include -#include "application.h" -#include "common/utility.h" -#include "guiutility.h" -#include "platform.h" -#include "theme.h" +#include "accountmanager.h" +#include "common/restartmanager.h" +#include "gui/application.h" +#include "gui/logbrowser.h" +#include "gui/networkinformation.h" +#include "libsync/configfile.h" +#include "libsync/platform.h" +#include "libsync/theme.h" +#include "resources/loadresources.h" +#include "common/version.h" +#include "gui/translations.h" +#include "libsync/logger.h" +#include "socketapi/socketapi.h" + +#include + +#ifdef WITH_AUTO_UPDATER #include "updater/updater.h" +#endif -#include +#include +#include +#include #include +#include +#include +#include +#ifdef Q_OS_WIN +#include +#endif + +#include using namespace std::chrono_literals; using namespace OCC; -void warnSystray() +Q_LOGGING_CATEGORY(lcMain, "gui.main", QtInfoMsg) + +namespace { +inline auto msgParseOptionsC() { - QMessageBox::critical(nullptr, qApp->translate("main.cpp", "System Tray not available"), - qApp->translate("main.cpp", "%1 requires on a working system tray. " - "If you are running XFCE, please follow " - "these instructions. " - "Otherwise, please install a system tray application such as 'trayer' and try again.") - .arg(Theme::instance()->appNameGUI())); + return QStringLiteral("MSG_PARSEOPTIONS:"); } -int main(int argc, char **argv) +// Helpers for displaying messages. Note that there is probably no console on Windows. +void displayHelpText(const QString &t) { - Q_INIT_RESOURCE(client); + Logger::instance()->attacheToConsole(); + std::cout << qUtf8Printable(t) << std::endl; +#ifdef Q_OS_WIN + // No console on Windows. + QString spaces(80, QLatin1Char(' ')); // Add a line of non-wrapped space to make the messagebox wide enough. + QString text = + QStringLiteral("
    ") + t.toHtmlEscaped() + QStringLiteral("
    ") + spaces + QStringLiteral("
    "); + QMessageBox::information(nullptr, Theme::instance()->appNameGUI(), text); +#endif +} - QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); +struct CommandLineOptions +{ + bool show = false; + bool quitInstance = false; + + QString logDir; + QString logFile; + bool logFlush = false; + bool logDebug = false; - // Create a `Platform` instance so it can set-up/tear-down stuff for us, and do any - // initialisation that needs to be done before creating a QApplication - auto platform = Platform::create(); + bool debugMode = false; +}; + +CommandLineOptions parseOptions(const QStringList &arguments) +{ + QCommandLineParser parser; - // Create the (Q)Application instance: - OCC::Application app(argc, argv); + QString descriptionText; + QTextStream descriptionTextStream(&descriptionText); - Utility::tweakUIStyle(); + descriptionTextStream << QApplication::translate("CommandLine", "%1 version %2\r\nFile synchronization desktop utility.") + .arg(Theme::instance()->appName(), OCC::Version::displayString()) + << Qt::endl; - // if handleStartup returns true, main() - // needs to terminate here, e.g. because - // the updater is triggered - Updater *updater = Updater::instance(); - if (updater && updater->handleStartup()) { - return 1; + if (Theme::instance()->appName() == QLatin1String("ownCloud")) { + descriptionTextStream + << Qt::endl + << Qt::endl + << QApplication::translate("CommandLine", "For more information, see %1", "link to homepage").arg(QStringLiteral("https://www.owncloud.com")); } - // if the application is already running, notify it. - if (app.isRunning()) { - qCInfo(lcApplication) << "Already running, exiting..."; - if (app.isSessionRestored()) { - // This call is mirrored with the one in Application::slotParseMessage - qCInfo(lcApplication) << "Session was restored, don't notify app!"; - return -1; - } + parser.setApplicationDescription(descriptionText); - QStringList args = app.arguments(); - if (args.size() > 1) { - QString msg = args.join(QLatin1String("|")); - if (!app.sendMessage(QLatin1String("MSG_PARSEOPTIONS:") + msg)) - return -1; + auto helpOption = parser.addHelpOption(); + auto versionOption = parser.addVersionOption(); + + // this little snippet saves a few lines below + auto addOption = [&parser](const QCommandLineOption &option) { + parser.addOption(option); + return option; + }; + + auto showSettingsLegacyOption = QCommandLineOption{{QStringLiteral("showsettings")}, QStringLiteral("Hidden legacy option")}; + showSettingsLegacyOption.setFlags(QCommandLineOption::HiddenFromHelp); + parser.addOption(showSettingsLegacyOption); + + auto showOption = addOption({{QStringLiteral("s"), QStringLiteral("show")}, + QApplication::translate("CommandLine", + "Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background.")}); + auto quitInstanceOption = addOption({{QStringLiteral("q"), QStringLiteral("quit")}, QApplication::translate("CommandLine", "Quit the running instance.")}); + auto logFileOption = addOption( + {QStringLiteral("logfile"), QApplication::translate("CommandLine", "Write log to file (use - to write to stdout)."), QStringLiteral("filename")}); + auto logDirOption = addOption( + {QStringLiteral("logdir"), QApplication::translate("CommandLine", "Write each sync log output in a new file in folder."), QStringLiteral("name")}); + auto logFlushOption = addOption({QStringLiteral("logflush"), QApplication::translate("CommandLine", "Flush the log file after every write.")}); + auto logDebugOption = addOption({QStringLiteral("logdebug"), QApplication::translate("CommandLine", "Output debug-level messages in the log.")}); + auto debugOption = addOption({QStringLiteral("debug"), QApplication::translate("CommandLine", "Enable debug mode.")}); + + parser.process(arguments); + + CommandLineOptions out; + if (parser.isSet(showOption) || parser.isSet(showSettingsLegacyOption)) { + out.show = true; + } + if (parser.isSet(quitInstanceOption)) { + out.quitInstance = true; + } + if (parser.isSet(logFileOption)) { + out.logFile = parser.value(logFileOption); + } + if (parser.isSet(logDirOption)) { + if (parser.isSet(logFileOption)) { + displayHelpText(QApplication::translate("CommandLine", "--logfile and --logdir are mutually exclusive")); + std::exit(1); } - return 0; + out.logDir = parser.value(logDirOption); + } + if (parser.isSet(logFlushOption)) { + out.logFlush = true; + } + if (parser.isSet(logDebugOption)) { + out.logDebug = true; + } + if (parser.isSet(debugOption)) { + out.logDebug = true; + out.debugMode = true; } - // We can't call isSystemTrayAvailable with appmenu-qt5 begause it hides the systemtray - // (issue #4693) - if (qgetenv("QT_QPA_PLATFORMTHEME") != "appmenu-qt5") - { - if (!QSystemTrayIcon::isSystemTrayAvailable()) { - // If the systemtray is not there, we will wait one second for it to maybe start - // (eg boot time) then we show the settings dialog if there is still no systemtray. - // On XFCE however, we show a message box with explainaition how to install a systemtray. - qCInfo(lcApplication) << "System tray is not available, waiting..."; - Utility::sleep(1); - - auto desktopSession = qgetenv("XDG_CURRENT_DESKTOP").toLower(); - if (desktopSession.isEmpty()) { - desktopSession = qgetenv("DESKTOP_SESSION").toLower(); - } - if (desktopSession == "xfce") { - int attempts = 0; - while (!QSystemTrayIcon::isSystemTrayAvailable()) { - attempts++; - if (attempts >= 30) { - qCWarning(lcApplication) << "System tray unavailable (xfce)"; - warnSystray(); - break; + return out; +} + +void showDowngradeDialog() +{ + QMessageBox box(QMessageBox::Warning, Theme::instance()->appNameGUI(), + QCoreApplication::translate("version check", + "Some settings were configured in newer versions of this client " + "and use features that are not available in this version")); + box.addButton(OCC::Application::tr("Quit"), QMessageBox::AcceptRole); + box.exec(); + QTimer::singleShot(0, qApp, &QApplication::quit); +} + +/** + * Check if the last version used to write the config file differs from the current version. + * If the current version is newer, update the config file with our current version. If the + * current version is older, refuse to do anything: this is a downgrade, and it is too risky to + * assume that things might work "just fine". + */ +bool checkClientVersion() +{ + ConfigFile configFile; + + // Did the client version change? + // (The client version is adjusted further down) + auto configVersion = QVersionNumber::fromString(configFile.clientVersionWithBuildNumberString()); + auto clientVersion = OCC::Version::versionWithBuildNumber(); + + if (configVersion == clientVersion) { + // no config backup needed + return true; + } + + // We allow downgrades as long as the major version stays the same. + if (clientVersion.majorVersion() < configVersion.majorVersion()) { + // We refuse to downgrade, too much can go wrong. + showDowngradeDialog(); + return false; + } + + // We're okay to continue. The settings will be updated in other parts, but here we bump the + // version we store in the config file. + configFile.backup(); + configFile.setClientVersionWithBuildNumberString(OCC::Version::versionWithBuildNumber().toString()); + return true; +} + +void setupLogging(const CommandLineOptions &options) +{ + // might be called from second instance + auto logger = Logger::instance(); + // call setLogFlush first, other log settings might already imply flushing + // so setting it false in the end will have undesired results. + logger->setLogFlush(options.logFlush); + + if (!options.logDir.isEmpty()) { + logger->setLogDir(options.logDir); + } + if (!options.logFile.isEmpty()) { + Q_ASSERT(options.logDir.isEmpty()); + logger->setLogFile(options.logFile); + } + logger->setLogDebug(options.logDebug); + + // Possibly configure logging from config file + LogBrowser::setupLoggingFromConfig(); + + qCInfo(lcMain) << "##################" << Theme::instance()->appName() << "locale:" << QLocale::system().name() + << "version:" << Theme::instance()->aboutVersions(Theme::VersionFormat::OneLiner); + qCInfo(lcMain) << "Arguments:" << qApp->arguments(); +} + +QString setupTranslations(QApplication *app) +{ + const auto trPath = Translations::translationsDirectoryPath(); + qCDebug(lcMain) << "Translations directory path:" << trPath; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + QStringList uiLanguages = QLocale::system().uiLanguages(QLocale::TagSeparator::Underscore); +#else + QStringList uiLanguages; + for (auto lang : QLocale::system().uiLanguages()) { + uiLanguages << lang.replace(QLatin1Char('-'), QLatin1Char('_')); + } +#endif + qCDebug(lcMain) << "UI languages:" << uiLanguages; + + // the user can also set a locale in the settings, so we need to load the config file + const ConfigFile cfg; + + // we need to track the enforced language separately, since we need to distinguish between locale-provided + // and user-enforced one below + const QString enforcedLocale = cfg.uiLanguage(); + qCDebug(lcMain) << "Enforced language:" << enforcedLocale; + + // note that user-enforced language are prioritized over the theme enforced one + // to make testing easier. + if (!enforcedLocale.isEmpty()) { + uiLanguages.prepend(enforcedLocale); + } + + QString displayLanguage; + + auto substLang = [](const QString &lang) { + // Map the more appropriate script codes + // to country codes as used by Qt and + // transifex translation conventions. + + if (lang == QLatin1String("zh_Hans")) { + // Simplified Chinese + return QStringLiteral("zh_CN"); + } else if (lang == QLatin1String("zh_Hant")) { + // Traditional Chinese + return QStringLiteral("zh_TW"); + } + + return lang; + }; + + for (QString lang : std::as_const(uiLanguages)) { + lang = substLang(lang); + const QString trFile = Translations::translationsFilePrefix() + lang; + if (QTranslator *translator = new QTranslator(app); translator->load(trFile, trPath) || lang.startsWith(QLatin1String("en"))) { + // Permissive approach: Qt and keychain translations + // may be missing, but Qt translations must be there in order + // for us to accept the language. Otherwise, we try with the next. + // "en" is an exception as it is the default language and may not + // have a translation file provided. + qCInfo(lcMain) << "Using" << lang << "translation" << translator->language(); + displayLanguage = lang; + + const QString qtTrPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath); + qCDebug(lcMain) << "qtTrPath:" << qtTrPath; + const QString qtTrFile = QLatin1String("qt_") + lang; + qCDebug(lcMain) << "qtTrFile:" << qtTrFile; + const QString qtBaseTrFile = QLatin1String("qtbase_") + lang; + qCDebug(lcMain) << "qtBaseTrFile:" << qtBaseTrFile; + + QTranslator *qtTranslator = new QTranslator(app); + QTranslator *qtkeychainTranslator = new QTranslator(app); + + if (!qtTranslator->load(qtTrFile, qtTrPath)) { + if (!qtTranslator->load(qtTrFile, trPath)) { + if (!qtTranslator->load(qtBaseTrFile, qtTrPath)) { + if (!qtTranslator->load(qtBaseTrFile, trPath)) { + qCCritical(lcMain) << "Could not load Qt translations"; + } } - Utility::sleep(1); } } - if (QSystemTrayIcon::isSystemTrayAvailable()) { - app.tryTrayAgain(); - } else if (desktopSession != "ubuntu") { - qCInfo(lcApplication) << "System tray still not available, showing window and trying again later"; - QTimer::singleShot(10s, &app, &Application::tryTrayAgain); - } else { - qCInfo(lcApplication) << "System tray still not available, but assuming it's fine on 'ubuntu' desktop"; + const QString qtkeychainTrFile = QLatin1String("qtkeychain_") + lang; + if (!qtkeychainTranslator->load(qtkeychainTrFile, qtTrPath)) { + if (!qtkeychainTranslator->load(qtkeychainTrFile, trPath)) { + qCCritical(lcMain) << "Could not load qtkeychain translations"; + } } + + if (!translator->isEmpty() && !qApp->installTranslator(translator)) { + qCCritical(lcMain) << "Failed to install translator"; + translator->deleteLater(); + } + if (!qtTranslator->isEmpty() && !qApp->installTranslator(qtTranslator)) { + qCCritical(lcMain) << "Failed to install Qt translator"; + qtTranslator->deleteLater(); + } + if (!qtkeychainTranslator->isEmpty() && !qApp->installTranslator(qtkeychainTranslator)) { + qCCritical(lcMain) << "Failed to install qtkeychain translator"; + qtkeychainTranslator->deleteLater(); + } + + // makes sure widgets with locale-dependent formatting, e.g., QDateEdit, display the correct formatting + // if the language is provided by the system locale anyway (i.e., coming from QLocale::system().uiLanguages()), we should + // not mess with the system locale, though + // if we did, we would enforce a locale for no apparent reason + // see https://github.com/owncloud/client/issues/8608 for more information + if (enforcedLocale == lang) { + QLocale newLocale(lang); + qCDebug(lcMain) << "language" << lang << "was enforced, changing default locale to" << newLocale; + QLocale::setDefault(newLocale); + } + break; + } else { + translator->deleteLater(); } } - return app.exec(); + return displayLanguage; +} +} // Anonymous namespace + +int main(int argc, char **argv) +{ + return RestartManager([](int argc, char **argv) { + // load the resources + const OCC::ResourcesLoader resource; + + // Create a `Platform` instance so it can set up/tear down stuff for us, and do any + // initialisation that needs to be done before creating a QApplication + const auto platform = Platform::create(); + + // Create the (Q)Application instance: + QApplication app(argc, argv); + app.setOrganizationDomain(Theme::instance()->orgDomainName()); + app.setApplicationName(Theme::instance()->appName()); + app.setWindowIcon(Theme::instance()->applicationIcon()); + app.setApplicationVersion(Theme::instance()->aboutVersions(Theme::VersionFormat::Url)); + +#ifdef Q_OS_LINUX + // HACK: + // With X11 arg0.name is used to map WM_CLASS to the desktop file. + // With Wayland, it uses app.desktopFileName() or app.organizationDomain() + // As we preferably use AppImages the real desktop file name is unknown, we just ensure it matches the StartupWMClass entry in our desktop file + if (qgetenv("XDG_SESSION_TYPE") == "wayland") { + // https://bugreports.qt.io/browse/QTBUG-77182 Qt 6.9 brings setAppId() but its private api + app.setDesktopFileName(QFileInfo(app.applicationFilePath()).baseName()); + } +#endif + + // Load the translations before option parsing, so we can localize help text and error messages. + const QString displayLanguage = setupTranslations(&app); + + // parse the arguments before we handle singleApplication + // errors and help/version need to be handled in this instance + const auto options = parseOptions(app.arguments()); + + KDSingleApplication singleApplication; + + if (!singleApplication.isPrimaryInstance()) { + // if the application is already running, notify it. + qCInfo(lcMain) << "Already running, exiting..."; + if (app.isSessionRestored()) { + // This call is mirrored with the one in Application::slotParseMessage + qCInfo(lcMain) << "Session was restored, don't notify app!"; + return -1; + } + + QStringList args = app.arguments(); + if (args.size() > 1) { + QString msg = args.join(QLatin1String("|")); + if (!singleApplication.sendMessage((msgParseOptionsC() + msg).toUtf8())) + return -1; + } + + return 0; + } + + // we have to collect this value before calling checkClientVersion, as that will create the config, which leaves us + // unable to know if this is really the first run later in the sequence, when the Application is instantiated. + // basically just save this for later. + bool firstRun = !ConfigFile().exists(); + + // Check if the user upgraded or downgraded. We do this as early as possible, to detect + // a possible downgrade. + if (!checkClientVersion()) { + return -1; + } + + setupLogging(options); + NetworkInformation::instance(); // + + platform->setApplication(&app); + + auto folderManager = FolderMan::createInstance(); + + if (!AccountManager::instance()->restore()) { + qCCritical(lcMain) << "Could not read the account settings, quitting"; + QMessageBox::critical(nullptr, QCoreApplication::translate("account loading", "Error accessing the configuration file"), + QCoreApplication::translate("account loading", "There was an error while accessing the configuration file at %1.") + .arg(ConfigFile::configFile()), + QMessageBox::Close); + return -1; + } + + std::unique_ptr ocApp = Application::createInstance(platform.get(), displayLanguage, options.debugMode); + ocApp->updateAutoRun(firstRun); + QObject::connect(platform.get(), &Platform::requestAttention, ocApp.get(), &Application::ensureVisible); + + QObject::connect(&singleApplication, &KDSingleApplication::messageReceived, ocApp.get(), [&](const QByteArray &message) { + const QString msg = QString::fromUtf8(message); + qCInfo(lcMain) << Q_FUNC_INFO << msg; + if (msg.startsWith(msgParseOptionsC())) { + const QStringList optionsStrings = msg.mid(msgParseOptionsC().size()).split(QLatin1Char('|')); + CommandLineOptions options = parseOptions(optionsStrings); + if (options.show) { + ocApp->ensureVisible(); + } + if (options.quitInstance) { + qApp->quit(); + } + } + }); + + platform->startServices(); + +#ifdef WITH_AUTO_UPDATER + if (Updater::instance()) { + // validate whether the last update was successful. + Updater::instance()->validateUpdate(); + } +#endif + + + // We cannot do this earlier, because the UI needs to be set up in order to + // show sync errors. We also want to wait for the auto-updater to finish, in case it needs + // to install an update. + // Set up the folders and start syncing. This includes a downgrade-detection, in which case the return value + // is empty. Note that the value 0 (zero) is a valid return value (non-empty), in which case + // the dialog is not shown. + if (!FolderMan::instance()->setupFoldersFromConfig().has_value()) { + // Empty return value: there was a downgrade detected on one of the databases + showDowngradeDialog(); + return -1; + } + + if (options.show) { + ocApp->ensureVisible(); + } + + // Now that everything is up and running, start accepting connections/requests from the shell integration. + folderManager->socketApi()->startShellIntegration(); + + return app.exec(); + }).exec(argc, argv); } diff --git a/src/gui/mainwindow/CMakeLists.txt b/src/gui/mainwindow/CMakeLists.txt new file mode 100644 index 00000000000..4179c84b0d3 --- /dev/null +++ b/src/gui/mainwindow/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources(owncloudGui PRIVATE + mainwindow.cpp + mainwindow.h + mainwindowcontroller.cpp + mainwindowcontroller.h + modalwrapperwidget.cpp + modalwrapperwidget.h + aboutview.cpp + aboutview.h + aboutview.ui + settingsview.cpp + settingsview.h + settingsview.ui +) + diff --git a/src/gui/mainwindow/aboutview.cpp b/src/gui/mainwindow/aboutview.cpp new file mode 100644 index 00000000000..f6a45f3e988 --- /dev/null +++ b/src/gui/mainwindow/aboutview.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "aboutview.h" +#include "ui_aboutview.h" + +#include "gui/guiutility.h" +#include "libsync/theme.h" + +// #include + +namespace OCC { + +AboutView::AboutView(QWidget *parent) + : QWidget(parent) + , ui(new Ui::AboutView) +{ + ui->setupUi(this); + ui->aboutText->setText(Theme::instance()->about()); + ui->icon->setPixmap(Theme::instance()->aboutIcon().pixmap(256)); + ui->versionInfo->setText(Theme::instance()->aboutVersions(Theme::VersionFormat::RichText)); + + connect(ui->versionInfo, &QTextBrowser::anchorClicked, this, &AboutView::openBrowserFromUrl); + connect(ui->aboutText, &QLabel::linkActivated, this, &AboutView::openBrowser); +} + +AboutView::~AboutView() +{ + delete ui; +} + +void AboutView::openBrowser(const QString &s) +{ + Utility::openBrowser(QUrl(s), this); +} + +void AboutView::openBrowserFromUrl(const QUrl &s) +{ + return openBrowser(s.toString()); +} + +} // OCC namespace diff --git a/src/gui/mainwindow/aboutview.h b/src/gui/mainwindow/aboutview.h new file mode 100644 index 00000000000..156255c530b --- /dev/null +++ b/src/gui/mainwindow/aboutview.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 + + +namespace OCC { + +namespace Ui { + class AboutView; +} + +class AboutView : public QWidget +{ + Q_OBJECT + +public: + explicit AboutView(QWidget *parent = nullptr); + ~AboutView(); + +private: + void openBrowser(const QString &s); + void openBrowserFromUrl(const QUrl &s); + +private: + Ui::AboutView *ui; +}; + +} diff --git a/src/gui/mainwindow/aboutview.ui b/src/gui/mainwindow/aboutview.ui new file mode 100644 index 00000000000..4ccd4d57f83 --- /dev/null +++ b/src/gui/mainwindow/aboutview.ui @@ -0,0 +1,146 @@ + + + OCC::AboutView + + + + 0 + 0 + 751 + 391 + + + + + 0 + 0 + + + + About + + + Qt::LayoutDirection::LeftToRight + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::FocusPolicy::StrongFocus + + + 0 + + + + About + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 256 + 256 + + + + + + + :/client/ownCloud/theme/colored/owncloud-icon.svg + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + 0 + + + + + + + + + + + Versions + + + + + + false + + + false + + + + + + + + + + + tabWidget + versionInfo + + + + diff --git a/src/gui/mainwindow/mainwindow.cpp b/src/gui/mainwindow/mainwindow.cpp new file mode 100644 index 00000000000..24659f10a32 --- /dev/null +++ b/src/gui/mainwindow/mainwindow.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "mainwindow.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "configfile.h" +#include "modalwrapperwidget.h" +#include "theme.h" + +#ifdef Q_OS_WIN +#include +#endif + +#ifdef Q_OS_MAC +#include "settingsdialog_mac.h" +void setActivationPolicy(ActivationPolicy policy); +#endif + +namespace OCC { +MainWindow::MainWindow() + : QMainWindow{nullptr} +{ + // required as group for saveGeometry call. the original name is "Settings" so we should factor this in + // and make sure we either accept that previous geometry is lost between upgrade/downgrades OR put the name back to Settings + // which imo is a terrible name but we could save the rename til next major version. + // in reality the stored geometry is just the size afaik but will double check. If we had user editable docking positions, + // subwidgets that can run along side the main window, etc. the stored geometry would be more important for user happiness. + setObjectName(QStringLiteral("MainWindow")); + setWindowTitle(Theme::instance()->appNameGUI()); + + // People perceive this as a Window, so also make Ctrl+W work + addAction(tr("Hide"), Qt::CTRL | Qt::Key_W, this, &MainWindow::hide); + + ConfigFile().restoreGeometry(this); + +#ifdef Q_OS_MAC + setActivationPolicy(ActivationPolicy::Accessory); +#endif + + buildWindow(); +} + +void MainWindow::buildWindow() +{ + // clang is complaining that call to minimumSizeHint bypasses virtual dispatch, but I'm not seeing any problem. + // the link to docs for that warning = 404 :D + // regardless, the evaluation is misleading as according to c++ standard, calling a virtual function in a ctr is guaranteed to + // call the override *for the class being constructed*. That is what we want so there is no problem here. + // this is part of the reason we have tidy turned off - it's a bit too touchy. + // question is, why is tidy butting in if we have it turned off? Figure this out later. + setMinimumSize(minimumSizeHint()); + QSize iconsSize(24, 24); + + _actionGroup = new QActionGroup(this); + _actionGroup->setExclusive(true); + + + _toolbar = new QToolBar(this); + _toolbar->setObjectName("mainWindowToolbar"); + // the height is in play if the toolbar is vertically oriented + // not sure what the default separator width is, but without setting this style sheet + // the space "around" the separator line itself was visibly "extra" if you eg select the activity button + // which makes the button "edge" clear. Anyway without this fix, the ... button looked really weird + // looked like it was pushed too far to the right with a larger gap on the left side vs the right + // this change helped a lot. + _toolbar->setStyleSheet("QToolBar::Separator { width: 1px; height: 1px; }"); + _toolbar->setIconSize(iconsSize); + _toolbar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + _toolbar->setMovable(false); + _toolbar->setAccessibleDescription(tr("Main toolbar for the application")); + _toolbar->setAccessibleName(tr("Main toolbar")); + addToolBar(Qt::TopToolBarArea, _toolbar); + + + QWidget *toolbarStretch = new QWidget(this); + toolbarStretch->setFocusPolicy(Qt::NoFocus); + // who knows, maybe someday we use the toolbar vertically + toolbarStretch->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + _stretchAction = _toolbar->addWidget(toolbarStretch); + + // need to stash the separator action so we can insert other actions before it, ie after the stretch section + _separatorAction = _toolbar->addSeparator(); + + QAction *moreAction = new QAction(tr("More"), this); + moreAction->setObjectName("moreAction"); + moreAction->setIcon(Resources::getCoreIcon("more")); + moreAction->setToolTip(tr("More")); + _toolbar->addAction(moreAction); + _moreButton = qobject_cast(_toolbar->widgetForAction(moreAction)); + _moreButton->setToolButtonStyle(Qt::ToolButtonIconOnly); + _moreButton->setFocusPolicy(Qt::StrongFocus); + if (Utility::isMac()) { + // does not work for QToolButton: + // button->setAttribute(Qt::WA_MacShowFocusRect, true); + // so add it to the style sheet + _moreButton->setStyleSheet("QToolButton:focus { border: 2px solid palette(highlight); }" + "QToolButton::menu-indicator { image: none; }"); + } else { + _moreButton->setStyleSheet("QToolButton::menu-indicator { image: none; }"); + } + + _moreButton->setPopupMode(QToolButton::InstantPopup); + // QToolButtons have default fixed size, shaped to their content (watch out if it's text only, especially!). + // In this toolbar all action buttons have icon+text which makes them effectively "taller" than the more button so there is + // dead space which doesn't accept clicks above and below the more icon + // So, make the more button expand vertically so you can click anywhere in the area, just like all the others. + _moreButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + + _widgetStack = new QStackedWidget(this); + setCentralWidget(_widgetStack); +} + +void MainWindow::setMoreMenuActions(const QList &actions) +{ + QMenu *moreMenu = _moreButton->menu(); + if (moreMenu) + moreMenu->clear(); + else + moreMenu = new QMenu(_moreButton); + moreMenu->addActions(actions); + _moreButton->setMenu(moreMenu); +} + +void MainWindow::startModal() +{ + _toolbar->setEnabled(false); +} + +void MainWindow::stopModal() +{ + _toolbar->setEnabled(true); +} + +void MainWindow::showModalWidget(ModalWrapperWidget *w) +{ + // ownCloudGui::raise(); + // not sure if we should have an assert here but for real, the incoming modal widget should + // never be in the current stack + Q_ASSERT(_widgetStack->indexOf(w) == -1); + + connect(w, &ModalWrapperWidget::finished, this, &MainWindow::endModalWidget); + _widgetStack->addWidget(w); + _widgetStack->setCurrentWidget(w); + startModal(); +} + +void MainWindow::endModalWidget() +{ + QWidget *target = qobject_cast(sender()); + if (target == nullptr) + return; + + if (_widgetStack->indexOf(target) >= 0) + _widgetStack->removeWidget(target); + target->deleteLater(); + stopModal(); + // ensure the current stack widget matches the currently "checked" action in the toolbar + _actionGroup->checkedAction()->toggled(true); +} + +void MainWindow::addViewAction(QAction *action) +{ + _toolbar->insertAction(_separatorAction, action); + configureAction(action); + updateFocusChain(); +} + +void MainWindow::addAccountAction(QAction *action) +{ + _toolbar->insertAction(_stretchAction, action); + configureAction(action); + updateFocusChain(); +} + +void MainWindow::configureAction(QAction *action) +{ + QWidget *widget = action->data().value(); + if (widget) { + QWidget *button = _toolbar->widgetForAction(action); + Q_ASSERT(button); + // todo: this does not seem to be working on the account buttons?! It only sets focus on click + // when the button is already selected. for the error and activity buttons, it sets focus on first + // click along with selecting the button. I do not get it. + button->setFocusPolicy(Qt::StrongFocus); + if (Utility::isMac()) { + // does not work for QToolButton: + // button->setAttribute(Qt::WA_MacShowFocusRect, true); + button->setStyleSheet("QToolButton:focus { border: 2px solid palette(highlight); }"); + } + + connect(action, &QAction::toggled, this, &MainWindow::onViewActionTriggered); + _actionGroup->addAction(action); + _widgetStack->addWidget(widget); + } +} +void MainWindow::removeAction(QAction *action) +{ + QWidget *widget = action->data().value(); + if (widget) { + action->disconnect(); + _actionGroup->removeAction(action); + _toolbar->removeAction(action); + _widgetStack->removeWidget(widget); + } +} + +void MainWindow::onViewActionTriggered(bool selected) +{ + if (!selected) + return; + + QAction *sourceAction = qobject_cast(sender()); + if (sourceAction) { + QWidget *widget = sourceAction->data().value(); + if (widget) + _widgetStack->setCurrentWidget(widget); + } +} + +void MainWindow::updateFocusChain() +{ + const auto actions = _toolbar->actions(); + if (actions.isEmpty()) + return; + + QWidget *prevItem = nullptr; + for (QAction *action : actions) { + if (action->objectName().isEmpty()) + continue; + if (QWidget *widget = _toolbar->widgetForAction(action)) { + if (widget->focusPolicy() == Qt::NoFocus) + continue; + if (prevItem == nullptr) { + prevItem = widget; + continue; + } + _toolbar->setTabOrder(prevItem, widget); + prevItem = widget; + } + } +} + +QSize MainWindow::minimumSizeHint() const +{ + const QSize min{800, 700}; // When changing this, please check macOS: widgets there have larger insets, so they take up more space. + const auto screen = windowHandle() ? windowHandle()->screen() : QApplication::screenAt(QCursor::pos()); + if (screen) { + const auto availableSize = screen->availableSize(); + if (availableSize.isValid()) { + // Assume we can use at least 90% of the screen, if the screen is smaller than 800x700 pixels. + // + // Note: this means that the wizards have even less space: with the style we use, the + // wizard tries to fit inside the window. So, if this is a common case that users have + // such small screens, and the contents of the wizard screen are squashed together (or + // not shown due to lack of space), we should consider putting that content in a + // scroll-view. + return min.boundedTo(availableSize * 0.9); + } + } + return min; +} + +void MainWindow::ensureVisible() +{ + show(); + raise(); + activateWindow(); + +#if defined(Q_OS_WIN) + // Windows disallows raising a Window when you're not the active application. + // Use a common hack to attach to the active application + const auto activeProcessId = GetWindowThreadProcessId(GetForegroundWindow(), nullptr); + if (activeProcessId != qApp->applicationPid()) { + const auto threadId = GetCurrentThreadId(); + // don't step here with a debugger... + if (AttachThreadInput(threadId, activeProcessId, true)) { + const auto hwnd = reinterpret_cast(winId()); + SetForegroundWindow(hwnd); + SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + AttachThreadInput(threadId, activeProcessId, false); + } + } + +#endif +} + +void MainWindow::setVisible(bool visible) +{ + if (!visible) { + ConfigFile cfg; + cfg.saveGeometry(this); + } + +#ifdef Q_OS_MAC + if (visible) { + setActivationPolicy(ActivationPolicy::Regular); + } else { + setActivationPolicy(ActivationPolicy::Accessory); + } +#endif + + QMainWindow::setVisible(visible); +} +} diff --git a/src/gui/mainwindow/mainwindow.h b/src/gui/mainwindow/mainwindow.h new file mode 100644 index 00000000000..153a59bf7ee --- /dev/null +++ b/src/gui/mainwindow/mainwindow.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "gui/owncloudguilib.h" +#include + +class QAction; +class QToolBar; +class QToolButton; +class QStackedWidget; +class QActionGroup; + +namespace OCC { + +class ModalWrapperWidget; + +class OWNCLOUDGUI_EXPORT MainWindow : public QMainWindow +{ + Q_OBJECT +public: + explicit MainWindow(); + + QSize minimumSizeHint() const override; + + void ensureVisible(); + + void setVisible(bool visible) override; + + void setMoreMenuActions(const QList &actions); + + void showModalWidget(ModalWrapperWidget *w); + + void addAccountAction(QAction *action); + void removeAction(QAction *action); + + void addViewAction(QAction *action); + +private slots: + void endModalWidget(); + void onViewActionTriggered(bool selected); + +private: + void buildWindow(); + // these just disable the main toolbar so the user can't do anything while a "modal" widget is shown + // in main window stack. We are adjusting the behavior of account modal widgets to *not* disable other functions as it should + // not cause any problems to eg switch to local/error activity view, or trigger one of the more menu actions while an account + // is "waiting" for the user to complete something there. + void startModal(); + void stopModal(); + void configureAction(QAction *action); + void updateFocusChain(); + + QToolBar *_toolbar = nullptr; + QStackedWidget *_widgetStack = nullptr; + QAction *_separatorAction = nullptr; + QAction *_stretchAction = nullptr; + QToolButton *_moreButton = nullptr; + QActionGroup *_actionGroup = nullptr; +}; +} diff --git a/src/gui/mainwindow/mainwindowcontroller.cpp b/src/gui/mainwindow/mainwindowcontroller.cpp new file mode 100644 index 00000000000..c66103177b1 --- /dev/null +++ b/src/gui/mainwindow/mainwindowcontroller.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "mainwindowcontroller.h" + +#include "aboutview.h" +#include "application.h" +#include "localactivitywidget.h" +#include "mainwindow.h" +#include "modalwrapperwidget.h" +#include "settingsview.h" +#include "syncerrorwidget.h" +#include "theme.h" + +#include +#include +#include + +namespace OCC { + +MainWindowController::MainWindowController(MainWindow *window, QObject *parent) + : QObject{parent} + , _window(window) +{ + setup(); +} + +void MainWindowController::setup() +{ + createSyncErrorsAction(); + createActivityAction(); + buildMenuActions(); +} + +void MainWindowController::buildMenuActions() +{ + QList menuActions; + + QAction *addAccountAction = new QAction(tr("Add account..."), this); + addAccountAction->setObjectName("addAcountAction"); + connect(addAccountAction, &QAction::triggered, this, &MainWindowController::requestAccountWizard); + menuActions.push_back(addAccountAction); + + QAction *settingsAction = new QAction(tr("Settings..."), this); + settingsAction->setObjectName("settingsAction"); + connect(settingsAction, &QAction::triggered, this, &MainWindowController::onSettings); + menuActions.push_back(settingsAction); + + QAction *aboutAction = new QAction(tr("About..."), this); + aboutAction->setObjectName("aboutAction"); + connect(aboutAction, &QAction::triggered, this, &MainWindowController::onAbout); + menuActions.push_back(aboutAction); + + QAction *separator = new QAction(this); + separator->setObjectName("sparatorAction"); + separator->setSeparator(true); + menuActions.push_back(separator); + + QAction *quitAction = new QAction(tr("Quit"), this); + quitAction->setObjectName("quitAction"); + connect(quitAction, &QAction::triggered, this, &MainWindowController::onQuit); + menuActions.push_back(quitAction); + _window->setMoreMenuActions(menuActions); +} + +void MainWindowController::createSyncErrorsAction() +{ + QAction *syncErrorsAction = new QAction(tr("Errors: %1").arg(0), this); + syncErrorsAction->setIcon(Resources::getCoreIcon("states/error")); + syncErrorsAction->setObjectName("syncErrorsAction"); + syncErrorsAction->setCheckable(true); + auto syncErrorWidget = new SyncErrorWidget(_window); + connect( + syncErrorWidget, &SyncErrorWidget::issueCountUpdated, this, [syncErrorsAction](int count) { syncErrorsAction->setText(tr("Errors: %1").arg(count)); }); + + syncErrorsAction->setData(QVariant::fromValue(syncErrorWidget)); + _window->addViewAction(syncErrorsAction); +} + +void MainWindowController::createActivityAction() +{ + QAction *activityAction = new QAction(tr("Activity"), this); + activityAction->setIcon(Resources::getCoreIcon("states/sync")); + activityAction->setObjectName("activityAction"); + activityAction->setCheckable(true); + auto localActivityWidget = new LocalActivityWidget(_window); + activityAction->setData(QVariant::fromValue(localActivityWidget)); + _window->addViewAction(activityAction); +} + +void MainWindowController::onSettings() +{ + SettingsView *settings = new SettingsView(_window); + ModalWrapperWidget *wrapper = new ModalWrapperWidget(settings, _window); + _window->showModalWidget(wrapper); +} + +void MainWindowController::onAbout() +{ + _window->ensureVisible(); + AboutView *aboutPanel = new AboutView(_window); + ModalWrapperWidget *wrapper = new ModalWrapperWidget(aboutPanel, _window); + _window->showModalWidget(wrapper); +} + +void MainWindowController::onHelp() +{ + QDesktopServices::openUrl(QUrl(Theme::instance()->helpUrl())); +} + +void MainWindowController::onQuit() +{ + // this is just copied/pasted from the original - most likely will change this to a simple dialog exec, then we + // don't need any delayed handling of the actual call to app quit + auto box = new QMessageBox(QMessageBox::Question, tr("Quit %1").arg(Theme::instance()->appNameGUI()), + tr("Are you sure you want to quit %1?").arg(Theme::instance()->appNameGUI()), QMessageBox::Yes | QMessageBox::No, _window); + box->setAttribute(Qt::WA_DeleteOnClose); + connect(box, &QMessageBox::accepted, this, [] { + // delay quit to prevent a Qt 6.6 crash in the destructor of the dialog + QTimer::singleShot(0, qApp, &QCoreApplication::quit); + }); + box->open(); +} +} diff --git a/src/gui/mainwindow/mainwindowcontroller.h b/src/gui/mainwindow/mainwindowcontroller.h new file mode 100644 index 00000000000..e34a0806323 --- /dev/null +++ b/src/gui/mainwindow/mainwindowcontroller.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +namespace OCC { + +class MainWindow; +class AccountsGuiController; + +class MainWindowController : public QObject +{ + Q_OBJECT +public: + explicit MainWindowController(MainWindow *window, QObject *parent = nullptr); + + // public for now + void setup(); + // called from tray menu, too. make this a signal/slot connection between the tray menu controller and main window controller + void onAbout(); + void onHelp(); + +signals: + void requestAccountWizard(); + +private: + void buildMenuActions(); + void createSyncErrorsAction(); + void createActivityAction(); + + void onSettings(); + void onQuit(); + + MainWindow *_window = nullptr; +}; +} diff --git a/src/gui/mainwindow/modalwrapperwidget.cpp b/src/gui/mainwindow/modalwrapperwidget.cpp new file mode 100644 index 00000000000..46a20362dd0 --- /dev/null +++ b/src/gui/mainwindow/modalwrapperwidget.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "modalwrapperwidget.h" + +#include +#include +#include +#include +#include + +namespace OCC { + +ModalWrapperWidget::ModalWrapperWidget(QWidget *content, QWidget *parent) + : QWidget{parent} +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + content->setParent(this); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(content); + if (QDialog *asDialog = qobject_cast(content)) { + connect(asDialog, &QDialog::finished, this, &ModalWrapperWidget::finished); + } else { + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok, Qt::Horizontal, this); + connect(buttons, &QDialogButtonBox::accepted, this, &ModalWrapperWidget::finished); + // yes I know there is a default "close" button, but it emits rejected which can have consequences + // I'm choosing close for the naming here because it doesn't imply any sort of "save" action, which will be important + // wrt the settings view, which updates values "live" as they are changed, not committed on "ok" or "save" + QPushButton *okButton = buttons->button(QDialogButtonBox::Ok); + okButton->setObjectName("closeButton"); + okButton->setText(tr("Close")); + layout->addWidget(buttons); + } + + setLayout(layout); +} + +} diff --git a/src/gui/mainwindow/modalwrapperwidget.h b/src/gui/mainwindow/modalwrapperwidget.h new file mode 100644 index 00000000000..932cf97301a --- /dev/null +++ b/src/gui/mainwindow/modalwrapperwidget.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +namespace OCC { + +/** + * @brief The ModalWrapperWidget class is used to show "child" widgets in the main window, in a quasi modal fashion, ie + * the toolbar is disabled and you can't do anything in the main window until you finish interacting with the content. + * + * This impl eliminates the need for caller to manage the lifetime of the widget they want to run as the content widget is reparented + * to the ModalWrapperWidget and the main window closes and deletes the wrapper when it receives the finished signal. It also eliminates + * the need for the content widget to create and manage its own buttons. In short, this class ordinarily expects a simple "panel" widget, + * and it adds a single "close" button below the content widget to dismiss the modal view. + * + * However, to support the updater, it is now also possible to pass a QDialog to the ctr in which case the dialog is used as-is + * with its own buttons in place. In this case the dialog's finished signal is forwarded to ModalWrapperWidget::finished. + * + * You *must* use a wrapper widget if you want to use MainWindow::showModalWidget + */ +class ModalWrapperWidget : public QWidget +{ + Q_OBJECT +public: + explicit ModalWrapperWidget(QWidget *content, QWidget *parent); + +signals: + void finished(); +}; + +} diff --git a/src/gui/mainwindow/settingsview.cpp b/src/gui/mainwindow/settingsview.cpp new file mode 100644 index 00000000000..8f08f27f5f7 --- /dev/null +++ b/src/gui/mainwindow/settingsview.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) by Daniel Molkentin + * + * 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 "settingsview.h" +#include "ui_settingsview.h" + +#include "application.h" +#include "ignorelisteditor.h" +#include "libsync/configfile.h" +#include "libsync/theme.h" +#include "logbrowser.h" +#include "translations.h" + +#include +#include +#include + +namespace OCC { + +SettingsView::SettingsView(QWidget *parent) + : QWidget(parent) + , _ui(new Ui::SettingsView) + , _currentlyLoading(false) +{ + _ui->setupUi(this); + + connect(_ui->desktopNotificationsCheckBox, &QAbstractButton::toggled, this, &SettingsView::slotToggleOptionalDesktopNotifications); + + reloadConfig(); + loadMiscSettings(); + + // misc + connect(_ui->monoIconsCheckBox, &QAbstractButton::toggled, this, &SettingsView::saveMiscSettings); + connect(_ui->crashreporterCheckBox, &QAbstractButton::toggled, this, &SettingsView::saveMiscSettings); + + connect(_ui->languageDropdown, QOverload::of(&QComboBox::activated), this, [this]() { + // first, store selected language in config file + saveMiscSettings(); + + // warn user that a language change requires a restart to take effect + QMessageBox::warning(this, tr("Warning"), tr("Language changes require a restart of this application to take effect."), QMessageBox::Ok); + }); + + /* 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. + */ + connect(_ui->syncHiddenFilesCheckBox, &QCheckBox::toggled, this, [](bool checked) { FolderMan::instance()->setIgnoreHiddenFiles(!checked); }); + + _ui->crashreporterCheckBox->setVisible(Theme::instance()->withCrashReporter()); + + _ui->moveToTrashCheckBox->setVisible(true); + connect(_ui->moveToTrashCheckBox, &QCheckBox::toggled, this, &SettingsView::moveToTrashChanged); /*[this](bool checked) { + ConfigFile().setMoveToTrash(checked); + Q_EMIT moveToTrashChanged(checked); + });*/ + + // OEM themes are not obliged to ship mono icons, so there + // is no point in offering an option + _ui->monoIconsCheckBox->setVisible(Resources::hasMonoTheme()); + + connect(_ui->ignoredFilesButton, &QAbstractButton::clicked, this, &SettingsView::slotIgnoreFilesEditor); + connect(_ui->logSettingsButton, &QPushButton::clicked, this, &SettingsView::slotShowLogSettings); + + // todo: after DC-300 is merged, completely get rid of the copyright label in the settingsview.ui + _ui->copyrightLabel->hide(); +} + +SettingsView::~SettingsView() +{ + delete _ui; +} + +void SettingsView::loadMiscSettings() +{ + QScopedValueRollback scope(_currentlyLoading, true); + ConfigFile cfgFile; + _ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons()); + _ui->desktopNotificationsCheckBox->setChecked(cfgFile.optionalDesktopNotifications()); + _ui->crashreporterCheckBox->setChecked(cfgFile.crashReporter()); + _ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons()); + + // the dropdown has to be populated before we can can pick an entry below based on the stored setting + loadLanguageNamesIntoDropdown(); + + const auto &locale = cfgFile.uiLanguage(); + const auto index = _ui->languageDropdown->findData(locale); + _ui->languageDropdown->setCurrentIndex(index < 0 ? 0 : index); +} + +void SettingsView::showEvent(QShowEvent *) +{ + reloadConfig(); +} + +void SettingsView::saveMiscSettings() +{ + if (_currentlyLoading) + return; + ConfigFile cfgFile; + bool isChecked = _ui->monoIconsCheckBox->isChecked(); + cfgFile.setMonoIcons(isChecked); + Theme::instance()->setSystrayUseMonoIcons(isChecked); + cfgFile.setCrashReporter(_ui->crashreporterCheckBox->isChecked()); + + // the first entry, identified by index 0, means "use default", which is a special case handled below + const QString pickedLocale = _ui->languageDropdown->currentData().toString(); + cfgFile.setUiLanguage(pickedLocale); +} + +void SettingsView::slotToggleLaunchOnStartup(bool enable) +{ + Theme *theme = Theme::instance(); + Utility::setLaunchOnStartup(theme->appName(), theme->appNameGUI(), enable); +} + +void SettingsView::slotToggleOptionalDesktopNotifications(bool enable) +{ + ConfigFile cfgFile; + cfgFile.setOptionalDesktopNotifications(enable); +} + +void SettingsView::slotIgnoreFilesEditor() +{ + if (_ignoreEditor.isNull()) { + _ignoreEditor = new IgnoreListEditor(ocApp()->mainWindow()); + _ignoreEditor->setAttribute(Qt::WA_DeleteOnClose, true); + ocApp()->ensureVisible(); + _ignoreEditor->open(); + } +} + +void SettingsView::reloadConfig() +{ + _ui->syncHiddenFilesCheckBox->setChecked(!FolderMan::instance()->ignoreHiddenFiles()); + _ui->moveToTrashCheckBox->setChecked(FolderMan::instance()->moveToTrash()); + if (Utility::hasSystemLaunchOnStartup(Theme::instance()->appName())) { + _ui->autostartCheckBox->setChecked(true); + _ui->autostartCheckBox->setDisabled(true); + _ui->autostartCheckBox->setToolTip(tr("You cannot disable autostart because system-wide autostart is enabled.")); + } else { + const bool hasAutoStart = Utility::hasLaunchOnStartup(Theme::instance()->appName()); + // make sure the binary location is correctly set + slotToggleLaunchOnStartup(hasAutoStart); + _ui->autostartCheckBox->setChecked(hasAutoStart); + connect(_ui->autostartCheckBox, &QAbstractButton::toggled, this, &SettingsView::slotToggleLaunchOnStartup); + } +} + +void SettingsView::loadLanguageNamesIntoDropdown() +{ + // allow method to be called more than once + _ui->languageDropdown->clear(); + + // if no option has been chosen explicitly by the user, the first entry shall be used + _ui->languageDropdown->addItem(tr("(use default)")); + + // initialize map of locales to language names + const auto availableLocales = []() { + auto rv = Translations::listAvailableTranslations().values(); + rv.sort(Qt::CaseInsensitive); + return rv; + }(); + + for (const auto &availableLocale : availableLocales) { + auto nativeLanguageName = QLocale(availableLocale).nativeLanguageName(); + + // fallback if there's a locale whose name Qt doesn't know + // this indicates a broken filename + if (nativeLanguageName.isEmpty()) { + qCDebug(lcApplication()) << "Warning: could not find native language name for locale" << availableLocale; + nativeLanguageName = tr("unknown (%1)").arg(availableLocale); + } + + QString entryText = QStringLiteral("%1 (%2)").arg(nativeLanguageName, availableLocale); + _ui->languageDropdown->addItem(entryText, availableLocale); + } +} + +void SettingsView::slotShowLogSettings() +{ + auto logBrowser = new LogBrowser(ocApp()->mainWindow()); + logBrowser->setAttribute(Qt::WA_DeleteOnClose); + ocApp()->ensureVisible(); + logBrowser->open(); +} + +} // namespace OCC diff --git a/src/gui/mainwindow/settingsview.h b/src/gui/mainwindow/settingsview.h new file mode 100644 index 00000000000..10f94aa0c07 --- /dev/null +++ b/src/gui/mainwindow/settingsview.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) by Daniel Molkentin + * + * 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 +#include +#include + +namespace OCC { +class IgnoreListEditor; +class SyncLogDialog; + +namespace Ui { + class SettingsView; +} + +/** + * @brief The GeneralSettings class + * @ingroup gui + */ +class SettingsView : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsView(QWidget *parent = nullptr); + ~SettingsView() override; + +Q_SIGNALS: + void moveToTrashChanged(bool trashIt); + +private Q_SLOTS: + void saveMiscSettings(); + void loadMiscSettings(); + void slotToggleLaunchOnStartup(bool); + void slotToggleOptionalDesktopNotifications(bool); + void slotIgnoreFilesEditor(); + void slotShowLogSettings(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void reloadConfig(); + void loadLanguageNamesIntoDropdown(); + + Ui::SettingsView *_ui; + QPointer _ignoreEditor; + bool _currentlyLoading; +}; + + +} // namespace OCC diff --git a/src/gui/mainwindow/settingsview.ui b/src/gui/mainwindow/settingsview.ui new file mode 100644 index 00000000000..2c18a459ae1 --- /dev/null +++ b/src/gui/mainwindow/settingsview.ui @@ -0,0 +1,289 @@ + + + OCC::SettingsView + + + + 0 + 0 + 791 + 686 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + 0 + 0 + 789 + 654 + + + + + + + General Settings + + + + 13 + + + + + Use Monochrome Icons in the system tray + + + + + + + Show Desktop Notifications + + + + + + + Start on Login + + + + + + + + + + 0 + 0 + + + + + + + Language + + + languageDropdown + + + + + + + + 0 + 0 + + + + Language selector + + + + + + + + + + + + + + + Advanced + + + + + + + + Sync hidden files + + + + + + + + 0 + 0 + + + + Show crash reporter + + + + + + + Move remotely deleted files to the local trash bin instead of deleting them + + + + + + + + + Edit Ignored Files + + + + + + + Log Settings + + + + + + + Qt::Orientation::Horizontal + + + + 555 + 20 + + + + + + + + + + + + + + + Network + + + + + + Qt::FocusPolicy::StrongFocus + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + ArrowCursor + + + Copyright ownCloud GmbH (A Kiteworks Company) + + + Qt::TextInteractionFlag::LinksAccessibleByMouse|Qt::TextInteractionFlag::TextSelectableByMouse + + + + + + + + + + + NetworkSettings + QWidget +
    networksettings.h
    + 1 +
    +
    + + autostartCheckBox + desktopNotificationsCheckBox + monoIconsCheckBox + languageDropdown + syncHiddenFilesCheckBox + crashreporterCheckBox + moveToTrashCheckBox + ignoredFilesButton + logSettingsButton + widget + scrollArea + + + +
    diff --git a/src/gui/manifest-mingw.rc b/src/gui/manifest-mingw.rc deleted file mode 100644 index e3125c9d583..00000000000 --- a/src/gui/manifest-mingw.rc +++ /dev/null @@ -1,3 +0,0 @@ -#include "winuser.h" - -CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "owncloud.exe.manifest-mingw" diff --git a/src/gui/models/activitylistmodel.cpp b/src/gui/models/activitylistmodel.cpp deleted file mode 100644 index 0d269708ef2..00000000000 --- a/src/gui/models/activitylistmodel.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 -#include -#include -#include -#include -#include - -#include "account.h" -#include "accountstate.h" -#include "accountmanager.h" -#include "accessmanager.h" -#include "folderman.h" -#include "guiutility.h" -#include "models.h" - -#include "activitydata.h" -#include "activitylistmodel.h" - -namespace OCC { - -Q_LOGGING_CATEGORY(lcActivity, "gui.activity", QtInfoMsg) - -ActivityListModel::ActivityListModel(QObject *parent) - : QAbstractTableModel(parent) -{ -} - -QVariant ActivityListModel::data(const QModelIndex &index, int role) const -{ - Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)); - if (!index.isValid()) { - return {}; - } - - const auto &a = _finalList.at(index.row()); - const AccountStatePtr accountState = AccountManager::instance()->account(a.uuid()); - if (!accountState) { - return {}; - } - const auto column = static_cast(index.column()); - switch (role) { - case Models::UnderlyingDataRole: - Q_FALLTHROUGH(); - case Qt::DisplayRole: - switch (column) { - case ActivityRole::Account: - return a.accName(); - case ActivityRole::Text: - return a.subject(); - case ActivityRole::PointInTime: - if (role == Models::UnderlyingDataRole) { - return a.dateTime(); - } else { - return Utility::timeAgoInWords(a.dateTime()); - } - case ActivityRole::Path: { - QStringList list = FolderMan::instance()->findFileInLocalFolders(a.file(), accountState->account()); - if (!list.isEmpty()) { - return list.at(0); - } - // File does not exist anymore? Let's try to open its path - list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a.file()).path(), accountState->account()); - if (!list.isEmpty()) { - return list.at(0); - } - return {}; - } - case ActivityRole::ColumnCount: - Q_UNREACHABLE(); - break; - } - break; - case Qt::ToolTipRole: - return tr("%1 %2 on %3").arg(a.subject(), Utility::timeAgoInWords(a.dateTime()), a.accName()); - case Qt::DecorationRole: - switch (column) { - case ActivityRole::Text: - if (!accountState->account()->avatar().isNull()) { - return QIcon(accountState->account()->avatar()); - } else { - return Utility::getCoreIcon(QStringLiteral("account")); - } - default: - return {}; - } - default: - return {}; - } - Q_UNREACHABLE(); -} - -QVariant ActivityListModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal) { - const auto actionRole = static_cast(section); - switch (role) { - case Qt::DisplayRole: - switch (actionRole) { - case ActivityRole::Text: - return tr("Activity"); - case ActivityRole::Account: - return tr("Account"); - case ActivityRole::PointInTime: - return tr("Time"); - case ActivityRole::Path: - return tr("Local path"); - case ActivityRole::ColumnCount: - Q_UNREACHABLE(); - break; - } - break; - case Models::StringFormatWidthRole: - switch (actionRole) { - case ActivityRole::Text: - return 120; - case ActivityRole::Account: - return 20; - case ActivityRole::PointInTime: - return 20; - case ActivityRole::Path: - return 30; - case ActivityRole::ColumnCount: - Q_UNREACHABLE(); - break; - } - break; - } - } - return QAbstractTableModel::headerData(section, orientation, role); -} - -int ActivityListModel::rowCount(const QModelIndex &parent) const -{ - Q_ASSERT(checkIndex(parent)); - if (parent.isValid()) { - return 0; - } - return _finalList.count(); -} - -int ActivityListModel::columnCount(const QModelIndex &parent) const -{ - Q_ASSERT(checkIndex(parent)); - if (parent.isValid()) { - return 0; - } - return static_cast(ActivityRole::ColumnCount); -} - -// current strategy: Fetch 100 items per Account -// ATTENTION: This method is const and thus it is not possible to modify -// the _activityLists hash or so. Doesn't make it easier... -bool ActivityListModel::canFetchMore(const QModelIndex &) const -{ - if (_activityLists.isEmpty()) - return true; - - for (auto i = _activityLists.begin(); i != _activityLists.end(); ++i) { - AccountState *ast = i.key(); - if (ast && ast->isConnected()) { - ActivityList activities = i.value(); - if (activities.count() == 0 && !_currentlyFetching.contains(ast)) { - return true; - } - } - } - - return false; -} - -void ActivityListModel::startFetchJob(AccountState *ast) -{ - if (!ast || !ast->isConnected()) { - return; - } - JsonApiJob *job = new JsonApiJob(ast->account(), QStringLiteral("ocs/v2.php/cloud/activity"), this); - QObject::connect(job, &JsonApiJob::jsonReceived, - this, [ast, this](const QJsonDocument &json, int statusCode) { - _currentlyFetching.remove(ast); - const auto activities = json.object().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toArray(); - - ActivityList list; - list.reserve(activities.size()); - for (const auto &activ : activities) { - const auto json = activ.toObject(); - list.append(Activity { Activity::ActivityType, - json.value(QStringLiteral("id")).toVariant().value(), - ast->account(), - json.value(QStringLiteral("subject")).toString(), - json.value(QStringLiteral("message")).toString(), - json.value(QStringLiteral("file")).toString(), - QUrl(json.value(QStringLiteral("link")).toString()), - QDateTime::fromString(json.value(QStringLiteral("date")).toString(), Qt::ISODate) }); - } - - _activityLists[ast] = std::move(list); - - emit activityJobStatusCode(ast, statusCode); - - combineActivityLists(); - }); - QUrlQuery params; - params.addQueryItem(QStringLiteral("page"), QStringLiteral("0")); - params.addQueryItem(QStringLiteral("pagesize"), QStringLiteral("100")); - job->addQueryParams(params); - - _currentlyFetching.insert(ast); - qCInfo(lcActivity) << "Start fetching activities for " << ast->account()->displayName(); - job->start(); -} - - -void ActivityListModel::combineActivityLists() -{ - ActivityList resultList; - for (const ActivityList &list : qAsConst(_activityLists)) { - resultList.append(list); - } - setActivityList(std::move(resultList)); -} - -void ActivityListModel::setActivityList(const ActivityList &&resultList) -{ - beginResetModel(); - _finalList = resultList; - endResetModel(); -} - -void ActivityListModel::fetchMore(const QModelIndex &) -{ - for (const AccountStatePtr &asp : AccountManager::instance()->accounts()) { - if (!_activityLists.contains(asp.data()) && asp->isConnected()) { - _activityLists[asp.data()] = ActivityList(); - startFetchJob(asp.data()); - } - } -} - -void ActivityListModel::slotRefreshActivity(AccountState *ast) -{ - if (ast && _activityLists.contains(ast)) { - _activityLists.remove(ast); - } - startFetchJob(ast); -} - -void ActivityListModel::slotRemoveAccount(AccountState *ast) -{ - if (_activityLists.contains(ast)) { - const auto accountToRemove = ast->account()->uuid(); - - QMutableListIterator it(_finalList); - - int i = 0; - while (it.hasNext()) { - Activity activity = it.next(); - if (activity.uuid() == accountToRemove) { - beginRemoveRows(QModelIndex(), i, i); - it.remove(); - endRemoveRows(); - } else { - ++i; - } - } - _activityLists.remove(ast); - _currentlyFetching.remove(ast); - } -} -} diff --git a/src/gui/models/activitylistmodel.h b/src/gui/models/activitylistmodel.h deleted file mode 100644 index 8c5b2422fd9..00000000000 --- a/src/gui/models/activitylistmodel.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef ACTIVITYLISTMODEL_H -#define ACTIVITYLISTMODEL_H - -#include - -#include "activitydata.h" - -class QJsonDocument; - -namespace OCC { - -Q_DECLARE_LOGGING_CATEGORY(lcActivity) - -class AccountState; - -/** - * @brief The ActivityListModel - * @ingroup gui - * - * Simple list model to provide the list view with data. - */ - -class ActivityListModel : public QAbstractTableModel -{ - Q_OBJECT -public: - enum class ActivityRole { - Text, - Account, - PointInTime, - Path, - - ColumnCount - }; - - - explicit ActivityListModel(QObject *parent = nullptr); - - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent) const override; - - bool canFetchMore(const QModelIndex &) const override; - void fetchMore(const QModelIndex &) override; - - ActivityList activityList() { return _finalList; } - - -public slots: - void slotRefreshActivity(AccountState *ast); - void slotRemoveAccount(AccountState *ast); - -signals: - void activityJobStatusCode(AccountState *ast, int statusCode); - -private: - void setActivityList(const ActivityList &&resultList); - void startFetchJob(AccountState *s); - void combineActivityLists(); - - QMap _activityLists; - ActivityList _finalList; - QSet _currentlyFetching; - - friend class TestActivityModel; -}; -} -#endif // ACTIVITYLISTMODEL_H diff --git a/src/gui/models/expandingheaderview.cpp b/src/gui/models/expandingheaderview.cpp index 3457d3f63d8..8bc1be5eb85 100644 --- a/src/gui/models/expandingheaderview.cpp +++ b/src/gui/models/expandingheaderview.cpp @@ -17,9 +17,7 @@ #include "configfile.h" #include -#include #include -#include using namespace OCC; @@ -29,16 +27,19 @@ ExpandingHeaderView::ExpandingHeaderView(const QString &objectName, QWidget *par setSectionsClickable(true); setHighlightSections(true); - connect(this, &QHeaderView::sectionCountChanged, this, &ExpandingHeaderView::resizeColumns); + connect(this, &QHeaderView::sectionCountChanged, this, [this] { resizeColumns(false); }); setObjectName(objectName); ConfigFile cfg; - cfg.restoreGeometryHeader(this); + if (!cfg.restoreGeometryHeader(this)) { + _requiresReset = true; + } +} - connect(qApp, &QApplication::aboutToQuit, this, [this] { - ConfigFile cfg; - cfg.saveGeometryHeader(this); - }); +ExpandingHeaderView::~ExpandingHeaderView() +{ + ConfigFile cfg; + cfg.saveGeometryHeader(this); } int ExpandingHeaderView::expandingColumn() const @@ -57,16 +58,39 @@ void ExpandingHeaderView::resizeEvent(QResizeEvent *event) resizeColumns(); } +bool ExpandingHeaderView::resizeToContent() const +{ + return _resizeToContent; +} + +void ExpandingHeaderView::setResizeToContent(bool newResizeToContent) +{ + _resizeToContent = newResizeToContent; +} + void ExpandingHeaderView::resizeColumns(bool reset) { int availableWidth = width(); const auto defaultSize = defaultSectionSize(); + if (_requiresReset && _resizeToContent) { + // wee need some rows to adjust to the content + if (model()->rowCount() == 0) { + return; + } + reset = true; + } + if (reset) { + _requiresReset = false; + if (_resizeToContent) { + resizeSections(QHeaderView::ResizeToContents); + } + } for (int i = 0; i < count(); ++i) { if (i == _expandingColumn || isSectionHidden(i)) { continue; } if (reset) { - resizeSection(i, defaultSize); + resizeSection(i, _resizeToContent ? sectionSize(i) : defaultSize); } availableWidth -= sectionSize(i); } @@ -75,5 +99,7 @@ void ExpandingHeaderView::resizeColumns(bool reset) void ExpandingHeaderView::addResetActionToMenu(QMenu *menu) { - menu->addAction(tr("Reset column sizes"), this, [this] { resizeColumns(true); }); + menu->addAction(tr("Reset column sizes"), this, [this] { + resizeColumns(true); + }); } diff --git a/src/gui/models/expandingheaderview.h b/src/gui/models/expandingheaderview.h index 41029c6a8ba..dfef8a6b2cf 100644 --- a/src/gui/models/expandingheaderview.h +++ b/src/gui/models/expandingheaderview.h @@ -22,6 +22,7 @@ class ExpandingHeaderView : public QHeaderView Q_OBJECT public: ExpandingHeaderView(const QString &objectName, QWidget *parent = nullptr); + ~ExpandingHeaderView(); int expandingColumn() const; void setExpandingColumn(int newExpandingColumn); @@ -29,11 +30,16 @@ class ExpandingHeaderView : public QHeaderView void resizeColumns(bool reset = false); void addResetActionToMenu(QMenu *menu); + bool resizeToContent() const; + void setResizeToContent(bool newResizeToContent); + protected: void resizeEvent(QResizeEvent *event) override; private: + bool _requiresReset = false; + bool _resizeToContent = false; int _expandingColumn = 0; }; diff --git a/src/gui/models/models.cpp b/src/gui/models/models.cpp index dc0b41987af..be83bd296b0 100644 --- a/src/gui/models/models.cpp +++ b/src/gui/models/models.cpp @@ -13,15 +13,22 @@ */ #include "models.h" +#include #include #include -#include -#include #include +#include +#include #include #include +void OCC::Models::SignalledQSortFilterProxyModel::setFilterFixedStringSignalled(const QString &pattern) +{ + setFilterFixedString(pattern); + Q_EMIT filterChanged(); +} + QString OCC::Models::formatSelection(const QModelIndexList &items, int dataRole) { if (items.isEmpty()) { @@ -46,10 +53,10 @@ QString OCC::Models::formatSelection(const QModelIndexList &items, int dataRole) } }; if (qApp->layoutDirection() == Qt::RightToLeft) { - stream << right; + stream << Qt::right; begin = QLatin1Char(','); } else { - stream << left; + stream << Qt::left; end = QLatin1Char(','); } @@ -62,7 +69,7 @@ QString OCC::Models::formatSelection(const QModelIndexList &items, int dataRole) << qSetFieldWidth(0) << end; }); - stream << endl; + stream << Qt::endl; for (const auto &index : items) { iterate([&](int c) { const auto &child = index.siblingAtColumn(c); @@ -72,42 +79,37 @@ QString OCC::Models::formatSelection(const QModelIndexList &items, int dataRole) << qSetFieldWidth(0) << end; }); - stream << endl; + stream << Qt::endl; } return out; } -std::function OCC::Models::addFilterMenuItems(QMenu *menu, const QStringList &candidates, QSortFilterProxyModel *model, int column, const QString &columnName, int role) +std::function OCC::Models::addFilterMenuItems(QMenu *menu, const QStringList &candidates, SignalledQSortFilterProxyModel *model, int column, const QString &columnName, int role) { - menu->addAction(qApp->translate("OCC::Models", "%1 Filter:").arg(columnName))->setEnabled(false); + menu->addAction(QApplication::translate("OCC::Models", "%1 Filter:").arg(columnName))->setEnabled(false); auto filterGroup = new QActionGroup(menu); filterGroup->setExclusive(true); - const auto currentFilter = model->filterRegExp().pattern(); + const auto currentFilter = model->filterRegularExpression().pattern(); auto addAction = [=](const QString &s, const QString &filter) { auto action = menu->addAction(s, menu, [=]() { model->setFilterRole(role); model->setFilterKeyColumn(column); - model->setFilterFixedString(filter); + model->setFilterFixedStringSignalled(filter); }); action->setCheckable(true); - if (currentFilter == filter) { - action->setChecked(true); - } + action->setChecked(currentFilter == QRegularExpression::escape(filter)); filterGroup->addAction(action); return action; }; - auto noFilter = addAction(QApplication::translate("OCC::Models", "No filter"), QString()); + auto noFilter = addAction(QApplication::translate("OCC::Models", "All"), QString()); for (const auto &c : candidates) { addAction(c, c); } - QTimer::singleShot(0, menu, [menu] { - menu->popup(QCursor::pos()); - }); auto resetFunction = [noFilter]() { noFilter->setChecked(true); @@ -115,3 +117,10 @@ std::function OCC::Models::addFilterMenuItems(QMenu *menu, const QString }; return resetFunction; } + +bool OCC::Models::FilteringProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + // column doesn't matter, since we do not filter based on a specific column but on a specific item role + const auto index = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent); + return sourceModel()->data(index, static_cast(filterRole())).toBool(); +} diff --git a/src/gui/models/models.h b/src/gui/models/models.h index 9b302171ff4..31e04b789ea 100644 --- a/src/gui/models/models.h +++ b/src/gui/models/models.h @@ -14,6 +14,7 @@ #pragma once #include +#include #include #include @@ -23,17 +24,37 @@ class QMenu; namespace OCC { namespace Models { + Q_NAMESPACE + enum DataRoles { UnderlyingDataRole = Qt::UserRole + 100, - StringFormatWidthRole // The width for a cvs formated column + StringFormatWidthRole, // The width for a cvs formatted column + + // data() should return boolean values for this role to work in conjunction with FilteringProxyModel + FilterRole, }; + Q_ENUM_NS(DataRoles) + + class SignalledQSortFilterProxyModel : public QSortFilterProxyModel + { + Q_OBJECT + + public: + using QSortFilterProxyModel::QSortFilterProxyModel; + + void setFilterFixedStringSignalled(const QString &pattern); + + Q_SIGNALS: + void filterChanged(); + }; + /** * Returns a cvs representation of a table */ QString formatSelection(const QModelIndexList &items, int dataRole = Qt::DisplayRole); - std::function addFilterMenuItems(QMenu *menu, const QStringList &candidates, QSortFilterProxyModel *model, int column, const QString &columnName, int role); + std::function addFilterMenuItems(QMenu *menu, const QStringList &candidates, SignalledQSortFilterProxyModel *model, int column, const QString &columnName, int role); /** * Returns a vector with indices @@ -55,5 +76,13 @@ namespace Models { { return range(0, end); } -}; -} + + class FilteringProxyModel : public QSortFilterProxyModel + { + using QSortFilterProxyModel::QSortFilterProxyModel; + + protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + }; +} // OCC::Models namespace +} // OCC namespace diff --git a/src/gui/models/protocolitemmodel.cpp b/src/gui/models/protocolitemmodel.cpp index 6b4ec9c8254..a4f28fb9de7 100644 --- a/src/gui/models/protocolitemmodel.cpp +++ b/src/gui/models/protocolitemmodel.cpp @@ -16,11 +16,7 @@ #include "account.h" #include "accountstate.h" #include "models.h" -#include "gui/folderman.h" - -#include "theme.h" - -#include +#include "resources.h" using namespace OCC; @@ -69,7 +65,7 @@ QVariant ProtocolItemModel::data(const QModelIndex &index, int role) const case ProtocolItemRole::File: return Utility::fileNameForGuiUse(item.path()); case ProtocolItemRole::Account: - return item.folder()->accountState()->account()->displayName(); + return item.folder()->accountState()->account()->displayNameWithHost(); case ProtocolItemRole::Status: return Utility::enumToDisplayName(item.status()); case ProtocolItemRole::ColumnCount: @@ -84,11 +80,12 @@ QVariant ProtocolItemModel::data(const QModelIndex &index, int role) const || status == SyncFileItem::FatalError || status == SyncFileItem::DetailError || status == SyncFileItem::BlacklistedError) { - return Theme::instance()->syncStateIcon(SyncResult::Error); + return Resources::getCoreIcon(QStringLiteral("states/error")); + ; } else if (Progress::isWarningKind(status) || status == SyncFileItem::Excluded) { - return Theme::instance()->syncStateIcon(SyncResult::Problem); + return Resources::getCoreIcon(QStringLiteral("states/information")); } else { - return Theme::instance()->syncStateIcon(SyncResult::Success); + return Resources::getCoreIcon(QStringLiteral("states/ok")); } } break; @@ -105,7 +102,7 @@ QVariant ProtocolItemModel::data(const QModelIndex &index, int role) const case ProtocolItemRole::File: return item.path(); case ProtocolItemRole::Account: - return item.folder()->accountState()->account()->displayName(); + return item.folder()->accountState()->account()->displayNameWithHost(); case ProtocolItemRole::Status: return item.status(); case ProtocolItemRole::ColumnCount: diff --git a/src/gui/models/protocolitemmodel.h b/src/gui/models/protocolitemmodel.h index 5d25d335cf8..52668dd3eda 100644 --- a/src/gui/models/protocolitemmodel.h +++ b/src/gui/models/protocolitemmodel.h @@ -13,16 +13,17 @@ */ #pragma once -#include +#include "gui/owncloudguilib.h" +#include "common/fixedsizeringbuffer.h" #include "protocolitem.h" -#include "common/fixedsizeringbuffer.h" +#include namespace OCC { -class ProtocolItemModel : public QAbstractTableModel +class OWNCLOUDGUI_EXPORT ProtocolItemModel : public QAbstractTableModel { Q_OBJECT public: @@ -37,6 +38,8 @@ class ProtocolItemModel : public QAbstractTableModel ColumnCount }; + Q_ENUM(ProtocolItemRole) + /** * @brief ProtocolItemModel * @param parent diff --git a/src/gui/navigationpanehelper.cpp b/src/gui/navigationpanehelper.cpp deleted file mode 100644 index 57fff692dce..00000000000 --- a/src/gui/navigationpanehelper.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) by Jocelyn Turcotte - * - * 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 "navigationpanehelper.h" -#include "accountmanager.h" -#include "configfile.h" -#include "folderman.h" - -#include -#include - -namespace OCC { - -Q_LOGGING_CATEGORY(lcNavPane, "gui.folder.navigationpane", QtInfoMsg) - -NavigationPaneHelper::NavigationPaneHelper(FolderMan *folderMan) - : _folderMan(folderMan) -{ - ConfigFile cfg; - _showInExplorerNavigationPane = cfg.showInExplorerNavigationPane(); - - _updateCloudStorageRegistryTimer.setSingleShot(true); - connect(&_updateCloudStorageRegistryTimer, &QTimer::timeout, this, &NavigationPaneHelper::updateCloudStorageRegistry); -} - -void NavigationPaneHelper::setShowInExplorerNavigationPane(bool show) -{ - if (_showInExplorerNavigationPane == show) - return; - - _showInExplorerNavigationPane = show; - // Re-generate a new CLSID when enabling, possibly throwing away the old one. - // updateCloudStorageRegistry will take care of removing any unknown CLSID our application owns from the registry. - for (auto *folder : _folderMan->map()) - folder->setNavigationPaneClsid(show ? QUuid::createUuid() : QUuid()); - - scheduleUpdateCloudStorageRegistry(); -} - -void NavigationPaneHelper::scheduleUpdateCloudStorageRegistry() -{ - // Schedule the update to happen a bit later to avoid doing the update multiple times in a row. - if (!_updateCloudStorageRegistryTimer.isActive()) - _updateCloudStorageRegistryTimer.start(500); -} - -void NavigationPaneHelper::updateCloudStorageRegistry() -{ - // Start by looking at every registered namespace extension for the sidebar, and look for an "ApplicationName" value - // that matches ours when we saved. - QVector entriesToRemove; - Utility::registryWalkSubKeys( - HKEY_CURRENT_USER, - QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace"), - [&entriesToRemove](HKEY key, const QString &subKey) { - QVariant appName = Utility::registryGetKeyValue(key, subKey, QStringLiteral("ApplicationName")); - if (appName.toString() == QLatin1String(APPLICATION_NAME)) { - QUuid clsid{ subKey }; - Q_ASSERT(!clsid.isNull()); - entriesToRemove.append(clsid); - } - }); - - // Then re-save every folder that has a valid navigationPaneClsid to the registry. - // We currently don't distinguish between new and existing CLSIDs, if it's there we just - // save over it. We at least need to update the tile in case we are suddently using multiple accounts. - for (auto *folder : _folderMan->map()) { - if (folder->vfs().mode() == Vfs::WindowsCfApi) - { - continue; - } - if (!folder->navigationPaneClsid().isNull()) { - // If it already exists, unmark it for removal, this is a valid sync root. - entriesToRemove.removeOne(folder->navigationPaneClsid()); - - QString clsidStr = folder->navigationPaneClsid().toString(); - QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr; - QString clsidPathWow64 = QString() % "Software\\Classes\\Wow6432Node\\CLSID\\" % clsidStr; - QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr; - - QString title = folder->shortGuiRemotePathOrAppName(); - // Write the account name in the sidebar only when using more than one account. - if (AccountManager::instance()->accounts().size() > 1) - title = title % " - " % folder->accountState()->account()->displayName(); - QString iconPath = QDir::toNativeSeparators(qApp->applicationFilePath()); - QString targetFolderPath = QDir::toNativeSeparators(folder->cleanPath()); - - qCInfo(lcNavPane) << "Explorer Cloud storage provider: saving path" << targetFolderPath << "to CLSID" << clsidStr; - // Steps taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/dn889934%28v=vs.85%29.aspx - // Step 1: Add your CLSID and name your extension - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QString(), REG_SZ, title); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QString(), REG_SZ, title); - // Step 2: Set the image for your icon - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath); - // Step 3: Add your extension to the Navigation Pane and make it visible - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1); - // Step 4: Set the location for your extension in the Navigation Pane - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41); - // Step 5: Provide the dll that hosts your extension. - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll")); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll")); - // Step 6: Define the instance object - // Indicate that your namespace extension should function like other file folder structures in File Explorer. - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}")); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}")); - // Step 7: Provide the file system attributes of the target folder - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11); - // Step 8: Set the path for the sync root - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath); - // Step 9: Set appropriate shell flags - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28); - // Step 10: Set the appropriate flags to control your shell behavior - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D); - Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D); - // Step 11: Register your extension in the namespace root - Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QString(), REG_SZ, title); - // Step 12: Hide your extension from the Desktop - Utility::registrySetKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr, REG_DWORD, 0x1); - - // For us, to later be able to iterate and find our own namespace entries and associated CLSID. - // Use the macro instead of the theme to make sure it matches with the uninstaller. - Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QStringLiteral("ApplicationName"), REG_SZ, QLatin1String(APPLICATION_NAME)); - } - } - - // Then remove anything that isn't in our folder list anymore. - for (const auto &clsid : qAsConst(entriesToRemove)) { - QString clsidStr = clsid.toString(); - QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr; - QString clsidPathWow64 = QString() % "Software\\Classes\\Wow6432Node\\CLSID\\" % clsidStr; - QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr; - - qCInfo(lcNavPane) << "Explorer Cloud storage provider: now unused, removing own CLSID" << clsidStr; - Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, clsidPath); - Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, clsidPathWow64); - Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, namespacePath); - Utility::registryDeleteKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr); - } -} - -} // namespace OCC diff --git a/src/gui/navigationpanehelper.h b/src/gui/navigationpanehelper.h deleted file mode 100644 index bb2510ddf63..00000000000 --- a/src/gui/navigationpanehelper.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) by Jocelyn Turcotte - * - * 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. - */ - - -#ifndef NAVIGATIONPANEHELPER_H -#define NAVIGATIONPANEHELPER_H - -#include -#include - -namespace OCC { - -class FolderMan; - -class NavigationPaneHelper : public QObject -{ - Q_OBJECT -public: - NavigationPaneHelper(FolderMan *folderMan); - - bool showInExplorerNavigationPane() const { return _showInExplorerNavigationPane; } - void setShowInExplorerNavigationPane(bool show); - - void scheduleUpdateCloudStorageRegistry(); - -private: - void updateCloudStorageRegistry(); - - FolderMan *_folderMan; - bool _showInExplorerNavigationPane; - QTimer _updateCloudStorageRegistryTimer; -}; - -} // namespace OCC -#endif // NAVIGATIONPANEHELPER_H diff --git a/src/gui/networkadapters/CMakeLists.txt b/src/gui/networkadapters/CMakeLists.txt new file mode 100644 index 00000000000..58b888f9fcd --- /dev/null +++ b/src/gui/networkadapters/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources(owncloudGui PRIVATE + resolveurladapter.cpp + resolveurladapter.h + webfingerlookupadapter.cpp + webfingerlookupadapter.h + discoverwebfingerserviceadapter.cpp + discoverwebfingerserviceadapter.h + determineauthtypeadapter.cpp + determineauthtypeadapter.h + fetchcapabilitiesadapter.cpp + fetchcapabilitiesadapter.h + userinfoadapter.cpp + userinfoadapter.h +) + diff --git a/src/gui/networkadapters/determineauthtypeadapter.cpp b/src/gui/networkadapters/determineauthtypeadapter.cpp new file mode 100644 index 00000000000..db848bad2cb --- /dev/null +++ b/src/gui/networkadapters/determineauthtypeadapter.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "determineauthtypeadapter.h" + +#include "abstractcorejob.h" +#include "common/utility.h" +#include "creds/credentialssupport.h" +#include "theme.h" + +#include +#include +#include +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcDetermineAuthTypeAdapter, "gui.networkadapters.determineauthtypeadapter"); + +DetermineAuthTypeAdapter::DetermineAuthTypeAdapter(QNetworkAccessManager *nam, const QUrl &url, QObject *parent) + : QObject(parent) + , _nam(nam) + , _url(url) +{ +} + +DetermineAuthTypeResult DetermineAuthTypeAdapter::getResult() +{ + DetermineAuthTypeResult res; + if (!_nam) + return res; + + // we explicitly use a legacy dav path here + QNetworkRequest req = AbstractCoreJobFactory::makeRequest(Utility::concatUrlPath(_url, QStringLiteral("remote.php/webdav/"))); + + req.setAttribute(DontAddCredentialsAttribute, true); + req.setAttribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Manual); + + QEventLoop waitLoop; + QNetworkReply *reply = _nam->sendCustomRequest(req, "PROPFIND"); + connect(reply, &QNetworkReply::finished, &waitLoop, &QEventLoop::quit); + waitLoop.exec(); + + if (reply->error() == QNetworkReply::AuthenticationRequiredError) { + // the header key is case insensitive per debug testing + const QByteArray rawHeader = reply->rawHeader(QByteArrayLiteral("www-authenticate")).toLower(); + + if (rawHeader.contains(QByteArrayLiteral("bearer "))) { + res.type = AuthenticationType::OAuth; + } else { + res.error = tr("Server does not support OAuth"); + if (rawHeader.isEmpty()) { + qCWarning(lcDetermineAuthTypeAdapter) << "Did not receive WWW-Authenticate reply to auth-test PROPFIND"; + } else { + qCWarning(lcDetermineAuthTypeAdapter) << "Did not receive bearer in WWW-Authenticate reply to auth-test PROPFIND"; + } + } + } else if (reply->error() == QNetworkReply::NoError) { + res.error = tr("Server did not ask for authorization"); + } else { + res.error = tr("Failed to determine authentication type: %1").arg(reply->errorString()); + } + + delete reply; + return res; +} +} diff --git a/src/gui/networkadapters/determineauthtypeadapter.h b/src/gui/networkadapters/determineauthtypeadapter.h new file mode 100644 index 00000000000..ab13c563e15 --- /dev/null +++ b/src/gui/networkadapters/determineauthtypeadapter.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "creds/credentialssupport.h" +#include +#include + +class QNetworkAccessManager; + +namespace OCC { + +struct DetermineAuthTypeResult +{ + QString error; + AuthenticationType type = AuthenticationType::Unknown; + bool success() const { return error.isEmpty(); } +}; + +/** + * @brief The DetermineAuthTypeAdapter class allows us to validate that a given server URL supports oauth authentication. + * If oauth is not supported, the authentication type will be reported as unknown. It is up to the caller to report an + * error or handle this condition in some responsible manner. + * + * This routine used to identify basic http server authentication as well but the Basic type is obsolete after client 6.0 + * + */ +class DetermineAuthTypeAdapter : public QObject +{ + Q_OBJECT + +public: + /** + * @brief DetermineAuthTypeAdapter + * @param nam the network access manager which will run the request. + * @param url the url for the request. + * @param parent follows the typical QObject parenting scheme, but this param is rarely needed since an instance should normally be + * created on the stack and will naturally destruct when it goes out of scope. + */ + DetermineAuthTypeAdapter(QNetworkAccessManager *nam, const QUrl &url, QObject *parent = nullptr); + + /** + * @brief getResult runs the request to determine authentication type synchronously + * @return the relevant data extracted from the reply + */ + DetermineAuthTypeResult getResult(); + +private: + QNetworkAccessManager *_nam; + QUrl _url; +}; +} diff --git a/src/gui/networkadapters/discoverwebfingerserviceadapter.cpp b/src/gui/networkadapters/discoverwebfingerserviceadapter.cpp new file mode 100644 index 00000000000..164355d177e --- /dev/null +++ b/src/gui/networkadapters/discoverwebfingerserviceadapter.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "discoverwebfingerserviceadapter.h" + +#include "abstractcorejob.h" +#include "common/utility.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcDiscoverWebFingerServiceAdapter, "gui.networkadapters.discoverwebfingerserviceadapter"); + +DiscoverWebFingerServiceAdapter::DiscoverWebFingerServiceAdapter(QNetworkAccessManager *nam, const QUrl &url, QObject *parent) + : QObject(parent) + , _nam(nam) + , _url(url) +{ +} + +DiscoverWebFingerServiceResult DiscoverWebFingerServiceAdapter::getResult() +{ + DiscoverWebFingerServiceResult result; + + if (!_nam) { + return result; + } + + // this first request needs to be done without any authentication, since our goal is to find a server to authenticate to before the actual (authenticated) + // WebFinger request + QNetworkRequest req = AbstractCoreJobFactory::makeRequest( + Utility::concatUrlPath(_url, QStringLiteral("/.well-known/webfinger"), {{QStringLiteral("resource"), _url.toString()}})); + + QNetworkReply *reply = _nam->get(req); + + QEventLoop waitLoop; + QObject::connect(reply, &QNetworkReply::finished, &waitLoop, &QEventLoop::quit); + waitLoop.exec(); + + result = processReply(reply); + + delete reply; + return result; +} + +/* for future async version of the adapter we can do something like: + +DiscoverWebfingerServiceAdapter::runAsync() { +QNetworkRequest req = .... + +QNetworkReply *reply = _nam->get(req); +connect(reply, reply::finished, this, [this, reply] () { +emit done(processReply(reply)); +reply->deleteLater +} + +In theory we could call getResult using QtConcurrent but I think this would add more overhead than it's worth for just a single operation. +*/ + +DiscoverWebFingerServiceResult DiscoverWebFingerServiceAdapter::processReply(QNetworkReply *reply) +{ + if (reply->error() != QNetworkReply::NoError) { + return formatError(reply->errorString()); + } + + const QString contentTypeHeader = reply->header(QNetworkRequest::ContentTypeHeader).toString(); + if (!contentTypeHeader.contains(QStringLiteral("application/json"), Qt::CaseInsensitive)) { + return formatError(QStringLiteral("server sent invalid content type: %1").arg(contentTypeHeader)); + } + + // next up, parse JSON + QJsonParseError error; + const auto doc = QJsonDocument::fromJson(reply->readAll(), &error); + // empty or invalid response + if (error.error != QJsonParseError::NoError || doc.isNull()) { + return formatError(QStringLiteral("could not parse JSON response from server.")); + } + + // make sure the reported subject matches the requested resource + QJsonValue value = doc.object().value(QStringLiteral("subject")); + if (value.isString()) { + const QString subject = value.toString(); + if (subject != _url.toString()) { + return formatError(QStringLiteral("reply sent for different subject (server): %1").arg(subject)); + } + } + + // check for an OIDC issuer in the list of links provided (we use the first that matches our conditions) + const auto links = doc.object().value(QStringLiteral("links")).toArray(); + for (const auto &link : links) { + const auto linkObject = link.toObject(); + + if (linkObject.value(QStringLiteral("rel")).toString() == QStringLiteral("http://openid.net/specs/connect/1.0/issuer")) { + // we have good faith in the server to provide a meaningful value and do not have to validate this any further + DiscoverWebFingerServiceResult result; + result.href = linkObject.value(QStringLiteral("href")).toString(); + return result; + } + } + + return formatError(QStringLiteral("could not find suitable relation in WebFinger response")); +} + +DiscoverWebFingerServiceResult DiscoverWebFingerServiceAdapter::formatError(const QString &errorDetail) +{ + DiscoverWebFingerServiceResult res; + res.error = _defaultError; + qCWarning(lcDiscoverWebFingerServiceAdapter) << errorDetail; + return res; +} +} diff --git a/src/gui/networkadapters/discoverwebfingerserviceadapter.h b/src/gui/networkadapters/discoverwebfingerserviceadapter.h new file mode 100644 index 00000000000..9163bfaa7e3 --- /dev/null +++ b/src/gui/networkadapters/discoverwebfingerserviceadapter.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) Lisa Reese (lisa.reese@kiteworks.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 +#include + +class QNetworkAccessManager; +class QNetworkReply; + +namespace OCC { + +/* + * simple struct that collects the result data into a simple bundle. + * + * error contains an error message if something went wrong + * href is the first link found in the response which matches "http://openid.net/specs/connect/1.0/issuer" + */ +struct DiscoverWebFingerServiceResult +{ + QString error; + QString href; + + bool success() const { return error.isEmpty() && !href.isEmpty(); } +}; + +/** + * This adapter allows the caller to find the href of a webfinger service associated with a given URL. No authentication is + * required for this operation, we are merely checking to see if webfinger is available, and if so, we collect the href + * for future use. + * + * The request is run synchronously, which is very useful when called from a gui because we can't continue until the required values are known. + * + * In future we may also extend the adapter to run in async mode as well, but for now it's not needed. + * + * A key benefit to using this impl is that the adapter can simply be allocated on the stack so there are no + * memory management concerns. It also simplifies and improves readability in the caller. + * + */ +class DiscoverWebFingerServiceAdapter : public QObject +{ + Q_OBJECT + +public: + /** + * @brief DiscoverWebFingerServiceAdapter + * @param nam - an instance of a network access manager to host the request + * @param url - the url for the request + * @param parent - typical QObject parenting scheme. A parent is rarely needed for this class because the typical use is to + * allocate a local instance on the stack, call getResult, then let the instance naturally go out of scope. + */ + DiscoverWebFingerServiceAdapter(QNetworkAccessManager *nam, const QUrl &url, QObject *parent = nullptr); + + /** + * @brief getResult runs the QNetworkRequest and handles the reply internally. + * @return the result instance with either an error or the successfully retrieved href to the webfinger service + * + * It is safe to call getResult repeatedly on the same adapter instance. + * + * Important: this function blocks the event loop to provide a simple, synchronous method of getting the href. For use in a gui that needs + * to collect pieces of data one at a time it is of no concern, but this outside of a gui the blocking behavior could be problematic. + */ + DiscoverWebFingerServiceResult getResult(); + +private: + DiscoverWebFingerServiceResult processReply(QNetworkReply *reply); + DiscoverWebFingerServiceResult formatError(const QString &errorDetail); + + QNetworkAccessManager *_nam; + QUrl _url; + + const QString _defaultError = tr("Invalid reply received from server."); +}; +} diff --git a/src/gui/networkadapters/fetchcapabilitiesadapter.cpp b/src/gui/networkadapters/fetchcapabilitiesadapter.cpp new file mode 100644 index 00000000000..790942113d2 --- /dev/null +++ b/src/gui/networkadapters/fetchcapabilitiesadapter.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) Lisa Reese (lisa.reese@kiteworks.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 "fetchcapabilitiesadapter.h" + +#include +#include +#include +#include +#include +#include + +#include "abstractcorejob.h" +#include "creds/credentialssupport.h" + +namespace OCC { + +Q_LOGGING_CATEGORY(lcFetchCapabilitesAdapter, "gui.networkadapters.fetchcapabiltiesadapter"); + + +FetchCapabilitiesAdapter::FetchCapabilitiesAdapter(QNetworkAccessManager *nam, const QString &authToken, const QUrl &url, QObject *parent) + : QObject{parent} + , _nam(nam) + , _url(url) + , _authorizationHeader(QStringLiteral("Bearer %1").arg(authToken)) +{ +} + +FetchCapabilitiesResult FetchCapabilitiesAdapter::getResult() +{ + FetchCapabilitiesResult result; + if (!_nam) { + return result; + } + + QUrlQuery urlQuery({{QStringLiteral("format"), QStringLiteral("json")}}); + + QNetworkRequest req = AbstractCoreJobFactory::makeRequest(Utility::concatUrlPath(_url, QStringLiteral("ocs/v2.php/cloud/capabilities"), urlQuery)); + + // next 4 stolen from webfingerlookupadapter + // we are not connected yet, so we need to handle the authentication manually + req.setRawHeader("Authorization", _authorizationHeader.toUtf8()); + req.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); + + // we just added the Authorization header, don't let the credentials AccessManager tamper with it + req.setAttribute(DontAddCredentialsAttribute, true); + req.setAttribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Manual); + + QNetworkReply *reply = _nam->get(req); + QEventLoop waitLoop; + QObject::connect(reply, &QNetworkReply::finished, &waitLoop, &QEventLoop::quit); + waitLoop.exec(); + + const QString contentTypeHeader = reply->header(QNetworkRequest::ContentTypeHeader).toString(); + if (!contentTypeHeader.contains(QStringLiteral("application/json"), Qt::CaseInsensitive)) { + result.error = tr("server sent invalid content type: %1.").arg(contentTypeHeader); + } + + QJsonParseError error; + const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &error); + if (error.error != QJsonParseError::NoError || doc.isNull()) { + result.error = tr("could not parse Capabilities from server."); + } + + // make sure the reported subject matches the requested resource + QJsonValue subjectValue = doc.object().value(QStringLiteral("subject")); + if (subjectValue.isString()) { + const QString subject = subjectValue.toString(); + if (subject != _url.toString()) { + result.error = tr("reply sent for different subject (server): %1").arg(subject); + } + } + QJsonObject capabilitiesValue = + doc.object().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toObject().value(QStringLiteral("capabilities")).toObject(); + + result.capabilities = Capabilities(_url, capabilitiesValue.toVariantMap()); + + delete reply; + return result; +} +} diff --git a/src/gui/networkadapters/fetchcapabilitiesadapter.h b/src/gui/networkadapters/fetchcapabilitiesadapter.h new file mode 100644 index 00000000000..6208356cbb3 --- /dev/null +++ b/src/gui/networkadapters/fetchcapabilitiesadapter.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) Lisa Reese (lisa.reese@kiteworks.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 +#include + +#include "capabilities.h" + +class QNetworkAccessManager; + +namespace OCC { + +struct FetchCapabilitiesResult +{ + QString error; + Capabilities capabilities = Capabilities(QUrl(), {}); + + bool success() const { return error.isEmpty() && capabilities.isValid(); } +}; + +class FetchCapabilitiesAdapter : public QObject +{ + Q_OBJECT +public: + explicit FetchCapabilitiesAdapter(QNetworkAccessManager *nam, const QString &authToken, const QUrl &url, QObject *parent = nullptr); + + FetchCapabilitiesResult getResult(); + +private: + QNetworkAccessManager *_nam; + QUrl _url; + QString _authorizationHeader; +}; + +} diff --git a/src/gui/networkadapters/resolveurladapter.cpp b/src/gui/networkadapters/resolveurladapter.cpp new file mode 100644 index 00000000000..07dffd43733 --- /dev/null +++ b/src/gui/networkadapters/resolveurladapter.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "resolveurladapter.h" + +#include "abstractcorejob.h" +#include "common/utility.h" +#include "gui/application.h" +#include "gui/tlserrordialog.h" + +#include +#include +#include + + +namespace OCC { + +Q_LOGGING_CATEGORY(lcResolveUrlAdapter, "gui.networkadapters.resolveurladapter") + +ResolveUrlAdapter::ResolveUrlAdapter(QNetworkAccessManager *nam, const QUrl &url, QWidget *errorDialogParent, QObject *parent) + : QObject(parent) + , _nam(nam) + , _url(url) + , _tlsDialogParent(errorDialogParent) +{ +} + +ResolveUrlResult ResolveUrlAdapter::getResult() +{ + ResolveUrlResult result; + + if (!_nam) { + return result; + } + + QNetworkRequest req = AbstractCoreJobFactory::makeRequest(Utility::concatUrlPath(_url, QStringLiteral("status.php"))); + req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy); + + QEventLoop waitLoop; + QNetworkReply *reply = _nam->get(req); + connect(reply, &QNetworkReply::sslErrors, this, &ResolveUrlAdapter::handleSslErrors); + connect(reply, &QNetworkReply::finished, &waitLoop, &QEventLoop::quit); + waitLoop.exec(); + + if (!_sslErrors.isEmpty()) { + result.error = tr("SSL failure when connecting to server at %1").arg(_url.toDisplayString()); + QString allErrors = _sslErrors.join(QStringLiteral("\n")); + qCritical(lcResolveUrlAdapter) << QStringLiteral("Attempt to resolve Url: %1 failed with SSL errors: %2.").arg(_url.toDisplayString(), allErrors); + } else if (reply->error() != QNetworkReply::NoError) { + result.error = tr("Could not detect compatible server at %1").arg(_url.toDisplayString()); + qCritical(lcResolveUrlAdapter) << QStringLiteral("Network error when resolving Url %1: %2.").arg(_url.toDisplayString(), reply->errorString()); + } else { + auto newUrl = reply->url(); + if (!newUrl.isValid()) { + result.error = tr("Resolved url is invalid %1").arg(newUrl.toDisplayString()); + } else if (!newUrl.matches(req.url(), QUrl::RemoveFilename)) { + qCWarning(lcResolveUrlAdapter) << _url << " redirect to " << newUrl << " is rejected"; + result.error = tr("Rejected redirect from %1 to %2").arg(_url.toDisplayString(), newUrl.toDisplayString()); + } else { + result.resolvedUrl = newUrl.adjusted(QUrl::RemoveFilename); + } + + if (!_certificates.isEmpty()) { + result.acceptedCertificates = _certificates; + } + } + delete reply; + return result; +} + +void ResolveUrlAdapter::handleSslErrors(const QList &errors) +{ + QNetworkReply *reply = qobject_cast(sender()); + Q_ASSERT(reply); + // the tls error dialog can only handle untrusted certificates not general ssl errors + auto filtered = errors; + filtered.erase(std::remove_if(filtered.begin(), filtered.end(), [](const QSslError &e) { return e.certificate().isNull(); }), filtered.end()); + if (filtered.isEmpty()) { + for (const auto &error : errors) { + _sslErrors.append(error.errorString()); + } + } else { + auto *tlsErrorDialog = new TlsErrorDialog(filtered, reply->url().host(), _tlsDialogParent); + + ocApp()->ensureVisible(); + // we have to exec here or the request finishes too fast + int res = tlsErrorDialog->exec(); + if (res == QDialog::DialogCode::Accepted) { + for (const auto &err : std::as_const(filtered)) + _certificates.insert(err.certificate()); + // let the reply finish! + reply->ignoreSslErrors(); + } else { + // it appears there is no need to abort the current reply when there are ssl errors - it emits finished even though there were un-ignored errors + // I'm not sure we can use the reply after finished is received in this case, though! It should still exist, but the docs suggest it is "torn down" + // (not sure what that means exactly) if errors are not ignored. + _sslErrors.append(QStringLiteral("User rejected invalid SSL certificate")); + } + } +} +} diff --git a/src/gui/networkadapters/resolveurladapter.h b/src/gui/networkadapters/resolveurladapter.h new file mode 100644 index 00000000000..627ca0aeb50 --- /dev/null +++ b/src/gui/networkadapters/resolveurladapter.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 +#include +#include +#include + +class QNetworkAccessManager; +class QWidget; + +namespace OCC { + +/** + * @brief The ResolveUrlResult struct is a simple container to collect the result of the network request + * error holds the error message if the operation failed + * resolvedUrl holds the final resolved url + * acceptedCertificates is an optional list of certificates the user accepted in the course of resolving the url + */ +struct ResolveUrlResult +{ + QString error; + QUrl resolvedUrl; + QSet acceptedCertificates; + bool success() const { return error.isEmpty() && resolvedUrl.isValid(); } +}; + +/** + * @brief The ResolveUrlAdapter class is used to determine if a given URL is reachable via a network request for status. + + * No authentication is in play, it's just a simple first pass reality check to make sure that the URL exists. + */ +class ResolveUrlAdapter : public QObject +{ + Q_OBJECT + +public: + /** + * @brief ResolveUrlAdapter + * @param nam is the network access manager provided by the caller + * @param url is the url which will be validated by the request + * @param errorDialogParent should be the current top level window. Because this adapter will often be used in the new account wizard + * which is already a modal dialog on top of the main window, and we get some quirky behavior on linux if we use the main window to + * parent the tls dialog + * @param parent fits normal QObject parenting concept. The intent of the adapter is to create an instance on the stack and + * let it naturally go out of scope after getResult has completed, so a parent should not be required in normal use + */ + explicit ResolveUrlAdapter(QNetworkAccessManager *nam, const QUrl &url, QWidget *errorDialogParent, QObject *parent = nullptr); + + /** + * @brief getResult executes a network request synchronously + * @return the result of the request + * + * Important detail: if the reply contains certificates the user will be asked to accept them before this function returns. + * If the user rejects the certificates, the net result is a ssl failure which will be reported as such. + */ + ResolveUrlResult getResult(); + +private: + void handleSslErrors(const QList &errors); + + QNetworkAccessManager *_nam; + QUrl _url; + QWidget *_tlsDialogParent; + + QStringList _sslErrors; + QSet _certificates; +}; +} diff --git a/src/gui/networkadapters/userinfoadapter.cpp b/src/gui/networkadapters/userinfoadapter.cpp new file mode 100644 index 00000000000..00df44dfe6c --- /dev/null +++ b/src/gui/networkadapters/userinfoadapter.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "userinfoadapter.h" + +#include "abstractcorejob.h" +#include "common/utility.h" +#include "creds/credentialssupport.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcUserInfoAdapter, "gui.networkadapters.userinfoadapter") + +UserInfoAdapter::UserInfoAdapter(QNetworkAccessManager *nam, const QString &authToken, const QUrl &url, QObject *parent) + : QObject(parent) + , _nam(nam) + , _url(url) + , _authorizationHeader(QStringLiteral("Bearer %1").arg(authToken)) +{ +} + +UserInfoResult UserInfoAdapter::getResult() +{ + UserInfoResult result; + if (!_nam) { + return result; + } + + QUrlQuery urlQuery({{QStringLiteral("format"), QStringLiteral("json")}}); + + auto req = AbstractCoreJobFactory::makeRequest(Utility::concatUrlPath(_url, QStringLiteral("ocs/v2.php/cloud/user"), urlQuery)); + + req.setRawHeader("Authorization", _authorizationHeader.toUtf8()); + req.setAttribute(DontAddCredentialsAttribute, true); + req.setAttribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Manual); + + QEventLoop waitLoop; + QNetworkReply *reply = _nam->get(req); + connect(reply, &QNetworkReply::finished, &waitLoop, &QEventLoop::quit); + waitLoop.exec(); + + const auto data = reply->readAll(); + const auto statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (reply->error() != QNetworkReply::NoError) { + result.error = tr("Network request to collect user info failed"); + qCritical(lcUserInfoAdapter) + << QStringLiteral("Network error when retrieving user info from: %1 %2.").arg(_url.toDisplayString(), reply->errorString()); + } else if (statusCode != 200) { + if (statusCode == 401) + // putting this special case in as we originally reported it explicitly in the wizard controller + result.error = tr("Unable to retrieve user info: invalid credentials."); + else + result.error = tr("Unexpected network response when retrieving user info."); + qCritical(lcUserInfoAdapter) << QStringLiteral("Bad response when retrieving user info from %1: %2.").arg(_url.toDisplayString(), statusCode); + } else { + QJsonParseError error = {}; + const auto json = QJsonDocument::fromJson(data, &error); + if (error.error == QJsonParseError::NoError) { + const auto jsonData = json.object().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toObject(); + result.userId = jsonData.value(QStringLiteral("id")).toString(); + result.displayName = jsonData.value(QStringLiteral("display-name")).toString(); + } else { + result.error = tr("Retrieving user info failed with JSON error."); + qCritical(lcUserInfoAdapter) + << QStringLiteral("JSON error when retrieving user info from %1: %2.").arg(_url.toDisplayString(), error.errorString()); + } + } + + return result; +} +} diff --git a/src/gui/networkadapters/userinfoadapter.h b/src/gui/networkadapters/userinfoadapter.h new file mode 100644 index 00000000000..9256e352f39 --- /dev/null +++ b/src/gui/networkadapters/userinfoadapter.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 +#include + +class QNetworkAccessManager; + +namespace OCC { + +/** + * @brief The UserInfoResult class is a simple container which holds the user info + * userId is the username, davUser, etc + * displayName is the "user friendly" username + * error is exactly that - an error message from the adapter when it fails. + */ +struct UserInfoResult +{ + QString userId; + QString displayName; + QString error; + + bool success() const { return error.isEmpty() && !userId.isEmpty() && !displayName.isEmpty(); } +}; + +/** + * @brief The UserInfoAdapter class is a simple implementation that retrieves the user info in a synchronous manner. + */ +class UserInfoAdapter : public QObject +{ + Q_OBJECT +public: + /** + * @brief UserInfoAdapter configures the adapter with the necessary data + * @param nam the QNetworkAccessManager this adapter should use to retrieve the info + * @param authToken the authentication token for this user + * @param url the url to use when retrieving the info. In practice this will either be the original url (when webfinger is not in play) + * or it will be the webfinger user endpoint that is discovered using the WebfingerLookupAdapter + * @param parent this class will normally be created on the stack so no parent is needed, but if you want to run it on the heap, give it + * give it a sensible parent Qt style. + */ + explicit UserInfoAdapter(QNetworkAccessManager *nam, const QString &authToken, const QUrl &url, QObject *parent = nullptr); + + /** + * @brief getResult runs the network request/reply synchronously + * @return the results from the reply, cleanly bundled in the result struct + */ + UserInfoResult getResult(); + +private: + QNetworkAccessManager *_nam; + QUrl _url; + QString _authorizationHeader; +}; +} diff --git a/src/gui/networkadapters/webfingerlookupadapter.cpp b/src/gui/networkadapters/webfingerlookupadapter.cpp new file mode 100644 index 00000000000..c09a4f37ea6 --- /dev/null +++ b/src/gui/networkadapters/webfingerlookupadapter.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "webfingerlookupadapter.h" + +#include "abstractcorejob.h" +#include "common/utility.h" +#include "creds/credentialssupport.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcWebFingerLookupAdapter, "gui.networkadapters.webfingerlookupadapter"); + +WebFingerLookupAdapter::WebFingerLookupAdapter(QNetworkAccessManager *nam, const QString &authToken, const QUrl &url, QObject *parent) + : QObject{parent} + , _nam(nam) + , _url(url) + , _authorizationHeader(QStringLiteral("Bearer %1").arg(authToken)) +{ +} + +WebFingerLookupResult WebFingerLookupAdapter::getResult() +{ + WebFingerLookupResult result; + + if (!_nam) { + return result; + } + + const QString resource = QStringLiteral("acct:me@%1").arg(_url.host()); + + auto req = + AbstractCoreJobFactory::makeRequest(Utility::concatUrlPath(_url, QStringLiteral("/.well-known/webfinger"), {{QStringLiteral("resource"), resource}})); + + // we are not connected yet, so we need to handle the authentication manually + req.setRawHeader("Authorization", _authorizationHeader.toUtf8()); + + // we just added the Authorization header, don't let the credentials AccessManager tamper with it + req.setAttribute(DontAddCredentialsAttribute, true); + req.setAttribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Manual); + + QEventLoop waitLoop; + QNetworkReply *reply = _nam->get(req); + connect(reply, &QNetworkReply::finished, &waitLoop, &QEventLoop::quit); + waitLoop.exec(); + + Q_ASSERT(reply->isFinished()); + + const auto statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (reply->error() != QNetworkReply::NoError || statusCode != 200) { + result.error = tr("Failed to retrieve user info"); + } + + const auto data = reply->readAll(); + QJsonParseError error = {}; + const auto json = QJsonDocument::fromJson(data, &error); + + if (error.error != QJsonParseError::NoError) { + result.error = error.errorString(); + } + + const auto links = json.object().value(QStringLiteral("links")).toArray(); + + for (const auto &link : links) { + const auto linkObject = link.toObject(); + + const QString rel = linkObject.value(QStringLiteral("rel")).toString(); + const QString href = linkObject.value(QStringLiteral("href")).toString(); + + if (rel != QStringLiteral("http://webfinger.owncloud/rel/server-instance")) { + continue; + } + + result.urls.append(QUrl::fromUserInput(href)); + } + if (result.urls.isEmpty()) + result.error = tr("WebFinger lookup returned no links"); + + delete reply; + return result; +} +} diff --git a/src/gui/networkadapters/webfingerlookupadapter.h b/src/gui/networkadapters/webfingerlookupadapter.h new file mode 100644 index 00000000000..c0885075670 --- /dev/null +++ b/src/gui/networkadapters/webfingerlookupadapter.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 +#include + + +class QNetworkAccessManager; + +namespace OCC { + +/** + * @brief The WebFingerLookupResult struct is a simple container for collecting the result of the WebFingerLookup + * + * error contains the error string if the request failed + * urls contains the webfinger links from the reply if the request succeeded + * + */ +struct WebFingerLookupResult +{ + QString error; + QList urls; + + bool success() const { return error.isEmpty() && !urls.isEmpty(); } +}; + +/** + * @brief The WebFingerLookupAdapter class is used to retrieve the URLs associated with a webfinger provider. + * Prior authentication is required. + * If the result is successful, we take the first entry from the URL list as the de facto url for the account. + */ + +class WebFingerLookupAdapter : public QObject +{ + Q_OBJECT +public: + /** + * @brief WebFingerLookupAdapter + * @param nam the network manager + * @param authToken the token to use with the request + * @param url for the request. Note this value should be the original server url, not the webfinger url (if it exists). I still do + * not fully understand why that is but will find out. + * @param parent is used for the standard QObject parenting scheme. This param should not be needed in most cases as the intent is + * to create the adapter on the stack then just let it go out of scope, no parenting required. + */ + explicit WebFingerLookupAdapter(QNetworkAccessManager *nam, const QString &authToken, const QUrl &url, QObject *parent = nullptr); + + /** + * @brief getResult runs the network request synchronously and is intended for use in a gui, where it makes sense to block until we + * have the required data to move on. + * @return the result of the network request. If successful the result contains a list of webfinger links. + * + * it is safe to call get result repeatedly, so long as the parameters passed to the adapter on construction are still valid. + */ + WebFingerLookupResult getResult(); + +private: + QNetworkAccessManager *_nam; + QUrl _url; + + QString _authorizationHeader; +}; +} diff --git a/src/gui/networkinformation.cpp b/src/gui/networkinformation.cpp new file mode 100644 index 00000000000..b1309fdd877 --- /dev/null +++ b/src/gui/networkinformation.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) by Erik Verbruggen + * + * 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 "networkinformation.h" + +#include + +using namespace OCC; + +Q_LOGGING_CATEGORY(lcNetInfo, "gui.netinfo", QtInfoMsg) + +NetworkInformation *NetworkInformation::_instance = nullptr; + +namespace { + +void loadQNetworkInformationBackend() +{ + if (!QNetworkInformation::loadDefaultBackend()) { + qCWarning(lcNetInfo) << "Failed to load default backend of QNetworkInformation."; + if (!QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Metered)) { + qCWarning(lcNetInfo) << "Failed to load backend of QNetworkInformation by metered feature."; + if (!QNetworkInformation::loadBackendByFeatures(QNetworkInformation::Feature::Reachability)) { + qCWarning(lcNetInfo) << "Failed to load backend of QNetworkInformation by reachability feature."; + qCWarning(lcNetInfo) << "Available backends:" << QNetworkInformation::availableBackends().join(QStringLiteral(", ")); + return; + } + } + } + qCDebug(lcNetInfo) << "Loaded network information backend:" << QNetworkInformation::instance()->backendName(); + qCDebug(lcNetInfo) << "Supported features:" << QNetworkInformation::instance()->supportedFeatures(); + qCDebug(lcNetInfo) << "Available backends:" << QNetworkInformation::availableBackends().join(QStringLiteral(", ")); + + if (auto qni = QNetworkInformation::instance()) { + QObject::connect(qni, &QNetworkInformation::reachabilityChanged, [qni](QNetworkInformation::Reachability reachability) { + qCInfo(lcNetInfo) << "Connection Status changed to:" << reachability << "captive portal status:" << qni->isBehindCaptivePortal(); + }); + } +} + +} // anonymous namespace + +NetworkInformation::NetworkInformation() { } + +NetworkInformation *NetworkInformation::instance() +{ + if (!_instance) { + _instance = new NetworkInformation; + + loadQNetworkInformationBackend(); + + if (auto qni = QNetworkInformation::instance()) { + connect(qni, &QNetworkInformation::isMeteredChanged, _instance, &NetworkInformation::isMeteredChanged); + connect(qni, &QNetworkInformation::reachabilityChanged, _instance, &NetworkInformation::reachabilityChanged); + connect(qni, &QNetworkInformation::isBehindCaptivePortalChanged, _instance, &NetworkInformation::slotIsBehindCaptivePortalChanged); + } + } + + return _instance; +} + +bool NetworkInformation::isMetered() +{ + if (auto *qNetInfo = QNetworkInformation::instance()) { + return qNetInfo->isMetered(); + } + + return false; +} + +bool NetworkInformation::supports(Features features) const +{ + if (auto *qNetInfo = QNetworkInformation::instance()) { + return qNetInfo->supports(features); + } + + return false; +} + +bool NetworkInformation::isForcedCaptivePortal() const +{ + return _forcedCaptivePortal; +} + +void NetworkInformation::setForcedCaptivePortal(bool onoff) +{ + if (_forcedCaptivePortal != onoff) { + _forcedCaptivePortal = onoff; + qCDebug(lcNetInfo) << "Switching forced captive portal to" << onoff; + + bool behindCaptivePortal = false; + + if (auto *qNetInfo = QNetworkInformation::instance()) { + behindCaptivePortal = qNetInfo->isBehindCaptivePortal(); + } + + if (behindCaptivePortal != _forcedCaptivePortal) { + Q_EMIT isBehindCaptivePortalChanged(_forcedCaptivePortal); + } + } +} + +bool NetworkInformation::isBehindCaptivePortal() const +{ + if (_forcedCaptivePortal) { + return true; + } + + if (auto *qNetInfo = QNetworkInformation::instance()) { + return qNetInfo->isBehindCaptivePortal(); + } + + return false; +} + +void NetworkInformation::slotIsBehindCaptivePortalChanged(bool state) +{ + qCDebug(lcNetInfo) << "OS signals behind captive portal changed to" << state << "forced captive portal flag:" << _forcedCaptivePortal; + + if (!_forcedCaptivePortal) { + Q_EMIT isBehindCaptivePortalChanged(state); + } +} diff --git a/src/gui/networkinformation.h b/src/gui/networkinformation.h new file mode 100644 index 00000000000..075fad6a784 --- /dev/null +++ b/src/gui/networkinformation.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) by Erik Verbruggen + * + * 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 "gui/owncloudguilib.h" + +#include + +namespace OCC { + +/** + * @brief Wrapper class for QNetworkInformation + * + * This class is used instead of QNetworkInformation so we do not need to check for an instance, + * and to facilitate debugging by being able to force certain network states (i.e. captive portal). + */ +class OWNCLOUDGUI_EXPORT NetworkInformation : public QObject +{ + Q_OBJECT + +public: + static NetworkInformation *instance(); + + bool isMetered(); + + using Feature = QNetworkInformation::Feature; + using Features = QNetworkInformation::Features; + using Reachability = QNetworkInformation::Reachability; + + bool supports(Features features) const; + + bool isForcedCaptivePortal() const; + void setForcedCaptivePortal(bool onoff); + bool isBehindCaptivePortal() const; + +Q_SIGNALS: + void isMeteredChanged(bool isMetered); + void reachabilityChanged(NetworkInformation::Reachability reachability); + void isBehindCaptivePortalChanged(bool state); + +private Q_SLOTS: + void slotIsBehindCaptivePortalChanged(bool state); + +private: + NetworkInformation(); + + static NetworkInformation *_instance; + + bool _forcedCaptivePortal = false; +}; + +} diff --git a/src/gui/networksettings.cpp b/src/gui/networksettings.cpp index 5fc329e8313..96cc3e1d435 100644 --- a/src/gui/networksettings.cpp +++ b/src/gui/networksettings.cpp @@ -13,30 +13,45 @@ */ #include "networksettings.h" +#include "networkinformation.h" #include "ui_networksettings.h" -#include "theme.h" -#include "configfile.h" -#include "application.h" +#include "accountmanager.h" +#include "clientproxy.h" #include "configfile.h" #include "folderman.h" -#include "accountmanager.h" +#include "theme.h" +#include #include #include -#include +#include +namespace { +auto proxyPasswordC() +{ + return QStringLiteral("Proxy/Password"); +} +} namespace OCC { +Q_LOGGING_CATEGORY(lcNetworkSettings, "gui.networksettings.gui", QtInfoMsg) + NetworkSettings::NetworkSettings(QWidget *parent) : QWidget(parent) + , _credentialManager(new CredentialManager(this)) , _ui(new Ui::NetworkSettings) { _ui->setupUi(this); + setFocusProxy(_ui->pauseSyncWhenMeteredCheckbox); + _ui->hostLineEdit->setPlaceholderText(tr("Hostname of proxy server")); + _ui->hostLineEdit->setAccessibleName(tr("Hostname of proxy server")); _ui->userLineEdit->setPlaceholderText(tr("Username for proxy server")); + _ui->userLineEdit->setAccessibleName(tr("Username for proxy server")); _ui->passwordLineEdit->setPlaceholderText(tr("Password for proxy server")); + _ui->passwordLineEdit->setAccessibleName(tr("Password for proxy server")); _ui->typeComboBox->addItem(tr("HTTP(S) proxy"), QNetworkProxy::HttpProxy); _ui->typeComboBox->addItem(tr("SOCKS5 proxy"), QNetworkProxy::Socks5Proxy); @@ -59,30 +74,24 @@ NetworkSettings::NetworkSettings(QWidget *parent) this, &NetworkSettings::checkAccountLocalhost); loadProxySettings(); - loadBWLimitSettings(); + removeBWLimitSettings(); + loadMeteredSettings(); // proxy connect(_ui->typeComboBox, static_cast(&QComboBox::currentIndexChanged), this, &NetworkSettings::saveProxySettings); - connect(_ui->proxyButtonGroup, static_cast(&QButtonGroup::buttonClicked), this, &NetworkSettings::saveProxySettings); + connect(_ui->proxyButtonGroup, &QButtonGroup::buttonClicked, this, &NetworkSettings::saveProxySettings); connect(_ui->hostLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings); connect(_ui->userLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings); connect(_ui->passwordLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings); connect(_ui->portSpinBox, &QAbstractSpinBox::editingFinished, this, &NetworkSettings::saveProxySettings); connect(_ui->authRequiredcheckBox, &QAbstractButton::toggled, this, &NetworkSettings::saveProxySettings); - connect(_ui->uploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); - connect(_ui->noUploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); - connect(_ui->autoUploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); - connect(_ui->downloadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); - connect(_ui->noDownloadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); - connect(_ui->autoDownloadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); - connect(_ui->downloadSpinBox, static_cast(&QSpinBox::valueChanged), this, &NetworkSettings::saveBWLimitSettings); - connect(_ui->uploadSpinBox, static_cast(&QSpinBox::valueChanged), this, &NetworkSettings::saveBWLimitSettings); - // Warn about empty proxy host connect(_ui->hostLineEdit, &QLineEdit::textChanged, this, &NetworkSettings::checkEmptyProxyHost); checkEmptyProxyHost(); checkAccountLocalhost(); + + connect(_ui->pauseSyncWhenMeteredCheckbox, &QAbstractButton::clicked, this, &NetworkSettings::saveMeteredSettings); } NetworkSettings::~NetworkSettings() @@ -124,32 +133,44 @@ void NetworkSettings::loadProxySettings() _ui->portSpinBox->setValue(port); _ui->authRequiredcheckBox->setChecked(cfgFile.proxyNeedsAuth()); _ui->userLineEdit->setText(cfgFile.proxyUser()); - _ui->passwordLineEdit->setText(cfgFile.proxyPassword()); + + const QString legacyPasswordKey = QStringLiteral("Proxy/pass"); + const QString legacyPassword = QString::fromUtf8(QByteArray::fromBase64(ConfigFile().makeQSettings().value(legacyPasswordKey).toByteArray())); + if (!legacyPassword.isEmpty()) { + qCWarning(lcNetworkSettings) << "Migrating legacy proxy password to keychain"; + ConfigFile().makeQSettings().remove(legacyPasswordKey); + _credentialManager->set(proxyPasswordC(), legacyPassword); + _ui->passwordLineEdit->setText(legacyPassword); + ClientProxy::setupQtProxyFromConfig(legacyPassword); + } else { + auto job = _credentialManager->get(proxyPasswordC()); + if (!job) { + qCWarning(lcNetworkSettings) << "get credentials job is null - most likely the key does not exist in the credentials manager"; + return; + } + connect(job, &CredentialJob::finished, this, [job, this] { + const QString password = job->data().toString(); + _ui->passwordLineEdit->setText(password); + ClientProxy::setupQtProxyFromConfig(password); + }); + } } -void NetworkSettings::loadBWLimitSettings() +void NetworkSettings::removeBWLimitSettings() { - ConfigFile cfgFile; + // This method should be removed in a future version. + QSettings settings = ConfigFile::makeQSettings(); + settings.remove("BWLimit"); +} - int useDownloadLimit = cfgFile.useDownloadLimit(); - if (useDownloadLimit >= 1) { - _ui->downloadLimitRadioButton->setChecked(true); - } else if (useDownloadLimit == 0) { - _ui->noDownloadLimitRadioButton->setChecked(true); - } else { - _ui->autoDownloadLimitRadioButton->setChecked(true); +void NetworkSettings::loadMeteredSettings() +{ + if (NetworkInformation::instance()->supports(NetworkInformation::Feature::Metered)) { + _ui->pauseSyncWhenMeteredCheckbox->setChecked(ConfigFile().pauseSyncWhenMetered()); + return; } - _ui->downloadSpinBox->setValue(cfgFile.downloadLimit()); - int useUploadLimit = cfgFile.useUploadLimit(); - if (useUploadLimit >= 1) { - _ui->uploadLimitRadioButton->setChecked(true); - } else if (useUploadLimit == 0) { - _ui->noUploadLimitRadioButton->setChecked(true); - } else { - _ui->autoUploadLimitRadioButton->setChecked(true); - } - _ui->uploadSpinBox->setValue(cfgFile.uploadLimit()); + _ui->pauseSyncWhenMeteredCheckbox->setVisible(false); } void NetworkSettings::saveProxySettings() @@ -162,19 +183,15 @@ void NetworkSettings::saveProxySettings() } else if (_ui->systemProxyRadioButton->isChecked()) { cfgFile.setProxyType(QNetworkProxy::DefaultProxy); } else if (_ui->manualProxyRadioButton->isChecked()) { - int type = _ui->typeComboBox->itemData(_ui->typeComboBox->currentIndex()).toInt(); - QString host = _ui->hostLineEdit->text(); - if (host.isEmpty()) + auto type = static_cast(_ui->typeComboBox->itemData(_ui->typeComboBox->currentIndex()).toInt()); + if (_ui->hostLineEdit->text().isEmpty()) { type = QNetworkProxy::NoProxy; - bool needsAuth = _ui->authRequiredcheckBox->isChecked(); - QString user = _ui->userLineEdit->text(); - QString pass = _ui->passwordLineEdit->text(); - cfgFile.setProxyType(type, _ui->hostLineEdit->text(), - _ui->portSpinBox->value(), needsAuth, user, pass); + } + _credentialManager->set(proxyPasswordC(), _ui->passwordLineEdit->text()); + cfgFile.setProxyType(type, _ui->hostLineEdit->text(), _ui->portSpinBox->value(), _ui->authRequiredcheckBox->isChecked(), _ui->userLineEdit->text()); } - ClientProxy proxy; - proxy.setupQtProxyFromConfig(); // Refresh the Qt proxy settings as the + ClientProxy::setupQtProxyFromConfig(_ui->passwordLineEdit->text()); // Refresh the Qt proxy settings as the // quota check can happen all the time. // ...and set the folders dirty, they refresh their proxy next time they @@ -186,34 +203,17 @@ void NetworkSettings::saveProxySettings() } } -void NetworkSettings::saveBWLimitSettings() +void NetworkSettings::saveMeteredSettings() { - ConfigFile cfgFile; - if (_ui->downloadLimitRadioButton->isChecked()) { - cfgFile.setUseDownloadLimit(1); - } else if (_ui->noDownloadLimitRadioButton->isChecked()) { - cfgFile.setUseDownloadLimit(0); - } else if (_ui->autoDownloadLimitRadioButton->isChecked()) { - cfgFile.setUseDownloadLimit(-1); - } - cfgFile.setDownloadLimit(_ui->downloadSpinBox->value()); - - if (_ui->uploadLimitRadioButton->isChecked()) { - cfgFile.setUseUploadLimit(1); - } else if (_ui->noUploadLimitRadioButton->isChecked()) { - cfgFile.setUseUploadLimit(0); - } else if (_ui->autoUploadLimitRadioButton->isChecked()) { - cfgFile.setUseUploadLimit(-1); - } - cfgFile.setUploadLimit(_ui->uploadSpinBox->value()); - - FolderMan::instance()->setDirtyNetworkLimits(); + bool pauseSyncWhenMetered = _ui->pauseSyncWhenMeteredCheckbox->isChecked(); + ConfigFile().setPauseSyncWhenMetered(pauseSyncWhenMetered); + FolderMan::instance()->scheduler()->setPauseSyncWhenMetered(pauseSyncWhenMetered); } void NetworkSettings::checkEmptyProxyHost() { if (_ui->hostLineEdit->isEnabled() && _ui->hostLineEdit->text().isEmpty()) { - _ui->hostLineEdit->setStyleSheet("border: 1px solid red"); + _ui->hostLineEdit->setStyleSheet(QStringLiteral("border: 1px solid red")); } else { _ui->hostLineEdit->setStyleSheet(QString()); } @@ -242,8 +242,8 @@ void NetworkSettings::checkAccountLocalhost() // effect for localhost (#7169) for (const auto &account : AccountManager::instance()->accounts()) { const auto host = account->account()->url().host(); - // Some typical url for localhost - if (host == "localhost" || host.startsWith("127.") || host == "[::1]") + // Some typical URL for localhost + if (host == QLatin1String("localhost") || host.startsWith(QLatin1String("127.")) || host == QLatin1String("[::1]")) visible = true; } } diff --git a/src/gui/networksettings.h b/src/gui/networksettings.h index 59086cd95c8..e12f6ab72fe 100644 --- a/src/gui/networksettings.h +++ b/src/gui/networksettings.h @@ -15,6 +15,8 @@ #ifndef MIRALL_NETWORKSETTINGS_H #define MIRALL_NETWORKSETTINGS_H +#include "libsync/creds/credentialmanager.h" + #include @@ -36,9 +38,9 @@ class NetworkSettings : public QWidget explicit NetworkSettings(QWidget *parent = nullptr); ~NetworkSettings() override; -private slots: +private Q_SLOTS: void saveProxySettings(); - void saveBWLimitSettings(); + void saveMeteredSettings(); /// Red marking of host field if empty and enabled void checkEmptyProxyHost(); @@ -50,7 +52,9 @@ private slots: private: void loadProxySettings(); - void loadBWLimitSettings(); + void removeBWLimitSettings(); + void loadMeteredSettings(); + CredentialManager *_credentialManager; Ui::NetworkSettings *_ui; }; diff --git a/src/gui/networksettings.ui b/src/gui/networksettings.ui index 7e2476126b4..ae051723ec6 100644 --- a/src/gui/networksettings.ui +++ b/src/gui/networksettings.ui @@ -7,13 +7,20 @@ 0 0 623 - 581 + 360
    Form + + + + Pause synchronization when the Internet connection is metered + + + @@ -47,25 +54,14 @@ - - - - - Specify proxy manually as - - - proxyButtonGroup - - - - - - - false - - - - + + + Specify proxy manually + + + proxyButtonGroup + + @@ -76,6 +72,40 @@ 0 + + + + + + Proxy type + + + typeComboBox + + + + + + + false + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + @@ -83,6 +113,9 @@ Host + + hostLineEdit + @@ -93,6 +126,9 @@ 0 + + Proxy Hostname + @@ -113,6 +149,9 @@ 0 + + Proxy Port Number + 1 @@ -167,7 +206,7 @@ - QLineEdit::Password + QLineEdit::EchoMode::Password @@ -187,162 +226,10 @@ - - - - - - - 0 - 0 - - - - Download Bandwidth - - - - - - No limit - - - true - - - - - - - Limit to 3/4 of estimated bandwidth - - - Limit automatically - - - - - - - - - Limit to - - - - - - - false - - - 9999 - - - 80 - - - - - - - KBytes/s - - - - - - - - - Qt::Vertical - - - - 147 - 25 - - - - - - - - - - - - 0 - 0 - - - - Upload Bandwidth - - - - - - No limit - - - true - - - - - - - Limit to 3/4 of estimated bandwidth - - - Limit automatically - - - - - - - - - Limit to - - - - - - - false - - - 1 - - - 9999 - - - 10 - - - - - - - KBytes/s - - - - - - - - - - - Qt::Vertical + Qt::Orientation::Vertical @@ -354,41 +241,20 @@ + + pauseSyncWhenMeteredCheckbox + noProxyRadioButton + systemProxyRadioButton + manualProxyRadioButton + typeComboBox + hostLineEdit + portSpinBox + authRequiredcheckBox + userLineEdit + passwordLineEdit + - - - downloadLimitRadioButton - toggled(bool) - downloadSpinBox - setEnabled(bool) - - - 73 - 69 - - - 131 - 78 - - - - - uploadLimitRadioButton - toggled(bool) - uploadSpinBox - setEnabled(bool) - - - 322 - 101 - - - 411 - 106 - - - - + diff --git a/src/gui/newaccountwizard/CMakeLists.txt b/src/gui/newaccountwizard/CMakeLists.txt new file mode 100644 index 00000000000..8dc1012f228 --- /dev/null +++ b/src/gui/newaccountwizard/CMakeLists.txt @@ -0,0 +1,21 @@ +target_sources(owncloudGui PRIVATE + newaccountwizardcontroller.cpp + newaccountwizardcontroller.h + newaccountmodel.cpp + newaccountmodel.h + urlpagecontroller.cpp + urlpagecontroller.h + oauthpagecontroller.cpp + oauthpagecontroller.h + authsuccesspagecontroller.cpp + authsuccesspagecontroller.h + advancedsettingspagecontroller.cpp + advancedsettingspagecontroller.h + wizardpagevalidator.h + newaccountwizard.cpp + newaccountwizard.h + newaccountbuilder.cpp + newaccountbuilder.h + newaccountenums.h +) + diff --git a/src/gui/newaccountwizard/advancedsettingspagecontroller.cpp b/src/gui/newaccountwizard/advancedsettingspagecontroller.cpp new file mode 100644 index 00000000000..9d804326d6d --- /dev/null +++ b/src/gui/newaccountwizard/advancedsettingspagecontroller.cpp @@ -0,0 +1,286 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "advancedsettingspagecontroller.h" +#include "common/vfs.h" +#include "folderman.h" + +#include "theme.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OCC { + + +AdvancedSettingsPageController::AdvancedSettingsPageController(QWizardPage *page, QObject *parent) + : QObject{parent} + , _page(page) +{ + gatherSyncInfo(); + buildPage(); +} + +void AdvancedSettingsPageController::buildPage() +{ + if (!_page) + return; + + _buttonGroup = new QButtonGroup(_page); + + QLabel *titleLabel = new QLabel(tr("Advanced settings"), _page); + QFont titleFont = titleLabel->font(); + titleFont.setPixelSize(20); + titleFont.setWeight(QFont::DemiBold); + titleLabel->setFont(titleFont); + titleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + QLabel *syncOptionsLabel = new QLabel(tr("Sync and download options"), _page); + QLabel *syncRootLabel = new QLabel(tr("Download location")); + QFont sectionFont = syncOptionsLabel->font(); + sectionFont.setPixelSize(16); + sectionFont.setWeight(QFont::DemiBold); + syncOptionsLabel->setFont(sectionFont); + syncRootLabel->setFont(sectionFont); + syncOptionsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + syncRootLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(50, 0, 50, 0); + layout->setSpacing(12); + layout->addStretch(1); + layout->addWidget(titleLabel, Qt::AlignLeft); + layout->addSpacing(8); + layout->addWidget(syncOptionsLabel, Qt::AlignLeft); + + QRadioButton *vfsButton = new QRadioButton(tr("Sync and download files as you use them (saves hard drive space)"), _page); + vfsButton->setFocusPolicy(Qt::StrongFocus); + _buttonGroup->addButton(vfsButton, NewAccount::SyncType::USE_VFS); + layout->addWidget(vfsButton, Qt::AlignLeft); + + QRadioButton *syncAllButton = new QRadioButton(tr("Automatically sync and download folders and files"), _page); + syncAllButton->setObjectName("SyncAllRadioButton"); + syncAllButton->setFocusPolicy(Qt::StrongFocus); + _buttonGroup->addButton(syncAllButton, NewAccount::SyncType::SYNC_ALL); + layout->addWidget(syncAllButton, Qt::AlignLeft); + + QRadioButton *selectiveSyncButton = new QRadioButton(tr("Sync and download folders manually"), _page); + selectiveSyncButton->setObjectName("SelectiveSyncRadioButton"); + selectiveSyncButton->setFocusPolicy(Qt::StrongFocus); + _buttonGroup->addButton(selectiveSyncButton, NewAccount::SyncType::SELECTIVE_SYNC); + layout->addWidget(selectiveSyncButton, Qt::AlignLeft); + + connect(_buttonGroup, &QButtonGroup::idClicked, this, &AdvancedSettingsPageController::syncTypeChanged); + + // if the branding does not specify a background color for the wizard we should just let the normal system colors + // rule. If not: + QPalette radioPalette = vfsButton->palette(); + QColor textColor = _page->palette().color(QPalette::Text); + if (Theme::instance()->wizardHeaderBackgroundColor().isValid()) { + QColor disabledColor(textColor); + // eh - I don't know if this will really work in all possible cases but I think it is the simplest way for now + disabledColor.setAlpha(128); + // unexpected, but these are the only ones that work to change the text color and the circle colors of the radio button + // on windows - need to test other platforms for sure + radioPalette.setColor(QPalette::WindowText, textColor); + radioPalette.setColor(QPalette::Window, textColor); + radioPalette.setColor(QPalette::Disabled, QPalette::Window, disabledColor); + } + // for this use case we want the text to remain "normal" regardless of branding. the only time we disable a button is when there + // is only one choice + radioPalette.setColor(QPalette::Disabled, QPalette::WindowText, textColor); + vfsButton->setPalette(radioPalette); + syncAllButton->setPalette(radioPalette); + selectiveSyncButton->setPalette(radioPalette); + + if (!_vfsIsAvailable) { + vfsButton->setVisible(false); + } else if (_forceVfs) { + vfsButton->setEnabled(false); + selectiveSyncButton->setVisible(false); + syncAllButton->setVisible(false); + } + + Q_ASSERT(_buttonGroup->button(_defaultSyncType)); + _buttonGroup->button(_defaultSyncType)->setChecked(true); + + _rootDirEdit = new QLineEdit(_page); + _rootDirEdit->setText(_defaultSyncRoot); + QPalette dirPalette = _rootDirEdit->palette(); + dirPalette.setColor(QPalette::Base, dirPalette.color(QPalette::Button)); + dirPalette.setColor(QPalette::Text, dirPalette.color(QPalette::ButtonText)); + _rootDirEdit->setPalette(dirPalette); + _rootDirEdit->setFocusPolicy(Qt::StrongFocus); + _rootDirEdit->setAccessibleName(tr("Download location on the local machine")); + // just clear the error if the user starts typing in the text edit + connect(_rootDirEdit, &QLineEdit::textEdited, this, [this] { + if (!_errorField->text().isEmpty()) + _errorField->setText({}); + }); + connect(_rootDirEdit, &QLineEdit::editingFinished, this, &AdvancedSettingsPageController::onRootDirFieldEdited); + + _folderButton = new QPushButton(tr("Browse…"), _page); + _folderButton->setObjectName("BrowseButton"); + _folderButton->setFocusPolicy(Qt::StrongFocus); + _folderButton->setAccessibleDescription(tr("Browse for a download location")); + connect(_folderButton, &QPushButton::clicked, this, &AdvancedSettingsPageController::showFolderPicker); + + layout->addSpacing(8); + layout->addWidget(syncRootLabel, Qt::AlignLeft); + + QHBoxLayout *folderPickerLayout = new QHBoxLayout(); + folderPickerLayout->addWidget(_rootDirEdit, Qt::AlignLeft); + folderPickerLayout->addWidget(_folderButton); + layout->addLayout(folderPickerLayout); + + _errorField = new QLabel(QString(), _page); + QPalette errorPalette = _errorField->palette(); + errorPalette.setColor(QPalette::Text, Qt::red); + _errorField->setPalette(errorPalette); + _errorField->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _errorField->setWordWrap(true); + _errorField->setAlignment(Qt::AlignLeft); + _errorField->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + layout->addWidget(_errorField); + + layout->addStretch(1); + _page->setLayout(layout); +} + +void AdvancedSettingsPageController::gatherSyncInfo() +{ + _vfsIsAvailable = VfsPluginManager::instance().bestAvailableVfsMode() == Vfs::WindowsCfApi; + _forceVfs = _vfsIsAvailable && Theme::instance()->forceVirtualFilesOption(); + if (!_vfsIsAvailable) + _defaultSyncType = NewAccount::SyncType::SYNC_ALL; + else + _defaultSyncType = NewAccount::SyncType::USE_VFS; + + _defaultSyncRoot = FolderMan::suggestSyncFolder(FolderMan::NewFolderType::SpacesSyncRoot, {}); +} + +AdvancedSettingsResult AdvancedSettingsPageController::defaultResult() +{ + AdvancedSettingsResult result; + result.syncRoot = _defaultSyncRoot; + result.syncType = _defaultSyncType; + return result; +} + +bool AdvancedSettingsPageController::validate() +{ + // this is a safety net because for unknown reasons, on windows when you hit enter to commit hand edited text + // in the QLineEdit, it ALSO triggers the finish button. No idea but this should block the finish so the user + // can see their last choice failed, and that the root has been reset to the default. + if (_lastHandEditedRootFailed) + return false; + + // normally I don't like taking values directly from the gui but in this case, it would be complete overkill to + // create a dedicated model just for these two values that only need to be collected when the user is done. + AdvancedSettingsResult result; + result.syncType = static_cast(_buttonGroup->checkedId()); + if (result.syncType == NewAccount::SyncType::SELECTIVE_SYNC) + result.syncRoot.clear(); + else + result.syncRoot = _rootDirEdit->text(); + Q_EMIT success(result); + return true; +} + +bool AdvancedSettingsPageController::validateSyncRoot(const QString &rootPath) +{ + QString errorMessageTemplate = tr("Invalid local download directory %1: %2"); + + if (rootPath == QDir::homePath()) { + _errorField->setText(errorMessageTemplate.arg(rootPath, tr("your user directory may not be chosen as the sync root."))); + return false; + } + + if (Utility::isMac()) { + QString filesystemType = FileSystem::fileSystemForPath(rootPath); + if (filesystemType != QStringLiteral("apfs")) { + _errorField->setText(errorMessageTemplate.arg(rootPath, tr("path is not located on a supported Apple File System."))); + } + } + + if (!QDir::isAbsolutePath(rootPath)) { + _errorField->setText(errorMessageTemplate.arg(rootPath, tr("path must be absolute."))); + return false; + } + + QString invalidPathErrorMessage = FolderMan::instance()->checkPathValidity(rootPath, FolderMan::NewFolderType::SpacesSyncRoot, {}); + if (!invalidPathErrorMessage.isEmpty()) { + _errorField->setText(errorMessageTemplate.arg(rootPath, invalidPathErrorMessage)); + return false; + } + + // I'm not testing the case that vfs is actually the chosen mode here as if vfs is available we should just block dirs that don't support it, + // the idea being that eg if they change the mode in the page after they select the dir, or use selective sync with vfs later, at least we know the default + // root they select here will not cause any issues down the road + // todo: use this same approach when fixing the folder wizard as part of DC-219 + if (_vfsIsAvailable) { + QString pathSupported = Vfs::pathSupportDetail(rootPath, Vfs::Mode::WindowsCfApi); + if (!pathSupported.isEmpty()) { + _errorField->setText(errorMessageTemplate.arg(rootPath, tr("selected path does not support using virtual file system. %1").arg(pathSupported))); + return false; + } + } + + return true; +} + +void AdvancedSettingsPageController::showFolderPicker() +{ + _errorField->setText({}); + _lastHandEditedRootFailed = false; + + QDir defaultDir(_defaultSyncRoot); + while (!defaultDir.exists()) + defaultDir.cdUp(); + QString chosenRoot = QFileDialog::getExistingDirectory(_page->parentWidget(), tr("Select sync root"), defaultDir.path()); + + if (chosenRoot.isEmpty()) + return; + + if (validateSyncRoot(chosenRoot)) + _rootDirEdit->setText(chosenRoot); +} + +void AdvancedSettingsPageController::onRootDirFieldEdited() +{ + QString chosenRoot = _rootDirEdit->text(); + if (!validateSyncRoot(chosenRoot)) { + _rootDirEdit->setText(_defaultSyncRoot); + _lastHandEditedRootFailed = true; + } else { + _lastHandEditedRootFailed = false; + _errorField->setText({}); + } +} + +void AdvancedSettingsPageController::syncTypeChanged(int id) +{ + bool enableFolderEditors = (id != NewAccount::SyncType::SELECTIVE_SYNC); + + _rootDirEdit->setEnabled(enableFolderEditors); + _folderButton->setEnabled(enableFolderEditors); +} +} diff --git a/src/gui/newaccountwizard/advancedsettingspagecontroller.h b/src/gui/newaccountwizard/advancedsettingspagecontroller.h new file mode 100644 index 00000000000..af94dbcfdff --- /dev/null +++ b/src/gui/newaccountwizard/advancedsettingspagecontroller.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "newaccountenums.h" +#include "wizardpagevalidator.h" + +#include + +class QWizardPage; +class QButtonGroup; +class QLineEdit; +class QLabel; +class QPushButton; + +namespace OCC { + +struct AdvancedSettingsResult +{ + NewAccount::SyncType syncType = NewAccount::SyncType::NONE; + QString syncRoot; +}; + +class AdvancedSettingsPageController : public QObject, public WizardPageValidator +{ + Q_OBJECT + +public: + explicit AdvancedSettingsPageController(QWizardPage *page, QObject *parent); + bool validate() override; + AdvancedSettingsResult defaultResult(); + +Q_SIGNALS: + void success(const OCC::AdvancedSettingsResult &result); + +private: + void gatherSyncInfo(); + void buildPage(); + void showFolderPicker(); + void onRootDirFieldEdited(); + void syncTypeChanged(int id); + bool validateSyncRoot(const QString &rootPath); + + NewAccount::SyncType _defaultSyncType = NewAccount::SyncType::NONE; + QString _defaultSyncRoot; + bool _vfsIsAvailable = false; + bool _forceVfs = false; + bool _lastHandEditedRootFailed = false; + + AdvancedSettingsResult _results; + + QWizardPage *_page = nullptr; + QButtonGroup *_buttonGroup = nullptr; + QLineEdit *_rootDirEdit = nullptr; + QPushButton *_folderButton; + QLabel *_errorField; +}; +} +Q_DECLARE_METATYPE(OCC::AdvancedSettingsResult) +// this type id is throwaway, we just use it to ensure we declare the meta type only ONCE +// also this is only required to use the type cross thread, or with QSignalSpy during testing +static const int advancedSettingsResultTypeId = qRegisterMetaType(); diff --git a/src/gui/newaccountwizard/authsuccesspagecontroller.cpp b/src/gui/newaccountwizard/authsuccesspagecontroller.cpp new file mode 100644 index 00000000000..1089e9b64cb --- /dev/null +++ b/src/gui/newaccountwizard/authsuccesspagecontroller.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "authsuccesspagecontroller.h" + +#include "theme.h" + +#include +#include +#include +#include +#include + +namespace OCC { + + +AuthSuccessPageController::AuthSuccessPageController(QWizardPage *page, QObject *parent) + : QObject{parent} + , _page(page) +{ + buildPage(); +} + +void AuthSuccessPageController::buildPage() +{ + if (!_page) + return; + + QString appName = Theme::instance()->appNameGUI(); + + QLabel *logoLabel = new QLabel({}, _page); + logoLabel->setPixmap(Theme::instance()->wizardHeaderLogo().pixmap(200, 100)); + logoLabel->setAlignment(Qt::AlignCenter); + logoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + logoLabel->setAccessibleName(tr("Application Name Logo %1").arg(appName)); + + QLabel *instructionLabel = new QLabel(tr("You're all set! Open %1 and get started.").arg(appName), _page); + QFont font = instructionLabel->font(); + font.setPixelSize(20); + font.setWeight(QFont::DemiBold); + instructionLabel->setFont(font); + instructionLabel->setWordWrap(true); + instructionLabel->setAlignment(Qt::AlignCenter); + instructionLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + QLabel *footerLogoLabel = nullptr; + if (!Theme::instance()->wizardFooterLogo().isNull()) { + footerLogoLabel = new QLabel({}, _page); + footerLogoLabel->setPixmap(Theme::instance()->wizardFooterLogo().pixmap(100, 52)); + footerLogoLabel->setAlignment(Qt::AlignCenter); + footerLogoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + // this is not the same logo as the header logo but we have no idea what this logo could be. + // this is the designer's suggestion so far + footerLogoLabel->setAccessibleName(tr("Additional logo defined by the organization")); + } + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(50, 0, 50, 0); + layout->setSpacing(12); + layout->addStretch(1); + layout->addWidget(logoLabel, Qt::AlignCenter); + layout->addSpacing(16); + layout->addWidget(instructionLabel, Qt::AlignCenter); + + if (footerLogoLabel) + layout->addWidget(footerLogoLabel, Qt::AlignCenter); + layout->addStretch(1); + _page->setLayout(layout); +} + +bool AuthSuccessPageController::validate() +{ + return true; +} +} diff --git a/src/gui/newaccountwizard/authsuccesspagecontroller.h b/src/gui/newaccountwizard/authsuccesspagecontroller.h new file mode 100644 index 00000000000..bd248db009e --- /dev/null +++ b/src/gui/newaccountwizard/authsuccesspagecontroller.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +#include "wizardpagevalidator.h" + +class QWizardPage; + +namespace OCC { +class AuthSuccessPageController : public QObject, public WizardPageValidator +{ + Q_OBJECT +public: + explicit AuthSuccessPageController(QWizardPage *page, QObject *parent); + bool validate() override; + +private: + void buildPage(); + + QWizardPage *_page = nullptr; +}; +} diff --git a/src/gui/newaccountwizard/newaccountbuilder.cpp b/src/gui/newaccountwizard/newaccountbuilder.cpp new file mode 100644 index 00000000000..4f46e96414e --- /dev/null +++ b/src/gui/newaccountwizard/newaccountbuilder.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "newaccountbuilder.h" + +#include "accountmanager.h" +#include "creds/abstractcredentials.h" + + +namespace OCC { + +NewAccountBuilder::NewAccountBuilder(const NewAccountModel &model, QObject *parent) + : QObject{parent} +{ + _account = AccountManager::instance()->createAccount(model); + _syncType = model.syncType(); +} + +void NewAccountBuilder::buildAccount() +{ + Q_ASSERT(_account); + // the account manager triggers the first checkConnection in the state when it's added - there should be no need to call it again explicitly + // either in the application or the folderman, as was the case previously + _accountState = AccountManager::instance()->addAccount(_account); + connect(_accountState, &AccountState::stateChanged, this, &NewAccountBuilder::onAccountStateChanged); + _accountState->setSettingUp(true); +} + +void NewAccountBuilder::onAccountStateChanged(AccountState::State state) +{ + if (state == AccountState::Connected) { + disconnect(_accountState, nullptr, this, nullptr); + completeAccountSetup(); + } + // On the off chance that the server has somehow broken down between first auth during the wizard's info collection process, and "now", when the + // user has accepted the wizard and the actual account is created, we want to remove the account and notify there was a problem. + else if (state == AccountState::ServiceUnavailable || state == AccountState::NetworkError || state == AccountState::MaintenanceMode + || state == AccountState::ConfigurationError) { + disconnect(_accountState, nullptr, this, nullptr); + QString serverError = _accountState->connectionErrors().isEmpty() ? tr("no error details are available") : _accountState->connectionErrors().first(); + QString error = tr("Unable to connect to server during account creation: %1.").arg(serverError); + Q_EMIT unableToCompleteAccountCreation(error); + // do this last as the wizard is going to auto-pop as soon as the account is deleted :/ + AccountManager::instance()->deleteAccount(_accountState); + } +} + +void NewAccountBuilder::completeAccountSetup() +{ + Q_ASSERT(_account); + + AccountManager::instance()->saveAccount(_account); + + // the creds should always be ready when creating new account as we don't get here unless the auth and all other checks succeeded + // the check is perfunctory. + if (_account->credentials()->ready()) { + // this really should be called by the creds, internally. Unfortunately there is no good way to do that yet. I think the account + // should call a creds->finishSetup in account::setCredentials or something like that to ensure the creds are fully viable once the account exists. + // unfortuantely, at the moment the account is not fully configured at the point that we call setCredentails, due to the upside + // down deps with accountState, which is why the account state has to check "was ever fetched" on the creds, then run the + // keychain retrieval for the first time if necessary = the creds are FINALLY ready to go. Someday we will be able to correct this, sadly, not now. + _account->credentials()->persist(); + // emitting credentialsFetched is required because we didn't use the credentials to auth the new user/account. In theory we should have a + // way to trigger the creds to emit this itself, but this is a future topic. same with calling persist. todo for future. + emit _account->credentialsFetched(); + } + + // if we are doing sync all or vfs the folder man should configure all the folders automatically + if (_syncType != NewAccount::SyncType::SELECTIVE_SYNC) { + bool useVfs = (_syncType == NewAccount::SyncType::USE_VFS); + emit requestSetUpSyncFoldersForAccount(_accountState, useVfs); + } else { + emit requestLoadSpacesOnly(_accountState); + } + // remove the placeholder state in the gui - this impl is not great as the account state can't decide for itself whether it's setting up + // or not, but for now, it's what we have + _accountState->setSettingUp(false); + + // if selective sync is selected, we need to run the folder wizard asap. + if (_syncType == NewAccount::SyncType::SELECTIVE_SYNC) + Q_EMIT requestFolderWizard(_account->uuid()); + + deleteLater(); +} +} diff --git a/src/gui/newaccountwizard/newaccountbuilder.h b/src/gui/newaccountwizard/newaccountbuilder.h new file mode 100644 index 00000000000..4c63ba84dfb --- /dev/null +++ b/src/gui/newaccountwizard/newaccountbuilder.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "newaccountmodel.h" +#include + +#include "libsync/account.h" +#include "accountstate.h" + +#include "newaccountenums.h" + +namespace OCC { + + +class NewAccountBuilder : public QObject +{ + Q_OBJECT +public: + explicit NewAccountBuilder(const NewAccountModel &model, QObject *parent = nullptr); + + void buildAccount(); + +Q_SIGNALS: + void requestSetUpSyncFoldersForAccount(OCC::AccountState *, bool useVfs); + void requestLoadSpacesOnly(OCC::AccountState *); + void requestFolderWizard(QUuid accountId); + void unableToCompleteAccountCreation(const QString &error); + +private: + void onAccountStateChanged(AccountState::State state); + void completeAccountSetup(); + + Account *_account = nullptr; + AccountState *_accountState = nullptr; + NewAccount::SyncType _syncType = NewAccount::SyncType::NONE; +}; +} diff --git a/src/gui/newaccountwizard/newaccountenums.h b/src/gui/newaccountwizard/newaccountenums.h new file mode 100644 index 00000000000..86f72eda369 --- /dev/null +++ b/src/gui/newaccountwizard/newaccountenums.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +namespace OCC::NewAccount { +Q_NAMESPACE + +// I am not making this a strongly typed enum class as it's used to support the radio button ids in the advanced settings gui. +// an old fashioned enum does not require as many casts back and forth, which for this use case is a good thing. +enum SyncType { NONE, USE_VFS, SYNC_ALL, SELECTIVE_SYNC }; + +Q_ENUM_NS(SyncType) + +} diff --git a/src/gui/newaccountwizard/newaccountmodel.cpp b/src/gui/newaccountwizard/newaccountmodel.cpp new file mode 100644 index 00000000000..8fe0db155d4 --- /dev/null +++ b/src/gui/newaccountwizard/newaccountmodel.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "newaccountmodel.h" + +namespace OCC { + +NewAccountModel::NewAccountModel(QObject *parent) + : QObject(parent) +{ +} + +QUrl NewAccountModel::serverUrl() const +{ + return _serverUrl; +} + +void NewAccountModel::setServerUrl(const QUrl &newServerUrl) +{ + // consider clearing auth info any time the url changes. it might be overkill though + if (_serverUrl == newServerUrl) + return; + _serverUrl = newServerUrl; + Q_EMIT serverUrlChanged(_serverUrl); +} + +QUrl NewAccountModel::webfingerAuthenticationUrl() const +{ + return _webfingerAuthenticationUrl; +} + +void NewAccountModel::setWebfingerAuthenticationUrl(const QUrl &newWebfingerAuthenticationUrl) +{ + if (_webfingerAuthenticationUrl == newWebfingerAuthenticationUrl) + return; + _webfingerAuthenticationUrl = newWebfingerAuthenticationUrl; + Q_EMIT webfingerAuthenticationUrlChanged(_webfingerAuthenticationUrl); +} + +QUrl NewAccountModel::webfingerUserInfoUrl() const +{ + return _webfingerUserInfoUrl; +} + +void NewAccountModel::setWebfingerUserInfoUrl(const QUrl &newWebfingerUserUrl) +{ + if (_webfingerUserInfoUrl == newWebfingerUserUrl) + return; + _webfingerUserInfoUrl = newWebfingerUserUrl; + Q_EMIT webfingerUserInfoUrlChanged(_webfingerUserInfoUrl); +} + +QSet NewAccountModel::trustedCertificates() const +{ + return _trustedCertificates; +} + +void NewAccountModel::setTrustedCertificates(const QSet &newTrustedCertificates) +{ + if (_trustedCertificates == newTrustedCertificates) + return; + _trustedCertificates = newTrustedCertificates; + Q_EMIT trustedCertificatesChanged(_trustedCertificates); +} + +QString NewAccountModel::displayName() const +{ + return _displayName; +} + +void NewAccountModel::setDisplayName(const QString &newDisplayName) +{ + if (_displayName == newDisplayName) + return; + _displayName = newDisplayName; + Q_EMIT displayNameChanged(_displayName); +} + +QString NewAccountModel::davUser() const +{ + return _davUser; +} + +void NewAccountModel::setDavUser(const QString &newDavUser) +{ + if (_davUser == newDavUser) + return; + _davUser = newDavUser; + Q_EMIT davUserChanged(_davUser); +} + +QString NewAccountModel::authToken() const +{ + return _authToken; +} + +void NewAccountModel::setAuthToken(const QString &newAuthToken) +{ + if (_authToken == newAuthToken) + return; + _authToken = newAuthToken; + Q_EMIT authTokenChanged(_authToken); +} + +QString NewAccountModel::refreshToken() const +{ + return _refreshToken; +} + +void NewAccountModel::setRefreshToken(const QString &newRefreshToken) +{ + if (_refreshToken == newRefreshToken) + return; + _refreshToken = newRefreshToken; + Q_EMIT refreshTokenChanged(_refreshToken); +} + +Capabilities NewAccountModel::capabilities() const +{ + return _capabilities; +} + +void NewAccountModel::setCapabilities(const Capabilities &newCapabilities) +{ + // if (_capabilities == newCapabilities) + // return; + _capabilities = newCapabilities; + Q_EMIT capabilitiesChanged(_capabilities); +} + +QString NewAccountModel::defaultSyncRoot() const +{ + return _defaultSyncRoot; +} + +void NewAccountModel::setDefaultSyncRoot(const QString &newDefaultSyncRoot) +{ + if (_defaultSyncRoot != newDefaultSyncRoot) { + _defaultSyncRoot = newDefaultSyncRoot; + Q_EMIT defaultSyncRootChanged(_defaultSyncRoot); + } +} + +NewAccount::SyncType NewAccountModel::syncType() const +{ + return _syncType; +} + +void NewAccountModel::setSyncType(NewAccount::SyncType newSyncType) +{ + if (_syncType == newSyncType) + return; + _syncType = newSyncType; + Q_EMIT syncTypeChanged(_syncType); +} + + +QUrl NewAccountModel::effectiveAuthenticationServerUrl() const +{ + if (!_webfingerAuthenticationUrl.isEmpty()) + return _webfingerAuthenticationUrl; + return _serverUrl; +} + +QUrl NewAccountModel::effectiveUserInfoUrl() const +{ + if (!_webfingerUserInfoUrl.isEmpty()) + return _webfingerUserInfoUrl; + else + return _serverUrl; +} + +bool NewAccountModel::isComplete() const +{ + bool authInfoComplete = !_davUser.isEmpty() && !_authToken.isEmpty() && !_refreshToken.isEmpty(); + bool syncInfoComplete = false; + if (_syncType != NewAccount::SyncType::NONE) { + if (_syncType == NewAccount::SyncType::SELECTIVE_SYNC && _defaultSyncRoot.isEmpty()) + syncInfoComplete = true; + else if ((_syncType == NewAccount::SyncType::USE_VFS || _syncType == NewAccount::SyncType::SYNC_ALL) && !_defaultSyncRoot.isEmpty()) + syncInfoComplete = true; + } + + bool complete = authInfoComplete && syncInfoComplete && effectiveUserInfoUrl().isValid() && _capabilities.isValid(); + return complete; +} +} diff --git a/src/gui/newaccountwizard/newaccountmodel.h b/src/gui/newaccountwizard/newaccountmodel.h new file mode 100644 index 00000000000..3c039dc9690 --- /dev/null +++ b/src/gui/newaccountwizard/newaccountmodel.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "capabilities.h" +#include "newaccountenums.h" + +#include +#include +#include +#include +#include + + +namespace OCC { +/** + * @brief The NewAccountModel class is used to store data that is accumulated in the process of creating a new account + * + * Our models should just be "dumb containers". Any business logic related to these values needs to be handled in some kind of controller element, + * in this case the NewAccountWizardController. + * Business logic does not belong in any gui model! + */ +class NewAccountModel : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QUrl serverUrl READ serverUrl WRITE setServerUrl NOTIFY serverUrlChanged) + Q_PROPERTY(QUrl webfingerAuthenticationUrl READ webfingerAuthenticationUrl WRITE setWebfingerAuthenticationUrl NOTIFY webfingerAuthenticationUrlChanged) + Q_PROPERTY(QUrl webfingerUserInfoUrl READ webfingerUserInfoUrl WRITE setWebfingerUserInfoUrl NOTIFY webfingerUserInfoUrlChanged) + Q_PROPERTY(QSet trustedCertificates READ trustedCertificates WRITE setTrustedCertificates NOTIFY trustedCertificatesChanged) + + Q_PROPERTY(QString displayName READ displayName WRITE setDisplayName NOTIFY displayNameChanged) + Q_PROPERTY(QString davUser READ davUser WRITE setDavUser NOTIFY davUserChanged) + + Q_PROPERTY(QString authToken READ authToken WRITE setAuthToken NOTIFY authTokenChanged) + Q_PROPERTY(QString refreshToken READ refreshToken WRITE setRefreshToken NOTIFY refreshTokenChanged) + + Q_PROPERTY(QString defaultSyncRoot READ defaultSyncRoot WRITE setDefaultSyncRoot NOTIFY defaultSyncRootChanged) + Q_PROPERTY(NewAccount::SyncType syncType READ syncType WRITE setSyncType NOTIFY syncTypeChanged) + + Q_PROPERTY(Capabilities capabilities READ capabilities WRITE setCapabilities NOTIFY capabilitiesChanged) + +public: + NewAccountModel(QObject *parent); + + /** The serverUrl is the first url we process in the account wizard. It may be provided by the user or the theme. + * If this url supports webfinger, it is used get the authentication url as well as the user info endpoint url which becomes the de facto + * url for all user related information and activities. + * If the url does not support webfinger, this is an all purpose url used for both authentication and all user related activities. + */ + QUrl serverUrl() const; + void setServerUrl(const QUrl &newServerUrl); + + /** The webfingerAuthenticationUrl is the url used for authentication if webfinger is in play. + * It is used for authentication only! (eg as the url argument for the initial oauth routine) + */ + QUrl webfingerAuthenticationUrl() const; + void setWebfingerAuthenticationUrl(const QUrl &newWebfingerAuthenticationUrl); + + /** the webfingerUserInfoUrl is the effective url passed to the account if webfinger lookup retrieved a non-empty list of user urls + * If the list is non-empty, we always take the first element in the list as the webfingerUserInfoUrl, and when the account is built, + * this becomes the effective url for the account. + */ + QUrl webfingerUserInfoUrl() const; + void setWebfingerUserInfoUrl(const QUrl &newWebfingerUserUrl); + + /** certificates may be provided by the server during the authentication routine. If they are provided the user must accept the certificates + * and we store them for future use on that server/account. + * If the server provides certificates and the user rejects them, the account creation cannot continue. + */ + QSet trustedCertificates() const; + void setTrustedCertificates(const QSet &newTrustedCertificates); + + QString displayName() const; + void setDisplayName(const QString &newDisplayName); + + QString davUser() const; + void setDavUser(const QString &newDavUser); + + QString authToken() const; + void setAuthToken(const QString &newAuthToken); + + QString refreshToken() const; + void setRefreshToken(const QString &newRefreshToken); + + Capabilities capabilities() const; + void setCapabilities(const Capabilities &newCapabilities); + + QString defaultSyncRoot() const; + void setDefaultSyncRoot(const QString &newDefaultSyncRoot); + + NewAccount::SyncType syncType() const; + void setSyncType(NewAccount::SyncType newSyncType); + + // convenience function that returns the webfingerAuthenticationUrl if it is non-empty, else it returns the serverUrl + QUrl effectiveAuthenticationServerUrl() const; + + // convenience function that returns the webfingerUserInfoUrl if it is nonempty, else it returns the serverUrl + QUrl effectiveUserInfoUrl() const; + + // verify that all required values are present in the model - this should be called at the "end" before passing the model to the account + // manager or other element that can create an account from the model values. + bool isComplete() const; + +Q_SIGNALS: + void serverUrlChanged(const QUrl &newUrl); + void webfingerAuthenticationUrlChanged(const QUrl &newWebfingerAuthenticationUrl); + void webfingerUserInfoUrlChanged(const QUrl &newWebfingerUserUrl); + void trustedCertificatesChanged(QSet trustedCertificates); + void displayNameChanged(QString displayName); + void davUserChanged(QString davUser); + void authTokenChanged(QString authToken); + void refreshTokenChanged(QString refreshToken); + void capabilitiesChanged(OCC::Capabilities capabilities); + void defaultSyncRootChanged(QString defaultSyncRoot); + void syncTypeChanged(NewAccount::SyncType syncType); + +private: + QUrl _serverUrl; + QUrl _webfingerAuthenticationUrl; + QUrl _webfingerUserInfoUrl; + QSet _trustedCertificates; + QString _syncRootDir; + QString _displayName; + QString _davUser; + QString _authToken; + QString _refreshToken; + Capabilities _capabilities{QUrl(), {}}; + QString _defaultSyncRoot; + NewAccount::SyncType _syncType = NewAccount::SyncType::NONE; +}; +} diff --git a/src/gui/newaccountwizard/newaccountwizard.cpp b/src/gui/newaccountwizard/newaccountwizard.cpp new file mode 100644 index 00000000000..06f1230d736 --- /dev/null +++ b/src/gui/newaccountwizard/newaccountwizard.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "newaccountwizard.h" + +#include "wizardpagevalidator.h" + +#include +#include + +namespace OCC { + +NewAccountWizard::NewAccountWizard(QWidget *parent) + : QWizard(parent) +{ +} + +bool NewAccountWizard::validateCurrentPage() +{ + QWizardPage *page = currentPage(); + if (page && _pageValidators.contains(page)) { + // fix the problem that user can click next button multiple times + // it should auto re-enable on forward, back, but won't let a successful validation be repeated on same page + button(WizardButton::NextButton)->setEnabled(false); + bool success = _pageValidators[page]->validate(); + if (!success) + button(WizardButton::NextButton)->setEnabled(true); + return success; + } + return false; +} + +int NewAccountWizard::addPage(QWizardPage *page, WizardPageValidator *validator) +{ + _pageValidators.insert(page, validator); + return QWizard::addPage(page); +} + +void NewAccountWizard::reject() +{ + QMessageBox::StandardButton result = QMessageBox::question( + this, tr("Cancel Setup"), tr("Do you really want to cancel the account setup?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (result == QMessageBox::Yes) + QWizard::reject(); +} +} diff --git a/src/gui/newaccountwizard/newaccountwizard.h b/src/gui/newaccountwizard/newaccountwizard.h new file mode 100644 index 00000000000..5da6f666a55 --- /dev/null +++ b/src/gui/newaccountwizard/newaccountwizard.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +namespace OCC { +class WizardPageValidator; + +class NewAccountWizard : public QWizard +{ + Q_OBJECT + +public: + NewAccountWizard(QWidget *parent); + + bool validateCurrentPage() override; + + /** overload of QWizard::addPage which also accepts the associated page validator */ + int addPage(QWizardPage *page, WizardPageValidator *validator); + +public Q_SLOTS: + + void reject() override; + +private: + QHash _pageValidators; +}; +} diff --git a/src/gui/newaccountwizard/newaccountwizardcontroller.cpp b/src/gui/newaccountwizard/newaccountwizardcontroller.cpp new file mode 100644 index 00000000000..0cc4acdeabc --- /dev/null +++ b/src/gui/newaccountwizard/newaccountwizardcontroller.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "newaccountwizardcontroller.h" + +#include "accessmanager.h" +#include "advancedsettingspagecontroller.h" +#include "application.h" +#include "authsuccesspagecontroller.h" +#include "common/utility.h" +#include "newaccountmodel.h" +#include "newaccountwizard.h" +#include "oauthpagecontroller.h" +#include "theme.h" +#include "urlpagecontroller.h" + +#include + +namespace OCC { + +NewAccountWizardController::NewAccountWizardController(NewAccountModel *model, NewAccountWizard *view, QObject *parent) + : QObject{parent} + , _model(model) + , _wizard(view) +{ + _accessManager = new AccessManager(this); + setupWizard(); + buildPages(); + buildButtonLayouts(); + connectWizard(); +} + +void NewAccountWizardController::setupWizard() +{ + if (!_wizard) { + return; + } + + updateColors(); + + _wizard->setFixedSize(600, 450); + _wizard->setWizardStyle(QWizard::ModernStyle); + _wizard->setWindowTitle(tr("Add account…")); + _wizard->setObjectName("NewAccountWizard"); + + QWizard::WizardOptions origOptions = _wizard->options(); + _wizard->setOptions(origOptions | QWizard::IndependentPages | QWizard::NoBackButtonOnStartPage); + // no cancel button is set by default on mac with the original options. just remove it to bring the cancel button back + _wizard->setOption(QWizard::NoCancelButton, false); + // same for the default button + _wizard->setOption(QWizard::NoDefaultButton, false); + + _wizard->setButtonText(QWizard::BackButton, tr("Back")); + _wizard->setButtonText(QWizard::CustomButton1, tr("Advanced")); + _wizard->setOption(QWizard::HaveCustomButton1, true); +} + +void NewAccountWizardController::buildPages() +{ + if (!_wizard) + return; + + QWizardPage *urlPage = new QWizardPage(_wizard); + UrlPageController *urlController = new UrlPageController(urlPage, _accessManager, this); + connect(urlController, &UrlPageController::success, this, &NewAccountWizardController::onUrlValidationCompleted); + _urlPageIndex = _wizard->addPage(urlPage, urlController); + + QWizardPage *oauthPage = new QWizardPage(_wizard); + _oauthController = new OAuthPageController(oauthPage, _accessManager, this); + connect(_oauthController, &OAuthPageController::success, this, &NewAccountWizardController::onOAuthValidationCompleted); + connect(_oauthController, &OAuthPageController::failure, this, &NewAccountWizardController::onOauthValidationFailed); + _oauthPageIndex = _wizard->addPage(oauthPage, _oauthController); + + QWizardPage *authSuccessPage = new QWizardPage(_wizard); + AuthSuccessPageController *authSuccessController = new AuthSuccessPageController(authSuccessPage, this); + _authSuccessPageIndex = _wizard->addPage(authSuccessPage, authSuccessController); + authSuccessPage->setFinalPage(true); + + QWizardPage *advancedSettingsPage = new QWizardPage(_wizard); + AdvancedSettingsPageController *advancedSettingsController = new AdvancedSettingsPageController(advancedSettingsPage, this); + connect(advancedSettingsController, &AdvancedSettingsPageController::success, this, &NewAccountWizardController::onAdvancedSettingsCompleted); + _advancedSettingsPageIndex = _wizard->addPage(advancedSettingsPage, advancedSettingsController); + advancedSettingsPage->setFinalPage(true); + // capture the default advanced settings in case the user never runs the advanced settings page + AdvancedSettingsResult defaultAdvancedSettings = advancedSettingsController->defaultResult(); + _model->setDefaultSyncRoot(defaultAdvancedSettings.syncRoot); + _model->setSyncType(defaultAdvancedSettings.syncType); +} + +void NewAccountWizardController::buildButtonLayouts() +{ + if (_wizard == nullptr) + return; + if (Utility::isMac()) { + _buttonLayouts.insert( + _urlPageIndex, QList{QWizard::Stretch, QWizard::WizardButton::CancelButton, QWizard::WizardButton::NextButton}); + _buttonLayouts.insert(_oauthPageIndex, + QList{ + QWizard::WizardButton::BackButton, QWizard::Stretch, QWizard::WizardButton::CancelButton, QWizard::WizardButton::NextButton}); + _buttonLayouts.insert(_authSuccessPageIndex, + QList{QWizard::WizardButton::BackButton, QWizard::Stretch, QWizard::WizardButton::CustomButton1, + QWizard::WizardButton::CancelButton, QWizard::WizardButton::FinishButton}); + _buttonLayouts.insert(_advancedSettingsPageIndex, + QList{ + QWizard::WizardButton::BackButton, QWizard::Stretch, QWizard::WizardButton::CancelButton, QWizard::WizardButton::FinishButton}); + } else { + _buttonLayouts.insert( + _urlPageIndex, QList{QWizard::Stretch, QWizard::WizardButton::NextButton, QWizard::WizardButton::CancelButton}); + _buttonLayouts.insert(_oauthPageIndex, + QList{ + QWizard::WizardButton::BackButton, QWizard::Stretch, QWizard::WizardButton::NextButton, QWizard::WizardButton::CancelButton}); + _buttonLayouts.insert(_authSuccessPageIndex, + QList{QWizard::WizardButton::BackButton, QWizard::Stretch, QWizard::WizardButton::CustomButton1, + QWizard::WizardButton::FinishButton, QWizard::WizardButton::CancelButton}); + _buttonLayouts.insert(_advancedSettingsPageIndex, + QList{ + QWizard::WizardButton::BackButton, QWizard::Stretch, QWizard::WizardButton::FinishButton, QWizard::WizardButton::CancelButton}); + } +} + +void NewAccountWizardController::connectWizard() +{ + if (!_wizard) + return; + + connect(_wizard, &QWizard::currentIdChanged, this, &NewAccountWizardController::onPageChanged); + // normally you'd want to check the index of the custom button passed by the signal to be sure it == 1 but we only have one custom + // button so keep it dumb. + connect(_wizard, &QWizard::customButtonClicked, this, &NewAccountWizardController::showAdvancedSettingsPage); +} + +void NewAccountWizardController::onUrlValidationCompleted(const OCC::UrlPageResults &result) +{ + if (!_wizard || !_model) + return; + + _model->setServerUrl(result.baseServerUrl); + _model->setWebfingerAuthenticationUrl(result.webfingerServiceUrl); + _model->setTrustedCertificates(result.certificates); + + // and then we have to explicitly set login url on the controller for the next page... + // yes this combination of values is gross however I do not want to pass the server and auth urls in the same function as it's way too easy to mix up the + // args! + _oauthController->setServerUrl(_model->serverUrl()); + _oauthController->setAuthenticationUrl(_model->effectiveAuthenticationServerUrl()); + _oauthController->setLookupWebfingerUrls(!_model->webfingerAuthenticationUrl().isEmpty()); + + // this is also a bit tricky: at this stage the page index is still == _urlPageIndex. However it is in the process of changing to + // the oauth page. We can't do the auto-validate on the oauth page here. + // note we should also not advance the page here as that would trigger the url page to validate AGAIN which is definitely not what we want - + // it already succeeded! + // so we just set a flag to auto-validate the oauth page and handle that when we are notified that the page actually changed -> see onPageChanged + _autoValidateOAuthPage = true; +} + +void NewAccountWizardController::onOAuthValidationCompleted(const OCC::OAuthPageResults &results) +{ + if (!_wizard || !_model) + return; + _model->setAuthToken(results.token); + _model->setRefreshToken(results.refreshToken); + _model->setDisplayName(results.displayName); + _model->setDavUser(results.userId); + _model->setCapabilities(results.capabilities); + _model->setWebfingerUserInfoUrl(results.webfingerUserUrl); + + _wizard->setCurrentId(_authSuccessPageIndex); + ocApp()->ensureVisible(); + // on mac, for unknown reasons, the main window is active after raise + _wizard->activateWindow(); +} + +void NewAccountWizardController::onOauthValidationFailed(const OCC::OAuthPageResults &results) +{ + if (!_wizard) + return; + Q_UNUSED(results); + ocApp()->ensureVisible(); + _wizard->activateWindow(); +} + +void NewAccountWizardController::onAdvancedSettingsCompleted(const OCC::AdvancedSettingsResult &result) +{ + _model->setDefaultSyncRoot(result.syncRoot); + _model->setSyncType(result.syncType); +} + +void NewAccountWizardController::showAdvancedSettingsPage() +{ + if (_advancedSettingsPageIndex > -1) + _wizard->setCurrentId(_advancedSettingsPageIndex); +} + +void NewAccountWizardController::onPageChanged(int newPageIndex) +{ + if (newPageIndex == _urlPageIndex) { + _wizard->setButtonLayout(_buttonLayouts[_urlPageIndex]); + _wizard->setButtonText(QWizard::WizardButton::NextButton, tr("Sign in")); + } else if (newPageIndex == _oauthPageIndex) { + _wizard->setButtonLayout(_buttonLayouts[_oauthPageIndex]); + _wizard->setButtonText(QWizard::WizardButton::NextButton, tr("Open sign in again")); + if (_autoValidateOAuthPage) { + _autoValidateOAuthPage = false; + _wizard->validateCurrentPage(); + } + } else if (newPageIndex == _authSuccessPageIndex) { + _wizard->setButtonLayout(_buttonLayouts[_authSuccessPageIndex]); + // for some reason - probably because this is not the last, last page ever, the focus kept coming out on the + // back button. this seems to fix it. + _wizard->button(QWizard::WizardButton::FinishButton)->setFocus(Qt::OtherFocusReason); + } else if (newPageIndex == _advancedSettingsPageIndex) { + _wizard->setButtonLayout(_buttonLayouts[_advancedSettingsPageIndex]); + } +} + +void NewAccountWizardController::updateColors() +{ + if (!_wizard) + return; + + QPalette palette = _wizard->palette(); + QColor themeBackground = Theme::instance()->wizardHeaderBackgroundColor(); + QColor themeForeground = Theme::instance()->wizardHeaderTitleColor(); + if (themeBackground.isValid()) { + palette.setColor(QPalette::Base, themeBackground); + } + if (themeForeground.isValid()) { + palette.setColor(QPalette::Text, themeForeground); + } + + _wizard->setPalette(palette); + _wizard->setAutoFillBackground(true); + +} +} diff --git a/src/gui/newaccountwizard/newaccountwizardcontroller.h b/src/gui/newaccountwizard/newaccountwizardcontroller.h new file mode 100644 index 00000000000..00bd08822a0 --- /dev/null +++ b/src/gui/newaccountwizard/newaccountwizardcontroller.h @@ -0,0 +1,108 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "advancedsettingspagecontroller.h" +#include "oauthpagecontroller.h" +#include "urlpagecontroller.h" + +#include +#include +#include + + +namespace OCC { + +class NewAccountModel; +class NewAccountWizard; +class AccessManager; + + +/** + * @brief The NewAccountWizardController class is a top level controller for the new account wizard gui. It does not contain any business logic associated with + * calculating values or retrieving information from the server, instead it orchestrates the behavior of the wizard, especially in relation to the results + * obtained by the page controllers. This controller is not a candidate for unit testing because the gui is an integral part of it's operation. + */ +class NewAccountWizardController : public QObject +{ + Q_OBJECT +public: + /** + * @brief NewAccountWizardController + * @param model must not be null. + * @param view which should not be null. The creator of the view is responsible for parenting it correctly to some other gui element + * @param parent normally this will be a pointer to whatever instantiated the controller, eg a manager or another controller, but since the + * triad is short lived, we may also manage the controller by instantiating it on the stack with no parent. + */ + explicit NewAccountWizardController(NewAccountModel *model, NewAccountWizard *view, QObject *parent); + + +protected Q_SLOTS: + // slots for handling signals from the wizard: + void onPageChanged(int newPageIndex); + void showAdvancedSettingsPage(); + + // slots for handling signals from the page controllers: + void onUrlValidationCompleted(const OCC::UrlPageResults &result); + + void onOAuthValidationCompleted(const OCC::OAuthPageResults &results); + // we need to raise the gui when oauth validation fails + void onOauthValidationFailed(const OCC::OAuthPageResults &results); + + void onAdvancedSettingsCompleted(const OCC::AdvancedSettingsResult &result); + + +private: + /** configures the wizard with proper settings */ + void setupWizard(); + + /** builds the page controller/page widget pairs */ + void buildPages(); + + void buildButtonLayouts(); + + // also key to setting up the wizard is that if we make a field mandatory (eg the url QLineEdit) and add a validator, the wizard + // will enable the "next" button only when the validator returns true for hasAcceptableInput. IMO this is mandatory as the next button is + // currently enabled even if the url field is empty + + /** connects "top level" wizard signals to local slots as needed */ + void connectWizard(); + + /** connects the model signals to local slots, as needed */ + void connectModel(); + + /** updates the QPalette of the wizard to use the theme colors, if they are valid */ + void updateColors(); + + /** this controller owns the access manager that will be used by the page controllers to run various network requests */ + AccessManager *_accessManager = nullptr; + + // using QPointer for the injected dependencies. For an impl like this it's more of a "best practice" formality as the whole + // bundle of stuff has a shared lifetime. + QPointer _model = nullptr; + QPointer _wizard = nullptr; + + // this is the only controller we need to explicitly keep our eye on as we need to give it values to start the oauth step + OAuthPageController *_oauthController = nullptr; + bool _autoValidateOAuthPage = false; + + QHash> _buttonLayouts; + + int _urlPageIndex = -1; + int _oauthPageIndex = -1; + int _authSuccessPageIndex = -1; + int _advancedSettingsPageIndex = -1; +}; +} diff --git a/src/gui/newaccountwizard/oauthpagecontroller.cpp b/src/gui/newaccountwizard/oauthpagecontroller.cpp new file mode 100644 index 00000000000..9fc321f7257 --- /dev/null +++ b/src/gui/newaccountwizard/oauthpagecontroller.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "oauthpagecontroller.h" + +#include "accessmanager.h" +#include "accountmanager.h" +#include "config/appconfig.h" +#include "networkadapters/fetchcapabilitiesadapter.h" +#include "networkadapters/userinfoadapter.h" +#include "networkadapters/webfingerlookupadapter.h" +#include "template.h" +#include "theme.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace OCC { + +OAuthPageController::OAuthPageController(QWizardPage *page, AccessManager *accessManager, QObject *parent) + : QObject{parent} + , _page(page) + , _accessManager(accessManager) + , _oauth(nullptr) +{ + buildPage(); + connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &OAuthPageController::clipboardChanged); +} + +void OAuthPageController::buildPage() +{ + if (!_page) + return; + + QString appName = Theme::instance()->appNameGUI(); + + QLabel *logoLabel = new QLabel({}, _page); + logoLabel->setPixmap(Theme::instance()->wizardHeaderLogo().pixmap(200, 100)); + logoLabel->setAlignment(Qt::AlignCenter); + logoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + logoLabel->setAccessibleName(tr("Application Name Logo %1").arg(appName)); + + QLabel *instructionLabel = + new QLabel(tr("Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address."), _page); + QFont font = instructionLabel->font(); + font.setPixelSize(14); + instructionLabel->setFont(font); + instructionLabel->setWordWrap(true); + instructionLabel->setAlignment(Qt::AlignCenter); + instructionLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + _urlField = new QLabel(_page); + _urlField->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + _urlField->setEnabled(false); + _urlField->setAccessibleName(tr("Sign in URL")); // Prevent screen readers from reading the full URL!!! + _urlField->setAccessibleDescription(tr("To copy the sign in URL to the clipboard, use the copy button")); + + _copyButton = new QPushButton(copyIcon(), QString(), _page); + _copyButton->setObjectName("CopyUrlToClipboardButton"); + _copyButton->setFlat(true); + _copyButton->setContentsMargins(0, 0, 0, 0); + _copyButton->setFixedSize(_urlField->height(), _urlField->height()); + _copyButton->setAccessibleDescription(tr("Copy URL to sign in")); + _copyButton->installEventFilter(this); + connect(_copyButton, &QPushButton::clicked, this, &OAuthPageController::copyUrlClicked); + + _errorField = new QLabel(QString(), _page); + QPalette errorPalette = _errorField->palette(); + errorPalette.setColor(QPalette::Text, Qt::red); + _errorField->setPalette(errorPalette); + _errorField->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _errorField->setWordWrap(true); + _errorField->setAlignment(Qt::AlignLeft); + _errorField->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + + QLabel *footerLogoLabel = nullptr; + if (!Theme::instance()->wizardFooterLogo().isNull()) { + footerLogoLabel = new QLabel({}, _page); + footerLogoLabel->setPixmap(Theme::instance()->wizardFooterLogo().pixmap(100, 52)); + footerLogoLabel->setAlignment(Qt::AlignCenter); + footerLogoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + footerLogoLabel->setAccessibleName(tr("Additional logo defined by the organization")); + } + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(50, 0, 50, 0); + layout->setSpacing(12); + layout->addStretch(1); + layout->addWidget(logoLabel, Qt::AlignCenter); + layout->addSpacing(16); + layout->addWidget(instructionLabel, Qt::AlignCenter); + + QHBoxLayout *urlAreaLayout = new QHBoxLayout(); + urlAreaLayout->setContentsMargins(0, 0, 0, 0); + urlAreaLayout->setSpacing(0); + urlAreaLayout->addWidget(_urlField, Qt::AlignLeft); + urlAreaLayout->addWidget(_copyButton); + layout->addLayout(urlAreaLayout, Qt::AlignCenter); + + layout->addWidget(_errorField, Qt::AlignLeft); + + if (footerLogoLabel) + layout->addWidget(footerLogoLabel, Qt::AlignCenter); + layout->addStretch(1); + _page->setLayout(layout); + + // seed the copy button tooltip for the first go + clipboardChanged(); +} + +void OAuthPageController::handleError(const QString &error) +{ + _errorField->setText(error); + _results.error = error; + Q_EMIT failure(_results); +} + +QIcon OAuthPageController::copyIcon() +{ + // this is required because the background color of the wizard pages can be set via theme and we can't pick an icon + // color that "fits" based on the current + // normally we would want to cache this but for this very special one off case I don't think it makes sense + const QString iconPath = QStringLiteral(":/client/resources/core/copy.svg"); + QPalette pagePalette = _page->palette(); + const QString color = pagePalette.color(QPalette::Text).name(); + QByteArray data = Resources::Template::renderTemplateFromFile(iconPath, {{QStringLiteral("color"), color}}).toUtf8(); + QBuffer buffer(&data); + QImageReader iconReader(&buffer, "svg"); + return QIcon(QPixmap::fromImageReader(&iconReader)); +} + +void OAuthPageController::setLookupWebfingerUrls(bool lookup) +{ + _lookupWebfingerUrls = lookup; +} + +void OAuthPageController::setServerUrl(const QUrl &url) +{ + _serverUrl = url; +} + +void OAuthPageController::setAuthenticationUrl(const QUrl &url) +{ + _authUrl = url; +} + +void OAuthPageController::copyUrlClicked() +{ + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(_authEndpoint); +} + +// I was going to implement an event filter for the tooltip but I think the positioning is risky given our previous issues with popup stuff +// on mac and/or linux. This is also the simplest, least error prone way to do it imo. +void OAuthPageController::clipboardChanged() +{ + QClipboard *clipboard = QGuiApplication::clipboard(); + if (clipboard->text() == _authEndpoint) + _copyButton->setToolTip(tr("URL copied")); + else + _copyButton->setToolTip(tr("Copy URL")); +} + +bool OAuthPageController::validate() +{ + // this should have already been cleaned up after the auth routine but who knows. Never hurts to be sure. + if (_oauth) { + delete _oauth; + _oauth = nullptr; + } + // this is a little bit tricky. Because we "auto advance" the wizard to the next page when the oauth step has succeeded, validate + // will be called *twice* + // first when the page change triggers the authentication step in the browser, then a second time when we auto-advance after the main controller has + // received the success signal. + // In the first call to validate, we always return false because we don't yet know if the asynchronous oauth routine has succeeded. + // the slot for handling the authentication result sets _oauthCompleted to true when all checks have passed, + // so the second call to validate triggered by programmatically advancing the page, returns true. + // we always reset the _oauthCompleted flag after success in case the user wants to go back to reauthenticate for whatever reason, they will start from + // scratch. + if (_oauthCompleted) { + _oauthCompleted = false; + return true; + } + + _results = {}; + _errorField->clear(); + _authEndpoint.clear(); + _urlField->clear(); + + AppConfig systemConfig; + _oauth = new OAuth(_authUrl, {}, systemConfig.openIdConfig(), _accessManager.get(), this); + // if we ever need to split out the auth link calculation, it's coming from fetchWellKnown which is a subset of + // the "full" authentication routine in the oauth impl + connect(_oauth, &OAuth::authorisationLinkChanged, this, &OAuthPageController::authUrlReady); + connect(_oauth, &OAuth::result, this, &OAuthPageController::handleOauthResult); + _oauth->startAuthentication(); + return false; +} + +void OAuthPageController::authUrlReady() +{ + _authEndpoint = _oauth->authorisationLink().toString(QUrl::FullyEncoded); + + // I hit this when demo was down - browser showed "We're having trouble connecting to the login service. + // If the problem continues, please contact support." + if (_authEndpoint.isEmpty()) { + handleError(tr("Unable to connect to the login service. If the problem continues, please contact support.")); + return; + } + + QFontMetrics metrics(_urlField->font()); + QString elidedText = metrics.elidedText(_authEndpoint, Qt::ElideRight, _urlField->width()); + _urlField->setText(elidedText); + // try to force an immediate repaint before the browser pops, so there is no chance that the oauth page appears "incomplete" + // for a moment + // note in my experience mac does not always honor repaint()s + _page->repaint(); + _oauth->openBrowser(); +} + +void OAuthPageController::handleOauthResult(OAuth::Result result, const QString &token, const QString &refreshToken) +{ + QString extraHelp = tr("Please copy the authentication URL using the button above, and provide this information to support."); + switch (result) { + case OAuth::Result::LoggedIn: { + _results.token = token; + _results.refreshToken = refreshToken; + // special note: if the original server url does not support webfinger, the _authUrl will equal the _serverUrl + // HOWEVER, we can't use this fact to determine whether webfinger is in play as some webfinger impls return the original + // _serverUrl (eg demo) as the authentication url and some don't (eg infinite). + if (_lookupWebfingerUrls) { + // it is important to use the original server url for the webfinger lookup because the webfinger authentication url *only* knows how to authenticate + // the webfinger service actually lives in the original server url so we have to use that to look up the webfinger user info endpoints. + WebFingerLookupAdapter lookup(_accessManager, token, _serverUrl); + const WebFingerLookupResult webfingerResult = lookup.getResult(); + if (!webfingerResult.success()) { + handleError(tr("Failed to look up WebFinger instances: %1").arg(webfingerResult.error)); + return; + } else { + if (!webfingerResult.urls.isEmpty()) + _results.webfingerUserUrl = webfingerResult.urls[0]; + } + } + + // if we have successfully found a user info endpoint url using webfinger, that becomes our de facto url for further account related activities. + // if webfinger is not in play we are just using the original server url. + const QUrl userInfoUrl = _results.webfingerUserUrl.isEmpty() ? _serverUrl : _results.webfingerUserUrl; + + UserInfoAdapter infoAdapter(_accessManager, _results.token, userInfoUrl); + UserInfoResult infoResult = infoAdapter.getResult(); + + if (infoResult.success()) { + if (AccountManager::instance()->accountForLoginExists(userInfoUrl, infoResult.userId)) { + handleError(tr("You are already connected to an account with these credentials.")); + return; + } else { + _results.displayName = infoResult.displayName; + _results.userId = infoResult.userId; + } + } else { + handleError(infoResult.error); + return; + } + + // Finally, get the capabilities so we can block oc10 accounts. Checking for spaces support is not + // great and this should be refined, but for now it's effective. + FetchCapabilitiesAdapter fetchCapabilities(_accessManager, _results.token, userInfoUrl); + FetchCapabilitiesResult capabilitiesResult = fetchCapabilities.getResult(); + if (!capabilitiesResult.success()) { + // I don't think we want to display the core error message as it's stuff like json errors and not + // useful to the user but we can change this after we have the discussion about error messages + handleError(tr("Unable to retrieve capabilities from server.")); + break; + } + if (!capabilitiesResult.capabilities.spacesSupport().enabled) { + handleError(tr("The server is not supported by this client.")); + break; + } else + _results.capabilities = capabilitiesResult.capabilities; + + _oauthCompleted = true; + Q_EMIT success(_results); + break; + } + case OAuth::Result::Error: { + handleError(tr("Error while trying to log in to OAuth2-enabled server. %1").arg(extraHelp)); + break; + } + // todo: this is returned when the oauth routine can't open the browser with that url - this error is misleading as there are multiple + // reasons this can fail! Needs further investigation + case OAuth::Result::NotSupported: { + // should never happen + handleError(tr("Unable to open browser with provided URL. %1").arg(extraHelp)); + break; + } + case OAuth::Result::ErrorInsecureUrl: { + handleError(tr("OAuth2 authentication requires a secured connection. %1").arg(extraHelp)); + break; + } + case OAuth::Result::ErrorIdPUnreachable: { + handleError(tr("Authorization server unreachable. %1").arg(extraHelp)); + break; + } + }; + delete _oauth; + _oauth = nullptr; +} +} diff --git a/src/gui/newaccountwizard/oauthpagecontroller.h b/src/gui/newaccountwizard/oauthpagecontroller.h new file mode 100644 index 00000000000..2312487b4d9 --- /dev/null +++ b/src/gui/newaccountwizard/oauthpagecontroller.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "capabilities.h" +#include "creds/oauth.h" +#include "wizardpagevalidator.h" + +#include +#include + +class QWizardPage; +class QLineEdit; +class QLabel; +class QPushButton; + +namespace OCC { + +class AccessManager; +class OAuth; + +/** + * @brief The OAuthPageResults class is a simple container that carries the results of the page validation + */ +struct OAuthPageResults +{ + QString token; + QString refreshToken; + QUrl webfingerUserUrl; + QString displayName; + QString userId; + Capabilities capabilities = {QUrl{}, {}}; + + QString error; +}; + +/** + * @brief The OAuthPageController class controls the authentication step in the NewAccountWizard + */ +class OAuthPageController : public QObject, public WizardPageValidator +{ + Q_OBJECT +public: + /** + * @brief OAuthPageController + * @param page is the page this controller will build and manage. Nullptr is acceptable eg for testing + * @param accessManager is the access manager that will be used for all network requests + * @param parent implements the usual Qt parenting scheme. + */ + explicit OAuthPageController(QWizardPage *page, AccessManager *accessManager, QObject *parent); + + /** + * @brief validate runs the OAuth routines to authenticate the user and gather the tokens for the account, and it also runs various + * "final checks" on the data to ensure we can or should use these credentials to create an account + * @return true if the all the checks pass, false if it fails. + * This function also emits success or failure signals, below, to provide the OAuthPageResults to interested parties. + */ + bool validate() override; + + /** + * @brief setServerUrl is the original url used to kick off the account creation process. This url is not necessarily the same as the authentication url + * (which may be a webfinger authentication url). Even if the authentication goes through webfinger, we still need to use the server url to eg do the + * webfinger lookup among other things. + */ + void setServerUrl(const QUrl &url); + + /** + * @brief setAuthenticationUrl sets the authentication url. This may be equal to the server url or it may be a webfinger authentication url. + * @param url + */ + void setAuthenticationUrl(const QUrl &url); + + void setLookupWebfingerUrls(bool lookup); + +Q_SIGNALS: + /** + * @brief success notifies interested parties that the validation routine succeeded + * @param results contains the data collected during the validation + */ + void success(const OCC::OAuthPageResults &results); + // for this controller the failure signal is important, as the main wizard controller uses it to raise the client window after the user + // has completed the browser login + /** + * @brief failure notifies interested parties that the validation failed + * @param results contains the error that led to the failure, and also may contain other legitimate values depending on how far the validation + * progressed before it failed + */ + void failure(const OCC::OAuthPageResults &results); + +protected Q_SLOTS: + /** + * @brief copyUrlClicked copies the content of the url field to the clipboard + */ + void copyUrlClicked(); + /** + * @brief clipboardChanged updates the tooltip on the copy button depending on whether the clipboard contains the current url or not + */ + void clipboardChanged(); + +private: + QPointer _page; + QPointer _accessManager; + QUrl _serverUrl; + QUrl _authUrl; + // this is calculated by the oauth routine - this is also what the copy button should put in the clipboard! + QString _authEndpoint; + OAuth *_oauth; + bool _lookupWebfingerUrls = false; + + QLabel *_urlField; + QLabel *_errorField; + QPushButton *_copyButton; + + bool _oauthCompleted = false; + OAuthPageResults _results; + + + void buildPage(); + QIcon copyIcon(); + /** displays the error in the page, sets the error value in the results and emits failure(results) */ + void handleError(const QString &error); + void handleOauthResult(OAuth::Result result, const QString &token = QString(), const QString &refreshToken = QString()); + void authUrlReady(); +}; +} +Q_DECLARE_METATYPE(OCC::OAuthPageResults) +// this type id is throwaway, we just use it to ensure we declare the meta type only ONCE +// also this is only required to use the type cross thread, or with QSignalSpy during testing +static const int oauthPageResultsTypeId = qRegisterMetaType(); diff --git a/src/gui/newaccountwizard/urlpagecontroller.cpp b/src/gui/newaccountwizard/urlpagecontroller.cpp new file mode 100644 index 00000000000..b778be9ffb9 --- /dev/null +++ b/src/gui/newaccountwizard/urlpagecontroller.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 "urlpagecontroller.h" + +#include "accessmanager.h" +#include "config/appconfig.h" +#include "networkadapters/determineauthtypeadapter.h" +#include "networkadapters/discoverwebfingerserviceadapter.h" +#include "networkadapters/resolveurladapter.h" +#include "theme.h" + +#include +#include +#include +#include +#include + +namespace OCC { + +UrlPageController::UrlPageController(QWizardPage *page, AccessManager *accessManager, QObject *parent) + : QObject(parent) + , _page(page) + , _accessManager(accessManager) +{ + buildPage(); + + if (_urlField == nullptr) { + return; + } + + AppConfig systemConfig; + QString serverUrl = systemConfig.serverUrl(); + // no server url was given by any means, so the user has to provide one + if (serverUrl.isEmpty()) { + return; + } + setUrl(serverUrl); + + // The system admin provides the url, don't let the user change it! + bool allowServerUrlChange = systemConfig.allowServerUrlChange(); + if (!allowServerUrlChange) { + _urlField->setEnabled(false); + _instructionLabel->setText(tr("Your web browser will be opened to complete sign in.")); + } +} + +void UrlPageController::buildPage() +{ + if (!_page) + return; + + QString appName = Theme::instance()->appNameGUI(); + + QLabel *logoLabel = new QLabel({}, _page); + logoLabel->setPixmap(Theme::instance()->wizardHeaderLogo().pixmap(200, 100)); + logoLabel->setAlignment(Qt::AlignCenter); + logoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + logoLabel->setAccessibleName(tr("%1 logo").arg(appName)); + + QLabel *welcomeLabel = new QLabel(tr("Welcome to %1").arg(appName), _page); + QFont welcomeFont = welcomeLabel->font(); + welcomeFont.setWeight(QFont::DemiBold); + welcomeFont.setPixelSize(20); + welcomeLabel->setFont(welcomeFont); + welcomeLabel->setAlignment(Qt::AlignCenter); + welcomeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + _instructionLabel = new QLabel(tr("Enter your server address to get started. Your web browser will be opened to complete sign in."), _page); + QFont font = _instructionLabel->font(); + font.setPixelSize(14); + _instructionLabel->setFont(font); + _instructionLabel->setWordWrap(true); + _instructionLabel->setAlignment(Qt::AlignCenter); + _instructionLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + _urlField = new QLineEdit(_page); + QPalette urlFieldPalette = _urlField->palette(); + urlFieldPalette.setColor(QPalette::Base, urlFieldPalette.color(QPalette::Button)); + urlFieldPalette.setColor(QPalette::Text, urlFieldPalette.color(QPalette::ButtonText)); + _urlField->setPalette(urlFieldPalette); + _urlField->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _urlField->setPlaceholderText(Theme::instance()->wizardUrlPlaceholder()); + _urlField->setFocusPolicy(Qt::StrongFocus); + _urlField->setAccessibleName(tr("Server address field")); + _urlField->setAccessibleDescription(tr("Enter your server address here")); + _urlField->setObjectName("ServerAddressLineEdit"); + + _errorField = new QLabel(QString(), _page); + QPalette errorPalette = _errorField->palette(); + errorPalette.setColor(QPalette::Text, Qt::red); + _errorField->setPalette(errorPalette); + QFont errorFont = _errorField->font(); + errorFont.setPixelSize(14); + _errorField->setFont(errorFont); + _errorField->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + _errorField->setWordWrap(true); + _errorField->setAlignment(Qt::AlignLeft); + _errorField->setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + + QLabel *footerLogoLabel = nullptr; + if (!Theme::instance()->wizardFooterLogo().isNull()) { + footerLogoLabel = new QLabel({}, _page); + footerLogoLabel->setPixmap(Theme::instance()->wizardFooterLogo().pixmap(100, 52)); + footerLogoLabel->setAlignment(Qt::AlignCenter); + footerLogoLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + footerLogoLabel->setAccessibleName(tr("Additional logo defined by the organization")); + } + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(50, 0, 50, 0); + layout->setSpacing(12); + layout->addStretch(1); + layout->addWidget(logoLabel, Qt::AlignCenter); + layout->addSpacing(16); + layout->addWidget(welcomeLabel, Qt::AlignCenter); + layout->addWidget(_instructionLabel, Qt::AlignCenter); + layout->addWidget(_urlField, Qt::AlignCenter); + layout->addWidget(_errorField, Qt::AlignLeft); + if (footerLogoLabel) + layout->addWidget(footerLogoLabel, Qt::AlignCenter); + layout->addStretch(1); + _page->setLayout(layout); + + _urlField->setFocus(Qt::OtherFocusReason); +} + +void UrlPageController::setUrl(const QString &urlText) +{ + QSignalBlocker blocker(_urlField); + _urlField->setText(urlText); +} + +void UrlPageController::handleError(const QString &error) +{ + _errorField->setText(error); + _results.error = error; + Q_EMIT failure(_results); +} + +QUrl UrlPageController::checkUrl() +{ + if (!_accessManager) + return QUrl(); + + QString userUrl = _urlField->text(); + + // we need to check the scheme to be sure it's not there, but using the old "string matching" technique was not great + // because it added https:// to urls that had valid schemes but which did not match http or https. That was more complicating + // than useful so now we only add https if there really is no valid scheme to start with. + // the caller should decide which schemes are allowed and take whatever action it sees fit + + // strict mode really does not appear to be useful wrt catching malformed urls but hey, why not. + QUrl testUrl(userUrl, QUrl::StrictMode); + QString scheme = testUrl.scheme(); + if (scheme.isEmpty()) { + userUrl.prepend(QStringLiteral("https://")); + testUrl.setScheme(QStringLiteral("https")); + // so far I don't see testUrl coming back as invalid even if it should be but it seems reasonable to set the scheme anyway before testing validity. + // we catch the invalid condition later using a new url after reconstructing it with the default scheme added to the input string. see comment below. + // no I can't explain this behavior - it's a Qt mystery + } + if (!testUrl.isValid()) { + // if it's already dead, stop trying + return testUrl; + } + + // we can't recycle the testUrl because for reasons completely unknown to me, if you try to adjust it or set the path it destroys + // the host so we get https:///kwdav/ every time, even if the testUrl was valid + // solution = instantiate a new url + QUrl finalUrl(userUrl); + + if (finalUrl.isValid()) { + finalUrl = finalUrl.adjusted(QUrl::RemoveUserInfo | QUrl::RemoveQuery); + const QString serverPathOverride = Theme::instance()->overrideServerPath(); + if (!serverPathOverride.isEmpty()) { + finalUrl.setPath(serverPathOverride); + } + } + return finalUrl; +} + +bool UrlPageController::validate() +{ + _results = {}; + _errorField->clear(); + + if (!_accessManager) { + handleError(QStringLiteral("No valid access manager is available")); + return false; + } + + // always clear the access manager data before revalidating the url + _accessManager->reset(); + + QUrl givenUrl = checkUrl(); + if (!givenUrl.isValid()) { + QString fullError = givenUrl.errorString(); + QStringList parts = fullError.split(QStringLiteral(";"), Qt::SkipEmptyParts); + handleError(tr("Invalid server URL: %1").arg(parts[0] + parts[1])); + return false; + } else if (givenUrl.scheme().isEmpty() || givenUrl.scheme() != QStringLiteral("https")) // scheme should not be empty but who knows + { + handleError(tr("Invalid URL scheme. Only https is accepted.")); + return false; + } + + setUrl(givenUrl.toDisplayString()); + + DiscoverWebFingerServiceAdapter webfingerServiceAdapter(_accessManager, givenUrl); + const DiscoverWebFingerServiceResult webfingerServiceResult = webfingerServiceAdapter.getResult(); + + // in case any kind of error occurs, we assume the WebFinger service is not available + if (webfingerServiceResult.success()) { + _results.baseServerUrl = givenUrl; + _results.webfingerServiceUrl = QUrl(webfingerServiceResult.href); + } else { + // first, we must resolve the actual server URL + // note we have to pass the wizard dialog to the adapter to parent the tlsErrorDialog + ResolveUrlAdapter urlResolver(_accessManager, givenUrl, _page->parentWidget()); + const ResolveUrlResult resolveUrlResult = urlResolver.getResult(); + + if (!resolveUrlResult.success()) { + handleError(resolveUrlResult.error); + return false; + } + + const QUrl finalUrl = resolveUrlResult.resolvedUrl; + // I don't know how likely it is that the resolved url will contain a query but from other code it is + // evident that url queries are banned. so if it comes back with a query on it, it's a nogo + if (finalUrl.hasQuery()) { + QString errorMsg = tr("The requested URL failed with query value: %1").arg(finalUrl.query()); + handleError(errorMsg); + return false; + } + + if (!resolveUrlResult.acceptedCertificates.isEmpty()) { + // future requests made through this access manager need to include any certificates + _accessManager->addCustomTrustedCaCertificates(resolveUrlResult.acceptedCertificates); + // save/return this also for the account setup, as the account maintains the set of certificates, too. + _results.certificates = resolveUrlResult.acceptedCertificates; + } + + // This is now a mere formality to be very very very sure that the base url uses oauth. In the unlikely event that it doesn't + // the job simply fails. + DetermineAuthTypeAdapter authTypeAdapter(_accessManager, finalUrl); + const DetermineAuthTypeResult authResult = authTypeAdapter.getResult(); + if (!authResult.success()) { + handleError(authResult.error); + return false; + } + + Q_ASSERT(authResult.type == AuthenticationType::OAuth); + _results.baseServerUrl = finalUrl; + } + Q_EMIT success(_results); + return true; +} + +} diff --git a/src/gui/newaccountwizard/urlpagecontroller.h b/src/gui/newaccountwizard/urlpagecontroller.h new file mode 100644 index 00000000000..71e0cc60266 --- /dev/null +++ b/src/gui/newaccountwizard/urlpagecontroller.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 +#include +#include +#include + +#include "wizardpagevalidator.h" + +class QWizardPage; +class QLineEdit; +class QLabel; + +namespace OCC { + +/** + * @brief The UrlPageResults class contains all the possible values this controller can collect. + * + * baseServerUrl is the "original" server url (https://somehost/ that appears in the gui either via user input or + * provided by the theme + * webfingerServiceUrl is the url for a webfinger service discovered during validation. If this is empty, no webfinger service is + * available + * trustedCertificates holds any certificates the user accepted in the course of validation. This will only be non-empty in + * exceptional cases (eg with very poorly configured hosts that do not provide legit, signed certs) + * error holds a beautified error message. This may go away as I don't think the owner of the controller needs to do anything with it. + */ +struct UrlPageResults +{ + QUrl baseServerUrl; + QUrl webfingerServiceUrl; + QSet certificates; + + QString error; +}; + +class AccessManager; + +/** + * @brief The UrlPageController class is responsible for doing the basic validation of an authentication url, and it + * also determines whether a webfinger service is available. + * the url may come from the user, or it may be pre-defined via the theme. + * + */ +class UrlPageController : public QObject, public WizardPageValidator +{ + Q_OBJECT + +public: + /** + * @brief UrlPageController + * @param page may be null + * @param accessManager is required to run network jobs to complete the validation + * @param parent should normally be the parent controller when on the heap + */ + explicit UrlPageController(QWizardPage *page, AccessManager *accessManager, QObject *parent); + + /** + * @brief validate evaluates the given url, performs various checks, and signals the UrlPageResults to interested parties + * @return true of the validation succeeded, false if it failed + */ + bool validate() override; + + // ideally we should have a QRegExValidator on the url line edit and only when that passes, + // the wizard will enable the next button. The url evaluation may still fail but "next" should not be enabled on an + // obviously bogus url. this is future as a final polishing step. + +Q_SIGNALS: + + /** + * @brief success is emitted when the validation has succeeded. + * note we do not track the authentication method anymore since there is only one. If OAuth is not supported by the target server the + * whole validation will fail. + */ + void success(const OCC::UrlPageResults &); + /** + * @brief failure is emitted when a validation error has occurred. Some of the values in the result may be valid, depending on + * how far the validation got before it failed. + */ + void failure(const OCC::UrlPageResults &); + +private: + QPointer _page; + QPointer _accessManager; + + QLabel *_instructionLabel = nullptr; + QLineEdit *_urlField = nullptr; + QLabel *_errorField = nullptr; + + UrlPageResults _results; + bool _urlValidated = false; + + /** sets up the wizard page with appropriate content and connect any signals to eg the url QLineEdit */ + void buildPage(); + /** sets the url value in the line edit */ + void setUrl(const QString &urlText); + /** displays the error in the page, sets the error value in the results and emits failure(error) */ + void handleError(const QString &error); + /** performs some rudimentary checks on the url format, adds https:// if there is no scheme provided + * we should replace this with a QValidator that uses a more stringent regex + * the validator::fixup method can be used to add the https scheme + */ + QUrl checkUrl(); +}; +} +Q_DECLARE_METATYPE(OCC::UrlPageResults) +// this type id is throwaway, we just use it to ensure we declare the meta type only ONCE +// also this is only required to use the type cross thread, or with QSignalSpy during testing +static const int urlPageResultsTypeId = qRegisterMetaType(); diff --git a/src/gui/newaccountwizard/wizardpagevalidator.h b/src/gui/newaccountwizard/wizardpagevalidator.h new file mode 100644 index 00000000000..ebdfb05320e --- /dev/null +++ b/src/gui/newaccountwizard/wizardpagevalidator.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) Lisa Reese + * + * 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 + +namespace OCC { + +/** this is a solution to allow sharing the page controllers' validation routine without exposing the page controller itself. + * the wizard page controllers will implement this interface and the wizard will call the validate function in an override of QWizard::validateCurrentPage + */ +class WizardPageValidator +{ +public: + virtual ~WizardPageValidator() { } + + /** + * @brief validate the data of a wizard page + * @return true if the validation succeeds, false if it does not + */ + virtual bool validate() = 0; +}; +} diff --git a/src/gui/notificationconfirmjob.cpp b/src/gui/notificationconfirmjob.cpp deleted file mode 100644 index 237353fb041..00000000000 --- a/src/gui/notificationconfirmjob.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 "notificationconfirmjob.h" -#include "networkjobs.h" -#include "account.h" - -#include - -namespace OCC { - -Q_DECLARE_LOGGING_CATEGORY(lcNotifications) - -NotificationConfirmJob::NotificationConfirmJob(AccountPtr account) - : AbstractNetworkJob(account, "") - , _widget(nullptr) -{ - setIgnoreCredentialFailure(true); -} - -void NotificationConfirmJob::setLinkAndVerb(const QUrl &link, const QByteArray &verb) -{ - _link = link; - _verb = verb; -} - -void NotificationConfirmJob::setWidget(NotificationWidget *widget) -{ - _widget = widget; -} - -NotificationWidget *NotificationConfirmJob::widget() -{ - return _widget; -} - -void NotificationConfirmJob::start() -{ - if (!_link.isValid()) { - qCWarning(lcNotifications) << "Attempt to trigger invalid URL: " << _link.toString(); - return; - } - QNetworkRequest req; - req.setRawHeader("Ocs-APIREQUEST", "true"); - req.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); - - sendRequest(_verb, _link, req); - - AbstractNetworkJob::start(); -} - -bool NotificationConfirmJob::finished() -{ - int replyCode = 0; - // FIXME: check for the reply code! - const QString replyStr = reply()->readAll(); - - if (replyStr.contains("")) { - QRegExp rex("(\\d+)"); - if (replyStr.contains(rex)) { - // this is a error message coming back from ocs. - replyCode = rex.cap(1).toInt(); - } - } - emit jobFinished(replyStr, replyCode); - - return true; -} -} diff --git a/src/gui/notificationconfirmjob.h b/src/gui/notificationconfirmjob.h deleted file mode 100644 index dfed4ce68fa..00000000000 --- a/src/gui/notificationconfirmjob.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef NOTIFICATIONCONFIRMJOB_H -#define NOTIFICATIONCONFIRMJOB_H - -#include "accountfwd.h" -#include "abstractnetworkjob.h" - -#include -#include -#include -#include - -namespace OCC { - -class NotificationWidget; - -/** - * @brief The NotificationConfirmJob class - * @ingroup gui - * - * Class to call an action-link of a notification coming from the server. - * All the communication logic is handled in this class. - * - */ -class NotificationConfirmJob : public AbstractNetworkJob -{ - Q_OBJECT - -public: - explicit NotificationConfirmJob(AccountPtr account); - - /** - * @brief Set the verb and link for the job - * - * @param verb currently supported GET PUT POST DELETE - */ - void setLinkAndVerb(const QUrl &link, const QByteArray &verb); - - /** - * @brief Start the OCS request - */ - void start() override; - - /** - * @brief setWidget stores the associated widget to be able to use - * it when the job has finished - * @param widget pointer to the notification widget to store - */ - void setWidget(NotificationWidget *widget); - - /** - * @brief widget - get the associated notification widget as stored - * with setWidget method. - * @return widget pointer to the notification widget - */ - NotificationWidget *widget(); - -signals: - - /** - * Result of the OCS request - * - * @param reply the reply - */ - void jobFinished(QString reply, int replyCode); - -private slots: - bool finished() override; - -private: - QByteArray _verb; - QUrl _link; - NotificationWidget *_widget; -}; -} - -#endif // NotificationConfirmJob_H diff --git a/src/gui/notificationwidget.cpp b/src/gui/notificationwidget.cpp deleted file mode 100644 index 266d013c14c..00000000000 --- a/src/gui/notificationwidget.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 "notificationwidget.h" -#include "QProgressIndicator.h" -#include "common/utility.h" -#include "common/asserts.h" -#include "guiutility.h" - -#include - -#include "ocsjob.h" - -namespace OCC { - -Q_LOGGING_CATEGORY(lcNotifications, "gui.notifications", QtInfoMsg) - -NotificationWidget::NotificationWidget(QWidget *parent) - : QWidget(parent) -{ - _ui.setupUi(this); - _progressIndi = new QProgressIndicator(this); - _ui.horizontalLayout->addWidget(_progressIndi); -} - -void NotificationWidget::setActivity(const Activity &activity) -{ - _myActivity = activity; - - _accountName = activity.accName(); - OC_ASSERT(!_accountName.isEmpty()); - - // _ui._headerLabel->setText( ); - _ui._subjectLabel->setVisible(!activity.subject().isEmpty()); - _ui._messageLabel->setVisible(!activity.message().isEmpty()); - - if (activity.link().isEmpty()) { - _ui._subjectLabel->setText(activity.subject()); - } else { - _ui._subjectLabel->setText(QString("%2") - .arg(activity.link().toString(QUrl::FullyEncoded), - activity.subject().toHtmlEscaped())); - _ui._subjectLabel->setTextFormat(Qt::RichText); - _ui._subjectLabel->setOpenExternalLinks(true); - } - - _ui._messageLabel->setText(activity.message()); - QString tText = tr("Created at %1").arg(Utility::timeAgoInWords(activity.dateTime())); - _ui._timeLabel->setText(tText); - - // always remove the buttons - qDeleteAll(_buttons); - _buttons.clear(); - - // display buttons for the links - if (activity.links().isEmpty()) { - // in case there is no action defined, do a close button. - QPushButton *b = _ui._buttonBox->addButton(QDialogButtonBox::Close); - b->setDefault(true); - connect(b, &QAbstractButton::clicked, this, [this]{ - QString doneText = tr("Closing in a few seconds..."); - _ui._timeLabel->setText(doneText); - emit requestCleanupAndBlacklist(_myActivity); - return; - }); - } else { - for (const auto &link : activity.links()) { - QPushButton *b = _ui._buttonBox->addButton(link._label, QDialogButtonBox::AcceptRole); - b->setDefault(link._isPrimary); - connect(b, &QAbstractButton::clicked, this, [this, b, link] { - slotButtonClicked(b, link); - }); - _buttons.append(b); - } - } -} - -Activity NotificationWidget::activity() const -{ - return _myActivity; -} - -void NotificationWidget::slotButtonClicked(QPushButton *buttonWidget, const ActivityLink &triggeredLink) -{ - buttonWidget->setEnabled(false); - _actionLabel = triggeredLink._label; - if (!triggeredLink._link.isEmpty()) { - qCInfo(lcNotifications) << "Notification Link: " << triggeredLink._verb << triggeredLink._link; - _progressIndi->startAnimation(); - emit sendNotificationRequest(_accountName, triggeredLink._link, triggeredLink._verb); - } -} - -void NotificationWidget::slotNotificationRequestFinished(int statusCode) -{ - QString doneText; - QLocale locale; - - QString timeStr = locale.toString(QTime::currentTime()); - - // the ocs API returns stat code 100 or 200 inside the xml if it succeeded. - if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) { - qCWarning(lcNotifications) << "Notification Request to Server failed, leave button visible."; - for (auto *button : qAsConst(_buttons)) { - button->setEnabled(true); - } - //: The second parameter is a time, such as 'failed at 09:58pm' - doneText = tr("%1 request failed at %2").arg(_actionLabel, timeStr); - } else { - // the call to the ocs API succeeded. - _ui._buttonBox->hide(); - - //: The second parameter is a time, such as 'selected at 09:58pm' - doneText = tr("'%1' selected at %2").arg(_actionLabel, timeStr); - } - _ui._timeLabel->setText(doneText); - - _progressIndi->stopAnimation(); -} - -void NotificationWidget::changeEvent(QEvent *e) -{ - switch (e->type()) { - case QEvent::StyleChange: - case QEvent::PaletteChange: - case QEvent::ThemeChange: - _ui._notifIcon->setPixmap(Utility::getCoreIcon(QStringLiteral("bell")).pixmap(_ui._notifIcon->size())); - break; - default: - break; - } - QWidget::changeEvent(e); -} -} diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h deleted file mode 100644 index 05c9173c976..00000000000 --- a/src/gui/notificationwidget.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef NOTIFICATIONWIDGET_H -#define NOTIFICATIONWIDGET_H - -#include - -#include "activitydata.h" - -#include "ui_notificationwidget.h" - -class QProgressIndicator; - -namespace OCC { - -class NotificationWidget : public QWidget -{ - Q_OBJECT -public: - explicit NotificationWidget(QWidget *parent = nullptr); - - bool readyToClose(); - Activity activity() const; - -signals: - void sendNotificationRequest(const QString &, const QString &link, const QByteArray &verb); - void requestCleanupAndBlacklist(const Activity &activity); - -public slots: - void setActivity(const Activity &activity); - void slotNotificationRequestFinished(int statusCode); - -protected: - void changeEvent(QEvent *) override; - -private: - void slotButtonClicked(QPushButton *buttonWidget, const ActivityLink &triggeredLink); - - Ui_NotificationWidget _ui; - Activity _myActivity; - QList _buttons; - QString _accountName; - QProgressIndicator *_progressIndi; - QString _actionLabel; -}; -} - -#endif // NOTIFICATIONWIDGET_H diff --git a/src/gui/notificationwidget.ui b/src/gui/notificationwidget.ui deleted file mode 100644 index 45290b93389..00000000000 --- a/src/gui/notificationwidget.ui +++ /dev/null @@ -1,135 +0,0 @@ - - - NotificationWidget - - - - 0 - 0 - 725 - 131 - - - - - 0 - 0 - - - - Form - - - - 0 - - - 0 - - - - - - - - 0 - 0 - - - - - 48 - 48 - - - - - 48 - 48 - - - - - - - :/client/resources/light/bell.svg - - - true - - - - - - - - - - 0 - 0 - - - - Lorem ipsum dolor sit amet - - - - - - - - 0 - 0 - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - false - - - true - - - - - - - - - - 8 - - - - TextLabel - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - - - - - - - - - - - - - - Qt::Horizontal - - - - - - - - - - diff --git a/src/gui/ocsjob.cpp b/src/gui/ocsjob.cpp deleted file mode 100644 index 42bd48d784c..00000000000 --- a/src/gui/ocsjob.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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 "ocsjob.h" -#include "networkjobs.h" -#include "account.h" - -#include -#include -#include - -namespace OCC { - -Q_LOGGING_CATEGORY(lcOcs, "gui.sharing.ocs", QtInfoMsg) - -OcsJob::OcsJob(AccountPtr account) - : AbstractNetworkJob(account, "") -{ - _passStatusCodes.append(OCS_SUCCESS_STATUS_CODE); - _passStatusCodes.append(OCS_SUCCESS_STATUS_CODE_V2); - setIgnoreCredentialFailure(true); -} - -void OcsJob::setVerb(const QByteArray &verb) -{ - _verb = verb; -} - -void OcsJob::addParam(const QString &name, const QString &value) -{ - _params.append(qMakePair(name, value)); -} - -void OcsJob::addPassStatusCode(int code) -{ - _passStatusCodes.append(code); -} - -void OcsJob::appendPath(const QString &id) -{ - setPath(path() + QLatin1Char('/') + id); -} - -static QUrlQuery percentEncodeQueryItems( - const QList> &items) -{ - QUrlQuery result; - // Note: QUrlQuery::setQueryItems() does not fully percent encode - // the query items, see #5042 - for (const auto &item : items) { - result.addQueryItem( - QUrl::toPercentEncoding(item.first), - QUrl::toPercentEncoding(item.second)); - } - return result; -} - -void OcsJob::start() -{ - QNetworkRequest req; - req.setRawHeader("Ocs-APIREQUEST", "true"); - req.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); - - QBuffer *buffer = new QBuffer; - - QUrlQuery queryItems; - if (_verb == "GET") { - queryItems = percentEncodeQueryItems(_params); - } else if (_verb == "POST" || _verb == "PUT") { - // Url encode the _postParams and put them in a buffer. - QByteArray postData; - for (const auto &tmp : qAsConst(_params)) { - if (!postData.isEmpty()) { - postData.append("&"); - } - postData.append(QUrl::toPercentEncoding(tmp.first)); - postData.append("="); - postData.append(QUrl::toPercentEncoding(tmp.second)); - } - buffer->setData(postData); - } - queryItems.addQueryItem(QLatin1String("format"), QLatin1String("json")); - QUrl url = Utility::concatUrlPath(account()->url(), path(), queryItems); - sendRequest(_verb, url, req, buffer); - AbstractNetworkJob::start(); -} - -bool OcsJob::finished() -{ - const QByteArray replyData = reply()->readAll(); - - QJsonParseError error; - auto json = QJsonDocument::fromJson(replyData, &error); - if (error.error != QJsonParseError::NoError) { - qCWarning(lcOcs) << "Could not parse reply to" - << _verb - << Utility::concatUrlPath(account()->url(), path()) - << _params - << error.errorString() - << ":" << replyData; - } - - QString message; - const int statusCode = getJsonReturnCode(json, message); - if (!_passStatusCodes.contains(statusCode)) { - qCWarning(lcOcs) << "Reply to" - << _verb - << Utility::concatUrlPath(account()->url(), path()) - << _params - << "has unexpected status code:" << statusCode << replyData; - emit ocsError(statusCode, message); - } else { - emit jobFinished(json); - } - return true; -} - -int OcsJob::getJsonReturnCode(const QJsonDocument &json, QString &message) -{ - //TODO proper checking - auto meta = json.object().value("ocs").toObject().value("meta").toObject(); - int code = meta.value("statuscode").toInt(); - message = meta.value("message").toString(); - - return code; -} -} diff --git a/src/gui/ocsjob.h b/src/gui/ocsjob.h deleted file mode 100644 index a7ec3f92e17..00000000000 --- a/src/gui/ocsjob.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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. - */ - -#ifndef OCSJOB_H -#define OCSJOB_H - -#include "accountfwd.h" -#include "abstractnetworkjob.h" - -#include -#include -#include -#include - -#define OCS_SUCCESS_STATUS_CODE 100 -// Apparantly the v2.php URLs can return that -#define OCS_SUCCESS_STATUS_CODE_V2 200 - -class QJsonDocument; - -namespace OCC { - -/** - * @brief The OcsShareJob class - * @ingroup gui - * - * Base class for jobs that talk to the OCS endpoints on the server. - * All the communication logic is handled in this class. - * - * All OCS jobs (e.g. sharing) should extend this class. - */ -class OcsJob : public AbstractNetworkJob -{ - Q_OBJECT - -protected: - explicit OcsJob(AccountPtr account); - - /** - * Set the verb for the job - * - * @param verb currently supported PUT POST DELETE - */ - void setVerb(const QByteArray &verb); - - /** - * Add a new parameter to the request. - * Depending on the verb this is GET or POST parameter - * - * @param name The name of the parameter - * @param value The value of the parameter - */ - void addParam(const QString &name, const QString &value); - - /** - * Set the post parameters - * - * @param postParams list of pairs to add (urlEncoded) to the body of the - * request - */ - void setPostParams(const QList> &postParams); - - /** - * List of expected statuscodes for this request - * A warning will be printed to the debug log if a different status code is - * encountered - * - * @param code Accepted status code - */ - void addPassStatusCode(int code); - - /** - * The base path for an OcsJob is always the same. But it could be the case that - * certain operations need to append something to the URL. - * - * This function appends the common id. so / - */ - void appendPath(const QString &id); - -public: - /** - * Parse the response and return the status code and the message of the - * reply (metadata) - * - * @param json The reply from OCS - * @param message The message that is set in the metadata - * @return The statuscode of the OCS response - */ - static int getJsonReturnCode(const QJsonDocument &json, QString &message); - -protected slots: - - /** - * Start the OCS request - */ - void start() override; - -signals: - - /** - * Result of the OCS request - * - * @param reply the reply - */ - void jobFinished(QJsonDocument reply); - - /** - * The status code was not one of the expected (passing) - * status code for this command - * - * @param statusCode The actual status code - * @param message The message provided by the server - */ - void ocsError(int statusCode, const QString &message); - -private slots: - bool finished() override; - -private: - QByteArray _verb; - QList> _params; - QVector _passStatusCodes; -}; -} - -#endif // OCSJOB_H diff --git a/src/gui/ocsshareejob.cpp b/src/gui/ocsshareejob.cpp deleted file mode 100644 index 0d1b6d44b2c..00000000000 --- a/src/gui/ocsshareejob.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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 "ocsshareejob.h" - -#include - -namespace OCC { - -OcsShareeJob::OcsShareeJob(AccountPtr account) - : OcsJob(account) -{ - setPath("ocs/v1.php/apps/files_sharing/api/v1/sharees"); - connect(this, &OcsJob::jobFinished, this, &OcsShareeJob::jobDone); -} - -void OcsShareeJob::getSharees(const QString &search, - const QString &itemType, - int page, - int perPage) -{ - setVerb("GET"); - - addParam(QString::fromLatin1("search"), search); - addParam(QString::fromLatin1("itemType"), itemType); - addParam(QString::fromLatin1("page"), QString::number(page)); - addParam(QString::fromLatin1("perPage"), QString::number(perPage)); - - start(); -} - -void OcsShareeJob::jobDone(const QJsonDocument &reply) -{ - emit shareeJobFinished(reply); -} -} diff --git a/src/gui/ocsshareejob.h b/src/gui/ocsshareejob.h deleted file mode 100644 index f511f5fb835..00000000000 --- a/src/gui/ocsshareejob.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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. - */ - -#ifndef OCSSHAREEJOB_H -#define OCSSHAREEJOB_H - -#include "ocsjob.h" - -class QJsonDocument; - -namespace OCC { - -/** - * @brief The OcsShareeJob class - * @ingroup gui - * - * Fetching sharees from the OCS Sharee API - */ -class OcsShareeJob : public OcsJob -{ - Q_OBJECT -public: - explicit OcsShareeJob(AccountPtr account); - - /** - * Get a list of sharees - * - * @param path Path to request shares for (default all shares) - */ - void getSharees(const QString &search, const QString &itemType, int page = 1, int perPage = 50); -signals: - /** - * Result of the OCS request - * - * @param reply The reply - */ - void shareeJobFinished(const QJsonDocument &reply); - -private slots: - void jobDone(const QJsonDocument &reply); -}; -} - -#endif // OCSSHAREEJOB_H diff --git a/src/gui/ocssharejob.cpp b/src/gui/ocssharejob.cpp deleted file mode 100644 index 8af2b485f30..00000000000 --- a/src/gui/ocssharejob.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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 "ocssharejob.h" -#include "networkjobs.h" -#include "account.h" - -#include -#include - -namespace OCC { - -OcsShareJob::OcsShareJob(AccountPtr account) - : OcsJob(account) -{ - setPath("ocs/v1.php/apps/files_sharing/api/v1/shares"); - connect(this, &OcsJob::jobFinished, this, &OcsShareJob::jobDone); -} - -void OcsShareJob::getShares(const QString &path) -{ - setVerb("GET"); - - addParam(QString::fromLatin1("path"), path); - addParam(QLatin1String("reshares"), QLatin1String("true")); - addPassStatusCode(404); - - start(); -} - -void OcsShareJob::deleteShare(const QString &shareId) -{ - appendPath(shareId); - setVerb("DELETE"); - - start(); -} - -void OcsShareJob::setExpireDate(const QString &shareId, const QDate &date) -{ - appendPath(shareId); - setVerb("PUT"); - - if (date.isValid()) { - addParam(QString::fromLatin1("expireDate"), date.toString("yyyy-MM-dd")); - } else { - addParam(QString::fromLatin1("expireDate"), QString()); - } - _value = date; - - start(); -} - -void OcsShareJob::setPassword(const QString &shareId, const QString &password) -{ - appendPath(shareId); - setVerb("PUT"); - - addParam(QString::fromLatin1("password"), password); - _value = password; - - start(); -} - -void OcsShareJob::setPublicUpload(const QString &shareId, bool publicUpload) -{ - appendPath(shareId); - setVerb("PUT"); - - const QString value = QString::fromLatin1(publicUpload ? "true" : "false"); - addParam(QString::fromLatin1("publicUpload"), value); - _value = publicUpload; - - start(); -} - -void OcsShareJob::setName(const QString &shareId, const QString &name) -{ - appendPath(shareId); - setVerb("PUT"); - addParam(QString::fromLatin1("name"), name); - _value = name; - - start(); -} - -void OcsShareJob::setPermissions(const QString &shareId, - const Share::Permissions permissions) -{ - appendPath(shareId); - setVerb("PUT"); - - addParam(QString::fromLatin1("permissions"), QString::number(permissions)); - _value = (int)permissions; - - start(); -} - -void OcsShareJob::createLinkShare(const QString &path, - const QString &name, - const QString &password, - const QDate &expireDate, - const Share::Permissions permissions) -{ - setVerb("POST"); - - addParam(QString::fromLatin1("path"), path); - addParam(QString::fromLatin1("shareType"), QString::number(Share::TypeLink)); - - if (!name.isEmpty()) { - addParam(QString::fromLatin1("name"), name); - } - if (!password.isEmpty()) { - addParam(QString::fromLatin1("password"), password); - } - if (!expireDate.isNull()) { - addParam(QString::fromLatin1("expireDate"), expireDate.toString("yyyy-MM-dd")); - } - if (permissions != SharePermissionDefault) { - addParam(QString::fromLatin1("permissions"), QString::number(permissions)); - } - - addPassStatusCode(403); - - start(); -} - -void OcsShareJob::createShare(const QString &path, - const Share::ShareType shareType, - const QString &shareWith, - const Share::Permissions permissions) -{ - setVerb("POST"); - - addParam(QString::fromLatin1("path"), path); - addParam(QString::fromLatin1("shareType"), QString::number(shareType)); - addParam(QString::fromLatin1("shareWith"), shareWith); - if (!(permissions & SharePermissionDefault)) { - addParam(QString::fromLatin1("permissions"), QString::number(permissions)); - } - - start(); -} - -void OcsShareJob::getSharedWithMe() -{ - setVerb("GET"); - addParam(QLatin1String("shared_with_me"), QLatin1String("true")); - start(); -} - -void OcsShareJob::jobDone(QJsonDocument reply) -{ - emit shareJobFinished(reply, _value); -} -} diff --git a/src/gui/ocssharejob.h b/src/gui/ocssharejob.h deleted file mode 100644 index 5c1947323ac..00000000000 --- a/src/gui/ocssharejob.h +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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. - */ - -#ifndef OCSSHAREJOB_H -#define OCSSHAREJOB_H - -#include "ocsjob.h" -#include "sharemanager.h" -#include -#include -#include - -class QJsonDocument; - -namespace OCC { - -/** - * @brief The OcsShareJob class - * @ingroup gui - * - * Handle talking to the OCS Share API. - * For creation, deletion and modification of shares. - */ -class OcsShareJob : public OcsJob -{ - Q_OBJECT -public: - /** - * Constructor for new shares or listing of shares - */ - explicit OcsShareJob(AccountPtr account); - - /** - * Get all the shares - * - * @param path Path to request shares for (default all shares) - */ - void getShares(const QString &path = ""); - - /** - * Delete the current Share - */ - void deleteShare(const QString &shareId); - - /** - * Set the expiration date of a share - * - * @param date The expire date, if this date is invalid the expire date - * will be removed - */ - void setExpireDate(const QString &shareId, const QDate &date); - - /** - * Set the password of a share - * - * @param password The password of the share, if the password is empty the - * share will be removed - */ - void setPassword(const QString &shareId, const QString &password); - - /** - * Set the share to be public upload - * - * @param publicUpload Set or remove public upload - */ - void setPublicUpload(const QString &shareId, bool publicUpload); - - /** - * Change the name of a share - */ - void setName(const QString &shareId, const QString &name); - - /** - * Set the permissions - * - * @param permissions - */ - void setPermissions(const QString &shareId, - const Share::Permissions permissions); - - /** - * Create a new link share - * - * @param path The path of the file/folder to share - * @param name The name of the link share, empty name auto-generates one - * @param password Optionally a password for the share - * @param expireDate Target expire data (may be null) - * @param permissions Desired permissions (SharePermissionDefault leaves to server) - */ - void createLinkShare(const QString &path, - const QString &name, - const QString &password, - const QDate &expireDate, - const Share::Permissions permissions); - - /** - * Create a new share - * - * @param path The path of the file/folder to share - * @param shareType The type of share (user/group/link/federated) - * @param shareWith The uid/gid/federated id to share with - * @param permissions The permissions the share will have - */ - void createShare(const QString &path, - const Share::ShareType shareType, - const QString &shareWith = "", - const Share::Permissions permissions = SharePermissionRead); - - /** - * Returns information on the items shared with the current user. - */ - void getSharedWithMe(); - -signals: - /** - * Result of the OCS request - * The value parameter is only set if this was a put request. - * e.g. if we set the password to 'foo' the QVariant will hold a QString with 'foo'. - * This is needed so we can update the share objects properly - * - * @param reply The reply - * @param value To what did we set a variable (if we set any). - */ - void shareJobFinished(QJsonDocument reply, QVariant value); - -private slots: - void jobDone(QJsonDocument reply); - -private: - QVariant _value; -}; -} - -#endif // OCSSHAREJOB_H diff --git a/src/gui/openfilemanager.cpp b/src/gui/openfilemanager.cpp index 656c2dc5743..99bd91da558 100644 --- a/src/gui/openfilemanager.cpp +++ b/src/gui/openfilemanager.cpp @@ -17,12 +17,11 @@ #include "application.h" #include "common/utility.h" +#include +#include #include #include -#include #include -#include -#include namespace OCC { @@ -33,15 +32,15 @@ static QStringList xdgDataDirs() // http://standards.freedesktop.org/basedir-spec/latest/ QString xdgDataDirsEnv = QFile::decodeName(qgetenv("XDG_DATA_DIRS")); if (xdgDataDirsEnv.isEmpty()) { - dirs.append(QString::fromLatin1("/usr/local/share")); - dirs.append(QString::fromLatin1("/usr/share")); + dirs.append(QStringLiteral("/usr/local/share")); + dirs.append(QStringLiteral("/usr/share")); } else { dirs = xdgDataDirsEnv.split(QLatin1Char(':')); } // local location QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME")); if (xdgDataHome.isEmpty()) { - xdgDataHome = QDir::homePath() + "/.local/share"; + xdgDataHome = QDir::homePath() + QStringLiteral("/.local/share"); } dirs.prepend(xdgDataHome); return dirs; @@ -51,9 +50,7 @@ static QStringList xdgDataDirs() static QString findDefaultFileManager() { QProcess p; - p.start("xdg-mime", QStringList() << "query" - << "default" - << "inode/directory", + p.start(QStringLiteral("xdg-mime"), QStringList() << QStringLiteral("query") << QStringLiteral("default") << QStringLiteral("inode/directory"), QFile::ReadOnly); p.waitForFinished(); QString fileName = QString::fromUtf8(p.readAll().trimmed()); @@ -78,7 +75,7 @@ static QString findDefaultFileManager() static bool checkDolphinCanSelect() { QProcess p; - p.start("dolphin", QStringList() << "--help", QFile::ReadOnly); + p.start(QStringLiteral("dolphin"), QStringList() << QStringLiteral("--help"), QFile::ReadOnly); p.waitForFinished(); return p.readAll().contains("--select"); } @@ -88,7 +85,7 @@ static bool checkDolphinCanSelect() void showInFileManager(const QString &localPath) { if (Utility::isWindows()) { - QString explorer = "explorer.exe "; // FIXME: we trust it's in PATH + QString explorer = QStringLiteral("explorer.exe "); // FIXME: we trust it's in PATH QFileInfo fi(localPath); // canonicalFilePath returns empty if the file does not exist @@ -112,40 +109,32 @@ void showInFileManager(const QString &localPath) p.waitForFinished(5000); } } else if (Utility::isMac()) { - QStringList scriptArgs; - scriptArgs << QLatin1String("-e") - << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"") - .arg(localPath); - QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs); - scriptArgs.clear(); - scriptArgs << QLatin1String("-e") - << QLatin1String("tell application \"Finder\" to activate"); - QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs); + QProcess::startDetached("/usr/bin/open", {"-R", localPath}); } else { QString app; QStringList args; static QString defaultManager = findDefaultFileManager(); QSettings desktopFile(defaultManager, QSettings::IniFormat); - QString exec = desktopFile.value("Desktop Entry/Exec").toString(); + QString exec = desktopFile.value(QStringLiteral("Desktop Entry/Exec")).toString(); QString fileToOpen = QFileInfo(localPath).absoluteFilePath(); QString pathToOpen = QFileInfo(localPath).absolutePath(); bool canHandleFile = false; // assume dumb fm - args = exec.split(' '); + args = exec.split(QLatin1Char(' ')); if (args.count() > 0) app = args.takeFirst(); - QString kdeSelectParam("--select"); + QString kdeSelectParam(QStringLiteral("--select")); - if (app.contains("konqueror") && !args.contains(kdeSelectParam)) { + if (app.contains(QLatin1String("konqueror")) && !args.contains(kdeSelectParam)) { // konq needs '--select' in order not to launch the file args.prepend(kdeSelectParam); canHandleFile = true; } - if (app.contains("dolphin")) { + if (app.contains(QLatin1String("dolphin"))) { static bool dolphinCanSelect = checkDolphinCanSelect(); if (dolphinCanSelect && !args.contains(kdeSelectParam)) { args.prepend(kdeSelectParam); @@ -154,29 +143,29 @@ void showInFileManager(const QString &localPath) } // whitelist - if (app.contains("nautilus") || app.contains("nemo")) { + if (app.contains(QLatin1String("nautilus")) || app.contains(QLatin1String("nemo"))) { canHandleFile = true; } static QString name; if (name.isEmpty()) { - name = desktopFile.value(QString::fromLatin1("Desktop Entry/Name[%1]").arg(ocApp()->displayLanguage())).toString(); + name = desktopFile.value(QStringLiteral("Desktop Entry/Name[%1]").arg(ocApp()->displayLanguage())).toString(); if (name.isEmpty()) { - name = desktopFile.value(QString::fromLatin1("Desktop Entry/Name")).toString(); + name = desktopFile.value(QStringLiteral("Desktop Entry/Name")).toString(); } } - std::replace(args.begin(), args.end(), QString::fromLatin1("%c"), name); - std::replace(args.begin(), args.end(), QString::fromLatin1("%u"), fileToOpen); - std::replace(args.begin(), args.end(), QString::fromLatin1("%U"), fileToOpen); - std::replace(args.begin(), args.end(), QString::fromLatin1("%f"), fileToOpen); - std::replace(args.begin(), args.end(), QString::fromLatin1("%F"), fileToOpen); + std::replace(args.begin(), args.end(), QStringLiteral("%c"), name); + std::replace(args.begin(), args.end(), QStringLiteral("%u"), fileToOpen); + std::replace(args.begin(), args.end(), QStringLiteral("%U"), fileToOpen); + std::replace(args.begin(), args.end(), QStringLiteral("%f"), fileToOpen); + std::replace(args.begin(), args.end(), QStringLiteral("%F"), fileToOpen); // fixme: needs to append --icon, according to http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables - QStringList::iterator it = std::find(args.begin(), args.end(), QString::fromLatin1("%i")); + QStringList::iterator it = std::find(args.begin(), args.end(), QStringLiteral("%i")); if (it != args.end()) { - (*it) = desktopFile.value("Desktop Entry/Icon").toString(); - args.insert(it, QString::fromLatin1("--icon")); // before + (*it) = desktopFile.value(QStringLiteral("Desktop Entry/Icon")).toString(); + args.insert(it, QStringLiteral("--icon")); // before } diff --git a/src/gui/owncloud.exe.manifest b/src/gui/owncloud.exe.manifest new file mode 100644 index 00000000000..88485516010 --- /dev/null +++ b/src/gui/owncloud.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + + true + + + diff --git a/src/gui/owncloud.exe.manifest-mingw b/src/gui/owncloud.exe.manifest-mingw deleted file mode 100644 index ecea6f7f567..00000000000 --- a/src/gui/owncloud.exe.manifest-mingw +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp deleted file mode 100644 index aed018200e5..00000000000 --- a/src/gui/owncloudgui.cpp +++ /dev/null @@ -1,1141 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 "application.h" -#include "owncloudgui.h" -#include "theme.h" -#include "folderman.h" -#include "configfile.h" -#include "progressdispatcher.h" -#include "owncloudsetupwizard.h" -#include "sharedialog.h" -#include "settingsdialog.h" -#include "logger.h" -#include "logbrowser.h" -#include "account.h" -#include "accountstate.h" -#include "openfilemanager.h" -#include "accountmanager.h" -#include "aboutdialog.h" -#include "common/syncjournalfilerecord.h" -#include "creds/abstractcredentials.h" -#include "guiutility.h" - -#include -#include -#include -#include -#include -#include - -#ifdef WITH_LIBCLOUDPROVIDERS -#include "libcloudproviders/libcloudproviders.h" -#endif - -using namespace std::chrono_literals; - -namespace OCC { - -const char propertyAccountC[] = "oc_account"; - -ownCloudGui::ownCloudGui(Application *parent) - : QObject(parent) - , _tray(new Systray(this)) - , _settingsDialog(new SettingsDialog(this)) - , _recentActionsMenu(nullptr) - , _app(parent) -{ - // for the beginning, set the offline icon until the account was verified - _tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true, /*currently visible?*/ false)); - - connect(_tray, &QSystemTrayIcon::activated, - this, &ownCloudGui::slotTrayClicked); - - setupActions(); - setupContextMenu(); - - _tray->show(); - -#ifdef WITH_LIBCLOUDPROVIDERS - auto exporter = new LibCloudProviders(this); - exporter->start(); - connect(exporter, &LibCloudProviders::showSettings, this, &ownCloudGui::slotShowSettings); -#endif - - ProgressDispatcher *pd = ProgressDispatcher::instance(); - connect(pd, &ProgressDispatcher::progressInfo, this, - &ownCloudGui::slotUpdateProgress); - - FolderMan *folderMan = FolderMan::instance(); - connect(folderMan, &FolderMan::folderSyncStateChange, - this, &ownCloudGui::slotSyncStateChange); - - connect(AccountManager::instance(), &AccountManager::accountAdded, - this, &ownCloudGui::updateContextMenuNeeded); - connect(AccountManager::instance(), &AccountManager::accountRemoved, - this, &ownCloudGui::updateContextMenuNeeded); -} - -ownCloudGui::~ownCloudGui() -{ - _settingsDialog->deleteLater(); -} - -// This should rather be in application.... or rather in ConfigFile? -void ownCloudGui::slotOpenSettingsDialog() -{ - // if account is set up, start the configuration wizard. - if (!AccountManager::instance()->accounts().isEmpty()) { - if (QApplication::activeWindow() != _settingsDialog) { - slotShowSettings(); - } else { - _settingsDialog->close(); - } - } else { - qCInfo(lcApplication) << "No configured folders yet, starting setup wizard"; - runNewAccountWizard(); - } -} - -void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason) -{ - if (_workaroundFakeDoubleClick) { - static QElapsedTimer last_click; - if (last_click.isValid() && last_click.elapsed() < 200) { - return; - } - last_click.start(); - } - - // Left click - if (reason == QSystemTrayIcon::Trigger) { -#ifdef Q_OS_MAC - // on macOS, a left click always opens menu. - // However if the settings dialog is already visible but hidden - // by other applications, this will bring it to the front. - if (_settingsDialog->isVisible()) { - raiseDialog(_settingsDialog); - } -#else - slotOpenSettingsDialog(); -#endif - } - // FIXME: Also make sure that any auto updater dialogue https://github.com/owncloud/client/issues/5613 - // or SSL error dialog also comes to front. -} - -void ownCloudGui::slotSyncStateChange(Folder *folder) -{ - slotComputeOverallSyncStatus(); - updateContextMenuNeeded(); - - if (!folder) { - return; // Valid, just a general GUI redraw was needed. - } - - auto result = folder->syncResult(); - - qCInfo(lcApplication) << "Sync state changed for folder " << folder->remoteUrl().toString() << ": " << result.statusString(); - - if (result.status() == SyncResult::Success - || result.status() == SyncResult::Problem - || result.status() == SyncResult::SyncAbortRequested - || result.status() == SyncResult::Error) { - Logger::instance()->enterNextLogFile(); - } - - if (result.status() == SyncResult::NotYetStarted) { - _settingsDialog->slotRefreshActivity(folder->accountState()); - } -} - -void ownCloudGui::slotFoldersChanged() -{ - slotComputeOverallSyncStatus(); - updateContextMenuNeeded(); -} - -void ownCloudGui::slotOpenPath(const QString &path) -{ - showInFileManager(path); -} - -void ownCloudGui::slotAccountStateChanged() -{ - updateContextMenuNeeded(); - slotComputeOverallSyncStatus(); -} - -void ownCloudGui::slotTrayMessageIfServerUnsupported(Account *account) -{ - if (account->serverVersionUnsupported()) { - slotShowTrayMessage( - tr("Unsupported Server Version"), - tr("The server on account %1 runs an unsupported version %2. " - "Using this client with unsupported server versions is untested and " - "potentially dangerous. Proceed at your own risk.") - .arg(account->displayName(), account->serverVersion())); - } -} - -void ownCloudGui::slotComputeOverallSyncStatus() -{ - bool allSignedOut = true; - bool allPaused = true; - bool allDisconnected = true; - QVector problemAccounts; - auto setStatusText = [&](const QString &text) { - // Don't overwrite the status if we're currently syncing - if (FolderMan::instance()->isAnySyncRunning()) - return; - _actionStatus->setText(text); - }; - - for (const auto &a : AccountManager::instance()->accounts()) { - if (!a->isSignedOut()) { - allSignedOut = false; - } - if (!a->isConnected()) { - problemAccounts.append(a); - } else { - allDisconnected = false; - } - } - - const auto &map = FolderMan::instance()->map(); - for (auto *f : map) { - if (!f->syncPaused()) { - allPaused = false; - } - } - - if (!problemAccounts.empty()) { - _tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible())); - if (allDisconnected) { - setStatusText(tr("Disconnected")); - } else { - setStatusText(tr("Disconnected from some accounts")); - } -#ifdef Q_OS_WIN - // Windows has a 128-char tray tooltip length limit. - QStringList accountNames; - for (const auto &a : qAsConst(problemAccounts)) { - accountNames.append(a->account()->displayName()); - } - _tray->setToolTip(tr("Disconnected from %1").arg(accountNames.join(QLatin1String(", ")))); -#else - QStringList messages; - messages.append(tr("Disconnected from accounts:")); - for (const auto &a : qAsConst(problemAccounts)) { - QString message = tr("Account %1: %2").arg(a->account()->displayName(), a->stateString(a->state())); - if (!a->connectionErrors().empty()) { - message += QLatin1String("\n"); - message += a->connectionErrors().join(QLatin1String("\n")); - } - messages.append(message); - } - _tray->setToolTip(messages.join(QLatin1String("\n\n"))); -#endif - return; - } - - if (allSignedOut) { - _tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible())); - _tray->setToolTip(tr("Please sign in")); - setStatusText(tr("Signed out")); - return; - } else if (allPaused) { - _tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true, contextMenuVisible())); - _tray->setToolTip(tr("Account synchronization is disabled")); - setStatusText(tr("Synchronization is paused")); - return; - } - - // display the info of the least successful sync (eg. do not just display the result of the latest sync) - QString trayMessage; - - auto trayOverallStatusResult = FolderMan::trayOverallStatus(map.values()); - - const QIcon statusIcon = Theme::instance()->syncStateIcon(trayOverallStatusResult.overallStatus(), true, contextMenuVisible()); - _tray->setIcon(statusIcon); - - // create the tray blob message, check if we have an defined state -#ifdef Q_OS_WIN - // Windows has a 128-char tray tooltip length limit. - trayMessage = FolderMan::instance()->trayTooltipStatusString(trayOverallStatusResult.overallStatus(), false); -#else - QStringList allStatusStrings; - for (auto *folder : map) { - QString folderMessage = FolderMan::trayTooltipStatusString( - folder->syncResult(), - folder->syncPaused()); - allStatusStrings += tr("Folder %1: %2").arg(folder->shortGuiLocalPath(), folderMessage); - } - trayMessage = allStatusStrings.join(QLatin1String("\n")); -#endif - _tray->setToolTip(trayMessage); - - switch (trayOverallStatusResult.overallStatus().status()) { - case SyncResult::Problem: - if (trayOverallStatusResult.overallStatus().hasUnresolvedConflicts()) { - setStatusText(tr("Unresolved %1 conflicts").arg(QString::number(trayOverallStatusResult.overallStatus().numNewConflictItems()))); - } else if (trayOverallStatusResult.overallStatus().numBlacklistErrors() != 0) { - setStatusText(tr("Ignored errors %1").arg(QString::number(trayOverallStatusResult.overallStatus().numBlacklistErrors()))); - } - break; - case SyncResult::Success: { - QString lastSyncDoneString; - // display only the time in case the last sync was today - if (QDateTime::currentDateTime().date() == trayOverallStatusResult.lastSyncDone.date()) { - lastSyncDoneString = QLocale().toString(trayOverallStatusResult.lastSyncDone.time()); - } else { - lastSyncDoneString = QLocale().toString(trayOverallStatusResult.lastSyncDone); - } - setStatusText(tr("Up to date (%1)").arg(lastSyncDoneString)); - } break; - case SyncResult::Undefined: - _tray->setToolTip(tr("There are no sync folders configured.")); - setStatusText(tr("No sync folders configured")); - default: - setStatusText(FolderMan::instance()->trayTooltipStatusString(trayOverallStatusResult.overallStatus(), false)); - } -} - -void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *menu, bool separateMenu) -{ - // Only show the name in the action if it's not part of an - // account sub menu. - QString browserOpen = tr("Open in browser"); - if (!separateMenu) { - browserOpen = tr("Open %1 in browser").arg(Theme::instance()->appNameGUI()); - } - auto actionOpenoC = menu->addAction(browserOpen); - actionOpenoC->setProperty(propertyAccountC, QVariant::fromValue(accountState->account())); - QObject::connect(actionOpenoC, &QAction::triggered, this, &ownCloudGui::slotOpenOwnCloud); - - FolderMan *folderMan = FolderMan::instance(); - bool firstFolder = true; - const auto &map = folderMan->map(); - bool singleSyncFolder = map.size() == 1 && Theme::instance()->singleSyncFolder(); - bool onePaused = false; - bool allPaused = true; - for (auto *folder : map) { - if (folder->accountState() != accountState.data()) { - continue; - } - - if (folder->syncPaused()) { - onePaused = true; - } else { - allPaused = false; - } - - if (firstFolder && !singleSyncFolder) { - firstFolder = false; - menu->addSeparator(); - menu->addAction(tr("Managed Folders:"))->setDisabled(true); - } - - QAction *action = menu->addAction(tr("Open folder '%1'").arg(folder->shortGuiLocalPath())); - auto alias = folder->alias(); - connect(action, &QAction::triggered, this, [this, alias] { this->slotFolderOpenAction(alias); }); - } - - menu->addSeparator(); - if (separateMenu) { - if (onePaused) { - QAction *enable = menu->addAction(tr("Unpause all folders")); - enable->setProperty(propertyAccountC, QVariant::fromValue(accountState)); - connect(enable, &QAction::triggered, this, &ownCloudGui::slotUnpauseAllFolders); - } - if (!allPaused) { - QAction *enable = menu->addAction(tr("Pause all folders")); - enable->setProperty(propertyAccountC, QVariant::fromValue(accountState)); - connect(enable, &QAction::triggered, this, &ownCloudGui::slotPauseAllFolders); - } - - if (accountState->isSignedOut()) { - QAction *signin = menu->addAction(tr("Log in...")); - signin->setProperty(propertyAccountC, QVariant::fromValue(accountState)); - connect(signin, &QAction::triggered, this, &ownCloudGui::slotLogin); - } else { - QAction *signout = menu->addAction(tr("Log out")); - signout->setProperty(propertyAccountC, QVariant::fromValue(accountState)); - connect(signout, &QAction::triggered, this, &ownCloudGui::slotLogout); - } - } -} - -SettingsDialog *ownCloudGui::settingsDialog() const -{ - return _settingsDialog; -} - -void ownCloudGui::slotContextMenuAboutToShow() -{ - _contextMenuVisibleManual = true; - - // Update icon in sys tray, as it might change depending on the context menu state - slotComputeOverallSyncStatus(); - - if (!_workaroundNoAboutToShowUpdate) { - updateContextMenu(); - } -} - -void ownCloudGui::slotContextMenuAboutToHide() -{ - _contextMenuVisibleManual = false; - - // Update icon in sys tray, as it might change depending on the context menu state - slotComputeOverallSyncStatus(); -} - -bool ownCloudGui::contextMenuVisible() const -{ - // On some platforms isVisible doesn't work and always returns false, - // elsewhere aboutToHide is unreliable. - if (_workaroundManualVisibility) - return _contextMenuVisibleManual; - return _contextMenu->isVisible(); -} - -void ownCloudGui::hideAndShowTray() -{ - _tray->hide(); - _tray->show(); -} - -static bool minimalTrayMenu() -{ - static QByteArray var = qgetenv("OWNCLOUD_MINIMAL_TRAY_MENU"); - return !var.isEmpty(); -} - -static bool updateWhileVisible() -{ - static QByteArray var = qgetenv("OWNCLOUD_TRAY_UPDATE_WHILE_VISIBLE"); - if (var == "1") { - return true; - } else if (var == "0") { - return false; - } else { - // triggers bug on OS X: https://bugreports.qt.io/browse/QTBUG-54845 - // or flickering on Xubuntu - return false; - } -} - -static QByteArray envForceQDBusTrayWorkaround() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_QDBUS_TRAY_WORKAROUND"); - return var; -} - -static QByteArray envForceWorkaroundShowAndHideTray() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_SHOW_HIDE"); - return var; -} - -static QByteArray envForceWorkaroundNoAboutToShowUpdate() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_NO_ABOUT_TO_SHOW"); - return var; -} - -static QByteArray envForceWorkaroundFakeDoubleClick() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_FAKE_DOUBLE_CLICK"); - return var; -} - -static QByteArray envForceWorkaroundManualVisibility() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_MANUAL_VISIBILITY"); - return var; -} - -void ownCloudGui::setupContextMenu() -{ - if (_contextMenu) { - return; - } - - _contextMenu.reset(new QMenu()); - _contextMenu->setTitle(Theme::instance()->appNameGUI()); - - _recentActionsMenu = new QMenu(tr("Recent Changes"), _contextMenu.data()); - - // this must be called only once after creating the context menu, or - // it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04). - _tray->setContextMenu(_contextMenu.data()); - - // The tray menu is surprisingly problematic. Being able to switch to - // a minimal version of it is a useful workaround and testing tool. - if (minimalTrayMenu()) { - if (! Theme::instance()->about().isEmpty()) { - _contextMenu->addSeparator(); - _contextMenu->addAction(_actionAbout); - } - _contextMenu->addAction(_actionQuit); - return; - } - - auto applyEnvVariable = [](bool *sw, const QByteArray &value) { - if (value == "1") - *sw = true; - if (value == "0") - *sw = false; - }; - - // This is an old compound flag that people might still depend on - bool qdbusmenuWorkarounds = false; - applyEnvVariable(&qdbusmenuWorkarounds, envForceQDBusTrayWorkaround()); - if (qdbusmenuWorkarounds) { - _workaroundFakeDoubleClick = true; - _workaroundNoAboutToShowUpdate = true; - _workaroundShowAndHideTray = true; - } - -#ifdef Q_OS_MAC - // https://bugreports.qt.io/browse/QTBUG-54633 - _workaroundNoAboutToShowUpdate = true; - _workaroundManualVisibility = true; -#endif - -#ifdef Q_OS_LINUX - // For KDE sessions if the platform plugin is missing, - // neither aboutToShow() updates nor the isVisible() call - // work. At least aboutToHide is reliable. - // https://github.com/owncloud/client/issues/6545 - static QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP"); - static QByteArray desktopSession = qgetenv("DESKTOP_SESSION"); - bool isKde = - xdgCurrentDesktop.contains("KDE") - || desktopSession.contains("plasma") - || desktopSession.contains("kde"); - QObject *platformMenu = reinterpret_cast(_tray->contextMenu()->platformMenu()); - if (isKde && platformMenu && platformMenu->metaObject()->className() == QLatin1String("QDBusPlatformMenu")) { - _workaroundManualVisibility = true; - _workaroundNoAboutToShowUpdate = true; - } -#endif - - applyEnvVariable(&_workaroundNoAboutToShowUpdate, envForceWorkaroundNoAboutToShowUpdate()); - applyEnvVariable(&_workaroundFakeDoubleClick, envForceWorkaroundFakeDoubleClick()); - applyEnvVariable(&_workaroundShowAndHideTray, envForceWorkaroundShowAndHideTray()); - applyEnvVariable(&_workaroundManualVisibility, envForceWorkaroundManualVisibility()); - - qCInfo(lcApplication) << "Tray menu workarounds:" - << "noabouttoshow:" << _workaroundNoAboutToShowUpdate - << "fakedoubleclick:" << _workaroundFakeDoubleClick - << "showhide:" << _workaroundShowAndHideTray - << "manualvisibility:" << _workaroundManualVisibility; - - - connect(&_delayedTrayUpdateTimer, &QTimer::timeout, this, &ownCloudGui::updateContextMenu); - _delayedTrayUpdateTimer.setInterval(2s); - _delayedTrayUpdateTimer.setSingleShot(true); - - connect(_contextMenu.data(), &QMenu::aboutToShow, this, &ownCloudGui::slotContextMenuAboutToShow); - // unfortunately aboutToHide is unreliable, it seems to work on OSX though - connect(_contextMenu.data(), &QMenu::aboutToHide, this, &ownCloudGui::slotContextMenuAboutToHide); - - // Populate the context menu now. - updateContextMenu(); -} - -void ownCloudGui::updateContextMenu() -{ - if (minimalTrayMenu()) { - return; - } - - // If it's visible, we can't update live, and it won't be updated lazily: reschedule - if (contextMenuVisible() && !updateWhileVisible() && _workaroundNoAboutToShowUpdate) { - if (!_delayedTrayUpdateTimer.isActive()) { - _delayedTrayUpdateTimer.start(); - } - return; - } - - if (_workaroundShowAndHideTray) { - // To make tray menu updates work with these bugs (see setupContextMenu) - // we need to hide and show the tray icon. We don't want to do that - // while it's visible! - if (contextMenuVisible()) { - if (!_delayedTrayUpdateTimer.isActive()) { - _delayedTrayUpdateTimer.start(); - } - return; - } - _tray->hide(); - } - - _contextMenu->clear(); - slotRebuildRecentMenus(); - - // We must call deleteLater because we might be called from the press in one of the actions. - for (auto *menu : qAsConst(_accountMenus)) { - menu->deleteLater(); - } - _accountMenus.clear(); - - - const auto &accountList = AccountManager::instance()->accounts(); - - bool isConfigured = (!accountList.isEmpty()); - bool atLeastOneConnected = false; - bool atLeastOneSignedOut = false; - bool atLeastOneSignedIn = false; - bool atLeastOnePaused = false; - bool atLeastOneNotPaused = false; - for (const auto &a : accountList) { - if (a->isConnected()) { - atLeastOneConnected = true; - } - if (a->isSignedOut()) { - atLeastOneSignedOut = true; - } else { - atLeastOneSignedIn = true; - } - } - - for (auto *f : FolderMan::instance()->map()) { - if (f->syncPaused()) { - atLeastOnePaused = true; - } else { - atLeastOneNotPaused = true; - } - } - - if (accountList.count() > 1) { - for (const auto &account : accountList) { - QMenu *accountMenu = new QMenu(account->account()->displayName(), _contextMenu.data()); - _accountMenus.append(accountMenu); - _contextMenu->addMenu(accountMenu); - - addAccountContextMenu(account, accountMenu, true); - } - } else if (accountList.count() == 1) { - addAccountContextMenu(accountList.first(), _contextMenu.data(), false); - } - - _contextMenu->addSeparator(); - - _contextMenu->addAction(_actionStatus); - if (isConfigured && atLeastOneConnected) { - _contextMenu->addMenu(_recentActionsMenu); - } - - _contextMenu->addSeparator(); - - if (accountList.isEmpty()) { - _contextMenu->addAction(_actionNewAccountWizard); - } - _contextMenu->addAction(_actionSettings); - if (!Theme::instance()->helpUrl().isEmpty()) { - _contextMenu->addAction(_actionHelp); - } - - if (_actionCrash) { - _contextMenu->addAction(_actionCrash); - _contextMenu->addAction(_actionCrashEnforce); - _contextMenu->addAction(_actionCrashFatal); - - } - - _contextMenu->addSeparator(); - if (atLeastOnePaused) { - QString text; - if (accountList.count() > 1) { - text = tr("Unpause all synchronization"); - } else { - text = tr("Unpause synchronization"); - } - QAction *action = _contextMenu->addAction(text); - connect(action, &QAction::triggered, this, &ownCloudGui::slotUnpauseAllFolders); - } - if (atLeastOneNotPaused) { - QString text; - if (accountList.count() > 1) { - text = tr("Pause all synchronization"); - } else { - text = tr("Pause synchronization"); - } - QAction *action = _contextMenu->addAction(text); - connect(action, &QAction::triggered, this, &ownCloudGui::slotPauseAllFolders); - } - if (atLeastOneSignedIn) { - if (accountList.count() > 1) { - _actionLogout->setText(tr("Log out of all accounts")); - } else { - _actionLogout->setText(tr("Log out")); - } - _contextMenu->addAction(_actionLogout); - } - if (atLeastOneSignedOut) { - if (accountList.count() > 1) { - _actionLogin->setText(tr("Log in to all accounts...")); - } else { - _actionLogin->setText(tr("Log in...")); - } - _contextMenu->addAction(_actionLogin); - } - - if (! Theme::instance()->about().isEmpty()) { - _contextMenu->addSeparator(); - _contextMenu->addAction(_actionAbout); - } - - _contextMenu->addAction(_actionQuit); - - if (_workaroundShowAndHideTray) { - _tray->show(); - } -} - -void ownCloudGui::updateContextMenuNeeded() -{ - // if it's visible and we can update live: update now - if (contextMenuVisible() && updateWhileVisible()) { - // Note: don't update while visible on OSX - // https://bugreports.qt.io/browse/QTBUG-54845 - updateContextMenu(); - return; - } - - // if we can't lazily update: update later - if (_workaroundNoAboutToShowUpdate) { - // Note: don't update immediately even in the invisible case - // as that can lead to extremely frequent menu updates - if (!_delayedTrayUpdateTimer.isActive()) { - _delayedTrayUpdateTimer.start(); - } - return; - } -} - -void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg, const QIcon &icon) -{ - // SyncResult::Problem is returns the info icon - _tray->showMessage(title, msg, icon.isNull() ? Theme::instance()->syncStateIcon(SyncResult::Problem) : icon); -} - -void ownCloudGui::slotShowOptionalTrayMessage(const QString &title, const QString &msg, const QIcon &icon) -{ - ConfigFile cfg; - if (cfg.optionalDesktopNotifications()) { - slotShowTrayMessage(title, msg, icon); - } -} - - -/* - * open the folder with the given Alias - */ -void ownCloudGui::slotFolderOpenAction(const QString &alias) -{ - Folder *f = FolderMan::instance()->folder(alias); - if (f) { - qCInfo(lcApplication) << "opening local url " << f->path(); - QUrl url = QUrl::fromLocalFile(f->path()); - -#ifdef Q_OS_WIN - // work around a bug in QDesktopServices on Win32, see i-net - QString filePath = f->path(); - - if (filePath.startsWith(QLatin1String("\\\\")) || filePath.startsWith(QLatin1String("//"))) - url = QUrl::fromLocalFile(QDir::toNativeSeparators(filePath)); - else - url = QUrl::fromLocalFile(filePath); -#endif - QDesktopServices::openUrl(url); - } -} - -void ownCloudGui::setupActions() -{ - _actionStatus = new QAction(tr("Unknown status"), this); - _actionStatus->setEnabled(false); - _actionSettings = new QAction(tr("Settings..."), this); - _actionNewAccountWizard = new QAction(tr("New account..."), this); - _actionRecent = new QAction(tr("Details..."), this); - _actionRecent->setEnabled(true); - - QObject::connect(_actionRecent, &QAction::triggered, this, &ownCloudGui::slotShowSyncProtocol); - QObject::connect(_actionSettings, &QAction::triggered, this, &ownCloudGui::slotShowSettings); - QObject::connect(_actionNewAccountWizard, &QAction::triggered, this, &ownCloudGui::runNewAccountWizard); - _actionHelp = new QAction(tr("Help"), this); - QObject::connect(_actionHelp, &QAction::triggered, this, &ownCloudGui::slotHelp); - _actionAbout = new QAction(tr("About %1").arg(Theme::instance()->appNameGUI()), this); - QObject::connect(_actionAbout, &QAction::triggered, this, &ownCloudGui::slotAbout); - _actionQuit = new QAction(tr("Quit %1").arg(Theme::instance()->appNameGUI()), this); - QObject::connect(_actionQuit, &QAction::triggered, _app, &QApplication::quit); - - _actionLogin = new QAction(tr("Log in..."), this); - connect(_actionLogin, &QAction::triggered, this, &ownCloudGui::slotLogin); - _actionLogout = new QAction(tr("Log out"), this); - connect(_actionLogout, &QAction::triggered, this, &ownCloudGui::slotLogout); - - if (_app->debugMode()) { - _actionCrash = new QAction("Crash now - Div by zero", this); - connect(_actionCrash, &QAction::triggered, _app, &Application::slotCrash); - _actionCrashEnforce = new QAction("Crash now - ENFORCE()", this); - connect(_actionCrashEnforce, &QAction::triggered, _app, &Application::slotCrashEnforce); - _actionCrashFatal = new QAction("Crash now - qFatal", this); - connect(_actionCrashFatal, &QAction::triggered, _app, &Application::slotCrashFatal); - } else { - _actionCrash = nullptr; - _actionCrashEnforce = nullptr; - _actionCrashFatal = nullptr; - } -} - -void ownCloudGui::slotRebuildRecentMenus() -{ - _recentActionsMenu->clear(); - if (!_recentItemsActions.isEmpty()) { - for (auto *a : qAsConst(_recentItemsActions)) { - _recentActionsMenu->addAction(a); - } - _recentActionsMenu->addSeparator(); - } else { - _recentActionsMenu->addAction(tr("No items synced recently"))->setEnabled(false); - } - // add a more... entry. - _recentActionsMenu->addAction(_actionRecent); -} - -/// Returns true if the completion of a given item should show up in the -/// 'Recent Activity' menu -static bool shouldShowInRecentsMenu(const SyncFileItem &item) -{ - return !Progress::isIgnoredKind(item._status) - && item._instruction != CSYNC_INSTRUCTION_EVAL - && item._instruction != CSYNC_INSTRUCTION_NONE; -} - - -void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &progress) -{ - Q_UNUSED(folder); - - if (progress.status() == ProgressInfo::Discovery) { - if (!progress._currentDiscoveredRemoteFolder.isEmpty()) { - _actionStatus->setText(tr("Checking for changes in remote '%1'") - .arg(progress._currentDiscoveredRemoteFolder)); - } else if (!progress._currentDiscoveredLocalFolder.isEmpty()) { - _actionStatus->setText(tr("Checking for changes in local '%1'") - .arg(progress._currentDiscoveredLocalFolder)); - } - } else if (progress.status() == ProgressInfo::Done) { - QTimer::singleShot(2s, this, &ownCloudGui::slotComputeOverallSyncStatus); - } - if (progress.status() != ProgressInfo::Propagation) { - return; - } - - if (progress.totalSize() == 0) { - qint64 currentFile = progress.currentFile(); - qint64 totalFileCount = qMax(progress.totalFiles(), currentFile); - QString msg; - if (progress.trustEta()) { - msg = tr("Syncing %1 of %2 (%3 left)") - .arg(currentFile) - .arg(totalFileCount) - .arg(Utility::durationToDescriptiveString2(progress.totalProgress().estimatedEta)); - } else { - msg = tr("Syncing %1 of %2") - .arg(currentFile) - .arg(totalFileCount); - } - _actionStatus->setText(msg); - } else { - QString totalSizeStr = Utility::octetsToString(progress.totalSize()); - QString msg; - if (progress.trustEta()) { - msg = tr("Syncing %1 (%2 left)") - .arg(totalSizeStr, Utility::durationToDescriptiveString2(progress.totalProgress().estimatedEta)); - } else { - msg = tr("Syncing %1") - .arg(totalSizeStr); - } - _actionStatus->setText(msg); - } - - _actionRecent->setIcon(QIcon()); // Fixme: Set a "in-progress"-item eventually. - - if (!progress._lastCompletedItem.isEmpty() - && shouldShowInRecentsMenu(progress._lastCompletedItem)) { - if (Progress::isWarningKind(progress._lastCompletedItem._status)) { - // display a warn icon if warnings happened. - _actionRecent->setIcon(Utility::getCoreIcon(QStringLiteral("warning"))); - } - - QString kindStr = Progress::asResultString(progress._lastCompletedItem); - QString timeStr = QTime::currentTime().toString("hh:mm"); - QString actionText = tr("%1 (%2, %3)").arg(progress._lastCompletedItem._file, kindStr, timeStr); - QAction *action = new QAction(actionText, this); - Folder *f = FolderMan::instance()->folder(folder); - if (f) { - QString fullPath = f->path() + '/' + progress._lastCompletedItem._file; - if (QFile(fullPath).exists()) { - connect(action, &QAction::triggered, this, [this, fullPath] { this->slotOpenPath(fullPath); }); - } else { - action->setEnabled(false); - } - } - if (_recentItemsActions.length() > 5) { - _recentItemsActions.takeFirst()->deleteLater(); - } - _recentItemsActions.append(action); - - // Update the "Recent" menu if the context menu is being shown, - // otherwise it'll be updated later, when the context menu is opened. - if (updateWhileVisible() && contextMenuVisible()) { - slotRebuildRecentMenus(); - } - } -} - -void ownCloudGui::slotLogin() -{ - if (auto account = qvariant_cast(sender()->property(propertyAccountC))) { - account->account()->resetRejectedCertificates(); - account->signIn(); - } else { - for (const auto &a : AccountManager::instance()->accounts()) { - a->signIn(); - } - } -} - -void ownCloudGui::slotLogout() -{ - auto list = AccountManager::instance()->accounts(); - if (auto account = qvariant_cast(sender()->property(propertyAccountC))) { - list.clear(); - list.insert(account->account()->uuid(), account); - } - - for (const auto &ai : qAsConst(list)) { - ai->signOutByUi(); - } -} - -void ownCloudGui::slotUnpauseAllFolders() -{ - setPauseOnAllFoldersHelper(false); -} - -void ownCloudGui::slotPauseAllFolders() -{ - setPauseOnAllFoldersHelper(true); -} - -void ownCloudGui::runNewAccountWizard() -{ - if (!_wizard) { - _wizard = new OwncloudSetupWizard(settingsDialog()); - connect(_wizard, &OwncloudSetupWizard::ownCloudWizardDone, _wizard, &OwncloudSetupWizard::deleteLater); - connect(_wizard, &OwncloudSetupWizard::ownCloudWizardDone, ocApp(), &Application::slotownCloudWizardDone); - FolderMan::instance()->setSyncEnabled(false); - _wizard->startWizard(); - } - raiseDialog(settingsDialog()); -} - -void ownCloudGui::setPauseOnAllFoldersHelper(bool pause) -{ - QList accounts; - if (auto account = qvariant_cast(sender()->property(propertyAccountC))) { - accounts.append(account.data()); - } else { - for (const auto &a : AccountManager::instance()->accounts()) { - accounts.append(a.data()); - } - } - for (auto *f : FolderMan::instance()->map()) { - if (accounts.contains(f->accountState())) { - f->setSyncPaused(pause); - if (pause) { - f->slotTerminateSync(); - } - } - } -} - -void ownCloudGui::slotShowGuiMessage(const QString &title, const QString &message) -{ - QMessageBox *msgBox = new QMessageBox(settingsDialog()); - msgBox->setWindowFlags(msgBox->windowFlags() | Qt::WindowStaysOnTopHint); - msgBox->setAttribute(Qt::WA_DeleteOnClose); - msgBox->setText(message); - msgBox->setWindowTitle(title); - msgBox->setIcon(QMessageBox::Information); - msgBox->open(); - raiseDialog(msgBox); -} - -void ownCloudGui::slotShowSettings() -{ - raiseDialog(_settingsDialog); -} - -void ownCloudGui::slotShowSyncProtocol() -{ - slotShowSettings(); - _settingsDialog->showActivityPage(); -} - - -void ownCloudGui::slotShutdown() -{ - // explicitly close windows. This is somewhat of a hack to ensure - // that saving the geometries happens ASAP during a OS shutdown - - // those do delete on close - _settingsDialog->close(); -} - -void ownCloudGui::slotToggleLogBrowser() -{ - auto logBrowser = new LogBrowser(settingsDialog()); - logBrowser->setAttribute(Qt::WA_DeleteOnClose); - logBrowser->open(); - raiseDialog(logBrowser); -} - -void ownCloudGui::slotOpenOwnCloud() -{ - if (auto account = qvariant_cast(sender()->property(propertyAccountC))) { - QDesktopServices::openUrl(account->url()); - } -} - -void ownCloudGui::slotHelp() -{ - QDesktopServices::openUrl(QUrl(Theme::instance()->helpUrl())); -} - -void ownCloudGui::raiseDialog(QWidget *raiseWidget) -{ - auto window = ocApp()->gui()->settingsDialog(); - OC_ASSERT(window); - OC_ASSERT_X(!qobject_cast(raiseWidget) || raiseWidget->parentWidget() == window, "raiseDialog should only be called with modal dialogs"); - if (!window) { - return; - } - window->showNormal(); - window->raise(); - raiseWidget->showNormal(); - raiseWidget->raise(); - window->activateWindow(); - raiseWidget->activateWindow(); - -#if defined(Q_OS_WIN) - // Windows disallows raising a Window when you're not the active application. - // Use a common hack to attach to the active application - const auto activeProcessId = GetWindowThreadProcessId(GetForegroundWindow(), nullptr); - if (activeProcessId != qApp->applicationPid()) { - const auto threadId = GetCurrentThreadId(); - // don't step here with a debugger... - if (AttachThreadInput(threadId, activeProcessId, true)) - { - const auto hwnd = reinterpret_cast(window->winId()); - SetForegroundWindow(hwnd); - SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); - AttachThreadInput(threadId, activeProcessId, false); - } - } -#endif -} - - -void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage) -{ - QString file; - const auto folder = FolderMan::instance()->folderForPath(localPath, &file); - if (!folder) { - qCWarning(lcApplication) << "Could not open share dialog for" << localPath << "no responsible folder found"; - return; - } - - const auto accountState = folder->accountState(); - - SyncJournalFileRecord fileRecord; - - bool resharingAllowed = true; // lets assume the good - if (folder->journalDb()->getFileRecord(file, &fileRecord) && fileRecord.isValid()) { - // check the permission: Is resharing allowed? - if (!fileRecord._remotePerm.isNull() && !fileRecord._remotePerm.hasPermission(RemotePermissions::CanReshare)) { - resharingAllowed = false; - } - } - - // As a first approximation, set the set of permissions that can be granted - // either to everything (resharing allowed) or nothing (no resharing). - // - // The correct value will be found with a propfind from ShareDialog. - // (we want to show the dialog directly, not wait for the propfind first) - SharePermissions maxSharingPermissions = - SharePermissionRead - | SharePermissionUpdate | SharePermissionCreate | SharePermissionDelete - | SharePermissionShare; - if (!resharingAllowed) { - maxSharingPermissions = SharePermission(0); - } - - - ShareDialog *w = nullptr; - if (_shareDialogs.contains(localPath) && _shareDialogs[localPath]) { - qCInfo(lcApplication) << "Raising share dialog" << sharePath << localPath; - w = _shareDialogs[localPath]; - } else { - qCInfo(lcApplication) << "Opening share dialog" << sharePath << localPath << maxSharingPermissions; - w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, startPage, settingsDialog()); - w->setAttribute(Qt::WA_DeleteOnClose, true); - - _shareDialogs[localPath] = w; - connect(w, &QObject::destroyed, this, &ownCloudGui::slotRemoveDestroyedShareDialogs); - } - w->open(); - raiseDialog(w); -} - -void ownCloudGui::slotRemoveDestroyedShareDialogs() -{ - QMutableMapIterator> it(_shareDialogs); - while (it.hasNext()) { - it.next(); - if (!it.value() || it.value() == sender()) { - it.remove(); - } - } -} - -void ownCloudGui::slotAbout() -{ - if(!_aboutDialog) { - _aboutDialog = new AboutDialog(_settingsDialog); - _aboutDialog->setAttribute(Qt::WA_DeleteOnClose); - _aboutDialog->open(); - } - raiseDialog(_aboutDialog); -} - - -} // end namespace diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h deleted file mode 100644 index f1a6af92f0a..00000000000 --- a/src/gui/owncloudgui.h +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef OWNCLOUDGUI_H -#define OWNCLOUDGUI_H - -#include "systray.h" -#include "connectionvalidator.h" -#include "progressdispatcher.h" - -#include -#include -#include -#include -#include -#include - -namespace OCC { - -class Folder; - -class AboutDialog; -class SettingsDialog; -class ShareDialog; -class Application; -class LogBrowser; -class AccountState; -class OwncloudSetupWizard; - -enum class ShareDialogStartPage { - UsersAndGroups, - PublicLinks, -}; - -/** - * @brief The ownCloudGui class - * @ingroup gui - */ -class ownCloudGui : public QObject -{ - Q_OBJECT -public: - explicit ownCloudGui(Application *parent = nullptr); - ~ownCloudGui() override; - - bool checkAccountExists(bool openSettings); - - static void raiseDialog(QWidget *raiseWidget); - void setupOverlayIcons(); - - /// Whether the tray menu is visible - bool contextMenuVisible() const; - - void hideAndShowTray(); - - SettingsDialog *settingsDialog() const; - - void runNewAccountWizard(); - -signals: - void setupProxy(); - -public slots: - void setupContextMenu(); - void updateContextMenu(); - void updateContextMenuNeeded(); - void slotContextMenuAboutToShow(); - void slotContextMenuAboutToHide(); - void slotComputeOverallSyncStatus(); - void slotShowTrayMessage(const QString &title, const QString &msg, const QIcon &icon = {}); - void slotShowOptionalTrayMessage(const QString &title, const QString &msg, const QIcon &icon = {}); - void slotFolderOpenAction(const QString &alias); - void slotRebuildRecentMenus(); - void slotUpdateProgress(const QString &folder, const ProgressInfo &progress); - void slotShowGuiMessage(const QString &title, const QString &message); - void slotFoldersChanged(); - void slotShowSettings(); - void slotShowSyncProtocol(); - void slotShutdown(); - void slotSyncStateChange(Folder *); - void slotTrayClicked(QSystemTrayIcon::ActivationReason reason); - void slotToggleLogBrowser(); - void slotOpenOwnCloud(); - void slotOpenSettingsDialog(); - void slotHelp(); - void slotAbout(); - void slotOpenPath(const QString &path); - void slotAccountStateChanged(); - void slotTrayMessageIfServerUnsupported(Account *account); - - /** - * Open a share dialog for a file or folder. - * - * sharePath is the full remote path to the item, - * localPath is the absolute local path to it (so not relative - * to the folder). - */ - void slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage); - - void slotRemoveDestroyedShareDialogs(); - -private slots: - void slotLogin(); - void slotLogout(); - void slotUnpauseAllFolders(); - void slotPauseAllFolders(); - -private: - void setPauseOnAllFoldersHelper(bool pause); - void setupActions(); - void addAccountContextMenu(AccountStatePtr accountState, QMenu *menu, bool separateMenu); - - Systray *_tray; - SettingsDialog *_settingsDialog; - // tray's menu - QScopedPointer _contextMenu; - - // Manually tracking whether the context menu is visible via aboutToShow - // and aboutToHide. Unfortunately aboutToHide isn't reliable everywhere - // so this only gets used with _workaroundManualVisibility (when the tray's - // isVisible() is unreliable) - bool _contextMenuVisibleManual = false; - - QMenu *_recentActionsMenu; - QVector _accountMenus; - bool _workaroundShowAndHideTray = false; - bool _workaroundNoAboutToShowUpdate = false; - bool _workaroundFakeDoubleClick = false; - bool _workaroundManualVisibility = false; - QTimer _delayedTrayUpdateTimer; - QMap> _shareDialogs; - - QAction *_actionLogin; - QAction *_actionLogout; - - QAction *_actionNewAccountWizard; - QAction *_actionSettings; - QAction *_actionStatus; - QAction *_actionEstimate; - QAction *_actionRecent; - QAction *_actionHelp; - QAction *_actionAbout; - QAction *_actionQuit; - QAction *_actionCrash; - QAction *_actionCrashEnforce; - QAction *_actionCrashFatal; - - - QList _recentItemsActions; - Application *_app; - QPointer _wizard; - QPointer _aboutDialog; -}; - -} // namespace OCC - -#endif // OWNCLOUDGUI_H diff --git a/src/gui/owncloudsetupwizard.cpp b/src/gui/owncloudsetupwizard.cpp deleted file mode 100644 index c0e4f3dbcc6..00000000000 --- a/src/gui/owncloudsetupwizard.cpp +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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 -#include -#include -#include -#include -#include - -#include "wizard/owncloudwizardcommon.h" -#include "wizard/owncloudwizard.h" -#include "owncloudsetupwizard.h" -#include "configfile.h" -#include "folderman.h" -#include "accessmanager.h" -#include "account.h" -#include "networkjobs.h" -#include "sslerrordialog.h" -#include "accountmanager.h" -#include "clientproxy.h" -#include "filesystem.h" -#include "owncloudgui.h" -#include "settingsdialog.h" - -#include "creds/dummycredentials.h" - -using namespace std::chrono_literals; - -namespace OCC { - -OwncloudSetupWizard::OwncloudSetupWizard(QWidget *parent) - : QObject(parent) - , _ocWizard(new OwncloudWizard(parent)) -{ - connect(_ocWizard, &OwncloudWizard::determineAuthType, - this, &OwncloudSetupWizard::slotCheckServer); - connect(_ocWizard, &OwncloudWizard::connectToOCUrl, - this, &OwncloudSetupWizard::slotConnectToOCUrl); - connect(_ocWizard, &OwncloudWizard::createLocalAndRemoteFolders, - this, &OwncloudSetupWizard::slotCreateLocalAndRemoteFolders); - /* basicSetupFinished might be called from a reply from the network. - slotAssistantFinished might destroy the temporary QNetworkAccessManager. - Therefore Qt::QueuedConnection is required */ - connect(_ocWizard, &OwncloudWizard::basicSetupFinished, - this, &OwncloudSetupWizard::slotAssistantFinished, Qt::QueuedConnection); - connect(_ocWizard, &OwncloudWizard::finished, this, &QObject::deleteLater); -} - -OwncloudSetupWizard::~OwncloudSetupWizard() -{ - _ocWizard->deleteLater(); -} - -void OwncloudSetupWizard::startWizard() -{ - AccountPtr account = AccountManager::createAccount(); - account->setCredentials(new DummyCredentials); - account->setUrl(Theme::instance()->overrideServerUrlV2()); - _ocWizard->setAccount(account); - _ocWizard->setOCUrl(account->url().toString()); - - _ocWizard->setStartId(WizardCommon::Page_ServerSetup); - - _ocWizard->restart(); - - _ocWizard->open(); - ownCloudGui::raiseDialog(_ocWizard); -} - -// also checks if an installation is valid and determines auth type in a second step -void OwncloudSetupWizard::slotCheckServer(const QString &urlString) -{ - QString fixedUrl = urlString; - QUrl url = QUrl::fromUserInput(fixedUrl); - // fromUserInput defaults to http, not http if no scheme is specified - if (!fixedUrl.startsWith("http://") && !fixedUrl.startsWith("https://")) { - url.setScheme("https"); - } - AccountPtr account = _ocWizard->account(); - account->setUrl(url); - - // Reset the proxy which might had been determined previously in ConnectionValidator::checkServerAndAuth() - // when there was a previous account. - account->networkAccessManager()->setProxy(QNetworkProxy(QNetworkProxy::NoProxy)); - - // And also reset the QSslConfiguration, for the same reason (#6832) - // Here the client certificate is added, if any. Later it'll be in HttpCredentials - account->setSslConfiguration(QSslConfiguration()); - auto sslConfiguration = account->getOrCreateSslConfig(); // let Account set defaults - account->setSslConfiguration(sslConfiguration); - - // Make sure TCP connections get re-established - account->networkAccessManager()->clearAccessCache(); - - // Lookup system proxy in a thread https://github.com/owncloud/client/issues/2993 - if (ClientProxy::isUsingSystemDefault()) { - qCDebug(lcWizard) << "Trying to look up system proxy"; - ClientProxy::lookupSystemProxyAsync(account->url(), - this, SLOT(slotSystemProxyLookupDone(QNetworkProxy))); - } else { - // We want to reset the QNAM proxy so that the global proxy settings are used (via ClientProxy settings) - account->networkAccessManager()->setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy)); - // use a queued invocation so we're as asynchronous as with the other code path - QMetaObject::invokeMethod(this, &OwncloudSetupWizard::slotFindServer, Qt::QueuedConnection); - } -} - -void OwncloudSetupWizard::slotSystemProxyLookupDone(const QNetworkProxy &proxy) -{ - if (proxy.type() != QNetworkProxy::NoProxy) { - qCInfo(lcWizard) << "Setting QNAM proxy to be system proxy" << printQNetworkProxy(proxy); - } else { - qCInfo(lcWizard) << "No system proxy set by OS"; - } - AccountPtr account = _ocWizard->account(); - account->networkAccessManager()->setProxy(proxy); - - slotFindServer(); -} - -void OwncloudSetupWizard::slotFindServer() -{ - AccountPtr account = _ocWizard->account(); - - // Set fake credentials before we check what credential it actually is. - account->setCredentials(new DummyCredentials); - - // Determining the actual server URL can be a multi-stage process - // 1. Check url/status.php with CheckServerJob - // If that works we're done. In that case we don't check the - // url directly for redirects, see #5954. - // 2. Check the url for permanent redirects (like url shorteners) - // 3. Check redirected-url/status.php with CheckServerJob - - // Step 1: Check url/status.php - CheckServerJob *job = new CheckServerJob(account, this); - connect(job, &CheckServerJob::instanceFound, this, &OwncloudSetupWizard::slotFoundServer); - connect(job, &CheckServerJob::timeout, this, &OwncloudSetupWizard::slotNoServerFoundTimeout); - connect(job, &CheckServerJob::instanceNotFound, this, &OwncloudSetupWizard::slotNoServerFound); - job->setTimeout((account->url().scheme() == "https") ? 30s : 10s); - job->start(); -} - -void OwncloudSetupWizard::slotFoundServer(const QUrl &url, const QJsonObject &info) -{ - auto serverVersion = CheckServerJob::version(info); - - qCDebug(lcWizard) << "Successfully connected to" << url.toString() << "version" << CheckServerJob::versionString(info) << "(" << serverVersion << ")"; - - // Note with newer servers we get the version actually only later in capabilities - // https://github.com/owncloud/core/pull/27473/files - _ocWizard->account()->setServerVersion(serverVersion); - const auto oldUrl = _ocWizard->account()->url(); - if (oldUrl != url) { - qCInfo(lcWizard) << oldUrl << "was redirected to" << url; - if (url.scheme() == QLatin1String("https") && oldUrl.host() == url.host()) { - _ocWizard->account()->setUrl(url); - } else { - auto accountState = new AccountState(_ocWizard->account()); - connect(accountState, &AccountState::urlUpdated, this, [accountState, this] { - accountState->deleteLater(); - slotDetermineAuthType(); - }); - accountState->updateUrlDialog(url); - return; - } - } - slotDetermineAuthType(); -} - -void OwncloudSetupWizard::slotNoServerFound(QNetworkReply *) -{ - auto job = qobject_cast(sender()); - - // Do this early because reply might be deleted in message box event loop - QString msg; - if (!_ocWizard->account()->url().isValid()) { - msg = tr("Invalid URL"); - } else { - msg = tr("Failed to connect to %1 at %2:
    %3") - .arg(Utility::escape(Theme::instance()->appNameGUI()), - Utility::escape(_ocWizard->account()->url().toString()), - Utility::escape(job->errorString())); - } - - // Displays message inside wizard and possibly also another message box - _ocWizard->displayError(msg); - - // Allow the credentials dialog to pop up again for the same URL. - // Maybe the user just clicked 'Cancel' by accident or changed his mind. - _ocWizard->account()->resetRejectedCertificates(); -} - -void OwncloudSetupWizard::slotNoServerFoundTimeout(const QUrl &url) -{ - _ocWizard->displayError( - tr("Timeout while trying to connect to %1 at %2.") - .arg(Utility::escape(Theme::instance()->appNameGUI()), Utility::escape(url.toString()))); -} - -void OwncloudSetupWizard::slotDetermineAuthType() -{ - DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this); - connect(job, &DetermineAuthTypeJob::authType, - _ocWizard, &OwncloudWizard::setAuthType); - job->start(); -} - -void OwncloudSetupWizard::slotConnectToOCUrl(const QString &url) -{ - qCInfo(lcWizard) << "Connect to url: " << url; - AbstractCredentials *creds = _ocWizard->getCredentials(); - _ocWizard->account()->setCredentials(creds); - _ocWizard->setField(QLatin1String("OCUrl"), url); - - qCDebug(lcWizard) << "Trying to connect to" << url; - testOwnCloudConnect(); -} - -void OwncloudSetupWizard::testOwnCloudConnect() -{ - auto job = new ConnectionValidator(_ocWizard->account(), this); - connect(job, &ConnectionValidator::connectionResult, this, [this](ConnectionValidator::Status status, const QStringList &errors){ - qDebug() << status; - if (status == ConnectionValidator::Connected) { - _ocWizard->successfulStep(); - return; - } - _ocWizard->show(); - if (_ocWizard->currentId() == WizardCommon::Page_OAuthCreds) { - _ocWizard->back(); - } - _ocWizard->displayError(errors.join(QLatin1Char('\n'))); - }); - job->checkServerAndUpdate(); -} - -void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders() -{ - qCInfo(lcWizard) << "Setup local sync folder for new oC connection " << _ocWizard->localFolder(); - const QDir fi(_ocWizard->localFolder()); - - bool nextStep = true; - if (fi.exists()) { - FileSystem::setFolderMinimumPermissions(_ocWizard->localFolder()); - Utility::setupFavLink(_ocWizard->localFolder()); - // there is an existing local folder. If its non empty, it can only be synced if the - // ownCloud is newly created. - qCDebug(lcWizard) << "Local sync folder" << _ocWizard->localFolder() << "already exists, setting it up for sync."; - } else { - bool ok = true; - if (fi.mkpath(_ocWizard->localFolder())) { - FileSystem::setFolderMinimumPermissions(_ocWizard->localFolder()); - Utility::setupFavLink(_ocWizard->localFolder()); - } else { - ok = false; - qCWarning(lcWizard) << "Failed to create " << fi.path(); - _ocWizard->displayError(tr("Could not create local folder %1").arg(Utility::escape(_ocWizard->localFolder()))); - nextStep = false; - } - qCDebug(lcWizard) << "Creating local sync folder" << _ocWizard->localFolder() << "success:" << ok; - } - if (nextStep) { - EntityExistsJob *job = new EntityExistsJob(_ocWizard->account(), Utility::concatUrlPath(_ocWizard->account()->davPath(), _ocWizard->remoteFolder()).path(), this); - connect(job, &EntityExistsJob::exists, this, &OwncloudSetupWizard::slotRemoteFolderExists); - job->start(); - } else { - finalizeSetup(false); - } -} - -// ### TODO move into EntityExistsJob once we decide if/how to return gui strings from jobs -void OwncloudSetupWizard::slotRemoteFolderExists(QNetworkReply *reply) -{ - auto job = qobject_cast(sender()); - bool ok = true; - QString error; - QNetworkReply::NetworkError errId = reply->error(); - - if (errId == QNetworkReply::NoError) { - qCInfo(lcWizard) << "Remote folder found, all cool!"; - } else if (errId == QNetworkReply::ContentNotFoundError) { - createRemoteFolder(); - } else { - error = tr("Error: %1").arg(job->errorString()); - ok = false; - } - - if (!ok) { - _ocWizard->displayError(Utility::escape(error)); - } - - finalizeSetup(ok); -} - -void OwncloudSetupWizard::createRemoteFolder() -{ - qCDebug(lcWizard) << "creating folder on ownCloud:" << _ocWizard->remoteFolder(); - - MkColJob *job = new MkColJob(_ocWizard->account(), _ocWizard->remoteFolder(), this); - connect(job, &MkColJob::finishedWithError, this, &OwncloudSetupWizard::slotCreateRemoteFolderFinished); - connect(job, &MkColJob::finishedWithoutError, this, [this] { - qCDebug(lcWizard) << "Remote folder" << _ocWizard->remoteFolder() << "created successfully."; - finalizeSetup(true); - }); - job->start(); -} - -void OwncloudSetupWizard::slotCreateRemoteFolderFinished(QNetworkReply *reply) -{ - auto error = reply->error(); - qCDebug(lcWizard) << "** webdav mkdir request finished " << error; - - bool success = true; - if (error == 202) { - qCDebug(lcWizard) << "The remote folder" << _ocWizard->remoteFolder() << "already exists. Connecting it for syncing."; - } else if (error > 202 && error < 300) { - _ocWizard->displayError(tr("The folder creation resulted in HTTP error code %1").arg((int)error)); - - qCDebug(lcWizard) << "The folder creation resulted in HTTP error code" << error; - } else if (error == QNetworkReply::OperationCanceledError) { - _ocWizard->displayError(tr("The remote folder creation failed because the provided credentials " - "are wrong!" - "
    Please go back and check your credentials.

    ")); - qCDebug(lcWizard) << "Remote folder creation failed probably because the provided credentials are wrong. Please go back and check your credentials."; - _ocWizard->resetRemoteFolder(); - success = false; - } else { - qCDebug(lcWizard) << "Remote folder" << _ocWizard->remoteFolder() << "creation failed with error" << error; - _ocWizard->displayError(tr("Remote folder %1 creation failed with error %2.").arg(Utility::escape(_ocWizard->remoteFolder())).arg(error)); - _ocWizard->resetRemoteFolder(); - success = false; - } - - finalizeSetup(success); -} - -void OwncloudSetupWizard::finalizeSetup(bool success) -{ - const QString localFolder = _ocWizard->localFolder(); - if (success) { - qCDebug(lcWizard) << "A sync connection from" << localFolder << "to remote directory" << _ocWizard->remoteFolder() << "was set up."; - qCDebug(lcWizard) << "Successfully connected"; - _ocWizard->successfulStep(); - } else { - // ### this is not quite true, pass in the real problem as optional parameter - qCDebug(lcWizard) << "Connection could not be established. Please check again."; - } -} - -bool OwncloudSetupWizard::ensureStartFromScratch(const QString &localFolder) -{ - // first try to rename (backup) the current local dir. - bool renameOk = false; - while (!renameOk) { - renameOk = FolderMan::instance()->startFromScratch(localFolder); - if (!renameOk) { - QMessageBox::StandardButton but; - but = QMessageBox::question(nullptr, tr("Folder rename failed"), - tr("Can't remove and back up the folder because the folder or a file in it is open in another program." - " Please close the folder or file and hit retry or cancel the setup."), - QMessageBox::Retry | QMessageBox::Abort, QMessageBox::Retry); - if (but == QMessageBox::Abort) { - break; - } - } - } - return renameOk; -} - -// Method executed when the user end has finished the basic setup. -void OwncloudSetupWizard::slotAssistantFinished(int result) -{ - if (result == QDialog::Rejected) { - // Wizard was cancelled - } else if (_ocWizard->manualFolderConfig()) { - applyAccountChanges(); - } else { - FolderMan *folderMan = FolderMan::instance(); - auto account = applyAccountChanges(); - - QString localFolder = FolderDefinition::prepareLocalPath(_ocWizard->localFolder()); - - bool startFromScratch = _ocWizard->field("OCSyncFromScratch").toBool(); - if (!startFromScratch || ensureStartFromScratch(localFolder)) { - qCInfo(lcWizard) << "Adding folder definition for" << localFolder << _ocWizard->remoteFolder(); - FolderDefinition folderDefinition; - folderDefinition.localPath = localFolder; - folderDefinition.targetPath = FolderDefinition::prepareTargetPath(_ocWizard->remoteFolder()); - folderDefinition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles(); - if (_ocWizard->useVirtualFileSync()) { - folderDefinition.virtualFilesMode = bestAvailableVfsMode(); - } -#ifdef Q_OS_WIN - if (folderMan->navigationPaneHelper().showInExplorerNavigationPane()) - folderDefinition.navigationPaneClsid = QUuid::createUuid(); -#endif - - auto f = folderMan->addFolder(account, folderDefinition); - if (f) { - if (folderDefinition.virtualFilesMode != Vfs::Off && _ocWizard->useVirtualFileSync()) - f->setRootPinState(PinState::OnlineOnly); - - f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, - _ocWizard->selectiveSyncBlacklist()); - if (!_ocWizard->isConfirmBigFolderChecked()) { - // The user already accepted the selective sync dialog. everything is in the white list - f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, - QStringList() << QLatin1String("/")); - } - } - qCDebug(lcWizard) << "Local sync folder" << localFolder << "successfully created!"; - } - } - - // notify others. - emit ownCloudWizardDone(result); -} - -AccountState *OwncloudSetupWizard::applyAccountChanges() -{ - AccountPtr newAccount = _ocWizard->account(); - - // Detach the account that is going to be saved from the - // wizard to ensure it doesn't accidentally get modified - // later (such as from running cleanup such as - // AbstractCredentialsWizardPage::cleanupPage()) - _ocWizard->setAccount(AccountManager::createAccount()); - - auto manager = AccountManager::instance(); - - auto newState = manager->addAccount(newAccount); - manager->save(); - return newState; -} - -} // namespace OCC diff --git a/src/gui/owncloudsetupwizard.h b/src/gui/owncloudsetupwizard.h deleted file mode 100644 index dadb23285ff..00000000000 --- a/src/gui/owncloudsetupwizard.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef OWNCLOUDSETUPWIZARD_H -#define OWNCLOUDSETUPWIZARD_H - -#include -#include -#include -#include -#include - -#include "accountfwd.h" -#include "theme.h" -#include "networkjobs.h" - -#include "wizard/owncloudwizardcommon.h" - -namespace OCC { - -class AccountState; - -class OwncloudWizard; - -/** - * @brief The OwncloudSetupWizard class - * @ingroup gui - */ -class OwncloudSetupWizard : public QObject -{ - Q_OBJECT -public: -signals: - // overall dialog close signal. - void ownCloudWizardDone(int); - -private slots: - void slotCheckServer(const QString &); - void slotSystemProxyLookupDone(const QNetworkProxy &proxy); - - void slotFindServer(); - void slotFoundServer(const QUrl &, const QJsonObject &); - void slotNoServerFound(QNetworkReply *reply); - void slotNoServerFoundTimeout(const QUrl &url); - - void slotDetermineAuthType(); - - void slotConnectToOCUrl(const QString &); - - void slotCreateLocalAndRemoteFolders(); - void slotRemoteFolderExists(QNetworkReply *); - void slotCreateRemoteFolderFinished(QNetworkReply *reply); - void slotAssistantFinished(int); - -private: - explicit OwncloudSetupWizard(QWidget *parent = nullptr); - ~OwncloudSetupWizard() override; - void startWizard(); - void testOwnCloudConnect(); - void createRemoteFolder(); - void finalizeSetup(bool); - bool ensureStartFromScratch(const QString &localFolder); - AccountState *applyAccountChanges(); - - OwncloudWizard *_ocWizard; - - friend class ownCloudGui; -}; -} - -#endif // OWNCLOUDSETUPWIZARD_H diff --git a/src/gui/platform.cpp b/src/gui/platform.cpp deleted file mode 100644 index 79024f5d4af..00000000000 --- a/src/gui/platform.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) by Erik Verbruggen - * - * 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 "platform.h" - -namespace OCC { - -Platform::~Platform() -{ -} - -} // OCC namespace diff --git a/src/gui/platform.h b/src/gui/platform.h deleted file mode 100644 index 89d4a79a920..00000000000 --- a/src/gui/platform.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) by Erik Verbruggen - * - * 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 - -namespace OCC { - -/** - * @brief The Platform is the baseclass for all platform classes, which in turn implement platform - * specific functionality for the GUI. - */ -class Platform -{ -public: - virtual ~Platform() = 0; - - static std::unique_ptr create(); -}; - -} // OCC namespace diff --git a/src/gui/platform_mac.mm b/src/gui/platform_mac.mm deleted file mode 100644 index 7a81d51f5de..00000000000 --- a/src/gui/platform_mac.mm +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * Copyright (C) by Erik Verbruggen - * - * 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 "application.h" -#include "platform.h" - -#import - -#include - -@interface OwnAppDelegate : NSObject -- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag; -@end - -@implementation OwnAppDelegate { -} - -- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag -{ - if (auto app = qobject_cast(QApplication::instance())) - app->showSettingsDialog(); - return YES; -} - -@end - -namespace OCC { - -class MacPlatform : public Platform -{ -public: - MacPlatform(); - ~MacPlatform() override; - -private: - QMacAutoReleasePool _autoReleasePool; - OwnAppDelegate *_appDelegate; -}; - -MacPlatform::MacPlatform() -{ - NSApplicationLoad(); - _appDelegate = [[OwnAppDelegate alloc] init]; - [[NSApplication sharedApplication] setDelegate:_appDelegate]; - - signal(SIGPIPE, SIG_IGN); -} - -MacPlatform::~MacPlatform() -{ - [_appDelegate release]; -} - -std::unique_ptr Platform::create() -{ - return std::make_unique(); -} - -} // namespace OCC diff --git a/src/gui/platform_win.cpp b/src/gui/platform_win.cpp deleted file mode 100644 index 167be35ed69..00000000000 --- a/src/gui/platform_win.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) by Erik Verbruggen - * - * 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 "application.h" -#include "platform.h" - -#include - -namespace OCC { - -class WinPlatform : public Platform -{ -public: - WinPlatform() - { - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); - } - - ~WinPlatform() override; -}; - -WinPlatform::~WinPlatform() -{ -} - -std::unique_ptr Platform::create() -{ - return std::make_unique(); -} - -} // namespace OCC diff --git a/src/gui/protocolitem.cpp b/src/gui/protocolitem.cpp index 9c6c421cb74..ccf771fc208 100644 --- a/src/gui/protocolitem.cpp +++ b/src/gui/protocolitem.cpp @@ -16,10 +16,7 @@ #include "folderman.h" #include "progressdispatcher.h" -#include -#include #include -#include using namespace OCC; @@ -35,7 +32,7 @@ ProtocolItem::ProtocolItem(Folder *folder, const SyncFileItemPtr &item) , _sizeIsRelevant(ProgressInfo::isSizeDependent(*item)) { if (!item->_responseTimeStamp.isEmpty()) { - _timestamp = QDateTime::fromString(QString::fromUtf8(item->_responseTimeStamp), Qt::RFC2822Date); + _timestamp = Utility::parseRFC1123Date(QString::fromUtf8(item->_responseTimeStamp)); } else { _timestamp = QDateTime::currentDateTimeUtc(); } @@ -44,11 +41,6 @@ ProtocolItem::ProtocolItem(Folder *folder, const SyncFileItemPtr &item) } } -ProtocolItem::ProtocolItem(const QString &folder, const SyncFileItemPtr &item) - : ProtocolItem(FolderMan::instance()->folder(folder), item) -{ -} - QString ProtocolItem::path() const { return _path; diff --git a/src/gui/protocolitem.h b/src/gui/protocolitem.h index 9f7cc195647..bdb66b91116 100644 --- a/src/gui/protocolitem.h +++ b/src/gui/protocolitem.h @@ -13,7 +13,9 @@ */ #pragma once -#include "folder.h" +#include "gui/owncloudguilib.h" + +#include "gui/folder.h" #include "csync/csync.h" #include "libsync/syncfileitem.h" @@ -22,14 +24,12 @@ namespace OCC { -class ProtocolItem +class OWNCLOUDGUI_EXPORT ProtocolItem { Q_GADGET public: ProtocolItem() = default; explicit ProtocolItem(Folder *folder, const SyncFileItemPtr &item); - explicit ProtocolItem(const QString &folder, const SyncFileItemPtr &item); - QString path() const; Folder *folder() const; @@ -54,8 +54,8 @@ class ProtocolItem Folder *_folder; QDateTime _timestamp; qint64 _size; - SyncFileItem::Status _status BITFIELD(4); - SyncFileItem::Direction _direction BITFIELD(3); + SyncFileItem::Status _status; + SyncFileItem::Direction _direction; QString _message; bool _sizeIsRelevant; diff --git a/src/gui/protocolwidget.cpp b/src/gui/protocolwidget.cpp deleted file mode 100644 index 73b33b31403..00000000000 --- a/src/gui/protocolwidget.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 -#include -#include - -#include "protocolwidget.h" -#include "configfile.h" -#include "syncresult.h" -#include "logger.h" -#include "theme.h" -#include "folderman.h" -#include "folder.h" -#include "openfilemanager.h" -#include "guiutility.h" -#include "accountmanager.h" -#include "accountstate.h" -#include "syncfileitem.h" - -#include "models/activitylistmodel.h" -#include "models/expandingheaderview.h" -#include "models/models.h" - -#include "ui_protocolwidget.h" - -namespace OCC { - -ProtocolWidget::ProtocolWidget(QWidget *parent) - : QWidget(parent) - , _ui(new Ui::ProtocolWidget) -{ - _ui->setupUi(this); - - connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted, - this, &ProtocolWidget::slotItemCompleted); - - connect(_ui->_tableView, &QTreeWidget::customContextMenuRequested, this, &ProtocolWidget::slotItemContextMenu); - - // Build the model-view "stack": - // _model <- _sortModel <- _statusSortModel <- _tableView - _model = new ProtocolItemModel(2000, false, this); - _sortModel = new QSortFilterProxyModel(this); - _sortModel->setSourceModel(_model); - _sortModel->setSortRole(Models::UnderlyingDataRole); - _ui->_tableView->setModel(_sortModel); - - auto header = new ExpandingHeaderView(QStringLiteral("ActivityListHeaderV2"), _ui->_tableView); - _ui->_tableView->setHorizontalHeader(header); - header->setSectionResizeMode(QHeaderView::Interactive); - header->setExpandingColumn(static_cast(ProtocolItemModel::ProtocolItemRole::File)); - header->setSortIndicator(static_cast(ProtocolItemModel::ProtocolItemRole::Time), Qt::DescendingOrder); - header->hideSection(static_cast(ProtocolItemModel::ProtocolItemRole::Status)); - header->setContextMenuPolicy(Qt::CustomContextMenu); - connect(header, &QHeaderView::customContextMenuRequested, header, [header, this] { - auto menu = showFilterMenu(header, _sortModel); - header->addResetActionToMenu(menu); - }); - - connect(_ui->_filterButton, &QAbstractButton::clicked, this, [this] { - showFilterMenu(_ui->_filterButton, _sortModel); - }); - - connect(FolderMan::instance(), &FolderMan::folderRemoved, this, [this](Folder *f) { - _model->remove_if([f](const ProtocolItem &item) { - return item.folder() == f; - }); - }); -} - -ProtocolWidget::~ProtocolWidget() -{ - delete _ui; -} - -QMenu *ProtocolWidget::showFilterMenu(QWidget *parent, QSortFilterProxyModel *model) -{ - auto menu = new QMenu(parent); - menu->setAttribute(Qt::WA_DeleteOnClose); - Models::addFilterMenuItems(menu, AccountManager::instance()->accountNames(), model, static_cast(ProtocolItemModel::ProtocolItemRole::Account), tr("Account"), Qt::DisplayRole); - menu->addSeparator(); - QTimer::singleShot(0, menu, [menu] { - menu->popup(QCursor::pos()); - }); - return menu; -} - -void ProtocolWidget::showContextMenu(QWidget *parent, ProtocolItemModel *model, const QModelIndexList &items) -{ - auto menu = new QMenu(parent); - menu->setAttribute(Qt::WA_DeleteOnClose); - - // keep in sync with ActivityWidget::slotItemContextMenu - menu->addAction(tr("Copy to clipboard"), parent, [text = Models::formatSelection(items)] { - QApplication::clipboard()->setText(text); - }); - - if (items.size() == 1) { - const auto &data = model->protocolItem(items.first()); - { - const QString localPath = data.folder()->path() + data.path(); - if (QFileInfo::exists(localPath)) { - // keep in sync with ActivityWidget::slotItemContextMenu - menu->addAction(tr("Show in file browser"), parent, [localPath] { - if (QFileInfo::exists(localPath)) { - showInFileManager(localPath); - } - }); - } - // "Open in Browser" action - { - fetchPrivateLinkUrl(data.folder()->accountState()->account(), data.folder()->remotePathTrailingSlash() + data.path(), parent, [parent, menu = QPointer(menu)](const QString &url) { - // as fetchPrivateLinkUrl is async we need to check the menu still exists - if (menu) { - menu->addAction(tr("Show in web browser"), parent, [url, parent] { - Utility::openBrowser(url, parent); - }); - } - }); - } - { - switch (data.status()) { - case SyncFileItem::DetailError: - Q_FALLTHROUGH(); - case SyncFileItem::SoftError: - Q_FALLTHROUGH(); - case SyncFileItem::BlacklistedError: - menu->addAction(tr("Retry sync"), parent, [data, folder = QPointer(data.folder())] { - if (folder) { - folder->journalDb()->wipeErrorBlacklistEntry(data.path()); - FolderMan::instance()->scheduleFolderNext(folder); - } - }); - default: - break; - } - } - } - } - menu->popup(QCursor::pos()); -} - -void ProtocolWidget::slotItemContextMenu() -{ - auto rows = _ui->_tableView->selectionModel()->selectedRows(); - for (int i = 0; i < rows.size(); ++i) { - rows[i] = _sortModel->mapToSource(rows[i]); - } - showContextMenu(this, _model, rows); -} - -void ProtocolWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item) -{ - if (!item->showInProtocolTab()) - return; - _model->addProtocolItem(ProtocolItem { folder, item }); -} - -} diff --git a/src/gui/protocolwidget.h b/src/gui/protocolwidget.h deleted file mode 100644 index ac6e111eb8d..00000000000 --- a/src/gui/protocolwidget.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef PROTOCOLWIDGET_H -#define PROTOCOLWIDGET_H - -#include -#include -#include - -#include "progressdispatcher.h" -#include "owncloudgui.h" -#include "models/protocolitemmodel.h" - -#include "protocolitem.h" -#include "ui_protocolwidget.h" - -class QPushButton; -class QSortFilterProxyModel; - -namespace OCC { -class ExpandingHeaderView; - -namespace Ui { - class ProtocolWidget; -} -class Application; - -/** - * @brief The ProtocolWidget class - * @ingroup gui - */ -class ProtocolWidget : public QWidget -{ - Q_OBJECT -public: - explicit ProtocolWidget(QWidget *parent = nullptr); - ~ProtocolWidget() override; - - static void showContextMenu(QWidget *parent, ProtocolItemModel *model, const QModelIndexList &items); - static QMenu *showFilterMenu(QWidget *parent, QSortFilterProxyModel *model); - -public slots: - void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); - -private slots: - void slotItemContextMenu(); - -private: - ProtocolItemModel *_model; - QSortFilterProxyModel *_sortModel; - Ui::ProtocolWidget *_ui; -}; -} -#endif // PROTOCOLWIDGET_H diff --git a/src/gui/protocolwidget.ui b/src/gui/protocolwidget.ui deleted file mode 100644 index 9c95ad8cc57..00000000000 --- a/src/gui/protocolwidget.ui +++ /dev/null @@ -1,83 +0,0 @@ - - - OCC::ProtocolWidget - - - - 0 - 0 - 612 - 515 - - - - Form - - - - - - - - Local sync protocol - - - Qt::PlainText - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Filter - - - - - - - - - Qt::CustomContextMenu - - - true - - - QAbstractItemView::SelectRows - - - false - - - true - - - false - - - false - - - - - - - - - - - diff --git a/src/gui/proxyauthdialog.cpp b/src/gui/proxyauthdialog.cpp deleted file mode 100644 index c5680bb7e7a..00000000000 --- a/src/gui/proxyauthdialog.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2015 by Christian Kamm - * - * 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 "proxyauthdialog.h" -#include "ui_proxyauthdialog.h" - -namespace OCC { - -ProxyAuthDialog::ProxyAuthDialog(QWidget *parent) - : QDialog(parent) - , ui(new Ui::ProxyAuthDialog) -{ - ui->setupUi(this); -} - -ProxyAuthDialog::~ProxyAuthDialog() -{ - delete ui; -} - -void ProxyAuthDialog::setProxyAddress(const QString &address) -{ - ui->proxyAddress->setText(address); -} - -QString ProxyAuthDialog::username() const -{ - return ui->usernameEdit->text(); -} - -QString ProxyAuthDialog::password() const -{ - return ui->passwordEdit->text(); -} - -void ProxyAuthDialog::reset() -{ - ui->usernameEdit->setFocus(); - ui->usernameEdit->clear(); - ui->passwordEdit->clear(); -} - -} // namespace OCC diff --git a/src/gui/proxyauthdialog.h b/src/gui/proxyauthdialog.h deleted file mode 100644 index 63a262b8a4c..00000000000 --- a/src/gui/proxyauthdialog.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2015 by Christian Kamm - * - * 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. - */ - -#ifndef OCC_PROXYAUTHDIALOG_H -#define OCC_PROXYAUTHDIALOG_H - -#include - -namespace OCC { - -namespace Ui { - class ProxyAuthDialog; -} - -/** - * @brief Ask for username and password for a given proxy. - * - * Used by ProxyAuthHandler. - */ -class ProxyAuthDialog : public QDialog -{ - Q_OBJECT - -public: - explicit ProxyAuthDialog(QWidget *parent = nullptr); - ~ProxyAuthDialog() override; - - void setProxyAddress(const QString &address); - - QString username() const; - QString password() const; - - /// Resets the dialog for new credential entry. - void reset(); - -private: - Ui::ProxyAuthDialog *ui; -}; - - -} // namespace OCC -#endif // OCC_PROXYAUTHDIALOG_H diff --git a/src/gui/proxyauthdialog.ui b/src/gui/proxyauthdialog.ui deleted file mode 100644 index f71bd250799..00000000000 --- a/src/gui/proxyauthdialog.ui +++ /dev/null @@ -1,122 +0,0 @@ - - - OCC::ProxyAuthDialog - - - - 0 - 0 - 351 - 141 - - - - - 0 - 0 - - - - Proxy authentication required - - - - - - The proxy server needs a username and password. - - - - - - - - - Username: - - - - - - - - - - Password: - - - - - - - QLineEdit::Password - - - - - - - Proxy: - - - - - - - TextLabel - - - Qt::PlainText - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - OCC::ProxyAuthDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - OCC::ProxyAuthDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/gui/proxyauthhandler.cpp b/src/gui/proxyauthhandler.cpp deleted file mode 100644 index 3dcc346f574..00000000000 --- a/src/gui/proxyauthhandler.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2015 by Christian Kamm - * - * 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 "proxyauthhandler.h" - -#include "proxyauthdialog.h" -#include "theme.h" -#include "configfile.h" -#include "account.h" - -#include - -#include - -using namespace OCC; - -Q_LOGGING_CATEGORY(lcProxy, "gui.credentials.proxy", QtInfoMsg) - -ProxyAuthHandler *ProxyAuthHandler::instance() -{ - static ProxyAuthHandler inst; - return &inst; -} - -ProxyAuthHandler::ProxyAuthHandler() - : _blocked(false) - , _waitingForDialog(0) - , _waitingForKeychain(0) - , _keychainJobRunning(false) -{ - _dialog = new ProxyAuthDialog(); - - _configFile.reset(new ConfigFile); - _settings.reset(new QSettings(ConfigFile::configFile(), QSettings::IniFormat)); - _settings->beginGroup(QLatin1String("Proxy")); - _settings->beginGroup(QLatin1String("Credentials")); -} - -ProxyAuthHandler::~ProxyAuthHandler() -{ - delete _dialog; -} - -void ProxyAuthHandler::handleProxyAuthenticationRequired( - const QNetworkProxy &proxy, - QAuthenticator *authenticator) -{ - if (!_dialog) { - return; - } - - QString key = proxy.hostName() + QLatin1Char(':') + QString::number(proxy.port()); - - // If the proxy server has changed, forget what we know. - if (key != _proxy) { - _proxy = key; - _username.clear(); - _password.clear(); - _blocked = false; - _gaveCredentialsTo.clear(); - - // If the user explicitly configured the proxy in the - // network settings, don't ask about it. - if (_configFile->proxyType() == QNetworkProxy::HttpProxy - || _configFile->proxyType() == QNetworkProxy::Socks5Proxy) { - _blocked = true; - } - } - - if (_blocked) { - return; - } - - // Find the responsible QNAM if possible. - QPointer sending_qnam = nullptr; - if (Account *account = qobject_cast(sender())) { - // Since we go into an event loop, it's possible for the account's qnam - // to be destroyed before we get back. We can use this to check for its - // liveness. - sending_qnam = account->sharedNetworkAccessManager().data(); - } - if (!sending_qnam) { - qCWarning(lcProxy) << "Could not get the sending QNAM for" << sender(); - } - - - qCInfo(lcProxy) << "Proxy auth required for" << key << proxy.type(); - - // If we already had a username but auth still failed, - // invalidate the old credentials! Unfortunately, authenticator->user() - // isn't reliable, so we also invalidate credentials if we previously - // gave presumably valid credentials to the same QNAM. - bool invalidated = false; - if (!_waitingForDialog && !_waitingForKeychain && (!authenticator->user().isEmpty() - || (sending_qnam && _gaveCredentialsTo.contains(sending_qnam)))) { - qCInfo(lcProxy) << "invalidating old creds" << key; - _username.clear(); - _password.clear(); - invalidated = true; - _gaveCredentialsTo.clear(); - } - - if (_username.isEmpty() || _waitingForKeychain) { - if (invalidated || !getCredsFromKeychain()) { - if (getCredsFromDialog()) { - storeCredsInKeychain(); - } else { - // dialog was cancelled, never ask for that proxy again - _blocked = true; - return; - } - } - } - - qCInfo(lcProxy) << "got creds for" << _proxy; - authenticator->setUser(_username); - authenticator->setPassword(_password); - if (sending_qnam) { - _gaveCredentialsTo.insert(sending_qnam); - connect(sending_qnam, &QObject::destroyed, - this, &ProxyAuthHandler::slotSenderDestroyed); - } -} - -void ProxyAuthHandler::slotKeychainJobDone() -{ - _keychainJobRunning = false; -} - -void ProxyAuthHandler::slotSenderDestroyed(QObject *obj) -{ - _gaveCredentialsTo.remove(obj); -} - -bool ProxyAuthHandler::getCredsFromDialog() -{ - // Open the credentials dialog - if (!_waitingForDialog) { - _dialog->reset(); - _dialog->setProxyAddress(_proxy); - _dialog->open(); - } - - // This function can be reentered while the dialog is open. - // If that's the case, continue processing the dialog until - // it's done. - ++_waitingForDialog; - while (_dialog && _dialog->isVisible()) { - QApplication::processEvents(QEventLoop::ExcludeSocketNotifiers, 200); - } - --_waitingForDialog; - - if (_dialog && _dialog->result() == QDialog::Accepted) { - qCInfo(lcProxy) << "got creds for" << _proxy << "from dialog"; - _username = _dialog->username(); - _password = _dialog->password(); - return true; - } - return false; -} - -bool ProxyAuthHandler::getCredsFromKeychain() -{ - using namespace QKeychain; - - if (_waitingForDialog) { - return false; - } - - qCDebug(lcProxy) << "trying to load" << _proxy; - - if (!_waitingForKeychain) { - _username = _settings->value(keychainUsernameKey()).toString(); - if (_username.isEmpty()) { - return false; - } - - _readPasswordJob.reset(new ReadPasswordJob(Theme::instance()->appName())); - _readPasswordJob->setSettings(_settings.data()); - _readPasswordJob->setInsecureFallback(false); - _readPasswordJob->setKey(keychainPasswordKey()); - _readPasswordJob->setAutoDelete(false); - connect(_readPasswordJob.data(), &QKeychain::Job::finished, - this, &ProxyAuthHandler::slotKeychainJobDone); - _keychainJobRunning = true; - _readPasswordJob->start(); - } - - // While we wait for the password job to be done, this code may be reentered. - // This really needs the counter and the flag here, because otherwise we get - // bad behavior when we reenter this code after the flag has been switched - // but before the while loop has finished. - ++_waitingForKeychain; - _keychainJobRunning = true; - while (_keychainJobRunning) { - QApplication::processEvents(QEventLoop::AllEvents, 200); - } - --_waitingForKeychain; - - if (_readPasswordJob->error() == NoError) { - qCInfo(lcProxy) << "got creds for" << _proxy << "from keychain"; - _password = _readPasswordJob->textData(); - return true; - } - - _username.clear(); - if (_readPasswordJob->error() != EntryNotFound) { - qCWarning(lcProxy) << "ReadPasswordJob failed with" << _readPasswordJob->errorString(); - } - return false; -} - -void ProxyAuthHandler::storeCredsInKeychain() -{ - using namespace QKeychain; - - if (_waitingForKeychain) { - return; - } - - qCInfo(lcProxy) << "storing" << _proxy; - - _settings->setValue(keychainUsernameKey(), _username); - - WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName(), this); - job->setSettings(_settings.data()); - job->setInsecureFallback(false); - job->setKey(keychainPasswordKey()); - job->setTextData(_password); - job->setAutoDelete(false); - connect(job, &QKeychain::Job::finished, this, &ProxyAuthHandler::slotKeychainJobDone); - _keychainJobRunning = true; - job->start(); - - ++_waitingForKeychain; - _keychainJobRunning = true; - while (_keychainJobRunning) { - QApplication::processEvents(QEventLoop::AllEvents, 200); - } - --_waitingForKeychain; - - job->deleteLater(); - if (job->error() != NoError) { - qCWarning(lcProxy) << "WritePasswordJob failed with" << job->errorString(); - } -} - -QString ProxyAuthHandler::keychainUsernameKey() const -{ - return QString::fromLatin1("%1/username").arg(_proxy); -} - -QString ProxyAuthHandler::keychainPasswordKey() const -{ - return QString::fromLatin1("%1/password").arg(_proxy); -} diff --git a/src/gui/proxyauthhandler.h b/src/gui/proxyauthhandler.h deleted file mode 100644 index 36f0e6c7695..00000000000 --- a/src/gui/proxyauthhandler.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2015 by Christian Kamm - * - * 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 "owncloudgui.h" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace QKeychain { -class Job; -class ReadPasswordJob; -} - -namespace OCC { - -class ConfigFile; -class ProxyAuthDialog; - -/** - * @brief Handle proxyAuthenticationRequired signals from our QNetworkAccessManagers. - * - * The main complication here is that the slot needs to return credential information - * synchronously - but running a dialog or getting password data from synchronous - * storage are asynchronous operations. This leads to reentrant calls that are - * fairly complicated to handle. - */ -class ProxyAuthHandler : public QObject -{ - Q_OBJECT - -public: - static ProxyAuthHandler *instance(); - - ~ProxyAuthHandler() override; - -public slots: - /// Intended for QNetworkAccessManager::proxyAuthenticationRequired() - void handleProxyAuthenticationRequired(const QNetworkProxy &proxy, - QAuthenticator *authenticator); - -private slots: - void slotKeychainJobDone(); - void slotSenderDestroyed(QObject *); - -private: - ProxyAuthHandler(); - - /// Runs the ProxyAuthDialog and returns true if new credentials were entered. - bool getCredsFromDialog(); - - /// Checks the keychain for credentials of the current proxy. - bool getCredsFromKeychain(); - - /// Stores the current credentials in the keychain. - void storeCredsInKeychain(); - - QString keychainUsernameKey() const; - QString keychainPasswordKey() const; - - /// The hostname:port of the current proxy, used for detecting switches - /// to a different proxy. - QString _proxy; - - QString _username; - QString _password; - - /// If the user cancels the credential dialog, blocked will be set to - /// true and we won't bother him again. - bool _blocked; - - /// In several instances handleProxyAuthenticationRequired() can be called - /// while it is still running. These counters detect what we're currently - /// waiting for. - int _waitingForDialog; - int _waitingForKeychain; - bool _keychainJobRunning; - - QPointer _dialog; - - /// The QSettings instance to securely store username/password in the keychain. - QScopedPointer _settings; - - /// Pointer to the most-recently-run ReadPasswordJob, needed due to reentrancy. - QScopedPointer _readPasswordJob; - - /// For checking the proxy config settings. - QScopedPointer _configFile; - - /// To distinguish between a new QNAM asking for credentials and credentials - /// failing for an existing QNAM, we keep track of the senders of the - /// proxyAuthRequired signal here. - QSet _gaveCredentialsTo; -}; - -} // namespace OCC diff --git a/src/gui/qmlutils.cpp b/src/gui/qmlutils.cpp new file mode 100644 index 00000000000..8c6c5278b81 --- /dev/null +++ b/src/gui/qmlutils.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "gui/qmlutils.h" + +#include "common/asserts.h" +#include "resources/resources.h" + +#include +#include +#include +#include +#include + +void OCC::QmlUtils::OCQuickWidget::setOCContext(const QUrl &src, QWidget *parentFocusWidget, QObject *ocContext, QJSEngine::ObjectOwnership ownership) +{ + if (ownership == QJSEngine::CppOwnership) { + // Destroying the `ocContext` will result in property changed signals, causing the re-evaluation + // of the bindings in the QML file, which in turn results in warnings about accessing a property + // of a `null` object. + // To prevent this, reset the source to an empty URL. + connect( + ocContext, &QObject::destroyed, this, [this] { setSource(QUrl()); }, Qt::DirectConnection); + } + rootContext()->setContextProperty(QStringLiteral("ocQuickWidget"), this); + rootContext()->setContextProperty(QStringLiteral("ocContext"), ocContext); + engine()->setObjectOwnership(ocContext, ownership); + engine()->addImageProvider(QStringLiteral("ownCloud"), new OCC::Resources::CoreImageProvider()); + setResizeMode(QQuickWidget::SizeRootObjectToView); + + // Ensure the parent widget used OC_DECLARE_WIDGET_FOCUS + Q_ASSERT(parentFocusWidget->metaObject()->indexOfMethod("focusNext()") != -1); + Q_ASSERT(parentFocusWidget->metaObject()->indexOfMethod("focusPrevious()") != -1); + _parentFocusWidget = parentFocusWidget; + + setSource(src); + if (!errors().isEmpty()) { + auto box = new QMessageBox(QMessageBox::Critical, QStringLiteral("QML Error"), QDebug::toString(errors())); + box->setAttribute(Qt::WA_DeleteOnClose); + box->exec(); + qFatal("A qml error occured %s", qPrintable(QDebug::toString(errors()))); + } +} + +void OCC::QmlUtils::OCQuickWidget::setOCContext(const QUrl &src, QWidget *ocContext) +{ + setOCContext(src, ocContext, ocContext, QJSEngine::ObjectOwnership::CppOwnership); +} + +void OCC::QmlUtils::OCQuickWidget::focusInEvent(QFocusEvent *event) +{ + switch (event->reason()) { + case Qt::TabFocusReason: + Q_EMIT focusFirst(); + break; + case Qt::BacktabFocusReason: + Q_EMIT focusLast(); + break; + default: + break; + } + QQuickWidget::focusInEvent(event); +} + +bool OCC::QmlUtils::OCQuickWidget::event(QEvent *event) +{ + if (event->type() == QEvent::EnabledChange) { + QTimer::singleShot(0, this, &OCQuickWidget::enabledChanged); + } + return QQuickWidget::event(event); +} diff --git a/src/gui/qmlutils.h b/src/gui/qmlutils.h new file mode 100644 index 00000000000..9db28e3edf8 --- /dev/null +++ b/src/gui/qmlutils.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 +#include + +class QUrl; + +#define OC_DECLARE_WIDGET_FOCUS \ +public: \ + Q_INVOKABLE void focusNext() \ + { \ + focusNextChild(); \ + } \ + Q_INVOKABLE void focusPrevious() \ + { \ + focusPreviousChild(); \ + } \ + \ +Q_SIGNALS: \ + void focusFirst(); \ + void focusLast(); \ + \ +private: + +namespace OCC::QmlUtils { +class OCQuickWidget : public QQuickWidget +{ + Q_OBJECT + // override of the enabled property of QWidget, but with a notifier + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged FINAL) + Q_PROPERTY(QWidget* parentFocusWidget MEMBER _parentFocusWidget FINAL) + QML_ELEMENT + QML_UNCREATABLE("C++") +public: + using QQuickWidget::QQuickWidget; + void setOCContext(const QUrl &src, QWidget *parentFocusWidget, QObject *ocContext, QJSEngine::ObjectOwnership ownership); + void setOCContext(const QUrl &src, QWidget *ocContext); + +Q_SIGNALS: + void focusFirst(); + void focusLast(); + + void enabledChanged(); + +protected: + void focusInEvent(QFocusEvent *event) override; + + bool event(QEvent *event) override; + +private: + QWidget *_parentFocusWidget; +}; +} diff --git a/src/gui/quotainfo.cpp b/src/gui/quotainfo.cpp deleted file mode 100644 index d059ec560bd..00000000000 --- a/src/gui/quotainfo.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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 "quotainfo.h" -#include "account.h" -#include "accountstate.h" -#include "networkjobs.h" -#include "folderman.h" -#include "creds/abstractcredentials.h" -#include - -#include - -using namespace std::chrono_literals; - -namespace OCC { - -namespace { - const auto defaultIntervalT = 30s; - const auto failIntervalT = 5s; -} - -QuotaInfo::QuotaInfo(AccountState *accountState, QObject *parent) - : QObject(parent) - , _accountState(accountState) - , _lastQuotaTotalBytes(0) - , _lastQuotaUsedBytes(0) - , _active(false) -{ - connect(accountState, &AccountState::stateChanged, - this, &QuotaInfo::slotAccountStateChanged); - connect(&_jobRestartTimer, &QTimer::timeout, this, &QuotaInfo::slotCheckQuota); - _jobRestartTimer.setSingleShot(true); -} - -void QuotaInfo::setActive(bool active) -{ - _active = active; - slotAccountStateChanged(); -} - - -void QuotaInfo::slotAccountStateChanged() -{ - if (canGetQuota()) { - const auto elapsed = std::chrono::seconds(_lastQuotaRecieved.secsTo(QDateTime::currentDateTime())); - if (_lastQuotaRecieved.isNull() || elapsed >= defaultIntervalT) { - slotCheckQuota(); - } else { - _jobRestartTimer.start(defaultIntervalT - elapsed); - } - } else { - _jobRestartTimer.stop(); - } -} - -void QuotaInfo::slotRequestFailed() -{ - _lastQuotaTotalBytes = 0; - _lastQuotaUsedBytes = 0; - _jobRestartTimer.start(failIntervalT); -} - -bool QuotaInfo::canGetQuota() const -{ - if (!_accountState || !_active) { - return false; - } - AccountPtr account = _accountState->account(); - return _accountState->isConnected() - && account->credentials() - && account->credentials()->ready(); -} - -QString QuotaInfo::quotaBaseFolder() const -{ - return Theme::instance()->quotaBaseFolder(); -} - -void QuotaInfo::slotCheckQuota() -{ - if (!canGetQuota()) { - return; - } - - if (_job) { - // The previous job was not finished? Then we cancel it! - _job->deleteLater(); - } - - AccountPtr account = _accountState->account(); - _job = new PropfindJob(account, quotaBaseFolder(), this); - _job->setProperties(QList() << "quota-available-bytes" - << "quota-used-bytes"); - connect(_job.data(), &PropfindJob::result, this, &QuotaInfo::slotUpdateLastQuota); - connect(_job.data(), &AbstractNetworkJob::networkError, this, &QuotaInfo::slotRequestFailed); - _job->start(); -} - -void QuotaInfo::slotUpdateLastQuota(const QMap &result) -{ - // The server can return fractional bytes (#1374) - // 1374532061.2 - qint64 avail = result["quota-available-bytes"].toDouble(); - _lastQuotaUsedBytes = result["quota-used-bytes"].toDouble(); - // negative value of the available quota have special meaning (#3940) - _lastQuotaTotalBytes = avail >= 0 ? _lastQuotaUsedBytes + avail : avail; - emit quotaUpdated(_lastQuotaTotalBytes, _lastQuotaUsedBytes); - _jobRestartTimer.start(defaultIntervalT); - _lastQuotaRecieved = QDateTime::currentDateTime(); -} -} diff --git a/src/gui/quotainfo.h b/src/gui/quotainfo.h deleted file mode 100644 index 36dcac05e55..00000000000 --- a/src/gui/quotainfo.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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. - */ - -#ifndef QUOTAINFO_H -#define QUOTAINFO_H - -#include -#include -#include -#include -#include - -namespace OCC { -class AccountState; -class PropfindJob; - -/** - * @brief handles getting the quota to display in the UI - * - * It is typically owned by the AccountSetting page. - * - * The quota is requested if these 3 conditions are met: - * - This object is active via setActive() (typically if the settings page is visible.) - * - The account is connected. - * - Every 30 seconds (defaultIntervalT) or 5 seconds in case of failure (failIntervalT) - * - * We only request the quota when the UI is visible otherwise this might slow down the server with - * too many requests. But we still need to do it every 30 seconds otherwise user complains that the - * quota is not updated fast enough when changed on the server. - * - * If the quota job is not finished within 30 seconds, it is cancelled and another one is started - * - * @ingroup gui - */ -class QuotaInfo : public QObject -{ - Q_OBJECT -public: - explicit QuotaInfo(OCC::AccountState *accountState, QObject *parent = nullptr); - - qint64 lastQuotaTotalBytes() const { return _lastQuotaTotalBytes; } - qint64 lastQuotaUsedBytes() const { return _lastQuotaUsedBytes; } - - /** - * When the quotainfo is active, it requests the quota at regular interval. - * When setting it to active it will request the quota immediately if the last time - * the quota was requested was more than the interval - */ - void setActive(bool active); - -public Q_SLOTS: - void slotCheckQuota(); - -private Q_SLOTS: - void slotUpdateLastQuota(const QMap &); - void slotAccountStateChanged(); - void slotRequestFailed(); - -Q_SIGNALS: - void quotaUpdated(qint64 total, qint64 used); - -private: - bool canGetQuota() const; - - /// Returns the folder that quota shall be retrieved for - QString quotaBaseFolder() const; - - QPointer _accountState; - qint64 _lastQuotaTotalBytes; - qint64 _lastQuotaUsedBytes; - QTimer _jobRestartTimer; - QDateTime _lastQuotaRecieved; // the time at which the quota was received last - bool _active; // if we should check at regular interval (when the UI is visible) - QPointer _job; // the currently running job -}; - - -} // namespace OCC - -#endif //QUOTAINFO_H diff --git a/src/gui/scheduling/etagwatcher.cpp b/src/gui/scheduling/etagwatcher.cpp new file mode 100644 index 00000000000..ac830995f40 --- /dev/null +++ b/src/gui/scheduling/etagwatcher.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "gui/scheduling/etagwatcher.h" + +#include "accountstate.h" +#include "folder.h" +#include "libsync/syncengine.h" + + +using namespace std::chrono_literals; + +using namespace OCC; + +Q_LOGGING_CATEGORY(lcEtagWatcher, "gui.scheduler.etagwatcher", QtInfoMsg) + +ETagWatcher::ETagWatcher(QObject *parent) + : QObject(parent) +{ +} + +// todo: #48 - this seems to be triggered on every timer driven sync check for each folder. Investigate. +void ETagWatcher::slotSpaceChanged(GraphApi::Space *space) +{ + QString spaceId = space->id(); + QUuid account = space->accountId(); + if (_lastEtagJobForSpace.contains(account) && _lastEtagJobForSpace[account].contains(spaceId)) { + QString etag = Utility::normalizeEtag(space->eTag()); + updateEtag(account, spaceId, etag); + } +} + +void ETagWatcher::slotFolderListChanged(const QUuid &accountId, const QList folders) +{ + if (folders.isEmpty()) { + // if the list is empty and there is no accountId, it means ALL folders have been removed, eg on app shutdown + if (accountId.isNull()) + _lastEtagJobForSpace.clear(); + else if (_lastEtagJobForSpace.contains(accountId)) + _lastEtagJobForSpace.remove(accountId); + return; + } + + for (Folder *f : folders) + onFolderAdded(accountId, f); +} + +void ETagWatcher::onFolderAdded(const QUuid &accountId, Folder *folder) +{ + Q_UNUSED(accountId); + + QString spaceId = folder->spaceId(); + if (_lastEtagJobForSpace.contains(accountId) && _lastEtagJobForSpace[accountId].contains(spaceId)) + return; + + _lastEtagJobForSpace[accountId].insert(spaceId, ETagInfo{{}, folder}); + + connect(folder->syncEngine(), &SyncEngine::rootEtagDiscovered, this, [accountId, folder, this](const QString &etag, const QDateTime &time) { + QString spaceId = folder->spaceId(); + if (_lastEtagJobForSpace.contains(accountId) && _lastEtagJobForSpace[accountId].contains(spaceId)) { + auto &info = _lastEtagJobForSpace[accountId][spaceId]; + info.etag = etag; + if (folder->accountState()) { + folder->accountState()->tagLastSuccessfulETagRequest(time); + } + } + }); +} + +void ETagWatcher::onFolderRemoved(const QUuid &accountId, Folder *folder) +{ + Q_UNUSED(accountId); + if (folder && _lastEtagJobForSpace.contains(accountId)) + _lastEtagJobForSpace[accountId].remove(folder->spaceId()); +} + +void ETagWatcher::updateEtag(const QUuid &accountId, const QString &spaceId, const QString &etag) +{ + // the server must provide a valid etag but there might be bugs + // https://github.com/owncloud/ocis/issues/7160 + if (!etag.isEmpty() && _lastEtagJobForSpace.contains(accountId) && _lastEtagJobForSpace[accountId].contains(spaceId)) { + auto &info = _lastEtagJobForSpace[accountId][spaceId]; + if (info.folder->canSync() && info.etag != etag) { + const bool folderWasSyncedOnce = !info.etag.isEmpty(); + info.etag = etag; + + if (folderWasSyncedOnce) { + // Only schedule a sync if the folder finished its initial sync. + qCDebug(lcEtagWatcher) << "Scheduling sync of" << info.folder->displayName() << info.folder->path() << "due to an etag change"; + emit requestEnqueueFolder(info.folder); + } + } + } else { + qCWarning(lcEtagWatcher) << "Invalid empty etag received for space" << spaceId; + } +} diff --git a/src/gui/scheduling/etagwatcher.h b/src/gui/scheduling/etagwatcher.h new file mode 100644 index 00000000000..cc0904b5501 --- /dev/null +++ b/src/gui/scheduling/etagwatcher.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "libsync/graphapi/space.h" + +#include + +#include + +namespace OCC { + +class FolderMan; +class Folder; + +class ETagWatcher : public QObject +{ + Q_OBJECT +public: + ETagWatcher(QObject *parent); + +public Q_SLOTS: + void slotSpaceChanged(GraphApi::Space *space); + + void slotFolderListChanged(const QUuid &accountId, const QList folders); + + void onFolderAdded(const QUuid &accountId, Folder *folder); + + void onFolderRemoved(const QUuid &accountId, Folder *folder); + + +Q_SIGNALS: + void requestEnqueueFolder(Folder *folder); + +private: + void updateEtag(const QUuid &accountId, const QString &spaceId, const QString &etag); + + struct ETagInfo + { + QString etag; + Folder *folder; + }; + + // we have to separate the data into accounts because it's NOT the case that all spaceid's are unique + // specifically, the Shares space always has the same space id -> see Space.cpp sharesIdC + // so we have a hash keyed on the uuid of the account, which in turn holds the hash for the space ids->etag info + QHash> _lastEtagJobForSpace; +}; + +} diff --git a/src/gui/scheduling/syncscheduler.cpp b/src/gui/scheduling/syncscheduler.cpp new file mode 100644 index 00000000000..59c9eb0a7ea --- /dev/null +++ b/src/gui/scheduling/syncscheduler.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "gui/scheduling/syncscheduler.h" + +#include "gui/folderman.h" +#include "gui/networkinformation.h" +#include "gui/scheduling/etagwatcher.h" +#include "libsync/configfile.h" +#include "libsync/syncengine.h" +#include "spacesmanager.h" + +using namespace std::chrono_literals; + +using namespace OCC; + +Q_LOGGING_CATEGORY(lcSyncScheduler, "gui.scheduler.syncscheduler", QtInfoMsg) + +class FolderPriorityQueue +{ +private: + struct Element + { + Element() { } + + Element(Folder *f, SyncScheduler::Priority p) + : folder(f) + , rawFolder(f) + , priority(p) + { + } + + // We don't own the folder, so it might get deleted + QPointer folder = nullptr; + // raw pointer for lookup in _scheduledFolders + Folder *rawFolder = nullptr; + SyncScheduler::Priority priority = SyncScheduler::Priority::Low; + + friend bool operator<(const Element &lhs, const Element &rhs) { return lhs.priority < rhs.priority; } + }; + + +public: + FolderPriorityQueue() = default; + + // todo #50 + void enqueueFolder(Folder *folder, SyncScheduler::Priority priority) + { + const auto [it, inserted] = _scheduledFolders.emplace(folder, priority); + if (inserted) { + // the folder is not yet scheduled + _queue.emplace(folder, priority); + } else { + // if the new priority is higher we need to rebuild the queue + if (priority > it->second) { + // we need to reorder the queue + // this is expensive + decltype(_queue) out; + for (; !_queue.empty(); _queue.pop()) { + const auto &tmp = _queue.top(); + if (tmp.folder != folder) { + out.push(std::move(tmp)); + } else { + out.emplace(folder, priority); + } + } + _queue = std::move(out); + // replace the the old priority + _scheduledFolders[folder] = priority; + } + } + } + + auto empty() { return _queue.empty(); } + auto size() { return _queue.size(); } + + std::pair pop() + { + Element out; + while (!_queue.empty() && !out.folder) { + // could be a nullptr by now + out = _queue.top(); + [[maybe_unused]] auto removed = _scheduledFolders.erase(_queue.top().rawFolder); + Q_ASSERT(removed = 1); + _queue.pop(); + } + return std::make_pair(out.folder, out.priority); + } + +private: + // the actual queue + std::priority_queue _queue; + // helper container to ensure we don't enqueue a Folder multiple times + std::unordered_map _scheduledFolders; +}; + +// Refactoring todo: I do not see where/how the syncs and etag watcher get updated when eg an account is removed. +// the main dep here is on the FolderMan which means we rely on the folder list being updated and/or folders deleted +// to disconnect this stuff, but afaik the accountStatePtr is ACTUALLY the first thing to go when an account is removed. +// this scheduler/watcher combo needs deep investigation as it might be the source of "mysterious" crashes. +SyncScheduler::SyncScheduler(FolderMan *parent) + : QObject(parent) + , _pauseSyncWhenMetered(ConfigFile().pauseSyncWhenMetered()) + , _queue(new FolderPriorityQueue) +{ + _watcher = new ETagWatcher(this); + + connect(parent, &FolderMan::folderAdded, _watcher, &ETagWatcher::onFolderAdded); + connect(parent, &FolderMan::folderRemoved, _watcher, &ETagWatcher::onFolderRemoved); + connect(parent, &FolderMan::folderListChanged, _watcher, &ETagWatcher::slotFolderListChanged); + + connect(_watcher, &ETagWatcher::requestEnqueueFolder, this, &SyncScheduler::handleEnqueueFolder); + + // Normal syncs are performed incremental but when fullLocalDiscoveryInterval times out + // a complete local discovery is performed. + // This timer here triggers a sync independent of etag changes on the server. + auto *fullLocalDiscoveryTimer = new QTimer(this); + fullLocalDiscoveryTimer->setInterval(ConfigFile().fullLocalDiscoveryInterval() + 2min); + connect(fullLocalDiscoveryTimer, &QTimer::timeout, this, [parent, this] { + // I'm doing it this way as std::as_const won't compile as it's "deleted" (no idea) + // and if I just hit folders() we get clazy complaining that it might detach :/ + const QList folders = parent->folders(); + for (auto *f : folders) { + if (f->isReady() && f->accountState()->state() == AccountState::State::Connected) { + enqueueFolder(f); + } + } + }); + fullLocalDiscoveryTimer->start(); +} + + +SyncScheduler::~SyncScheduler() +{ + delete _queue; +} + +void SyncScheduler::connectSpacesManager(OCC::GraphApi::SpacesManager *spaceMan) +{ + connect(spaceMan, &GraphApi::SpacesManager::spaceChanged, _watcher, &ETagWatcher::slotSpaceChanged, Qt::UniqueConnection); +} + +void SyncScheduler::handleEnqueueFolder(Folder *folder) +{ + enqueueFolder(folder); +} + +void SyncScheduler::enqueueFolder(Folder *folder, Priority priority) +{ + if (!folder->canSync()) { + qCWarning(lcSyncScheduler) << "Cannot enqueue folder" << folder->path() << ": folder is marked as cannot sync"; + return; + } + + qCInfo(lcSyncScheduler) << "Enqueue" << folder->path() << priority << "QueueSize:" << _queue->size(); + _queue->enqueueFolder(folder, priority); + if (!_currentSync) { + startNext(); + } +} + +void SyncScheduler::startNext() +{ + if (!_running) { + qCInfo(lcSyncScheduler) << "Scheduler is paused, next sync is not started"; + return; + } + + if (!_currentSync.isNull()) { + qCInfo(lcSyncScheduler) << "Another sync is already running, waiting for that to finish before starting a new sync"; + return; + } + + auto nextSync = _queue->pop(); + while (nextSync.first && !nextSync.first->canSync()) { + nextSync = _queue->pop(); + } + _currentSync = nextSync.first; + auto syncPriority = nextSync.second; + + if (!_currentSync.isNull()) { + if (_pauseSyncWhenMetered && NetworkInformation::instance()->isMetered()) { + if (syncPriority == Priority::High) { + qCInfo(lcSyncScheduler) << "Scheduler is paused due to metered internet connection, BUT next sync is HIGH priority, so allow sync to start"; + } else { + enqueueFolder(_currentSync, syncPriority); + qCInfo(lcSyncScheduler) << "Scheduler is paused due to metered internet connection, next sync is not started"; + return; + } + } + + connect( + _currentSync, &Folder::syncFinished, this, + [this](const SyncResult &result) { + qCInfo(lcSyncScheduler) << "Sync finished for" << _currentSync->path() << "with status" << result.status(); + _currentSync = nullptr; + startNext(); + }, + Qt::SingleShotConnection); + connect(_currentSync, &Folder::destroyed, this, &SyncScheduler::startNext, Qt::SingleShotConnection); + qCInfo(lcSyncScheduler) << "Starting sync for" << _currentSync->path(); + _currentSync->startSync(); + } +} + +void SyncScheduler::start() +{ + _running = true; + startNext(); +} + +void SyncScheduler::stop() +{ + _running = false; +} + +void SyncScheduler::setPauseSyncWhenMetered(bool pauseSyncWhenMetered) +{ + _pauseSyncWhenMetered = pauseSyncWhenMetered; + if (!pauseSyncWhenMetered) { + startNext(); + } +} diff --git a/src/gui/scheduling/syncscheduler.h b/src/gui/scheduling/syncscheduler.h new file mode 100644 index 00000000000..1e406d38859 --- /dev/null +++ b/src/gui/scheduling/syncscheduler.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "etagwatcher.h" +#include "gui/folder.h" + +#include + +#include +#include + + +class FolderPriorityQueue; + +namespace OCC { + +class FolderMan; + +class SyncScheduler : public QObject +{ + Q_OBJECT +public: + enum class Priority : uint8_t { + // Normal sync triggered by etag change or something similar + Low, + + // Related to a user action + Medium, + + // Usually triggered by a user (ForceSync) + High + }; + Q_ENUM(Priority); + + explicit SyncScheduler(FolderMan *parent); + ~SyncScheduler() override; + + void enqueueFolder(Folder *folder, Priority priority = Priority::Low); + + void start(); + + void stop(); + + void setPauseSyncWhenMetered(bool pauseSyncWhenMetered); + + void connectSpacesManager(OCC::GraphApi::SpacesManager *spaceMan); + +public Q_SLOTS: + + void handleEnqueueFolder(Folder *f); + +private: + void startNext(); + + bool _running = false; + bool _pauseSyncWhenMetered; + ETagWatcher *_watcher = nullptr; + QPointer _currentSync; + FolderPriorityQueue *_queue; +}; +} diff --git a/src/gui/selectivesyncdialog.cpp b/src/gui/selectivesyncdialog.cpp deleted file mode 100644 index e1609186248..00000000000 --- a/src/gui/selectivesyncdialog.cpp +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 "selectivesyncdialog.h" -#include "folder.h" -#include "account.h" -#include "networkjobs.h" -#include "theme.h" -#include "folderman.h" -#include "configfile.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace OCC { - - -class SelectiveSyncTreeViewItem : public QTreeWidgetItem -{ -public: - SelectiveSyncTreeViewItem(int type = QTreeWidgetItem::Type) - : QTreeWidgetItem(type) - { - } - SelectiveSyncTreeViewItem(const QStringList &strings, int type = QTreeWidgetItem::Type) - : QTreeWidgetItem(strings, type) - { - } - SelectiveSyncTreeViewItem(QTreeWidget *view, int type = QTreeWidgetItem::Type) - : QTreeWidgetItem(view, type) - { - } - SelectiveSyncTreeViewItem(QTreeWidgetItem *parent, int type = QTreeWidgetItem::Type) - : QTreeWidgetItem(parent, type) - { - } - -private: - bool operator<(const QTreeWidgetItem &other) const override - { - int column = treeWidget()->sortColumn(); - if (column == 1) { - return data(1, Qt::UserRole).toLongLong() < other.data(1, Qt::UserRole).toLongLong(); - } - return QTreeWidgetItem::operator<(other); - } -}; - -SelectiveSyncWidget::SelectiveSyncWidget(AccountPtr account, QWidget *parent) - : QWidget(parent) - , _account(account) - , _inserting(false) - , _folderTree(new QTreeWidget(this)) -{ - _loading = new QLabel(tr("Loading ..."), _folderTree); - - auto layout = new QVBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - - auto header = new QLabel(this); - header->setText(tr("Deselect remote folders you do not wish to synchronize.")); - header->setWordWrap(true); - layout->addWidget(header); - - layout->addWidget(_folderTree); - - connect(_folderTree, &QTreeWidget::itemExpanded, - this, &SelectiveSyncWidget::slotItemExpanded); - connect(_folderTree, &QTreeWidget::itemChanged, - this, &SelectiveSyncWidget::slotItemChanged); - _folderTree->setSortingEnabled(true); - _folderTree->sortByColumn(0, Qt::AscendingOrder); - _folderTree->setColumnCount(2); - _folderTree->header()->setSectionResizeMode(0, QHeaderView::QHeaderView::ResizeToContents); - _folderTree->header()->setSectionResizeMode(1, QHeaderView::QHeaderView::ResizeToContents); - _folderTree->header()->setStretchLastSection(true); - _folderTree->headerItem()->setText(0, tr("Name")); - _folderTree->headerItem()->setText(1, tr("Size")); - - ConfigFile::setupDefaultExcludeFilePaths(_excludedFiles); - _excludedFiles.reloadExcludeFiles(); -} - -QSize SelectiveSyncWidget::sizeHint() const -{ - return QWidget::sizeHint().expandedTo(QSize(600, 600)); -} - -void SelectiveSyncWidget::refreshFolders() -{ - LsColJob *job = new LsColJob(_account, _folderPath, this); - job->setProperties(QList() << "resourcetype" - << "http://owncloud.org/ns:size"); - connect(job, &LsColJob::directoryListingSubfolders, - this, &SelectiveSyncWidget::slotUpdateDirectories); - connect(job, &LsColJob::finishedWithError, - this, &SelectiveSyncWidget::slotLscolFinishedWithError); - job->start(); - _folderTree->clear(); - _loading->show(); - _loading->move(10, _folderTree->header()->height() + 10); -} - -void SelectiveSyncWidget::setFolderInfo(const QString &folderPath, const QString &rootName, const QStringList &oldBlackList) -{ - _folderPath = folderPath; - _rootName = rootName; - _oldBlackList = oldBlackList; - refreshFolders(); -} - -static QTreeWidgetItem *findFirstChild(QTreeWidgetItem *parent, const QString &text) -{ - for (int i = 0; i < parent->childCount(); ++i) { - QTreeWidgetItem *child = parent->child(i); - if (child->text(0) == text) { - return child; - } - } - return nullptr; -} - -void SelectiveSyncWidget::recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path, qint64 size) -{ - QFileIconProvider prov; - QIcon folderIcon = prov.icon(QFileIconProvider::Folder); - if (pathTrail.size() == 0) { - if (path.endsWith('/')) { - path.chop(1); - } - parent->setToolTip(0, path); - parent->setData(0, Qt::UserRole, path); - } else { - SelectiveSyncTreeViewItem *item = static_cast(findFirstChild(parent, pathTrail.first())); - if (!item) { - item = new SelectiveSyncTreeViewItem(parent); - if (parent->checkState(0) == Qt::Checked - || parent->checkState(0) == Qt::PartiallyChecked) { - item->setCheckState(0, Qt::Checked); - for (const auto &str : qAsConst(_oldBlackList)) { - if (str == path || str == QLatin1String("/")) { - item->setCheckState(0, Qt::Unchecked); - break; - } else if (str.startsWith(path)) { - item->setCheckState(0, Qt::PartiallyChecked); - } - } - } else if (parent->checkState(0) == Qt::Unchecked) { - item->setCheckState(0, Qt::Unchecked); - } - item->setIcon(0, folderIcon); - item->setText(0, pathTrail.first()); - if (size >= 0) { - item->setText(1, Utility::octetsToString(size)); - item->setData(1, Qt::UserRole, size); - } - // item->setData(0, Qt::UserRole, pathTrail.first()); - item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); - } - - pathTrail.removeFirst(); - recursiveInsert(item, pathTrail, path, size); - } -} - -void SelectiveSyncWidget::slotUpdateDirectories(QStringList list) -{ - auto job = qobject_cast(sender()); - QScopedValueRollback isInserting(_inserting, true); - - SelectiveSyncTreeViewItem *root = static_cast(_folderTree->topLevelItem(0)); - - const QString pathToRemove = Utility::concatUrlPath(_account->davUrl(), _folderPath).path(); - - // Check for excludes. - list.erase(std::remove_if(list.begin(), list.end(), [&pathToRemove, this](const QString &it) { - return _excludedFiles.isExcludedRemote(it, pathToRemove, FolderMan::instance()->ignoreHiddenFiles(), ItemTypeDirectory); - }), - list.end()); - - // Since / cannot be in the blacklist, expand it to the actual - // list of top-level folders as soon as possible. - if (_oldBlackList == QStringList("/")) { - _oldBlackList.clear(); - for (auto path : qAsConst(list)) { - path.remove(pathToRemove); - if (path.isEmpty()) { - continue; - } - _oldBlackList.append(path); - } - } - - if (!root && list.size() <= 1) { - _loading->setText(tr("No subfolders currently on the server.")); - _loading->resize(_loading->sizeHint()); // because it's not in a layout - return; - } else { - _loading->hide(); - } - - if (!root) { - root = new SelectiveSyncTreeViewItem(_folderTree); - root->setText(0, _rootName); - root->setIcon(0, Theme::instance()->applicationIcon()); - root->setData(0, Qt::UserRole, QString()); - root->setCheckState(0, Qt::Checked); - qint64 size = job ? job->sizes().value(pathToRemove, -1) : -1; - if (size >= 0) { - root->setText(1, Utility::octetsToString(size)); - root->setData(1, Qt::UserRole, size); - } - } - - Utility::sortFilenames(list); - for (auto path : qAsConst(list)) { - auto size = job ? job->sizes().value(path) : 0; - path.remove(pathToRemove); - QStringList paths = path.split('/'); - if (paths.last().isEmpty()) - paths.removeLast(); - if (paths.isEmpty()) - continue; - if (!path.endsWith('/')) { - path.append('/'); - } - recursiveInsert(root, paths, path, size); - } - - // Root is partially checked if any children are not checked - for (int i = 0; i < root->childCount(); ++i) { - const auto child = root->child(i); - if (child->checkState(0) != Qt::Checked) { - root->setCheckState(0, Qt::PartiallyChecked); - break; - } - } - - root->setExpanded(true); -} - -void SelectiveSyncWidget::slotLscolFinishedWithError(QNetworkReply *r) -{ - if (r->error() == QNetworkReply::ContentNotFoundError) { - _loading->setText(tr("No subfolders currently on the server.")); - } else { - _loading->setText(tr("An error occurred while loading the list of sub folders.")); - } - _loading->resize(_loading->sizeHint()); // because it's not in a layout -} - -void SelectiveSyncWidget::slotItemExpanded(QTreeWidgetItem *item) -{ - QString dir = item->data(0, Qt::UserRole).toString(); - if (dir.isEmpty()) - return; - LsColJob *job = new LsColJob(_account, _folderPath + dir, this); - job->setProperties(QList() << "resourcetype" - << "http://owncloud.org/ns:size"); - connect(job, &LsColJob::directoryListingSubfolders, - this, &SelectiveSyncWidget::slotUpdateDirectories); - job->start(); -} - -void SelectiveSyncWidget::slotItemChanged(QTreeWidgetItem *item, int col) -{ - if (col != 0 || _inserting) - return; - - if (item->checkState(0) == Qt::Checked) { - // If we are checked, check that we may need to check the parent as well if - // all the siblings are also checked - QTreeWidgetItem *parent = item->parent(); - if (parent && parent->checkState(0) != Qt::Checked) { - bool hasUnchecked = false; - for (int i = 0; i < parent->childCount(); ++i) { - if (parent->child(i)->checkState(0) != Qt::Checked) { - hasUnchecked = true; - break; - } - } - if (!hasUnchecked) { - parent->setCheckState(0, Qt::Checked); - } else if (parent->checkState(0) == Qt::Unchecked) { - parent->setCheckState(0, Qt::PartiallyChecked); - } - } - // also check all the children - for (int i = 0; i < item->childCount(); ++i) { - if (item->child(i)->checkState(0) != Qt::Checked) { - item->child(i)->setCheckState(0, Qt::Checked); - } - } - } - - if (item->checkState(0) == Qt::Unchecked) { - QTreeWidgetItem *parent = item->parent(); - if (parent && parent->checkState(0) == Qt::Checked) { - parent->setCheckState(0, Qt::PartiallyChecked); - } - - // Uncheck all the children - for (int i = 0; i < item->childCount(); ++i) { - if (item->child(i)->checkState(0) != Qt::Unchecked) { - item->child(i)->setCheckState(0, Qt::Unchecked); - } - } - - // Can't uncheck the root. - if (!parent) { - item->setCheckState(0, Qt::PartiallyChecked); - } - } - - if (item->checkState(0) == Qt::PartiallyChecked) { - QTreeWidgetItem *parent = item->parent(); - if (parent && parent->checkState(0) != Qt::PartiallyChecked) { - parent->setCheckState(0, Qt::PartiallyChecked); - } - } -} - -QStringList SelectiveSyncWidget::createBlackList(QTreeWidgetItem *root) const -{ - if (!root) { - root = _folderTree->topLevelItem(0); - } - if (!root) - return QStringList(); - - switch (root->checkState(0)) { - case Qt::Unchecked: - return QStringList(root->data(0, Qt::UserRole).toString() + "/"); - case Qt::Checked: - return QStringList(); - case Qt::PartiallyChecked: - break; - } - - QStringList result; - if (root->childCount()) { - for (int i = 0; i < root->childCount(); ++i) { - result += createBlackList(root->child(i)); - } - } else { - // We did not load from the server so we re-use the one from the old black list - QString path = root->data(0, Qt::UserRole).toString(); - for (const auto &it : _oldBlackList) { - if (it.startsWith(path)) - result += it; - } - } - return result; -} - -QStringList SelectiveSyncWidget::oldBlackList() const -{ - return _oldBlackList; -} - -qint64 SelectiveSyncWidget::estimatedSize(QTreeWidgetItem *root) -{ - if (!root) { - root = _folderTree->topLevelItem(0); - } - if (!root) - return -1; - - - switch (root->checkState(0)) { - case Qt::Unchecked: - return 0; - case Qt::Checked: - return root->data(1, Qt::UserRole).toLongLong(); - case Qt::PartiallyChecked: - break; - } - - qint64 result = 0; - if (root->childCount()) { - for (int i = 0; i < root->childCount(); ++i) { - auto r = estimatedSize(root->child(i)); - if (r < 0) - return r; - result += r; - } - } else { - // We did not load from the server so we have no idea how much we will sync from this branch - return -1; - } - return result; -} - - -SelectiveSyncDialog::SelectiveSyncDialog(AccountPtr account, Folder *folder, QWidget *parent, Qt::WindowFlags f) - : QDialog(parent, f) - , _folder(folder) - , _okButton(nullptr) // defined in init() -{ - bool ok; - init(account); - QStringList selectiveSyncList = _folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok); - if (ok) { - _selectiveSync->setFolderInfo(_folder->remotePath(), _folder->alias(), selectiveSyncList); - } else { - _okButton->setEnabled(false); - } - // Make sure we don't get crashes if the folder is destroyed while we are still open - connect(_folder, &QObject::destroyed, this, &QObject::deleteLater); -} - -SelectiveSyncDialog::SelectiveSyncDialog(AccountPtr account, const QString &folder, - const QStringList &blacklist, QWidget *parent, Qt::WindowFlags f) - : QDialog(parent, f) - , _folder(nullptr) -{ - init(account); - _selectiveSync->setFolderInfo(folder, folder, blacklist); -} - -void SelectiveSyncDialog::init(const AccountPtr &account) -{ - setWindowTitle(tr("Choose What to Sync")); - QVBoxLayout *layout = new QVBoxLayout(this); - _selectiveSync = new SelectiveSyncWidget(account, this); - layout->addWidget(_selectiveSync); - QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal); - _okButton = buttonBox->addButton(QDialogButtonBox::Ok); - connect(_okButton, &QPushButton::clicked, this, &SelectiveSyncDialog::accept); - QPushButton *button; - button = buttonBox->addButton(QDialogButtonBox::Cancel); - connect(button, &QAbstractButton::clicked, this, &QDialog::reject); - layout->addWidget(buttonBox); -} - -void SelectiveSyncDialog::accept() -{ - if (_folder) { - bool ok; - auto oldBlackListSet = _folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok).toSet(); - if (!ok) { - return; - } - QStringList blackList = _selectiveSync->createBlackList(); - _folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList); - - FolderMan *folderMan = FolderMan::instance(); - if (_folder->isSyncRunning()) { - _folder->slotTerminateSync(); - } - - //The part that changed should not be read from the DB on next sync because there might be new folders - // (the ones that are no longer in the blacklist) - auto blackListSet = blackList.toSet(); - const auto changes = (oldBlackListSet - blackListSet) + (blackListSet - oldBlackListSet); - for (const auto &it : changes) { - _folder->journalDb()->schedulePathForRemoteDiscovery(it); - _folder->schedulePathForLocalDiscovery(it); - } - // Also make sure we see the local file that had been ignored before - _folder->slotNextSyncFullLocalDiscovery(); - - folderMan->scheduleFolder(_folder); - } - QDialog::accept(); -} - -QStringList SelectiveSyncDialog::createBlackList() const -{ - return _selectiveSync->createBlackList(); -} - -QStringList SelectiveSyncDialog::oldBlackList() const -{ - return _selectiveSync->oldBlackList(); -} - -qint64 SelectiveSyncDialog::estimatedSize() -{ - return _selectiveSync->estimatedSize(); -} -} diff --git a/src/gui/selectivesyncdialog.h b/src/gui/selectivesyncdialog.h deleted file mode 100644 index 3426ec55099..00000000000 --- a/src/gui/selectivesyncdialog.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 -#include -#include "accountfwd.h" - -#include "csync_exclude.h" - -class QTreeWidgetItem; -class QTreeWidget; -class QNetworkReply; -class QLabel; -namespace OCC { - -class Folder; - -/** - * @brief The SelectiveSyncWidget contains a folder tree with labels - * @ingroup gui - */ -class SelectiveSyncWidget : public QWidget -{ - Q_OBJECT -public: - explicit SelectiveSyncWidget(AccountPtr account, QWidget *parent = nullptr); - - /// Returns a list of blacklisted paths, each including the trailing / - QStringList createBlackList(QTreeWidgetItem *root = nullptr) const; - - /** Returns the oldBlackList passed into setFolderInfo(), except that - * a "/" entry is expanded to all top-level folder names. - */ - QStringList oldBlackList() const; - - // Estimates the total size of checked items (recursively) - qint64 estimatedSize(QTreeWidgetItem *root = nullptr); - - // oldBlackList is a list of excluded paths, each including a trailing / - void setFolderInfo(const QString &folderPath, const QString &rootName, - const QStringList &oldBlackList = QStringList()); - - QSize sizeHint() const override; - -private slots: - void slotUpdateDirectories(QStringList); - void slotItemExpanded(QTreeWidgetItem *); - void slotItemChanged(QTreeWidgetItem *, int); - void slotLscolFinishedWithError(QNetworkReply *); - -private: - void refreshFolders(); - void recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path, qint64 size); - - AccountPtr _account; - - QString _folderPath; - QString _rootName; - QStringList _oldBlackList; - - bool _inserting; // set to true when we are inserting new items on the list - QLabel *_loading; - - QTreeWidget *_folderTree; - - // During account setup we want to filter out excluded folders from the - // view without having a Folder.SyncEngine.ExcludedFiles instance. - ExcludedFiles _excludedFiles; -}; - -/** - * @brief The SelectiveSyncDialog class - * @ingroup gui - */ -class SelectiveSyncDialog : public QDialog -{ - Q_OBJECT -public: - // Dialog for a specific folder (used from the account settings button) - explicit SelectiveSyncDialog(AccountPtr account, Folder *folder, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); - - // Dialog for the whole account (Used from the wizard) - explicit SelectiveSyncDialog(AccountPtr account, const QString &folder, const QStringList &blacklist, QWidget *parent = nullptr, Qt::WindowFlags f = nullptr); - - void accept() override; - - QStringList createBlackList() const; - QStringList oldBlackList() const; - - // Estimate the size of the total of sync'ed files from the server - qint64 estimatedSize(); - -private: - void init(const AccountPtr &account); - - SelectiveSyncWidget *_selectiveSync; - - Folder *_folder; - QPushButton *_okButton; -}; -} diff --git a/src/gui/selectivesyncwidget.cpp b/src/gui/selectivesyncwidget.cpp new file mode 100644 index 00000000000..df0ce7f2886 --- /dev/null +++ b/src/gui/selectivesyncwidget.cpp @@ -0,0 +1,444 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "selectivesyncwidget.h" + +#include "gui/folderman.h" +#include "libsync/configfile.h" +#include "libsync/networkjobs.h" +#include "libsync/theme.h" + +#include "resources/resources.h" + +#include +#include +#include +#include +#include + +namespace OCC { + + +class SelectiveSyncTreeViewItem : public QTreeWidgetItem +{ +public: + SelectiveSyncTreeViewItem(int type = QTreeWidgetItem::Type) + : QTreeWidgetItem(type) + { + } + SelectiveSyncTreeViewItem(const QStringList &strings, int type = QTreeWidgetItem::Type) + : QTreeWidgetItem(strings, type) + { + } + SelectiveSyncTreeViewItem(QTreeWidget *view, int type = QTreeWidgetItem::Type) + : QTreeWidgetItem(view, type) + { + } + SelectiveSyncTreeViewItem(QTreeWidgetItem *parent, int type = QTreeWidgetItem::Type) + : QTreeWidgetItem(parent, type) + { + } + +private: + bool operator<(const QTreeWidgetItem &other) const override + { + int column = treeWidget()->sortColumn(); + if (column == 1) { + return data(1, Qt::UserRole).toLongLong() < other.data(1, Qt::UserRole).toLongLong(); + } + return QTreeWidgetItem::operator<(other); + } +}; + +SelectiveSyncWidget::SelectiveSyncWidget(Account *account, QWidget *parent) + : QWidget(parent) + , _account(account) + , _inserting(false) + , _folderTree(new QTreeWidget(this)) +{ + _loading = new QLabel(tr("Loading …"), _folderTree); + + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + auto header = new QLabel(this); + header->setText(tr("Deselect remote folders you do not wish to synchronize.")); + header->setWordWrap(true); + layout->addWidget(header); + + layout->addWidget(_folderTree); + + connect(_folderTree, &QTreeWidget::itemExpanded, this, &SelectiveSyncWidget::slotItemExpanded); + connect(_folderTree, &QTreeWidget::itemChanged, this, &SelectiveSyncWidget::slotItemChanged); + _folderTree->setSortingEnabled(true); + _folderTree->sortByColumn(0, Qt::AscendingOrder); + _folderTree->setColumnCount(2); + _folderTree->header()->setSectionResizeMode(0, QHeaderView::QHeaderView::ResizeToContents); + _folderTree->header()->setSectionResizeMode(1, QHeaderView::QHeaderView::ResizeToContents); + _folderTree->header()->setStretchLastSection(true); + _folderTree->headerItem()->setText(0, tr("Name")); + _folderTree->headerItem()->setText(1, tr("Size")); + + ConfigFile::setupDefaultExcludeFilePaths(_excludedFiles); + _excludedFiles.reloadExcludeFiles(); +} + +QSize SelectiveSyncWidget::sizeHint() const +{ + return QWidget::sizeHint().expandedTo(QSize(600, 600)); +} + +// todo DC-150 or beyond: jobs should not be run in the gui! create a controller to run the job, and eliminate the account dep here in this widget +void SelectiveSyncWidget::refreshFolders() +{ + if (!_account) + return; + + auto *job = new PropfindJob(_account, davUrl(), _folderPath, PropfindJob::Depth::One, this); + job->setProperties({QByteArrayLiteral("resourcetype"), QByteArrayLiteral("http://owncloud.org/ns:size")}); + connect(job, &PropfindJob::directoryListingSubfolders, this, &SelectiveSyncWidget::slotUpdateDirectories); + connect(job, &PropfindJob::finishedWithError, this, [job, this] { + if (job->reply()->error() == QNetworkReply::ContentNotFoundError) { + _loading->setText(tr("Currently there are no subfolders on the server.")); + } else { + _loading->setText(tr("An error occurred while loading the list of subfolders.")); + } + _loading->resize(_loading->sizeHint()); // because it's not in a layout + }); + job->start(); + _folderTree->clear(); + _loading->show(); + _loading->move(10, _folderTree->header()->height() + 10); +} + +void SelectiveSyncWidget::setFolderInfo(const QString &folderPath, const QString &rootName, const QSet &oldBlackList) +{ + _folderPath = Utility::stripTrailingSlash(folderPath); + _rootName = rootName; + _oldBlackList = oldBlackList; + refreshFolders(); +} + +static QTreeWidgetItem *findFirstChild(QTreeWidgetItem *parent, const QString &text) +{ + for (int i = 0; i < parent->childCount(); ++i) { + QTreeWidgetItem *child = parent->child(i); + if (child->text(0) == text) { + return child; + } + } + return nullptr; +} + +/** + * @brief Recursively insert an item in the tree + * @param parent The parent item into which a child is inserted + * @param pathTrail The tail of the path that still needs to be inserted + * @param path The full path + * @param size The size of the folder + * @param showChildIndicator False if it is known that a folder does not have any child items + * (and should not show the expansion triangle), true otherwise. + * + * The algorithm: for inserting "folder/subfolder",start with the root item, and pass in the path + * both as list of (sub)folders, and the whole path as string. Then search for the already existing + * "folder" item, and if not found, add it. Then recurse with this item as the parent, and "subfolder" + * as the `pathTrail`. Repeat this again, and the last recursive call is with the item for "subfolder", + * and an empty `pathTrail`. This item is used to set the full path as the tooltip, and set the + * `showChildIndicator`. + * + * By default, the `showChildIndicator` is set to true. When the folder is expanded, and a PROPFIND + * is done, *and* there are no child folders, a recursive insert is done for the path (which already + * is in the tree), and the `showChildIndicator` set to false. + * + * @note Note about the `showChildIndicator`: if set to false, this will only be applied to the very + * last item of the path/pathTrail: all parent items need to be expandable in order to reach + * that last folder. So all parents of that folder will have expansion triangles shown. + */ +void SelectiveSyncWidget::recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path, qint64 size, bool showChildIndicator) +{ + if (pathTrail.size() == 0) { + if (path.endsWith(QLatin1Char('/'))) { + path.chop(1); + } + parent->setToolTip(0, path); + parent->setData(0, Qt::UserRole, path); + parent->setChildIndicatorPolicy(showChildIndicator ? QTreeWidgetItem::ShowIndicator : QTreeWidgetItem::DontShowIndicator); + } else { + SelectiveSyncTreeViewItem *item = static_cast(findFirstChild(parent, pathTrail.first())); + if (!item) { + item = new SelectiveSyncTreeViewItem(parent); + if (parent->checkState(0) == Qt::Checked || parent->checkState(0) == Qt::PartiallyChecked) { + item->setCheckState(0, Qt::Checked); + for (const auto &str : std::as_const(_oldBlackList)) { + if (str == path || str == QLatin1Char('/')) { + item->setCheckState(0, Qt::Unchecked); + break; + } else if (str.startsWith(path)) { + item->setCheckState(0, Qt::PartiallyChecked); + } + } + } else if (parent->checkState(0) == Qt::Unchecked) { + item->setCheckState(0, Qt::Unchecked); + } + item->setIcon(0, Resources::getCoreIcon(QStringLiteral("folder-sync"))); + item->setText(0, pathTrail.first()); + if (size >= 0) { + item->setText(1, Utility::octetsToString(size)); + item->setData(1, Qt::UserRole, size); + } + + // Mark all sub-folders as potentially having child folders. When expanded, a PROPFIND + // will be done, and sub-folders will be added. If no sub-folders are found by the PROPFIND, + // the triangle will be hidden. + item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); + } + + pathTrail.removeFirst(); + recursiveInsert(item, pathTrail, path, size, showChildIndicator); + } +} + +void SelectiveSyncWidget::slotUpdateDirectories(QStringList list) +{ + auto job = qobject_cast(sender()); + QScopedValueRollback isInserting(_inserting, true); + + SelectiveSyncTreeViewItem *root = static_cast(_folderTree->topLevelItem(0)); + + const QString rootPath = Utility::ensureTrailingSlash(Utility::concatUrlPath(davUrl(), _folderPath).path()); + const bool ignoreHiddenFiles = FolderMan::instance()->ignoreHiddenFiles(); + + // Check for excludes. + list.erase(std::remove_if(list.begin(), list.end(), + [&rootPath, ignoreHiddenFiles, this]( + const QString &it) { return _excludedFiles.isExcludedRemote(it, rootPath, ignoreHiddenFiles, ItemTypeDirectory); }), + list.end()); + + QStringList relativeList; + relativeList.reserve(list.size()); + for (const QString &path : std::as_const(list)) { + Q_ASSERT(path.startsWith(rootPath)); + const QString relativePath = path.mid(rootPath.size()); + if (!relativePath.isEmpty()) { + relativeList.append(relativePath); + } + } + + // Since / cannot be in the blacklist, expand it to the actual + // list of top-level folders as soon as possible. + if (_oldBlackList.size() == 1 && _oldBlackList.contains(QLatin1String("/"))) { + _oldBlackList.clear(); + for (const QString &path : std::as_const(relativeList)) { + _oldBlackList.insert(path); + } + } + + if (!root && list.size() <= 1) { + _loading->setText(tr("Currently there are no subfolders on the server.")); + _loading->resize(_loading->sizeHint()); // because it's not in a layout + return; + } else { + _loading->hide(); + } + + if (!root) { + root = new SelectiveSyncTreeViewItem(_folderTree); + root->setText(0, _rootName); + root->setIcon(0, Theme::instance()->applicationIcon()); + root->setData(0, Qt::UserRole, QString()); + root->setCheckState(0, Qt::Checked); + qint64 size = job ? job->sizes().value(rootPath, -1) : -1; + if (size >= 0) { + root->setText(1, Utility::octetsToString(size)); + root->setData(1, Qt::UserRole, size); + } + } + + // The base directory of the propfind is always returned, so if there is just 1 entry, there + // are no child folders. EXCEPT for the root directory, because that entry is filtered out of + // the `relativeList` above. The case of no sub-folders in the root is handled above, so if + // the original list contains the `rootPath`, we show the indicator. + const bool showChildIndicator = relativeList.size() > 1 || list.contains(rootPath); + + Utility::sortFilenames(relativeList); + for (const QString &path : std::as_const(relativeList)) { + const auto size = job ? job->sizes().value(Utility::ensureTrailingSlash(Utility::concatUrlPathItems({rootPath, path}))) : 0; + const QStringList paths = path.split(QLatin1Char('/'), Qt::SkipEmptyParts); + if (paths.isEmpty()) { + continue; + } + recursiveInsert(root, paths, Utility::ensureTrailingSlash(path), size, showChildIndicator); + } + + // Root is partially checked if any children are not checked + for (int i = 0; i < root->childCount(); ++i) { + const auto child = root->child(i); + if (child->checkState(0) != Qt::Checked) { + root->setCheckState(0, Qt::PartiallyChecked); + break; + } + } + + root->setExpanded(true); +} + +void SelectiveSyncWidget::slotItemExpanded(QTreeWidgetItem *item) +{ + if (!_account) + return; + + QString dir = item->data(0, Qt::UserRole).toString(); + if (dir.isEmpty()) { + return; + } + PropfindJob *job = new PropfindJob(_account, davUrl(), Utility::concatUrlPathItems({_folderPath, dir}), PropfindJob::Depth::One, this); + job->setProperties({QByteArrayLiteral("resourcetype"), QByteArrayLiteral("http://owncloud.org/ns:size")}); + connect(job, &PropfindJob::directoryListingSubfolders, this, &SelectiveSyncWidget::slotUpdateDirectories); + job->start(); +} + +void SelectiveSyncWidget::slotItemChanged(QTreeWidgetItem *item, int col) +{ + if (col != 0 || _inserting) + return; + + if (item->checkState(0) == Qt::Checked) { + // If we are checked, check that we may need to check the parent as well if + // all the siblings are also checked + QTreeWidgetItem *parent = item->parent(); + if (parent && parent->checkState(0) != Qt::Checked) { + bool hasUnchecked = false; + for (int i = 0; i < parent->childCount(); ++i) { + if (parent->child(i)->checkState(0) != Qt::Checked) { + hasUnchecked = true; + break; + } + } + if (!hasUnchecked) { + parent->setCheckState(0, Qt::Checked); + } else if (parent->checkState(0) == Qt::Unchecked) { + parent->setCheckState(0, Qt::PartiallyChecked); + } + } + // also check all the children + for (int i = 0; i < item->childCount(); ++i) { + if (item->child(i)->checkState(0) != Qt::Checked) { + item->child(i)->setCheckState(0, Qt::Checked); + } + } + } + + if (item->checkState(0) == Qt::Unchecked) { + QTreeWidgetItem *parent = item->parent(); + if (parent && parent->checkState(0) == Qt::Checked) { + parent->setCheckState(0, Qt::PartiallyChecked); + } + + // Uncheck all the children + for (int i = 0; i < item->childCount(); ++i) { + if (item->child(i)->checkState(0) != Qt::Unchecked) { + item->child(i)->setCheckState(0, Qt::Unchecked); + } + } + + // Can't uncheck the root. + if (!parent) { + item->setCheckState(0, Qt::PartiallyChecked); + } + } + + if (item->checkState(0) == Qt::PartiallyChecked) { + QTreeWidgetItem *parent = item->parent(); + if (parent && parent->checkState(0) != Qt::PartiallyChecked) { + parent->setCheckState(0, Qt::PartiallyChecked); + } + } +} + +QSet SelectiveSyncWidget::createBlackList(QTreeWidgetItem *root) const +{ + if (!root) { + root = _folderTree->topLevelItem(0); + } + if (!root) + return {}; + + switch (root->checkState(0)) { + case Qt::Unchecked: + return {root->data(0, Qt::UserRole).toString() + QLatin1Char('/')}; + case Qt::Checked: + return {}; + case Qt::PartiallyChecked: + break; + } + + QSet result; + if (root->childCount()) { + for (int i = 0; i < root->childCount(); ++i) { + result += createBlackList(root->child(i)); + } + } else { + // We did not load from the server so we re-use the one from the old black list + QString path = root->data(0, Qt::UserRole).toString(); + for (const auto &it : _oldBlackList) { + if (it.startsWith(path)) + result += it; + } + } + return result; +} + +qint64 SelectiveSyncWidget::estimatedSize(QTreeWidgetItem *root) +{ + if (!root) { + root = _folderTree->topLevelItem(0); + } + if (!root) + return -1; + + + switch (root->checkState(0)) { + case Qt::Unchecked: + return 0; + case Qt::Checked: + return root->data(1, Qt::UserRole).toLongLong(); + case Qt::PartiallyChecked: + break; + } + + qint64 result = 0; + if (root->childCount()) { + for (int i = 0; i < root->childCount(); ++i) { + auto r = estimatedSize(root->child(i)); + if (r < 0) + return r; + result += r; + } + } else { + // We did not load from the server so we have no idea how much we will sync from this branch + return -1; + } + return result; +} + +void SelectiveSyncWidget::setDavUrl(const QUrl &davUrl) +{ + _davUrl = davUrl; +} + +QUrl SelectiveSyncWidget::davUrl() const +{ + Q_ASSERT(!_davUrl.isEmpty()); + return _davUrl; +} +} diff --git a/src/gui/selectivesyncwidget.h b/src/gui/selectivesyncwidget.h new file mode 100644 index 00000000000..5c2e382f5fa --- /dev/null +++ b/src/gui/selectivesyncwidget.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 +#include +#include + +#include "csync_exclude.h" + +class QTreeWidgetItem; +class QTreeWidget; +class QNetworkReply; +class QLabel; +namespace OCC { + +class Folder; +class Account; + +/** + * @brief The SelectiveSyncWidget contains a folder tree with labels + * @ingroup gui + */ +class SelectiveSyncWidget : public QWidget +{ + Q_OBJECT +public: + explicit SelectiveSyncWidget(Account *account, QWidget *parent); + + /// Returns a list of blacklisted paths, each including the trailing / + QSet createBlackList(QTreeWidgetItem *root = nullptr) const; + + // Estimates the total size of checked items (recursively) + qint64 estimatedSize(QTreeWidgetItem *root = nullptr); + + // oldBlackList is a list of excluded paths, each including a trailing / + void setFolderInfo(const QString &folderPath, const QString &rootName, const QSet &oldBlackList = {}); + + QSize sizeHint() const override; + + void setDavUrl(const QUrl &davUrl); + +private Q_SLOTS: + void slotUpdateDirectories(QStringList); + void slotItemExpanded(QTreeWidgetItem *); + void slotItemChanged(QTreeWidgetItem *, int); + +private: + void refreshFolders(); + void recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path, qint64 size, bool showChildIndicator); + QUrl davUrl() const; + +private: + QPointer _account; + + QString _folderPath; + QString _rootName; + QSet _oldBlackList; + + QUrl _davUrl; + + bool _inserting; // set to true when we are inserting new items on the list + QLabel *_loading; + + QTreeWidget *_folderTree; + + // During account setup we want to filter out excluded folders from the + // view without having a Folder.SyncEngine.ExcludedFiles instance. + ExcludedFiles _excludedFiles; +}; + +} diff --git a/src/gui/servernotificationhandler.cpp b/src/gui/servernotificationhandler.cpp deleted file mode 100644 index e1c3318710d..00000000000 --- a/src/gui/servernotificationhandler.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 "servernotificationhandler.h" -#include "accountstate.h" -#include "capabilities.h" -#include "networkjobs.h" - -#include -#include - -namespace OCC { - -Q_LOGGING_CATEGORY(lcServerNotification, "gui.servernotification", QtInfoMsg) - -const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications"); - -ServerNotificationHandler::ServerNotificationHandler(QObject *parent) - : QObject(parent) -{ -} - -void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr) -{ - // check connectivity and credentials - if (!(ptr && ptr->isConnected() && ptr->account() && ptr->account()->credentials() && ptr->account()->credentials()->ready())) { - deleteLater(); - return; - } - // check if the account has notifications enabled. If the capabilities are - // not yet valid, its assumed that notifications are available. - if (ptr->account()->capabilities().isValid()) { - if (!ptr->account()->capabilities().notificationsAvailable()) { - qCInfo(lcServerNotification) << "Account" << ptr->account()->displayName() << "does not have notifications enabled."; - deleteLater(); - return; - } - } - - // if the previous notification job has finished, start next. - _notificationJob = new JsonApiJob(ptr->account(), notificationsPath, this); - QObject::connect(_notificationJob.data(), &JsonApiJob::jsonReceived, - this, &ServerNotificationHandler::slotNotificationsReceived); - _notificationJob->setProperty("AccountStatePtr", QVariant::fromValue(ptr)); - - _notificationJob->start(); -} - -void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &json, int statusCode) -{ - if (statusCode != 200) { - qCWarning(lcServerNotification) << "Notifications failed with status code " << statusCode; - deleteLater(); - return; - } - - const auto ¬ifies = json.object().value(QLatin1String("ocs")).toObject().value(QLatin1String("data")).toArray(); - AccountState *ai = qvariant_cast(sender()->property("AccountStatePtr")); - - ActivityList list; - list.reserve(notifies.size()); - for (const auto &element : notifies) { - const auto json = element.toObject(); - const auto id = json.value(QStringLiteral("notification_id")).toVariant().value(); - - const auto actions = json.value("actions").toArray(); - QVector linkList; - linkList.reserve(actions.size() + 1); - for (const auto &action : actions) { - const auto actionJson = action.toObject(); - ActivityLink al; - al._label = QUrl::fromPercentEncoding(actionJson.value(QStringLiteral("label")).toString().toUtf8()); - al._link = actionJson.value(QStringLiteral("link")).toString(); - al._verb = actionJson.value(QStringLiteral("type")).toString().toUtf8(); - al._isPrimary = actionJson.value(QStringLiteral("primary")).toBool(); - linkList.append(al); - } - - // Add another action to dismiss notification on server - // https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user - ActivityLink al; - al._label = tr("Dismiss"); - al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(id)).toString(); - al._verb = "DELETE"; - al._isPrimary = false; - linkList.append(al); - - list.append(Activity { - Activity::NotificationType, - id, - ai->account(), - json.value(QStringLiteral("subject")).toString(), - json.value(QStringLiteral("message")).toString(), - QString(), - QUrl(json.value(QStringLiteral("link")).toString()), - QDateTime::fromString(json.value(QStringLiteral("datetime")).toString(), Qt::ISODate), - std::move(linkList) }); - } - emit newNotificationList(list); - - deleteLater(); -} -} diff --git a/src/gui/servernotificationhandler.h b/src/gui/servernotificationhandler.h deleted file mode 100644 index e9b6c9a298e..00000000000 --- a/src/gui/servernotificationhandler.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ - -#ifndef SERVERNOTIFICATIONHANDLER_H -#define SERVERNOTIFICATIONHANDLER_H - -#include - -#include "activitywidget.h" - -class QJsonDocument; - -namespace OCC { - -class ServerNotificationHandler : public QObject -{ - Q_OBJECT -public: - explicit ServerNotificationHandler(QObject *parent = nullptr); - -signals: - void newNotificationList(ActivityList); - -public slots: - void slotFetchNotifications(AccountState *ptr); - -private slots: - void slotNotificationsReceived(const QJsonDocument &json, int statusCode); - -private: - QPointer _notificationJob; -}; -} - -#endif // SERVERNOTIFICATIONHANDLER_H diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp deleted file mode 100644 index 9d1b1fca0e2..00000000000 --- a/src/gui/settingsdialog.cpp +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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 "settingsdialog.h" -#include "ui_settingsdialog.h" - -#include "application.h" -#include "folderman.h" -#include "theme.h" -#include "generalsettings.h" -#include "guiutility.h" -#include "networksettings.h" -#include "accountsettings.h" -#include "configfile.h" -#include "progressdispatcher.h" -#include "owncloudgui.h" -#include "activitywidget.h" -#include "accountmanager.h" -#include "protocolwidget.h" -#include "owncloudsetupwizard.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_MAC -#include "settingsdialog_mac.h" - -void setActivationPolicy(ActivationPolicy policy); -#endif - -namespace { -auto minimumSizeHint(const QWidget *w) -{ - const QSize min { 800, 700 }; // When changing this, please check macOS: widgets there have larger insets, so they take up more space. - const auto screen = w->windowHandle() ? w->windowHandle()->screen() : QApplication::screenAt(QCursor::pos()); - if (screen) { - const auto availableSize = screen->availableSize(); - if (availableSize.isValid()) { - // Assume we can use at least 90% of the screen, if the screen is smaller than 800x700 pixels. - // - // Note: this means that the wizards have even less space: with the style we use, the - // wizard tries to fit inside the window. So, if this is a common case that users have - // such small screens, and the contents of the wizard screen are squashed together (or - // not shown due to lack of space), we should consider putting that content in a - // scroll-view. - return min.boundedTo(availableSize * 0.9); - } - } - return min; -} - -const QString TOOLBAR_CSS() -{ - return QStringLiteral("QToolBar { background: %1; margin: 0; padding: 0; border: none; border-bottom: 1px solid %2; spacing: 0; } " - "QToolBar QToolButton { background: %1; border: none; border-bottom: 1px solid %2; margin: 0; padding: 5px; } " - "QToolBar QToolBarExtension { padding:0; } " - "QToolBar QToolButton:checked { background: %3; color: %4; }"); -} - -const float BUTTONSIZERATIO = 1.618f; // golden ratio - - -/** display name with two lines that is displayed in the settings - */ -QString shortDisplayNameForSettings(OCC::Account *account) -{ - QString user = account->davDisplayName(); - if (user.isEmpty()) { - user = account->credentials()->user(); - } - QString host = account->url().host(); - int port = account->url().port(); - if (port > 0 && port != 80 && port != 443) { - host.append(QLatin1Char(':')); - host.append(QString::number(port)); - } - return QStringLiteral("%1\n%2").arg(user, host); -} -} - - -namespace OCC { - -class ToolButtonAction : public QWidgetAction -{ - Q_OBJECT -public: - explicit ToolButtonAction(const QIcon &icon, const QString &text, QObject *parent) - : QWidgetAction(parent) - { - setIcon(icon); - setText(text); - setCheckable(true); - } - - explicit ToolButtonAction(const QString &icon, const QString &text, QObject *parent) - : QWidgetAction(parent) - { - setText(text); - setIconName(icon); - setCheckable(true); - } - - - QWidget *createWidget(QWidget *parent) override - { - auto toolbar = qobject_cast(parent); - if (!toolbar) { - // this means we are in the extention menu, no special action here - return nullptr; - } - - QToolButton *btn = new QToolButton(toolbar); - QString objectName = QLatin1String("settingsdialog_toolbutton_"); - objectName += text(); - btn->setObjectName(objectName); - - btn->setDefaultAction(this); - btn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); - btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding); - // icon size is fixed, we can't use the toolbars actual size hint as it might not be defined yet - btn->setMinimumWidth(toolbar->iconSize().height() * BUTTONSIZERATIO); - return btn; - } - - QString iconName() const - { - return _iconName; - } - - void setIconName(const QString &iconName) - { - if (_iconName != iconName) { - _iconName = iconName; - updateIcon(); - } - } - - void updateIcon() - { - if (!_iconName.isEmpty()) { - setIcon(Utility::getCoreIcon(_iconName)); - } - } - -private: - QString _iconName; -}; - -SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent) - : QMainWindow(parent, Qt::WindowFlags() & ~Qt::WindowContextHelpButtonHint) - , _ui(new Ui::SettingsDialog) - , _gui(gui) -{ - ConfigFile cfg; - _ui->setupUi(this); - - // People perceive this as a Window, so also make Ctrl+W work - QAction *closeWindowAction = new QAction(this); - closeWindowAction->setShortcut(QKeySequence("Ctrl+W")); - connect(closeWindowAction, &QAction::triggered, this, &SettingsDialog::hide); - addAction(closeWindowAction); - - setObjectName("Settings"); // required as group for saveGeometry call - setWindowTitle(Theme::instance()->appNameGUI()); - - _actionGroup = new QActionGroup(this); - _actionGroup->setExclusive(true); - - - if (Theme::instance()->multiAccount()) { - _addAccountAction = createActionWithIcon(QStringLiteral("plus-solid"), tr("Add account")); - _addAccountAction->setCheckable(false); - connect(_addAccountAction, &QAction::triggered, this, []{ - // don't directly connect here, ocApp might not be defined yet - ocApp()->gui()->runNewAccountWizard(); - }); - _ui->toolBar->addAction(_addAccountAction); - } - - // Note: all the actions have a '\n' because the account name is in two lines and - // all buttons must have the same size in order to keep a good layout - _activityAction = createActionWithIcon(QStringLiteral("activity"), tr("Activity")); - _actionGroup->addAction(_activityAction); - _ui->toolBar->addAction(_activityAction); - _activitySettings = new ActivitySettings; - _ui->stack->addWidget(_activitySettings); - connect(_activitySettings, &ActivitySettings::guiLog, _gui, - [this](const QString &title, const QString &msg) { - _gui->slotShowOptionalTrayMessage(title, msg); - }); - _activitySettings->setNotificationRefreshInterval(cfg.notificationRefreshInterval()); - - QAction *generalAction = createActionWithIcon(QStringLiteral("settings"), tr("Settings")); - _actionGroup->addAction(generalAction); - _ui->toolBar->addAction(generalAction); - GeneralSettings *generalSettings = new GeneralSettings; - _ui->stack->addWidget(generalSettings); - QObject::connect(generalSettings, &GeneralSettings::showAbout, gui, &ownCloudGui::slotAbout); - - QWidget *spacer = new QWidget(); - spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); - _ui->toolBar->addWidget(spacer); - - const auto appNameGui = Theme::instance()->appNameGUI(); - - QAction *quitAction = createActionWithIcon(QStringLiteral("quit"), tr("Quit %1").arg(appNameGui)); - quitAction->setCheckable(false); - connect(quitAction, &QAction::triggered, this, [this, appNameGui] { - const auto reply = QMessageBox::question(this, tr("Quit %1").arg(appNameGui), - tr("Are you sure you want to quit %1?").arg(appNameGui), - QMessageBox::Yes | QMessageBox::No); - if (reply == QMessageBox::Yes) { - qApp->quit(); - } - }); - _ui->toolBar->addAction(quitAction); - - _actionGroupWidgets.insert(_activityAction, _activitySettings); - _actionGroupWidgets.insert(generalAction, generalSettings); - - connect(_actionGroup, &QActionGroup::triggered, this, &SettingsDialog::slotSwitchPage); - - connect(AccountManager::instance(), &AccountManager::accountAdded, - this, &SettingsDialog::accountAdded); - connect(AccountManager::instance(), &AccountManager::accountRemoved, - this, &SettingsDialog::accountRemoved); - for (const auto &ai : AccountManager::instance()->accounts()) { - accountAdded(ai.data()); - } - - QTimer::singleShot(0, this, &SettingsDialog::showFirstPage); - - connect(_ui->hideButton, &QPushButton::clicked, this, &SettingsDialog::hide); - - QAction *showLogWindow = new QAction(this); - showLogWindow->setShortcut(QKeySequence("F12")); - connect(showLogWindow, &QAction::triggered, gui, &ownCloudGui::slotToggleLogBrowser); - addAction(showLogWindow); - - QAction *showLogWindow2 = new QAction(this); - showLogWindow2->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L)); - connect(showLogWindow2, &QAction::triggered, gui, &ownCloudGui::slotToggleLogBrowser); - addAction(showLogWindow2); - - customizeStyle(); - - cfg.restoreGeometry(this); - setMinimumSize(::minimumSizeHint(this)); -#ifdef Q_OS_MAC - setActivationPolicy(ActivationPolicy::Accessory); -#endif -} - -SettingsDialog::~SettingsDialog() -{ - delete _ui; -} - -QSize SettingsDialog::sizeHintForChild() const -{ - return ::minimumSizeHint(this) * 0.9; -} - -QWidget* SettingsDialog::currentPage() -{ - return _ui->stack->currentWidget(); -} - -void SettingsDialog::changeEvent(QEvent *e) -{ - switch (e->type()) { - case QEvent::StyleChange: - case QEvent::PaletteChange: - case QEvent::ThemeChange: - customizeStyle(); - break; - default: - break; - } - - QMainWindow::changeEvent(e); -} - -void SettingsDialog::setVisible(bool visible) -{ - if (!visible) - { - ConfigFile cfg; - cfg.saveGeometry(this); - } - -#ifdef Q_OS_MAC - if (visible) { - setActivationPolicy(ActivationPolicy::Regular); - } else { - setActivationPolicy(ActivationPolicy::Accessory); - } -#endif - QMainWindow::setVisible(visible); -} - -void SettingsDialog::slotSwitchPage(QAction *action) -{ - _ui->stack->setCurrentWidget(_actionGroupWidgets.value(action)); -} - -void SettingsDialog::showFirstPage() -{ - const QList &actions = _ui->toolBar->actions(); - if (!actions.empty()) { - actions.at(_addAccountAction ? 1 : 0)->trigger(); - } -} - -void SettingsDialog::showActivityPage() -{ - if (_activityAction) { - _activityAction->trigger(); - } -} - -void SettingsDialog::showIssuesList() -{ - if (!_activityAction) - return; - _activityAction->trigger(); - _activitySettings->slotShowIssuesTab(); -} - -void SettingsDialog::accountAdded(AccountState *s) -{ - bool brandingSingleAccount = !Theme::instance()->multiAccount(); - - QAction *accountAction; - const QPixmap avatar = s->account()->avatar(); - const QString actionText = brandingSingleAccount ? tr("Account") : s->account()->displayName(); - if (avatar.isNull()) { - accountAction = createActionWithIcon(QStringLiteral("account"), - actionText); - } else { - const QIcon icon(AvatarJob::makeCircularAvatar(avatar)); - accountAction = new ToolButtonAction(icon, actionText, this); - } - - if (!brandingSingleAccount) { - accountAction->setToolTip(s->account()->displayName()); - accountAction->setIconText(shortDisplayNameForSettings(s->account().data())); - } - _ui->toolBar->insertAction(_addAccountAction ? _ui->toolBar->actions().at(1) : _ui->toolBar->actions().at(0), accountAction); - auto accountSettings = new AccountSettings(s, this); - QString objectName = QLatin1String("accountSettings_"); - objectName += s->account()->displayName(); - accountSettings->setObjectName(objectName); - _ui->stack->insertWidget(0 , accountSettings); - - _actionGroup->addAction(accountAction); - _actionGroupWidgets.insert(accountAction, accountSettings); - _actionForAccount.insert(s->account().data(), accountAction); - accountAction->trigger(); - - connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged); - connect(accountSettings, &AccountSettings::openFolderAlias, - _gui, &ownCloudGui::slotFolderOpenAction); - connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialog::showIssuesList); - connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialog::slotAccountAvatarChanged); - connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialog::slotAccountDisplayNameChanged); - - // Refresh immediatly when getting online - connect(s, &AccountState::isConnectedChanged, this, &SettingsDialog::slotRefreshActivityAccountStateSender); - - slotRefreshActivity(s); -} - -void SettingsDialog::slotAccountAvatarChanged() -{ - Account *account = static_cast(sender()); - if (account && _actionForAccount.contains(account)) { - QAction *action = _actionForAccount[account]; - if (action) { - const QPixmap pix = account->avatar(); - if (!pix.isNull()) { - action->setIcon(AvatarJob::makeCircularAvatar(pix)); - } - } - } -} - -void SettingsDialog::slotAccountDisplayNameChanged() -{ - Account *account = static_cast(sender()); - if (account && _actionForAccount.contains(account)) { - QAction *action = _actionForAccount[account]; - if (action) { - QString displayName = account->displayName(); - action->setText(displayName); - action->setIconText(shortDisplayNameForSettings(account)); - } - } -} - -void SettingsDialog::accountRemoved(AccountState *s) -{ - for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) { - auto as = qobject_cast(*it); - if (!as) { - continue; - } - if (as->accountsState() == s) { - _ui->toolBar->removeAction(it.key()); - - if (_ui->stack->currentWidget() == it.value()) { - showFirstPage(); - } - - it.key()->deleteLater(); - it.value()->deleteLater(); - _actionGroupWidgets.erase(it); - break; - } - } - - if (_actionForAccount.contains(s->account().data())) { - _actionForAccount.remove(s->account().data()); - } - _activitySettings->slotRemoveAccount(s); -} - -void SettingsDialog::customizeStyle() -{ - QString highlightColor(palette().highlight().color().name()); - QString highlightTextColor(palette().highlightedText().color().name()); - QString dark(palette().dark().color().name()); - QString background(palette().base().color().name()); - _ui->toolBar->setStyleSheet(TOOLBAR_CSS().arg(background, dark, highlightColor, highlightTextColor)); - - const auto &toolButtonActions = findChildren(); - for (auto *a : toolButtonActions) { - a->updateIcon(); - } -} - - -QAction *SettingsDialog::createActionWithIcon(const QString &icon, const QString &text) -{ - QAction *action = new ToolButtonAction(icon, text, this); - return action; -} - -void SettingsDialog::slotRefreshActivityAccountStateSender() -{ - slotRefreshActivity(qobject_cast(sender())); -} - -void SettingsDialog::slotRefreshActivity(AccountState *accountState) -{ - if (accountState) { - _activitySettings->slotRefresh(accountState); - } -} - -} // namespace OCC - -#include "settingsdialog.moc" diff --git a/src/gui/settingsdialog.h b/src/gui/settingsdialog.h deleted file mode 100644 index c96e559e056..00000000000 --- a/src/gui/settingsdialog.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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. - */ - -#ifndef SETTINGSDIALOG_H -#define SETTINGSDIALOG_H - -#include -#include -#include - -#include "progressdispatcher.h" -#include "owncloudgui.h" - -class QAction; -class QActionGroup; -class QToolBar; -class QStandardItemModel; - -namespace OCC { - -class AccountState; - -namespace Ui { - class SettingsDialog; -} -class AccountSettings; -class Application; -class FolderMan; -class ownCloudGui; -class ActivitySettings; - -/** - * @brief The SettingsDialog class - * @ingroup gui - */ -class SettingsDialog : public QMainWindow -{ - Q_OBJECT - Q_PROPERTY(QWidget* currentPage READ currentPage) - -public: - explicit SettingsDialog(ownCloudGui *gui, QWidget *parent = nullptr); - ~SettingsDialog() override; - - QSize sizeHintForChild() const; - - QWidget* currentPage(); - -public slots: - void showFirstPage(); - void showActivityPage(); - void showIssuesList(); - void slotSwitchPage(QAction *action); - void slotRefreshActivity(AccountState *accountState); - void slotRefreshActivityAccountStateSender(); - void slotAccountAvatarChanged(); - void slotAccountDisplayNameChanged(); - -protected: - void changeEvent(QEvent *) override; - void setVisible(bool visible) override; - -private slots: - void accountAdded(AccountState *); - void accountRemoved(AccountState *); - -private: - void customizeStyle(); - - QAction *createActionWithIcon(const QString &iconName, const QString &text); - - Ui::SettingsDialog *const _ui; - - QActionGroup *_actionGroup; - // Maps the actions from the action group to the corresponding widgets - QHash _actionGroupWidgets; - - // Maps the action in the dialog to their according account. Needed in - // case the account avatar changes - QHash _actionForAccount; - - ActivitySettings *_activitySettings; - - QAction *_activityAction; - QAction *_addAccountAction = nullptr; - ownCloudGui *_gui; - -}; -} - -#endif // SETTINGSDIALOG_H diff --git a/src/gui/settingsdialog.ui b/src/gui/settingsdialog.ui deleted file mode 100644 index 1971156bc90..00000000000 --- a/src/gui/settingsdialog.ui +++ /dev/null @@ -1,76 +0,0 @@ - - - OCC::SettingsDialog - - - - 0 - 0 - 800 - 500 - - - - MainWindow - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Hide - - - - - - - - - - Qt::PreventContextMenu - - - false - - - - 32 - 32 - - - - Qt::ToolButtonTextUnderIcon - - - false - - - TopToolBarArea - - - false - - - - - - diff --git a/src/gui/settingsdialog_mac.mm b/src/gui/settingsdialog_mac.mm index b63b9d4304c..729a66bb001 100644 --- a/src/gui/settingsdialog_mac.mm +++ b/src/gui/settingsdialog_mac.mm @@ -13,6 +13,7 @@ */ #include "settingsdialog_mac.h" +#include "common/utility.h" #import #include @@ -31,7 +32,10 @@ void setActivationPolicy(ActivationPolicy policy) mode = NSApplicationActivationPolicyProhibited; break; } - if (![NSApp setActivationPolicy:mode]) { - qWarning() << "setActivationPolicy" << static_cast(policy) << "failed"; + + if (mode != NSApp.activationPolicy) { + if (![NSApp setActivationPolicy:mode]) { + qWarning() << "setActivationPolicy" << static_cast(policy) << "failed"; + } } } diff --git a/src/gui/sharedialog.cpp b/src/gui/sharedialog.cpp deleted file mode 100644 index ae6e440bc75..00000000000 --- a/src/gui/sharedialog.cpp +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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 "sharedialog.h" -#include "sharee.h" -#include "sharelinkwidget.h" -#include "shareusergroupwidget.h" -#include "ui_sharedialog.h" - -#include "account.h" -#include "accountstate.h" -#include "application.h" -#include "configfile.h" -#include "settingsdialog.h" -#include "theme.h" -#include "thumbnailjob.h" - -#include -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -namespace OCC { - -static const int thumbnailSize = 40; - -ShareDialog::ShareDialog(QPointer accountState, - const QString &sharePath, - const QString &localPath, - SharePermissions maxSharingPermissions, - ShareDialogStartPage startPage, - QWidget *parent) - : QDialog(parent) - , _ui(new Ui::ShareDialog) - , _accountState(accountState) - , _sharePath(sharePath) - , _localPath(localPath) - , _maxSharingPermissions(maxSharingPermissions) - , _startPage(startPage) - , _linkWidget(nullptr) - , _userGroupWidget(nullptr) - , _progressIndicator(nullptr) -{ - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - setAttribute(Qt::WA_DeleteOnClose); - setObjectName("SharingDialog"); - - _ui->setupUi(this); - - QPushButton *closeButton = _ui->buttonBox->button(QDialogButtonBox::Close); - connect(closeButton, &QAbstractButton::clicked, this, &QWidget::close); - - // We want to act on account state changes - connect(_accountState.data(), &AccountState::stateChanged, this, &ShareDialog::slotAccountStateChanged); - - // Because people press enter in the dialog and we don't want to close for that - closeButton->setDefault(false); - closeButton->setAutoDefault(false); - - // Set icon - QFileInfo f_info(_localPath); - QFileIconProvider icon_provider; - const QIcon icon = icon_provider.icon(f_info); - if (!icon.isNull()) { - auto pixmap = icon.pixmap(thumbnailSize, thumbnailSize); - _ui->label_icon->setPixmap(pixmap); - } else { - _ui->label_icon->hide(); - } - - // Set filename - QString fileName = QFileInfo(_sharePath).fileName(); - _ui->label_name->setText(tr("%1").arg(fileName)); - - _ui->label_sharePath->setWordWrap(true); - QString ocDir(_sharePath); - ocDir.truncate(ocDir.length() - fileName.length()); - - // remove leading and trailing spaces - ocDir.remove(QRegularExpression(QStringLiteral("^/*|/*$"))); - - if (ocDir.isEmpty()) { - _ui->label_name->setVisible(true); - _ui->label_sharePath->setVisible(false); - _ui->label_sharePath->setText(QString()); - } else { - _ui->label_name->setVisible(true); - _ui->label_sharePath->setVisible(true); - _ui->label_sharePath->setText(tr("Folder: %2").arg(ocDir)); - } - - this->setWindowTitle(tr("%1 Sharing").arg(Theme::instance()->appNameGUI())); - - if (!accountState->account()->capabilities().shareAPI()) { - 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); - _ui->shareWidgets->hide(); - return; - } - - if (QFileInfo(_localPath).isFile()) { - ThumbnailJob *job = new ThumbnailJob(_sharePath, _accountState->account(), this); - connect(job, &ThumbnailJob::jobFinished, this, &ShareDialog::slotThumbnailFetched); - job->start(); - } - - _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({ QByteArrayLiteral("http://open-collaboration-services.org/ns:share-permissions"), - QByteArrayLiteral("http://owncloud.org/ns:privatelink") }); - job->setTimeout(10s); - connect(job, &PropfindJob::result, this, &ShareDialog::slotPropfindReceived); - connect(job, &PropfindJob::finishedWithError, this, &ShareDialog::slotPropfindError); - job->start(); - - resize(ocApp()->gui()->settingsDialog()->sizeHintForChild()); -} - -ShareDialog::~ShareDialog() -{ - delete _ui; -} - -void ShareDialog::slotPropfindReceived(const QMap &result) -{ - const auto &receivedPermissions = result["share-permissions"]; - if (!receivedPermissions.isEmpty()) { - _maxSharingPermissions = static_cast(receivedPermissions.toInt()); - qCInfo(lcSharing) << "Received sharing permissions for" << _sharePath << _maxSharingPermissions; - } - auto privateLinkUrl = result["privatelink"]; - if (!privateLinkUrl.isEmpty()) { - qCInfo(lcSharing) << "Received private link url for" << _sharePath << privateLinkUrl; - _privateLinkUrl = privateLinkUrl; - } - - showSharingUi(); -} - -void ShareDialog::slotPropfindError() -{ - // On error show the share ui anyway. The user can still see shares, - // delete them and so on, even though adding new shares or granting - // some of the permissions might fail. - - showSharingUi(); -} - -void ShareDialog::showSharingUi() -{ - _progressIndicator->stopAnimation(); - - auto theme = Theme::instance(); - - // There's no difference between being unable to reshare and - // being unable to reshare with reshare permission. - bool canReshare = _maxSharingPermissions & SharePermissionShare; - - if (!canReshare) { - auto label = new QLabel(this); - label->setText(tr("The file can not be shared because it was shared without sharing permission.")); - label->setWordWrap(true); - label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - layout()->replaceWidget(_ui->shareWidgets, label); - return; - } - - // We only do user/group sharing from 8.2.0 - bool userGroupSharing = - theme->userGroupSharing() - && _accountState->account()->serverVersionInt() >= Account::makeServerVersion(8, 2, 0); - - if (userGroupSharing) { - _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, this); - _ui->shareWidgets->addTab(_userGroupWidget, tr("Users and Groups")); - _userGroupWidget->getShares(); - } - - if (theme->linkSharing()) { - _linkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this); - _linkWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - _ui->shareWidgets->addTab(_linkWidget, tr("Public Links")); - _linkWidget->getShares(); - - if (_startPage == ShareDialogStartPage::PublicLinks) - _ui->shareWidgets->setCurrentWidget(_linkWidget); - } -} - -QSize ShareDialog::minimumSizeHint() const -{ - return ocApp()->gui()->settingsDialog()->sizeHintForChild(); -} - -void ShareDialog::slotThumbnailFetched(const int &statusCode, const QPixmap &reply) -{ - if (statusCode != 200) { - qCWarning(lcSharing) << "Thumbnail status code: " << statusCode; - return; - } - if (reply.isNull()) { - qCWarning(lcSharing) << "Invalid pixmap"; - return; - } - const auto p = reply.scaledToHeight(thumbnailSize, Qt::SmoothTransformation); - _ui->label_icon->setPixmap(p); - _ui->label_icon->show(); -} - -void ShareDialog::slotAccountStateChanged(int state) -{ - bool enabled = (state == AccountState::State::Connected); - qCDebug(lcSharing) << "Account connected?" << enabled; - - if (_userGroupWidget != nullptr) { - _userGroupWidget->setEnabled(enabled); - } - - if (_linkWidget != nullptr) { - _linkWidget->setEnabled(enabled); - } -} -} diff --git a/src/gui/sharedialog.h b/src/gui/sharedialog.h deleted file mode 100644 index a8f7a1759ea..00000000000 --- a/src/gui/sharedialog.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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. - */ - -#ifndef SHAREDIALOG_H -#define SHAREDIALOG_H - -#include "accountstate.h" -#include "sharepermissions.h" -#include "owncloudgui.h" - -#include -#include -#include -#include - -class QProgressIndicator; - -namespace OCC { - -namespace Ui { - class ShareDialog; -} - -class ShareLinkWidget; -class ShareUserGroupWidget; - -class ShareDialog : public QDialog -{ - Q_OBJECT - -public: - explicit ShareDialog(QPointer accountState, - const QString &sharePath, - const QString &localPath, - SharePermissions maxSharingPermissions, - ShareDialogStartPage startPage, - QWidget *parent); - ~ShareDialog() override; - - QSize minimumSizeHint() const override; - -private slots: - void slotPropfindReceived(const QMap &result); - void slotPropfindError(); - void slotThumbnailFetched(const int &statusCode, const QPixmap &reply); - void slotAccountStateChanged(int state); - -private: - void showSharingUi(); - - Ui::ShareDialog *_ui; - QPointer _accountState; - QString _sharePath; - QString _localPath; - SharePermissions _maxSharingPermissions; - QByteArray _numericFileId; - QString _privateLinkUrl; - ShareDialogStartPage _startPage; - - ShareLinkWidget *_linkWidget; - ShareUserGroupWidget *_userGroupWidget; - QProgressIndicator *_progressIndicator; -}; -} - -#endif // SHAREDIALOG_H diff --git a/src/gui/sharedialog.ui b/src/gui/sharedialog.ui deleted file mode 100644 index 73d2361597e..00000000000 --- a/src/gui/sharedialog.ui +++ /dev/null @@ -1,100 +0,0 @@ - - - OCC::ShareDialog - - - - 0 - 0 - 151 - 93 - - - - - 0 - 0 - - - - Dialog - - - - - - - - TextLabel - - - - - - - - - - 0 - 0 - - - - share label - - - Qt::PlainText - - - - - - - - 0 - 0 - - - - - 50 - false - - - - ownCloud Path: - - - Qt::PlainText - - - - - - - - - - - - - - - - - 0 - 0 - - - - QDialogButtonBox::Close - - - - - - - - - - diff --git a/src/gui/sharee.cpp b/src/gui/sharee.cpp deleted file mode 100644 index 9c6db60d100..00000000000 --- a/src/gui/sharee.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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 "sharee.h" -#include "ocsshareejob.h" - -#include -#include -#include - -namespace OCC { - -Q_LOGGING_CATEGORY(lcSharing, "gui.sharing", QtInfoMsg) - -Sharee::Sharee(const QString shareWith, - const QString displayName, - const Type type) - : _shareWith(shareWith) - , _displayName(displayName) - , _type(type) -{ -} - -QString Sharee::format() const -{ - QString formatted = _displayName; - - if (_type == Type::Group) { - formatted += QLatin1String(" (group)"); - } else if (_type == Type::Federated) { - formatted += QLatin1String(" (remote)"); - } - - return formatted; -} - -QString Sharee::shareWith() const -{ - return _shareWith; -} - -QString Sharee::displayName() const -{ - return _displayName; -} - -Sharee::Type Sharee::type() const -{ - return _type; -} - -ShareeModel::ShareeModel(const AccountPtr &account, const QString &type, QObject *parent) - : QAbstractListModel(parent) - , _account(account) - , _type(type) -{ -} - -void ShareeModel::fetch(const QString &search, const ShareeSet &blacklist) -{ - _search = search; - _shareeBlacklist = blacklist; - OcsShareeJob *job = new OcsShareeJob(_account); - connect(job, &OcsShareeJob::shareeJobFinished, this, &ShareeModel::shareesFetched); - connect(job, &OcsJob::ocsError, this, &ShareeModel::displayErrorMessage); - job->getSharees(_search, _type, 1, 50); -} - -void ShareeModel::shareesFetched(const QJsonDocument &reply) -{ - auto data = reply.object().value("ocs").toObject().value("data").toObject(); - - QVector> newSharees; - - /* - * Todo properly loop all of this - */ - auto exact = data.value("exact").toObject(); - { - const auto &users = exact.value("users").toArray(); - for (const auto &user : users) { - newSharees.append(parseSharee(user.toObject())); - } - const auto &groups = exact.value("groups").toArray(); - for (const auto &group : groups) { - newSharees.append(parseSharee(group.toObject())); - } - const auto &remotes = exact.value("remotes").toArray(); - for (const auto &remote : remotes) { - newSharees.append(parseSharee(remote.toObject())); - } - } - - { - const auto &users = data.value("users").toArray(); - for (const auto &user : users) { - newSharees.append(parseSharee(user.toObject())); - } - } - { - const auto &groups = data.value("groups").toArray(); - for (const auto &group : groups) { - newSharees.append(parseSharee(group.toObject())); - } - } - { - const auto &remotes = data.value("remotes").toArray(); - for (const auto &remote : remotes) { - newSharees.append(parseSharee(remote.toObject())); - } - } - - // Filter sharees that we have already shared with - QVector> filteredSharees; - for (const auto &sharee : qAsConst(newSharees)) { - bool found = false; - for (const auto &blacklistSharee : qAsConst(_shareeBlacklist)) { - if (sharee->type() == blacklistSharee->type() && sharee->shareWith() == blacklistSharee->shareWith()) { - found = true; - break; - } - } - - if (found == false) { - filteredSharees.append(sharee); - } - } - - setNewSharees(filteredSharees); - shareesReady(); -} - -QSharedPointer ShareeModel::parseSharee(const QJsonObject &data) -{ - QString displayName = data.value("label").toString(); - const QString shareWith = data.value("value").toObject().value("shareWith").toString(); - Sharee::Type type = (Sharee::Type)data.value("value").toObject().value("shareType").toInt(); - const QString additionalInfo = data.value("value").toObject().value("shareWithAdditionalInfo").toString(); - if (!additionalInfo.isEmpty()) { - displayName = tr("%1 (%2)", "sharee (shareWithAdditionalInfo)").arg(displayName, additionalInfo); - } - - return QSharedPointer(new Sharee(shareWith, displayName, type)); -} - - -// Helper function for setNewSharees (could be a lambda when we can use them) -static QSharedPointer shareeFromModelIndex(const QModelIndex &idx) -{ - return idx.data(Qt::UserRole).value>(); -} - -struct FindShareeHelper -{ - const QSharedPointer &sharee; - bool operator()(const QSharedPointer &s2) const - { - return s2->format() == sharee->format() && s2->displayName() == sharee->format(); - } -}; - -/* Set the new sharee - - Do that while preserving the model index so the selection stays -*/ -void ShareeModel::setNewSharees(const QVector> &newSharees) -{ - layoutAboutToBeChanged(); - const auto persistent = persistentIndexList(); - QVector> oldPersistantSharee; - oldPersistantSharee.reserve(persistent.size()); - - std::transform(persistent.begin(), persistent.end(), std::back_inserter(oldPersistantSharee), - shareeFromModelIndex); - - _sharees = newSharees; - - QModelIndexList newPersistant; - newPersistant.reserve(persistent.size()); - for (const auto &sharee : qAsConst(oldPersistantSharee)) { - FindShareeHelper helper = { sharee }; - auto it = std::find_if(_sharees.constBegin(), _sharees.constEnd(), helper); - if (it == _sharees.constEnd()) { - newPersistant << QModelIndex(); - } else { - newPersistant << index(it - _sharees.constBegin()); - } - } - - changePersistentIndexList(persistent, newPersistant); - layoutChanged(); -} - - -int ShareeModel::rowCount(const QModelIndex &) const -{ - return _sharees.size(); -} - -QVariant ShareeModel::data(const QModelIndex &index, int role) const -{ - if (index.row() < 0 || index.row() > _sharees.size()) { - return QVariant(); - } - - const auto &sharee = _sharees.at(index.row()); - if (role == Qt::DisplayRole) { - return sharee->format(); - - } else if (role == Qt::EditRole) { - // This role is used by the completer - it should match - // the full name and the user name and thus we include both - // in the output here. But we need to take care this string - // doesn't leak to the user. - return QString(sharee->displayName() + " (" + sharee->shareWith() + ")"); - - } else if (role == Qt::UserRole) { - return QVariant::fromValue(sharee); - } - - return QVariant(); -} - -QSharedPointer ShareeModel::getSharee(int at) -{ - if (at < 0 || at > _sharees.size()) { - return QSharedPointer(nullptr); - } - - return _sharees.at(at); -} -} diff --git a/src/gui/sharee.h b/src/gui/sharee.h deleted file mode 100644 index 6b56642e87b..00000000000 --- a/src/gui/sharee.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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. - */ - -#ifndef SHAREE_H -#define SHAREE_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "accountfwd.h" - -class QJsonDocument; -class QJsonObject; - -namespace OCC { - -Q_DECLARE_LOGGING_CATEGORY(lcSharing) - -class Sharee -{ -public: - // Keep in sync with Share::ShareType - enum Type { - User = 0, - Group = 1, - Federated = 6 - }; - - explicit Sharee(const QString shareWith, - const QString displayName, - const Type type); - - QString format() const; - QString shareWith() const; - QString displayName() const; - Type type() const; - -private: - QString _shareWith; - QString _displayName; - Type _type; -}; - - -class ShareeModel : public QAbstractListModel -{ - Q_OBJECT -public: - explicit ShareeModel(const AccountPtr &account, const QString &type, QObject *parent = nullptr); - - typedef QVector> ShareeSet; // FIXME: make it a QSet when Sharee can be compared - void fetch(const QString &search, const ShareeSet &blacklist); - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - - QSharedPointer getSharee(int at); - - QString currentSearch() const { return _search; } - -signals: - void shareesReady(); - void displayErrorMessage(int code, const QString &); - -private slots: - void shareesFetched(const QJsonDocument &reply); - -private: - QSharedPointer parseSharee(const QJsonObject &data); - void setNewSharees(const QVector> &newSharees); - - AccountPtr _account; - QString _search; - QString _type; - - QVector> _sharees; - QVector> _shareeBlacklist; -}; -} - -Q_DECLARE_METATYPE(QSharedPointer) - -#endif //SHAREE_H diff --git a/src/gui/sharelinkwidget.cpp b/src/gui/sharelinkwidget.cpp deleted file mode 100644 index 7960a6dd19c..00000000000 --- a/src/gui/sharelinkwidget.cpp +++ /dev/null @@ -1,735 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * Copyright (C) 2015 by Klaas Freitag - * - * 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 "sharelinkwidget.h" -#include "ui_sharelinkwidget.h" -#include "account.h" -#include "capabilities.h" - -#include "sharemanager.h" -#include "guiutility.h" - -#include "QProgressIndicator.h" -#include -#include -#include -#include -#include -#include -#include - -namespace OCC { - -const char propertyShareC[] = "oc_share"; - -ShareLinkWidget::ShareLinkWidget(AccountPtr account, - const QString &sharePath, - const QString &localPath, - SharePermissions maxSharingPermissions, - QWidget *parent) - : QWidget(parent) - , _ui(new Ui::ShareLinkWidget) - , _account(account) - , _sharePath(sharePath) - , _localPath(localPath) - , _manager(nullptr) - , _expiryRequired(false) - , _namesSupported(true) -{ - _ui->setupUi(this); - - _ui->linkShares->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); - _ui->linkShares->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - _ui->linkShares->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); - - //Is this a file or folder? - QFileInfo fi(localPath); - _isFile = fi.isFile(); - - // Note: the share name cannot be longer than 64 characters - _ui->nameLineEdit->setText(tr("Public link")); - - // the following progress indicator widgets are added to layouts which makes them - // automatically deleted once the dialog dies. - _pi_create = new QProgressIndicator(); - _pi_password = new QProgressIndicator(); - _pi_date = new QProgressIndicator(); - _pi_editing = new QProgressIndicator(); - _ui->horizontalLayout_create->insertWidget(2, _pi_create); - _ui->horizontalLayout_password->addWidget(_pi_password); - _ui->layout_editing->addWidget(_pi_editing); - _ui->horizontalLayout_expire->insertWidget(_ui->horizontalLayout_expire->count() - 1, _pi_date); - - connect(_ui->nameLineEdit, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotShareNameEntered); - connect(_ui->createShareButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotShareNameEntered); - connect(_ui->linkShares, &QTableWidget::itemSelectionChanged, this, &ShareLinkWidget::slotShareSelectionChanged); - connect(_ui->linkShares, &QTableWidget::itemChanged, this, &ShareLinkWidget::slotNameEdited); - connect(_ui->checkBox_password, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCheckBoxPasswordClicked); - connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotPasswordReturnPressed); - connect(_ui->lineEdit_password, &QLineEdit::textChanged, this, &ShareLinkWidget::slotPasswordChanged); - connect(_ui->pushButton_setPassword, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPasswordReturnPressed); - connect(_ui->checkBox_expire, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCheckBoxExpireClicked); - connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotExpireDateChanged); - connect(_ui->radio_readOnly, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPermissionsClicked); - connect(_ui->radio_readWrite, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPermissionsClicked); - connect(_ui->radio_uploadOnly, &QAbstractButton::clicked, this, &ShareLinkWidget::slotPermissionsClicked); - - _ui->errorLabel->hide(); - - bool sharingPossible = true; - if (!_account->capabilities().sharePublicLink()) { - displayError(tr("Link shares have been disabled")); - sharingPossible = false; - } else if (!(maxSharingPermissions & SharePermissionShare)) { - displayError(tr("The file can not be shared because it was shared without sharing permission.")); - sharingPossible = false; - } - if (!sharingPossible) { - _ui->nameLineEdit->setEnabled(false); - _ui->createShareButton->setEnabled(false); - } - - // Older servers don't support multiple public link shares - if (!_account->capabilities().sharePublicLinkMultiple()) { - _namesSupported = false; - _ui->createShareButton->setText(tr("Create public link share")); - _ui->nameLineEdit->hide(); - _ui->nameLineEdit->clear(); // so we don't send a name - _ui->nameLabel->hide(); - } - - _ui->shareProperties->setEnabled(false); - - _ui->pushButton_setPassword->setEnabled(false); - _ui->lineEdit_password->setEnabled(false); - _ui->pushButton_setPassword->setEnabled(false); - _ui->checkBox_password->setText(tr("P&assword protect")); - - _ui->calendar->setMinimumDate(QDate::currentDate().addDays(1)); - _ui->calendar->setEnabled(false); - - // 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."; - return; - } - - - // Parse capabilities - - if (!_account->capabilities().sharePublicLinkAllowUpload()) { - _ui->radio_readWrite->setEnabled(false); - _ui->radio_uploadOnly->setEnabled(false); - } - - // If expiredate is enforced do not allow disable and set max days - if (_account->capabilities().sharePublicLinkEnforceExpireDate()) { - _ui->checkBox_expire->setEnabled(false); - _ui->calendar->setMaximumDate(capabilityDefaultExpireDate()); - _expiryRequired = true; - } - - // Hide permissions that are unavailable for files or disabled by capability. - bool rwVisible = !_isFile && _account->capabilities().sharePublicLinkAllowUpload(); - bool uploadOnlyVisible = rwVisible && _account->capabilities().sharePublicLinkSupportsUploadOnly(); - _ui->radio_readWrite->setVisible(rwVisible); - _ui->label_readWrite->setVisible(rwVisible); - _ui->radio_uploadOnly->setVisible(uploadOnlyVisible); - _ui->label_uploadOnly->setVisible(uploadOnlyVisible); - - // Prepare sharing menu - - _linkContextMenu = new QMenu(this); - connect(_linkContextMenu, &QMenu::triggered, - this, &ShareLinkWidget::slotLinkContextMenuActionTriggered); - _openLinkAction = _linkContextMenu->addAction(tr("Open link in browser")); - _copyLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard")); - _copyDirectLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard (direct download)")); - _emailLinkAction = _linkContextMenu->addAction(tr("Send link by email")); - _emailDirectLinkAction = _linkContextMenu->addAction(tr("Send link by email (direct download)")); - _deleteLinkAction = _linkContextMenu->addAction(tr("Delete")); - - /* - * Create the share manager and connect it properly - */ - if (sharingPossible) { - _manager = new ShareManager(_account, this); - connect(_manager, &ShareManager::sharesFetched, this, &ShareLinkWidget::slotSharesFetched); - connect(_manager, &ShareManager::linkShareCreated, this, &ShareLinkWidget::slotCreateShareFetched); - connect(_manager, &ShareManager::linkShareCreationForbidden, this, &ShareLinkWidget::slotCreateShareForbidden); - connect(_manager, &ShareManager::serverError, this, &ShareLinkWidget::slotServerError); - } - - auto retainSizeWhenHidden = [](QWidget *w) { - auto sp = w->sizePolicy(); - sp.setRetainSizeWhenHidden(true); - w->setSizePolicy(sp); - }; - retainSizeWhenHidden(_ui->pushButton_setPassword); - retainSizeWhenHidden(_ui->create); - - // If this starts out empty the first call to slotShareSelectionChanged() - // will not properly initialize the ui state if "Create new..." is the - // only option. So pre-fill with an invalid share id. - _selectedShareId = QStringLiteral("!&no-share-selected"); -} - -ShareLinkWidget::~ShareLinkWidget() -{ - delete _ui; -} - -void ShareLinkWidget::getShares() -{ - if (_manager) { - _manager->fetchShares(_sharePath); - } -} - -void ShareLinkWidget::slotSharesFetched(const QList> &shares) -{ - const QString versionString = _account->serverVersion(); - qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares"; - - // Select the share that was previously selected, - // except if an explicit override was asked for - QString reselectShareId = _selectedShareId; - if (!_newShareOverrideSelectionId.isEmpty()) { - reselectShareId = _newShareOverrideSelectionId; - _newShareOverrideSelectionId.clear(); - } - - auto table = _ui->linkShares; - - // Wipe the table without updating the ui elements, we - // might want their state untouched if the same share ends - // up being selected - disconnect(table, &QTableWidget::itemSelectionChanged, this, &ShareLinkWidget::slotShareSelectionChanged); - table->clearContents(); - table->setRowCount(0); - connect(table, &QTableWidget::itemSelectionChanged, this, &ShareLinkWidget::slotShareSelectionChanged); - - auto deleteIcon = QIcon::fromTheme(QStringLiteral("user-trash"), - Utility::getCoreIcon(QStringLiteral("delete"))); - - for (const auto &share : shares) { - if (share->getShareType() != Share::TypeLink) { - continue; - } - auto linkShare = qSharedPointerDynamicCast(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(share.data(), &Share::permissionsSet, this, &ShareLinkWidget::slotPermissionsSet); - connect(linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireSet); - connect(linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet); - connect(linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError); - - // Build the table row - auto row = table->rowCount(); - table->insertRow(row); - - auto nameItem = new QTableWidgetItem; - auto name = shareName(*linkShare); - if (!_namesSupported) { - nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable); - } - nameItem->setText(name); - nameItem->setData(Qt::UserRole, QVariant::fromValue(linkShare)); - table->setItem(row, 0, nameItem); - - auto dotdotdotButton = new QToolButton; - dotdotdotButton->setText("..."); - dotdotdotButton->setProperty(propertyShareC, QVariant::fromValue(linkShare)); - connect(dotdotdotButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotContextMenuButtonClicked); - table->setCellWidget(row, 1, dotdotdotButton); - - auto deleteButton = new QToolButton; - deleteButton->setIcon(deleteIcon); - deleteButton->setProperty(propertyShareC, QVariant::fromValue(linkShare)); - deleteButton->setToolTip(tr("Delete link share")); - connect(deleteButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotDeleteShareClicked); - table->setCellWidget(row, 2, deleteButton); - - // Reestablish the previous selection - if (reselectShareId == share->getId()) { - table->selectRow(row); - } - } - - // Add create-new entry - if (_namesSupported || table->rowCount() == 0) { - auto row = table->rowCount(); - table->insertRow(row); - auto createItem = new QTableWidgetItem; - createItem->setFlags(createItem->flags() & ~Qt::ItemIsEditable); - createItem->setText(tr("Create new...")); - auto font = createItem->font(); - font.setItalic(true); - createItem->setFont(font); - table->setItem(row, 0, createItem); - auto dummyItem = new QTableWidgetItem; - dummyItem->setFlags(dummyItem->flags() & ~Qt::ItemIsEditable); - table->setItem(row, 1, dummyItem->clone()); - table->setItem(row, 2, dummyItem); - } - - if (!selectedShare()) { - if (table->rowCount() != 0) { - // Select the first share by default - table->selectRow(0); - } else { - // explicitly note the deselection, - // since this was not triggered on table clear above - slotShareSelectionChanged(); - } - } -} - -void ShareLinkWidget::slotShareSelectionChanged() -{ - // Disable running progress indicators - _pi_create->stopAnimation(); - _pi_editing->stopAnimation(); - _pi_date->stopAnimation(); - _pi_password->stopAnimation(); - - _ui->errorLabel->hide(); - - auto share = selectedShare(); - bool selectionUnchanged = false; - bool createNew = !share; - if (share) { - selectionUnchanged = _selectedShareId == share->getId(); - _selectedShareId = share->getId(); - } else { - selectionUnchanged = _selectedShareId.isEmpty(); - _selectedShareId.clear(); - } - - _ui->shareProperties->setEnabled(true); - - // Public upload state (files can only be read-only, box is hidden for them) - _ui->widget_editing->setEnabled(!_isFile); - if (!selectionUnchanged) { - if (share && share->getPublicUpload()) { - if (share->getShowFileListing()) { - _ui->radio_readWrite->setChecked(true); - } else { - _ui->radio_uploadOnly->setChecked(true); - } - } else { - _ui->radio_readOnly->setChecked(true); - } - } - - const auto capabilities = _account->capabilities(); - const bool passwordRequired = - (_ui->radio_readOnly->isChecked() && capabilities.sharePublicLinkEnforcePasswordForReadOnly()) - || (_ui->radio_readWrite->isChecked() && capabilities.sharePublicLinkEnforcePasswordForReadWrite()) - || (_ui->radio_uploadOnly->isChecked() && capabilities.sharePublicLinkEnforcePasswordForUploadOnly()); - - // Password state - _ui->pushButton_setPassword->setVisible(!createNew); - _ui->checkBox_password->setText(tr("P&assword protect")); - if (createNew) { - if (passwordRequired) { - _ui->checkBox_password->setChecked(true); - _ui->lineEdit_password->setPlaceholderText(tr("Please Set Password")); - _ui->lineEdit_password->setEnabled(true); - } else if (!selectionUnchanged || _ui->lineEdit_password->text().isEmpty()) { - // force to off if no password was entered yet - _ui->checkBox_password->setChecked(false); - _ui->lineEdit_password->setPlaceholderText(QString()); - _ui->lineEdit_password->setEnabled(false); - } - } else if (!selectionUnchanged) { - if (share && share->isPasswordSet()) { - _ui->checkBox_password->setChecked(true); - _ui->lineEdit_password->setPlaceholderText("********"); - _ui->lineEdit_password->setEnabled(true); - } else { - _ui->checkBox_password->setChecked(false); - _ui->lineEdit_password->setPlaceholderText(QString()); - _ui->lineEdit_password->setEnabled(false); - } - _ui->lineEdit_password->setText(QString()); - _ui->pushButton_setPassword->setEnabled(false); - } - // The following is done to work with old shares when the pw requirement - // is enabled on the server later: users can add pws to old shares. - _ui->checkBox_password->setEnabled(!_ui->checkBox_password->isChecked() || !passwordRequired); - - // Expiry state - _ui->calendar->setMinimumDate(QDate::currentDate().addDays(1)); - if (!selectionUnchanged) { - if (share && share->getExpireDate().isValid()) { - _ui->checkBox_expire->setChecked(true); - _ui->calendar->setDate(share->getExpireDate()); - _ui->calendar->setEnabled(true); - } else if (createNew) { - const QDate defaultExpire = capabilityDefaultExpireDate(); - if (defaultExpire.isValid()) - _ui->calendar->setDate(defaultExpire); - const bool enabled = _expiryRequired || defaultExpire.isValid(); - _ui->checkBox_expire->setChecked(enabled); - _ui->calendar->setEnabled(enabled); - } else { - _ui->checkBox_expire->setChecked(false); - _ui->calendar->setEnabled(false); - } - } - // Allows checking expiry on old shares created before it was required. - _ui->checkBox_expire->setEnabled(!_ui->checkBox_expire->isChecked() || !_expiryRequired); - - // Name and create button - _ui->create->setVisible(createNew); -} - -void ShareLinkWidget::setExpireDate(const QDate &date) -{ - if (auto current = selectedShare()) { - _pi_date->startAnimation(); - _ui->errorLabel->hide(); - current->setExpireDate(date); - } -} - -void ShareLinkWidget::slotExpireSet() -{ - if (sender() == selectedShare().data()) { - slotShareSelectionChanged(); - } -} - -void ShareLinkWidget::slotExpireDateChanged(const QDate &date) -{ - if (_ui->checkBox_expire->isChecked()) { - setExpireDate(date); - } -} - -void ShareLinkWidget::slotPasswordReturnPressed() -{ - if (selectedShare()) { - setPassword(_ui->lineEdit_password->text()); - _ui->lineEdit_password->clearFocus(); - } -} - -void ShareLinkWidget::slotPasswordChanged(const QString &newText) -{ - // disable the set-password button - _ui->pushButton_setPassword->setEnabled(newText.length() > 0); -} - -void ShareLinkWidget::slotNameEdited(QTableWidgetItem *item) -{ - if (!_namesSupported) { - return; - } - - QString newName = item->text(); - auto share = item->data(Qt::UserRole).value>(); - if (share && newName != share->getName() && newName != share->getToken()) { - share->setName(newName); - - // the server doesn't necessarily apply the desired name but may assign a different one - // for instance, when the user removes a custom name by emptying the field, the server assigns the ID as name - // therefore, we need to fetch the name(s) from the server again to display accurate information - getShares(); - } -} - -void ShareLinkWidget::setPassword(const QString &password) -{ - if (auto current = selectedShare()) { - _pi_password->startAnimation(); - _ui->errorLabel->hide(); - - _ui->checkBox_password->setEnabled(false); - _ui->lineEdit_password->setEnabled(false); - - current->setPassword(password); - } -} - -void ShareLinkWidget::slotPasswordSet() -{ - auto share = selectedShare(); - if (sender() != share.data()) - return; - - _pi_password->stopAnimation(); - _ui->checkBox_password->setEnabled(true); - _ui->lineEdit_password->setText(QString()); - if (share->isPasswordSet()) { - _ui->lineEdit_password->setPlaceholderText("********"); - _ui->lineEdit_password->setEnabled(true); - } else { - _ui->lineEdit_password->setPlaceholderText(QString()); - _ui->lineEdit_password->setEnabled(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::slotShareNameEntered() -{ - if (!_manager) { - return; - } - _pi_create->startAnimation(); - _manager->createLinkShare( - _sharePath, - _ui->nameLineEdit->text(), - _ui->checkBox_password->isChecked() ? _ui->lineEdit_password->text() : QString(), - _ui->checkBox_expire->isChecked() ? _ui->calendar->date() : QDate(), - uiPermissionState()); -} - -void ShareLinkWidget::slotDeleteShareFetched() -{ - getShares(); -} - -void ShareLinkWidget::slotCreateShareFetched(const QSharedPointer &share) -{ - _pi_create->stopAnimation(); - _pi_password->stopAnimation(); - _ui->nameLineEdit->clear(); - - _newShareOverrideSelectionId = share->getId(); - getShares(); -} - -void ShareLinkWidget::slotCreateShareForbidden(const QString &message) -{ - // Show the message, so users can adjust and try again - _pi_create->stopAnimation(); - if (!message.isEmpty()) { - _ui->errorLabel->setText(message); - _ui->errorLabel->show(); - } -} - -void ShareLinkWidget::slotCheckBoxPasswordClicked() -{ - if (_ui->checkBox_password->checkState() == Qt::Checked) { - _ui->lineEdit_password->setEnabled(true); - _ui->pushButton_setPassword->setEnabled(true); - _ui->lineEdit_password->setPlaceholderText(tr("Please Set Password")); - _ui->lineEdit_password->setFocus(); - } else { - setPassword(QString()); - _ui->lineEdit_password->setPlaceholderText(QString()); - _pi_password->startAnimation(); - _ui->lineEdit_password->setEnabled(false); - _ui->pushButton_setPassword->setEnabled(false); - } -} - -void ShareLinkWidget::slotCheckBoxExpireClicked() -{ - if (_ui->checkBox_expire->checkState() == Qt::Checked) { - const QDate tomorrow = QDate::currentDate().addDays(1); - QDate defaultDate = capabilityDefaultExpireDate(); - if (!defaultDate.isValid()) - defaultDate = tomorrow; - setExpireDate(defaultDate); - _ui->calendar->setDate(defaultDate); - _ui->calendar->setMinimumDate(tomorrow); - _ui->calendar->setEnabled(true); - } else { - setExpireDate(QDate()); - _ui->calendar->setEnabled(false); - } -} - -void ShareLinkWidget::emailShareLink(const QUrl &url) -{ - QString fileName = _sharePath.mid(_sharePath.lastIndexOf('/') + 1); - Utility::openEmailComposer( - tr("I shared %1 with you").arg(fileName), - url.toString(), - this); -} - -void ShareLinkWidget::openShareLink(const QUrl &url) -{ - Utility::openBrowser(url, this); -} - -void ShareLinkWidget::confirmAndDeleteShare(const QSharedPointer &share) -{ - auto messageBox = new QMessageBox( - QMessageBox::Question, - tr("Confirm Link Share Deletion"), - tr("

    Do you really want to delete the public link share %1?

    " - "

    Note: This action cannot be undone.

    ") - .arg(shareName(*share)), - QMessageBox::NoButton, - this); - QPushButton *yesButton = - messageBox->addButton(tr("Delete"), QMessageBox::YesRole); - messageBox->addButton(tr("Cancel"), QMessageBox::NoRole); - - connect(messageBox, &QMessageBox::finished, this, - [messageBox, yesButton, share]() { - if (messageBox->clickedButton() == yesButton) - share->deleteShare(); - }); - messageBox->open(); -} - -QString ShareLinkWidget::shareName(const LinkShare &share) const -{ - QString name = share.getName(); - if (!name.isEmpty()) - return name; - if (!_namesSupported) - return tr("Public link"); - return share.getToken(); -} - -void ShareLinkWidget::slotContextMenuButtonClicked() -{ - auto share = sender()->property(propertyShareC).value>(); - bool downloadEnabled = share->getShowFileListing(); - _copyDirectLinkAction->setVisible(downloadEnabled); - _emailDirectLinkAction->setVisible(downloadEnabled); - - _linkContextMenu->setProperty(propertyShareC, QVariant::fromValue(share)); - _linkContextMenu->exec(QCursor::pos()); -} - -void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action) -{ - auto share = sender()->property(propertyShareC).value>(); - - if (action == _deleteLinkAction) { - confirmAndDeleteShare(share); - } else if (action == _copyLinkAction) { - QApplication::clipboard()->setText(share->getLink().toString()); - } else if (action == _copyDirectLinkAction) { - QApplication::clipboard()->setText(share->getDirectDownloadLink().toString()); - } else if (action == _emailLinkAction) { - emailShareLink(share->getLink()); - } else if (action == _emailDirectLinkAction) { - emailShareLink(share->getDirectDownloadLink()); - } else if (action == _openLinkAction) { - openShareLink(share->getLink()); - } -} - -void ShareLinkWidget::slotDeleteShareClicked() -{ - auto share = sender()->property(propertyShareC).value>(); - confirmAndDeleteShare(share); -} - -SharePermissions ShareLinkWidget::uiPermissionState() const -{ - if (_ui->radio_readWrite->isChecked()) { - return SharePermissionRead | SharePermissionCreate - | SharePermissionUpdate | SharePermissionDelete; - } else if (_ui->radio_uploadOnly->isChecked()) { - return SharePermissionCreate; - } - return SharePermissionRead; -} - -void ShareLinkWidget::slotPermissionsClicked() -{ - if (auto current = selectedShare()) { - _ui->widget_editing->setEnabled(false); - _pi_editing->startAnimation(); - _ui->errorLabel->hide(); - - current->setPermissions(uiPermissionState()); - } else { - // Password may now be required, update ui. - slotShareSelectionChanged(); - } -} - -QSharedPointer ShareLinkWidget::selectedShare() const -{ - const auto items = _ui->linkShares->selectedItems(); - if (items.isEmpty()) { - return QSharedPointer(); - } - - return items.first()->data(Qt::UserRole).value>(); -} - -QDate ShareLinkWidget::capabilityDefaultExpireDate() const -{ - if (!_account->capabilities().sharePublicLinkDefaultExpire()) - return QDate(); - return QDate::currentDate().addDays( - _account->capabilities().sharePublicLinkDefaultExpireDateDays()); -} - -void ShareLinkWidget::slotPermissionsSet() -{ - if (sender() == selectedShare().data()) { - slotShareSelectionChanged(); - } -} - -void ShareLinkWidget::slotServerError(int code, const QString &message) -{ - _pi_create->stopAnimation(); - _pi_date->stopAnimation(); - _pi_password->stopAnimation(); - _pi_editing->stopAnimation(); - - // Reset UI state - _selectedShareId.clear(); - slotShareSelectionChanged(); - - qCWarning(lcSharing) << "Error from server" << code << message; - displayError(message); -} - -void ShareLinkWidget::slotPasswordSetError(int code, const QString &message) -{ - slotServerError(code, message); - - _ui->lineEdit_password->setEnabled(true); - _ui->lineEdit_password->setFocus(); -} - -void ShareLinkWidget::displayError(const QString &errMsg) -{ - _ui->errorLabel->setText(errMsg); - _ui->errorLabel->show(); -} -} diff --git a/src/gui/sharelinkwidget.h b/src/gui/sharelinkwidget.h deleted file mode 100644 index 7364fba24f8..00000000000 --- a/src/gui/sharelinkwidget.h +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * Copyright (C) 2015 by Klaas Freitag - * - * 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. - */ - -#ifndef SHARELINKWIDGET_H -#define SHARELINKWIDGET_H - -#include "accountfwd.h" -#include "sharepermissions.h" -#include "QProgressIndicator.h" -#include "sharepermissions.h" -#include -#include -#include - -class QMenu; -class QTableWidgetItem; - -namespace OCC { - -namespace Ui { - class ShareLinkWidget; -} - -class AbstractCredentials; -class QuotaInfo; -class SyncResult; -class LinkShare; -class Share; -class ShareManager; - -/** - * @brief The ShareDialog class - * @ingroup gui - */ -class ShareLinkWidget : public QWidget -{ - Q_OBJECT - -public: - explicit ShareLinkWidget(AccountPtr account, - const QString &sharePath, - const QString &localPath, - SharePermissions maxSharingPermissions, - QWidget *parent = nullptr); - ~ShareLinkWidget() override; - void getShares(); - -private slots: - void slotSharesFetched(const QList> &shares); - void slotShareSelectionChanged(); - - void slotShareNameEntered(); - void slotDeleteShareClicked(); - void slotCheckBoxPasswordClicked(); - void slotCheckBoxExpireClicked(); - void slotPasswordReturnPressed(); - void slotPermissionsClicked(); - void slotExpireDateChanged(const QDate &date); - void slotPasswordChanged(const QString &newText); - void slotNameEdited(QTableWidgetItem *item); - - void slotContextMenuButtonClicked(); - void slotLinkContextMenuActionTriggered(QAction *action); - - void slotDeleteShareFetched(); - void slotCreateShareFetched(const QSharedPointer &share); - void slotCreateShareForbidden(const QString &message); - void slotPasswordSet(); - void slotExpireSet(); - void slotPermissionsSet(); - - void slotServerError(int code, const QString &message); - void slotPasswordSetError(int code, const QString &message); - -private: - void displayError(const QString &errMsg); - - void setPassword(const QString &password); - void setExpireDate(const QDate &date); - - void copyShareLink(const QUrl &url); - void emailShareLink(const QUrl &url); - void openShareLink(const QUrl &url); - - /** Confirm with the user and then delete the share */ - void confirmAndDeleteShare(const QSharedPointer &share); - - /** Retrieve a share's name, accounting for _namesSupported */ - QString shareName(const LinkShare &share) const; - - /** Permission implied by current ui state */ - SharePermissions uiPermissionState() const; - - /** - * Retrieve the selected share, returning 0 if none. - * - * Returning 0 means that the "Create new..." item is selected. - */ - QSharedPointer selectedShare() const; - - /** Returns the default expire date as requested by the server, invalid otherwise */ - QDate capabilityDefaultExpireDate() const; - - Ui::ShareLinkWidget *_ui; - AccountPtr _account; - QString _sharePath; - QString _localPath; - QString _shareUrl; - - QProgressIndicator *_pi_create; - QProgressIndicator *_pi_password; - QProgressIndicator *_pi_date; - QProgressIndicator *_pi_editing; - - ShareManager *_manager; - - bool _isFile; - bool _expiryRequired; - bool _namesSupported; - - // For maintaining the selection and temporary ui state - // when getShares() finishes, but the selection didn't - // change. - QString _selectedShareId; - - // When a new share is created, we want to select it - // the next time getShares() finishes. This stores its id. - QString _newShareOverrideSelectionId; - - QMenu *_linkContextMenu = nullptr; - QAction *_deleteLinkAction = nullptr; - QAction *_openLinkAction = nullptr; - QAction *_copyLinkAction = nullptr; - QAction *_copyDirectLinkAction = nullptr; - QAction *_emailLinkAction = nullptr; - QAction *_emailDirectLinkAction = nullptr; -}; -} - -#endif // SHARELINKWIDGET_H diff --git a/src/gui/sharelinkwidget.ui b/src/gui/sharelinkwidget.ui deleted file mode 100644 index 6b31dc0deaf..00000000000 --- a/src/gui/sharelinkwidget.ui +++ /dev/null @@ -1,358 +0,0 @@ - - - OCC::ShareLinkWidget - - - - 0 - 0 - 441 - 568 - - - - Share NewDocument.odt - - - - - - Anyone with the link has access to the file/folder - - - Qt::PlainText - - - true - - - - - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOff - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - 3 - - - false - - - false - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - Download / View - - - - - - - - - - 0 - 0 - - - - Recipients can view or download contents. - - - true - - - - - - - Download / View / Edit - - - - - - - - 0 - 0 - - - - Recipients can view, download, edit, delete and upload contents. - - - true - - - - - - - Upload only (File Drop) - - - - - - - - 0 - 0 - - - - Receive files from others without revealing the contents of the folder. - - - true - - - - - - - - - - - - - 0 - 0 - - - - Set password - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 20 - - - - - QLineEdit::Password - - - - - - - - 0 - 0 - - - - Set &password - - - - - - - - - 0 - - - - - Set &expiration date - - - - - - - true - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Name: - - - - - - - Enter name of new share... - - - - - - - &Create - - - - - - - - - - - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 123 - 121 - 134 - - - - - - - - TextLabel - - - Qt::PlainText - - - - - - - - nameLineEdit - createShareButton - linkShares - checkBox_password - lineEdit_password - pushButton_setPassword - checkBox_expire - calendar - - - - diff --git a/src/gui/sharemanager.cpp b/src/gui/sharemanager.cpp deleted file mode 100644 index 126bec9f948..00000000000 --- a/src/gui/sharemanager.cpp +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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 "sharemanager.h" -#include "ocssharejob.h" -#include "account.h" -#include "folderman.h" -#include "accountstate.h" - -#include -#include -#include -#include - -namespace OCC { - -/** - * When a share is modified, we need to tell the folders so they can adjust overlay icons - */ -static void updateFolder(const AccountPtr &account, const QString &path) -{ - for (auto *f : FolderMan::instance()->map()) { - if (f->accountState()->account() != account) - continue; - auto folderPath = f->remotePath(); - if (path.startsWith(folderPath) && (path == folderPath || folderPath.endsWith('/') || path[folderPath.size()] == '/')) { - // Workaround the fact that the server does not invalidate the etags of parent directories - // when something is shared. - auto relative = path.midRef(f->remotePathTrailingSlash().length()); - f->journalDb()->schedulePathForRemoteDiscovery(relative.toString()); - - // Schedule a sync so it can update the remote permission flag and let the socket API - // know about the shared icon. - f->scheduleThisFolderSoon(); - } - } -} - - -Share::Share(AccountPtr account, - const QString &id, - const QString &path, - const ShareType shareType, - const Permissions permissions, - const QSharedPointer shareWith) - : _account(account) - , _id(id) - , _path(path) - , _shareType(shareType) - , _permissions(permissions) - , _shareWith(shareWith) -{ -} - -AccountPtr Share::account() const -{ - return _account; -} - -QString Share::path() const -{ - return _path; -} - -QString Share::getId() const -{ - return _id; -} - -Share::ShareType Share::getShareType() const -{ - return _shareType; -} - -QSharedPointer Share::getShareWith() const -{ - return _shareWith; -} - -void Share::setPermissions(Permissions permissions) -{ - OcsShareJob *job = new OcsShareJob(_account); - connect(job, &OcsShareJob::shareJobFinished, this, &Share::slotPermissionsSet); - connect(job, &OcsJob::ocsError, this, &Share::slotOcsError); - job->setPermissions(getId(), permissions); -} - -void Share::slotPermissionsSet(const QJsonDocument &, const QVariant &value) -{ - _permissions = (Permissions)value.toInt(); - emit permissionsSet(); -} - -Share::Permissions Share::getPermissions() const -{ - return _permissions; -} - -void Share::deleteShare() -{ - OcsShareJob *job = new OcsShareJob(_account); - connect(job, &OcsShareJob::shareJobFinished, this, &Share::slotDeleted); - connect(job, &OcsJob::ocsError, this, &Share::slotOcsError); - job->deleteShare(getId()); -} - -void Share::slotDeleted() -{ - emit shareDeleted(); - - updateFolder(_account, _path); -} - -void Share::slotOcsError(int statusCode, const QString &message) -{ - emit serverError(statusCode, message); -} - -QUrl LinkShare::getLink() const -{ - return _url; -} - -QUrl LinkShare::getDirectDownloadLink() const -{ - QUrl url = _url; - url.setPath(url.path() + "/download"); - return url; -} - -QDate LinkShare::getExpireDate() const -{ - return _expireDate; -} - -bool LinkShare::isPasswordSet() const -{ - return _passwordSet; -} - -LinkShare::LinkShare(AccountPtr account, - const QString &id, - const QString &path, - const QString &name, - const QString &token, - Permissions permissions, - bool passwordSet, - const QUrl &url, - const QDate &expireDate) - : Share(account, id, path, Share::TypeLink, permissions) - , _name(name) - , _token(token) - , _passwordSet(passwordSet) - , _expireDate(expireDate) - , _url(url) -{ -} - -bool LinkShare::getPublicUpload() const -{ - return _permissions & SharePermissionCreate; -} - -bool LinkShare::getShowFileListing() const -{ - return _permissions & SharePermissionRead; -} - -QString LinkShare::getName() const -{ - return _name; -} - -void LinkShare::setName(const QString &name) -{ - OcsShareJob *job = new OcsShareJob(_account); - connect(job, &OcsShareJob::shareJobFinished, this, &LinkShare::slotNameSet); - connect(job, &OcsJob::ocsError, this, &LinkShare::slotOcsError); - job->setName(getId(), name); -} - -QString LinkShare::getToken() const -{ - return _token; -} - -void LinkShare::setPassword(const QString &password) -{ - OcsShareJob *job = new OcsShareJob(_account); - connect(job, &OcsShareJob::shareJobFinished, this, &LinkShare::slotPasswordSet); - connect(job, &OcsJob::ocsError, this, &LinkShare::slotSetPasswordError); - job->setPassword(getId(), password); -} - -void LinkShare::slotPasswordSet(const QJsonDocument &, const QVariant &value) -{ - _passwordSet = value.toString() != ""; - emit passwordSet(); -} - -void LinkShare::setExpireDate(const QDate &date) -{ - OcsShareJob *job = new OcsShareJob(_account); - connect(job, &OcsShareJob::shareJobFinished, this, &LinkShare::slotExpireDateSet); - connect(job, &OcsJob::ocsError, this, &LinkShare::slotOcsError); - job->setExpireDate(getId(), date); -} - -void LinkShare::slotExpireDateSet(const QJsonDocument &reply, const QVariant &value) -{ - auto data = reply.object().value("ocs").toObject().value("data").toObject(); - - /* - * If the reply provides a data back (more REST style) - * they use this date. - */ - if (data.value("expiration").isString()) { - _expireDate = QDate::fromString(data.value("expiration").toString(), "yyyy-MM-dd 00:00:00"); - } else { - _expireDate = value.toDate(); - } - emit expireDateSet(); -} - -void LinkShare::slotSetPasswordError(int statusCode, const QString &message) -{ - emit passwordSetError(statusCode, message); -} - -void LinkShare::slotNameSet(const QJsonDocument &, const QVariant &value) -{ - _name = value.toString(); - emit nameSet(); -} - -ShareManager::ShareManager(AccountPtr account, QObject *parent) - : QObject(parent) - , _account(account) -{ -} - -void ShareManager::createLinkShare(const QString &path, - const QString &name, - const QString &password, - const QDate &expireDate, - const Share::Permissions permissions) -{ - OcsShareJob *job = new OcsShareJob(_account); - connect(job, &OcsShareJob::shareJobFinished, this, &ShareManager::slotLinkShareCreated); - connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError); - job->createLinkShare(path, name, password, expireDate, permissions); -} - -void ShareManager::slotLinkShareCreated(const QJsonDocument &reply) -{ - QString message; - int code = OcsShareJob::getJsonReturnCode(reply, message); - - // A 403 generally means some of the settings for the share are not allowed. - // Maybe a password is required, or the expire date isn't acceptable. - if (code == 403) { - emit linkShareCreationForbidden(message); - return; - } - - //Parse share - auto data = reply.object().value("ocs").toObject().value("data").toObject(); - QSharedPointer share(parseLinkShare(data)); - - emit linkShareCreated(share); - - updateFolder(_account, share->path()); -} - - -void ShareManager::createShare(const QString &path, - const Share::ShareType shareType, - const QString shareWith, - const Share::Permissions desiredPermissions) -{ - auto job = new OcsShareJob(_account); - connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError); - connect(job, &OcsShareJob::shareJobFinished, this, - [=](const QJsonDocument &reply) { - // Note: The following code attempts to determine if the item was shared with - // the user and what the permissions were. It doesn't do a good job at it since - // the == path comparison will mean it doesn't work for subitems of shared - // folders. Also, it's nicer if the calling code determines the share-permissions - // (see maxSharingPermissions) via a PropFind and passes in valid permissions. - // Remove this code for >= 2.7.0. - - // Find existing share permissions (if this was shared with us) - Share::Permissions existingPermissions = SharePermissionDefault; - const auto &array = reply.object()[QLatin1String("ocs")].toObject()[QLatin1String("data")].toArray(); - for (const auto &element : array) { - auto map = element.toObject(); - if (map["file_target"] == path) - existingPermissions = Share::Permissions(map["permissions"].toInt()); - } - - // Limit the permissions we request for a share to the ones the item - // was shared with initially. - auto validPermissions = desiredPermissions; - if (validPermissions == SharePermissionDefault) { - validPermissions = existingPermissions; - } - if (existingPermissions != SharePermissionDefault) { - validPermissions &= existingPermissions; - } - - OcsShareJob *job = new OcsShareJob(_account); - connect(job, &OcsShareJob::shareJobFinished, this, &ShareManager::slotShareCreated); - connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError); - job->createShare(path, shareType, shareWith, validPermissions); - }); - job->getSharedWithMe(); -} - - -void ShareManager::slotShareCreated(const QJsonDocument &reply) -{ - //Parse share - auto data = reply.object().value("ocs").toObject().value("data").toObject(); - QSharedPointer share(parseShare(data)); - - emit shareCreated(share); - - updateFolder(_account, share->path()); -} - -void ShareManager::fetchShares(const QString &path) -{ - OcsShareJob *job = new OcsShareJob(_account); - connect(job, &OcsShareJob::shareJobFinished, this, &ShareManager::slotSharesFetched); - connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError); - job->getShares(path); -} - -void ShareManager::slotSharesFetched(const QJsonDocument &reply) -{ - const auto &tmpShares = reply.object().value(QLatin1String("ocs")).toObject().value(QLatin1String("data")).toArray(); - const QString versionString = _account->serverVersion(); - qCDebug(lcSharing) << versionString << "Fetched" << tmpShares.count() << "shares"; - - QList> shares; - - for (const auto &share : tmpShares) { - auto data = share.toObject(); - - auto shareType = data.value("share_type").toInt(); - - QSharedPointer newShare; - - if (shareType == Share::TypeLink) { - newShare = parseLinkShare(data); - } else { - newShare = parseShare(data); - } - - shares.append(QSharedPointer(newShare)); - } - - qCDebug(lcSharing) << "Sending " << shares.count() << "shares"; - emit sharesFetched(shares); -} - -QSharedPointer ShareManager::parseLinkShare(const QJsonObject &data) -{ - QUrl url; - - // From ownCloud server 8.2 the url field is always set for public shares - if (data.contains("url")) { - url = QUrl(data.value("url").toString()); - } else if (_account->serverVersionInt() >= Account::makeServerVersion(8, 0, 0)) { - // From ownCloud server version 8 on, a different share link scheme is used. - url = QUrl(Utility::concatUrlPath(_account->url(), QLatin1String("index.php/s/") + data.value("token").toString())).toString(); - } else { - QUrlQuery queryArgs; - queryArgs.addQueryItem(QLatin1String("service"), QLatin1String("files")); - queryArgs.addQueryItem(QLatin1String("t"), data.value("token").toString()); - url = QUrl(Utility::concatUrlPath(_account->url(), QLatin1String("public.php"), queryArgs).toString()); - } - - QDate expireDate; - if (data.value("expiration").isString()) { - expireDate = QDate::fromString(data.value("expiration").toString(), "yyyy-MM-dd 00:00:00"); - } - - return QSharedPointer(new LinkShare(_account, - data.value("id").toVariant().toString(), // "id" used to be an integer, support both - data.value("path").toString(), - data.value("name").toString(), - data.value("token").toString(), - (Share::Permissions)data.value("permissions").toInt(), - data.value("share_with").isString(), // has password? - url, - expireDate)); -} - -QSharedPointer ShareManager::parseShare(const QJsonObject &data) -{ - QSharedPointer sharee(new Sharee(data.value("share_with").toString(), - data.value("share_with_displayname").toString(), - (Sharee::Type)data.value("share_type").toInt())); - - return QSharedPointer(new Share(_account, - data.value("id").toVariant().toString(), // "id" used to be an integer, support both - data.value("path").toString(), - (Share::ShareType)data.value("share_type").toInt(), - (Share::Permissions)data.value("permissions").toInt(), - sharee)); -} - -void ShareManager::slotOcsError(int statusCode, const QString &message) -{ - emit serverError(statusCode, message); -} -} diff --git a/src/gui/sharemanager.h b/src/gui/sharemanager.h deleted file mode 100644 index 213d8e3eeec..00000000000 --- a/src/gui/sharemanager.h +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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. - */ - -#ifndef SHAREMANAGER_H -#define SHAREMANAGER_H - -#include "accountfwd.h" -#include "sharee.h" -#include "sharepermissions.h" - -#include -#include -#include -#include -#include -#include - -class QJsonDocument; -class QJsonObject; - -namespace OCC { - -class Share : public QObject -{ - Q_OBJECT - -public: - /** - * Possible share types - * Need to be in sync with Sharee::Type - */ - enum ShareType { - TypeUser = Sharee::User, - TypeGroup = Sharee::Group, - TypeLink = 3, - TypeRemote = Sharee::Federated - }; - - typedef SharePermissions Permissions; - - /* - * Constructor for shares - */ - explicit Share(AccountPtr account, - const QString &id, - const QString &path, - const ShareType shareType, - const Permissions permissions = SharePermissionDefault, - const QSharedPointer shareWith = QSharedPointer(nullptr)); - - /** - * The account the share is defined on. - */ - AccountPtr account() const; - - QString path() const; - - /* - * Get the id - */ - QString getId() const; - - /* - * Get the shareType - */ - ShareType getShareType() const; - - /* - * Get the shareWith - */ - QSharedPointer getShareWith() const; - - /* - * Get permissions - */ - Permissions getPermissions() const; - - /* - * Set the permissions of a share - * - * On success the permissionsSet signal is emitted - * In case of a server error the serverError signal is emitted. - */ - void setPermissions(Permissions permissions); - - /** - * Deletes a share - * - * On success the shareDeleted signal is emitted - * In case of a server error the serverError signal is emitted. - */ - void deleteShare(); - -signals: - void permissionsSet(); - void shareDeleted(); - void serverError(int code, const QString &message); - -protected: - AccountPtr _account; - QString _id; - QString _path; - ShareType _shareType; - Permissions _permissions; - QSharedPointer _shareWith; - -protected slots: - void slotOcsError(int statusCode, const QString &message); - -private slots: - void slotDeleted(); - void slotPermissionsSet(const QJsonDocument &, const QVariant &value); -}; - -/** - * A Link share is just like a regular share but then slightly different. - * There are several methods in the API that either work differently for - * link shares or are only available to link shares. - */ -class LinkShare : public Share -{ - Q_OBJECT -public: - explicit LinkShare(AccountPtr account, - const QString &id, - const QString &path, - const QString &name, - const QString &token, - const Permissions permissions, - bool passwordSet, - const QUrl &url, - const QDate &expireDate); - - /* - * Get the share link - */ - QUrl getLink() const; - - /* - * The share's link for direct downloading. - */ - QUrl getDirectDownloadLink() const; - - /* - * Get the publicUpload status of this share - */ - bool getPublicUpload() const; - - /* - * Whether directory listings are available (READ permission) - */ - bool getShowFileListing() const; - - /* - * Returns the name of the link share. Can be empty. - */ - QString getName() const; - - /* - * Set the name of the link share. - * - * Emits either nameSet() or serverError(). - */ - void setName(const QString &name); - - /* - * Returns the token of the link share. - */ - QString getToken() const; - - /* - * Set the password - * - * On success the passwordSet signal is emitted - * In case of a server error the serverError signal is emitted. - */ - void setPassword(const QString &password); - - /* - * Is the password set? - */ - bool isPasswordSet() const; - - /* - * Get the expiration date - */ - QDate getExpireDate() const; - - /* - * Set the expiration date - * - * On success the expireDateSet signal is emitted - * In case of a server error the serverError signal is emitted. - */ - void setExpireDate(const QDate &expireDate); - -signals: - void expireDateSet(); - void passwordSet(); - void passwordSetError(int statusCode, const QString &message); - void nameSet(); - -private slots: - void slotPasswordSet(const QJsonDocument &, const QVariant &value); - void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value); - void slotSetPasswordError(int statusCode, const QString &message); - void slotNameSet(const QJsonDocument &, const QVariant &value); - -private: - QString _name; - QString _token; - bool _passwordSet; - QDate _expireDate; - QUrl _url; -}; - -/** - * The share manager allows for creating, retrieving and deletion - * of shares. It abstracts away from the OCS Share API, all the usages - * shares should talk to this manager and not use OCS Share Job directly - */ -class ShareManager : public QObject -{ - Q_OBJECT -public: - explicit ShareManager(AccountPtr _account, QObject *parent = nullptr); - - /** - * Tell the manager to create a link share - * - * @param path The path of the linkshare relative to the user folder on the server - * @param name The name of the created share, may be empty - * @param password The password of the share, may be empty - * @param expireDate Expire date for share, may be null - * @param permissions Desired permissions, SharePermissionDefault lets server decide - * - * On success the signal linkShareCreated is emitted - * On 403 error the linkShareCreationForbidden error is emitted (if params forbid creation) - * In case of a server error the serverError signal is emitted - */ - void createLinkShare(const QString &path, - const QString &name, - const QString &password = QString(), - const QDate &expireDate = QDate(), - const Share::Permissions permissions = SharePermissionDefault); - - /** - * Tell the manager to create a new share - * - * @param path The path of the share relative to the user folder on the server - * @param shareType The type of share (TypeUser, TypeGroup, TypeRemote) - * @param Permissions The share permissions - * - * On success the signal shareCreated is emitted - * In case of a server error the serverError signal is emitted - */ - void createShare(const QString &path, - const Share::ShareType shareType, - const QString shareWith, - const Share::Permissions permissions); - - /** - * Fetch all the shares for path - * - * @param path The path to get the shares for relative to the users folder on the server - * - * On success the sharesFetched signal is emitted - * In case of a server error the serverError signal is emitted - */ - void fetchShares(const QString &path); - -signals: - void shareCreated(const QSharedPointer &share); - void linkShareCreated(const QSharedPointer &share); - void sharesFetched(const QList> &shares); - void serverError(int code, const QString &message); - - /** Emitted when creating a link share creation fails with 403. - * - * @param message the error message reported by the server - * - * See createLinkShare(). Usually for "needs password" but with - * server plugins it can also happen for other issues. - */ - void linkShareCreationForbidden(const QString &message); - -private slots: - void slotSharesFetched(const QJsonDocument &reply); - void slotLinkShareCreated(const QJsonDocument &reply); - void slotShareCreated(const QJsonDocument &reply); - void slotOcsError(int statusCode, const QString &message); -private: - QSharedPointer parseLinkShare(const QJsonObject &data); - QSharedPointer parseShare(const QJsonObject &data); - - AccountPtr _account; -}; -} - -#endif // SHAREMANAGER_H diff --git a/src/gui/sharepermissions.h b/src/gui/sharepermissions.h deleted file mode 100644 index 92ce957668d..00000000000 --- a/src/gui/sharepermissions.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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. - */ - -#ifndef SHAREPERMISSIONS_H -#define SHAREPERMISSIONS_H - -#include - -namespace OCC { - -/** - * Possible permissions, must match the server permission constants - */ -enum SharePermission { - SharePermissionRead = 1 << 0, - SharePermissionUpdate = 1 << 1, - SharePermissionCreate = 1 << 2, - SharePermissionDelete = 1 << 3, - SharePermissionShare = 1 << 4, - SharePermissionDefault = 1 << 30 -}; -Q_DECLARE_FLAGS(SharePermissions, SharePermission) -Q_DECLARE_OPERATORS_FOR_FLAGS(SharePermissions) - -} // namespace OCC - -#endif diff --git a/src/gui/shareusergroupwidget.cpp b/src/gui/shareusergroupwidget.cpp deleted file mode 100644 index cc1f2305202..00000000000 --- a/src/gui/shareusergroupwidget.cpp +++ /dev/null @@ -1,622 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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 "shareusergroupwidget.h" -#include "ui_shareusergroupwidget.h" -#include "ui_shareuserline.h" -#include "account.h" -#include "folderman.h" -#include "folder.h" -#include "accountmanager.h" -#include "theme.h" -#include "configfile.h" -#include "capabilities.h" -#include "guiutility.h" -#include "thumbnailjob.h" -#include "sharee.h" -#include "sharemanager.h" -#include "guiutility.h" - -#include "QProgressIndicator.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace OCC { - -ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, - const QString &sharePath, - const QString &localPath, - SharePermissions maxSharingPermissions, - const QString &privateLinkUrl, - QWidget *parent) - : QWidget(parent) - , _ui(new Ui::ShareUserGroupWidget) - , _account(account) - , _sharePath(sharePath) - , _localPath(localPath) - , _maxSharingPermissions(maxSharingPermissions) - , _privateLinkUrl(privateLinkUrl) - , _disableCompleterActivated(false) -{ - setAttribute(Qt::WA_DeleteOnClose); - setObjectName("SharingDialogUG"); // required as group for saveGeometry call - - _ui->setupUi(this); - - //Is this a file or folder? - _isFile = QFileInfo(localPath).isFile(); - - _completer = new QCompleter(this); - _completerModel = new ShareeModel(_account, - _isFile ? QLatin1String("file") : QLatin1String("folder"), - _completer); - connect(_completerModel, &ShareeModel::shareesReady, this, &ShareUserGroupWidget::slotShareesReady); - connect(_completerModel, &ShareeModel::displayErrorMessage, this, &ShareUserGroupWidget::displayError); - - _completer->setModel(_completerModel); - _completer->setCaseSensitivity(Qt::CaseInsensitive); - _completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion); - _ui->shareeLineEdit->setCompleter(_completer); - - _manager = new ShareManager(_account, this); - connect(_manager, &ShareManager::sharesFetched, this, &ShareUserGroupWidget::slotSharesFetched); - connect(_manager, &ShareManager::shareCreated, this, &ShareUserGroupWidget::getShares); - connect(_manager, &ShareManager::serverError, this, &ShareUserGroupWidget::displayError); - connect(_ui->shareeLineEdit, &QLineEdit::returnPressed, this, &ShareUserGroupWidget::slotLineEditReturn); - connect(_ui->privateLinkText, &QLabel::linkActivated, this, &ShareUserGroupWidget::slotPrivateLinkShare); - - _ui->privateLinkText->setText(tr("You can direct people to this shared file or folder %1 by giving them a private link") - .arg(QStringLiteral("").arg(_privateLinkUrl))); - - // By making the next two QueuedConnections we can override - // the strings the completer sets on the line edit. - connect(_completer, qOverload(&QCompleter::activated), this, &ShareUserGroupWidget::slotCompleterActivated, - Qt::QueuedConnection); - connect(_completer, qOverload(&QCompleter::highlighted), this, &ShareUserGroupWidget::slotCompleterHighlighted, - Qt::QueuedConnection); - - // Queued connection so this signal is recieved after textChanged - connect(_ui->shareeLineEdit, &QLineEdit::textEdited, - this, &ShareUserGroupWidget::slotLineEditTextEdited, Qt::QueuedConnection); - connect(&_completionTimer, &QTimer::timeout, this, &ShareUserGroupWidget::searchForSharees); - _completionTimer.setSingleShot(true); - _completionTimer.setInterval(600); - - setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding); - _ui->errorLabel->hide(); - - // Setup the sharee search progress indicator - _ui->shareeHorizontalLayout->addWidget(&_pi_sharee); -} - -ShareUserGroupWidget::~ShareUserGroupWidget() -{ - delete _ui; -} - -void ShareUserGroupWidget::on_shareeLineEdit_textChanged(const QString &) -{ - _completionTimer.stop(); -} - -void ShareUserGroupWidget::slotLineEditTextEdited(const QString &text) -{ - _disableCompleterActivated = false; - // First textChanged is called first and we stopped the timer when the text is changed, programatically or not - // Then we restart the timer here if the user touched a key - if (!text.isEmpty()) { - _completionTimer.start(); - } -} - -void ShareUserGroupWidget::slotLineEditReturn() -{ - _disableCompleterActivated = false; - // did the user type in one of the options? - const auto text = _ui->shareeLineEdit->text(); - for (int i = 0; i < _completerModel->rowCount(); ++i) { - const auto sharee = _completerModel->getSharee(i); - if (sharee->format() == text - || sharee->displayName() == text - || sharee->shareWith() == text) { - slotCompleterActivated(_completerModel->index(i)); - // make sure we do not send the same item twice (because return is called when we press - // return to activate an item inthe completer) - _disableCompleterActivated = true; - return; - } - } - - // nothing found? try to refresh completion - _completionTimer.start(); -} - - -void ShareUserGroupWidget::searchForSharees() -{ - _completionTimer.stop(); - _pi_sharee.startAnimation(); - ShareeModel::ShareeSet blacklist; - - // Add the current user to _sharees since we can't share with ourself - QSharedPointer currentUser(new Sharee(_account->credentials()->user(), "", Sharee::Type::User)); - blacklist << currentUser; - - const auto &shareUserLines = _ui->scrollArea->findChildren(); - for (auto *sw : shareUserLines) { - blacklist << sw->share()->getShareWith(); - } - _ui->errorLabel->hide(); - _completerModel->fetch(_ui->shareeLineEdit->text(), blacklist); -} - -void ShareUserGroupWidget::getShares() -{ - _manager->fetchShares(_sharePath); -} - -void ShareUserGroupWidget::slotSharesFetched(const QList> &shares) -{ - QScrollArea *scrollArea = _ui->scrollArea; - - auto newViewPort = new QWidget(scrollArea); - auto layout = new QVBoxLayout(newViewPort); - layout->setMargin(0); - layout->setSpacing(0); - - QSize minimumSize = newViewPort->sizeHint(); - int x = 0; - - for (const auto &share : shares) { - // We don't handle link shares - if (share->getShareType() == Share::TypeLink) { - continue; - } - - ShareUserLine *s = new ShareUserLine(share, _maxSharingPermissions, _isFile, _ui->scrollArea); - connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize); - connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares); - s->setBackgroundRole(layout->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase); - layout->addWidget(s); - - x++; - if (x <= 3) { - minimumSize = newViewPort->sizeHint(); - } else { - minimumSize.rwidth() = qMax(newViewPort->sizeHint().width(), minimumSize.width()); - } - } - if (layout->isEmpty()) { - layout->addWidget(new QLabel(tr("The item is not shared with any users or groups"))); - } else { - layout->addStretch(1); - } - - minimumSize.rwidth() += layout->spacing(); - minimumSize.rheight() += layout->spacing(); - scrollArea->setMinimumSize(minimumSize); - scrollArea->setWidget(newViewPort); - - _disableCompleterActivated = false; - _ui->shareeLineEdit->setEnabled(true); -} - -void ShareUserGroupWidget::slotAdjustScrollWidgetSize() -{ - QScrollArea *scrollArea = _ui->scrollArea; - if (scrollArea->findChildren().count() <= 3) { - auto minimumSize = scrollArea->widget()->sizeHint(); - auto spacing = scrollArea->widget()->layout()->spacing(); - minimumSize.rwidth() += spacing; - minimumSize.rheight() += spacing; - scrollArea->setMinimumSize(minimumSize); - } -} - -void ShareUserGroupWidget::slotPrivateLinkShare() -{ - auto menu = new QMenu(this); - menu->setAttribute(Qt::WA_DeleteOnClose); - - menu->addAction(tr("Open link in browser"), - this, &ShareUserGroupWidget::slotPrivateLinkOpenBrowser); - menu->addAction(tr("Copy link to clipboard"), - this, &ShareUserGroupWidget::slotPrivateLinkCopy); - menu->addAction(tr("Send link by email"), - this, &ShareUserGroupWidget::slotPrivateLinkEmail); - - menu->exec(QCursor::pos()); -} - -void ShareUserGroupWidget::slotShareesReady() -{ - _pi_sharee.stopAnimation(); - if (_completerModel->rowCount() == 0) { - displayError(0, tr("No results for '%1'").arg(_completerModel->currentSearch())); - return; - } - _completer->complete(); -} - -void ShareUserGroupWidget::slotCompleterActivated(const QModelIndex &index) -{ - if (_disableCompleterActivated) - return; - // The index is an index from the QCompletion model which is itelf a proxy - // model proxying the _completerModel - auto sharee = qvariant_cast>(index.data(Qt::UserRole)); - if (sharee.isNull()) { - return; - } - - /* - * Add spinner to the bottom of the widget list - */ - auto viewPort = _ui->scrollArea->widget(); - auto layout = qobject_cast(viewPort->layout()); - auto indicator = new QProgressIndicator(viewPort); - indicator->startAnimation(); - if (layout->count() == 1) { - // No shares yet! Remove the label, add some stretch. - delete layout->itemAt(0)->widget(); - layout->addStretch(1); - } - layout->insertWidget(layout->count() - 1, indicator); - - /* - * Don't send the reshare permissions for federated shares for servers <9.1 - * https://github.com/owncloud/core/issues/22122#issuecomment-185637344 - * https://github.com/owncloud/client/issues/4996 - */ - const SharePermissions defaultPermissions = static_cast(_account->capabilities().defaultPermissions()); - if (sharee->type() == Sharee::Federated - && _account->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) { - SharePermissions permissions = SharePermissionRead | SharePermissionUpdate; - if (!_isFile) { - permissions |= SharePermissionCreate | SharePermissionDelete; - } - permissions &= defaultPermissions; - _manager->createShare(_sharePath, Share::ShareType(sharee->type()), - sharee->shareWith(), permissions); - } else { - _manager->createShare(_sharePath, Share::ShareType(sharee->type()), - sharee->shareWith(), _maxSharingPermissions & defaultPermissions); - } - - _ui->shareeLineEdit->setEnabled(false); - _ui->shareeLineEdit->setText(QString()); -} - -void ShareUserGroupWidget::slotCompleterHighlighted(const QModelIndex &index) -{ - // By default the completer would set the text to EditRole, - // override that here. - _ui->shareeLineEdit->setText(index.data(Qt::DisplayRole).toString()); -} - -void ShareUserGroupWidget::displayError(int code, const QString &message) -{ - _pi_sharee.stopAnimation(); - - // Also remove the spinner in the widget list, if any - qDeleteAll(_ui->scrollArea->findChildren()); - - qCWarning(lcSharing) << "Sharing error from server" << code << message; - _ui->errorLabel->setText(message); - _ui->errorLabel->show(); - _ui->shareeLineEdit->setEnabled(true); -} - -void ShareUserGroupWidget::slotPrivateLinkOpenBrowser() -{ - Utility::openBrowser(_privateLinkUrl, this); -} - -void ShareUserGroupWidget::slotPrivateLinkCopy() -{ - QApplication::clipboard()->setText(_privateLinkUrl); -} - -void ShareUserGroupWidget::slotPrivateLinkEmail() -{ - Utility::openEmailComposer( - tr("I shared something with you"), - _privateLinkUrl, - this); -} - -ShareUserLine::ShareUserLine(QSharedPointer share, - SharePermissions maxSharingPermissions, - bool isFile, - QWidget *parent) - : QWidget(parent) - , _ui(new Ui::ShareUserLine) - , _share(share) - , _isFile(isFile) -{ - _ui->setupUi(this); - - _ui->sharedWith->setText(share->getShareWith()->format()); - - // Create detailed permissions menu - QMenu *menu = new QMenu(this); - _permissionCreate = new QAction(tr("create"), this); - _permissionCreate->setCheckable(true); - _permissionCreate->setEnabled(maxSharingPermissions & SharePermissionCreate); - _permissionUpdate = new QAction(tr("change"), this); - _permissionUpdate->setCheckable(true); - _permissionUpdate->setEnabled(maxSharingPermissions & SharePermissionUpdate); - _permissionDelete = new QAction(tr("delete"), this); - _permissionDelete->setCheckable(true); - _permissionDelete->setEnabled(maxSharingPermissions & SharePermissionDelete); - - menu->addAction(_permissionUpdate); - /* - * Files can't have create or delete permissions - */ - if (!_isFile) { - menu->addAction(_permissionCreate); - menu->addAction(_permissionDelete); - } - _ui->permissionToolButton->setMenu(menu); - _ui->permissionToolButton->setPopupMode(QToolButton::InstantPopup); - _ui->permissionToolButton->setIcon(Utility::getCoreIcon(QStringLiteral("more"))); - - // If there's only a single entry in the detailed permission menu, hide it - if (menu->actions().size() == 1) { - _ui->permissionToolButton->hide(); - } - - // Set the permissions checkboxes - displayPermissions(); - - _ui->permissionShare->setEnabled(maxSharingPermissions & SharePermissionShare); - _ui->permissionsEdit->setEnabled(maxSharingPermissions - & (SharePermissionCreate | SharePermissionUpdate | SharePermissionDelete)); - - connect(_permissionUpdate, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged); - connect(_permissionCreate, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged); - connect(_permissionDelete, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged); - connect(_ui->permissionShare, &QAbstractButton::clicked, this, &ShareUserLine::slotPermissionsChanged); - connect(_ui->permissionsEdit, &QAbstractButton::clicked, this, &ShareUserLine::slotEditPermissionsChanged); - - /* - * We don't show permssion share for federated shares with server <9.1 - * https://github.com/owncloud/core/issues/22122#issuecomment-185637344 - * https://github.com/owncloud/client/issues/4996 - */ - if (share->getShareType() == Share::TypeRemote - && share->account()->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) { - _ui->permissionShare->setVisible(false); - _ui->permissionToolButton->setVisible(false); - } - - connect(share.data(), &Share::permissionsSet, this, &ShareUserLine::slotPermissionsSet); - connect(share.data(), &Share::shareDeleted, this, &ShareUserLine::slotShareDeleted); - - _ui->deleteShareButton->setIcon(QIcon::fromTheme(QStringLiteral("user-trash"), - Utility::getCoreIcon(QStringLiteral("delete")))); - - if (!share->account()->capabilities().shareResharing()) { - _ui->permissionShare->hide(); - } - - loadAvatar(); -} - -void ShareUserLine::loadAvatar() -{ - const int avatarSize = 36; - - // Set size of the placeholder - _ui->avatar->setMinimumHeight(avatarSize); - _ui->avatar->setMinimumWidth(avatarSize); - _ui->avatar->setMaximumHeight(avatarSize); - _ui->avatar->setMaximumWidth(avatarSize); - _ui->avatar->setAlignment(Qt::AlignCenter); - - /* Create the fallback avatar. - * - * This will be shown until the avatar image data arrives. - */ - const QByteArray hash = QCryptographicHash::hash(_ui->sharedWith->text().toUtf8(), QCryptographicHash::Md5); - double hue = static_cast(hash[0]) / 255.; - - // See core/js/placeholder.js for details on colors and styling - const QColor bg = QColor::fromHslF(hue, 0.7, 0.68); - const QString style = QString(R"(* { - color: #fff; - background-color: %1; - border-radius: %2px; - text-align: center; - line-height: %2px; - font-size: %2px; - })").arg(bg.name(), QString::number(avatarSize / 2)); - _ui->avatar->setStyleSheet(style); - - // The avatar label is the first character of the user name. - const QString text = _share->getShareWith()->displayName(); - _ui->avatar->setText(text.at(0).toUpper()); - - /* Start the network job to fetch the avatar data. - * - * Currently only regular users can have avatars. - */ - if (_share->getShareWith()->type() == Sharee::User) { - auto account = _share->account(); - auto capabilities = account->capabilities(); - if (capabilities.isValid() && capabilities.avatarsAvailable()) { - AvatarJob *job = new AvatarJob(account, _share->getShareWith()->shareWith(), avatarSize, this); - connect(job, &AvatarJob::avatarPixmap, this, &ShareUserLine::slotAvatarLoaded); - job->start(); - } - } -} - -void ShareUserLine::slotAvatarLoaded(const QPixmap &avatar) -{ - if (avatar.isNull()) - return; - - const QPixmap _avatar = AvatarJob::makeCircularAvatar(avatar); - _ui->avatar->setPixmap(_avatar); - - // Remove the stylesheet for the fallback avatar - _ui->avatar->setStyleSheet(""); -} - -void ShareUserLine::on_deleteShareButton_clicked() -{ - setEnabled(false); - _share->deleteShare(); -} - -ShareUserLine::~ShareUserLine() -{ - delete _ui; -} - -void ShareUserLine::slotEditPermissionsChanged() -{ - setEnabled(false); - - // Can never manually be set to "partial". - // This works because the state cycle for clicking is - // unchecked -> partial -> checked -> unchecked. - if (_ui->permissionsEdit->checkState() == Qt::PartiallyChecked) { - _ui->permissionsEdit->setCheckState(Qt::Checked); - } - - Share::Permissions permissions = SharePermissionRead; - - if (_ui->permissionShare->checkState() == Qt::Checked) { - permissions |= SharePermissionShare; - } - - if (_ui->permissionsEdit->checkState() == Qt::Checked) { - if (_permissionUpdate->isEnabled()) - permissions |= SharePermissionUpdate; - - /* - * Files can't have create or delete permisisons - */ - if (!_isFile) { - if (_permissionCreate->isEnabled()) - permissions |= SharePermissionCreate; - if (_permissionDelete->isEnabled()) - permissions |= SharePermissionDelete; - } - } - - _share->setPermissions(permissions); -} - -void ShareUserLine::slotPermissionsChanged() -{ - setEnabled(false); - - Share::Permissions permissions = SharePermissionRead; - - if (_permissionUpdate->isChecked()) { - permissions |= SharePermissionUpdate; - } - - if (_permissionCreate->isChecked()) { - permissions |= SharePermissionCreate; - } - - if (_permissionDelete->isChecked()) { - permissions |= SharePermissionDelete; - } - - if (_ui->permissionShare->checkState() == Qt::Checked) { - permissions |= SharePermissionShare; - } - - _share->setPermissions(permissions); -} - -void ShareUserLine::slotDeleteAnimationFinished() -{ - emit resizeRequested(); - emit visualDeletionDone(); - deleteLater(); - - // 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, &ShareUserLine::destroyed, parentWidget(), qOverload<>(&QWidget::repaint)); -} - -void ShareUserLine::slotShareDeleted() -{ - QPropertyAnimation *animation = new QPropertyAnimation(this, "maximumHeight", this); - - animation->setDuration(500); - animation->setStartValue(height()); - animation->setEndValue(0); - - connect(animation, &QAbstractAnimation::finished, this, &ShareUserLine::slotDeleteAnimationFinished); - connect(animation, &QVariantAnimation::valueChanged, this, &ShareUserLine::resizeRequested); - - animation->start(); -} - -void ShareUserLine::slotPermissionsSet() -{ - displayPermissions(); - setEnabled(true); -} - -QSharedPointer ShareUserLine::share() const -{ - return _share; -} - -void ShareUserLine::displayPermissions() -{ - const SharePermissions perm = _share->getPermissions(); - - _permissionUpdate->setChecked(perm & SharePermissionUpdate); - _permissionCreate->setChecked(!_isFile && perm & SharePermissionCreate); - _permissionDelete->setChecked(!_isFile && perm & SharePermissionDelete); - - if (perm & SharePermissionUpdate - && (_isFile - || (perm & SharePermissionCreate - && perm & SharePermissionDelete))) { - _ui->permissionsEdit->setCheckState(Qt::Checked); - } else if (perm & (SharePermissionUpdate | SharePermissionCreate | SharePermissionDelete)) { - _ui->permissionsEdit->setCheckState(Qt::PartiallyChecked); - } else { - _ui->permissionsEdit->setCheckState(Qt::Unchecked); - } - - _ui->permissionShare->setChecked(perm & SharePermissionShare); -} -} diff --git a/src/gui/shareusergroupwidget.h b/src/gui/shareusergroupwidget.h deleted file mode 100644 index db2ef6b3c51..00000000000 --- a/src/gui/shareusergroupwidget.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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. - */ - -#ifndef SHAREUSERGROUPWIDGET_H -#define SHAREUSERGROUPWIDGET_H - -#include "accountfwd.h" -#include "sharepermissions.h" -#include "QProgressIndicator.h" -#include -#include -#include -#include -#include -#include - -class QAction; -class QCompleter; -class QModelIndex; - -namespace OCC { - -namespace Ui { - class ShareUserGroupWidget; - class ShareUserLine; -} - -class AbstractCredentials; -class QuotaInfo; -class SyncResult; -class Share; -class Sharee; -class ShareManager; -class ShareeModel; - -/** - * @brief The ShareDialog (user/group) class - * @ingroup gui - */ -class ShareUserGroupWidget : public QWidget -{ - Q_OBJECT - -public: - explicit ShareUserGroupWidget(AccountPtr account, - const QString &sharePath, - const QString &localPath, - SharePermissions maxSharingPermissions, - const QString &privateLinkUrl, - QWidget *parent = nullptr); - ~ShareUserGroupWidget() override; - -public slots: - void getShares(); - -private slots: - void slotSharesFetched(const QList> &shares); - - void on_shareeLineEdit_textChanged(const QString &text); - void searchForSharees(); - void slotLineEditTextEdited(const QString &text); - - void slotLineEditReturn(); - void slotCompleterActivated(const QModelIndex &index); - void slotCompleterHighlighted(const QModelIndex &index); - void slotShareesReady(); - void slotAdjustScrollWidgetSize(); - void slotPrivateLinkShare(); - void displayError(int code, const QString &message); - - void slotPrivateLinkOpenBrowser(); - void slotPrivateLinkCopy(); - void slotPrivateLinkEmail(); - -private: - Ui::ShareUserGroupWidget *_ui; - AccountPtr _account; - QString _sharePath; - QString _localPath; - SharePermissions _maxSharingPermissions; - QString _privateLinkUrl; - - QCompleter *_completer; - ShareeModel *_completerModel; - QTimer _completionTimer; - - bool _isFile; - bool _disableCompleterActivated; // in order to avoid that we share the contents twice - ShareManager *_manager; - - QProgressIndicator _pi_sharee; -}; - -/** - * The widget displayed for each user/group share - */ -class ShareUserLine : public QWidget -{ - Q_OBJECT - -public: - explicit ShareUserLine(QSharedPointer Share, - SharePermissions maxSharingPermissions, - bool isFile, - QWidget *parent = nullptr); - ~ShareUserLine() override; - - QSharedPointer share() const; - -signals: - void visualDeletionDone(); - void resizeRequested(); - -private slots: - void on_deleteShareButton_clicked(); - void slotPermissionsChanged(); - void slotEditPermissionsChanged(); - void slotDeleteAnimationFinished(); - - void slotShareDeleted(); - void slotPermissionsSet(); - - void slotAvatarLoaded(const QPixmap &avatar); - -private: - void displayPermissions(); - void loadAvatar(); - - Ui::ShareUserLine *_ui; - QSharedPointer _share; - bool _isFile; - - QAction *_permissionCreate; - QAction *_permissionUpdate; - QAction *_permissionDelete; -}; -} - -#endif // SHAREUSERGROUPWIDGET_H diff --git a/src/gui/shareusergroupwidget.ui b/src/gui/shareusergroupwidget.ui deleted file mode 100644 index 1e57219e2c1..00000000000 --- a/src/gui/shareusergroupwidget.ui +++ /dev/null @@ -1,120 +0,0 @@ - - - OCC::ShareUserGroupWidget - - - - 0 - 0 - 397 - 273 - - - - Share NewDocument.odt - - - - - - - - Share with users or groups ... - - - - - - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 123 - 121 - 134 - - - - - - - - Placeholder for Error text - - - Qt::PlainText - - - true - - - - - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOff - - - true - - - - - 0 - 0 - 377 - 185 - - - - - - - - - - - - - true - - - - - - - - - diff --git a/src/gui/shareuserline.ui b/src/gui/shareuserline.ui deleted file mode 100644 index 62288a89d8d..00000000000 --- a/src/gui/shareuserline.ui +++ /dev/null @@ -1,93 +0,0 @@ - - - OCC::ShareUserLine - - - true - - - - 0 - 0 - 468 - 46 - - - - Form - - - true - - - - - - - - TextLabel - - - - - - - TextLabel - - - Qt::PlainText - - - - - - - Qt::Horizontal - - - - 40 - 15 - - - - - - - - can share - - - - - - - can edit - - - - - - - ... - - - - - - - - - - - .. - - - - - - - - - - diff --git a/src/gui/socketapi/CMakeLists.txt b/src/gui/socketapi/CMakeLists.txt index 78ce96cfda7..e59060345f0 100644 --- a/src/gui/socketapi/CMakeLists.txt +++ b/src/gui/socketapi/CMakeLists.txt @@ -1,8 +1,7 @@ -target_sources(owncloudCore PRIVATE +target_sources(owncloudGui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/socketapi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/socketuploadjob.cpp ) if( APPLE ) - target_sources(owncloudCore PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/socketapisocket_mac.mm) + target_sources(owncloudGui PRIVATE socketapisocket_mac.mm) endif() diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp index c1dad91843c..edb930f13ab 100644 --- a/src/gui/socketapi/socketapi.cpp +++ b/src/gui/socketapi/socketapi.cpp @@ -15,46 +15,37 @@ */ #include "socketapi.h" +#include "scheduling/syncscheduler.h" #include "socketapi_p.h" -#include "socketapi/socketuploadjob.h" +#include "gui/commonstrings.h" -#include "account.h" #include "accountmanager.h" #include "accountstate.h" -#include "capabilities.h" #include "common/asserts.h" #include "common/syncjournalfilerecord.h" #include "common/version.h" -#include "config.h" -#include "configfile.h" #include "filesystem.h" #include "folder.h" #include "folderman.h" #include "guiutility.h" -#include "sharemanager.h" #include "syncengine.h" #include "syncfileitem.h" #include "theme.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include #include -#include #include -#include +#include +#include +#include +#include +#include -#include -#include #include #include #include @@ -62,9 +53,6 @@ #include -#include -#include - #ifdef Q_OS_MAC #include #endif @@ -73,10 +61,15 @@ // This is the version that is returned when the client asks for the VERSION. // The first number should be changed if there is an incompatible change that breaks old clients. // The second number should be changed when there are new features. -#define MIRALL_SOCKET_API_VERSION "1.1" +#define SOCKET_API_VERSION "1.1" namespace { +const QString unregisterPathMessageC() +{ + return QStringLiteral("UNREGISTER_PATH"); +} + const QLatin1Char RecordSeparator() { return QLatin1Char('\x1e'); @@ -88,96 +81,16 @@ QStringList split(const QString &data) return data.split(RecordSeparator()); } -#if GUI_TESTING - -using namespace OCC; - -QList allObjects(const QList &widgets) -{ - QList objects; - std::copy(widgets.constBegin(), widgets.constEnd(), std::back_inserter(objects)); - - objects << qApp; - - return objects; -} - -QObject *findWidget(const QString &queryString, const QList &widgets = QApplication::allWidgets()) -{ - auto objects = allObjects(widgets); - - QList::const_iterator foundWidget; - - if (queryString.contains('>')) { - qCDebug(lcSocketApi) << "queryString contains >"; - - auto subQueries = queryString.split('>', QString::SkipEmptyParts); - Q_ASSERT(subQueries.count() == 2); - - auto parentQueryString = subQueries[0].trimmed(); - qCDebug(lcSocketApi) << "Find parent: " << parentQueryString; - auto parent = findWidget(parentQueryString); - - if (!parent) { - return nullptr; - } - - auto childQueryString = subQueries[1].trimmed(); - auto child = findWidget(childQueryString, parent->findChildren()); - qCDebug(lcSocketApi) << "found child: " << !!child; - return child; - - } else if (queryString.startsWith('#')) { - auto objectName = queryString.mid(1); - qCDebug(lcSocketApi) << "find objectName: " << objectName; - foundWidget = std::find_if(objects.constBegin(), objects.constEnd(), [&](QObject *widget) { - return widget->objectName() == objectName; - }); - } else { - QList matches; - std::copy_if(objects.constBegin(), objects.constEnd(), std::back_inserter(matches), [&](QObject *widget) { - return widget->inherits(queryString.toLatin1()); - }); - - std::for_each(matches.constBegin(), matches.constEnd(), [](QObject *w) { - if (!w) - return; - qCDebug(lcSocketApi) << "WIDGET: " << w->objectName() << w->metaObject()->className(); - }); - - if (matches.empty()) { - return nullptr; - } - return matches[0]; - } - - if (foundWidget == objects.constEnd()) { - return nullptr; - } - - return *foundWidget; -} -#endif - -static inline QString removeTrailingSlash(QString path) -{ - Q_ASSERT(path.endsWith(QLatin1Char('/'))); - path.truncate(path.length() - 1); - return path; -} - -static QString buildMessage(const QString &verb, const QString &path, const QString &status = QString()) +QString buildMessage(const QString &verb, const QString &path, const QString &status = QString()) { QString msg(verb); if (!status.isEmpty()) { - msg.append(QLatin1Char(':')); - msg.append(status); + msg += QLatin1Char(':') + status; } if (!path.isEmpty()) { - msg.append(QLatin1Char(':')); - QFileInfo fi(path); - msg.append(QDir::toNativeSeparators(fi.absoluteFilePath())); + const QFileInfo fi(path); + msg += QLatin1Char(':') + QDir::toNativeSeparators(fi.absoluteFilePath()); } return msg; } @@ -186,7 +99,6 @@ static QString buildMessage(const QString &verb, const QString &path, const QStr namespace OCC { Q_LOGGING_CATEGORY(lcSocketApi, "gui.socketapi", QtInfoMsg) -Q_LOGGING_CATEGORY(lcPublicLink, "gui.socketapi.publiclink", QtInfoMsg) void SocketListener::sendMessage(const QString &message, bool doWait) const { @@ -215,25 +127,24 @@ SocketApi::SocketApi(QObject *parent) : QObject(parent) { qRegisterMetaType("SocketListener*"); - qRegisterMetaType>("QSharedPointer"); qRegisterMetaType>("QSharedPointer"); - const QString socketPath = Utility::socketApiSocketPath(); + _socketPath = Utility::socketApiSocketPath(); // Remove any old socket that might be lying around: - SocketApiServer::removeServer(socketPath); + SocketApiServer::removeServer(_socketPath); // Create the socket path: - if (!Utility::isMac()) { + if (!Utility::isMac() && !Utility::isWindows()) { + // Not on Windows: a named pipe doesn't need a directory in the filesystem // Not on macOS: there the directory is there, and created for us by the sandboxing // environment, because we belong to an App Group. - QFileInfo info(socketPath); + QFileInfo info(_socketPath); if (!info.dir().exists()) { - bool result = info.dir().mkpath("."); + bool result = info.dir().mkpath(QStringLiteral(".")); qCDebug(lcSocketApi) << "creating" << info.dir().path() << result; if (result) { - QFile::setPermissions(socketPath, - QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)); + QFile::setPermissions(_socketPath, QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)); } } } @@ -241,18 +152,7 @@ SocketApi::SocketApi(QObject *parent) // Wire up the server instance to us, so we can accept new connections: connect(&_localServer, &SocketApiServer::newConnection, this, &SocketApi::slotNewConnection); - // Start listeneing: - if (_localServer.listen(socketPath)) { - qCInfo(lcSocketApi) << "server started, listening at " << socketPath; - } else { - qCWarning(lcSocketApi) << "can't start server" << socketPath; - } - - // folder watcher - connect(FolderMan::instance(), &FolderMan::folderSyncStateChange, this, &SocketApi::slotUpdateFolderView); - - // Now we're ready to start the native shell integration: - Utility::startShellIntegration(); + connect(AccountManager::instance(), &AccountManager::accountRemoved, this, &SocketApi::unregisterAccount); } SocketApi::~SocketApi() @@ -264,6 +164,19 @@ SocketApi::~SocketApi() _listeners.clear(); } +void SocketApi::startShellIntegration() +{ + // Start listening: + if (_localServer.listen(_socketPath)) { + qCInfo(lcSocketApi) << "server started, listening at " << _socketPath; + } else { + qCWarning(lcSocketApi) << "can't start server" << _socketPath; + } + + // Now we're ready to start the native shell integration: + Utility::startShellIntegration(); +} + void SocketApi::slotNewConnection() { // Note that on macOS this is not actually a line-based QIODevice, it's a SocketApiSocket which is our @@ -288,9 +201,14 @@ void SocketApi::slotNewConnection() auto listener = QSharedPointer::create(socket); _listeners.insert(socket, listener); - for (Folder *f : FolderMan::instance()->map()) { + for (const auto &a : std::as_const(_registeredAccounts)) { + if (a && !a->defaultSyncRoot().isEmpty()) { + broadcastMessage(buildRegisterPathMessage(Utility::stripTrailingSlash(a->defaultSyncRoot()))); + } + } + for (Folder *f : FolderMan::instance()->folders()) { if (f->canSync()) { - QString message = buildRegisterPathMessage(removeTrailingSlash(f->path())); + QString message = buildRegisterPathMessage(Utility::stripTrailingSlash(f->path())); listener->sendMessage(message); } } @@ -309,59 +227,43 @@ void SocketApi::slotReadSocket() // a SocketListener that doesn't send any messages. static auto invalidListener = QSharedPointer::create(nullptr); const auto listener = _listeners.value(socket, invalidListener); - while (socket->canReadLine()) { - // Make sure to normalize the input from the socket to - // make sure that the path will match, especially on OS X. - const QString line = QString::fromUtf8(socket->readLine().trimmed()).normalized(QString::NormalizationForm_C); + while (true) { + // listener->socket is a QPointer and will be null as soon as the socket is deleted in the read loop + auto listenerSocket = listener->socket; + if (listenerSocket.isNull()) { + break; + } + if (!socket->canReadLine()) { + break; + } + QString line = QString::fromUtf8(socket->readLine()); + // Note: do NOT use QString::trimmed() here! That will also remove any trailing spaces (which _are_ part of the filename)! + line.chop(1); // remove the '\n' + qCInfo(lcSocketApi) << "Received SocketAPI message <--" << line << "from" << socket; - const int argPos = line.indexOf(QLatin1Char(':')); - const QByteArray command = line.midRef(0, argPos).toUtf8().toUpper(); + const qsizetype argPos = line.indexOf(QLatin1Char(':')); + const QString command = line.mid(0, argPos).toUpper(); const int indexOfMethod = [&] { QByteArray functionWithArguments = QByteArrayLiteral("command_"); - if (command.startsWith("ASYNC_")) { - functionWithArguments += command + QByteArrayLiteral("(QSharedPointer)"); - } else if (command.startsWith("V2/")) { - functionWithArguments += QByteArrayLiteral("V2_") + command.mid(3) + QByteArrayLiteral("(QSharedPointer)"); + if (command.startsWith(QLatin1String("V2/"))) { + functionWithArguments += QByteArrayLiteral("V2_") + command.mid(3).toUtf8() + QByteArrayLiteral("(QSharedPointer)"); } else { - functionWithArguments += command + QByteArrayLiteral("(QString,SocketListener*)"); + functionWithArguments += command.toUtf8() + QByteArrayLiteral("(QString,SocketListener*)"); } - Q_ASSERT(staticQtMetaObject.normalizedSignature(functionWithArguments) == functionWithArguments); - const auto out = staticMetaObject.indexOfMethod(functionWithArguments); + Q_ASSERT(staticMetaObject.normalizedSignature(functionWithArguments.constData()) == functionWithArguments); + const auto out = staticMetaObject.indexOfMethod(functionWithArguments.constData()); if (out == -1) { listener->sendError(QStringLiteral("Function %1 not found").arg(QString::fromUtf8(functionWithArguments))); } - OC_ASSERT(out != -1); return out; }(); - const auto argument = argPos != -1 ? line.midRef(argPos + 1) : QStringRef(); - if (command.startsWith("ASYNC_")) { - auto arguments = argument.split('|'); - if (arguments.size() != 2) { - listener->sendError(QStringLiteral("argument count is wrong")); - return; - } - - auto json = QJsonDocument::fromJson(arguments[1].toUtf8()).object(); - - auto jobId = arguments[0]; - - auto socketApiJob = QSharedPointer( - new SocketApiJob(jobId.toString(), listener, json), &QObject::deleteLater); - if (indexOfMethod != -1) { - staticMetaObject.method(indexOfMethod) - .invoke(this, Qt::QueuedConnection, - Q_ARG(QSharedPointer, socketApiJob)); - } else { - qCWarning(lcSocketApi) << "The command is not supported by this version of the client:" << command - << "with argument:" << argument; - socketApiJob->reject(QStringLiteral("command not found")); - } - } else if (command.startsWith("V2/")) { + const auto argument = argPos != -1 ? line.mid(argPos + 1) : QString(); + if (command.startsWith(QLatin1String("V2/"))) { QJsonParseError error; const auto json = QJsonDocument::fromJson(argument.toUtf8(), &error).object(); if (error.error != QJsonParseError::NoError) { - qCWarning(lcSocketApi()) << "Invalid json" << argument.toString() << error.errorString(); + qCWarning(lcSocketApi()) << "Invalid json" << argument << error.errorString(); listener->sendError(error.errorString()); return; } @@ -379,38 +281,57 @@ void SocketApi::slotReadSocket() if (indexOfMethod != -1) { // to ensure that listener is still valid we need to call it with Qt::DirectConnection OC_ASSERT(thread() == QThread::currentThread()) - staticMetaObject.method(indexOfMethod) - .invoke(this, Qt::DirectConnection, Q_ARG(QString, argument.toString()), - Q_ARG(SocketListener*, listener.data())); + staticMetaObject.method(indexOfMethod).invoke(this, Qt::DirectConnection, Q_ARG(QString, argument), Q_ARG(SocketListener *, listener.data())); } } } } -void SocketApi::slotRegisterPath(const QString &alias) + +void SocketApi::registerAccount(Account *a) { // Make sure not to register twice to each connected client - if (_registeredAliases.contains(alias)) + if (!a || _registeredAccounts.contains(a->uuid())) { return; + } - Folder *f = FolderMan::instance()->folder(alias); - if (f) { - broadcastMessage(buildRegisterPathMessage(removeTrailingSlash(f->path()))); + _registeredAccounts.insert(a->uuid(), a); + + if (!a->defaultSyncRoot().isEmpty()) { + broadcastMessage(buildRegisterPathMessage(Utility::stripTrailingSlash(a->defaultSyncRoot()))); } +} + +void SocketApi::unregisterAccount(AccountState *state) +{ + if (!state || !state->account()) + return; + + Account *a = state->account(); - _registeredAliases.insert(alias); + if (!a->defaultSyncRoot().isEmpty()) { + broadcastMessage(buildMessage(unregisterPathMessageC(), Utility::stripTrailingSlash(a->defaultSyncRoot()))); + } + _registeredAccounts.remove(a->uuid()); } -void SocketApi::slotUnregisterPath(const QString &alias) +void SocketApi::slotRegisterPath(Folder *folder) { - if (!_registeredAliases.contains(alias)) + // Make sure not to register twice to each connected client + if (_registeredFolders.contains(folder)) return; - Folder *f = FolderMan::instance()->folder(alias); - if (f) - broadcastMessage(buildMessage(QLatin1String("UNREGISTER_PATH"), removeTrailingSlash(f->path()), QString()), true); + broadcastMessage(buildRegisterPathMessage(Utility::stripTrailingSlash(folder->path()))); + _registeredFolders.insert(folder); +} + +void SocketApi::slotUnregisterPath(Folder *folder) +{ + if (!_registeredFolders.contains(folder)) + return; - _registeredAliases.remove(alias); + broadcastMessage(buildMessage(unregisterPathMessageC(), Utility::stripTrailingSlash(folder->path()), QString()), true); + _registeredFolders.remove(folder); } void SocketApi::slotUpdateFolderView(Folder *f) @@ -430,14 +351,17 @@ void SocketApi::slotUpdateFolderView(Folder *f) Q_FALLTHROUGH(); case SyncResult::Problem: Q_FALLTHROUGH(); - case SyncResult::Error: - Q_FALLTHROUGH(); - case SyncResult::SetupError: { - const QString rootPath = removeTrailingSlash(f->path()); - broadcastStatusPushMessage(rootPath, f->syncEngine().syncFileStatusTracker().fileStatus("")); + case SyncResult::Error: { + const QString rootPath = Utility::stripTrailingSlash(f->path()); + broadcastStatusPushMessage(rootPath, f->syncEngine()->fileStatus(QString())); broadcastMessage(buildMessage(QStringLiteral("UPDATE_VIEW"), rootPath)); - } break; + break; + } + case SyncResult::SetupError: + [[fallthrough]]; + case SyncResult::Unavailable: + [[fallthrough]]; case OCC::SyncResult::Undefined: Q_FALLTHROUGH(); case OCC::SyncResult::NotYetStarted: @@ -445,65 +369,26 @@ void SocketApi::slotUpdateFolderView(Folder *f) case OCC::SyncResult::SyncRunning: Q_FALLTHROUGH(); case OCC::SyncResult::SyncAbortRequested: - qCDebug(lcSocketApi) << "Not sending UPDATE_VIEW for" << f->alias() << "because status() is" << f->syncResult().status(); + [[fallthrough]]; + case OCC::SyncResult::Offline: + qCDebug(lcSocketApi) << "Not sending UPDATE_VIEW for" << f->path() << "because status() is" << f->syncResult().status(); } } } void SocketApi::broadcastMessage(const QString &msg, bool doWait) { - for (const auto &listener : qAsConst(_listeners)) { + for (const auto &listener : std::as_const(_listeners)) { listener->sendMessage(msg, doWait); } } -void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage) -{ - auto theme = Theme::instance(); - - auto fileData = FileData::get(localFile); - auto shareFolder = fileData.folder; - if (!shareFolder) { - const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile); - // files that are not within a sync folder are not synced. - listener->sendMessage(message); - } else if (!shareFolder->accountState()->isConnected()) { - const QString message = QLatin1String("SHARE:NOTCONNECTED:") + QDir::toNativeSeparators(localFile); - // if the folder isn't connected, don't open the share dialog - listener->sendMessage(message); - } else if (!theme->linkSharing() && (!theme->userGroupSharing() || shareFolder->accountState()->account()->serverVersionInt() < Account::makeServerVersion(8, 2, 0))) { - const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - } else { - // If the file doesn't have a journal record, it might not be uploaded yet - if (!fileData.journalRecord().isValid()) { - const QString message = QLatin1String("SHARE:NOTSYNCED:") + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - return; - } - - auto &remotePath = fileData.serverRelativePath; - - // Can't share root folder - if (remotePath == "/") { - const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - return; - } - - const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile); - listener->sendMessage(message); - - emit shareCommandReceived(remotePath, fileData.localPath, startPage); - } -} - void SocketApi::broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus) { - QString msg = buildMessage(QLatin1String("STATUS"), systemPath, fileStatus.toSocketAPIString()); - Q_ASSERT(!systemPath.endsWith('/')); - uint directoryHash = qHash(systemPath.left(systemPath.lastIndexOf('/'))); - for (const auto &listener : qAsConst(_listeners)) { + QString msg = buildMessage(QStringLiteral("STATUS"), systemPath, fileStatus.toSocketAPIString()); + Q_ASSERT(!systemPath.endsWith(QLatin1Char('/'))); + auto directoryHash = qHash(systemPath.left(systemPath.lastIndexOf(QLatin1Char('/')))); + for (const auto &listener : std::as_const(_listeners)) { listener->sendMessageIfDirectoryMonitored(msg, directoryHash); } } @@ -524,8 +409,8 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList statusString = SyncFileStatus(SyncFileStatus::StatusNone).toSocketAPIString(); } else { // The user probably visited this directory in the file shell. - // Let the listener know that it should now send status pushes for sibblings of this file. - QString directory = fileData.localPath.left(fileData.localPath.lastIndexOf('/')); + // Let the listener know that it should now send status pushes for siblings of this file. + QString directory = fileData.localPath.left(fileData.localPath.lastIndexOf(QLatin1Char('/'))); listener->registerMonitoredDirectory(qHash(directory)); statusString = fileData.syncFileStatus().toSocketAPIString(); @@ -537,135 +422,56 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener) { - processShareRequest(localFile, listener, ShareDialogStartPage::UsersAndGroups); -} - -void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener) -{ - processShareRequest(localFile, listener, ShareDialogStartPage::PublicLinks); -} - -void SocketApi::command_VERSION(const QString &, SocketListener *listener) -{ - listener->sendMessage(QStringLiteral("VERSION:%1:%2").arg(OCC::Version::versionWithBuildNumber().toString(), QStringLiteral(MIRALL_SOCKET_API_VERSION))); -} - -void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener) -{ - listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI())); -} - -class GetOrCreatePublicLinkShare : public QObject -{ - Q_OBJECT -public: - GetOrCreatePublicLinkShare(const AccountPtr &account, - const QString &serverPath, QObject *parent) - : QObject(parent) - , _account(account) - , _shareManager(account) - , _serverPath(serverPath) - { - connect(&_shareManager, &ShareManager::sharesFetched, - this, &GetOrCreatePublicLinkShare::sharesFetched); - connect(&_shareManager, &ShareManager::linkShareCreated, - this, &GetOrCreatePublicLinkShare::linkShareCreated); - connect(&_shareManager, &ShareManager::linkShareCreationForbidden, - this, &GetOrCreatePublicLinkShare::linkShareCreationForbidden); - connect(&_shareManager, &ShareManager::serverError, - this, &GetOrCreatePublicLinkShare::serverError); - } - - void run() - { - qCDebug(lcPublicLink) << "Fetching shares"; - _shareManager.fetchShares(_serverPath); - } - -private slots: - void sharesFetched(const QList> &shares) - { - auto shareName = SocketApi::tr("Context menu share"); - - // If shares will expire, create a new one every day. - QDate expireDate; - if (_account->capabilities().sharePublicLinkDefaultExpire()) { - shareName = SocketApi::tr("Context menu share %1").arg(QDate::currentDate().toString(Qt::ISODate)); - expireDate = QDate::currentDate().addDays( - _account->capabilities().sharePublicLinkDefaultExpireDateDays()); + auto fileData = FileData::get(localFile); + Folder *shareFolder = fileData.folder; + if (!shareFolder) { + const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile); + // files that are not within a sync folder are not synced. + listener->sendMessage(message); + } else if (!shareFolder->isConnected()) { + const QString message = QLatin1String("SHARE:NOTCONNECTED:") + QDir::toNativeSeparators(localFile); + // if the folder isn't connected, don't open the share dialog + listener->sendMessage(message); + } else if (!shareFolder->accountState()->account()->capabilities().shareAPI() + || !shareFolder->accountState()->account()->capabilities().sharePublicLink()) { + const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile); + listener->sendMessage(message); + } else { + // If the file doesn't have a journal record, it might not be uploaded yet + if (!fileData.journalRecord().isValid()) { + const QString message = QLatin1String("SHARE:NOTSYNCED:") + QDir::toNativeSeparators(localFile); + listener->sendMessage(message); + return; } - // If there already is a context menu share, reuse it - for (const auto &share : shares) { - const auto linkShare = qSharedPointerDynamicCast(share); - if (!linkShare) - continue; + auto &remotePath = fileData.serverRelativePath; - if (linkShare->getName() == shareName) { - qCDebug(lcPublicLink) << "Found existing share, reusing"; - return success(linkShare->getLink().toString()); - } + // Can't share root folder + if (remotePath == QLatin1String("/")) { + const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile); + listener->sendMessage(message); + return; } - // otherwise create a new one - qCDebug(lcPublicLink) << "Creating new share"; - QString noPassword; - _shareManager.createLinkShare(_serverPath, shareName, noPassword, expireDate); - } - - void linkShareCreated(const QSharedPointer &share) - { - qCDebug(lcPublicLink) << "New share created"; - success(share->getLink().toString()); - } - - void linkShareCreationForbidden(const QString &message) - { - qCInfo(lcPublicLink) << "Could not create link share:" << message; - emit error(message); - deleteLater(); - } - - void serverError(int code, const QString &message) - { - qCWarning(lcPublicLink) << "Share fetch/create error" << code << message; - emit error(message); - deleteLater(); - } - -signals: - void done(const QString &link); - void error(const QString &message); + const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile); + listener->sendMessage(message); -private: - void success(const QString &link) - { - emit done(link); - deleteLater(); + Q_EMIT shareCommandReceived(remotePath, fileData.localPath); } +} - AccountPtr _account; - ShareManager _shareManager; - QString _serverPath; -}; - -void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *) +void SocketApi::command_VERSION(const QString &, SocketListener *listener) { - auto fileData = FileData::get(localFile); - if (!fileData.folder) - return; + listener->sendMessage(QStringLiteral("VERSION:%1:%2").arg(OCC::Version::versionWithBuildNumber().toString(), QStringLiteral(SOCKET_API_VERSION))); +} - AccountPtr account = fileData.folder->accountState()->account(); - auto job = new GetOrCreatePublicLinkShare(account, fileData.serverRelativePath, this); - connect(job, &GetOrCreatePublicLinkShare::done, this, - [](const QString &url) { copyUrlToClipboard(url); }); - connect(job, &GetOrCreatePublicLinkShare::error, this, - [=]() { emit shareCommandReceived(fileData.serverRelativePath, fileData.localPath, ShareDialogStartPage::PublicLinks); }); - job->run(); +void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener) +{ + listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is the application name").arg(Theme::instance()->appNameGUI())); } // Fetches the private link url asynchronously and then calls the target slot -void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::function &targetFun) +void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::function &targetFun) { auto fileData = FileData::get(localFile); if (!fileData.folder) { @@ -673,15 +479,13 @@ void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::f return; } - auto record = fileData.journalRecord(); - if (!record.isValid()) - return; + if (!fileData.isSyncFolder()) { + auto record = fileData.journalRecord(); + if (!record.isValid()) + return; + } - fetchPrivateLinkUrl( - fileData.folder->accountState()->account(), - fileData.serverRelativePath, - this, - targetFun); + fetchPrivateLinkUrl(fileData.folder->accountState()->account(), fileData.folder->webDavUrl(), fileData.serverRelativePath, this, targetFun); } void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *) @@ -701,19 +505,22 @@ void SocketApi::command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListen void SocketApi::command_OPEN_PRIVATE_LINK_VERSIONS(const QString &localFile, SocketListener *) { - auto openVersionsLink = [](const QString &link) { - QUrl url(link); - QUrlQuery query(url); - query.addQueryItem(QStringLiteral("details"), QStringLiteral("versionsTabView")); - url.setQuery(query); - Utility::openBrowser(url, nullptr); - }; - fetchPrivateLinkUrlHelper(localFile, openVersionsLink); + const auto fileData = FileData::get(localFile); + if (fileData.isValid() && fileData.folder->accountState()->account()->capabilities().filesSharing().sharing_roles) { + fetchPrivateLinkUrl(fileData.folder->accountState()->account(), fileData.folder->webDavUrl(), fileData.serverRelativePath, this, [](const QUrl &url) { + const auto queryUrl = Utility::concatUrlPath(url, QString(), {{QStringLiteral("details"), QStringLiteral("versions")}}); + Utility::openBrowser(queryUrl, nullptr); + }); + } else { + fetchPrivateLinkUrlHelper(localFile, [](const QUrl &link) { + Utility::openBrowser(Utility::concatUrlPath(link, {}, {{QStringLiteral("details"), QStringLiteral("versionsTabView")}}), nullptr); + }); + } } -void SocketApi::copyUrlToClipboard(const QString &link) +void SocketApi::copyUrlToClipboard(const QUrl &link) { - QApplication::clipboard()->setText(link); + QApplication::clipboard()->setText(link.toString()); } void SocketApi::command_MAKE_AVAILABLE_LOCALLY(const QString &filesArg, SocketListener *) @@ -726,11 +533,11 @@ void SocketApi::command_MAKE_AVAILABLE_LOCALLY(const QString &filesArg, SocketLi continue; // Update the pin state on all items - data.folder->vfs().setPinState(data.folderRelativePath, PinState::AlwaysLocal); + std::ignore = data.folder->vfs().setPinState(data.folderRelativePath, PinState::AlwaysLocal); // Trigger sync data.folder->schedulePathForLocalDiscovery(data.folderRelativePath); - data.folder->scheduleThisFolderSoon(); + FolderMan::instance()->scheduler()->enqueueFolder(data.folder, SyncScheduler::Priority::Medium); } } @@ -745,17 +552,24 @@ void SocketApi::command_MAKE_ONLINE_ONLY(const QString &filesArg, SocketListener continue; // Update the pin state on all items - data.folder->vfs().setPinState(data.folderRelativePath, PinState::OnlineOnly); + std::ignore = data.folder->vfs().setPinState(data.folderRelativePath, PinState::OnlineOnly); // Trigger sync data.folder->schedulePathForLocalDiscovery(data.folderRelativePath); - data.folder->scheduleThisFolderSoon(); + FolderMan::instance()->scheduler()->enqueueFolder(data.folder, SyncScheduler::Priority::Medium); } } void SocketApi::command_DELETE_ITEM(const QString &localFile, SocketListener *) { QFileInfo info(localFile); + // ensure the given path is under control by ownCloud + auto data = FileData::get(localFile); + if (!data.isValid() || !data.folder->isReady()) + return; + // ensure the given path exists locally + if (!info.exists()) + return; auto result = QMessageBox::question( nullptr, tr("Confirm deletion"), @@ -767,7 +581,10 @@ void SocketApi::command_DELETE_ITEM(const QString &localFile, SocketListener *) return; if (info.isDir()) { - FileSystem::removeRecursively(localFile); + FileSystem::RemoveEntryList removed; + FileSystem::RemoveEntryList locked; + FileSystem::RemoveErrorList errors; + FileSystem::removeRecursively(localFile, &removed, &locked, &errors); } else { QFile(localFile).remove(); } @@ -784,7 +601,7 @@ void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *) // If it's a conflict, we want to save it under the base name by default if (Utility::isConflictFile(defaultDirAndName)) { - defaultDirAndName = fileData.folder->journalDb()->conflictFileBaseName(fileData.folderRelativePath.toUtf8()); + defaultDirAndName = QString::fromUtf8(fileData.folder->journalDb()->conflictFileBaseName(fileData.folderRelativePath.toUtf8())); } // If the parent doesn't accept new files, go to the root of the sync folder @@ -800,7 +617,7 @@ void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *) auto target = QFileDialog::getSaveFileName( nullptr, - tr("Select new location..."), + tr("Select new location…"), defaultDirAndName, QString(), nullptr, QFileDialog::HideNameFilterDetails); if (target.isEmpty()) @@ -815,21 +632,17 @@ void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *) } } -void SocketApi::command_V2_LIST_ACCOUNTS(const QSharedPointer &job) const +void SocketApi::command_OPEN_APP_LINK(const QString &localFile, [[maybe_unused]] SocketListener *listener) { - QJsonArray out; - for (auto acc : AccountManager::instance()->accounts()) { - out << QJsonObject({ { QStringLiteral("name"), acc->account()->displayName() }, - { QStringLiteral("id"), acc->account()->id() }, - { QStringLiteral("uuid"), acc->account()->uuid().toString(QUuid::WithoutBraces) } }); + const auto data = FileData::get(localFile); + if (!data.folder) { + return; + } + const auto &provider = data.folder->accountState()->account()->appProvider(); + const auto record = data.journalRecord(); + if (record.isValid()) { + provider.open(data.folder->accountState()->account(), localFile, record._fileId); } - job->success({ { QStringLiteral("accounts"), out } }); -} - -void SocketApi::command_V2_UPLOAD_FILES_FROM(const QSharedPointer &job) const -{ - auto uploadJob = new SocketUploadJob(job); - uploadJob->start(); } void SocketApi::command_V2_GET_CLIENT_ICON(const QSharedPointer &job) const @@ -837,7 +650,7 @@ void SocketApi::command_V2_GET_CLIENT_ICON(const QSharedPointer OC_ASSERT(job); const auto &arguments = job->arguments(); - const auto size = arguments.value("size"); + const auto size = arguments.value(QStringLiteral("size")); if (size.isUndefined()) { qCWarning(lcSocketApi) << "Icon size not given in " << Q_FUNC_INFO; job->failure(QStringLiteral("cannot get client icon")); @@ -847,63 +660,61 @@ void SocketApi::command_V2_GET_CLIENT_ICON(const QSharedPointer QByteArray data; const Theme *theme = Theme::instance(); // return an empty answer if the end point was disabled - if (theme->enableSocketApiIconSupport()) { - const QIcon appIcon = theme->applicationIcon(); - qCDebug(lcSocketApi) << Q_FUNC_INFO << " got icon from theme: " << appIcon; - - // convert to pixmap (might be smaller if size is not available) - const QPixmap pixmap = appIcon.pixmap(QSize(size.toInt(), size.toInt())); - - // Convert pixmap to in-memory PNG - QByteArray png; - QBuffer pngBuffer(&png); - auto success = pngBuffer.open(QIODevice::WriteOnly); - if (!success) { - qCWarning(lcSocketApi) << "Error opening buffer for png in " << Q_FUNC_INFO; - job->failure(QStringLiteral("cannot get client icon")); - return; - } - - success = pixmap.save(&pngBuffer, "PNG"); - if (!success) { - qCWarning(lcSocketApi) << "Error saving client icon as png in " << Q_FUNC_INFO; - job->failure(QStringLiteral("cannot get client icon")); - return; - } + const QIcon appIcon = theme->applicationIcon(); + qCDebug(lcSocketApi) << Q_FUNC_INFO << " got icon from theme: " << appIcon; + + // convert to pixmap (might be smaller if size is not available) + const QPixmap pixmap = appIcon.pixmap(QSize(size.toInt(), size.toInt())); + + // Convert pixmap to in-memory PNG + QByteArray png; + QBuffer pngBuffer(&png); + auto success = pngBuffer.open(QIODevice::WriteOnly); + if (!success) { + qCWarning(lcSocketApi) << "Error opening buffer for png in " << Q_FUNC_INFO; + job->failure(QStringLiteral("cannot get client icon")); + return; + } - data = pngBuffer.data().toBase64(); + success = pixmap.save(&pngBuffer, "PNG"); + if (!success) { + qCWarning(lcSocketApi) << "Error saving client icon as png in " << Q_FUNC_INFO; + job->failure(QStringLiteral("cannot get client icon")); + return; } - job->success({ { QStringLiteral("png"), QString::fromUtf8(data) } }); + + data = pngBuffer.data().toBase64(); + + job->success({{QStringLiteral("png"), QString::fromUtf8(data)}}); } -void SocketApi::emailPrivateLink(const QString &link) +void SocketApi::emailPrivateLink(const QUrl &link) { Utility::openEmailComposer( tr("I shared something with you"), - link, + link.toString(), nullptr); } -void OCC::SocketApi::openPrivateLink(const QString &link) +void SocketApi::openPrivateLink(const QUrl &link) { Utility::openBrowser(link, nullptr); } void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *listener) { - static std::array, 5> strings { { - { "SHARE_MENU_TITLE", tr("Share...") }, - { "CONTEXT_MENU_TITLE", Theme::instance()->appNameGUI() }, - { "COPY_PRIVATE_LINK_MENU_TITLE", tr("Copy private link to clipboard") }, - { "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email...") }, + static std::array, 5> strings { { + { QStringLiteral("SHARE_MENU_TITLE"), tr("Share…") }, + { QStringLiteral("CONTEXT_MENU_TITLE"), Theme::instance()->appNameGUI() }, + { QStringLiteral("COPY_PRIVATE_LINK_MENU_TITLE"), tr("Copy private link to clipboard") }, } }; - listener->sendMessage(QString("GET_STRINGS:BEGIN")); + listener->sendMessage(QStringLiteral("GET_STRINGS:BEGIN")); for (auto key_value : strings) { - if (argument.isEmpty() || argument == QLatin1String(key_value.first)) { - listener->sendMessage(QString("STRING:%1:%2").arg(key_value.first, key_value.second)); + if (argument.isEmpty() || argument == key_value.first) { + listener->sendMessage(QStringLiteral("STRING:%1:%2").arg(key_value.first, key_value.second)); } } - listener->sendMessage(QString("GET_STRINGS:END")); + listener->sendMessage(QStringLiteral("GET_STRINGS:END")); } void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener) @@ -913,36 +724,21 @@ void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketLi auto flagString = isOnTheServer ? QStringLiteral("::") : QStringLiteral(":d:"); auto capabilities = fileData.folder->accountState()->account()->capabilities(); - auto theme = Theme::instance(); - if (!capabilities.shareAPI() || !(theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink()))) + if (!capabilities.shareAPI() || !capabilities.sharePublicLink()) return; // If sharing is globally disabled, do not show any sharing entries. // If there is no permission to share for this file, add a disabled entry saying so if (isOnTheServer && !record._remotePerm.isNull() && !record._remotePerm.hasPermission(RemotePermissions::CanReshare)) { - listener->sendMessage(QStringLiteral("MENU_ITEM:DISABLED:d:") + (!record.isDirectory() ? tr("Resharing this file is not allowed") : tr("Resharing this folder is not allowed"))); + listener->sendMessage(QStringLiteral("MENU_ITEM:DISABLED:d:") + + (record.isDirectory() ? tr("Resharing this folder is not allowed") : tr("Resharing this file is not allowed"))); } else { - listener->sendMessage(QStringLiteral("MENU_ITEM:SHARE") + flagString + tr("Share...")); - - // Do we have public links? - bool publicLinksEnabled = theme->linkSharing() && capabilities.sharePublicLink(); - - // Is is possible to create a public link without user choices? - bool canCreateDefaultPublicLink = publicLinksEnabled - && !capabilities.sharePublicLinkEnforcePasswordForReadOnly(); - - if (canCreateDefaultPublicLink) { - listener->sendMessage(QStringLiteral("MENU_ITEM:COPY_PUBLIC_LINK") + flagString + tr("Create and copy public link to clipboard")); - } else if (publicLinksEnabled) { - listener->sendMessage(QStringLiteral("MENU_ITEM:MANAGE_PUBLIC_LINKS") + flagString + tr("Copy public link to clipboard")); - } + listener->sendMessage(QStringLiteral("MENU_ITEM:SHARE") + flagString + tr("Share…")); } - listener->sendMessage(QStringLiteral("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy private link to clipboard")); - - // Disabled: only providing email option for private links would look odd, - // and the copy option is more general. - //listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email...")); + if (capabilities.privateLinkPropertyAvailable()) { + listener->sendMessage(QStringLiteral("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy private link to clipboard")); + } } SocketApi::FileData SocketApi::FileData::get(const QString &localFile) @@ -958,25 +754,20 @@ SocketApi::FileData SocketApi::FileData::get(const QString &localFile) return data; data.serverRelativePath = QDir(data.folder->remotePath()).filePath(data.folderRelativePath); - if (data.folder->isReady()) { - data.serverRelativePath = data.folder->vfs().underlyingFileName(data.serverRelativePath); - } return data; } -QString SocketApi::FileData::folderRelativePathNoVfsSuffix() const +bool SocketApi::FileData::isSyncFolder() const { - if (folder->isReady()) { - return folder->vfs().underlyingFileName(folderRelativePath); - } - return folderRelativePath; + return folderRelativePath.isEmpty(); } SyncFileStatus SocketApi::FileData::syncFileStatus() const { - if (!folder) + if (!folder || !folder->canSync()) { return SyncFileStatus::StatusNone; - return folder->syncEngine().syncFileStatusTracker().fileStatus(folderRelativePath); + } + return folder->syncEngine()->fileStatus(folderRelativePath); } SyncJournalFileRecord SocketApi::FileData::journalRecord() const @@ -990,14 +781,100 @@ SyncJournalFileRecord SocketApi::FileData::journalRecord() const SocketApi::FileData SocketApi::FileData::parentFolder() const { - return FileData::get(QFileInfo(localPath).dir().path().toUtf8()); + return FileData::get(QFileInfo(localPath).dir().path()); +} + +bool SocketApi::FileData::isValid() const +{ + return folder; } void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener) { - listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN")); + listener->sendMessage(QStringLiteral("GET_MENU_ITEMS:BEGIN")); const QStringList files = split(argument); + // Some options only show for single files + if (files.size() == 1) { + const FileData fileData = FileData::get(files.first()); + if (fileData.isValid() && fileData.folder->isConnected()) { + const auto &capabilities = fileData.folder->accountState()->account()->capabilities(); + if (!fileData.isSyncFolder()) { + const auto record = fileData.journalRecord(); + const bool isOnTheServer = record.isValid(); + const auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:"); + + const auto app = fileData.folder->accountState()->account()->appProvider().app(fileData.localPath); + if (!app.defaultApplication.isEmpty()) { + listener->sendMessage(QStringLiteral("MENU_ITEM:OPEN_APP_LINK") + flagString + tr("Open in %1").arg(app.defaultApplication)); + } + + sendSharingContextMenuOptions(fileData, listener); + + if (capabilities.privateLinkPropertyAvailable()) { + listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + CommonStrings::showInWebBrowser()); + } + // Add link to versions pane if possible + if (capabilities.versioningEnabled() && isOnTheServer && !record.isDirectory()) { + if (capabilities.privateLinkDetailsParamAvailable() || capabilities.filesSharing().sharing_roles) { + listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK_VERSIONS") + flagString + tr("Show file versions in web browser")); + } + } + + // Conflict files get conflict resolution actions + const bool isConflict = Utility::isConflictFile(fileData.folderRelativePath); + if (isConflict || !isOnTheServer) { + // Check whether this new file is in a read-only directory + const QFileInfo fileInfo(fileData.localPath); + const auto parentDir = fileData.parentFolder(); + const auto parentRecord = parentDir.journalRecord(); + const bool canAddToDir = !parentRecord._remotePerm.isNull() + && ((fileInfo.isFile() && parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile)) + || (fileInfo.isDir() && parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))); + const bool canChangeFile = !isOnTheServer + || (record._remotePerm.hasPermission(RemotePermissions::CanDelete) && record._remotePerm.hasPermission(RemotePermissions::CanMove) + && record._remotePerm.hasPermission(RemotePermissions::CanRename)); + + if (isConflict && canChangeFile) { + if (canAddToDir) { + if (isOnTheServer) { + // Conflict file that is already uploaded + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Rename…")); + } else { + // Local-only conflict file + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Rename and upload…")); + } + } else { + if (isOnTheServer) { + // Uploaded conflict file in read-only directory + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and rename…")); + } else { + if (isOnTheServer) { + // Uploaded conflict file in read-only directory + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and rename…")); + } else { + // Local-only conflict file in a read-only dir + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move, rename and upload…")); + } + } + listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete local changes")); + } + + // File in a read-only directory? + if (!isConflict && !isOnTheServer && !canAddToDir) { + listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and upload…")); + listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete")); + } + } + } + } else { + // we are a sync root + if (capabilities.privateLinkPropertyAvailable()) { + listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK::") + CommonStrings::showInWebBrowser()); + } + } + } + } // Find the common sync folder. // syncFolder will be null if files are in different folders. Folder *folder = nullptr; @@ -1012,73 +889,6 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe } } } - - // Some options only show for single files - if (files.size() == 1) { - FileData fileData = FileData::get(files.first()); - auto record = fileData.journalRecord(); - bool isOnTheServer = record.isValid(); - auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:"); - - if (fileData.folder && fileData.folder->accountState()->isConnected()) { - sendSharingContextMenuOptions(fileData, listener); - listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser")); - - // Add link to versions pane if possible - auto &capabilities = folder->accountState()->account()->capabilities(); - if (capabilities.versioningEnabled() - && capabilities.privateLinkDetailsParamAvailable() - && isOnTheServer - && !record.isDirectory()) { - listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK_VERSIONS") + flagString + tr("Show file versions in browser")); - } - - // Conflict files get conflict resolution actions - bool isConflict = Utility::isConflictFile(fileData.folderRelativePath); - if (isConflict || !isOnTheServer) { - // Check whether this new file is in a read-only directory - QFileInfo fileInfo(fileData.localPath); - auto parentDir = fileData.parentFolder(); - auto parentRecord = parentDir.journalRecord(); - bool canAddToDir = parentRecord._remotePerm.isNull() - || (fileInfo.isFile() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile)) - || (fileInfo.isDir() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories)); - bool canChangeFile = - !isOnTheServer - || (record._remotePerm.hasPermission(RemotePermissions::CanDelete) - && record._remotePerm.hasPermission(RemotePermissions::CanMove) - && record._remotePerm.hasPermission(RemotePermissions::CanRename)); - - if (isConflict && canChangeFile) { - if (canAddToDir) { - if (isOnTheServer) { - // Conflict file that is already uploaded - listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Rename...")); - } else { - // Local-only conflict file - listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Rename and upload...")); - } - } else { - if (isOnTheServer) { - // Uploaded conflict file in read-only directory - listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and rename...")); - } else { - // Local-only conflict file in a read-only dir - listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move, rename and upload...")); - } - } - listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete local changes")); - } - - // File in a read-only directory? - if (!isConflict && !isOnTheServer && !canAddToDir) { - listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and upload...")); - listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete")); - } - } - } - } - // File availability actions if (folder && folder->isReady() @@ -1121,8 +931,7 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe // TODO: Should be a submenu, should use icons auto makePinContextMenu = [&](bool makeAvailableLocally, bool freeSpace) { listener->sendMessage(QStringLiteral("MENU_SEPARATOR:d::")); - listener->sendMessage(QStringLiteral("MENU_ITEM:CURRENT_PIN:d:") - + Utility::vfsCurrentAvailabilityText(*combined)); + listener->sendMessage(QStringLiteral("MENU_ITEM:CURRENT_PIN:d:") + Utility::enumToDisplayName(combined.get())); listener->sendMessage(QStringLiteral("MENU_ITEM:MAKE_AVAILABLE_LOCALLY:") + (makeAvailableLocally ? QStringLiteral(":") : QStringLiteral("d:")) + Utility::vfsPinActionText()); @@ -1151,204 +960,15 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe listener->sendMessage(QStringLiteral("GET_MENU_ITEMS:END")); } -#if GUI_TESTING -void SocketApi::command_ASYNC_LIST_WIDGETS(const QSharedPointer &job) -{ - QString response; - for (auto &widget : allObjects(QApplication::allWidgets())) { - auto objectName = widget->objectName(); - if (!objectName.isEmpty()) { - response += objectName + ":" + widget->property("text").toString() + ", "; - } - } - job->resolve(response); -} - -void SocketApi::command_ASYNC_INVOKE_WIDGET_METHOD(const QSharedPointer &job) -{ - auto &arguments = job->arguments(); - - auto widget = findWidget(arguments["objectName"].toString()); - if (!widget) { - job->reject(QLatin1String("widget not found")); - return; - } - - QMetaObject::invokeMethod(widget, arguments["method"].toString().toUtf8().constData()); - job->resolve(); -} - -void SocketApi::command_ASYNC_GET_WIDGET_PROPERTY(const QSharedPointer &job) -{ - QString widgetName = job->arguments()[QLatin1String("objectName")].toString(); - auto widget = findWidget(widgetName); - if (!widget) { - QString message = QString(QLatin1String("Widget not found: 2: %1")).arg(widgetName); - job->reject(message); - return; - } - - auto propertyName = job->arguments()[QLatin1String("property")].toString(); - - auto segments = propertyName.split('.'); - - QObject *currentObject = widget; - QString value; - for (int i = 0; i < segments.count(); i++) { - auto segment = segments.at(i); - auto var = currentObject->property(segment.toUtf8().constData()); - - if (var.canConvert()) { - var.convert(QMetaType::QString); - value = var.value(); - break; - } - - auto tmpObject = var.value(); - if (tmpObject) { - currentObject = tmpObject; - } else { - QString message = QString(QLatin1String("Widget not found: 3: %1")).arg(widgetName); - job->reject(message); - return; - } - } - - job->resolve(value); -} - -void SocketApi::command_ASYNC_SET_WIDGET_PROPERTY(const QSharedPointer &job) -{ - auto &arguments = job->arguments(); - QString widgetName = arguments["objectName"].toString(); - auto widget = findWidget(widgetName); - if (!widget) { - QString message = QString(QLatin1String("Widget not found: 4: %1")).arg(widgetName); - job->reject(message); - return; - } - widget->setProperty(arguments["property"].toString().toUtf8().constData(), - arguments["value"]); - - job->resolve(); -} - -void SocketApi::command_ASYNC_WAIT_FOR_WIDGET_SIGNAL(const QSharedPointer &job) -{ - auto &arguments = job->arguments(); - QString widgetName = arguments["objectName"].toString(); - auto widget = findWidget(arguments["objectName"].toString()); - if (!widget) { - QString message = QString(QLatin1String("Widget not found: 5: %1")).arg(widgetName); - job->reject(message); - return; - } - - ListenerClosure *closure = new ListenerClosure([job]() { job->resolve("signal emitted"); }); - - auto signalSignature = arguments["signalSignature"].toString(); - signalSignature.prepend("2"); - auto utf8 = signalSignature.toUtf8(); - auto signalSignatureFinal = utf8.constData(); - connect(widget, signalSignatureFinal, closure, SLOT(closureSlot()), Qt::QueuedConnection); -} - -void SocketApi::command_ASYNC_TRIGGER_MENU_ACTION(const QSharedPointer &job) -{ - auto &arguments = job->arguments(); - - auto objectName = arguments["objectName"].toString(); - auto widget = findWidget(objectName); - if (!widget) { - QString message = QString(QLatin1String("Object not found: 1: %1")).arg(objectName); - job->reject(message); - return; - } - - auto children = widget->findChildren(); - for (auto childWidget : children) { - // foo is the popupwidget! - auto actions = childWidget->actions(); - for (auto action : actions) { - if (action->objectName() == arguments["actionName"].toString()) { - action->trigger(); - - job->resolve("action found"); - return; - } - } - } - - QString message = QString(QLatin1String("Action not found: 1: %1")).arg(arguments["actionName"].toString()); - job->reject(message); -} - -void SocketApi::command_ASYNC_ASSERT_ICON_IS_EQUAL(const QSharedPointer &job) -{ - auto widget = findWidget(job->arguments()[QLatin1String("queryString")].toString()); - if (!widget) { - QString message = QString(QLatin1String("Object not found: 6: %1")).arg(job->arguments()["queryString"].toString()); - job->reject(message); - return; - } - - auto propertyName = job->arguments()[QLatin1String("propertyPath")].toString(); - - auto segments = propertyName.split('.'); - - QObject *currentObject = widget; - QIcon value; - for (int i = 0; i < segments.count(); i++) { - auto segment = segments.at(i); - auto var = currentObject->property(segment.toUtf8().constData()); - - if (var.canConvert()) { - var.convert(QMetaType::QIcon); - value = var.value(); - break; - } - - auto tmpObject = var.value(); - if (tmpObject) { - currentObject = tmpObject; - } else { - job->reject(QString(QLatin1String("Icon not found: %1")).arg(propertyName)); - } - } - - auto iconName = job->arguments()[QLatin1String("iconName")].toString(); - if (value.name() == iconName) { - job->resolve(); - } else { - job->reject("iconName " + iconName + " does not match: " + value.name()); - } -} -#endif - QString SocketApi::buildRegisterPathMessage(const QString &path) { QFileInfo fi(path); - QString message = QLatin1String("REGISTER_PATH:"); + QString message = QStringLiteral("REGISTER_PATH:"); message.append(QDir::toNativeSeparators(fi.absoluteFilePath())); return message; } -void SocketApiJob::resolve(const QString &response) -{ - _socketListener->sendMessage(QStringLiteral("RESOLVE|") + _jobId + QLatin1Char('|') + response); -} - -void SocketApiJob::resolve(const QJsonObject &response) -{ - resolve(QJsonDocument { response }.toJson()); -} - -void SocketApiJob::reject(const QString &response) -{ - _socketListener->sendMessage(QStringLiteral("REJECT|") + _jobId + QLatin1Char('|') + response); -} - -SocketApiJobV2::SocketApiJobV2(const QSharedPointer &socketListener, const QByteArray &command, const QJsonObject &arguments) +SocketApiJobV2::SocketApiJobV2(const QSharedPointer &socketListener, const QString &command, const QJsonObject &arguments) : _socketListener(socketListener) , _command(command) , _jobId(arguments[QStringLiteral("id")].toString()) @@ -1373,7 +993,7 @@ void SocketApiJobV2::doFinish(const QJsonObject &obj) const if (!_warning.isEmpty()) { data[QStringLiteral("warning")] = _warning; } - _socketListener->sendMessage(_command + QStringLiteral("_RESULT:") + QJsonDocument(data).toJson(QJsonDocument::Compact)); + _socketListener->sendMessage(_command + QStringLiteral("_RESULT:") + QString::fromUtf8(QJsonDocument(data).toJson(QJsonDocument::Compact))); Q_EMIT finished(); } @@ -1388,5 +1008,3 @@ void SocketApiJobV2::setWarning(const QString &warning) } } // namespace OCC - -#include "socketapi.moc" diff --git a/src/gui/socketapi/socketapi.h b/src/gui/socketapi/socketapi.h index 84229b1dd0c..55556a75bb8 100644 --- a/src/gui/socketapi/socketapi.h +++ b/src/gui/socketapi/socketapi.h @@ -12,15 +12,16 @@ * for more details. */ -#ifndef SOCKETAPI_H -#define SOCKETAPI_H +#pragma once + +#include "gui/owncloudguilib.h" -#include "syncfileitem.h" #include "common/syncfilestatus.h" -#include "sharedialog.h" // for the ShareDialogStartPage #include "common/syncjournalfilerecord.h" +#include "libsync/account.h" +#include "syncfileitem.h" -#include "config.h" +#include #if defined(Q_OS_MAC) #include "socketapisocket_mac.h" @@ -32,14 +33,13 @@ using SocketApiSocket = QLocalSocket; class QUrl; class QLocalSocket; -class QStringList; namespace OCC { +class AccountState; class SyncFileStatus; class Folder; class SocketListener; -class SocketApiJob; class SocketApiJobV2; Q_DECLARE_LOGGING_CATEGORY(lcSocketApi) @@ -48,7 +48,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcSocketApi) * @brief The SocketApi class * @ingroup gui */ -class SocketApi : public QObject +class OWNCLOUDGUI_EXPORT SocketApi : public QObject { Q_OBJECT @@ -56,22 +56,29 @@ class SocketApi : public QObject explicit SocketApi(QObject *parent = nullptr); ~SocketApi() override; -public slots: + void startShellIntegration(); + +public Q_SLOTS: + // todo: registerAccount is called directly in accountState ctr but should be connected to AccountManager::accountAdded instead asap + void registerAccount(Account *a); + // unregisterAccount *is* connected to accountRemoved but hopefully we can get rid of the AccountState part and send the account itself + // asap. + void unregisterAccount(AccountState *state); void slotUpdateFolderView(Folder *f); - void slotUnregisterPath(const QString &alias); - void slotRegisterPath(const QString &alias); + void slotUnregisterPath(Folder *f); + void slotRegisterPath(Folder *f); void broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus); -signals: - void shareCommandReceived(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage); +Q_SIGNALS: + void shareCommandReceived(const QString &sharePath, const QString &localPath); -private slots: +private Q_SLOTS: void slotNewConnection(); void slotReadSocket(); - static void copyUrlToClipboard(const QString &link); - static void emailPrivateLink(const QString &link); - static void openPrivateLink(const QString &link); + static void copyUrlToClipboard(const QUrl &link); + static void emailPrivateLink(const QUrl &link); + static void openPrivateLink(const QUrl &link); private: // Helper structure for getting information on a file @@ -84,23 +91,21 @@ private slots: SyncJournalFileRecord journalRecord() const; FileData parentFolder() const; - // Relative path of the file locally, without any vfs suffix - QString folderRelativePathNoVfsSuffix() const; - - Folder *folder; + Folder *folder = nullptr; // Absolute path of the file locally. (May be a virtual file) QString localPath; // Relative path of the file locally, as in the DB. (May be a virtual file) QString folderRelativePath; // Path of the file on the server (In case of virtual file, it points to the actual file) QString serverRelativePath; + + bool isSyncFolder() const; + + bool isValid() const; }; void broadcastMessage(const QString &msg, bool doWait = false); - // opens share dialog, sends reply - void processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage); - Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener); Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener); @@ -110,8 +115,6 @@ private slots: // The context menu actions Q_INVOKABLE void command_SHARE(const QString &localFile, SocketListener *listener); - Q_INVOKABLE void command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener); - Q_INVOKABLE void command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *listener); @@ -121,9 +124,7 @@ private slots: Q_INVOKABLE void command_DELETE_ITEM(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_MOVE_ITEM(const QString &localFile, SocketListener *listener); - // External sync - Q_INVOKABLE void command_V2_LIST_ACCOUNTS(const QSharedPointer &job) const; - Q_INVOKABLE void command_V2_UPLOAD_FILES_FROM(const QSharedPointer &job) const; + Q_INVOKABLE void command_OPEN_APP_LINK(const QString &localFile, SocketListener *listener); // Sends the id and the client icon as PNG image (base64 encoded) in Json key "png" // e.g. { "id" : "1", "arguments" : { "png" : "hswehs343dj8..." } } or an error message in key "error" @@ -133,7 +134,7 @@ private slots: Q_INVOKABLE void command_V2_GET_CLIENT_ICON(const QSharedPointer &job) const; // Fetch the private link and call targetFun - void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function &targetFun); + void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function &targetFun); /** Sends translated/branded strings that may be useful to the integration */ Q_INVOKABLE void command_GET_STRINGS(const QString &argument, SocketListener *listener); @@ -150,22 +151,14 @@ private slots: */ Q_INVOKABLE void command_GET_MENU_ITEMS(const QString &argument, SocketListener *listener); -#if GUI_TESTING - Q_INVOKABLE void command_ASYNC_ASSERT_ICON_IS_EQUAL(const QSharedPointer &job); - Q_INVOKABLE void command_ASYNC_LIST_WIDGETS(const QSharedPointer &job); - Q_INVOKABLE void command_ASYNC_INVOKE_WIDGET_METHOD(const QSharedPointer &job); - Q_INVOKABLE void command_ASYNC_GET_WIDGET_PROPERTY(const QSharedPointer &job); - Q_INVOKABLE void command_ASYNC_SET_WIDGET_PROPERTY(const QSharedPointer &job); - Q_INVOKABLE void command_ASYNC_WAIT_FOR_WIDGET_SIGNAL(const QSharedPointer &job); - Q_INVOKABLE void command_ASYNC_TRIGGER_MENU_ACTION(const QSharedPointer &job); -#endif - QString buildRegisterPathMessage(const QString &path); - QSet _registeredAliases; + QString _socketPath; + QSet _registeredFolders; + // todo: we really should not keep any pointer to the account, as we only ever need the defaultSyncRoot, but this needs to go into a future + // refactoring to ensure full testing of that change. IMO we could just store the defaultSyncRoot alone, no uuid or account required at all + QHash> _registeredAccounts; QMap> _listeners; SocketApiServer _localServer; }; } - -#endif // SOCKETAPI_H diff --git a/src/gui/socketapi/socketapi_p.h b/src/gui/socketapi/socketapi_p.h index fd7ee953d10..4d92bef1e26 100644 --- a/src/gui/socketapi/socketapi_p.h +++ b/src/gui/socketapi/socketapi_p.h @@ -14,19 +14,13 @@ * for more details. */ -#ifndef SOCKETAPI_P_H -#define SOCKETAPI_P_H +#pragma once -#include #include #include -#include #include -#include -#include - namespace OCC { class BloomFilter @@ -42,16 +36,12 @@ class BloomFilter { } - void storeHash(uint hash) + void storeHash(size_t hash) { hashBits.setBit((hash & 0xFFFF) % NumBits); hashBits.setBit((hash >> 16) % NumBits); } - bool isHashMaybeStored(uint hash) const - { - return hashBits.testBit((hash & 0xFFFF) % NumBits) - && hashBits.testBit((hash >> 16) % NumBits); - } + bool isHashMaybeStored(size_t hash) const { return hashBits.testBit((hash & 0xFFFF) % NumBits) && hashBits.testBit((hash >> 16) % NumBits); } private: QBitArray hashBits; @@ -77,78 +67,29 @@ class SocketListener sendMessage(QStringLiteral("ERROR:") + message, doWait); } - void sendMessageIfDirectoryMonitored(const QString &message, uint systemDirectoryHash) const + void sendMessageIfDirectoryMonitored(const QString &message, size_t systemDirectoryHash) const { if (_monitoredDirectoriesBloomFilter.isHashMaybeStored(systemDirectoryHash)) sendMessage(message, false); } - void registerMonitoredDirectory(uint systemDirectoryHash) - { - _monitoredDirectoriesBloomFilter.storeHash(systemDirectoryHash); - } + void registerMonitoredDirectory(size_t systemDirectoryHash) { _monitoredDirectoriesBloomFilter.storeHash(systemDirectoryHash); } private: BloomFilter _monitoredDirectoriesBloomFilter; }; -class ListenerClosure : public QObject -{ - Q_OBJECT -public: - using CallbackFunction = std::function; - ListenerClosure(CallbackFunction callback) - : callback_(callback) - { - } - -public slots: - void closureSlot() - { - callback_(); - deleteLater(); - } - -private: - CallbackFunction callback_; -}; - -class SocketApiJob : public QObject -{ - Q_OBJECT -public: - explicit SocketApiJob(const QString &jobId, const QSharedPointer &socketListener, const QJsonObject &arguments) - : _jobId(jobId) - , _socketListener(socketListener) - , _arguments(arguments) - { - } - - void resolve(const QString &response = QString()); - - void resolve(const QJsonObject &response); - - const QJsonObject &arguments() { return _arguments; } - - void reject(const QString &response); - -protected: - QString _jobId; - QSharedPointer _socketListener; - QJsonObject _arguments; -}; - class SocketApiJobV2 : public QObject { Q_OBJECT public: - explicit SocketApiJobV2(const QSharedPointer &socketListener, const QByteArray &command, const QJsonObject &arguments); + explicit SocketApiJobV2(const QSharedPointer &socketListener, const QString &command, const QJsonObject &arguments); void success(const QJsonObject &response) const; void failure(const QString &error) const; const QJsonObject &arguments() const { return _arguments; } - QByteArray command() const { return _command; } + QString command() const { return _command; } QString warning() const; void setWarning(const QString &warning); @@ -160,7 +101,7 @@ class SocketApiJobV2 : public QObject void doFinish(const QJsonObject &obj) const; QSharedPointer _socketListener; - const QByteArray _command; + const QString _command; QString _jobId; QJsonObject _arguments; QString _warning; @@ -168,5 +109,3 @@ class SocketApiJobV2 : public QObject } Q_DECLARE_METATYPE(OCC::SocketListener *) - -#endif // SOCKETAPI_P_H diff --git a/src/gui/socketapi/socketapisocket_mac.h b/src/gui/socketapi/socketapisocket_mac.h index cbacdf02893..b508e00ba79 100644 --- a/src/gui/socketapi/socketapisocket_mac.h +++ b/src/gui/socketapi/socketapisocket_mac.h @@ -35,7 +35,7 @@ class SocketApiSocket : public QIODevice qint64 bytesAvailable() const override; bool canReadLine() const override; -signals: +Q_SIGNALS: void disconnected(); private: @@ -58,7 +58,7 @@ class SocketApiServer : public QObject static bool removeServer(const QString &) { return false; } -signals: +Q_SIGNALS: void newConnection(); private: diff --git a/src/gui/socketapi/socketapisocket_mac.mm b/src/gui/socketapi/socketapisocket_mac.mm index 938494e2117..0667a954f47 100644 --- a/src/gui/socketapi/socketapisocket_mac.mm +++ b/src/gui/socketapi/socketapisocket_mac.mm @@ -90,7 +90,7 @@ - (void)sendMessage:(NSData *)msg { if (self.wrapper) { self.wrapper->inBuffer += QByteArray::fromRawNSData(msg); - emit self.wrapper->q_ptr->readyRead(); + Q_EMIT self.wrapper->q_ptr->readyRead(); } } @@ -101,7 +101,7 @@ - (void)connectionDidDie:(NSNotification *)notification if (self.wrapper) { self.wrapper->disconnectRemote(); - emit self.wrapper->q_ptr->disconnected(); + Q_EMIT self.wrapper->q_ptr->disconnected(); } } @end @@ -126,7 +126,7 @@ - (void)registerClient:(NSDistantObject *)remoteEnd SocketApiSocketPrivate *socketPrivate = new SocketApiSocketPrivate(remoteEnd); SocketApiSocket *socket = new SocketApiSocket(server, socketPrivate); self.wrapper->pendingConnections.append(socket); - emit server->newConnection(); + Q_EMIT server->newConnection(); [remoteEnd registerTransmitter:socketPrivate->localEnd]; } @@ -181,7 +181,7 @@ - (void)registerClient:(NSDistantObject *)remoteEnd } @catch (NSException *) { // connectionDidDie can be notified too late, also interpret any sending exception as a disconnection. d->disconnectRemote(); - emit disconnected(); + Q_EMIT disconnected(); return -1; } } diff --git a/src/gui/socketapi/socketuploadjob.cpp b/src/gui/socketapi/socketuploadjob.cpp deleted file mode 100644 index 5a98a273405..00000000000 --- a/src/gui/socketapi/socketuploadjob.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (C) by Hannah von Reth - * - * 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 "socketuploadjob.h" -#include "socketapi_p.h" - -#include "application.h" -#include "accountmanager.h" -#include "common/syncjournaldb.h" -#include "csync_exclude.h" -#include "progressdispatcher.h" -#include "syncengine.h" -#include "theme.h" - -#include -#include -#include -#include -#include -#include - -using namespace OCC; - -namespace { -const QString backupTagNameC() -{ - return QStringLiteral("backup_finished"); -} -const QUrl tagUrl(const OCC::AccountPtr &account) -{ - return Utility::concatUrlPath(account->url(), QStringLiteral("remote.php/dav/systemtags")); -} -} - -SocketUploadJob::SocketUploadJob(const QSharedPointer &job) - : _apiJob(job) -{ - connect(job.data(), &SocketApiJobV2::finished, this, &SocketUploadJob::deleteLater); -} - -void SocketUploadJob::prepareTag(const AccountPtr &account) -{ - auto tagJob = new OCC::SimpleNetworkJob(account, this); - connect(tagJob, &OCC::SimpleNetworkJob::finishedSignal, this, [account, this] { - auto propfindJob = new OCC::LsColJob(account, tagUrl(account), this); - propfindJob->setProperties({ QByteArrayLiteral("http://owncloud.org/ns:display-name"), QByteArrayLiteral("http://owncloud.org/ns:id") }); - - connect(propfindJob, &LsColJob::directoryListingIterated, this, [this](const QString &, const QMap &data) { - if (data[QStringLiteral("display-name")] == backupTagNameC()) { - _finisedTagId = data[QStringLiteral("id")].toInt(); - } - }); - connect(propfindJob, &LsColJob::finishedWithError, this, [this] { - fail(tr("Failed to rerieve tags")); - }); - propfindJob->start(); - }); - const QJsonObject json({ { QStringLiteral("name"), backupTagNameC() } }); - QNetworkRequest req; - tagJob->prepareRequest(QByteArrayLiteral("POST"), tagUrl(account), req, json); - tagJob->start(); -} - -void SocketUploadJob::start() -{ - _localPath = _apiJob->arguments()[QLatin1String("localPath")].toString(); - auto remotePath = _apiJob->arguments()[QLatin1String("remotePath")].toString(); - if (!remotePath.startsWith(QLatin1Char('/'))) { - remotePath = QLatin1Char('/') + remotePath; - } - - const auto pattern = _apiJob->arguments()[QLatin1String("pattern")].toString(); - const auto excludes = _apiJob->arguments()[QLatin1String("excludes")].toArray(); - const auto accname = _apiJob->arguments()[QLatin1String("account")][QLatin1String("name")].toString(); - const auto accUUID = QUuid::fromString(_apiJob->arguments()[QLatin1String("account")][QLatin1String("uuid")].toString()); - AccountStatePtr account; - if (accUUID.isNull()) { - _apiJob->setWarning("Using the name as identifier is deprecated, please use the uuid"); - account = AccountManager::instance()->account(accname); - } else { - account = AccountManager::instance()->account(accUUID); - } - - logMessage(_localPath, tr("Backup of %1 started").arg(QDir::toNativeSeparators(_localPath))); - - if (!account) { - fail(tr("Failed to find %1").arg(QString::fromUtf8(QJsonDocument(_apiJob->arguments()[QLatin1String("account")].toObject()).toJson()))); - return; - } - - if (!QFileInfo(_localPath).isAbsolute()) { - fail(tr("Local path must be a an absolute path")); - return; - } - auto tmp = new QTemporaryFile(); - if (!tmp->open()) { - fail(tr("Failed to create temporary database")); - return; - } - - auto db = new SyncJournalDb(tmp->fileName(), this); - auto engine = new SyncEngine(account->account(), _localPath.endsWith(QLatin1Char('/')) ? _localPath : _localPath + QLatin1Char('/'), remotePath, db); - engine->setParent(db); - tmp->setParent(db); - - for (const auto &i : excludes) { - engine->excludedFiles().addManualExclude(i.toString()); - } - - connect(engine, &OCC::SyncEngine::transmissionProgress, this, [this](const ProgressInfo &info) { - Q_EMIT ProgressDispatcher::instance()->progressInfo(_localPath, info); - }); - connect(engine, &OCC::SyncEngine::itemCompleted, this, [this](const OCC::SyncFileItemPtr item) { - if (item->_errorString.isEmpty()) { - _syncedFiles.append(item->_file); - } else { - _errorFiles.append(QStringLiteral("%1: %2").arg(item->_file, item->_errorString)); - } - }); - - connect(engine, &OCC::SyncEngine::finished, this, [engine, this](bool ok) { - if (ok) { - auto tagJob = new OCC::SimpleNetworkJob(engine->account(), this); - connect(tagJob, &OCC::SimpleNetworkJob::finishedSignal, this, [tagJob, this] { - if (tagJob->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 201) { - logMessage(_localPath, tr("Backup of %1 succeeded").arg(QDir::toNativeSeparators(_localPath))); - _apiJob->success({ { QStringLiteral("localPath"), _localPath }, { QStringLiteral("syncedFiles"), QJsonArray::fromStringList(_syncedFiles) } }); - } else { - fail(tr("Failed to set success tag")); - } - }); - OC_ASSERT(_finisedTagId > 0); - tagJob->prepareRequest(QByteArrayLiteral("PUT"), Utility::concatUrlPath(engine->account()->url(), QStringLiteral("remote.php/dav/systemtags-relations/files/%1/%2").arg(_backupFileId, QString::number(_finisedTagId)))); - tagJob->start(); - } else { - fail(tr("Failed to create backup: %1").arg(_errorFiles.join(", "))); - } - }); - connect(engine, &OCC::SyncEngine::syncError, this, [this](const QString &error, ErrorCategory) { - fail(error); - }); - - auto opt = engine->syncOptions(); - opt.setFilePattern(pattern); - if (!opt.fileRegex().isValid()) { - fail(opt.fileRegex().errorString()); - return; - } - engine->setSyncOptions(opt); - - prepareTag(account->account()); - - // create the dir, fail if it already exists - auto mkdir = new OCC::MkColJob(engine->account(), remotePath); - connect(mkdir, &OCC::MkColJob::finishedWithoutError, this, [engine, remotePath, this]{ - - // we need the int file id without the instance id so we can't use the OC-FileId - auto propfindJob = new PropfindJob(engine->account(), remotePath, this); - propfindJob->setProperties({ QByteArrayLiteral("http://owncloud.org/ns:fileid") }); - - connect(propfindJob, &PropfindJob::result, this, [engine, this](const QMap &data) { - _backupFileId = data[QStringLiteral("fileid")].toUtf8(); - engine->startSync(); - }); - connect(propfindJob, &PropfindJob::finishedWithError, this, [this] { - fail(tr("Failed to file id tags")); - }); - propfindJob->start(); - }); - connect(mkdir, &OCC::MkColJob::finishedWithError, this, [remotePath, this](QNetworkReply *reply) { - if (reply->error() == 202) { - fail(QStringLiteral("Destination %1 already exists").arg(remotePath)); - } else { - fail(reply->errorString()); - } - }); - mkdir->start(); -} - -void SocketUploadJob::fail(const QString &error) -{ - logMessage(_localPath, tr("Backup of %1 failed with: %2").arg(QDir::toNativeSeparators(_localPath), error), false); - _apiJob->failure(error); -} - -void SocketUploadJob::logMessage(const QString &localPath, const QString &message, bool ok) -{ - auto item = SyncFileItemPtr::create(); - QIcon icon; // null icon will cause the default icon - if (ok) { - item->_status = SyncFileItem::Success; - item->_messageString = message; - } else { - icon = Theme::instance()->syncStateIcon(SyncResult::Error); - item->_status = SyncFileItem::FatalError; - item->_errorString = message; - } - ocApp()->gui()->slotShowTrayMessage(tr("%1 backup").arg(Theme::instance()->appNameGUI()), message, icon); - item->_responseTimeStamp = QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8(); - Q_EMIT ProgressDispatcher::instance()->itemCompleted(QDir::toNativeSeparators(localPath), item); -} diff --git a/src/gui/socketapi/socketuploadjob.h b/src/gui/socketapi/socketuploadjob.h deleted file mode 100644 index 557cabfaa04..00000000000 --- a/src/gui/socketapi/socketuploadjob.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) by Hannah von Reth - * - * 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 -#include "socketapi.h" -#include "account.h" - -namespace OCC { - -class SyncJournalDb; -class SyncEngine; - -class SocketUploadJob : public QObject -{ - Q_OBJECT -public: - SocketUploadJob(const QSharedPointer &job); - void start(); - -private: - void fail(const QString &error); - - // create a fake SyncFileItemPtr to display a message in the protocol - void logMessage(const QString &localPath, const QString &message, bool ok = true); - - QString _localPath; - QSharedPointer _apiJob; - QStringList _syncedFiles; - QStringList _errorFiles; - int _finisedTagId = -1; - QByteArray _backupFileId; - - void prepareTag(const AccountPtr &account); -}; -} diff --git a/src/gui/spaces/CMakeLists.txt b/src/gui/spaces/CMakeLists.txt new file mode 100644 index 00000000000..bd54549b5ae --- /dev/null +++ b/src/gui/spaces/CMakeLists.txt @@ -0,0 +1,7 @@ +target_sources(owncloudGui + PRIVATE + spacesmodel.cpp + spacesbrowser.cpp + spacesbrowser.ui + spaceimageprovider.cpp +) diff --git a/src/gui/spaces/qml/SpaceDelegate.qml b/src/gui/spaces/qml/SpaceDelegate.qml new file mode 100644 index 00000000000..8c506ac431c --- /dev/null +++ b/src/gui/spaces/qml/SpaceDelegate.qml @@ -0,0 +1,87 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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. + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: spaceDelegate + property alias title: title.text + property alias description: description.text + property alias descriptionWrapMode: description.wrapMode + property alias imageSource: image.source + property alias statusSource: statusIcon.source + + default property alias content: colLayout.data + + ColumnLayout { + anchors.fill: parent + spacing: 10 + + RowLayout { + Layout.alignment: Qt.AlignTop + Layout.fillWidth: true + + Pane { + Accessible.ignored: true + Layout.preferredHeight: normalSize - 20 + Layout.preferredWidth: normalSize - 20 + Layout.alignment: Qt.AlignTop + background: Rectangle { + color: spaceDelegate.palette.alternateBase + } + Image { + id: image + anchors.fill: parent + fillMode: Image.PreserveAspectFit + sourceSize.width: width + sourceSize.height: height + } + } + ColumnLayout { + id: colLayout + spacing: 6 + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.fillWidth: true + + RowLayout { + Layout.fillWidth: true + Image { + id: statusIcon + Layout.preferredHeight: 16 + Layout.preferredWidth: 16 + visible: statusSource + sourceSize.width: width + sourceSize.height: height + } + Label { + id: title + Accessible.ignored: true + Layout.fillWidth: true + font.bold: true + font.pointSize: 15 + elide: Text.ElideRight + } + } + Label { + id: description + Accessible.ignored: true + Layout.fillWidth: true + elide: Text.ElideRight + } + } + } + } +} diff --git a/src/gui/spaces/qml/SpacesView.qml b/src/gui/spaces/qml/SpacesView.qml new file mode 100644 index 00000000000..8226b4dc1c5 --- /dev/null +++ b/src/gui/spaces/qml/SpacesView.qml @@ -0,0 +1,141 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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. + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import org.ownCloud.gui 1.0 +import org.ownCloud.libsync 1.0 + +Pane { + id: spacesView + // TODO: not cool + readonly property real normalSize: 170 + + readonly property SpacesBrowser spacesBrowser: ocContext + readonly property OCQuickWidget widget: ocQuickWidget + + Accessible.role: Accessible.List + Accessible.name: qsTr("Spaces") + + ScrollView { + id: scrollView + anchors.fill: parent + clip: true + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + + Connections { + target: widget + + function onFocusFirst() { + listView.forceActiveFocus(Qt.TabFocusReason); + } + + function onFocusLast() { + listView.forceActiveFocus(Qt.TabFocusReason); + } + } + + ListView { + id: listView + anchors.fill: parent + spacing: 20 + focus: true + boundsBehavior: Flickable.StopAtBounds + + model: spacesBrowser.model + + Component.onCompleted: { + // clear the selection delayed, else the palette is messed up + currentIndex = -1; + } + + onCurrentItemChanged: { + if (currentItem) { + spacesBrowser.currentSpace = currentItem.space; + listView.currentItem.forceActiveFocus(Qt.TabFocusReason); + } else { + // clear the selected item + spacesBrowser.currentSpace = null; + } + } + + delegate: FocusScope { + id: spaceDelegate + required property string name + required property string subtitle + required property string accessibleDescription + required property Space space + + required property int index + + width: ListView.view.width - scrollView.ScrollBar.vertical.width - 10 + + implicitHeight: normalSize + + Pane { + id: delegatePane + + anchors.fill: parent + + Accessible.name: spaceDelegate.accessibleDescription + Accessible.role: Accessible.ListItem + Accessible.selectable: true + Accessible.selected: space === spacesBrowser.currentSpace + + clip: true + + activeFocusOnTab: true + focus: true + + Keys.onBacktabPressed: { + widget.parentFocusWidget.focusPrevious(); + } + + Keys.onTabPressed: { + widget.parentFocusWidget.focusNext(); + } + + background: Rectangle { + color: spaceDelegate.ListView.isCurrentItem ? scrollView.palette.highlight : scrollView.palette.base + } + + RowLayout { + anchors.fill: parent + + spacing: 10 + SpaceDelegate { + Layout.fillWidth: true + Layout.fillHeight: true + + title: spaceDelegate.name + description: spaceDelegate.subtitle + imageSource: spaceDelegate.space.image.qmlImageUrl + descriptionWrapMode: Label.WordWrap + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + spaceDelegate.ListView.view.currentIndex = spaceDelegate.index; + } + } + } + } + } + } +} diff --git a/src/gui/spaces/spaceimageprovider.cpp b/src/gui/spaces/spaceimageprovider.cpp new file mode 100644 index 00000000000..1a0f6ddce34 --- /dev/null +++ b/src/gui/spaces/spaceimageprovider.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "spaceimageprovider.h" +#include "libsync/graphapi/spacesmanager.h" +#include "resources/qmlresources.h" +#include "resources/resources.h" + +using namespace OCC; +using namespace Spaces; + +SpaceImageProvider::SpaceImageProvider(GraphApi::SpacesManager *spacesMgr) + : QQuickImageProvider(QQuickImageProvider::Pixmap, QQuickImageProvider::ForceAsynchronousImageLoading) + , _spacesManager(spacesMgr) +{ +} + +QPixmap SpaceImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + if (!_spacesManager) + return {}; + + QIcon icon; + if (id == QLatin1String("placeholder")) { + icon = Resources::getCoreIcon(QStringLiteral("defaultSpaceImage")); + } else { + const auto ids = id.split(QLatin1Char('/')); + const auto *space = _spacesManager->space(ids.last()); + if (space) { + icon = space->image()->image(); + } + } + Q_ASSERT(!icon.isNull()); + return Resources::pixmap(requestedSize, icon, QIcon::Normal, size); +} diff --git a/src/gui/spaces/spaceimageprovider.h b/src/gui/spaces/spaceimageprovider.h new file mode 100644 index 00000000000..9e1905e3767 --- /dev/null +++ b/src/gui/spaces/spaceimageprovider.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "spacesmanager.h" +#include + +#include + +namespace OCC::Spaces { +class SpaceImageProvider : public QQuickImageProvider +{ + Q_OBJECT +public: + SpaceImageProvider(GraphApi::SpacesManager *spacesMgr); + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; + + +private: + QPointer _spacesManager; +}; + +} diff --git a/src/gui/spaces/spacesbrowser.cpp b/src/gui/spaces/spacesbrowser.cpp new file mode 100644 index 00000000000..f1d7d6b7b94 --- /dev/null +++ b/src/gui/spaces/spacesbrowser.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "spacesbrowser.h" +#include "ui_spacesbrowser.h" + +#include "spacesmodel.h" + +#include "gui/accountmanager.h" +#include "gui/spaces/spaceimageprovider.h" + +#include +#include +#include + +using namespace OCC; +using namespace OCC::Spaces; + +namespace { +class SpaceFilter : public QSortFilterProxyModel +{ + using QSortFilterProxyModel::QSortFilterProxyModel; + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override + { + auto index = sourceModel()->index(sourceRow, 0, sourceParent); + return index.data(static_cast(SpacesModel::Roles::Enabled)).toBool() && !index.data(static_cast(SpacesModel::Roles::IsSynced)).toBool(); + } +}; +} + +SpacesBrowser::SpacesBrowser(QWidget *parent) + : QWidget(parent) + , ui(new Ui::SpacesBrowser) +{ + ui->setupUi(this); + + _model = new SpacesModel(this); + _sortModel = new SpaceFilter(this); + _sortModel->setSourceModel(_model); + _sortModel->setSortRole(static_cast(SpacesModel::Roles::Priority)); + _sortModel->sort(0, Qt::DescendingOrder); + + ui->quickWidget->setOCContext(QUrl(QStringLiteral("qrc:/qt/qml/org/ownCloud/gui/spaces/qml/SpacesView.qml")), this); + + setFocusProxy(ui->quickWidget); +} + +SpacesBrowser::~SpacesBrowser() +{ + delete ui; +} + +void SpacesBrowser::setSpacesManager(OCC::GraphApi::SpacesManager *spacesMgr) +{ + if (spacesMgr) { + _model->setSpacesManager(spacesMgr); + ui->quickWidget->engine()->addImageProvider(QStringLiteral("space"), new Spaces::SpaceImageProvider(spacesMgr)); + } +} + +GraphApi::Space *SpacesBrowser::currentSpace() +{ + return _currentSpace; +} + +QSortFilterProxyModel *SpacesBrowser::model() +{ + return _sortModel; +} diff --git a/src/gui/spaces/spacesbrowser.h b/src/gui/spaces/spacesbrowser.h new file mode 100644 index 00000000000..73cf41a958d --- /dev/null +++ b/src/gui/spaces/spacesbrowser.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "gui/qmlutils.h" + +#include "libsync/account.h" +#include "libsync/graphapi/space.h" + +#include +#include + +namespace Ui { +class SpacesBrowser; +} + +namespace OCC::Spaces { +class SpacesModel; + +class SpacesBrowser : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QSortFilterProxyModel *model MEMBER _sortModel READ model CONSTANT) + Q_PROPERTY(GraphApi::Space *currentSpace MEMBER _currentSpace READ currentSpace NOTIFY currentSpaceChanged) + QML_ELEMENT + QML_UNCREATABLE("C++ only") + OC_DECLARE_WIDGET_FOCUS +public: + explicit SpacesBrowser(QWidget *parent = nullptr); + ~SpacesBrowser(); + + void setSpacesManager(OCC::GraphApi::SpacesManager *spacesMgr); + + GraphApi::Space *currentSpace(); + + QSortFilterProxyModel *model(); + +Q_SIGNALS: + void currentSpaceChanged(GraphApi::Space *space); + +private: + ::Ui::SpacesBrowser *ui; + + SpacesModel *_model; + QSortFilterProxyModel *_sortModel; + GraphApi::Space *_currentSpace = nullptr; +}; + +} diff --git a/src/gui/spaces/spacesbrowser.ui b/src/gui/spaces/spacesbrowser.ui new file mode 100644 index 00000000000..7d0af54b730 --- /dev/null +++ b/src/gui/spaces/spacesbrowser.ui @@ -0,0 +1,48 @@ + + + SpacesBrowser + + + + 0 + 0 + 400 + 300 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::FocusPolicy::StrongFocus + + + + + + + + OCC::QmlUtils::OCQuickWidget + QWidget +
    gui/qmlutils.h
    + 1 +
    +
    + + +
    diff --git a/src/gui/spaces/spacesmodel.cpp b/src/gui/spaces/spacesmodel.cpp new file mode 100644 index 00000000000..4b7ba570947 --- /dev/null +++ b/src/gui/spaces/spacesmodel.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "spacesmodel.h" + +#include "gui/folderman.h" +#include "libsync/graphapi/spacesmanager.h" + +#include + +using namespace OCC::Spaces; + +SpacesModel::SpacesModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +int SpacesModel::rowCount(const QModelIndex &parent) const +{ + Q_ASSERT(checkIndex(parent)); + if (parent.isValid()) + return 0; + return static_cast(_spacesList.size()); +} + +QVariant SpacesModel::data(const QModelIndex &index, int role) const +{ + Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)); + + auto *space = _spacesList.at(index.row()); + switch (static_cast(role)) { + case Roles::IsSynced: + return FolderMan::instance()->isSpaceSynced(space); + case Roles::Name: + return space->displayName(); + case Roles::Subtitle: + return space->description(); + case Roles::WebUrl: + return space->webUrl(); + case Roles::WebDavUrl: + return space->webDavUrl(); + case Roles::Priority: + // everything will be sorted in descending order, multiply the priority by 100 and prefer A over Z by appling a negative factor + return QVariant::fromValue( + space->sortPriority() * 100 - (space->displayName().isEmpty() ? 0 : static_cast(space->displayName().at(0).toLower().unicode()))); + case Roles::Space: + return QVariant::fromValue(space); + case Roles::Enabled: + return !space->disabled(); + case Roles::AccessibleDescriptionRole: + return QStringLiteral("%1,%2").arg(space->displayName(), space->description()); + } + return {}; +} + +void SpacesModel::setSpacesManager(GraphApi::SpacesManager *spacesManager) +{ + Q_ASSERT(!_spacesManager); + _spacesManager = spacesManager; + beginResetModel(); + _spacesList = _spacesManager->spaces(); + endResetModel(); + connect(_spacesManager, &GraphApi::SpacesManager::updated, this, [this] { + const auto newSpaces = _spacesManager->spaces(); + if (_spacesList != newSpaces) { + beginResetModel(); + _spacesList = newSpaces; + endResetModel(); + } + }); + + connect(_spacesManager, &GraphApi::SpacesManager::spaceChanged, this, [this](GraphApi::Space *space) { + const auto row = _spacesList.indexOf(space); + if (row != -1) { + const auto index = createIndex(row, 0); + Q_EMIT dataChanged(index, index); + } + }); +} +QHash SpacesModel::roleNames() const +{ + return { + {static_cast(Roles::Name), "name"}, + {static_cast(Roles::Subtitle), "subtitle"}, + {static_cast(Roles::Space), "space"}, + {static_cast(Roles::AccessibleDescriptionRole), "accessibleDescription"}, + }; +} diff --git a/src/gui/spaces/spacesmodel.h b/src/gui/spaces/spacesmodel.h new file mode 100644 index 00000000000..5a5ea10f201 --- /dev/null +++ b/src/gui/spaces/spacesmodel.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 + +namespace OCC::GraphApi { +class SpacesManager; +class Space; +}; + +namespace OCC::Spaces { +class SpacesModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum class Roles { + AccessibleDescriptionRole = Qt::AccessibleDescriptionRole, + IsSynced = Qt::UserRole + 1, + Name, + Subtitle, + WebUrl, + WebDavUrl, + Priority, + Enabled, + Space, + }; + Q_ENUM(Roles) + explicit SpacesModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + + void setSpacesManager(GraphApi::SpacesManager *spacesManager); + +private: + GraphApi::SpacesManager *_spacesManager = nullptr; + QVector _spacesList; +}; +} diff --git a/src/gui/sslbutton.cpp b/src/gui/sslbutton.cpp deleted file mode 100644 index 8dfd43835c4..00000000000 --- a/src/gui/sslbutton.cpp +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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 "sslbutton.h" -#include "account.h" -#include "accountstate.h" -#include "theme.h" -#include "guiutility.h" - -#include -#include -#include -#include -#include -#include - -namespace OCC { - -Q_LOGGING_CATEGORY(lcSsl, "gui.ssl", QtInfoMsg) - -SslButton::SslButton(QWidget *parent) - : QToolButton(parent) -{ - setPopupMode(QToolButton::InstantPopup); - setAutoRaise(true); - - _menu = new QMenu(this); - QObject::connect(_menu, &QMenu::aboutToShow, - this, &SslButton::slotUpdateMenu); - setMenu(_menu); -} - -static QString addCertDetailsField(const QString &key, const QString &value) -{ - if (value.isEmpty()) - return QString(); - - return QLatin1String("") + key - + QLatin1String("") + value - + QLatin1String(""); -} - - -// necessary indication only, not sufficient for primary validation! -static bool isSelfSigned(const QSslCertificate &certificate) -{ - return certificate.issuerInfo(QSslCertificate::CommonName) == certificate.subjectInfo(QSslCertificate::CommonName) - && certificate.issuerInfo(QSslCertificate::OrganizationalUnitName) == certificate.subjectInfo(QSslCertificate::OrganizationalUnitName); -} - -QMenu *SslButton::buildCertMenu(QMenu *parent, const QSslCertificate &cert, - const QList &userApproved, int pos, const QList &systemCaCertificates) -{ - QString cn = QStringList(cert.subjectInfo(QSslCertificate::CommonName)).join(QChar(';')); - QString ou = QStringList(cert.subjectInfo(QSslCertificate::OrganizationalUnitName)).join(QChar(';')); - QString org = QStringList(cert.subjectInfo(QSslCertificate::Organization)).join(QChar(';')); - QString country = QStringList(cert.subjectInfo(QSslCertificate::CountryName)).join(QChar(';')); - QString state = QStringList(cert.subjectInfo(QSslCertificate::StateOrProvinceName)).join(QChar(';')); - QString issuer = QStringList(cert.issuerInfo(QSslCertificate::CommonName)).join(QChar(';')); - if (issuer.isEmpty()) - issuer = QStringList(cert.issuerInfo(QSslCertificate::OrganizationalUnitName)).join(QChar(';')); - QString sha1 = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha1).toHex(), false); - QByteArray sha265hash = cert.digest(QCryptographicHash::Sha256).toHex(); - QString sha256escaped = - Utility::escape(Utility::formatFingerprint(sha265hash.left(sha265hash.length() / 2), false)) - + QLatin1String("
    ") - + Utility::escape(Utility::formatFingerprint(sha265hash.mid(sha265hash.length() / 2), false)); - QString serial = QString::fromUtf8(cert.serialNumber()); - QString effectiveDate = cert.effectiveDate().date().toString(); - QString expiryDate = cert.expiryDate().date().toString(); - QString sna = QStringList(cert.subjectAlternativeNames().values()).join(" "); - - QString details; - QTextStream stream(&details); - - stream << QLatin1String(""); - - stream << tr("

    Certificate Details

    "); - - stream << QLatin1String(""); - stream << addCertDetailsField(tr("Common Name (CN):"), Utility::escape(cn)); - stream << addCertDetailsField(tr("Subject Alternative Names:"), Utility::escape(sna).replace(" ", "
    ")); - stream << addCertDetailsField(tr("Organization (O):"), Utility::escape(org)); - stream << addCertDetailsField(tr("Organizational Unit (OU):"), Utility::escape(ou)); - stream << addCertDetailsField(tr("State/Province:"), Utility::escape(state)); - stream << addCertDetailsField(tr("Country:"), Utility::escape(country)); - stream << addCertDetailsField(tr("Serial:"), Utility::escape(serial)); - stream << QLatin1String("
    "); - - stream << tr("

    Issuer

    "); - - stream << QLatin1String(""); - stream << addCertDetailsField(tr("Issuer:"), Utility::escape(issuer)); - stream << addCertDetailsField(tr("Issued on:"), Utility::escape(effectiveDate)); - stream << addCertDetailsField(tr("Expires on:"), Utility::escape(expiryDate)); - stream << QLatin1String("
    "); - - stream << tr("

    Fingerprints

    "); - - stream << QLatin1String(""); - - stream << addCertDetailsField(tr("SHA-256:"), sha256escaped); - stream << addCertDetailsField(tr("SHA-1:"), Utility::escape(sha1)); - stream << QLatin1String("
    "); - - if (userApproved.contains(cert)) { - stream << tr("

    Note: This certificate was manually approved

    "); - } - stream << QLatin1String(""); - - QString txt; - if (pos > 0) { - txt += QString(2 * pos, ' '); - if (!Utility::isWindows()) { - // doesn't seem to work reliably on Windows - txt += QChar(0x21AA); // nicer '->' symbol - txt += QChar(' '); - } - } - - QString certId = cn.isEmpty() ? ou : cn; - - if (systemCaCertificates.contains(cert)) { - txt += certId; - } else { - if (isSelfSigned(cert)) { - txt += tr("%1 (self-signed)").arg(certId); - } else { - txt += tr("%1").arg(certId); - } - } - - // create label first - QLabel *label = new QLabel(parent); - label->setStyleSheet(QLatin1String("QLabel { padding: 8px; }")); - label->setTextFormat(Qt::RichText); - label->setText(details); - - // plug label into widget action - QWidgetAction *action = new QWidgetAction(parent); - action->setDefaultWidget(label); - // plug action into menu - QMenu *menu = new QMenu(parent); - menu->menuAction()->setText(txt); - menu->addAction(action); - - return menu; -} - -void SslButton::updateAccountState(AccountState *accountState) -{ - if (!accountState || !accountState->isConnected()) { - setVisible(false); - return; - } else { - setVisible(true); - } - _accountState = accountState; - - AccountPtr account = _accountState->account(); - if (account->url().scheme() == QLatin1String("https")) { - setIcon(Utility::getCoreIcon(QStringLiteral("lock-https"))); - QSslCipher cipher = account->_sessionCipher; - setToolTip(tr("This connection is encrypted using %1 bit %2.\n").arg(cipher.usedBits()).arg(cipher.name())); - } else { - setIcon(Utility::getCoreIcon(QStringLiteral("lock-http"))); - setToolTip(tr("This connection is NOT secure as it is not encrypted.\n")); - } -} - -void SslButton::slotUpdateMenu() -{ - _menu->clear(); - - if (!_accountState) { - return; - } - - AccountPtr account = _accountState->account(); - - _menu->addAction(tr("Server version: %1").arg(account->serverVersion()))->setEnabled(false); - - if (account->isHttp2Supported()) { - _menu->addAction("HTTP/2")->setEnabled(false); - } - - if (account->url().scheme() == QLatin1String("https")) { - QString sslVersion = account->_sessionCipher.protocolString() - + ", " + account->_sessionCipher.authenticationMethod() - + ", " + account->_sessionCipher.keyExchangeMethod() - + ", " + account->_sessionCipher.encryptionMethod(); - _menu->addAction(sslVersion)->setEnabled(false); - - if (account->_sessionTicket.isEmpty()) { - _menu->addAction(tr("No support for SSL session tickets/identifiers"))->setEnabled(false); - } - - QList chain = account->_peerCertificateChain; - - if (chain.isEmpty()) { - qCWarning(lcSsl) << "Empty certificate chain"; - return; - } - - _menu->addAction(tr("Certificate information:"))->setEnabled(false); - - const auto systemCerts = QSslConfiguration::systemCaCertificates(); - - QList tmpChain; - for (const auto &cert : qAsConst(chain)) { - tmpChain << cert; - if (systemCerts.contains(cert)) - break; - } - chain = tmpChain; - - // find trust anchor (informational only, verification is done by QSslSocket!) - for (const QSslCertificate &rootCA : systemCerts) { - if (rootCA.issuerInfo(QSslCertificate::CommonName) == chain.last().issuerInfo(QSslCertificate::CommonName) - && rootCA.issuerInfo(QSslCertificate::Organization) == chain.last().issuerInfo(QSslCertificate::Organization)) { - chain.append(rootCA); - break; - } - } - - QListIterator it(chain); - it.toBack(); - int i = 0; - while (it.hasPrevious()) { - _menu->addMenu(buildCertMenu(_menu, it.previous(), account->approvedCerts(), i, systemCerts)); - i++; - } - } else { - _menu->addAction(tr("The connection is not secure"))->setEnabled(false); - } -} - -} // namespace OCC diff --git a/src/gui/sslbutton.h b/src/gui/sslbutton.h deleted file mode 100644 index 30fd23f048f..00000000000 --- a/src/gui/sslbutton.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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. - */ - -#ifndef SSLBUTTON_H -#define SSLBUTTON_H - -#include -#include -#include - -class QAction; -class QSslCertificate; -class QSslConfiguration; - -namespace OCC { - -class AccountState; - -/** - * @brief The SslButton class - * @ingroup gui - */ -class SslButton : public QToolButton -{ - Q_OBJECT -public: - explicit SslButton(QWidget *parent = nullptr); - void updateAccountState(AccountState *accountState); - -public slots: - void slotUpdateMenu(); - -private: - QMenu *buildCertMenu(QMenu *parent, const QSslCertificate &cert, - const QList &userApproved, int pos, const QList &systemCaCertificates); - QPointer _accountState; - QMenu *_menu; -}; - -} // namespace OCC - -#endif // SSLBUTTON_H diff --git a/src/gui/sslerrordialog.cpp b/src/gui/sslerrordialog.cpp deleted file mode 100644 index dbd066034b1..00000000000 --- a/src/gui/sslerrordialog.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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 "configfile.h" -#include "sslerrordialog.h" - -#include -#include -#include - - -#include "ui_sslerrordialog.h" - -namespace OCC { - -Q_LOGGING_CATEGORY(lcSslErrorDialog, "gui.sslerrordialog", QtInfoMsg) - -namespace Utility { - // Used for QSSLCertificate::subjectInfo which returns a QStringList in Qt5, but a QString in Qt4 - QString escape(const QStringList &l) { return escape(l.join(';')); } -} - -bool SslDialogErrorHandler::handleErrors(const QList &errors, const QSslConfiguration &, QList *certs, AccountPtr account) -{ - Q_ASSERT(certs); - - SslErrorDialog dlg(account); - // whether the failing certs have previously been accepted - if (dlg.checkFailingCertsKnown(errors)) { - *certs = dlg.unknownCerts(); - return true; - } - // whether the user accepted the certs - if (dlg.exec() == QDialog::Accepted) { - if (dlg.trustConnection()) { - *certs = dlg.unknownCerts(); - return true; - } - } - return false; -} - -SslErrorDialog::SslErrorDialog(AccountPtr account, QWidget *parent) - : QDialog(parent) - , _allTrusted(false) - , _ui(new Ui::SslErrorDialog) - , _account(account) -{ - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - _ui->setupUi(this); - setWindowTitle(tr("Untrusted Certificate")); - QPushButton *okButton = - _ui->_dialogButtonBox->button(QDialogButtonBox::Ok); - QPushButton *cancelButton = - _ui->_dialogButtonBox->button(QDialogButtonBox::Cancel); - okButton->setEnabled(false); - connect(_ui->_cbTrustConnect, &QAbstractButton::clicked, - okButton, &QWidget::setEnabled); - - if (okButton) { - okButton->setDefault(true); - connect(okButton, &QAbstractButton::clicked, this, &QDialog::accept); - connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); - } -} - -SslErrorDialog::~SslErrorDialog() -{ - delete _ui; -} - - -QString SslErrorDialog::styleSheet() const -{ - const QString style = QLatin1String( - "#cert {margin-left: 5px;} " - "#ca_error { color:#a00011; margin-left:5px; margin-right:5px; }" - "#ca_error p { margin-top: 2px; margin-bottom:2px; }" - "#ccert { margin-left: 5px; }" - "#issuer { margin-left: 5px; }" - "tt { font-size: small; }"); - - return style; -} -#define QL(x) QLatin1String(x) - -bool SslErrorDialog::checkFailingCertsKnown(const QList &errors) -{ - // check if unknown certs caused errors. - _unknownCerts.clear(); - - QStringList errorStrings; - - QList trustedCerts = _account->approvedCerts(); - - for (int i = 0; i < errors.count(); ++i) { - QSslError error = errors.at(i); - if (trustedCerts.contains(error.certificate()) || _unknownCerts.contains(error.certificate())) { - continue; - } - errorStrings += error.errorString(); - if (!error.certificate().isNull()) { - _unknownCerts.append(error.certificate()); - } - } - - // if there are no errors left, all Certs were known. - if (errorStrings.isEmpty()) { - _allTrusted = true; - return true; - } - - QString msg = QL(""); - msg += QL(""); - msg += QL(""); - - auto host = _account->url().host(); - msg += QL("

    ") + tr("Cannot connect securely to %1:").arg(host) + QL("

    "); - // loop over the unknown certs and line up their errors. - msg += QL("
    "); - for (const auto &cert : qAsConst(_unknownCerts)) { - msg += QL("
    "); - // add the errors for this cert - for (const auto &err : errors) { - if (err.certificate() == cert) { - msg += QL("

    ") + err.errorString() + QL("

    "); - } - } - msg += QL("
    "); - msg += certDiv(cert); - if (_unknownCerts.count() > 1) { - msg += QL("
    "); - } - } - msg += QL("
    "); - - QTextDocument *doc = new QTextDocument(nullptr); - QString style = styleSheet(); - doc->addResource(QTextDocument::StyleSheetResource, QUrl(QL("format.css")), style); - doc->setHtml(msg); - - _ui->_tbErrors->setDocument(doc); - _ui->_tbErrors->show(); - - return false; -} - -QString SslErrorDialog::certDiv(QSslCertificate cert) const -{ - QString msg; - msg += QL("
    "); - msg += QL("

    ") + tr("with Certificate %1").arg(Utility::escape(cert.subjectInfo(QSslCertificate::CommonName))) + QL("

    "); - - msg += QL("
    "); - QStringList li; - - QString org = Utility::escape(cert.subjectInfo(QSslCertificate::Organization)); - QString unit = Utility::escape(cert.subjectInfo(QSslCertificate::OrganizationalUnitName)); - QString country = Utility::escape(cert.subjectInfo(QSslCertificate::CountryName)); - if (unit.isEmpty()) - unit = tr("<not specified>"); - if (org.isEmpty()) - org = tr("<not specified>"); - if (country.isEmpty()) - country = tr("<not specified>"); - li << tr("Organization: %1").arg(org); - li << tr("Unit: %1").arg(unit); - li << tr("Country: %1").arg(country); - msg += QL("

    ") + li.join(QL("
    ")) + QL("

    "); - - msg += QL("

    "); - - QString md5sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Md5).toHex()); - QString sha1sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha1).toHex()); - msg += tr("Fingerprint (MD5): %1").arg(md5sum) + QL("
    "); - msg += tr("Fingerprint (SHA1): %1").arg(sha1sum) + QL("
    "); - msg += QL("
    "); - msg += tr("Effective Date: %1").arg(cert.effectiveDate().toString()) + QL("
    "); - msg += tr("Expiration Date: %1").arg(cert.expiryDate().toString()) + QL("

    "); - - msg += QL("
    "); - - msg += QL("

    ") + tr("Issuer: %1").arg(Utility::escape(cert.issuerInfo(QSslCertificate::CommonName))) + QL("

    "); - msg += QL("
    "); - li.clear(); - li << tr("Organization: %1").arg(Utility::escape(cert.issuerInfo(QSslCertificate::Organization))); - li << tr("Unit: %1").arg(Utility::escape(cert.issuerInfo(QSslCertificate::OrganizationalUnitName))); - li << tr("Country: %1").arg(Utility::escape(cert.issuerInfo(QSslCertificate::CountryName))); - msg += QL("

    ") + li.join(QL("
    ")) + QL("

    "); - msg += QL("
    "); - msg += QL("
    "); - - return msg; -} - -bool SslErrorDialog::trustConnection() -{ - if (_allTrusted) - return true; - - bool stat = (_ui->_cbTrustConnect->checkState() == Qt::Checked); - qCInfo(lcSslErrorDialog) << "SSL-Connection is trusted: " << stat; - - return stat; -} - -} // end namespace diff --git a/src/gui/sslerrordialog.h b/src/gui/sslerrordialog.h deleted file mode 100644 index d0acd09b0a4..00000000000 --- a/src/gui/sslerrordialog.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * 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. - */ -#ifndef SSLERRORDIALOG_H -#define SSLERRORDIALOG_H - -#include -#include -#include -#include - -#include "account.h" - -class QSslError; -class QSslCertificate; - -namespace OCC { - -namespace Ui { - class SslErrorDialog; -} - -/** - * @brief The SslDialogErrorHandler class - * @ingroup gui - */ -class SslDialogErrorHandler : public AbstractSslErrorHandler -{ -public: - bool handleErrors(const QList &errors, const QSslConfiguration &conf, QList *certs, AccountPtr) override; -}; - -/** - * @brief The SslErrorDialog class - * @ingroup gui - */ -class SslErrorDialog : public QDialog -{ - Q_OBJECT -public: - explicit SslErrorDialog(AccountPtr account, QWidget *parent = nullptr); - ~SslErrorDialog() override; - bool checkFailingCertsKnown(const QList &errors); - bool trustConnection(); - QList unknownCerts() const { return _unknownCerts; } - -private: - QString styleSheet() const; - bool _allTrusted; - - QString certDiv(QSslCertificate) const; - - QList _unknownCerts; - QString _customConfigHandle; - Ui::SslErrorDialog *_ui; - AccountPtr _account; -}; -} // end namespace - -#endif // SSLERRORDIALOG_H diff --git a/src/gui/sslerrordialog.ui b/src/gui/sslerrordialog.ui deleted file mode 100644 index 4fba2a81aa8..00000000000 --- a/src/gui/sslerrordialog.ui +++ /dev/null @@ -1,47 +0,0 @@ - - - OCC::SslErrorDialog - - - - 0 - 0 - 640 - 560 - - - - Form - - - - - - true - - - - - - - - 11 - - - - Trust this certificate anyway - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - diff --git a/src/gui/syncerrorwidget.cpp b/src/gui/syncerrorwidget.cpp new file mode 100644 index 00000000000..5aad4138b09 --- /dev/null +++ b/src/gui/syncerrorwidget.cpp @@ -0,0 +1,448 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 "account.h" +#include "accountmanager.h" +#include "commonstrings.h" +#include "expandingheaderview.h" +#include "folder.h" +#include "folderman.h" +#include "syncerrorwidget.h" +#include "libsync/configfile.h" +#include "models/models.h" +#include "localactivitywidget.h" +#include "syncengine.h" +#include "syncfileitem.h" +#include "theme.h" + +#include +#include +#include + +#include "ui_syncerrorwidget.h" + +namespace { +bool persistsUntilLocalDiscovery(const OCC::ProtocolItem &data) +{ + return data.status() == OCC::SyncFileItem::Conflict + || (data.status() == OCC::SyncFileItem::FileIgnored && data.direction() == OCC::SyncFileItem::Up) + || data.status() == OCC::SyncFileItem::Excluded; +} + +} +namespace OCC { + +class SyncFileItemStatusSetSortFilterProxyModel : public Models::SignalledQSortFilterProxyModel +{ +public: + using StatusSet = std::array; + + explicit SyncFileItemStatusSetSortFilterProxyModel(QObject *parent = nullptr) + : Models::SignalledQSortFilterProxyModel(parent) + { + restoreFilter(); + } + + ~SyncFileItemStatusSetSortFilterProxyModel() override + { + } + + StatusSet filter() const + { + return _filter; + } + + void setFilter(const StatusSet &newFilter, bool save = true) + { + if (_filter != newFilter) { + _filter = newFilter; + if (save) { + saveFilter(); + } + invalidateFilter(); + Q_EMIT filterChanged(); + } + } + + void resetFilter() + { + setFilter(defaultFilter()); + } + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override + { + QModelIndex idx = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent); + + bool ok = false; + int sourceData = sourceModel()->data(idx, filterRole()).toInt(&ok); + if (!ok) { + return false; + } + + return _filter[static_cast(sourceData)]; + } + + int filterCount() + { + StatusSet defaultSet = defaultFilter(); + OC_ASSERT(defaultSet.size() == _filter.size()); + + int count = 0; + // The number of filters is the number of items in the current filter that differ from the default set. + for (size_t i = 0, ei = defaultSet.size(); i != ei; ++i) { + // All errors are shown as a single filter, and they are all turned on or off together. + // So to show them as 1 filter, ignore the first three errors... + switch (i) { + case SyncFileItem::Status::FatalError: + Q_FALLTHROUGH(); + case SyncFileItem::Status::NormalError: + Q_FALLTHROUGH(); + case SyncFileItem::Status::SoftError: + break; + + // ... but *do* count the last one ... + case SyncFileItem::Status::DetailError: + Q_FALLTHROUGH(); + default: + // ... just like the other status items: + if (defaultSet[i] != _filter[i]) { + count += 1; + } + } + } + return count; + } + +private: + static StatusSet defaultFilter() + { + StatusSet defaultSet; + defaultSet.fill(true); + defaultSet[SyncFileItem::NoStatus] = false; + defaultSet[SyncFileItem::Success] = false; + return defaultSet; + } + + void saveFilter() + { + QStringList checked; + for (uint8_t s = SyncFileItem::NoStatus; s < SyncFileItem::StatusCount; ++s) { + if (_filter[s]) { + checked.append(Utility::enumToString(static_cast(s))); + } + } + ConfigFile().setIssuesWidgetFilter(checked); + } + + void restoreFilter() + { + StatusSet filter = {}; + bool filterNeedsReset = true; // If there is no filter, the `true` value will cause a reset. + std::optional checked = ConfigFile().issuesWidgetFilter(); + + if (checked.has_value()) { + // There is a filter, but it can be empty (user unchecked all checkboxes), and in that case we do not want to reset the filter. + filterNeedsReset = false; + + for (const QString &s : std::as_const(checked.value())) { + auto status = Utility::stringToEnum(s); + if (static_cast(status) == -1) { + // The string value is not a valid enum value, so stop processing, and queue a reset. + filterNeedsReset = true; + break; + } else { + filter[status] = true; + } + } + } + + if (filterNeedsReset) { + // If there was no filter in the config file, or if one of the values is invalid, reset the filter. + resetFilter(); + } else { + // There is a valid filter, so apply it. Also don't save it, because we just loaded it successfully + setFilter(filter, false); + } + } + +private: + StatusSet _filter = {}; +}; + +/** + * If more issues are reported than this they will not show up + * to avoid performance issues around sorting this many issues. + */ + +SyncErrorWidget::SyncErrorWidget(QWidget *parent) + : QWidget(parent) + , _ui(new Ui::SyncErrorWidget) +{ + _ui->setupUi(this); + + connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo, + this, &SyncErrorWidget::slotProgressInfo); + connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted, + this, &SyncErrorWidget::slotItemCompleted); + connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError, + this, [this](Folder *folder, const QString &message, ErrorCategory) { + auto item = SyncFileItemPtr::create(); + item->_status = SyncFileItem::NormalError; + item->_errorString = message; + _model->addProtocolItem(ProtocolItem { folder, item }); + }); + + connect(ProgressDispatcher::instance(), &ProgressDispatcher::excluded, this, [this](Folder *f, const QString &file) { + auto item = SyncFileItemPtr::create(); + item->_status = SyncFileItem::FilenameReserved; + item->_file = file; + item->_errorString = tr("The file %1 was ignored as its name is reserved by %2").arg(file, Theme::instance()->appNameGUI()); + _model->addProtocolItem(ProtocolItem { f, item }); + }); + + _model = new ProtocolItemModel(20000, true, this); + _sortModel = new Models::SignalledQSortFilterProxyModel(this); + connect(_sortModel, &Models::SignalledQSortFilterProxyModel::filterChanged, this, &SyncErrorWidget::filterChanged); + _sortModel->setSourceModel(_model); + _statusSortModel = new SyncFileItemStatusSetSortFilterProxyModel(this); // Note: this will restore a previously set filter, if there was one. + connect(_statusSortModel, &Models::SignalledQSortFilterProxyModel::filterChanged, this, &SyncErrorWidget::filterChanged); + _statusSortModel->setSourceModel(_sortModel); + _statusSortModel->setSortRole(Qt::DisplayRole); // Sorting should be done based on the text in the column cells, but... + _statusSortModel->setFilterRole(Models::UnderlyingDataRole); // ... filtering should be done on the underlying enum value. + _statusSortModel->setFilterKeyColumn(static_cast(ProtocolItemModel::ProtocolItemRole::Status)); + _ui->_tableView->setModel(_statusSortModel); + + auto header = new ExpandingHeaderView(QStringLiteral("ActivityErrorListHeaderV2"), _ui->_tableView); + _ui->_tableView->setHorizontalHeader(header); + header->setSectionResizeMode(QHeaderView::Interactive); + header->setExpandingColumn(static_cast(ProtocolItemModel::ProtocolItemRole::Action)); + header->setSortIndicator(static_cast(ProtocolItemModel::ProtocolItemRole::Time), Qt::DescendingOrder); + + connect(_ui->_tableView, &QTableView::customContextMenuRequested, this, &SyncErrorWidget::slotItemContextMenu); + _ui->_tableView->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(header, &QHeaderView::customContextMenuRequested, this, [this, header]() { + auto menu = showFilterMenu(header); + menu->addAction(tr("Reset column sizes"), header, [header] { header->resizeColumns(true); }); + }); + + connect(_ui->_filterButton, &QAbstractButton::clicked, this, [this] { + showFilterMenu(_ui->_filterButton); + }); + filterChanged(); // Set the appropriate label. + + _ui->_tooManyIssuesWarning->hide(); + connect(_model, &ProtocolItemModel::rowsInserted, this, [this] { + Q_EMIT issueCountUpdated(_model->rowCount()); + _ui->_tooManyIssuesWarning->setVisible(_model->isModelFull()); + }); + connect(_model, &ProtocolItemModel::modelReset, this, [this] { + Q_EMIT issueCountUpdated(_model->rowCount()); + _ui->_tooManyIssuesWarning->setVisible(_model->isModelFull()); + }); + + _ui->_conflictHelp->hide(); + _ui->_conflictHelp->setText( + tr("There were conflicts.
    Check the documentation on how to resolve them.") + .arg(Theme::instance()->conflictHelpUrl())); + + connect(FolderMan::instance(), &FolderMan::folderRemoved, this, &SyncErrorWidget::onFolderRemoved); + + connect(FolderMan::instance(), &FolderMan::folderListChanged, this, &SyncErrorWidget::onFolderListChanged); +} + +SyncErrorWidget::~SyncErrorWidget() +{ + delete _ui; +} + +void SyncErrorWidget::onFolderListChanged(const QUuid &accountId, const QList folders) +{ + Q_UNUSED(accountId); + _model->remove_if([folders](const ProtocolItem &item) { return (!folders.contains(item.folder())); }); +} + +void SyncErrorWidget::onFolderRemoved(const QUuid &accountId, Folder *f) +{ + Q_UNUSED(accountId); + _model->remove_if([f](const ProtocolItem &item) { return item.folder() == f; }); +} + +QMenu *SyncErrorWidget::showFilterMenu(QWidget *parent) +{ + auto menu = new QMenu(parent); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setAccessibleName(tr("Filter menu")); + + auto accountFilterReset = Models::addFilterMenuItems(menu, AccountManager::instance()->accountNames(), _sortModel, static_cast(ProtocolItemModel::ProtocolItemRole::Account), tr("Account"), Qt::DisplayRole); + menu->addSeparator(); + auto statusFilterReset = addStatusFilter(menu); + menu->addSeparator(); + addResetFiltersAction(menu, { accountFilterReset, statusFilterReset }); + + QTimer::singleShot(0, menu, [menu] { + // FIXME: when activated by the keyboard, this position can be anywhere. + menu->popup(QCursor::pos()); + // accessibility + menu->setFocus(); + }); + + return menu; +} + +void SyncErrorWidget::addResetFiltersAction(QMenu *menu, const QList> &resetFunctions) +{ + menu->addAction(QCoreApplication::translate("OCC::Models", "Reset Filters"), [resetFunctions]() { + for (const auto &reset : resetFunctions) { + reset(); + } + }); +} + +void SyncErrorWidget::slotProgressInfo(Folder *folder, const ProgressInfo &progress) +{ + if (progress.status() == ProgressInfo::Reconcile) { + // Wipe all non-persistent entries - as well as the persistent ones + // in cases where a local discovery was done. + SyncEngine *engine = folder->syncEngine(); + if (!engine) + return; + const auto style = engine->lastLocalDiscoveryStyle(); + _model->remove_if([&](const ProtocolItem &item) { + if (item.folder() != folder) { + return false; + } + if (item.direction() == SyncFileItem::None && item.status() == SyncFileItem::FilenameReserved) { + // TODO: don't clear syncErrors and excludes for now. + // make them either unique or remove them on the next sync? + return false; + } + if (style == LocalDiscoveryStyle::FilesystemOnly) { + return true; + } + if (!persistsUntilLocalDiscovery(item)) { + return true; + } + // Definitely wipe the entry if the file no longer exists + if (!QFileInfo::exists(folder->path() + item.path())) { + return true; + } + + auto path = QFileInfo(item.path()).dir().path(); + if (path == QLatin1Char('.')) + path.clear(); + + return engine->shouldDiscoverLocally(path); + }); + } + if (progress.status() == ProgressInfo::Done) { + // We keep track very well of pending conflicts. + // Inform other components about them. + QStringList conflicts; + for (const auto &rawData : _model->rawData()) { + if (rawData.folder() == folder && rawData.status() == SyncFileItem::Conflict) { + conflicts.append(rawData.path()); + } + } + Q_EMIT ProgressDispatcher::instance()->folderConflicts(folder, conflicts); + + _ui->_conflictHelp->setHidden(Theme::instance()->conflictHelpUrl().isEmpty() || conflicts.isEmpty()); + } +} + +void SyncErrorWidget::slotItemCompleted(Folder *folder, const SyncFileItemPtr &item) +{ + if (!item->showInIssuesTab()) + return; + _model->addProtocolItem(ProtocolItem { folder, item }); +} + +void SyncErrorWidget::filterChanged() +{ + // We have two filters here: the filter by status (which can have multiple items checked *off*... + int filterCount = _statusSortModel->filterCount(); + // .. and the filter on the account name, which can only be 1 item checked: + if (!_sortModel->filterRegularExpression().pattern().isEmpty()) { + filterCount += 1; + } + + _ui->_filterButton->setText(filterCount > 0 ? CommonStrings::filterButtonText(filterCount) : tr("Filter")); +} + +void SyncErrorWidget::slotItemContextMenu(const QPoint &pos) +{ + auto rows = _ui->_tableView->selectionModel()->selectedRows(); + for (int i = 0; i < rows.size(); ++i) { + rows[i] = _statusSortModel->mapToSource(rows[i]); + rows[i] = _sortModel->mapToSource(rows[i]); + } + // WHAT IS THIS?! + LocalActivityWidget::showContextMenu(this, _ui->_tableView, _sortModel, _model, rows, pos); +} + +std::function SyncErrorWidget::addStatusFilter(QMenu *menu) +{ + menu->addAction(QCoreApplication::translate("OCC::Models", "Status Filter:"))->setEnabled(false); + + // Use a QActionGroup to contain all status filter items, so we can find them back easily to reset. + auto statusFilterGroup = new QActionGroup(menu); + statusFilterGroup->setExclusive(false); + + const auto initialFilter = _statusSortModel->filter(); + + { // Add all errors under 1 action: + auto action = menu->addAction(Utility::enumToDisplayName(SyncFileItem::NormalError), this, [this](bool checked) { + auto currentFilter = _statusSortModel->filter(); + for (const auto &item : SyncFileItem::ErrorStatusItems) { + currentFilter[item] = checked; + } + _statusSortModel->setFilter(currentFilter); + }); + action->setCheckable(true); + action->setChecked(initialFilter[SyncFileItem::ErrorStatusItems[0]]); + statusFilterGroup->addAction(action); + } + menu->addSeparator(); + + // list of OtherDisplayableStatusItems with the localised name + std::vector> otherStatusItems; + otherStatusItems.reserve(SyncFileItem::OtherDisplayableStatusItems.size()); + for (const auto &item : SyncFileItem::OtherDisplayableStatusItems) { + otherStatusItems.emplace_back(Utility::enumToDisplayName(item), item); + } + std::sort(otherStatusItems.begin(), otherStatusItems.end(), [](const auto &a, const auto &b) { + return a.first < b.first; + }); + for (const auto &item : otherStatusItems) { + auto action = menu->addAction(item.first, menu, [this, item](bool checked) { + auto currentFilter = _statusSortModel->filter(); + currentFilter[item.second] = checked; + _statusSortModel->setFilter(currentFilter); + }); + action->setCheckable(true); + action->setChecked(initialFilter[item.second]); + statusFilterGroup->addAction(action); + } + + menu->addSeparator(); + + // Add action to reset all filters at once: + return [statusFilterGroup, this]() { + for (QAction *action : statusFilterGroup->actions()) { + action->setChecked(true); + } + _statusSortModel->resetFilter(); + }; +} +} diff --git a/src/gui/syncerrorwidget.h b/src/gui/syncerrorwidget.h new file mode 100644 index 00000000000..391e9065c26 --- /dev/null +++ b/src/gui/syncerrorwidget.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 + +#include "progressdispatcher.h" + +namespace OCC { + +class SyncFileItemStatusSetSortFilterProxyModel; +class ProtocolItemModel; +class Folder; + +namespace Models { + class SignalledQSortFilterProxyModel; +} + +namespace Ui { + class SyncErrorWidget; +} + +/** + * @brief The SyncErrorWidget class + * @ingroup gui + */ +class SyncErrorWidget : public QWidget +{ + Q_OBJECT +public: + explicit SyncErrorWidget(QWidget *parent = nullptr); + ~SyncErrorWidget() override; + +public Q_SLOTS: + void slotProgressInfo(Folder *folder, const ProgressInfo &progress); + void slotItemCompleted(Folder *folder, const SyncFileItemPtr &item); + void filterChanged(); + +Q_SIGNALS: + void issueCountUpdated(int); + +private Q_SLOTS: + QMenu *showFilterMenu(QWidget *parent); + void slotItemContextMenu(const QPoint &pos); + void onFolderListChanged(const QUuid &accountId, const QList folders); + void onFolderRemoved(const QUuid &accountId, Folder *f); + +private: + static void addResetFiltersAction(QMenu *menu, const QList> &resetFunctions); + std::function addStatusFilter(QMenu *menu); + + ProtocolItemModel *_model; + Models::SignalledQSortFilterProxyModel *_sortModel; + SyncFileItemStatusSetSortFilterProxyModel *_statusSortModel; + + Ui::SyncErrorWidget *_ui; +}; +} diff --git a/src/gui/syncerrorwidget.ui b/src/gui/syncerrorwidget.ui new file mode 100644 index 00000000000..b8117c84dcb --- /dev/null +++ b/src/gui/syncerrorwidget.ui @@ -0,0 +1,104 @@ + + + OCC::SyncErrorWidget + + + + 0 + 0 + 580 + 578 + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::ContextMenuPolicy::CustomContextMenu + + + Issues table + + + false + + + true + + + QAbstractItemView::SelectionBehavior::SelectRows + + + false + + + true + + + true + + + false + + + + + + + + + + + There were too many issues. Not all will be visible here. + + + true + + + + + + + There were conflicts. Check the documentation on how to resolve them. + + + true + + + true + + + + + + + + + + + _filterButton + _tableView + + + + diff --git a/src/gui/syncrunfilelog.cpp b/src/gui/syncrunfilelog.cpp index 496c8d892dd..599bdc1b2bd 100644 --- a/src/gui/syncrunfilelog.cpp +++ b/src/gui/syncrunfilelog.cpp @@ -13,7 +13,6 @@ */ #include -#include #include "syncrunfilelog.h" #include "common/utility.h" @@ -51,18 +50,22 @@ void SyncRunFileLog::start(const QString &folderPath) QFile::rename(filename, newFilename); } _file.reset(new QFile(filename)); - _file->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text); - _out.reset(new QDebug(_file.data())); - _out->noquote(); + + + // we use a text stream to ensure the encoding is ok + // when outputting info, we use QDebug to ensure we can use the debug operators + _out.reset(new QTextStream(_file.data())); + _out->setEncoding(QStringConverter::Utf8); if (!exists) { // We are creating a new file, add the note. - *_out << "# timestamp | duration | file | instruction | dir | modtime | etag | " + *_out << "Log for:" << folderPath << Qt::endl + << "# timestamp | duration | file | instruction | dir | modtime | etag | " "size | fileId | status | errorString | http result code | " "other size | other modtime | X-Request-ID" - << endl; + << Qt::endl; FileSystem::setFileHidden(filename, true); } @@ -70,49 +73,48 @@ void SyncRunFileLog::start(const QString &folderPath) _totalDuration.start(); _lapDuration.start(); - *_out << "#=#=#=# Syncrun started " << dateTimeStr() << endl; + *_out << "#=#=#=# Syncrun started " << dateTimeStr() << Qt::endl; } void SyncRunFileLog::logItem(const SyncFileItem &item) { // don't log the directory items that are in the list - if (item._direction == SyncFileItem::None - || item._instruction == CSYNC_INSTRUCTION_IGNORE) { + if (item._direction == SyncFileItem::None || item.instruction() == CSYNC_INSTRUCTION_IGNORE) { return; } const QChar L = QLatin1Char('|'); - *_out << dateTimeStr(QDateTime::fromString(QString::fromUtf8(item._responseTimeStamp), Qt::RFC2822Date)) << L - << ((item._instruction != CSYNC_INSTRUCTION_RENAME) ? item.destination() : item._file + QStringLiteral(" -> ") + item._renameTarget) << L - << item._instruction << L - << item._direction << L - << L - << item._modtime << L - << item._etag << L - << item._size << L - << item._fileId << L - << item._status << L - << item._errorString << L - << item._httpErrorCode << L - << item._previousSize << L - << item._previousModtime << L - << item._requestId << L - << endl; + QString tmp; + { + QDebug(&tmp).noquote() << dateTimeStr(Utility::parseRFC1123Date(QString::fromUtf8(item._responseTimeStamp))) << L + << ((item.instruction() != CSYNC_INSTRUCTION_RENAME) ? item.destination() + : item._file + QStringLiteral(" -> ") + item._renameTarget) + << L << item.instruction() << L << item._direction << L << L << item._modtime << L << item._etag << L << item._size << L + << item._fileId << L << item._status << L << item._errorString << L << item._httpErrorCode << L << item._previousSize << L + << item._previousModtime << L << item._requestId << L << Qt::endl; + } + *_out << tmp; } void SyncRunFileLog::logLap(const QString &name) { - *_out << "#=#=#=#=#" << name << dateTimeStr() - << "(last step:" << _lapDuration.restart() << "msec" - << ", total:" << _totalDuration.elapsed() << "msec)" - << endl; + QString tmp; + { + QDebug(&tmp).noquote() << "#=#=#=#=#" << name << dateTimeStr() << "(last step:" << _lapDuration.restart() << "msec" + << ", total:" << _totalDuration.elapsed() << "msec)" << Qt::endl; + } + *_out << tmp; } void SyncRunFileLog::finish() { - *_out << "#=#=#=# Syncrun finished" << dateTimeStr() - << "(last step:" << _lapDuration.elapsed() << "msec" - << ", total:" << _totalDuration.elapsed() << "msec)" - << endl; + QString tmp; + { + QDebug(&tmp).noquote() << "#=#=#=# Syncrun finished" << dateTimeStr() << "(last step:" << _lapDuration.elapsed() << "msec" + << ", total:" << _totalDuration.elapsed() << "msec)" << Qt::endl; + } + *_out << tmp; + _out->flush(); + _out->reset(); _file->close(); } } diff --git a/src/gui/syncrunfilelog.h b/src/gui/syncrunfilelog.h index 8a5fb0e1d67..d4496a89171 100644 --- a/src/gui/syncrunfilelog.h +++ b/src/gui/syncrunfilelog.h @@ -43,7 +43,7 @@ class SyncRunFileLog QScopedPointer _file; QElapsedTimer _totalDuration; QElapsedTimer _lapDuration; - QScopedPointer _out; + QScopedPointer _out; }; } diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp deleted file mode 100644 index f3382446c88..00000000000 --- a/src/gui/systray.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) by Cédric Bellegarde - * - * 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 "systray.h" -#include "theme.h" -#include "config.h" - -#ifdef USE_FDO_NOTIFICATIONS -#include -#include -#include -#include -#define NOTIFICATIONS_SERVICE "org.freedesktop.Notifications" -#define NOTIFICATIONS_PATH "/org/freedesktop/Notifications" -#define NOTIFICATIONS_IFACE "org.freedesktop.Notifications" -#endif - -namespace OCC { - -#ifdef Q_OS_OSX -void *createOsXNotificationCenterDelegate(); -void releaseOsXNotificationCenterDelegate(void *delegate); -void sendOsXUserNotification(const QString &title, const QString &message); -#endif - -Systray::Systray(QObject *parent) - : QSystemTrayIcon(parent) -#ifdef Q_OS_OSX - , delegate(createOsXNotificationCenterDelegate()) -#endif -{ -} - -Systray::~Systray() -{ -#ifdef Q_OS_OSX - if (delegate) { - releaseOsXNotificationCenterDelegate(delegate); - } -#endif // Q_OS_OSX -} - -void Systray::showMessage(const QString &title, const QString &message, const QIcon &icon, int millisecondsTimeoutHint) -{ -#ifdef Q_OS_OSX - Q_UNUSED(icon) - Q_UNUSED(millisecondsTimeoutHint) - - sendOsXUserNotification(title, message); -#else -#ifdef USE_FDO_NOTIFICATIONS - if (QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) { - QList args = QList() << APPLICATION_NAME << quint32(0) << APPLICATION_ICON_NAME - << title << message << QStringList() << QVariantMap() << qint32(-1); - QDBusMessage method = QDBusMessage::createMethodCall(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify"); - method.setArguments(args); - QDBusConnection::sessionBus().asyncCall(method); - } else -#endif - { - QSystemTrayIcon::showMessage(title, message, icon, millisecondsTimeoutHint); - } -#endif // Q_OS_OSX -} - -void Systray::setToolTip(const QString &tip) -{ - QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip)); -} - -} // namespace OCC diff --git a/src/gui/systray.h b/src/gui/systray.h deleted file mode 100644 index 1a8fdb32e20..00000000000 --- a/src/gui/systray.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) by Cédric Bellegarde - * - * 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. - */ - -#ifndef SYSTRAY_H -#define SYSTRAY_H - -#include - -class QIcon; - -namespace OCC { - -/** - * @brief The Systray class - * @ingroup gui - */ -class Systray : public QSystemTrayIcon -{ - Q_OBJECT - -public: - Systray(QObject *parent = nullptr); - virtual ~Systray(); - - void showMessage(const QString &title, const QString &message, const QIcon &icon, int millisecondsTimeoutHint = 10000); - void setToolTip(const QString &tip); - -private: - void *delegate = nullptr; -}; - -} // namespace OCC - -#endif //SYSTRAY_H diff --git a/src/gui/systray.mm b/src/gui/systray.mm deleted file mode 100644 index 1e35b3f98a9..00000000000 --- a/src/gui/systray.mm +++ /dev/null @@ -1,53 +0,0 @@ -#include - -#import -#import -#import - -@interface OurDelegate : NSObject - -- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center - shouldPresentNotification:(NSUserNotification *)notification; - -@end - -@implementation OurDelegate - -// Always show, even if app is active at the moment. -- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center - shouldPresentNotification:(NSUserNotification *)notification -{ - Q_UNUSED(center) - Q_UNUSED(notification) - - return YES; -} - -@end - -namespace OCC { - -void *createOsXNotificationCenterDelegate() -{ - auto delegate = [[OurDelegate alloc] init]; - [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:delegate]; - - return delegate; -} - -void releaseOsXNotificationCenterDelegate(void *delegate) -{ - [static_cast(delegate) release]; -} - -void sendOsXUserNotification(const QString &title, const QString &message) -{ - @autoreleasepool { - NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease]; - [notification setTitle:[NSString stringWithUTF8String:title.toUtf8().data()]]; - [notification setInformativeText:[NSString stringWithUTF8String:message.toUtf8().data()]]; - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; - } -} - -} // namespace OCC diff --git a/src/gui/thumbnailjob.cpp b/src/gui/thumbnailjob.cpp deleted file mode 100644 index 05d81f134ac..00000000000 --- a/src/gui/thumbnailjob.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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 "thumbnailjob.h" -#include "networkjobs.h" -#include "account.h" - -namespace OCC { - -ThumbnailJob::ThumbnailJob(const QString &path, AccountPtr account, QObject *parent) - : AbstractNetworkJob(account, QLatin1String("index.php/apps/files/api/v1/thumbnail/150/150/") + path, parent) -{ - setIgnoreCredentialFailure(true); -} - -void ThumbnailJob::start() -{ - sendRequest("GET", makeAccountUrl(path())); - AbstractNetworkJob::start(); -} - -bool ThumbnailJob::finished() -{ - const auto result = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - QPixmap p; - if (result == 200) { - p.loadFromData(reply()->readAll()); - if (p.isNull()) { - qWarning() << Q_FUNC_INFO << "Invalid thumbnail"; - } - } - emit jobFinished(result, p); - return true; -} -} diff --git a/src/gui/thumbnailjob.h b/src/gui/thumbnailjob.h deleted file mode 100644 index 90a67a89958..00000000000 --- a/src/gui/thumbnailjob.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) by Roeland Jago Douma - * - * 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. - */ - -#ifndef THUMBNAILJOB_H -#define THUMBNAILJOB_H - -#include "networkjobs.h" -#include "accountfwd.h" - -namespace OCC { - -/** - * @brief Job to fetch a thumbnail for a file - * @ingroup gui - * - * Job that allows fetching a preview (of 150x150 for now) of a given file. - * Once the job has finished the jobFinished signal will be emitted. - */ -class ThumbnailJob : public AbstractNetworkJob -{ - Q_OBJECT -public: - explicit ThumbnailJob(const QString &path, AccountPtr account, QObject *parent = nullptr); -public slots: - void start() override; -signals: - /** - * @param statusCode the HTTP status code - * @param reply the content of the reply - * - * Signal that the job is done. If the statusCode is 200 (success). - * If the status code is different the content is invalid. - */ - void jobFinished(int statusCode, QPixmap reply); -private slots: - bool finished() override; -}; -} - -#endif // THUMBNAILJOB_H diff --git a/src/gui/tlserrordialog.cpp b/src/gui/tlserrordialog.cpp new file mode 100644 index 00000000000..deac06ad6b9 --- /dev/null +++ b/src/gui/tlserrordialog.cpp @@ -0,0 +1,116 @@ +/* +* Copyright (C) Fabian Müller +* +* 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 "tlserrordialog.h" +#include "common/utility.h" +#include "ui_tlserrordialog.h" + +#include + +namespace OCC { +TlsErrorDialog::TlsErrorDialog(const QList &sslErrors, const QString &host, QWidget *parent) + : QDialog(parent) + , _ui(new Ui::TlsErrorDialog) +{ + _ui->setupUi(this); + + _ui->hostnameLabel->setText(tr("Cannot connect securely to %1").arg(host)); + + QStringList errorStrings; + + for (const auto &error : sslErrors) { + errorStrings << error.errorString() << describeCertificateHtml(error.certificate()); + } + + _ui->textBrowser->setHtml(errorStrings.join(QStringLiteral("\n"))); + + // FIXME: add checkbox for second confirmation + + connect(_ui->buttonBox, &QDialogButtonBox::accepted, this, &TlsErrorDialog::accept); + connect(_ui->buttonBox, &QDialogButtonBox::rejected, this, &TlsErrorDialog::reject); + + // Try to set the No-button (which will also Q_EMIT the reject signal when cliced) as the default + if (QPushButton *noButton = _ui->buttonBox->button(QDialogButtonBox::No)) { + noButton->setDefault(true); + } +} + +TlsErrorDialog::~TlsErrorDialog() +{ + delete _ui; +} + +QString TlsErrorDialog::describeCertificateHtml(const QSslCertificate &certificate) +{ + auto formatFingerprint = [certificate](QCryptographicHash::Algorithm algorithm) { + return Utility::escape(QString::fromUtf8(certificate.digest(algorithm).toHex())); + }; + + auto formatInfo = [](const QStringList &stringList) { + return Utility::escape(stringList.join(QStringLiteral(", "))); + }; + + auto escapeValueOrNotSpecified = [&](const QStringList &stringList) { + if (stringList.isEmpty()) { + return tr("<not specified>"); + } else { + return formatInfo(stringList); + } + }; + + QString msg = tr( + "
    " + "

    with Certificate %1

    " + "
    " + "

    " + "Organization: %2
    " + "Unit: %3
    " + "Country: %4" + "

    " + "

    " + "Fingerprint (MD5): %5
    " + "Fingerprint (SHA1): %6
    " + "Fingerprint (SHA256): %7
    " + "
    " + "Effective Date: %8" + "Expiration Date: %9" + "

    " + "

    Issuer: %10

    " + "
    " + "

    " + "Organization: %11
    " + "Unit: %12
    " + "Country: %13" + "

    " + "
    " + "
    ") + .arg( + formatInfo(certificate.subjectInfo(QSslCertificate::CommonName)), + escapeValueOrNotSpecified(certificate.subjectInfo(QSslCertificate::Organization)), + escapeValueOrNotSpecified(certificate.subjectInfo(QSslCertificate::OrganizationalUnitName)), + escapeValueOrNotSpecified(certificate.subjectInfo(QSslCertificate::CountryName)), + formatFingerprint(QCryptographicHash::Md5), + formatFingerprint(QCryptographicHash::Sha1), + formatFingerprint(QCryptographicHash::Sha256), + certificate.effectiveDate().toString(), + certificate.expiryDate().toString(), + formatInfo(certificate.issuerInfo(QSslCertificate::CommonName)), + escapeValueOrNotSpecified(certificate.issuerInfo(QSslCertificate::Organization)), + escapeValueOrNotSpecified(certificate.issuerInfo(QSslCertificate::OrganizationalUnitName)), + escapeValueOrNotSpecified(certificate.issuerInfo(QSslCertificate::CountryName))); + + return msg; +} + +} // OCC diff --git a/src/gui/tlserrordialog.h b/src/gui/tlserrordialog.h new file mode 100644 index 00000000000..f3a177975e2 --- /dev/null +++ b/src/gui/tlserrordialog.h @@ -0,0 +1,40 @@ +/* +* Copyright (C) Fabian Müller +* +* 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 +#include + +namespace OCC { + +namespace Ui { + class TlsErrorDialog; +} + +class TlsErrorDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TlsErrorDialog(const QList &sslErrors, const QString &host, QWidget *parent); + ~TlsErrorDialog() override; + +private: + static QString describeCertificateHtml(const QSslCertificate &certificate); + + Ui::TlsErrorDialog *_ui; +}; + +} diff --git a/src/gui/tlserrordialog.ui b/src/gui/tlserrordialog.ui new file mode 100644 index 00000000000..1ce4638f332 --- /dev/null +++ b/src/gui/tlserrordialog.ui @@ -0,0 +1,57 @@ + + + OCC::TlsErrorDialog + + + + 0 + 0 + 534 + 493 + + + + TLS Certificate Error + + + + + + + 0 + 0 + + + + Cannot connect securely to host hostname (placeholder) + + + + + + + + + + + 0 + 0 + + + + Do you want to trust this certificate anyway? + + + + + + + QDialogButtonBox::No|QDialogButtonBox::Yes + + + + + + + + diff --git a/src/gui/tooltipupdater.cpp b/src/gui/tooltipupdater.cpp deleted file mode 100644 index 205dfc64fb3..00000000000 --- a/src/gui/tooltipupdater.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) by Christian Kamm - * - * 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 "tooltipupdater.h" - -#include -#include -#include - -using namespace OCC; - -ToolTipUpdater::ToolTipUpdater(QTreeView *treeView) - : QObject(treeView) - , _treeView(treeView) -{ - connect(_treeView->model(), &QAbstractItemModel::dataChanged, - this, &ToolTipUpdater::dataChanged); - _treeView->viewport()->installEventFilter(this); -} - -bool ToolTipUpdater::eventFilter(QObject * /*obj*/, QEvent *ev) -{ - if (ev->type() == QEvent::ToolTip) { - QHelpEvent *helpEvent = static_cast(ev); - _toolTipPos = helpEvent->globalPos(); - } - return false; -} - -void ToolTipUpdater::dataChanged(const QModelIndex &topLeft, - const QModelIndex &bottomRight, - const QVector &roles) -{ - if (!QToolTip::isVisible() || !roles.contains(Qt::ToolTipRole) || _toolTipPos.isNull()) { - return; - } - - // Was it the item under the cursor that changed? - auto index = _treeView->indexAt(_treeView->mapFromGlobal(QCursor::pos())); - if (topLeft == bottomRight && index != topLeft) { - return; - } - - // Update the currently active tooltip - QToolTip::showText(_toolTipPos, _treeView->model()->data(index, Qt::ToolTipRole).toString()); -} diff --git a/src/gui/tooltipupdater.h b/src/gui/tooltipupdater.h deleted file mode 100644 index 55448ef7b85..00000000000 --- a/src/gui/tooltipupdater.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) by Christian Kamm - * - * 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 -#include - -class QTreeView; -class QModelIndex; - -namespace OCC { - -/** - * @brief Updates tooltips of items in a QTreeView when they change. - * @ingroup gui - * - * Usually tooltips are not updated as they change. Since we want to - * use tooltips to show rapidly updating progress information, we - * need to make sure that that information is displayed to the user - * as it changes. - * - * To accomplish that, the eventFilter() stores the tooltip's position - * and the dataChanged() slot updates the tooltip if Qt::ToolTipRole - * gets updated while a tooltip is shown. - */ -class ToolTipUpdater : public QObject -{ - Q_OBJECT -public: - ToolTipUpdater(QTreeView *treeView); - -protected: - bool eventFilter(QObject *obj, QEvent *ev) override; - -private slots: - void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); - -private: - QTreeView *_treeView; - QPoint _toolTipPos; -}; - -} // namespace OCC diff --git a/src/gui/translations.cpp b/src/gui/translations.cpp index 5f0bbaa8d5d..1ed15821b62 100644 --- a/src/gui/translations.cpp +++ b/src/gui/translations.cpp @@ -17,13 +17,9 @@ #include "translations.h" -#include "config.h" -#include "theme.h" - #include -#include -#include #include +#include #include namespace OCC { @@ -52,7 +48,7 @@ namespace Translations { QSet availableTranslations; // calculate a glob pattern which can be used in the iterator below to match only translations files - QString pattern = translationsFilePrefix() + "*" + translationsFileSuffix(); + const QString pattern = translationsFilePrefix() + QLatin1Char('*') + translationsFileSuffix(); QDirIterator it(Translations::translationsDirectoryPath(), QStringList() << pattern); diff --git a/src/gui/translations.h b/src/gui/translations.h index e9fb6d29fc8..f126da94782 100644 --- a/src/gui/translations.h +++ b/src/gui/translations.h @@ -12,8 +12,9 @@ * for more details. */ -#ifndef TRANSLATIONSMANAGER_H -#define TRANSLATIONSMANAGER_H +#pragma once + +#include "gui/owncloudguilib.h" #include #include @@ -25,25 +26,23 @@ namespace Translations { /** * @return translation files' filename prefix */ - const QString translationsFilePrefix(); + OWNCLOUDGUI_EXPORT const QString translationsFilePrefix(); /** * @returntranslation files' filename suffix */ - const QString translationsFileSuffix(); + OWNCLOUDGUI_EXPORT const QString translationsFileSuffix(); /** * @return path to translation files */ - QString translationsDirectoryPath(); + OWNCLOUDGUI_EXPORT QString translationsDirectoryPath(); /** * @return list of locales for which translations are available */ - QSet listAvailableTranslations(); + OWNCLOUDGUI_EXPORT QSet listAvailableTranslations(); } // namespace Translations } // namespace OCC - -#endif // TRANSLATIONSMANAGER_H diff --git a/src/gui/traymenucontroller.cpp b/src/gui/traymenucontroller.cpp new file mode 100644 index 00000000000..a7294655cce --- /dev/null +++ b/src/gui/traymenucontroller.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 "traymenucontroller.h" + +#include "account.h" +#include "application.h" +#include "common/restartmanager.h" +#include "configfile.h" +#include "guiutility.h" +#include "libsync/theme.h" + +#include +#include + +#ifdef Q_OS_WIN +#include +#endif + +using namespace std::chrono_literals; + +namespace OCC { + +// simple helper to compute the overall status for the tray icon +SyncResult::Status trayOverallStatus() +{ + TrayOverallStatusResult result; + for (auto *folder : FolderMan::instance()->folders()) { + result.addResult(folder); + } + if (result.overallStatus().status() == SyncResult::Undefined) { + return SyncResult::Offline; + } + return result.overallStatus().status(); +} + +TrayMenuController::TrayMenuController(QObject *parent) + : QObject(parent) + , _tray(new QSystemTrayIcon(this)) +{ + + connect(_tray, &QSystemTrayIcon::activated, this, &TrayMenuController::slotTrayClicked); + + setupTrayContextMenu(); + + // init systray + slotComputeOverallSyncStatus(); + _tray->show(); + + // Refactoring todo: we have a very nasty habit of connecting to globally available managers, etc inside constructors or similar. + // this should only happen when we are dealing with a formal dependency: eg a dep passed by injection and kept as member, + // or a dep that is instantiated inside the class (also with a member kept) + // in cases like this one, the external deps should be instantiated externally and connected externally. This is normally part + // of an app building routine. The global singletons have to go and this is an important step to achieving that. + FolderMan *folderMan = FolderMan::instance(); + connect(folderMan, &FolderMan::folderSyncStateChange, this, &TrayMenuController::slotSyncStateChange); +} + +TrayMenuController::~TrayMenuController() +{ +} + +void TrayMenuController::slotTrayClicked(QSystemTrayIcon::ActivationReason reason) +{ + // Left click + if (reason == QSystemTrayIcon::Trigger) { + // this covers left click - on mac it shows the menu so we don't want to do anything + // more. on windows (and linux, presumably, given the original code) we just show the + // main window on left click. + if (!Utility::isMac()) + ocApp()->ensureVisible(); + } +} + +void TrayMenuController::slotSyncStateChange(Folder *folder) +{ + slotComputeOverallSyncStatus(); + + if (!folder) { + return; // Valid, just a general GUI redraw was needed. + } + + auto result = folder->syncResult(); + + qCInfo(lcApplication) << "Sync state changed for folder " << folder->remoteUrl().toString() << ": " << Utility::enumToDisplayName(result.status()); +} + +void TrayMenuController::slotTrayMessageIfServerUnsupported(Account *account) +{ + if (account->serverSupportLevel() != Account::ServerSupportLevel::Supported) { + slotShowTrayMessage(tr("Unsupported Server Version"), + tr("The server on account %1 runs an unsupported version %2. " + "Using this client with unsupported server versions is untested and " + "potentially dangerous. Proceed at your own risk.") + .arg(account->displayNameWithHost(), account->capabilities().status().versionString())); + } +} + +QIcon TrayMenuController::getTrayStatusIcon(const SyncResult::Status &status) const +{ + auto contextMenuVisible = _tray->contextMenu() && _tray->contextMenu()->isVisible(); + return Theme::instance()->themeTrayIcon(SyncResult{status}, contextMenuVisible); +} + +void TrayMenuController::slotComputeOverallSyncStatus() +{ + auto status = trayOverallStatus(); + const QIcon statusIcon = getTrayStatusIcon(status); + _tray->setIcon(statusIcon); +} + +void TrayMenuController::setupTrayContextMenu() +{ + // using the main window as parent for memory management + // todo: review this as for some reason I remember there is some other way it's supposed to be done + auto menu = new QMenu(ocApp()->mainWindow()); + menu->setTitle(Theme::instance()->appNameGUI()); + + _tray->setContextMenu(menu); + + auto app = ocApp(); + // Populate the context menu now. + menu->addAction(Theme::instance()->applicationIcon(), tr("Show %1").arg(Theme::instance()->appNameGUI()), app, &Application::ensureVisible); + menu->addSeparator(); + + if (ocApp()->debugMode()) { + auto *debugMenu = menu->addMenu(QStringLiteral("Debug actions")); + debugMenu->addAction(QStringLiteral("Crash if asserts enabled - OC_ENSURE"), this, [] { + if (OC_ENSURE(false)) { + Q_UNREACHABLE(); + } + }); + debugMenu->addAction(QStringLiteral("Crash if asserts enabled - Q_ASSERT"), this, [] { Q_ASSERT(false); }); + debugMenu->addAction(QStringLiteral("Crash now - Utility::crash()"), this, [] { Utility::crash(); }); + debugMenu->addAction(QStringLiteral("Crash now - OC_ENFORCE()"), this, [] { OC_ENFORCE(false); }); + debugMenu->addAction(QStringLiteral("Crash now - qFatal"), this, [] { qFatal("la Qt fatale"); }); + debugMenu->addAction(QStringLiteral("Restart now"), this, [] { RestartManager::requestRestart(); }); + debugMenu->addSeparator(); + auto captivePortalCheckbox = debugMenu->addAction(QStringLiteral("Behind Captive Portal")); + captivePortalCheckbox->setCheckable(true); + captivePortalCheckbox->setChecked(NetworkInformation::instance()->isForcedCaptivePortal()); + connect(captivePortalCheckbox, &QAction::triggered, [](bool checked) { NetworkInformation::instance()->setForcedCaptivePortal(checked); }); + + menu->addSeparator(); + } + + if (!Theme::instance()->helpUrl().isEmpty()) { + menu->addAction(tr("Help"), this, &TrayMenuController::requestShowHelp); + } + + if (! Theme::instance()->about().isEmpty()) { + menu->addAction(tr("About %1").arg(Theme::instance()->appNameGUI()), this, &TrayMenuController::requestShowAbout); + } + + menu->addAction(tr("Quit %1").arg(Theme::instance()->appNameGUI()), this, &QApplication::quit); +} + +void TrayMenuController::slotShowTrayMessage(const QString &title, const QString &msg, const QIcon &icon) +{ + _tray->showMessage(title, msg, icon.isNull() ? Resources::getCoreIcon(QStringLiteral("states/information")) : icon); +} + +void TrayMenuController::slotShowOptionalTrayMessage(const QString &title, const QString &msg, const QIcon &icon) +{ + ConfigFile cfg; + if (cfg.optionalDesktopNotifications()) { + slotShowTrayMessage(title, msg, icon); + } +} + +void TrayMenuController::slotShowShareInBrowser(const QString &sharePath, const QString &localPath) +{ + QString file; + const auto folder = FolderMan::instance()->folderForPath(localPath, &file); + if (!folder) { + qCWarning(lcApplication) << "Could not open browser share page for" << localPath << "no responsible folder found"; + return; + } + + if (folder->accountState()->account()->capabilities().filesSharing().sharing_roles) { + fetchPrivateLinkUrl(folder->accountState()->account(), folder->webDavUrl(), sharePath, this, [](const QUrl &url) { + const auto queryUrl = Utility::concatUrlPath(url, QString(), {{QStringLiteral("details"), QStringLiteral("sharing")}}); + Utility::openBrowser(queryUrl, nullptr); + }); + } +} + + +} // end namespace diff --git a/src/gui/traymenucontroller.h b/src/gui/traymenucontroller.h new file mode 100644 index 00000000000..ae701280728 --- /dev/null +++ b/src/gui/traymenucontroller.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) by Klaas Freitag + * + * 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 "gui/owncloudguilib.h" +#include "syncresult.h" + +#include +#include +#include + +namespace OCC { + +class Account; +class Folder; + +class ShareDialog; + +/** + * @brief The ownCloudGui class + * @ingroup gui + */ +class OWNCLOUDGUI_EXPORT TrayMenuController : public QObject +{ + Q_OBJECT +public: + explicit TrayMenuController(QObject *parent); + ~TrayMenuController() override; + +Q_SIGNALS: + void requestShowAbout(); + void requestShowHelp(); + +public Q_SLOTS: + void setupTrayContextMenu(); + void slotComputeOverallSyncStatus(); + void slotShowTrayMessage(const QString &title, const QString &msg, const QIcon &icon = {}); + void slotShowOptionalTrayMessage(const QString &title, const QString &msg, const QIcon &icon = {}); + void slotSyncStateChange(Folder *); + void slotTrayClicked(QSystemTrayIcon::ActivationReason reason); + void slotTrayMessageIfServerUnsupported(Account *account); + + /** + * Open a share dialog for a file or folder. + * + * sharePath is the full remote path to the item, + * localPath is the absolute local path to it (so not relative + * to the folder). + */ + void slotShowShareInBrowser(const QString &sharePath, const QString &localPath); + +private: + QIcon getTrayStatusIcon(const SyncResult::Status &status) const; + + QSystemTrayIcon *_tray; + QPointer _shareDialog; +}; + +} // namespace OCC diff --git a/src/gui/updater/CMakeLists.txt b/src/gui/updater/CMakeLists.txt index 6d9f4eb685c..5dd26e9e2f7 100644 --- a/src/gui/updater/CMakeLists.txt +++ b/src/gui/updater/CMakeLists.txt @@ -1,4 +1,4 @@ -if(APPLE AND NOT BUILD_LIBRARIES_ONLY) +if(APPLE) if (SPARKLE_FOUND) list(APPEND OWNCLOUD_BUNDLED_RESOURCES "${PROJECT_SOURCE_DIR}/admin/osx/deny_autoupdate_com.owncloud.desktopclient.plist" @@ -6,3 +6,56 @@ if(APPLE AND NOT BUILD_LIBRARIES_ONLY) set(OWNCLOUD_BUNDLED_RESOURCES ${OWNCLOUD_BUNDLED_RESOURCES} PARENT_SCOPE) endif() endif() + +target_sources(owncloudGui PRIVATE + ocupdater.cpp + ocupdater.h + newversionavailablewidget.cpp + newversionavailablewidget.h + newversionavailablewidget.ui + updateinfo.cpp + updateinfo.h + updater.cpp + updater.h + updatedownloadedwidget.cpp + updatedownloadedwidget.h + updatedownloadedwidget.ui +) + +if(SPARKLE_FOUND) + # Define this, we need to check in updater.cpp + target_compile_definitions(owncloudGui PUBLIC HAVE_SPARKLE) + target_sources(owncloudGui PRIVATE + sparkleupdater_mac.mm + sparkleupdater.h + ) + target_link_libraries(owncloudGui PRIVATE ${SPARKLE_LIBRARY}) +endif() + +if(WITH_APPIMAGEUPDATER) + message(STATUS "Including built-in libappimageupdate based updater") + + set(appimageupdater_sources + ${CMAKE_CURRENT_SOURCE_DIR}/appimageupdateavailablewidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/appimageupdateavailablewidget.h + ${CMAKE_CURRENT_SOURCE_DIR}/appimageupdateavailablewidget.ui + ${CMAKE_CURRENT_SOURCE_DIR}/appimageupdater.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/appimageupdater.h + ) + + target_sources(owncloudGui PRIVATE ${appimageupdater_sources}) + + # as libappimageupdate uses exceptions, we define a shim that catches all incoming exceptions + # and handles them in a suitable way + # we have to enable exceptions for this one compilation unit + set_source_files_properties(${appimageupdater_sources} + TARGET_DIRECTORY owncloudGui + PROPERTIES COMPILE_OPTIONS "-fexceptions" + ) + + target_compile_definitions(owncloudGui PRIVATE WITH_APPIMAGEUPDATER) + target_link_libraries(owncloudGui PRIVATE libappimageupdate) + + find_package(Threads REQUIRED) + target_link_libraries(owncloudGui PUBLIC ${CMAKE_THREAD_LIBS_INIT}) +endif() diff --git a/src/gui/updater/appimageupdateavailablewidget.cpp b/src/gui/updater/appimageupdateavailablewidget.cpp new file mode 100644 index 00000000000..2b9457cc57a --- /dev/null +++ b/src/gui/updater/appimageupdateavailablewidget.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 by Fabian Müller + * + * 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 "appimageupdateavailablewidget.h" +#include "ui_appimageupdateavailablewidget.h" + +#include "theme.h" + +#include "resources/resources.h" + +#include + +namespace OCC { + +AppImageUpdateAvailableWidget::AppImageUpdateAvailableWidget(const QVersionNumber ¤tVersion, const QVersionNumber &newVersion, QWidget *parent) + : QDialog(parent) + , _ui(new Ui::AppImageUpdateAvailableWidgetUi) +{ + _ui->setupUi(this); + + const auto *theme = Theme::instance(); + + // the strings in the .ui file are not marked for translation, they're just placeholders + _ui->installedVersionLabel->setText(tr("Installed version: %1").arg(currentVersion.toString())); + _ui->availableVersionLabel->setText(tr("Available update: %1").arg(newVersion.toString())); + _ui->infoLabel->setText(tr("An update is available for this AppImage of %1. Do you want to install this update?\n\nThe update will be performed in the " + "background, and overwrite the current AppImage file. You need to restart the app to complete the update.") + .arg(theme->appNameGUI())); + + _ui->appIconLabel->setPixmap(theme->aboutIcon().pixmap(QSize(128, 128))); + + // we use custom icons to ensure a unified look on all platforms + _ui->buttonBox->button(QDialogButtonBox::Ok)->setIcon(Resources::getCoreIcon(QStringLiteral("check"))); + _ui->buttonBox->button(QDialogButtonBox::Cancel)->setIcon(Resources::getCoreIcon(QStringLiteral("ban"))); + _ui->skipButton->setIcon(Resources::getCoreIcon(QStringLiteral("step-forward"))); + + // the minimum size of the info label (and a few other labels) depends on their contents + // we can't persuade the dialog to resize automatically to the recommended size in Qt Designer, so we do it manually + // resize(sizeHint()); + // also, we want to prevent users from reducing the widget size too much, i.e., widgets would be hidden partially + // setMinimumSize(sizeHint()); + + connect(_ui->skipButton, &QPushButton::clicked, this, &AppImageUpdateAvailableWidget::slotSkipUpdate); +} + +void AppImageUpdateAvailableWidget::slotSkipUpdate() +{ + emit skipUpdateButtonClicked(); + emit finished(QDialog::Rejected); +} + +AppImageUpdateAvailableWidget::~AppImageUpdateAvailableWidget() +{ + delete _ui; +} + +} diff --git a/src/gui/updater/appimageupdateavailablewidget.h b/src/gui/updater/appimageupdateavailablewidget.h new file mode 100644 index 00000000000..771daea33b8 --- /dev/null +++ b/src/gui/updater/appimageupdateavailablewidget.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 by Fabian Müller + * + * 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 +#include +#include + +namespace Ui { +class AppImageUpdateAvailableWidgetUi; +} + +namespace OCC { + +/** + * @brief Dialog shown when updates for the running AppImage are available + * @ingroup gui + */ +class AppImageUpdateAvailableWidget : public QDialog +{ + Q_OBJECT +public: + explicit AppImageUpdateAvailableWidget(const QVersionNumber ¤tVersion, const QVersionNumber &newVersion, QWidget *parent = nullptr); + + ~AppImageUpdateAvailableWidget() override; + +Q_SIGNALS: + /** + * Emitted when an update is explicitly skipped by the user. + */ + void skipUpdateButtonClicked(); + +private slots: + + void slotSkipUpdate(); + +private: + ::Ui::AppImageUpdateAvailableWidgetUi *_ui; +}; + +} diff --git a/src/gui/updater/appimageupdateavailablewidget.ui b/src/gui/updater/appimageupdateavailablewidget.ui new file mode 100644 index 00000000000..833f82cd8c3 --- /dev/null +++ b/src/gui/updater/appimageupdateavailablewidget.ui @@ -0,0 +1,182 @@ + + + AppImageUpdateAvailableWidgetUi + + + + 0 + 0 + 556 + 241 + + + + + 0 + 0 + + + + + + + QLayout::SizeConstraint::SetMinimumSize + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + 6 + + + + + + 0 + 0 + + + + Installed version: + + + + + + + + 0 + 0 + + + + Available version: + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Minimum + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + + 350 + 10 + + + + Info text placeholder + + + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop + + + true + + + + + + + 10 + + + + + Skip this version + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + + + + + + + + + + + buttonBox + rejected() + AppImageUpdateAvailableWidgetUi + reject() + + + 367 + 211 + + + 277 + 120 + + + + + buttonBox + accepted() + AppImageUpdateAvailableWidgetUi + accept() + + + 367 + 211 + + + 277 + 120 + + + + + + accept() + reject() + + diff --git a/src/gui/updater/appimageupdater.cpp b/src/gui/updater/appimageupdater.cpp new file mode 100644 index 00000000000..6541d614ae7 --- /dev/null +++ b/src/gui/updater/appimageupdater.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2022 by Fabian Müller + * + * 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 +#include +#include +#include +#include + +#include "appimageupdater.h" +#include "common/version.h" +#include "libsync/configfile.h" +#include "mainwindow/modalwrapperwidget.h" +#include "theme.h" +#include "updater_private.h" + +#include "appimageupdateavailablewidget.h" +#include "application.h" + +using namespace OCC; +using namespace std::chrono_literals; + +namespace { + +/** + * libappimageupdate uses exceptions, but the client does not + * This little shim adapts the interface to one usable within this project + */ +class AppImageUpdaterShim : public QObject +{ + Q_OBJECT + +private: + explicit AppImageUpdaterShim(const QString &zsyncFileUrl, QObject *parent = nullptr) + : QObject(parent) + , _updater(Utility::appImageLocation().toStdString(), true) + { + QString updateInformation(QStringLiteral("zsync|") + zsyncFileUrl); + _updater.setUpdateInformation(updateInformation.toStdString()); + } + + void _logStatusMessages() + { + std::string currentStatusMessage; + + while (_updater.nextStatusMessage(currentStatusMessage)) { + qCInfo(lcUpdater) << "AppImageUpdate:" << QString::fromStdString(currentStatusMessage); + } + } + +public: + static AppImageUpdaterShim *makeInstance(const QString &updateInformation, QObject *parent) + { + try { + return new AppImageUpdaterShim(updateInformation, parent); + } catch (const std::exception &e) { + qCCritical(lcUpdater) << "Failed to create updater shim:" << e.what(); + return nullptr; + } + } + + bool isUpdateAvailable() noexcept + { + try { + bool updateAvailable; + + if (!_updater.checkForChanges(updateAvailable)) { + _logStatusMessages(); + return false; + } + + _logStatusMessages(); + return updateAvailable; + } catch (const std::exception &e) { + _logStatusMessages(); + qCCritical(lcUpdater) << "Checking for update failed:" << e.what(); + return false; + } + } + + void startUpdateInBackground() noexcept + { + // monitor progress and log status messages + auto *timer = new QTimer(this); + + timer->setInterval(100ms); + + connect(timer, &QTimer::timeout, this, [=]() { + _logStatusMessages(); + + if (_updater.isDone()) { + Q_EMIT finished(!_updater.hasError()); + timer->stop(); + } + }); + + _updater.start(); + timer->start(); + } + +Q_SIGNALS: + void finished(bool successfully); + +private: + appimage::update::Updater _updater; +}; + +} // namespace + +AppImageUpdater::AppImageUpdater(const QUrl &url) + : OCUpdater(url) +{ +} + +void AppImageUpdater::versionInfoArrived(const UpdateInfo &info) +{ + const auto ¤tVersion = Version::versionWithBuildNumber(); + const auto newVersion = QVersionNumber::fromString(info.version()); + + if (info.version().isEmpty() || currentVersion >= newVersion) { + qCInfo(lcUpdater) << "Client is on latest version!"; + setDownloadState(UpToDate); + return; + } + + const auto previouslySkippedVersion = this->previouslySkippedVersion(); + if (previouslySkippedVersion >= newVersion) { + qCInfo(lcUpdater) << "Update" << previouslySkippedVersion << "was skipped previously by user"; + setDownloadState(UpToDate); + return; + } + + const auto appImageUpdaterShim = AppImageUpdaterShim::makeInstance(info.downloadUrl(), this); + + if (appImageUpdaterShim == nullptr) { + setDownloadState(DownloadFailed); + return; + } + + if (!appImageUpdaterShim->isUpdateAvailable()) { + qCCritical(lcUpdater) << "Update server reported that update is available, but AppImageUpdate disagrees, aborting"; + setDownloadState(DownloadFailed); + return; + } + + auto widget = new AppImageUpdateAvailableWidget(currentVersion, newVersion, ocApp()->mainWindow()); + + connect(widget, &AppImageUpdateAvailableWidget::skipUpdateButtonClicked, this, [newVersion]() { + qCInfo(lcUpdater) << "Update" << newVersion << "skipped by user"; + setPreviouslySkippedVersion(newVersion); + }); + + connect(widget, &AppImageUpdateAvailableWidget::accepted, this, [this, widget, appImageUpdaterShim]() { + // binding AppImageUpdaterShim shared pointer to finished callback makes sure the updater is cleaned up when it's done + connect(appImageUpdaterShim, &AppImageUpdaterShim::finished, this, [this](bool succeeded) { + if (succeeded) { + qCInfo(lcUpdater) << "AppImage update complete"; + setDownloadState(DownloadComplete); + } else { + qCInfo(lcUpdater) << "AppImage update failed"; + setDownloadState(DownloadFailed); + } + }); + + setDownloadState(Downloading); + appImageUpdaterShim->startUpdateInBackground(); + }); + + ModalWrapperWidget *wrapper = new ModalWrapperWidget(widget, ocApp()->mainWindow()); + ocApp()->showModalWidget(wrapper); +} + +void AppImageUpdater::backgroundCheckForUpdate() +{ + OCUpdater::backgroundCheckForUpdate(); +} + +#include "appimageupdater.moc" diff --git a/src/gui/updater/appimageupdater.h b/src/gui/updater/appimageupdater.h new file mode 100644 index 00000000000..0561678d10b --- /dev/null +++ b/src/gui/updater/appimageupdater.h @@ -0,0 +1,40 @@ +/* +* Copyright (C) 2022 by Fabian Müller +* +* 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 +#include + +#include + +namespace OCC { + +/** + * @brief AppImage Updater using AppImageUpdate + * @ingroup gui + */ +class AppImageUpdater : public OCUpdater +{ + Q_OBJECT + +public: + explicit AppImageUpdater(const QUrl &url); + void backgroundCheckForUpdate() override; + +private: + void versionInfoArrived(const UpdateInfo &succeeded) override; +}; + +} // namespace OCC diff --git a/src/gui/updater/newversionavailablewidget.cpp b/src/gui/updater/newversionavailablewidget.cpp new file mode 100644 index 00000000000..4719da97df8 --- /dev/null +++ b/src/gui/updater/newversionavailablewidget.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 by Erik Verbruggen + * + * 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 "newversionavailablewidget.h" +#include "theme.h" +#include "ui_newversionavailablewidget.h" + +#include +#include + +namespace OCC { + +NewVersionAvailableWidget::NewVersionAvailableWidget(QWidget *parent, const QString &statusMessage) + : QDialog(parent) + , _ui(new ::Ui::Ui_NewVersionAvailableWidget) +{ + _ui->setupUi(this); + + _ui->icon->setPixmap(Theme::instance()->applicationIcon().pixmap(128, 128)); + _ui->label->setText(statusMessage); + + QPushButton *skipButton = _ui->buttonBox->addButton(tr("Skip this version"), QDialogButtonBox::ResetRole); + QPushButton *getUpdateButton = _ui->buttonBox->addButton(tr("Get update"), QDialogButtonBox::AcceptRole); + QPushButton *rejectButton = _ui->buttonBox->addButton(tr("Skip this time"), QDialogButtonBox::RejectRole); + + connect(skipButton, &QAbstractButton::clicked, this, &NewVersionAvailableWidget::skipVersion); + connect(rejectButton, &QAbstractButton::clicked, this, &QDialog::reject); + connect(getUpdateButton, &QAbstractButton::clicked, this, &QDialog::accept); +} + +NewVersionAvailableWidget::~NewVersionAvailableWidget() +{ + delete _ui; +} + +void NewVersionAvailableWidget::skipVersion() +{ + Q_EMIT versionSkipped(); + Q_EMIT finished(QDialog::Rejected); +} + +/*void NewVersionAvailableWidget::notNow() +{ + Q_EMIT noUpdateNow(); + Q_EMIT finished(); +} + +void NewVersionAvailableWidget::getUpdate() +{ + Q_EMIT updateNow(); + Q_EMIT finished(); +}*/ + +} // OCC namespace diff --git a/src/gui/updater/newversionavailablewidget.h b/src/gui/updater/newversionavailablewidget.h new file mode 100644 index 00000000000..a5ef3e88048 --- /dev/null +++ b/src/gui/updater/newversionavailablewidget.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 by Fabian Müller + * + * 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 +#include + +namespace Ui { +class Ui_NewVersionAvailableWidget; +} + +namespace OCC { + +class NewVersionAvailableWidget : public QDialog +{ + Q_OBJECT + +public: + explicit NewVersionAvailableWidget(QWidget *parent, const QString &statusMessage); + ~NewVersionAvailableWidget(); + +private Q_SLOTS: + void skipVersion(); + +Q_SIGNALS: + void versionSkipped(); + +private: + ::Ui::Ui_NewVersionAvailableWidget *_ui; +}; + +} diff --git a/src/gui/updater/newversionavailablewidget.ui b/src/gui/updater/newversionavailablewidget.ui new file mode 100644 index 00000000000..8599425c36e --- /dev/null +++ b/src/gui/updater/newversionavailablewidget.ui @@ -0,0 +1,126 @@ + + + Ui::NewVersionAvailableWidget + + + + 0 + 0 + 400 + 300 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + 128 + 128 + + + + + + + true + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + Qt::AlignmentFlag::AlignCenter + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Close|QDialogButtonBox::StandardButton::Ok + + + + + + + + + buttonBox + accepted() + Ui::NewVersionAvailableWidget + accept() + + + 199 + 279 + + + 199 + 149 + + + + + buttonBox + rejected() + Ui::NewVersionAvailableWidget + reject() + + + 199 + 279 + + + 199 + 149 + + + + + diff --git a/src/gui/updater/ocupdater.cpp b/src/gui/updater/ocupdater.cpp index d116afdbd43..fa1c2335f0c 100644 --- a/src/gui/updater/ocupdater.cpp +++ b/src/gui/updater/ocupdater.cpp @@ -16,41 +16,56 @@ #include "application.h" #include "common/utility.h" #include "common/version.h" +#include "config/appconfig.h" #include "configfile.h" +#include "mainwindow/modalwrapperwidget.h" #include "theme.h" + +#include "updatedownloadedwidget.h" +#include "updater/newversionavailablewidget.h" #include "updater/ocupdater.h" +#include "updater/updater_private.h" #include #include -#include #include +#include #include -#include +using namespace std::chrono_literals; using namespace std::chrono_literals; namespace OCC { -static const char updateAvailableC[] = "Updater/updateAvailable"; -static const char updateTargetVersionC[] = "Updater/updateTargetVersion"; -static const char updateTargetVersionStringC[] = "Updater/updateTargetVersionString"; -static const char seenVersionC[] = "Updater/seenVersion"; -static const char autoUpdateAttemptedC[] = "Updater/autoUpdateAttempted"; - - -UpdaterScheduler::UpdaterScheduler(QObject *parent) +UpdaterScheduler::UpdaterScheduler(Application *app, QObject *parent) : QObject(parent) + , _skipUpdateCheck(AppConfig().skipUpdateCheck()) { connect(&_updateCheckTimer, &QTimer::timeout, this, &UpdaterScheduler::slotTimerFired); // Note: the sparkle-updater is not an OCUpdater - if (OCUpdater *updater = qobject_cast(Updater::instance())) { - connect(updater, &OCUpdater::newUpdateAvailable, - this, &UpdaterScheduler::updaterAnnouncement); - connect(updater, &OCUpdater::requestRestart, this, &UpdaterScheduler::requestRestart); + if (auto *updater = qobject_cast(Updater::instance())) { + connect(updater, &OCUpdater::updateAvailableThroughSystem, app, + [app, updater]() { app->tray()->slotShowTrayMessage(tr("Update available"), updater->statusString()); }); + + connect(updater, &OCUpdater::updateDownloaded, this, [app, updater, this]() { + // prevent dialog from being displayed twice (rather unlikely, but it won't hurt) + if (_updateDownloadedWidget == nullptr) { + _updateDownloadedWidget = new UpdateDownloadedWidget(app->mainWindow(), updater->statusString()); + ModalWrapperWidget *wrapper = new ModalWrapperWidget(_updateDownloadedWidget, app->mainWindow()); + ocApp()->showModalWidget(wrapper); + + connect(_updateDownloadedWidget, &UpdateDownloadedWidget::accepted, this, []() { Updater::instance()->applyUpdateAndRestart(); }); + } + }); + + connect(updater, &OCUpdater::retryUpdateCheckLater, this, [this]() { + qCInfo(lcUpdater) << "Retrying update check in 10 minutes"; + QTimer::singleShot(10min, this, &UpdaterScheduler::slotTimerFired); + }); } // at startup, do a check in any case. @@ -73,8 +88,8 @@ void UpdaterScheduler::slotTimerFired() } // consider the skipUpdateCheck flag in the config. - if (cfg.skipUpdateCheck()) { - qCInfo(lcUpdater) << "Skipping update check because of config file"; + if (_skipUpdateCheck) { + qCInfo(lcUpdater) << "Skipping update check because of system config"; return; } @@ -96,30 +111,6 @@ OCUpdater::OCUpdater(const QUrl &url) { } -void OCUpdater::setUpdateUrl(const QUrl &url) -{ - _updateUrl = url; -} - -bool OCUpdater::performUpdate() -{ - QSettings settings(ConfigFile::configFile(), QSettings::IniFormat); - QString updateFile = settings.value(updateAvailableC).toString(); - if (!updateFile.isEmpty() && QFile(updateFile).exists() - && !updateSucceeded() /* Someone might have run the updater manually between restarts */) { - const QString name = Theme::instance()->appNameGUI(); - if (QMessageBox::information(nullptr, tr("New %1 Update Ready").arg(name), - tr("A new update for %1 is about to be installed. The updater may ask\n" - "for additional privileges during the process.") - .arg(name), - QMessageBox::Ok)) { - slotStartInstaller(); - return true; - } - } - return false; -} - void OCUpdater::backgroundCheckForUpdate() { int dlState = downloadState(); @@ -148,9 +139,13 @@ QString OCUpdater::statusString() const switch (downloadState()) { case Downloading: - return tr("Downloading %1. Please wait...").arg(updateVersion); + return tr("Downloading %1. Please wait…").arg(updateVersion); case DownloadComplete: - return tr("%1 available. Restart application to start the update.").arg(updateVersion); + if (Utility::runningInAppImage()) { + return tr("%1 installed successfully. Restart the application to finish installing the update.").arg(updateVersion); + } else { + return tr("%1 available. Restart application to start the update.").arg(updateVersion); + } case DownloadFailed: return tr("Could not download update. Please click here to download the update manually.").arg(_updateInfo.web()); case DownloadTimedOut: @@ -165,7 +160,7 @@ QString OCUpdater::statusString() const #endif return tr("New %1 available. Please use the system's update tool to install it.").arg(updateVersion); case CheckingServer: - return tr("Checking update server..."); + return tr("Checking update server…"); case Unknown: return tr("Update status is unknown: Did not check for new updates."); case UpToDate: @@ -175,7 +170,7 @@ QString OCUpdater::statusString() const } } -int OCUpdater::downloadState() const +OCUpdater::DownloadState OCUpdater::downloadState() const { return _state; } @@ -184,47 +179,22 @@ void OCUpdater::setDownloadState(DownloadState state) { auto oldState = _state; _state = state; - emit downloadStateChanged(); + Q_EMIT downloadStateChanged(); // show the notification if the download is complete (on every check) // or once for system based updates. - if (_state == OCUpdater::DownloadComplete || (oldState != OCUpdater::UpdateOnlyAvailableThroughSystem - && _state == OCUpdater::UpdateOnlyAvailableThroughSystem)) { - emit newUpdateAvailable(tr("Update Check"), statusString()); + if (_state == OCUpdater::DownloadComplete) { + Q_EMIT updateDownloaded(); + } + + if (oldState != OCUpdater::UpdateOnlyAvailableThroughSystem && _state == OCUpdater::UpdateOnlyAvailableThroughSystem) { + Q_EMIT updateAvailableThroughSystem(); } } -void OCUpdater::slotStartInstaller() +QString OCUpdater::availableVersionString() const { - QSettings settings(ConfigFile::configFile(), QSettings::IniFormat); - QString updateFile = settings.value(updateAvailableC).toString(); - settings.setValue(autoUpdateAttemptedC, true); - settings.sync(); - qCInfo(lcUpdater) << "Running updater" << updateFile; - - if(updateFile.endsWith(".exe")) { - QProcess::startDetached(updateFile, QStringList() << "/S" - << "/launch"); - } else if(updateFile.endsWith(".msi")) { - // When MSIs are installed without gui they cannot launch applications - // as they lack the user context. That is why we need to run the client - // manually here. We wrap the msiexec and client invocation in a powershell - // script because owncloud.exe will be shut down for installation. - // | Out-Null forces powershell to wait for msiexec to finish. - auto preparePathForPowershell = [](QString path) { - path.replace("'", "''"); - - return QDir::toNativeSeparators(path); - }; - - QString msiLogFile = ConfigFile::configPath() + "msi.log"; - const QString command = QStringLiteral("&{msiexec /norestart /passive /i '%1' /L*V '%2'| Out-Null ; &'%3'}") - .arg(preparePathForPowershell(updateFile), - preparePathForPowershell(msiLogFile), - preparePathForPowershell(QCoreApplication::applicationFilePath())); - - QProcess::startDetached("powershell.exe", QStringList{"-Command", command}); - } + return _updateInfo.versionString(); } void OCUpdater::checkForUpdate() @@ -239,12 +209,12 @@ void OCUpdater::checkForUpdate() void OCUpdater::slotOpenUpdateUrl() { - QDesktopServices::openUrl(_updateInfo.web()); + QDesktopServices::openUrl(QUrl(_updateInfo.web())); } bool OCUpdater::updateSucceeded() const { - QSettings settings(ConfigFile::configFile(), QSettings::IniFormat); + auto settings = ConfigFile::makeQSettings(); const auto targetVersionInt = QVersionNumber::fromString(settings.value(updateTargetVersionC).toString()); return Version::versionWithBuildNumber() >= targetVersionInt; @@ -258,6 +228,7 @@ void OCUpdater::slotVersionInfoArrived() if (reply->error() != QNetworkReply::NoError) { qCWarning(lcUpdater) << "Failed to reach version check url: " << reply->errorString(); setDownloadState(OCUpdater::Unknown); + Q_EMIT retryUpdateCheckLater(); return; } @@ -270,6 +241,7 @@ void OCUpdater::slotVersionInfoArrived() } else { qCWarning(lcUpdater) << "Could not parse update information."; setDownloadState(OCUpdater::Unknown); + Q_EMIT retryUpdateCheckLater(); } } @@ -278,14 +250,31 @@ void OCUpdater::slotTimedOut() setDownloadState(DownloadTimedOut); } +QVersionNumber OCUpdater::previouslySkippedVersion() +{ + auto settings = ConfigFile::makeQSettings(); + return QVersionNumber::fromString(settings.value(previouslySkippedVersionC).toString()); +} + +void OCUpdater::setPreviouslySkippedVersion(const QVersionNumber &previouslySkippedVersion) +{ + setPreviouslySkippedVersion(previouslySkippedVersion.toString()); +} + +void OCUpdater::setPreviouslySkippedVersion(const QString &previouslySkippedVersionString) +{ + auto settings = ConfigFile::makeQSettings(); + settings.setValue(previouslySkippedVersionC, previouslySkippedVersionString); +} + //////////////////////////////////////////////////////////////////////// -NSISUpdater::NSISUpdater(const QUrl &url) +WindowsUpdater::WindowsUpdater(const QUrl &url) : OCUpdater(url) { } -void NSISUpdater::slotWriteFile() +void WindowsUpdater::slotWriteFile() { QNetworkReply *reply = qobject_cast(sender()); if (_file->isOpen()) { @@ -293,9 +282,9 @@ void NSISUpdater::slotWriteFile() } } -void NSISUpdater::wipeUpdateData() +void WindowsUpdater::wipeUpdateData() { - QSettings settings(ConfigFile::configFile(), QSettings::IniFormat); + auto settings = ConfigFile::makeQSettings(); QString updateFileName = settings.value(updateAvailableC).toString(); if (!updateFileName.isEmpty()) QFile::remove(updateFileName); @@ -305,19 +294,24 @@ void NSISUpdater::wipeUpdateData() settings.remove(autoUpdateAttemptedC); } -void NSISUpdater::slotDownloadFinished() +void WindowsUpdater::slotDownloadFinished() { QNetworkReply *reply = qobject_cast(sender()); + const QUrl url(reply->url()); reply->deleteLater(); + _file->close(); if (reply->error() != QNetworkReply::NoError) { setDownloadState(DownloadFailed); return; } + const auto status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); + if (status != 200) { + setDownloadState(DownloadFailed); + return; + } - QUrl url(reply->url()); - _file->close(); - QSettings settings(ConfigFile::configFile(), QSettings::IniFormat); + auto settings = ConfigFile::makeQSettings(); // remove previously downloaded but not used installer QFile oldTargetFile(settings.value(updateAvailableC).toString()); @@ -333,15 +327,14 @@ void NSISUpdater::slotDownloadFinished() settings.setValue(updateAvailableC, _targetFile); } -void NSISUpdater::versionInfoArrived(const UpdateInfo &info) +void WindowsUpdater::versionInfoArrived(const UpdateInfo &info) { - QSettings settings(ConfigFile::configFile(), QSettings::IniFormat); + auto settings = ConfigFile::makeQSettings(); const auto infoVersion = QVersionNumber::fromString(info.version()); - const auto seenString = settings.value(seenVersionC).toString(); - const auto seenVersion = QVersionNumber::fromString(seenString); + const auto previouslySkippedVersion = this->previouslySkippedVersion(); qCInfo(lcUpdater) << "Version info arrived:" << "Your version:" << Version::versionWithBuildNumber() - << "Skipped version:" << seenVersion << seenString + << "Skipped version:" << previouslySkippedVersion << "Available version:" << infoVersion << info.version() << "Available version string:" << info.versionString() << "Web url:" << info.web() @@ -351,21 +344,23 @@ void NSISUpdater::versionInfoArrived(const UpdateInfo &info) qCInfo(lcUpdater) << "No version information available at the moment"; setDownloadState(UpToDate); } else if (infoVersion <= Version::versionWithBuildNumber() - || infoVersion <= seenVersion) { + || infoVersion <= previouslySkippedVersion) { qCInfo(lcUpdater) << "Client is on latest version!"; setDownloadState(UpToDate); } else { - QString url = info.downloadUrl(); + const QString url = info.downloadUrl(); if (url.isEmpty()) { - showNoUrlDialog(info); + showNewVersionAvailableWidget(info); } else { - _targetFile = ConfigFile::configPath() + url.mid(url.lastIndexOf('/') + 1); - if (QFile(_targetFile).exists()) { + _targetFile = ConfigFile::configPath() + url.mid(url.lastIndexOf(QLatin1Char('/')) + 1); + if (QFile::exists(_targetFile)) { setDownloadState(DownloadComplete); } else { - QNetworkReply *reply = qnam()->get(QNetworkRequest(QUrl(url))); - connect(reply, &QIODevice::readyRead, this, &NSISUpdater::slotWriteFile); - connect(reply, &QNetworkReply::finished, this, &NSISUpdater::slotDownloadFinished); + auto request = QNetworkRequest(QUrl(url)); + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + QNetworkReply *reply = qnam()->get(request); + connect(reply, &QIODevice::readyRead, this, &WindowsUpdater::slotWriteFile); + connect(reply, &QNetworkReply::finished, this, &WindowsUpdater::slotDownloadFinished); setDownloadState(Downloading); _file.reset(new QTemporaryFile); _file->setAutoRemove(true); @@ -375,63 +370,26 @@ void NSISUpdater::versionInfoArrived(const UpdateInfo &info) } } -void NSISUpdater::showNoUrlDialog(const UpdateInfo &info) +void WindowsUpdater::showNewVersionAvailableWidget(const UpdateInfo &info) { // if the version tag is set, there is a newer version. - QDialog *msgBox = new QDialog; - msgBox->setAttribute(Qt::WA_DeleteOnClose); - msgBox->setWindowFlags(msgBox->windowFlags() & ~Qt::WindowContextHelpButtonHint); - - QIcon infoIcon = msgBox->style()->standardIcon(QStyle::SP_MessageBoxInformation); - int iconSize = msgBox->style()->pixelMetric(QStyle::PM_MessageBoxIconSize); - - msgBox->setWindowIcon(infoIcon); - - QVBoxLayout *layout = new QVBoxLayout(msgBox); - QHBoxLayout *hlayout = new QHBoxLayout; - layout->addLayout(hlayout); - - msgBox->setWindowTitle(tr("New Version Available")); - - QLabel *ico = new QLabel; - ico->setFixedSize(iconSize, iconSize); - ico->setPixmap(infoIcon.pixmap(iconSize)); - QLabel *lbl = new QLabel; QString txt = tr("

    A new version of the %1 Client is available.

    " "

    %2 is available for download. The installed version is %3.

    ") .arg(Utility::escape(Theme::instance()->appNameGUI()), Utility::escape(info.versionString()), Utility::escape(Version::versionWithBuildNumber().toString())); + auto *widget = new NewVersionAvailableWidget(ocApp()->mainWindow(), txt); - lbl->setText(txt); - lbl->setTextFormat(Qt::RichText); - lbl->setWordWrap(true); + connect(widget, &NewVersionAvailableWidget::versionSkipped, this, &WindowsUpdater::slotSetPreviouslySkippedVersion); + connect(widget, &NewVersionAvailableWidget::accepted, this, &WindowsUpdater::slotOpenUpdateUrl); - hlayout->addWidget(ico); - hlayout->addWidget(lbl); - - QDialogButtonBox *bb = new QDialogButtonBox; - QPushButton *skip = bb->addButton(tr("Skip this version"), QDialogButtonBox::ResetRole); - QPushButton *reject = bb->addButton(tr("Skip this time"), QDialogButtonBox::AcceptRole); - QPushButton *getupdate = bb->addButton(tr("Get update"), QDialogButtonBox::AcceptRole); - - connect(skip, &QAbstractButton::clicked, msgBox, &QDialog::reject); - connect(reject, &QAbstractButton::clicked, msgBox, &QDialog::reject); - connect(getupdate, &QAbstractButton::clicked, msgBox, &QDialog::accept); - - connect(skip, &QAbstractButton::clicked, this, &NSISUpdater::slotSetSeenVersion); - connect(getupdate, &QAbstractButton::clicked, this, &NSISUpdater::slotOpenUpdateUrl); - - layout->addWidget(bb); - - msgBox->open(); - ownCloudGui::raiseDialog(msgBox); + ModalWrapperWidget *wrapper = new ModalWrapperWidget(widget, ocApp()->mainWindow()); + ocApp()->showModalWidget(wrapper); } -void NSISUpdater::showUpdateErrorDialog(const QString &targetVersion) +void WindowsUpdater::showUpdateErrorDialog(const QString &targetVersion) { QDialog *msgBox = new QDialog; msgBox->setAttribute(Qt::WA_DeleteOnClose); - msgBox->setWindowFlags(msgBox->windowFlags() & ~Qt::WindowContextHelpButtonHint); QIcon infoIcon = msgBox->style()->standardIcon(QStyle::SP_MessageBoxInformation); int iconSize = msgBox->style()->pixelMetric(QStyle::PM_MessageBoxIconSize); @@ -473,11 +431,11 @@ void NSISUpdater::showUpdateErrorDialog(const QString &targetVersion) connect(skip, &QAbstractButton::clicked, this, [this]() { wipeUpdateData(); - slotSetSeenVersion(); + slotSetPreviouslySkippedVersion(); }); // askagain: do nothing connect(retry, &QAbstractButton::clicked, this, [this]() { - slotStartInstaller(); + applyUpdateAndRestart(); qApp->quit(); }); connect(getupdate, &QAbstractButton::clicked, this, [this]() { @@ -489,11 +447,10 @@ void NSISUpdater::showUpdateErrorDialog(const QString &targetVersion) msgBox->open(); } -bool NSISUpdater::handleStartup() +void WindowsUpdater::validateUpdate() { - ConfigFile cfg; - QSettings settings(ConfigFile::configFile(), QSettings::IniFormat); - QString updateFileName = settings.value(updateAvailableC).toString(); + const auto settings = ConfigFile::makeQSettings(); + const QString updateFileName = settings.value(updateAvailableC).toString(); // has the previous run downloaded an update? if (!updateFileName.isEmpty() && QFile(updateFileName).exists()) { qCInfo(lcUpdater) << "An updater file is available"; @@ -504,48 +461,79 @@ bool NSISUpdater::handleStartup() qCInfo(lcUpdater) << "The requested update attempt has succeeded" << Version::versionWithBuildNumber(); wipeUpdateData(); - return false; } else { // auto update failed. Ask user what to do qCInfo(lcUpdater) << "The requested update attempt has failed" << settings.value(updateTargetVersionC).toString(); showUpdateErrorDialog(settings.value(updateTargetVersionStringC).toString()); - return false; } - } else { - qCInfo(lcUpdater) << "Triggering an update"; - return performUpdate(); } } - return false; } -void NSISUpdater::slotSetSeenVersion() +void WindowsUpdater::slotSetPreviouslySkippedVersion() +{ + setPreviouslySkippedVersion(updateInfo().version()); +} + +void WindowsUpdater::applyUpdateAndRestart() { - QSettings settings(ConfigFile::configFile(), QSettings::IniFormat); - settings.setValue(seenVersionC, updateInfo().version()); + Q_ASSERT(downloadState() == DownloadState::DownloadComplete); + + auto settings = ConfigFile::makeQSettings(); + QString updateFile = settings.value(updateAvailableC).toString(); + settings.setValue(autoUpdateAttemptedC, true); + settings.sync(); + qCInfo(lcUpdater) << "Running updater" << updateFile; + + Q_ASSERT(updateFile.endsWith(QLatin1String(".msi"))); + // When MSIs are installed without gui they cannot launch applications + // as they lack the user context. That is why we need to run the client + // manually here. We wrap the msiexec and client invocation in a powershell + // script because owncloud.exe will be shut down for installation. + // | Out-Null forces powershell to wait for msiexec to finish. + auto preparePathForPowershell = [](QString path) { + path.replace(QLatin1String("'"), QLatin1String("''")); + + return QDir::toNativeSeparators(path); + }; + + QString msiLogFile = ConfigFile::configPath() + QStringLiteral("msi.log"); + const QString command = + QStringLiteral("&{msiexec /norestart /passive /i '%1' /L*V '%2'| Out-Null ; &'%3'}") + .arg(preparePathForPowershell(updateFile), preparePathForPowershell(msiLogFile), preparePathForPowershell(QCoreApplication::applicationFilePath())); + + QProcess::startDetached(QStringLiteral("powershell.exe"), QStringList{QStringLiteral("-Command"), command}); + QTimer::singleShot(0, QApplication::instance(), &QApplication::quit); } //////////////////////////////////////////////////////////////////////// +namespace { + QDateTime applicationMTime() + { + return QFileInfo(QApplication::applicationFilePath()).lastModified().toUTC(); + } +} + PassiveUpdateNotifier::PassiveUpdateNotifier(const QUrl &url) : OCUpdater(url) + , _initialAppMTime(applicationMTime()) { - // remember the version of the currently running binary. On Linux it might happen that the - // package management updates the package while the app is running. This is detected in the - // updater slot: If the installed binary on the hd has a different version than the one - // running, the running app is restarted. That happens in folderman. - _runningAppVersion = Utility::versionOfInstalledBinary(); } void PassiveUpdateNotifier::backgroundCheckForUpdate() { - if (Utility::isLinux()) { - // on linux, check if the installed binary is still the same version - // as the one that is running. If not, restart if possible. - const QByteArray fsVersion = Utility::versionOfInstalledBinary(); - if (!(fsVersion.isEmpty() || _runningAppVersion.isEmpty()) && fsVersion != _runningAppVersion) { - emit requestRestart(); + if (Utility::isLinux() && !Utility::runningInAppImage()) { + // a bit of a naive approach, since it doesn't exclude downgrades or forced reinstalls + // whenever the application binary's mtime changes, we assume it has changed, since users are expected not to call touch on it or otherwise change it + // manually + const auto currentMTime = applicationMTime(); + qCDebug(lcUpdater) << "initial mtime:" << _initialAppMTime << "current mtime:" << currentMTime; + if (currentMTime != _initialAppMTime) { + qCInfo(lcUpdater) << "Binary mtime changed since application started, requesting restart"; + setDownloadState(DownloadState::DownloadComplete); + Q_EMIT updateDownloaded(); } } diff --git a/src/gui/updater/ocupdater.h b/src/gui/updater/ocupdater.h index dbdb6be5177..f8f0cdd9881 100644 --- a/src/gui/updater/ocupdater.h +++ b/src/gui/updater/ocupdater.h @@ -12,16 +12,22 @@ * for more details. */ -#ifndef OCUPDATER_H -#define OCUPDATER_H +#pragma once +#include "gui/owncloudguilib.h" +#include "application.h" +#include "updater/updatedownloadedwidget.h" +#include "updater/updateinfo.h" +#include "updater/updater.h" + +#include #include -#include +#include #include #include +#include +#include -#include "updater/updateinfo.h" -#include "updater/updater.h" class QNetworkAccessManager; class QNetworkReply; @@ -39,9 +45,9 @@ namespace OCC { * complex class design was set up: * * For Windows and Linux, the updaters are inherited from OCUpdater, while - * the MacOSX SparkleUpdater directly uses the class Updater. On windows, + * the macOSX SparkleUpdater directly uses the class Updater. On windows, * NSISUpdater starts the update if a new version of the client is available. - * On MacOSX, the sparkle framework handles the installation of the new + * On macOSX, the sparkle framework handles the installation of the new * version. On Linux, the update capabilities of the underlying linux distro * are relied on, and thus the PassiveUpdateNotifier just shows a notification * if there is a new version once at every start of the application. @@ -49,84 +55,103 @@ namespace OCC { * Simple class diagram of the updater: * * +---------------------------+ - * +-----+ UpdaterScheduler +-----+ - * | +------------+--------------+ | - * v v v - * +------------+ +---------------------+ +----------------+ - * |NSISUpdater | |PassiveUpdateNotifier| | SparkleUpdater | - * +-+----------+ +---+-----------------+ +-----+----------+ - * | | | - * | v +------------------+ - * | +---------------+ v - * +-->| OCUpdater +------+ - * +--------+------+ | + * +-----+ UpdaterScheduler +-----+------------------+ + * | +------------+--------------+ | | + * v v v v + * +------------+ +---------------------+ +----------------+ +-----------------+ + * |NSISUpdater | |PassiveUpdateNotifier| | SparkleUpdater | | AppImageUpdater | + * +-+----------+ +---+-----------------+ +-----+----------+ +-----------------+ + * | | | | + * | v +------------------+ | + * | +---------------+ v | + * +-->| OCUpdater +------+ | + * +--------+------+ |<--------------------------------+ * | Updater | * +-------------+ */ -class UpdaterScheduler : public QObject +class OWNCLOUDGUI_EXPORT UpdaterScheduler : public QObject { Q_OBJECT public: - UpdaterScheduler(QObject *parent); - -signals: + // must pass app explicitly (as we're instantiated from Application's ctor, we cannot use ocApp()) + explicit UpdaterScheduler(Application *app, QObject *parent = nullptr); + +Q_SIGNALS: + /** + * Show an update-related status message on the UI. + * @param title message title + * @param msg message content + */ void updaterAnnouncement(const QString &title, const QString &msg); - void requestRestart(); -private slots: +private Q_SLOTS: void slotTimerFired(); private: + const bool _skipUpdateCheck; QTimer _updateCheckTimer; /** Timer for the regular update check. */ + + // make sure we are going to show only one of them at once + QPointer _updateDownloadedWidget = nullptr; }; /** * @brief Class that uses an ownCloud proprietary XML format to fetch update information * @ingroup gui */ -class OCUpdater : public Updater +class OWNCLOUDGUI_EXPORT OCUpdater : public Updater { Q_OBJECT public: - enum DownloadState { Unknown = 0, + enum DownloadState { + Unknown = 0, CheckingServer, UpToDate, Downloading, DownloadComplete, DownloadFailed, DownloadTimedOut, - UpdateOnlyAvailableThroughSystem }; - explicit OCUpdater(const QUrl &url); + UpdateOnlyAvailableThroughSystem + }; + Q_ENUM(DownloadState); - void setUpdateUrl(const QUrl &url); - - bool performUpdate(); + explicit OCUpdater(const QUrl &url); void checkForUpdate() override; QString statusString() const; - int downloadState() const; + DownloadState downloadState() const; void setDownloadState(DownloadState state); -signals: + QString availableVersionString() const; + +Q_SIGNALS: void downloadStateChanged(); - void newUpdateAvailable(const QString &header, const QString &message); - void requestRestart(); -public slots: - // FIXME Maybe this should be in the NSISUpdater which should have been called WindowsUpdater - void slotStartInstaller(); + // it is up to the scheduler how to display either of these + // while in one case a system native notification may be adequate, the other may require a sophisticated dialog with action buttons + void updateAvailableThroughSystem(); + void updateDownloaded(); -protected slots: + /** + * Schedule retry of update check in the future. For use when an update failed previously due to a (temporary) problem which might be resolved in a reasonable amount of time. + */ + void retryUpdateCheckLater(); + +protected Q_SLOTS: void backgroundCheckForUpdate() override; void slotOpenUpdateUrl(); -private slots: +private Q_SLOTS: void slotVersionInfoArrived(); void slotTimedOut(); protected: + static QVersionNumber previouslySkippedVersion(); + static void setPreviouslySkippedVersion(const QVersionNumber &previouslySkippedVersion); + static void setPreviouslySkippedVersion(const QString &previouslySkippedVersionString); + virtual void versionInfoArrived(const UpdateInfo &info) = 0; bool updateSucceeded() const; QNetworkAccessManager *qnam() const { return _accessManager; } @@ -134,7 +159,7 @@ private slots: private: QUrl _updateUrl; - int _state; + DownloadState _state; QNetworkAccessManager *_accessManager; QTimer *_timeoutWatchdog; /** Timer to guard the timeout of an individual network request */ UpdateInfo _updateInfo; @@ -144,24 +169,29 @@ private slots: * @brief Windows Updater Using NSIS * @ingroup gui */ -class NSISUpdater : public OCUpdater +class OWNCLOUDGUI_EXPORT WindowsUpdater : public OCUpdater { Q_OBJECT public: - explicit NSISUpdater(const QUrl &url); - bool handleStartup() override; -private slots: - void slotSetSeenVersion(); + explicit WindowsUpdater(const QUrl &url); + void validateUpdate() override; + + void applyUpdateAndRestart() override; + +private Q_SLOTS: + void slotSetPreviouslySkippedVersion(); void slotDownloadFinished(); void slotWriteFile(); private: void wipeUpdateData(); - void showNoUrlDialog(const UpdateInfo &info); + void showNewVersionAvailableWidget(const UpdateInfo &info); void showUpdateErrorDialog(const QString &targetVersion); void versionInfoArrived(const UpdateInfo &info) override; QScopedPointer _file; QString _targetFile; + + friend class TestUpdater; }; /** @@ -176,13 +206,10 @@ class PassiveUpdateNotifier : public OCUpdater Q_OBJECT public: explicit PassiveUpdateNotifier(const QUrl &url); - bool handleStartup() override { return false; } void backgroundCheckForUpdate() override; private: void versionInfoArrived(const UpdateInfo &info) override; - QByteArray _runningAppVersion; + const QDateTime _initialAppMTime; }; } - -#endif // OC_UPDATER diff --git a/src/gui/updater/sparkleupdater.h b/src/gui/updater/sparkleupdater.h index 00aa510476f..34e92261790 100644 --- a/src/gui/updater/sparkleupdater.h +++ b/src/gui/updater/sparkleupdater.h @@ -33,7 +33,6 @@ class SparkleUpdater : public Updater // unused in this updater void checkForUpdate() override; void backgroundCheckForUpdate() override; - bool handleStartup() override { return false; } QString statusString(); diff --git a/src/gui/updater/sparkleupdater_mac.mm b/src/gui/updater/sparkleupdater_mac.mm index 1ba1e2c235d..d7871975814 100644 --- a/src/gui/updater/sparkleupdater_mac.mm +++ b/src/gui/updater/sparkleupdater_mac.mm @@ -59,7 +59,13 @@ - (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update - (void) updater:(SUUpdater *)updater didAbortWithError:(NSError *)error { Q_UNUSED(updater) - qCDebug(OCC::lcUpdater) << error.description; + + // Warning for end-user: + qCWarning(OCC::lcUpdater) << error.localizedDescription; + + // Full error, log it for when we need to debug problems (this will hold the full error object, + // including possible nested `NSError`s): + qCInfo(OCC::lcUpdater) << "Full error:" << error.description; } - (void)updater:(SUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcast @@ -69,7 +75,6 @@ - (void)updater:(SUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcas qCDebug(OCC::lcUpdater) << ""; } - @end diff --git a/src/gui/updater/updatedownloadedwidget.cpp b/src/gui/updater/updatedownloadedwidget.cpp new file mode 100644 index 00000000000..b77aa31b774 --- /dev/null +++ b/src/gui/updater/updatedownloadedwidget.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 by Fabian Müller + * + * 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 "updatedownloadedwidget.h" +#include "theme.h" +#include "ui_updatedownloadedwidget.h" + +#include +#include + +namespace OCC { + +UpdateDownloadedWidget::UpdateDownloadedWidget(QWidget *parent, const QString &statusMessage) + : QDialog(parent) + , _ui(new ::Ui::UpdateDownloadedWidget) +{ + _ui->setupUi(this); + + _ui->iconLabel->setPixmap(Theme::instance()->applicationIcon().pixmap(128, 128)); + _ui->iconLabel->setText(QString()); + + _ui->descriptionLabel->setText(statusMessage); + + const auto noButton = _ui->buttonBox->button(QDialogButtonBox::No); + const auto yesButton = _ui->buttonBox->button(QDialogButtonBox::Yes); + + noButton->setText(tr("Restart later")); + + yesButton->setText(tr("Restart now")); + yesButton->setDefault(true); +} + +UpdateDownloadedWidget::~UpdateDownloadedWidget() +{ + delete _ui; +} + +/* +void UpdateDownloadedWidget::accept() +{ + Q_EMIT accepted(); + Q_EMIT finished(); +} + +void UpdateDownloadedWidget::reject() +{ + Q_EMIT finished(); +} +*/ +} // OCC namespace diff --git a/src/gui/updater/updatedownloadedwidget.h b/src/gui/updater/updatedownloadedwidget.h new file mode 100644 index 00000000000..ccb6773390f --- /dev/null +++ b/src/gui/updater/updatedownloadedwidget.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 by Fabian Müller + * + * 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 + +namespace Ui { +class UpdateDownloadedWidget; +} + +namespace OCC { + +class UpdateDownloadedWidget : public QDialog +{ + Q_OBJECT + +public: + explicit UpdateDownloadedWidget(QWidget *parent, const QString &statusMessage); + ~UpdateDownloadedWidget(); + +private: + ::Ui::UpdateDownloadedWidget *_ui; +}; + +} diff --git a/src/gui/updater/updatedownloadedwidget.ui b/src/gui/updater/updatedownloadedwidget.ui new file mode 100644 index 00000000000..53392872539 --- /dev/null +++ b/src/gui/updater/updatedownloadedwidget.ui @@ -0,0 +1,163 @@ + + + UpdateDownloadedWidget + + + + 0 + 0 + 414 + 414 + + + + + 0 + 0 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + 0 + 0 + + + + + 128 + 0 + + + + + 128 + 128 + + + + icon (placeholder) + + + Qt::AlignmentFlag::AlignCenter + + + + + + + + + + 0 + 0 + + + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + + + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop + + + + + + + + 0 + 0 + + + + + 260 + 0 + + + + Update soandso has been downloaded. Restart now to perform the update? + + + Qt::AlignmentFlag::AlignCenter + + + true + + + + + + + Qt::Orientation::Vertical + + + + 20 + 277 + + + + + + + + QDialogButtonBox::StandardButton::No|QDialogButtonBox::StandardButton::Yes + + + + + + + + + buttonBox + accepted() + UpdateDownloadedWidget + accept() + + + 206 + 393 + + + 206 + 206 + + + + + buttonBox + rejected() + UpdateDownloadedWidget + reject() + + + 206 + 393 + + + 206 + 206 + + + + + diff --git a/src/gui/updater/updateinfo.cpp b/src/gui/updater/updateinfo.cpp index 1114b487202..d2c7531dda7 100644 --- a/src/gui/updater/updateinfo.cpp +++ b/src/gui/updater/updateinfo.cpp @@ -88,10 +88,10 @@ UpdateInfo UpdateInfo::parseString(const QString &xml, bool *ok) int errorLine, errorCol; QDomDocument doc; if (!doc.setContent(xml, false, &errorMsg, &errorLine, &errorCol)) { - qCWarning(lcUpdater).noquote().nospace() << errorMsg << " at " << errorLine << "," << errorCol - << "\n" << xml.splitRef("\n").value(errorLine-1) << "\n" - << QString(" ").repeated(errorCol - 1) << "^\n" - << "->" << xml << "<-"; + qCWarning(lcUpdater).noquote().nospace() << errorMsg << " at " << errorLine << "," << errorCol << "\n" + << xml.split(QStringLiteral("\n")).value(errorLine - 1) << "\n" + << QStringLiteral(" ").repeated(errorCol - 1) << "^\n" + << "->" << xml << "<-"; if (ok) *ok = false; return UpdateInfo(); diff --git a/src/gui/updater/updateinfo.h b/src/gui/updater/updateinfo.h index a57c6f04020..1bf2b8a4f1c 100644 --- a/src/gui/updater/updateinfo.h +++ b/src/gui/updater/updateinfo.h @@ -1,6 +1,8 @@ // This file is generated by kxml_compiler from occinfo.xml. -#ifndef UPDATEINFO_H -#define UPDATEINFO_H + +#pragma once + +#include "gui/owncloudguilib.h" #include #include @@ -8,7 +10,7 @@ namespace OCC { -class UpdateInfo +class OWNCLOUDGUI_EXPORT UpdateInfo { public: void setVersion(const QString &v); @@ -33,5 +35,3 @@ class UpdateInfo }; } // namespace OCC - -#endif // UPDATEINFO_H diff --git a/src/gui/updater/updater.cpp b/src/gui/updater/updater.cpp index 8713989e9ca..7077fdbff59 100644 --- a/src/gui/updater/updater.cpp +++ b/src/gui/updater/updater.cpp @@ -20,17 +20,50 @@ #include "updater/sparkleupdater.h" #include "updater/ocupdater.h" +#ifdef WITH_APPIMAGEUPDATER +#include "updater/appimageupdater.h" +#endif + +#include "common/restartmanager.h" #include "common/utility.h" #include "common/version.h" #include "configfile.h" #include "theme.h" -#include "config.h" - namespace OCC { Q_LOGGING_CATEGORY(lcUpdater, "gui.updater", QtInfoMsg) +[[deprecated("updateChannel is no longer supported. the key will be removed in 8.0")]] +const QString updateChannelC() { return QStringLiteral("updateChannel"); } + +QString updateChannel() +{ + // remove deprecated updateChannel setting once we hit 8.0 + auto settings = ConfigFile::makeQSettings(); + QString updateChannelKey = updateChannelC(); + // todo: #52 + /* + if (settings.contains(updateChannelKey)) { + settings.remove(updateChannelKey); + } + */ + // until then, migrate old setting if present + settings.setValue(updateChannelKey, "ocis"); + + // By now only two channels are supported: ocis and beta-ocis + const QString suffix = OCC::Version::suffix(); + if (suffix.startsWith(QLatin1String("daily")) + || suffix.startsWith(QLatin1String("nightly")) + || suffix.startsWith(QLatin1String("alpha")) + || suffix.startsWith(QLatin1String("rc")) + || suffix.startsWith(QLatin1String("beta"))) { + return "ocis-beta"; + } + + return "ocis"; +} + Updater *Updater::_instance = nullptr; Updater *Updater::instance() @@ -45,9 +78,9 @@ QUrl Updater::updateUrl() { QUrl updateBaseUrl(QString::fromLocal8Bit(qgetenv("OCC_UPDATE_URL"))); if (updateBaseUrl.isEmpty()) { - updateBaseUrl = QUrl(QLatin1String(APPLICATION_UPDATE_URL)); + updateBaseUrl = Theme::instance()->updateCheckUrl(); } - if (!updateBaseUrl.isValid() || updateBaseUrl.host() == ".") { + if (!updateBaseUrl.isValid() || updateBaseUrl.host() == QLatin1String(".")) { return QUrl(); } @@ -66,6 +99,13 @@ QUrl Updater::updateUrl() return updateBaseUrl; } +void Updater::validateUpdate() { } + +void Updater::applyUpdateAndRestart() +{ + RestartManager::requestRestart(); +} + QUrlQuery Updater::getQueryParams() { QUrlQuery query; @@ -81,49 +121,40 @@ QUrlQuery Updater::getQueryParams() platform = QStringLiteral("macos"); } - QString sysInfo = getSystemInfo(); - if (!sysInfo.isEmpty()) { - query.addQueryItem(QStringLiteral("client"), sysInfo); - } query.addQueryItem(QStringLiteral("version"), Version::versionWithBuildNumber().toString()); query.addQueryItem(QStringLiteral("platform"), platform); query.addQueryItem(QStringLiteral("oem"), theme->appName()); query.addQueryItem(QStringLiteral("buildArch"), QSysInfo::buildCpuArchitecture()); - query.addQueryItem(QStringLiteral("currentArch"), QSysInfo::currentCpuArchitecture()); + query.addQueryItem(QStringLiteral("currentArch"), Utility::currentCpuArch()); - query.addQueryItem(QStringLiteral("versionsuffix"), OCC::Version::suffix()); + // client updater server is now aware of packaging format + // TODO: add packaging string for other supported platforms, too +#ifdef WITH_APPIMAGEUPDATER + query.addQueryItem(QStringLiteral("packaging"), QStringLiteral("AppImage")); +#endif - auto channel = ConfigFile().updateChannel(); - if (channel != QLatin1String("stable")) { - query.addQueryItem(QStringLiteral("channel"), channel); - } - return query; -} + query.addQueryItem(QStringLiteral("versionsuffix"), OCC::Version::suffix()); + auto channel = updateChannel(); + query.addQueryItem("channel", channel); -QString Updater::getSystemInfo() -{ -#ifdef Q_OS_LINUX - QProcess process; - process.start(QLatin1String("lsb_release -a")); - process.waitForFinished(); - QByteArray output = process.readAllStandardOutput(); - qCDebug(lcUpdater) << "Sys Info size: " << output.length(); - if (output.length() > 1024) - output.clear(); // don't send too much. - - return QString::fromLocal8Bit(output.toBase64()); -#else - return QString(); -#endif + // requested by #11328 + // we use the names Qt gives us instead of inventing our own labels + // see https://doc.qt.io/qt-6/qsysinfo.html#productVersion + query.addQueryItem(QStringLiteral("kernelType"), QSysInfo::kernelType()); + query.addQueryItem(QStringLiteral("kernelVersion"), QSysInfo::kernelVersion()); + query.addQueryItem(QStringLiteral("productType"), QSysInfo::productType()); + query.addQueryItem(QStringLiteral("productVersion"), QSysInfo::productVersion()); + + return query; } // To test, cmake with -DAPPLICATION_UPDATE_URL="http://127.0.0.1:8080/test.rss" Updater *Updater::create() { auto url = updateUrl(); - qCDebug(lcUpdater) << url; + qCDebug(lcUpdater) << "Updater URL: "<< url.toDisplayString(); if (url.isEmpty()) { qCWarning(lcUpdater) << "Not a valid updater URL, will not do update check"; return nullptr; @@ -133,8 +164,14 @@ Updater *Updater::create() return new SparkleUpdater(url); #elif defined(Q_OS_WIN32) // Also for MSI - return new NSISUpdater(url); + return new WindowsUpdater(url); #else +#ifdef WITH_APPIMAGEUPDATER + if (Utility::runningInAppImage()) { + return new AppImageUpdater(url); + } +#endif + // the best we can do is notify about updates return new PassiveUpdateNotifier(url); #endif diff --git a/src/gui/updater/updater.h b/src/gui/updater/updater.h index 40145030231..daa35ee66a3 100644 --- a/src/gui/updater/updater.h +++ b/src/gui/updater/updater.h @@ -15,6 +15,8 @@ #ifndef UPDATER_H #define UPDATER_H +#include "gui/owncloudguilib.h" + #include #include @@ -25,7 +27,7 @@ namespace OCC { Q_DECLARE_LOGGING_CATEGORY(lcUpdater) -class Updater : public QObject +class OWNCLOUDGUI_EXPORT Updater : public QObject { Q_OBJECT public: @@ -34,7 +36,12 @@ class Updater : public QObject virtual void checkForUpdate() = 0; virtual void backgroundCheckForUpdate() = 0; - virtual bool handleStartup() = 0; + virtual void validateUpdate(); + + /*** + * This function will just restart on most platforms + */ + virtual void applyUpdateAndRestart(); protected: Updater() @@ -43,7 +50,6 @@ class Updater : public QObject } private: - static QString getSystemInfo(); static QUrlQuery getQueryParams(); static Updater *create(); static Updater *_instance; diff --git a/src/gui/updater/updater_private.h b/src/gui/updater/updater_private.h new file mode 100644 index 00000000000..7c9d7a3d0d6 --- /dev/null +++ b/src/gui/updater/updater_private.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 by Fabian Müller + * + * 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 + +namespace OCC { +static const QString updateAvailableC = QStringLiteral("Updater/updateAvailable"); +static const QString updateTargetVersionC = QStringLiteral("Updater/updateTargetVersion"); +static const QString updateTargetVersionStringC = QStringLiteral("Updater/updateTargetVersionString"); +// the config file key's name is preserved for legacy reasons +static const QString previouslySkippedVersionC = QStringLiteral("Updater/seenVersion"); +static const QString autoUpdateAttemptedC = QStringLiteral("Updater/autoUpdateAttempted"); +} diff --git a/src/gui/version.rc.in b/src/gui/version.rc.in deleted file mode 100644 index e4e40715bf3..00000000000 --- a/src/gui/version.rc.in +++ /dev/null @@ -1,37 +0,0 @@ -#include "winresrc.h" - -#define VER_FILEVERSION @MIRALL_VERSION_MAJOR@,@MIRALL_VERSION_MINOR@,@MIRALL_VERSION_PATCH@,@MIRALL_VERSION_BUILD@ -#define VER_FILEVERSION_STR "@MIRALL_VERSION_MAJOR@.@MIRALL_VERSION_MINOR@.@MIRALL_VERSION_PATCH@.@MIRALL_VERSION_BUILD@\0" - -#define VER_PRODUCTVERSION @MIRALL_VERSION_MAJOR@,@MIRALL_VERSION_MINOR@,@MIRALL_VERSION_PATCH@,@MIRALL_VERSION_BUILD@ -#define VER_PRODUCTVERSION_STR "@MIRALL_VERSION_MAJOR@.@MIRALL_VERSION_MINOR@.@MIRALL_VERSION_PATCH@.@MIRALL_VERSION_BUILD@\0" - -#define VER_PRODUCTNAME_STR "@APPLICATION_NAME@" -#define VER_COMPANYNAME_STR "@APPLICATION_VENDOR@" -#define VER_COPYRIGHT_STR "(c) @MIRALL_VERSION_YEAR@" - -VS_VERSION_INFO VERSIONINFO -FILEVERSION VER_FILEVERSION -PRODUCTVERSION VER_PRODUCTVERSION -FILEOS VOS__WINDOWS32 -FILETYPE VFT_APP -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "080904b0" - BEGIN - VALUE "CompanyName", VER_COMPANYNAME_STR - VALUE "LegalCopyright", VER_COPYRIGHT_STR - VALUE "FileVersion", VER_FILEVERSION_STR - VALUE "Comment", VER_PRODUCTNAME_STR - VALUE "FileDescription", VER_PRODUCTNAME_STR - VALUE "ProductName", VER_PRODUCTNAME_STR - VALUE "ProductVersion", VER_PRODUCTVERSION_STR - - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x809, 1200 - END -END diff --git a/src/gui/wizard/abstractcredswizardpage.cpp b/src/gui/wizard/abstractcredswizardpage.cpp deleted file mode 100644 index 067985129b3..00000000000 --- a/src/gui/wizard/abstractcredswizardpage.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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 "creds/abstractcredentials.h" -#include "creds/dummycredentials.h" -#include "account.h" -#include "wizard/owncloudwizard.h" - -#include "wizard/abstractcredswizardpage.h" -#include - -namespace OCC { - -void AbstractCredentialsWizardPage::cleanupPage() -{ - // Reset the credentials when the 'Back' button is used. - - AccountPtr account = static_cast(wizard())->account(); - AbstractCredentials *creds = account->credentials(); - if (creds && !qobject_cast(creds)) { - account->setCredentials(new DummyCredentials); - } -} - -OwncloudWizard *AbstractWizardPage::owncloudWizard() const -{ - return qobject_cast(wizard()); -} -} diff --git a/src/gui/wizard/abstractcredswizardpage.h b/src/gui/wizard/abstractcredswizardpage.h deleted file mode 100644 index 47e26af1ac7..00000000000 --- a/src/gui/wizard/abstractcredswizardpage.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) by Krzesimir Nowak - * - * 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. - */ - -#ifndef MIRALL_WIZARD_ABSTRACT_CREDS_WIZARD_PAGE_H -#define MIRALL_WIZARD_ABSTRACT_CREDS_WIZARD_PAGE_H - -#include - -namespace OCC { -class OwncloudWizard; -class AbstractCredentials; - -/** - * @brief The AbstractCredentialsWizardPage class - * @ingroup gui - */ - -class AbstractWizardPage : public QWizardPage -{ -public: - OwncloudWizard *owncloudWizard() const; -}; - -class AbstractCredentialsWizardPage : public AbstractWizardPage -{ -public: - void cleanupPage() override; - virtual AbstractCredentials *getCredentials() const = 0; -}; - -} // namespace OCC - -#endif diff --git a/src/gui/wizard/owncloudadvancedsetuppage.cpp b/src/gui/wizard/owncloudadvancedsetuppage.cpp deleted file mode 100644 index bdaba3ad176..00000000000 --- a/src/gui/wizard/owncloudadvancedsetuppage.cpp +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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 -#include -#include -#include -#include - -#include "QProgressIndicator.h" - -#include "wizard/owncloudwizard.h" -#include "wizard/owncloudwizardcommon.h" -#include "wizard/owncloudadvancedsetuppage.h" -#include "account.h" -#include "theme.h" -#include "configfile.h" -#include "selectivesyncdialog.h" -#include -#include "creds/abstractcredentials.h" -#include "creds/oauth.h" -#include "networkjobs.h" -#include "guiutility.h" - -namespace OCC { - -OwncloudAdvancedSetupPage::OwncloudAdvancedSetupPage() - : AbstractWizardPage() - , _ui() - , _checking(false) - , _created(false) - , _localFolderValid(false) -{ - _ui.setupUi(this); - - Theme *theme = Theme::instance(); - setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(theme->appNameGUI()))); - setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Setup local folder options"))); - - registerField(QLatin1String("OCSyncFromScratch"), _ui.cbSyncFromScratch); - - connect(_ui.pbSelectLocalFolder, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotSelectFolder); - setButtonText(QWizard::FinishButton, tr("Connect...")); - - connect(_ui.rSyncEverything, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotSyncEverythingClicked); - connect(_ui.rVirtualFileSync, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotVirtualFileSyncClicked); - connect(_ui.rVirtualFileSync, &QRadioButton::toggled, this, [this](bool checked) { - owncloudWizard()->setUseVirtualFileSync(checked); - if (checked) { - _ui.lSelectiveSyncSizeLabel->clear(); - _selectiveSyncBlacklist.clear(); - } - }); - connect(_ui.bSelectiveSync, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotSelectiveSyncClicked); - connect(_ui.rManualFolder, &QAbstractButton::clicked, this, [this] { setRadioChecked(_ui.rManualFolder); }); - - connect(_ui.rSyncEverything, &QRadioButton::toggled, _ui.syncEverythingWidget, &QWidget::setEnabled); - connect(_ui.rManualFolder, &QRadioButton::toggled, _ui.whereToSyncWidget, &QWidget::setDisabled); - - QIcon appIcon = theme->applicationIcon(); - _ui.lServerIcon->setPixmap(appIcon.pixmap(_ui.lServerIcon->size())); - if (theme->wizardHideExternalStorageConfirmationCheckbox()) { - _ui.confCheckBoxExternal->hide(); - } - if (theme->wizardHideFolderSizeLimitCheckbox()) { - _ui.confCheckBoxSize->hide(); - _ui.confSpinBox->hide(); - _ui.confTraillingSizeLabel->hide(); - } - _ui.lLocalIcon->setPixmap(Utility::getCoreIcon(QStringLiteral("folder-sync")).pixmap(_ui.lLocalIcon->size())); - - _ui.rVirtualFileSync->setText(tr("Use &virtual files instead of downloading content immediately%1").arg(bestAvailableVfsMode() == Vfs::WindowsCfApi ? QString() : tr(" (experimental)"))); - - connect(this, &OwncloudAdvancedSetupPage::completeChanged, this, [this] { - if (wizard() && owncloudWizard()->authType() == OCC::DetermineAuthTypeJob::AuthType::OAuth) { - // For OAuth, disable the back button in the Page_AdvancedSetup because we don't want - // to re-open the browser. - // HACK: the wizard will reenable the buttons on completeChanged, so delay it - QTimer::singleShot(0, this, [this]{ - wizard()->button(QWizard::BackButton)->setEnabled(false); - }); - } - }); -} - -bool OwncloudAdvancedSetupPage::isComplete() const -{ - return manualFolderConfig() || (!_checking && _localFolderValid); -} - -void OwncloudAdvancedSetupPage::initializePage() -{ - if (owncloudWizard()->useVirtualFileSync()) { - setRadioChecked(_ui.rVirtualFileSync); - } - const auto vfsMode = bestAvailableVfsMode(); - if (Theme::instance()->forceVirtualFilesOption() && vfsMode == Vfs::WindowsCfApi) { - _ui.syncTypeWidget->hide(); - } else { - if (!Theme::instance()->showVirtualFilesOption() || vfsMode == Vfs::Off || (vfsMode != Vfs::WindowsCfApi && !Theme::instance()->enableExperimentalFeatures())) { - // If the layout were wrapped in a widget, the auto-grouping of the - // radio buttons no longer works and there are surprising margins. - // Just manually hide the button and remove the layout. - _ui.rVirtualFileSync->hide(); - _ui.wSyncStrategy->layout()->removeItem(_ui.lVirtualFileSync); - setRadioChecked(_ui.rSyncEverything); - } else { -#ifdef Q_OS_WIN - if (vfsMode == Vfs::WindowsCfApi) { - qobject_cast(_ui.wSyncStrategy->layout())->insertItem(0, _ui.lVirtualFileSync); - } -#endif - } - } - _checking = false; - _ui.lSelectiveSyncSizeLabel->clear(); - _ui.lSyncEverythingSizeLabel->clear(); - - // call to init label - updateStatus(); - - // ensure "next" gets the focus, not obSelectLocalFolder - QTimer::singleShot(0, wizard()->button(QWizard::FinishButton), qOverload<>(&QWidget::setFocus)); - - auto acc = owncloudWizard()->account(); - auto quotaJob = new PropfindJob(acc, owncloudWizard()->remoteFolder(), this); - quotaJob->setProperties(QList() << "http://owncloud.org/ns:size"); - - connect(quotaJob, &PropfindJob::result, this, &OwncloudAdvancedSetupPage::slotQuotaRetrieved); - quotaJob->start(); - - ConfigFile cfgFile; - auto newFolderLimit = cfgFile.newBigFolderSizeLimit(); - _ui.confCheckBoxSize->setChecked(newFolderLimit.first); - _ui.confSpinBox->setValue(newFolderLimit.second); - _ui.confCheckBoxExternal->setChecked(cfgFile.confirmExternalStorage()); -} - -// Called if the user changes the user- or url field. Adjust the texts and -// evtl. warnings on the dialog. -void OwncloudAdvancedSetupPage::updateStatus() -{ - const QString locFolder = owncloudWizard()->localFolder(); - - // check if the local folder exists. If so, and if its not empty, show a warning. - QString errorStr = FolderMan::instance()->checkPathValidityForNewFolder(locFolder); - _localFolderValid = errorStr.isEmpty(); - - _ui.pbSelectLocalFolder->setText(QDir::toNativeSeparators(locFolder)); - if (owncloudWizard()->remoteFolder() != QLatin1String("/")) { - _ui.rSyncEverything->setText(tr("Sync the folder '%1'").arg(owncloudWizard()->remoteFolder())); - } - - if (!QDir(locFolder).entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty()) { - _ui.syncModeLabel->setText(tr("

    Warning: The local folder is not empty. " - "Pick a resolution!

    ")); - _ui.resolutionStackedWidget->setCurrentIndex(1); - } else { - _ui.resolutionStackedWidget->setCurrentIndex(0); - } - - setErrorString(errorStr); - emit completeChanged(); -} - -int OwncloudAdvancedSetupPage::nextId() const -{ - // tells the caller that this is the last dialog page - return -1; -} - -QStringList OwncloudAdvancedSetupPage::selectiveSyncBlacklist() const -{ - return _selectiveSyncBlacklist; -} - -bool OwncloudAdvancedSetupPage::manualFolderConfig() const -{ - return _ui.rManualFolder->isChecked(); -} - -bool OwncloudAdvancedSetupPage::isConfirmBigFolderChecked() const -{ - return _ui.rSyncEverything->isChecked() && _ui.confCheckBoxSize->isChecked(); -} - -bool OwncloudAdvancedSetupPage::validatePage() -{ - if (manualFolderConfig()) { - return true; - } - - if (owncloudWizard()->useVirtualFileSync()) { - const auto availability = Vfs::checkAvailability(owncloudWizard()->localFolder(), bestAvailableVfsMode()); - if (!availability) { - auto msg = new QMessageBox(QMessageBox::Warning, tr("Virtual files are not available for the selected folder"), availability.error(), QMessageBox::Ok, this); - msg->setAttribute(Qt::WA_DeleteOnClose); - msg->open(); - return false; - } - } - - if (!_created) { - setErrorString(QString()); - _checking = true; - _ui.progressIndicator->startAnimation(); - emit completeChanged(); - - if (_ui.rSyncEverything->isChecked()) { - ConfigFile cfgFile; - cfgFile.setNewBigFolderSizeLimit(_ui.confCheckBoxSize->isChecked(), - _ui.confSpinBox->value()); - cfgFile.setConfirmExternalStorage(_ui.confCheckBoxExternal->isChecked()); - } - - emit owncloudWizard()->createLocalAndRemoteFolders(); - return false; - } else { - // connecting is running - _checking = false; - emit completeChanged(); - _ui.progressIndicator->stopAnimation(); - return true; - } -} - -void OwncloudAdvancedSetupPage::setErrorString(const QString &err) -{ - if (!err.isEmpty()) { - auto msg = new QMessageBox(QMessageBox::Warning, tr("Error"), err, QMessageBox::Ok, this); - msg->setAttribute(Qt::WA_DeleteOnClose); - msg->open(); - } - _checking = false; - emit completeChanged(); -} - -void OwncloudAdvancedSetupPage::directoriesCreated() -{ - _checking = false; - _created = true; - _ui.progressIndicator->stopAnimation(); - emit completeChanged(); -} - -void OwncloudAdvancedSetupPage::setRemoteFolder(const QString &remoteFolder) -{ - if (!remoteFolder.isEmpty()) { - owncloudWizard()->setRemoteFolder(remoteFolder); - } -} - -void OwncloudAdvancedSetupPage::slotSelectFolder() -{ - QString dir = QFileDialog::getExistingDirectory(nullptr, tr("Local Sync Folder"), QDir::homePath()); - if (!dir.isEmpty()) { - _ui.pbSelectLocalFolder->setText(dir); - owncloudWizard()->setLocalFolder(dir); - updateStatus(); - } -} - -void OwncloudAdvancedSetupPage::slotSelectiveSyncClicked() -{ - AccountPtr acc = owncloudWizard()->account(); - SelectiveSyncDialog *dlg = new SelectiveSyncDialog(acc, owncloudWizard()->remoteFolder(), _selectiveSyncBlacklist, this); - dlg->setAttribute(Qt::WA_DeleteOnClose); - - connect(dlg, &SelectiveSyncDialog::finished, this, [this, dlg]{ - const int result = dlg->result(); - bool updateBlacklist = false; - - // We need to update the selective sync blacklist either when the dialog - // was accepted in that - // case the stub blacklist of / was expanded to the actual list of top - // level folders by the selective sync dialog. - if (result == QDialog::Accepted) { - _selectiveSyncBlacklist = dlg->createBlackList(); - updateBlacklist = true; - } else if (result == QDialog::Rejected && _selectiveSyncBlacklist == QStringList("/")) { - _selectiveSyncBlacklist = dlg->oldBlackList(); - updateBlacklist = true; - } - - if (updateBlacklist) { - if (!_selectiveSyncBlacklist.isEmpty()) { - auto s = dlg->estimatedSize(); - if (s > 0) { - _ui.lSelectiveSyncSizeLabel->setText(tr("(%1)").arg(Utility::octetsToString(s))); - } else { - _ui.lSelectiveSyncSizeLabel->setText(QString()); - } - } else { - setRadioChecked(_ui.rSyncEverything); - _ui.lSelectiveSyncSizeLabel->setText(QString()); - } - wizard()->setProperty("blacklist", _selectiveSyncBlacklist); - } - - }); - dlg->open(); -} - -void OwncloudAdvancedSetupPage::slotVirtualFileSyncClicked() -{ - if (!_ui.rVirtualFileSync->isChecked()) { - OwncloudWizard::askExperimentalVirtualFilesFeature(this, [this](bool enable) { - if (!enable) - return; - setRadioChecked(_ui.rVirtualFileSync); - }); - } -} - -void OwncloudAdvancedSetupPage::slotSyncEverythingClicked() -{ - _ui.lSelectiveSyncSizeLabel->setText(QString()); - setRadioChecked(_ui.rSyncEverything); - _selectiveSyncBlacklist.clear(); -} - -void OwncloudAdvancedSetupPage::slotQuotaRetrieved(const QMap &result) -{ - _ui.lSyncEverythingSizeLabel->setText(tr("(%1)").arg(Utility::octetsToString(result["size"].toDouble()))); -} - -void OwncloudAdvancedSetupPage::setRadioChecked(QRadioButton *radio) -{ - // We don't want clicking the radio buttons to immediately adjust the checked state - // for selective sync and virtual file sync, so we keep them uncheckable until - // they should be checked. - radio->setCheckable(true); - radio->setChecked(true); - - if (radio != _ui.rVirtualFileSync) - _ui.rVirtualFileSync->setCheckable(false); - - emit completeChanged(); -} - -} // namespace OCC diff --git a/src/gui/wizard/owncloudadvancedsetuppage.h b/src/gui/wizard/owncloudadvancedsetuppage.h deleted file mode 100644 index 467349241ed..00000000000 --- a/src/gui/wizard/owncloudadvancedsetuppage.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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. - */ - -#ifndef MIRALL_OWNCLOUD_ADVANCED_SETUP_PAGE_H -#define MIRALL_OWNCLOUD_ADVANCED_SETUP_PAGE_H - -#include - -#include "ui_owncloudadvancedsetuppage.h" -#include "wizard/abstractcredswizardpage.h" -#include "wizard/owncloudwizardcommon.h" - -class QProgressIndicator; - -namespace OCC { - -/** - * @brief The OwncloudAdvancedSetupPage class - * @ingroup gui - */ -class OwncloudAdvancedSetupPage : public AbstractWizardPage -{ - Q_OBJECT -public: - OwncloudAdvancedSetupPage(); - - bool isComplete() const override; - void initializePage() override; - int nextId() const override; - bool validatePage() override; - QStringList selectiveSyncBlacklist() const; - bool manualFolderConfig() const; - bool isConfirmBigFolderChecked() const; - void setRemoteFolder(const QString &remoteFolder); - void setMultipleFoldersExist(bool exist); - void directoriesCreated(); - -public slots: - void setErrorString(const QString &); - -private slots: - void slotSelectFolder(); - void slotSyncEverythingClicked(); - void slotSelectiveSyncClicked(); - void slotVirtualFileSyncClicked(); - void slotQuotaRetrieved(const QMap &result); - -private: - void setRadioChecked(QRadioButton *radio); - void updateStatus(); - - Ui_OwncloudAdvancedSetupPage _ui; - bool _checking; - bool _created; - bool _localFolderValid; - QStringList _selectiveSyncBlacklist; -}; - -} // namespace OCC - -#endif diff --git a/src/gui/wizard/owncloudadvancedsetuppage.ui b/src/gui/wizard/owncloudadvancedsetuppage.ui deleted file mode 100644 index 713cb54de67..00000000000 --- a/src/gui/wizard/owncloudadvancedsetuppage.ui +++ /dev/null @@ -1,526 +0,0 @@ - - - OwncloudAdvancedSetupPage - - - - 0 - 0 - 800 - 600 - - - - Form - - - - - - - - - - - - 0 - 0 - - - - - 48 - 48 - - - - - 48 - 48 - - - - - - - :/client/ownCloud/theme/colored/owncloud-icon.svg - - - true - - - Qt::AlignCenter - - - - - - - Server - - - false - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - 0 - - - 0 - - - - - - - S&ynchronize everything from server - - - true - - - - - - - TextLabel - - - Qt::PlainText - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - Ask for confirmation before synchroni&zing folders larger than - - - - - - - 999999 - - - 99 - - - - - - - MB - - - - - - - - - Ask for confirmation before synchronizing e&xternal storages - - - - - - - - - Choose what to sync - - - - - - - TextLabel - - - Qt::PlainText - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - - 0 - 0 - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - Manually create folder sync connections - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - 0 - 0 - - - - Use &virtual files !PLACEHOLDER! - - - false - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - 0 - 0 - - - - - 48 - 48 - - - - - 48 - 48 - - - - - - - :/client/resources/light/folder-sync.svg - - - true - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - &Local Folder - - - Qt::AlignCenter - - - pbSelectLocalFolder - - - - - - - Qt::Vertical - - - - 20 - 10 - - - - - - - - - - - - 0 - - - - - - 0 - 0 - - - - pbSelectLocalFolder - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 0 - 0 - - - - 0 - - - - - - - - Status message - - - Qt::RichText - - - true - - - - - - - &Keep local data - - - true - - - - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - true - - - - - - - Qt::Vertical - - - - 20 - 10 - - - - - - - - - - - - - - - - - - - - - QProgressIndicator - QWidget -
    QProgressIndicator.h
    - 1 -
    -
    - - rSyncEverything - confCheckBoxSize - confSpinBox - bSelectiveSync - rManualFolder - rVirtualFileSync - - - - - -
    diff --git a/src/gui/wizard/owncloudhttpcredspage.cpp b/src/gui/wizard/owncloudhttpcredspage.cpp deleted file mode 100644 index 6853af1ac9b..00000000000 --- a/src/gui/wizard/owncloudhttpcredspage.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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 "QProgressIndicator.h" - -#include "creds/httpcredentialsgui.h" -#include "theme.h" -#include "account.h" -#include "configfile.h" -#include "wizard/owncloudhttpcredspage.h" -#include "wizard/owncloudwizardcommon.h" -#include "wizard/owncloudwizard.h" - -namespace OCC { - -OwncloudHttpCredsPage::OwncloudHttpCredsPage(QWidget *parent) - : AbstractCredentialsWizardPage() - , _ui() - , _connected(false) - , _progressIndi(new QProgressIndicator(this)) -{ - _ui.setupUi(this); - - if (parent) { - _ocWizard = qobject_cast(parent); - } - - registerField(QLatin1String("OCUser*"), _ui.leUsername); - registerField(QLatin1String("OCPasswd*"), _ui.lePassword); - - Theme *theme = Theme::instance(); - switch (theme->userIDType()) { - case Theme::UserIDUserName: - // default, handled in ui file - break; - case Theme::UserIDEmail: - _ui.usernameLabel->setText(tr("&Email")); - break; - case Theme::UserIDCustom: - _ui.usernameLabel->setText(theme->customUserID()); - break; - default: - break; - } - _ui.leUsername->setPlaceholderText(theme->userIDHint()); - - setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(Theme::instance()->appNameGUI()))); - setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Enter user credentials"))); - - _ui.resultLayout->addWidget(_progressIndi); - stopSpinner(); -} - -void OwncloudHttpCredsPage::initializePage() -{ - WizardCommon::initErrorLabel(_ui.errorLabel); - - OwncloudWizard *ocWizard = qobject_cast(wizard()); - AbstractCredentials *cred = ocWizard->account()->credentials(); - HttpCredentials *httpCreds = qobject_cast(cred); - if (httpCreds) { - const QString user = httpCreds->fetchUser(); - if (!user.isEmpty()) { - _ui.leUsername->setText(user); - } - } else { - QUrl url = ocWizard->account()->url(); - - // If the final url does not have a username, check the - // user specified url too. Sometimes redirects can lose - // the user:pw information. - if (url.userName().isEmpty()) { - url = ocWizard->ocUrl(); - } - - const QString user = url.userName(); - const QString password = url.password(); - - if (!user.isEmpty()) { - _ui.leUsername->setText(user); - } - if (!password.isEmpty()) { - _ui.lePassword->setText(password); - } - } - _ui.tokenLabel->setText(HttpCredentialsGui::requestAppPasswordText(ocWizard->account().data())); - _ui.tokenLabel->setVisible(!_ui.tokenLabel->text().isEmpty()); - _ui.leUsername->setFocus(); -} - -void OwncloudHttpCredsPage::cleanupPage() -{ - _ui.leUsername->clear(); - _ui.lePassword->clear(); -} - -bool OwncloudHttpCredsPage::validatePage() -{ - if (_ui.leUsername->text().isEmpty() || _ui.lePassword->text().isEmpty()) { - return false; - } - - if (!_connected) { - _ui.errorLabel->setVisible(false); - startSpinner(); - - // Reset cookies to ensure the username / password is actually used - OwncloudWizard *ocWizard = qobject_cast(wizard()); - ocWizard->account()->clearCookieJar(); - - emit completeChanged(); - emit connectToOCUrl(field("OCUrl").toString().simplified()); - - if (Theme::instance()->wizardSkipAdvancedPage()) { - emit owncloudWizard()->createLocalAndRemoteFolders(); - } - - return false; - } else { - // Reset, to require another connection attempt next time - _connected = false; - - emit completeChanged(); - stopSpinner(); - return true; - } - return true; -} - -int OwncloudHttpCredsPage::nextId() const -{ - if (Theme::instance()->wizardSkipAdvancedPage()) { - return -1; - } - return WizardCommon::Page_AdvancedSetup; -} - -void OwncloudHttpCredsPage::setConnected() -{ - _connected = true; - stopSpinner(); -} - -void OwncloudHttpCredsPage::startSpinner() -{ - _ui.resultLayout->setEnabled(true); - _progressIndi->setVisible(true); - _progressIndi->startAnimation(); -} - -void OwncloudHttpCredsPage::stopSpinner() -{ - _ui.resultLayout->setEnabled(false); - _progressIndi->setVisible(false); - _progressIndi->stopAnimation(); -} - -void OwncloudHttpCredsPage::setErrorString(const QString &err) -{ - if (err.isEmpty()) { - _ui.errorLabel->setVisible(false); - } else { - _ui.errorLabel->setVisible(true); - _ui.errorLabel->setText(err); - } - emit completeChanged(); - stopSpinner(); -} - -AbstractCredentials *OwncloudHttpCredsPage::getCredentials() const -{ - return new HttpCredentialsGui(_ui.leUsername->text(), _ui.lePassword->text()); -} - - -} // namespace OCC diff --git a/src/gui/wizard/owncloudhttpcredspage.h b/src/gui/wizard/owncloudhttpcredspage.h deleted file mode 100644 index 97ac628d1ff..00000000000 --- a/src/gui/wizard/owncloudhttpcredspage.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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. - */ - -#ifndef MIRALL_OWNCLOUD_HTTP_CREDS_PAGE_H -#define MIRALL_OWNCLOUD_HTTP_CREDS_PAGE_H - -#include "wizard/abstractcredswizardpage.h" -#include "wizard/owncloudwizard.h" - -#include "ui_owncloudhttpcredspage.h" - -class QProgressIndicator; - -namespace OCC { - -/** - * @brief The OwncloudHttpCredsPage class - */ -class OwncloudHttpCredsPage : public AbstractCredentialsWizardPage -{ - Q_OBJECT -public: - OwncloudHttpCredsPage(QWidget *parent); - - AbstractCredentials *getCredentials() const override; - - void initializePage() override; - void cleanupPage() override; - bool validatePage() override; - int nextId() const override; - void setConnected(); - void setErrorString(const QString &err); - -Q_SIGNALS: - void connectToOCUrl(const QString &); - -private: - void startSpinner(); - void stopSpinner(); - Ui_OwncloudHttpCredsPage _ui; - bool _connected; - QProgressIndicator *_progressIndi; - OwncloudWizard *_ocWizard; -}; - -} // namespace OCC - -#endif diff --git a/src/gui/wizard/owncloudhttpcredspage.ui b/src/gui/wizard/owncloudhttpcredspage.ui deleted file mode 100644 index 846aef42bdd..00000000000 --- a/src/gui/wizard/owncloudhttpcredspage.ui +++ /dev/null @@ -1,100 +0,0 @@ - - - OwncloudHttpCredsPage - - - - 0 - 0 - 350 - 248 - - - - Form - - - - - - TextLabel - - - Qt::RichText - - - true - - - true - - - - - - - - - &Username - - - leUsername - - - - - - - &Password - - - lePassword - - - - - - - Error Label - - - Qt::RichText - - - true - - - - - - - - - - QLineEdit::Password - - - - - - - - - Qt::Vertical - - - - 20 - 68 - - - - - - - - - - - - diff --git a/src/gui/wizard/owncloudoauthcredspage.cpp b/src/gui/wizard/owncloudoauthcredspage.cpp deleted file mode 100644 index e577c4faca5..00000000000 --- a/src/gui/wizard/owncloudoauthcredspage.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 -#include -#include - -#include "application.h" -#include "wizard/owncloudoauthcredspage.h" -#include "theme.h" -#include "account.h" -#include "cookiejar.h" -#include "settingsdialog.h" -#include "wizard/owncloudwizardcommon.h" -#include "wizard/owncloudwizard.h" -#include "creds/httpcredentialsgui.h" - -namespace OCC { - -OwncloudOAuthCredsPage::OwncloudOAuthCredsPage() - : AbstractCredentialsWizardPage() -{ - _ui.setupUi(this); - - _ui.topLabel->hide(); - _ui.bottomLabel->hide(); - - WizardCommon::initErrorLabel(_ui.errorLabel); - - setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(Theme::instance()->appNameGUI()))); - setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Login in your browser"))); - - connect(_ui.openLinkButton, &QCommandLinkButton::clicked, [this] { - _ui.errorLabel->hide(); - if (_asyncAuth) - _asyncAuth->openBrowser(); - }); - _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) { - _asyncAuth->authorisationLinkAsync([](const QUrl &link) { - QApplication::clipboard()->setText(link.toString(QUrl::FullyEncoded)); - }); - } - }); - menu->setAttribute(Qt::WA_DeleteOnClose); - menu->popup(_ui.openLinkButton->mapToGlobal(pos)); - }); -} - -void OwncloudOAuthCredsPage::initializePage() -{ - OwncloudWizard *ocWizard = qobject_cast(wizard()); - Q_ASSERT(ocWizard); - ocWizard->account()->setCredentials(new HttpCredentialsGui); - _asyncAuth.reset(new OAuth(ocWizard->account().data(), this)); - connect(_asyncAuth.data(), &OAuth::result, this, &OwncloudOAuthCredsPage::asyncAuthResult, Qt::QueuedConnection); - _asyncAuth->startAuthentication(); - ocApp()->gui()->settingsDialog()->showMinimized(); -} - -void OCC::OwncloudOAuthCredsPage::cleanupPage() -{ - // The next or back button was activated, show the wizard again - wizard()->showNormal(); - _asyncAuth.reset(); -} - -void OwncloudOAuthCredsPage::asyncAuthResult(OAuth::Result r, const QString &user, - const QString &token, const QString &refreshToken) -{ - switch (r) { - case OAuth::NotSupported: { - /* OAuth not supported (can't open browser), fallback to HTTP credentials */ - owncloudWizard()->back(); - owncloudWizard()->setAuthType(DetermineAuthTypeJob::AuthType::Basic); - break; - } - case OAuth::Error: - /* Error while getting the access token. (Timeout, or the server did not accept our client credentials */ - _ui.errorLabel->show(); - wizard()->showNormal(); - break; - case OAuth::LoggedIn: { - _token = token; - _user = user; - _refreshToken = refreshToken; - emit connectToOCUrl(owncloudWizard()->account()->url().toString()); - if (Theme::instance()->wizardSkipAdvancedPage()) { - emit owncloudWizard()->createLocalAndRemoteFolders(); - } - break; - } - } -} - -int OwncloudOAuthCredsPage::nextId() const -{ - if (Theme::instance()->wizardSkipAdvancedPage()) { - return -1; - } - return WizardCommon::Page_AdvancedSetup; -} - -void OwncloudOAuthCredsPage::setConnected() -{ - wizard()->showNormal(); -} - -AbstractCredentials *OwncloudOAuthCredsPage::getCredentials() const -{ - return new HttpCredentialsGui(_user, _token, _refreshToken); -} - -bool OwncloudOAuthCredsPage::isComplete() const -{ - return false; /* We can never go forward manually */ -} - -} // namespace OCC diff --git a/src/gui/wizard/owncloudoauthcredspage.h b/src/gui/wizard/owncloudoauthcredspage.h deleted file mode 100644 index 32341eb1cde..00000000000 --- a/src/gui/wizard/owncloudoauthcredspage.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 -#include -#include -#include -#include - -#include "wizard/abstractcredswizardpage.h" -#include "accountfwd.h" -#include "creds/oauth.h" - -#include "ui_owncloudoauthcredspage.h" - - -namespace OCC { - - -class OwncloudOAuthCredsPage : public AbstractCredentialsWizardPage -{ - Q_OBJECT -public: - OwncloudOAuthCredsPage(); - - AbstractCredentials *getCredentials() const override; - - void initializePage() override; - void cleanupPage() override; - int nextId() const override; - void setConnected(); - bool isComplete() const override; - -public Q_SLOTS: - void asyncAuthResult(OAuth::Result, const QString &user, const QString &token, - const QString &reniewToken); - -signals: - void connectToOCUrl(const QString &); - -public: - QString _user; - QString _token; - QString _refreshToken; - QScopedPointer _asyncAuth; - Ui_OwncloudOAuthCredsPage _ui; -}; - -} // namespace OCC diff --git a/src/gui/wizard/owncloudoauthcredspage.ui b/src/gui/wizard/owncloudoauthcredspage.ui deleted file mode 100644 index 285ef3a2b58..00000000000 --- a/src/gui/wizard/owncloudoauthcredspage.ui +++ /dev/null @@ -1,87 +0,0 @@ - - - OwncloudOAuthCredsPage - - - - 0 - 0 - 424 - 373 - - - - Form - - - - - - TextLabel - - - Qt::RichText - - - Qt::AlignCenter - - - true - - - - - - - Please switch to your browser to proceed. - - - true - - - - - - - An error occurred while connecting. Please try again. - - - Qt::PlainText - - - - - - - Re-open Browser - - - - - - - Qt::Vertical - - - - 20 - 127 - - - - - - - - TextLabel - - - Qt::RichText - - - - - - - - diff --git a/src/gui/wizard/owncloudsetuppage.cpp b/src/gui/wizard/owncloudsetuppage.cpp deleted file mode 100644 index dfb7f33c47e..00000000000 --- a/src/gui/wizard/owncloudsetuppage.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "QProgressIndicator.h" - -#include "guiutility.h" -#include "wizard/owncloudwizardcommon.h" -#include "wizard/owncloudsetuppage.h" -#include "theme.h" -#include "account.h" - -#include "ui_owncloudsetuppage.h" - -namespace OCC { - -OwncloudSetupPage::OwncloudSetupPage(QWidget *parent) - : QWizardPage() - , _ui(new Ui_OwncloudSetupPage) - , _oCUrl() - , _ocUser() - , _authTypeKnown(false) - , _checking(false) - , _progressIndi(new QProgressIndicator(this)) -{ - _ui->setupUi(this); - _ocWizard = qobject_cast(parent); - - Theme *theme = Theme::instance(); - setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(theme->appNameGUI()))); - setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Setup %1 server").arg(theme->appNameGUI()))); - - if (theme->overrideServerUrlV2().isEmpty()) { - _ui->leUrl->setPostfix(theme->wizardUrlPostfix()); - _ui->leUrl->setPlaceholderText(theme->wizardUrlHint()); - } else { - _ui->leUrl->setEnabled(false); - } - - - registerField(QLatin1String("OCUrl*"), _ui->leUrl); - - _ui->resultLayout->addWidget(_progressIndi); - stopSpinner(); - - slotUrlChanged(QString()); // don't jitter UI - connect(_ui->leUrl, &QLineEdit::textChanged, this, &OwncloudSetupPage::slotUrlChanged); - connect(_ui->leUrl, &QLineEdit::editingFinished, this, &OwncloudSetupPage::slotUrlEditFinished); -} - -void OwncloudSetupPage::setServerUrl(const QString &newUrl) -{ - _oCUrl = newUrl; - if (_oCUrl.isEmpty()) { - _ui->leUrl->clear(); - return; - } - - _ui->leUrl->setText(_oCUrl); -} - -// slot hit from textChanged of the url entry field. -void OwncloudSetupPage::slotUrlChanged(const QString &url) -{ - _authTypeKnown = false; - - QString newUrl = url; - if (url.endsWith("index.php")) { - newUrl.chop(9); - } - if (_ocWizard && _ocWizard->account()) { - QString webDavPath = _ocWizard->account()->davPath(); - if (url.endsWith(webDavPath)) { - newUrl.chop(webDavPath.length()); - } - if (webDavPath.endsWith(QLatin1Char('/'))) { - webDavPath.chop(1); // cut off the slash - if (url.endsWith(webDavPath)) { - newUrl.chop(webDavPath.length()); - } - } - } - if (newUrl != url) { - _ui->leUrl->setText(newUrl); - } - - if (!url.startsWith(QLatin1String("https://"))) { - _ui->urlLabel->setPixmap(Utility::getCoreIcon(QStringLiteral("lock-http")).pixmap(_ui->urlLabel->size())); - _ui->urlLabel->setToolTip(tr("This url is NOT secure as it is not encrypted.\n" - "It is not advisable to use it.")); - } else { - _ui->urlLabel->setPixmap(Utility::getCoreIcon(QStringLiteral("lock-https")).pixmap(_ui->urlLabel->size())); - _ui->urlLabel->setToolTip(tr("This url is secure. You can use it.")); - } -} - -void OwncloudSetupPage::slotUrlEditFinished() -{ - QString url = _ui->leUrl->fullText(); - if (QUrl(url).isRelative() && !url.isEmpty()) { - // no scheme defined, set one - url.prepend("https://"); - _ui->leUrl->setFullText(url); - } -} - -bool OwncloudSetupPage::isComplete() const -{ - return !_ui->leUrl->text().isEmpty() && !_checking; -} - -void OwncloudSetupPage::initializePage() -{ - WizardCommon::initErrorLabel(_ui->errorLabel); - - _authTypeKnown = false; - _checking = false; - - QAbstractButton *nextButton = wizard()->button(QWizard::NextButton); - QPushButton *pushButton = qobject_cast(nextButton); - if (pushButton) - pushButton->setDefault(true); - - // If url is overriden by theme, it's already set and - // we just check the server type and switch to second page - // immediately. - if (Theme::instance()->overrideServerUrlV2().isEmpty()) { - _ui->leUrl->setFocus(); - } else { - setCommitPage(true); - // Hack: setCommitPage() changes caption, but after an error this page could still be visible - setButtonText(QWizard::CommitButton, tr("&Next >")); - validatePage(); - } -} - -int OwncloudSetupPage::nextId() const -{ - switch (_ocWizard->authType()) { - case DetermineAuthTypeJob::AuthType::Basic: - return WizardCommon::Page_HttpCreds; - case DetermineAuthTypeJob::AuthType::OAuth: - return WizardCommon::Page_OAuthCreds; - case DetermineAuthTypeJob::AuthType::Unknown: - Q_UNREACHABLE(); - } - return WizardCommon::Page_HttpCreds; -} - -QString OwncloudSetupPage::url() const -{ - QString url = _ui->leUrl->fullText().simplified(); - return url; -} - -bool OwncloudSetupPage::validatePage() -{ - if (!_authTypeKnown) { - slotUrlEditFinished(); - QString u = url(); - QUrl qurl(u); - if (!qurl.isValid() || qurl.host().isEmpty()) { - setErrorString(tr("Invalid URL")); - return false; - } - - setErrorString(QString()); - _checking = true; - startSpinner(); - emit completeChanged(); - - emit determineAuthType(u); - return false; - } else { - // connecting is running - stopSpinner(); - _checking = false; - emit completeChanged(); - return true; - } -} - -void OwncloudSetupPage::setAuthType() -{ - _authTypeKnown = true; - stopSpinner(); -} - -void OwncloudSetupPage::setErrorString(const QString &err) -{ - if (err.isEmpty()) { - _ui->errorLabel->setVisible(false); - } else { - _ui->errorLabel->setVisible(true); - _ui->errorLabel->setText(err); - } - _checking = false; - emit completeChanged(); - stopSpinner(); -} - -void OwncloudSetupPage::startSpinner() -{ - _ui->resultLayout->setEnabled(true); - _progressIndi->setVisible(true); - _progressIndi->startAnimation(); -} - -void OwncloudSetupPage::stopSpinner() -{ - _ui->resultLayout->setEnabled(false); - _progressIndi->setVisible(false); - _progressIndi->stopAnimation(); -} - -OwncloudSetupPage::~OwncloudSetupPage() -{ -} - -} // namespace OCC diff --git a/src/gui/wizard/owncloudsetuppage.h b/src/gui/wizard/owncloudsetuppage.h deleted file mode 100644 index 8caa9d7ec6a..00000000000 --- a/src/gui/wizard/owncloudsetuppage.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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. - */ - -#ifndef MIRALL_OWNCLOUD_SETUP_PAGE_H -#define MIRALL_OWNCLOUD_SETUP_PAGE_H - -#include -#include - -#include "wizard/owncloudwizardcommon.h" -#include "wizard/owncloudwizard.h" - -class QLabel; -class QVariant; -class QProgressIndicator; -class QButtonGroup; -class Ui_OwncloudSetupPage; - -namespace OCC { - -/** - * @brief The OwncloudSetupPage class - * @ingroup gui - */ -class OwncloudSetupPage : public QWizardPage -{ - Q_OBJECT -public: - OwncloudSetupPage(QWidget *parent = nullptr); - ~OwncloudSetupPage() override; - - bool isComplete() const override; - void initializePage() override; - int nextId() const override; - void setServerUrl(const QString &); - void setAllowPasswordStorage(bool); - bool validatePage() override; - QString url() const; - QString localFolder() const; - void setRemoteFolder(const QString &remoteFolder); - void setMultipleFoldersExist(bool exist); - void setAuthType(); - -public slots: - void setErrorString(const QString &); - void startSpinner(); - void stopSpinner(); - -protected slots: - void slotUrlChanged(const QString &); - void slotUrlEditFinished(); - -signals: - void determineAuthType(const QString &); - -private: - QScopedPointer _ui; - - QString _oCUrl; - QString _ocUser; - bool _authTypeKnown; - bool _checking; - bool _multipleFoldersExist; - - QProgressIndicator *_progressIndi; - QButtonGroup *_selectiveSyncButtons; - QString _remoteFolder; - OwncloudWizard *_ocWizard; -}; - -} // namespace OCC - -#endif diff --git a/src/gui/wizard/owncloudsetuppage.ui b/src/gui/wizard/owncloudsetuppage.ui deleted file mode 100644 index cf65e3bf0fd..00000000000 --- a/src/gui/wizard/owncloudsetuppage.ui +++ /dev/null @@ -1,202 +0,0 @@ - - - OwncloudSetupPage - - - - 0 - 0 - 506 - 515 - - - - Form - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 13 - - - - - - - - - - Ser&ver Address - - - leUrl - - - - - - - - 0 - 0 - - - - https://... - - - - - - - - 0 - 0 - - - - - 16 - 16 - - - - - 16 - 16 - - - - - - - :/client/resources/light/lock-https.svg - - - true - - - - - - - - - Qt::Vertical - - - QSizePolicy::Minimum - - - - 20 - 18 - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 40 - 20 - - - - - - - - - - - 0 - 0 - - - - Error Label - - - Qt::RichText - - - true - - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 200 - - - - - - - - 0 - - - - - - - - OCC::PostfixLineEdit - QLineEdit -
    wizard/postfixlineedit.h
    -
    -
    - - - - -
    diff --git a/src/gui/wizard/owncloudwizard.cpp b/src/gui/wizard/owncloudwizard.cpp deleted file mode 100644 index 8f537509879..00000000000 --- a/src/gui/wizard/owncloudwizard.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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 "account.h" -#include "config.h" -#include "configfile.h" -#include "theme.h" - -#include "application.h" -#include "settingsdialog.h" - -#include "wizard/owncloudwizard.h" -#include "wizard/owncloudsetuppage.h" -#include "wizard/owncloudhttpcredspage.h" -#include "wizard/owncloudoauthcredspage.h" -#include "wizard/owncloudadvancedsetuppage.h" - -#include "common/vfs.h" - -#include "QProgressIndicator.h" - -#include -#include -#include -#include - -#include - -namespace { -auto initLocalFolder() -{ - auto localFolder = OCC::Theme::instance()->defaultClientFolder(); - // Update the local folder - this is not guaranteed to find a good one - // if its a relative path, prepend with users home dir, otherwise use as absolute path - - if (!QDir(localFolder).isAbsolute()) { - localFolder = QDir::homePath() + QDir::separator() + localFolder; - } - return OCC::FolderMan::instance()->findGoodPathForNewSyncFolder(localFolder); -} -} -namespace OCC { - -Q_LOGGING_CATEGORY(lcWizard, "gui.wizard", QtInfoMsg) - -OwncloudWizard::OwncloudWizard(QWidget *parent) - : QWizard(parent, Qt::WindowFlags() & ~Qt::WindowContextHelpButtonHint) - , _remoteFolder(Theme::instance()->defaultServerFolder()) - , _localFolder(initLocalFolder()) - , _useVirtualFileSync(bestAvailableVfsMode() == Vfs::WindowsCfApi) - , _account(nullptr) - , _setupPage(new OwncloudSetupPage(this)) - , _httpCredsPage(new OwncloudHttpCredsPage(this)) - , _oauthCredsPage(new OwncloudOAuthCredsPage) - , _advancedSetupPage(new OwncloudAdvancedSetupPage) - , _credentialsPage(nullptr) -{ - setObjectName("owncloudWizard"); - - setPage(WizardCommon::Page_ServerSetup, _setupPage); - setPage(WizardCommon::Page_HttpCreds, _httpCredsPage); - setPage(WizardCommon::Page_OAuthCreds, _oauthCredsPage); - setPage(WizardCommon::Page_AdvancedSetup, _advancedSetupPage); - - connect(this, &QDialog::finished, this, &OwncloudWizard::basicSetupFinished); - - // note: start Id is set by the calling class depending on if the - // welcome text is to be shown or not. - setWizardStyle(QWizard::ModernStyle); - - connect(this, &QWizard::currentIdChanged, this, &OwncloudWizard::slotCurrentPageChanged); - connect(_setupPage, &OwncloudSetupPage::determineAuthType, this, &OwncloudWizard::determineAuthType); - connect(_httpCredsPage, &OwncloudHttpCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl); - connect(_oauthCredsPage, &OwncloudOAuthCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl); - - // banner size requires a fixed size - setFixedSize(ocApp()->gui()->settingsDialog()->sizeHintForChild()); - - Theme *theme = Theme::instance(); - setWindowTitle(tr("%1 Connection Wizard").arg(theme->appNameGUI())); - setWizardStyle(QWizard::ModernStyle); - setPixmap(QWizard::BannerPixmap, theme->wizardHeaderBanner({ width(), 78 })); - setPixmap(QWizard::LogoPixmap, theme->wizardHeaderLogo().pixmap(132, 63)); - setOption(QWizard::NoBackButtonOnStartPage); - setOption(QWizard::NoBackButtonOnLastPage); - setOption(QWizard::NoCancelButton, false); - setOption(QWizard::CancelButtonOnLeft); - setTitleFormat(Qt::RichText); - setSubTitleFormat(Qt::RichText); -} - -void OwncloudWizard::setAccount(AccountPtr account) -{ - _account = account; -} - -AccountPtr OwncloudWizard::account() const -{ - return _account; -} - -const QString &OwncloudWizard::localFolder() const -{ - return _localFolder; -} - -void OwncloudWizard::setLocalFolder(const QString &newLocalFolder) -{ - _localFolder = newLocalFolder; -} - -QStringList OwncloudWizard::selectiveSyncBlacklist() const -{ - return _advancedSetupPage->selectiveSyncBlacklist(); -} - -bool OwncloudWizard::useVirtualFileSync() const -{ - return _useVirtualFileSync; -} - -bool OwncloudWizard::manualFolderConfig() const -{ - return _advancedSetupPage->manualFolderConfig(); -} - -bool OwncloudWizard::isConfirmBigFolderChecked() const -{ - return _advancedSetupPage->isConfirmBigFolderChecked(); -} - -QString OwncloudWizard::ocUrl() const -{ - QString url = field("OCUrl").toString().simplified(); - return url; -} - -void OwncloudWizard::setRemoteFolder(const QString &remoteFolder) -{ - _remoteFolder = remoteFolder; -} - -void OwncloudWizard::successfulStep() -{ - const int id(currentId()); - - switch (id) { - case WizardCommon::Page_HttpCreds: - _httpCredsPage->setConnected(); - break; - - case WizardCommon::Page_OAuthCreds: - _oauthCredsPage->setConnected(); - break; - - case WizardCommon::Page_AdvancedSetup: - _advancedSetupPage->directoriesCreated(); - break; - - case WizardCommon::Page_ServerSetup: - qCWarning(lcWizard, "Should not happen at this stage."); - break; - } - - ownCloudGui::raiseDialog(this); - if (nextId() == -1) { - disconnect(this, &QDialog::finished, this, &OwncloudWizard::basicSetupFinished); - emit basicSetupFinished(QDialog::Accepted); - } else { - next(); - } -} - -void OwncloudWizard::setUseVirtualFileSync(bool newUseVirtualFileSync) -{ - _useVirtualFileSync = newUseVirtualFileSync; -} - -const QString &OwncloudWizard::remoteFolder() const -{ - return _remoteFolder; -} - -void OwncloudWizard::resetRemoteFolder() -{ - _remoteFolder = Theme::instance()->defaultServerFolder(); -} - -DetermineAuthTypeJob::AuthType OwncloudWizard::authType() const -{ - return _authType; -} - -void OwncloudWizard::setAuthType(DetermineAuthTypeJob::AuthType type) -{ - _authType = type; - _setupPage->setAuthType(); - if (type == DetermineAuthTypeJob::AuthType::OAuth) { - _credentialsPage = _oauthCredsPage; - } else { // try Basic auth even for "Unknown" - _credentialsPage = _httpCredsPage; - } - next(); -} - -// TODO: update this function -void OwncloudWizard::slotCurrentPageChanged(int id) -{ - qCDebug(lcWizard) << "Current Wizard page changed to " << id; - - if (id == WizardCommon::Page_ServerSetup) { - emit clearPendingRequests(); - } -} - -void OwncloudWizard::displayError(const QString &msg) -{ - switch (currentId()) { - case WizardCommon::Page_ServerSetup: - _setupPage->setErrorString(msg); - break; - - case WizardCommon::Page_HttpCreds: - _httpCredsPage->setErrorString(msg); - break; - - case WizardCommon::Page_AdvancedSetup: - _advancedSetupPage->setErrorString(msg); - break; - } -} - -void OwncloudWizard::setOCUrl(const QString &url) -{ - _setupPage->setServerUrl(url); -} - -AbstractCredentials *OwncloudWizard::getCredentials() const -{ - if (_credentialsPage) { - return _credentialsPage->getCredentials(); - } - - return nullptr; -} - -void OwncloudWizard::askExperimentalVirtualFilesFeature(QWidget *receiver, const std::function &callback) -{ - const auto bestVfsMode = bestAvailableVfsMode(); - QMessageBox *msgBox = nullptr; - QPushButton *acceptButton = nullptr; - switch (bestVfsMode) - { - case Vfs::WindowsCfApi: - callback(true); - return; - case Vfs::WithSuffix: - msgBox = new QMessageBox( - QMessageBox::Warning, - tr("Enable experimental feature?"), - tr("When the \"virtual files\" mode is enabled no files will be downloaded initially. " - "Instead, a tiny \"%1\" file will be created for each file that exists on the server. " - "The contents can be downloaded by running these files or by using their context menu." - "\n\n" - "The virtual files mode is mutually exclusive with selective sync. " - "Currently unselected folders will be translated to online-only folders " - "and your selective sync settings will be reset." - "\n\n" - "Switching to this mode will abort any currently running synchronization." - "\n\n" - "This is a new, experimental mode. If you decide to use it, please report any " - "issues that come up.") - .arg(APPLICATION_DOTVIRTUALFILE_SUFFIX), QMessageBox::NoButton, receiver); - acceptButton = msgBox->addButton(tr("Enable experimental placeholder mode"), QMessageBox::AcceptRole); - msgBox->addButton(tr("Stay safe"), QMessageBox::RejectRole); - break; - case Vfs::Off: - Q_UNREACHABLE(); - } - - connect(msgBox, &QMessageBox::accepted, receiver, [callback, msgBox, acceptButton] { - callback(msgBox->clickedButton() == acceptButton); - msgBox->deleteLater(); - }); - msgBox->open(); -} - -} // end namespace diff --git a/src/gui/wizard/owncloudwizard.h b/src/gui/wizard/owncloudwizard.h deleted file mode 100644 index 4a4d31f2828..00000000000 --- a/src/gui/wizard/owncloudwizard.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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. - */ - -#ifndef MIRALL_OWNCLOUD_WIZARD_H -#define MIRALL_OWNCLOUD_WIZARD_H - -#include -#include -#include -#include - -#include "networkjobs.h" -#include "wizard/owncloudwizardcommon.h" -#include "accountfwd.h" - -namespace OCC { - -Q_DECLARE_LOGGING_CATEGORY(lcWizard) - -class OwncloudSetupPage; -class OwncloudHttpCredsPage; -class OwncloudOAuthCredsPage; -class OwncloudAdvancedSetupPage; -class AbstractCredentials; -class AbstractCredentialsWizardPage; - -/** - * @brief The OwncloudWizard class - * @ingroup gui - */ -class OwncloudWizard : public QWizard -{ - Q_OBJECT -public: - OwncloudWizard(QWidget *parent = nullptr); - - void setAccount(AccountPtr account); - AccountPtr account() const; - void setOCUrl(const QString &); - - QString ocUrl() const; - QStringList selectiveSyncBlacklist() const; - bool manualFolderConfig() const; - bool isConfirmBigFolderChecked() const; - - void displayError(const QString &); - AbstractCredentials *getCredentials() const; - - /** - * Shows a dialog explaining the virtual files mode and warning about it - * being experimental. Calles the callback with true if enabling was - * chosen. - */ - static void askExperimentalVirtualFilesFeature(QWidget *receiver, const std::function &callback); - - DetermineAuthTypeJob::AuthType authType() const; - - void setLocalFolder(const QString &newLocalFolder); - const QString &localFolder() const; - - void setRemoteFolder(const QString &); - const QString &remoteFolder() const; - void resetRemoteFolder(); - - void setUseVirtualFileSync(bool newUseVirtualFileSync); - bool useVirtualFileSync() const; - -public slots: - void setAuthType(DetermineAuthTypeJob::AuthType type); - void slotCurrentPageChanged(int); - void successfulStep(); - -signals: - void createLocalAndRemoteFolders(); - void clearPendingRequests(); - void determineAuthType(const QString &); - void connectToOCUrl(const QString &); - // make sure to connect to this, rather than finished(int)!! - void basicSetupFinished(int); - void needCertificate(); - -private: - QString _remoteFolder; - QString _localFolder; - bool _useVirtualFileSync; - AccountPtr _account; - OwncloudSetupPage *_setupPage; - OwncloudHttpCredsPage *_httpCredsPage; - OwncloudOAuthCredsPage *_oauthCredsPage; - OwncloudAdvancedSetupPage *_advancedSetupPage; - AbstractCredentialsWizardPage *_credentialsPage; - DetermineAuthTypeJob::AuthType _authType; - - friend class OwncloudSetupWizard; -}; - -} // namespace OCC - -#endif diff --git a/src/gui/wizard/owncloudwizardcommon.cpp b/src/gui/wizard/owncloudwizardcommon.cpp deleted file mode 100644 index 03b85bedcfc..00000000000 --- a/src/gui/wizard/owncloudwizardcommon.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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 -#include -#include - -#include "wizard/owncloudwizardcommon.h" -#include "theme.h" - -namespace OCC { - -namespace WizardCommon { - - QString titleTemplate() - { - return QString::fromLatin1("").arg(Theme::instance()->wizardHeaderTitleColor().name()) + QString::fromLatin1("%1"); - } - - QString subTitleTemplate() - { - return QString::fromLatin1("").arg(Theme::instance()->wizardHeaderSubTitleColor().name()) + QString::fromLatin1("%1"); - } - - void initErrorLabel(QLabel *errorLabel) - { - QString style = QLatin1String("border: 1px solid #eed3d7; border-radius: 5px; padding: 3px;" - "background-color: #f2dede; color: #b94a48;"); - - errorLabel->setStyleSheet(style); - errorLabel->setWordWrap(true); - errorLabel->setVisible(false); - } - -} // ns WizardCommon - -} // namespace OCC diff --git a/src/gui/wizard/owncloudwizardcommon.h b/src/gui/wizard/owncloudwizardcommon.h deleted file mode 100644 index 961e411bcb7..00000000000 --- a/src/gui/wizard/owncloudwizardcommon.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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. - */ - -#ifndef MIRALL_OWNCLOUD_WIZARD_COMMON_H -#define MIRALL_OWNCLOUD_WIZARD_COMMON_H - -class QVariant; -class QLabel; - -namespace OCC { - -namespace WizardCommon { - - QString titleTemplate(); - QString subTitleTemplate(); - void initErrorLabel(QLabel *errorLabel); - - enum SyncMode { - SelectiveMode, - BoxMode - }; - - enum Pages { - Page_ServerSetup, - Page_HttpCreds, - Page_OAuthCreds, - Page_AdvancedSetup - }; - -} // ns WizardCommon - -} // namespace OCC - -#endif // MIRALL_OWNCLOUD_WIZARD_COMMON_H diff --git a/src/gui/wizard/postfixlineedit.cpp b/src/gui/wizard/postfixlineedit.cpp deleted file mode 100644 index 1cc44a248b1..00000000000 --- a/src/gui/wizard/postfixlineedit.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2016 by Daniel Molkentin - * - * 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 -#include - -#include "postfixlineedit.h" - -namespace OCC { - -const int horizontalMargin(4); -const int verticalMargin(4); - -PostfixLineEdit::PostfixLineEdit(QWidget *parent) - : QLineEdit(parent) -{ -} - -void PostfixLineEdit::setPostfix(const QString &postfix) -{ - _postfix = postfix; - QFontMetricsF fm(font()); - QMargins tm = textMargins(); - tm.setRight(tm.right() + fm.width(_postfix) + verticalMargin); - setTextMargins(tm); -} - -QString PostfixLineEdit::postfix() const -{ - return _postfix; -} - -QString PostfixLineEdit::fullText() const -{ - return text() + _postfix; -} - -void PostfixLineEdit::setFullText(const QString &text) -{ - QString prefixString = text; - if (prefixString.endsWith(postfix())) { - prefixString.chop(postfix().length()); - } - setText(prefixString); -} - -void PostfixLineEdit::paintEvent(QPaintEvent *pe) -{ - QLineEdit::paintEvent(pe); - QPainter p(this); - - // - p.setPen(palette().color(QPalette::Disabled, QPalette::Text)); - QFontMetricsF fm(font()); - int start = rect().right() - fm.width(_postfix); - QStyleOptionFrame panel; - initStyleOption(&panel); - QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this); - r.setTop(r.top() + horizontalMargin - 1); - QRect postfixRect(r); - - postfixRect.setLeft(start - verticalMargin); - p.drawText(postfixRect, _postfix); -} - -} // namespace OCC diff --git a/src/gui/wizard/postfixlineedit.h b/src/gui/wizard/postfixlineedit.h deleted file mode 100644 index 1e8078f05e0..00000000000 --- a/src/gui/wizard/postfixlineedit.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2016 by Daniel Molkentin - * - * 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. - */ - -#ifndef OCC_POSTFIXLINEEDIT_H -#define OCC_POSTFIXLINEEDIT_H - -#include -#include -#include - -namespace OCC { - -/** - * @brief A lineedit class with a pre-set postfix. - * - * Useful e.g. for setting a fixed domain name. - */ - -class PostfixLineEdit : public QLineEdit -{ - Q_OBJECT -public: - PostfixLineEdit(QWidget *parent); - - /** @brief sets an optional postfix shown greyed out */ - void setPostfix(const QString &postfix); - /** @brief retrives the postfix */ - QString postfix() const; - /** @brief retrieves combined text() and postfix() */ - QString fullText() const; - - /** @brief sets text() from full text, discarding prefix() */ - void setFullText(const QString &text); - -protected: - void paintEvent(QPaintEvent *pe) override; - -private: - QString _postfix; -}; - - -} // namespace OCC - -#endif // OCC_POSTFIXLINEEDIT_H diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index b33d755b5f6..5c5278c306f 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -1,11 +1,12 @@ -add_definitions(-DQT_NO_CAST_TO_ASCII - -DQT_NO_CAST_FROM_ASCII - -DQT_NO_URL_CAST_FROM_STRING - -DQT_NO_CAST_FROM_BYTEARRAY) +find_package(LibreGraphAPI 1.0.4 REQUIRED) +set_package_properties(LibreGraphAPI PROPERTIES + URL https://github.com/owncloud/libre-graph-api-cpp-qt-client.git + DESCRIPTION "Libre Graph is a free API for cloud collaboration inspired by the MS Graph API." + TYPE OPTIONAL +) set(libsync_SRCS account.cpp - bandwidthmanager.cpp capabilities.cpp cookiejar.cpp discovery.cpp @@ -20,16 +21,17 @@ set(libsync_SRCS networkjobs.cpp owncloudpropagator.cpp owncloudtheme.cpp + platform.cpp progressdispatcher.cpp propagatorjobs.cpp propagatedownload.cpp - propagateupload.cpp - propagateuploadv1.cpp - propagateuploadng.cpp + propagateuploadcommon.cpp + propagateuploadfile.cpp propagateuploadtus.cpp propagateremotedelete.cpp propagateremotemove.cpp propagateremotemkdir.cpp + putfilejob.cpp syncengine.cpp syncfileitem.cpp syncfilestatustracker.cpp @@ -37,68 +39,93 @@ set(libsync_SRCS syncresult.cpp syncoptions.cpp theme.cpp + uploaddevice.cpp + creds/credentialmanager.cpp - creds/dummycredentials.cpp creds/abstractcredentials.cpp - creds/credentialscommon.cpp - creds/oauth.cpp + + networkjobs/fetchuserinfojobfactory.cpp + networkjobs/checkserverjobfactory.cpp + networkjobs/jsonjob.cpp + networkjobs/resources.cpp + + abstractcorejob.cpp + + appprovider.cpp + config/appconfig.cpp + config/openidconfig.cpp ) -if(TOKEN_AUTH_ONLY) - set (libsync_SRCS ${libsync_SRCS} creds/tokencredentials.cpp) -else() - set (libsync_SRCS ${libsync_SRCS} creds/httpcredentials.cpp) +if(WIN32) + list(APPEND libsync_SRCS platform_win.cpp) +elseif(UNIX) + if (APPLE) + list(APPEND libsync_SRCS platform_mac.mm platform_mac_deprecated.mm) + else() + list(APPEND libsync_SRCS platform_unix.cpp) + endif() endif() +set(libsync_SRCS ${libsync_SRCS}) + # These headers are installed for libowncloudsync to be used by 3rd party apps -set(owncloudsync_HEADERS - account.h - syncengine.h - configfile.h - networkjobs.h - progressdispatcher.h - syncfileitem.h - syncresult.h +INSTALL( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/owncloudlib.h + logger.h + accessmanager.h + DESTINATION ${KDE_INSTALL_INCLUDEDIR}/${APPLICATION_SHORTNAME}/libsync ) -set(creds_HEADERS - creds/abstractcredentials.h - creds/httpcredentials.h -) +add_library(libsync SHARED ${libsync_SRCS} + libsync.qrc + creds/credentialssupport.h) +set_target_properties(libsync PROPERTIES EXPORT_NAME SyncCore) -IF (NOT APPLE) - INSTALL( - FILES ${owncloudsync_HEADERS} - DESTINATION ${INCLUDE_INSTALL_DIR}/${synclib_NAME}/mirall - ) - INSTALL( - FILES ${creds_HEADERS} - DESTINATION ${INCLUDE_INSTALL_DIR}/${synclib_NAME}/creds - ) -ENDIF(NOT APPLE) +target_link_libraries(libsync + PUBLIC + csync + owncloudResources + Qt::Core + Qt::Network + Qt::Widgets + Qt::QuickControls2 + + PRIVATE + Qt::Concurrent + ZLIB::ZLIB + Qt6Keychain::Qt6Keychain +) -add_library(libsync SHARED ${libsync_SRCS}) -target_link_libraries(libsync PUBLIC - csync - Qt5::Core Qt5::Network +apply_common_target_settings(libsync) +ecm_add_qml_module(libsync + URI org.ownCloud.libsync + VERSION 1.0 + NAMESPACE OCC + GENERATE_PLUGIN_SOURCE ) +ecm_finalize_qml_module(libsync DESTINATION ${KDE_INSTALL_QMLDIR}) + +target_link_libraries(libsync PUBLIC $) +add_subdirectory(graphapi) + if ( APPLE ) target_link_libraries(libsync PUBLIC /System/Library/Frameworks/CoreServices.framework /System/Library/Frameworks/Foundation.framework /System/Library/Frameworks/AppKit.framework + /System/Library/Frameworks/IOKit.framework ) endif() -if (NOT TOKEN_AUTH_ONLY) - find_package(Qt5 REQUIRED COMPONENTS Widgets) - target_link_libraries(libsync PUBLIC Qt5::Widgets qt5keychain) -endif() - if(Inotify_FOUND) target_include_directories(libsync PRIVATE ${Inotify_INCLUDE_DIRS}) - target_link_libraries(libsync PUBLIC ${Inotify_LIBRARIES} ) + target_link_libraries(libsync PUBLIC ${Inotify_LIBRARIES}) +endif() + +if(NO_MSG_HANDLER) + target_compile_definitions(libsync PRIVATE -DNO_MSG_HANDLER=1) endif() GENERATE_EXPORT_HEADER(libsync @@ -108,11 +135,20 @@ GENERATE_EXPORT_HEADER(libsync ) set_target_properties(libsync PROPERTIES - OUTPUT_NAME "${synclib_NAME}" + OUTPUT_NAME "ownCloudLibSync" VERSION ${MIRALL_VERSION} SOVERSION ${MIRALL_SOVERSION} ) -install(TARGETS libsync ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) + +if(WITH_CRASHREPORTER) + set_source_files_properties(platform.cpp PROPERTIES COMPILE_DEFINITIONS CRASHREPORTER_EXECUTABLE="${CRASHREPORTER_EXECUTABLE}") + target_link_libraries(libsync PRIVATE CrashReporterQt::Handler) + + if(UNIX AND NOT MAC) + find_package(Threads REQUIRED) + target_link_libraries(libsync PUBLIC Threads::Threads) + endif() +endif() -add_subdirectory(vfs) +install(TARGETS libsync EXPORT ${APPLICATION_SHORTNAME}Config ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/libsync/abstractcorejob.cpp b/src/libsync/abstractcorejob.cpp new file mode 100644 index 00000000000..09180592579 --- /dev/null +++ b/src/libsync/abstractcorejob.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) Hannah von Reth + * Copyright (C) Fabian Müller + * + * 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 "abstractcorejob.h" +#include "common/asserts.h" + +using namespace OCC; + +AbstractCoreJobFactory::AbstractCoreJobFactory(QNetworkAccessManager *nam) + : _nam(nam) +{ +} + +AbstractCoreJobFactory::~AbstractCoreJobFactory() +{ +} + +void AbstractCoreJobFactory::setJobResult(CoreJob *job, const QVariant &result) +{ + job->setResult(result); +} + +void AbstractCoreJobFactory::setJobError(CoreJob *job, const QString &errorMessage) +{ + job->setError(errorMessage); +} + +const QVariant &CoreJob::result() const +{ + return _result; +} + +const QString &CoreJob::errorMessage() const +{ + return _errorMessage; +} + +QNetworkReply *CoreJob::reply() const +{ + return _reply; +} + +bool CoreJob::success() const +{ + return _success; +} + +void CoreJob::setResult(const QVariant &result) +{ + if (OC_ENSURE(assertNotFinished())) { + _success = true; + _result = result; + + Q_EMIT finished(); + } +} + +void CoreJob::setError(const QString &errorMessage) +{ + if (OC_ENSURE(assertNotFinished())) { + _errorMessage = errorMessage; + + Q_EMIT finished(); + } +} + +CoreJob::CoreJob(QNetworkReply *reply, QObject *parent) + : QObject(parent) + , _reply(reply) +{ + _reply->setParent(this); +} + +bool CoreJob::assertNotFinished() const +{ + OC_ASSERT(_result.isNull()); + OC_ASSERT(_errorMessage.isEmpty()); + return _result.isNull() && _errorMessage.isEmpty(); +} diff --git a/src/libsync/abstractcorejob.h b/src/libsync/abstractcorejob.h new file mode 100644 index 00000000000..f2755902bff --- /dev/null +++ b/src/libsync/abstractcorejob.h @@ -0,0 +1,146 @@ +/* + * Copyright (C) Hannah von Reth + * Copyright (C) Fabian Müller + * + * 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 +#include + +#include "abstractnetworkjob.h" +#include "owncloudlib.h" + +namespace OCC { + +/** + * This class manages an (HTTP) network job's result. It holds a result on success and error details on failures. + * The class is universally usable for all kinds of network requests, there is no difference in handling the responses. + * Instead, instances are created that start a suitable request and wire up the signals accordingly. + * In contrast to the traditional network jobs (e.g., SimpleNetworkJob), core jobs are not bound to an account. Therefore, + * they can be used with ease in situations where an account object is not available (e.g., the new wizard). + */ +class OWNCLOUDSYNC_EXPORT CoreJob : public QObject +{ + Q_OBJECT + + friend class AbstractCoreJobFactory; + +public: + explicit CoreJob(QNetworkReply *reply, QObject *parent); + + [[nodiscard]] const QVariant &result() const; + + [[nodiscard]] const QString &errorMessage() const; + + [[nodiscard]] QNetworkReply *reply() const; + + [[nodiscard]] bool success() const; + +protected: + /** + * Set job result. This emits the finished() signal, and marks the job as successful. + * The job result or error can be set only once. + * @param result job result + */ + void setResult(const QVariant &result); + + /** + * Set job error details. This emits the finished() signal, and marks the job as failed. + * The job result or error can be set only once. + * @param errorMessage network error or other suitable error message + */ + void setError(const QString &errorMessage); + +Q_SIGNALS: + /** + * Emitted when a result or error is set. May be emitted once only. + */ + void finished(); + + /** + * Emitted whenever the user accepts a CA certificate which is not trusted yet using the TlsErrorDialog. + * Handlers should store the certificate in the access manager passed to the corresponding job. + * @param caCertificate accepted CA certificate + */ + void caCertificateAccepted(const QSslCertificate &caCertificate); + +private: + // job result/error should be set only once, because that emits the "finished" signal + [[nodiscard]] bool assertNotFinished() const; + + bool _success = false; + + QVariant _result; + + QString _errorMessage; + QNetworkReply *_reply = nullptr; +}; + + +/** + * Abstract base class for core job factories. + * + * Jobs are built by the startJob factory method, which creates a job instance as well as a network request, wires the required signals up, then sends the request. + */ +class OWNCLOUDSYNC_EXPORT AbstractCoreJobFactory +{ +public: + /** + * Create a new instance + * @param nam network access manager used to send the requests + * @param parent optional parent which will be set at this object's parent as well as all jobs' parent. + */ + explicit AbstractCoreJobFactory(QNetworkAccessManager *nam); + virtual ~AbstractCoreJobFactory(); + + /** + * Send network request and return associated job. + * @param url URL to send request to + * @param parent becomes the parent of the job for auto-cleanup + * @return job + */ + virtual CoreJob *startJob(const QUrl &url, QObject *parent) = 0; + + /** + * Factory to create QNetworkRequests with properly set timeout. + */ + template + static QNetworkRequest makeRequest(Params... params) + { + auto request = QNetworkRequest(params...); + + const auto timeoutMilliseconds = static_cast(std::chrono::duration_cast(AbstractNetworkJob::httpTimeout).count()); + request.setTransferTimeout(timeoutMilliseconds); + + return request; + } + +protected: + + QNetworkAccessManager *_nam; + + /** + * Set job result. Needed because the jobs' methods are protected, and this class is a friend of Job. + * The job result or error can be set only once. + * @param result job result + */ + static void setJobResult(CoreJob *job, const QVariant &result); + + /** + * Set job error details. Needed because the jobs' methods are protected, and this class is a friend of Job. + * @param errorMessage network error or other suitable error message + */ + static void setJobError(CoreJob *job, const QString &errorMessage); +}; +} diff --git a/src/libsync/abstractnetworkjob.cpp b/src/libsync/abstractnetworkjob.cpp index 196b61c8ca7..151ae2ab594 100644 --- a/src/libsync/abstractnetworkjob.cpp +++ b/src/libsync/abstractnetworkjob.cpp @@ -13,27 +13,12 @@ * for more details. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include #include "common/asserts.h" #include "networkjobs.h" #include "account.h" -#include "owncloudpropagator.h" #include "httplogger.h" #include "creds/abstractcredentials.h" @@ -44,7 +29,7 @@ using namespace std::chrono; using namespace std::chrono_literals; namespace { -const int MaxRetryCount = 5; +constexpr int MaxRetryCount = 5; } @@ -53,82 +38,55 @@ namespace OCC { Q_LOGGING_CATEGORY(lcNetworkJob, "sync.networkjob", QtInfoMsg) // If not set, it is overwritten by the Application constructor with the value from the config -std::chrono::seconds AbstractNetworkJob::httpTimeout = std::chrono::seconds(qEnvironmentVariableIntValue("OWNCLOUD_TIMEOUT")); +seconds AbstractNetworkJob::httpTimeout = [] { + const auto def = qEnvironmentVariableIntValue("OWNCLOUD_TIMEOUT"); + if (def <= 0) { + return AbstractNetworkJob::DefaultHttpTimeout; + } + return seconds(def); +}(); -AbstractNetworkJob::AbstractNetworkJob(AccountPtr account, const QString &path, QObject *parent) +AbstractNetworkJob::AbstractNetworkJob(Account *account, const QUrl &baseUrl, const QString &path, QObject *parent) : QObject(parent) - , _timedout(false) , _account(account) - , _ignoreCredentialFailure(false) - , _reply(nullptr) + , _baseUrl(baseUrl) , _path(path) { // Since we hold a QSharedPointer to the account, this makes no sense. (issue #6893) - OC_ASSERT(account != parent); - - _timer.setSingleShot(true); - _timer.setInterval(httpTimeout.count() ? httpTimeout : 5min); // default to 5 minutes. - connect(&_timer, &QTimer::timeout, this, &AbstractNetworkJob::slotTimeout); - - connect(this, &AbstractNetworkJob::networkActivity, this, &AbstractNetworkJob::resetTimeout); - - // Network activity on the propagator jobs (GET/PUT) keeps all requests alive. - // This is a workaround for OC instances which only support one - // parallel up and download - if (_account) { - connect(_account.data(), &Account::propagatorNetworkActivity, this, &AbstractNetworkJob::resetTimeout); - } + Q_ASSERT(account != parent); + Q_ASSERT(baseUrl.isValid()); } -void AbstractNetworkJob::setReply(QNetworkReply *reply) +QUrl AbstractNetworkJob::url() const { - QNetworkReply *old = _reply; - _reply = reply; - delete old; + return Utility::concatUrlPath(_baseUrl, _path, _query); } -void AbstractNetworkJob::setTimeout(const std::chrono::seconds sec) -{ - _timer.start(sec); -} -void AbstractNetworkJob::resetTimeout() +void AbstractNetworkJob::setQuery(const QUrlQuery &query) { - qint64 interval = _timer.interval(); - _timer.stop(); - _timer.start(interval); + _query = query; } -void AbstractNetworkJob::setIgnoreCredentialFailure(bool ignore) -{ - _ignoreCredentialFailure = ignore; -} - -void AbstractNetworkJob::setPath(const QString &path) +void AbstractNetworkJob::setTimeout(const std::chrono::seconds sec) { - _path = path; + _timeout = sec; } -void AbstractNetworkJob::setupConnections(QNetworkReply *reply) +void AbstractNetworkJob::setForceIgnoreCredentialFailure(bool ignore) { - connect(reply, &QNetworkReply::finished, this, &AbstractNetworkJob::slotFinished); - connect(reply, &QNetworkReply::encrypted, this, &AbstractNetworkJob::networkActivity); - connect(reply->manager(), &QNetworkAccessManager::proxyAuthenticationRequired, this, &AbstractNetworkJob::networkActivity); - connect(reply, &QNetworkReply::sslErrors, this, &AbstractNetworkJob::networkActivity); - connect(reply, &QNetworkReply::metaDataChanged, this, &AbstractNetworkJob::networkActivity); - connect(reply, &QNetworkReply::downloadProgress, this, &AbstractNetworkJob::networkActivity); - connect(reply, &QNetworkReply::uploadProgress, this, &AbstractNetworkJob::networkActivity); + _forceIgnoreCredentialFailure = ignore; } -QNetworkReply *AbstractNetworkJob::addTimer(QNetworkReply *reply) +bool AbstractNetworkJob::ignoreCredentialFailure() const { - reply->setProperty("timer", QVariant::fromValue(&_timer)); - return reply; + return _forceIgnoreCredentialFailure || _isAuthenticationJob; } -bool AbstractNetworkJob::isAuthenticationJob() const +QNetworkReply *AbstractNetworkJob::reply() const { - return _isAuthenticationJob; + Q_ASSERT(_reply); + return _reply; } void AbstractNetworkJob::setAuthenticationJob(bool b) @@ -138,7 +96,7 @@ void AbstractNetworkJob::setAuthenticationJob(bool b) bool AbstractNetworkJob::needsRetry() const { - if (isAuthenticationJob()) { + if (_isAuthenticationJob) { qCDebug(lcNetworkJob) << "Not Retry auth job" << this << url(); return false; } @@ -148,118 +106,119 @@ bool AbstractNetworkJob::needsRetry() const } if (auto reply = this->reply()) { + // we had an unsupported redirect if (!reply->attribute(QNetworkRequest::RedirectionTargetAttribute).isNull()) { return true; } - if (reply->error() != QNetworkReply::NoError) { - if (reply->error() == QNetworkReply::AuthenticationRequiredError) { + switch (reply->error()) { + case QNetworkReply::AuthenticationRequiredError: + return true; + case QNetworkReply::ContentReSendError: + if (_reply->attribute(QNetworkRequest::Http2WasUsedAttribute).toBool()) { return true; } + [[fallthrough]]; + default: + break; } } return false; } - -void AbstractNetworkJob::sendRequest(const QByteArray &verb, const QUrl &url, +void AbstractNetworkJob::sendRequest(const QByteArray &verb, const QNetworkRequest &req, QIODevice *requestBody) { + if (!_account) { + slotFinished(); + return; + } + _verb = verb; + _request = req; - _request.setUrl(url); + + // allow to transfer files with a big compression ratio + _request.setDecompressedSafetyCheckThreshold(-1); + + _request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, _storeInCache); + + if (_cacheLoadControl.has_value()) { + _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, _cacheLoadControl.value()); + } + _requestBody = requestBody; - if (!isAuthenticationJob() && _account->jobQueue()->enqueue(this)) { + + Q_ASSERT(_request.url().isEmpty() || _request.url() == url()); + Q_ASSERT(_request.transferTimeout() == 0 || _request.transferTimeout() == duration_cast(_timeout).count()); + + _request.setUrl(url()); + _request.setPriority(_priority); + _request.setTransferTimeout(duration_cast(_timeout).count()); + + if (!_isAuthenticationJob && _account->jobQueue()->enqueue(this)) { return; } - auto reply = _account->sendRawRequest(verb, url, req, requestBody); + + auto reply = _account->sendRawRequest(verb, _request.url(), _request, requestBody); + if (_requestBody) { - _requestBody->setParent(reply); + _requestBody->setParent(this); } + adoptRequest(reply); } -void AbstractNetworkJob::adoptRequest(QNetworkReply *reply) +void AbstractNetworkJob::adoptRequest(QPointer reply) { - addTimer(reply); - setReply(reply); - setupConnections(reply); - newReplyHook(reply); - _request = reply->request(); -} + std::swap(_reply, reply); + if (reply) { + reply->disconnect(); + reply->abort(); + reply->deleteLater(); + } -QUrl AbstractNetworkJob::makeAccountUrl(const QString &relativePath) const -{ - return Utility::concatUrlPath(_account->url(), relativePath); -} + _request = _reply->request(); -QUrl AbstractNetworkJob::makeDavUrl(const QString &relativePath) const -{ - // ensure we always used the remote folder - return Utility::concatUrlPath(_account->davUrl(), OC_ENSURE(relativePath.startsWith(QLatin1Char('/'))) ? relativePath : QLatin1Char('/') + relativePath); + connect(_reply, &QNetworkReply::finished, this, &AbstractNetworkJob::slotFinished); + + newReplyHook(_reply); } void AbstractNetworkJob::slotFinished() -{ - _timer.stop(); +{ + _finished = true; - if (_reply->error() == QNetworkReply::SslHandshakeFailedError) { - qCWarning(lcNetworkJob) << "SslHandshakeFailedError: " << errorString() << " : can be caused by a webserver wanting SSL client certificates"; + if (!_reply || !_account) { + qCWarning(lcNetworkJob) << "Network job finished but reply and/or account is nullptr - aborting" << this; + Q_EMIT networkError(nullptr); + deleteLater(); + return; } - // Qt doesn't yet transparently resend HTTP2 requests, do so here - const auto maxHttp2Resends = 3; - QByteArray verb = HttpLogger::requestVerb(*reply()); - if (_reply->error() == QNetworkReply::ContentReSendError - && _reply->attribute(QNetworkRequest::Http2WasUsedAttribute).toBool()) { - if ((_requestBody && !_requestBody->isSequential()) || verb.isEmpty()) { - qCWarning(lcNetworkJob) << "Can't resend HTTP2 request, verb or body not suitable" - << _reply->request().url() << verb << _requestBody; - } else if (_http2ResendCount >= maxHttp2Resends) { - qCWarning(lcNetworkJob) << "Not resending HTTP2 request, number of resends exhausted" - << _reply->request().url() << _http2ResendCount; - } else { - qCInfo(lcNetworkJob) << "HTTP2 resending" << _reply->request().url(); - _http2ResendCount++; - - resetTimeout(); - if (_requestBody) { - if (!_requestBody->isOpen()) - _requestBody->open(QIODevice::ReadOnly); - _requestBody->seek(0); - } - sendRequest( - verb, - _reply->request().url(), - _reply->request(), - _requestBody); - return; - } + + if (!_account->credentials()->stillValid(_reply) && !ignoreCredentialFailure()) { + _account->invalidCredentialsEncountered(); } if (_reply->error() != QNetworkReply::NoError) { if (_account->jobQueue()->retry(this)) { - qCDebug(lcNetworkJob) << "Queuing: " << _reply->url() << " for retry"; + qCDebug(lcNetworkJob) << "Queued:" << this << "for retry"; return; } - if (!_ignoreCredentialFailure || _reply->error() != QNetworkReply::AuthenticationRequiredError) { - qCWarning(lcNetworkJob) << this << _reply->error() << errorString() - << _reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (_reply->error() == QNetworkReply::ProxyAuthenticationRequiredError) { - qCWarning(lcNetworkJob) << _reply->rawHeader("Proxy-Authenticate"); - } + if (_reply->error() == QNetworkReply::OperationCanceledError && !_aborted) { + _timedout = true; } - emit networkError(_reply); + Q_EMIT networkError(_reply); } // get the Date timestamp from reply _responseTimestamp = _reply->rawHeader("Date"); - if (!_account->credentials()->stillValid(_reply) && !_ignoreCredentialFailure) { - Q_EMIT _account->invalidCredentials(); - } - if (!reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).isNull() && !(isAuthenticationJob() || reply()->request().hasRawHeader(QByteArrayLiteral("OC-Connection-Validator")))) { + if (!reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).isNull() + && !(_isAuthenticationJob || reply()->request().hasRawHeader(QByteArrayLiteral("OC-Connection-Validator")))) { Q_EMIT _account->unknownConnectionState(); - qCWarning(lcNetworkJob) << this << "Unsupported redirect on" << _reply->url().toString() << "to" << reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); + qCWarning(lcNetworkJob) << this << "Unsupported redirect on" << _reply->url().toString() << "to" + << reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); Q_EMIT networkError(_reply); if (_account->jobQueue()->retry(this)) { qCWarning(lcNetworkJob) << "Retry Nr:" << _retryCount << _reply->url(); @@ -268,20 +227,23 @@ void AbstractNetworkJob::slotFinished() qCWarning(lcNetworkJob) << "Don't retry:" << _reply->url(); } } - - bool discard = finished(); - if (discard) { - qCDebug(lcNetworkJob) << "Network job finished" << this; - deleteLater(); - } + Q_EMIT aboutToFinishSignal(AbstractNetworkJob::QPrivateSignal()); + finished(); + Q_EMIT finishedSignal(AbstractNetworkJob::QPrivateSignal()); + qCDebug(lcNetworkJob) << "Network job finished" << this; + deleteLater(); } -QByteArray AbstractNetworkJob::responseTimestamp() +QByteArray AbstractNetworkJob::responseTimestamp() const { - OC_ASSERT(!_responseTimestamp.isEmpty()); return _responseTimestamp; } +QDateTime OCC::AbstractNetworkJob::responseQTimeStamp() const +{ + return Utility::parseRFC1123Date(QString::fromUtf8(responseTimestamp())); +} + QByteArray AbstractNetworkJob::requestId() { return _reply ? _reply->request().rawHeader("X-Request-ID") : QByteArray(); @@ -323,49 +285,28 @@ QString AbstractNetworkJob::errorStringParsingBody(QByteArray *body) AbstractNetworkJob::~AbstractNetworkJob() { - setReply(nullptr); + if (!_finished && !_aborted && !_timedout) { + qCCritical(lcNetworkJob) << "Deleting running job" << this; + } + if (_reply) { + _reply->disconnect(); + _reply->abort(); + _reply->deleteLater(); + _reply.clear(); + } } void AbstractNetworkJob::start() { - _timer.start(); qCInfo(lcNetworkJob) << "Created" << this << "for" << parent(); } -void AbstractNetworkJob::slotTimeout() -{ - _timedout = true; - qCWarning(lcNetworkJob) << "Network job" << this << "timeout"; - onTimedOut(); -} - -void AbstractNetworkJob::onTimedOut() -{ - abort(); -} - QString AbstractNetworkJob::replyStatusString() { Q_ASSERT(reply()); if (reply()->error() == QNetworkReply::NoError) { return QStringLiteral("OK"); } else { - const QString enumStr = QString::fromUtf8(QMetaEnum::fromType().valueToKey(static_cast(reply()->error()))); - return QStringLiteral("%1 %2").arg(enumStr, errorString()); - } -} - -NetworkJobTimeoutPauser::NetworkJobTimeoutPauser(QNetworkReply *reply) -{ - _timer = reply->property("timer").value(); - if (!_timer.isNull()) { - _timer->stop(); - } -} - -NetworkJobTimeoutPauser::~NetworkJobTimeoutPauser() -{ - if (!_timer.isNull()) { - _timer->start(); + return QStringLiteral("%1, %2").arg(Utility::enumToString(reply()->error()), errorString()); } } @@ -421,44 +362,80 @@ void AbstractNetworkJob::retry() { OC_ENFORCE(!_verb.isEmpty()); _retryCount++; - qCInfo(lcNetworkJob) << "Restarting" << _verb << _request.url() << "for the" << _retryCount << "time"; - resetTimeout(); + qCInfo(lcNetworkJob) << "Restarting" << this << "for the" << _retryCount << "time"; if (_requestBody) { - _requestBody->seek(0); + if (!_requestBody->isSequential()) { + Q_ASSERT(_requestBody->isOpen()); + _requestBody->seek(0); + } else { + qCWarning(lcNetworkJob) << "Can't resend request, body not suitable" << this; + abort(); + return; + } } - sendRequest(_verb, _request.url(), _request, _requestBody); + sendRequest(_verb, _request, _requestBody); } void AbstractNetworkJob::abort() { if (_reply) { + // calling abort will trigger the execution of finished() + // with _reply->error() == QNetworkReply::OperationCanceledError + // the api user can then decide whether to discard this job or retry it. + // The order is important, mark as _aborted before we abort the reply which will trigger slotFinished + _aborted = true; _reply->abort(); } else { deleteLater(); } } +void AbstractNetworkJob::setPriority(QNetworkRequest::Priority priority) +{ + _priority = priority; +} + +QNetworkRequest::Priority AbstractNetworkJob::priority() const +{ + return _priority; +} + +int AbstractNetworkJob::httpStatusCode() const +{ + return reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); +} + +void AbstractNetworkJob::setStoreInCache(bool storeInCache) +{ + _storeInCache = storeInCache; +} + +void AbstractNetworkJob::setCacheLoadControl(QNetworkRequest::CacheLoadControl cacheLoadControl) +{ + _cacheLoadControl = cacheLoadControl; +} + } // namespace OCC QDebug operator<<(QDebug debug, const OCC::AbstractNetworkJob *job) { QDebugStateSaver saver(debug); debug.setAutoInsertSpaces(false); - debug << job->metaObject()->className() << "(" << job->url().toDisplayString(); - if (auto reply = job->reply()) { - debug << ", " << reply->request().rawHeader("Original-Request-ID") - << ", " << reply->request().rawHeader("X-Request-ID"); + debug << job->metaObject()->className() << "(" << job->url().toDisplayString() << ", " << job->_verb; + if (auto reply = job->_reply) { + debug << ", Original-Request-ID: " << reply->request().rawHeader("Original-Request-ID") + << ", X-Request-ID: " << reply->request().rawHeader("X-Request-ID"); const auto errorString = reply->rawHeader(QByteArrayLiteral("OC-ErrorString")); if (!errorString.isEmpty()) { - debug << ", " << errorString; + debug << ", Error:" << errorString; } if (reply->error() != QNetworkReply::NoError) { - debug << ", " << reply->errorString(); + debug << ", NetworkError: " << reply->errorString(); } } if (job->_timedout) { - debug << ", timedout"; + debug << ", timed out"; } debug << ")"; return debug.maybeSpace(); diff --git a/src/libsync/abstractnetworkjob.h b/src/libsync/abstractnetworkjob.h index 68171885cf1..42f855474f2 100644 --- a/src/libsync/abstractnetworkjob.h +++ b/src/libsync/abstractnetworkjob.h @@ -15,22 +15,21 @@ #pragma once -#include "accountfwd.h" -#include "jobqueue.h" - -#include "common/asserts.h" - #include "owncloudlib.h" +#include "account.h" +#include "jobqueue.h" -#include -#include +#include +#include #include +#include +#include #include -#include -#include #include +#include #include +#include class QUrl; @@ -39,7 +38,7 @@ OWNCLOUDSYNC_EXPORT QDebug operator<<(QDebug debug, const OCC::AbstractNetworkJo namespace OCC { -class AbstractSslErrorHandler; +using HeaderMap = QMap; /** * @brief The AbstractNetworkJob class @@ -49,29 +48,42 @@ class OWNCLOUDSYNC_EXPORT AbstractNetworkJob : public QObject { Q_OBJECT public: - explicit AbstractNetworkJob(AccountPtr account, const QString &path, QObject *parent = nullptr); + explicit AbstractNetworkJob(Account *account, const QUrl &baseUrl, const QString &path, QObject *parent = nullptr); ~AbstractNetworkJob() override; virtual void start(); - AccountPtr account() const { return _account; } - void setPath(const QString &path); - QString path() const { return _path; } + QNetworkReply *reply() const; - QUrl url() const { return _request.url(); } + void setForceIgnoreCredentialFailure(bool ignore); + bool ignoreCredentialFailure() const; - QNetworkReply *reply() const { return _reply; } + /** + * @brief Returns the timestamp from the reply + * + * @return Empty if the job was aborted, or an error occurred and no response was received. + * Otherwise the timestamp from the reply header. Note that the timestamp might be + * absent if the reply didn't contain one. + */ + QByteArray responseTimestamp() const; + /** + * See responseTimestamp(), now converted to a QDateTime. If there was no response timestamp, + * return an invalid `QDateTime`. + */ + QDateTime responseQTimeStamp() const; - void setIgnoreCredentialFailure(bool ignore); - bool ignoreCredentialFailure() const { return _ignoreCredentialFailure; } + int httpStatusCode() const; - QByteArray responseTimestamp(); /* Content of the X-Request-ID header. (Only set after the request is sent) */ QByteArray requestId(); - auto timeoutSec() const { return std::chrono::duration_cast(std::chrono::milliseconds(_timer.interval())); } + auto timeoutSec() const { return _timeout; } bool timedOut() const { return _timedout; } + bool aborted() const { return _aborted; } + + void setPriority(QNetworkRequest::Priority priority); + QNetworkRequest::Priority priority() const; /** Returns an error message, if any. */ QString errorString() const; @@ -98,8 +110,13 @@ class OWNCLOUDSYNC_EXPORT AbstractNetworkJob : public QObject */ static std::chrono::seconds httpTimeout; + /** + * The default 5 minutes timeout if none is specified by the config. + * Qt's default would be 30s. + */ + static constexpr std::chrono::seconds DefaultHttpTimeout { 5 * 60 }; + /** whether or noth this job should be restarted after authentication */ - bool isAuthenticationJob() const; void setAuthenticationJob(bool b); /** How many times was that job retried */ @@ -108,39 +125,54 @@ class OWNCLOUDSYNC_EXPORT AbstractNetworkJob : public QObject virtual bool needsRetry() const; -public slots: void setTimeout(const std::chrono::seconds sec); - void resetTimeout(); -signals: + + /** + * Configure whether to store replies in a cache configured in the corresponding network access manager (if one is set there). + * By default, replies are not stored in the cache. Qt normally would store all eligible resources (as configured by server policies) in the cache. + * See also \ref setCacheLoadControl + * @param storeInCache whether to store replies in the cache + */ + void setStoreInCache(bool storeInCache); + + /** + * Configure whether to load data from the cache. + * We inherit Qt's default behavior (which in Qt 5.15 and 6.4 is to prefer a network response but provide a cached value if the server permits it in its + * cache policies when the server cannot be reached). + * See also \ref setStoreInCache + * @param cacheLoadControl a policy suitable for the current job + */ + void setCacheLoadControl(QNetworkRequest::CacheLoadControl cacheLoadControl); + +Q_SIGNALS: /** Emitted on network error. * * \a reply is never null */ void networkError(QNetworkReply *reply); - void networkActivity(); + + /** + * The job is done + */ + void aboutToFinishSignal(QPrivateSignal); + void finishedSignal(QPrivateSignal); protected: + /* + * The absolute url: baseUrl() + path() + query() + */ + QUrl url() const; + /** Initiate a network request, returning a QNetworkReply. * * Calls setReply() and setupConnections() on it. * * Takes ownership of the requestBody (to allow redirects). */ - void sendRequest(const QByteArray &verb, const QUrl &url, + void sendRequest(const QByteArray &verb, const QNetworkRequest &req = QNetworkRequest(), QIODevice *requestBody = nullptr); - void setReply(QNetworkReply *reply); - - /** Makes this job drive a pre-made QNetworkReply - * - * This reply cannot have a QIODevice request body because we can't get - * at it and thus not resend it in case of redirects. - */ - void adoptRequest(QNetworkReply *reply); - - void setupConnections(QNetworkReply *reply); - /** Can be used by derived classes to set up the network reply. * * Particularly useful when the request is redirected and reply() @@ -149,45 +181,45 @@ public slots: */ virtual void newReplyHook(QNetworkReply *) {} - /// Creates a url for the account from a relative path - QUrl makeAccountUrl(const QString &relativePath) const; - - /// Like makeAccountUrl() but uses the account's dav base path - QUrl makeDavUrl(const QString &relativePath) const; - /** Called at the end of QNetworkReply::finished processing. - * - * Returning true triggers a deleteLater() of this job. */ - virtual bool finished() = 0; + virtual void finished() = 0; - /** Called on timeout. - * - * The default implementation aborts the reply. + QString replyStatusString(); + + /* + * The url query appended to the url. + * The query will not be set as part of the body. + * The query must be fully encoded. */ - virtual void onTimedOut(); + void setQuery(const QUrlQuery &query); + QUrlQuery _query; QByteArray _responseTimestamp; - bool _timedout; // set to true when the timeout slot is received - QString replyStatusString(); +private: + QPointer _account; -private slots: + /** Makes this job drive a pre-made QNetworkReply + * + * This reply cannot have a QIODevice request body because we can't get + * at it and thus not resend it in case of redirects. + */ + void adoptRequest(QPointer reply); void slotFinished(); - void slotTimeout(); -protected: - AccountPtr _account; + const QUrl _baseUrl; + const QString _path; + + std::chrono::seconds _timeout = httpTimeout; + bool _timedout = false; // set to true when the timeout slot is received + bool _aborted = false; + bool _finished = false; + bool _forceIgnoreCredentialFailure = false; -private: - QNetworkReply *addTimer(QNetworkReply *reply); - bool _ignoreCredentialFailure; QNetworkRequest _request; QByteArray _verb; QPointer _reply; // (QPointer because the NetworkManager may be destroyed before the jobs at exit) - QString _path; - QTimer _timer; - int _http2ResendCount = 0; // Set by the xyzRequest() functions and needed to be able to redirect // requests, should it be required. @@ -198,20 +230,14 @@ private slots: bool _isAuthenticationJob = false; int _retryCount = 0; - friend QDebug(::operator<<)(QDebug debug, const AbstractNetworkJob *job); -}; + // by default, we don't intend to store responses in the cache (if one is set in the account's access manager) + bool _storeInCache = false; + // we use Qt's default cache load behavior unless the user explicitly requests a different behavior + std::optional _cacheLoadControl = std::nullopt; -/** - * @brief Internal Helper class - */ -class NetworkJobTimeoutPauser -{ -public: - NetworkJobTimeoutPauser(QNetworkReply *reply); - ~NetworkJobTimeoutPauser(); + QNetworkRequest::Priority _priority = QNetworkRequest::NormalPriority; -private: - QPointer _timer; + friend QDebug(::operator<<)(QDebug debug, const AbstractNetworkJob *job); }; @@ -239,4 +265,3 @@ QString OWNCLOUDSYNC_EXPORT errorMessage(const QString &baseError, const QByteAr QString OWNCLOUDSYNC_EXPORT networkReplyErrorString(const QNetworkReply &reply); } // namespace OCC - diff --git a/src/libsync/accessmanager.cpp b/src/libsync/accessmanager.cpp index 04be8158702..258e135896b 100644 --- a/src/libsync/accessmanager.cpp +++ b/src/libsync/accessmanager.cpp @@ -12,22 +12,17 @@ * for more details. */ -#include -#include -#include -#include #include -#include #include -#include -#include #include -#include "cookiejar.h" #include "accessmanager.h" #include "common/utility.h" +#include "cookiejar.h" #include "httplogger.h" +#include + namespace OCC { Q_LOGGING_CATEGORY(lcAccessManager, "sync.accessmanager", QtInfoMsg) @@ -35,10 +30,26 @@ Q_LOGGING_CATEGORY(lcAccessManager, "sync.accessmanager", QtInfoMsg) AccessManager::AccessManager(QObject *parent) : QNetworkAccessManager(parent) { -#ifndef Q_OS_LINUX - // Atempt to workaround for https://github.com/owncloud/client/issues/3969 - setConfiguration(QNetworkConfiguration()); -#endif + setCookieJar(new CookieJar); + + connect(this, &AccessManager::sslErrors, this, [this](QNetworkReply *reply, const QList &errors) { + auto filtered = errors; + filtered.erase(std::remove_if( + filtered.begin(), filtered.end(), [this](const QSslError &e) { + return !_customTrustedCaCertificates.contains(e.certificate()); + }), + filtered.end()); + reply->ignoreSslErrors(filtered); + }); +} + +void AccessManager::reset() +{ + _customTrustedCaCertificates.clear(); + // afaik we really do want to use this, not clearConnectionCache, as we need to ensure auth data is also cleared + clearAccessCache(); + // QNetworkAccessManager takes ownership of the cookie jar and deletes the old one when a new one is set, so no + // leaks here. setCookieJar(new CookieJar); } @@ -55,6 +66,15 @@ QNetworkReply *AccessManager::createRequest(QNetworkAccessManager::Operation op, // Some firewalls reject requests that have a "User-Agent" but no "Accept" header newRequest.setRawHeader(QByteArrayLiteral("Accept"), QByteArrayLiteral("*/*")); + // Set the language, so messages from the server are localized correctly. + newRequest.setRawHeader("Accept-Language", QLocale().name().toUtf8()); + + // we don't follow redirects, if we receive one the ConnectionValidator is triggered + // -> default to manual redirection + if (newRequest.attribute(QNetworkRequest::RedirectPolicyAttribute).isNull()) { + newRequest.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy); + } + QByteArray verb = newRequest.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray(); // For PROPFIND (assumed to be a WebDAV op), set xml/utf8 as content type/encoding // This needs extension @@ -64,13 +84,13 @@ QNetworkReply *AccessManager::createRequest(QNetworkAccessManager::Operation op, // Generate a new request id const QByteArray requestId = generateRequestId(); - qInfo(lcAccessManager) << op << verb << newRequest.url().toString() << "has X-Request-ID" << requestId; newRequest.setRawHeader(QByteArrayLiteral("X-Request-ID"), requestId); const auto originalIdKey = QByteArrayLiteral("Original-Request-ID"); if (!newRequest.hasRawHeader(originalIdKey)) { newRequest.setRawHeader(originalIdKey, requestId); } + // todo: #23 if (newRequest.url().scheme() == QLatin1String("https")) { // Not for "http": QTBUG-61397 // http2 seems to cause issues, as with our recommended server setup we don't support http2, disable it by default for now static const bool http2EnabledEnv = qEnvironmentVariableIntValue("OWNCLOUD_HTTP2_ENABLED") == 1; @@ -78,9 +98,60 @@ QNetworkReply *AccessManager::createRequest(QNetworkAccessManager::Operation op, newRequest.setAttribute(QNetworkRequest::Http2AllowedAttribute, http2EnabledEnv); } + // allow http pipelining + newRequest.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); + + auto sslConfiguration = newRequest.sslConfiguration(); + + sslConfiguration.setSslOption(QSsl::SslOptionDisableSessionTickets, false); + sslConfiguration.setSslOption(QSsl::SslOptionDisableSessionSharing, false); + sslConfiguration.setSslOption(QSsl::SslOptionDisableSessionPersistence, false); + if (!_customTrustedCaCertificates.isEmpty()) { + // for some reason, passing an empty list causes the default chain to be removed + // this behavior does not match the documentation + sslConfiguration.addCaCertificates({ _customTrustedCaCertificates.begin(), _customTrustedCaCertificates.end() }); + } + newRequest.setSslConfiguration(sslConfiguration); + const auto reply = QNetworkAccessManager::createRequest(op, newRequest, outgoingData); HttpLogger::logRequest(reply, op, outgoingData); return reply; } +QSet AccessManager::customTrustedCaCertificates() +{ + return _customTrustedCaCertificates; +} + +void AccessManager::setCustomTrustedCaCertificates(const QSet &certificates) +{ + _customTrustedCaCertificates = certificates; + // we have to terminate the existing (cached) connection to make the access manager re-evaluate the certificate sent by the server + clearConnectionCache(); +} + +void AccessManager::addCustomTrustedCaCertificates(const QSet &certificates) +{ + _customTrustedCaCertificates.unite(certificates); + + // we have to terminate the existing (cached) connection to make the access manager re-evaluate the certificate sent by the server + clearConnectionCache(); +} + +CookieJar *AccessManager::ownCloudCookieJar() const +{ + auto jar = qobject_cast(cookieJar()); + Q_ASSERT(jar); + return jar; +} + +QList AccessManager::filterSslErrors(const QList &errors) const +{ + auto filtered = errors; + filtered.erase(std::remove_if(filtered.begin(), filtered.end(), + [this](const QSslError &e) { return e.certificate().isNull() || _customTrustedCaCertificates.contains(e.certificate()); }), + filtered.end()); + return filtered; +} + } // namespace OCC diff --git a/src/libsync/accessmanager.h b/src/libsync/accessmanager.h index 4caf380a0f9..1d30bb6becb 100644 --- a/src/libsync/accessmanager.h +++ b/src/libsync/accessmanager.h @@ -22,6 +22,7 @@ class QByteArray; class QUrl; namespace OCC { +class CookieJar; /** * @brief The AccessManager class @@ -32,12 +33,35 @@ class OWNCLOUDSYNC_EXPORT AccessManager : public QNetworkAccessManager Q_OBJECT public: + void reset(); + static QByteArray generateRequestId(); - AccessManager(QObject *parent = nullptr); + AccessManager(QObject *parent); + + QSet customTrustedCaCertificates(); + + /*** + * Warning calling those will break running network jobs + */ + void setCustomTrustedCaCertificates(const QSet &certificates); + /*** + * Warning calling those will break running network jobs + */ + void addCustomTrustedCaCertificates(const QSet &certificates); + + CookieJar *ownCloudCookieJar() const; + + /*** + * Remove all errors for already accepted certificates + */ + QList filterSslErrors(const QList &errors) const; protected: QNetworkReply *createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData = nullptr) override; + +private: + QSet _customTrustedCaCertificates; }; } // namespace OCC diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 6d014beebde..0e7e5e937af 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -13,74 +13,104 @@ */ #include "account.h" -#include "cookiejar.h" -#include "networkjobs.h" #include "accessmanager.h" +#include "capabilities.h" +#include "cookiejar.h" #include "creds/abstractcredentials.h" #include "creds/credentialmanager.h" -#include "capabilities.h" -#include "theme.h" -#include "common/asserts.h" +#include "graphapi/spacesmanager.h" +#include "networkjobs.h" +#include "networkjobs/resources.h" +#include "resources.h" -#include +#include +#include #include -#include -#include #include -#include #include -#include -#include +#include #include -#include #include namespace OCC { Q_LOGGING_CATEGORY(lcAccount, "sync.account", QtInfoMsg) -Account::Account(QObject *parent) +QString Account::_customCommonCacheDirectory = {}; + +void Account::setCommonCacheDirectory(const QString &directory) +{ + _customCommonCacheDirectory = directory; +} + +QString Account::commonCacheDirectory() +{ + if (_customCommonCacheDirectory.isEmpty()) { + return QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + } + + return _customCommonCacheDirectory; +} + +Account::Account(const QUuid &uuid, const QString &user, const QUrl &url, QObject *parent) : QObject(parent) - , _uuid(QUuid::createUuid()) - , _capabilities(QVariantMap()) - , _davPath(Theme::instance()->webDavPath()) + , _uuid(uuid) + , _davUser(user) + , _url(url) + , _capabilities({}, {}) , _jobQueue(this) , _queueGuard(&_jobQueue) , _credentialManager(new CredentialManager(this)) { - qRegisterMetaType("AccountPtr"); -} + qRegisterMetaType("Account"); -AccountPtr Account::create() -{ - AccountPtr acc = AccountPtr(new Account); - acc->setSharedThis(acc); - return acc; + _cacheDirectory = QStringLiteral("%1/accounts/%2").arg(commonCacheDirectory(), _uuid.toString(QUuid::WithoutBraces)); + QDir().mkpath(_cacheDirectory); + + // we need to make sure the directory we pass to the resources cache exists + const QString resourcesCacheDir = QStringLiteral("%1/resources/").arg(_cacheDirectory); + if (QFileInfo::exists(resourcesCacheDir)) { + // Some versions of the client failed to remove the temporary cache directory on exit, and + // after a crash the directory would not be removed either. This can result in multiple + // GB of cached space images. So clean this up before creating a new cache. + // todo: #32 + if (!QDir(resourcesCacheDir).removeRecursively()) { + qCWarning(lcAccount) << "Resources cache was not fully cleaned"; + } + } + QDir().mkpath(resourcesCacheDir); + _resourcesCache = new ResourcesCache(resourcesCacheDir, this); + + _spacesManager = new GraphApi::SpacesManager(this); } Account::~Account() { } -QString Account::davPath() const +void Account::cleanupForRemoval() { - if (capabilities().chunkingNg()) { - // The chunking-ng means the server prefer to use the new webdav URL - return QLatin1String("/remote.php/dav/files/") + davUser() + QLatin1Char('/'); + // Abort any pending jobs: the account credentials are invalidated. + jobQueue()->clear(); + + // Stop the spaces manager, because it might start a new `Drives` job before the account is deleted. + if (_spacesManager) { + delete _spacesManager; + _spacesManager = nullptr; } - // make sure to have a trailing slash - if (!_davPath.endsWith(QLatin1Char('/'))) { - QString dp(_davPath); - dp.append(QLatin1Char('/')); - return dp; + // Stop the resource cache (including any pending jobs), because this uses the cache directory. + delete _resourcesCache; + _resourcesCache = nullptr; + + if (!QDir(_cacheDirectory).removeRecursively()) { + qCWarning(lcAccount) << "Cache directory" << _cacheDirectory << "was not fully removed"; } - return _davPath; } -void Account::setSharedThis(AccountPtr sharedThis) +QString Account::davPath() const { - _sharedThis = sharedThis.toWeakRef(); + return QLatin1String("/remote.php/dav/files/") + davUser() + QLatin1Char('/'); } CredentialManager *Account::credentialManager() const @@ -93,41 +123,41 @@ QUuid Account::uuid() const return _uuid; } -AccountPtr Account::sharedFromThis() +QString Account::davUser() const { - return _sharedThis.toStrongRef(); + return _davUser; } -QString Account::davUser() const +QIcon Account::avatar() { - return _davUser.isEmpty() ? _credentials->user() : _davUser; + if (_avatarImg.isNull()) { + // note this caches the built avatar, so once it exists it just returns a cached version. + return Resources::buildAvatar(initials(), uuid()); + } + return _avatarImg; } -void Account::setDavUser(const QString &newDavUser) +void Account::setAvatar(const QIcon &img) { - if (_davUser == newDavUser) + // this excludes the condition that we get a null image on every. single. run. of the fetchServerSettings, which + // is also responsible for fetching the avatar iff the caps show avatars are supported. + // however we are going to have the same problem with non-null avatars, and comparing them to see if the new one really + // is different is much more complicated and needs a deeper fix. + // see dc-324 for final solution + if (img.isNull() && _avatarImg.isNull()) return; - _davUser = newDavUser; - emit wantsAccountSaved(this); + _avatarImg = img; + Q_EMIT avatarChanged(); } -#ifndef TOKEN_AUTH_ONLY -QPixmap Account::avatar() const -{ - return _avatarImg; -} -void Account::setAvatar(const QPixmap &img) +bool Account::hasAvatar() const { - _avatarImg = img; - emit accountChangedAvatar(); + return !_avatarImg.isNull(); } -#endif -QString Account::displayName() const +QString Account::displayNameWithHost() const { QString user = davDisplayName(); - if (user.isEmpty()) - user = davUser(); QString host = _url.host(); const int port = url().port(); if (port > 0 && port != 80 && port != 443) { @@ -136,65 +166,86 @@ QString Account::displayName() const return tr("%1@%2").arg(user, host); } +QString Account::initials() const +{ + QString out; + for (const auto &p : davDisplayName().split(QLatin1Char(' '), Qt::SkipEmptyParts)) { + out.append(p.first(1)); + } + return out; +} + +QGradient::Preset Account::avatarGradient() const +{ + return static_cast(qHash(displayNameWithHost()) % QGradient::NumPresets + 1); +} + QString Account::davDisplayName() const { + if (_displayName.isEmpty()) { + return davUser(); + } return _displayName; } void Account::setDavDisplayName(const QString &newDisplayName) { - _displayName = newDisplayName; - emit accountChangedDisplayName(); + if (_displayName != newDisplayName) { + _displayName = newDisplayName; + Q_EMIT displayNameChanged(); + } } -QString Account::id() const +QString Account::groupIndex() const { - return _id; + return _groupIndex; } AbstractCredentials *Account::credentials() const { - return _credentials.data(); + return _credentials; } +// todo: in a sane world the credentials should be instantiated by the account (as parent) as using this setter there isthe chance that it could be set or unset +// randomly or even multiple times like this is really not good. For now we have to pass the creds in as the tests use their own subclass of AbstractCredentials +// = FakeCredentials, so that has to be worked out before we can move this creds handling into the account proper, where it should be. void Account::setCredentials(AbstractCredentials *cred) { - // set active credential manager + if (!cred || _credentials == cred) + return; + + // keep any cookies for new nam QNetworkCookieJar *jar = nullptr; if (_am) { jar = _am->cookieJar(); jar->setParent(nullptr); - - _am = QSharedPointer(); + } + // get rid of the old credentials if they exist - imo this should never happen but who knows + // note the access manager is parented by the creds so let that take care of deleting the am, but verify it's gone + if (_credentials) { + delete _credentials; + _credentials = nullptr; + Q_ASSERT(_am == nullptr); } - // The order for these two is important! Reading the credential's - // settings accesses the account as well as account->_credentials, - _credentials.reset(cred); - cred->setAccount(this); - - // Note: This way the QNAM can outlive the Account and Credentials. - // This is necessary to avoid issues with the QNAM being deleted while - // processing slotHandleSslErrors(). - _am = QSharedPointer(_credentials->createQNAM(), &QObject::deleteLater); - + _credentials = cred; + _am = _credentials->createAccessManager(); if (jar) { _am->setCookieJar(jar); } - connect(_am.data(), &QNetworkAccessManager::sslErrors, - this, &Account::slotHandleSslErrors); - connect(_am.data(), &QNetworkAccessManager::proxyAuthenticationRequired, - this, &Account::proxyAuthenticationRequired); - connect(_credentials.data(), &AbstractCredentials::fetched, - this, &Account::slotCredentialsFetched); - connect(_credentials.data(), &AbstractCredentials::asked, - this, &Account::slotCredentialsAsked); - connect(_credentials.data(), &AbstractCredentials::authenticationStarted, this, [this] { - _queueGuard.block(); - }); - connect(_credentials.data(), &AbstractCredentials::authenticationFailed, this, [this] { - _queueGuard.clear(); + + // the network access manager takes ownership when setCache is called, so we have to reinitialize it every time we reset the manager + _networkCache = new QNetworkDiskCache(this); + const QString networkCacheLocation = (QStringLiteral("%1/network/").arg(_cacheDirectory)); + _networkCache->setCacheDirectory(networkCacheLocation); + _am->setCache(_networkCache); + + connect(_credentials, &AbstractCredentials::fetched, this, [this] { + Q_EMIT credentialsFetched(); + _queueGuard.unblock(); }); + connect(_credentials, &AbstractCredentials::authenticationStarted, this, [this] { _queueGuard.block(); }); + connect(_credentials, &AbstractCredentials::authenticationFailed, this, [this] { _queueGuard.clear(); }); } QUrl Account::davUrl() const @@ -207,62 +258,20 @@ QUrl Account::davUrl() const */ void Account::clearCookieJar() { - auto jar = qobject_cast(_am->cookieJar()); - OC_ASSERT(jar); - jar->setAllCookies(QList()); - emit wantsAccountSaved(this); -} - -/*! This shares our official cookie jar (containing all the tasty - authentication cookies) with another QNAM while making sure - of not losing its ownership. */ -void Account::lendCookieJarTo(QNetworkAccessManager *guest) -{ - auto jar = _am->cookieJar(); - auto oldParent = jar->parent(); - guest->setCookieJar(jar); // takes ownership of our precious cookie jar - jar->setParent(oldParent); // takes it back -} - -QString Account::cookieJarPath() -{ - return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + QStringLiteral("/cookies") + id() + QStringLiteral(".db"); + qCInfo(lcAccount) << "Clearing cookies"; + _am->cookieJar()->deleteLater(); + _am->setCookieJar(new CookieJar); } -void Account::resetNetworkAccessManager() -{ - if (!_credentials || !_am) { - return; - } - - qCDebug(lcAccount) << "Resetting QNAM"; - QNetworkCookieJar *jar = _am->cookieJar(); - - // Use a QSharedPointer to allow locking the life of the QNAM on the stack. - // Make it call deleteLater to make sure that we can return to any QNAM stack frames safely. - _am = QSharedPointer(_credentials->createQNAM(), &QObject::deleteLater); - - _am->setCookieJar(jar); // takes ownership of the old cookie jar - connect(_am.data(), &QNetworkAccessManager::sslErrors, this, - &Account::slotHandleSslErrors); - connect(_am.data(), &QNetworkAccessManager::proxyAuthenticationRequired, - this, &Account::proxyAuthenticationRequired); -} - -QNetworkAccessManager *Account::networkAccessManager() -{ - return _am.data(); -} - -QSharedPointer Account::sharedNetworkAccessManager() +AccessManager *Account::accessManager() const { return _am; } QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data) { + Q_ASSERT(verb.isUpper()); req.setUrl(url); - req.setSslConfiguration(this->getOrCreateSslConfig()); if (verb == "HEAD" && !data) { return _am->head(req); } else if (verb == "GET" && !data) { @@ -277,248 +286,132 @@ QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url, return _am->sendCustomRequest(req, verb, data); } -void Account::setSslConfiguration(const QSslConfiguration &config) -{ - _sslConfiguration = config; -} - -QSslConfiguration Account::getOrCreateSslConfig() +void Account::setApprovedCerts(const QSet &certs) { - if (!_sslConfiguration.isNull()) { - // Will be set by CheckServerJob::finished() - // We need to use a central shared config to get SSL session tickets - return _sslConfiguration; - } - - // if setting the client certificate fails, you will probably get an error similar to this: - // "An internal error number 1060 happened. SSL handshake failed, client certificate was requested: SSL error: sslv3 alert handshake failure" - QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration(); - - // Try hard to re-use session for different requests - sslConfig.setSslOption(QSsl::SslOptionDisableSessionTickets, false); - sslConfig.setSslOption(QSsl::SslOptionDisableSessionSharing, false); - sslConfig.setSslOption(QSsl::SslOptionDisableSessionPersistence, false); - - return sslConfig; + _approvedCerts = certs; + _am->setCustomTrustedCaCertificates(_approvedCerts); } -void Account::setApprovedCerts(const QList certs) +void Account::addApprovedCerts(const QSet &certs) { - _approvedCerts = certs; - QSslSocket::addDefaultCaCertificates(certs); + _approvedCerts.unite(certs); + _am->setCustomTrustedCaCertificates(_approvedCerts); + Q_EMIT wantsAccountSaved(this); } -void Account::addApprovedCerts(const QList certs) +QUrl Account::url() const { - _approvedCerts += certs; + return _url; } -void Account::resetRejectedCertificates() +QString Account::hostName() const { - _rejectedCertificates.clear(); + return _url.host(); } -void Account::setSslErrorHandler(AbstractSslErrorHandler *handler) +JobQueue *Account::jobQueue() { - _sslErrorHandler.reset(handler); + return &_jobQueue; } -void Account::setUrl(const QUrl &url) +void Account::clearAMCache() { - _url = url; + _am->clearAccessCache(); } -QVariant Account::credentialSetting(const QString &key) const +const Capabilities &Account::capabilities() const { - if (_credentials) { - QString prefix = _credentials->authType(); - QVariant value = _settingsMap.value(prefix + QLatin1Char('_') + key); - if (value.isNull()) { - value = _settingsMap.value(key); - } - return value; - } - return QVariant(); + return _capabilities; } -void Account::setCredentialSetting(const QString &key, const QVariant &value) +bool Account::hasCapabilities() const { - if (_credentials) { - QString prefix = _credentials->authType(); - _settingsMap.insert(prefix + QLatin1Char('_') + key, value); - } + return _capabilities.isValid(); } -void Account::slotHandleSslErrors(QPointer reply, const QList &errors) +void Account::setCapabilities(const Capabilities &caps) { - const NetworkJobTimeoutPauser pauser(reply); + if (!caps.isValid()) + return; - // Copy info out of reply ASAP - if (reply.isNull()) { + // this should not happen: + if (!caps.spacesSupport().enabled) { + qCWarning(lcAccount) << "trying to set capabilities instance that doesn't support spaces - aborting"; return; } - const QString urlString = reply->url().toString(); - const auto sslConfiguration = reply->sslConfiguration(); - - qCDebug(lcAccount) << "SSL diagnostics for url " << urlString; - QList filteredErrors; - QList ignoredErrors; - for (const auto &error : qAsConst(errors)) { - if (error.error() == QSslError::UnableToGetLocalIssuerCertificate) { - // filter out this "error" - qCDebug(lcAccount) << "- Info for " << error.certificate() << ": " << error.errorString() - << ". Local SSL certificates are known and always accepted."; - ignoredErrors << error; - } else { - qCDebug(lcAccount) << "- Error for " << error.certificate() << ": " - << error.errorString() << "(" << int(error.error()) << ")" - << "\n"; - filteredErrors << error; - } + const bool versionChanged = + caps.status().legacyVersion != _capabilities.status().legacyVersion || caps.status().productversion != _capabilities.status().productversion; + _capabilities = caps; + if (versionChanged) { + Q_EMIT serverVersionChanged(this); } +} - // ask the _sslErrorHandler what to do with filteredErrors - const auto handleErrors = [&urlString, &sslConfiguration, this](const QList &filteredErrors) -> QList { - if (filteredErrors.isEmpty()) { - return {}; - } - bool allPreviouslyRejected = true; - for (const auto &error : qAsConst(filteredErrors)) { - if (!_rejectedCertificates.contains(error.certificate())) { - allPreviouslyRejected = false; - break; - } - } - - // If all certs have previously been rejected by the user, don't ask again. - if (allPreviouslyRejected) { - qCInfo(lcAccount) << "SSL diagnostics for url " << urlString - << ": certificates not trusted by user decision, returning."; - return {}; - } - - if (_sslErrorHandler.isNull()) { - qCWarning(lcAccount) << Q_FUNC_INFO << " called without a valid SSL error handler for account" << url() - << "(" << urlString << ")"; - return {}; - } +Account::ServerSupportLevel Account::serverSupportLevel() const +{ + if (!hasCapabilities()) { + // not detected yet + // should only happen when reloading an account from config? + return ServerSupportLevel::Unknown; + } - // SslDialogErrorHandler::handleErrors will run an event loop that might execute - // the deleteLater() of the QNAM before we have the chance of unwinding our stack. - // Keep a ref here on our stackframe to make sure that it doesn't get deleted before - // handleErrors returns. - QSharedPointer qnamLock = _am; - QList approvedCerts; - if (_sslErrorHandler->handleErrors(filteredErrors, sslConfiguration, &approvedCerts, sharedFromThis())) { - if (!approvedCerts.isEmpty()) { - QSslSocket::addDefaultCaCertificates(approvedCerts); - addApprovedCerts(approvedCerts); - emit wantsAccountSaved(this); - - // all ssl certs are known and accepted. We can ignore the problems right away. - qCDebug(lcAccount) << "Certs are known and trusted! This is not an actual error."; - } - return filteredErrors; - } else { - // Mark all involved certificates as rejected, so we don't ask the user again. - for (const auto &error : qAsConst(filteredErrors)) { - if (!_rejectedCertificates.contains(error.certificate())) { - _rejectedCertificates.append(error.certificate()); - } - } - } - return {}; - }; - - // Call `handleErrors` NOW, BEFORE checking if the scoped `reply` pointer. The lambda might take - // a long time to complete: if a dialog is shown, the user could be "slow" to click it away, and - // the reply might have been deleted at that point. So if we'd do this call inside the if below, - // the object inside the reply guarded pointer could there, but might be gone *after* we finish - // with `ignoreSslErrors`. Even when the scenario with a deleted reply happens, the handling is - // still needed: the `handleErrors` call will also set the default CA certificates through - // `QSslSocket::addDefaultCaCertificates()`, so future requests/replies can use those - // user-approved certificates. - auto moreIgnoredErrors = handleErrors(filteredErrors); - - // always apply the filter when we leave the scope - if (reply) { - // Warning: Do *not* use ignoreSslErrors() (without args) here: - // it permanently ignores all SSL errors for this host, even - // certificate changes. - reply->ignoreSslErrors(ignoredErrors + moreIgnoredErrors); + // todo: #34 + // ocis and kiteworks allegedly + // this seems awfully loosey goosey but I've been told it should work + if (!capabilities().status().productversion.isEmpty()) { + return ServerSupportLevel::Supported; } -} -void Account::slotCredentialsFetched() -{ - emit credentialsFetched(_credentials.data()); - _queueGuard.unblock(); + return ServerSupportLevel::Unsupported; } -void Account::slotCredentialsAsked() +QString Account::defaultSyncRoot() const { - emit credentialsAsked(_credentials.data()); + return _defaultSyncRoot; } - -JobQueue *Account::jobQueue() +// todo: #43 this seems to be used as a quasi switch for filtering oc10 from spaces support but needs more investigation +// if we need to eg update the sync root because it is not yet set, use defaultSyncRoot.isEmpty, not this thing. +bool Account::hasDefaultSyncRoot() const { - return &_jobQueue; + return !_defaultSyncRoot.isEmpty(); } -void Account::clearQNAMCache() +void Account::setDefaultSyncRoot(const QString &syncRoot) { - _am->clearAccessCache(); + Q_ASSERT(_defaultSyncRoot.isEmpty()); + if (!syncRoot.isEmpty()) { + _defaultSyncRoot = syncRoot; + } } -const Capabilities &Account::capabilities() const +void Account::setAppProvider(AppProvider &&p) { - return _capabilities; + _appProvider = std::move(p); } -void Account::setCapabilities(const QVariantMap &caps) +const AppProvider &Account::appProvider() const { - _capabilities = Capabilities(caps); + return _appProvider; } -QString Account::serverVersion() const +void Account::invalidCredentialsEncountered() { - return _serverVersion; + Q_EMIT invalidCredentials(Account::QPrivateSignal()); } -int Account::serverVersionInt() const +ResourcesCache *Account::resourcesCache() const { - // FIXME: Use Qt 5.5 QVersionNumber - auto components = serverVersion().split(QLatin1Char('.')); - return makeServerVersion(components.value(0).toInt(), - components.value(1).toInt(), - components.value(2).toInt()); + return _resourcesCache; } -int Account::makeServerVersion(int majorVersion, int minorVersion, int patchVersion) -{ - return (majorVersion << 16) + (minorVersion << 8) + patchVersion; -} +} // namespace OCC -bool Account::serverVersionUnsupported() const -{ - if (serverVersionInt() == 0) { - // not detected yet, assume it is fine. - return false; - } - // Older version which is not "end of life" according to https://github.com/owncloud/core/wiki/Maintenance-and-Release-Schedule - return serverVersionInt() < makeServerVersion(10, 0, 0) || serverVersion().endsWith(QLatin1String("Nextcloud")); -} -void Account::setServerVersion(const QString &version) +QDebug operator<<(QDebug debug, const OCC::Account *acc) { - if (version == _serverVersion) { - return; - } - - auto oldServerVersion = _serverVersion; - _serverVersion = version; - emit serverVersionChanged(this, oldServerVersion, version); + QDebugStateSaver saver(debug); + debug.setAutoInsertSpaces(false); + debug << "OCC::Account(" << acc->displayNameWithHost() << ")"; + return debug.maybeSpace(); } - -} // namespace OCC diff --git a/src/libsync/account.h b/src/libsync/account.h index 7c60e8c600c..0b1bcac311c 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -16,53 +16,39 @@ #ifndef SERVERCONNECTION_H #define SERVERCONNECTION_H -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "owncloudlib.h" -#ifndef TOKEN_AUTH_ONLY -#include -#endif - -#include "common/utility.h" -#include +#include "appprovider.h" #include "capabilities.h" #include "jobqueue.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + class QSettings; class QNetworkReply; -class QUrl; -class QNetworkAccessManager; +class AccessManager; namespace OCC { class CredentialManager; class AbstractCredentials; -class Account; -typedef QSharedPointer AccountPtr; -class QuotaInfo; class AccessManager; -class SimpleNetworkJob; +namespace GraphApi { + class SpacesManager; +} -/** - * @brief Reimplement this to handle SSL errors from libsync - * @ingroup libsync - */ -class AbstractSslErrorHandler -{ -public: - virtual ~AbstractSslErrorHandler() {} - virtual bool handleErrors(const QList &, const QSslConfiguration &conf, QList *, AccountPtr) = 0; -}; +class ResourcesCache; /** * @brief The Account class represents an account on an ownCloud Server @@ -74,43 +60,86 @@ class AbstractSslErrorHandler class OWNCLOUDSYNC_EXPORT Account : public QObject { Q_OBJECT - Q_PROPERTY(QString id MEMBER _id) - Q_PROPERTY(QString davUser MEMBER _davUser) - Q_PROPERTY(QString displayName MEMBER _displayName) - Q_PROPERTY(QUrl url MEMBER _url) + Q_PROPERTY(QUuid uid READ uuid CONSTANT) + Q_PROPERTY(QString davUser READ davUser CONSTANT) + Q_PROPERTY(QString davDisplayName READ davDisplayName NOTIFY displayNameChanged) + Q_PROPERTY(QString displayNameWithHost READ displayNameWithHost NOTIFY displayNameChanged) + Q_PROPERTY(QString initials READ initials NOTIFY displayNameChanged) + Q_PROPERTY(QString hostName READ hostName CONSTANT) + Q_PROPERTY(bool hasAvatar READ hasAvatar NOTIFY avatarChanged) + Q_PROPERTY(QGradient::Preset avatarGradient READ avatarGradient NOTIFY displayNameChanged) + Q_PROPERTY(QUrl url READ url CONSTANT) + QML_ELEMENT + QML_UNCREATABLE("Only created in the C++ code") public: - static AccountPtr create(); + /** + * Set a custom directory which all accounts created after this call will share to store their cached files in. + */ + static void setCommonCacheDirectory(const QString &directory); + static QString commonCacheDirectory(); + + // static AccountPtr create(const QUuid &uuid, const QString &user, const QUrl &url); + Account(const QUuid &uuid, const QString &user, const QUrl &url, QObject *parent = nullptr); ~Account() override; - AccountPtr sharedFromThis(); + void cleanupForRemoval(); + + /** + * The unique identifier for the account. + * This value is immutable after construction + */ + QUuid uuid() const; /** - * The user that can be used in dav url. + * The user associated with the account. + * This value is immutable after construction. + * + * note that this isn't always a human readable "user name" - with some servers it will be an account id + * and not a normal "name" * - * This can very well be different frome the login user that's - * stored in credentials()->user(). */ QString davUser() const; - void setDavUser(const QString &newDavUser); + + /** + * url is the user endpoint for the account + * This value is immutable after construction. + */ + QUrl url() const; + + /*** + * This is the default folder containing all spaces. + */ + QString defaultSyncRoot() const; + + /*** + * Whether we have defaultSyncRoot defined. + */ + // todo: #43 + bool hasDefaultSyncRoot() const; + + /*** + * Set defaultSyncRoot and creates the path on the filesystem. + * Setting an empty string will have no effect. + */ + void setDefaultSyncRoot(const QString &syncRoot); QString davDisplayName() const; void setDavDisplayName(const QString &newDisplayName); -#ifndef TOKEN_AUTH_ONLY - QPixmap avatar() const; - void setAvatar(const QPixmap &img); -#endif + QIcon avatar(); + void setAvatar(const QIcon &img); + bool hasAvatar() const; /// The name of the account as shown in the toolbar - QString displayName() const; + QString displayNameWithHost() const; + QString initials() const; + QGradient::Preset avatarGradient() const; - /// The internal id of the account. - Q_DECL_DEPRECATED_X("Use uuid") QString id() const; + /// The value used to group the account's setttings + QString groupIndex() const; - /** Server url of the account */ - void setUrl(const QUrl &url); - QUrl url() const { return _url; } + QString hostName() const; /** * @brief The possibly themed dav path for the account. It has @@ -118,9 +147,8 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject * @returns the (themeable) dav path for the account. */ QString davPath() const; - void setDavPath(const QString &s) { _davPath = s; } - /** Returns webdav entry URL, based on url() */ + /** Returns WebDAV entry URL, based on url() */ QUrl davUrl() const; /** Holds the accounts credentials */ @@ -138,157 +166,117 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject QNetworkRequest req = QNetworkRequest(), QIODevice *data = nullptr); - /** The ssl configuration during the first connection */ - QSslConfiguration getOrCreateSslConfig(); - QSslConfiguration sslConfiguration() const { return _sslConfiguration; } - void setSslConfiguration(const QSslConfiguration &config); - // Because of bugs in Qt, we use this to store info needed for the SSL Button - QSslCipher _sessionCipher; - QByteArray _sessionTicket; - QList _peerCertificateChain; - - /** The certificates of the account */ - QList approvedCerts() const { return _approvedCerts; } - void setApprovedCerts(const QList certs); - void addApprovedCerts(const QList certs); - - // Usually when a user explicitly rejects a certificate we don't - // ask again. After this call, a dialog will again be shown when - // the next unknown certificate is encountered. - void resetRejectedCertificates(); + QSet approvedCerts() const { return _approvedCerts; } - // pluggable handler - void setSslErrorHandler(AbstractSslErrorHandler *handler); + /*** + * Warning calling those will break running network jobs on the current access manager + */ + void setApprovedCerts(const QSet &certs); - // To be called by credentials only, for storing username and the like - QVariant credentialSetting(const QString &key) const; - void setCredentialSetting(const QString &key, const QVariant &value); + /*** + * Warning calling those will break running network jobs on the current access manager + */ + void addApprovedCerts(const QSet &certs); /** Access the server capabilities */ const Capabilities &capabilities() const; - void setCapabilities(const QVariantMap &caps); - - /** Access the server version - * - * For servers >= 10.0.0, this can be the empty string until capabilities - * have been received. - */ - QString serverVersion() const; + void setCapabilities(const Capabilities &caps); - /** Server version for easy comparison. - * - * Example: serverVersionInt() >= makeServerVersion(11, 2, 3) - * - * Will be 0 if the version is not available yet. - */ - int serverVersionInt() const; + bool hasCapabilities() const; - static int makeServerVersion(int majorVersion, int minorVersion, int patchVersion); - void setServerVersion(const QString &version); + void setAppProvider(AppProvider &&p); + const AppProvider &appProvider() const; - /** Whether the server is too old. - * - * Not supporting server versions is a gradual process. There's a hard - * compatibility limit (see ConnectionValidator) that forbids connecting - * to extremely old servers. And there's a weak "untested, not - * recommended, potentially dangerous" limit, that users might want - * to go beyond. - * - * This function returns true if the server is beyond the weak limit. - */ - bool serverVersionUnsupported() const; + enum class ServerSupportLevel { + Supported, + Unknown, + Unsupported + }; + Q_ENUMS(ServerSupportLevel) + ServerSupportLevel serverSupportLevel() const; /** True when the server connection is using HTTP2 */ bool isHttp2Supported() { return _http2Supported; } void setHttp2Supported(bool value) { _http2Supported = value; } void clearCookieJar(); - void lendCookieJarTo(QNetworkAccessManager *guest); - QString cookieJarPath(); - void resetNetworkAccessManager(); - QNetworkAccessManager *networkAccessManager(); - QSharedPointer sharedNetworkAccessManager(); + AccessManager *accessManager() const; JobQueue *jobQueue(); - QUuid uuid() const; - CredentialManager *credentialManager() const; -public slots: - /// Used when forgetting credentials - void clearQNAMCache(); - void slotHandleSslErrors(QPointer, const QList &); + GraphApi::SpacesManager *spacesManager() const { return _spacesManager; } + + /** + * We encountered an authentication error. + */ + void invalidCredentialsEncountered(); -signals: - /// Emitted whenever there's network activity - void propagatorNetworkActivity(); + ResourcesCache *resourcesCache() const; - /// Triggered by handleInvalidCredentials() - void invalidCredentials(); +public Q_SLOTS: + /// Used when forgetting credentials + void clearAMCache(); - void credentialsFetched(AbstractCredentials *credentials); - void credentialsAsked(AbstractCredentials *credentials); +Q_SIGNALS: + /// Triggered by invalidCredentialsEncountered() + // this signal is emited when a network job failed due to invalid credentials + void invalidCredentials(QPrivateSignal); - /// Forwards from QNetworkAccessManager::proxyAuthenticationRequired(). - void proxyAuthenticationRequired(const QNetworkProxy &, QAuthenticator *); + void credentialsFetched(); // e.g. when the approved SSL certificates changed + // todo: #15 void wantsAccountSaved(Account *acc); - void serverVersionChanged(Account *account, const QString &newVersion, const QString &oldVersion); + void serverVersionChanged(Account *account); - void accountChangedAvatar(); - void accountChangedDisplayName(); + void avatarChanged(); + void displayNameChanged(); void unknownConnectionState(); - void requestUrlUpdate(const QUrl &newUrl); - -protected Q_SLOTS: - void slotCredentialsFetched(); - void slotCredentialsAsked(); + // the signal exists on the Account object as the Approvider itself can change during runtime + void appProviderErrorOccured(const QString &error); private: - Account(QObject *parent = nullptr); - void setSharedThis(AccountPtr sharedThis); + // directory all newly created accounts store their various caches in + static QString _customCommonCacheDirectory; - QWeakPointer _sharedThis; - QString _id; + QString _groupIndex; QUuid _uuid; QString _davUser; QString _displayName; -#ifndef TOKEN_AUTH_ONLY - QPixmap _avatarImg; -#endif - QMap _settingsMap; + QString _defaultSyncRoot; + QIcon _avatarImg; + QUrl _url; + QString _cacheDirectory; - QList _approvedCerts; - QSslConfiguration _sslConfiguration; + QSet _approvedCerts; Capabilities _capabilities; - QString _serverVersion; - QScopedPointer _sslErrorHandler; - QuotaInfo *_quotaInfo; - QSharedPointer _am; - QScopedPointer _credentials; + QPointer _am; + QPointer _networkCache = nullptr; + QPointer _resourcesCache; + QPointer _credentials; bool _http2Supported = false; - /// Certificates that were explicitly rejected by the user - QList _rejectedCertificates; - - static QString _configFileName; - - QString _davPath; // defaults to value from theme, might be overwritten in brandings JobQueue _jobQueue; JobQueueGuard _queueGuard; CredentialManager *_credentialManager; + AppProvider _appProvider; + + GraphApi::SpacesManager *_spacesManager = nullptr; friend class AccountManager; }; } -Q_DECLARE_METATYPE(OCC::AccountPtr) +Q_DECLARE_METATYPE(OCC::Account) + + +QDebug OWNCLOUDSYNC_EXPORT operator<<(QDebug debug, const OCC::Account *job); #endif //SERVERCONNECTION_H diff --git a/src/libsync/accountfwd.h b/src/libsync/accountfwd.h deleted file mode 100644 index 04b16eabb4f..00000000000 --- a/src/libsync/accountfwd.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) by Daniel Molkentin - * - * 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. - */ - -#ifndef SERVERFWD_H -#define SERVERFWD_H - -#include - -namespace OCC { - -class Account; -class AccountState; - -using AccountPtr = QSharedPointer; -using AccountStatePtr = QExplicitlySharedDataPointer; - -} // namespace OCC - -#endif //SERVERFWD diff --git a/src/libsync/appprovider.cpp b/src/libsync/appprovider.cpp new file mode 100644 index 00000000000..5a198dcb25a --- /dev/null +++ b/src/libsync/appprovider.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "appprovider.h" + +#include "common/utility.h" +#include "libsync/account.h" +#include "libsync/networkjobs/jsonjob.h" + +#include +#include +#include +#include + +using namespace OCC; + +Q_LOGGING_CATEGORY(lcAppProvider, "sync.appprovider", QtInfoMsg) + +AppProvider::Provider::Provider(const QJsonObject &provider) + : mimeType(provider.value(QStringLiteral("mime_type")).toString()) + , extension(provider.value(QStringLiteral("extension")).toString()) + , name(provider.value(QStringLiteral("name")).toString()) + , description(provider.value(QStringLiteral("description")).toString()) + , icon(provider.value(QStringLiteral("icon")).toString()) + , defaultApplication(provider.value(QStringLiteral("default_application")).toString()) + , allowCreation(provider.value(QStringLiteral("allow_creation")).toBool()) +{ +} + +bool AppProvider::Provider::isValid() const +{ + return !mimeType.isEmpty(); +} + +AppProvider::AppProvider(const QJsonObject &apps) +{ + const auto mimTypes = apps.value(QStringLiteral("mime-types")).toArray(); + _providers.reserve(apps.size()); + for (const auto &type : mimTypes) { + Provider p(type.toObject()); + _providers.insert(p.mimeType, std::move(p)); + } +} + +const AppProvider::Provider &AppProvider::app(const QMimeType &mimeType) const +{ + if (auto it = Utility::optionalFind(_providers, mimeType.name())) { + return it->value(); + } + static const AppProvider::Provider nullProvider { {} }; + return nullProvider; +} + +const AppProvider::Provider &AppProvider::app(const QString &localPath) const +{ + QMimeDatabase db; + auto mimeType = db.mimeTypeForFile(localPath); + return app(mimeType); +} + +bool AppProvider::open(Account *account, const QString &localPath, const QByteArray &fileId) const +{ + if (!account) + return false; + + const auto &a = app(localPath); + if (a.isValid()) { + SimpleNetworkJob::UrlQuery query { { QStringLiteral("file_id"), QString::fromUtf8(fileId) } }; + auto *job = new JsonJob(account, account->capabilities().appProviders().openWebUrl, {}, "POST", query); + QObject::connect(job, &JsonJob::finishedSignal, [account, job, localPath] { + if (job->httpStatusCode() == 200) { + const auto url = QUrl(job->data().value(QStringLiteral("uri")).toString()); + const auto result = QDesktopServices::openUrl(url); + qCDebug(lcAppProvider) << "start browser" << url << result; + } else { + Q_EMIT account->appProviderErrorOccured( + QCoreApplication::translate("AppProvider", "Failed to open %1 in web. Error: %2.").arg(localPath, job->reply()->errorString())); + } + }); + job->start(); + return true; + } + return false; +} diff --git a/src/libsync/appprovider.h b/src/libsync/appprovider.h new file mode 100644 index 00000000000..2358d9001f9 --- /dev/null +++ b/src/libsync/appprovider.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 + */ +#pragma once + +#include "owncloudlib.h" + +#include +#include +#include +#include +#include + +namespace OCC { + +class Account; + +class OWNCLOUDSYNC_EXPORT AppProvider +{ +public: + struct OWNCLOUDSYNC_EXPORT Provider + { + // the server might provide multiple apps but no default + // for now we only support default apps + Provider() = default; + explicit Provider(const QJsonObject &provider); + QString mimeType; + QString extension; + QString name; + QString description; + QUrl icon; + QString defaultApplication; + bool allowCreation = false; + + bool isValid() const; + }; + + explicit AppProvider(const QJsonObject &apps = {}); + + const Provider &app(const QMimeType &mimeType) const; + const Provider &app(const QString &localPath) const; + + bool open(Account *account, const QString &localPath, const QByteArray &fileId) const; + + +private: + QHash _providers; +}; + +} diff --git a/src/libsync/bandwidthmanager.cpp b/src/libsync/bandwidthmanager.cpp deleted file mode 100644 index ac3516a095e..00000000000 --- a/src/libsync/bandwidthmanager.cpp +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright (C) by Markus Goetz - * - * 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 "owncloudpropagator.h" -#include "propagatedownload.h" -#include "propagateupload.h" -#include "propagatorjobs.h" -#include "common/utility.h" - -#ifdef Q_OS_WIN -#include -#include -#endif - -#include -#include -#include - -namespace OCC { - -Q_LOGGING_CATEGORY(lcBandwidthManager, "sync.bandwidthmanager", QtInfoMsg) - -// Because of the many layers of buffering inside Qt (and probably the OS and the network) -// we cannot lower this value much more. If we do, the estimated bw will be very high -// because the buffers fill fast while the actual network algorithms are not relevant yet. -static qint64 relativeLimitMeasuringTimerIntervalMsec = 1000 * 2; -// See also WritingState in http://code.woboq.org/qt5/qtbase/src/network/access/qhttpprotocolhandler.cpp.html#_ZN20QHttpProtocolHandler11sendRequestEv - -// FIXME At some point: -// * Register device only after the QNR received its metaDataChanged() signal -// * Incorporate Qt buffer fill state (it's a negative absolute delta). -// * Incorporate SSL overhead (percentage) -// * For relative limiting, do less measuring and more delaying+giving quota -// * For relative limiting, smoothen measurements - -BandwidthManager::BandwidthManager(OwncloudPropagator *p) - : QObject() - , _propagator(p) - , _relativeLimitCurrentMeasuredDevice(nullptr) - , _relativeUploadLimitProgressAtMeasuringRestart(0) - , _currentUploadLimit(0) - , _relativeLimitCurrentMeasuredJob(nullptr) - , _currentDownloadLimit(0) -{ - _currentUploadLimit = _propagator->_uploadLimit; - _currentDownloadLimit = _propagator->_downloadLimit; - - QObject::connect(&_switchingTimer, &QTimer::timeout, this, &BandwidthManager::switchingTimerExpired); - _switchingTimer.setInterval(10 * 1000); - _switchingTimer.start(); - QMetaObject::invokeMethod(this, "switchingTimerExpired", Qt::QueuedConnection); - - // absolute uploads/downloads - QObject::connect(&_absoluteLimitTimer, &QTimer::timeout, this, &BandwidthManager::absoluteLimitTimerExpired); - _absoluteLimitTimer.setInterval(1000); - _absoluteLimitTimer.start(); - - // Relative uploads - QObject::connect(&_relativeUploadMeasuringTimer, &QTimer::timeout, - this, &BandwidthManager::relativeUploadMeasuringTimerExpired); - _relativeUploadMeasuringTimer.setInterval(relativeLimitMeasuringTimerIntervalMsec); - _relativeUploadMeasuringTimer.start(); - _relativeUploadMeasuringTimer.setSingleShot(true); // will be restarted from the delay timer - QObject::connect(&_relativeUploadDelayTimer, &QTimer::timeout, - this, &BandwidthManager::relativeUploadDelayTimerExpired); - _relativeUploadDelayTimer.setSingleShot(true); // will be restarted from the measuring timer - - // Relative downloads - QObject::connect(&_relativeDownloadMeasuringTimer, &QTimer::timeout, - this, &BandwidthManager::relativeDownloadMeasuringTimerExpired); - _relativeDownloadMeasuringTimer.setInterval(relativeLimitMeasuringTimerIntervalMsec); - _relativeDownloadMeasuringTimer.start(); - _relativeDownloadMeasuringTimer.setSingleShot(true); // will be restarted from the delay timer - QObject::connect(&_relativeDownloadDelayTimer, &QTimer::timeout, - this, &BandwidthManager::relativeDownloadDelayTimerExpired); - _relativeDownloadDelayTimer.setSingleShot(true); // will be restarted from the measuring timer -} - -BandwidthManager::~BandwidthManager() -{ -} - -void BandwidthManager::registerUploadDevice(UploadDevice *p) -{ - _absoluteUploadDeviceList.push_back(p); - _relativeUploadDeviceList.push_back(p); - QObject::connect(p, &QObject::destroyed, this, &BandwidthManager::unregisterUploadDevice); - - if (usingAbsoluteUploadLimit()) { - p->setBandwidthLimited(true); - p->setChoked(false); - } else if (usingRelativeUploadLimit()) { - p->setBandwidthLimited(true); - p->setChoked(true); - } else { - p->setBandwidthLimited(false); - p->setChoked(false); - } -} - -void BandwidthManager::unregisterUploadDevice(QObject *o) -{ - auto p = reinterpret_cast(o); // note, we might already be in the ~QObject - _absoluteUploadDeviceList.remove(p); - _relativeUploadDeviceList.remove(p); - if (p == _relativeLimitCurrentMeasuredDevice) { - _relativeLimitCurrentMeasuredDevice = nullptr; - _relativeUploadLimitProgressAtMeasuringRestart = 0; - } -} - -void BandwidthManager::registerDownloadJob(GETJob *j) -{ - _downloadJobList.push_back(j); - QObject::connect(j, &QObject::destroyed, this, &BandwidthManager::unregisterDownloadJob); - - if (usingAbsoluteDownloadLimit()) { - j->setBandwidthLimited(true); - j->setChoked(false); - } else if (usingRelativeDownloadLimit()) { - j->setBandwidthLimited(true); - j->setChoked(true); - } else { - j->setBandwidthLimited(false); - j->setChoked(false); - } -} - -void BandwidthManager::unregisterDownloadJob(QObject *o) -{ - GETJob *j = reinterpret_cast(o); // note, we might already be in the ~QObject - _downloadJobList.remove(j); - if (_relativeLimitCurrentMeasuredJob == j) { - _relativeLimitCurrentMeasuredJob = nullptr; - _relativeDownloadLimitProgressAtMeasuringRestart = 0; - } -} - -void BandwidthManager::relativeUploadMeasuringTimerExpired() -{ - if (!usingRelativeUploadLimit() || _relativeUploadDeviceList.empty()) { - // Not in this limiting mode, just wait 1 sec to continue the cycle - _relativeUploadDelayTimer.setInterval(1000); - _relativeUploadDelayTimer.start(); - return; - } - if (_relativeLimitCurrentMeasuredDevice == nullptr) { - qCDebug(lcBandwidthManager) << "No device set, just waiting 1 sec"; - _relativeUploadDelayTimer.setInterval(1000); - _relativeUploadDelayTimer.start(); - return; - } - - qCDebug(lcBandwidthManager) << _relativeUploadDeviceList.size() << "Starting Delay"; - - qint64 relativeLimitProgressMeasured = (_relativeLimitCurrentMeasuredDevice->_readWithProgress - + _relativeLimitCurrentMeasuredDevice->_read) - / 2; - qint64 relativeLimitProgressDifference = relativeLimitProgressMeasured - _relativeUploadLimitProgressAtMeasuringRestart; - qCDebug(lcBandwidthManager) << _relativeUploadLimitProgressAtMeasuringRestart - << relativeLimitProgressMeasured << relativeLimitProgressDifference; - - qint64 speedkBPerSec = (relativeLimitProgressDifference / relativeLimitMeasuringTimerIntervalMsec * 1000.0) / 1024.0; - qCDebug(lcBandwidthManager) << relativeLimitProgressDifference / 1024 << "kB =>" << speedkBPerSec << "kB/sec on full speed (" - << _relativeLimitCurrentMeasuredDevice->_readWithProgress << _relativeLimitCurrentMeasuredDevice->_read - << qAbs(_relativeLimitCurrentMeasuredDevice->_readWithProgress - - _relativeLimitCurrentMeasuredDevice->_read) - << ")"; - - qint64 uploadLimitPercent = -_currentUploadLimit; - // don't use too extreme values - uploadLimitPercent = qMin(uploadLimitPercent, qint64(90)); - uploadLimitPercent = qMax(qint64(10), uploadLimitPercent); - qint64 wholeTimeMsec = (100.0 / uploadLimitPercent) * relativeLimitMeasuringTimerIntervalMsec; - qint64 waitTimeMsec = wholeTimeMsec - relativeLimitMeasuringTimerIntervalMsec; - qint64 realWaitTimeMsec = waitTimeMsec + wholeTimeMsec; - qCDebug(lcBandwidthManager) << waitTimeMsec << " - " << realWaitTimeMsec << " msec for " << uploadLimitPercent << "%"; - - // We want to wait twice as long since we want to give all - // devices the same quota we used now since we don't want - // any upload to timeout - _relativeUploadDelayTimer.setInterval(realWaitTimeMsec); - _relativeUploadDelayTimer.start(); - - auto deviceCount = _relativeUploadDeviceList.size(); - qint64 quotaPerDevice = relativeLimitProgressDifference * (uploadLimitPercent / 100.0) / deviceCount + 1.0; - for (auto *ud : _relativeUploadDeviceList) { - ud->setBandwidthLimited(true); - ud->setChoked(false); - ud->giveBandwidthQuota(quotaPerDevice); - qCDebug(lcBandwidthManager) << "Gave" << quotaPerDevice / 1024.0 << "kB to" << ud; - } - _relativeLimitCurrentMeasuredDevice = nullptr; -} - -void BandwidthManager::relativeUploadDelayTimerExpired() -{ - // Switch to measuring state - _relativeUploadMeasuringTimer.start(); // always start to continue the cycle - - if (!usingRelativeUploadLimit()) { - return; // oh, not actually needed - } - - if (_relativeUploadDeviceList.empty()) { - return; - } - - qCDebug(lcBandwidthManager) << _relativeUploadDeviceList.size() << "Starting measuring"; - - // Take first device and then append it again (= we round robin all devices) - _relativeLimitCurrentMeasuredDevice = _relativeUploadDeviceList.front(); - _relativeUploadDeviceList.pop_front(); - _relativeUploadDeviceList.push_back(_relativeLimitCurrentMeasuredDevice); - - _relativeUploadLimitProgressAtMeasuringRestart = (_relativeLimitCurrentMeasuredDevice->_readWithProgress - + _relativeLimitCurrentMeasuredDevice->_read) - / 2; - _relativeLimitCurrentMeasuredDevice->setBandwidthLimited(false); - _relativeLimitCurrentMeasuredDevice->setChoked(false); - - // choke all other UploadDevices - for (auto *ud : qAsConst(_relativeUploadDeviceList)) { - if (ud != _relativeLimitCurrentMeasuredDevice) { - ud->setBandwidthLimited(true); - ud->setChoked(true); - } - } - - // now we're in measuring state -} - -// for downloads: -void BandwidthManager::relativeDownloadMeasuringTimerExpired() -{ - if (!usingRelativeDownloadLimit() || _downloadJobList.empty()) { - // Not in this limiting mode, just wait 1 sec to continue the cycle - _relativeDownloadDelayTimer.setInterval(1000); - _relativeDownloadDelayTimer.start(); - return; - } - if (_relativeLimitCurrentMeasuredJob == nullptr) { - qCDebug(lcBandwidthManager) << "No job set, just waiting 1 sec"; - _relativeDownloadDelayTimer.setInterval(1000); - _relativeDownloadDelayTimer.start(); - return; - } - - qCDebug(lcBandwidthManager) << _downloadJobList.size() << "Starting Delay"; - - qint64 relativeLimitProgressMeasured = _relativeLimitCurrentMeasuredJob->currentDownloadPosition(); - qint64 relativeLimitProgressDifference = relativeLimitProgressMeasured - _relativeDownloadLimitProgressAtMeasuringRestart; - qCDebug(lcBandwidthManager) << _relativeDownloadLimitProgressAtMeasuringRestart - << relativeLimitProgressMeasured << relativeLimitProgressDifference; - - qint64 speedkBPerSec = (relativeLimitProgressDifference / relativeLimitMeasuringTimerIntervalMsec * 1000.0) / 1024.0; - qCDebug(lcBandwidthManager) << relativeLimitProgressDifference / 1024 << "kB =>" << speedkBPerSec << "kB/sec on full speed (" - << _relativeLimitCurrentMeasuredJob->currentDownloadPosition(); - - qint64 downloadLimitPercent = -_currentDownloadLimit; - // don't use too extreme values - downloadLimitPercent = qMin(downloadLimitPercent, qint64(90)); - downloadLimitPercent = qMax(qint64(10), downloadLimitPercent); - qint64 wholeTimeMsec = (100.0 / downloadLimitPercent) * relativeLimitMeasuringTimerIntervalMsec; - qint64 waitTimeMsec = wholeTimeMsec - relativeLimitMeasuringTimerIntervalMsec; - qint64 realWaitTimeMsec = waitTimeMsec + wholeTimeMsec; - qCDebug(lcBandwidthManager) << waitTimeMsec << " - " << realWaitTimeMsec << " msec for " << downloadLimitPercent << "%"; - - // We want to wait twice as long since we want to give all - // devices the same quota we used now since we don't want - // any download to timeout - _relativeDownloadDelayTimer.setInterval(realWaitTimeMsec); - _relativeDownloadDelayTimer.start(); - - auto jobCount = _downloadJobList.size(); - qint64 quota = relativeLimitProgressDifference * (downloadLimitPercent / 100.0); - if (quota > 20 * 1024) { - qCInfo(lcBandwidthManager) << "ADJUSTING QUOTA FROM " << quota << " TO " << quota - 20 * 1024; - quota -= 20 * 1024; - } - qint64 quotaPerJob = quota / jobCount + 1.0; - for (auto *gfj : _downloadJobList) { - gfj->setBandwidthLimited(true); - gfj->setChoked(false); - gfj->giveBandwidthQuota(quotaPerJob); - qCDebug(lcBandwidthManager) << "Gave" << quotaPerJob / 1024.0 << "kB to" << gfj; - } - _relativeLimitCurrentMeasuredDevice = nullptr; -} - -void BandwidthManager::relativeDownloadDelayTimerExpired() -{ - // Switch to measuring state - _relativeDownloadMeasuringTimer.start(); // always start to continue the cycle - - if (!usingRelativeDownloadLimit()) { - return; // oh, not actually needed - } - - if (_downloadJobList.empty()) { - qCDebug(lcBandwidthManager) << _downloadJobList.size() << "No jobs?"; - return; - } - - qCDebug(lcBandwidthManager) << _downloadJobList.size() << "Starting measuring"; - - // Take first device and then append it again (= we round robin all devices) - _relativeLimitCurrentMeasuredJob = _downloadJobList.front(); - _downloadJobList.pop_front(); - _downloadJobList.push_back(_relativeLimitCurrentMeasuredJob); - - _relativeDownloadLimitProgressAtMeasuringRestart = _relativeLimitCurrentMeasuredJob->currentDownloadPosition(); - _relativeLimitCurrentMeasuredJob->setBandwidthLimited(false); - _relativeLimitCurrentMeasuredJob->setChoked(false); - - // choke all other download jobs - for (auto *gfj : _downloadJobList) { - if (gfj != _relativeLimitCurrentMeasuredJob) { - gfj->setBandwidthLimited(true); - gfj->setChoked(true); - } - } - - // now we're in measuring state -} - -// end downloads - -void BandwidthManager::switchingTimerExpired() -{ - qint64 newUploadLimit = _propagator->_uploadLimit; - if (newUploadLimit != _currentUploadLimit) { - qCInfo(lcBandwidthManager) << "Upload Bandwidth limit changed" << _currentUploadLimit << newUploadLimit; - _currentUploadLimit = newUploadLimit; - for (auto *ud : _relativeUploadDeviceList) { - if (newUploadLimit == 0) { - ud->setBandwidthLimited(false); - ud->setChoked(false); - } else if (newUploadLimit > 0) { - ud->setBandwidthLimited(true); - ud->setChoked(false); - } else if (newUploadLimit < 0) { - ud->setBandwidthLimited(true); - ud->setChoked(true); - } - } - } - qint64 newDownloadLimit = _propagator->_downloadLimit; - if (newDownloadLimit != _currentDownloadLimit) { - qCInfo(lcBandwidthManager) << "Download Bandwidth limit changed" << _currentDownloadLimit << newDownloadLimit; - _currentDownloadLimit = newDownloadLimit; - for (auto *j : _downloadJobList) { - if (usingAbsoluteDownloadLimit()) { - j->setBandwidthLimited(true); - j->setChoked(false); - } else if (usingRelativeDownloadLimit()) { - j->setBandwidthLimited(true); - j->setChoked(true); - } else { - j->setBandwidthLimited(false); - j->setChoked(false); - } - } - } -} - -void BandwidthManager::absoluteLimitTimerExpired() -{ - if (usingAbsoluteUploadLimit() && !_absoluteUploadDeviceList.empty()) { - qint64 quotaPerDevice = _currentUploadLimit / _absoluteUploadDeviceList.size(); - qCDebug(lcBandwidthManager) << quotaPerDevice << _absoluteUploadDeviceList.size() << _currentUploadLimit; - for (auto *device : qAsConst(_absoluteUploadDeviceList)) { - device->giveBandwidthQuota(quotaPerDevice); - qCDebug(lcBandwidthManager) << "Gave " << quotaPerDevice / 1024.0 << " kB to" << device; - } - } - if (usingAbsoluteDownloadLimit() && !_downloadJobList.empty()) { - qint64 quotaPerJob = _currentDownloadLimit / _downloadJobList.size(); - qCDebug(lcBandwidthManager) << quotaPerJob << _downloadJobList.size() << _currentDownloadLimit; - for (auto *j : _downloadJobList) { - j->giveBandwidthQuota(quotaPerJob); - qCDebug(lcBandwidthManager) << "Gave " << quotaPerJob / 1024.0 << " kB to" << j; - } - } -} -} diff --git a/src/libsync/bandwidthmanager.h b/src/libsync/bandwidthmanager.h deleted file mode 100644 index a75ea973abc..00000000000 --- a/src/libsync/bandwidthmanager.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) by Markus Goetz - * - * 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. - */ - -#ifndef BANDWIDTHMANAGER_H -#define BANDWIDTHMANAGER_H - -#include -#include -#include - -#include - -namespace OCC { - -class UploadDevice; -class GETJob; -class OwncloudPropagator; - -/** - * @brief The BandwidthManager class - * @ingroup libsync - */ -class BandwidthManager : public QObject -{ - Q_OBJECT -public: - BandwidthManager(OwncloudPropagator *p); - ~BandwidthManager() override; - - bool usingAbsoluteUploadLimit() { return _currentUploadLimit > 0; } - bool usingRelativeUploadLimit() { return _currentUploadLimit < 0; } - bool usingAbsoluteDownloadLimit() { return _currentDownloadLimit > 0; } - bool usingRelativeDownloadLimit() { return _currentDownloadLimit < 0; } - - -public slots: - void registerUploadDevice(UploadDevice *); - void unregisterUploadDevice(QObject *); - - void registerDownloadJob(GETJob *); - void unregisterDownloadJob(QObject *); - - void absoluteLimitTimerExpired(); - void switchingTimerExpired(); - - void relativeUploadMeasuringTimerExpired(); - void relativeUploadDelayTimerExpired(); - - void relativeDownloadMeasuringTimerExpired(); - void relativeDownloadDelayTimerExpired(); - -private: - // for switching between absolute and relative bw limiting - QTimer _switchingTimer; - - // FIXME this timer and this variable should be replaced - // by the propagator emitting the changed limit values to us as signal - OwncloudPropagator *_propagator; - - // for absolute up/down bw limiting - QTimer _absoluteLimitTimer; - - // FIXME merge these two lists - std::list _absoluteUploadDeviceList; - std::list _relativeUploadDeviceList; - - QTimer _relativeUploadMeasuringTimer; - - // for relative bw limiting, we need to wait this amount before measuring again - QTimer _relativeUploadDelayTimer; - - // the device measured - UploadDevice *_relativeLimitCurrentMeasuredDevice; - - // for measuring how much progress we made at start - qint64 _relativeUploadLimitProgressAtMeasuringRestart; - qint64 _currentUploadLimit; - - std::list _downloadJobList; - QTimer _relativeDownloadMeasuringTimer; - - // for relative bw limiting, we need to wait this amount before measuring again - QTimer _relativeDownloadDelayTimer; - - // the device measured - GETJob *_relativeLimitCurrentMeasuredJob; - - // for measuring how much progress we made at start - qint64 _relativeDownloadLimitProgressAtMeasuringRestart; - - qint64 _currentDownloadLimit; -}; -} - -#endif diff --git a/src/libsync/capabilities.cpp b/src/libsync/capabilities.cpp index 227fd55e087..b67fe35f66d 100644 --- a/src/libsync/capabilities.cpp +++ b/src/libsync/capabilities.cpp @@ -14,20 +14,30 @@ #include "capabilities.h" -#include #include +#include using namespace std::chrono; namespace OCC { -Capabilities::Capabilities(const QVariantMap &capabilities) +Capabilities::Capabilities(const QUrl &url, const QVariantMap &capabilities) : _capabilities(capabilities) , _fileSharingCapabilities(_capabilities.value(QStringLiteral("files_sharing")).toMap()) , _fileSharingPublicCapabilities(_fileSharingCapabilities.value(QStringLiteral("public"), {}).toMap()) , _tusSupport(_capabilities.value(QStringLiteral("files")).toMap().value(QStringLiteral("tus_support")).toMap()) + , _spaces(_capabilities.value(QStringLiteral("spaces")).toMap()) + , _status(_capabilities.value(QStringLiteral("core")).toMap().value(QStringLiteral("status")).toMap()) + , _appProviders(AppProviders::findVersion( + url, _capabilities.value(QStringLiteral("files")).toMap().value(QStringLiteral("app_providers")).toList(), QVersionNumber({1, 1, 0}))) + , _filesSharing(_fileSharingCapabilities) +{ +} + +QVariantMap Capabilities::raw() const { + return _capabilities; } bool Capabilities::shareAPI() const @@ -42,64 +52,6 @@ bool Capabilities::sharePublicLink() const return shareAPI() && _fileSharingPublicCapabilities.value(QStringLiteral("enabled"), true).toBool(); } -bool Capabilities::sharePublicLinkAllowUpload() const -{ - return _fileSharingPublicCapabilities.value(QStringLiteral("upload")).toBool(); -} - -bool Capabilities::sharePublicLinkSupportsUploadOnly() const -{ - return _fileSharingPublicCapabilities.value(QStringLiteral("supports_upload_only")).toBool(); -} - -static bool getEnforcePasswordCapability(const QVariantMap &fileSharingPublicCapabilities, const QString &name) -{ - auto value = fileSharingPublicCapabilities[QStringLiteral("password")].toMap()[QStringLiteral("enforced_for")].toMap()[name]; - if (!value.isValid()) - return fileSharingPublicCapabilities[QStringLiteral("password")].toMap()[QStringLiteral("enforced")].toBool(); - return value.toBool(); -} - -bool Capabilities::sharePublicLinkEnforcePasswordForReadOnly() const -{ - return getEnforcePasswordCapability(_fileSharingPublicCapabilities, QStringLiteral("read_only")); -} - -bool Capabilities::sharePublicLinkEnforcePasswordForReadWrite() const -{ - return getEnforcePasswordCapability(_fileSharingPublicCapabilities, QStringLiteral("read_write")); -} - -bool Capabilities::sharePublicLinkEnforcePasswordForUploadOnly() const -{ - return getEnforcePasswordCapability(_fileSharingPublicCapabilities, QStringLiteral("upload_only")); -} - -bool Capabilities::sharePublicLinkDefaultExpire() const -{ - return _fileSharingPublicCapabilities.value(QStringLiteral("expire_date")).toMap().value(QStringLiteral("enabled")).toBool(); -} - -int Capabilities::sharePublicLinkDefaultExpireDateDays() const -{ - return _fileSharingPublicCapabilities.value(QStringLiteral("expire_date")).toMap().value(QStringLiteral("days")).toInt(); -} - -bool Capabilities::sharePublicLinkEnforceExpireDate() const -{ - return _fileSharingPublicCapabilities.value(QStringLiteral("expire_date")).toMap().value(QStringLiteral("enforced")).toBool(); -} - -bool Capabilities::sharePublicLinkMultiple() const -{ - return _fileSharingPublicCapabilities.value(QStringLiteral("multiple")).toBool(); -} - -bool Capabilities::shareResharing() const -{ - return _fileSharingCapabilities.value(QStringLiteral("resharing")).toBool(); -} - int Capabilities::defaultPermissions() const { return _fileSharingCapabilities.value(QStringLiteral("default_permissions"), 1).toInt(); @@ -121,57 +73,41 @@ bool Capabilities::isValid() const return !_capabilities.isEmpty(); } -QList Capabilities::supportedChecksumTypes() const +QList Capabilities::supportedChecksumTypes() const { - QList list; + // TODO: compute once + QList list; const auto &supportedTypes = _capabilities.value(QStringLiteral("checksums")).toMap().value(QStringLiteral("supportedTypes")).toList(); for (const auto &t : supportedTypes) { - list.push_back(t.toByteArray()); + list.push_back(CheckSums::fromByteArray(t.toByteArray())); } return list; } -QByteArray Capabilities::preferredUploadChecksumType() const +CheckSums::Algorithm Capabilities::preferredUploadChecksumType() const { - return qEnvironmentVariable("OWNCLOUD_CONTENT_CHECKSUM_TYPE", - _capabilities.value(QStringLiteral("checksums")).toMap() - .value(QStringLiteral("preferredUploadType"), QStringLiteral("SHA1")).toString()).toUtf8(); + static auto envType = CheckSums::fromByteArray(qgetenv("OWNCLOUD_CONTENT_CHECKSUM_TYPE")); + if (envType != CheckSums::Algorithm::NONE && envType != CheckSums::Algorithm::PARSE_ERROR) { + return envType; + } + const auto val = _capabilities.value(QStringLiteral("checksums")).toMap().value(QStringLiteral("preferredUploadType"), QStringLiteral("SHA1")).toString().toUpper().toUtf8(); + return CheckSums::fromByteArray(val); } -QByteArray Capabilities::uploadChecksumType() const +CheckSums::Algorithm Capabilities::uploadChecksumType() const { - QByteArray preferred = preferredUploadChecksumType(); - if (!preferred.isEmpty()) + auto preferred = preferredUploadChecksumType(); + if (preferred != CheckSums::Algorithm::PARSE_ERROR) return preferred; - QList supported = supportedChecksumTypes(); + QList supported = supportedChecksumTypes(); if (!supported.isEmpty()) return supported.first(); - return QByteArray(); -} - -bool Capabilities::chunkingNg() const -{ - if (!bigfilechunkingEnabled()) - { - return false; - } - static const auto chunkng = qgetenv("OWNCLOUD_CHUNKING_NG"); - if (chunkng == "0") - return false; - if (chunkng == "1") - return true; - return _capabilities.value(QStringLiteral("dav")).toMap().value(QStringLiteral("chunking")).toByteArray() >= "1.0"; + return CheckSums::Algorithm::PARSE_ERROR; } -bool Capabilities::bigfilechunkingEnabled() const +const Status &Capabilities::status() const { - bool ok; - const int chunkSize = qEnvironmentVariableIntValue("OWNCLOUD_CHUNK_SIZE", &ok); - if (ok && chunkSize == 0) - { - return false; - } - return _capabilities.value(QStringLiteral("files")).toMap().value(QStringLiteral("bigfilechunking"), true).toBool(); + return _status; } const TusSupport &Capabilities::tusSupport() const @@ -179,9 +115,9 @@ const TusSupport &Capabilities::tusSupport() const return _tusSupport; } -bool Capabilities::chunkingParallelUploadDisabled() const +const SpaceSupport &Capabilities::spacesSupport() const { - return _capabilities.value(QStringLiteral("dav")).toMap().value(QStringLiteral("chunkingParallelUploadDisabled")).toBool(); + return _spaces; } bool Capabilities::privateLinkPropertyAvailable() const @@ -209,16 +145,6 @@ QString Capabilities::invalidFilenameRegex() const return _capabilities[QStringLiteral("dav")].toMap()[QStringLiteral("invalidFilenameRegex")].toString(); } -bool Capabilities::uploadConflictFiles() const -{ - static auto envIsSet = !qEnvironmentVariableIsEmpty("OWNCLOUD_UPLOAD_CONFLICT_FILES"); - static int envValue = qEnvironmentVariableIntValue("OWNCLOUD_UPLOAD_CONFLICT_FILES"); - if (envIsSet) - return envValue != 0; - - return _capabilities[QStringLiteral("uploadConflictFiles")].toBool(); -} - bool Capabilities::versioningEnabled() const { return _capabilities.value(QStringLiteral("files")).toMap().value(QStringLiteral("versioning")).toBool(); @@ -227,8 +153,7 @@ bool Capabilities::versioningEnabled() const bool Capabilities::avatarsAvailable() const { auto userCaps = _fileSharingCapabilities.value(QStringLiteral("user")).toMap(); - // true by default for older servers, because turning off profile pictures was introduced in later versions - return userCaps.value(QStringLiteral("profile_picture"), true).toBool(); + return userCaps.value(QStringLiteral("profile_picture"), false).toBool(); } QStringList Capabilities::blacklistedFiles() const @@ -236,9 +161,29 @@ QStringList Capabilities::blacklistedFiles() const return _capabilities.value(QStringLiteral("files")).toMap().value(QStringLiteral("blacklisted_files")).toStringList(); } +Status::Status(const QVariantMap &status) +{ + legacyVersion = QVersionNumber::fromString(status.value(QStringLiteral("version")).toString()); + legacyVersionString = status.value(QStringLiteral("versionstring")).toString(); + edition = status.value(QStringLiteral("edition")).toString(); + productname = status.value(QStringLiteral("productname")).toString(); + product = status.value(QStringLiteral("product")).toString(); + productversion = status.value(QStringLiteral("productversion")).toString(); +} + +QVersionNumber Status::version() const +{ + return productversion.isEmpty() ? legacyVersion : QVersionNumber::fromString(productversion); +} + +QString Status::versionString() const +{ + return productversion.isEmpty() ? legacyVersionString : productversion; +} + TusSupport::TusSupport(const QVariantMap &tus_support) { - if (tus_support.isEmpty() || qEnvironmentVariableIsSet("OWNCLOUD_NO_TUS")) { + if (tus_support.isEmpty()) { return; } version = QVersionNumber::fromString(tus_support.value(QStringLiteral("version")).toString()); @@ -254,4 +199,53 @@ bool TusSupport::isValid() const return !version.isNull(); } +SpaceSupport::SpaceSupport(const QVariantMap &spaces_support) +{ + if (spaces_support.isEmpty()) { + return; + } + enabled = spaces_support.value(QStringLiteral("enabled")).toBool(); + hasMultiplePersonalSpaces = spaces_support.value(QStringLiteral("has_multiple_personal_spaces")).toBool(); + version = QVersionNumber::fromString(spaces_support.value(QStringLiteral("version")).toString()); +} + +bool SpaceSupport::isValid() const +{ + return !version.isNull(); +} + +Capabilities::AppProviders::AppProviders(const QUrl &baseUrl, const QVariantMap &appProviders) + : enabled(appProviders.value(QStringLiteral("enabled")).toBool()) + , version(QVersionNumber::fromString(appProviders.value(QStringLiteral("version")).toString())) + , appsUrl(baseUrl.resolved(appProviders.value(QStringLiteral("apps_url")).toUrl())) + , openUrl(baseUrl.resolved(appProviders.value(QStringLiteral("open_url")).toUrl())) + , newUrl(baseUrl.resolved(appProviders.value(QStringLiteral("new_url")).toUrl())) + , openWebUrl(baseUrl.resolved(appProviders.value(QStringLiteral("open_web_url")).toUrl())) +{ +} + + +const Capabilities::AppProviders &Capabilities::appProviders() const +{ + return _appProviders; +} + +Capabilities::AppProviders Capabilities::AppProviders::findVersion(const QUrl &baseUrl, const QVariantList &list, const QVersionNumber &v) +{ + auto it = std::find_if(list.cbegin(), list.cend(), [&v](const auto &it) { + return QVersionNumber::fromString(it.toMap().value(QStringLiteral("version")).toString()) == v; + }); + return it != list.cend() ? Capabilities::AppProviders{baseUrl, it->toMap()} : Capabilities::AppProviders{}; +} + +const FilesSharing &Capabilities::filesSharing() const +{ + return _filesSharing; +} + +FilesSharing::FilesSharing(const QVariantMap &filesSharing) + : sharing_roles(filesSharing.value(QStringLiteral("sharing_roles"), false).toBool()) +{ +} + } // namespace OCC diff --git a/src/libsync/capabilities.h b/src/libsync/capabilities.h index 08e9ab4ffd8..70ec089cfec 100644 --- a/src/libsync/capabilities.h +++ b/src/libsync/capabilities.h @@ -18,14 +18,45 @@ #include "owncloudlib.h" -#include +#include "common/checksumalgorithms.h" + #include +#include #include namespace OCC { +// todo: #34 +struct OWNCLOUDSYNC_EXPORT Status +{ + /** + 1 + 0 + 0 + 10.11.0.0 + 10.11.0 + Community + Infinite Scale + Infinite Scale + 2.0.0-beta1+7c2e3201b + */ + + Status(const QVariantMap &status); + // legacy version + QVersionNumber legacyVersion; + // legacy version + QString legacyVersionString; + + QString edition; + QString productname; + QString product; + QString productversion; + + QVersionNumber version() const; + QString versionString() const; +}; -struct TusSupport +struct OWNCLOUDSYNC_EXPORT TusSupport { /** @@ -46,6 +77,42 @@ struct TusSupport bool isValid() const; }; +struct OWNCLOUDSYNC_EXPORT SpaceSupport +{ + /** + "spaces": { + "version": "0.0.1", + "enabled": true, + "has_multiple_personal_spaces": true + } + */ + SpaceSupport(const QVariantMap &spaces_support); + bool enabled = false; + bool hasMultiplePersonalSpaces = false; + QVersionNumber version; + + bool isValid() const; +}; + +struct OWNCLOUDSYNC_EXPORT FilesSharing +{ + /** + "api_enabled": true, + "resharing": true, + "group_sharing": true, + "sharing_roles": true, + "auto_accept_share": true, + "share_with_group_members_only": true, + "share_with_membership_groups_only": true, + "search_min_length": 3, + "default_permissions": 22, + */ + FilesSharing(const QVariantMap &filesSharing); + + // TODO: add more + bool sharing_roles = false; +}; + /** * @brief The Capabilities class represents the capabilities of an ownCloud * server @@ -54,27 +121,36 @@ struct TusSupport class OWNCLOUDSYNC_EXPORT Capabilities { public: - Capabilities(const QVariantMap &capabilities); + struct OWNCLOUDSYNC_EXPORT AppProviders + { + /** + "app_providers": [ + { + "enabled": true, + "version": "1.0.0", + "apps_url": "/app/list", + "open_url": "/app/open", + "new_url": "/app/new" + } + */ + AppProviders() = default; + static AppProviders findVersion(const QUrl &baseUrl, const QVariantList &list, const QVersionNumber &v); + AppProviders(const QUrl &baseUrl, const QVariantMap &appProviders); + + bool enabled = false; + QVersionNumber version; + QUrl appsUrl; + QUrl openUrl; + QUrl newUrl; + QUrl openWebUrl; + }; + + + Capabilities(const QUrl &url, const QVariantMap &capabilities); bool shareAPI() const; bool sharePublicLink() const; - bool sharePublicLinkAllowUpload() const; - bool sharePublicLinkSupportsUploadOnly() const; - /** Whether read-only link shares require a password. - * - * Returns sharePublicLinkEnforcePassword() if the fine-grained - * permission isn't available. - */ - bool sharePublicLinkEnforcePasswordForReadOnly() const; - bool sharePublicLinkEnforcePasswordForReadWrite() const; - bool sharePublicLinkEnforcePasswordForUploadOnly() const; - - bool sharePublicLinkDefaultExpire() const; - int sharePublicLinkDefaultExpireDateDays() const; - bool sharePublicLinkEnforceExpireDate() const; - bool sharePublicLinkMultiple() const; - bool shareResharing() const; /** Remote Poll interval. * * returns the requested poll interval in seconds to be used by the client. @@ -85,15 +161,9 @@ class OWNCLOUDSYNC_EXPORT Capabilities // TODO: return SharePermission int defaultPermissions() const; - bool chunkingNg() const; - - /// Wheter to use chunking - bool bigfilechunkingEnabled() const; - + const Status &status() const; const TusSupport &tusSupport() const; - - /// disable parallel upload in chunking - bool chunkingParallelUploadDisabled() const; + const SpaceSupport &spacesSupport() const; /// Whether the "privatelink" DAV property is available bool privateLinkPropertyAvailable() const; @@ -110,7 +180,7 @@ class OWNCLOUDSYNC_EXPORT Capabilities /** * Returns the checksum types the server understands. * - * When the client uses one of these checksumming algorithms in + * When the client uses one of these checksum algorithms in * the OC-Checksum header of a file upload, the server will use * it to validate that data was transmitted correctly. * @@ -118,24 +188,21 @@ class OWNCLOUDSYNC_EXPORT Capabilities * Default: [] * Possible entries: "Adler32", "MD5", "SHA1" */ - QList supportedChecksumTypes() const; + QList supportedChecksumTypes() const; /** * The checksum algorithm that the server recommends for file uploads. * This is just a preference, any algorithm listed in supportedTypes may be used. * * Path: checksums/preferredUploadType - * Default: empty, meaning "no preference" - * Possible values: empty or any of the supportedTypes */ - QByteArray preferredUploadChecksumType() const; + OCC::CheckSums::Algorithm preferredUploadChecksumType() const; /** * Helper that returns the preferredUploadChecksumType() if set, or one - * of the supportedChecksumTypes() if it isn't. May return an empty - * QByteArray if no checksum types are supported. + * of the supportedChecksumTypes() if it isn't. */ - QByteArray uploadChecksumType() const; + CheckSums::Algorithm uploadChecksumType() const; /** * List of HTTP error codes should be guaranteed to eventually reset @@ -172,23 +239,30 @@ class OWNCLOUDSYNC_EXPORT Capabilities */ QStringList blacklistedFiles() const; - /** - * Whether conflict files should remain local (default) or should be uploaded. - */ - bool uploadConflictFiles() const; - /** Is versioning available? */ bool versioningEnabled() const; /** Are avatars (profile pictures) available? */ bool avatarsAvailable() const; + const AppProviders &appProviders() const; + + const FilesSharing &filesSharing() const; + + // const Migration &migration() const; + + QVariantMap raw() const; + private: QVariantMap _capabilities; QVariantMap _fileSharingCapabilities; QVariantMap _fileSharingPublicCapabilities; TusSupport _tusSupport; + SpaceSupport _spaces; + Status _status; + AppProviders _appProviders; + FilesSharing _filesSharing; }; } -#endif //CAPABILITIES_H +#endif // CAPABILITIES_H diff --git a/src/libsync/config/appconfig.cpp b/src/libsync/config/appconfig.cpp new file mode 100644 index 00000000000..530adff63c4 --- /dev/null +++ b/src/libsync/config/appconfig.cpp @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller + +#include "appconfig.h" + +#include "../theme.h" +#include "common/utility.h" +#include "openidconfig.h" + +#include +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcSystemConfig, "sync.systemconfig", QtInfoMsg) + +namespace chrono = std::chrono; + +AppConfig::AppConfig() +{ + _serverUrl = Theme::instance()->overrideServerUrl(); + // If a theme provides a hardcoded URL, do not allow for URL change. + _allowServerURLChange = Theme::instance()->overrideServerUrl().isEmpty(); + _skipUpdateCheck = false; + _openIdConfig = loadOpenIdConfigFromTheme(); + + if (!Theme::instance()->allowSystemConfigOverrides()) + return; + + // Load all overrides + + auto format = Utility::isWindows() ? QSettings::NativeFormat : QSettings::IniFormat; + const QSettings system(configPath(QOperatingSystemVersion::currentType(), *Theme::instance()), format); + + _serverUrl = system.value(SetupServerUrlKey, QString()).toString(); + if (system.contains(SetupAllowServerUrlChangeKey)) { + _allowServerURLChange = system.value(SetupAllowServerUrlChangeKey).toBool(); + } + + _skipUpdateCheck = system.value(UpdaterSkipUpdateCheckKey, false).toBool(); + + OpenIdConfig systemConfig = loadOpenIdConfigFromSystemConfig(system); + if (systemConfig.isValid()) { + qCInfo(lcSystemConfig()) << "Using OpenID config from system config"; + _openIdConfig = systemConfig; + } +} + +OpenIdConfig AppConfig::loadOpenIdConfigFromTheme() +{ + Theme *theme = Theme::instance(); + + QString clientId = theme->oauthClientId(); + QString clientSecret = theme->oauthClientSecret(); + QVector ports = theme->oauthPorts(); + QString scopes = theme->openIdConnectScopes(); + QString prompt = theme->openIdConnectPrompt(); + + OpenIdConfig cfg(clientId, clientSecret, ports, scopes, prompt); + OC_ASSERT(cfg.isValid()); + + return cfg; +} + +OpenIdConfig AppConfig::loadOpenIdConfigFromSystemConfig(const QSettings &system) +{ + QString clientId = system.value(OidcClientIdKey, QString()).toString(); + QString clientSecret = system.value(OidcClientSecretKey, QString()).toString(); + QString scopes = system.value(OidcScopesKey, QString()).toString(); + QString prompt = system.value(OidcPromptKey, QString()).toString(); + + QVector ports; + QVariant portsVar = system.value(OidcPortsKey); + QStringList parts; + if (portsVar.typeId() == QMetaType::QString) { + // Windows registry: "8080,8888" is a QString + parts = portsVar.toString().split(QLatin1Char(','), Qt::SkipEmptyParts); + } else { + // .ini files (Linux, macOS): "8080,8888" is a QStringlist + parts = portsVar.toStringList(); + } + for (const QString &p : std::as_const(parts)) { + bool ok = false; + quint16 val = p.trimmed().toUShort(&ok); + if (ok) { + ports.append(val); + } + } + + if (ports.isEmpty()) + ports.append(0); // 0 means any port + + return OpenIdConfig(clientId, clientSecret, ports, scopes, prompt); +} + +QString AppConfig::configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme) +{ + // Important: these paths conform to how names typically work on the systems on which they are used. This includes usage of upper-/lowercase. + + if (os == QOperatingSystemVersion::Windows) { + // We use HKEY_LOCAL_MACHINE\Software\Policies since this is the location where GPO operates. + // Note: use of uppercase/camelcase is common. + return QString("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2").arg(theme.vendor(), theme.appNameGUI()); + } + + if (os == QOperatingSystemVersion::MacOS) { + // We use a subfolder to have one common location where in the future more files can be stored (like icons, images and such) + // ini is used on macOS in contrary to plist because they are easier to maintain. + // Note: rev-domain notation and lowercase is typically used. + return QString("/Library/Preferences/%1/%2.ini").arg(theme.orgDomainName(), theme.appName()); + } + + // On Unix style systems, the application name in lowercase is typically used. + return QString("/etc/%1/%1.ini").arg(theme.appName()); +} + +bool AppConfig::allowServerUrlChange() const +{ + return _allowServerURLChange; +} + +QString AppConfig::serverUrl() const +{ + return _serverUrl; +} + +bool AppConfig::skipUpdateCheck() const +{ + return _skipUpdateCheck; +} + +OpenIdConfig AppConfig::openIdConfig() const +{ + return _openIdConfig; +} + +} // OCC namespace diff --git a/src/libsync/config/appconfig.h b/src/libsync/config/appconfig.h new file mode 100644 index 00000000000..de9fe3a3b4b --- /dev/null +++ b/src/libsync/config/appconfig.h @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller + +#pragma once + +#include "../theme.h" +#include "owncloudlib.h" +#include "openidconfig.h" + +#include +#include +#include + + +namespace OCC { + +/** + * @brief The AppConfig class + * @ingroup libsync + * @note This class provides access to system-wide configuration settings. + * These settings are typically read-only and affect the behavior of the application globally. + * On Windows, settings are read from the registry path: + * HKEY_LOCAL_MACHINE\Software\Policies\\ + * On macOS, settings are read from the file: + * /Library/Preferences//.ini + * On Linux and other Unix-like systems, settings are read from the file: + * /etc//.ini + * + * @example owncloud.ini + * [Setup] + * ServerUrl=https://cloud.example.com + * AllowServerUrlChange=false + * MoveToTrash=true + * + * [Updater] + * SkipUpdateCheck=true + * + * [OpenIDConnect] + * ClientId=your-client-id + * ClientSecret=your-client-secret + * Ports=8080,8443 + * Scopes=openid offline_access email profile + * Prompt=select_account consent + * + * @example owncloud.reg (Windows Registry) + * Windows Registry Editor Version 5.00 + * + * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud] + * + * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\Setup] + * "ServerUrl"="https://cloud.example.com" + * "AllowServerUrlChange"=dword:00000000 + * "MoveToTrash"=dword:00000001 + * + * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\Updater] + * "SkipUpdateCheck"=dword:00000001 + * + * [HKEY_LOCAL_MACHINE\Software\Policies\ownCloud\ownCloud\OpenIDConnect] + * "ClientId"="your-client-id" + * "ClientSecret"="your-client-secret" + * "Ports"="8080,8443" + * "Scopes"="openid offline_access email profile" + * "Prompt"="select_account consent" + * + */ +class OWNCLOUDSYNC_EXPORT AppConfig +{ +public: + explicit AppConfig(); + /** + * Determine if changing the server URL is allowed based on system configuration. + * This value is only relevant if SystemConfig::serverUrl() returns a non-empty string. + * @return True if changing the server URL is allowed, false otherwise. + */ + bool allowServerUrlChange() const; + + /** + * Retrieve the server URL from the system configuration or theme. + * @return The server URL as a QString. If not set, returns an empty string. + */ + QString serverUrl() const; + + /** + * Retrieve the OpenID Connect configuration from the system configuration or the theme. + * The configuration includes client ID, client secret, ports, scopes, and prompt settings. + * The returned OpenIdConfig object may have empty values if not set in the system configuration. + * @return An OpenIdConfig object containing the OpenID Connect settings. + */ + OpenIdConfig openIdConfig() const; + + /** + * Determine if update checks should be skipped based on system configuration. + * @return True if update checks should be skipped, false otherwise. + */ + bool skipUpdateCheck() const; + + /** + * Get the path to the system configuration file or registry path based on the operating system and theme. + * @param os operating system type + * @param theme the theme instance + * @return the path to the system configuration file or registry path + * @internal kept public for testing purposes + */ + static QString configPath(const QOperatingSystemVersion::OSType& os, const Theme& theme); + +private: + static OpenIdConfig loadOpenIdConfigFromTheme(); + static OpenIdConfig loadOpenIdConfigFromSystemConfig(const QSettings &system); + +private: // System settings keys + // Setup related keys + inline static const QString SetupAllowServerUrlChangeKey = QStringLiteral("Setup/AllowServerUrlChange"); + inline static const QString SetupServerUrlKey = QStringLiteral("Setup/ServerUrl"); + // Updater related keys + inline static const QString UpdaterSkipUpdateCheckKey = QStringLiteral("Updater/SkipUpdateCheck"); + // OpenID Connect related keys + inline static const QString OidcClientIdKey = QStringLiteral("OpenIDConnect/ClientId"); + inline static const QString OidcClientSecretKey = QStringLiteral("OpenIDConnect/ClientSecret"); + inline static const QString OidcPortsKey = QStringLiteral("OpenIDConnect/Ports"); + inline static const QString OidcScopesKey = QStringLiteral("OpenIDConnect/Scopes"); + inline static const QString OidcPromptKey = QStringLiteral("OpenIDConnect/Prompt"); + +private: + bool _allowServerURLChange; + QString _serverUrl; + bool _skipUpdateCheck; + OpenIdConfig _openIdConfig; +}; + +} // OCC namespace diff --git a/src/libsync/config/openidconfig.cpp b/src/libsync/config/openidconfig.cpp new file mode 100644 index 00000000000..2ef1154af16 --- /dev/null +++ b/src/libsync/config/openidconfig.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller + +#include "openidconfig.h" + +namespace OCC { + +OpenIdConfig::OpenIdConfig() +{ +} + +OpenIdConfig::OpenIdConfig(const QString &clientId, const QString &clientSecret, const QList &ports, const QString &scopes, const QString &prompt) + : _clientId(clientId) + , _clientSecret(clientSecret) + , _ports(ports) + , _scopes(scopes) + , _prompt(prompt) +{ +} + +QString OpenIdConfig::clientId() const { + return _clientId; +} + +QString OpenIdConfig::clientSecret() const { + return _clientSecret; +} + +QList OpenIdConfig::ports() const { + return _ports; +} + +QString OpenIdConfig::scopes() const { + return _scopes; +} + +QString OpenIdConfig::prompt() const { + return _prompt; +} + +bool OpenIdConfig::isValid() const +{ + return !_clientId.isEmpty() && !_ports.isEmpty(); +} + +} diff --git a/src/libsync/config/openidconfig.h b/src/libsync/config/openidconfig.h new file mode 100644 index 00000000000..515723d8e78 --- /dev/null +++ b/src/libsync/config/openidconfig.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2026 Thomas Müller + +#pragma once +#include "owncloudlib.h" + +#include +#include +#include + +namespace OCC { + +/** + * @brief The OpenIdConfig class + * @ingroup libsync + * @note This class encapsulates the configuration settings required for OpenID Connect authentication. + */ +class OWNCLOUDSYNC_EXPORT OpenIdConfig +{ +public: + OpenIdConfig(); + explicit OpenIdConfig(const QString &clientId, const QString &clientSecret, const QList &ports, const QString &scopes, const QString &prompt); + + QString clientId() const; + QString clientSecret() const; + QList ports() const; + QString scopes() const; + QString prompt() const; + + bool isValid() const; + +private: + QString _clientId; + QString _clientSecret; + QList _ports; + QString _scopes; + QString _prompt; +}; +} diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp index 6d52dc9d9a1..d2328faf808 100644 --- a/src/libsync/configfile.cpp +++ b/src/libsync/configfile.cpp @@ -12,24 +12,20 @@ * for more details. */ -#include "config.h" - #include "common/asserts.h" #include "common/utility.h" -#include "common/version.h" + +#include "config/appconfig.h" +#ifdef Q_OS_WIN +#include "common/utility_win.h" +#endif #include "configfile.h" #include "logger.h" #include "theme.h" -#include "creds/abstractcredentials.h" - #include "csync_exclude.h" -#ifndef TOKEN_AUTH_ONLY -#include #include -#endif - #include #include #include @@ -51,50 +47,57 @@ Q_LOGGING_CATEGORY(lcConfigFile, "sync.configfile", QtInfoMsg) namespace { const QString logHttpC() { return QStringLiteral("logHttp"); } const QString remotePollIntervalC() { return QStringLiteral("remotePollInterval"); } -//const QString caCertsKeyC() { return QStringLiteral("CaCertificates"); } only used from account.cpp const QString forceSyncIntervalC() { return QStringLiteral("forceSyncInterval"); } const QString fullLocalDiscoveryIntervalC() { return QStringLiteral("fullLocalDiscoveryInterval"); } -const QString notificationRefreshIntervalC() { return QStringLiteral("notificationRefreshInterval"); } const QString monoIconsC() { return QStringLiteral("monoIcons"); } const QString promptDeleteC() { return QStringLiteral("promptDeleteAllFiles"); } const QString crashReporterC() { return QStringLiteral("crashReporter"); } -const QString optionalDesktopNoficationsC() { return QStringLiteral("optionalDesktopNotifications"); } -const QString showInExplorerNavigationPaneC() { return QStringLiteral("showInExplorerNavigationPane"); } -const QString skipUpdateCheckC() { return QStringLiteral("skipUpdateCheck"); } +const QString optionalDesktopNotificationsC() +{ + return QStringLiteral("optionalDesktopNotifications"); +} const QString updateCheckIntervalC() { return QStringLiteral("updateCheckInterval"); } -const QString updateChannelC() { return QStringLiteral("updateChannel"); } const QString uiLanguageC() { return QStringLiteral("uiLanguage"); } const QString geometryC() { return QStringLiteral("geometry"); } const QString timeoutC() { return QStringLiteral("timeout"); } -const QString chunkSizeC() { return QStringLiteral("chunkSize"); } -const QString minChunkSizeC() { return QStringLiteral("minChunkSize"); } -const QString maxChunkSizeC() { return QStringLiteral("maxChunkSize"); } -const QString targetChunkUploadDurationC() { return QStringLiteral("targetChunkUploadDuration"); } const QString automaticLogDirC() { return QStringLiteral("logToTemporaryLogDir"); } -const QString deleteOldLogsAfterHoursC() { return QStringLiteral("temporaryLogDirDeleteOldLogsAfterHours"); } -const QString showExperimentalOptionsC() { return QStringLiteral("showExperimentalOptions"); } +const QString numberOfLogsToKeepC() +{ + return QStringLiteral("numberOfLogsToKeep"); +} + +// The key `clientVersion` stores the version *with* build number of the config file. It is named +// this way, because before 5.0, only the version *without* build number was stored. const QString clientVersionC() { return QStringLiteral("clientVersion"); } const QString proxyHostC() { return QStringLiteral("Proxy/host"); } const QString proxyTypeC() { return QStringLiteral("Proxy/type"); } const QString proxyPortC() { return QStringLiteral("Proxy/port"); } -const QString proxyUserC() { return QStringLiteral("Proxy/user"); } -const QString proxyPassC() { return QStringLiteral("Proxy/pass"); } +const QString proxyUserC() +{ + return QStringLiteral("Proxy/user"); +} const QString proxyNeedsAuthC() { return QStringLiteral("Proxy/needsAuth"); } -const QString useUploadLimitC() { return QStringLiteral("BWLimit/useUploadLimit"); } -const QString useDownloadLimitC() { return QStringLiteral("BWLimit/useDownloadLimit"); } -const QString uploadLimitC() { return QStringLiteral("BWLimit/uploadLimit"); } -const QString downloadLimitC() { return QStringLiteral("BWLimit/downloadLimit"); } +const QString pauseSyncWhenMeteredC() +{ + return QStringLiteral("pauseWhenMetered"); +} + +const QString issuesWidgetFilterC() +{ + return QStringLiteral("issuesWidgetFilter"); +} -const QString newBigFolderSizeLimitC() { return QStringLiteral("newBigFolderSizeLimit"); } -const QString useNewBigFolderSizeLimitC() { return QStringLiteral("useNewBigFolderSizeLimit"); } -const QString confirmExternalStorageC() { return QStringLiteral("confirmExternalStorage"); } -const QString moveToTrashC() { return QStringLiteral("moveToTrash"); } +QString excludeFileNameC() +{ + return QStringLiteral("sync-exclude.lst"); } +} // anonymous namespace + QString ConfigFile::_confDir = QString(); -const std::chrono::seconds DefaultRemotePollInterval { 30 }; // default remote poll time in milliseconds +const std::chrono::seconds DefaultRemotePollInterval { 30 }; static chrono::milliseconds millisecondsValue(const QSettings &setting, const QString &key, chrono::milliseconds defaultValue) @@ -105,18 +108,6 @@ static chrono::milliseconds millisecondsValue(const QSettings &setting, const QS ConfigFile::ConfigFile() { QSettings::setDefaultFormat(QSettings::IniFormat); - - const QString config = configFile(); - - QSettings settings(config, QSettings::IniFormat); - settings.beginGroup(defaultConnection()); - - // run init only once - static bool init = [this]() { - setLogHttp(logHttp()); - return false; - }(); - Q_UNUSED(init); } bool ConfigFile::setConfDir(const QString &value) @@ -141,125 +132,78 @@ bool ConfigFile::setConfDir(const QString &value) bool ConfigFile::optionalDesktopNotifications() const { - QSettings settings(configFile(), QSettings::IniFormat); - return settings.value(optionalDesktopNoficationsC(), true).toBool(); + auto settings = makeQSettings(); + return settings.value(optionalDesktopNotificationsC(), true).toBool(); } -bool ConfigFile::showInExplorerNavigationPane() const +std::optional ConfigFile::issuesWidgetFilter() const { - QSettings settings(configFile(), QSettings::IniFormat); - return settings.value(showInExplorerNavigationPaneC(), QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10).toBool(); + auto settings = makeQSettings(); + if (settings.contains(issuesWidgetFilterC())) { + return settings.value(issuesWidgetFilterC()).toStringList(); + } + + return {}; } -void ConfigFile::setShowInExplorerNavigationPane(bool show) +void ConfigFile::setIssuesWidgetFilter(const QStringList &checked) { - QSettings settings(configFile(), QSettings::IniFormat); - settings.setValue(showInExplorerNavigationPaneC(), show); + auto settings = makeQSettings(); + settings.setValue(issuesWidgetFilterC(), checked); settings.sync(); } std::chrono::seconds ConfigFile::timeout() const { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); const auto val = settings.value(timeoutC()).toInt(); // default to 5 min return val ? std::chrono::seconds(val) : 5min; } -qint64 ConfigFile::chunkSize() const -{ - QSettings settings(configFile(), QSettings::IniFormat); - return settings.value(chunkSizeC(), 10 * 1000 * 1000).toLongLong(); // default to 10 MB -} - -qint64 ConfigFile::maxChunkSize() const -{ - QSettings settings(configFile(), QSettings::IniFormat); - return settings.value(maxChunkSizeC(), 100 * 1000 * 1000).toLongLong(); // default to 100 MB -} - -qint64 ConfigFile::minChunkSize() const -{ - QSettings settings(configFile(), QSettings::IniFormat); - return settings.value(minChunkSizeC(), 1000 * 1000).toLongLong(); // default to 1 MB -} - -chrono::milliseconds ConfigFile::targetChunkUploadDuration() const -{ - QSettings settings(configFile(), QSettings::IniFormat); - return millisecondsValue(settings, targetChunkUploadDurationC(), chrono::minutes(1)); -} - void ConfigFile::setOptionalDesktopNotifications(bool show) { - QSettings settings(configFile(), QSettings::IniFormat); - settings.setValue(optionalDesktopNoficationsC(), show); + auto settings = makeQSettings(); + settings.setValue(optionalDesktopNotificationsC(), show); settings.sync(); } void ConfigFile::saveGeometry(QWidget *w) { -#ifndef TOKEN_AUTH_ONLY OC_ASSERT(!w->objectName().isNull()); - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(w->objectName()); settings.setValue(geometryC(), w->saveGeometry()); settings.sync(); -#endif } void ConfigFile::restoreGeometry(QWidget *w) { -#ifndef TOKEN_AUTH_ONLY w->restoreGeometry(getValue(geometryC(), w->objectName()).toByteArray()); -#endif } void ConfigFile::saveGeometryHeader(QHeaderView *header) { -#ifndef TOKEN_AUTH_ONLY if (!header) return; OC_ASSERT(!header->objectName().isEmpty()); - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(header->objectName()); settings.setValue(geometryC(), header->saveState()); settings.sync(); -#endif } -void ConfigFile::restoreGeometryHeader(QHeaderView *header) +bool ConfigFile::restoreGeometryHeader(QHeaderView *header) { -#ifndef TOKEN_AUTH_ONLY - if (!header) - return; - OC_ASSERT(!header->objectName().isNull()); + Q_ASSERT(header && !header->objectName().isNull()); - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(header->objectName()); - header->restoreState(settings.value(geometryC()).toByteArray()); -#endif -} - -QVariant ConfigFile::getPolicySetting(const QString &setting, const QVariant &defaultValue) const -{ - if (Utility::isWindows()) { - // check for policies first and return immediately if a value is found. - QSettings userPolicy(QStringLiteral("HKEY_CURRENT_USER\\Software\\Policies\\%1\\%2") - .arg(QStringLiteral(APPLICATION_VENDOR), Theme::instance()->appNameGUI()), - QSettings::NativeFormat); - if (userPolicy.contains(setting)) { - return userPolicy.value(setting); - } - - QSettings machinePolicy(QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\Policies\\%1\\%2") - .arg(QStringLiteral(APPLICATION_VENDOR), Theme::instance()->appNameGUI()), - QSettings::NativeFormat); - if (machinePolicy.contains(setting)) { - return machinePolicy.value(setting); - } + if (settings.contains(geometryC())) { + header->restoreState(settings.value(geometryC()).toByteArray()); + return true; } - return defaultValue; + return false; } QString ConfigFile::configPath() @@ -276,12 +220,10 @@ QString ConfigFile::configPath() return dir; } -static const QLatin1String exclFile("sync-exclude.lst"); - QString ConfigFile::excludeFile(Scope scope) const { #ifdef Q_OS_WIN - Utility::NtfsPermissionLookupRAII ntfs_perm; + QNtfsPermissionCheckGuard ntfs_perm; #endif // prefer sync-exclude.lst, but if it does not exist, check for // exclude.lst for compatibility reasons in the user writeable @@ -290,13 +232,13 @@ QString ConfigFile::excludeFile(Scope scope) const switch (scope) { case UserScope: - fi.setFile(configPath(), exclFile); + fi.setFile(configPath(), excludeFileNameC()); if (!fi.isReadable()) { fi.setFile(configPath(), QStringLiteral("exclude.lst")); } if (!fi.isReadable()) { - fi.setFile(configPath(), exclFile); + fi.setFile(configPath(), excludeFileNameC()); } return fi.absoluteFilePath(); case SystemScope: @@ -304,52 +246,18 @@ QString ConfigFile::excludeFile(Scope scope) const } OC_ASSERT(false); - return QString(); + return {}; } QString ConfigFile::excludeFileFromSystem() { - QFileInfo fi; -#ifdef Q_OS_WIN - fi.setFile(QCoreApplication::applicationDirPath(), exclFile); -#endif -#ifdef Q_OS_UNIX - fi.setFile(QStringLiteral(SYSCONFDIR "/%1").arg(Theme::instance()->appName()), exclFile); - if (!fi.exists()) { - // Prefer to return the preferred path! Only use the fallback location - // if the other path does not exist and the fallback is valid. - QFileInfo nextToBinary(QCoreApplication::applicationDirPath(), exclFile); - if (nextToBinary.exists()) { - fi = nextToBinary; - } else { - // For AppImage, the file might reside under a temporary mount path - QDir d(QCoreApplication::applicationDirPath()); // supposed to be /tmp/mount.xyz/usr/bin - d.cdUp(); // go out of bin - d.cdUp(); // go out of usr - if (!d.isRoot()) { // it is really a mountpoint - if (d.cd(QStringLiteral("etc")) && d.cd(Theme::instance()->appName())) { - QFileInfo inMountDir(d, exclFile); - if (inMountDir.exists()) { - fi = inMountDir; - } - }; - } - } - } -#endif -#ifdef Q_OS_MAC - // exec path is inside the bundle - fi.setFile(QCoreApplication::applicationDirPath(), - QLatin1String("../Resources/") + exclFile); -#endif - - return fi.absoluteFilePath(); + return ":/libsync/sync-exclude.lst"; } QString ConfigFile::backup() const { QString baseFile = configFile(); - auto versionString = clientVersionString(); + auto versionString = clientVersionWithBuildNumberString(); if (!versionString.isEmpty()) versionString.prepend(QLatin1Char('_')); const QString backupFile = @@ -371,6 +279,11 @@ QString ConfigFile::configFile() return configPath() + Theme::instance()->configFileName(); } +QSettings ConfigFile::makeQSettings() +{ + return { configFile(), QSettings::IniFormat }; +} + bool ConfigFile::exists() { return QFileInfo::exists(configFile()); @@ -384,7 +297,7 @@ QString ConfigFile::defaultConnection() const void ConfigFile::storeData(const QString &group, const QString &key, const QVariant &value) { const QString con(group.isEmpty() ? defaultConnection() : group); - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(con); settings.setValue(key, value); @@ -394,7 +307,7 @@ void ConfigFile::storeData(const QString &group, const QString &key, const QVari void ConfigFile::removeData(const QString &group, const QString &key) { const QString con(group.isEmpty() ? defaultConnection() : group); - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(con); settings.remove(key); @@ -403,7 +316,7 @@ void ConfigFile::removeData(const QString &group, const QString &key) bool ConfigFile::dataExists(const QString &group, const QString &key) const { const QString con(group.isEmpty() ? defaultConnection() : group); - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(con); return settings.contains(key); @@ -415,14 +328,16 @@ chrono::milliseconds ConfigFile::remotePollInterval(std::chrono::seconds default if (connection.isEmpty()) con = defaultConnection(); - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(con); auto defaultPollInterval { DefaultRemotePollInterval }; - // The server default-capabilities is set to 60, which is, if interpreted in milliseconds, - // pretty small. If the value is above 5 seconds, it was set intentionally. + // The server default-capabilities was set to 60 in some server releases, + // which, if interpreted in milliseconds, is pretty small. + // If the value is above 5 seconds, it was set intentionally. // Server admins have to set the value in Milliseconds! + // i.e. set to greater than 5000 milliseconds on the server to be effective. if (defaultVal > chrono::seconds(5)) { defaultPollInterval = defaultVal; } @@ -444,7 +359,7 @@ void ConfigFile::setRemotePollInterval(chrono::milliseconds interval, const QStr qCWarning(lcConfigFile) << "Remote Poll interval of " << interval.count() << " is below five seconds."; return; } - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(con); settings.setValue(remotePollIntervalC(), qlonglong(interval.count())); settings.sync(); @@ -457,7 +372,7 @@ chrono::milliseconds ConfigFile::forceSyncInterval(std::chrono::seconds remoteFr QString con(connection); if (connection.isEmpty()) con = defaultConnection(); - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(con); auto defaultInterval = chrono::hours(2); @@ -471,26 +386,9 @@ chrono::milliseconds ConfigFile::forceSyncInterval(std::chrono::seconds remoteFr chrono::milliseconds OCC::ConfigFile::fullLocalDiscoveryInterval() const { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(defaultConnection()); - return millisecondsValue(settings, fullLocalDiscoveryIntervalC(), chrono::hours(1)); -} - -chrono::milliseconds ConfigFile::notificationRefreshInterval(const QString &connection) const -{ - QString con(connection); - if (connection.isEmpty()) - con = defaultConnection(); - QSettings settings(configFile(), QSettings::IniFormat); - settings.beginGroup(con); - - auto defaultInterval = chrono::minutes(5); - auto interval = millisecondsValue(settings, notificationRefreshIntervalC(), defaultInterval); - if (interval < chrono::minutes(1)) { - qCWarning(lcConfigFile) << "Notification refresh interval smaller than one minute, setting to one minute"; - interval = chrono::minutes(1); - } - return interval; + return millisecondsValue(settings, fullLocalDiscoveryIntervalC(), 1h); } chrono::milliseconds ConfigFile::updateCheckInterval(const QString &connection) const @@ -498,7 +396,7 @@ chrono::milliseconds ConfigFile::updateCheckInterval(const QString &connection) QString con(connection); if (connection.isEmpty()) con = defaultConnection(); - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.beginGroup(con); auto defaultInterval = chrono::hours(10); @@ -512,73 +410,21 @@ chrono::milliseconds ConfigFile::updateCheckInterval(const QString &connection) return interval; } -bool ConfigFile::skipUpdateCheck(const QString &connection) const -{ - QString con(connection); - if (connection.isEmpty()) - con = defaultConnection(); - - QVariant fallback = getValue(skipUpdateCheckC(), con, false); - fallback = getValue(skipUpdateCheckC(), QString(), fallback); - - QVariant value = getPolicySetting(skipUpdateCheckC(), fallback); - return value.toBool(); -} - -void ConfigFile::setSkipUpdateCheck(bool skip, const QString &connection) -{ - QString con(connection); - if (connection.isEmpty()) - con = defaultConnection(); - - QSettings settings(configFile(), QSettings::IniFormat); - settings.beginGroup(con); - - settings.setValue(skipUpdateCheckC(), QVariant(skip)); - settings.sync(); -} - -QString ConfigFile::updateChannel() const -{ - QString defaultUpdateChannel = QStringLiteral("stable"); - const QString suffix = OCC::Version::suffix(); - if (suffix.startsWith(QLatin1String("daily")) - || suffix.startsWith(QLatin1String("nightly")) - || suffix.startsWith(QLatin1String("alpha")) - || suffix.startsWith(QLatin1String("rc")) - || suffix.startsWith(QLatin1String("beta"))) { - defaultUpdateChannel = QStringLiteral("beta"); - } - - QSettings settings(configFile(), QSettings::IniFormat); - return settings.value(updateChannelC(), defaultUpdateChannel).toString(); -} - -void ConfigFile::setUpdateChannel(const QString &channel) -{ - QSettings settings(configFile(), QSettings::IniFormat); - settings.setValue(updateChannelC(), channel); -} - QString ConfigFile::uiLanguage() const { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); return settings.value(uiLanguageC(), QString()).toString(); } void ConfigFile::setUiLanguage(const QString &uiLanguage) { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.setValue(uiLanguageC(), uiLanguage); } -void ConfigFile::setProxyType(int proxyType, - const QString &host, - int port, bool needsAuth, - const QString &user, - const QString &pass) +void ConfigFile::setProxyType(QNetworkProxy::ProxyType proxyType, const QString &host, int port, bool needsAuth, const QString &user) { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.setValue(proxyTypeC(), proxyType); @@ -587,7 +433,6 @@ void ConfigFile::setProxyType(int proxyType, settings.setValue(proxyPortC(), port); settings.setValue(proxyNeedsAuthC(), needsAuth); settings.setValue(proxyUserC(), user); - settings.setValue(proxyPassC(), pass.toUtf8().toBase64()); } settings.sync(); } @@ -595,39 +440,16 @@ void ConfigFile::setProxyType(int proxyType, QVariant ConfigFile::getValue(const QString ¶m, const QString &group, const QVariant &defaultValue) const { - QVariant systemSetting; - if (Utility::isMac()) { - QSettings systemSettings(QStringLiteral("/Library/Preferences/" APPLICATION_REV_DOMAIN ".plist"), QSettings::NativeFormat); - if (!group.isEmpty()) { - systemSettings.beginGroup(group); - } - systemSetting = systemSettings.value(param, defaultValue); - } else if (Utility::isUnix()) { - QSettings systemSettings(QStringLiteral(SYSCONFDIR "/%1/%1.conf").arg(Theme::instance()->appName()), QSettings::NativeFormat); - if (!group.isEmpty()) { - systemSettings.beginGroup(group); - } - systemSetting = systemSettings.value(param, defaultValue); - } else { // Windows - QSettings systemSettings(QStringLiteral("HKEY_LOCAL_MACHINE\\Software\\" APPLICATION_VENDOR "\\%1") - .arg(Theme::instance()->appNameGUI()), - QSettings::NativeFormat); - if (!group.isEmpty()) { - systemSettings.beginGroup(group); - } - systemSetting = systemSettings.value(param, defaultValue); - } - - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); if (!group.isEmpty()) settings.beginGroup(group); - return settings.value(param, systemSetting); + return settings.value(param, defaultValue); } void ConfigFile::setValue(const QString &key, const QVariant &value) { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.setValue(key, value); } @@ -660,167 +482,93 @@ QString ConfigFile::proxyUser() const return getValue(proxyUserC()).toString(); } -QString ConfigFile::proxyPassword() const -{ - QByteArray pass = getValue(proxyPassC()).toByteArray(); - return QString::fromUtf8(QByteArray::fromBase64(pass)); -} - -int ConfigFile::useUploadLimit() const -{ - return getValue(useUploadLimitC(), QString(), 0).toInt(); -} - -int ConfigFile::useDownloadLimit() const -{ - return getValue(useDownloadLimitC(), QString(), 0).toInt(); -} - -void ConfigFile::setUseUploadLimit(int val) +bool ConfigFile::pauseSyncWhenMetered() const { - setValue(useUploadLimitC(), val); + return getValue(pauseSyncWhenMeteredC(), {}, false).toBool(); } -void ConfigFile::setUseDownloadLimit(int val) +void ConfigFile::setPauseSyncWhenMetered(bool isChecked) { - setValue(useDownloadLimitC(), val); -} - -int ConfigFile::uploadLimit() const -{ - return getValue(uploadLimitC(), QString(), 10).toInt(); -} - -int ConfigFile::downloadLimit() const -{ - return getValue(downloadLimitC(), QString(), 80).toInt(); -} - -void ConfigFile::setUploadLimit(int kbytes) -{ - setValue(uploadLimitC(), kbytes); -} - -void ConfigFile::setDownloadLimit(int kbytes) -{ - setValue(downloadLimitC(), kbytes); -} - -QPair ConfigFile::newBigFolderSizeLimit() const -{ - auto defaultValue = Theme::instance()->newBigFolderSizeLimit(); - qint64 value = getValue(newBigFolderSizeLimitC(), QString(), defaultValue).toLongLong(); - bool use = value >= 0 && getValue(useNewBigFolderSizeLimitC(), QString(), true).toBool(); - return qMakePair(use, qMax(0, value)); -} - -void ConfigFile::setNewBigFolderSizeLimit(bool isChecked, qint64 mbytes) -{ - setValue(newBigFolderSizeLimitC(), mbytes); - setValue(useNewBigFolderSizeLimitC(), isChecked); -} - -bool ConfigFile::confirmExternalStorage() const -{ - return getValue(confirmExternalStorageC(), QString(), true).toBool(); -} - -void ConfigFile::setConfirmExternalStorage(bool isChecked) -{ - setValue(confirmExternalStorageC(), isChecked); -} - -bool ConfigFile::moveToTrash() const -{ - return getValue(moveToTrashC(), QString(), false).toBool(); -} - -void ConfigFile::setMoveToTrash(bool isChecked) -{ - setValue(moveToTrashC(), isChecked); + setValue(pauseSyncWhenMeteredC(), isChecked); } bool ConfigFile::promptDeleteFiles() const { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); return settings.value(promptDeleteC(), true).toBool(); } void ConfigFile::setPromptDeleteFiles(bool promptDeleteFiles) { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.setValue(promptDeleteC(), promptDeleteFiles); } bool ConfigFile::monoIcons() const { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); bool monoDefault = false; // On Mac we want bw by default #ifdef Q_OS_MAC // OEM themes are not obliged to ship mono icons - monoDefault = (0 == (strcmp("ownCloud", APPLICATION_NAME))); + monoDefault = Theme::instance()->appNameGUI() == QStringLiteral("ownCloud"); #endif return settings.value(monoIconsC(), monoDefault).toBool(); } void ConfigFile::setMonoIcons(bool useMonoIcons) { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.setValue(monoIconsC(), useMonoIcons); } bool ConfigFile::crashReporter() const { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); return settings.value(crashReporterC(), true).toBool(); } void ConfigFile::setCrashReporter(bool enabled) { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.setValue(crashReporterC(), enabled); } bool ConfigFile::automaticLogDir() const { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); return settings.value(automaticLogDirC(), false).toBool(); } void ConfigFile::setAutomaticLogDir(bool enabled) { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.setValue(automaticLogDirC(), enabled); } -Optional ConfigFile::automaticDeleteOldLogsAge() const +int ConfigFile::automaticDeleteOldLogs() const { - QSettings settings(configFile(), QSettings::IniFormat); - auto value = settings.value(deleteOldLogsAfterHoursC()); - if (!value.isValid()) - return chrono::hours(4); - auto hours = value.toInt(); - if (hours <= 0) - return {}; - return chrono::hours(hours); + auto settings = makeQSettings(); + return settings.value(numberOfLogsToKeepC()).toInt(); } -void ConfigFile::setAutomaticDeleteOldLogsAge(Optional expireTime) +void ConfigFile::setAutomaticDeleteOldLogs(int number) { - QSettings settings(configFile(), QSettings::IniFormat); - if (!expireTime) { - settings.setValue(deleteOldLogsAfterHoursC(), -1); - } else { - settings.setValue(deleteOldLogsAfterHoursC(), QVariant::fromValue(expireTime->count())); - } + auto settings = makeQSettings(); + settings.setValue(numberOfLogsToKeepC(), number); } -void ConfigFile::setLogHttp(bool b) +void ConfigFile::configureHttpLogging(std::optional enable) { - QSettings settings(configFile(), QSettings::IniFormat); - settings.setValue(logHttpC(), b); - const QSet rule = { QStringLiteral("sync.httplogger=true") }; - if (b) { + if (enable == std::nullopt) { + enable = logHttp(); + } + + auto settings = makeQSettings(); + settings.setValue(logHttpC(), enable.value()); + + static const QSet rule = { QStringLiteral("sync.httplogger=true") }; + + if (enable.value()) { Logger::instance()->addLogRule(rule); } else { Logger::instance()->removeLogRule(rule); @@ -829,31 +577,26 @@ void ConfigFile::setLogHttp(bool b) bool ConfigFile::logHttp() const { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); return settings.value(logHttpC(), false).toBool(); } -bool ConfigFile::showExperimentalOptions() const -{ - QSettings settings(configFile(), QSettings::IniFormat); - return settings.value(showExperimentalOptionsC(), false).toBool(); -} - -QString ConfigFile::clientVersionString() const +QString ConfigFile::clientVersionWithBuildNumberString() const { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); return settings.value(clientVersionC(), QString()).toString(); } -void ConfigFile::setClientVersionString(const QString &version) +void ConfigFile::setClientVersionWithBuildNumberString(const QString &version) { - QSettings settings(configFile(), QSettings::IniFormat); + auto settings = makeQSettings(); settings.setValue(clientVersionC(), version); } std::unique_ptr ConfigFile::settingsWithGroup(const QString &group) { - auto settings = std::make_unique(ConfigFile::configFile(), QSettings::IniFormat); + // this actually works by move magic + std::unique_ptr settings(new QSettings(makeQSettings())); settings->beginGroup(group); return settings; } diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h index 1f64a90680b..92081d13230 100644 --- a/src/libsync/configfile.h +++ b/src/libsync/configfile.h @@ -12,17 +12,19 @@ * for more details. */ -#ifndef CONFIGFILE_H -#define CONFIGFILE_H +#pragma once #include "owncloudlib.h" -#include -#include + +#include #include +#include #include #include + #include -#include "common/result.h" +#include +#include class QWidget; class QHeaderView; @@ -41,12 +43,15 @@ class OWNCLOUDSYNC_EXPORT ConfigFile public: static QString configPath(); static QString configFile(); + static QSettings makeQSettings(); static bool exists(); ConfigFile(); - enum Scope { UserScope, - SystemScope }; + enum Scope { + UserScope, + SystemScope + }; QString excludeFile(Scope scope) const; static QString excludeFileFromSystem(); // doesn't access config dir @@ -61,16 +66,11 @@ class OWNCLOUDSYNC_EXPORT ConfigFile QString defaultConnection() const; - bool passwordStorageAllowed(const QString &connection = QString()); - /* Server poll interval in milliseconds */ std::chrono::milliseconds remotePollInterval(std::chrono::seconds defaultVal, const QString &connection = QString()) const; /* Set poll interval. Value in milliseconds has to be larger than 5000 */ void setRemotePollInterval(std::chrono::milliseconds interval, const QString &connection = QString()); - /* Interval to check for new notifications */ - std::chrono::milliseconds notificationRefreshInterval(const QString &connection = QString()) const; - /* Force sync interval, in milliseconds */ std::chrono::milliseconds forceSyncInterval(std::chrono::seconds remoteFromCapabilities, const QString &connection = QString()) const; @@ -97,64 +97,42 @@ class OWNCLOUDSYNC_EXPORT ConfigFile bool automaticLogDir() const; void setAutomaticLogDir(bool enabled); - /** Wheter the automaticLogDir should expire logs, and after how many hours */ - Optional automaticDeleteOldLogsAge() const; - void setAutomaticDeleteOldLogsAge(Optional expireTime); + /** Number of log files to keep */ + int automaticDeleteOldLogs() const; + void setAutomaticDeleteOldLogs(int number); /** Whether to log http traffic */ - void setLogHttp(bool b); bool logHttp() const; - // Whether experimental UI options should be shown - bool showExperimentalOptions() const; + /** + * Set up HTTP logging. + * This method should be called during application startup to make sure no messages are missed. + */ + void configureHttpLogging(std::optional enable = std::nullopt); // proxy settings - void setProxyType(int proxyType, - const QString &host = QString(), - int port = 0, bool needsAuth = false, - const QString &user = QString(), - const QString &pass = QString()); + void setProxyType( + QNetworkProxy::ProxyType proxyType, const QString &host = QString(), int port = 0, bool needsAuth = false, const QString &user = QString()); int proxyType() const; QString proxyHostName() const; int proxyPort() const; bool proxyNeedsAuth() const; QString proxyUser() const; - QString proxyPassword() const; - - /** 0: no limit, 1: manual, >0: automatic */ - int useUploadLimit() const; - int useDownloadLimit() const; - void setUseUploadLimit(int); - void setUseDownloadLimit(int); - /** in kbyte/s */ - int uploadLimit() const; - int downloadLimit() const; - void setUploadLimit(int kbytes); - void setDownloadLimit(int kbytes); - /** [checked, size in MB] **/ - QPair newBigFolderSizeLimit() const; - void setNewBigFolderSizeLimit(bool isChecked, qint64 mbytes); - bool confirmExternalStorage() const; - void setConfirmExternalStorage(bool); - - /** If we should move the files deleted on the server in the trash */ - bool moveToTrash() const; - void setMoveToTrash(bool); + bool pauseSyncWhenMetered() const; + void setPauseSyncWhenMetered(bool isChecked); + + /// Used for testing, so we do not change the user's config file. static bool setConfDir(const QString &value); bool optionalDesktopNotifications() const; void setOptionalDesktopNotifications(bool show); - bool showInExplorerNavigationPane() const; - void setShowInExplorerNavigationPane(bool show); + std::optional issuesWidgetFilter() const; + void setIssuesWidgetFilter(const QStringList &checked); std::chrono::seconds timeout() const; - qint64 chunkSize() const; - qint64 maxChunkSize() const; - qint64 minChunkSize() const; - std::chrono::milliseconds targetChunkUploadDuration() const; void saveGeometry(QWidget *w); void restoreGeometry(QWidget *w); @@ -162,22 +140,16 @@ class OWNCLOUDSYNC_EXPORT ConfigFile // how often the check about new versions runs std::chrono::milliseconds updateCheckInterval(const QString &connection = QString()) const; - bool skipUpdateCheck(const QString &connection = QString()) const; - void setSkipUpdateCheck(bool, const QString &); - - QString updateChannel() const; - void setUpdateChannel(const QString &channel); - QString uiLanguage() const; void setUiLanguage(const QString &uiLanguage); void saveGeometryHeader(QHeaderView *header); - void restoreGeometryHeader(QHeaderView *header); + bool restoreGeometryHeader(QHeaderView *header); /** The client version that last used this settings file. Updated by configVersionMigration() at client startup. */ - QString clientVersionString() const; - void setClientVersionString(const QString &version); + QString clientVersionWithBuildNumberString() const; + void setClientVersionWithBuildNumberString(const QString &version); /** Returns a new settings pre-set in a specific group. */ static std::unique_ptr settingsWithGroup(const QString &group); @@ -186,7 +158,6 @@ class OWNCLOUDSYNC_EXPORT ConfigFile static void setupDefaultExcludeFilePaths(ExcludedFiles &excludedFiles); protected: - QVariant getPolicySetting(const QString &policy, const QVariant &defaultValue = QVariant()) const; void storeData(const QString &group, const QString &key, const QVariant &value); void removeData(const QString &group, const QString &key); bool dataExists(const QString &group, const QString &key) const; @@ -203,4 +174,3 @@ class OWNCLOUDSYNC_EXPORT ConfigFile static QString _confDir; }; } -#endif // CONFIGFILE_H diff --git a/src/libsync/cookiejar.cpp b/src/libsync/cookiejar.cpp index 75535d87696..b9f24f30236 100644 --- a/src/libsync/cookiejar.cpp +++ b/src/libsync/cookiejar.cpp @@ -14,59 +14,15 @@ #include "cookiejar.h" -#include "configfile.h" +// todo: #31 -#include -#include #include #include -#include -#include namespace OCC { Q_LOGGING_CATEGORY(lcCookieJar, "sync.cookiejar", QtInfoMsg) -namespace { - const unsigned int JAR_VERSION = 23; -} - -QDataStream &operator<<(QDataStream &stream, const QList &list) -{ - stream << JAR_VERSION; - stream << quint32(list.size()); - for (int i = 0; i < list.size(); ++i) - stream << list.at(i).toRawForm(); - return stream; -} - -QDataStream &operator>>(QDataStream &stream, QList &list) -{ - list.clear(); - - quint32 version; - stream >> version; - - if (version != JAR_VERSION) - return stream; - - quint32 count; - stream >> count; - for (quint32 i = 0; i < count; ++i) { - QByteArray value; - stream >> value; - QList newCookies = QNetworkCookie::parseCookies(value); - if (newCookies.count() == 0 && value.length() != 0) { - qCWarning(lcCookieJar) << "CookieJar: Unable to parse saved cookie:" << value; - } - for (int j = 0; j < newCookies.count(); ++j) - list.append(newCookies.at(j)); - if (stream.atEnd()) - break; - } - return stream; -} - CookieJar::CookieJar(QObject *parent) : QNetworkCookieJar(parent) { @@ -76,78 +32,11 @@ CookieJar::~CookieJar() { } -bool CookieJar::setCookiesFromUrl(const QList &cookieList, const QUrl &url) -{ - if (QNetworkCookieJar::setCookiesFromUrl(cookieList, url)) { - Q_EMIT newCookiesForUrl(cookieList, url); - return true; - } - - return false; -} - -QList CookieJar::cookiesForUrl(const QUrl &url) const -{ - QList cookies = QNetworkCookieJar::cookiesForUrl(url); - qCDebug(lcCookieJar) << url << "requests:" << cookies; - return cookies; -} - -void CookieJar::clearSessionCookies() -{ - setAllCookies(removeExpired(allCookies())); -} - -bool CookieJar::save(const QString &fileName) -{ - const QFileInfo info(fileName); - if (!info.dir().exists()) - { - info.dir().mkpath(QStringLiteral(".")); - } - - qCDebug(lcCookieJar) << fileName; - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) - { - return false; - } - QDataStream stream(&file); - stream << removeExpired(allCookies()); - file.close(); - return true; -} - -bool CookieJar::restore(const QString &fileName) -{ - const QFileInfo info(fileName); - if (!info.exists()) - { - return false; - } - - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) - { - return false; - } - QDataStream stream(&file); - QList list; - stream >> list; - setAllCookies(removeExpired(list)); - file.close(); - return true; -} - -QList CookieJar::removeExpired(const QList &cookies) +CookieJar *CookieJar::clone(QObject *parent) { - QList updatedList; - for (const auto &cookie : cookies) { - if (cookie.expirationDate() > QDateTime::currentDateTimeUtc() && !cookie.isSessionCookie()) { - updatedList << cookie; - } - } - return updatedList; + auto newJar = new CookieJar(parent); + newJar->setAllCookies(allCookies()); + return newJar; } } // namespace OCC diff --git a/src/libsync/cookiejar.h b/src/libsync/cookiejar.h index 6d3703aaa1f..b1706333879 100644 --- a/src/libsync/cookiejar.h +++ b/src/libsync/cookiejar.h @@ -22,7 +22,7 @@ namespace OCC { /** - * @brief The CookieJar class + * @brief A clonable cookie jar. This can be used when we don't want to spoil the original cookie jar. * @ingroup libsync */ class OWNCLOUDSYNC_EXPORT CookieJar : public QNetworkCookieJar @@ -31,22 +31,11 @@ class OWNCLOUDSYNC_EXPORT CookieJar : public QNetworkCookieJar public: explicit CookieJar(QObject *parent = nullptr); ~CookieJar() override; - bool setCookiesFromUrl(const QList &cookieList, const QUrl &url) override; - QList cookiesForUrl(const QUrl &url) const override; - void clearSessionCookies(); - - using QNetworkCookieJar::setAllCookies; - using QNetworkCookieJar::allCookies; - - bool save(const QString &fileName); - bool restore(const QString &fileName); - -signals: - void newCookiesForUrl(const QList &cookieList, const QUrl &url); - -private: - QList removeExpired(const QList &cookies); + /** + * Return a clone of this cookie jar, with a copy of all cookies. + */ + CookieJar *clone(QObject *parent = nullptr); }; } // namespace OCC diff --git a/src/libsync/creds/abstractcredentials.cpp b/src/libsync/creds/abstractcredentials.cpp index cc82beb9698..eb6f877d977 100644 --- a/src/libsync/creds/abstractcredentials.cpp +++ b/src/libsync/creds/abstractcredentials.cpp @@ -13,8 +13,6 @@ */ #include -#include -#include #include "account.h" #include "common/asserts.h" @@ -24,16 +22,12 @@ namespace OCC { Q_LOGGING_CATEGORY(lcCredentials, "sync.credentials", QtInfoMsg) -AbstractCredentials::AbstractCredentials() - : _account(nullptr) - , _wasFetched(false) +AbstractCredentials::AbstractCredentials(Account *account, QObject *parent) + : QObject(parent) + , _account(account) + , _wasEverFetched(false) { } -void AbstractCredentials::setAccount(Account *account) -{ - OC_ENFORCE_X(!_account, "should only setAccount once"); - _account = account; -} } // namespace OCC diff --git a/src/libsync/creds/abstractcredentials.h b/src/libsync/creds/abstractcredentials.h index 8326f5d698a..4cbbe55c22c 100644 --- a/src/libsync/creds/abstractcredentials.h +++ b/src/libsync/creds/abstractcredentials.h @@ -12,60 +12,57 @@ * for more details. */ -#ifndef MIRALL_CREDS_ABSTRACT_CREDENTIALS_H -#define MIRALL_CREDS_ABSTRACT_CREDENTIALS_H +#pragma once #include +#include -#include +#include "accessmanager.h" +#include "account.h" #include "owncloudlib.h" -#include "accountfwd.h" +#include class QNetworkAccessManager; class QNetworkReply; namespace OCC { class AbstractNetworkJob; +class AccountModalWidget; class OWNCLOUDSYNC_EXPORT AbstractCredentials : public QObject { Q_OBJECT public: - AbstractCredentials(); + // note the account is also the parent and effective owner of the creds!!! + AbstractCredentials(Account *account, QObject *parent); // No need for virtual destructor - QObject already has one. - /** The bound account for the credentials instance. - * - * Credentials are always used in conjunction with an account. - * Calling Account::setCredentials() will call this function. - * Credentials only live as long as the underlying account object. - */ - virtual void setAccount(Account *account); - - virtual QString authType() const = 0; - virtual QString user() const = 0; - virtual QNetworkAccessManager *createQNAM() const = 0; + virtual AccessManager *createAccessManager() const = 0; /** Whether there are credentials that can be used for a connection attempt. */ virtual bool ready() const = 0; - /** Whether fetchFromKeychain() was called before. */ - bool wasFetched() const { return _wasFetched; } + /** Whether fetchFromKeychain() was ever called before. */ + // todo: DC-128 - evaluate the need for this. it's weird. + bool wasEverFetched() const { return _wasEverFetched; } /** Trigger (async) fetching of credential information * - * Should set _wasFetched = true, and later emit fetched() when done. + * Should set _wasEverFetched = true, update the ready() state, and fetched() is emitted */ virtual void fetchFromKeychain() = 0; /** Ask credentials from the user (typically async) * - * Should emit asked() when done. + * Should Q_EMIT asked() when done. */ virtual void askFromUser() = 0; + // todo: this is an unholy impl that is called from all sorts of locations, the most concerning of which is the + // abstractnetworkjob virtual bool stillValid(QNetworkReply *reply) = 0; + virtual void persist() = 0; /** Invalidates token used to authorize requests, it will no longer be used. @@ -87,6 +84,13 @@ class OWNCLOUDSYNC_EXPORT AbstractCredentials : public QObject */ virtual void forgetSensitiveData() = 0; + /* If we don't have a valid refresh token, return false. + * otherwise: + * if the refresh routine is running, return true + * if it is not running, start the refresh and return true. + */ + virtual bool refreshAccessToken() = 0; + Q_SIGNALS: /** Emitted when fetchFromKeychain() is done. * @@ -96,21 +100,30 @@ class OWNCLOUDSYNC_EXPORT AbstractCredentials : public QObject // TODO: rename void fetched(); - /** Emitted when askFromUser() is done. - * - * Note that ready() can be true or false, depending on whether the user provided - * data or not. - */ - void asked(); - void authenticationStarted(); void authenticationFailed(); + /* + * Request to log out. + * The connected account should be marked as logged out + * and no automatic tries to connect should be made. + */ + void requestLogout(); + + // this is a passthrough to connect to the concrete Credentials RequestAuthenticationController requestAccountModal signal, + // which provides the login widget wrapped in an account modal widget. + // using a signal to run the auth widget removes hard dependency on mainwindow/accounts gui to somehow provide access to account view publicly. + // this is only invoked via the Credentials sublcass. Reminder that this abstraction only exists to support FakeCredentials impl in the tests + // which is questionable, but what we have at the moment. + void requestAccountModal(OCC::AccountModalWidget *widget); + protected: - Account *_account; - bool _wasFetched; + // the account should be the parent of the creds, so it should not go out of scope while we are using this. + // however, those may be famous last words depending on how the teardown happens so keep it in a weak ref + QPointer _account; + + // todo: DC-112 I don't understand why this is needed but will try to figure it out + bool _wasEverFetched; }; } // namespace OCC - -#endif diff --git a/src/libsync/creds/credentialmanager.cpp b/src/libsync/creds/credentialmanager.cpp index 8e055c6d782..7d21cf3bf78 100644 --- a/src/libsync/creds/credentialmanager.cpp +++ b/src/libsync/creds/credentialmanager.cpp @@ -5,6 +5,7 @@ #include "theme.h" #include "common/asserts.h" +#include "common/chronoelapsedtimer.h" #include #include @@ -16,68 +17,86 @@ using namespace std::chrono_literals; using namespace OCC; -Q_LOGGING_CATEGORY(lcCredentaislManager, "sync.credentials.manager", QtDebugMsg) +Q_LOGGING_CATEGORY(lcCredentialsManager, "sync.credentials.manager", QtDebugMsg) namespace { +constexpr auto tiemoutC = 5s; QString credentialKeyC() { - return QStringLiteral("%1_credentials").arg(Theme::instance()->appName()); + return QString("%1_credentials").arg(Theme::instance()->appName()); +} } -QString accountKey(const Account *acc) +CredentialManager::CredentialManager(Account *acc) + : QObject(acc) + , _account(acc) { - OC_ASSERT(!acc->url().isEmpty()); - return QStringLiteral("%1:%2:%3").arg(credentialKeyC(), acc->url().host(), acc->uuid().toString(QUuid::WithoutBraces)); } -QString scope(const CredentialManager *const manager) +CredentialManager::CredentialManager(QObject *parent) + : QObject(parent) { - return manager->account() ? accountKey(manager->account()) : credentialKeyC(); } -QString scopedKey(const CredentialManager *const manager, const QString &key) +QString CredentialManager::scope() const { - return scope(manager) + QLatin1Char(':') + key; -} + return _account ? accountKey() : credentialKeyC(); } -CredentialManager::CredentialManager(Account *acc) - : QObject(acc) - , _account(acc) +QString CredentialManager::scopedKey(const QString &key) const { + return scope() + QLatin1Char(':') + key; } -CredentialManager::CredentialManager(QObject *parent) - : QObject(parent) +QString CredentialManager::accountKey() const { -} + if (!_account || _account->url().isEmpty()) + return QString(); + return QString("%1:%2:%3").arg(credentialKeyC(), _account->url().host(), _account->uuid().toString(QUuid::WithoutBraces)); +} CredentialJob *CredentialManager::get(const QString &key) { - qCInfo(lcCredentaislManager) << "get" << scopedKey(this, key); - auto out = new CredentialJob(this, key); + if (!contains(key)) { + qCWarning(lcCredentialsManager) << "We don't know" << key << "skipping retrieval from keychain"; + return nullptr; + } + const QString scopedKey = this->scopedKey(key); + qCInfo(lcCredentialsManager) << "get" << scopedKey; + auto out = new CredentialJob(this, scopedKey); out->start(); return out; } QKeychain::Job *CredentialManager::set(const QString &key, const QVariant &data) { - qCInfo(lcCredentaislManager) << "set" << scopedKey(this, key); + OC_ASSERT(!data.isNull()); + const QString scoped = scopedKey(key); + qCInfo(lcCredentialsManager) << "set" << scoped; auto writeJob = new QKeychain::WritePasswordJob(Theme::instance()->appName()); - writeJob->setKey(scopedKey(this, key)); - connect(writeJob, &QKeychain::WritePasswordJob::finished, this, [writeJob, key, this] { + writeJob->setKey(scoped); + + auto timer = new QTimer(writeJob); + timer->setInterval(tiemoutC); + + Utility::ChronoElapsedTimer elapsedTimer; + connect(timer, &QTimer::timeout, writeJob, + [writeJob, elapsedTimer] { qCWarning(lcCredentialsManager) << "set" << writeJob->key() << "has not yet finished." << elapsedTimer.duration(); }); + connect(writeJob, &QKeychain::WritePasswordJob::finished, this, [writeJob, key, elapsedTimer, this] { if (writeJob->error() == QKeychain::NoError) { - qCInfo(lcCredentaislManager) << "added" << scopedKey(this, key); + qCInfo(lcCredentialsManager) << "added" << writeJob->key() << "after" << elapsedTimer.duration(); // just a list, the values don't matter credentialsList().setValue(key, true); } else { - qCWarning(lcCredentaislManager) << "Failed to set:" << scopedKey(this, key) << writeJob->errorString(); + qCWarning(lcCredentialsManager) << "Failed to set:" << writeJob->key() << writeJob->errorString() << "after" << elapsedTimer.duration(); } }); writeJob->setBinaryData(QCborValue::fromVariant(data).toCbor()); // start is delayed so we can directly call it writeJob->start(); + timer->start(); + return writeJob; } @@ -86,15 +105,16 @@ QKeychain::Job *CredentialManager::remove(const QString &key) OC_ASSERT(contains(key)); // remove immediately to prevent double invocation by clear() credentialsList().remove(key); - qCInfo(lcCredentaislManager) << "del" << scopedKey(this, key); + const QString scoped = scopedKey(key); + qCInfo(lcCredentialsManager) << "del" << scoped; auto keychainJob = new QKeychain::DeletePasswordJob(Theme::instance()->appName()); - keychainJob->setKey(scopedKey(this, key)); - connect(keychainJob, &QKeychain::DeletePasswordJob::finished, this, [keychainJob, key, this] { + keychainJob->setKey(scoped); + connect(keychainJob, &QKeychain::DeletePasswordJob::finished, this, [keychainJob, scoped] { OC_ASSERT(keychainJob->error() != QKeychain::EntryNotFound); if (keychainJob->error() == QKeychain::NoError) { - qCInfo(lcCredentaislManager) << "removed" << scopedKey(this, key); + qCInfo(lcCredentialsManager) << "removed" << scoped; } else { - qCWarning(lcCredentaislManager) << "Failed to remove:" << scopedKey(this, key) << keychainJob->errorString(); + qCWarning(lcCredentialsManager) << "Failed to remove:" << scoped << keychainJob->errorString(); } }); // start is delayed so we can directly call it @@ -114,15 +134,14 @@ QVector> CredentialManager::clear(const QString &group) return out; } -const Account *CredentialManager::account() const -{ - return _account; -} - bool CredentialManager::contains(const QString &key) const { return credentialsList().contains(key); } +bool CredentialManager::isEmpty() +{ + return knownKeys().isEmpty(); +} QStringList CredentialManager::knownKeys(const QString &group) const { @@ -149,15 +168,14 @@ QSettings &CredentialManager::credentialsList() const { // delayed init as scope requires a fully inizialised acc if (!_credentialsList) { - _credentialsList = ConfigFile::settingsWithGroup(QStringLiteral("Credentials/") + scope(this)); + _credentialsList = ConfigFile::settingsWithGroup("Credentials/" + scope()); } return *_credentialsList; } -CredentialJob::CredentialJob(CredentialManager *parent, const QString &key) +CredentialJob::CredentialJob(CredentialManager *parent, const QString &scopedKey) : QObject(parent) - , _key(key) - , _parent(parent) + , _scopedKey(scopedKey) { connect(this, &CredentialJob::finished, this, &CredentialJob::deleteLater); } @@ -179,27 +197,19 @@ QKeychain::Error CredentialJob::error() const void CredentialJob::start() { - if (!_parent->contains(_key)) { - _error = QKeychain::EntryNotFound; - // QKeychain is started delayed, emit the signal delayed to make sure we are connected - QTimer::singleShot(0, this, &CredentialJob::finished); - return; - } - _job = new QKeychain::ReadPasswordJob(Theme::instance()->appName()); - _job->setKey(scopedKey(_parent, _key)); + _job->setKey(_scopedKey); connect(_job, &QKeychain::ReadPasswordJob::finished, this, [this] { #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) if (_retryOnKeyChainError && (_job->error() == QKeychain::NoBackendAvailable || _job->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(lcCredentaislManager) << "Backend unavailable (yet?) Retrying in a few seconds." << _job->errorString(); + qCInfo(lcCredentialsManager) << "Backend unavailable (yet?) Retrying in a few seconds." << _job->errorString(); QTimer::singleShot(10s, this, &CredentialJob::start); _retryOnKeyChainError = false; } #endif - OC_ASSERT(_job->error() != QKeychain::EntryNotFound); if (_job->error() == QKeychain::NoError) { QCborParserError error; const auto obj = QCborValue::fromCbor(_job->binaryData(), &error); @@ -211,7 +221,7 @@ void CredentialJob::start() _data = obj.toVariant(); OC_ASSERT(_data.isValid()); } else { - qCWarning(lcCredentaislManager) << "Failed to read client id" << _job->errorString(); + qCWarning(lcCredentialsManager) << "Failed to get password" << _scopedKey << _job->errorString(); _error = _job->error(); _errorString = _job->errorString(); } @@ -219,8 +229,3 @@ void CredentialJob::start() }); _job->start(); } - -QString CredentialJob::key() const -{ - return _key; -} diff --git a/src/libsync/creds/credentialmanager.h b/src/libsync/creds/credentialmanager.h index e9b8fa88d28..5c3f58e92da 100644 --- a/src/libsync/creds/credentialmanager.h +++ b/src/libsync/creds/credentialmanager.h @@ -5,7 +5,7 @@ #include "owncloudlib.h" -#include +#include #include @@ -17,7 +17,7 @@ class OWNCLOUDSYNC_EXPORT CredentialManager : public QObject { Q_OBJECT public: - // global credentials + // generic credentials - this is currently needed by the proxy network settings CredentialManager(QObject *parent); // account related credentials explicit CredentialManager(Account *acc); @@ -31,49 +31,56 @@ class OWNCLOUDSYNC_EXPORT CredentialManager : public QObject QVector> clear(const QString &group = {}); bool contains(const QString &key) const; - const Account *account() const; + + /** + * @brief isEmpty tests the manager to see if it has any keys + * @return true if the manager has no keys + */ + bool isEmpty(); + +protected: + QString scopedKey(const QString &key) const; + QString scope() const; + QString accountKey() const; private: QSettings &credentialsList() const; - - // TestCredentialManager QStringList knownKeys(const QString &group = {}) const; const Account *const _account = nullptr; mutable std::unique_ptr _credentialsList; - - friend class TestCredentialManager; }; class OWNCLOUDSYNC_EXPORT CredentialJob : public QObject { Q_OBJECT + public: - QString key() const; + CredentialJob(CredentialManager *parent, const QString &scopedKey); + void start(); QKeychain::Error error() const; - + // this is questionable. variants are implicitly shared so I don't see any value returning a ref const QVariant &data() const; - QString errorString() const; Q_SIGNALS: void finished(); private: - CredentialJob(CredentialManager *parent, const QString &key); - void start(); - QString _key; + QString _scopedKey; QVariant _data; QKeychain::Error _error = QKeychain::NoError; QString _errorString; - bool _retryOnKeyChainError = true; QKeychain::ReadPasswordJob *_job; + // mac and linux only! + bool _retryOnKeyChainError = true; - CredentialManager *const _parent; - - friend class CredentialManager; + // this is "required" to allow the test to get the actual underlying job which is passed to setFallbackEnabled, which is only implemented + // in TestCredentialManager. I do not believe tests should ever implement extra functionality that does not exist in the actual runtime, + // they should only rely on REAL functionality. + // this absolutely breaks that rule and has to be fixed. friend class TestCredentialManager; }; diff --git a/src/libsync/creds/credentialscommon.cpp b/src/libsync/creds/credentialscommon.cpp deleted file mode 100644 index b56b8193dee..00000000000 --- a/src/libsync/creds/credentialscommon.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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 -#include -#include -#include -#include - -#include "creds/credentialscommon.h" - -#include "common/utility.h" -#include "account.h" -#include "syncengine.h" - -namespace OCC { - - -} // namespace OCC diff --git a/src/libsync/creds/credentialscommon.h b/src/libsync/creds/credentialscommon.h deleted file mode 100644 index e2cf1a6ff11..00000000000 --- a/src/libsync/creds/credentialscommon.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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. - */ - -#ifndef MIRALL_CREDS_COMMON_H -#define MIRALL_CREDS_COMMON_H - -#include "owncloudlib.h" - -namespace OCC { - -} // namespace OCC - -#endif diff --git a/src/libsync/creds/credentialssupport.h b/src/libsync/creds/credentialssupport.h new file mode 100644 index 00000000000..fc30bee05a9 --- /dev/null +++ b/src/libsync/creds/credentialssupport.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) Lisa Reese (lisa.reese@kiteworks.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 + +namespace OCC { + +inline constexpr QNetworkRequest::Attribute DontAddCredentialsAttribute = QNetworkRequest::User; + +enum class AuthenticationType { Unknown = 0, OAuth }; +} diff --git a/src/libsync/creds/dummycredentials.cpp b/src/libsync/creds/dummycredentials.cpp deleted file mode 100644 index 35ce02179b5..00000000000 --- a/src/libsync/creds/dummycredentials.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) by Krzesimir Nowak - * - * 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 "creds/dummycredentials.h" -#include "accessmanager.h" - -namespace OCC { - -QString DummyCredentials::authType() const -{ - return QStringLiteral("dummy"); -} - -QString DummyCredentials::user() const -{ - return _user; -} - -QNetworkAccessManager *DummyCredentials::createQNAM() const -{ - return new AccessManager; -} - -bool DummyCredentials::ready() const -{ - return true; -} - -bool DummyCredentials::stillValid(QNetworkReply *reply) -{ - Q_UNUSED(reply) - return true; -} - -void DummyCredentials::fetchFromKeychain() -{ - _wasFetched = true; - Q_EMIT(fetched()); -} - -void DummyCredentials::askFromUser() -{ - Q_EMIT(asked()); -} - -void DummyCredentials::persist() -{ -} - -} // namespace OCC diff --git a/src/libsync/creds/dummycredentials.h b/src/libsync/creds/dummycredentials.h deleted file mode 100644 index f9fc11dc509..00000000000 --- a/src/libsync/creds/dummycredentials.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) by Krzesimir Nowak - * - * 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. - */ - -#ifndef MIRALL_CREDS_DUMMY_CREDENTIALS_H -#define MIRALL_CREDS_DUMMY_CREDENTIALS_H - -#include "creds/abstractcredentials.h" - -namespace OCC { - -class OWNCLOUDSYNC_EXPORT DummyCredentials : public AbstractCredentials -{ - Q_OBJECT - -public: - QString _user; - QString _password; - QString authType() const override; - QString user() const override; - QNetworkAccessManager *createQNAM() const override; - bool ready() const override; - bool stillValid(QNetworkReply *reply) override; - void fetchFromKeychain() override; - void askFromUser() override; - void persist() override; - void invalidateToken() override {} - void forgetSensitiveData() override{}; -}; - -} // namespace OCC - -#endif diff --git a/src/libsync/creds/httpcredentials.cpp b/src/libsync/creds/httpcredentials.cpp deleted file mode 100644 index 3f886e5a8e0..00000000000 --- a/src/libsync/creds/httpcredentials.cpp +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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 "creds/httpcredentials.h" -#include "creds/httpcredentials_p.h" - -#include "account.h" -#include "accessmanager.h" -#include "configfile.h" -#include "theme.h" -#include "syncengine.h" -#include "oauth.h" -#include "creds/credentialscommon.h" -#include "creds/credentialmanager.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -Q_LOGGING_CATEGORY(lcHttpCredentials, "sync.credentials.http", QtInfoMsg) -Q_LOGGING_CATEGORY(lcHttpLegacyCredentials, "sync.credentials.http.legacy", QtInfoMsg) - -namespace { -constexpr int CredentialVersion = 1; -const char authenticationFailedC[] = "owncloud-authentication-failed"; - -auto isOAuthC() -{ - return QStringLiteral("oauth"); -} - -auto passwordKeyC() -{ - return QStringLiteral("http/password"); -} - -auto refreshTokenKeyC() -{ - return QStringLiteral("http/oauthtoken"); -} - -auto CredentialVersionKey() -{ - return QStringLiteral("CredentialVersion"); -} - -const QString userC() -{ - return QStringLiteral("user"); -} -} - -namespace OCC { - -class HttpCredentialsAccessManager : public AccessManager -{ - Q_OBJECT -public: - HttpCredentialsAccessManager(const HttpCredentials *cred, QObject *parent = nullptr) - : AccessManager(parent) - , _cred(cred) - { - } - -protected: - QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) override - { - QNetworkRequest req(request); - if (!req.attribute(HttpCredentials::DontAddCredentialsAttribute).toBool()) { - if (_cred && !_cred->_password.isEmpty()) { - if (_cred->isUsingOAuth()) { - req.setRawHeader("Authorization", "Bearer " + _cred->_password.toUtf8()); - } else { - QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->_password.toUtf8()).toBase64(); - req.setRawHeader("Authorization", "Basic " + credHash); - } - } else if (!request.url().password().isEmpty()) { - // Typically the requests to get or refresh the OAuth access token. The client - // credentials are put in the URL from the code making the request. - QByteArray credHash = request.url().userInfo().toUtf8().toBase64(); - req.setRawHeader("Authorization", "Basic " + credHash); - } - } - return AccessManager::createRequest(op, req, outgoingData); - } - -private: - // The credentials object dies along with the account, while the QNAM might - // outlive both. - QPointer _cred; -}; - -HttpCredentials::HttpCredentials(DetermineAuthTypeJob::AuthType authType, const QString &user, const QString &password) - : _user(user) - , _password(password) - , _ready(true) - , _retryOnKeyChainError(false) - , _authType(authType) -{ -} - -QString HttpCredentials::authType() const -{ - return QStringLiteral("http"); -} - -QString HttpCredentials::user() const -{ - return _user; -} - -void HttpCredentials::setAccount(Account *account) -{ - AbstractCredentials::setAccount(account); - if (_user.isEmpty()) { - fetchUser(); - } - const auto isOauth = account->credentialSetting(isOAuthC()); - if (isOauth.isValid()) { - _authType = isOauth.toBool() ? DetermineAuthTypeJob::AuthType::OAuth : DetermineAuthTypeJob::AuthType::Basic; - } -} - -QNetworkAccessManager *HttpCredentials::createQNAM() const -{ - AccessManager *qnam = new HttpCredentialsAccessManager(this); - - connect(qnam, &QNetworkAccessManager::authenticationRequired, - this, &HttpCredentials::slotAuthentication); - - return qnam; -} - -bool HttpCredentials::ready() const -{ - return _ready; -} - -QString HttpCredentials::fetchUser() -{ - _user = _account->credentialSetting(userC()).toString(); - return _user; -} - -void HttpCredentials::fetchFromKeychain() -{ - _wasFetched = true; - - // User must be fetched from config file - fetchUser(); - - if (!_ready && !_refreshToken.isEmpty()) { - // This happens if the credentials are still loaded from the keychain, bur we are called - // here because the auth is invalid, so this means we simply need to refresh the credentials - refreshAccessToken(); - return; - } - - if (_ready) { - Q_EMIT fetched(); - } else { - fetchFromKeychainHelper(); - } -} - -void HttpCredentials::fetchFromKeychainHelper() -{ - Q_ASSERT(!_user.isEmpty()); - const int version = _account->credentialSetting(CredentialVersionKey()).toInt(); - if (version < CredentialVersion && !_credentialMigration) { - auto migration = new HttpLegacyCredentials(this); - _credentialMigration = migration; - migration->fetchFromKeychainHelper(); - return; - } - - auto job = _account->credentialManager()->get(isUsingOAuth() ? refreshTokenKeyC() : passwordKeyC()); - connect(job, &CredentialJob::finished, this, [job, this] { - if (job->error() != QKeychain::NoError) { - qCWarning(lcHttpCredentials) << "Could not retrieve client password from keychain" << job->errorString(); - - // we come here if the password is empty or any other keychain - // error happend. - - _fetchErrorString = job->error() != QKeychain::EntryNotFound ? job->errorString() : QString(); - - _password.clear(); - _ready = false; - emit fetched(); - return; - } - const auto data = job->data().toString(); - if (OC_ENSURE(!data.isEmpty())) { - if (isUsingOAuth()) { - _refreshToken = data; - refreshAccessToken(); - } else { - _password = data; - _ready = true; - emit fetched(); - } - } - }); -} - -bool HttpCredentials::stillValid(QNetworkReply *reply) -{ - // The function is called in order to determine whether we need to ask the user for a password - // if we are using oaut we already started a refresh in slotAuthentication. - // If the refresh fails we will emit authenticationFailed ourself. - if (isUsingOAuth()) { - return true; - } - return ((reply->error() != QNetworkReply::AuthenticationRequiredError) - // returned if user or password is incorrect - && (reply->error() != QNetworkReply::OperationCanceledError - || !reply->property(authenticationFailedC).toBool())); -} - -bool HttpCredentials::refreshAccessToken() -{ - if (_refreshToken.isEmpty()) - return false; - if (_isRenewingOAuthToken) { - return true; - } - _isRenewingOAuthToken = true; - - // don't touch _ready or the account state will start a new authentication - // _ready = false; - - OAuth *oauth = new OAuth(_account, this); - connect(oauth, &OAuth::refreshError, this, [oauth, this](QNetworkReply::NetworkError error, const QString &errorString) { - oauth->deleteLater(); - _isRenewingOAuthToken = false; - switch (error) { - // most probably a timeout - case QNetworkReply::OperationCanceledError: - Q_FALLTHROUGH(); - case QNetworkReply::RemoteHostClosedError: - Q_FALLTHROUGH(); - case QNetworkReply::ConnectionRefusedError: - Q_FALLTHROUGH(); - case QNetworkReply::HostNotFoundError: - Q_FALLTHROUGH(); - case QNetworkReply::TimeoutError: - Q_FALLTHROUGH(); - case QNetworkReply::TemporaryNetworkFailureError: - Q_FALLTHROUGH(); - case QNetworkReply::NetworkSessionFailedError: - Q_FALLTHROUGH(); - case QNetworkReply::InternalServerError: - Q_FALLTHROUGH(); - case QNetworkReply::ServiceUnavailableError: - Q_FALLTHROUGH(); - case QNetworkReply::UnknownNetworkError: - QTimer::singleShot(30s, this, &HttpCredentials::refreshAccessToken); - break; - default: - // something is broken - // start fresh - qCWarning(lcHttpCredentials) << "Token refresh encountered an unsupported network error" << errorString << "-> log out"; - forgetSensitiveData(); - Q_EMIT _account->invalidCredentials(); - } - Q_EMIT authenticationFailed(); - }); - - connect(oauth, &OAuth::refreshFinished, this, [this, oauth](const QString &accessToken, const QString &refreshToken){ - oauth->deleteLater(); - _isRenewingOAuthToken = false; - if (refreshToken.isEmpty()) { - // an error occured, log out - forgetSensitiveData(); - Q_EMIT _account->invalidCredentials(); - Q_EMIT authenticationFailed(); - return; - } - _refreshToken = refreshToken; - if (!accessToken.isNull()) - { - _ready = true; - _password = accessToken; - persist(); - } - emit fetched(); - }); - oauth->refreshAuthentication(_refreshToken); - Q_EMIT authenticationStarted(); - return true; -} - -void HttpCredentials::invalidateToken() -{ - if (!_password.isEmpty()) { - _previousPassword = _password; - } - _password = QString(); - _ready = false; - - // User must be fetched from config file to generate a valid key - fetchUser(); - - // clear the session cookie. - _account->clearCookieJar(); - - if (!_refreshToken.isEmpty()) { - // Only invalidate the access_token (_password) but keep the _refreshToken in the keychain - // (when coming from forgetSensitiveData, the _refreshToken is cleared) - return; - } - - _account->credentialManager()->clear(QStringLiteral("http")); - // let QNAM forget about the password - // This needs to be done later in the event loop because we might be called (directly or - // indirectly) from QNetworkAccessManagerPrivate::authenticationRequired, which itself - // is a called from a BlockingQueuedConnection from the Qt HTTP thread. And clearing the - // cache needs to synchronize again with the HTTP thread. - QTimer::singleShot(0, _account, &Account::clearQNAMCache); -} - -void HttpCredentials::forgetSensitiveData() -{ - // need to be done before invalidateToken, so it actually deletes the refresh_token from the keychain - _refreshToken.clear(); - - invalidateToken(); - _previousPassword.clear(); -} - -void HttpCredentials::persist() -{ - if (_user.isEmpty()) { - // We never connected or fetched the user, there is nothing to save. - return; - } - _account->setCredentialSetting(CredentialVersionKey(), CredentialVersion); - _account->setCredentialSetting(userC(), _user); - _account->setCredentialSetting(isOAuthC(), isUsingOAuth()); - Q_EMIT _account->wantsAccountSaved(_account); - - // write secrets to the keychain - if (isUsingOAuth()) { - _account->credentialManager()->set(refreshTokenKeyC(), _refreshToken); - } else { - _account->credentialManager()->set(passwordKeyC(), _password); - } -} - -void HttpCredentials::slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) -{ - if (!_ready) - return; - Q_UNUSED(authenticator) - // Because of issue #4326, we need to set the login and password manually at every requests - // Thus, if we reach this signal, those credentials were invalid and we terminate. - qCWarning(lcHttpCredentials) << "Stop request: Authentication failed for " << reply->url().toString() << reply->request().rawHeader("Original-Request-ID"); - reply->setProperty(authenticationFailedC, true); - - if (!_isRenewingOAuthToken && isUsingOAuth()) { - qCInfo(lcHttpCredentials) << "Refreshing token"; - refreshAccessToken(); - } -} - -} // namespace OCC - -#include "httpcredentials.moc" diff --git a/src/libsync/creds/httpcredentials.h b/src/libsync/creds/httpcredentials.h deleted file mode 100644 index 8b9f7260dd7..00000000000 --- a/src/libsync/creds/httpcredentials.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Krzesimir Nowak - * - * 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. - */ - -#ifndef MIRALL_CREDS_HTTP_CREDENTIALS_H -#define MIRALL_CREDS_HTTP_CREDENTIALS_H - -#include "creds/abstractcredentials.h" -#include "networkjobs.h" - -#include -#include -#include -#include - -class QNetworkReply; -class QAuthenticator; - -namespace QKeychain { -class Job; -class WritePasswordJob; -class ReadPasswordJob; -} - -namespace OCC { - -/* - The authentication system is this way because of Shibboleth. - There used to be two different ways to authenticate: Shibboleth and HTTP Basic Auth. - AbstractCredentials can be inherited from both ShibbolethCrendentials and HttpCredentials. - - HttpCredentials is then split in HttpCredentials and HttpCredentialsGui. - - This class handle both HTTP Basic Auth and OAuth. But anything that needs GUI to ask the user - is in HttpCredentialsGui. - - */ -class OWNCLOUDSYNC_EXPORT HttpCredentials : public AbstractCredentials -{ - Q_OBJECT - friend class HttpCredentialsAccessManager; - friend class HttpLegacyCredentials; - -public: - /// Don't add credentials if this is set on a QNetworkRequest - static constexpr QNetworkRequest::Attribute DontAddCredentialsAttribute = QNetworkRequest::User; - - explicit HttpCredentials(DetermineAuthTypeJob::AuthType authType, const QString &user, const QString &password); - - QString authType() const override; - QNetworkAccessManager *createQNAM() const override; - bool ready() const override; - void fetchFromKeychain() override; - bool stillValid(QNetworkReply *reply) override; - void persist() override; - QString user() const override; - void invalidateToken() override; - void forgetSensitiveData() override; - QString fetchUser(); - virtual bool sslIsTrusted() { return false; } - - /* If we still have a valid refresh token, try to refresh it assynchronously and emit fetched() - * otherwise return false - */ - bool refreshAccessToken(); - - // To fetch the user name as early as possible - void setAccount(Account *account) override; - - // Whether we are using OAuth - bool isUsingOAuth() const { return _authType == DetermineAuthTypeJob::AuthType::OAuth; } -protected: - HttpCredentials() = default; - - /// Wipes legacy keychain locations - void deleteOldKeychainEntries(); - - void slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator); - void fetchFromKeychainHelper(); - - QString _user; - QString _password; // user's password, or access_token for OAuth - QString _refreshToken; // OAuth _refreshToken, set if OAuth is used. - QString _previousPassword; - - QString _fetchErrorString; - bool _ready = false; - bool _isRenewingOAuthToken = false; - bool _retryOnKeyChainError = true; // true if we haven't done yet any reading from keychain - - DetermineAuthTypeJob::AuthType _authType = DetermineAuthTypeJob::AuthType::Unknown; - -private: - // HttpLegacyCredentials is incompelte - QPointer _credentialMigration; -}; - - -} // namespace OCC - -#endif diff --git a/src/libsync/creds/httpcredentials_p.h b/src/libsync/creds/httpcredentials_p.h deleted file mode 100644 index e3bb277c760..00000000000 --- a/src/libsync/creds/httpcredentials_p.h +++ /dev/null @@ -1,211 +0,0 @@ -#pragma once - -#include "httpcredentials.h" -#include "common/depreaction.h" - -#include "account.h" -#include "configfile.h" -#include "theme.h" - -#include -#include - -#include - -using namespace std::chrono_literals; - -OC_DISABLE_DEPRECATED_WARNING - -Q_DECLARE_LOGGING_CATEGORY(lcHttpLegacyCredentials) - -namespace { -void addSettingsToJob(QKeychain::Job *job) -{ - auto settings = OCC::ConfigFile::settingsWithGroup(OCC::Theme::instance()->appName()); - settings->setParent(job); // make the job parent to make setting deleted properly - job->setSettings(settings.release()); -} - - -QString keychainKey(const QString &url, const QString &user, const QString &accountId) -{ - QString u(url); - if (u.isEmpty()) { - qCWarning(lcHttpLegacyCredentials) << "Empty url in keyChain, error!"; - return {}; - } - if (user.isEmpty()) { - qCWarning(lcHttpLegacyCredentials) << "Error: User is empty!"; - return {}; - } - - if (!u.endsWith(QLatin1Char('/'))) { - u.append(QLatin1Char('/')); - } - - QString key = user + QLatin1Char(':') + u; - if (!accountId.isEmpty()) { - key += QLatin1Char(':') + accountId; - } -#ifdef Q_OS_WIN - // On Windows the credential keys aren't namespaced properly - // by qtkeychain. To work around that we manually add namespacing - // to the generated keys. See #6125. - // It's safe to do that since the key format is changing for 2.4 - // anyway to include the account ids. That means old keys can be - // migrated to new namespaced keys on windows for 2.4. - key.prepend(OCC::Theme::instance()->appNameGUI() + QLatin1Char('_')); -#endif - return key; -} -} // ns - -namespace OCC { - -class HttpLegacyCredentials : public QObject -{ - Q_OBJECT - - auto isOAuthC() - { - return QStringLiteral("oauth"); - } - -public: - HttpLegacyCredentials(HttpCredentials *parent) - : QObject(parent) - , _parent(parent) - { - } - - void fetchFromKeychainHelper() - { - qCInfo(lcHttpLegacyCredentials) << "Started migration of < 2.8 credentials to 2.9+"; - slotReadPasswordFromKeychain(); - } - -private: - HttpCredentials *_parent; - bool _retryOnKeyChainError = true; - - bool _keychainMigration = false; - - bool keychainUnavailableRetryLater(QKeychain::ReadPasswordJob *incoming) - { -#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) - Q_ASSERT(!incoming->insecureFallback()); // If insecureFallback is set, the next test would be pointless - if (_retryOnKeyChainError && (incoming->error() == QKeychain::NoBackendAvailable || incoming->error() == QKeychain::OtherError)) { - // Could be that the backend was not yet available. Wait some extra seconds. - // (Issues #4274 and #6522) - // (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain) - qCInfo(lcHttpLegacyCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incoming->errorString(); - QTimer::singleShot(10s, this, &HttpLegacyCredentials::fetchFromKeychainHelper); - _retryOnKeyChainError = false; - return true; - } -#else - Q_UNUSED(incoming); -#endif - _retryOnKeyChainError = false; - return false; - } - - void slotReadPasswordFromKeychain() - { - const QString kck = keychainKey( - _parent->_account->url().toString(), - _parent->_user, - _keychainMigration ? QString() : _parent->_account->id()); - - QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(Theme::instance()->appName()); - addSettingsToJob(job); - job->setInsecureFallback(false); - job->setKey(kck); - connect(job, &QKeychain::ReadPasswordJob::finished, this, &HttpLegacyCredentials::slotReadJobDone); - job->start(); - } - - - void slotReadJobDone(QKeychain::Job *incoming) - { - Q_ASSERT(!_parent->_user.isEmpty()); - auto job = qobject_cast(incoming); - QKeychain::Error error = job->error(); - - // If we can't find the credentials at the keys that include the account id, - // try to read them from the legacy locations that don't have a account id. - if (!_keychainMigration && error == QKeychain::EntryNotFound) { - qCWarning(lcHttpLegacyCredentials) - << "Could not find keychain entries, attempting to read from legacy locations"; - _keychainMigration = true; - fetchFromKeychainHelper(); - return; - } - - const auto data = job->textData(); - if (error != QKeychain::NoError || data.isEmpty()) { - // we come here if the password is empty or any other keychain - // error happend. - qCWarning(lcHttpLegacyCredentials) << "Migrating old keychain entries failed" << job->errorString(); - _parent->_fetchErrorString = job->error() != QKeychain::EntryNotFound ? job->errorString() : QString(); - - _parent->_password.clear(); - _parent->_ready = false; - emit _parent->fetched(); - } else { - qCWarning(lcHttpLegacyCredentials) << "Migrated old keychain entries"; - if (_parent->_account->credentialSetting(isOAuthC()).toBool()) { - _parent->_refreshToken = data; - _parent->refreshAccessToken(); - } else { - // All cool, the keychain did not come back with error. - // Still, the password can be empty which indicates a problem and - // the password dialog has to be opened. - _parent->_password = data; - _parent->_ready = true; - emit _parent->fetched(); - } - } - // If keychain data was read from legacy location, wipe these entries and store new ones - deleteOldKeychainEntries(); - if (_parent->_ready) { - _parent->persist(); - qCWarning(lcHttpLegacyCredentials) << "Migrated old keychain entries"; - } - deleteLater(); - } - - void slotWriteJobDone(QKeychain::Job *job) - { - if (job && job->error() != QKeychain::NoError) { - qCWarning(lcHttpLegacyCredentials) << "Error while writing password" - << job->error() << job->errorString(); - } - } - - void deleteOldKeychainEntries() - { - // oooold - auto startDeleteJob = [this](const QString &key, bool migratePreId = false) { - QKeychain::DeletePasswordJob *job = new QKeychain::DeletePasswordJob(Theme::instance()->appName()); - addSettingsToJob(job); - job->setInsecureFallback(true); - job->setKey(keychainKey(_parent->_account->url().toString(), key, migratePreId ? QString() : _parent->_account->id())); - job->start(); - connect(job, &QKeychain::DeletePasswordJob::finished, this, [job] { - if (job->error() != QKeychain::NoError) { - qCWarning(lcHttpLegacyCredentials) << "Failed to delete legacy credentials" << job->key() << job->errorString(); - } - }); - }; - - // old - startDeleteJob(_parent->_user, true); - // pre 2.8 - startDeleteJob(_parent->_user); - } -}; - -} - -OC_ENABLE_DEPRECATED_WARNING diff --git a/src/libsync/creds/oauth.cpp b/src/libsync/creds/oauth.cpp deleted file mode 100644 index 7ddd67299af..00000000000 --- a/src/libsync/creds/oauth.cpp +++ /dev/null @@ -1,563 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 "creds/oauth.h" - -#include "account.h" -#include "common/version.h" -#include "credentialmanager.h" -#include "creds/httpcredentials.h" -#include "networkjobs.h" -#include "theme.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -using namespace OCC; -Q_LOGGING_CATEGORY(lcOauth, "sync.credentials.oauth", QtInfoMsg) - -namespace { -const QString clientSecretC() -{ - return QStringLiteral("http/clientSecret"); -} - -QVariant getRequiredField(const QVariantMap &json, const QString &s, QString *error) -{ - const auto out = json.constFind(s); - if (out == json.constEnd()) { - error->append(QStringLiteral("\tError: Missing field %1\n").arg(s)); - return {}; - } - return *out; -} - -void httpReplyAndClose(const QPointer &socket, const QByteArray &code, const QByteArray &html, - const QByteArray &moreHeaders = {}) -{ - if (!socket) - return; // socket can have been deleted if the browser was closed - // clang format has issues with the next line... - const QByteArray msg = QByteArrayLiteral("HTTP/1.1 ") % code % QByteArrayLiteral("\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\nContent-Length: ") % QByteArray::number(html.length()) % (!moreHeaders.isEmpty() ? QByteArrayLiteral("\r\n") % moreHeaders : QByteArray()) % QByteArrayLiteral("\r\n\r\n") % html; - qCDebug(lcOauth) << msg; - socket->write(msg); - socket->disconnectFromHost(); - // We don't want that deleting the server too early prevent queued data to be sent on this socket. - // The socket will be deleted after disconnection because disconnected is connected to deleteLater - socket->setParent(nullptr); -} - -class RegisterClientJob : public QObject -{ - Q_OBJECT -public: - RegisterClientJob(Account *accout, const QUrl ®istrationEndpoint, QObject *parent) - : QObject(parent) - , _account(accout) - , _registrationEndpoint(registrationEndpoint) - { - connect(this, &RegisterClientJob::errorOccured, this, &RegisterClientJob::deleteLater); - connect(this, &RegisterClientJob::finished, this, &RegisterClientJob::deleteLater); - } - - void start() - { - auto job = _account->credentialManager()->get(clientSecretC()); - connect(job, &CredentialJob::finished, this, [this, job] { - qDebug() << job->errorString() << job->error(); - if (job->data().isValid()) { - rgisterClientFinished(job->data().value()); - } else { - qCCritical(lcOauth) << "Failed to read client id" << job->errorString(); - rgisterClientOnline(); - } - }); - } - -Q_SIGNALS: - void finished(const QString &clientId, const QString &clientSecret); - void errorOccured(const QString &error); - -private: - void rgisterClientOnline() - { - auto job = new OCC::SimpleNetworkJob(_account->sharedFromThis(), this); - job->setAuthenticationJob(true); - connect(job, &OCC::SimpleNetworkJob::finishedSignal, this, [this, job] { - const auto data = job->reply()->readAll(); - QJsonParseError error; - const auto json = QJsonDocument::fromJson(data, &error); - if (error.error == QJsonParseError::NoError) { - rgisterClientFinished(json.object().toVariantMap()); - } else { - qCWarning(lcOauth) << "Failed to register the client" << error.errorString() << data; - Q_EMIT errorOccured(error.errorString()); - } - }); - const QJsonObject json({ { QStringLiteral("client_name"), QStringLiteral("%1 %2").arg(Theme::instance()->appNameGUI(), OCC::Version::versionWithBuildNumber().toString()) }, - { QStringLiteral("redirect_uris"), QJsonArray { QStringLiteral("http://127.0.0.1") } }, - { QStringLiteral("application_type"), QStringLiteral("native") }, - { QStringLiteral("token_endpoint_auth_method"), QStringLiteral("client_secret_basic") } }); - job->prepareRequest("POST", _registrationEndpoint, QNetworkRequest(), json); - job->start(); - } - - void rgisterClientFinished(const QVariantMap &data) - { - { - QString error; - const auto expireDate = getRequiredField(data, QStringLiteral("client_secret_expires_at"), &error).value(); - if (!error.isEmpty()) { - Q_EMIT errorOccured(error); - return; - } - // 0 means it doesn't expire - if (expireDate) { - const auto qExpireDate = QDateTime::fromSecsSinceEpoch(expireDate); - qCInfo(lcOauth) << "Client id iessued at:" << QDateTime::fromSecsSinceEpoch(data[QStringLiteral("client_id_issued_at")].value()) - << "expires at" << qExpireDate; - if (QDateTime::currentDateTimeUtc() > qExpireDate) { - qCDebug(lcOauth) << "Client registration expired"; - rgisterClientOnline(); - return; - } - } - } - _account->credentialManager()->set(clientSecretC(), data); - QString error; - const auto client_id = getRequiredField(data, QStringLiteral("client_id"), &error).toString(); - const auto client_secret = getRequiredField(data, QStringLiteral("client_secret"), &error).toString(); - if (!error.isEmpty()) { - Q_EMIT errorOccured(error); - return; - } - Q_EMIT finished(client_id, client_secret); - } - -private: - Account *_account; - QUrl _registrationEndpoint; -}; - -} - -OAuth::OAuth(Account *account, QObject *parent) - : QObject(parent) - , _account(account) - , _clientId(Theme::instance()->oauthClientId()) - , _clientSecret(Theme::instance()->oauthClientSecret()) - , _redirectUrl(Theme::instance()->oauthLocalhost()) -{ -} - -OAuth::~OAuth() -{ -} - -void OAuth::startAuthentication() -{ - // Listen on the socket to get a port which will be used in the redirect_uri - if (!_server.listen(QHostAddress::LocalHost)) { - emit result(NotSupported, QString()); - return; - } - - _pkceCodeVerifier = generateRandomString(24); - OC_ASSERT(_pkceCodeVerifier.size() == 128) - _state = generateRandomString(8); - - connect(this, &OAuth::fetchWellKnownFinished, this, [this] { - if (_registrationEndpoint.isValid()) { - auto job = new RegisterClientJob(_account, _registrationEndpoint, this); - connect(job, &RegisterClientJob::finished, this, [this](const QString &clientId, const QString &clientSecret) { - _clientId = clientId; - _clientSecret = clientSecret; - Q_EMIT authorisationLinkChanged(authorisationLink()); - }); - connect(job, &RegisterClientJob::errorOccured, this, [this](const QString &error) { - qCWarning(lcOauth) << "Failed to dynamically register the client, try the default client id" << error; - Q_EMIT authorisationLinkChanged(authorisationLink()); - }); - job->start(); - } else { - Q_EMIT authorisationLinkChanged(authorisationLink()); - } - }); - fetchWellKnown(); - - openBrowser(); - - QObject::connect(&_server, &QTcpServer::newConnection, this, [this] { - while (QPointer socket = _server.nextPendingConnection()) { - QObject::connect(socket.data(), &QTcpSocket::disconnected, socket.data(), &QTcpSocket::deleteLater); - QObject::connect(socket.data(), &QIODevice::readyRead, this, [this, socket] { - const QByteArray peek = socket->peek(qMin(socket->bytesAvailable(), 4000LL)); //The code should always be within the first 4K - if (!peek.contains('\n')) - return; // wait until we find a \n - qCDebug(lcOauth) << "Server provided:" << peek; - const auto getPrefix = QByteArrayLiteral("GET /?"); - if (!peek.startsWith(getPrefix)) { - httpReplyAndClose(socket, QByteArrayLiteral("404 Not Found"), QByteArrayLiteral("404 Not Found

    404 Not Found

    ")); - return; - } - const auto endOfUrl = peek.indexOf(' ', getPrefix.length()); - const QUrlQuery args(QUrl::fromPercentEncoding(peek.mid(getPrefix.length(), endOfUrl - getPrefix.length()))); - if (args.queryItemValue(QStringLiteral("state")).toUtf8() != _state) { - httpReplyAndClose(socket, QByteArrayLiteral("400 Bad Request"), QByteArrayLiteral("400 Bad Request

    400 Bad Request

    ")); - return; - } - auto job = postTokenRequest({ - { QStringLiteral("grant_type"), QStringLiteral("authorization_code") }, - { QStringLiteral("code"), args.queryItemValue(QStringLiteral("code")) }, - { QStringLiteral("redirect_uri"), QStringLiteral("%1:%2").arg(_redirectUrl, QString::number(_server.serverPort())) }, - { QStringLiteral("code_verifier"), QString::fromUtf8(_pkceCodeVerifier) }, - }); - QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this, socket](QNetworkReply *reply) { - qScopeGuard([this] { - // close the server once we are done here - _server.close(); - }); - const auto jsonData = reply->readAll(); - QJsonParseError jsonParseError; - const auto data = QJsonDocument::fromJson(jsonData, &jsonParseError).object().toVariantMap(); - QString fieldsError; - const QString accessToken = getRequiredField(data, QStringLiteral("access_token"), &fieldsError).toString(); - const QString refreshToken = getRequiredField(data, QStringLiteral("refresh_token"), &fieldsError).toString(); - const QString tokenType = getRequiredField(data, QStringLiteral("token_type"), &fieldsError).toString().toLower(); - const QString user = data[QStringLiteral("user_id")].toString(); - const QUrl messageUrl = QUrl::fromEncoded(data[QStringLiteral("message_url")].toByteArray()); - - if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError - || !fieldsError.isEmpty() - || tokenType != QLatin1String("bearer")) { - // do we have error message suitable for users? - QString errorReason = data[QStringLiteral("error_description")].toString(); - if (errorReason.isEmpty()) { - // fall back to technical error - errorReason = data[QStringLiteral("error")].toString(); - } - if (!errorReason.isEmpty()) { - errorReason = tr("Error returned from the server: %1") - .arg(errorReason.toHtmlEscaped()); - } else if (reply->error() != QNetworkReply::NoError) { - errorReason = tr("There was an error accessing the 'token' endpoint:
    %1") - .arg(reply->errorString().toHtmlEscaped()); - } else if (jsonParseError.error != QJsonParseError::NoError) { - errorReason = tr("Could not parse the JSON returned from the server:
    %1") - .arg(jsonParseError.errorString()); - } else if (tokenType != QStringLiteral("bearer")) { - errorReason = tr("Unsupported token type: %1").arg(tokenType); - } else if (!fieldsError.isEmpty()) { - errorReason = tr("The reply from the server did not contain all expected fields\n:%1").arg(fieldsError); - } else { - errorReason = tr("Unknown Error"); - } - qCWarning(lcOauth) << "Error when getting the accessToken" << errorReason << "received data:" << jsonData; - httpReplyAndClose(socket, QByteArrayLiteral("500 Internal Server Error"), - tr("

    Login Error

    %1

    ").arg(errorReason).toUtf8()); - emit result(Error); - return; - } - if (!user.isEmpty()) { - finalize(socket, accessToken, refreshToken, user, messageUrl); - return; - } - // If the reply don't contains the user id, we must do another call to query it - JsonApiJob *job = new JsonApiJob(_account->sharedFromThis(), QStringLiteral("ocs/v2.php/cloud/user"), this); - job->setTimeout(qMin(job->timeoutSec(), 30s)); - QNetworkRequest req; - // We are not connected yet so we need to handle the authentication manually - req.setRawHeader("Authorization", "Bearer " + accessToken.toUtf8()); - // We just added the Authorization header, don't let HttpCredentialsAccessManager tamper with it - req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true); - job->startWithRequest(req); - connect(job, &JsonApiJob::jsonReceived, this, [=](const QJsonDocument &json, int status) { - if (status != 200) { - httpReplyAndClose(socket, QByteArrayLiteral("500 Internal Server Error"), - tr("

    Login Error

    Failed to retrieve user info

    ").toUtf8()); - emit result(Error); - } else { - const QString user = json.object().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toObject().value(QStringLiteral("id")).toString(); - finalize(socket, accessToken, refreshToken, user, messageUrl); - } - }); - }); - job->start(); - }); - } - }); -} - -void OAuth::refreshAuthentication(const QString &refreshToken) -{ - _isRefreshingToken = true; - auto refresh = [this, &refreshToken] { - auto job = postTokenRequest({ { QStringLiteral("grant_type"), QStringLiteral("refresh_token") }, - { QStringLiteral("refresh_token"), refreshToken } }); - connect(job, &SimpleNetworkJob::finishedSignal, this, [this, refreshToken](QNetworkReply *reply) { - const auto jsonData = reply->readAll(); - QString accessToken; - QString newRefreshToken = refreshToken; - QJsonParseError jsonParseError; - // https://developer.okta.com/docs/reference/api/oidc/#response-properties-2 - const auto data = QJsonDocument::fromJson(jsonData, &jsonParseError).object().toVariantMap(); - const QString error = data.value(QStringLiteral("error")).toString(); - if (!error.isEmpty()) { - if (error == QLatin1String("invalid_grant") || - error == QLatin1String("invalid_request")) { - newRefreshToken.clear(); - } else { - qCWarning(lcOauth) << tr("Error while refreshing the token: %1 : %2").arg(error, data.value(QStringLiteral("error_description")).toString()); - } - } else if (reply->error() != QNetworkReply::NoError) { - qCWarning(lcOauth) << tr("Error while refreshing the token: %1 : %2").arg(reply->errorString(), QString::fromUtf8(jsonData)); - Q_EMIT refreshError(reply->error(), reply->errorString()); - return; - } else { - if (jsonParseError.error != QJsonParseError::NoError || data.isEmpty()) { - // Invalid or empty JSON: Network error maybe? - qCWarning(lcOauth) << tr("Error while refreshing the token: %1 : %2").arg(jsonParseError.errorString(), QString::fromUtf8(jsonData)); - } else { - QString error; - accessToken = getRequiredField(data, QStringLiteral("access_token"), &error).toString(); - if (!error.isEmpty()) { - qCWarning(lcOauth) << tr("The reply from the server did not contain all expected fields\n:%1\nReceived data: %2").arg(error, QString::fromUtf8(jsonData)); - } - - const auto refresh_token = data.find(QStringLiteral("refresh_token")); - if (refresh_token != data.constEnd()) { - newRefreshToken = refresh_token.value().toString(); - } - } - } - Q_EMIT refreshFinished(accessToken, newRefreshToken); - }); - job->start(); - }; - connect(this, &OAuth::fetchWellKnownFinished, this, [this, refresh] { - if (_registrationEndpoint.isValid()) { - auto registerJob = new RegisterClientJob(_account, _registrationEndpoint, this); - connect(registerJob, &RegisterClientJob::finished, this, [this, refresh](const QString &clientId, const QString &clientSecret) { - _clientId = clientId; - _clientSecret = clientSecret; - refresh(); - }); - connect(registerJob, &RegisterClientJob::errorOccured, this, [this](const QString &error) { - qCWarning(lcOauth) << "Failed to dynamically register the client, try the default client id" << error; - Q_EMIT refreshFinished(QString(), QString()); - }); - registerJob->start(); - } else { - refresh(); - } - }); - fetchWellKnown(); -} - -void OAuth::finalize(const QPointer &socket, const QString &accessToken, - const QString &refreshToken, const QString &user, const QUrl &messageUrl) -{ - if (!_account->davUser().isNull() && user != _account->davUser()) { - // Connected with the wrong user - qCWarning(lcOauth) << "We expected the user" << _account->davUser() << "but the server answered with user" << user; - const QString message = tr("

    Wrong user

    " - "

    You logged-in with user %1, but must login with user %2.
    " - "Please log out of %3 in another tab, then click here " - "and log in as user %2

    ") - .arg(user, _account->davUser(), Theme::instance()->appNameGUI(), - authorisationLink().toString(QUrl::FullyEncoded)); - httpReplyAndClose(socket, QByteArrayLiteral("403 Forbidden"), message.toUtf8()); - // We are still listening on the socket so we will get the new connection - return; - } - const auto loginSuccessfullHtml = QByteArrayLiteral("

    Login Successful

    You can close this window.

    "); - if (messageUrl.isValid()) { - httpReplyAndClose(socket, QByteArrayLiteral("303 See Other"), loginSuccessfullHtml, - QByteArrayLiteral("Location: ") + messageUrl.toEncoded()); - } else { - httpReplyAndClose(socket, QByteArrayLiteral("200 OK"), loginSuccessfullHtml); - } - emit result(LoggedIn, user, accessToken, refreshToken); -} - -SimpleNetworkJob *OAuth::postTokenRequest(const QList> &queryItems) -{ - const QUrl requestTokenUrl = _tokenEndpoint.isEmpty() ? Utility::concatUrlPath(_account->url(), QStringLiteral("/index.php/apps/oauth2/api/v1/token")) : _tokenEndpoint; - QNetworkRequest req; - const QByteArray basicAuth = QStringLiteral("%1:%2").arg(_clientId, _clientSecret).toUtf8().toBase64(); - req.setRawHeader("Authorization", "Basic " + basicAuth); - req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true); - - QUrlQuery arguments; - arguments.setQueryItems(QList> { { QStringLiteral("client_id"), _clientId }, - { QStringLiteral("client_secret"), _clientSecret }, - { QStringLiteral("scope"), Theme::instance()->openIdConnectScopes() } } - << queryItems); - auto job = new SimpleNetworkJob(_account->sharedFromThis(), this); - job->setAuthenticationJob(true); - job->prepareRequest("POST", requestTokenUrl, req, arguments); - job->setTimeout(qMin(30s, job->timeoutSec())); - return job; -} - -QByteArray OAuth::generateRandomString(size_t size) const -{ - // TODO: do we need a varaible size? - std::vector buffer(size, 0); - QRandomGenerator::global()->fillRange(buffer.data(), static_cast(size)); - return QByteArray(reinterpret_cast(buffer.data()), static_cast(size * sizeof(quint32))).toBase64(QByteArray::Base64UrlEncoding); -} - -QUrl OAuth::authorisationLink() const -{ - Q_ASSERT(_server.isListening()); - QUrlQuery query; - const QByteArray code_challenge = QCryptographicHash::hash(_pkceCodeVerifier, QCryptographicHash::Sha256) - .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - query.setQueryItems({ { QStringLiteral("response_type"), QStringLiteral("code") }, - { QStringLiteral("client_id"), _clientId }, - { QStringLiteral("redirect_uri"), QStringLiteral("%1:%2").arg(_redirectUrl, QString::number(_server.serverPort())) }, - { QStringLiteral("code_challenge"), QString::fromLatin1(code_challenge) }, - { QStringLiteral("code_challenge_method"), QStringLiteral("S256") }, - { QStringLiteral("scope"), Theme::instance()->openIdConnectScopes() }, - { QStringLiteral("prompt"), Theme::instance()->openIdConnectPrompt() }, - { QStringLiteral("state"), QString::fromUtf8(_state) } }); - - if (!_account->davUser().isNull()) { - const QString davUser = _account->davUser().replace(QLatin1Char('+'), QStringLiteral("%2B")); // Issue #7762; - // open id connect - query.addQueryItem(QStringLiteral("login_hint"), davUser); - // oc 10 - query.addQueryItem(QStringLiteral("user"), davUser); - } - const QUrl url = _authEndpoint.isValid() - ? Utility::concatUrlPath(_authEndpoint, {}, query) - : Utility::concatUrlPath(_account->url(), QStringLiteral("/index.php/apps/oauth2/authorize"), query); - return url; -} - -void OAuth::authorisationLinkAsync(std::function callback) const -{ - if (_wellKnownFinished) { - callback(authorisationLink()); - } else { - connect(this, &OAuth::authorisationLinkChanged, callback); - } -} - -void OAuth::fetchWellKnown() -{ - auto checkServer = new CheckServerJob(_account->sharedFromThis(), this); - checkServer->setTimeout(qMin(30s, checkServer->timeoutSec())); - connect(checkServer, &CheckServerJob::instanceNotFound, this, [this](QNetworkReply *reply) { - if (_isRefreshingToken) { - Q_EMIT refreshError(reply->error(), reply->errorString()); - } else { - Q_EMIT result(Error); - } - }); - connect(checkServer, &CheckServerJob::instanceFound, this, [this] { - const QPair urls = Theme::instance()->oauthOverrideAuthUrl(); - if (!urls.first.isNull()) { - OC_ASSERT(!urls.second.isNull()); - _authEndpoint = QUrl::fromUserInput(urls.first); - _tokenEndpoint = QUrl::fromUserInput(urls.second); - _wellKnownFinished = true; - Q_EMIT fetchWellKnownFinished(); - } else { - auto job = new SimpleNetworkJob(_account->sharedFromThis(), this); - job->setAuthenticationJob(true); - job->prepareRequest("GET", Utility::concatUrlPath(_account->url(), QStringLiteral("/.well-known/openid-configuration"))); - job->setTimeout(qMin(30s, job->timeoutSec())); - QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) { - _wellKnownFinished = true; - if (reply->error() != QNetworkReply::NoError) { - // Most likely the file does not exist, default to the normal endpoint - Q_EMIT fetchWellKnownFinished(); - return; - } - const auto jsonData = reply->readAll(); - QJsonParseError jsonParseError; - const QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object(); - if (jsonParseError.error == QJsonParseError::NoError) { - _authEndpoint = QUrl::fromEncoded(json[QStringLiteral("authorization_endpoint")].toString().toUtf8()); - _tokenEndpoint = QUrl::fromEncoded(json[QStringLiteral("token_endpoint")].toString().toUtf8()); - _registrationEndpoint = QUrl::fromEncoded(json[QStringLiteral("registration_endpoint")].toString().toUtf8()); - _redirectUrl = QStringLiteral("http://127.0.0.1"); - } else if (jsonParseError.error == QJsonParseError::IllegalValue) { - qCDebug(lcOauth) << ".well-known did not return json, the server most probably does not support oidc"; - } else { - qCWarning(lcOauth) << "Json parse error in well-known: " << jsonParseError.errorString(); - } - Q_EMIT fetchWellKnownFinished(); - }); - job->start(); - } - }); - checkServer->start(); -} - -/** - * Checks whether a URL returned by the server is valid. - * @param url URL to validate - * @return true if validation is successful, false otherwise - */ -bool isUrlValid(const QUrl &url) -{ - qCDebug(lcOauth()) << "Checking URL for validity:" << url; - - // we have hardcoded the oauthOverrideAuth - const auto overrideUrl = Theme::instance()->oauthOverrideAuthUrl(); - if (!overrideUrl.first.isEmpty()) { - return QUrl::fromUserInput(overrideUrl.first).matches(url, QUrl::RemoveQuery); - } - - // the following allowlist contains URL schemes accepted as valid - // OAuth 2.0 URLs must be HTTPS to be in compliance with the specification - // for unit tests, we also permit the nonexisting oauthtest scheme - const QStringList allowedSchemes({ QStringLiteral("https"), QStringLiteral("oauthtest") }); - return allowedSchemes.contains(url.scheme()); -} - -void OAuth::openBrowser() -{ - authorisationLinkAsync([this](const QUrl &link) { - if (!isUrlValid(link)) { - qCWarning(lcOauth) << "URL validation failed"; - QMetaObject::invokeMethod(qApp, "slotShowGuiMessage", Qt::QueuedConnection, - Q_ARG(QString, tr("Oauth2 Error")), - Q_ARG(QString, tr("Oauth2 authentication requires a secured connection."))); - emit result(Error, QString()); - return; - } - - if (!QDesktopServices::openUrl(link)) { - qCWarning(lcOauth) << "QDesktopServices::openUrl Failed"; - // We cannot open the browser, then we claim we don't support OAuth. - emit result(NotSupported, QString()); - } - }); -} - -#include "oauth.moc" diff --git a/src/libsync/creds/oauth.h b/src/libsync/creds/oauth.h deleted file mode 100644 index 850ad479f16..00000000000 --- a/src/libsync/creds/oauth.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 "accountfwd.h" -#include "owncloudlib.h" - -#include -#include -#include -#include - - -namespace OCC { -class SimpleNetworkJob; - -/** - * Job that do the authorization grant and fetch the access token - * - * Normal workflow: - * - * --> start() - * | - * +----> fetchWellKnown() query the ".well-known/openid-configuration" endpoint - * | - * +----> openBrowser() open the browser after fetchWellKnown finished to the specified page - * | (or the default 'oauth2/authorize' if fetchWellKnown does not exist) - * | Then the browser will redirect to http://localhost:xxx - * | - * +----> _server starts listening on a TCP port waiting for an HTTP request with a 'code' - * | - * v - * request the access_token and the refresh_token via 'apps/oauth2/api/v1/token' - * | - * +-> Request the user_id is not present - * | | - * v v - * finalize(...): emit result(...) - * - */ -class OWNCLOUDSYNC_EXPORT OAuth : public QObject -{ - Q_OBJECT -public: - enum Result { NotSupported, - LoggedIn, - Error }; - Q_ENUM(Result); - - OAuth(Account *account, QObject *parent); - ~OAuth() override; - void startAuthentication(); - void refreshAuthentication(const QString &refreshToken); - void openBrowser(); - QUrl authorisationLink() const; - /** - * Call the callback when the call to the well-known endpoint finishes. - * (or immediatly if it is ready) - * The callback will not be called if this object gets destroyed - */ - void authorisationLinkAsync(std::function callback) const; - -signals: - /** - * The state has changed. - * when logged in, token has the value of the token. - */ - void result(OAuth::Result result, const QString &user = QString(), const QString &token = QString(), const QString &refreshToken = QString()); - - /** - * emitted when the call to the well-known endpoint is finished - */ - void authorisationLinkChanged(const QUrl &); - - void refreshError(QNetworkReply::NetworkError error, const QString &errorString); - void refreshFinished(const QString &accessToken, const QString &refreshToken); - - void fetchWellKnownFinished(); - -private: - void fetchWellKnown(); - void finalize(const QPointer &socket, const QString &accessToken, - const QString &refreshToken, const QString &userId, const QUrl &messageUrl); - - SimpleNetworkJob *postTokenRequest(const QList> &queryItems); - - QByteArray generateRandomString(size_t size) const; - - - Account* _account; - QTcpServer _server; - bool _wellKnownFinished = false; - - QString _clientId; - QString _clientSecret; - - QUrl _authEndpoint; - QUrl _tokenEndpoint; - QUrl _registrationEndpoint; - QString _redirectUrl; - QByteArray _pkceCodeVerifier; - QByteArray _state; - - bool _isRefreshingToken = false; -}; - -} // namespace OCC diff --git a/src/libsync/creds/tokencredentials.cpp b/src/libsync/creds/tokencredentials.cpp deleted file mode 100644 index dd13f4ddbd2..00000000000 --- a/src/libsync/creds/tokencredentials.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (c) by Markus Goetz - * - * 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 -#include -#include -#include -#include - -#include "account.h" -#include "accessmanager.h" -#include "common/utility.h" -#include "theme.h" -#include "creds/credentialscommon.h" -#include "creds/tokencredentials.h" - - -namespace OCC { - -Q_LOGGING_CATEGORY(lcTokenCredentials, "sync.credentials.token", QtInfoMsg) - -namespace { - - const char authenticationFailedC[] = "owncloud-authentication-failed"; - -} // ns - -class TokenCredentialsAccessManager : public AccessManager -{ -public: - friend class TokenCredentials; - TokenCredentialsAccessManager(const TokenCredentials *cred, QObject *parent = 0) - : AccessManager(parent) - , _cred(cred) - { - } - -protected: - QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) - { - if (_cred->user().isEmpty() || _cred->password().isEmpty()) { - qCWarning(lcTokenCredentials) << "Empty user/password provided!"; - } - - QNetworkRequest req(request); - - QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64(); - req.setRawHeader(QByteArray("Authorization"), QByteArray("Basic ") + credHash); - - // A pre-authenticated cookie - QByteArray token = _cred->_token.toUtf8(); - if (token.length() > 0) { - setRawCookie(token, request.url()); - } - - return AccessManager::createRequest(op, req, outgoingData); - } - -private: - const TokenCredentials *_cred; -}; - -TokenCredentials::TokenCredentials() - : _user() - , _password() - , _ready(false) -{ -} - -TokenCredentials::TokenCredentials(const QString &user, const QString &password, const QString &token) - : _user(user) - , _password(password) - , _token(token) - , _ready(true) -{ -} - -QString TokenCredentials::authType() const -{ - return QString::fromLatin1("token"); -} - -QString TokenCredentials::user() const -{ - return _user; -} - -QString TokenCredentials::password() const -{ - return _password; -} - -QNetworkAccessManager *TokenCredentials::createQNAM() const -{ - AccessManager *qnam = new TokenCredentialsAccessManager(this); - - connect(qnam, &AccessManager::authenticationRequired, - this, &TokenCredentials::slotAuthentication); - - return qnam; -} - -bool TokenCredentials::ready() const -{ - return _ready; -} - -void TokenCredentials::fetchFromKeychain() -{ - _wasFetched = true; - Q_EMIT fetched(); -} - -void TokenCredentials::askFromUser() -{ - emit asked(); -} - - -bool TokenCredentials::stillValid(QNetworkReply *reply) -{ - return ((reply->error() != QNetworkReply::AuthenticationRequiredError) - // returned if user/password or token are incorrect - && (reply->error() != QNetworkReply::OperationCanceledError - || !reply->property(authenticationFailedC).toBool())); -} - -void TokenCredentials::invalidateToken() -{ - qCInfo(lcTokenCredentials) << "Invalidating token"; - _ready = false; - _account->clearCookieJar(); - _token = QString(); - _user = QString(); - _password = QString(); -} - -void TokenCredentials::forgetSensitiveData() -{ - invalidateToken(); -} - -void TokenCredentials::persist() -{ -} - - -void TokenCredentials::slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) -{ - Q_UNUSED(authenticator) - // we cannot use QAuthenticator, because it sends username and passwords with latin1 - // instead of utf8 encoding. Instead, we send it manually. Thus, if we reach this signal, - // those credentials were invalid and we terminate. - qCWarning(lcTokenCredentials) << "Stop request: Authentication failed for " << reply->url().toString(); - reply->setProperty(authenticationFailedC, true); - reply->close(); -} - -} // namespace OCC diff --git a/src/libsync/creds/tokencredentials.h b/src/libsync/creds/tokencredentials.h deleted file mode 100644 index 2cc8517774a..00000000000 --- a/src/libsync/creds/tokencredentials.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (c) by Markus Goetz - * - * 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. - */ - -#ifndef MIRALL_CREDS_HTTP_CREDENTIALS_H -#define MIRALL_CREDS_HTTP_CREDENTIALS_H - -#include - -#include "creds/abstractcredentials.h" - -class QNetworkReply; -class QAuthenticator; - -namespace QKeychain { -class Job; -} - -namespace OCC { - -class TokenCredentialsAccessManager; -class OWNCLOUDSYNC_EXPORT TokenCredentials : public AbstractCredentials -{ - Q_OBJECT - -public: - friend class TokenCredentialsAccessManager; - TokenCredentials(); - TokenCredentials(const QString &user, const QString &password, const QString &token); - - QString authType() const override; - QNetworkAccessManager *createQNAM() const override; - bool ready() const override; - void askFromUser() override; - void fetchFromKeychain() override; - bool stillValid(QNetworkReply *reply) override; - void persist() override; - QString user() const override; - void invalidateToken() override; - void forgetSensitiveData() override; - - QString password() const; -private Q_SLOTS: - void slotAuthentication(QNetworkReply *, QAuthenticator *); - -private: - QString _user; - QString _password; - QString _token; // the cookies - bool _ready; -}; - -} // namespace OCC - -#endif diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 4dd5bc69594..dff4b242b6c 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -13,22 +13,20 @@ */ #include "discovery.h" -#include "common/checksums.h" -#include "common/syncjournaldb.h" #include "csync.h" -#include "csync_exclude.h" -#include "owncloudpropagator.h" -#include "syncengine.h" #include "syncfileitem.h" -#include "vio/csync_vio_local.h" -#include -#include +#include "csync/csync_exclude.h" +#include "csync/vio/csync_vio_local.h" + +#include "common/checksums.h" +#include "common/syncjournaldb.h" + +#include "libsync/theme.h" + -#include #include #include -#include #include namespace OCC { @@ -41,6 +39,8 @@ void ProcessDirectoryJob::start() if (_queryServer == NormalQuery) { _serverJob = startAsyncServerQuery(); + if (!_serverJob) + return; } else { _serverQueryDone = true; } @@ -90,12 +90,8 @@ void ProcessDirectoryJob::process() auto pathU8 = _currentFolder._original.toUtf8(); if (!_discoveryData->_statedb->listFilesInPath(pathU8, [&](const SyncJournalFileRecord &rec) { auto name = pathU8.isEmpty() ? QString::fromUtf8(rec._path) : QString::fromUtf8(rec._path.constData() + (pathU8.size() + 1)); - if (rec.isVirtualFile() && isVfsWithSuffix()) { - name = chopVirtualFileSuffix(name); - } auto &dbEntry = entries[name].dbEntry; dbEntry = rec; - setupDbPinStateActions(dbEntry); })) { dbError(); return; @@ -104,34 +100,6 @@ void ProcessDirectoryJob::process() for (auto &e : _localNormalQueryEntries) { entries[e.name].localEntry = e; } - if (isVfsWithSuffix()) { - // For vfs-suffix the local data for suffixed files should usually be associated - // with the non-suffixed name. Unless both names exist locally or there's - // other data about the suffixed file. - // This is done in a second path in order to not depend on the order of - // _localNormalQueryEntries. - for (auto &e : _localNormalQueryEntries) { - if (!e.isVirtualFile) - continue; - auto &suffixedEntry = entries[e.name]; - bool hasOtherData = suffixedEntry.serverEntry.isValid() || suffixedEntry.dbEntry.isValid(); - - auto nonvirtualName = chopVirtualFileSuffix(e.name); - auto &nonvirtualEntry = entries[nonvirtualName]; - // If the non-suffixed entry has no data, move it - if (!nonvirtualEntry.localEntry.isValid()) { - std::swap(nonvirtualEntry.localEntry, suffixedEntry.localEntry); - if (!hasOtherData) - entries.erase(e.name); - } else if (!hasOtherData) { - // Normally a lone local suffixed file would be processed under the - // unsuffixed name. In this special case it's under the suffixed name. - // To avoid lots of special casing, make sure PathTuple::addName() - // will be called with the unsuffixed name anyway. - suffixedEntry.nameOverride = nonvirtualName; - } - } - } _localNormalQueryEntries.clear(); // @@ -143,34 +111,25 @@ void ProcessDirectoryJob::process() PathTuple path; path = _currentFolder.addName(e.nameOverride.isEmpty() ? f.first : e.nameOverride); - if (isVfsWithSuffix()) { - // Without suffix vfs the paths would be good. But since the dbEntry and localEntry - // can have different names from f.first when suffix vfs is on, make sure the - // corresponding _original and _local paths are right. - - if (e.dbEntry.isValid()) { - path._original = QString::fromUtf8(e.dbEntry._path); - } else if (e.localEntry.isVirtualFile) { - // We don't have a db entry - but it should be at this path - path._original = PathTuple::pathAppend(_currentFolder._original, e.localEntry.name); - } - if (e.localEntry.isValid()) { - path._local = PathTuple::pathAppend(_currentFolder._local, e.localEntry.name); - } else if (e.dbEntry.isVirtualFile()) { - // We don't have a local entry - but it should be at this path - addVirtualFileSuffix(path._local); - } - } - // If the filename starts with a . we consider it a hidden file // For windows, the hidden state is also discovered within the vio // local stat function. // Recall file shall not be ignored (#4420) - bool isHidden = e.localEntry.isHidden || (f.first[0] == QLatin1Char('.') && f.first != QLatin1String(".sys.admin#recall#")); - if (handleExcluded(path._target, e.localEntry.name, - e.localEntry.isDirectory || e.serverEntry.isDirectory, isHidden, - e.localEntry.isSymLink)) + const bool isHidden = [&] { + return e.localEntry.isHidden || f.first[0] == QLatin1Char('.'); + }(); + if (handleExcluded(path._target, + e.localEntry.name, + e.localEntry.isDirectory || e.serverEntry.isDirectory, + isHidden, + e.localEntry.isSymLink)) { + // the file only exists in the db + if (!e.localEntry.isValid() && e.dbEntry.isValid()) { + qCWarning(lcDisco) << "Removing db entry for non existing ignored file:" << path._original; + _discoveryData->_statedb->deleteFileRecord(path._original, true); + } continue; + } if (_queryServer == InBlackList || _discoveryData->isInSelectiveSyncBlackList(path._original)) { processBlacklisted(path, e.localEntry, e.dbEntry); @@ -183,11 +142,11 @@ void ProcessDirectoryJob::process() bool ProcessDirectoryJob::handleExcluded(const QString &path, const QString &localName, bool isDirectory, bool isHidden, bool isSymlink) { - auto excluded = _discoveryData->_excludes->traversalPatternMatch(&path, isDirectory ? ItemTypeDirectory : ItemTypeFile); + auto excluded = _discoveryData->_excludes->traversalPatternMatch(path, isDirectory ? ItemTypeDirectory : ItemTypeFile); // FIXME: move to ExcludedFiles 's regexp ? bool isInvalidPattern = false; - if (excluded == CSYNC_NOT_EXCLUDED && !_discoveryData->_invalidFilenameRx.isEmpty()) { + if (excluded == CSYNC_NOT_EXCLUDED && !_discoveryData->_invalidFilenameRx.pattern().isEmpty()) { if (path.contains(_discoveryData->_invalidFilenameRx)) { excluded = CSYNC_FILE_EXCLUDE_INVALID_CHAR; isInvalidPattern = true; @@ -202,35 +161,20 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const QString &loc isInvalidPattern = true; } - auto localCodec = QTextCodec::codecForLocale(); - if (!OCC::Utility::isWindows() && localCodec->mibEnum() != 106) { - // If the locale codec is not UTF-8, we must check that the filename from the server can - // be encoded in the local file system. - // (Note: on windows, the FS is always UTF-16, so we don't need to check) - // - // We cannot use QTextCodec::canEncode() since that can incorrectly return true, see - // https://bugreports.qt.io/browse/QTBUG-6925. - QTextEncoder encoder(localCodec, QTextCodec::ConvertInvalidToNull); - if (encoder.fromUnicode(path).contains('\0')) { - qCWarning(lcDisco) << "Cannot encode " << path << " to local encoding " << localCodec->name(); - excluded = CSYNC_FILE_EXCLUDE_CANNOT_ENCODE; - } - } - if (excluded == CSYNC_NOT_EXCLUDED && !isSymlink) { return false; } else if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED || excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) { - emit _discoveryData->silentlyExcluded(path); + Q_EMIT _discoveryData->silentlyExcluded(path); return true; } else if (excluded == CSYNC_FILE_EXCLUDE_RESERVED) { - emit _discoveryData->excluded(path, excluded); + Q_EMIT _discoveryData->excluded(path); return true; } auto item = SyncFileItemPtr::create(); item->_file = path; item->_originalFile = path; - item->_instruction = CSYNC_INSTRUCTION_IGNORE; + item->setInstruction(CSYNC_INSTRUCTION_IGNORE); if (isSymlink) { /* Symbolic links are ignored. */ @@ -301,16 +245,22 @@ bool ProcessDirectoryJob::handleExcluded(const QString &path, const QString &loc } _childIgnored = true; - emit _discoveryData->itemDiscovered(item); + Q_EMIT _discoveryData->itemDiscovered(item); return true; } -void ProcessDirectoryJob::processFile(PathTuple path, +void ProcessDirectoryJob::processFile(const PathTuple &path, const LocalInfo &localEntry, const RemoteInfo &serverEntry, const SyncJournalFileRecord &dbEntry) { const char *hasServer = serverEntry.isValid() ? "true" : _queryServer == ParentNotChanged ? "db" : "false"; const char *hasLocal = localEntry.isValid() ? "true" : _queryLocal == ParentNotChanged ? "db" : "false"; + + // The code below is formatted like this ON PURPOSE: each field is db/local/server, one field + // per (source code) line, so it's easy to understand. + // The percent-encoded file name as it would be passed in an HTTP request. This can be used to + // debug Unicode encoding/normalization issues. + // clang-format off qCInfo(lcDisco).nospace() << "Processing " << path._original << " | valid: " << dbEntry.isValid() << "/" << hasLocal << "/" << hasServer << " | mtime: " << dbEntry._modtime << "/" << localEntry.modtime << "/" << serverEntry.modtime @@ -320,7 +270,9 @@ void ProcessDirectoryJob::processFile(PathTuple path, << " | perm: " << dbEntry._remotePerm << "//" << serverEntry.remotePerm << " | fileid: " << dbEntry._fileId << "//" << serverEntry.fileId << " | inode: " << dbEntry._inode << "/" << localEntry.inode << "/" - << " | type: " << dbEntry._type << "/" << localEntry.type << "/" << (serverEntry.isDirectory ? ItemTypeDirectory : ItemTypeFile); + << " | type: " << dbEntry._type << "/" << localEntry.type << "/" << (serverEntry.isDirectory ? ItemTypeDirectory : ItemTypeFile) + << " (" << QUrl::fromLocalFile(path._original).toEncoded().sliced(std::string_view("file:").length()) << ")"; + // clang-format on if (_discoveryData->isRenamed(path._original)) { qCDebug(lcDisco) << "Ignoring renamed"; @@ -333,15 +285,16 @@ void ProcessDirectoryJob::processFile(PathTuple path, item->_previousSize = dbEntry._fileSize; item->_previousModtime = dbEntry._modtime; - // The item shall only have this type if the db request for the virtual download - // was successful (like: no conflicting remote remove etc). This decision is done - // either in processFileAnalyzeRemoteInfo() or further down here. - if (item->_type == ItemTypeVirtualFileDownload) + if (item->_type == ItemTypeVirtualFileDownload) { + // The item shall only have this type if the db request for the virtual download + // was successful (like: no conflicting remote remove etc.). This decision is done + // either in processFileAnalyzeRemoteInfo() or further down here. item->_type = ItemTypeVirtualFile; - // Similarly db entries with a dehydration request denote a regular file - // until the request is processed. - if (item->_type == ItemTypeVirtualFileDehydration) + } else if (item->_type == ItemTypeVirtualFileDehydration) { + // Similarly db entries with a dehydration request denote a regular file + // until the request is processed. item->_type = ItemTypeFile; + } if (serverEntry.isValid()) { processFileAnalyzeRemoteInfo(item, path, localEntry, serverEntry, dbEntry); @@ -359,7 +312,7 @@ void ProcessDirectoryJob::processFile(PathTuple path, || localEntry.type == ItemTypeVirtualFileDownload) && (localEntry.isValid() || _queryLocal == ParentNotChanged)) { item->_direction = SyncFileItem::Down; - item->_instruction = CSYNC_INSTRUCTION_SYNC; + item->setInstruction(CSYNC_INSTRUCTION_SYNC); item->_type = ItemTypeVirtualFileDownload; } @@ -370,12 +323,12 @@ void ProcessDirectoryJob::processFile(PathTuple path, // Returns true if the checksum was successfully computed static bool computeLocalChecksum(const QByteArray &header, const QString &path, const SyncFileItemPtr &item) { - auto type = parseChecksumHeaderType(header); - if (!type.isEmpty()) { + const auto checksumHeader = ChecksumHeader::parseChecksumHeader(header); + if (checksumHeader.isValid()) { // TODO: compute async? - QByteArray checksum = ComputeChecksum::computeNowOnFile(path, type); + QByteArray checksum = ComputeChecksum::computeNowOnFile(path, checksumHeader.type()); if (!checksum.isEmpty()) { - item->_checksumHeader = makeChecksumHeader(type, checksum); + item->_checksumHeader = ChecksumHeader(checksumHeader.type(), checksum).makeChecksumHeader(); return true; } } @@ -386,6 +339,11 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( const SyncFileItemPtr &item, PathTuple path, const LocalInfo &localEntry, const RemoteInfo &serverEntry, const SyncJournalFileRecord &dbEntry) { + if (!_discoveryData->_syncOptions.isValid()) { + emit _discoveryData->fatalError(tr("the VFS instance is missing. Unable to continue.")); + return; + } + item->_checksumHeader = serverEntry.checksumHeader; item->_fileId = serverEntry.fileId; item->_remotePerm = serverEntry.remotePerm; @@ -397,19 +355,24 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( // Check for missing server data { QStringList missingData; - if (serverEntry.size == -1) - missingData.append(tr("size")); - if (serverEntry.remotePerm.isNull()) - missingData.append(tr("permissions")); - if (serverEntry.etag.isEmpty()) - missingData.append(tr("etag")); - if (serverEntry.fileId.isEmpty()) - missingData.append(tr("file id")); + if (serverEntry.size == -1) { + missingData.append(QStringLiteral("size")); + } + if (serverEntry.remotePerm.isNull()) { + missingData.append(QStringLiteral("permissions")); + } + if (serverEntry.etag.isEmpty()) { + missingData.append(QStringLiteral("etag")); + } + if (serverEntry.fileId.isEmpty()) { + missingData.append(QStringLiteral("id")); + } if (!missingData.isEmpty()) { - item->_instruction = CSYNC_INSTRUCTION_ERROR; + item->setInstruction(CSYNC_INSTRUCTION_ERROR); _childIgnored = true; item->_errorString = tr("server reported no %1").arg(missingData.join(QLatin1String(", "))); - emit _discoveryData->itemDiscovered(item); + qCWarning(lcDisco) << item->_errorString; + Q_EMIT _discoveryData->itemDiscovered(item); return; } } @@ -419,32 +382,39 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( if (serverEntry.isDirectory != dbEntry.isDirectory()) { // If the type of the entity changed, it's like NEW, but // needs to delete the other entity first. - item->_instruction = CSYNC_INSTRUCTION_TYPE_CHANGE; + item->setInstruction(CSYNC_INSTRUCTION_TYPE_CHANGE); item->_direction = SyncFileItem::Down; item->_modtime = serverEntry.modtime; item->_size = serverEntry.size; + if (dbEntry.isDirectory()) { + // TODO: move the decision to the backend + if (_discoveryData->_syncOptions.vfs()->mode() != Vfs::Off && _pinState != PinState::AlwaysLocal) { + item->_type = ItemTypeVirtualFile; + } + } } else if ((dbEntry._type == ItemTypeVirtualFileDownload || localEntry.type == ItemTypeVirtualFileDownload) && (localEntry.isValid() || _queryLocal == ParentNotChanged)) { - // The above check for the localEntry existing is important. Otherwise it breaks + // The above check for the localEntry existing is important. Otherwise, it breaks // the case where a file is moved and simultaneously tagged for download in the db. item->_direction = SyncFileItem::Down; - item->_instruction = CSYNC_INSTRUCTION_SYNC; + item->setInstruction(CSYNC_INSTRUCTION_SYNC); item->_type = ItemTypeVirtualFileDownload; - } else if (dbEntry._etag != serverEntry.etag) { + item->_size = serverEntry.size; + } else if (dbEntry._etag != serverEntry.etag.toUtf8()) { item->_direction = SyncFileItem::Down; item->_modtime = serverEntry.modtime; item->_size = serverEntry.size; if (serverEntry.isDirectory) { OC_ENFORCE(dbEntry.isDirectory()); - item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; + item->setInstruction(CSYNC_INSTRUCTION_UPDATE_METADATA); } else if (!localEntry.isValid() && _queryLocal != ParentNotChanged) { // Deleted locally, changed on server - item->_instruction = CSYNC_INSTRUCTION_NEW; + item->setInstruction(CSYNC_INSTRUCTION_NEW); } else { - item->_instruction = CSYNC_INSTRUCTION_SYNC; + item->setInstruction(CSYNC_INSTRUCTION_SYNC); } } else if (dbEntry._remotePerm != serverEntry.remotePerm || dbEntry._fileId != serverEntry.fileId) { - item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; + item->setInstruction(CSYNC_INSTRUCTION_UPDATE_METADATA); item->_direction = SyncFileItem::Down; } else { processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, ParentNotChanged); @@ -458,33 +428,17 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( // Unknown in db: new file on the server Q_ASSERT(!dbEntry.isValid()); - item->_instruction = CSYNC_INSTRUCTION_NEW; + item->setInstruction(CSYNC_INSTRUCTION_NEW); item->_direction = SyncFileItem::Down; item->_modtime = serverEntry.modtime; item->_size = serverEntry.size; auto postProcessServerNew = [=]() mutable { - if (item->isDirectory()) { - _pendingAsyncJobs++; - _discoveryData->checkSelectiveSyncNewFolder(path._server, serverEntry.remotePerm, - [=](bool result) { - --_pendingAsyncJobs; - if (!result) { - processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer); - } - QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); - }); - return; - } // Turn new remote files into virtual files if the option is enabled. - auto &opts = _discoveryData->_syncOptions; - if (!localEntry.isValid() - && item->_type == ItemTypeFile - && opts._vfs->mode() != Vfs::Off + // TODO: move the decision to the backend + if (!localEntry.isValid() && item->_type == ItemTypeFile && _discoveryData->_syncOptions.vfs()->mode() != Vfs::Off && _pinState != PinState::AlwaysLocal) { item->_type = ItemTypeVirtualFile; - if (isVfsWithSuffix()) - addVirtualFileSuffix(path._original); } processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer); }; @@ -531,7 +485,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( done = true; return; } - if (!serverEntry.isDirectory && base._etag != serverEntry.etag) { + if (!serverEntry.isDirectory && base._etag != serverEntry.etag.toUtf8()) { /* File with different etag, don't do a rename, but download the file again */ qCInfo(lcDisco, "file etag different, not a rename"); done = true; @@ -579,7 +533,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( _discoveryData->_renamedItemsRemote.insert(originalPath, path._target); item->_modtime = base._modtime; item->_inode = base._inode; - item->_instruction = CSYNC_INSTRUCTION_RENAME; + item->setInstruction(CSYNC_INSTRUCTION_RENAME); item->_direction = SyncFileItem::Down; item->_renameTarget = path._target; item->_file = adjustedOriginalPath; @@ -594,26 +548,33 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( done = true; } else { // we need to make a request to the server to know that the original file is deleted on the server + if (_discoveryData->_account == nullptr) { + Q_EMIT _discoveryData->fatalError(tr("account was deleted. Unable to continue")); + return; + } _pendingAsyncJobs++; - auto job = new RequestEtagJob(_discoveryData->_account, _discoveryData->_remoteFolder + originalPath, this); - connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult &etag) mutable { + auto job = new RequestEtagJob(_discoveryData->_account, _discoveryData->_baseUrl, _discoveryData->_remoteFolder + originalPath, this); + connect(job, &RequestEtagJob::finishedSignal, this, [=]() mutable { _pendingAsyncJobs--; QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); - if (etag || etag.error().code != 404 || + if (job->httpStatusCode() == 207 || // Somehow another item claimed this original path, consider as if it existed _discoveryData->isRenamed(originalPath)) { // If the file exist or if there is another error, consider it is a new file. postProcessServerNew(); return; - } - - // The file do not exist, it is a rename + } else if (job->httpStatusCode() == 404) { + // The file do not exist, it is a rename - // In case the deleted item was discovered in parallel - _discoveryData->findAndCancelDeletedJob(originalPath); + // In case the deleted item was discovered in parallel + _discoveryData->findAndCancelDeletedJob(originalPath); - postProcessRename(path); - processFileFinalize(item, path, item->isDirectory(), item->_instruction == CSYNC_INSTRUCTION_RENAME ? NormalQuery : ParentDontExist, _queryServer); + postProcessRename(path); + processFileFinalize( + item, path, item->isDirectory(), item->instruction() == CSYNC_INSTRUCTION_RENAME ? NormalQuery : ParentDontExist, _queryServer); + return; + } + Q_EMIT _discoveryData->fatalError(tr("Error while doing a rename, unhandled status code: %1").arg(job->httpStatusCode())); }); job->start(); done = true; // Ideally, if the origin still exist on the server, we should continue searching... but that'd be difficult @@ -628,50 +589,121 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo( return; // We went async } - if (item->_instruction == CSYNC_INSTRUCTION_NEW) { + if (item->instruction() == CSYNC_INSTRUCTION_NEW) { postProcessServerNew(); return; } processFileAnalyzeLocalInfo(item, path, localEntry, serverEntry, dbEntry, _queryServer); } +bool ProcessDirectoryJob::isMove( + const SyncFileItemPtr &item, const PathTuple &path, const LocalInfo &localEntry, const SyncJournalFileRecord &base, const QString &originalPath) const +{ + // Go from "cheap" to check up to "expensive" to check + + if (!base.isValid()) { + qCInfo(lcDisco) << "Not a move, no item in db with inode" << localEntry.inode; + return false; + } + + // Type changes (file->dir or vice-versa) are not moves + if (base.isDirectory() != item->isDirectory()) { + qCInfo(lcDisco) << "Not a move, types don't match" << base._type << item->_type << localEntry.type; + return false; + } + + // Directories and virtual files don't need size/mtime equality + if (!localEntry.isDirectory && !base.isVirtualFile() && (base._modtime != localEntry.modtime || base._fileSize != localEntry.size)) { + qCInfo(lcDisco) << "Not a move, mtime or size differs, " + << "modtime:" << base._modtime << localEntry.modtime << ", " + << "size:" << base._fileSize << localEntry.size; + return false; + } + + if (_discoveryData->isRenamed(originalPath)) { + qCInfo(lcDisco) << "Not a move, base path already renamed"; + return false; + } + + // If the old file still exists, it is not a move + if (QFile::exists(_discoveryData->_localDir + originalPath)) { + // However...: + if ( + // Exception 1: If the rename only changes case (like "foo" -> "Foo"), the + // old filename might still point to the same file on a case-insensitive filesystem + !(Utility::fsCasePreservingButCaseInsensitive() && originalPath.compare(path._local, Qt::CaseInsensitive) == 0 && originalPath != path._local) + // Exception 2: normalization change + && !( + originalPath != path._local && originalPath.normalized(QString::NormalizationForm_C) == path._local.normalized(QString::NormalizationForm_C))) { + qCInfo(lcDisco) << "Not a move, base file still exists at" << originalPath; + return false; + } + } + + // Verify the checksum where possible + if (!base._checksumHeader.isEmpty() && item->_type == ItemTypeFile && base._type == ItemTypeFile) { + if (computeLocalChecksum(base._checksumHeader, _discoveryData->_localDir + path._original, item)) { + qCInfo(lcDisco) << "checking checksum of potential rename " << path._original << item->_checksumHeader << base._checksumHeader; + if (item->_checksumHeader != base._checksumHeader) { + qCInfo(lcDisco) << "Not a move, checksums differ"; + return false; + } + } else { + // We cannot calculate the checksum, so we cannot assume the contents of the old file and the new file are the same. + return false; + } + } + + return true; +} + void ProcessDirectoryJob::processFileAnalyzeLocalInfo( - const SyncFileItemPtr &item, PathTuple path, const LocalInfo &localEntry, + const SyncFileItemPtr &item, const PathTuple &path, const LocalInfo &localEntry, const RemoteInfo &serverEntry, const SyncJournalFileRecord &dbEntry, QueryMode recurseQueryServer) { - bool noServerEntry = (_queryServer != ParentNotChanged && !serverEntry.isValid()) - || (_queryServer == ParentNotChanged && !dbEntry.isValid()); + if (!_discoveryData->_syncOptions.isValid()) { + emit _discoveryData->fatalError(tr("the VFS instance is missing. Unable to continue.")); + return; + } + const bool noServerEntry = (_queryServer != ParentNotChanged && !serverEntry.isValid()) || (_queryServer == ParentNotChanged && !dbEntry.isValid()); - if (noServerEntry) + if (noServerEntry) { recurseQueryServer = ParentDontExist; + } - bool serverModified = item->_instruction == CSYNC_INSTRUCTION_NEW || item->_instruction == CSYNC_INSTRUCTION_SYNC - || item->_instruction == CSYNC_INSTRUCTION_RENAME || item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE; + const bool serverModified = [&] { + bool modifiedOnServer = + (item->instruction() & (CSYNC_INSTRUCTION_NEW | CSYNC_INSTRUCTION_SYNC | CSYNC_INSTRUCTION_RENAME | CSYNC_INSTRUCTION_TYPE_CHANGE)); - // Decay server modifications to UPDATE_METADATA if the local virtual exists - bool hasLocalVirtual = localEntry.isVirtualFile || (_queryLocal == ParentNotChanged && dbEntry.isVirtualFile()); - bool virtualFileDownload = item->_type == ItemTypeVirtualFileDownload; - if (serverModified && !virtualFileDownload && hasLocalVirtual) { - item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; - serverModified = false; - item->_type = ItemTypeVirtualFile; - } + // Decay server modifications to UPDATE_METADATA if the local virtual exists + const bool hasLocalVirtual = localEntry.isVirtualFile || (_queryLocal == ParentNotChanged && dbEntry.isVirtualFile()); + const bool virtualFileDownload = item->_type == ItemTypeVirtualFileDownload || item->instruction() == CSYNC_INSTRUCTION_TYPE_CHANGE; + if (modifiedOnServer && !virtualFileDownload && hasLocalVirtual) { + item->setInstruction(CSYNC_INSTRUCTION_UPDATE_METADATA); + modifiedOnServer = false; + item->_type = ItemTypeVirtualFile; + } - if (dbEntry.isVirtualFile() && !virtualFileDownload) - item->_type = ItemTypeVirtualFile; + if (dbEntry.isVirtualFile() && !virtualFileDownload) { + item->_type = ItemTypeVirtualFile; + } + return modifiedOnServer; + }(); _childModified |= serverModified; - auto finalize = [&] { + auto finalize = [item, localEntry, serverEntry, this](const PathTuple &path, QueryMode recurseQueryServer) { bool recurse = item->isDirectory() || localEntry.isDirectory || serverEntry.isDirectory; // Even if we have a local directory: If the remote is a file that's propagated as a // conflict we don't need to recurse into it. (local c1.owncloud, c1/ ; remote: c1) - if (item->_instruction == CSYNC_INSTRUCTION_CONFLICT && !item->isDirectory()) + if (item->instruction() == CSYNC_INSTRUCTION_CONFLICT && !item->isDirectory()) recurse = false; if (_queryLocal != NormalQuery && _queryServer != NormalQuery) recurse = false; - auto recurseQueryLocal = _queryLocal == ParentNotChanged ? ParentNotChanged : localEntry.isDirectory || item->_instruction == CSYNC_INSTRUCTION_RENAME ? NormalQuery : ParentDontExist; + auto recurseQueryLocal = _queryLocal == ParentNotChanged ? ParentNotChanged + : localEntry.isDirectory || item->instruction() == CSYNC_INSTRUCTION_RENAME ? NormalQuery + : ParentDontExist; processFileFinalize(item, path, recurse, recurseQueryLocal, recurseQueryServer); }; @@ -680,12 +712,12 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( // Not modified locally (ParentNotChanged) if (noServerEntry) { // not on the server: Removed on the server, delete locally - item->_instruction = CSYNC_INSTRUCTION_REMOVE; + item->setInstruction(CSYNC_INSTRUCTION_REMOVE); item->_direction = SyncFileItem::Down; } else if (dbEntry._type == ItemTypeVirtualFileDehydration) { // dehydration requested item->_direction = SyncFileItem::Down; - item->_instruction = CSYNC_INSTRUCTION_SYNC; + item->setInstruction(CSYNC_INSTRUCTION_SYNC); item->_type = ItemTypeVirtualFileDehydration; } } else if (noServerEntry) { @@ -693,93 +725,49 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( qCInfo(lcDisco) << "Stale DB entry"; _discoveryData->_statedb->deleteFileRecord(path._original, true); return; - } else if (dbEntry._type == ItemTypeVirtualFile && isVfsWithSuffix()) { - // If the virtual file is removed, recreate it. - // This is a precaution since the suffix files don't look like the real ones - // and we don't want users to accidentally delete server data because they - // might not expect that deleting the placeholder will have a remote effect. - item->_instruction = CSYNC_INSTRUCTION_NEW; - item->_direction = SyncFileItem::Down; - item->_type = ItemTypeVirtualFile; } else if (!serverModified) { // Removed locally: also remove on the server. if (!dbEntry._serverHasIgnoredFiles) { - item->_instruction = CSYNC_INSTRUCTION_REMOVE; + item->setInstruction(CSYNC_INSTRUCTION_REMOVE); item->_direction = SyncFileItem::Up; } } - finalize(); + finalize(path, recurseQueryServer); return; } Q_ASSERT(localEntry.isValid()); item->_inode = localEntry.inode; - if (dbEntry.isValid()) { - bool typeChange = localEntry.isDirectory != dbEntry.isDirectory(); + const bool typeChange = localEntry.isDirectory != dbEntry.isDirectory(); if (!typeChange && localEntry.isVirtualFile) { if (noServerEntry) { - item->_instruction = CSYNC_INSTRUCTION_REMOVE; + item->setInstruction(CSYNC_INSTRUCTION_REMOVE); item->_direction = SyncFileItem::Down; - } else if (!dbEntry.isVirtualFile() && isVfsWithSuffix()) { - // If we find what looks to be a spurious "abc.owncloud" the base file "abc" - // might have been renamed to that. Make sure that the base file is not - // deleted from the server. - if (dbEntry._modtime == localEntry.modtime && dbEntry._fileSize == localEntry.size) { - qCInfo(lcDisco) << "Base file was renamed to virtual file:" << item->_file; - item->_direction = SyncFileItem::Down; - item->_instruction = CSYNC_INSTRUCTION_SYNC; - item->_type = ItemTypeVirtualFileDehydration; - addVirtualFileSuffix(item->_file); - item->_renameTarget = item->_file; - } else { - qCInfo(lcDisco) << "Virtual file with non-virtual db entry, ignoring:" << item->_file; - item->_instruction = CSYNC_INSTRUCTION_IGNORE; - } } } else if (!typeChange && ((dbEntry._modtime == localEntry.modtime && dbEntry._fileSize == localEntry.size) || localEntry.isDirectory)) { // Local file unchanged. if (noServerEntry) { - item->_instruction = CSYNC_INSTRUCTION_REMOVE; + item->setInstruction(CSYNC_INSTRUCTION_REMOVE); item->_direction = SyncFileItem::Down; } else if (dbEntry._type == ItemTypeVirtualFileDehydration || localEntry.type == ItemTypeVirtualFileDehydration) { item->_direction = SyncFileItem::Down; - item->_instruction = CSYNC_INSTRUCTION_SYNC; + item->setInstruction(CSYNC_INSTRUCTION_SYNC); item->_type = ItemTypeVirtualFileDehydration; - } else if (!serverModified - && (dbEntry._inode != localEntry.inode - || _discoveryData->_syncOptions._vfs->needsMetadataUpdate(*item))) { - item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; + } else if (!serverModified && (dbEntry._inode != localEntry.inode || _discoveryData->_syncOptions.vfs()->needsMetadataUpdate(*item))) { + item->setInstruction(CSYNC_INSTRUCTION_UPDATE_METADATA); item->_direction = SyncFileItem::Down; } - } else if (!typeChange && isVfsWithSuffix() - && dbEntry.isVirtualFile() && !localEntry.isVirtualFile - && dbEntry._inode == localEntry.inode - && dbEntry._modtime == localEntry.modtime - && localEntry.size == 1) { - // A suffix vfs file can be downloaded by renaming it to remove the suffix. - // This check leaks some details of VfsSuffix, particularly the size of placeholders. - item->_direction = SyncFileItem::Down; - if (noServerEntry) { - item->_instruction = CSYNC_INSTRUCTION_REMOVE; - item->_type = ItemTypeFile; - } else { - item->_instruction = CSYNC_INSTRUCTION_SYNC; - item->_type = ItemTypeVirtualFileDownload; - item->_previousSize = 1; - } - } else if (serverModified - || (isVfsWithSuffix() && dbEntry.isVirtualFile())) { + } else if (serverModified) { // There's a local change and a server change: Conflict! - // Alternatively, this might be a suffix-file that's virtual in the db but - // not locally. These also become conflicts. For in-place placeholders that's + // For in-place placeholders that's // not necessary: they could be replaced by real files and should then trigger // a regular SYNC upwards when there's no server change. processFileConflict(item, path, localEntry, serverEntry, dbEntry); } else if (typeChange) { - item->_instruction = CSYNC_INSTRUCTION_TYPE_CHANGE; + item->setInstruction(CSYNC_INSTRUCTION_TYPE_CHANGE); item->_direction = SyncFileItem::Up; item->_checksumHeader.clear(); item->_size = localEntry.size; @@ -788,10 +776,10 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( _childModified = true; } else { // Local file was changed - item->_instruction = CSYNC_INSTRUCTION_SYNC; + item->setInstruction(CSYNC_INSTRUCTION_SYNC); if (noServerEntry) { // Special case! deleted on server, modified on client, the instruction is then NEW - item->_instruction = CSYNC_INSTRUCTION_NEW; + item->setInstruction(CSYNC_INSTRUCTION_NEW); } item->_direction = SyncFileItem::Up; item->_checksumHeader.clear(); @@ -806,12 +794,12 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( if (computeLocalChecksum(dbEntry._checksumHeader, _discoveryData->_localDir + path._local, item) && item->_checksumHeader == dbEntry._checksumHeader) { qCInfo(lcDisco) << "NOTE: Checksums are identical, file did not actually change: " << path._local; - item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; + item->setInstruction(CSYNC_INSTRUCTION_UPDATE_METADATA); } } } - finalize(); + finalize(path, recurseQueryServer); return; } @@ -820,18 +808,18 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( if (localEntry.isVirtualFile && !noServerEntry) { // Somehow there is a missing DB entry while the virtual file already exists. // The instruction should already be set correctly. - OC_ASSERT(item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA); + OC_ASSERT(item->instruction() == CSYNC_INSTRUCTION_UPDATE_METADATA); OC_ASSERT(item->_type == ItemTypeVirtualFile); - finalize(); + finalize(path, recurseQueryServer); return; } else if (serverModified) { processFileConflict(item, path, localEntry, serverEntry, dbEntry); - finalize(); + finalize(path, recurseQueryServer); return; } // New local file or rename - item->_instruction = CSYNC_INSTRUCTION_NEW; + item->setInstruction(CSYNC_INSTRUCTION_NEW); item->_direction = SyncFileItem::Up; item->_checksumHeader.clear(); item->_size = localEntry.size; @@ -839,83 +827,56 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( item->_type = localEntry.isDirectory ? ItemTypeDirectory : localEntry.isVirtualFile ? ItemTypeVirtualFile : ItemTypeFile; _childModified = true; - auto postProcessLocalNew = [item, localEntry, path, this]() { + auto postProcessLocalNew = [item, localEntry, this](const PathTuple &path) { if (localEntry.isVirtualFile) { - const bool isPlaceHolder = _discoveryData->_syncOptions._vfs->isDehydratedPlaceholder(_discoveryData->_localDir + path._local); + const bool isPlaceHolder = _discoveryData->_syncOptions.vfs()->isDehydratedPlaceholder(_discoveryData->_localDir + path._local); if (isPlaceHolder) { qCWarning(lcDisco) << "Wiping virtual file without db entry for" << path._local; - item->_instruction = CSYNC_INSTRUCTION_REMOVE; + item->setInstruction(CSYNC_INSTRUCTION_REMOVE); item->_direction = SyncFileItem::Down; } else { qCWarning(lcDisco) << "Virtual file without db entry for" << path._local << "but looks odd, keeping"; - item->_instruction = CSYNC_INSTRUCTION_IGNORE; + item->setInstruction(CSYNC_INSTRUCTION_IGNORE); } } }; // Check if it is a move - OCC::SyncJournalFileRecord base; + SyncJournalFileRecord base; if (!_discoveryData->_statedb->getFileRecordByInode(localEntry.inode, &base)) { dbError(); return; } const auto originalPath = QString::fromUtf8(base._path); - // Function to gradually check conditions for accepting a move-candidate - auto moveCheck = [&]() { - if (!base.isValid()) { - qCInfo(lcDisco) << "Not a move, no item in db with inode" << localEntry.inode; - return false; - } - if (base.isDirectory() != item->isDirectory()) { - qCInfo(lcDisco) << "Not a move, types don't match" << base._type << item->_type << localEntry.type; - return false; - } - // Directories and virtual files don't need size/mtime equality - if (!localEntry.isDirectory && !base.isVirtualFile() - && (base._modtime != localEntry.modtime || base._fileSize != localEntry.size)) { - qCInfo(lcDisco) << "Not a move, mtime or size differs, " - << "modtime:" << base._modtime << localEntry.modtime << ", " - << "size:" << base._fileSize << localEntry.size; - return false; - } - - // The old file must have been deleted. - if (QFile::exists(_discoveryData->_localDir + originalPath) - // Exception: If the rename changes case only (like "foo" -> "Foo") the - // old filename might still point to the same file. - && !(Utility::fsCasePreserving() - && originalPath.compare(path._local, Qt::CaseInsensitive) == 0 - && originalPath != path._local)) { - qCInfo(lcDisco) << "Not a move, base file still exists at" << originalPath; - return false; - } - - // Verify the checksum where possible - if (!base._checksumHeader.isEmpty() && item->_type == ItemTypeFile && base._type == ItemTypeFile) { - if (computeLocalChecksum(base._checksumHeader, _discoveryData->_localDir + path._original, item)) { - qCInfo(lcDisco) << "checking checksum of potential rename " << path._original << item->_checksumHeader << base._checksumHeader; - if (item->_checksumHeader != base._checksumHeader) { - qCInfo(lcDisco) << "Not a move, checksums differ"; - return false; - } - } - } - - if (_discoveryData->isRenamed(originalPath)) { - qCInfo(lcDisco) << "Not a move, base path already renamed"; - return false; - } - - return true; - }; - // If it's not a move it's just a local-NEW - if (!moveCheck()) { - postProcessLocalNew(); - finalize(); - return; + if (!isMove(item, path, localEntry, base, originalPath)) { + postProcessLocalNew(path); + finalize(path, recurseQueryServer); + return; + } + + // File size, mod time, checksum the same, but the name is different. Now check if the move is + // *only* a normalization change, not a capitalization change. + if (originalPath.normalized(QString::NormalizationForm_C) == path._local.normalized(QString::NormalizationForm_C)) { + // Issue a local rename: this is a technical encoding change, and not visible to end-users + QString adjustedOriginalPath = _discoveryData->adjustRenamedPath(originalPath, SyncFileItem::Down); + _discoveryData->_renamedItemsLocal.insert(originalPath, path._local); + item->_modtime = base._modtime; + item->_inode = base._inode; + item->_etag = base._etag; + item->_checksumHeader = base._checksumHeader; + item->_fileId = base._fileId; + item->_remotePerm = base._remotePerm; + item->setInstruction(CSYNC_INSTRUCTION_RENAME); + item->_direction = SyncFileItem::Down; + item->_renameTarget = adjustedOriginalPath; + item->_file = path._local; + item->_originalFile = originalPath; + qCInfo(lcDisco) << "Rename detected (down) " << item->_file << " -> " << item->_renameTarget; + finalize(path, recurseQueryServer); + return; } // Check local permission if we are allowed to put move the file here @@ -929,8 +890,8 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( // If we can create the destination, do that. // Permission errors on the destination will be handled by checkPermissions later. - postProcessLocalNew(); - finalize(); + postProcessLocalNew(path); + finalize(path, recurseQueryServer); // If the destination upload will work, we're fine with the source deletion. // If the source deletion can't work, checkPermissions will error. @@ -949,16 +910,17 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( } else { // Signal to future checkPermissions() to forbid the REMOVE and set to restore instead qCInfo(lcDisco) << "Preventing future remove on source" << originalPath; - _discoveryData->_forbiddenDeletes[originalPath + QLatin1Char('/')] = true; + _discoveryData->_forbiddenDeletes.insert(originalPath + QLatin1Char('/')); } return; } auto wasDeletedOnClient = _discoveryData->findAndCancelDeletedJob(originalPath); - auto processRename = [item, originalPath, base, this](PathTuple &path) { + auto processRename = [item, originalPath, base, this](PathTuple path) { auto adjustedOriginalPath = _discoveryData->adjustRenamedPath(originalPath, SyncFileItem::Down); _discoveryData->_renamedItemsLocal.insert(originalPath, path._target); + // TODO: move to SyncFileItem so its easier to refactor if item changes in any way... item->_renameTarget = path._target; path._server = adjustedOriginalPath; item->_file = path._server; @@ -966,64 +928,64 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( item->_originalFile = path._original; item->_modtime = base._modtime; item->_inode = base._inode; - item->_instruction = CSYNC_INSTRUCTION_RENAME; + item->setInstruction(CSYNC_INSTRUCTION_RENAME); item->_direction = SyncFileItem::Up; item->_fileId = base._fileId; item->_remotePerm = base._remotePerm; - item->_etag = base._etag; + item->_etag = QString::fromUtf8(base._etag); item->_type = base._type; // Discard any download/dehydrate tags on the base file. // They could be preserved and honored in a follow-up sync, // but it complicates handling a lot and will happen rarely. - if (item->_type == ItemTypeVirtualFileDownload) + if (item->_type == ItemTypeVirtualFileDownload) { item->_type = ItemTypeVirtualFile; - if (item->_type == ItemTypeVirtualFileDehydration) + } else if (item->_type == ItemTypeVirtualFileDehydration) { item->_type = ItemTypeFile; + } qCInfo(lcDisco) << "Rename detected (up) " << item->_file << " -> " << item->_renameTarget; + return path; }; if (wasDeletedOnClient.first) { - recurseQueryServer = wasDeletedOnClient.second == base._etag ? ParentNotChanged : NormalQuery; - processRename(path); + finalize(processRename(path), wasDeletedOnClient.second.toUtf8() == base._etag ? ParentNotChanged : NormalQuery); } else { + if (_discoveryData->_account == nullptr) { + Q_EMIT _discoveryData->fatalError(tr("account was deleted. Unable to continue")); + return; + } + // We must query the server to know if the etag has not changed _pendingAsyncJobs++; QString serverOriginalPath = _discoveryData->_remoteFolder + _discoveryData->adjustRenamedPath(originalPath, SyncFileItem::Down); - if (base.isVirtualFile() && isVfsWithSuffix()) { - serverOriginalPath = chopVirtualFileSuffix(serverOriginalPath); - } - auto job = new RequestEtagJob(_discoveryData->_account, serverOriginalPath, this); - connect(job, &RequestEtagJob::finishedWithResult, this, [=](const HttpResult &etag) mutable { - if (!etag || (etag.get() != base._etag && !item->isDirectory()) || _discoveryData->isRenamed(originalPath)) { - qCInfo(lcDisco) << "Can't rename because the etag has changed or the directory is gone" << originalPath; - // Can't be a rename, leave it as a new. - postProcessLocalNew(); - } else { - // In case the deleted item was discovered in parallel - _discoveryData->findAndCancelDeletedJob(originalPath); - processRename(path); - recurseQueryServer = etag.get() == base._etag ? ParentNotChanged : NormalQuery; - } - processFileFinalize(item, path, item->isDirectory(), NormalQuery, recurseQueryServer); - _pendingAsyncJobs--; - QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); - }); + auto job = new RequestEtagJob(_discoveryData->_account.get(), _discoveryData->_baseUrl, serverOriginalPath, this); + connect(job, &RequestEtagJob::finishedSignal, this, + [job, recurseQueryServer, path = path, postProcessLocalNew, processRename, base, item, originalPath, this] { + if (job->httpStatusCode() == 404 || (job->etag().toUtf8() != base._etag && !item->isDirectory()) || _discoveryData->isRenamed(originalPath)) { + qCInfo(lcDisco) << "Can't rename because the etag has changed or the directory is gone" << originalPath; + // Can't be a rename, leave it as a new. + postProcessLocalNew(path); + processFileFinalize(item, path, item->isDirectory(), NormalQuery, recurseQueryServer); + } else { + // In case the deleted item was discovered in parallel + _discoveryData->findAndCancelDeletedJob(originalPath); + processFileFinalize(item, processRename(path), item->isDirectory(), NormalQuery, job->etag().toUtf8() == base._etag ? ParentNotChanged : NormalQuery); + } + _pendingAsyncJobs--; + QTimer::singleShot(0, _discoveryData, &DiscoveryPhase::scheduleMoreJobs); + }); job->start(); - return; } - - finalize(); } -void ProcessDirectoryJob::processFileConflict(const SyncFileItemPtr &item, ProcessDirectoryJob::PathTuple path, const LocalInfo &localEntry, const RemoteInfo &serverEntry, const SyncJournalFileRecord &dbEntry) +void ProcessDirectoryJob::processFileConflict(const SyncFileItemPtr &item, const ProcessDirectoryJob::PathTuple &path, const LocalInfo &localEntry, const RemoteInfo &serverEntry, const SyncJournalFileRecord &dbEntry) { item->_previousSize = localEntry.size; item->_previousModtime = localEntry.modtime; if (serverEntry.isDirectory && localEntry.isDirectory) { // Folders of the same path are always considered equals - item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; + item->setInstruction(CSYNC_INSTRUCTION_UPDATE_METADATA); return; } @@ -1034,8 +996,14 @@ void ProcessDirectoryJob::processFileConflict(const SyncFileItemPtr &item, Proce // If there's no content hash, use heuristics if (serverEntry.checksumHeader.isEmpty()) { // If the size or mtime is different, it's definitely a conflict. - bool isConflict = (serverEntry.size != localEntry.size) || (serverEntry.modtime != localEntry.modtime); - + bool sizeDiffers = serverEntry.size != localEntry.size; + bool modTimeDiffers = serverEntry.modtime != localEntry.modtime; + bool isConflict = sizeDiffers || modTimeDiffers; + if (isConflict) { + qCDebug(lcDisco) << serverEntry.name << ": detected conflict: size difference: " << sizeDiffers << "mtime difference: " << modTimeDiffers; + } else { + qCDebug(lcDisco) << serverEntry.name << ": no conflict detected"; + } // It could be a conflict even if size and mtime match! // // In older client versions we always treated these cases as a @@ -1049,23 +1017,21 @@ void ProcessDirectoryJob::processFileConflict(const SyncFileItemPtr &item, Proce // In particular this kind of NEW/NEW situation with identical // sizes and mtimes pops up when the local database is lost for // whatever reason. - item->_instruction = isConflict ? CSYNC_INSTRUCTION_CONFLICT : CSYNC_INSTRUCTION_UPDATE_METADATA; + item->setInstruction(isConflict ? CSYNC_INSTRUCTION_CONFLICT : CSYNC_INSTRUCTION_UPDATE_METADATA); item->_direction = isConflict ? SyncFileItem::None : SyncFileItem::Down; return; } // Do we have an UploadInfo for this? // Maybe the Upload was completed, but the connection was broken just before - // we recieved the etag (Issue #5106) + // we received the etag (Issue #5106) auto up = _discoveryData->_statedb->getUploadInfo(path._original); if (up._valid && up._contentChecksum == serverEntry.checksumHeader) { // Solve the conflict into an upload, or update meta data - item->_instruction = up._modtime == localEntry.modtime && up._size == localEntry.size - ? CSYNC_INSTRUCTION_UPDATE_METADATA - : CSYNC_INSTRUCTION_SYNC; + item->setInstruction(up._modtime == localEntry.modtime && up._size == localEntry.size ? CSYNC_INSTRUCTION_UPDATE_METADATA : CSYNC_INSTRUCTION_SYNC); item->_direction = SyncFileItem::Up; - if (item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA) { + if (item->instruction() == CSYNC_INSTRUCTION_UPDATE_METADATA) { // Update the etag and other server metadata in the journal already Q_ASSERT(item->_file == path._original); Q_ASSERT(item->_size == serverEntry.size); @@ -1080,7 +1046,7 @@ void ProcessDirectoryJob::processFileConflict(const SyncFileItemPtr &item, Proce } // Rely on content hash comparisons to optimize away non-conflicts inside the job - item->_instruction = CSYNC_INSTRUCTION_CONFLICT; + item->setInstruction(CSYNC_INSTRUCTION_CONFLICT); item->_direction = SyncFileItem::None; } @@ -1088,37 +1054,20 @@ void ProcessDirectoryJob::processFileFinalize( const SyncFileItemPtr &item, PathTuple path, bool recurse, QueryMode recurseQueryLocal, QueryMode recurseQueryServer) { - // Adjust target path for virtual-suffix files - if (isVfsWithSuffix()) { - if (item->_type == ItemTypeVirtualFile) { - addVirtualFileSuffix(path._target); - if (item->_instruction == CSYNC_INSTRUCTION_RENAME) - addVirtualFileSuffix(item->_renameTarget); - else - addVirtualFileSuffix(item->_file); - } else if (item->_type == ItemTypeVirtualFileDehydration - && item->_instruction == CSYNC_INSTRUCTION_SYNC) { - if (item->_renameTarget.isEmpty()) { - item->_renameTarget = item->_file; - addVirtualFileSuffix(item->_renameTarget); - } - } - } - - if (path._original != path._target && (item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA || item->_instruction == CSYNC_INSTRUCTION_NONE)) { - OC_ASSERT(_dirItem && _dirItem->_instruction == CSYNC_INSTRUCTION_RENAME); + if (path._original != path._target && (item->instruction() & (CSYNC_INSTRUCTION_UPDATE_METADATA | CSYNC_INSTRUCTION_NONE))) { + OC_ASSERT(_dirItem && _dirItem->instruction() == CSYNC_INSTRUCTION_RENAME); // This is because otherwise subitems are not updated! (ideally renaming a directory could // update the database for all items! See PropagateDirectory::slotSubJobsFinished) - item->_instruction = CSYNC_INSTRUCTION_RENAME; + item->setInstruction(CSYNC_INSTRUCTION_RENAME); item->_renameTarget = path._target; item->_direction = _dirItem->_direction; } - qCInfo(lcDisco) << "Discovered" << item->_file << item->_instruction << item->_direction << item->_type; + qCInfo(lcDisco) << "Discovered" << item->_file << item->instruction() << item->_direction << item->_type; - if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_SYNC) - item->_instruction = CSYNC_INSTRUCTION_UPDATE_METADATA; - bool removed = item->_instruction == CSYNC_INSTRUCTION_REMOVE; + if (item->isDirectory() && item->instruction() == CSYNC_INSTRUCTION_SYNC) + item->setInstruction(CSYNC_INSTRUCTION_UPDATE_METADATA); + bool removed = item->instruction() == CSYNC_INSTRUCTION_REMOVE; if (checkPermissions(item)) { if (item->_isRestoration && item->isDirectory()) recurse = true; @@ -1137,10 +1086,10 @@ void ProcessDirectoryJob::processFileFinalize( } else { if (removed // For the purpose of rename deletion, restored deleted placeholder is as if it was deleted - || (item->_type == ItemTypeVirtualFile && item->_instruction == CSYNC_INSTRUCTION_NEW)) { + || (item->_type == ItemTypeVirtualFile && item->instruction() == CSYNC_INSTRUCTION_NEW)) { _discoveryData->_deletedItem[path._original] = item; } - emit _discoveryData->itemDiscovered(item); + Q_EMIT _discoveryData->itemDiscovered(item); } } @@ -1156,23 +1105,23 @@ void ProcessDirectoryJob::processBlacklisted(const PathTuple &path, const OCC::L item->_inode = localEntry.inode; item->_isSelectiveSync = true; if (dbEntry.isValid() && ((dbEntry._modtime == localEntry.modtime && dbEntry._fileSize == localEntry.size) || (localEntry.isDirectory && dbEntry.isDirectory()))) { - item->_instruction = CSYNC_INSTRUCTION_REMOVE; + item->setInstruction(CSYNC_INSTRUCTION_REMOVE); item->_direction = SyncFileItem::Down; } else { - item->_instruction = CSYNC_INSTRUCTION_IGNORE; + item->setInstruction(CSYNC_INSTRUCTION_IGNORE); item->_status = SyncFileItem::FileIgnored; - item->_errorString = tr("Ignored because of the \"choose what to sync\" blacklist"); + item->_errorString = tr("SelectiveSync: Ignored because its path is deselected"); _childIgnored = true; } - qCInfo(lcDisco) << "Discovered (blacklisted) " << item->_file << item->_instruction << item->_direction << item->isDirectory(); + qCInfo(lcDisco) << "Discovered (blacklisted) " << item->_file << item->instruction() << item->_direction << item->isDirectory(); - if (item->isDirectory() && item->_instruction != CSYNC_INSTRUCTION_IGNORE) { + if (item->isDirectory() && item->instruction() != CSYNC_INSTRUCTION_IGNORE) { auto job = new ProcessDirectoryJob(path, item, NormalQuery, InBlackList, this); connect(job, &ProcessDirectoryJob::finished, this, &ProcessDirectoryJob::subJobFinished); _queuedJobs.push_back(job); } else { - emit _discoveryData->itemDiscovered(item); + Q_EMIT _discoveryData->itemDiscovered(item); } } @@ -1183,7 +1132,7 @@ bool ProcessDirectoryJob::checkPermissions(const OCC::SyncFileItemPtr &item) return true; } - switch (item->_instruction) { + switch (item->instruction()) { case CSYNC_INSTRUCTION_TYPE_CHANGE: case CSYNC_INSTRUCTION_NEW: { const auto perms = !_rootPermissions.isNull() ? _rootPermissions @@ -1193,12 +1142,12 @@ bool ProcessDirectoryJob::checkPermissions(const OCC::SyncFileItemPtr &item) return true; } else if (item->isDirectory() && !perms.hasPermission(RemotePermissions::CanAddSubDirectories)) { qCWarning(lcDisco) << "checkForPermission: ERROR" << item->_file; - item->_instruction = CSYNC_INSTRUCTION_ERROR; + item->setInstruction(CSYNC_INSTRUCTION_ERROR); item->_errorString = tr("Not allowed because you don't have permission to add subfolders to that folder"); return false; } else if (!item->isDirectory() && !perms.hasPermission(RemotePermissions::CanAddFile)) { qCWarning(lcDisco) << "checkForPermission: ERROR" << item->_file; - item->_instruction = CSYNC_INSTRUCTION_ERROR; + item->setInstruction(CSYNC_INSTRUCTION_ERROR); item->_errorString = tr("Not allowed because you don't have permission to add files in that folder"); return false; } @@ -1211,7 +1160,7 @@ bool ProcessDirectoryJob::checkPermissions(const OCC::SyncFileItemPtr &item) return true; } if (!perms.hasPermission(RemotePermissions::CanWrite)) { - item->_instruction = CSYNC_INSTRUCTION_CONFLICT; + item->setInstruction(CSYNC_INSTRUCTION_CONFLICT); item->_errorString = tr("Not allowed to upload this file because it is read-only on the server, restoring"); item->_direction = SyncFileItem::Down; item->_isRestoration = true; @@ -1226,12 +1175,13 @@ bool ProcessDirectoryJob::checkPermissions(const OCC::SyncFileItemPtr &item) } case CSYNC_INSTRUCTION_REMOVE: { QString fileSlash = item->_file + QLatin1Char('/'); - auto forbiddenIt = _discoveryData->_forbiddenDeletes.upperBound(fileSlash); - if (forbiddenIt != _discoveryData->_forbiddenDeletes.begin()) - forbiddenIt -= 1; - if (forbiddenIt != _discoveryData->_forbiddenDeletes.end() - && fileSlash.startsWith(forbiddenIt.key())) { - item->_instruction = CSYNC_INSTRUCTION_NEW; + auto forbiddenIt = _discoveryData->_forbiddenDeletes.upper_bound(fileSlash); + if (forbiddenIt != _discoveryData->_forbiddenDeletes.cbegin()) { + forbiddenIt--; + } + if (forbiddenIt != _discoveryData->_forbiddenDeletes.cend() + && fileSlash.startsWith(*forbiddenIt)) { + item->setInstruction(CSYNC_INSTRUCTION_NEW); item->_direction = SyncFileItem::Down; item->_isRestoration = true; item->_errorString = tr("Moved to invalid target, restoring"); @@ -1244,7 +1194,7 @@ bool ProcessDirectoryJob::checkPermissions(const OCC::SyncFileItemPtr &item) return true; } if (!perms.hasPermission(RemotePermissions::CanDelete)) { - item->_instruction = CSYNC_INSTRUCTION_NEW; + item->setInstruction(CSYNC_INSTRUCTION_NEW); item->_direction = SyncFileItem::Down; item->_isRestoration = true; item->_errorString = tr("Not allowed to remove, restoring"); @@ -1304,7 +1254,7 @@ void ProcessDirectoryJob::subJobFinished() _childModified |= job->_childModified; if (job->_dirItem) - emit _discoveryData->itemDiscovered(job->_dirItem); + Q_EMIT _discoveryData->itemDiscovered(job->_dirItem); int count = _runningJobs.removeAll(job); OC_ASSERT(count == 1); @@ -1315,31 +1265,31 @@ void ProcessDirectoryJob::subJobFinished() int ProcessDirectoryJob::processSubJobs(int nbJobs) { if (_queuedJobs.empty() && _runningJobs.empty() && _pendingAsyncJobs == 0) { - _pendingAsyncJobs = -1; // We're finished, we don't want to emit finished again + _pendingAsyncJobs = -1; // We're finished, we don't want to Q_EMIT finished again if (_dirItem) { - if (_childModified && _dirItem->_instruction == CSYNC_INSTRUCTION_REMOVE) { + if (_childModified && _dirItem->instruction() == CSYNC_INSTRUCTION_REMOVE) { // re-create directory that has modified contents - _dirItem->_instruction = CSYNC_INSTRUCTION_NEW; + _dirItem->setInstruction(CSYNC_INSTRUCTION_NEW); _dirItem->_direction = _dirItem->_direction == SyncFileItem::Up ? SyncFileItem::Down : SyncFileItem::Up; } - if (_childModified && _dirItem->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE && !_dirItem->isDirectory()) { + if (_childModified && _dirItem->instruction() == CSYNC_INSTRUCTION_TYPE_CHANGE && !_dirItem->isDirectory()) { // Replacing a directory by a file is a conflict, if the directory had modified children - _dirItem->_instruction = CSYNC_INSTRUCTION_CONFLICT; + _dirItem->setInstruction(CSYNC_INSTRUCTION_CONFLICT); if (_dirItem->_direction == SyncFileItem::Up) { _dirItem->_type = ItemTypeDirectory; _dirItem->_direction = SyncFileItem::Down; } } - if (_childIgnored && _dirItem->_instruction == CSYNC_INSTRUCTION_REMOVE) { + if (_childIgnored && _dirItem->instruction() == CSYNC_INSTRUCTION_REMOVE) { // Do not remove a directory that has ignored files - _dirItem->_instruction = CSYNC_INSTRUCTION_NONE; + _dirItem->setInstruction(CSYNC_INSTRUCTION_NONE); } } - emit finished(); + Q_EMIT finished(); } int started = 0; - for (auto *rj : qAsConst(_runningJobs)) { + for (auto *rj : std::as_const(_runningJobs)) { started += rj->processSubJobs(nbJobs - started); if (started >= nbJobs) return started; @@ -1357,81 +1307,77 @@ int ProcessDirectoryJob::processSubJobs(int nbJobs) void ProcessDirectoryJob::dbError() { - Q_EMIT _discoveryData->fatalError(tr("Error while reading the database")); -} - -void ProcessDirectoryJob::addVirtualFileSuffix(QString &str) const -{ - str.append(_discoveryData->_syncOptions._vfs->fileSuffix()); -} - -bool ProcessDirectoryJob::hasVirtualFileSuffix(const QString &str) const -{ - if (!isVfsWithSuffix()) - return false; - return str.endsWith(_discoveryData->_syncOptions._vfs->fileSuffix()); -} - -QString ProcessDirectoryJob::chopVirtualFileSuffix(const QString &str) const -{ - if (!isVfsWithSuffix()) - return str; - // ensure we do it only with virtual files in this class - Q_ASSERT(hasVirtualFileSuffix(str)); - return _discoveryData->_syncOptions._vfs->underlyingFileName(str); + Q_EMIT _discoveryData->fatalError(tr("Error while reading the database.")); } DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery() { - auto serverJob = new DiscoverySingleDirectoryJob(_discoveryData->_account, - _discoveryData->_remoteFolder + _currentFolder._server, this); + if (_discoveryData->_account == nullptr) { + Q_EMIT _discoveryData->fatalError(tr("account was deleted. Unable to continue.")); + return nullptr; + } + + auto discoveryJob = + new DiscoverySingleDirectoryJob(_discoveryData->_account.get(), _discoveryData->_baseUrl, _discoveryData->_remoteFolder + _currentFolder._server, this); if (!_dirItem) - serverJob->setIsRootPath(); // query the fingerprint on the root - connect(serverJob, &DiscoverySingleDirectoryJob::etag, this, &ProcessDirectoryJob::etag); + discoveryJob->setIsRootPath(); // query the fingerprint on the root + connect(discoveryJob, &DiscoverySingleDirectoryJob::etag, this, &ProcessDirectoryJob::etag); _discoveryData->_currentlyActiveJobs++; _pendingAsyncJobs++; - connect(serverJob, &DiscoverySingleDirectoryJob::finished, this, [this, serverJob](const auto &results) { + connect(discoveryJob, &DiscoverySingleDirectoryJob::finished, this, [this, discoveryJob](const auto &results) { _discoveryData->_currentlyActiveJobs--; _pendingAsyncJobs--; if (results) { _serverNormalQueryEntries = *results; _serverQueryDone = true; - if (!serverJob->_dataFingerprint.isEmpty() && _discoveryData->_dataFingerprint.isEmpty()) - _discoveryData->_dataFingerprint = serverJob->_dataFingerprint; + if (!discoveryJob->_dataFingerprint.isEmpty() && _discoveryData->_dataFingerprint.isEmpty()) + _discoveryData->_dataFingerprint = discoveryJob->_dataFingerprint; if (_localQueryDone) this->process(); } else { auto code = results.error().code; qCWarning(lcDisco) << "Server error in directory" << _currentFolder._server << code; - if (_dirItem && code >= 403) { - // In case of an HTTP error, we ignore that directory - // 403 Forbidden can be sent by the server if the file firewall is active. - // A file or directory should be ignored and sync must continue. See #3490 - // The server usually replies with the custom "503 Storage not available" - // if some path is temporarily unavailable. But in some cases a standard 503 - // is returned too. Thus we can't distinguish the two and will treat any - // 503 as request to ignore the folder. See #3113 #2884. - // Similarly, the server might also return 404 or 50x in case of bugs. #7199 #7586 - _dirItem->_instruction = CSYNC_INSTRUCTION_IGNORE; - _dirItem->_errorString = results.error().message; - emit this->finished(); + if (discoveryJob->isRootPath()) { + if (code == 404 && _discoveryData->isSpace()) { + Q_EMIT _discoveryData->fatalError(tr("This Space is currently unavailable")); + return; + } } else { - // Fatal for the root job since it has no SyncFileItem, or for the network errors - emit _discoveryData->fatalError(tr("Server replied with an error while reading directory '%1' : %2") - .arg(_currentFolder._server, results.error().message)); + if (code >= 403) { + // In case of an HTTP error, we ignore that directory + // 403 Forbidden can be sent by the server if the file firewall is active. + // A file or directory should be ignored and sync must continue. See #3490 + // The server usually replies with the custom "503 Storage not available" + // if some path is temporarily unavailable. But in some cases a standard 503 + // is returned too. Thus, we can't distinguish the two and will treat any + // 503 as request to ignore the folder. See #3113 #2884. + // Similarly, the server might also return 404 or 50x in case of bugs. #7199 #7586 + _dirItem->setInstruction(CSYNC_INSTRUCTION_IGNORE); + _dirItem->_errorString = results.error().message; + Q_EMIT this->finished(); + return; + } } + // Fatal for the root job since it has no SyncFileItem, or for the network errors + Q_EMIT _discoveryData->fatalError( + tr("Server replied with an error while reading directory '%1' : %2") + .arg(_currentFolder._server.isEmpty() ? QStringLiteral("/") : _currentFolder._server, results.error().message)); } }); - connect(serverJob, &DiscoverySingleDirectoryJob::firstDirectoryPermissions, this, - [this](const RemotePermissions &perms) { _rootPermissions = perms; }); - serverJob->start(); - return serverJob; + connect(discoveryJob, &DiscoverySingleDirectoryJob::firstDirectoryPermissions, this, [this](const RemotePermissions &perms) { _rootPermissions = perms; }); + discoveryJob->start(); + return discoveryJob; } void ProcessDirectoryJob::startAsyncLocalQuery() { + if (!_discoveryData->_syncOptions.isValid()) { + emit _discoveryData->fatalError(tr("the VFS instance is missing. Unable to continue.")); + return; + } + QString localPath = _discoveryData->_localDir + _currentFolder._local; - auto localJob = new DiscoverySingleLocalDirectoryJob(_discoveryData->_account, localPath, _discoveryData->_syncOptions._vfs.data()); + auto localJob = new DiscoverySingleLocalDirectoryJob(localPath, _discoveryData->_syncOptions.vfs()); _discoveryData->_currentlyActiveJobs++; _pendingAsyncJobs++; @@ -1448,7 +1394,7 @@ void ProcessDirectoryJob::startAsyncLocalQuery() if (_serverJob) _serverJob->abort(); - emit _discoveryData->fatalError(msg); + Q_EMIT _discoveryData->fatalError(msg); }); connect(localJob, &DiscoverySingleLocalDirectoryJob::finishedNonFatalError, this, [this](const QString &msg) { @@ -1456,12 +1402,12 @@ void ProcessDirectoryJob::startAsyncLocalQuery() _pendingAsyncJobs--; if (_dirItem) { - _dirItem->_instruction = CSYNC_INSTRUCTION_IGNORE; + _dirItem->setInstruction(CSYNC_INSTRUCTION_IGNORE); _dirItem->_errorString = msg; - emit this->finished(); + Q_EMIT this->finished(); } else { // Fatal for the root job since it has no SyncFileItem - emit _discoveryData->fatalError(msg); + Q_EMIT _discoveryData->fatalError(msg); } }); @@ -1480,39 +1426,17 @@ void ProcessDirectoryJob::startAsyncLocalQuery() pool->start(localJob); // QThreadPool takes ownership } - -bool ProcessDirectoryJob::isVfsWithSuffix() const -{ - return _discoveryData->_syncOptions._vfs->mode() == Vfs::WithSuffix; -} - void ProcessDirectoryJob::computePinState(PinState parentState) { + if (!_discoveryData->_syncOptions.isValid()) { + emit _discoveryData->fatalError(tr("the VFS instance is missing. Unable to continue.")); + return; + } + _pinState = parentState; if (_queryLocal != ParentDontExist) { - if (auto state = _discoveryData->_syncOptions._vfs->pinState(_currentFolder._local)) // ouch! pin local or original? + if (auto state = _discoveryData->_syncOptions.vfs()->pinState(_currentFolder._local)) // ouch! pin local or original? _pinState = *state; } } - -void ProcessDirectoryJob::setupDbPinStateActions(SyncJournalFileRecord &record) -{ - // Only suffix-vfs uses the db for pin states. - // Other plugins will set localEntry._type according to the file's pin state. - if (!isVfsWithSuffix()) - return; - - auto pin = _discoveryData->_statedb->internalPinStates().rawForPath(record._path); - if (!pin || *pin == PinState::Inherited) - pin = _pinState; - - // OnlineOnly hydrated files want to be dehydrated - if (record._type == ItemTypeFile && *pin == PinState::OnlineOnly) - record._type = ItemTypeVirtualFileDehydration; - - // AlwaysLocal dehydrated files want to be hydrated - if (record._type == ItemTypeVirtualFile && *pin == PinState::AlwaysLocal) - record._type = ItemTypeVirtualFileDownload; -} - } diff --git a/src/libsync/discovery.h b/src/libsync/discovery.h index 64da6bb1e62..4636ebb9e73 100644 --- a/src/libsync/discovery.h +++ b/src/libsync/discovery.h @@ -14,11 +14,11 @@ #pragma once -#include +#include "common/syncjournaldb.h" #include "discoveryphase.h" #include "syncfileitem.h" -#include "common/asserts.h" -#include "common/syncjournaldb.h" +#include +#include class ExcludedFiles; @@ -33,11 +33,11 @@ class SyncJournalDb; * - Stat all the entries in the local file system for this directory * - Merge all information (and the information from the database) in order to know what needs * to be done for every file within this directory. - * - For every sub-directory within this directory, "recursively" create a new ProcessDirectoryJob. + * - For every subdirectory within this directory, "recursively" create a new ProcessDirectoryJob. * * This job is tightly coupled with the DiscoveryPhase class. * - * After being start()'ed this job will perform work asynchronously and emit finished() when done. + * After being started this job will perform work asynchronously and Q_EMIT finished() when done. * * Internally, this job will call DiscoveryPhase::scheduleMoreJobs when one of its sub-jobs is * finished. DiscoveryPhase::scheduleMoreJobs will call processSubJobs() to continue work until @@ -50,6 +50,7 @@ class ProcessDirectoryJob : public QObject Q_OBJECT struct PathTuple; + public: enum QueryMode { NormalQuery, @@ -70,7 +71,7 @@ class ProcessDirectoryJob : public QObject computePinState(basePinState); } - /// For creating subjobs + /// For creating sub-jobs explicit ProcessDirectoryJob(const PathTuple &path, const SyncFileItemPtr &dirItem, QueryMode queryLocal, QueryMode queryServer, ProcessDirectoryJob *parent) @@ -85,7 +86,7 @@ class ProcessDirectoryJob : public QObject } void start(); - /** Start up to nbJobs, return the number of job started; emit finished() when done */ + /** Start up to nbJobs, return the number of job started; Q_EMIT finished() when done */ int processSubJobs(int nbJobs); SyncFileItemPtr _dirItem; @@ -150,16 +151,20 @@ class ProcessDirectoryJob : public QObject * * This main function delegates some work to the processFile* functions. */ - void processFile(PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &); + void processFile(const PathTuple &, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &); /// processFile helper for when remote information is available, typically flows into AnalyzeLocalInfo when done void processFileAnalyzeRemoteInfo(const SyncFileItemPtr &item, PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &); + /// Function to gradually check conditions for accepting a move-candidate + bool isMove( + const SyncFileItemPtr &item, const PathTuple &path, const LocalInfo &localEntry, const SyncJournalFileRecord &base, const QString &originalPath) const; + /// processFile helper for reconciling local changes - void processFileAnalyzeLocalInfo(const SyncFileItemPtr &item, PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &, QueryMode recurseQueryServer); + void processFileAnalyzeLocalInfo(const SyncFileItemPtr &item, const PathTuple &, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &, QueryMode recurseQueryServer); /// processFile helper for local/remote conflicts - void processFileConflict(const SyncFileItemPtr &item, PathTuple, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &); + void processFileConflict(const SyncFileItemPtr &item, const PathTuple &, const LocalInfo &, const RemoteInfo &, const SyncJournalFileRecord &); /// processFile helper for common final processing void processFileFinalize(const SyncFileItemPtr &item, PathTuple, bool recurse, QueryMode recurseQueryLocal, QueryMode recurseQueryServer); @@ -193,13 +198,6 @@ class ProcessDirectoryJob : public QObject /** An DB operation failed */ void dbError(); - void addVirtualFileSuffix(QString &str) const; - bool hasVirtualFileSuffix(const QString &str) const; - Q_REQUIRED_RESULT QString chopVirtualFileSuffix(const QString &str) const; - - /** Convenience to detect suffix-vfs modes */ - bool isVfsWithSuffix() const; - /** Start a remote discovery network job * * It fills _serverNormalQueryEntries and sets _serverQueryDone when done. @@ -220,18 +218,6 @@ class ProcessDirectoryJob : public QObject */ void computePinState(PinState parentState); - /** Adjust record._type if the db pin state suggests it. - * - * If the pin state is stored in the database (suffix vfs only right now) - * its effects won't be seen in localEntry._type. Instead the effects - * should materialize in dbEntry._type. - * - * This function checks whether the combination of file type and pin - * state suggests a hydration or dehydration action and changes the - * _type field accordingly. - */ - void setupDbPinStateActions(SyncJournalFileRecord &record); - QueryMode _queryServer = QueryMode::NormalQuery; QueryMode _queryLocal = QueryMode::NormalQuery; @@ -240,7 +226,7 @@ class ProcessDirectoryJob : public QObject QVector _localNormalQueryEntries; // Whether the local/remote directory item queries are done. Will be set - // even even for do-nothing (!= NormalQuery) queries. + // even for do-nothing (!= NormalQuery) queries. bool _serverQueryDone = false; bool _localQueryDone = false; @@ -261,22 +247,23 @@ class ProcessDirectoryJob : public QObject /** The queued and running jobs for subdirectories. * - * The jobs are enqueued while processind directory entries and + * The jobs are enqueued while processing directory entries and * then gradually run via calls to processSubJobs(). */ std::deque _queuedJobs; QVector _runningJobs; - DiscoveryPhase *_discoveryData; + // this is passed in from SyncEngine which owns the original pointer. + QPointer _discoveryData; PathTuple _currentFolder; bool _childModified = false; // the directory contains modified item what would prevent deletion bool _childIgnored = false; // The directory contains ignored item that would prevent deletion PinState _pinState = PinState::Unspecified; // The directory's pin-state, see computePinState() -signals: +Q_SIGNALS: void finished(); // The root etag of this directory was fetched - void etag(const QByteArray &, const QDateTime &time); + void etag(const QString &, const QDateTime &time); }; } diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 2a6c400710d..eca92a9db0d 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -15,30 +15,24 @@ #include "discoveryphase.h" #include "discovery.h" -#include "account.h" #include "common/asserts.h" #include "common/checksums.h" -#include #include "vio/csync_vio_local.h" +#include +#include #include #include -#include -#include -#include -#include -#include - namespace OCC { Q_LOGGING_CATEGORY(lcDiscovery, "sync.discovery", QtInfoMsg) /* Given a sorted list of paths ending with '/', return whether or not the given path is within one of the paths of the list*/ -static bool findPathInList(const QStringList &list, const QString &path) +static bool findPathInList(const std::set &list, const QString &path) { - if (list.size() == 1 && list.first() == QLatin1String("/")) { + if (list.size() == 1 && *list.cbegin() == QLatin1String("/")) { // Special case for the case "/" is there, it matches everything return true; } @@ -63,7 +57,7 @@ static bool findPathInList(const QStringList &list, const QString &path) bool DiscoveryPhase::isInSelectiveSyncBlackList(const QString &path) const { - if (_selectiveSyncBlackList.isEmpty()) { + if (_selectiveSyncBlackList.empty()) { // If there is no black list, everything is allowed return false; } @@ -76,71 +70,13 @@ bool DiscoveryPhase::isInSelectiveSyncBlackList(const QString &path) const return false; } -void DiscoveryPhase::checkSelectiveSyncNewFolder(const QString &path, RemotePermissions remotePerm, - std::function callback) -{ - if (_syncOptions._confirmExternalStorage && _syncOptions._vfs->mode() == Vfs::Off - && remotePerm.hasPermission(RemotePermissions::IsMounted)) { - // external storage. - - /* Note: DiscoverySingleDirectoryJob::directoryListingIteratedSlot make sure that only the - * root of a mounted storage has 'M', all sub entries have 'm' */ - - // Only allow it if the white list contains exactly this path (not parents) - // We want to ask confirmation for external storage even if the parents where selected - if (_selectiveSyncWhiteList.contains(path + QLatin1Char('/'))) { - return callback(false); - } - - emit newBigFolder(path, true); - return callback(true); - } - - // If this path or the parent is in the white list, then we do not block this file - if (findPathInList(_selectiveSyncWhiteList, path)) { - return callback(false); - } - - auto limit = _syncOptions._newBigFolderSizeLimit; - if (limit < 0 || _syncOptions._vfs->mode() != Vfs::Off) { - // no limit, everything is allowed; - return callback(false); - } - - // do a PROPFIND to know the size of this folder - auto propfindJob = new PropfindJob(_account, _remoteFolder + path, this); - propfindJob->setProperties(QList() << "resourcetype" - << "http://owncloud.org/ns:size"); - QObject::connect(propfindJob, &PropfindJob::finishedWithError, - this, [=] { return callback(false); }); - QObject::connect(propfindJob, &PropfindJob::result, this, [=](const QMap &values) { - auto result = values.value(QStringLiteral("size")).toLongLong(); - if (result >= limit) { - // we tell the UI there is a new folder - emit newBigFolder(path, false); - return callback(true); - } else { - // it is not too big, put it in the white list (so we will not do more query for the children) - // and and do not block. - auto p = path; - if (!p.endsWith(QLatin1Char('/'))) - p += QLatin1Char('/'); - _selectiveSyncWhiteList.insert( - std::upper_bound(_selectiveSyncWhiteList.begin(), _selectiveSyncWhiteList.end(), p), - p); - return callback(false); - } - }); - propfindJob->start(); -} - /* Given a path on the remote, give the path as it is when the rename is done */ QString DiscoveryPhase::adjustRenamedPath(const QString &original, SyncFileItem::Direction d) const { return OCC::adjustRenamedPath(d == SyncFileItem::Down ? _renamedItemsRemote : _renamedItemsLocal, original); } -QString adjustRenamedPath(const QMap &renamedItems, const QString &original) +QString adjustRenamedPath(const QHash &renamedItems, const QString &original) { int slashPos = original.size(); while ((slashPos = original.lastIndexOf(QLatin1Char('/'), slashPos - 1)) > 0) { @@ -152,14 +88,14 @@ QString adjustRenamedPath(const QMap &renamedItems, const QStr return original; } -QPair DiscoveryPhase::findAndCancelDeletedJob(const QString &originalPath) +QPair DiscoveryPhase::findAndCancelDeletedJob(const QString &originalPath) { bool result = false; - QByteArray oldEtag; - auto it = _deletedItem.find(originalPath); - if (it != _deletedItem.end()) { + QString oldEtag; + auto it = _deletedItem.constFind(originalPath); + if (it != _deletedItem.cend()) { const auto &item = *it; - const SyncInstructions instruction = item->_instruction; + const SyncInstructions instruction = item->instruction(); if (instruction == CSYNC_INSTRUCTION_IGNORE && item->_type == ItemTypeVirtualFile) { // re-creation of virtual files count as a delete // restoration after a prohibited move @@ -189,7 +125,7 @@ QPair DiscoveryPhase::findAndCancelDeletedJob(const QString &o qCWarning(lcDiscovery) << "item->_remotePerm" << item->_remotePerm; OC_ENFORCE(false); } - item->_instruction = CSYNC_INSTRUCTION_NONE; + item->setInstruction(CSYNC_INSTRUCTION_NONE); result = true; oldEtag = item->_etag; } @@ -210,7 +146,7 @@ void DiscoveryPhase::startJob(ProcessDirectoryJob *job) OC_ENFORCE(_currentRootJob == sender()); _currentRootJob = nullptr; if (job->_dirItem) - emit itemDiscovered(job->_dirItem); + Q_EMIT itemDiscovered(job->_dirItem); job->deleteLater(); // Once the main job has finished recurse here to execute the remaining @@ -219,23 +155,21 @@ void DiscoveryPhase::startJob(ProcessDirectoryJob *job) auto nextJob = _queuedDeletedDirectories.take(_queuedDeletedDirectories.firstKey()); startJob(nextJob); } else { - emit finished(); + Q_EMIT finished(); } }); _currentRootJob = job; job->start(); } -void DiscoveryPhase::setSelectiveSyncBlackList(const QStringList &list) +void DiscoveryPhase::setSelectiveSyncBlackList(const QSet &list) { - _selectiveSyncBlackList = list; - std::sort(_selectiveSyncBlackList.begin(), _selectiveSyncBlackList.end()); + _selectiveSyncBlackList = {list.cbegin(), list.cend()}; } -void DiscoveryPhase::setSelectiveSyncWhiteList(const QStringList &list) +void DiscoveryPhase::setSelectiveSyncWhiteList(const QSet &list) { - _selectiveSyncWhiteList = list; - std::sort(_selectiveSyncWhiteList.begin(), _selectiveSyncWhiteList.end()); + _selectiveSyncWhiteList = {list.cbegin(), list.cend()}; } void DiscoveryPhase::scheduleMoreJobs() @@ -246,8 +180,18 @@ void DiscoveryPhase::scheduleMoreJobs() } } -DiscoverySingleLocalDirectoryJob::DiscoverySingleLocalDirectoryJob(const AccountPtr &account, const QString &localPath, OCC::Vfs *vfs, QObject *parent) - : QObject(parent), QRunnable(), _localPath(localPath), _account(account), _vfs(vfs) +bool DiscoveryPhase::isSpace() const +{ + if (!_account) + return false; + return !(Utility::urlEqual(_account->davUrl(), _baseUrl) || _account->davUrl().isParentOf(_baseUrl)); +} + +DiscoverySingleLocalDirectoryJob::DiscoverySingleLocalDirectoryJob(const QString &localPath, OCC::Vfs *vfs, QObject *parent) + : QObject(parent) + , QRunnable() + , _localPath(localPath) + , _vfs(vfs) { qRegisterMetaType >("QVector"); } @@ -264,7 +208,7 @@ void DiscoverySingleLocalDirectoryJob::run() { QString errorString = tr("Error while opening directory %1").arg(localPath); if (errno == EACCES) { errorString = tr("Directory not accessible on client, permission denied"); - emit finishedNonFatalError(errorString); + Q_EMIT finishedNonFatalError(errorString); return; } else if (errno == ENOENT) { errorString = tr("Directory not found: %1").arg(localPath); @@ -273,7 +217,7 @@ void DiscoverySingleLocalDirectoryJob::run() { // Just consider it is empty return; } - emit finishedFatalError(errorString); + Q_EMIT finishedFatalError(errorString); return; } @@ -286,22 +230,7 @@ void DiscoverySingleLocalDirectoryJob::run() { if (dirent->type == ItemTypeSkip) continue; LocalInfo i; - static QTextCodec *codec = QTextCodec::codecForName("UTF-8"); - OC_ASSERT(codec); - QTextCodec::ConverterState state; - i.name = codec->toUnicode(dirent->path.constData(), dirent->path.size(), &state); - if (state.invalidChars > 0 || state.remainingChars > 0) { - emit childIgnored(true); - auto item = SyncFileItemPtr::create(); - //item->_file = _currentFolder._target + i.name; - // FIXME ^^ do we really need to use _target or is local fine? - item->_file = _localPath + i.name; - item->_instruction = CSYNC_INSTRUCTION_IGNORE; - item->_status = SyncFileItem::NormalError; - item->_errorString = tr("Filename encoding is not valid"); - emit itemDiscovered(item); - continue; - } + i.name = dirent->path; i.modtime = dirent->modtime; i.size = dirent->size; i.inode = dirent->inode; @@ -317,7 +246,7 @@ void DiscoverySingleLocalDirectoryJob::run() { // Note: Windows vio converts any error into EACCES qCWarning(lcDiscovery) << "readdir failed for file in " << localPath << " - errno: " << errno; - emit finishedFatalError(tr("Error while reading directory %1").arg(localPath)); + Q_EMIT finishedFatalError(tr("Error while reading directory %1").arg(localPath)); return; } @@ -327,13 +256,14 @@ void DiscoverySingleLocalDirectoryJob::run() { qCWarning(lcDiscovery) << "closedir failed for file in " << localPath << " - errno: " << errno; } - emit finished(results); + Q_EMIT finished(results); } -DiscoverySingleDirectoryJob::DiscoverySingleDirectoryJob(const AccountPtr &account, const QString &path, QObject *parent) +DiscoverySingleDirectoryJob::DiscoverySingleDirectoryJob(Account *account, const QUrl &baseUrl, const QString &path, QObject *parent) : QObject(parent) , _subPath(path) , _account(account) + , _baseUrl(baseUrl) , _ignoredFirst(false) , _isRootPath(false) , _isExternalStorage(false) @@ -342,79 +272,89 @@ DiscoverySingleDirectoryJob::DiscoverySingleDirectoryJob(const AccountPtr &accou void DiscoverySingleDirectoryJob::start() { + if (!_account) { + Q_EMIT finished(HttpError{0, tr("The account was deleted before we could start the propfind job")}); + deleteLater(); + return; + } + // Start the actual HTTP job - LsColJob *lsColJob = new LsColJob(_account, _subPath, this); - - QList props; - props << "resourcetype" - << "getlastmodified" - << "getcontentlength" - << "getetag" - << "http://owncloud.org/ns:id" - << "http://owncloud.org/ns:downloadURL" - << "http://owncloud.org/ns:dDC" - << "http://owncloud.org/ns:permissions" - << "http://owncloud.org/ns:checksums"; - if (_isRootPath) + _proFindJob = new PropfindJob(_account, _baseUrl, _subPath, PropfindJob::Depth::One, this); + + QList props { + "resourcetype", + "getlastmodified", + "getcontentlength", + "getetag", + "http://owncloud.org/ns:id", + "http://owncloud.org/ns:downloadURL", + "http://owncloud.org/ns:dDC", + "http://owncloud.org/ns:permissions", + "http://owncloud.org/ns:checksums", + "http://owncloud.org/ns:share-types" + }; + if (_isRootPath) { props << "http://owncloud.org/ns:data-fingerprint"; - if (_account->serverVersionInt() >= Account::makeServerVersion(10, 0, 0)) { - // Server older than 10.0 have performances issue if we ask for the share-types on every PROPFIND - props << "http://owncloud.org/ns:share-types"; } - lsColJob->setProperties(props); - QObject::connect(lsColJob, &LsColJob::directoryListingIterated, - this, &DiscoverySingleDirectoryJob::directoryListingIteratedSlot); - QObject::connect(lsColJob, &LsColJob::finishedWithError, this, &DiscoverySingleDirectoryJob::lsJobFinishedWithErrorSlot); - QObject::connect(lsColJob, &LsColJob::finishedWithoutError, this, &DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot); - lsColJob->start(); + _proFindJob->setProperties(props); - _lsColJob = lsColJob; + QObject::connect(_proFindJob, &PropfindJob::directoryListingIterated, + this, &DiscoverySingleDirectoryJob::directoryListingIteratedSlot); + QObject::connect(_proFindJob, &PropfindJob::finishedWithError, this, [this] { + QString msg = _proFindJob->errorString(); + if (_proFindJob->reply()->error() == QNetworkReply::NoError + && !_proFindJob->reply()->header(QNetworkRequest::ContentTypeHeader).toString().contains(QLatin1String("application/xml; charset=utf-8"))) { + msg = tr("Server error: PROPFIND reply is not XML formatted!"); + } + Q_EMIT finished(HttpError{_proFindJob->httpStatusCode(), msg}); + deleteLater(); + }); + QObject::connect(_proFindJob, &PropfindJob::finishedWithoutError, this, &DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot); + _proFindJob->start(); } void DiscoverySingleDirectoryJob::abort() { - if (_lsColJob && _lsColJob->reply()) { - _lsColJob->reply()->abort(); + if (_proFindJob) { + _proFindJob->abort(); } } static void propertyMapToRemoteInfo(const QMap &map, RemoteInfo &result) { - for (auto it = map.constBegin(); it != map.constEnd(); ++it) { - QString property = it.key(); - QString value = it.value(); - if (property == QLatin1String("resourcetype")) { - result.isDirectory = value.contains(QLatin1String("collection")); - } else if (property == QLatin1String("getlastmodified")) { - const auto date = QDateTime::fromString(value, Qt::RFC2822Date); - Q_ASSERT(date.isValid()); - result.modtime = date.toTime_t(); - } else if (property == QLatin1String("getcontentlength")) { - // See #4573, sometimes negative size values are returned - bool ok = false; - qlonglong ll = value.toLongLong(&ok); - if (ok && ll >= 0) { - result.size = ll; - } else { - result.size = 0; - } - } else if (property == QLatin1String("getetag")) { - result.etag = Utility::normalizeEtag(value.toUtf8()); - } else if (property == QLatin1String("id")) { - result.fileId = value.toUtf8(); - } else if (property == QLatin1String("downloadURL")) { - result.directDownloadUrl = value; - } else if (property == QLatin1String("dDC")) { - result.directDownloadCookies = value; - } else if (property == QLatin1String("permissions")) { - result.remotePerm = RemotePermissions::fromServerString(value); - } else if (property == QLatin1String("checksums")) { - result.checksumHeader = findBestChecksum(value.toUtf8()); - } else if (property == QLatin1String("share-types") && !value.isEmpty()) { - // Since QMap is sorted, "share-types" is always after "permissions". - if (result.remotePerm.isNull()) { + result.directDownloadUrl = map.value(QStringLiteral("downloadURL")); + result.directDownloadCookies = map.value(QStringLiteral("dDC")); + + if (auto it = Utility::optionalFind(map, QStringLiteral("resourcetype"))) { + result.isDirectory = it->value().contains(QStringLiteral("collection")); + } + if (auto it = Utility::optionalFind(map, QStringLiteral("getlastmodified"))) { + const auto date = Utility::parseRFC1123Date(**it); + Q_ASSERT(date.isValid()); + result.modtime = date.toSecsSinceEpoch(); + } + if (auto it = Utility::optionalFind(map, QStringLiteral("getcontentlength"))) { + // See #4573, sometimes negative size values are returned + result.size = std::max(0, it->value().toLongLong()); + } + if (auto it = Utility::optionalFind(map, QStringLiteral("getetag"))) { + result.etag = Utility::normalizeEtag(it->value()); + } + if (auto it = Utility::optionalFind(map, QStringLiteral("id"))) { + result.fileId = it->value().toUtf8(); + } + if (auto it = Utility::optionalFind(map, QStringLiteral("checksums"))) { + result.checksumHeader = findBestChecksum(it->value().toUtf8()); + } + if (auto it = Utility::optionalFind(map, QStringLiteral("permissions"))) { + result.remotePerm = RemotePermissions::fromServerString(it->value()); + } + if (auto it = Utility::optionalFind(map, QStringLiteral("share-types"))) { + const QString &value = it->value(); + if (!value.isEmpty()) { + if (!map.contains(QStringLiteral("permissions"))) { qWarning() << "Server returned a share type, but no permissions?"; // Empty permissions will cause a sync failure } else { @@ -433,13 +373,13 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(const QString &fi if (!_ignoredFirst) { // The first entry is for the folder itself, we should process it differently. _ignoredFirst = true; - if (map.contains(QStringLiteral("permissions"))) { - auto perm = RemotePermissions::fromServerString(map.value(QStringLiteral("permissions"))); - emit firstDirectoryPermissions(perm); + if (auto it = Utility::optionalFind(map, QStringLiteral("permissions"))) { + auto perm = RemotePermissions::fromServerString(it->value()); + Q_EMIT firstDirectoryPermissions(perm); _isExternalStorage = perm.hasPermission(RemotePermissions::IsMounted); } - if (map.contains(QStringLiteral("data-fingerprint"))) { - _dataFingerprint = map.value(QStringLiteral("data-fingerprint")).toUtf8(); + if (auto it = Utility::optionalFind(map, QStringLiteral("data-fingerprint"))) { + _dataFingerprint = it->value().toUtf8(); if (_dataFingerprint.isEmpty()) { // Placeholder that means that the server supports the feature even if it did not set one. _dataFingerprint = "[empty]"; @@ -466,9 +406,9 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(const QString &fi } //This works in concerto with the RequestEtagJob and the Folder object to check if the remote folder changed. - if (map.contains(QStringLiteral("getetag"))) { - if (_firstEtag.isEmpty()) { - _firstEtag = parseEtag(map.value(QStringLiteral("getetag")).toUtf8()); // for directory itself + if (_firstEtag.isEmpty()) { + if (auto it = Utility::optionalFind(map, QStringLiteral("getetag"))) { + _firstEtag = Utility::normalizeEtag(it->value()); // for directory itself } } } @@ -478,31 +418,23 @@ void DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot() if (!_ignoredFirst) { // This is a sanity check, if we haven't _ignoredFirst then it means we never received any directoryListingIteratedSlot // which means somehow the server XML was bogus - emit finished(HttpError{ 0, tr("Server error: PROPFIND reply is not XML formatted!") }); + Q_EMIT finished(HttpError{0, tr("Server error: PROPFIND reply is not XML formatted!")}); deleteLater(); return; } else if (!_error.isEmpty()) { - emit finished(HttpError{ 0, _error }); + Q_EMIT finished(HttpError{0, _error}); deleteLater(); return; } - emit etag(_firstEtag, QDateTime::fromString(QString::fromUtf8(_lsColJob->responseTimestamp()), Qt::RFC2822Date)); - emit finished(_results); - deleteLater(); -} - -void DiscoverySingleDirectoryJob::lsJobFinishedWithErrorSlot(QNetworkReply *r) -{ - QString contentType = r->header(QNetworkRequest::ContentTypeHeader).toString(); - int httpCode = r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - QString msg = r->errorString(); - qCWarning(lcDiscovery) << "LSCOL job error" << r->errorString() << httpCode << r->error(); - if (r->error() == QNetworkReply::NoError - && !contentType.contains(QLatin1String("application/xml; charset=utf-8"))) { - msg = tr("Server error: PROPFIND reply is not XML formatted!"); + auto lastResponse = _proFindJob->responseQTimeStamp(); + if (!lastResponse.isValid()) { + // If the responose had no valid "Date" header, use "now", as the job just finished. + lastResponse = QDateTime::currentDateTimeUtc(); } - emit finished(HttpError{ httpCode, msg }); + + Q_EMIT etag(_firstEtag, lastResponse); + Q_EMIT finished(_results); deleteLater(); } } diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index e12fc6a0253..cb270bc5c0f 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -15,20 +15,16 @@ #pragma once #include -#include -#include -#include -#include -#include + +#include "account.h" #include "networkjobs.h" -#include -#include -#include -#include -#include "syncoptions.h" #include "syncfileitem.h" +#include "syncoptions.h" -#include "csync/csync_exclude.h" +#include +#include + +class ExcludedFiles; namespace OCC { @@ -38,7 +34,6 @@ enum class LocalDiscoveryStyle { }; -class Account; class SyncJournalDb; class ProcessDirectoryJob; @@ -49,7 +44,7 @@ struct RemoteInfo { /** FileName of the entry (this does not contains any directory or path, just the plain name */ QString name; - QByteArray etag; + QString etag; QByteArray fileId; QByteArray checksumHeader; OCC::RemotePermissions remotePerm; @@ -86,20 +81,19 @@ class DiscoverySingleLocalDirectoryJob : public QObject, public QRunnable { Q_OBJECT public: - explicit DiscoverySingleLocalDirectoryJob(const AccountPtr &account, const QString &localPath, OCC::Vfs *vfs, QObject *parent = nullptr); + explicit DiscoverySingleLocalDirectoryJob(const QString &localPath, OCC::Vfs *vfs, QObject *parent = nullptr); void run() override; -signals: +Q_SIGNALS: void finished(QVector result); void finishedFatalError(QString errorString); void finishedNonFatalError(QString errorString); void itemDiscovered(SyncFileItemPtr item); void childIgnored(bool b); -private slots: + private: QString _localPath; - AccountPtr _account; OCC::Vfs* _vfs; public: }; @@ -114,28 +108,29 @@ class DiscoverySingleDirectoryJob : public QObject { Q_OBJECT public: - explicit DiscoverySingleDirectoryJob(const AccountPtr &account, const QString &path, QObject *parent = nullptr); + explicit DiscoverySingleDirectoryJob(Account *account, const QUrl &baseUrl, const QString &path, QObject *parent = nullptr); // Specify that this is the root and we need to check the data-fingerprint void setIsRootPath() { _isRootPath = true; } + bool isRootPath() const { return _isRootPath; } void start(); void abort(); // This is not actually a network job, it is just a job -signals: +Q_SIGNALS: void firstDirectoryPermissions(RemotePermissions); - void etag(const QByteArray &, const QDateTime &time); + void etag(const QString &, const QDateTime &time); void finished(const HttpResult> &result); -private slots: +private Q_SLOTS: void directoryListingIteratedSlot(const QString &, const QMap &); void lsJobFinishedWithoutErrorSlot(); - void lsJobFinishedWithErrorSlot(QNetworkReply *); private: QVector _results; QString _subPath; - QByteArray _firstEtag; - AccountPtr _account; + QString _firstEtag; + QPointer _account; + const QUrl _baseUrl; // The first result is for the directory itself and need to be ignored. // This flag is true if it was already ignored. bool _ignoredFirst; @@ -145,7 +140,7 @@ private slots: bool _isExternalStorage; // If set, the discovery will finish with an error QString _error; - QPointer _lsColJob; + QPointer _proFindJob; public: QByteArray _dataFingerprint; @@ -165,7 +160,7 @@ class DiscoveryPhase : public QObject * can be changed. See findAndCancelDeletedJob(). Note that * itemDiscovered() will already have been emitted for the item. */ - QMap _deletedItem; + QHash _deletedItem; /** Maps the db-path of a deleted folder to its queued job. * @@ -177,11 +172,12 @@ class DiscoveryPhase : public QObject * * See findAndCancelDeletedJob(). */ + // needs to be ordered QMap _queuedDeletedDirectories; // map source (original path) -> destinations (current server or local path) - QMap _renamedItemsRemote; - QMap _renamedItemsLocal; + QHash _renamedItemsRemote; + QHash _renamedItemsLocal; // set of paths that should not be removed even though they are removed locally: // there was a move to an invalid destination and now the source should be restored @@ -190,8 +186,8 @@ class DiscoveryPhase : public QObject // All entries should have a trailing slash (even files), so lookup with // lowerBound() is reliable. // - // The value of this map doesn't matter. - QMap _forbiddenDeletes; + // needs to be sorted + std::set _forbiddenDeletes; /** Returns whether the db-path has been renamed locally or on the remote. * @@ -203,18 +199,13 @@ class DiscoveryPhase : public QObject int _currentlyActiveJobs = 0; // both must contain a sorted list - QStringList _selectiveSyncBlackList; - QStringList _selectiveSyncWhiteList; + std::set _selectiveSyncBlackList; + std::set _selectiveSyncWhiteList; void scheduleMoreJobs(); bool isInSelectiveSyncBlackList(const QString &path) const; - // Check if the new folder should be deselected or not. - // May be async. "Return" via the callback, true if the item is blacklisted - void checkSelectiveSyncNewFolder(const QString &path, RemotePermissions rp, - std::function callback); - /** Given an original path, return the target path obtained when renaming is done. * * Note that it only considers parent directory renames. So if A/B got renamed to C/D, @@ -233,46 +224,59 @@ class DiscoveryPhase : public QObject * * See _deletedItem and _queuedDeletedDirectories. */ - QPair findAndCancelDeletedJob(const QString &originalPath); + QPair findAndCancelDeletedJob(const QString &originalPath); public: // input + DiscoveryPhase(Account *account, const SyncOptions &options, const QUrl &baseUrl, QObject *parent) + : QObject(parent) + , _syncOptions(options) + , _baseUrl(baseUrl) + , _account(account) + { + } + + const SyncOptions _syncOptions; + const QUrl _baseUrl; QString _localDir; // absolute path to the local directory. ends with '/' QString _remoteFolder; // remote folder, ends with '/' SyncJournalDb *_statedb; - AccountPtr _account; - SyncOptions _syncOptions; ExcludedFiles *_excludes; - QRegExp _invalidFilenameRx; // FIXME: maybe move in ExcludedFiles + QRegularExpression _invalidFilenameRx; // FIXME: maybe move in ExcludedFiles QStringList _serverBlacklistedFiles; // The blacklist from the capabilities bool _ignoreHiddenFiles = false; std::function _shouldDiscoverLocaly; void startJob(ProcessDirectoryJob *); - void setSelectiveSyncBlackList(const QStringList &list); - void setSelectiveSyncWhiteList(const QStringList &list); + void setSelectiveSyncBlackList(const QSet &list); + void setSelectiveSyncWhiteList(const QSet &list); // output QByteArray _dataFingerprint; bool _anotherSyncNeeded = false; -signals: + /** + * @return whether we are syncing to a ocis space or not + */ + bool isSpace() const; + +Q_SIGNALS: void fatalError(const QString &errorString); void itemDiscovered(const SyncFileItemPtr &item); void finished(); - // A new folder was discovered and was not synced because of the confirmation feature - void newBigFolder(const QString &folder, bool isExternal); - /** For excluded items that don't show up in itemDiscovered() * * The path is relative to the sync folder, similar to item->_file */ void silentlyExcluded(const QString &folderPath); - void excluded(const QString &folderPath, CSYNC_EXCLUDE_TYPE reason); + void excluded(const QString &folderPath); + +private: + QPointer _account; }; /// Implementation of DiscoveryPhase::adjustRenamedPath -QString adjustRenamedPath(const QMap &renamedItems, const QString &original); +QString adjustRenamedPath(const QHash &renamedItems, const QString &original); } diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index cd890de296d..1ca244edcf1 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -14,21 +14,30 @@ #include "filesystem.h" +#include "common/asserts.h" #include "common/utility.h" +#include +#include #include #include -#include -#include -#include #include "csync.h" #include "vio/csync_vio_local.h" #include "std/c_time.h" +#if defined(Q_OS_MAC) || defined(Q_OS_LINUX) +#include +#endif + #ifdef Q_OS_WIN32 +#include "common/utility_win.h" #include #endif +namespace { +static constexpr unsigned MaxValueSize = 1023; // This is without a terminating NUL character +} // anonymous namespace + namespace OCC { bool FileSystem::fileEquals(const QString &fn1, const QString &fn2) @@ -41,7 +50,7 @@ bool FileSystem::fileEquals(const QString &fn1, const QString &fn2) return false; } - if (getSize(fn1) != getSize(fn2)) { + if (getSize(QFileInfo{fn1}) != getSize(QFileInfo{fn2})) { return false; } @@ -82,33 +91,39 @@ bool FileSystem::setModTime(const QString &filename, time_t modTime) int rc = c_utimes(filename, times); if (rc != 0) { qCWarning(lcFileSystem) << "Error setting mtime for" << filename - << "failed: rc" << rc << ", errno:" << errno; + << "failed: rc" << rc << ", error message:" << strerror(errno); + Q_ASSERT(false); return false; } return true; } -bool FileSystem::fileChanged(const QString &fileName, - qint64 previousSize, - time_t previousMtime) +bool FileSystem::fileChanged(const QFileInfo &info, qint64 previousSize, time_t previousMtime, std::optional previousInode) { - return getSize(fileName) != previousSize - || getModTime(fileName) != previousMtime; -} - -bool FileSystem::verifyFileUnchanged(const QString &fileName, - qint64 previousSize, - time_t previousMtime) -{ - const qint64 actualSize = getSize(fileName); - const time_t actualMtime = getModTime(fileName); - if (actualSize != previousSize || actualMtime != previousMtime) { - qCInfo(lcFileSystem) << "File" << fileName << "has changed:" - << "size: " << previousSize << "<->" << actualSize - << ", mtime: " << previousMtime << "<->" << actualMtime; - return false; + // previousMtime == -1 indicates the file does not exist + if (!info.exists() && previousMtime != -1) { + qCDebug(lcFileSystem) << info.filePath() << "was removed"; + return true; } - return true; + const qint64 actualSize = getSize(info); + if (actualSize != previousSize) { + qCDebug(lcFileSystem) << "File" << info.filePath() << "has changed: size: " << previousSize << "<->" << actualSize; + return true; + } else { + const time_t actualMtime = getModTime(info.filePath()); + if (actualMtime != previousMtime) { + qCDebug(lcFileSystem) << "File" << info.filePath() << "has changed: mtime: " << previousMtime << "<->" << actualMtime; + return true; + } else if (previousInode.has_value()) { + quint64 actualIndoe; + FileSystem::getInode(info.filePath(), &actualIndoe); + if (previousInode.value() != actualIndoe) { + qCDebug(lcFileSystem) << "File" << info.filePath() << "has changed: inode" << previousInode.value() << "<-->" << actualIndoe; + return true; + } + } + } + return false; } #ifdef Q_OS_WIN @@ -125,59 +140,56 @@ static qint64 getSizeWithCsync(const QString &filename) } #endif -qint64 FileSystem::getSize(const QString &filename) +qint64 FileSystem::getSize(const QFileInfo &info) { #ifdef Q_OS_WIN - if (isLnkFile(filename)) { + if (isLnkFile(info.fileName())) { // Qt handles .lnk as symlink... https://doc.qt.io/qt-5/qfileinfo.html#details - return getSizeWithCsync(filename); + return getSizeWithCsync(info.filePath()); } #endif - return QFileInfo(filename).size(); + return info.size(); } // Code inspired from Qt5's QDir::removeRecursively -bool FileSystem::removeRecursively(const QString &path, const std::function &onDeleted, QStringList *errors) +bool FileSystem::removeRecursively(const QString &path, + RemoveEntryList *success, + RemoveEntryList *locked, + RemoveErrorList *errors) { bool allRemoved = true; QDirIterator di(path, QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); + QString removeError; while (di.hasNext()) { di.next(); const QFileInfo &fi = di.fileInfo(); - bool removeOk = false; // The use of isSymLink here is okay: // we never want to go into this branch for .lnk files - bool isDir = fi.isDir() && !fi.isSymLink() && !FileSystem::isJunction(fi.absoluteFilePath()); + const bool isDir = fi.isDir() && !fi.isSymLink() && !FileSystem::isJunction(fi.absoluteFilePath()); if (isDir) { - removeOk = removeRecursively(path + QLatin1Char('/') + di.fileName(), onDeleted, errors); // recursive + allRemoved &= removeRecursively(path + QLatin1Char('/') + di.fileName(), success, locked, errors); // recursive } else { - QString removeError; - removeOk = FileSystem::remove(di.filePath(), &removeError); - if (removeOk) { - if (onDeleted) - onDeleted(di.filePath(), false); + if (FileSystem::isFileLocked(di.filePath(), FileSystem::LockMode::Exclusive)) { + locked->push_back({ di.filePath(), isDir }); + allRemoved = false; } else { - if (errors) { - errors->append(QCoreApplication::translate("FileSystem", "Error removing '%1': %2") - .arg(QDir::toNativeSeparators(di.filePath()), removeError)); + if (FileSystem::remove(di.filePath(), &removeError)) { + success->push_back({ di.filePath(), isDir }); + } else { + errors->push_back({ { di.filePath(), isDir }, removeError }); + qCWarning(lcFileSystem) << "Error removing " << di.filePath() << ':' << removeError; + allRemoved = false; } - qCWarning(lcFileSystem) << "Error removing " << di.filePath() << ':' << removeError; } } - if (!removeOk) - allRemoved = false; } if (allRemoved) { allRemoved = QDir().rmdir(path); if (allRemoved) { - if (onDeleted) - onDeleted(path, true); + success->push_back({ path, true }); } else { - if (errors) { - errors->append(QCoreApplication::translate("FileSystem", "Could not remove folder '%1'") - .arg(QDir::toNativeSeparators(path))); - } + errors->push_back({ { path, true }, QCoreApplication::translate("FileSystem", "Could not remove folder") }); qCWarning(lcFileSystem) << "Error removing folder" << path; } } @@ -194,5 +206,100 @@ bool FileSystem::getInode(const QString &filename, quint64 *inode) return false; } +namespace { + +#ifdef Q_OS_LINUX + Q_ALWAYS_INLINE ssize_t getxattr(const char *path, const char *name, void *value, size_t size, u_int32_t, int) + { + return ::getxattr(path, name, value, size); + } + + Q_ALWAYS_INLINE int setxattr(const char *path, const char *name, const void *value, size_t size, u_int32_t, int) + { + return ::setxattr(path, name, value, size, 0); + } + + Q_ALWAYS_INLINE int removexattr(const char *path, const char *name, int) + { + return ::removexattr(path, name); + } +#endif // Q_OS_LINUX + +} // anonymous namespace + +std::optional FileSystem::Tags::get(const QString &path, const QString &key) +{ +#if defined(Q_OS_MAC) || defined(Q_OS_LINUX) + QString platformKey = key; + if (Utility::isLinux()) { + platformKey = QStringLiteral("user.") + platformKey; + } + + QByteArray value(MaxValueSize + 1, '\0'); // Add a NUL character to terminate a string + auto size = getxattr(path.toUtf8().constData(), platformKey.toUtf8().constData(), value.data(), MaxValueSize, 0, 0); + if (size != -1) { + value.truncate(size); + return value; + } +#elif defined(Q_OS_WIN) + QFile file(QStringLiteral("%1:%2").arg(path, key)); + if (file.open(QIODevice::ReadOnly)) { + return file.readAll(); + } +#endif // Q_OS_MAC || Q_OS_LINUX + + return {}; +} + +OCC::Result FileSystem::Tags::set(const QString &path, const QString &key, const QByteArray &value) +{ + OC_ASSERT(value.size() < MaxValueSize) + +#if defined(Q_OS_MAC) || defined(Q_OS_LINUX) + QString platformKey = key; + if (Utility::isLinux()) { + platformKey = QStringLiteral("user.") + platformKey; + } + + auto result = setxattr(path.toUtf8().constData(), platformKey.toUtf8().constData(), value.constData(), value.size(), 0, 0); + if (result != 0) { + return QString::fromUtf8(strerror(errno)); + } + + return {}; +#elif defined(Q_OS_WIN) + QFile file(QStringLiteral("%1:%2").arg(path, key)); + if (!file.open(QIODevice::WriteOnly)) { + return file.errorString(); + } + auto bytesWritten = file.write(value); + if (bytesWritten != value.size()) { + return QStringLiteral("wrote %1 out of %2 bytes").arg(QString::number(bytesWritten), QString::number(value.size())); + } + + return {}; +#else + return QStringLiteral("function not implemented"); +#endif // Q_OS_MAC || Q_OS_LINUX +} + +bool FileSystem::Tags::remove(const QString &path, const QString &key) +{ +#if defined(Q_OS_MAC) || defined(Q_OS_LINUX) + QString platformKey = key; + if (Utility::isLinux()) { + platformKey = QStringLiteral("user.%1").arg(platformKey); + } + + auto result = removexattr(path.toUtf8().constData(), platformKey.toUtf8().constData(), 0); + + return result == 0; +#elif defined(Q_OS_WIN) + return QFile::remove(QStringLiteral("%1:%2").arg(path, key)); +#else + return false; +#endif // Q_OS_MAC || Q_OS_LINUX +} + } // namespace OCC diff --git a/src/libsync/filesystem.h b/src/libsync/filesystem.h index 602f74bb7f2..3ad4f507e62 100644 --- a/src/libsync/filesystem.h +++ b/src/libsync/filesystem.h @@ -14,8 +14,6 @@ #pragma once -#include "config.h" - #include #include #include @@ -23,6 +21,7 @@ #include // Chain in the base include and extend the namespace #include "common/filesystembase.h" +#include "common/result.h" class QFile; @@ -61,7 +60,7 @@ namespace FileSystem { * Use this over QFileInfo::size() to avoid bugs with lnk files on Windows. * See https://bugreports.qt.io/browse/QTBUG-24831. */ - qint64 OWNCLOUDSYNC_EXPORT getSize(const QString &filename); + qint64 OWNCLOUDSYNC_EXPORT getSize(const QFileInfo &info); /** * @brief Retrieve a file inode with csync @@ -71,31 +70,42 @@ namespace FileSystem { /** * @brief Check if \a fileName has changed given previous size and mtime * - * Nonexisting files are covered through mtime: they have an mtime of -1. + * Nonexisting files are covered through mtime: they have an previousMtime of -1. * * @return true if the file's mtime or size are not what is expected. */ - bool OWNCLOUDSYNC_EXPORT fileChanged(const QString &fileName, - qint64 previousSize, - time_t previousMtime); + bool OWNCLOUDSYNC_EXPORT fileChanged(const QFileInfo &info, qint64 previousSize, time_t previousMtime, std::optional previousInode = {}); - /** - * @brief Like !fileChanged() but with verbose logging if the file *did* change. - */ - bool OWNCLOUDSYNC_EXPORT verifyFileUnchanged(const QString &fileName, - qint64 previousSize, - time_t previousMtime); + + struct RemoveEntry + { + const QString path; + const bool isDir; + }; + struct RemoveError + { + const RemoveEntry entry; + const QString error; + }; + + using RemoveEntryList = std::vector; + using RemoveErrorList = std::vector; /** * Removes a directory and its contents recursively * * Returns true if all removes succeeded. - * onDeleted() is called for each deleted file or directory, including the root. - * errors are collected in errors. */ bool OWNCLOUDSYNC_EXPORT removeRecursively(const QString &path, - const std::function &onDeleted = nullptr, - QStringList *errors = nullptr); + RemoveEntryList *success, + RemoveEntryList *locked, + RemoveErrorList *errors); + + namespace Tags { + std::optional OWNCLOUDSYNC_EXPORT get(const QString &path, const QString &key); + OCC::Result OWNCLOUDSYNC_EXPORT set(const QString &path, const QString &key, const QByteArray &value); + bool OWNCLOUDSYNC_EXPORT remove(const QString &path, const QString &key); + } } /** @} */ diff --git a/src/libsync/graphapi/CMakeLists.txt b/src/libsync/graphapi/CMakeLists.txt new file mode 100644 index 00000000000..54de1204269 --- /dev/null +++ b/src/libsync/graphapi/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources(libsync PRIVATE + jobs/drives.cpp + space.cpp + spacesmanager.cpp) + +target_include_directories(libsync PUBLIC $) diff --git a/src/libsync/graphapi/jobs/drives.cpp b/src/libsync/graphapi/jobs/drives.cpp new file mode 100644 index 00000000000..416861d5a5e --- /dev/null +++ b/src/libsync/graphapi/jobs/drives.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "drives.h" + +#include "account.h" + +#include +#include + + +using namespace OCC; +using namespace GraphApi; + +namespace { + +const auto mountpointC = QLatin1String("mountpoint"); +} + +Drives::Drives(Account *account, QObject *parent) + : JsonJob(account, account->url(), QStringLiteral("/graph/v1.0/me/drives"), "GET", {}, {}, parent) +{ +} + +Drives::~Drives() { } + +const QList &Drives::drives() const +{ + if (_drives.isEmpty() && parseError().error == QJsonParseError::NoError) { + OpenAPI::OAICollection_of_drives drives; + drives.fromJsonObject(data()); + _drives = drives.getValue(); + // At the moment we don't support mountpoints but use the Share Jail + _drives.erase( + std::remove_if(_drives.begin(), _drives.end(), [](const OpenAPI::OAIDrive &it) { return it.getDriveType() == mountpointC; }), _drives.end()); + } + return _drives; +} diff --git a/src/libsync/graphapi/jobs/drives.h b/src/libsync/graphapi/jobs/drives.h new file mode 100644 index 00000000000..bda75cca589 --- /dev/null +++ b/src/libsync/graphapi/jobs/drives.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "networkjobs/jsonjob.h" + +#include "owncloudlib.h" +#include + +namespace OCC { +namespace GraphApi { + + + class OWNCLOUDSYNC_EXPORT Drives : public JsonJob + { + Q_OBJECT + public: + Drives(Account *account, QObject *parent = nullptr); + ~Drives(); + const QList &drives() const; + + private: + mutable QList _drives; + }; +} +} diff --git a/src/libsync/graphapi/space.cpp b/src/libsync/graphapi/space.cpp new file mode 100644 index 00000000000..f42d34353cb --- /dev/null +++ b/src/libsync/graphapi/space.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "space.h" + +#include "libsync/graphapi/spacesmanager.h" +#include "libsync/networkjobs.h" +#include "libsync/networkjobs/resources.h" + +#include "resources/resources.h" + +using namespace OCC; +using namespace GraphApi; + +namespace { + +const auto personalC = QLatin1String("personal"); + +// https://github.com/cs3org/reva/blob/0cde0a3735beaa14ebdfd8988c3eb77b3c2ab0e6/pkg/utils/utils.go#L56-L59 + +// important detail about this id: the "shares" folder always has this id, even across different accounts! +// this means that we can never assume that space id's are unique across accounts, to the contrary, if a share is +// in play they most definitely are not. This concept could theoretically extend to other folders so we need to index +// space id against account id for many operations. +const auto sharesIdC = QLatin1String("a0ca6a90-a365-4782-871e-d44447bbc668$a0ca6a90-a365-4782-871e-d44447bbc668"); +} + +Space::Space(SpacesManager *spacesManager, const OpenAPI::OAIDrive &drive, const bool hasManyPersonalSpaces) + : QObject(spacesManager) + , _spaceManager(spacesManager) + , _image(new SpaceImage(this)) + , _hasManyPersonalSpaces(hasManyPersonalSpaces) +{ + // todo future refactoring: get this setDrive out of the ctr since it potentially kicks off a job for the SpaceImage before the Space is fully constructed + // propose removing the drive arg from the ctr completely, and moving the call to setDrive to the spacesmanager such that any call to + // "new" space is immediately followed by setDrive. + if (_spaceManager->account()) + _accountId = _spaceManager->account()->uuid(); + setDrive(drive); + connect(_image, &SpaceImage::imageChanged, this, &Space::imageChanged); +} + +QUuid Space::accountId() const +{ + return _accountId; +} + +bool Space::setDrive(const OpenAPI::OAIDrive &drive) +{ + + // first config naturally has an empty drive - reality check that updated drives are always valid + Q_ASSERT(drive.isValid()); + + QString curTag = _drive.getRoot().getETag(); + QString newTag = drive.getRoot().getETag(); + Q_ASSERT(!newTag.isEmpty()); + // the tag should change when the space is edited on the server. I verified that it changes on space rename as well + // as on changing the space image so we may have further wrinkles if there is an error in logic server side, but for now + // we want to reduce updates to "only when something changed" else everything is auto-refreshed periodically (eg every 30s) + if (curTag == newTag) + return false; + + _drive = drive; + _image->update(); + return true; +} + +QString Space::displayName() const +{ + if (_hasManyPersonalSpaces) { + return _drive.getName(); + } + + // other systems like oCIS have one personal and one shared space and their names are hard coded + if (_drive.getDriveType() == personalC) { + return tr("Personal"); + } + if (_drive.getId() == sharesIdC) { + // don't call it ShareJail + return tr("Shares"); + } + return _drive.getName(); +} + +QString Space::description() const +{ + return _drive.getDescription(); +} + + +uint32_t Space::sortPriority() const +{ + if (_drive.getDriveType() == personalC) { + return 100; + } else if (_drive.getId() == sharesIdC) { + return 50; + } + return 0; +} + +bool Space::disabled() const +{ + // this is how disabled spaces are represented in the graph API + return _drive.getRoot().getDeleted().getState() == QLatin1String("trashed"); +} + +SpaceImage *Space::image() const +{ + return _image; +} + +QString Space::id() const +{ + return _drive.getRoot().getId(); +} + +QUrl Space::webDavUrl() const +{ + return QUrl(_drive.getRoot().getWebDavUrl()); +} + +QUrl Space::webUrl() const +{ + return QUrl(_drive.getRoot().getWebUrl()); +} + +QString Space::eTag() const +{ + return _drive.getETag(); +} + +QList Space::getSpecialItems() const +{ + return _drive.getSpecial(); +} + +OpenAPI::OAIQuota Space::quota() const +{ + return _drive.getQuota(); +} + +SpaceImage::SpaceImage(Space *space) + : QObject(space) + , _space(space) +{ +} + +QIcon SpaceImage::image() const +{ + return _image; +} + +// this has to stay until we get rid of the folderWizard +QUrl SpaceImage::qmlImageUrl() const +{ + if (!_image.isNull()) { + return QUrl(QStringLiteral("image://space/%1/%2").arg(etag(), _space->id())); + } else { + // invalid space id to display the placeholder + return QUrl(QStringLiteral("image://space/placeholder")); + } +} + +// todo: ideally do the update WITH any other drive changes that may happen. +// the current problem is that we can't rely on spaceChanged to tell us the image changed because spaceChanged is emitted +// prior to calling this update (so if you get the image in response to spaceChanged you get the "old" image, not the new one). +// this means we have to do some fancy tricks to ensure we connect to the imageChanged signal "at just the right time" +// see the FolderItemUpdater for the example ick. +void SpaceImage::update() +{ + const auto &special = _space->getSpecialItems(); + const auto img = std::find_if(special.cbegin(), special.cend(), [](const auto &it) { return it.getSpecialFolder().getName() == QLatin1String("image"); }); + if (img != special.cend()) + { + // issue 12057: verified the image etag does change when the space's icon has been updated via the web interface + // check the etag before updating the members and creating the job. This should eliminate *many* pointless resource jobs which + // will exacerbate whatever is making the app crash when the space tries to clean up it's child jobs and one is already gone + QString newEtag = Utility::normalizeEtag(img->getETag()); + if (_etag == newEtag) + return; + + _etag = newEtag; + _url = QUrl(img->getWebDavUrl()); + + // todo DC-150: this job should run in the spaces manager, then just set the image on this spaceImage object. We don't need all these deps floating + // around at every level and we *definitely* don't want the spaces manager to hand out the account to whoever wants it + auto job = _space->_spaceManager->account()->resourcesCache()->makeGetJob(_url, this); + + QObject::connect(job, &SimpleNetworkJob::finishedSignal, _space, [job, this] { + if (job->httpStatusCode() == 200) { + _image = job->asIcon(); + job->deleteLater(); + emit imageChanged(); + } + }); + job->start(); + } +} diff --git a/src/libsync/graphapi/space.h b/src/libsync/graphapi/space.h new file mode 100644 index 00000000000..5db605ca3dd --- /dev/null +++ b/src/libsync/graphapi/space.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "owncloudlib.h" + +#include + +#include +#include +#include + +namespace OCC { +namespace GraphApi { + class SpacesManager; + class Space; + + class OWNCLOUDSYNC_EXPORT SpaceImage : public QObject + { + Q_OBJECT + Q_PROPERTY(QUrl qmlImageUrl READ qmlImageUrl NOTIFY imageChanged) + QML_ELEMENT + QML_UNCREATABLE("C++ only") + public: + SpaceImage(Space *space); + + [[nodiscard]] QUrl url() const { return _url; } + [[nodiscard]] QString etag() const { return _etag; } + [[nodiscard]] QIcon image() const; + + [[nodiscard]] QUrl qmlImageUrl() const; + + Q_SIGNALS: + void imageChanged(); + + private: + void update(); + + QUrl _url; + QString _etag; + QIcon _image; + Space *_space = nullptr; + + friend class Space; + }; + + class OWNCLOUDSYNC_EXPORT Space : public QObject + { + Q_OBJECT + Q_PROPERTY(SpaceImage *image READ image NOTIFY imageChanged) + QML_ELEMENT + QML_UNCREATABLE("Spaces can only be created by the SpacesManager") + public: + /*** + * Returns the display name of the drive. + * This is identical to drive.getName() for most drives. + * Exceptions: Personal spaces + */ + QString displayName() const; + QString description() const; + QUrl webUrl() const; + QString id() const; + QUrl webDavUrl() const; + QString eTag() const; + OpenAPI::OAIQuota quota() const; + + QUuid accountId() const; + + /*** + * Assign a sortPriority to a drive, used for sorting + */ + uint32_t sortPriority() const; + + /** + * Whether a drive object has been deleted. + */ + bool disabled() const; + + SpaceImage *image() const; + + + Q_SIGNALS: + void imageChanged(); + + protected: + QList getSpecialItems() const; + + + private: + Space(SpacesManager *spaceManager, const OpenAPI::OAIDrive &drive, bool hasManyPersonalSpaces); + bool setDrive(const OpenAPI::OAIDrive &drive); + + SpacesManager *_spaceManager; + QUuid _accountId; + OpenAPI::OAIDrive _drive; + + SpaceImage *_image; + + bool _hasManyPersonalSpaces; + + friend class SpacesManager; + friend class SpaceImage; + }; + +} // OCC +} // GraphApi diff --git a/src/libsync/graphapi/spacesmanager.cpp b/src/libsync/graphapi/spacesmanager.cpp new file mode 100644 index 00000000000..e48d2a6f2da --- /dev/null +++ b/src/libsync/graphapi/spacesmanager.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "spacesmanager.h" + +#include "libsync/account.h" +#include "libsync/creds/abstractcredentials.h" +#include "libsync/graphapi/jobs/drives.h" + + +#include + +#include + +using namespace std::chrono_literals; + +using namespace OCC; +using namespace GraphApi; + +namespace { +constexpr auto refreshTimeoutC = 30s; +} + +SpacesManager::SpacesManager(Account *parent) + : QObject(parent) + , _account(parent) + , _refreshTimer(new QTimer(this)) +{ + connect(_account, &Account::credentialsFetched, this, &SpacesManager::refresh); + + _refreshTimer->setInterval(refreshTimeoutC); + // the timer will be restarted once we received drives data + _refreshTimer->setSingleShot(true); + connect(_refreshTimer, &QTimer::timeout, this, &SpacesManager::refresh); +} + +void SpacesManager::refresh() +{ + if (!_account || !_account->accessManager()) { + return; + } + if (!_account->credentials()->ready()) { + return; + } + + // TODO: leak the job until we fixed the ownership https://github.com/owncloud/client/issues/11203 + // todo todo: I can't identify a leak here but who knows what lurks in the job handling...validate it's ok, as it seems to be + auto drivesJob = new Drives(_account, nullptr); + drivesJob->setTimeout(refreshTimeoutC); + connect(drivesJob, &Drives::finishedSignal, this, [drivesJob, this] { + drivesJob->deleteLater(); + + // a system which provides multiple personal spaces the name of the drive is always used as display name + auto hasManyPersonalSpaces = _account->capabilities().spacesSupport().hasMultiplePersonalSpaces; + QList newSpaces; + QList deletedSpaces; + + if (drivesJob->httpStatusCode() == 200) { + QList oldKeys = _spaces.keys(); + for (const auto &dr : drivesJob->drives()) { + bool driveDisabled = dr.getRoot().getDeleted().getState() == QLatin1String("trashed"); + // we need to treat any newly disabled spaces as if they were deleted so leave it alone. + // if an existing space is now disabled it will remain in the old key list for removal, below + if (driveDisabled) + continue; + + auto *space = _spaces.value(dr.getId(), nullptr); + if (space) { + oldKeys.removeOne(dr.getId()); + bool changed = space->setDrive(dr); + if (changed) + emit spaceChanged(space); + } else { + space = new Space(this, dr, hasManyPersonalSpaces); + _spaces.insert(dr.getId(), space); + emit spaceAdded(_account->uuid(), space); + newSpaces.append(space); + } + } + for (const QString &id : std::as_const(oldKeys)) { + auto *oldSpace = _spaces.take(id); + if (oldSpace) { + emit spaceAboutToBeRemoved(_account->uuid(), oldSpace); + deletedSpaces.append(id); + oldSpace->deleteLater(); + } + } + if (!_ready) { + _ready = true; + Q_EMIT ready(); + } + } + if (!newSpaces.isEmpty()) + emit spacesAdded(_account->uuid(), newSpaces, _spaces.count()); + if (!deletedSpaces.isEmpty()) + emit spacesRemoved(_account->uuid(), deletedSpaces, _spaces.count()); + // todo: remove this once the old accountSettings are gone + Q_EMIT updated(_account); + _refreshTimer->start(); + }); + _refreshTimer->stop(); + drivesJob->start(); +} + +Space *SpacesManager::space(const QString &id) const +{ + if (id.isEmpty()) + return nullptr; + return _spaces.value(id, nullptr); +} + +Account *SpacesManager::account() const +{ + return _account; +} + +QVector SpacesManager::spaces() const +{ + return {_spaces.begin(), _spaces.end()}; +} diff --git a/src/libsync/graphapi/spacesmanager.h b/src/libsync/graphapi/spacesmanager.h new file mode 100644 index 00000000000..9cf82a5201d --- /dev/null +++ b/src/libsync/graphapi/spacesmanager.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "owncloudlib.h" + +#include "libsync/graphapi/space.h" + +#include + +class QTimer; + +namespace OCC { + +class Account; + +namespace GraphApi { + + class OWNCLOUDSYNC_EXPORT SpacesManager : public QObject + { + Q_OBJECT + + public: + SpacesManager(Account *parent); + + Space *space(const QString &id) const; + + QVector spaces() const; + + // todo DC-150: remove this accessor and take responsibility for running job to retrieve/update space image as needed + // once that is complete we can get rid of the account memeber entirely (and even revert the parent arg to a simple QObject) + // by passing the value for hasManyPersonalSpaces to this via ctr + Account *account() const; + + bool isReady() const { return _ready; } + int spacesCount() const { return _spaces.count(); } + + Q_SIGNALS: + void spaceChanged(Space *space) const; + // I think this will go + void updated(Account *account); + void ready() const; + void spaceAdded(QUuid accountId, OCC::GraphApi::Space *space); + void spaceAboutToBeRemoved(QUuid accountId, OCC::GraphApi::Space *space); + // these are emitted after any/all processing of active spaces is complete, so eg for the space deleted + // we can only provide space id's since the pointers are gone. + void spacesAdded(QUuid accountId, QList spaces, int totalSpaceCount); + void spacesRemoved(QUuid accountId, QList deletedSpaces, int totalSpaceCount); + + private: + void refresh(); + + QPointer _account; + QTimer *_refreshTimer; + QHash _spaces; + bool _ready = false; + }; + +} +} diff --git a/src/libsync/httplogger.cpp b/src/libsync/httplogger.cpp index 8f22bbe4bdf..7f39883ee77 100644 --- a/src/libsync/httplogger.cpp +++ b/src/libsync/httplogger.cpp @@ -14,72 +14,122 @@ #include "httplogger.h" -#include -#include +#include "common/chronoelapsedtimer.h" + #include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std::chrono; namespace { Q_LOGGING_CATEGORY(lcNetworkHttp, "sync.httplogger", QtWarningMsg) const qint64 PeekSize = 1024 * 1024; -const QByteArray XRequestId(){ - return QByteArrayLiteral("X-Request-ID"); -} - bool isTextBody(const QString &s) { - static const QRegularExpression regexp(QStringLiteral("^(text/.*|(application/(xml|json|x-www-form-urlencoded)(;|$)))")); + static const QRegularExpression regexp(QStringLiteral("^(text/.*?|(application/(xml|.*?json|x-www-form-urlencoded)(;|$)))")); return regexp.match(s).hasMatch(); } -void logHttp(const QByteArray &verb, const QString &url, const QByteArray &id, const QString &contentType, const QList &header, QIODevice *device) +struct HttpContext +{ + HttpContext(const QNetworkRequest &request) + : originalUrl(QString::fromUtf8(request.url().toEncoded())) // Encoded URL as it is passed "over the wire" + , lastUrl(request.url()) + , id(QString::fromUtf8(request.rawHeader(QByteArrayLiteral("X-Request-ID")))) + { + } + + void addRedirect(const QUrl &url) + { + lastUrl = url; + redirectUrls.append(url.toString()); + } + + const QString originalUrl; + QUrl lastUrl; + QStringList redirectUrls; + const QString id; + + OCC::Utility::ChronoElapsedTimer timer; + bool send = false; +}; + +void logHttp(const QByteArray &verb, HttpContext *ctx, QJsonObject &&header, QIODevice *device, bool cached = false) { + static const bool redact = !qEnvironmentVariableIsSet("OWNCLOUD_HTTPLOGGER_NO_REDACT"); const auto reply = qobject_cast(device); - const auto contentLength = device ? device->size() : 0; - QString msg; - QTextStream stream(&msg); - stream << id << ": "; - if (!reply) { - stream << "Request: "; - } else { - stream << "Response: "; + + if (redact) { + const QString authKey = QStringLiteral("Authorization"); + const QString auth = header.value(authKey).toString(); + if (!auth.isEmpty()) { + header.insert(authKey, auth.startsWith(QStringLiteral("Bearer ")) ? QStringLiteral("Bearer [redacted]") : QStringLiteral("Basic [redacted]")); + } } - stream << verb; - if (reply) { - stream << " " << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + QJsonObject info{{QStringLiteral("method"), QString::fromUtf8(verb)}, {QStringLiteral("id"), ctx->id}, {QStringLiteral("url"), ctx->originalUrl}}; + + if (!ctx->redirectUrls.isEmpty()) { + info.insert(QStringLiteral("redirects"), QJsonArray::fromStringList(ctx->redirectUrls)); } - stream << " " << url << " Header: { "; - for (const auto &it : header) { - stream << it.first << ": "; - if (it.first == "Authorization") { - stream << (it.second.startsWith("Bearer ") ? "Bearer" : "Basic"); - stream << " [redacted]"; - } else { - stream << it.second; + + if (reply) { + // respond + QJsonObject replyInfo{{QStringLiteral("status"), reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()}, + {QStringLiteral("cached"), reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()}, + // downcast to int, this is json + {QStringLiteral("duration"), static_cast(duration_cast(ctx->timer.duration()).count())}, // + {QStringLiteral("durationString"), QDebug::toString(ctx->timer.duration())}, + {QStringLiteral("version"), + QStringLiteral("HTTP %1").arg( + reply->attribute(QNetworkRequest::Http2WasUsedAttribute).toBool() ? QStringLiteral("1.1") : QStringLiteral("2"))}}; + if (reply->error() != QNetworkReply::NoError) { + replyInfo.insert(QStringLiteral("error"), reply->errorString()); } - stream << ", "; + info.insert(QStringLiteral("reply"), replyInfo); + } else { + // request + info.insert(QStringLiteral("cached"), cached); } - stream << "} Data: ["; + + const auto contentLength = device ? device->size() : 0; + QJsonObject body = {{QStringLiteral("length"), device ? QString::number(contentLength) : QStringLiteral("null")}}; if (contentLength > 0) { + QString contentType = header.value(QStringLiteral("Content-Type")).toString(); + if (contentType.isEmpty()) { + contentType = header.value(QStringLiteral("content-type")).toString(); + } if (isTextBody(contentType)) { if (!device->isOpen()) { - Q_ASSERT(dynamic_cast(device)); // should we close it again? device->open(QIODevice::ReadOnly); } Q_ASSERT(device->pos() == 0); - stream << device->peek(PeekSize); + QString data = QString::fromUtf8(device->peek(PeekSize)); if (PeekSize < contentLength) { - stream << "...(" << (contentLength - PeekSize) << "bytes elided)"; + data += QStringLiteral("...(%1 bytes elided)").arg(QString::number(contentLength - PeekSize)); } + body[QStringLiteral("data")] = data; } else { - stream << contentLength << " bytes of " << contentType << " data"; + body[QStringLiteral("data")] = QStringLiteral("%1 bytes of %2 data").arg(QString::number(contentLength), contentType); } } - stream << "]"; - qCInfo(lcNetworkHttp) << msg; + + qCInfo(lcNetworkHttp).noquote() << (reply ? "RESPONSE" : "REQUEST") << ctx->id + << QJsonDocument{QJsonObject{{reply ? QStringLiteral("response") : QStringLiteral("request"), + QJsonObject{{QStringLiteral("info"), info}, {QStringLiteral("header"), header}, + {QStringLiteral("body"), body}}}}} + .toJson(QJsonDocument::Compact); } } @@ -88,31 +138,57 @@ namespace OCC { void HttpLogger::logRequest(QNetworkReply *reply, QNetworkAccessManager::Operation operation, QIODevice *device) { - const auto request = reply->request(); if (!lcNetworkHttp().isInfoEnabled()) { return; } - const auto keys = request.rawHeaderList(); - QList header; - header.reserve(keys.size()); - for (const auto &key : keys) { - header << qMakePair(key, request.rawHeader(key)); - } - logHttp(requestVerb(operation, request), - request.url().toString(), - request.rawHeader(XRequestId()), - request.header(QNetworkRequest::ContentTypeHeader).toString(), - header, - device); - - QObject::connect(reply, &QNetworkReply::finished, reply, [reply] { - logHttp(requestVerb(*reply), - reply->url().toString(), - reply->request().rawHeader(XRequestId()), - reply->header(QNetworkRequest::ContentTypeHeader).toString(), - reply->rawHeaderPairs(), - reply); - }); + + auto ctx = std::make_unique(reply->request()); + + const auto logError = [reply, operation, id = ctx->id]() { + auto url = reply->request().url(); + qCInfo(lcNetworkHttp).noquote().nospace() << "An error occurred for " << url.toDisplayString() << ": " << reply->errorString() << " (" << reply->error() + << ", " << operation << "), request-id: " << id; + }; + + // device should still exist, lets still use a qpointer to ensure we have valid data + const auto logSend = [ctx = ctx.get(), operation, reply, device = QPointer(device), deviceRaw = device, logError](bool cached = false) { + Q_ASSERT(!deviceRaw || device); + if (!ctx->send) { + ctx->send = true; + ctx->timer.reset(); + } else if (ctx->lastUrl != reply->url()) { + // this is a redirect + ctx->addRedirect(reply->url()); + } else { + // Probably an error (time-out?). + logError(); + } + + const auto request = reply->request(); + QJsonObject header; + for (const auto &key : request.rawHeaderList()) { + header[QString::fromUtf8(key)] = QString::fromUtf8(request.rawHeader(key)); + } + logHttp(requestVerb(operation, request), ctx, std::move(header), device, cached); + }; + QObject::connect(reply, &QNetworkReply::requestSent, reply, logSend, Qt::DirectConnection); + QObject::connect(reply, &QNetworkReply::errorOccurred, reply, logError, Qt::DirectConnection); + + + QObject::connect( + reply, &QNetworkReply::finished, reply, + [reply, ctx = std::move(ctx), logSend] { + ctx->timer.stop(); + if (!ctx->send) { + logSend(true); + } + QJsonObject header; + for (const auto &[key, value] : reply->rawHeaderPairs()) { + header[QString::fromUtf8(key)] = QString::fromUtf8(value); + } + logHttp(requestVerb(*reply), ctx.get(), std::move(header), reply); + }, + Qt::DirectConnection); } QByteArray HttpLogger::requestVerb(QNetworkAccessManager::Operation operation, const QNetworkRequest &request) diff --git a/src/libsync/jobqueue.cpp b/src/libsync/jobqueue.cpp index 72b9f3db00a..06702508b36 100644 --- a/src/libsync/jobqueue.cpp +++ b/src/libsync/jobqueue.cpp @@ -31,7 +31,7 @@ JobQueue::JobQueue(Account *account) void JobQueue::block() { _blocked++; - qCDebug(lcJobQUeue) << "block:" << _blocked << _account->displayName(); + qCDebug(lcJobQUeue) << "block:" << _blocked << _account->displayNameWithHost(); } void JobQueue::unblock() @@ -40,7 +40,7 @@ void JobQueue::unblock() return; } _blocked--; - qCDebug(lcJobQUeue) << "unblock:" << _blocked << _account->displayName(); + qCDebug(lcJobQUeue) << "unblock:" << _blocked << _account->displayNameWithHost(); if (_blocked == 0) { auto tmp = std::move(_jobs); for (auto job : tmp) { diff --git a/src/libsync/jobqueue.h b/src/libsync/jobqueue.h index 70a24b65abd..438849c1c14 100644 --- a/src/libsync/jobqueue.h +++ b/src/libsync/jobqueue.h @@ -47,14 +47,15 @@ class OWNCLOUDSYNC_EXPORT JobQueue size_t size() const; -private: - void block(); - void unblock(); /** * Clear the queue and abort all jobs */ void clear(); +private: + void block(); + void unblock(); + Account *_account; uint _blocked = 0; std::vector> _jobs; diff --git a/src/libsync/libsync.qrc b/src/libsync/libsync.qrc new file mode 100644 index 00000000000..6342c4490f9 --- /dev/null +++ b/src/libsync/libsync.qrc @@ -0,0 +1,5 @@ + + + sync-exclude.lst + + diff --git a/src/libsync/localdiscoverytracker.cpp b/src/libsync/localdiscoverytracker.cpp index 44cb909cab7..37b8b4bfc91 100644 --- a/src/libsync/localdiscoverytracker.cpp +++ b/src/libsync/localdiscoverytracker.cpp @@ -22,7 +22,8 @@ using namespace OCC; Q_LOGGING_CATEGORY(lcLocalDiscoveryTracker, "sync.localdiscoverytracker", QtInfoMsg) -LocalDiscoveryTracker::LocalDiscoveryTracker() +LocalDiscoveryTracker::LocalDiscoveryTracker(QObject *parent) + : QObject(parent) { } @@ -64,23 +65,50 @@ void LocalDiscoveryTracker::slotItemCompleted(const SyncFileItemPtr &item) // // For failures, we want to add the file to the list so the next sync // will be able to retry it. - if (item->_status == SyncFileItem::Success - || item->_status == SyncFileItem::FileIgnored - || item->_status == SyncFileItem::Restoration - || item->_status == SyncFileItem::Conflict - || (item->_status == SyncFileItem::NoStatus - && (item->_instruction == CSYNC_INSTRUCTION_NONE - || item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA))) { - if (_previousLocalDiscoveryPaths.erase(item->_file)) + + switch (item->_status) { + case SyncFileItem::NoStatus: + // we can't use the flags operator with CSYNC_INSTRUCTION_NONE + if (item->instruction() & ~(CSYNC_INSTRUCTION_NONE | CSYNC_INSTRUCTION_UPDATE_METADATA)) { + break; + } + Q_FALLTHROUGH(); + case SyncFileItem::Success: + Q_FALLTHROUGH(); + case SyncFileItem::FileIgnored: + Q_FALLTHROUGH(); + case SyncFileItem::Restoration: + Q_FALLTHROUGH(); + case SyncFileItem::Conflict: + Q_FALLTHROUGH(); + case OCC::SyncFileItem::Message: + if (_previousLocalDiscoveryPaths.erase(item->_file)) { qCDebug(lcLocalDiscoveryTracker) << "wiped successful item" << item->_file; - if (!item->_renameTarget.isEmpty() && _previousLocalDiscoveryPaths.erase(item->_renameTarget)) + } + if (!item->_renameTarget.isEmpty() && _previousLocalDiscoveryPaths.erase(item->_renameTarget)) { qCDebug(lcLocalDiscoveryTracker) << "wiped successful item" << item->_renameTarget; - } else if (item->_status == SyncFileItem::StatusCount) { + } + return; + case OCC::SyncFileItem::FatalError: + Q_FALLTHROUGH(); + case OCC::SyncFileItem::NormalError: + Q_FALLTHROUGH(); + case OCC::SyncFileItem::SoftError: + Q_FALLTHROUGH(); + case OCC::SyncFileItem::DetailError: + Q_FALLTHROUGH(); + case OCC::SyncFileItem::BlacklistedError: + Q_FALLTHROUGH(); + case OCC::SyncFileItem::Excluded: + [[fallthrough]]; + case OCC::SyncFileItem::FilenameReserved: + break; + case SyncFileItem::StatusCount: Q_UNREACHABLE(); - } else { - _localDiscoveryPaths.insert(item->_file); - qCDebug(lcLocalDiscoveryTracker) << "inserted error item" << item->_file; } + + _localDiscoveryPaths.insert(item->_file); + qCDebug(lcLocalDiscoveryTracker) << "inserted error item" << item->_file; } void LocalDiscoveryTracker::slotSyncFinished(bool success) diff --git a/src/libsync/localdiscoverytracker.h b/src/libsync/localdiscoverytracker.h index 1893e9402dd..ade70ecf634 100644 --- a/src/libsync/localdiscoverytracker.h +++ b/src/libsync/localdiscoverytracker.h @@ -50,7 +50,7 @@ class OWNCLOUDSYNC_EXPORT LocalDiscoveryTracker : public QObject { Q_OBJECT public: - LocalDiscoveryTracker(); + LocalDiscoveryTracker(QObject *parent); /** Adds a path that must be locally rediscovered later. * @@ -68,7 +68,7 @@ class OWNCLOUDSYNC_EXPORT LocalDiscoveryTracker : public QObject /** Access list of files that shall be locally rediscovered. */ const std::set &localDiscoveryPaths() const; -public slots: +public Q_SLOTS: /** * Success and failure of sync items adjust what the next sync is * supposed to do. diff --git a/src/libsync/logger.cpp b/src/libsync/logger.cpp index d5c89f27496..5863e963c12 100644 --- a/src/libsync/logger.cpp +++ b/src/libsync/logger.cpp @@ -13,14 +13,12 @@ */ #include "logger.h" - -#include "config.h" +#include "configfile.h" +#include "theme.h" #include -#include -#include +#include #include -#include #include @@ -33,7 +31,9 @@ #endif namespace { -constexpr int CrashLogSize = 20; +constexpr int crashLogSizeC = 20; +constexpr int maxLogSizeC = 1024 * 1024 * 100; // 100 MiB +constexpr int minLogsToKeepC = 5; #ifdef Q_OS_WIN bool isDebuggerPresent() @@ -52,21 +52,22 @@ namespace OCC { Logger *Logger::instance() { static auto *log = [] { - auto log = new Logger; + auto _log = new Logger; qAddPostRoutine([] { Logger::instance()->close(); delete Logger::instance(); }); - return log; + return _log; }(); return log; } Logger::Logger(QObject *parent) : QObject(parent) + , _maxLogFiles(std::max(ConfigFile().automaticDeleteOldLogs(), minLogsToKeepC)) { qSetMessagePattern(loggerPattern()); - _crashLog.resize(CrashLogSize); + _crashLog.resize(crashLogSizeC); #ifndef NO_MSG_HANDLER qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &ctx, const QString &message) { Logger::instance()->doLog(type, ctx, message); @@ -77,19 +78,19 @@ Logger::Logger(QObject *parent) Logger::~Logger() { #ifndef NO_MSG_HANDLER - qInstallMessageHandler(0); + qInstallMessageHandler(nullptr); #endif } QString Logger::loggerPattern() { - return QStringLiteral("%{time MM-dd hh:mm:ss:zzz} [ %{type} %{category} ]%{if-debug}\t[ %{function} ]%{endif}:\t%{message}"); + return QStringLiteral("%{time yy-MM-dd hh:mm:ss:zzz} [ %{type} %{category} ]%{if-debug}\t[ %{function} ]%{endif}:\t%{message}"); } bool Logger::isLoggingToFile() const { QMutexLocker lock(&_mutex); - return _logstream; + return !_logstream.isNull(); } void Logger::doLog(QtMsgType type, const QMessageLogContext &ctx, const QString &message) @@ -97,7 +98,7 @@ void Logger::doLog(QtMsgType type, const QMessageLogContext &ctx, const QString const QString msg = qFormatLogMessage(type, ctx, message) + QLatin1Char('\n'); { QMutexLocker lock(&_mutex); - _crashLogIndex = (_crashLogIndex + 1) % CrashLogSize; + _crashLogIndex = (_crashLogIndex + 1) % crashLogSizeC; _crashLog[_crashLogIndex] = msg; if (_logstream) { (*_logstream) << msg; @@ -117,7 +118,33 @@ void Logger::doLog(QtMsgType type, const QMessageLogContext &ctx, const QString Utility::crash(); #endif } + if (!_logDirectory.isEmpty()) { + if (_logFile.size() > maxLogSizeC) { + rotateLog(); + } + } + } +} + +void Logger::open(const QString &name) +{ + bool openSucceeded = false; + if (name == QLatin1Char('-')) { + attacheToConsole(); + setLogFlush(true); + openSucceeded = _logFile.open(stdout, QIODevice::WriteOnly); + } else { + _logFile.setFileName(name); + openSucceeded = _logFile.open(QIODevice::WriteOnly); + } + + if (!openSucceeded) { + std::cerr << "Failed to open the log file" << std::endl; + return; } + _logstream.reset(new QTextStream(&_logFile)); + _logstream->setEncoding(QStringConverter::Utf8); + (*_logstream) << Theme::instance()->aboutVersions(Theme::VersionFormat::OneLiner) << " " << qApp->applicationName() << Qt::endl; } void Logger::close() @@ -142,32 +169,18 @@ void Logger::setLogFile(const QString &name) return; } - bool openSucceeded = false; - if (name == QLatin1String("-")) { - attacheToConsole(); - openSucceeded = _logFile.open(stdout, QIODevice::WriteOnly); - } else { - _logFile.setFileName(name); - openSucceeded = _logFile.open(QIODevice::WriteOnly); - } - - if (!openSucceeded) { - std::cerr << "Failed to open the log file" << std::endl; - return; - } - - _logstream.reset(new QTextStream(&_logFile)); - _logstream->setCodec("UTF-8"); + open(name); } -void Logger::setLogExpire(std::chrono::hours expire) +void Logger::setMaxLogFiles(int i) { - _logExpire = expire; + _maxLogFiles = std::max(i, std::max(ConfigFile().automaticDeleteOldLogs(), minLogsToKeepC)); } void Logger::setLogDir(const QString &dir) { _logDirectory = dir; + rotateLog(); } void Logger::setLogFlush(bool flush) @@ -188,7 +201,7 @@ void Logger::setLogDebug(bool debug) QString Logger::temporaryFolderLogDirPath() const { - return QDir::temp().filePath(QStringLiteral(APPLICATION_SHORTNAME "-logdir")); + return QDir::temp().absoluteFilePath(QStringLiteral("%1-logdir").arg(QCoreApplication::applicationName())); } void Logger::setupTemporaryFolderLogDir() @@ -205,8 +218,6 @@ void Logger::disableTemporaryFolderLogDir() { if (!_temporaryFolderLogDir) return; - - enterNextLogFile(); setLogDir(QString()); setLogDebug(false); setLogFile(QString()); @@ -229,12 +240,12 @@ void Logger::setLogRules(const QSet &rules) void Logger::dumpCrashLog() { - QFile logFile(QDir::tempPath() + QStringLiteral("/" APPLICATION_NAME "-crash.log")); + QFile logFile(QStringLiteral("%1/%2-crash.log").arg(QDir::tempPath(), qApp->applicationName())); if (logFile.open(QFile::WriteOnly)) { QTextStream out(&logFile); - out.setCodec("UTF-8"); - for (int i = 1; i <= CrashLogSize; ++i) { - out << _crashLog[(_crashLogIndex + i) % CrashLogSize]; + out.setEncoding(QStringConverter::Utf8); + for (int i = 1; i <= crashLogSizeC; ++i) { + out << _crashLog[(_crashLogIndex + i) % crashLogSizeC]; } } } @@ -261,7 +272,7 @@ static bool compressLog(const QString &originalName, const QString &targetName) return true; } -void Logger::enterNextLogFile() +void Logger::rotateLog() { if (!_logDirectory.isEmpty()) { @@ -271,44 +282,53 @@ void Logger::enterNextLogFile() } // Tentative new log name, will be adjusted if one like this already exists - QDateTime now = QDateTime::currentDateTime(); - QString newLogName = now.toString(QStringLiteral("yyyyMMdd_HHmm")) + QStringLiteral("_owncloud.log"); - - // Expire old log files and deal with conflicts - const auto &files = dir.entryList(QStringList(QStringLiteral("*owncloud.log.*")), - QDir::Files, QDir::Name); - QRegExp rx(QStringLiteral(R"(.*owncloud\.log\.(\d+).*)")); - int maxNumber = -1; - for (const auto &s : files) { - if (_logExpire.count() > 0) { - std::chrono::seconds expireSeconds(_logExpire); - QFileInfo fileInfo(dir.absoluteFilePath(s)); - if (fileInfo.lastModified().addSecs(expireSeconds.count()) < now) { - dir.remove(s); - } - } - if (s.startsWith(newLogName) && rx.exactMatch(s)) { - maxNumber = qMax(maxNumber, rx.cap(1).toInt()); - } + const QString logName = dir.filePath(QStringLiteral("%1.log").arg(qApp->applicationName())); + QString previousLog; + + if (_logFile.isOpen()) { + _logFile.close(); } - newLogName.append(QLatin1Char('.') + QString::number(maxNumber + 1)); - - auto previousLog = _logFile.fileName(); - setLogFile(dir.filePath(newLogName)); - - // Compress the previous log file. On a restart this can be the most recent - // log file. - auto logToCompress = previousLog; - if (logToCompress.isEmpty() && files.size() > 0 && !files.last().endsWith(QLatin1String(".gz"))) - logToCompress = dir.absoluteFilePath(files.last()); - if (!logToCompress.isEmpty()) { - QString compressedName = logToCompress + QStringLiteral(".gz"); - if (compressLog(logToCompress, compressedName)) { - QFile::remove(logToCompress); - } else { - QFile::remove(compressedName); + // rename previous log file if size != 0 + const auto info = QFileInfo(logName); + if (info.exists(logName) && info.size() != 0) { + previousLog = + dir.filePath(QStringLiteral("%1-%2.log").arg(qApp->applicationName(), info.birthTime().toString(QStringLiteral("MMdd_hh.mm.ss.zzz")))); + if (!QFile(logName).rename(previousLog)) { + std::cerr << "Failed to rename: " << qPrintable(logName) << " to " << qPrintable(previousLog) << std::endl; } } + + const auto now = QDateTime::currentDateTime(); + open(logName); + // set the creation time to now + _logFile.setFileTime(now, QFileDevice::FileTime::FileBirthTime); + + std::ignore = QtConcurrent::run([now, previousLog, dir, maxLogFiles = _maxLogFiles] { + // Compress the previous log file. + if (!previousLog.isEmpty() && QFileInfo::exists(previousLog)) { + QString compressedName = QStringLiteral("%1.gz").arg(previousLog); + if (compressLog(previousLog, compressedName)) { + QFile::remove(previousLog); + } else { + QFile::remove(compressedName); + } + } + + // Expire old log files and deal with conflicts + { + auto oldLogFiles = dir.entryList(QStringList(QStringLiteral("*%1-*.log.gz").arg(qApp->applicationName())), QDir::Files, QDir::Name); + + // keeping the last maxLogFiles files in total (need to subtract one from maxLogFiles to ensure the limit) + std::sort(oldLogFiles.begin(), oldLogFiles.end(), std::greater()); + oldLogFiles.erase(oldLogFiles.begin(), oldLogFiles.begin() + std::min(maxLogFiles - 1, oldLogFiles.size())); + + for (const auto &s : oldLogFiles) { + if (!QFile::remove(dir.absoluteFilePath(s))) { + std::cerr << "warning: failed to remove old log file" << qPrintable(s) << std::endl; + } + } + } + }); } } diff --git a/src/libsync/logger.h b/src/libsync/logger.h index 95a0e18b09f..8f3f69ad3e7 100644 --- a/src/libsync/logger.h +++ b/src/libsync/logger.h @@ -15,15 +15,14 @@ #ifndef LOGGER_H #define LOGGER_H -#include -#include #include #include +#include +#include +#include +#include #include -#include -#include -#include "common/utility.h" #include "owncloudlib.h" namespace OCC { @@ -47,10 +46,15 @@ class OWNCLOUDSYNC_EXPORT Logger : public QObject static Logger *instance(); void setLogFile(const QString &name); - void setLogExpire(std::chrono::hours expire); void setLogDir(const QString &dir); void setLogFlush(bool flush); + /** + * Set the maximum number of logs files to keep. + * Setting values below 5 will have no effect. + */ + void setMaxLogFiles(int i); + bool logDebug() const { return _logDebug; } void setLogDebug(bool debug); @@ -78,28 +82,29 @@ class OWNCLOUDSYNC_EXPORT Logger : public QObject } void setLogRules(const QSet &rules); -public slots: - void enterNextLogFile(); - private: Logger(QObject *parent = nullptr); ~Logger() override; + void rotateLog(); + + void open(const QString &name); void close(); void dumpCrashLog(); QFile _logFile; bool _doFileFlush = false; - std::chrono::hours _logExpire; bool _logDebug = false; QScopedPointer _logstream; - mutable QMutex _mutex; + mutable QRecursiveMutex _mutex; QString _logDirectory; bool _temporaryFolderLogDir = false; QSet _logRules; QVector _crashLog; int _crashLogIndex = 0; bool _consoleIsAttached = false; + + int _maxLogFiles; }; } // namespace OCC diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index 1a70e1cc5cc..8026ae68a9e 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -13,136 +13,46 @@ * for more details. */ -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include #include -#include -#include -#ifndef TOKEN_AUTH_ONLY +#include #include #include -#endif -#include "networkjobs.h" #include "account.h" -#include "owncloudpropagator.h" +#include "networkjobs.h" -#include "creds/abstractcredentials.h" -#include "creds/httpcredentials.h" using namespace std::chrono_literals; namespace OCC { Q_LOGGING_CATEGORY(lcEtagJob, "sync.networkjob.etag", QtInfoMsg) -Q_LOGGING_CATEGORY(lcLsColJob, "sync.networkjob.lscol", QtInfoMsg) -Q_LOGGING_CATEGORY(lcCheckServerJob, "sync.networkjob.checkserver", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropfindJob, "sync.networkjob.propfind", QtInfoMsg) Q_LOGGING_CATEGORY(lcAvatarJob, "sync.networkjob.avatar", QtInfoMsg) Q_LOGGING_CATEGORY(lcMkColJob, "sync.networkjob.mkcol", QtInfoMsg) -Q_LOGGING_CATEGORY(lcProppatchJob, "sync.networkjob.proppatch", QtInfoMsg) -Q_LOGGING_CATEGORY(lcJsonApiJob, "sync.networkjob.jsonapi", QtInfoMsg) -Q_LOGGING_CATEGORY(lcDetermineAuthTypeJob, "sync.networkjob.determineauthtype", QtInfoMsg) - -QByteArray parseEtag(const QByteArray &header) -{ - if (header.isEmpty()) - return QByteArray(); - QByteArray arr = header; - // Weak E-Tags can appear when gzip compression is on, see #3946 - if (arr.startsWith("W/")) - arr = arr.mid(2); - - // https://github.com/owncloud/client/issues/1195 - arr.replace("-gzip", ""); - - if (arr.length() >= 2 && arr.startsWith('"') && arr.endsWith('"')) { - arr = arr.mid(1, arr.length() - 2); - } - return arr; -} - -RequestEtagJob::RequestEtagJob(AccountPtr account, const QString &path, QObject *parent) - : AbstractNetworkJob(account, path, parent) +RequestEtagJob::RequestEtagJob(Account *account, const QUrl &rootUrl, const QString &path, QObject *parent) + : PropfindJob(account, rootUrl, path, PropfindJob::Depth::Zero, parent) { -} - -void RequestEtagJob::start() -{ - QNetworkRequest req; - req.setRawHeader("Depth", "0"); - - QByteArray xml("\n" - "\n" - " \n" - " \n" - " \n" - "\n"); - QBuffer *buf = new QBuffer(this); - buf->setData(xml); - buf->open(QIODevice::ReadOnly); - // assumes ownership - sendRequest("PROPFIND", makeDavUrl(path()), req, buf); - AbstractNetworkJob::start(); -} - -bool RequestEtagJob::finished() -{ - qCInfo(lcEtagJob) << "Request Etag of" << reply()->request().url() << "FINISHED WITH STATUS" - << replyStatusString(); - - auto httpCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (httpCode == 207) { - // Parse DAV response - QXmlStreamReader reader(reply()); - reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration(QStringLiteral("d"), QStringLiteral("DAV:"))); - QByteArray etag; - while (!reader.atEnd()) { - QXmlStreamReader::TokenType type = reader.readNext(); - if (type == QXmlStreamReader::StartElement && reader.namespaceUri() == QLatin1String("DAV:")) { - QString name = reader.name().toString(); - if (name == QLatin1String("getetag")) { - auto etagText = reader.readElementText(); - auto parsedTag = parseEtag(etagText.toUtf8()); - if (!parsedTag.isEmpty()) { - etag += parsedTag; - } else { - etag += etagText.toUtf8(); - } - } - } + setProperties({ QByteArrayLiteral("getetag") }); + connect(this, &PropfindJob::directoryListingIterated, this, [this](const QString &, const QMap &value) { + _etag = Utility::normalizeEtag(value.value(QStringLiteral("getetag"))); + // the server returned a 207 but no etag, something is wrong + if (!OC_ENSURE_NOT(_etag.isEmpty())) { + abort(); } - emit etagRetreived(etag, QDateTime::fromString(QString::fromUtf8(_responseTimestamp), Qt::RFC2822Date)); - emit finishedWithResult(etag); - } else { - emit finishedWithResult(HttpError{ httpCode, errorString() }); - } - return true; + }); } -/*********************************************************************************************/ - -MkColJob::MkColJob(AccountPtr account, const QString &path, QObject *parent) - : AbstractNetworkJob(account, path, parent) +const QString &RequestEtagJob::etag() const { + return _etag; } +/*********************************************************************************************/ -MkColJob::MkColJob(AccountPtr account, const QUrl &url, - const QMap &extraHeaders, QObject *parent) - : AbstractNetworkJob(account, QString(), parent) - , _url(url) +MkColJob::MkColJob(Account *account, const QUrl &url, const QString &path, const QMap &extraHeaders, QObject *parent) + : AbstractNetworkJob(account, url, path, parent) , _extraHeaders(extraHeaders) { } @@ -157,15 +67,11 @@ void MkColJob::start() } // assumes ownership - if (_url.isValid()) { - sendRequest("MKCOL", _url, req); - } else { - sendRequest("MKCOL", makeDavUrl(path()), req); - } + sendRequest("MKCOL", req); AbstractNetworkJob::start(); } -bool MkColJob::finished() +void MkColJob::finished() { qCInfo(lcMkColJob) << "MKCOL of" << reply()->request().url() << "FINISHED WITH STATUS" << replyStatusString(); @@ -175,7 +81,6 @@ bool MkColJob::finished() } else { Q_EMIT finishedWithoutError(); } - return true; } /*********************************************************************************************/ @@ -218,7 +123,7 @@ bool LsColXMLParser::parse(const QByteArray &xml, QHash *sizes, QString currentHref; QMap currentTmpProperties; QMap currentHttp200Properties; - bool currentPropsHaveHttp200 = false; + bool currentPropsAreValid = false; bool insidePropstat = false; bool insideProp = false; bool insideMultiStatus = false; @@ -233,7 +138,7 @@ bool LsColXMLParser::parse(const QByteArray &xml, QHash *sizes, // but the result will have URL encoding.. QString hrefString = QString::fromUtf8(QByteArray::fromPercentEncoding(reader.readElementText().toUtf8())); if (!hrefString.startsWith(expectedPath)) { - qCWarning(lcLsColJob) << "Invalid href" << hrefString << "expected starting with" << expectedPath; + qCWarning(lcPropfindJob) << "Invalid href" << hrefString << "expected starting with" << expectedPath; return false; } currentHref = hrefString; @@ -242,10 +147,10 @@ bool LsColXMLParser::parse(const QByteArray &xml, QHash *sizes, insidePropstat = true; } else if (name == QLatin1String("status") && insidePropstat) { QString httpStatus = reader.readElementText(); - if (httpStatus.startsWith(QLatin1String("HTTP/1.1 200"))) { - currentPropsHaveHttp200 = true; + if (httpStatus.startsWith(QLatin1String("HTTP/1.1 200")) || httpStatus.startsWith(QLatin1String("HTTP/1.1 425"))) { + currentPropsAreValid = true; } else { - currentPropsHaveHttp200 = false; + currentPropsAreValid = false; } } else if (name == QLatin1String("prop")) { insideProp = true; @@ -278,15 +183,15 @@ bool LsColXMLParser::parse(const QByteArray &xml, QHash *sizes, if (currentHref.endsWith(QLatin1Char('/'))) { currentHref.chop(1); } - emit directoryListingIterated(currentHref, currentHttp200Properties); + Q_EMIT directoryListingIterated(currentHref, currentHttp200Properties); currentHref.clear(); currentHttp200Properties.clear(); } else if (reader.name() == QLatin1String("propstat")) { insidePropstat = false; - if (currentPropsHaveHttp200) { + if (currentPropsAreValid) { currentHttp200Properties = std::move(currentTmpProperties); } - currentPropsHaveHttp200 = false; + currentPropsAreValid = false; } else if (reader.name() == QLatin1String("prop")) { insideProp = false; } @@ -296,100 +201,58 @@ bool LsColXMLParser::parse(const QByteArray &xml, QHash *sizes, if (reader.hasError()) { // XML Parser error? Whatever had been emitted before will come as directoryListingIterated - qCWarning(lcLsColJob) << "ERROR" << reader.errorString() << xml; + qCWarning(lcPropfindJob) << "ERROR" << reader.errorString() << xml; return false; } else if (!insideMultiStatus) { - qCWarning(lcLsColJob) << "ERROR no WebDAV response?" << xml; + qCWarning(lcPropfindJob) << "ERROR no WebDAV response?" << xml; return false; } else { - emit directoryListingSubfolders(folders); - emit finishedWithoutError(); + Q_EMIT directoryListingSubfolders(folders); + Q_EMIT finishedWithoutError(); } return true; } /*********************************************************************************************/ -LsColJob::LsColJob(AccountPtr account, const QString &path, QObject *parent) - : AbstractNetworkJob(account, QString(), parent) - , _url(makeDavUrl(path)) -{ -} - -LsColJob::LsColJob(AccountPtr account, const QUrl &url, QObject *parent) - : AbstractNetworkJob(account, QString(), parent) - , _url(url) +PropfindJob::PropfindJob(Account *account, const QUrl &url, const QString &path, Depth depth, QObject *parent) + : AbstractNetworkJob(account, url, path, parent) + , _depth(depth) { + // Always have a higher priority than the propagator because we use this from the UI + // and really want this to be done first (no matter what internal scheduling QNAM uses). + // Also possibly useful for avoiding false timeouts. + setPriority(QNetworkRequest::HighPriority); } -void LsColJob::setProperties(const QList &properties) +void PropfindJob::setProperties(const QList &properties) { _properties = properties; } -QList LsColJob::properties() const +QList PropfindJob::properties() const { return _properties; } -void LsColJob::start() +void PropfindJob::start() { QNetworkRequest req; - req.setRawHeader(QByteArrayLiteral("Depth"), QByteArrayLiteral("1")); - startImpl(req); -} - -// TODO: Instead of doing all in this slot, we should iteratively parse in readyRead(). This -// would allow us to be more asynchronous in processing while data is coming from the network, -// not all in one big blob at the end. -bool LsColJob::finished() -{ - qCInfo(lcLsColJob) << "LSCOL of" << reply()->request().url() << "FINISHED WITH STATUS" - << replyStatusString(); - - QString contentType = reply()->header(QNetworkRequest::ContentTypeHeader).toString(); - int httpCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (httpCode == 207 && contentType.contains(QLatin1String("application/xml; charset=utf-8"))) { - LsColXMLParser parser; - connect(&parser, &LsColXMLParser::directoryListingSubfolders, - this, &LsColJob::directoryListingSubfolders); - connect(&parser, &LsColXMLParser::directoryListingIterated, - this, &LsColJob::directoryListingIterated); - connect(&parser, &LsColXMLParser::finishedWithError, - this, &LsColJob::finishedWithError); - connect(&parser, &LsColXMLParser::finishedWithoutError, - this, &LsColJob::finishedWithoutError); - - QString expectedPath = reply()->request().url().path(); // something like "/owncloud/remote.php/webdav/folder" - if (!parser.parse(reply()->readAll(), &_sizes, expectedPath)) { - // XML parse error - emit finishedWithError(reply()); - } - } else if (httpCode == 207) { - // wrong content type - emit finishedWithError(reply()); - } else { - // wrong HTTP code or any other network error - emit finishedWithError(reply()); - } - - return true; -} + req.setRawHeader(QByteArrayLiteral("Depth"), QByteArray::number(static_cast(_depth))); + req.setRawHeader(QByteArrayLiteral("Prefer"), QByteArrayLiteral("return=minimal")); -void LsColJob::startImpl(const QNetworkRequest &req) -{ if (_properties.isEmpty()) { - qCWarning(lcLsColJob) << "Propfind with no properties!"; + qCWarning(lcPropfindJob) << "Propfind with no properties!"; } QByteArray data; { QTextStream stream(&data, QIODevice::WriteOnly); - stream.setCodec("UTF-8"); + stream.setEncoding(QStringConverter::Utf8); stream << QByteArrayLiteral("" "" ""); - for (const QByteArray &prop : qAsConst(_properties)) { + for (const QByteArray &prop : std::as_const(_properties)) { const int colIdx = prop.lastIndexOf(':'); if (colIdx >= 0) { stream << QByteArrayLiteral("<") << prop.mid(colIdx + 1) << QByteArrayLiteral(" xmlns=\"") << prop.left(colIdx) << QByteArrayLiteral("\"/>"); @@ -397,210 +260,85 @@ void LsColJob::startImpl(const QNetworkRequest &req) stream << QByteArrayLiteral(""); } } - stream << QByteArrayLiteral("M" + stream << QByteArrayLiteral("" "\n"); } QBuffer *buf = new QBuffer(this); buf->setData(data); buf->open(QIODevice::ReadOnly); - sendRequest(QByteArrayLiteral("PROPFIND"), _url, req, buf); + sendRequest(QByteArrayLiteral("PROPFIND"), req, buf); AbstractNetworkJob::start(); } -const QHash &LsColJob::sizes() const -{ - return _sizes; -} - -/*********************************************************************************************/ - -CheckServerJob::CheckServerJob(AccountPtr account, QObject *parent) - : AbstractNetworkJob(account, QStringLiteral("status.php"), parent) - , _subdirFallback(false) -{ - setIgnoreCredentialFailure(true); - setAuthenticationJob(true); -} - -void CheckServerJob::start() -{ - _serverUrl = account()->url(); - QNetworkRequest req; - // don't authenticate the request to a possibly external service - req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true); - req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); - req.setRawHeader(QByteArrayLiteral("OC-Connection-Validator"), QByteArrayLiteral("desktop")); - req.setMaximumRedirectsAllowed(_maxRedirectsAllowed); - sendRequest("GET", Utility::concatUrlPath(_serverUrl, path()), req); - connect(reply(), &QNetworkReply::metaDataChanged, this, &CheckServerJob::metaDataChangedSlot); - connect(reply(), &QNetworkReply::encrypted, this, &CheckServerJob::encryptedSlot); - connect(reply(), &QNetworkReply::redirected, this, [this] { - const auto code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (code == 302 || code == 307) { - _redirectDistinct = false; - } - }); - AbstractNetworkJob::start(); -} - -void CheckServerJob::onTimedOut() -{ - qCWarning(lcCheckServerJob) << "TIMEOUT"; - if (reply() && reply()->isRunning()) { - emit timeout(reply()->url()); - } else if (!reply()) { - qCWarning(lcCheckServerJob) << "Timeout even there was no reply?"; - } - AbstractNetworkJob::onTimedOut(); -} - -QString CheckServerJob::version(const QJsonObject &info) -{ - return info.value(QLatin1String("version")).toString() + QLatin1Char('-') + info.value(QLatin1String("productname")).toString(); -} - -QString CheckServerJob::versionString(const QJsonObject &info) -{ - return info.value(QLatin1String("versionstring")).toString(); -} - -bool CheckServerJob::installed(const QJsonObject &info) -{ - return info.value(QLatin1String("installed")).toBool(); -} - -static void mergeSslConfigurationForSslButton(const QSslConfiguration &config, AccountPtr account) -{ - if (config.peerCertificateChain().length() > 0) { - account->_peerCertificateChain = config.peerCertificateChain(); - } - if (!config.sessionCipher().isNull()) { - account->_sessionCipher = config.sessionCipher(); - } - if (config.sessionTicket().length() > 0) { - account->_sessionTicket = config.sessionTicket(); - } -} - -void CheckServerJob::encryptedSlot() -{ - mergeSslConfigurationForSslButton(reply()->sslConfiguration(), account()); -} - -int CheckServerJob::maxRedirectsAllowed() const -{ - return _maxRedirectsAllowed; -} - -void CheckServerJob::setMaxRedirectsAllowed(int maxRedirectsAllowed) -{ - _maxRedirectsAllowed = maxRedirectsAllowed; -} - -void CheckServerJob::metaDataChangedSlot() -{ - account()->setSslConfiguration(reply()->sslConfiguration()); - mergeSslConfigurationForSslButton(reply()->sslConfiguration(), account()); -} - -bool CheckServerJob::finished() -{ - const QUrl targetUrl = reply()->url().adjusted(QUrl::RemoveFilename); - if (targetUrl.scheme() == QLatin1String("https") - && reply()->sslConfiguration().sessionTicket().isEmpty() - && reply()->error() == QNetworkReply::NoError) { - qCWarning(lcCheckServerJob) << "No SSL session identifier / session ticket is used, this might impact sync performance negatively."; - } - if (_serverUrl != targetUrl) { - if (_redirectDistinct) { - _serverUrl = targetUrl; - } else { - if (_firstTry) { - qCWarning(lcCheckServerJob) << "Server might have moved, retry"; - _firstTry = false; - _redirectDistinct = true; - start(); - return false; - } else { - qCWarning(lcCheckServerJob) << "We got a temporary moved server aborting"; - emit instanceNotFound(reply()); - return true; +// TODO: Instead of doing all in this slot, we should iteratively parse in readyRead(). This +// would allow us to be more asynchronous in processing while data is coming from the network, +// not all in one big blob at the end. +void PropfindJob::finished() +{ + qCInfo(lcPropfindJob) << "LSCOL of" << reply()->request().url() << "FINISHED WITH STATUS" + << replyStatusString(); + + const QString contentType = reply()->header(QNetworkRequest::ContentTypeHeader).toString(); + if (httpStatusCode() == 207) { + if (contentType.contains(QLatin1String("application/xml; charset=utf-8"))) { + LsColXMLParser parser; + connect(&parser, &LsColXMLParser::directoryListingSubfolders, this, &PropfindJob::directoryListingSubfolders); + connect(&parser, &LsColXMLParser::directoryListingIterated, this, &PropfindJob::directoryListingIterated); + connect(&parser, &LsColXMLParser::finishedWithoutError, this, &PropfindJob::finishedWithoutError); + if (_depth == Depth::Zero) { + connect(&parser, &LsColXMLParser::directoryListingIterated, + [&parser, counter = 0, this](const QString &name, const QMap &) mutable { + counter++; + // With a depths of 0 we must receive only one listing + if (OC_ENSURE(counter == 1)) { + disconnect(&parser, &LsColXMLParser::directoryListingIterated, this, &PropfindJob::directoryListingIterated); + } else { + qCCritical(lcPropfindJob) << "Received superfluous directory listing for depth 0 propfind" << counter << "Path:" << name; + } + }); } - } - } - mergeSslConfigurationForSslButton(reply()->sslConfiguration(), account()); - - const int httpStatus = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (reply()->error() == QNetworkReply::TooManyRedirectsError) { - qCWarning(lcCheckServerJob) << "error:" << reply()->errorString(); - emit instanceNotFound(reply()); - } else if (httpStatus != 200 || reply()->bytesAvailable() == 0) { - qCWarning(lcCheckServerJob) << "error: status.php replied " << httpStatus; - emit instanceNotFound(reply()); - } else { - const QByteArray body = reply()->peek(4 * 1024); - QJsonParseError error; - auto status = QJsonDocument::fromJson(body, &error); - // empty or invalid response - if (error.error != QJsonParseError::NoError || status.isNull()) { - qCWarning(lcCheckServerJob) << "status.php from server is not valid JSON!" << body << reply()->request().url() << error.errorString(); - } - - qCInfo(lcCheckServerJob) << "status.php returns: " << status << " " << reply()->error() << " Reply: " << reply(); - if (status.object().contains(QStringLiteral("installed"))) { - emit instanceFound(_serverUrl, status.object()); + const QString expectedPath = reply()->request().url().path(); // something like "/owncloud/remote.php/webdav/folder" + if (!parser.parse(reply()->readAll(), &_sizes, expectedPath)) { + // XML parse error + Q_EMIT finishedWithError(); + } } else { - qCWarning(lcCheckServerJob) << "No proper answer on " << reply()->url(); - emit instanceNotFound(reply()); + // wrong content type + qCWarning(lcPropfindJob) << "Unexpected ContentTypeHeader:" << contentType; + Q_EMIT finishedWithError(); } + } else { + // wrong HTTP code or any other network error + Q_EMIT finishedWithError(); } - return true; } -/*********************************************************************************************/ - - -void PropfindJob::start() +const QHash &PropfindJob::sizes() const { - connect(this, &LsColJob::directoryListingIterated, this, [this](const QString &, const QMap &values) { - OC_ASSERT(!_done); - _done = true; - Q_EMIT result(values); - }); - QNetworkRequest req; - // Always have a higher priority than the propagator because we use this from the UI - // and really want this to be done first (no matter what internal scheduling QNAM uses). - // Also possibly useful for avoiding false timeouts. - req.setPriority(QNetworkRequest::HighPriority); - req.setRawHeader(QByteArrayLiteral("Depth"), QByteArrayLiteral("0")); - startImpl(req); + return _sizes; } - /*********************************************************************************************/ -#ifndef TOKEN_AUTH_ONLY -AvatarJob::AvatarJob(AccountPtr account, const QString &userId, int size, QObject *parent) - : AbstractNetworkJob(account, QString(), parent) +AvatarJob::AvatarJob(Account *account, const QString &userId, int size, QObject *parent) + : AbstractNetworkJob(account, account->url(), QStringLiteral("remote.php/dav/avatars/%1/%2.png").arg(userId, QString::number(size)), parent) { - if (account->serverVersionInt() >= Account::makeServerVersion(10, 0, 0)) { - _avatarUrl = Utility::concatUrlPath(account->url(), QStringLiteral("remote.php/dav/avatars/%1/%2.png").arg(userId, QString::number(size))); - } else { - _avatarUrl = Utility::concatUrlPath(account->url(), QStringLiteral("index.php/avatar/%1/%2").arg(userId, QString::number(size))); - } + setStoreInCache(true); } void AvatarJob::start() { - QNetworkRequest req; - sendRequest("GET", _avatarUrl, req); + sendRequest("GET"); AbstractNetworkJob::start(); } QPixmap AvatarJob::makeCircularAvatar(const QPixmap &baseAvatar) { + if (baseAvatar.isNull()) + return {}; + int dim = baseAvatar.width(); QPixmap avatar(dim, dim); @@ -619,7 +357,7 @@ QPixmap AvatarJob::makeCircularAvatar(const QPixmap &baseAvatar) return avatar; } -bool AvatarJob::finished() +void AvatarJob::finished() { int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -633,267 +371,128 @@ bool AvatarJob::finished() } } } - emit avatarPixmap(avImage); - return true; + Q_EMIT avatarPixmap(avImage); } -#endif /*********************************************************************************************/ -ProppatchJob::ProppatchJob(AccountPtr account, const QString &path, QObject *parent) - : AbstractNetworkJob(account, path, parent) -{ -} - -void ProppatchJob::start() -{ - if (_properties.isEmpty()) { - qCWarning(lcProppatchJob) << "Proppatch with no properties!"; - } - QNetworkRequest req; - - QByteArray propStr; - QMapIterator it(_properties); - while (it.hasNext()) { - it.next(); - QByteArray keyName = it.key(); - QByteArray keyNs; - if (keyName.contains(':')) { - int colIdx = keyName.lastIndexOf(":"); - keyNs = keyName.left(colIdx); - keyName = keyName.mid(colIdx + 1); - } - - propStr += " <" + keyName; - if (!keyNs.isEmpty()) { - propStr += " xmlns=\"" + keyNs + "\" "; - } - propStr += ">"; - propStr += it.value(); - propStr += "\n"; - } - QByteArray xml = "\n" - "\n" - " \n" - + propStr + " \n" - "\n"; - - QBuffer *buf = new QBuffer(this); - buf->setData(xml); - buf->open(QIODevice::ReadOnly); - sendRequest("PROPPATCH", makeDavUrl(path()), req, buf); - AbstractNetworkJob::start(); -} - -void ProppatchJob::setProperties(QMap properties) -{ - _properties = properties; -} - -QMap ProppatchJob::properties() const -{ - return _properties; -} - -bool ProppatchJob::finished() -{ - qCInfo(lcProppatchJob) << "PROPPATCH of" << reply()->request().url() << "FINISHED WITH STATUS" - << replyStatusString(); - - int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - if (http_result_code == 207) { - emit success(); - } else { - qCWarning(lcProppatchJob) << "*not* successful, http result code is" << http_result_code - << (http_result_code == 302 ? reply()->header(QNetworkRequest::LocationHeader).toString() : QLatin1String("")); - emit finishedWithError(); - } - return true; -} - -/*********************************************************************************************/ - -EntityExistsJob::EntityExistsJob(AccountPtr account, const QString &path, QObject *parent) - : AbstractNetworkJob(account, path, parent) +EntityExistsJob::EntityExistsJob(Account *account, const QUrl &rootUrl, const QString &path, QObject *parent) + : AbstractNetworkJob(account, rootUrl, path, parent) { } void EntityExistsJob::start() { - sendRequest("HEAD", makeAccountUrl(path())); + sendRequest("HEAD"); AbstractNetworkJob::start(); } -bool EntityExistsJob::finished() +void EntityExistsJob::finished() { - emit exists(reply()); - return true; + Q_EMIT exists(reply()); } /*********************************************************************************************/ -JsonApiJob::JsonApiJob(const AccountPtr &account, const QString &path, QObject *parent) - : AbstractNetworkJob(account, path, parent) -{ -} - -void JsonApiJob::addQueryParams(const QUrlQuery ¶ms) -{ - _additionalParams = params; -} - -void JsonApiJob::start() +SimpleNetworkJob::SimpleNetworkJob( + Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, const QNetworkRequest &req, QObject *parent) + : AbstractNetworkJob(account, rootUrl, path, parent) + , _request(req) + , _verb(verb) { - startWithRequest(QNetworkRequest()); } -void OCC::JsonApiJob::startWithRequest(QNetworkRequest req) +SimpleNetworkJob::SimpleNetworkJob( + Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, const UrlQuery &arguments, const QNetworkRequest &req, QObject *parent) + : SimpleNetworkJob(account, rootUrl, path, verb, req, parent) { - req.setRawHeader("OCS-APIREQUEST", "true"); - auto query = _additionalParams; - query.addQueryItem(QStringLiteral("format"), QStringLiteral("json")); - QUrl url = Utility::concatUrlPath(account()->url(), path(), query); - sendRequest("GET", url, req); - AbstractNetworkJob::start(); -} - -bool JsonApiJob::finished() -{ - qCInfo(lcJsonApiJob) << "JsonApiJob of" << reply()->request().url() << "FINISHED WITH STATUS" - << replyStatusString(); - - int statusCode = 0; - - if (reply()->error() != QNetworkReply::NoError) { - qCWarning(lcJsonApiJob) << "Network error: " << this << errorString() << reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - emit jsonReceived(QJsonDocument(), statusCode); - return true; - } - - QString jsonStr = QString::fromUtf8(reply()->readAll()); - if (jsonStr.contains(QLatin1String(""))) { - QRegExp rex(QStringLiteral("(\\d+)")); - if (jsonStr.contains(rex)) { - // this is a error message coming back from ocs. - statusCode = rex.cap(1).toInt(); + Q_ASSERT((QList { "GET", "PUT", "POST", "DELETE", "HEAD", "PATCH" }.contains(verb))); + if (!arguments.isEmpty()) { + QUrlQuery args; + // ensure everything is percent encoded + // this is especially important for parameters that contain spaces or + + for (const auto &item : arguments) { + args.addQueryItem( + QString::fromUtf8(QUrl::toPercentEncoding(item.first)), + QString::fromUtf8(QUrl::toPercentEncoding(item.second))); } - - } else { - QRegExp rex(QStringLiteral("\"statuscode\":(\\d+),")); - // example: "{"ocs":{"meta":{"status":"ok","statuscode":100,"message":null},"data":{"version":{"major":8,"minor":"... (504) - if (jsonStr.contains(rex)) { - statusCode = rex.cap(1).toInt(); + if (verb == QByteArrayLiteral("POST") || verb == QByteArrayLiteral("PUT") || verb == QByteArrayLiteral("PATCH")) { + _request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded; charset=UTF-8")); + _body = args.query(QUrl::FullyEncoded).toUtf8(); + _device = new QBuffer(&_body); + } else { + setQuery(args); } } - - QJsonParseError error; - auto json = QJsonDocument::fromJson(jsonStr.toUtf8(), &error); - // empty or invalid response - if (error.error != QJsonParseError::NoError || json.isNull()) { - qCWarning(lcJsonApiJob) << "invalid JSON!" << jsonStr << error.errorString(); - emit jsonReceived(json, statusCode); - return true; - } - - emit jsonReceived(json, statusCode); - return true; } -DetermineAuthTypeJob::DetermineAuthTypeJob(AccountPtr account, QObject *parent) - : AbstractNetworkJob(account, QString(), parent) +SimpleNetworkJob::SimpleNetworkJob(Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, const QJsonObject &arguments, + const QNetworkRequest &req, QObject *parent) + : SimpleNetworkJob(account, rootUrl, path, verb, QJsonDocument(arguments).toJson(), req, parent) { - setAuthenticationJob(true); - setIgnoreCredentialFailure(true); + _request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); } -void DetermineAuthTypeJob::start() +SimpleNetworkJob::SimpleNetworkJob( + Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, QIODevice *requestBody, const QNetworkRequest &req, QObject *parent) + : SimpleNetworkJob(account, rootUrl, path, verb, req, parent) { - qCInfo(lcDetermineAuthTypeJob) << "Determining auth type for" << _account->davUrl(); - - QNetworkRequest req; - // Prevent HttpCredentialsAccessManager from setting an Authorization header. - req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true); - // Don't reuse previous auth credentials - req.setAttribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Manual); - sendRequest("PROPFIND", _account->davUrl(), req); - AbstractNetworkJob::start(); + _device = requestBody; } -bool DetermineAuthTypeJob::finished() +SimpleNetworkJob::SimpleNetworkJob( + Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, QByteArray &&requestBody, const QNetworkRequest &req, QObject *parent) + : SimpleNetworkJob(account, rootUrl, path, verb, new QBuffer(&_body), req, parent) { - auto authChallenge = reply()->rawHeader("WWW-Authenticate").toLower(); - auto result = AuthType::Basic; - if (authChallenge.contains("bearer ")) { - result = AuthType::OAuth; - } else if (authChallenge.isEmpty()) { - qCWarning(lcDetermineAuthTypeJob) << "Did not receive WWW-Authenticate reply to auth-test PROPFIND"; - } - qCInfo(lcDetermineAuthTypeJob) << "Auth type for" << _account->davUrl() << "is" << result; - emit this->authType(result); - return true; + _body = std::move(requestBody); } -SimpleNetworkJob::SimpleNetworkJob(AccountPtr account, QObject *parent) - : AbstractNetworkJob(account, QString(), parent) +SimpleNetworkJob::~SimpleNetworkJob() { } - void SimpleNetworkJob::start() { - sendRequest(_simpleVerb, _simpleUrl, _simpleRequest, _simpleBody); + Q_ASSERT(!_verb.isEmpty()); + // AbstractNetworkJob will take ownership of the buffer + sendRequest(_verb, _request, _device); AbstractNetworkJob::start(); } -void SimpleNetworkJob::prepareRequest(const QByteArray &verb, const QUrl &url, - const QNetworkRequest &req, QIODevice *requestBody) +void SimpleNetworkJob::addNewReplyHook(std::function &&hook) { - _simpleVerb = verb; - _simpleUrl = url; - _simpleRequest = req; - _simpleBody = requestBody; + _replyHooks.push_back(hook); } -void SimpleNetworkJob::prepareRequest(const QByteArray &verb, const QUrl &url, const QNetworkRequest &req, const QUrlQuery &arguments) +void SimpleNetworkJob::finished() { - // not a leak - auto requestBody = new QBuffer {}; - requestBody->setData(arguments.query(QUrl::FullyEncoded).toUtf8()); - auto newReq = req; - newReq.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded; charset=UTF-8")); - return prepareRequest(verb, url, newReq, requestBody); -} - -void SimpleNetworkJob::prepareRequest(const QByteArray &verb, const QUrl &url, const QNetworkRequest &req, const QJsonObject &arguments) -{ - // not a leak - auto requestBody = new QBuffer {}; - requestBody->setData(QJsonDocument(arguments).toJson()); - auto newReq = req; - newReq.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json")); - return prepareRequest(verb, url, newReq, requestBody); + if (_device) { + _device->close(); + } } -bool SimpleNetworkJob::finished() +void SimpleNetworkJob::newReplyHook(QNetworkReply *reply) { - emit finishedSignal(reply()); - return true; + for (const auto &hook : _replyHooks) { + hook(reply); + } } -void fetchPrivateLinkUrl(AccountPtr account, const QString &remotePath, QObject *target, - std::function targetFun) +void fetchPrivateLinkUrl( + Account *account, const QUrl &baseUrl, const QString &remotePath, QObject *target, const std::function &targetFun) { - // Retrieve the new link by PROPFIND - PropfindJob *job = new PropfindJob(account, remotePath, target); - job->setProperties({ QByteArrayLiteral("http://owncloud.org/ns:privatelink") }); - job->setTimeout(10s); - QObject::connect(job, &PropfindJob::result, target, [=](const QMap &result) { - auto privateLinkUrl = result[QStringLiteral("privatelink")]; - if (!privateLinkUrl.isEmpty()) { - targetFun(privateLinkUrl); - } - }); - job->start(); + if (account && account->capabilities().privateLinkPropertyAvailable()) { + // Retrieve the new link by PROPFIND + auto *job = new PropfindJob(account, baseUrl, remotePath, PropfindJob::Depth::Zero, target); + job->setProperties({ QByteArrayLiteral("http://owncloud.org/ns:privatelink") }); + job->setTimeout(10s); + QObject::connect(job, &PropfindJob::directoryListingIterated, target, [=](const QString &, const QMap &result) { + auto privateLinkUrl = result[QStringLiteral("privatelink")]; + if (!privateLinkUrl.isEmpty()) { + targetFun(QUrl(privateLinkUrl)); + } + }); + job->start(); + } } } // namespace OCC diff --git a/src/libsync/networkjobs.h b/src/libsync/networkjobs.h index b9346fd4099..8b8f2792631 100644 --- a/src/libsync/networkjobs.h +++ b/src/libsync/networkjobs.h @@ -18,17 +18,14 @@ #include "abstractnetworkjob.h" #include "common/result.h" +#include #include #include class QUrl; -class QJsonObject; namespace OCC { -/** Strips quotes and gzip annotations */ -OWNCLOUDSYNC_EXPORT QByteArray parseEtag(const QByteArray &header); - struct HttpError { int code; // HTTP error code @@ -46,18 +43,18 @@ class OWNCLOUDSYNC_EXPORT EntityExistsJob : public AbstractNetworkJob { Q_OBJECT public: - explicit EntityExistsJob(AccountPtr account, const QString &path, QObject *parent = nullptr); + explicit EntityExistsJob(Account *account, const QUrl &rootUrl, const QString &path, QObject *parent = nullptr); void start() override; -signals: +Q_SIGNALS: void exists(QNetworkReply *); -private slots: - bool finished() override; +private Q_SLOTS: + void finished() override; }; /** - * @brief The LsColJob class + * @brief The PropfindJob class parser * @ingroup libsync */ class OWNCLOUDSYNC_EXPORT LsColXMLParser : public QObject @@ -68,19 +65,21 @@ class OWNCLOUDSYNC_EXPORT LsColXMLParser : public QObject bool parse(const QByteArray &xml, QHash *sizes, const QString &expectedPath); -signals: +Q_SIGNALS: void directoryListingSubfolders(const QStringList &items); void directoryListingIterated(const QString &name, const QMap &properties); - void finishedWithError(QNetworkReply *reply); void finishedWithoutError(); }; -class OWNCLOUDSYNC_EXPORT LsColJob : public AbstractNetworkJob +class OWNCLOUDSYNC_EXPORT PropfindJob : public AbstractNetworkJob { Q_OBJECT public: - explicit LsColJob(AccountPtr account, const QString &path, QObject *parent = nullptr); - explicit LsColJob(AccountPtr account, const QUrl &url, QObject *parent = nullptr); + enum class Depth { + Zero, + One + } Q_ENUMS(Depth); + explicit PropfindJob(Account *account, const QUrl &url, const QString &path, Depth depth, QObject *parent = nullptr); void start() override; /** @@ -94,51 +93,25 @@ class OWNCLOUDSYNC_EXPORT LsColJob : public AbstractNetworkJob void setProperties(const QList &properties); QList properties() const; + // TODO: document... const QHash &sizes() const; -signals: +Q_SIGNALS: void directoryListingSubfolders(const QStringList &items); void directoryListingIterated(const QString &name, const QMap &properties); - void finishedWithError(QNetworkReply *reply); + void finishedWithError(); void finishedWithoutError(); -private slots: - bool finished() override; - -protected: - void startImpl(const QNetworkRequest &req); +private Q_SLOTS: + void finished() override; private: QList _properties; - QUrl _url; // Used instead of path() if the url is specified in the constructor QHash _sizes; + Depth _depth; }; -/** - * @brief The PropfindJob class - * - * Setting the desired properties with setProperties() is mandatory. - * - * Note that this job is only for querying one item. - * There is also the LsColJob which can be used to list collections - * - * @ingroup libsync - */ -class OWNCLOUDSYNC_EXPORT PropfindJob : public LsColJob -{ - Q_OBJECT -public: - using LsColJob::LsColJob; - void start() override; - -signals: - void result(const QMap &values); - -private: - bool _done = false; -}; -#ifndef TOKEN_AUTH_ONLY /** * @brief Retrieves the account users avatar from the server using a GET request. * @@ -154,64 +127,22 @@ class OWNCLOUDSYNC_EXPORT AvatarJob : public AbstractNetworkJob * @param userId The user for which to obtain the avatar * @param size The size of the avatar (square so size*size) */ - explicit AvatarJob(AccountPtr account, const QString &userId, int size, QObject *parent = nullptr); + explicit AvatarJob(Account *account, const QString &userId, int size, QObject *parent = nullptr); void start() override; /** The retrieved avatar images don't have the circle shape by default */ static QPixmap makeCircularAvatar(const QPixmap &baseAvatar); -signals: +Q_SIGNALS: /** * @brief avatarPixmap - returns either a valid pixmap or not. */ void avatarPixmap(const QPixmap &); -private slots: - bool finished() override; - -private: - QUrl _avatarUrl; -}; -#endif - -/** - * @brief Send a Proppatch request - * - * Setting the desired properties with setProperties() is mandatory. - * - * WARNING: Untested! - * - * @ingroup libsync - */ -class OWNCLOUDSYNC_EXPORT ProppatchJob : public AbstractNetworkJob -{ - Q_OBJECT -public: - explicit ProppatchJob(AccountPtr account, const QString &path, QObject *parent = nullptr); - void start() override; - - /** - * Used to specify which properties shall be set. - * - * The property keys can - * - contain no colon: they refer to a property in the DAV: namespace - * - contain a colon: and thus specify an explicit namespace, - * e.g. "ns:with:colons:bar", which is "bar" in the "ns:with:colons" namespace - */ - void setProperties(QMap properties); - QMap properties() const; - -signals: - void success(); - void finishedWithError(); - -private slots: - bool finished() override; - -private: - QMap _properties; +private Q_SLOTS: + void finished() override; }; /** @@ -221,185 +152,35 @@ private slots: class OWNCLOUDSYNC_EXPORT MkColJob : public AbstractNetworkJob { Q_OBJECT - QUrl _url; // Only used if the constructor taking a url is taken. - QMap _extraHeaders; + HeaderMap _extraHeaders; public: - explicit MkColJob(AccountPtr account, const QString &path, QObject *parent = nullptr); - explicit MkColJob(AccountPtr account, const QUrl &url, - const QMap &extraHeaders, QObject *parent = nullptr); + explicit MkColJob(Account *account, const QUrl &url, const QString &path, const HeaderMap &extraHeaders, QObject *parent = nullptr); void start() override; -signals: +Q_SIGNALS: void finishedWithError(QNetworkReply *reply); void finishedWithoutError(); private: - bool finished() override; + void finished() override; }; -/** - * @brief The CheckServerJob class - * @ingroup libsync - */ -class OWNCLOUDSYNC_EXPORT CheckServerJob : public AbstractNetworkJob -{ - Q_OBJECT -public: - explicit CheckServerJob(AccountPtr account, QObject *parent = nullptr); - void start() override; - - static QString version(const QJsonObject &info); - static QString versionString(const QJsonObject &info); - static bool installed(const QJsonObject &info); - - int maxRedirectsAllowed() const; - void setMaxRedirectsAllowed(int maxRedirectsAllowed); - -signals: - /** Emitted when a status.php was successfully read. - * - * \a url see _serverStatusUrl (does not include "/status.php") - * \a info The status.php reply information - */ - void instanceFound(const QUrl &url, const QJsonObject &info); - - /** Emitted on invalid status.php reply. - * - * \a reply is never null - */ - void instanceNotFound(QNetworkReply *reply); - - /** A timeout occurred. - * - * \a url The specific url where the timeout happened. - */ - void timeout(const QUrl &url); - -private: - bool finished() override; - void onTimedOut() override; -private slots: - virtual void metaDataChangedSlot(); - virtual void encryptedSlot(); - -private: - bool _subdirFallback; - - /** The permanent-redirect adjusted account url. - * - * Note that temporary redirects or a permanent redirect behind a temporary - * one do not affect this url. - */ - QUrl _serverUrl; - - int _maxRedirectsAllowed = 5; - - /** we only got permanent redirects */ - bool _redirectDistinct = true; - /** only retry once, the first try might give us needed cookies - the second is supposed to succeed - */ - bool _firstTry = true; -}; - - /** * @brief The RequestEtagJob class */ -class OWNCLOUDSYNC_EXPORT RequestEtagJob : public AbstractNetworkJob -{ - Q_OBJECT -public: - explicit RequestEtagJob(AccountPtr account, const QString &path, QObject *parent = nullptr); - void start() override; - -signals: - void etagRetreived(const QByteArray &etag, const QDateTime &time); - void finishedWithResult(const HttpResult &etag); - -private slots: - bool finished() override; -}; - -/** - * @brief Job to check an API that return JSON - * - * Note! you need to be in the connected state before calling this because of a server bug: - * https://github.com/owncloud/core/issues/12930 - * - * To be used like this: - * \code - * _job = new JsonApiJob(account, QLatin1String("ocs/v1.php/foo/bar"), this); - * connect(job, SIGNAL(jsonReceived(QJsonDocument)), ...) - * The received QVariantMap is null in case of error - * \encode - * - * @ingroup libsync - */ -class OWNCLOUDSYNC_EXPORT JsonApiJob : public AbstractNetworkJob +class OWNCLOUDSYNC_EXPORT RequestEtagJob : public PropfindJob { Q_OBJECT public: - explicit JsonApiJob(const AccountPtr &account, const QString &path, QObject *parent = nullptr); - - /** - * @brief addQueryParams - add more parameters to the ocs call - * @param params: list pairs of strings containing the parameter name and the value. - * - * All parameters from the passed list are appended to the query. Note - * that the format=json parameter is added automatically and does not - * need to be set this way. - * - * This function needs to be called before start() obviously. - */ - void addQueryParams(const QUrlQuery ¶ms); + explicit RequestEtagJob(Account *account, const QUrl &rootUrl, const QString &path, QObject *parent = nullptr); -public slots: - void start() override; - /** - * Start which allow to specify a request that might contains already headers or attributes - */ - void startWithRequest(QNetworkRequest request); - -protected: - bool finished() override; -signals: - - /** - * @brief jsonReceived - signal to report the json answer from ocs - * @param json - the parsed json document - * @param statusCode - the OCS status code: 100 (!) for success - */ - void jsonReceived(const QJsonDocument &json, int statusCode); + const QString &etag() const; private: - QUrlQuery _additionalParams; + QString _etag; }; -/** - * @brief Checks with auth type to use for a server - * @ingroup libsync - */ -class OWNCLOUDSYNC_EXPORT DetermineAuthTypeJob : public AbstractNetworkJob -{ - Q_OBJECT -public: - enum class AuthType { - Basic, // also the catch-all fallback for backwards compatibility reasons - OAuth, - Unknown - }; - Q_ENUM(AuthType) - - explicit DetermineAuthTypeJob(AccountPtr account, QObject *parent = nullptr); - void start() override; -signals: - void authType(AuthType); - -protected Q_SLOTS: - bool finished() override; -}; /** * @brief A basic job around a network request without extra funtionality @@ -411,32 +192,37 @@ class OWNCLOUDSYNC_EXPORT SimpleNetworkJob : public AbstractNetworkJob { Q_OBJECT public: - explicit SimpleNetworkJob(AccountPtr account, QObject *parent = nullptr); + using UrlQuery = QList>; - void prepareRequest(const QByteArray &verb, const QUrl &url, - const QNetworkRequest &req = QNetworkRequest(), - QIODevice *requestBody = nullptr); + // fully qualified urls can be passed in the QNetworkRequest + explicit SimpleNetworkJob(Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, QIODevice *requestBody = nullptr, + const QNetworkRequest &req = {}, QObject *parent = nullptr); + explicit SimpleNetworkJob(Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, const UrlQuery &arguments, + const QNetworkRequest &req = {}, QObject *parent = nullptr); + explicit SimpleNetworkJob(Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, const QJsonObject &arguments, + const QNetworkRequest &req = {}, QObject *parent = nullptr); + explicit SimpleNetworkJob(Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, QByteArray &&requestBody, + const QNetworkRequest &req = {}, QObject *parent = nullptr); - void prepareRequest(const QByteArray &verb, const QUrl &url, - const QNetworkRequest &req, - const QUrlQuery &arguments); - - void prepareRequest(const QByteArray &verb, const QUrl &url, - const QNetworkRequest &req, - const QJsonObject &arguments); + virtual ~SimpleNetworkJob(); void start() override; -signals: - void finishedSignal(QNetworkReply *reply); -private slots: - bool finished() override; + void addNewReplyHook(std::function &&hook); + +protected: + void finished() override; + void newReplyHook(QNetworkReply *) override; + + QNetworkRequest _request; private: - QByteArray _simpleVerb; - QUrl _simpleUrl; - QIODevice *_simpleBody; - QNetworkRequest _simpleRequest; + explicit SimpleNetworkJob(Account *account, const QUrl &rootUrl, const QString &path, const QByteArray &verb, const QNetworkRequest &req, QObject *parent); + + QByteArray _verb; + QByteArray _body; + QIODevice *_device = nullptr; + std::vector> _replyHooks; }; /** @@ -451,8 +237,8 @@ private slots: * Note: targetFun is guaranteed to be called only through the event * loop and never directly. */ -void OWNCLOUDSYNC_EXPORT fetchPrivateLinkUrl(AccountPtr account, const QString &remotePath, QObject *target, - std::function targetFun); +void OWNCLOUDSYNC_EXPORT fetchPrivateLinkUrl( + Account *account, const QUrl &baseUrl, const QString &remotePath, QObject *target, const std::function &targetFun); } // namespace OCC diff --git a/src/libsync/networkjobs/checkserverjobfactory.cpp b/src/libsync/networkjobs/checkserverjobfactory.cpp new file mode 100644 index 00000000000..60ae002bb11 --- /dev/null +++ b/src/libsync/networkjobs/checkserverjobfactory.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) Fabian Müller + * + * 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 "checkserverjobfactory.h" + +#include "common/utility.h" +#include "libsync/accessmanager.h" +#include "libsync/account.h" +#include "libsync/cookiejar.h" +#include "libsync/creds/abstractcredentials.h" +#include "libsync/theme.h" + +#include + +namespace { + +// FIXME: this is not a permanent solution, eventually we want to replace the job factories with job classes so we can store such information there +class CheckServerCoreJob : OCC::CoreJob +{ + Q_OBJECT + friend OCC::CheckServerJobFactory; + +public: + using OCC::CoreJob::CoreJob; + +}; + +} + +namespace OCC { + +Q_LOGGING_CATEGORY(lcCheckServerJob, "sync.checkserverjob", QtInfoMsg) + +CheckServerJobResult::CheckServerJobResult() = default; + +CheckServerJobResult::CheckServerJobResult(const QJsonObject &statusObject, const QUrl &serverUrl) + : _statusObject(statusObject) + , _serverUrl(serverUrl) +{ +} + +QJsonObject CheckServerJobResult::statusObject() const +{ + return _statusObject; +} + +QUrl CheckServerJobResult::serverUrl() const +{ + return _serverUrl; +} + +// thinking out loud here: +// it may be we can turn this into a job adapter. +// consider: +// adapter::createFromAccount returns the adapter +// adapter::startJob -> returns nothing! +// could signal out "finished" with a copy of the result? +// once "finished" it could delete the job, woopie do! +// caller could keep an adapter around as member for re-use if needed, or hit and quit. +CheckServerJobFactory CheckServerJobFactory::createFromAccount(const Account *account, bool clearCookies) +{ + // this is horrid but we can't return an "empty" factory so just crash. + // TODO: the jobs need a lot of work. + Q_ASSERT(account); + + // in order to receive all ssl erorrs we need a fresh QNam + // see startJob where we parent the nam to the job itself to ensure it is cleaned up asap + auto nam = account->credentials()->createAccessManager(); + nam->setCustomTrustedCaCertificates(account->approvedCerts()); + // do we start with the old cookies or new + if (!clearCookies) { + const auto newJar = account->accessManager()->ownCloudCookieJar()->clone(); + nam->setCookieJar(newJar); + } + return CheckServerJobFactory(nam); +} + +CoreJob *CheckServerJobFactory::startJob(const QUrl &url, QObject *parent) +{ + // the custom job class is used to store some state we need to maintain until the job has finished + + auto req = makeRequest(Utility::concatUrlPath(url, QStringLiteral("status.php"))); + + // We never want to follow any redirects. At least until proven otherwise. + req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy); + req.setRawHeader(QByteArrayLiteral("OC-Connection-Validator"), QByteArrayLiteral("desktop")); + req.setMaximumRedirectsAllowed(0); + + auto job = new CheckServerCoreJob(_nam->get(req), parent); + // this looks shady, but for the check server jobs we create a new nam on every run. The old approach was to parent it to the value + // passed to the factory but in fact that was often the connection validator which lives for a really long time, and hence is not the + // best parent for that temp nam. + // setting the job as parent is a more reasonable choice but this is not a great place for it - + _nam->setParent(job); + + // make this handle maintenance mode - only if necessary + /* QObject::connect(job->reply(), &QNetworkReply::redirected, job, [job] { + // const auto code = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + // if (code == 302 || code == 307) { + // job->_redirectDistinct = false; + //} + });*/ + + QObject::connect(job->reply(), &QNetworkReply::finished, job, [url, job] { + + const QUrl targetUrl = job->reply()->url().adjusted(QUrl::RemoveFilename); + + // TODO: still needed? + if (targetUrl.scheme() == QLatin1String("https") + && job->reply()->sslConfiguration().sessionTicket().isEmpty() + && job->reply()->error() == QNetworkReply::NoError) { + qCWarning(lcCheckServerJob) << "No SSL session identifier / session ticket is used, this might impact sync performance negatively."; + } + + // I'm not sure this can even happen when using ManualRedirectPolicy + if (!Utility::urlEqual(url, targetUrl)) { + qCWarning(lcCheckServerJob) << "We got a redirect from " << url << " to " << targetUrl << ", aborting"; + setJobError(job, QStringLiteral("Redirect from server")); + } + + const int httpStatus = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (job->reply()->error() == QNetworkReply::TooManyRedirectsError) { + qCWarning(lcCheckServerJob) << "error:" << job->reply()->errorString(); + setJobError(job, job->reply()->errorString()); + } else if (httpStatus != 200 || job->reply()->bytesAvailable() == 0) { + qCWarning(lcCheckServerJob) << "error: status.php replied" << httpStatus; + setJobError(job, QStringLiteral("Invalid HTTP status code received for status.php: %1").arg(httpStatus)); + } else { + const QByteArray body = job->reply()->peek(4 * 1024); + QJsonParseError error; + auto status = QJsonDocument::fromJson(body, &error); + // empty or invalid response + if (error.error != QJsonParseError::NoError || status.isNull()) { + qCWarning(lcCheckServerJob) << "status.php from server is not valid JSON!" << body << job->reply()->request().url() << error.errorString(); + } + + qCInfo(lcCheckServerJob) << "status.php returns: " << status << " " << job->reply()->error() << " Reply: " << job->reply(); + + if (status.object().contains(QStringLiteral("installed"))) { + CheckServerJobResult result(status.object(), url); + setJobResult(job, QVariant::fromValue(result)); + } else { + qCWarning(lcCheckServerJob) << "No proper answer on " << job->reply()->url(); + setJobError(job, QStringLiteral("Did not receive expected reply from server")); + } + } + }); + + return job; +} + +} // OCC + +#include "checkserverjobfactory.moc" diff --git a/src/libsync/networkjobs/checkserverjobfactory.h b/src/libsync/networkjobs/checkserverjobfactory.h new file mode 100644 index 00000000000..04325472b9a --- /dev/null +++ b/src/libsync/networkjobs/checkserverjobfactory.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) Fabian Müller + * + * 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 "abstractcorejob.h" + +#include + +namespace OCC { + +class OWNCLOUDSYNC_EXPORT CheckServerJobResult +{ + +public: + CheckServerJobResult(); + CheckServerJobResult(const QJsonObject &statusObject, const QUrl &serverUrl); + + QJsonObject statusObject() const; + QUrl serverUrl() const; + +private: + const QJsonObject _statusObject; + const QUrl _serverUrl; +}; + + +class OWNCLOUDSYNC_EXPORT CheckServerJobFactory : public AbstractCoreJobFactory +{ +public: + using AbstractCoreJobFactory::AbstractCoreJobFactory; + + /** + * clearCookies: Whether to clear the cookies before we start the CheckServerJob job + */ + static CheckServerJobFactory createFromAccount(const Account *account, bool clearCookies); + + // the parent arg should be a fallback - we should be deleting jobs explicitly as soon as they are finished + // also note we create a "one off" nam for each checkServerJob so that is parented by the job for ensured cleanup + CoreJob *startJob(const QUrl &url, QObject *parent) override; + +}; + +} // OCC + +Q_DECLARE_METATYPE(OCC::CheckServerJobResult) diff --git a/src/libsync/networkjobs/fetchuserinfojobfactory.cpp b/src/libsync/networkjobs/fetchuserinfojobfactory.cpp new file mode 100644 index 00000000000..14404dd7c21 --- /dev/null +++ b/src/libsync/networkjobs/fetchuserinfojobfactory.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) Fabian Müller + * + * 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 "fetchuserinfojobfactory.h" +#include "common/utility.h" +#include "creds/credentialssupport.h" + +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(lcFetchUserInfoJob, "sync.networkjob.fetchuserinfojob", QtInfoMsg); + +namespace OCC { + +FetchUserInfoJobFactory FetchUserInfoJobFactory::fromOAuth2Credentials(QNetworkAccessManager *nam, const QString &bearerToken) +{ + QString authorizationHeader = QStringLiteral("Bearer %1").arg(bearerToken); + return { nam, authorizationHeader }; +} + +FetchUserInfoJobFactory::FetchUserInfoJobFactory(QNetworkAccessManager *nam, const QString &authHeaderValue) + : AbstractCoreJobFactory(nam) + , _authorizationHeader(authHeaderValue) +{ +} + +CoreJob *FetchUserInfoJobFactory::startJob(const QUrl &url, QObject *parent) +{ + QUrlQuery urlQuery({ { QStringLiteral("format"), QStringLiteral("json") } }); + + auto req = makeRequest(Utility::concatUrlPath(url, QStringLiteral("ocs/v2.php/cloud/user"), urlQuery)); + + // We are not connected yet so we need to handle the authentication manually + req.setRawHeader("Authorization", _authorizationHeader.toUtf8()); + // todo: #20 - apparently this is oc10 related - there are many instances but they should go. + req.setRawHeader(QByteArrayLiteral("OCS-APIREQUEST"), QByteArrayLiteral("true")); + + // We just added the Authorization header, don't let the credentials AccessManager tamper with it + req.setAttribute(OCC::DontAddCredentialsAttribute, true); + req.setAttribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Manual); + + auto *job = new CoreJob(_nam->get(req), parent); + + QObject::connect(job->reply(), &QNetworkReply::finished, job, [job] { + const auto data = job->reply()->readAll(); + const auto statusCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (job->reply()->error() != QNetworkReply::NoError || statusCode != 200) { + setJobError(job, QApplication::translate("FetchUserInfoJobFactory", "Failed to retrieve user info")); + } else { + qCDebug(lcFetchUserInfoJob) << data; + + QJsonParseError error = {}; + const auto json = QJsonDocument::fromJson(data, &error); + + if (error.error == QJsonParseError::NoError) { + const auto jsonData = json.object().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toObject(); + + FetchUserInfoResult result(jsonData.value(QStringLiteral("id")).toString(), jsonData.value(QStringLiteral("display-name")).toString()); + + setJobResult(job, QVariant::fromValue(result)); + } else { + setJobError(job, error.errorString()); + } + } + }); + + return job; +} + +} // OCC diff --git a/src/libsync/networkjobs/fetchuserinfojobfactory.h b/src/libsync/networkjobs/fetchuserinfojobfactory.h new file mode 100644 index 00000000000..dc7b3ffc00d --- /dev/null +++ b/src/libsync/networkjobs/fetchuserinfojobfactory.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) Fabian Müller + * + * 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 "abstractcorejob.h" +namespace OCC { + +class OWNCLOUDSYNC_EXPORT FetchUserInfoResult +{ +public: + FetchUserInfoResult() = default; + + FetchUserInfoResult(const QString &userName, const QString &displayName) + { + _userName = userName; + _displayName = displayName; + } + + QString userName() const + { + return _userName; + } + + QString displayName() const + { + return _displayName; + }; + +private: + QString _userName; + QString _displayName; +}; + +class OWNCLOUDSYNC_EXPORT FetchUserInfoJobFactory : public AbstractCoreJobFactory +{ +public: + static FetchUserInfoJobFactory fromOAuth2Credentials(QNetworkAccessManager *nam, const QString &bearerToken); + + CoreJob *startJob(const QUrl &url, QObject *parent) override; + +private: + FetchUserInfoJobFactory(QNetworkAccessManager *nam, const QString &authHeaderValue); + + QString _authorizationHeader; +}; + +} // OCC + +Q_DECLARE_METATYPE(OCC::FetchUserInfoResult) diff --git a/src/libsync/networkjobs/jsonjob.cpp b/src/libsync/networkjobs/jsonjob.cpp new file mode 100644 index 00000000000..d89fd075d21 --- /dev/null +++ b/src/libsync/networkjobs/jsonjob.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "jsonjob.h" + +#include "account.h" +#include "common/utility.h" + +#include +#include +#include + +Q_LOGGING_CATEGORY(lcJsonApiJob, "sync.networkjob.jsonapi", QtInfoMsg) + +using namespace OCC; + +JsonJob::~JsonJob() +{ +} + +void JsonJob::finished() +{ + if (reply()->error() != QNetworkReply::NoError) { + qCWarning(lcJsonApiJob) << "Network error: " << this << errorString(); + } else { + parse(reply()->readAll()); + } + SimpleNetworkJob::finished(); +} + +void JsonJob::parse(const QByteArray &data) +{ + const auto doc = QJsonDocument::fromJson(data, &_parseError); + // empty or invalid response + if (_parseError.error != QJsonParseError::NoError || doc.isNull()) { + qCWarning(lcJsonApiJob) << "invalid JSON!" << data << _parseError.errorString(); + } else { + _data = doc.object(); + } +} + +const QJsonParseError &JsonJob::parseError() const +{ + return _parseError; +} + +const QJsonObject &JsonJob::data() const +{ + return _data; +} + + +JsonApiJob::JsonApiJob(Account *account, const QString &path, const QByteArray &verb, const UrlQuery &arguments, const QNetworkRequest &req, QObject *parent) + : JsonJob(account, account->url(), path, verb, arguments, req, parent) +{ + _request.setRawHeader(QByteArrayLiteral("OCS-APIREQUEST"), QByteArrayLiteral("true")); + + auto q = _query; + // HACK: q will be empty for POST, PUT, PATCH request as we utilise the body (see SimpleNetworkJob) + // however the oc api requires the format header to be part of the url + q.addQueryItem(QStringLiteral("format"), QStringLiteral("json")); + setQuery(q); +} + +JsonApiJob::JsonApiJob(Account *account, const QString &path, const UrlQuery &arguments, const QNetworkRequest &req, QObject *parent) + : JsonApiJob(account, path, QByteArrayLiteral("GET"), arguments, req, parent) +{ +} + +int JsonApiJob::ocsStatus() const +{ + return _ocsStatus; +} + +void JsonApiJob::parse(const QByteArray &rawData) +{ + static const QRegularExpression rex(QStringLiteral("(\\d+)")); + const auto match = rex.match(QString::fromUtf8(rawData)); + if (match.hasMatch()) { + // this is a error message coming back from ocs. + _ocsStatus = match.captured(1).toInt(); + } else { + JsonJob::parse(rawData); + // example: "{"ocs":{"meta":{"status":"ok","statuscode":100,"message":null},"data":{"version":{"major":8,"minor":"... (504) + if (parseError().error == QJsonParseError::NoError) { + if (data().contains(QLatin1String("ocs"))) { + const auto meta = data().value(QLatin1String("ocs")).toObject().value(QLatin1String("meta")).toObject(); + _ocsStatus = meta.value(QLatin1String("statuscode")).toInt(); + _ocsMessage = meta.value(QLatin1String("message")).toString(); + } else { + _ocsMessage = parseError().errorString(); + } + } + } +} +const QString &JsonApiJob::ocsMessage() const +{ + return _ocsMessage; +} +bool JsonApiJob::ocsSuccess() const +{ + // v1 api: 100 + // v2 api: 200 as in http + return ocsStatus() == 100 || ocsStatus() == 200; +} diff --git a/src/libsync/networkjobs/jsonjob.h b/src/libsync/networkjobs/jsonjob.h new file mode 100644 index 00000000000..a97af454574 --- /dev/null +++ b/src/libsync/networkjobs/jsonjob.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "networkjobs.h" + +#include +#include +#include + +namespace OCC { + + +class OWNCLOUDSYNC_EXPORT JsonJob : public SimpleNetworkJob +{ + Q_OBJECT +public: + using SimpleNetworkJob::SimpleNetworkJob; + virtual ~JsonJob(); + + const QJsonObject &data() const; + const QJsonParseError &parseError() const; + +protected: + void finished() override; + + virtual void parse(const QByteArray &data); + + +private: + QJsonParseError _parseError; + QJsonObject _data; +}; + + +/** + * @brief Job to check an API that return JSON + * + * Note! you need to be in the connected state before calling this because of a server bug: + * https://github.com/owncloud/core/issues/12930 + * + * To be used like this: + * \code + * _job = new JsonApiJob(account, QLatin1String("ocs/v1.php/foo/bar"), this); + * connect(job, SIGNAL(jsonReceived(QJsonDocument)), ...) + * The received QVariantMap is null in case of error + * \encode + * + * @ingroup libsync + */ +class OWNCLOUDSYNC_EXPORT JsonApiJob : public JsonJob +{ + Q_OBJECT +public: + explicit JsonApiJob(Account *account, const QString &path, const QByteArray &verb, const UrlQuery &arguments, const QNetworkRequest &req, QObject *parent); + explicit JsonApiJob(Account *account, const QString &path, const UrlQuery &arguments, const QNetworkRequest &req, QObject *parent); + + // the OCS status code: 100 (!) for success + int ocsStatus() const; + + const QString &ocsMessage() const; + + // whether the job was successful + bool ocsSuccess() const; + +private: + int _ocsStatus = 0; + QString _ocsMessage; + +protected: + using JsonJob::JsonJob; + virtual void parse(const QByteArray &data) override; +}; + +} diff --git a/src/libsync/networkjobs/resources.cpp b/src/libsync/networkjobs/resources.cpp new file mode 100644 index 00000000000..ea7e3868ecc --- /dev/null +++ b/src/libsync/networkjobs/resources.cpp @@ -0,0 +1,116 @@ +#include "resources.h" + +#include +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcResources, "sync.networkjob.resource") + +namespace { + QString hashMd5(const QString &data) + { + QCryptographicHash hash(QCryptographicHash::Algorithm::Md5); + hash.addData(data.toLatin1()); + return QString::fromLatin1(hash.result().toHex()); + } + + QString cacheKey(QNetworkReply *reply) + { + const QString urlHash = hashMd5(reply->url().toString()); + const QString eTagHash = hashMd5(reply->header(QNetworkRequest::ETagHeader).toString()); + + const auto db = QMimeDatabase(); + + QString extension = db.mimeTypeForName(reply->header(QNetworkRequest::ContentTypeHeader).toString()).preferredSuffix(); + if (extension.isEmpty()) { + extension = db.mimeTypeForData(reply).preferredSuffix(); + } + if (extension.isEmpty()) { + extension = QStringLiteral("unknown"); + } + + return QStringLiteral("%1-%2.%3").arg(urlHash, eTagHash, extension); + } +} + +void ResourceJob::finished() +{ + if (reply()->error() != QNetworkReply::NoError) { + qCWarning(lcResources) << "Network error: " << this << errorString(); + } else { + _cacheKey = cacheKey(reply()); + } + + // storing the file on disk enables Qt to apply some optimizations, e.g., in QIcon + // also, specifically for icons, loading from disk allows easy management of scalable and non-scalable entities + if (httpStatusCode() == 200 && reply()->size() > 0) { + const QString path = _cache->path(_cacheKey); + + qCDebug(lcResources) << "cache file path:" << path; + + // furthermore, we can skip writing the file if the cache key has not changed (i.e., a file exists) and the file has come from the network cache + if (QFileInfo::exists(path) && reply()->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool()) { + qCDebug(lcResources) << "file has come from network cache, skipping writing"; + } else { + QFile cacheFile(path); + + // note: we want to truncate the file if it exists + if (!cacheFile.open(QIODevice::WriteOnly)) { + qCCritical(lcResources) << "failed to open cache file for writing:" << cacheFile.fileName(); + } else { + if (!cacheFile.write(reply()->readAll())) { + qCCritical(lcResources) << "failed to write to cache file:" << cacheFile.fileName(); + } + } + } + } + + SimpleNetworkJob::finished(); +} + +QIcon ResourceJob::asIcon() const +{ + if (_cacheKey.isEmpty()) { + // This can happen when a network error occurred when retrieving the icon. + return {}; + } + + // storing the file on disk enables Qt to apply some optimizations (e.g., caching of rendered pixmaps) + return QIcon(_cache->path(_cacheKey)); +} + +ResourceJob::ResourceJob(ResourcesCache *cache, const QUrl &rootUrl, const QString &path, QObject *parent) + : SimpleNetworkJob(cache->account(), rootUrl, path, "GET", {}, {}, parent) + , _cache(cache) +{ + setStoreInCache(true); +} + +ResourcesCache::ResourcesCache(const QString &cacheDirectory, Account *account) + : QObject(account) + , _account(account) + , _cacheDirectory(QStringLiteral("%1/tmp.XXXXXX").arg(cacheDirectory)) +{ + Q_ASSERT(_cacheDirectory.isValid()); +} + +ResourceJob *ResourcesCache::makeGetJob(const QUrl &rootUrl, QObject *parent) +{ + return new ResourceJob(this, rootUrl, {}, parent); +} + +Account *ResourcesCache::account() const +{ + return _account; +} + +QString ResourcesCache::path(const QString &cacheKey) const +{ + Q_ASSERT(!cacheKey.isEmpty()); + Q_ASSERT(_cacheDirectory.isValid()); + return _cacheDirectory.filePath(cacheKey); +} + +} diff --git a/src/libsync/networkjobs/resources.h b/src/libsync/networkjobs/resources.h new file mode 100644 index 00000000000..0b731adfb19 --- /dev/null +++ b/src/libsync/networkjobs/resources.h @@ -0,0 +1,64 @@ +#pragma once + +#include "account.h" +#include "networkjobs.h" + +#include +#include +#include + +namespace OCC { + +class ResourcesCache; + +/** + * This job automatically downloads all available data from the server and stores it in a temporary cache directory on the disk. + * For convenience, a couple of conversion functions are available to convert the binary data to common Qt classes such as QIcon. + */ +class OWNCLOUDSYNC_EXPORT ResourceJob : public SimpleNetworkJob +{ +public: + void finished() override; + + QIcon asIcon() const; + +protected: + explicit ResourceJob(ResourcesCache *cache, const QUrl &rootUrl, const QString &path, QObject *parent); + +private: + const ResourcesCache *_cache; + QString _cacheKey; + + friend class ResourcesCache; +}; + + +class OWNCLOUDSYNC_EXPORT ResourcesCache : public QObject +{ + Q_OBJECT + +public: + /** + * + * @param cacheDirectory path to temporary cache parent directory (note: this directory must exist) + * @param account + */ + explicit ResourcesCache(const QString &cacheDirectory, Account *account); + + /** + * Create a simple get job, but store the result in the cache. + * + * Note: the caller is responsible for deleting the job. + */ + ResourceJob *makeGetJob(const QUrl &url, QObject *parent); + + Account *account() const; + + QString path(const QString &cacheKey) const; + +private: + Account *_account; + QTemporaryDir _cacheDirectory; +}; + +} // namespace OCC diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index a4d3cbdc28d..4e89531a579 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -14,24 +14,24 @@ */ #include "owncloudpropagator.h" +#include "account.h" +#include "common/asserts.h" #include "common/syncjournaldb.h" #include "common/syncjournalfilerecord.h" +#include "common/utility.h" +#include "discoveryphase.h" +#include "filesystem.h" #include "propagatedownload.h" -#include "propagateupload.h" -#include "propagateuploadtus.h" #include "propagateremotedelete.h" -#include "propagateremotemove.h" #include "propagateremotemkdir.h" +#include "propagateremotemove.h" +#include "propagateuploadfile.h" +#include "propagateuploadtus.h" #include "propagatorjobs.h" -#include "filesystem.h" -#include "common/utility.h" -#include "account.h" -#include "common/asserts.h" -#include "discoveryphase.h" +#include "vio/csync_vio_local.h" #ifdef Q_OS_WIN -#include -#include +#include "common/utility_win.h" #endif #include @@ -41,7 +41,6 @@ #include #include #include -#include #include using namespace std::chrono_literals; @@ -77,16 +76,9 @@ qint64 freeSpaceLimit() return value; } -OwncloudPropagator::~OwncloudPropagator() -{ -} - - int OwncloudPropagator::maximumActiveTransferJob() { - if (_downloadLimit != 0 - || _uploadLimit != 0 - || !_syncOptions._parallelNetworkJobs) { + if (!_syncOptions._parallelNetworkJobs) { // disable parallelism when there is a network limit. return 1; } @@ -113,12 +105,12 @@ PropagateItemJob::~PropagateItemJob() bool PropagateItemJob::scheduleSelfOrChild() { - if (_state != NotYetStarted) { + if (state() != NotYetStarted) { return false; } - qCInfo(lcPropagator) << "Starting" << _item->_instruction << "propagation of" << _item->destination() << "by" << this; + qCInfo(lcPropagator) << "Starting propagation of" << _item << "by" << this; - _state = Running; + setState(Running); if (thread() != QApplication::instance()->thread()) { QMetaObject::invokeMethod(this, &PropagateItemJob::start); // We could be in a different thread (neon jobs) } else { @@ -152,7 +144,7 @@ static SyncJournalErrorBlacklistRecord createBlacklistEntry( entry._file = item._file; entry._errorString = item._errorString; entry._lastTryModtime = item._modtime; - entry._lastTryEtag = item._etag; + entry._lastTryEtag = item._etag.toUtf8(); entry._lastTryTime = Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc()); entry._renameTarget = item._renameTarget; entry._retryCount = old._retryCount + 1; @@ -175,13 +167,11 @@ static SyncJournalErrorBlacklistRecord createBlacklistEntry( entry._ignoreDuration = qBound(minBlacklistTime, entry._ignoreDuration, maxBlacklistTime); - if (item._status == SyncFileItem::SoftError) { - // Track these errors, but don't actively suppress them. - entry._ignoreDuration = 0; - } - if (item._httpErrorCode == 507) { - entry._errorCategory = SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage; + entry._errorCategory = SyncJournalErrorBlacklistRecord::Category::InsufficientRemoteStorage; + } else if (item._httpErrorCode == 0 && item._status == SyncFileItem::SoftError) { + // assume a local error + entry._errorCategory = SyncJournalErrorBlacklistRecord::Category::LocalSoftError; } return entry; @@ -195,11 +185,9 @@ static void blacklistUpdate(SyncJournalDb *journal, SyncFileItem &item) { SyncJournalErrorBlacklistRecord oldEntry = journal->errorBlacklistEntry(item._file); - const bool mayBlacklist = (item._status == SyncFileItem::NormalError) - || ((item._status == SyncFileItem::SoftError - || item._status == SyncFileItem::DetailError) - && item._httpErrorCode != 0 // or non-local error - ); + const bool mayBlacklist = item._status == SyncFileItem::NormalError + || item._status == SyncFileItem::SoftError + || item._status == SyncFileItem::DetailError; // No new entry? Possibly remove the old one, then done. if (!mayBlacklist) { @@ -215,31 +203,27 @@ static void blacklistUpdate(SyncJournalDb *journal, SyncFileItem &item) // Suppress the error if it was and continues to be blacklisted. // An ignoreDuration of 0 mean we're tracking the error, but not actively // suppressing it. - if (item._hasBlacklistEntry && newEntry._ignoreDuration > 0) { - item._status = SyncFileItem::BlacklistedError; - - qCInfo(lcPropagator) << "blacklisting " << item._file - << " for " << newEntry._ignoreDuration - << ", retry count " << newEntry._retryCount; - - return; - } - // Some soft errors might become louder on repeat occurrence if (item._status == SyncFileItem::SoftError - && newEntry._retryCount > 1) { - qCWarning(lcPropagator) << "escalating soft error on " << item._file + && newEntry._retryCount > 1 + && item._httpErrorCode != 0) { + qCWarning(lcPropagator) << "escalating http soft error on " << item._file << " to normal error, " << item._httpErrorCode; item._status = SyncFileItem::NormalError; - return; + } else if (item._status != SyncFileItem::SoftError && item._hasBlacklistEntry && newEntry._ignoreDuration > 0) { + item._status = SyncFileItem::BlacklistedError; } + + qCInfo(lcPropagator) << "blacklisting " << item._file + << " for " << newEntry._ignoreDuration + << ", retry count " << newEntry._retryCount; } void PropagateItemJob::done(SyncFileItem::Status statusArg, const QString &errorString) { // Duplicate calls to done() are a logic error - OC_ENFORCE(_state != Finished); - _state = Finished; + OC_ENFORCE(state() != Finished); + setState(Finished); _item->_status = statusArg; @@ -265,13 +249,19 @@ void PropagateItemJob::done(SyncFileItem::Status statusArg, const QString &error // Blacklist handling switch (_item->_status) { case SyncFileItem::SoftError: + [[fallthrough]]; case SyncFileItem::FatalError: + [[fallthrough]]; case SyncFileItem::NormalError: + [[fallthrough]]; case SyncFileItem::DetailError: + [[fallthrough]]; + case SyncFileItem::Message: // Check the blacklist, possibly adjusting the item (including its status) blacklistUpdate(propagator()->_journal, *_item); break; case SyncFileItem::Success: + [[fallthrough]]; case SyncFileItem::Restoration: if (_item->_hasBlacklistEntry) { // wipe blacklist entry. @@ -283,10 +273,16 @@ void PropagateItemJob::done(SyncFileItem::Status statusArg, const QString &error } break; case SyncFileItem::Conflict: + [[fallthrough]]; case SyncFileItem::FileIgnored: + [[fallthrough]]; case SyncFileItem::NoStatus: + [[fallthrough]]; case SyncFileItem::BlacklistedError: + [[fallthrough]]; case SyncFileItem::Excluded: + [[fallthrough]]; + case SyncFileItem::FilenameReserved: // nothing break; case SyncFileItem::StatusCount: @@ -297,9 +293,19 @@ void PropagateItemJob::done(SyncFileItem::Status statusArg, const QString &error qCWarning(lcPropagator) << "Could not complete propagation of" << _item->destination() << "by" << this << "with status" << _item->_status << "and error:" << _item->_errorString; else qCInfo(lcPropagator) << "Completed propagation of" << _item->destination() << "by" << this << "with status" << _item->_status; - emit propagator()->itemCompleted(_item); - emit finished(_item->_status); + // Will be handled in PropagateDirectory::slotSubJobsFinished at the end + if (!_item->isDirectory() || _item->_relevantDirectoyInstruction) { + // we are either not a directory or we are a PropagateDirectory job + // and an actual instruction was performed for this directory. + Q_ASSERT(!_item->_relevantDirectoyInstruction || qobject_cast(this)); + Q_EMIT propagator()->itemCompleted(_item); + } else { + // the directory needs to call done() in PropagateDirectory::slotSubJobsFinished + // we don't notify itemCompleted yet as the directory is only complete once its child items are complete. + _item->_relevantDirectoyInstruction = true; + } + Q_EMIT finished(_item->_status); if (_item->_status == SyncFileItem::FatalError) { // Abort all remaining jobs. propagator()->abort(); @@ -310,13 +316,15 @@ void PropagateItemJob::done(SyncFileItem::Status statusArg, const QString &error PropagateItemJob *OwncloudPropagator::createJob(const SyncFileItemPtr &item) { - bool deleteExisting = item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE; - switch (item->_instruction) { + qCDebug(lcPropagator) << "Propagating:" << item; + const bool deleteExisting = item->instruction() == CSYNC_INSTRUCTION_TYPE_CHANGE; + switch (item->instruction()) { case CSYNC_INSTRUCTION_REMOVE: - if (item->_direction == SyncFileItem::Down) + if (item->_direction == SyncFileItem::Down) { return new PropagateLocalRemove(this, item); - else + } else { return new PropagateRemoteDelete(this, item); + } case CSYNC_INSTRUCTION_NEW: case CSYNC_INSTRUCTION_TYPE_CHANGE: case CSYNC_INSTRUCTION_CONFLICT: @@ -332,26 +340,21 @@ PropagateItemJob *OwncloudPropagator::createJob(const SyncFileItemPtr &item) return job; } } //fall through - case CSYNC_INSTRUCTION_SYNC: + case CSYNC_INSTRUCTION_SYNC: { if (item->_direction != SyncFileItem::Up) { auto job = new PropagateDownloadFile(this, item); job->setDeleteExistingFolder(deleteExisting); return job; - } else { - PropagateUploadFileCommon *job = nullptr; - if (account()->capabilities().tusSupport().isValid()) { - job = new PropagateUploadFileTUS(this, item); - } else { - if (item->_size > syncOptions()._initialChunkSize && account()->capabilities().chunkingNg()) { - // Item is above _initialChunkSize, thus will be classified as to be chunked - job = new PropagateUploadFileNG(this, item); - } else { - job = new PropagateUploadFileV1(this, item); - } - } + } + if (account()->capabilities().tusSupport().isValid()) { + auto job = new PropagateUploadFileTUS(this, item); job->setDeleteExisting(deleteExisting); return job; } + auto job = new PropagateUploadFile(this, item); + job->setDeleteExisting(deleteExisting); + return job; + } case CSYNC_INSTRUCTION_RENAME: if (item->_direction == SyncFileItem::Up) { return new PropagateRemoteMove(this, item); @@ -369,9 +372,6 @@ PropagateItemJob *OwncloudPropagator::createJob(const SyncFileItemPtr &item) case CSYNC_INSTRUCTION_ERROR: return new PropagateIgnoreJob(this, item); case CSYNC_INSTRUCTION_NONE: - case CSYNC_INSTRUCTION_STAT_ERROR: - case CSYNC_INSTRUCTION_EVAL_RENAME: - case CSYNC_INSTRUCTION_EVAL: return nullptr; } Q_UNREACHABLE(); @@ -383,128 +383,168 @@ qint64 OwncloudPropagator::smallFileSize() return smallFileSize; } +/** + * This builds all the jobs needed for the propagation. + * Each directory is a PropagateDirectory job, which contains the files in it. + */ void OwncloudPropagator::start(SyncFileItemSet &&items) { - /* This builds all the jobs needed for the propagation. - * Each directory is a PropagateDirectory job, which contains the files in it. - * In order to do that we loop over the items. (which are sorted by destination) - * When we enter a directory, we can create the directory job and push it on the stack. */ + // The items list is sorted in such a way that an item for a directory come before any items + // inside that directory. For example: + // - A <--- directory + // - A/B <--- directory + // - A/B/1 <--- file + // - A/2 <--- file + // This is important, because we rely on the fact that all actions for items inside a directory + // are grouped together, and that an action on the directory itself preceeds the actions for + // its child items. _rootJob.reset(new PropagateRootDirectory(this)); + + // The algorithm could be done recursively, but the implementation is done iteratively in order + // to prevent us running out of stack space. So the next 3 variables are used to maintain the + // state. + + // The directories stack below has the current directory on top of the stack. So if we're + // processing directory `/A/B/C`, then the stack looks like this: + // - PropagateDirectory job for A/B/C <--- top-of-stack + // - PropagateDirectory job for A/B + // - PropagateDirectory job for A + // - PropagateDirectory job for / <--- bottom-of-stack + // + // IMPORTANT: this parent stack is also used when a directory is removed + // (`CSYNC_INSTRUCTION_REMOVE` in the last step below), in order to suppress updating the etag + // of the parents of the to-be-removed directory. See the comment in the code for more information. QStack> directories; - directories.push(qMakePair(QString(), _rootJob.data())); - QVector directoriesToRemove; - QString removedDirectory; + directories.push({QString(), _rootJob.data()}); + + // Due to the ordering of the items, it can happen that we have the following list: + // - remove directory A + // - remove file A/a + // - etc. + // So when we find a have a sync instruction to remove a directory, we remember it in the + // `currentRemoveDirectoryJob` variable, so we know/see it when processing the next item + // in the list. + PropagatorJob *currentRemoveDirectoryJob = nullptr; + + // We skip items inside conflict directories. So, when we see an item that's marked as such, + // remember its name to see if in the next iteration, we will hit an item for that directory. + // See the `else` statement in the second step. QString maybeConflictDirectory; - for (const auto &item : qAsConst(items)) { - if (!removedDirectory.isEmpty() && item->_file.startsWith(removedDirectory)) { - // this is an item in a directory which is going to be removed. - PropagateDirectory *delDirJob = qobject_cast(directoriesToRemove.first()); - if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) { + for (const auto &item : std::as_const(items)) { + // First check if this is an item in a directory which is going to be removed. + if (currentRemoveDirectoryJob && FileSystem::isChildPathOf(item->_file, currentRemoveDirectoryJob->path())) { + // Check the sync instruction for the item: + if (item->instruction() == CSYNC_INSTRUCTION_REMOVE) { // already taken care of. (by the removal of the parent directory) - // increase the number of subjobs that would be there. - if (delDirJob) { + if (auto delDirJob = qobject_cast(currentRemoveDirectoryJob)) { + // increase the number of sub jobs that would be there. delDirJob->increaseAffectedCount(); } continue; - } else if (item->isDirectory() - && (item->_instruction == CSYNC_INSTRUCTION_NEW - || item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE)) { - // create a new directory within a deleted directory? That can happen if the directory + } else if (item->isDirectory() && (item->instruction() & (CSYNC_INSTRUCTION_NEW | CSYNC_INSTRUCTION_TYPE_CHANGE))) { + // Create a new directory within a deleted directory? That can happen if the directory // etag was not fetched properly on the previous sync because the sync was aborted // while uploading this directory (which is now removed). We can ignore it. - if (delDirJob) { + if (auto delDirJob = qobject_cast(currentRemoveDirectoryJob)) { + // increase the number of sub jobs that would be there. delDirJob->increaseAffectedCount(); } continue; - } else if (item->_instruction == CSYNC_INSTRUCTION_IGNORE) { + } else if (item->instruction() & (CSYNC_INSTRUCTION_IGNORE | CSYNC_INSTRUCTION_NONE)) { continue; - } else if (item->_instruction == CSYNC_INSTRUCTION_RENAME) { + } else if (item->instruction() != CSYNC_INSTRUCTION_RENAME) { // all is good, the rename will be executed before the directory deletion - } else { - qCWarning(lcPropagator) << "WARNING: Job within a removed directory? This should not happen!" - << item->_file << item->_instruction; + qCWarning(lcPropagator) << "WARNING: Job within a removed directory? This should not happen!" << item->_file << item->instruction(); + Q_ASSERT(false); // we shouldn't land here, but assert for debug purposes } } - // If a CONFLICT item contains files these can't be processed because + // Second step: if a CONFLICT item contains files, these files can't be processed because // the conflict handling is likely to rename the directory. This can happen // when there's a new local directory at the same time as a remote file. if (!maybeConflictDirectory.isEmpty()) { - if (item->destination().startsWith(maybeConflictDirectory)) { - qCInfo(lcPropagator) << "Skipping job inside CONFLICT directory" - << item->_file << item->_instruction; - item->_instruction = CSYNC_INSTRUCTION_NONE; + if (FileSystem::isChildPathOf(item->destination(), maybeConflictDirectory)) { + // We're processing an item in a CONFLICT directory. + qCInfo(lcPropagator) << "Skipping job inside CONFLICT directory" << item->_file << item->instruction(); + item->setInstruction(CSYNC_INSTRUCTION_NONE); continue; } else { + // This is not an item inside the conflict directory, which means we're done + // processing the conflict directory in the previous iteration, so clear the string. maybeConflictDirectory.clear(); } } - while (!item->destination().startsWith(directories.top().first)) { + // Third step: pop the directory stack when we're finished there. + // For example, we were processing directory `A/B`, which means that the + // `PropagateDirectory` job for `B` is on top of the stack. If we now have + // an item `A/c`, then we're back to processing directory `A`, so we have + // to pop the `B` job off of the stack. + while (!FileSystem::isChildPathOf(item->destination(), directories.top().first)) { directories.pop(); } + // The last step is to add a subtask for the item. There are 4 cases covered here: + // a type change vs. a removal, and a file vs. a directory. if (item->isDirectory()) { PropagateDirectory *dir = new PropagateDirectory(this, item); - if (item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE - && item->_direction == SyncFileItem::Up) { + if (item->instruction() == CSYNC_INSTRUCTION_TYPE_CHANGE && item->_direction == SyncFileItem::Up) { // Skip all potential uploads to the new folder. // Processing them now leads to problems with permissions: // checkForPermissions() has already run and used the permissions // of the file we're about to delete to decide whether uploading // to the new dir is ok... - for (const auto &item2 : qAsConst(items)) { - if (item2->destination().startsWith(item->destination() + QLatin1Char('/'))) { - item2->_instruction = CSYNC_INSTRUCTION_NONE; + for (const auto &item2 : std::as_const(items)) { + if (item2 != item && FileSystem::isChildPathOf(item2->destination(), item->destination())) { + item2->setInstruction(CSYNC_INSTRUCTION_NONE); _anotherSyncNeeded = true; } } } - if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) { + if (item->instruction() == CSYNC_INSTRUCTION_REMOVE) { // We do the removal of directories at the end, because there might be moves from // these directories that will happen later. - directoriesToRemove.prepend(dir); - removedDirectory = item->_file + QLatin1Char('/'); + currentRemoveDirectoryJob = dir; + _rootJob->addDeleteJob(currentRemoveDirectoryJob); // We should not update the etag of parent directories of the removed directory // since it would be done before the actual remove (issue #1845) // NOTE: Currently this means that we don't update those etag at all in this sync, // but it should not be a problem, they will be updated in the next sync. - for (int i = 0; i < directories.size(); ++i) { - if (directories[i].second->item()->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA) - directories[i].second->item()->_instruction = CSYNC_INSTRUCTION_NONE; + for (auto &aDir : directories) { + if (aDir.second->item()->instruction() == CSYNC_INSTRUCTION_UPDATE_METADATA) { + aDir.second->item()->setInstruction(CSYNC_INSTRUCTION_NONE); + _anotherSyncNeeded = true; + } } } else { - PropagateDirectory *currentDirJob = directories.top().second; - currentDirJob->appendJob(dir); + directories.top().second->appendJob(dir); } - directories.push(qMakePair(item->destination() + QLatin1Char('/'), dir)); + + directories.push(qMakePair(item->destination(), dir)); } else { - if (item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE) { + // The item is not a directory, but a file. + if (item->instruction() == CSYNC_INSTRUCTION_TYPE_CHANGE) { // will delete directories, so defer execution - directoriesToRemove.prepend(createJob(item)); - removedDirectory = item->_file + QLatin1Char('/'); + currentRemoveDirectoryJob = createJob(item); + _rootJob->addDeleteJob(currentRemoveDirectoryJob); } else { directories.top().second->appendTask(item); } - if (item->_instruction == CSYNC_INSTRUCTION_CONFLICT) { + if (item->instruction() == CSYNC_INSTRUCTION_CONFLICT) { // This might be a file or a directory on the local side. If it's a // directory we want to skip processing items inside it. - maybeConflictDirectory = item->_file + QLatin1Char('/'); + maybeConflictDirectory = item->_file; } } } - for (auto *it : qAsConst(directoriesToRemove)) { - _rootJob->_dirDeletionJobs.appendJob(it); - } - connect(_rootJob.data(), &PropagatorJob::finished, this, &OwncloudPropagator::emitFinished); _jobScheduled = false; @@ -516,37 +556,47 @@ const SyncOptions &OwncloudPropagator::syncOptions() const return _syncOptions; } -void OwncloudPropagator::setSyncOptions(const SyncOptions &syncOptions) -{ - _syncOptions = syncOptions; - _chunkSize = syncOptions._initialChunkSize; -} - Result OwncloudPropagator::localFileNameClash(const QString &relFile) { OC_ASSERT(!relFile.isEmpty()); - if (!relFile.isEmpty() && Utility::fsCasePreserving()) { + if (!relFile.isEmpty() && Utility::fsCasePreservingButCaseInsensitive()) { const QFileInfo fileInfo(_localDir + relFile); - qCDebug(lcPropagator) << "CaseClashCheck for " << fileInfo.filePath(); #ifdef Q_OS_MAC + // APFS is case preserving but case ignoring. It is also normalization preserving and normalization ignoring. + // So we have to check for both if (!fileInfo.exists()) { + // No file with an "equivalent" name exists. return false; } else { - // Need to normalize to composited form because of QTBUG-39622/QTBUG-55896 - const QString cName = fileInfo.canonicalFilePath().normalized(QString::NormalizationForm_C); - if (fileInfo.filePath() != cName && !cName.endsWith(relFile, Qt::CaseSensitive)) { - qCWarning(lcPropagator) << "Detected case clash between" << fileInfo.filePath() << "and" << cName; - return cName; + // Check if the existing file is an *exact* name match with the remote file. Do this by + // checking each file name in the directory of `relFile`. + QString fileName = fileInfo.fileName(); // The file name of the to-be-downloaded file + bool hasFileInExactNormalizationAndCaseForm = false; + auto handle = csync_vio_local_opendir(fileInfo.path()); + while (true) { + std::unique_ptr dirent = csync_vio_local_readdir(handle, nullptr); + if (!dirent) + break; + + if (fileName == dirent->path) { + // Exact match: no case differences, and no normalization differences, so no clash. + hasFileInExactNormalizationAndCaseForm = true; + break; + } + } + csync_vio_local_closedir(handle); + + if (!hasFileInExactNormalizationAndCaseForm) { + qCWarning(lcPropagator) << "Detected clash for" << fileInfo.filePath(); + return fileInfo.filePath(); } } #elif defined(Q_OS_WIN) WIN32_FIND_DATA FindFileData; - HANDLE hFind; - - hFind = FindFirstFileW(reinterpret_cast(FileSystem::longWinPath(fileInfo.filePath()).utf16()), &FindFileData); + const Utility::Handle hFind(FindFirstFileW(reinterpret_cast(FileSystem::longWinPath(fileInfo.filePath()).utf16()), &FindFileData), + [](HANDLE h) { FindClose(h); }); if (hFind != INVALID_HANDLE_VALUE) { const QString realFileName = QString::fromWCharArray(FindFileData.cFileName); - FindClose(hFind); if (!fileInfo.filePath().endsWith(realFileName, Qt::CaseSensitive)) { const QString clashName = fileInfo.path() + QLatin1Char('/') + realFileName; @@ -555,7 +605,7 @@ Result OwncloudPropagator::localFileNameClash(const QString &relF } } #else - // On Linux, the file system is case sensitive, but this code is useful for testing. + // On Linux, the file system is case-sensitive, but this code is useful for testing. // Just check that there is no other file with the same name and different casing. const QString fn = fileInfo.fileName(); const QStringList list = fileInfo.dir().entryList({ fn }); @@ -649,13 +699,14 @@ void OwncloudPropagator::scheduleNextJobImpl() void OwncloudPropagator::reportFileTotal(const SyncFileItem &item, qint64 newSize) { - emit updateFileTotal(item, newSize); + Q_EMIT updateFileTotal(item, newSize); } void OwncloudPropagator::abort() { if (_abortRequested) return; + _abortRequested = true; if (_rootJob) { // Connect to abortFinished which signals that abort has been asynchronously finished connect(_rootJob.data(), &PropagateDirectory::abortFinished, this, &OwncloudPropagator::emitFinished); @@ -677,10 +728,10 @@ void OwncloudPropagator::abort() void OwncloudPropagator::reportProgress(const SyncFileItem &item, qint64 bytes) { - emit progress(item, bytes); + Q_EMIT progress(item, bytes); } -AccountPtr OwncloudPropagator::account() const +Account *OwncloudPropagator::account() const { return _account; } @@ -703,31 +754,24 @@ OwncloudPropagator::DiskSpaceResult OwncloudPropagator::diskSpaceCheck() const return DiskSpaceOk; } -bool OwncloudPropagator::createConflict(const SyncFileItemPtr &item, - PropagatorCompositeJob *composite, QString *error) +bool OwncloudPropagator::createConflict(const SyncFileItemPtr &item, QString *error) { QString fn = fullLocalPath(item->_file); QString renameError; auto conflictModTime = FileSystem::getModTime(fn); - QString conflictUserName; - if (account()->capabilities().uploadConflictFiles()) - conflictUserName = account()->davDisplayName(); - QString conflictFileName = Utility::makeConflictFileName( - item->_file, Utility::qDateTimeFromTime_t(conflictModTime), conflictUserName); + QString conflictFileName = Utility::makeConflictFileName(item->_file, Utility::qDateTimeFromTime_t(conflictModTime)); QString conflictFilePath = fullLocalPath(conflictFileName); // If the file is locked, we want to retry this sync when it // becomes available again. if (FileSystem::isFileLocked(fn, FileSystem::LockMode::Exclusive)) { - emit seenLockedFile(fn, FileSystem::LockMode::Exclusive); + Q_EMIT seenLockedFile(fn, FileSystem::LockMode::Exclusive); if (error) *error = tr("File %1 is currently in use").arg(fn); return false; } - emit touchedFile(fn); - emit touchedFile(conflictFilePath); if (!FileSystem::rename(fn, conflictFilePath, &renameError)) { // If the rename fails, don't replace it. if (error) @@ -752,25 +796,6 @@ bool OwncloudPropagator::createConflict(const SyncFileItemPtr &item, _journal->setConflictRecord(conflictRecord); - // Create a new upload job if the new conflict file should be uploaded - if (account()->capabilities().uploadConflictFiles()) { - if (composite && !QFileInfo(conflictFilePath).isDir()) { - SyncFileItemPtr conflictItem = SyncFileItemPtr(new SyncFileItem); - conflictItem->_file = conflictFileName; - conflictItem->_type = ItemTypeFile; - conflictItem->_direction = SyncFileItem::Up; - conflictItem->_instruction = CSYNC_INSTRUCTION_NEW; - conflictItem->_modtime = conflictModTime; - conflictItem->_size = item->_previousSize; - emit newItem(conflictItem); - composite->appendTask(conflictItem); - } else { - // Directories we can't process in one go. The next sync run - // will take care of uploading the conflict dir contents. - _anotherSyncNeeded = true; - } - } - return true; } @@ -781,17 +806,37 @@ QString OwncloudPropagator::adjustRenamedPath(const QString &original) const Result OwncloudPropagator::updatePlaceholder(const SyncFileItem &item, const QString &fileName, const QString &replacesFile) { - return syncOptions()._vfs->updateMetadata(item, fileName, replacesFile); + if (!_syncOptions.isValid()) { + QString error = tr("vfs instance is not available."); + return error; + } + + Q_ASSERT([&] { + if (item._type == ItemTypeVirtualFileDehydration) { + // when dehydrating the file must not be pinned + // don't use destination() with suffix placeholder + const auto pin = syncOptions().vfs()->pinState(item._file); + if (pin && pin.get() == PinState::AlwaysLocal) { + return false; + } + } + return true; + }()); + return syncOptions().vfs()->updateMetadata(item, fileName, replacesFile); } Result OwncloudPropagator::updateMetadata(const SyncFileItem &item) { const QString fsPath = fullLocalPath(item.destination()); const auto result = updatePlaceholder(item, fsPath, {}); - if (!result || result.get() != Vfs::ConvertToPlaceholderResult::Ok) { + if (!result) { return result; } - const auto record = item.toSyncJournalFileRecordWithInode(fsPath); + auto record = item.toSyncJournalFileRecordWithInode(fsPath); + if (result.get() == Vfs::ConvertToPlaceholderResult::Locked) { + record._hasDirtyPlaceholder = true; + Q_EMIT seenLockedFile(fullLocalPath(item._file), FileSystem::LockMode::Exclusive); + } const auto dBresult = _journal->setFileRecord(record); if (!dBresult) { return dBresult.error(); @@ -801,15 +846,23 @@ Result OwncloudPropagator::updateMetad // ================================================================================ -PropagatorJob::PropagatorJob(OwncloudPropagator *propagator) +PropagatorJob::PropagatorJob(OwncloudPropagator *propagator, const QString &path) : QObject(propagator) - , _state(NotYetStarted) + , _propagator(propagator) + , _path(path) + , _jobState(NotYetStarted) +{ +} + +void PropagatorJob::setState(JobState state) { + _jobState = state; } OwncloudPropagator *PropagatorJob::propagator() const { - return qobject_cast(parent()); + // not a fan but... + return _propagator; } // ================================================================================ @@ -832,30 +885,29 @@ void PropagatorCompositeJob::slotSubJobAbortFinished() // Emit abort if last job has been aborted if (_abortsCount == 0) { - emit abortFinished(); + Q_EMIT abortFinished(); } } void PropagatorCompositeJob::appendJob(PropagatorJob *job) { - job->setAssociatedComposite(this); _jobsToDo.append(job); } bool PropagatorCompositeJob::scheduleSelfOrChild() { - if (_state == Finished) { + if (state() == Finished) { return false; } // Start the composite job - if (_state == NotYetStarted) { - _state = Running; + if (state() == NotYetStarted) { + setState(Running); } // Ask all the running composite jobs if they have something new to schedule. for (int i = 0; i < _runningJobs.size(); ++i) { - OC_ASSERT(_runningJobs.at(i)->_state == Running); + OC_ASSERT(_runningJobs.at(i)->state() == Running); if (possiblyRunNextJob(_runningJobs.at(i))) { return true; @@ -876,7 +928,7 @@ bool PropagatorCompositeJob::scheduleSelfOrChild() _tasksToDo.erase(_tasksToDo.begin()); PropagatorJob *job = propagator()->createJob(nextTask); if (!job) { - qCWarning(lcDirectory) << "Useless task found for file" << nextTask->destination() << "instruction" << nextTask->_instruction; + qCWarning(lcDirectory) << "Useless task found for file" << nextTask->destination() << "instruction" << nextTask->instruction(); continue; } appendJob(job); @@ -890,11 +942,11 @@ bool PropagatorCompositeJob::scheduleSelfOrChild() return possiblyRunNextJob(nextJob); } - // If neither us or our children had stuff left to do we could hang. Make sure + // If neither us nor our children had stuff left to do we could hang. Make sure // we mark this job as finished so that the propagator can schedule a new one. if (_jobsToDo.isEmpty() && _tasksToDo.empty() && _runningJobs.isEmpty()) { // Our parent jobs are already iterating over their running jobs, post to the event loop - // to avoid removing ourself from that list while they iterate. + // to avoid removing ourselves from that list while they iterate. QMetaObject::invokeMethod(this, &PropagatorCompositeJob::finalize, Qt::QueuedConnection); } return false; @@ -907,18 +959,24 @@ void PropagatorCompositeJob::slotSubJobFinished(SyncFileItem::Status status) // Delete the job and remove it from our list of jobs. subJob->deleteLater(); - int i = _runningJobs.indexOf(subJob); - OC_ENFORCE(i >= 0); // should only happen if this function is called more than once - _runningJobs.remove(i); + OC_ENFORCE(_runningJobs.removeAll(subJob) == 1); // we removed exactly one item // Any sub job error will cause the whole composite to fail. This is important // for knowing whether to update the etag in PropagateDirectory, for example. - if (status == SyncFileItem::FatalError - || status == SyncFileItem::NormalError - || status == SyncFileItem::SoftError - || status == SyncFileItem::DetailError - || status == SyncFileItem::BlacklistedError) { - _hasError = status; + switch (status) { + case SyncFileItem::FatalError: + [[fallthrough]]; + case SyncFileItem::NormalError: + [[fallthrough]]; + case SyncFileItem::SoftError: + [[fallthrough]]; + case SyncFileItem::DetailError: + [[fallthrough]]; + case SyncFileItem::BlacklistedError: + _errorPaths.insert(subJob->path(), status); + break; + default: + break; } if (_jobsToDo.isEmpty() && _tasksToDo.empty() && _runningJobs.isEmpty()) { @@ -932,11 +990,11 @@ void PropagatorCompositeJob::finalize() { // The propagator will do parallel scheduling and this could be posted // multiple times on the event loop, ignore the duplicate calls. - if (_state == Finished) + if (state() == Finished) return; - _state = Finished; - emit finished(_hasError == SyncFileItem::NoStatus ? SyncFileItem::Success : _hasError); + setState(Finished); + Q_EMIT finished(_errorPaths.empty() ? SyncFileItem::Success : _errorPaths.last()); } qint64 PropagatorCompositeJob::committedDiskSpace() const @@ -948,16 +1006,20 @@ qint64 PropagatorCompositeJob::committedDiskSpace() const return needed; } +PropagatorCompositeJob::PropagatorCompositeJob(OwncloudPropagator *propagator, const QString &path) + : PropagatorJob(propagator, path) +{ +} + // ================================================================================ PropagateDirectory::PropagateDirectory(OwncloudPropagator *propagator, const SyncFileItemPtr &item) : PropagateItemJob(propagator, item) , _firstJob(propagator->createJob(item)) - , _subJobs(propagator) + , _subJobs(propagator, path()) { if (_firstJob) { - connect(_firstJob.data(), &PropagatorJob::finished, this, &PropagateDirectory::slotFirstJobFinished); - _firstJob->setAssociatedComposite(&_subJobs); + connect(_firstJob.get(), &PropagatorJob::finished, this, &PropagateDirectory::slotFirstJobFinished); } connect(&_subJobs, &PropagatorJob::finished, this, &PropagateDirectory::slotSubJobsFinished); } @@ -977,19 +1039,19 @@ PropagatorJob::JobParallelism PropagateDirectory::parallelism() bool PropagateDirectory::scheduleSelfOrChild() { - if (_state == Finished) { + if (state() == Finished) { return false; } - if (_state == NotYetStarted) { - _state = Running; + if (state() == NotYetStarted) { + setState(Running); } - if (_firstJob && _firstJob->_state == NotYetStarted) { + if (_firstJob && _firstJob->state() == NotYetStarted) { return _firstJob->scheduleSelfOrChild(); } - if (_firstJob && _firstJob->_state == Running) { + if (_firstJob && _firstJob->state() == Running) { // Don't schedule any more job until this is done. return false; } @@ -999,12 +1061,19 @@ bool PropagateDirectory::scheduleSelfOrChild() void PropagateDirectory::slotFirstJobFinished(SyncFileItem::Status status) { - _firstJob.take()->deleteLater(); + _firstJob.release()->deleteLater(); - if (status != SyncFileItem::Success - && status != SyncFileItem::Restoration - && status != SyncFileItem::Conflict) { - if (_state != Finished) { + switch (status) { + // non critical states + case SyncFileItem::Success: + [[fallthrough]]; + case SyncFileItem::Restoration: + [[fallthrough]]; + case SyncFileItem::Conflict: + break; + // handle all other cases as errors + default: + if (state() != Finished) { // Synchronously abort abort(AbortType::Synchronous); done(status); @@ -1017,44 +1086,70 @@ void PropagateDirectory::slotFirstJobFinished(SyncFileItem::Status status) void PropagateDirectory::slotSubJobsFinished(const SyncFileItem::Status status) { - if (!_item->isEmpty() && status == SyncFileItem::Success) { - // If a directory is renamed, recursively delete any stale items - // that may still exist below the old path. - if (_item->_instruction == CSYNC_INSTRUCTION_RENAME - && _item->_originalFile != _item->_renameTarget) { - propagator()->_journal->deleteFileRecord(_item->_originalFile, true); + if (OC_ENSURE(!_item->isEmpty())) { + // report an error if the actual action on the folder failed + if (_item->_relevantDirectoyInstruction && _item->_status != SyncFileItem::Success) { + Q_ASSERT(_item->_status != SyncFileItem::NoStatus); + qCWarning(lcDirectory) << "PropagateDirectory completed with" << status << "the directory job itself is marked as" << _item->_status; + done(_item->_status); + return; } - if (_item->_instruction == CSYNC_INSTRUCTION_NEW && _item->_direction == SyncFileItem::Down) { - // special case for local MKDIR, set local directory mtime - // (it's not synced later at all, but can be nice to have it set initially) - FileSystem::setModTime(propagator()->fullLocalPath(_item->destination()), _item->_modtime); - } + if (status == SyncFileItem::Success) { + // If a directory is renamed, recursively delete any stale items + // that may still exist below the old path. + if (_item->instruction() == CSYNC_INSTRUCTION_RENAME && _item->_originalFile != _item->_renameTarget) { + // TODO: check result but it breaks TestDatabaseError + propagator()->_journal->deleteFileRecord(_item->_originalFile, true); + } - // For new directories we always want to update the etag once - // the directory has been propagated. Otherwise the directory - // could appear locally without being added to the database. - if (_item->_instruction & (CSYNC_INSTRUCTION_RENAME | CSYNC_INSTRUCTION_NEW | CSYNC_INSTRUCTION_UPDATE_METADATA)) { - const auto result = propagator()->updateMetadata(*_item); - if (!result) { - qCWarning(lcDirectory) << "Error writing to the database for file" << _item->_file << "with" << result.error(); - done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error())); - return; - } else if (result.get() == Vfs::ConvertToPlaceholderResult::Locked) { - done(SyncFileItem::SoftError, tr("%1 the folder is currently in use").arg(_item->destination())); + if (_item->instruction() == CSYNC_INSTRUCTION_NEW && _item->_direction == SyncFileItem::Down) { + // special case for local MKDIR, set local directory mtime + // (it's not synced later at all, but can be nice to have it set initially) + OC_ASSERT(FileSystem::setModTime(propagator()->fullLocalPath(_item->destination()), _item->_modtime)); + } + // For new directories we always want to update the etag once + // the directory has been propagated. Otherwise, the directory + // could appear locally without being added to the database. + // Additionally, we need to convert those folders to placeholders with cfapi vfs. + if (_item->instruction() & (CSYNC_INSTRUCTION_RENAME | CSYNC_INSTRUCTION_NEW | CSYNC_INSTRUCTION_UPDATE_METADATA)) { + // metatdata changes are relevant + _item->_relevantDirectoyInstruction = true; + _item->_status = SyncFileItem::Success; + const auto result = propagator()->updateMetadata(*_item); + if (!result) { + qCWarning(lcDirectory) << "Error writing to the database for file" << _item->_file << "with" << result.error(); + done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error())); + return; + } else if (result.get() == Vfs::ConvertToPlaceholderResult::Locked) { + done(SyncFileItem::SoftError, tr("%1 the folder is currently in use").arg(_item->destination())); + return; + } + } + if (_item->_relevantDirectoyInstruction) { + done(_item->_status); return; } } } - // don't call done, we only propagate the state of the child items - Q_ASSERT(_state != Finished); - _state = Finished; - emit finished(status); + + // don't call done, we only propagate the state of the child items, + // and we don't want error handling for this folder for an error that happened on a child + Q_ASSERT(state() != Finished); + setState(Finished); + Q_EMIT finished(status); + if (_item->_relevantDirectoyInstruction) { + Q_EMIT propagator()->itemCompleted(_item); + } } PropagateRootDirectory::PropagateRootDirectory(OwncloudPropagator *propagator) - : PropagateDirectory(propagator, SyncFileItemPtr(new SyncFileItem)) - , _dirDeletionJobs(propagator) + : PropagateDirectory(propagator, SyncFileItemPtr([] { + auto f = new SyncFileItem; + f->_file = QLatin1Char('/'); + return f; + }())) + , _dirDeletionJobs(propagator, path()) { connect(&_dirDeletionJobs, &PropagatorJob::finished, this, &PropagateRootDirectory::slotDirDeletionJobsFinished); } @@ -1067,10 +1162,11 @@ PropagatorJob::JobParallelism PropagateRootDirectory::parallelism() void PropagateRootDirectory::abort(PropagatorJob::AbortType abortType) { - if (_firstJob) + if (_firstJob) { // Force first job to abort synchronously // even if caller allows async abort (asyncAbort) _firstJob->abort(AbortType::Synchronous); + } if (abortType == AbortType::Asynchronous) { struct AbortsFinished { @@ -1082,12 +1178,12 @@ void PropagateRootDirectory::abort(PropagatorJob::AbortType abortType) connect(&_subJobs, &PropagatorCompositeJob::abortFinished, this, [this, abortStatus]() { abortStatus->subJobsFinished = true; if (abortStatus->subJobsFinished && abortStatus->dirDeletionFinished) - emit abortFinished(); + Q_EMIT abortFinished(); }); connect(&_dirDeletionJobs, &PropagatorCompositeJob::abortFinished, this, [this, abortStatus]() { abortStatus->dirDeletionFinished = true; if (abortStatus->subJobsFinished && abortStatus->dirDeletionFinished) - emit abortFinished(); + Q_EMIT abortFinished(); }); } _subJobs.abort(abortType); @@ -1101,40 +1197,67 @@ qint64 PropagateRootDirectory::committedDiskSpace() const bool PropagateRootDirectory::scheduleSelfOrChild() { - if (_state == Finished) + if (state() == Finished) { return false; + } - if (PropagateDirectory::scheduleSelfOrChild()) + if (PropagateDirectory::scheduleSelfOrChild()) { return true; + } // Important: Finish _subJobs before scheduling any deletes. - if (_subJobs._state != Finished) + if (_subJobs.state() != Finished) { return false; + } return _dirDeletionJobs.scheduleSelfOrChild(); } void PropagateRootDirectory::slotSubJobsFinished(SyncFileItem::Status status) { - if (status != SyncFileItem::Success - && status != SyncFileItem::Restoration - && status != SyncFileItem::Conflict) { - if (_state != Finished) { - // Synchronously abort - abort(AbortType::Synchronous); - _state = Finished; - emit finished(status); + switch (status) { + // non critical states + case SyncFileItem::Success: + [[fallthrough]]; + case SyncFileItem::Restoration: + [[fallthrough]]; + case SyncFileItem::Conflict: + break; + // handle all other cases as errors + default: + _status = status; + if (state() != Finished) { + auto subJob = qobject_cast(sender()); + if (OC_ENSURE(subJob)) { + const QMap errorPaths = subJob->errorPaths(); + auto deleteJobs = _dirDeletionJobs.jobsToDo(); + deleteJobs.erase(std::remove_if(deleteJobs.begin(), deleteJobs.end(), + [&](auto *p) { + for (const auto &[path, error] : Utility::asKeyValueRange(errorPaths)) { + const QFileInfo info(path); + if (FileSystem::isChildPathOf(p->path(), info.isDir() ? info.filePath() : info.path())) { + return true; + } + } + return false; + }), + deleteJobs.end()); + _dirDeletionJobs.setJobsToDo(deleteJobs); + } } - return; } - propagator()->scheduleNextJob(); } void PropagateRootDirectory::slotDirDeletionJobsFinished(SyncFileItem::Status status) { - _state = Finished; - emit finished(status); + setState(Finished); + Q_EMIT finished(_status != SyncFileItem::NoStatus ? _status : status); +} + +void PropagateRootDirectory::addDeleteJob(PropagatorJob *job) +{ + _dirDeletionJobs.appendJob(job); } // ================================================================================ @@ -1169,13 +1292,9 @@ void OCC::PropagateUpdateMetaDataJob::start() const QString filePath = propagator()->fullLocalPath(_item->destination()); if (_item->_direction == SyncFileItem::Down) { - // If the 'W' remote permission changed, update the local filesystem SyncJournalFileRecord prev; if (propagator()->_journal->getFileRecord(_item->_file, &prev) - && prev.isValid() - && prev._remotePerm.hasPermission(RemotePermissions::CanWrite) != _item->_remotePerm.hasPermission(RemotePermissions::CanWrite)) { - const bool isReadOnly = !_item->_remotePerm.isNull() && !_item->_remotePerm.hasPermission(RemotePermissions::CanWrite); - FileSystem::setFileReadOnlyWeak(filePath, isReadOnly); + && prev.isValid()) { if (_item->_checksumHeader.isEmpty()) { _item->_checksumHeader = prev._checksumHeader; } diff --git a/src/libsync/owncloudpropagator.h b/src/libsync/owncloudpropagator.h index 25ef60624a1..0df714a837a 100644 --- a/src/libsync/owncloudpropagator.h +++ b/src/libsync/owncloudpropagator.h @@ -15,20 +15,17 @@ #ifndef OWNCLOUDPROPAGATOR_H #define OWNCLOUDPROPAGATOR_H -#include #include +#include + +#include #include -#include #include -#include -#include -#include +#include "account.h" +#include "common/syncjournaldb.h" #include "csync.h" #include "syncfileitem.h" -#include "common/syncjournaldb.h" -#include "bandwidthmanager.h" -#include "accountfwd.h" #include "syncoptions.h" namespace OCC { @@ -63,19 +60,23 @@ class PropagatorJob : public QObject Q_OBJECT public: - explicit PropagatorJob(OwncloudPropagator *propagator); + explicit PropagatorJob(OwncloudPropagator *propagator, const QString &path); enum AbortType { Synchronous, Asynchronous }; + Q_ENUM(AbortType) enum JobState { NotYetStarted, Running, Finished }; - JobState _state; + Q_ENUM(JobState) + + JobState state() const { return _jobState; } + void setState(JobState state); enum JobParallelism { @@ -87,6 +88,7 @@ class PropagatorJob : public QObject are executed. */ WaitForFinished, }; + Q_ENUM(JobParallelism) virtual JobParallelism parallelism() { return FullParallelism; } @@ -102,29 +104,23 @@ class PropagatorJob : public QObject */ virtual qint64 committedDiskSpace() const { return 0; } - /** Set the associated composite job - * - * Used only from PropagatorCompositeJob itself, when a job is added - * and from PropagateDirectory to associate the subJobs with the first - * job. - */ - void setAssociatedComposite(PropagatorCompositeJob *job) { _associatedComposite = job; } + const QString path() { return _path; } -public slots: +public Q_SLOTS: /* - * Asynchronous abort requires emit of abortFinished() signal, - * while synchronous is expected to abort immedietaly. - */ + * Asynchronous abort requires Q_EMIT of abortFinished() signal, + * while synchronous is expected to abort immediately. + */ virtual void abort(PropagatorJob::AbortType abortType) { if (abortType == AbortType::Asynchronous) - emit abortFinished(); + Q_EMIT abortFinished(); } - /** Starts this job, or a new subjob + /** Starts this job, or a new sub job * returns true if a job was started. */ virtual bool scheduleSelfOrChild() = 0; -signals: +Q_SIGNALS: /** * Emitted when the job is fully finished */ @@ -134,18 +130,14 @@ public slots: * Emitted when the abort is fully finished */ void abortFinished(SyncFileItem::Status status = SyncFileItem::NormalError); + protected: OwncloudPropagator *propagator() const; - /** If this job gets added to a composite job, this will point to the parent. - * - * For the PropagateDirectory::_firstJob it will point to - * PropagateDirectory::_subJobs. - * - * That can be useful for jobs that want to spawn follow-up jobs without - * becoming composite jobs themselves. - */ - PropagatorCompositeJob *_associatedComposite = nullptr; +private: + OwncloudPropagator *_propagator; + QString _path; + JobState _jobState; }; /* @@ -162,35 +154,27 @@ class PropagateItemJob : public PropagatorJob public: PropagateItemJob(OwncloudPropagator *propagator, const SyncFileItemPtr &item) - : PropagatorJob(propagator) + : PropagatorJob(propagator, item->destination()) , _item(item) { } ~PropagateItemJob() override; bool scheduleSelfOrChild() override; -public slots: + + const SyncFileItem &item() const { return *_item.data(); } +public Q_SLOTS: virtual void start() = 0; }; /** - * @brief Job that runs subjobs. It becomes finished only when all subjobs are finished. + * @brief Job that runs sub jobs. It becomes finished only when all sub jobs are finished. * @ingroup libsync */ class PropagatorCompositeJob : public PropagatorJob { Q_OBJECT public: - QVector _jobsToDo; - SyncFileItemSet _tasksToDo; - QVector _runningJobs; - SyncFileItem::Status _hasError; // NoStatus, or NormalError / SoftError if there was an error - quint64 _abortsCount; - - explicit PropagatorCompositeJob(OwncloudPropagator *propagator) - : PropagatorJob(propagator) - , _hasError(SyncFileItem::NoStatus), _abortsCount(0) - { - } + explicit PropagatorCompositeJob(OwncloudPropagator *propagator, const QString &path); ~PropagatorCompositeJob() override { @@ -210,14 +194,14 @@ class PropagatorCompositeJob : public PropagatorJob /* * Abort synchronously or asynchronously - some jobs - * require to be finished without immediete abort (abort on job might + * require to be finished without immediate abort (abort on job might * cause conflicts/duplicated files - owncloud/client/issues/5949) */ void abort(PropagatorJob::AbortType abortType) override { if (!_runningJobs.empty()) { _abortsCount = _runningJobs.size(); - for (auto *j : qAsConst(_runningJobs)) { + for (auto *j : std::as_const(_runningJobs)) { if (abortType == AbortType::Asynchronous) { connect(j, &PropagatorJob::abortFinished, this, &PropagatorCompositeJob::slotSubJobAbortFinished); @@ -225,17 +209,22 @@ class PropagatorCompositeJob : public PropagatorJob j->abort(abortType); } } else if (abortType == AbortType::Asynchronous){ - emit abortFinished(); + Q_EMIT abortFinished(); } } qint64 committedDiskSpace() const override; -private slots: + QMap errorPaths() const { return _errorPaths; } + + const QVector &jobsToDo() { return _jobsToDo; } + void setJobsToDo(const QVector &todo) { _jobsToDo = todo; } + +private Q_SLOTS: void slotSubJobAbortFinished(); bool possiblyRunNextJob(PropagatorJob *next) { - if (next->_state == NotYetStarted) { + if (next->state() == NotYetStarted) { connect(next, &PropagatorJob::finished, this, &PropagatorCompositeJob::slotSubJobFinished); } return next->scheduleSelfOrChild(); @@ -243,6 +232,13 @@ private slots: void slotSubJobFinished(SyncFileItem::Status status); void finalize(); + +private: + QVector _jobsToDo; + SyncFileItemSet _tasksToDo; + QVector _runningJobs; + QMap _errorPaths; // NoStatus, or NormalError / SoftError if there was an error + quint64 _abortsCount = 0; }; /** @@ -254,7 +250,7 @@ class OWNCLOUDSYNC_EXPORT PropagateDirectory : public PropagateItemJob Q_OBJECT public: // e.g: create the directory - QScopedPointer _firstJob; + std::unique_ptr _firstJob; PropagatorCompositeJob _subJobs; @@ -300,7 +296,7 @@ class OWNCLOUDSYNC_EXPORT PropagateDirectory : public PropagateItemJob return _item; } -private slots: +private Q_SLOTS: void start() override {}; void slotFirstJobFinished(SyncFileItem::Status status); virtual void slotSubJobsFinished(const SyncFileItem::Status status); @@ -317,8 +313,6 @@ class OWNCLOUDSYNC_EXPORT PropagateRootDirectory : public PropagateDirectory { Q_OBJECT public: - PropagatorCompositeJob _dirDeletionJobs; - explicit PropagateRootDirectory(OwncloudPropagator *propagator); bool scheduleSelfOrChild() override; @@ -327,9 +321,15 @@ class OWNCLOUDSYNC_EXPORT PropagateRootDirectory : public PropagateDirectory qint64 committedDiskSpace() const override; -private slots: + void addDeleteJob(PropagatorJob *job); + +private Q_SLOTS: void slotSubJobsFinished(SyncFileItem::Status status) override; void slotDirDeletionJobsFinished(SyncFileItem::Status status); + +private: + PropagatorCompositeJob _dirDeletionJobs; + SyncFileItem::Status _status = SyncFileItem::NoStatus; }; /** @@ -348,11 +348,11 @@ class PropagateIgnoreJob : public PropagateItemJob { SyncFileItem::Status status = _item->_status; if (status == SyncFileItem::NoStatus) { - if (_item->_instruction == CSYNC_INSTRUCTION_ERROR) { + if (_item->instruction() == CSYNC_INSTRUCTION_ERROR) { status = SyncFileItem::NormalError; } else { status = SyncFileItem::FileIgnored; - OC_ASSERT(_item->_instruction == CSYNC_INSTRUCTION_IGNORE); + OC_ASSERT(_item->instruction() == CSYNC_INSTRUCTION_IGNORE); } } done(status, _item->_errorString); @@ -364,40 +364,34 @@ class OWNCLOUDSYNC_EXPORT OwncloudPropagator : public QObject Q_OBJECT public: SyncJournalDb *const _journal; - bool _finishedEmited; // used to ensure that finished is only emitted once + bool _finishedEmitted; // used to ensure that finished is only emitted once public: - OwncloudPropagator(AccountPtr account, const QString &localDir, - const QString &remoteFolder, SyncJournalDb *progressDb) - : _journal(progressDb) - , _finishedEmited(false) - , _bandwidthManager(this) + OwncloudPropagator(Account *account, const SyncOptions &options, const QUrl &baseUrl, const QString &localDir, const QString &remoteFolder, + SyncJournalDb *progressDb, QObject *parent) + : QObject(parent) + , _journal(progressDb) + , _finishedEmitted(false) , _anotherSyncNeeded(false) - , _chunkSize(10 * 1000 * 1000) // 10 MB, overridden in setSyncOptions , _account(account) + , _syncOptions(options) , _localDir((localDir.endsWith(QLatin1Char('/'))) ? localDir : localDir + QLatin1Char('/')) , _remoteFolder((remoteFolder.endsWith(QLatin1Char('/'))) ? remoteFolder : remoteFolder + QLatin1Char('/')) + , _webDavUrl(baseUrl) { qRegisterMetaType("PropagatorJob::AbortType"); } - ~OwncloudPropagator() override; - void start(SyncFileItemSet &&_syncedItems); const SyncOptions &syncOptions() const; - void setSyncOptions(const SyncOptions &syncOptions); - - int _downloadLimit = 0; - int _uploadLimit = 0; - BandwidthManager _bandwidthManager; bool _abortRequested = false; /** The list of currently active jobs. - This list contains the jobs that are currently using ressources and is used purely to + This list contains the jobs that are currently using resources and is used purely to know how many jobs there is currently running for the scheduler. - Jobs add themself to the list when they do an assynchronous operation. + Jobs add themselves to the list when they do an asynchronous operation. Jobs can be several time on the list (example, when several chunks are uploaded in parallel) */ QList _activeJobList; @@ -407,7 +401,7 @@ class OWNCLOUDSYNC_EXPORT OwncloudPropagator : public QObject /** Per-folder quota guesses. * - * This starts out empty. When an upload in a folder fails due to insufficent + * This starts out empty. When an upload in a folder fails due to insufficient * remote quota, the quota guess is updated to be attempted_size-1 at maximum. * * Note that it will usually just an upper limit for the actual quota - but @@ -421,13 +415,6 @@ class OWNCLOUDSYNC_EXPORT OwncloudPropagator : public QObject /* the maximum number of jobs using bandwidth (uploads or downloads, in parallel) */ int maximumActiveTransferJob(); - /** The size to use for upload chunks. - * - * Will be dynamically adjusted after each chunk upload finishes - * if Capabilities::desiredChunkUploadDuration has a target - * chunk-upload duration set. - */ - qint64 _chunkSize; qint64 smallFileSize(); /* The maximum number of active jobs in parallel */ @@ -470,33 +457,39 @@ class OWNCLOUDSYNC_EXPORT OwncloudPropagator : public QObject void abort(); - AccountPtr account() const; + Account *account() const; + + QUrl webDavUrl() const + { + return _webDavUrl; + } enum DiskSpaceResult { DiskSpaceOk, DiskSpaceFailure, DiskSpaceCritical }; + Q_ENUM(DiskSpaceResult) /** Checks whether there's enough disk space available to complete * all jobs that are currently running. */ DiskSpaceResult diskSpaceCheck() const; + bool moveToTrash() const { return _moveToTrash; } + void setMoveToTrash(bool trashIt) { _moveToTrash = trashIt; } + /** Handles a conflict by renaming the file 'item'. * * Sets up conflict records. * - * It also creates a new upload job in composite if the item that's - * moved away is a file and conflict uploads are requested. - * * Returns true on success, false and error on error. */ - bool createConflict(const SyncFileItemPtr &item, - PropagatorCompositeJob *composite, QString *error); + bool createConflict(const SyncFileItemPtr &item, QString *error); // Map original path (as in the DB) to target final path - QMap _renamedDirectories; + // TODO: no public members... + QHash _renamedDirectories; QString adjustRenamedPath(const QString &original) const; /** Update the database for an item. @@ -509,12 +502,12 @@ class OWNCLOUDSYNC_EXPORT OwncloudPropagator : public QObject Result updateMetadata(const SyncFileItem &item); - /** Update the the placeholder and takes over some metadata from replacesFile + /** Update the placeholder and takes over some metadata from replacesFile * * Will also trigger a Vfs::updateMetadata. */ Result updatePlaceholder(const SyncFileItem &item, const QString &fileName, const QString &replacesFile); -private slots: +private Q_SLOTS: void abortTimeout() { @@ -526,14 +519,14 @@ private slots: /** Emit the finished signal and make sure it is only emitted once */ void emitFinished(SyncFileItem::Status status) { - if (!_finishedEmited) - emit finished(status == SyncFileItem::Success); - _finishedEmited = true; + if (!_finishedEmitted) + Q_EMIT finished(status == SyncFileItem::Success); + _finishedEmitted = true; } void scheduleNextJobImpl(); -signals: +Q_SIGNALS: void newItem(const SyncFileItemPtr &); void itemCompleted(const SyncFileItemPtr &); void progress(const SyncFileItem &, qint64 bytes); @@ -543,24 +536,19 @@ private slots: /** Emitted when propagation has problems with a locked file. */ void seenLockedFile(const QString &fileName, FileSystem::LockMode mode); - /** Emitted when propagation touches a file. - * - * Used to track our own file modifications such that notifications - * from the file watcher about these can be ignored. - */ - void touchedFile(const QString &fileName); - void insufficientLocalStorage(); void insufficientRemoteStorage(); private: - AccountPtr _account; + QPointer _account; QScopedPointer _rootJob; SyncOptions _syncOptions; bool _jobScheduled = false; + bool _moveToTrash = false; const QString _localDir; // absolute path to the local directory. ends with '/' const QString _remoteFolder; // remote folder, ends with '/' + const QUrl _webDavUrl; // full WebDAV URL, might be the same as in the }; /** diff --git a/src/libsync/owncloudpropagator_p.h b/src/libsync/owncloudpropagator_p.h index d1296270020..69c410335a4 100644 --- a/src/libsync/owncloudpropagator_p.h +++ b/src/libsync/owncloudpropagator_p.h @@ -15,26 +15,19 @@ #pragma once -#include "owncloudpropagator.h" #include "syncfileitem.h" -#include "networkjobs.h" #include #include namespace OCC { -inline QByteArray getEtagFromReply(QNetworkReply *reply) +inline QString getEtagFromReply(QNetworkReply *reply) { - QByteArray ocEtag = parseEtag(reply->rawHeader("OC-ETag")); - QByteArray etag = parseEtag(reply->rawHeader("ETag")); - QByteArray ret = ocEtag; - if (ret.isEmpty()) { - ret = etag; + QByteArray rawEtag = reply->rawHeader("OC-ETag"); + if (rawEtag.isEmpty()) { + rawEtag = reply->rawHeader("ETag"); } - if (ocEtag.length() > 0 && ocEtag != etag) { - qCDebug(lcPropagator) << "Quite peculiar, we have an etag != OC-Etag [no problem!]" << etag << ocEtag; - } - return ret; + return Utility::normalizeEtag(QString::fromUtf8(rawEtag)); } /** @@ -60,7 +53,15 @@ inline SyncFileItem::Status classifyError(QNetworkReply::NetworkError nerror, case 423: // "Locked" // Should be temporary. - Q_FALLTHROUGH(); + if (anotherSyncNeeded != nullptr) { + *anotherSyncNeeded = true; + } + return SyncFileItem::Message; + case 425: + // "Too Early" + // The file is currently post processed after an upload + // once the post processing finished we get a new etag and retry + return SyncFileItem::Message; case 502: // "Bad Gateway" // Should be temporary. diff --git a/src/libsync/owncloudtheme.cpp b/src/libsync/owncloudtheme.cpp index 9e7f297516f..b71f44a43fe 100644 --- a/src/libsync/owncloudtheme.cpp +++ b/src/libsync/owncloudtheme.cpp @@ -14,17 +14,10 @@ #include "owncloudtheme.h" +#include +#include #include #include -#ifndef TOKEN_AUTH_ONLY -#include -#include -#endif -#include - -#include "common/utility.h" -#include "common/version.h" -#include "config.h" namespace OCC { @@ -33,7 +26,6 @@ ownCloudTheme::ownCloudTheme() { } -#ifndef TOKEN_AUTH_ONLY QColor ownCloudTheme::wizardHeaderBackgroundColor() const { return QColor(4, 30, 66); @@ -44,20 +36,24 @@ QColor ownCloudTheme::wizardHeaderTitleColor() const return Qt::white; } -QColor ownCloudTheme::wizardHeaderSubTitleColor() const +QIcon ownCloudTheme::wizardHeaderLogo() const { - return QColor(78, 133, 200); + return Resources::themeUniversalIcon(QStringLiteral("wizard_logo")); } -QIcon ownCloudTheme::wizardHeaderLogo() const +QIcon ownCloudTheme::aboutIcon() const { - return themeUniversalIcon(QStringLiteral("wizard_logo")); + return Resources::themeUniversalIcon(QStringLiteral("oc-image-about")); } -QIcon ownCloudTheme::aboutIcon() const +bool ownCloudTheme::moveToTrashDefaultValue() const { - return themeUniversalIcon(QStringLiteral("oc-image-about")); + // for the vanilla ownCloud client move-to-trash option is enabled by default + return true; } -#endif +bool ownCloudTheme::allowSystemConfigOverrides() const +{ + return true; +} } diff --git a/src/libsync/owncloudtheme.h b/src/libsync/owncloudtheme.h index cfbf9f54d55..801e5c9afbe 100644 --- a/src/libsync/owncloudtheme.h +++ b/src/libsync/owncloudtheme.h @@ -12,8 +12,7 @@ * for more details. */ -#ifndef OWNCLOUD_THEME_H -#define OWNCLOUD_THEME_H +#pragma once #include "theme.h" @@ -28,19 +27,11 @@ class ownCloudTheme : public Theme Q_OBJECT public: ownCloudTheme(); -#ifndef TOKEN_AUTH_ONLY QColor wizardHeaderBackgroundColor() const override; QColor wizardHeaderTitleColor() const override; - QColor wizardHeaderSubTitleColor() const override; QIcon wizardHeaderLogo() const override; QIcon aboutIcon() const override; -#endif - - // For owncloud-brandings *do* show the virtual files option. - bool showVirtualFilesOption() const override { return true; } - bool enableExperimentalFeatures() const override { return true; }; - -private: + bool moveToTrashDefaultValue() const override; + bool allowSystemConfigOverrides() const override; }; } -#endif // OWNCLOUD_MIRALL_THEME_H diff --git a/src/libsync/platform.cpp b/src/libsync/platform.cpp new file mode 100644 index 00000000000..0d4635469e0 --- /dev/null +++ b/src/libsync/platform.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) by Erik Verbruggen + * + * 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 "platform.h" + +#include "platform_unix.h" +#if defined(Q_OS_WIN) +#include "platform_win.h" +#endif + +#if defined(Q_OS_MAC) +#include "platform_mac.h" +#endif + +#include "configfile.h" + +#ifdef CRASHREPORTER_EXECUTABLE +#include + +#include + +#endif + +namespace OCC { + +void Platform::migrate() +{ +} + +void Platform::setApplication(QCoreApplication *application) +{ +#ifdef CRASHREPORTER_EXECUTABLE + if (ConfigFile().crashReporter()) { + auto *crashHandler = + new CrashReporter::Handler(QDir::tempPath(), true, QStringLiteral("%1/" CRASHREPORTER_EXECUTABLE).arg(application->applicationDirPath())); + connect(application, &QCoreApplication::aboutToQuit, this, [crashHandler] { delete crashHandler; }); + } +#else + Q_UNUSED(application) +#endif +} + +void Platform::startServices() { } + +std::unique_ptr Platform::create() +{ + // we need to make sure the platform class is initialized before a Q(Core)Application has been set up + // the constructors run some initialization code that affects Qt's initialization + Q_ASSERT(QCoreApplication::instance() == nullptr); + +#if defined(Q_OS_WIN) + return std::make_unique(); +#elif defined(Q_OS_LINUX) + return std::make_unique(); +#elif defined(Q_OS_MAC) + return std::make_unique(); +#else + Q_UNREACHABLE(); +#endif +} + +} // OCC namespace diff --git a/src/libsync/platform.h b/src/libsync/platform.h new file mode 100644 index 00000000000..64bd464a984 --- /dev/null +++ b/src/libsync/platform.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) by Erik Verbruggen + * + * 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. + */ + +#ifndef PLATFORM_H +#define PLATFORM_H + +#include +#include + +#include + +#include "owncloudlib.h" + +namespace OCC { + +/** + * @brief The Platform is the baseclass for all platform classes, which in turn implement platform + * specific functionality for the GUI. + */ +class OWNCLOUDSYNC_EXPORT Platform : public QObject +{ + Q_OBJECT +public: + using QObject::QObject; + + static std::unique_ptr create(); + + virtual void migrate(); + + virtual void setApplication(QCoreApplication *application); + + virtual void startServices(); + +Q_SIGNALS: + void requestAttention(); +}; + +} // OCC namespace + +#endif // PLATFORM_H diff --git a/src/libsync/platform_mac.h b/src/libsync/platform_mac.h new file mode 100644 index 00000000000..007297cc45c --- /dev/null +++ b/src/libsync/platform_mac.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) by Fabian Müller + * + * 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 "platform.h" + +#include +#include + +namespace OCC { + +class MacPlatformPrivate; + +class MacPlatform : public Platform +{ +public: + MacPlatform(); + ~MacPlatform() override; + + void migrate() override; + + void startServices() override; + +private: + Q_DECLARE_PRIVATE(MacPlatform) + QScopedPointer d_ptr; +}; + +} // namespace OCC diff --git a/src/libsync/platform_mac.mm b/src/libsync/platform_mac.mm new file mode 100644 index 00000000000..57a6eb7e2f1 --- /dev/null +++ b/src/libsync/platform_mac.mm @@ -0,0 +1,213 @@ +/* + * Copyright (C) by Daniel Molkentin + * Copyright (C) by Erik Verbruggen + * + * 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 "platform_mac.h" + +#include +#include + +#import + +#include +#include + + +// defined in platform_mac_deprecated.mm +namespace OCC { + +void migrateLaunchOnStartup(); + +Q_LOGGING_CATEGORY(lcPlatform, "sync.platform.macos") +} // OCC namespace + +@interface OwnAppDelegate : NSObject +- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag; +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app; + +@property (readwrite) OCC::MacPlatform *platform; +@end + +@implementation OwnAppDelegate { +} + +- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag +{ + Q_EMIT _platform->requestAttention(); + return YES; +} + +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app +{ + Q_UNUSED(app) + + // We do not use `NSCoder` classes (nor does Qt), nor override the `initWithCoder` method, so we are fine with SecureCoding. + return YES; +} + +@end + +namespace { + +// Inspired by https://ladydebug.com/blog/2020/05/21/programmatically-capture-energy-saver-event-on-mac/ +class PowerNotificationsListener +{ +public: + void registerForNotifications() + { + rootPowerDomain = IORegisterForSystemPower(this, ¬ifyPortRef, sleepWakeupCallBack, ¬ifierObj); + if (rootPowerDomain == IO_OBJECT_NULL) { + qCWarning(OCC::lcPlatform) << "Failed to register for system power notifications!"; + return; + } + + qCDebug(OCC::lcPlatform) << "IORegisterForSystemPower OK! Root port:" << rootPowerDomain; + + // add the notification port to the application runloop + CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes); + } + +private: + static void sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument) + { + Q_UNUSED(service) + + auto listener = static_cast(refParam); + + switch (messageType) { + case kIOMessageCanSystemSleep: + /* Idle sleep is about to kick in. This message will not be sent for forced sleep. + * Applications have a chance to prevent sleep by calling IOCancelPowerChange. + * Most applications should not prevent idle sleep. Power Management waits up to + * 30 seconds for you to either allow or deny idle sleep. If you don’t acknowledge + * this power change by calling either IOAllowPowerChange or IOCancelPowerChange, + * the system will wait 30 seconds then go to sleep. + */ + + qCInfo(OCC::lcPlatform) << "System power message: can system sleep?"; + + // Uncomment to cancel idle sleep + // IOCancelPowerChange(thiz->rootPowerDomain, reinterpret_cast(messageArgument)); + + // Allow idle sleep + IOAllowPowerChange(listener->rootPowerDomain, reinterpret_cast(messageArgument)); + break; + + case kIOMessageSystemWillNotSleep: + /* Announces that the system has retracted a previous attempt to sleep; it + * follows `kIOMessageCanSystemSleep`. + */ + qCInfo(OCC::lcPlatform) << "System power message: system will NOT sleep."; + break; + + case kIOMessageSystemWillSleep: + /* The system WILL go to sleep. If you do not call IOAllowPowerChange or + * IOCancelPowerChange to acknowledge this message, sleep will be delayed by + * 30 seconds. + * + * NOTE: If you call IOCancelPowerChange to deny sleep it returns kIOReturnSuccess, + * however the system WILL still go to sleep. + */ + + qCInfo(OCC::lcPlatform) << "System power message: system WILL sleep."; + + IOAllowPowerChange(listener->rootPowerDomain, reinterpret_cast(messageArgument)); + break; + + case kIOMessageSystemWillPowerOn: + /* Announces that the system is beginning to power the device tree; most devices + * are still unavailable at this point. + */ + /* From the documentation: + * + * - kIOMessageSystemWillPowerOn is delivered at early wakeup time, before most hardware + * has been powered on. Be aware that any attempts to access disk, network, the display, + * etc. may result in errors or blocking your process until those resources become + * available. + * + * So we do NOT log this event. + */ + break; + + case kIOMessageSystemHasPoweredOn: + /* Announces that the system and its devices have woken up. */ + qCInfo(OCC::lcPlatform) << "System power message: system has powered on."; + break; + + default: + qCInfo(OCC::lcPlatform) << "System power message: other event: " << messageType; + /* Not a system sleep and wake notification. */ + break; + } + } + +private: + IONotificationPortRef notifyPortRef = nullptr; // notification port allocated by IORegisterForSystemPower + io_object_t notifierObj = IO_OBJECT_NULL; // notifier object, used to deregister later + io_connect_t rootPowerDomain = IO_OBJECT_NULL; // a reference to the Root Power Domain IOService +}; + +} // anonynous namespace + +namespace OCC { + +/* + * We need to instantiate an autorelease pool here. Normally a + * QApplication would take care of this, but some cocoa/carbon code is + * run before the QApplication is instantiated, and cocoa/carbon need + * such a pool. + */ +class MacPlatformPrivate +{ +public: + ~MacPlatformPrivate() { [autoReleasePool release]; } + NSAutoreleasePool *autoReleasePool = [[NSAutoreleasePool alloc] init]; + OwnAppDelegate *appDelegate; + PowerNotificationsListener listener; +}; + +MacPlatform::MacPlatform() + : d_ptr(new MacPlatformPrivate) +{ + Q_D(MacPlatform); + + NSApplicationLoad(); + d->appDelegate = [[OwnAppDelegate alloc] init]; + d->appDelegate.platform = this; + [[NSApplication sharedApplication] setDelegate:d->appDelegate]; + + signal(SIGPIPE, SIG_IGN); +} + +MacPlatform::~MacPlatform() +{ + Q_D(MacPlatform); + [d->appDelegate release]; +} + +void MacPlatform::migrate() +{ + Platform::migrate(); + + migrateLaunchOnStartup(); +} + +void MacPlatform::startServices() +{ + Q_D(MacPlatform); + + d->listener.registerForNotifications(); +} + +} // namespace OCC diff --git a/src/libsync/platform_mac_deprecated.mm b/src/libsync/platform_mac_deprecated.mm new file mode 100644 index 00000000000..7b1d4d8480a --- /dev/null +++ b/src/libsync/platform_mac_deprecated.mm @@ -0,0 +1,157 @@ +/* + * Copyright (C) by Erik Verbruggen + * + * 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 "common/utility.h" + +#include +#include + +#include +#include + +// This file contains migration code, that uses deprecated API. In order to suppress these "known" +// warnings, we suppress them. If in a future version of macOS the API is actually removed, we can +// drop this whole file, because we cannot migrate. +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +namespace OCC { + +namespace Deprecated { + + bool hasLaunchOnStartup() + { + // this is quite some duplicate code with setLaunchOnStartup, at some point we should fix this FIXME. + bool returnValue = false; + QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath(); + CFStringRef folderCFStr = CFStringCreateWithCString(nullptr, filePath.toUtf8().data(), kCFStringEncodingUTF8); + CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr, folderCFStr, kCFURLPOSIXPathStyle, true); + if (LSSharedFileListRef loginItems = LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems, nullptr)) { + // We need to iterate over the items and check which one is "ours". + UInt32 seedValue; + CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue); + CFStringRef appUrlRefString = CFURLGetString(urlRef); // no need for release + for (int i = 0, ei = CFArrayGetCount(itemsArray); i < ei; i++) { + LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i); + CFURLRef itemUrlRef = nullptr; + + if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, nullptr) == noErr && itemUrlRef) { + CFStringRef itemUrlString = CFURLGetString(itemUrlRef); + if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) { + returnValue = true; + } + CFRelease(itemUrlRef); + } + } + CFRelease(itemsArray); + CFRelease(loginItems); + } + CFRelease(urlRef); + CFRelease(folderCFStr); + return returnValue; + } + + void setLaunchOnStartup(bool enable) + { + QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath(); + CFStringRef folderCFStr = CFStringCreateWithCString(nullptr, filePath.toUtf8().data(), kCFStringEncodingUTF8); + CFURLRef urlRef = CFURLCreateWithFileSystemPath(nullptr, folderCFStr, kCFURLPOSIXPathStyle, true); + if (LSSharedFileListRef loginItems = LSSharedFileListCreate(nullptr, kLSSharedFileListSessionLoginItems, nullptr)) { + if (enable) { + // Insert an item to the list. + LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItems, + kLSSharedFileListItemLast, nullptr, nullptr, + urlRef, nullptr, nullptr); + if (item) { + CFRelease(item); + } else { + qCWarning(lcUtility) << "Failed to insert ourself into launch on startup list"; + } + } else { + // We need to iterate over the items and check which one is "ours". + UInt32 seedValue; + CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue); + CFStringRef appUrlRefString = CFURLGetString(urlRef); + for (int i = 0, ei = CFArrayGetCount(itemsArray); i < ei; i++) { + // The line below has idiomatic Objective-C, but modern C++ doesn't like these casts. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wcast-qual" + LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i); +#pragma clang diagnostic pop + + CFURLRef itemUrlRef = nullptr; + + if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, nullptr) == noErr && itemUrlRef) { + CFStringRef itemUrlString = CFURLGetString(itemUrlRef); + if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) { + LSSharedFileListItemRemove(loginItems, item); // remove it! + } + CFRelease(itemUrlRef); + } + } + CFRelease(itemsArray); + } + CFRelease(loginItems); + } else { + qCWarning(lcUtility) << "Failed to retrieve loginItems"; + } + + CFRelease(folderCFStr); + CFRelease(urlRef); + } + +} // Deprecated namespace + +/// Migrate from deprecated API of "Login Items" to launchd plist files (if needed). +void migrateLaunchOnStartup() +{ + Q_ASSERT(QCoreApplication::instance() != nullptr); + + bool hasDeprecatedLaunchOnStartup = Deprecated::hasLaunchOnStartup(); + + // Three possibilities here for `hasDeprecatedLaunchOnStartup`: + // - true: (deprecated) launch on startup + // - false 1: no launch on startup, but not migrated yet + // - false 2: migrated, and the deprecated launch on startup has been removed + // + // So, now check if the LaunchAgents plist file is there. + + bool hasLaunchAgentsPlist = QFile::exists(QStringLiteral("%1/Library/LaunchAgents/%2.plist").arg(QDir::homePath(), QCoreApplication::organizationDomain())); + + // Simple case first: + if (hasLaunchAgentsPlist && !hasDeprecatedLaunchOnStartup) { + // Migration has already happened, so nothing to do. + return; + } + + // Weird case: yes there is a plist file, but hasDeprecatedLaunchOnStartup is true. In that + // case we'll assume migration had a problem, or whatever. So just treat this as a not (fully) + // migrated case. + + // Ok, so now we need to migrate. + + // First write the new plist file. + Utility::setLaunchOnStartup({}, {}, hasDeprecatedLaunchOnStartup); + + // Then remove the deprecated entry. + Deprecated::setLaunchOnStartup(false); + + // And we're done. +} + +} // OCC namespace diff --git a/src/gui/platform_unix.cpp b/src/libsync/platform_unix.cpp similarity index 76% rename from src/gui/platform_unix.cpp rename to src/libsync/platform_unix.cpp index e0e796fe36a..2ce07a59c10 100644 --- a/src/gui/platform_unix.cpp +++ b/src/libsync/platform_unix.cpp @@ -12,33 +12,24 @@ * for more details. */ +#include "platform_unix.h" + +#include +#include #include #include #include -#include - -#include "guiutility.h" -#include "platform.h" namespace OCC { Q_LOGGING_CATEGORY(lcPlatform, "gui.platform") -class UnixPlatform : public Platform +UnixPlatform::UnixPlatform() { -public: - UnixPlatform() - { - signal(SIGPIPE, SIG_IGN); - setLimitsForCoreDumps(); - } - - ~UnixPlatform() override; - -private: - void setLimitsForCoreDumps(); -}; + signal(SIGPIPE, SIG_IGN); + setLimitsForCoreDumps(); +} UnixPlatform::~UnixPlatform() { @@ -60,9 +51,5 @@ void UnixPlatform::setLimitsForCoreDumps() } } -std::unique_ptr Platform::create() -{ - return std::make_unique(); -} } // namespace OCC diff --git a/src/libsync/platform_unix.h b/src/libsync/platform_unix.h new file mode 100644 index 00000000000..7be7f6afa03 --- /dev/null +++ b/src/libsync/platform_unix.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) by Fabian Müller + * + * 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 "platform.h" + +#include + +namespace OCC { + +class UnixPlatform : public Platform +{ +public: + UnixPlatform(); + + ~UnixPlatform() override; + +private: + void setLimitsForCoreDumps(); +}; + +} // namespace OCC diff --git a/src/libsync/platform_win.cpp b/src/libsync/platform_win.cpp new file mode 100644 index 00000000000..02314fd5332 --- /dev/null +++ b/src/libsync/platform_win.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) by Erik Verbruggen + * + * 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 "platform_win.h" + +#ifdef Q_OS_WIN +#include "common/utility_win.h" +#endif + +#include +#include +#include +#include + + +#include +#include + +#include "theme.h" + +#include "common/asserts.h" + +using namespace std::chrono; +using namespace std::chrono_literals; + +namespace { + +struct +{ + HANDLE windowMessageWatcherEvent = CreateEventW(nullptr, true, false, nullptr); + bool windowMessageWatcherRun = true; + std::thread *watcherThread = nullptr; +} watchWMCtx; + +} // anonymous namespace + +namespace OCC { + +Q_LOGGING_CATEGORY(lcPlatform, "sync.platform.windows") + +WinPlatform::WinPlatform() +{ + QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +} + +WinPlatform::~WinPlatform() +{ +} + +void WinPlatform::setApplication(QCoreApplication *application) +{ + Platform::setApplication(application); + // Ensure OpenSSL config file is only loaded from app directory + const QString opensslConf = QCoreApplication::applicationDirPath() + QStringLiteral("/openssl.cnf"); + qputenv("OPENSSL_CONF", opensslConf.toLocal8Bit()); + + if (auto guiApp = qobject_cast(application)) { + // let qt pick which style for windows instead of hard coding it. + // can't set quick style to anything other than fusion or it will crash on start + // qml is going away so no, I am not going to take the time to find out how to load the other default windows + // styles for qml. Frankly I'm pretty shocked it doesn't just work naturally the way normal style does. + // also note: if we don't set the style to fusion, the qml is pretty ratty looking, eg dark mode is ignored. + // + QQuickStyle::setStyle(QStringLiteral("Fusion")); + } +} + +void OCC::WinPlatform::startServices() +{ + startShutdownWatcher(); +} + +void WinPlatform::startShutdownWatcher() +{ + if (watchWMCtx.watcherThread) { + return; + } + // Qt only receives window message if a window was displayed at least once + // create an invisible window to handle WM_ENDSESSION + // We also block a system shutdown until we are properly shutdown our selfs + // In the unlikely case that we require more than 5s Windows will require a fullscreen message + // with our icon, title and the reason why we are blocking the shutdown. + + // ensure to initialise the icon in the main thread + HICON icon = {}; + if (qobject_cast(qApp)) { + icon = Theme::instance()->applicationIcon().pixmap(64, 64).toImage().toHICON(); + } + watchWMCtx.watcherThread = new std::thread([icon] { + WNDCLASS wc = {}; + wc.hInstance = GetModuleHandle(nullptr); + wc.lpszClassName = L"ocWindowMessageWatcher"; + wc.hIcon = icon; + wc.lpfnWndProc = [](HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -> LRESULT { + // qDebug() << MSG { hwnd, msg, wParam, lParam, 0, {} }; + if (msg == WM_QUERYENDSESSION) { + qCDebug(lcPlatform) << "Received WM_QUERYENDSESSION"; + return 1; + } else if (msg == WM_ENDSESSION) { + qCDebug(lcPlatform) << "Received WM_ENDSESSION quitting"; + QMetaObject::invokeMethod(qApp, &QApplication::quit); + auto start = steady_clock::now(); + if (lParam == ENDSESSION_LOGOFF) { + // block the windows shutdown until we are done + const QString description = QApplication::translate("Utility", "Shutting down %1").arg(Theme::instance()->appNameGUI()); + qCDebug(lcPlatform) << "Block shutdown until we are ready" << description; + OC_ASSERT(ShutdownBlockReasonCreate(hwnd, reinterpret_cast(description.utf16()))); + } + WaitForSingleObject(watchWMCtx.windowMessageWatcherEvent, INFINITE); + if (lParam == ENDSESSION_LOGOFF) { + OC_ASSERT(ShutdownBlockReasonDestroy(hwnd)); + } + qCInfo(lcPlatform) << "WM_ENDSESSION successfully shut down" << (steady_clock::now() - start); + watchWMCtx.windowMessageWatcherRun = false; + return 0; + } + return DefWindowProc(hwnd, msg, wParam, lParam); + }; + OC_ASSERT(RegisterClass(&wc)); + + auto watcherWindow = CreateWindowW(wc.lpszClassName, reinterpret_cast(Theme::instance()->appNameGUI().utf16()), WS_OVERLAPPED, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, wc.hInstance, nullptr); + OC_ASSERT_X(watcherWindow, Utility::formatWinError(GetLastError()).toUtf8().constData()); + + MSG msg; + while (watchWMCtx.windowMessageWatcherRun) { + if (!PeekMessageW(&msg, watcherWindow, 0, 0, PM_REMOVE)) { + // We are in a separate thread, and polling for events. When no events are queued, wait a bit before peeking in the queue again. + std::this_thread::sleep_for(100ms); + } else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + }); + + qAddPostRoutine([] { + qCDebug(OCC::lcUtility) << "app closed"; + SetEvent(watchWMCtx.windowMessageWatcherEvent); + }); +} + +} // namespace OCC diff --git a/src/libsync/platform_win.h b/src/libsync/platform_win.h new file mode 100644 index 00000000000..9882b12fc3a --- /dev/null +++ b/src/libsync/platform_win.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) by Fabian Müller + * + * 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 "platform.h" + +namespace OCC { + +class WinPlatform : public Platform +{ +public: + WinPlatform(); + + ~WinPlatform() override; + + void setApplication(QCoreApplication *application) override; + + void startServices() override; + +private: + /// Utility thread that takes care of proper Windows logout handling. + void startShutdownWatcher(); +}; + +} // namespace OCC diff --git a/src/libsync/progressdispatcher.cpp b/src/libsync/progressdispatcher.cpp index c08938966d9..bce7f902a22 100644 --- a/src/libsync/progressdispatcher.cpp +++ b/src/libsync/progressdispatcher.cpp @@ -14,9 +14,9 @@ #include "progressdispatcher.h" -#include -#include #include +#include +#include namespace OCC { @@ -24,7 +24,7 @@ ProgressDispatcher *ProgressDispatcher::_instance = nullptr; QString Progress::asResultString(const SyncFileItem &item) { - switch (item._instruction) { + switch (item.instruction()) { case CSYNC_INSTRUCTION_SYNC: case CSYNC_INSTRUCTION_NEW: case CSYNC_INSTRUCTION_TYPE_CHANGE: @@ -40,22 +40,21 @@ QString Progress::asResultString(const SyncFileItem &item) return QCoreApplication::translate("progress", "Uploaded"); } case CSYNC_INSTRUCTION_CONFLICT: - return QCoreApplication::translate("progress", "Server version downloaded, copied changed local file into conflict file"); + return QCoreApplication::translate("progress", "Server version downloaded, local copy was backed up as conflict file"); case CSYNC_INSTRUCTION_REMOVE: return QCoreApplication::translate("progress", "Deleted"); - case CSYNC_INSTRUCTION_EVAL_RENAME: case CSYNC_INSTRUCTION_RENAME: - return QCoreApplication::translate("progress", "Moved to %1").arg(item._renameTarget); + if (QFileInfo(item._file).path() == QFileInfo(item._renameTarget).path()) + return QCoreApplication::translate("progress", "Renamed %1 to %2").arg(item._file, item._renameTarget); + else + return QCoreApplication::translate("progress", "Moved %1 to %2").arg(item._file, item._renameTarget); case CSYNC_INSTRUCTION_IGNORE: return QCoreApplication::translate("progress", "Ignored"); - case CSYNC_INSTRUCTION_STAT_ERROR: - return QCoreApplication::translate("progress", "Filesystem access error"); case CSYNC_INSTRUCTION_ERROR: return QCoreApplication::translate("progress", "Error"); case CSYNC_INSTRUCTION_UPDATE_METADATA: return QCoreApplication::translate("progress", "Updated local metadata"); case CSYNC_INSTRUCTION_NONE: - case CSYNC_INSTRUCTION_EVAL: return QCoreApplication::translate("progress", "Unknown"); } return QCoreApplication::translate("progress", "Unknown"); @@ -63,7 +62,7 @@ QString Progress::asResultString(const SyncFileItem &item) QString Progress::asActionString(const SyncFileItem &item) { - switch (item._instruction) { + switch (item.instruction()) { case CSYNC_INSTRUCTION_CONFLICT: case CSYNC_INSTRUCTION_SYNC: case CSYNC_INSTRUCTION_NEW: @@ -74,19 +73,15 @@ QString Progress::asActionString(const SyncFileItem &item) return QCoreApplication::translate("progress", "uploading"); case CSYNC_INSTRUCTION_REMOVE: return QCoreApplication::translate("progress", "deleting"); - case CSYNC_INSTRUCTION_EVAL_RENAME: case CSYNC_INSTRUCTION_RENAME: return QCoreApplication::translate("progress", "moving"); case CSYNC_INSTRUCTION_IGNORE: return QCoreApplication::translate("progress", "ignoring"); - case CSYNC_INSTRUCTION_STAT_ERROR: - return QCoreApplication::translate("progress", "error"); case CSYNC_INSTRUCTION_ERROR: return QCoreApplication::translate("progress", "error"); case CSYNC_INSTRUCTION_UPDATE_METADATA: return QCoreApplication::translate("progress", "updating local metadata"); case CSYNC_INSTRUCTION_NONE: - case CSYNC_INSTRUCTION_EVAL: break; } return QString(); @@ -130,7 +125,7 @@ ProgressInfo::ProgressInfo() void ProgressInfo::reset() { - _status = Starting; + _status = None; _currentItems.clear(); _currentDiscoveredRemoteFolder.clear(); @@ -165,13 +160,10 @@ bool ProgressInfo::isUpdatingEstimates() const static bool shouldCountProgress(const SyncFileItem &item) { - const auto instruction = item._instruction; + const auto instruction = item.instruction(); // Skip any ignored, error or non-propagated files and directories. - if (instruction == CSYNC_INSTRUCTION_NONE - || instruction == CSYNC_INSTRUCTION_UPDATE_METADATA - || instruction == CSYNC_INSTRUCTION_IGNORE - || instruction == CSYNC_INSTRUCTION_ERROR) { + if (instruction & (CSYNC_INSTRUCTION_NONE | CSYNC_INSTRUCTION_UPDATE_METADATA | CSYNC_INSTRUCTION_IGNORE | CSYNC_INSTRUCTION_ERROR)) { return false; } @@ -364,7 +356,7 @@ void ProgressInfo::updateEstimates() void ProgressInfo::recomputeCompletedSize() { qint64 r = _totalSizeOfCompletedJobs; - for (const auto &i : qAsConst(_currentItems)) { + for (const auto &i : std::as_const(_currentItems)) { if (isSizeDependent(i._item)) r += i._progress._completed; } diff --git a/src/libsync/progressdispatcher.h b/src/libsync/progressdispatcher.h index fdcc16883fa..eebc6a0a1be 100644 --- a/src/libsync/progressdispatcher.h +++ b/src/libsync/progressdispatcher.h @@ -25,8 +25,6 @@ #include "syncfileitem.h" -#include "csync/csync_exclude.h" - namespace OCC { class Folder; /** @@ -46,8 +44,7 @@ class OWNCLOUDSYNC_EXPORT ProgressInfo : public QObject /** Records the status of the sync run */ enum Status { - /// Emitted once at start - Starting, + None, /** * Emitted once without _currentDiscoveredFolder when it starts, @@ -69,6 +66,7 @@ class OWNCLOUDSYNC_EXPORT ProgressInfo : public QObject */ Done }; + Q_ENUM(Status); Status status() const; @@ -120,12 +118,8 @@ class OWNCLOUDSYNC_EXPORT ProgressInfo : public QObject static inline bool isSizeDependent(const SyncFileItem &item) { return !item.isDirectory() - && (item._instruction == CSYNC_INSTRUCTION_CONFLICT - || item._instruction == CSYNC_INSTRUCTION_SYNC - || item._instruction == CSYNC_INSTRUCTION_NEW - || item._instruction == CSYNC_INSTRUCTION_TYPE_CHANGE) - && !(item._type == ItemTypeVirtualFile - || item._type == ItemTypeVirtualFileDehydration); + && (item.instruction() & (CSYNC_INSTRUCTION_CONFLICT | CSYNC_INSTRUCTION_SYNC | CSYNC_INSTRUCTION_NEW | CSYNC_INSTRUCTION_TYPE_CHANGE)) + && !(item._type == ItemTypeVirtualFile || item._type == ItemTypeVirtualFileDehydration); } /** @@ -233,7 +227,7 @@ class OWNCLOUDSYNC_EXPORT ProgressInfo : public QObject */ Estimates fileProgress(const SyncFileItem &item) const; -private slots: +private Q_SLOTS: /** * Called every second once started, this function updates the * estimates. @@ -268,15 +262,23 @@ namespace Progress { OWNCLOUDSYNC_EXPORT bool isIgnoredKind(SyncFileItem::Status); } -/** Type of error - * - * Used for ProgressDispatcher::syncError. May trigger error interactivity - * in IssuesWidget. - */ -enum class ErrorCategory { - Normal, - InsufficientRemoteStorage, -}; +// work around for only having one namespace OCC, and this enum not beeing in a QObject +namespace ErrorCategoryPrivate { + OWNCLOUDSYNC_EXPORT Q_NAMESPACE; + + /** Type of error + * + * Used for ProgressDispatcher::syncError. May trigger error interactivity + * in SyncErrorWidget. + */ + enum class ErrorCategory { + Normal, + InsufficientRemoteStorage, + }; + + Q_ENUM_NS(ErrorCategory); +} +using ErrorCategoryPrivate::ErrorCategory; /** * @file progressdispatcher.h @@ -294,7 +296,7 @@ class OWNCLOUDSYNC_EXPORT ProgressDispatcher : public QObject static ProgressDispatcher *instance(); ~ProgressDispatcher() override; -signals: +Q_SIGNALS: /** @brief Signals the progress of data transmission. @@ -302,24 +304,24 @@ class OWNCLOUDSYNC_EXPORT ProgressDispatcher : public QObject @param[out] progress A struct with all progress info. */ - void progressInfo(const QString &folder, const ProgressInfo &progress); + void progressInfo(Folder *folder, const ProgressInfo &progress); /** * @brief: the item was completed by a job */ - void itemCompleted(const QString &folder, const SyncFileItemPtr &item); + void itemCompleted(Folder *folder, const SyncFileItemPtr &item); /** * @brief A new folder-wide sync error was seen. */ - void syncError(const QString &folder, const QString &message, ErrorCategory category); + void syncError(Folder *folder, const QString &message, ErrorCategory category); - void excluded(Folder *folder, const QString &path, CSYNC_EXCLUDE_TYPE reason); + void excluded(Folder *folder, const QString &path); /** * @brief Emitted for a folder when a sync is done, listing all pending conflicts */ - void folderConflicts(const QString &folder, const QStringList &conflictPaths); + void folderConflicts(Folder *folder, const QStringList &conflictPaths); private: ProgressDispatcher(QObject *parent = nullptr); diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index 661b140c1ca..b126186c3a9 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -12,24 +12,25 @@ * for more details. */ -#include "config.h" -#include "owncloudpropagator_p.h" #include "propagatedownload.h" -#include "networkjobs.h" #include "account.h" -#include "common/syncjournaldb.h" -#include "common/syncjournalfilerecord.h" -#include "common/utility.h" #include "filesystem.h" +#include "networkjobs.h" +#include "owncloudpropagator_p.h" #include "propagatorjobs.h" -#include "common/checksums.h" + #include "common/asserts.h" +#include "common/checksums.h" +#include "common/syncjournaldb.h" +#include "common/syncjournalfilerecord.h" +#include "common/utility.h" #include "common/vfs.h" +#include "libsync/theme.h" + #include #include #include -#include #include #include @@ -45,6 +46,19 @@ namespace OCC { Q_LOGGING_CATEGORY(lcGetJob, "sync.networkjob.get", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropagateDownload, "sync.propagator.download", QtInfoMsg) +namespace { + void preserveGroupOwnership(const QString &fileName, const QFileInfo &fi) + { +#ifdef Q_OS_UNIX + if (chown(fileName.toLocal8Bit().constData(), -1, fi.groupId()) != 0) { + qCWarning(lcPropagateDownload) << "Unable to chown" << fileName << "to previous group owner" << strerror(errno); + } +#else + Q_UNUSED(fileName); + Q_UNUSED(fi); +#endif + } +} // Always coming in with forward slashes. // In csync_excluded_no_ctx we ignore all files with longer than 254 chars // This function also adds a dot at the beginning of the filename to hide the file on OS X and Linux @@ -72,33 +86,26 @@ QString OWNCLOUDSYNC_EXPORT createDownloadTmpFileName(const QString &previous) } // DOES NOT take ownership of the device. -GETFileJob::GETFileJob(AccountPtr account, const QString &path, QIODevice *device, - const QMap &headers, const QByteArray &expectedEtagForResume, - qint64 resumeStart, QObject *parent) - : GETJob(account, path, parent) +GETFileJob::GETFileJob(Account *account, const QUrl &url, const QString &path, QIODevice *device, const QMap &headers, + const QString &expectedEtagForResume, qint64 resumeStart, QObject *parent) + : AbstractNetworkJob(account, url, path, parent) , _device(device) , _headers(headers) , _expectedEtagForResume(expectedEtagForResume) , _expectedContentLength(-1) , _contentLength(-1) , _resumeStart(resumeStart) - , _hasEmittedFinishedSignal(false) { -} + connect(this, &GETFileJob::networkError, this, [this] { + if (timedOut()) { + qCWarning(lcGetJob) << this << "timeout"; + _errorString = tr("Connection Timeout"); + _errorStatus = SyncFileItem::FatalError; + } + }); -GETFileJob::GETFileJob(AccountPtr account, const QUrl &url, QIODevice *device, - const QMap &headers, const QByteArray &expectedEtagForResume, - qint64 resumeStart, QObject *parent) - : GETJob(account, url.toString(QUrl::FullyEncoded), parent) - , _device(device) - , _headers(headers) - , _expectedEtagForResume(expectedEtagForResume) - , _expectedContentLength(-1) - , _contentLength(-1) - , _resumeStart(resumeStart) - , _directDownloadUrl(url) - , _hasEmittedFinishedSignal(false) -{ + // Long downloads must not block non-propagation jobs. + setPriority(QNetworkRequest::LowPriority); } void GETFileJob::start() @@ -114,23 +121,18 @@ void GETFileJob::start() req.setRawHeader(it.key(), it.value()); } - req.setPriority(QNetworkRequest::LowPriority); // Long downloads must not block non-propagation jobs. + sendRequest("GET", req); - if (_directDownloadUrl.isEmpty()) { - sendRequest("GET", makeDavUrl(path()), req); - } else { - // Use direct URL - sendRequest("GET", _directDownloadUrl, req); - } + AbstractNetworkJob::start(); +} - qCDebug(lcGetJob) << _bandwidthManager << _bandwidthChoked << _bandwidthLimited; - if (_bandwidthManager) { - _bandwidthManager->registerDownloadJob(this); +void GETFileJob::finished() +{ + if (reply()->bytesAvailable() && _httpOk) { + // we were throttled, write out the remaining data + slotReadyRead(); + Q_ASSERT(!reply()->bytesAvailable()); } - - connect(this, &AbstractNetworkJob::networkActivity, account().data(), &Account::propagatorNetworkActivity); - - AbstractNetworkJob::start(); } void GETFileJob::newReplyHook(QNetworkReply *reply) @@ -138,7 +140,6 @@ void GETFileJob::newReplyHook(QNetworkReply *reply) reply->setReadBufferSize(16 * 1024); // keep low so we can easier limit the bandwidth connect(reply, &QNetworkReply::metaDataChanged, this, &GETFileJob::slotMetaDataChanged); - connect(reply, &QIODevice::readyRead, this, &GETFileJob::slotReadyRead); connect(reply, &QNetworkReply::finished, this, &GETFileJob::slotReadyRead); connect(reply, &QNetworkReply::downloadProgress, this, &GETFileJob::downloadProgress); } @@ -153,12 +154,11 @@ void GETFileJob::slotMetaDataChanged() if (httpStatus == 301 || httpStatus == 302 || httpStatus == 303 || httpStatus == 307 || httpStatus == 308 || httpStatus == 401) { - // Redirects and auth failures (oauth token renew) are handled by AbstractNetworkJob and + // Redirects and auth failures (OAuth token renew) are handled by AbstractNetworkJob and // will end up restarting the job. We do not want to process further data from the initial // request. newReplyHook() will reestablish signal connections for the follow-up request. - bool ok = disconnect(reply(), &QNetworkReply::finished, this, &GETFileJob::slotReadyRead) - && disconnect(reply(), &QNetworkReply::readyRead, this, &GETFileJob::slotReadyRead); - OC_ASSERT(ok); + disconnect(reply(), &QNetworkReply::finished, this, &GETFileJob::slotReadyRead); + disconnect(reply(), &QNetworkReply::readyRead, this, &GETFileJob::slotReadyRead); return; } @@ -175,23 +175,18 @@ void GETFileJob::slotMetaDataChanged() } _etag = getEtagFromReply(reply()); - if (!_directDownloadUrl.isEmpty() && !_etag.isEmpty()) { - qCInfo(lcGetJob) << "Direct download used, ignoring server ETag" << _etag; - _etag = QByteArray(); // reset received ETag - } else if (!_directDownloadUrl.isEmpty()) { - // All fine, ETag empty and directDownloadUrl used - } else if (_etag.isEmpty()) { + if (_etag.isEmpty()) { qCWarning(lcGetJob) << "No E-Tag reply by server, considering it invalid"; _errorString = tr("No E-Tag received from server, check Proxy/Gateway"); _errorStatus = SyncFileItem::NormalError; - reply()->abort(); + abort(); return; } else if (!_expectedEtagForResume.isEmpty() && _expectedEtagForResume != _etag) { qCWarning(lcGetJob) << "We received a different E-Tag for resuming!" << _expectedEtagForResume << "vs" << _etag; _errorString = tr("We received a different E-Tag for resuming. Retrying next time."); _errorStatus = SyncFileItem::NormalError; - reply()->abort(); + abort(); return; } @@ -202,16 +197,17 @@ void GETFileJob::slotMetaDataChanged() << _expectedContentLength << "vs" << _contentLength; _errorString = tr("We received an unexpected download Content-Length."); _errorStatus = SyncFileItem::NormalError; - reply()->abort(); + abort(); return; } qint64 start = 0; - QString ranges = QString::fromUtf8(reply()->rawHeader("Content-Range")); + const QString ranges = QString::fromUtf8(reply()->rawHeader("Content-Range")); if (!ranges.isEmpty()) { - QRegExp rx(QStringLiteral("bytes (\\d+)-")); - if (rx.indexIn(ranges) >= 0) { - start = rx.cap(1).toLongLong(); + static QRegularExpression rx(QStringLiteral("bytes (\\d+)-")); + const auto match = rx.match(ranges); + if (match.hasMatch()) { + start = match.captured(1).toLongLong(); } } if (start != _resumeStart) { @@ -222,14 +218,14 @@ void GETFileJob::slotMetaDataChanged() if (!_device->open(QIODevice::WriteOnly)) { _errorString = _device->errorString(); _errorStatus = SyncFileItem::NormalError; - reply()->abort(); + abort(); return; } _resumeStart = 0; } else { _errorString = tr("Server returned wrong content-range"); _errorStatus = SyncFileItem::NormalError; - reply()->abort(); + abort(); return; } } @@ -238,32 +234,8 @@ void GETFileJob::slotMetaDataChanged() if (!lastModified.isNull()) { _lastModified = Utility::qDateTimeToTime_t(lastModified.toDateTime()); } - - _saveBodyToFile = true; -} - -void GETJob::setBandwidthManager(BandwidthManager *bwm) -{ - _bandwidthManager = bwm; -} - -void GETJob::setChoked(bool c) -{ - _bandwidthChoked = c; - QMetaObject::invokeMethod(this, "slotReadyRead", Qt::QueuedConnection); -} - -void GETJob::setBandwidthLimited(bool b) -{ - _bandwidthLimited = b; - QMetaObject::invokeMethod(this, "slotReadyRead", Qt::QueuedConnection); -} - -void GETJob::giveBandwidthQuota(qint64 q) -{ - _bandwidthQuota = q; - qCDebug(lcGetJob) << "Got" << q << "bytes"; - QMetaObject::invokeMethod(this, "slotReadyRead", Qt::QueuedConnection); + _httpOk = true; + connect(reply(), &QIODevice::readyRead, this, &GETFileJob::slotReadyRead); } qint64 GETFileJob::currentDownloadPosition() @@ -276,73 +248,37 @@ qint64 GETFileJob::currentDownloadPosition() void GETFileJob::slotReadyRead() { - if (!reply()) + Q_ASSERT(reply()); + if (!_httpOk) { return; - int bufferSize = qMin(1024 * 8ll, reply()->bytesAvailable()); + } + + const qint64 bufferSize = std::min(1024 * 8, reply()->bytesAvailable()); QByteArray buffer(bufferSize, Qt::Uninitialized); - while (reply()->bytesAvailable() > 0 && _saveBodyToFile) { - if (_bandwidthChoked) { - qCWarning(lcGetJob) << "Download choked"; - break; - } + while (reply()->bytesAvailable() > 0) { qint64 toRead = bufferSize; - if (_bandwidthLimited) { - toRead = qMin(qint64(bufferSize), _bandwidthQuota); - if (toRead == 0) { - qCWarning(lcGetJob) << "Out of quota"; - break; - } - _bandwidthQuota -= toRead; - } - - qint64 r = reply()->read(buffer.data(), toRead); - if (r < 0) { + const qint64 read = reply()->read(buffer.data(), toRead); + if (read < 0) { _errorString = networkReplyErrorString(*reply()); _errorStatus = SyncFileItem::NormalError; qCWarning(lcGetJob) << "Error while reading from device: " << _errorString; - reply()->abort(); + abort(); return; } - qint64 w = _device->write(buffer.constData(), r); - if (w != r) { + const qint64 written = _device->write(buffer.constData(), read); + if (written != read) { _errorString = _device->errorString(); _errorStatus = SyncFileItem::NormalError; - qCWarning(lcGetJob) << "Error while writing to file" << w << r << _errorString; - reply()->abort(); + qCWarning(lcGetJob) << "Error while writing to file" << written << read << _errorString; + abort(); return; } } - - if (reply()->isFinished() && (reply()->bytesAvailable() == 0 || !_saveBodyToFile)) { - qCDebug(lcGetJob) << "Actually finished!"; - if (_bandwidthManager) { - _bandwidthManager->unregisterDownloadJob(this); - } - if (!_hasEmittedFinishedSignal) { - qCInfo(lcGetJob) << "GET of" << reply()->request().url().toString() << "FINISHED WITH STATUS" - << replyStatusString() - << reply()->rawHeader("Content-Range") << reply()->rawHeader("Content-Length"); - - emit finishedSignal(); - } - _hasEmittedFinishedSignal = true; - deleteLater(); - } } -void GETJob::onTimedOut() -{ - qCWarning(lcGetJob) << this << "timeout"; - if (reply()) { - _errorString = tr("Connection Timeout"); - _errorStatus = SyncFileItem::FatalError; - } - AbstractNetworkJob::onTimedOut(); -} - -QString GETJob::errorString() const +QString GETFileJob::errorString() const { if (!_errorString.isEmpty()) { return _errorString; @@ -352,19 +288,18 @@ QString GETJob::errorString() const void PropagateDownloadFile::start() { - if (propagator()->_abortRequested) + if (propagator()->_abortRequested || !propagator()->syncOptions().isValid()) return; - qCDebug(lcPropagateDownload) << _item->_file << propagator()->_activeJobList.count(); _stopwatch.start(); - auto &syncOptions = propagator()->syncOptions(); - auto &vfs = syncOptions._vfs; + Vfs *vfs = propagator()->syncOptions().vfs(); + + const QString fsPath = propagator()->fullLocalPath(_item->_file); // For virtual files just dehydrate or create the file and be done if (_item->_type == ItemTypeVirtualFileDehydration) { - const QString fsPath = propagator()->fullLocalPath(_item->_file); - if (!FileSystem::verifyFileUnchanged(fsPath, _item->_previousSize, _item->_previousModtime)) { + if (FileSystem::fileChanged(QFileInfo{fsPath}, _item->_previousSize, _item->_previousModtime)) { propagator()->_anotherSyncNeeded = true; done(SyncFileItem::SoftError, tr("File has changed since discovery")); return; @@ -375,12 +310,6 @@ void PropagateDownloadFile::start() return; } qCDebug(lcPropagateDownload) << "dehydrating file" << _item->_file; - auto r = vfs->dehydratePlaceholder(*_item); - if (!r) { - done(SyncFileItem::NormalError, r.error()); - return; - } - propagator()->_journal->deleteFileRecord(_item->_originalFile); updateMetadata(false); return; } @@ -388,50 +317,63 @@ void PropagateDownloadFile::start() qCWarning(lcPropagateDownload) << "ignored virtual file type of" << _item->_file; _item->_type = ItemTypeFile; } + + if (_deleteExisting) { + deleteExistingFolder(); + + // check for error with deletion + if (state() == Finished) { + return; + } + } + if (_item->_type == ItemTypeVirtualFile) { - qCDebug(lcPropagateDownload) << "creating virtual file" << _item->_file; + qCDebug(lcPropagateDownload) << "creating virtual file" << _item; // do a klaas' case clash check. if (auto clash = propagator()->localFileNameClash(_item->_file)) { done(SyncFileItem::NormalError, tr("File %1 can not be downloaded because of a local file name clash with %2!").arg(QDir::toNativeSeparators(_item->_file), QDir::toNativeSeparators(clash.get()))); return; } + const bool isConflict = _item->instruction() == CSYNC_INSTRUCTION_CONFLICT && QFileInfo(fsPath).isDir(); + if (isConflict) { + QString error; + if (!propagator()->createConflict(_item, &error)) { + done(SyncFileItem::SoftError, error); + return; + } + } auto r = vfs->createPlaceholder(*_item); if (!r) { done(SyncFileItem::NormalError, r.error()); return; } - updateMetadata(false); + updateMetadata(isConflict); return; } - if (_deleteExisting) { - deleteExistingFolder(); - - // check for error with deletion - if (_state == Finished) { - return; - } - } - // If we have a conflict where size of the file is unchanged, // compare the remote checksum to the local one. // Maybe it's not a real conflict and no download is necessary! // If the hashes are collision safe and identical, we assume the content is too. // For weak checksums, we only do that if the mtimes are also identical. - const auto csync_is_collision_safe_hash = [](const QByteArray &checksum_header) - { - return checksum_header.startsWith("SHA") - || checksum_header.startsWith("MD5:"); + const auto csync_is_collision_safe_hash = [](const QByteArray &checksum_header) { + const bool safe = std::any_of(CheckSums::SafeAlgorithms.begin(), CheckSums::SafeAlgorithms.end(), [checksum_header = checksum_header.toUpper()](auto &it) { + return checksum_header.startsWith(it.second.data()); + }); + if (!safe) { + qWarning(lcPropagateDownload) << checksum_header << "is considered unsave"; + return false; + } + return true; }; - if (_item->_instruction == CSYNC_INSTRUCTION_CONFLICT - && _item->_size == _item->_previousSize - && !_item->_checksumHeader.isEmpty() - && (csync_is_collision_safe_hash(_item->_checksumHeader) - || _item->_modtime == _item->_previousModtime)) { + + if (_item->instruction() == CSYNC_INSTRUCTION_CONFLICT && _item->_size == _item->_previousSize && !_item->_checksumHeader.isEmpty() + && (csync_is_collision_safe_hash(_item->_checksumHeader) || _item->_modtime == _item->_previousModtime)) { qCDebug(lcPropagateDownload) << _item->_file << "may not need download, computing checksum"; auto computeChecksum = new ComputeChecksum(this); - computeChecksum->setChecksumType(parseChecksumHeaderType(_item->_checksumHeader)); + const auto checksumHeader = ChecksumHeader::parseChecksumHeader(_item->_checksumHeader); + computeChecksum->setChecksumType(checksumHeader.type()); connect(computeChecksum, &ComputeChecksum::done, this, &PropagateDownloadFile::conflictChecksumComputed); propagator()->_activeJobList.append(this); @@ -442,10 +384,11 @@ void PropagateDownloadFile::start() startDownload(); } -void PropagateDownloadFile::conflictChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum) +void PropagateDownloadFile::conflictChecksumComputed(CheckSums::Algorithm checksumType, const QByteArray &checksum) { propagator()->_activeJobList.removeOne(this); - if (makeChecksumHeader(checksumType, checksum) == _item->_checksumHeader) { + const auto checksumHeader = ChecksumHeader::parseChecksumHeader(_item->_checksumHeader); + if (checksumHeader == ChecksumHeader(checksumType, checksum)) { // No download necessary, just update fs and journal metadata qCDebug(lcPropagateDownload) << _item->_file << "remote and local checksum match"; @@ -454,7 +397,6 @@ void PropagateDownloadFile::conflictChecksumComputed(const QByteArray &checksumT auto fn = propagator()->fullLocalPath(_item->_file); if (_item->_modtime != _item->_previousModtime) { FileSystem::setModTime(fn, _item->_modtime); - emit propagator()->touchedFile(fn); } _item->_modtime = FileSystem::getModTime(fn); updateMetadata(/*isConflict=*/false); @@ -477,7 +419,7 @@ void PropagateDownloadFile::startDownload() // becomes available again const auto targetFile = propagator()->fullLocalPath(_item->_file); if (FileSystem::isFileLocked(targetFile, FileSystem::LockMode::Exclusive)) { - emit propagator()->seenLockedFile(targetFile, FileSystem::LockMode::Exclusive); + Q_EMIT propagator()->seenLockedFile(targetFile, FileSystem::LockMode::Exclusive); done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(QDir::toNativeSeparators(_item->_file))); return; } @@ -487,12 +429,12 @@ void PropagateDownloadFile::startDownload() const SyncJournalDb::DownloadInfo progressInfo = propagator()->_journal->getDownloadInfo(_item->_file); if (progressInfo._valid) { // if the etag has changed meanwhile, remove the already downloaded part. - if (progressInfo._etag != _item->_etag) { + if (progressInfo._etag != _item->_etag.toUtf8()) { FileSystem::remove(propagator()->fullLocalPath(progressInfo._tmpfile)); propagator()->_journal->setDownloadInfo(_item->_file, SyncJournalDb::DownloadInfo()); } else { tmpFileName = progressInfo._tmpfile; - _expectedEtagForResume = progressInfo._etag; + _expectedEtagForResume = QString::fromUtf8(progressInfo._etag); } } @@ -529,7 +471,7 @@ void PropagateDownloadFile::startDownload() // these detail errors only in the error view. done(SyncFileItem::DetailError, tr("The download would reduce free local disk space below the limit")); - emit propagator()->insufficientLocalStorage(); + Q_EMIT propagator()->insufficientLocalStorage(); } else if (diskSpaceResult == OwncloudPropagator::DiskSpaceCritical) { done(SyncFileItem::FatalError, tr("Free space on disk is less than %1").arg(Utility::octetsToString(criticalFreeSpaceLimit()))); @@ -545,7 +487,7 @@ void PropagateDownloadFile::startDownload() { SyncJournalDb::DownloadInfo pi; - pi._etag = _item->_etag; + pi._etag = _item->_etag.toUtf8(); pi._tmpfile = tmpFileName; pi._valid = true; propagator()->_journal->setDownloadInfo(_item->_file, pi); @@ -561,9 +503,8 @@ void PropagateDownloadFile::startFullDownload() if (_item->_directDownloadUrl.isEmpty()) { // Normal job, download from oC instance - _job = new GETFileJob(propagator()->account(), - propagator()->fullRemotePath(_item->_file), - &_tmpFile, headers, _expectedEtagForResume, _resumeStart, this); + _job = new GETFileJob(propagator()->account(), propagator()->webDavUrl(), propagator()->fullRemotePath(_item->_file), &_tmpFile, headers, + _expectedEtagForResume, _resumeStart, this); } else { // We were provided a direct URL, use that one qCInfo(lcPropagateDownload) << "directDownloadUrl given for " << _item->_file << _item->_directDownloadUrl; @@ -573,12 +514,11 @@ void PropagateDownloadFile::startFullDownload() } QUrl url = QUrl::fromUserInput(_item->_directDownloadUrl); - _job = new GETFileJob(propagator()->account(), - url, - &_tmpFile, headers, _expectedEtagForResume, _resumeStart, this); + _job = new GETFileJob(propagator()->account(), url, {}, &_tmpFile, headers, _expectedEtagForResume, _resumeStart, this); } - _job->setBandwidthManager(&propagator()->_bandwidthManager); - connect(_job.data(), &GETJob::finishedSignal, this, &PropagateDownloadFile::slotGetFinished); + _job->setExpectedContentLength(_item->_size - _resumeStart); + + connect(_job.data(), &GETFileJob::finishedSignal, this, &PropagateDownloadFile::slotGetFinished); connect(qobject_cast(_job.data()), &GETFileJob::downloadProgress, this, &PropagateDownloadFile::slotDownloadProgress); propagator()->_activeJobList.append(this); @@ -587,7 +527,7 @@ void PropagateDownloadFile::startFullDownload() qint64 PropagateDownloadFile::committedDiskSpace() const { - if (_state == Running) { + if (state() == Running) { return qBound(0LL, _item->_size - _resumeStart - _downloadProgress, _item->_size); } return 0; @@ -603,7 +543,7 @@ void PropagateDownloadFile::slotGetFinished() { propagator()->_activeJobList.removeOne(this); - GETJob *job = _job; + GETFileJob *job = _job; OC_ASSERT(job); _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -627,7 +567,7 @@ void PropagateDownloadFile::slotGetFinished() qCWarning(lcPropagateDownload) << "server replied 404, assuming file was deleted"; } - // Don't keep the temporary file if it is empty or we + // Don't keep the temporary file if it is empty, or we // used a bad range header or the file's not on the server anymore. if (_tmpFile.exists() && (_tmpFile.size() == 0 || badRangeHeader || fileNotFound)) { _tmpFile.close(); @@ -680,7 +620,8 @@ void PropagateDownloadFile::slotGetFinished() if (!job->etag().isEmpty()) { // The etag will be empty if we used a direct download URL. // (If it was really empty by the server, the GETFileJob will have errored - _item->_etag = parseEtag(job->etag()); + Q_ASSERT(job->etag() == Utility::normalizeEtag(job->etag())); + _item->_etag = job->etag(); } if (job->lastModified()) { // It is possible that the file was modified on the server since we did the discovery phase @@ -696,16 +637,14 @@ void PropagateDownloadFile::slotGetFinished() * truncated, as described here: https://github.com/owncloud/mirall/issues/2528 */ const QByteArray sizeHeader("Content-Length"); - qint64 bodySize = job->reply()->rawHeader(sizeHeader).toLongLong(); - bool hasSizeHeader = !job->reply()->rawHeader(sizeHeader).isEmpty(); + bool hasSizeHeader = true; + qint64 bodySize = job->reply()->rawHeader(sizeHeader).toLongLong(&hasSizeHeader); // Qt removes the content-length header for transparently decompressed HTTP1 replies // but not for HTTP2 or SPDY replies. For these it remains and contains the size // of the compressed data. See QTBUG-73364. const auto contentEncoding = job->reply()->rawHeader("content-encoding").toLower(); - if ((contentEncoding == "gzip" || contentEncoding == "deflate") - && (job->reply()->attribute(QNetworkRequest::Http2WasUsedAttribute).toBool() - || job->reply()->attribute(QNetworkRequest::SpdyWasUsedAttribute).toBool())) { + if ((job->reply()->attribute(QNetworkRequest::Http2WasUsedAttribute).toBool()) && (contentEncoding == "gzip" || contentEncoding == "deflate")) { bodySize = 0; hasSizeHeader = false; } @@ -715,12 +654,12 @@ void PropagateDownloadFile::slotGetFinished() // This happened when trying to resume a file. The Content-Range header was files, Content-Length was == 0 qCDebug(lcPropagateDownload) << bodySize << _item->_size << _tmpFile.size() << job->resumeStart(); FileSystem::remove(_tmpFile.fileName()); - done(SyncFileItem::SoftError, QStringLiteral("Broken webserver returning empty content length for non-empty file on resume")); + done(SyncFileItem::SoftError, tr("Broken webserver returned empty content length for non-empty file on resume")); return; } - if (bodySize > 0 && bodySize != _tmpFile.size() - job->resumeStart()) { - qCDebug(lcPropagateDownload) << bodySize << _tmpFile.size() << job->resumeStart(); + if (bodySize > 0 && (bodySize != (_tmpFile.size() - job->resumeStart()))) { + qCDebug(lcPropagateDownload) << bodySize << "!=" << (_tmpFile.size() - job->resumeStart()) << _tmpFile.size() << job->resumeStart(); propagator()->_anotherSyncNeeded = true; done(SyncFileItem::SoftError, tr("The file could not be downloaded completely.")); return; @@ -756,7 +695,7 @@ void PropagateDownloadFile::slotGetFinished() } // Do checksum validation for the download. If there is no checksum header, the validator - // will also emit the validated() signal to continue the flow in slot transmissionChecksumValidated() + // will also Q_EMIT the validated() signal to continue the flow in slot transmissionChecksumValidated() // as this is (still) also correct. ValidateChecksumHeader *validator = new ValidateChecksumHeader(this); connect(validator, &ValidateChecksumHeader::validated, @@ -787,6 +726,7 @@ void PropagateDownloadFile::deleteExistingFolder() // Delete the directory if it is empty! QDir dir(existingDir); if (dir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries).count() == 0) { + qCDebug(lcPropagateDownload) << "deleting existing dir" << existingDir << "to replace it with a file"; if (dir.rmdir(existingDir)) { return; } @@ -794,94 +734,20 @@ void PropagateDownloadFile::deleteExistingFolder() } QString error; - if (!propagator()->createConflict(_item, _associatedComposite, &error)) { + if (!propagator()->createConflict(_item, &error)) { done(SyncFileItem::NormalError, error); } } -namespace { // Anonymous namespace for the recall feature - static QString makeRecallFileName(const QString &fn) - { - QString recallFileName(fn); - // Add _recall-XXXX before the extension. - int dotLocation = recallFileName.lastIndexOf(QLatin1Char('.')); - // If no extension, add it at the end (take care of cases like foo/.hidden or foo.bar/file) - if (dotLocation <= recallFileName.lastIndexOf(QLatin1Char('/')) + 1) { - dotLocation = recallFileName.size(); - } - - QString timeString = QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMdd-hhmmss")); - recallFileName.insert(dotLocation, QStringLiteral("_.sys.admin#recall#-") + timeString); - - return recallFileName; - } - - void handleRecallFile(const QString &filePath, const QString &folderPath, SyncJournalDb &journal) - { - qCDebug(lcPropagateDownload) << "handleRecallFile: " << filePath; - - FileSystem::setFileHidden(filePath, true); - - QFile file(filePath); - if (!file.open(QIODevice::ReadOnly)) { - qCWarning(lcPropagateDownload) << "Could not open recall file" << file.errorString(); - return; - } - QFileInfo existingFile(filePath); - QDir baseDir = existingFile.dir(); - - while (!file.atEnd()) { - QByteArray line = file.readLine(); - line.chop(1); // remove trailing \n - - QString recalledFile = QDir::cleanPath(baseDir.filePath(QString::fromUtf8(line))); - if (!recalledFile.startsWith(folderPath) || !recalledFile.startsWith(baseDir.path())) { - qCWarning(lcPropagateDownload) << "Ignoring recall of " << recalledFile; - continue; - } - - // Path of the recalled file in the local folder - QString localRecalledFile = recalledFile.mid(folderPath.size()); - - SyncJournalFileRecord record; - if (!journal.getFileRecord(localRecalledFile, &record) || !record.isValid()) { - qCWarning(lcPropagateDownload) << "No db entry for recall of" << localRecalledFile; - continue; - } - - qCInfo(lcPropagateDownload) << "Recalling" << localRecalledFile << "Checksum:" << record._checksumHeader; - - QString targetPath = makeRecallFileName(recalledFile); - - qCDebug(lcPropagateDownload) << "Copy recall file: " << recalledFile << " -> " << targetPath; - // Remove the target first, QFile::copy will not overwrite it. - FileSystem::remove(targetPath); - QFile::copy(recalledFile, targetPath); - } - } - - static void preserveGroupOwnership(const QString &fileName, const QFileInfo &fi) - { -#ifdef Q_OS_UNIX - if (chown(fileName.toLocal8Bit().constData(), -1, fi.groupId()) != 0) { - qCWarning(lcPropagateDownload) << "Unable to chown" << fileName << "to previous group owner" << strerror(errno); - } -#else - Q_UNUSED(fileName); - Q_UNUSED(fi); -#endif - } -} // end namespace - -void PropagateDownloadFile::transmissionChecksumValidated(const QByteArray &checksumType, const QByteArray &checksum) +void PropagateDownloadFile::transmissionChecksumValidated(CheckSums::Algorithm checksumType, const QByteArray &checksum) { - const QByteArray theContentChecksumType = propagator()->account()->capabilities().preferredUploadChecksumType(); + const CheckSums::Algorithm theContentChecksumType = propagator()->account()->capabilities().preferredUploadChecksumType(); // Reuse transmission checksum as content checksum. // // We could do this more aggressively and accept both MD5 and SHA1 // instead of insisting on the exactly correct checksum type. - if (theContentChecksumType == checksumType || theContentChecksumType.isEmpty()) { + if (theContentChecksumType == checksumType || theContentChecksumType != CheckSums::Algorithm::PARSE_ERROR) { return contentChecksumComputed(checksumType, checksum); } @@ -894,9 +760,9 @@ void PropagateDownloadFile::transmissionChecksumValidated(const QByteArray &chec computeChecksum->start(_tmpFile.fileName()); } -void PropagateDownloadFile::contentChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum) +void PropagateDownloadFile::contentChecksumComputed(CheckSums::Algorithm checksumType, const QByteArray &checksum) { - _item->_checksumHeader = makeChecksumHeader(checksumType, checksum); + _item->_checksumHeader = ChecksumHeader(checksumType, checksum).makeChecksumHeader(); downloadFinished(); } @@ -938,14 +804,10 @@ void PropagateDownloadFile::downloadFinished() } } - // Apply the remote permissions - FileSystem::setFileReadOnlyWeak(_tmpFile.fileName(), !_item->_remotePerm.isNull() && !_item->_remotePerm.hasPermission(RemotePermissions::CanWrite)); - - bool isConflict = _item->_instruction == CSYNC_INSTRUCTION_CONFLICT - && (QFileInfo(fn).isDir() || !FileSystem::fileEquals(fn, _tmpFile.fileName())); + bool isConflict = _item->instruction() == CSYNC_INSTRUCTION_CONFLICT && (QFileInfo(fn).isDir() || !FileSystem::fileEquals(fn, _tmpFile.fileName())); if (isConflict) { QString error; - if (!propagator()->createConflict(_item, _associatedComposite, &error)) { + if (!propagator()->createConflict(_item, &error)) { done(SyncFileItem::SoftError, error); return; } @@ -959,7 +821,7 @@ void PropagateDownloadFile::downloadFinished() // the discovery phase and now. const qint64 expectedSize = _item->_previousSize; const time_t expectedMtime = _item->_previousModtime; - if (!FileSystem::verifyFileUnchanged(fn, expectedSize, expectedMtime)) { + if (FileSystem::fileChanged(QFileInfo{fn}, expectedSize, expectedMtime)) { propagator()->_anotherSyncNeeded = true; done(SyncFileItem::SoftError, tr("File has changed since discovery")); return; @@ -968,16 +830,15 @@ void PropagateDownloadFile::downloadFinished() // If the file is locked, we want to retry this sync when it // becomes available again if (FileSystem::isFileLocked(fn, FileSystem::LockMode::Exclusive)) { - emit propagator()->seenLockedFile(fn, FileSystem::LockMode::Exclusive); + Q_EMIT propagator()->seenLockedFile(fn, FileSystem::LockMode::Exclusive); done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(fn)); return; } QString error; - emit propagator()->touchedFile(fn); // The fileChanged() check is done above to generate better error messages. if (!FileSystem::uncheckedRenameReplace(_tmpFile.fileName(), fn, &error)) { - qCWarning(lcPropagateDownload) << "Rename failed:" << _tmpFile.fileName() << "=>" << fn; + qCWarning(lcPropagateDownload) << "Rename failed:" << _tmpFile.fileName() << "=>" << fn << "with error:" << error; propagator()->_anotherSyncNeeded = true; done(SyncFileItem::SoftError, error); return; @@ -986,42 +847,13 @@ void PropagateDownloadFile::downloadFinished() FileSystem::setFileHidden(fn, false); // Maybe we downloaded a newer version of the file than we thought we would... - // Get up to date information for the journal. - _item->_size = FileSystem::getSize(fn); + // Get up-to-date information for the journal. + _item->_size = FileSystem::getSize(QFileInfo{fn}); // Maybe what we downloaded was a conflict file? If so, set a conflict record. // (the data was prepared in slotGetFinished above) - if (_conflictRecord.isValid()) + if (_conflictRecord.isValid()) { propagator()->_journal->setConflictRecord(_conflictRecord); - - auto vfs = propagator()->syncOptions()._vfs; - if (vfs && vfs->mode() == Vfs::WithSuffix) { - // TODO: move to the suffix plugin - // If the virtual file used to have a different name and db - // entry, remove it transfer its old pin state. - if (_item->_type == ItemTypeVirtualFileDownload) { - const QString virtualFile = _item->_file + vfs->fileSuffix(); - const QString virtualFileAbsPath = propagator()->fullLocalPath(virtualFile); - qCDebug(lcPropagateDownload) << "Download of previous virtual file finished" << virtualFileAbsPath; - if (QFileInfo::exists(virtualFileAbsPath)) { - if (!FileSystem::remove(virtualFileAbsPath, &error)) { - done(SyncFileItem::FatalError, error); - return; - } - } - OC_ASSERT(propagator()->_journal->deleteFileRecord(virtualFile)); - // Move the pin state to the new location - auto pin = propagator()->_journal->internalPinStates().rawForPath(virtualFile.toUtf8()); - if (pin && *pin != PinState::Inherited) { - vfs->setPinState(_item->_file, *pin); - vfs->setPinState(virtualFile, PinState::Inherited); - } - } - - // Ensure the pin state isn't contradictory - auto pin = vfs->pinState(_item->_file); - if (pin && *pin == PinState::OnlineOnly) - vfs->setPinState(_item->_file, PinState::Unspecified); } updateMetadata(isConflict); @@ -1042,14 +874,6 @@ void PropagateDownloadFile::updateMetadata(bool isConflict) done(isConflict ? SyncFileItem::Conflict : SyncFileItem::Success); - // handle the special recall file - if (!_item->_remotePerm.hasPermission(RemotePermissions::IsShared) - && (_item->_file == QLatin1String(".sys.admin#recall#") - || _item->_file.endsWith(QLatin1String("/.sys.admin#recall#")))) { - const QString fn = propagator()->fullLocalPath(_item->destination()); - handleRecallFile(fn, propagator()->localPath(), *propagator()->_journal); - } - const auto duration = std::chrono::milliseconds(_stopwatch.elapsed()); if (isLikelyFinishedQuickly() && duration > 5s) { qCWarning(lcPropagateDownload) << "WARNING: Unexpectedly slow connection, took" << duration.count() << "ms for" << _item->_size - _resumeStart << "bytes for" << _item->_file; @@ -1068,11 +892,11 @@ void PropagateDownloadFile::slotDownloadProgress(qint64 received, qint64) void PropagateDownloadFile::abort(PropagatorJob::AbortType abortType) { - if (_job && _job->reply()) - _job->reply()->abort(); - + if (_job) { + _job->abort(); + } if (abortType == AbortType::Asynchronous) { - emit abortFinished(); + Q_EMIT abortFinished(); } } } diff --git a/src/libsync/propagatedownload.h b/src/libsync/propagatedownload.h index 3f01ba06c65..6e31a6c435a 100644 --- a/src/libsync/propagatedownload.h +++ b/src/libsync/propagatedownload.h @@ -13,108 +13,42 @@ */ #pragma once -#include "owncloudpropagator.h" +#include "common/checksumalgorithms.h" #include "networkjobs.h" +#include "owncloudpropagator.h" -#include #include namespace OCC { -class OWNCLOUDSYNC_EXPORT GETJob : public AbstractNetworkJob -{ - Q_OBJECT -protected: - QByteArray _etag; - time_t _lastModified = 0; - QString _errorString; - SyncFileItem::Status _errorStatus = SyncFileItem::NoStatus; - bool _bandwidthLimited = false; // if _bandwidthQuota will be used - bool _bandwidthChoked = false; // if download is paused (won't read on readyRead()) - qint64 _bandwidthQuota = 0; - QPointer _bandwidthManager = nullptr; - -public: - GETJob(AccountPtr account, const QString &path, QObject *parent = nullptr) - : AbstractNetworkJob(account, path, parent) - { - } - - ~GETJob() override - { - if (_bandwidthManager) { - _bandwidthManager->unregisterDownloadJob(this); - } - } - - virtual qint64 currentDownloadPosition() = 0; - virtual qint64 resumeStart() { return 0; } - - QByteArray &etag() { return _etag; } - time_t lastModified() { return _lastModified; } - - void setErrorString(const QString &s) { _errorString = s; } - QString errorString() const; - SyncFileItem::Status errorStatus() { return _errorStatus; } - void setErrorStatus(const SyncFileItem::Status &s) { _errorStatus = s; } - void setBandwidthManager(BandwidthManager *bwm); - void setChoked(bool c); - void setBandwidthLimited(bool b); - void giveBandwidthQuota(qint64 q); - void onTimedOut() override; - -signals: - void finishedSignal(); -}; - /** * @brief Downloads the remote file via GET * @ingroup libsync */ -class OWNCLOUDSYNC_EXPORT GETFileJob : public GETJob +class OWNCLOUDSYNC_EXPORT GETFileJob : public AbstractNetworkJob { Q_OBJECT QIODevice *_device; QMap _headers; - QByteArray _expectedEtagForResume; + QString _expectedEtagForResume; qint64 _expectedContentLength; qint64 _contentLength; qint64 _resumeStart; - QUrl _directDownloadUrl; - bool _hasEmittedFinishedSignal; - - /// Will be set to true once we've seen a 2xx response header - bool _saveBodyToFile = false; public: // DOES NOT take ownership of the device. - explicit GETFileJob(AccountPtr account, const QString &path, QIODevice *device, - const QMap &headers, const QByteArray &expectedEtagForResume, - qint64 resumeStart, QObject *parent = nullptr); // For directDownloadUrl: - explicit GETFileJob(AccountPtr account, const QUrl &url, QIODevice *device, - const QMap &headers, const QByteArray &expectedEtagForResume, - qint64 resumeStart, QObject *parent = nullptr); + explicit GETFileJob(Account *account, const QUrl &url, const QString &path, QIODevice *device, const QMap &headers, + const QString &expectedEtagForResume, qint64 resumeStart, QObject *parent = nullptr); - qint64 currentDownloadPosition() override; + qint64 currentDownloadPosition(); void start() override; - bool finished() override - { - if (_saveBodyToFile && reply()->bytesAvailable()) { - return false; - } else { - if (!_hasEmittedFinishedSignal) { - emit finishedSignal(); - } - _hasEmittedFinishedSignal = true; - return true; // discard - } - } + void finished() override; void newReplyHook(QNetworkReply *reply) override; - qint64 resumeStart() override + qint64 resumeStart() { return _resumeStart; } @@ -123,12 +57,29 @@ class OWNCLOUDSYNC_EXPORT GETFileJob : public GETJob qint64 expectedContentLength() const { return _expectedContentLength; } void setExpectedContentLength(qint64 size) { _expectedContentLength = size; } -private slots: + QString &etag() { return _etag; } + time_t lastModified() { return _lastModified; } + + void setErrorString(const QString &s) { _errorString = s; } + QString errorString() const; + SyncFileItem::Status errorStatus() { return _errorStatus; } + void setErrorStatus(const SyncFileItem::Status &s) { _errorStatus = s; } + +private Q_SLOTS: void slotReadyRead(); void slotMetaDataChanged(); -signals: +Q_SIGNALS: void downloadProgress(qint64, qint64); + +protected: + bool restartDevice(); + + QString _etag; + time_t _lastModified = 0; + QString _errorString; + SyncFileItem::Status _errorStatus = SyncFileItem::NoStatus; + bool _httpOk = false; }; /** @@ -177,7 +128,7 @@ private slots: class PropagateDownloadFile : public PropagateItemJob { Q_OBJECT - QByteArray _expectedEtagForResume; + QString _expectedEtagForResume; public: PropagateDownloadFile(OwncloudPropagator *propagator, const SyncFileItemPtr &item) @@ -204,19 +155,19 @@ class PropagateDownloadFile : public PropagateItemJob */ void setDeleteExistingFolder(bool enabled); -private slots: +private Q_SLOTS: /// Called when ComputeChecksum on the local file finishes, /// maybe the local and remote checksums are identical? - void conflictChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum); + void conflictChecksumComputed(CheckSums::Algorithm checksumType, const QByteArray &checksum); /// Called to start downloading the remote file void startDownload(); void startFullDownload(); /// Called when the GETJob finishes void slotGetFinished(); /// Called when the download's checksum header was validated - void transmissionChecksumValidated(const QByteArray &checksumType, const QByteArray &checksum); + void transmissionChecksumValidated(CheckSums::Algorithm checksumType, const QByteArray &checksum); /// Called when the download's checksum computation is done - void contentChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum); + void contentChecksumComputed(CheckSums::Algorithm checksumType, const QByteArray &checksum); void downloadFinished(); /// Called when it's time to update the db metadata void updateMetadata(bool isConflict); @@ -230,7 +181,7 @@ private slots: qint64 _resumeStart; qint64 _downloadProgress; - QPointer _job; + QPointer _job; QFile _tmpFile; bool _deleteExisting; ConflictRecord _conflictRecord; diff --git a/src/libsync/propagateremotedelete.cpp b/src/libsync/propagateremotedelete.cpp index 52e601e3305..ae927a23752 100644 --- a/src/libsync/propagateremotedelete.cpp +++ b/src/libsync/propagateremotedelete.cpp @@ -13,9 +13,9 @@ */ #include "propagateremotedelete.h" -#include "owncloudpropagator_p.h" #include "account.h" #include "common/asserts.h" +#include "owncloudpropagator_p.h" #include @@ -24,35 +24,21 @@ namespace OCC { Q_LOGGING_CATEGORY(lcDeleteJob, "sync.networkjob.delete", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropagateRemoteDelete, "sync.propagator.remotedelete", QtInfoMsg) -DeleteJob::DeleteJob(AccountPtr account, const QString &path, QObject *parent) - : AbstractNetworkJob(account, path, parent) -{ -} - -DeleteJob::DeleteJob(AccountPtr account, const QUrl &url, QObject *parent) - : AbstractNetworkJob(account, QString(), parent) - , _url(url) +DeleteJob::DeleteJob(Account *account, const QUrl &url, const QString &path, QObject *parent) + : AbstractNetworkJob(account, url, path, parent) { } void DeleteJob::start() { - QNetworkRequest req; - if (_url.isValid()) { - sendRequest("DELETE", _url, req); - } else { - sendRequest("DELETE", makeDavUrl(path()), req); - } + sendRequest("DELETE"); AbstractNetworkJob::start(); } -bool DeleteJob::finished() +void DeleteJob::finished() { qCInfo(lcDeleteJob) << "DELETE of" << reply()->request().url() << "FINISHED WITH STATUS" << replyStatusString(); - - emit finishedSignal(); - return true; } void PropagateRemoteDelete::start() @@ -62,9 +48,7 @@ void PropagateRemoteDelete::start() qCDebug(lcPropagateRemoteDelete) << _item->_file; - _job = new DeleteJob(propagator()->account(), - propagator()->fullRemotePath(_item->_file), - this); + _job = new DeleteJob(propagator()->account(), propagator()->webDavUrl(), propagator()->fullRemotePath(_item->_file), this); connect(_job.data(), &DeleteJob::finishedSignal, this, &PropagateRemoteDelete::slotDeleteJobFinished); propagator()->_activeJobList.append(this); _job->start(); @@ -72,11 +56,11 @@ void PropagateRemoteDelete::start() void PropagateRemoteDelete::abort(PropagatorJob::AbortType abortType) { - if (_job && _job->reply()) - _job->reply()->abort(); - + if (_job) { + _job->abort(); + } if (abortType == AbortType::Asynchronous) { - emit abortFinished(); + Q_EMIT abortFinished(); } } diff --git a/src/libsync/propagateremotedelete.h b/src/libsync/propagateremotedelete.h index 48bda01b948..f33656a0e1c 100644 --- a/src/libsync/propagateremotedelete.h +++ b/src/libsync/propagateremotedelete.h @@ -25,20 +25,15 @@ namespace OCC { class DeleteJob : public AbstractNetworkJob { Q_OBJECT - QUrl _url; // Only used if the constructor taking a url is taken. public: - explicit DeleteJob(AccountPtr account, const QString &path, QObject *parent = nullptr); - explicit DeleteJob(AccountPtr account, const QUrl &url, QObject *parent = nullptr); + explicit DeleteJob(Account *account, const QUrl &url, const QString &path, QObject *parent = nullptr); void start() override; - bool finished() override; - -signals: - void finishedSignal(); + void finished() override; }; /** - * @brief The PropagateRemoteDelete class + * @brief Propagate a local delete to the server * @ingroup libsync */ class PropagateRemoteDelete : public PropagateItemJob @@ -56,7 +51,7 @@ class PropagateRemoteDelete : public PropagateItemJob bool isLikelyFinishedQuickly() override { return !_item->isDirectory(); } -private slots: +private Q_SLOTS: void slotDeleteJobFinished(); }; } diff --git a/src/libsync/propagateremotemkdir.cpp b/src/libsync/propagateremotemkdir.cpp index a1d951c57a2..33561ec4731 100644 --- a/src/libsync/propagateremotemkdir.cpp +++ b/src/libsync/propagateremotemkdir.cpp @@ -13,11 +13,11 @@ */ #include "propagateremotemkdir.h" -#include "owncloudpropagator_p.h" #include "account.h" +#include "common/asserts.h" #include "common/syncjournalfilerecord.h" +#include "owncloudpropagator_p.h" #include "propagateremotedelete.h" -#include "common/asserts.h" #include #include @@ -45,9 +45,7 @@ void PropagateRemoteMkdir::start() return slotStartMkcolJob(); } - _job = new DeleteJob(propagator()->account(), - propagator()->fullRemotePath(_item->_file), - this); + _job = new DeleteJob(propagator()->account(), propagator()->webDavUrl(), propagator()->fullRemotePath(_item->_file), this); connect(qobject_cast(_job), &DeleteJob::finishedSignal, this, &PropagateRemoteMkdir::slotStartMkcolJob); _job->start(); } @@ -59,9 +57,7 @@ void PropagateRemoteMkdir::slotStartMkcolJob() qCDebug(lcPropagateRemoteMkdir) << _item->_file; - _job = new MkColJob(propagator()->account(), - propagator()->fullRemotePath(_item->_file), - this); + _job = new MkColJob(propagator()->account(), propagator()->webDavUrl(), propagator()->fullRemotePath(_item->_file), {}, this); connect(qobject_cast(_job), &MkColJob::finishedWithError, this, &PropagateRemoteMkdir::slotMkcolJobFinished); connect(qobject_cast(_job), &MkColJob::finishedWithoutError, this, &PropagateRemoteMkdir::slotMkcolJobFinished); _job->start(); @@ -69,11 +65,11 @@ void PropagateRemoteMkdir::slotStartMkcolJob() void PropagateRemoteMkdir::abort(PropagatorJob::AbortType abortType) { - if (_job && _job->reply()) - _job->reply()->abort(); - + if (_job) { + _job->abort(); + } if (abortType == AbortType::Asynchronous) { - emit abortFinished(); + Q_EMIT abortFinished(); } } @@ -114,14 +110,15 @@ void PropagateRemoteMkdir::slotMkcolJobFinished() _item->_fileId = _job->reply()->rawHeader("OC-FileId"); propagator()->_activeJobList.append(this); - auto propfindJob = new PropfindJob(_job->account(), _job->path(), this); + auto propfindJob = + new PropfindJob(propagator()->account(), propagator()->webDavUrl(), propagator()->fullRemotePath(_item->_file), PropfindJob::Depth::Zero, this); propfindJob->setProperties({"http://owncloud.org/ns:permissions"}); - connect(propfindJob, &PropfindJob::result, this, [this](const QMap &result) { + connect(propfindJob, &PropfindJob::directoryListingIterated, this, [this](const QString &, const QMap &result) { propagator()->_activeJobList.removeOne(this); _item->_remotePerm = RemotePermissions::fromServerString(result.value(QStringLiteral("permissions"))); success(); }); - connect(propfindJob, &PropfindJob::finishedWithError, this, [this]{ + connect(propfindJob, &PropfindJob::finishedWithError, this, [this] { // ignore the PROPFIND error propagator()->_activeJobList.removeOne(this); done(SyncFileItem::NormalError); @@ -176,8 +173,6 @@ void PropagateRemoteMkdir::retryUpdateMetadata(std::chrono::steady_clock::time_p retryUpdateMetadata(start, item); }); return; - case Vfs::ConvertToPlaceholderResult::Error: - Q_UNREACHABLE(); } } else { done(SyncFileItem::FatalError, result.error()); diff --git a/src/libsync/propagateremotemkdir.h b/src/libsync/propagateremotemkdir.h index c75be43e50a..6c9794ac42a 100644 --- a/src/libsync/propagateremotemkdir.h +++ b/src/libsync/propagateremotemkdir.h @@ -19,7 +19,7 @@ namespace OCC { /** - * @brief The PropagateRemoteMkdir class + * @brief Propagate a local mkdir to the server * @ingroup libsync */ class PropagateRemoteMkdir : public PropagateItemJob @@ -48,7 +48,7 @@ class PropagateRemoteMkdir : public PropagateItemJob */ void setDeleteExisting(bool enabled); -private slots: +private Q_SLOTS: void slotStartMkcolJob(); void slotMkcolJobFinished(); void success(); diff --git a/src/libsync/propagateremotemove.cpp b/src/libsync/propagateremotemove.cpp index 9b5e0be2b61..8e72cf63d6e 100644 --- a/src/libsync/propagateremotemove.cpp +++ b/src/libsync/propagateremotemove.cpp @@ -28,18 +28,9 @@ namespace OCC { Q_LOGGING_CATEGORY(lcMoveJob, "sync.networkjob.move", QtInfoMsg) Q_LOGGING_CATEGORY(lcPropagateRemoteMove, "sync.propagator.remotemove", QtInfoMsg) -MoveJob::MoveJob(AccountPtr account, const QString &path, - const QString &destination, QObject *parent) - : AbstractNetworkJob(account, path, parent) +MoveJob::MoveJob(Account *account, const QUrl &url, const QString &path, const QString &destination, const HeaderMap &extraHeaders, QObject *parent) + : AbstractNetworkJob(account, url, path, parent) , _destination(destination) -{ -} - -MoveJob::MoveJob(AccountPtr account, const QUrl &url, const QString &destination, - QMap extraHeaders, QObject *parent) - : AbstractNetworkJob(account, QString(), parent) - , _destination(destination) - , _url(url) , _extraHeaders(extraHeaders) { } @@ -51,22 +42,15 @@ void MoveJob::start() for (auto it = _extraHeaders.constBegin(); it != _extraHeaders.constEnd(); ++it) { req.setRawHeader(it.key(), it.value()); } - if (_url.isValid()) { - sendRequest("MOVE", _url, req); - } else { - sendRequest("MOVE", makeDavUrl(path()), req); - } + sendRequest("MOVE", req); AbstractNetworkJob::start(); } -bool MoveJob::finished() +void MoveJob::finished() { qCInfo(lcMoveJob) << "MOVE of" << reply()->request().url() << "FINISHED WITH STATUS" << replyStatusString(); - - emit finishedSignal(); - return true; } void PropagateRemoteMove::start() @@ -83,64 +67,11 @@ void PropagateRemoteMove::start() } QString remoteSource = propagator()->fullRemotePath(origin); - QString remoteDestination = QDir::cleanPath(propagator()->account()->davUrl().path() + propagator()->fullRemotePath(_item->_renameTarget)); - - auto &vfs = propagator()->syncOptions()._vfs; - auto itype = _item->_type; - OC_ASSERT(itype != ItemTypeVirtualFileDownload && itype != ItemTypeVirtualFileDehydration); - if (vfs->mode() == Vfs::WithSuffix && itype != ItemTypeDirectory) { - const auto suffix = vfs->fileSuffix(); - bool sourceHadSuffix = remoteSource.endsWith(suffix); - bool destinationHadSuffix = remoteDestination.endsWith(suffix); - - // Remote source and destination definitely shouldn't have the suffix - if (sourceHadSuffix) - remoteSource.chop(suffix.size()); - if (destinationHadSuffix) - remoteDestination.chop(suffix.size()); - - QString folderTarget = _item->_renameTarget; - - // Users can rename the file *and at the same time* add or remove the vfs - // suffix. That's a complicated case where a remote rename plus a local hydration - // change is requested. We don't currently deal with that. Instead, the rename - // is propagated and the local vfs suffix change is reverted. - // The discovery would still set up _renameTarget without the changed - // suffix, since that's what must be propagated to the remote but the local - // file may have a different name. folderTargetAlt will contain this potential - // name. - QString folderTargetAlt = folderTarget; - if (itype == ItemTypeFile) { - OC_ASSERT(!sourceHadSuffix && !destinationHadSuffix); + QString remoteDestination = QDir::cleanPath(propagator()->webDavUrl().path() + propagator()->fullRemotePath(_item->_renameTarget)); - // If foo -> bar.owncloud, the rename target will be "bar" - folderTargetAlt = folderTarget + suffix; - - } else if (itype == ItemTypeVirtualFile) { - OC_ASSERT(sourceHadSuffix && destinationHadSuffix); - - // If foo.owncloud -> bar, the rename target will be "bar.owncloud" - folderTargetAlt.chop(suffix.size()); - } - - QString localTarget = propagator()->fullLocalPath(folderTarget); - QString localTargetAlt = propagator()->fullLocalPath(folderTargetAlt); - - // If the expected target doesn't exist but a file with different hydration - // state does, rename the local file to bring it in line with what the discovery - // has set up. - if (!FileSystem::fileExists(localTarget) && FileSystem::fileExists(localTargetAlt)) { - QString error; - if (!FileSystem::uncheckedRenameReplace(localTargetAlt, localTarget, &error)) { - done(SyncFileItem::NormalError, tr("Could not rename %1 to %2, error: %3") - .arg(folderTargetAlt, folderTarget, error)); - return; - } - qCInfo(lcPropagateRemoteMove) << "Suffix vfs required local rename of" - << folderTargetAlt << "to" << folderTarget; - } - } - _job = new MoveJob(propagator()->account(), remoteSource, remoteDestination, this); + auto itemType = _item->_type; + OC_ASSERT(itemType != ItemTypeVirtualFileDownload && itemType != ItemTypeVirtualFileDehydration); + _job = new MoveJob(propagator()->account(), propagator()->webDavUrl(), remoteSource, remoteDestination, {}, this); connect(_job.data(), &MoveJob::finishedSignal, this, &PropagateRemoteMove::slotMoveJobFinished); propagator()->_activeJobList.append(this); _job->start(); @@ -148,11 +79,11 @@ void PropagateRemoteMove::start() void PropagateRemoteMove::abort(PropagatorJob::AbortType abortType) { - if (_job && _job->reply()) - _job->reply()->abort(); - + if (_job) { + _job->abort(); + } if (abortType == AbortType::Asynchronous) { - emit abortFinished(); + Q_EMIT abortFinished(); } } @@ -190,6 +121,10 @@ void PropagateRemoteMove::slotMoveJobFinished() void PropagateRemoteMove::finalize() { + if (!propagator()->syncOptions().isValid()) { + qCWarning(lcPropagateRemoteMove) << "vfs instance is null, unable to finalize"; + return; + } // Retrieve old db data. // if reading from db failed still continue hoping that deleteFileRecord // reopens the db successfully. @@ -197,12 +132,12 @@ void PropagateRemoteMove::finalize() // to the new record. It is not a problem to skip it here. SyncJournalFileRecord oldRecord; propagator()->_journal->getFileRecord(_item->_originalFile, &oldRecord); - auto &vfs = propagator()->syncOptions()._vfs; + Vfs *vfs = propagator()->syncOptions().vfs(); auto pinState = vfs->pinState(_item->_originalFile); // Delete old db data. propagator()->_journal->deleteFileRecord(_item->_originalFile); - vfs->setPinState(_item->_originalFile, PinState::Inherited); + std::ignore = vfs->setPinState(_item->_originalFile, PinState::Inherited); SyncFileItem newItem(*_item); newItem._type = _item->_type; @@ -246,7 +181,7 @@ bool PropagateRemoteMove::adjustSelectiveSync(SyncJournalDb *journal, const QStr bool ok; // We only care about preserving the blacklist. The white list should anyway be empty. // And the undecided list will be repopulated on the next sync, if there is anything too big. - QStringList list = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok); + auto list = journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, ok); if (!ok) return false; @@ -256,15 +191,19 @@ bool PropagateRemoteMove::adjustSelectiveSync(SyncJournalDb *journal, const QStr QString from = from_ + QLatin1String("/"); QString to = to_ + QLatin1String("/"); + QSet out; + out.reserve(list.size()); for (auto it = list.begin(); it != list.end(); ++it) { if (it->startsWith(from)) { - *it = it->replace(0, from.size(), to); + out.insert(QString(*it).replace(0, from.size(), to)); changed = true; + } else { + out.insert(*it); } } if (changed) { - journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, list); + journal->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, out); } return true; } diff --git a/src/libsync/propagateremotemove.h b/src/libsync/propagateremotemove.h index 567896dd8e3..081493cf20c 100644 --- a/src/libsync/propagateremotemove.h +++ b/src/libsync/propagateremotemove.h @@ -26,23 +26,18 @@ class MoveJob : public AbstractNetworkJob { Q_OBJECT const QString _destination; - const QUrl _url; // Only used (instead of path) when the constructor taking an URL is used - QMap _extraHeaders; + HeaderMap _extraHeaders; public: - explicit MoveJob(AccountPtr account, const QString &path, const QString &destination, QObject *parent = nullptr); - explicit MoveJob(AccountPtr account, const QUrl &url, const QString &destination, - QMap _extraHeaders, QObject *parent = nullptr); + explicit MoveJob( + Account *account, const QUrl &url, const QString &path, const QString &destination, const HeaderMap &_extraHeaders, QObject *parent = nullptr); void start() override; - bool finished() override; - -signals: - void finishedSignal(); + void finished() override; }; /** - * @brief The PropagateRemoteMove class + * @brief Propagate a local move (or rename) to the server * @ingroup libsync */ class PropagateRemoteMove : public PropagateItemJob @@ -64,7 +59,7 @@ class PropagateRemoteMove : public PropagateItemJob */ static bool adjustSelectiveSync(SyncJournalDb *journal, const QString &from, const QString &to); -private slots: +private Q_SLOTS: void slotMoveJobFinished(); void finalize(); }; diff --git a/src/libsync/propagateupload.cpp b/src/libsync/propagateupload.cpp deleted file mode 100644 index 7e7b23df3cd..00000000000 --- a/src/libsync/propagateupload.cpp +++ /dev/null @@ -1,644 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 "config.h" -#include "propagateupload.h" -#include "owncloudpropagator_p.h" -#include "networkjobs.h" -#include "account.h" -#include "common/syncjournaldb.h" -#include "common/syncjournalfilerecord.h" -#include "common/utility.h" -#include "filesystem.h" -#include "propagatorjobs.h" -#include "common/checksums.h" -#include "syncengine.h" -#include "propagateremotedelete.h" -#include "common/asserts.h" - -#include -#include -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -namespace OCC { - -Q_LOGGING_CATEGORY(lcPutJob, "sync.networkjob.put", QtInfoMsg) -Q_LOGGING_CATEGORY(lcPropagateUpload, "sync.propagator.upload", QtInfoMsg) -Q_LOGGING_CATEGORY(lcPropagateUploadV1, "sync.propagator.upload.v1", QtInfoMsg) -Q_LOGGING_CATEGORY(lcPropagateUploadNG, "sync.propagator.upload.ng", QtInfoMsg) - -/** - * We do not want to upload files that are currently being modified. - * To avoid that, we don't upload files that have a modification time - * that is too close to the current time. - * - * This interacts with the msBetweenRequestAndSync delay in the folder - * manager. If that delay between file-change notification and sync - * has passed, we should accept the file for upload here. - */ -static bool fileIsStillChanging(const SyncFileItem &item) -{ - const QDateTime modtime = Utility::qDateTimeFromTime_t(item._modtime); - const qint64 msSinceMod = modtime.msecsTo(QDateTime::currentDateTimeUtc()); - - return std::chrono::milliseconds(msSinceMod) < SyncEngine::minimumFileAgeForUpload - // if the mtime is too much in the future we *do* upload the file - && msSinceMod > -10000; -} - -PUTFileJob::~PUTFileJob() -{ - // Make sure that we destroy the QNetworkReply before our _device of which it keeps an internal pointer. - setReply(nullptr); -} - -void PUTFileJob::start() -{ - QNetworkRequest req; - for (auto it = _headers.cbegin(); it != _headers.cend(); ++it) { - req.setRawHeader(it.key(), it.value()); - } - - req.setPriority(QNetworkRequest::LowPriority); // Long uploads must not block non-propagation jobs. - - if (_url.isValid()) { - sendRequest("PUT", _url, req, _device); - } else { - sendRequest("PUT", makeDavUrl(path()), req, _device); - } - - connect(reply(), &QNetworkReply::uploadProgress, this, &PUTFileJob::uploadProgress); - connect(this, &AbstractNetworkJob::networkActivity, account().data(), &Account::propagatorNetworkActivity); - _requestTimer.start(); - AbstractNetworkJob::start(); -} - -bool PUTFileJob::finished() -{ - _device->close(); - - qCInfo(lcPutJob) << "PUT of" << reply()->request().url().toString() << "FINISHED WITH STATUS" - << replyStatusString() - << reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() - << reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute); - - emit finishedSignal(); - return true; -} - -void PropagateUploadFileCommon::setDeleteExisting(bool enabled) -{ - _deleteExisting = enabled; -} - - -void PropagateUploadFileCommon::start() -{ - if (propagator()->_abortRequested) { - return; - } - - // Check if the specific file can be accessed - if (propagator()->hasCaseClashAccessibilityProblem(_item->_file)) { - done(SyncFileItem::NormalError, tr("File %1 cannot be uploaded because another file with the same name, differing only in case, exists").arg(QDir::toNativeSeparators(_item->_file))); - return; - } - - // Check if we believe that the upload will fail due to remote quota limits - const qint64 quotaGuess = propagator()->_folderQuota.value( - QFileInfo(_item->_file).path(), std::numeric_limits::max()); - if (_item->_size > quotaGuess) { - // Necessary for blacklisting logic - _item->_httpErrorCode = 507; - emit propagator()->insufficientRemoteStorage(); - done(SyncFileItem::DetailError, tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_item->_size))); - return; - } - - propagator()->_activeJobList.append(this); - - if (!_deleteExisting) { - return slotComputeContentChecksum(); - } - - auto job = new DeleteJob(propagator()->account(), - propagator()->fullRemotePath(_item->_file), - this); - _jobs.append(job); - connect(job, &DeleteJob::finishedSignal, this, &PropagateUploadFileCommon::slotComputeContentChecksum); - connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); - job->start(); -} - -void PropagateUploadFileCommon::slotComputeContentChecksum() -{ - if (propagator()->_abortRequested) { - return; - } - - const QString filePath = propagator()->fullLocalPath(_item->_file); - - // remember the modtime before checksumming to be able to detect a file - // change during the checksum calculation - _item->_modtime = FileSystem::getModTime(filePath); - - const QByteArray checksumType = propagator()->account()->capabilities().preferredUploadChecksumType(); - - // Maybe the discovery already computed the checksum? - QByteArray existingChecksumType, existingChecksum; - parseChecksumHeader(_item->_checksumHeader, &existingChecksumType, &existingChecksum); - if (existingChecksumType == checksumType) { - slotComputeTransmissionChecksum(checksumType, existingChecksum); - return; - } - - // Compute the content checksum. - auto computeChecksum = new ComputeChecksum(this); - computeChecksum->setChecksumType(checksumType); - - connect(computeChecksum, &ComputeChecksum::done, - this, &PropagateUploadFileCommon::slotComputeTransmissionChecksum); - connect(computeChecksum, &ComputeChecksum::done, - computeChecksum, &QObject::deleteLater); - computeChecksum->start(filePath); -} - -void PropagateUploadFileCommon::slotComputeTransmissionChecksum(const QByteArray &contentChecksumType, const QByteArray &contentChecksum) -{ - _item->_checksumHeader = makeChecksumHeader(contentChecksumType, contentChecksum); - - // Reuse the content checksum as the transmission checksum if possible - const auto supportedTransmissionChecksums = - propagator()->account()->capabilities().supportedChecksumTypes(); - if (supportedTransmissionChecksums.contains(contentChecksumType)) { - slotStartUpload(contentChecksumType, contentChecksum); - return; - } - - // Compute the transmission checksum. - auto computeChecksum = new ComputeChecksum(this); - if (uploadChecksumEnabled()) { - computeChecksum->setChecksumType(propagator()->account()->capabilities().uploadChecksumType()); - } else { - computeChecksum->setChecksumType(QByteArray()); - } - - connect(computeChecksum, &ComputeChecksum::done, - this, &PropagateUploadFileCommon::slotStartUpload); - connect(computeChecksum, &ComputeChecksum::done, - computeChecksum, &QObject::deleteLater); - const QString filePath = propagator()->fullLocalPath(_item->_file); - computeChecksum->start(filePath); -} - -void PropagateUploadFileCommon::slotStartUpload(const QByteArray &transmissionChecksumType, const QByteArray &transmissionChecksum) -{ - // Remove ourselfs from the list of active job, before any posible call to done() - // When we start chunks, we will add it again, once for every chunks. - propagator()->_activeJobList.removeOne(this); - - _transmissionChecksumHeader = makeChecksumHeader(transmissionChecksumType, transmissionChecksum); - - // If no checksum header was not set, reuse the transmission checksum as the content checksum. - if (_item->_checksumHeader.isEmpty()) { - _item->_checksumHeader = _transmissionChecksumHeader; - } - - const QString fullFilePath = propagator()->fullLocalPath(_item->_file); - - if (!FileSystem::fileExists(fullFilePath)) { - done(SyncFileItem::SoftError, tr("File Removed")); - return; - } - time_t prevModtime = _item->_modtime; // the _item value was set in PropagateUploadFile::start() - // but a potential checksum calculation could have taken some time during which the file could - // have been changed again, so better check again here. - - _item->_modtime = FileSystem::getModTime(fullFilePath); - if (prevModtime != _item->_modtime) { - propagator()->_anotherSyncNeeded = true; - done(SyncFileItem::SoftError, tr("Local file changed during syncing. It will be resumed.")); - return; - } - - qint64 fileSize = FileSystem::getSize(fullFilePath); - _item->_size = fileSize; - - // But skip the file if the mtime is too close to 'now'! - // That usually indicates a file that is still being changed - // or not yet fully copied to the destination. - if (fileIsStillChanging(*_item)) { - propagator()->_anotherSyncNeeded = true; - done(SyncFileItem::SoftError, tr("Local file changed during sync.")); - return; - } - - doStartUpload(); -} - -UploadDevice::UploadDevice(const QString &fileName, qint64 start, qint64 size, BandwidthManager *bwm) - : _file(fileName) - , _start(start) - , _size(size) - , _read(0) - , _bandwidthManager(bwm) - , _bandwidthQuota(0) - , _readWithProgress(0) - , _bandwidthLimited(false) - , _choked(false) -{ - _bandwidthManager->registerUploadDevice(this); -} - - -UploadDevice::~UploadDevice() -{ - if (_bandwidthManager) { - _bandwidthManager->unregisterUploadDevice(this); - } -} - -bool UploadDevice::open(QIODevice::OpenMode mode) -{ - if (mode & QIODevice::WriteOnly) - return false; - - // Get the file size now: _file.fileName() is no longer reliable - // on all platforms after openAndSeekFileSharedRead(). - auto fileDiskSize = FileSystem::getSize(_file.fileName()); - - QString openError; - if (!FileSystem::openAndSeekFileSharedRead(&_file, &openError, _start)) { - setErrorString(openError); - return false; - } - - _size = qBound(0ll, _size, fileDiskSize - _start); - _read = 0; - - return QIODevice::open(mode); -} - -void UploadDevice::close() -{ - _file.close(); - QIODevice::close(); -} - -qint64 UploadDevice::writeData(const char *, qint64) -{ - OC_ASSERT_X(false, "write to read only device"); - return 0; -} - -qint64 UploadDevice::readData(char *data, qint64 maxlen) -{ - if (_size - _read <= 0) { - // at end - if (_bandwidthManager) { - _bandwidthManager->unregisterUploadDevice(this); - } - return -1; - } - maxlen = qMin(maxlen, _size - _read); - if (maxlen <= 0) { - return 0; - } - if (isChoked()) { - return 0; - } - if (isBandwidthLimited()) { - maxlen = qMin(maxlen, _bandwidthQuota); - if (maxlen <= 0) { // no quota - return 0; - } - _bandwidthQuota -= maxlen; - } - - auto c = _file.read(data, maxlen); - if (c < 0) { - setErrorString(_file.errorString()); - return -1; - } - _read += c; - return c; -} - -void UploadDevice::slotJobUploadProgress(qint64 sent, qint64 t) -{ - if (sent == 0 || t == 0) { - return; - } - // used by the BandwidthManager - _readWithProgress = sent; -} - -bool UploadDevice::atEnd() const -{ - return _read >= _size; -} - -qint64 UploadDevice::size() const -{ - return _size; -} - -qint64 UploadDevice::bytesAvailable() const -{ - return _size - _read + QIODevice::bytesAvailable(); -} - -// random access, we can seek -bool UploadDevice::isSequential() const -{ - return false; -} - -bool UploadDevice::seek(qint64 pos) -{ - if (!QIODevice::seek(pos)) { - return false; - } - if (pos < 0 || pos > _size) { - return false; - } - _read = pos; - _file.seek(_start + pos); - return true; -} - -void UploadDevice::giveBandwidthQuota(qint64 bwq) -{ - if (!atEnd()) { - _bandwidthQuota = bwq; - QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection); // tell QNAM that we have quota - } -} - -void UploadDevice::setBandwidthLimited(bool b) -{ - _bandwidthLimited = b; - QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection); -} - -void UploadDevice::setChoked(bool b) -{ - _choked = b; - if (!_choked) { - QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection); - } -} - -void PropagateUploadFileCommon::done(SyncFileItem::Status status, const QString &errorString) -{ - _finished = true; - PropagateItemJob::done(status, errorString); -} - -void PropagateUploadFileCommon::checkResettingErrors() -{ - if (_item->_httpErrorCode == 412 - || propagator()->account()->capabilities().httpErrorCodesThatResetFailingChunkedUploads().contains(_item->_httpErrorCode)) { - auto uploadInfo = propagator()->_journal->getUploadInfo(_item->_file); - uploadInfo._errorCount += 1; - if (uploadInfo._errorCount > 3) { - qCInfo(lcPropagateUpload) << "Reset transfer of" << _item->_file - << "due to repeated error" << _item->_httpErrorCode; - uploadInfo = SyncJournalDb::UploadInfo(); - } else { - qCInfo(lcPropagateUpload) << "Error count for maybe-reset error" << _item->_httpErrorCode - << "on file" << _item->_file - << "is" << uploadInfo._errorCount; - } - propagator()->_journal->setUploadInfo(_item->_file, uploadInfo); - propagator()->_journal->commit(QStringLiteral("Upload info")); - } -} - -void PropagateUploadFileCommon::commonErrorHandling(AbstractNetworkJob *job) -{ - QByteArray replyContent; - QString errorString = job->errorStringParsingBody(&replyContent); - qCDebug(lcPropagateUpload) << replyContent; // display the XML error in the debug - - if (_item->_httpErrorCode == 412) { - // Precondition Failed: Either an etag or a checksum mismatch. - - // Maybe the bad etag is in the database, we need to clear the - // parent folder etag so we won't read from DB next sync. - propagator()->_journal->schedulePathForRemoteDiscovery(_item->_file); - propagator()->_anotherSyncNeeded = true; - } - - // Ensure errors that should eventually reset the chunked upload are tracked. - checkResettingErrors(); - - SyncFileItem::Status status = classifyError(job->reply()->error(), _item->_httpErrorCode, - &propagator()->_anotherSyncNeeded, replyContent); - - // Insufficient remote storage. - if (_item->_httpErrorCode == 507) { - // Update the quota expectation - const auto path = QFileInfo(_item->_file).path(); - auto quotaIt = propagator()->_folderQuota.find(path); - if (quotaIt != propagator()->_folderQuota.end()) { - quotaIt.value() = qMin(quotaIt.value(), _item->_size - 1); - } else { - propagator()->_folderQuota[path] = _item->_size - 1; - } - - // Set up the error - status = SyncFileItem::DetailError; - errorString = tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_item->_size)); - emit propagator()->insufficientRemoteStorage(); - } - - abortWithError(status, errorString); -} - -void PropagateUploadFileCommon::adjustLastJobTimeout(AbstractNetworkJob *job, qint64 fileSize) -{ - job->setTimeout(qBound( - job->timeoutSec(), - // Calculate 3 minutes for each gigabyte of data - std::chrono::minutes(static_cast((3min).count() * fileSize / 1e9)), - // Maximum of 30 minutes - 30min)); -} - -void PropagateUploadFileCommon::slotJobDestroyed(QObject *job) -{ - _jobs.erase(std::remove(_jobs.begin(), _jobs.end(), job), _jobs.end()); -} - -// This function is used whenever there is an error occuring and jobs might be in progress -void PropagateUploadFileCommon::abortWithError(SyncFileItem::Status status, const QString &error) -{ - if (_aborting) - return; - abort(AbortType::Synchronous); - done(status, error); -} - -QMap PropagateUploadFileCommon::headers() -{ - QMap headers; - headers[QByteArrayLiteral("Content-Type")] = QByteArrayLiteral("application/octet-stream"); - headers[QByteArrayLiteral("X-OC-Mtime")] = QByteArray::number(qint64(_item->_modtime)); - if (qEnvironmentVariableIntValue("OWNCLOUD_LAZYOPS")) - headers[QByteArrayLiteral("OC-LazyOps")] = QByteArrayLiteral("true"); - - if (_item->_file.contains(QLatin1String(".sys.admin#recall#"))) { - // This is a file recall triggered by the admin. Note: the - // recall list file created by the admin and downloaded by the - // client (.sys.admin#recall#) also falls into this category - // (albeit users are not supposed to mess up with it) - - // We use a special tag header so that the server may decide to store this file away in some admin stage area - // And not directly in the user's area (which would trigger redownloads etc). - headers["OC-Tag"] = ".sys.admin#recall#"; - } - - if (!_item->_etag.isEmpty() && _item->_etag != "empty_etag" - && _item->_instruction != CSYNC_INSTRUCTION_NEW // On new files never send a If-Match - && _item->_instruction != CSYNC_INSTRUCTION_TYPE_CHANGE - && !_deleteExisting) { - // We add quotes because the owncloud server always adds quotes around the etag, and - // csync_owncloud.c's owncloud_file_id always strips the quotes. - headers[QByteArrayLiteral("If-Match")] = '"' + _item->_etag + '"'; - } - - // Set up a conflict file header pointing to the original file - auto conflictRecord = propagator()->_journal->conflictRecord(_item->_file.toUtf8()); - if (conflictRecord.isValid()) { - headers[QByteArrayLiteral("OC-Conflict")] = "1"; - if (!conflictRecord.initialBasePath.isEmpty()) - headers[QByteArrayLiteral("OC-ConflictInitialBasePath")] = conflictRecord.initialBasePath; - if (!conflictRecord.baseFileId.isEmpty()) - headers[QByteArrayLiteral("OC-ConflictBaseFileId")] = conflictRecord.baseFileId; - if (conflictRecord.baseModtime != -1) - headers[QByteArrayLiteral("OC-ConflictBaseMtime")] = QByteArray::number(conflictRecord.baseModtime); - if (!conflictRecord.baseEtag.isEmpty()) - headers[QByteArrayLiteral("OC-ConflictBaseEtag")] = conflictRecord.baseEtag; - } - - return headers; -} - -void PropagateUploadFileCommon::finalize() -{ - // Update the quota, if known - if (!_quotaUpdated) { - auto quotaIt = propagator()->_folderQuota.find(QFileInfo(_item->_file).path()); - if (quotaIt != propagator()->_folderQuota.end()) { - quotaIt.value() -= _item->_size; - } - _quotaUpdated = true; - } - - if (_item->_remotePerm.isNull()) { - qCWarning(lcPropagateUpload) << "PropagateUploadFileCommon::finalize: Missing permissions for" << propagator()->fullRemotePath(_item->_file); - auto permCheck = new PropfindJob(propagator()->account(), propagator()->fullRemotePath(_item->_file)); - _jobs.append(permCheck); - permCheck->setProperties({ "http://owncloud.org/ns:permissions" }); - connect(permCheck, &PropfindJob::result, this, [this, permCheck](const QMap &map) { - _item->_remotePerm = RemotePermissions::fromServerString(map.value(QStringLiteral("permissions"))); - finalize(); - slotJobDestroyed(permCheck); - }); - connect(permCheck, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); - permCheck->start(); - return; - } - - // Update the database entry - const auto result = propagator()->updateMetadata(*_item); - if (!result) { - done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error())); - return; - } else if (result.get() == Vfs::ConvertToPlaceholderResult::Locked) { - done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(_item->_file)); - return; - } - - // Files that were new on the remote shouldn't have online-only pin state - // even if their parent folder is online-only. - if (_item->_instruction == CSYNC_INSTRUCTION_NEW - || _item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE) { - auto &vfs = propagator()->syncOptions()._vfs; - const auto pin = vfs->pinState(_item->_file); - if (pin && *pin == PinState::OnlineOnly) { - vfs->setPinState(_item->_file, PinState::Unspecified); - } - } - - // Remove from the progress database: - propagator()->_journal->setUploadInfo(_item->_file, SyncJournalDb::UploadInfo()); - propagator()->_journal->commit(QStringLiteral("upload file start")); - - done(SyncFileItem::Success); -} - -void PropagateUploadFileCommon::abortNetworkJobs( - PropagatorJob::AbortType abortType, - const std::function &mayAbortJob) -{ - if (_aborting) - return; - _aborting = true; - - // Count the number of jobs that need aborting, and emit the overall - // abort signal when they're all done. - QSharedPointer runningCount(new int(0)); - auto oneAbortFinished = [this, runningCount]() { - (*runningCount)--; - if (*runningCount == 0) { - emit this->abortFinished(); - } - }; - - // Abort all running jobs, except for explicitly excluded ones - for (auto *job : qAsConst(_jobs)) { - auto reply = job->reply(); - if (!reply || !reply->isRunning()) - continue; - - (*runningCount)++; - - // If a job should not be aborted that means we'll never abort before - // the hard abort timeout signal comes as runningCount will never go to - // zero. - // We may however finish before that if the un-abortable job completes - // normally. - if (!mayAbortJob(job)) - continue; - - // Abort the job - if (abortType == AbortType::Asynchronous) { - // Connect to finished signal of job reply to asynchonously finish the abort - connect(reply, &QNetworkReply::finished, this, oneAbortFinished); - } - reply->abort(); - } - - if (*runningCount == 0 && abortType == AbortType::Asynchronous) - emit abortFinished(); -} -} diff --git a/src/libsync/propagateupload.h b/src/libsync/propagateupload.h deleted file mode 100644 index bd7d0158e76..00000000000 --- a/src/libsync/propagateupload.h +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 "owncloudpropagator.h" -#include "networkjobs.h" - -#include -#include -#include - -namespace OCC { - -Q_DECLARE_LOGGING_CATEGORY(lcPutJob) -Q_DECLARE_LOGGING_CATEGORY(lcPropagateUpload) -Q_DECLARE_LOGGING_CATEGORY(lcPropagateUploadV1) -Q_DECLARE_LOGGING_CATEGORY(lcPropagateUploadNG) - -class BandwidthManager; - -/** - * @brief The UploadDevice class - * @ingroup libsync - */ -class UploadDevice : public QIODevice -{ - Q_OBJECT -public: - UploadDevice(const QString &fileName, qint64 start, qint64 size, BandwidthManager *bwm); - ~UploadDevice() override; - - bool open(QIODevice::OpenMode mode) override; - void close() override; - - qint64 writeData(const char *, qint64) override; - qint64 readData(char *data, qint64 maxlen) override; - bool atEnd() const override; - qint64 size() const override; - qint64 bytesAvailable() const override; - bool isSequential() const override; - bool seek(qint64 pos) override; - - void setBandwidthLimited(bool); - bool isBandwidthLimited() { return _bandwidthLimited; } - void setChoked(bool); - bool isChoked() { return _choked; } - void giveBandwidthQuota(qint64 bwq); - -signals: - -private: - /// The local file to read data from - QFile _file; - - /// Start of the file data to use - qint64 _start = 0; - /// Amount of file data after _start to use - qint64 _size = 0; - /// Position between _start and _start+_size - qint64 _read = 0; - - // Bandwidth manager related - QPointer _bandwidthManager; - qint64 _bandwidthQuota; - qint64 _readWithProgress; - bool _bandwidthLimited; // if _bandwidthQuota will be used - bool _choked; // if upload is paused (readData() will return 0) - friend class BandwidthManager; -public slots: - void slotJobUploadProgress(qint64 sent, qint64 t); -}; - -/** - * @brief The PUTFileJob class - * @ingroup libsync - */ -class PUTFileJob : public AbstractNetworkJob -{ - Q_OBJECT - -private: - QIODevice *_device; - QMap _headers; - QString _errorString; - QUrl _url; - QElapsedTimer _requestTimer; - -public: - explicit PUTFileJob(AccountPtr account, const QString &path, std::unique_ptr device, - const QMap &headers, int chunk, QObject *parent = nullptr) - : AbstractNetworkJob(account, path, parent) - , _device(device.release()) - , _headers(headers) - , _chunk(chunk) - { - _device->setParent(this); - } - explicit PUTFileJob(AccountPtr account, const QUrl &url, std::unique_ptr device, - const QMap &headers, int chunk, QObject *parent = nullptr) - : AbstractNetworkJob(account, QString(), parent) - , _device(device.release()) - , _headers(headers) - , _url(url) - , _chunk(chunk) - { - _device->setParent(this); - } - ~PUTFileJob() override; - - int _chunk; - - void start() override; - - bool finished() override; - - QIODevice *device() - { - return _device; - } - - QString errorString() - { - return _errorString.isEmpty() ? AbstractNetworkJob::errorString() : _errorString; - } - - std::chrono::milliseconds msSinceStart() const - { - return std::chrono::milliseconds(_requestTimer.elapsed()); - } - -signals: - void finishedSignal(); - void uploadProgress(qint64, qint64); - -}; - -/** - * @brief The PropagateUploadFileCommon class is the code common between all chunking algorithms - * @ingroup libsync - * - * State Machine: - * - * +---> start() --> (delete job) -------+ - * | | - * +--> slotComputeContentChecksum() <---+ - * | - * v - * slotComputeTransmissionChecksum() - * | - * v - * slotStartUpload() -> doStartUpload() - * . - * . - * v - * finalize() or abortWithError() - */ -class PropagateUploadFileCommon : public PropagateItemJob -{ - Q_OBJECT - -protected: - QVector _jobs; /// network jobs that are currently in transit - bool _finished BITFIELD(1); /// Tells that all the jobs have been finished - bool _deleteExisting BITFIELD(1); - - /** Whether an abort is currently ongoing. - * - * Important to avoid duplicate aborts since each finishing PUTFileJob might - * trigger an abort on error. - */ - bool _aborting BITFIELD(1); - - QByteArray _transmissionChecksumHeader; - -public: - PropagateUploadFileCommon(OwncloudPropagator *propagator, const SyncFileItemPtr &item) - : PropagateItemJob(propagator, item) - , _finished(false) - , _deleteExisting(false) - , _aborting(false) - { - } - - /** - * Whether an existing entity with the same name may be deleted before - * the upload. - * - * Default: false. - */ - void setDeleteExisting(bool enabled); - - void start() override; - - bool isLikelyFinishedQuickly() override { return _item->_size < propagator()->smallFileSize(); } - -private slots: - void slotComputeContentChecksum(); - // Content checksum computed, compute the transmission checksum - void slotComputeTransmissionChecksum(const QByteArray &contentChecksumType, const QByteArray &contentChecksum); - // transmission checksum computed, prepare the upload - void slotStartUpload(const QByteArray &transmissionChecksumType, const QByteArray &transmissionChecksum); - -public: - virtual void doStartUpload() = 0; - - void finalize(); - void abortWithError(SyncFileItem::Status status, const QString &error); - -public slots: - void slotJobDestroyed(QObject *job); - -protected: - void done(SyncFileItem::Status status, const QString &errorString = QString()) override; - - /** - * Aborts all running network jobs, except for the ones that mayAbortJob - * returns false on and, for async aborts, emits abortFinished when done. - */ - void abortNetworkJobs( - AbortType abortType, - const std::function &mayAbortJob); - - /** - * Checks whether the current error is one that should reset the whole - * transfer if it happens too often. If so: Bump UploadInfo::errorCount - * and maybe perform the reset. - */ - void checkResettingErrors(); - - /** - * Error handling functionality that is shared between jobs. - */ - void commonErrorHandling(AbstractNetworkJob *job); - - /** - * Increases the timeout for the final MOVE/PUT for large files. - * - * This is an unfortunate workaround since the drawback is not being able to - * detect real disconnects in a timely manner. Shall go away when the server - * response starts coming quicker, or there is some sort of async api. - * - * See #6527, enterprise#2480 - */ - static void adjustLastJobTimeout(AbstractNetworkJob *job, qint64 fileSize); - - /** Bases headers that need to be sent on the PUT, or in the MOVE for chunking-ng */ - QMap headers(); - -private: - bool _quotaUpdated = false; -}; - -/** - * @ingroup libsync - * - * Propagation job, impementing the old chunking agorithm - * - */ -class PropagateUploadFileV1 : public PropagateUploadFileCommon -{ - Q_OBJECT - -private: - /** - * That's the start chunk that was stored in the database for resuming. - * In the non-resuming case it is 0. - * If we are resuming, this is the first chunk we need to send - */ - int _startChunk = 0; - /** - * This is the next chunk that we need to send. Starting from 0 even if _startChunk != 0 - * (In other words, _startChunk + _currentChunk is really the number of the chunk we need to send next) - * (In other words, _currentChunk is the number of the chunk that we already sent or started sending) - */ - int _currentChunk = 0; - int _chunkCount = 0; /// Total number of chunks for this file - uint _transferId = 0; /// transfer id (part of the url) - - qint64 chunkSize() const { - // Old chunking does not use dynamic chunking algorithm, and does not adjusts the chunk size respectively, - // thus this value should be used as the one classifing item to be chunked - return propagator()->syncOptions()._initialChunkSize; - } - -public: - PropagateUploadFileV1(OwncloudPropagator *propagator, const SyncFileItemPtr &item) - : PropagateUploadFileCommon(propagator, item) - { - } - - void doStartUpload() override; -public slots: - void abort(PropagatorJob::AbortType abortType) override; -private slots: - void startNextChunk(); - void slotPutFinished(); - void slotUploadProgress(qint64, qint64); -}; - -/** - * @ingroup libsync - * - * Propagation job, impementing the new chunking agorithm - * - */ -class PropagateUploadFileNG : public PropagateUploadFileCommon -{ - Q_OBJECT -private: - /** Amount of data that was already sent in bytes. - * - * If this job is resuming an upload, this number includes bytes that were - * sent in previous jobs. - */ - qint64 _sent = 0; - - /** Amount of data that needs to be sent to the server in bytes. - * - * For normal uploads this will be the file size. - * - * This value is intended to be comparable to _sent: it's always the total - * amount of data that needs to be present at the server to finish the upload - - * regardless of whether previous jobs have already sent something. - */ - qint64 _bytesToUpload; - - uint _transferId = 0; /// transfer id (part of the url) - qint64 _currentChunkOffset = 0; /// byte offset of the next chunk data that will be sent - qint64 _currentChunkSize = 0; /// current chunk size - bool _removeJobError = false; /// if not null, there was an error removing the job - - // Map chunk number with its size from the PROPFIND on resume. - // (Only used from slotPropfindIterate/slotPropfindFinished because the LsColJob use signals to report data.) - struct ServerChunkInfo - { - qint64 size; - QString originalName; - }; - QMap _serverChunks; - - // Vector with expected PUT ranges. - struct UploadRangeInfo - { - qint64 start; - qint64 size; - qint64 end() const { return start + size; } - }; - QVector _rangesToUpload; - - /** - * Return the URL of a chunk. - * If chunkOffset == -1, returns the URL of the parent folder containing the chunks - */ - QUrl chunkUrl(qint64 chunkOffset = -1); - - /** - * Finds the range starting at 'start' in _rangesToUpload and removes the first - * 'size' bytes from it. If it becomes empty, remove the range. - * - * Retuns false if no matching range was found. - */ - bool markRangeAsDone(qint64 start, qint64 size); - -public: - PropagateUploadFileNG(OwncloudPropagator *propagator, const SyncFileItemPtr &item); - void doStartUpload() override; - -private: - void doStartUploadNext(); - void startNewUpload(); - void startNextChunk(); - void doFinalMove(); -public slots: - void abort(AbortType abortType) override; -private slots: - void slotPropfindFinished(); - void slotPropfindFinishedWithError(); - void slotPropfindIterate(const QString &name, const QMap &properties); - void slotDeleteJobFinished(); - void slotMkColFinished(); - void slotPutFinished(); - void slotMoveJobFinished(); - void slotUploadProgress(qint64, qint64); -}; -} diff --git a/src/libsync/propagateuploadcommon.cpp b/src/libsync/propagateuploadcommon.cpp new file mode 100644 index 00000000000..9cbc18d04ea --- /dev/null +++ b/src/libsync/propagateuploadcommon.cpp @@ -0,0 +1,448 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "propagateuploadcommon.h" + +#include "account.h" +#include "common/asserts.h" +#include "common/checksums.h" +#include "common/syncjournaldb.h" +#include "common/syncjournalfilerecord.h" +#include "common/utility.h" +#include "filesystem.h" +#include "networkjobs.h" +#include "owncloudpropagator_p.h" +#include "propagateremotedelete.h" +#include "syncengine.h" + +#include "libsync/theme.h" + +#include +#include + +#include + +using namespace std::chrono_literals; + +namespace OCC { + +Q_LOGGING_CATEGORY(lcPropagateUpload, "sync.propagator.upload", QtInfoMsg) + +/** + * We do not want to upload files that are currently being modified. + * To avoid that, we don't upload files that have a modification time + * that is too close to the current time. + * + * This interacts with the msBetweenRequestAndSync delay in the folder + * manager. If that delay between file-change notification and sync + * has passed, we should accept the file for upload here. + */ +static bool fileIsStillChanging(const SyncFileItem &item) +{ + const QDateTime modtime = Utility::qDateTimeFromTime_t(item._modtime); + const auto secondsSinceMod = std::chrono::seconds(modtime.secsTo(QDateTime::currentDateTimeUtc())); + + return secondsSinceMod < SyncEngine::minimumFileAgeForUpload + // if the mtime is too much in the future we *do* upload the file + && secondsSinceMod > -1s; +} + +const QString PropagateUploadCommon::fileChangedMessage() +{ + return tr("Local file changed during sync. It will be resumed."); +} + +void PropagateUploadCommon::setDeleteExisting(bool enabled) +{ + _deleteExisting = enabled; +} + + +void PropagateUploadCommon::start() +{ + if (propagator()->_abortRequested) { + return; + } + + // Check if the specific file can be accessed + if (propagator()->hasCaseClashAccessibilityProblem(_item->_file)) { + done(SyncFileItem::NormalError, tr("File %1 cannot be uploaded because another file with the same name, differing only in case, exists").arg(QDir::toNativeSeparators(_item->_file))); + return; + } + + // Check if we believe that the upload will fail due to remote quota limits + const qint64 quotaGuess = propagator()->_folderQuota.value( + QFileInfo(_item->_file).path(), std::numeric_limits::max()); + if (_item->_size > quotaGuess) { + // Necessary for blacklisting logic + _item->_httpErrorCode = 507; + Q_EMIT propagator()->insufficientRemoteStorage(); + done(SyncFileItem::DetailError, tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_item->_size))); + return; + } + + propagator()->_activeJobList.append(this); + + if (!_deleteExisting) { + return slotComputeContentChecksum(); + } + + auto job = new DeleteJob(propagator()->account(), propagator()->webDavUrl(), propagator()->fullRemotePath(_item->_file), this); + addChildJob(job); + connect(job, &DeleteJob::finishedSignal, this, &PropagateUploadCommon::slotComputeContentChecksum); + job->start(); +} + +void PropagateUploadCommon::slotComputeContentChecksum() +{ + if (propagator()->_abortRequested) { + return; + } + + const QString filePath = propagator()->fullLocalPath(_item->_file); + + // remember the modtime before computing the checksum to be able to detect a file + // change during the checksum calculation + _item->_modtime = FileSystem::getModTime(filePath); + + const auto checksumType = propagator()->account()->capabilities().preferredUploadChecksumType(); + + // Maybe the discovery already computed the checksum? + const auto checksumHeader = ChecksumHeader::parseChecksumHeader(_item->_checksumHeader); + if (checksumHeader.type() == checksumType) { + slotComputeTransmissionChecksum(checksumType, checksumHeader.checksum()); + return; + } + + // we must be able to read the file + if (FileSystem::isFileLocked(filePath, FileSystem::LockMode::SharedRead)) { + Q_EMIT propagator()->seenLockedFile(filePath, FileSystem::LockMode::SharedRead); + abortWithError(SyncFileItem::SoftError, tr("%1 the file is currently in use").arg(filePath)); + return; + } + + // Compute the content checksum. + auto computeChecksum = new ComputeChecksum(this); + computeChecksum->setChecksumType(checksumType); + + connect(computeChecksum, &ComputeChecksum::done, + this, &PropagateUploadCommon::slotComputeTransmissionChecksum); + connect(computeChecksum, &ComputeChecksum::done, + computeChecksum, &QObject::deleteLater); + computeChecksum->start(filePath); +} + +void PropagateUploadCommon::slotComputeTransmissionChecksum(CheckSums::Algorithm contentChecksumType, const QByteArray &contentChecksum) +{ + _item->_checksumHeader = ChecksumHeader(contentChecksumType, contentChecksum).makeChecksumHeader(); + + // Reuse the content checksum as the transmission checksum if possible + const auto supportedTransmissionChecksums = + propagator()->account()->capabilities().supportedChecksumTypes(); + if (supportedTransmissionChecksums.contains(contentChecksumType)) { + slotStartUpload(contentChecksumType, contentChecksum); + return; + } + + const QString filePath = propagator()->fullLocalPath(_item->_file); + // we must be able to read the file + if (FileSystem::isFileLocked(filePath, FileSystem::LockMode::SharedRead)) { + Q_EMIT propagator()->seenLockedFile(filePath, FileSystem::LockMode::SharedRead); + abortWithError(SyncFileItem::SoftError, tr("%1 the file is currently in use").arg(filePath)); + return; + } + + // Compute the transmission checksum. + auto computeChecksum = new ComputeChecksum(this); + if (uploadChecksumEnabled()) { + computeChecksum->setChecksumType(propagator()->account()->capabilities().uploadChecksumType()); + } else { + computeChecksum->setChecksumType(CheckSums::Algorithm::PARSE_ERROR); + } + + connect(computeChecksum, &ComputeChecksum::done, + this, &PropagateUploadCommon::slotStartUpload); + connect(computeChecksum, &ComputeChecksum::done, + computeChecksum, &QObject::deleteLater); + computeChecksum->start(filePath); +} + +void PropagateUploadCommon::slotStartUpload(CheckSums::Algorithm transmissionChecksumType, const QByteArray &transmissionChecksum) +{ + // Remove ourselves from the list of active job, before any possible call to done() + // When we start chunks, we will add it again, once for every chunk. + propagator()->_activeJobList.removeOne(this); + + _transmissionChecksumHeader = ChecksumHeader(transmissionChecksumType, transmissionChecksum).makeChecksumHeader(); + + // If no checksum header was not set, reuse the transmission checksum as the content checksum. + if (_item->_checksumHeader.isEmpty()) { + _item->_checksumHeader = _transmissionChecksumHeader; + } + + const QString fullFilePath = propagator()->fullLocalPath(_item->_file); + + if (!FileSystem::fileExists(fullFilePath)) { + done(SyncFileItem::SoftError, tr("File Removed")); + return; + } + _item->_size = FileSystem::getSize(QFileInfo{fullFilePath}); + + const time_t prevModtime = _item->_modtime; // the _item value was set in PropagateUploadFile::start() + // but a potential checksum calculation could have taken some time during which the file could + // have been changed again, so better check again here. + + // But skip the file if the mtime is too close to 'now'! + // That usually indicates a file that is still being changed + // or not yet fully copied to the destination. + _item->_modtime = FileSystem::getModTime(fullFilePath); + if (prevModtime != _item->_modtime || fileIsStillChanging(*_item)) { + propagator()->_anotherSyncNeeded = true; + done(SyncFileItem::Message, fileChangedMessage()); + return; + } + + doStartUpload(); +} + +void PropagateUploadCommon::done(SyncFileItem::Status status, const QString &errorString) +{ + _finished = true; + PropagateItemJob::done(status, errorString); +} + +void PropagateUploadCommon::checkResettingErrors() +{ + if (_item->_httpErrorCode == 412 + || propagator()->account()->capabilities().httpErrorCodesThatResetFailingChunkedUploads().contains(_item->_httpErrorCode)) { + auto uploadInfo = propagator()->_journal->getUploadInfo(_item->_file); + uploadInfo._errorCount += 1; + if (uploadInfo._errorCount > 3) { + qCInfo(lcPropagateUpload) << "Reset transfer of" << _item->_file + << "due to repeated error" << _item->_httpErrorCode; + uploadInfo = SyncJournalDb::UploadInfo(); + } else { + qCInfo(lcPropagateUpload) << "Error count for maybe-reset error" << _item->_httpErrorCode + << "on file" << _item->_file + << "is" << uploadInfo._errorCount; + } + propagator()->_journal->setUploadInfo(_item->_file, uploadInfo); + propagator()->_journal->commit(QStringLiteral("Upload info")); + } +} + +void PropagateUploadCommon::commonErrorHandling(AbstractNetworkJob *job) +{ + QByteArray replyContent; + QString errorString = job->errorStringParsingBody(&replyContent); + qCDebug(lcPropagateUpload) << replyContent; // display the XML error in the debug + + if (_item->_httpErrorCode == 412) { + // Precondition Failed: Either an etag or a checksum mismatch. + + // Maybe the bad etag is in the database, we need to clear the + // parent folder etag so we won't read from DB next sync. + propagator()->_journal->schedulePathForRemoteDiscovery(_item->_file); + propagator()->_anotherSyncNeeded = true; + } + + // Ensure errors that should eventually reset the chunked upload are tracked. + checkResettingErrors(); + + SyncFileItem::Status status = classifyError(job->reply()->error(), _item->_httpErrorCode, &(propagator()->_anotherSyncNeeded), replyContent); + + // Insufficient remote storage. + if (_item->_httpErrorCode == 507) { + // Update the quota expectation + const auto path = QFileInfo(_item->_file).path(); + auto quotaIt = propagator()->_folderQuota.find(path); + if (quotaIt != propagator()->_folderQuota.end()) { + quotaIt.value() = qMin(quotaIt.value(), _item->_size - 1); + } else { + propagator()->_folderQuota[path] = _item->_size - 1; + } + + // Set up the error + status = SyncFileItem::DetailError; + errorString = tr("Upload of %1 exceeds the quota for the folder").arg(Utility::octetsToString(_item->_size)); + Q_EMIT propagator()->insufficientRemoteStorage(); + } + + abortWithError(status, errorString); +} + +void PropagateUploadCommon::adjustLastJobTimeout(AbstractNetworkJob *job, qint64 fileSize) +{ + // Calculate 3 minutes for each gigabyte of data + const auto timeout = std::chrono::minutes(static_cast((3min).count() * fileSize / 1e9)); + // Maximum of 30 minutes + // not less than the default/current val + if (timeout > job->timeoutSec() && timeout < 30min) { + job->setTimeout(timeout); + } +} + +// This function is used whenever there is an error occurring and jobs might be in progress +void PropagateUploadCommon::abortWithError(SyncFileItem::Status status, const QString &error) +{ + qCWarning(lcPropagateUpload) << Q_FUNC_INFO << _item->_file << error; + if (!_aborting) { + abort(AbortType::Synchronous); + done(status, error); + } +} + +void PropagateUploadCommon::addChildJob(AbstractNetworkJob *job) +{ + _childJobs.insert(job); + connect( + job, &AbstractNetworkJob::aboutToFinishSignal, this, [job, this] { + _childJobs.erase(job); + }, + Qt::DirectConnection); +} + +QMap PropagateUploadCommon::headers() +{ + QMap headers; + headers[QByteArrayLiteral("Content-Type")] = QByteArrayLiteral("application/octet-stream"); + headers[QByteArrayLiteral("X-OC-Mtime")] = QByteArray::number(qint64(_item->_modtime)); + + if (!_item->_etag.isEmpty() && _item->_etag != QLatin1String("empty_etag") + && (_item->instruction() & ~(CSYNC_INSTRUCTION_NEW | CSYNC_INSTRUCTION_TYPE_CHANGE)) // On new files never send an "If-Match" + && !_deleteExisting) { + // We add quotes because the owncloud server always adds quotes around the etag, and + // csync_owncloud.c's owncloud_file_id always strips the quotes. + headers[QByteArrayLiteral("If-Match")] = QStringLiteral("\"%1\"").arg(_item->_etag).toUtf8(); + } + + // Set up a conflict file header pointing to the original file + auto conflictRecord = propagator()->_journal->conflictRecord(_item->_file.toUtf8()); + if (conflictRecord.isValid()) { + headers[QByteArrayLiteral("OC-Conflict")] = "1"; + if (!conflictRecord.initialBasePath.isEmpty()) + headers[QByteArrayLiteral("OC-ConflictInitialBasePath")] = conflictRecord.initialBasePath; + if (!conflictRecord.baseFileId.isEmpty()) + headers[QByteArrayLiteral("OC-ConflictBaseFileId")] = conflictRecord.baseFileId; + if (conflictRecord.baseModtime != -1) + headers[QByteArrayLiteral("OC-ConflictBaseMtime")] = QByteArray::number(conflictRecord.baseModtime); + if (!conflictRecord.baseEtag.isEmpty()) + headers[QByteArrayLiteral("OC-ConflictBaseEtag")] = conflictRecord.baseEtag; + } + + return headers; +} + +void PropagateUploadCommon::finalize() +{ + OC_ENFORCE(state() != Finished && propagator()->syncOptions().isValid()); + + // Update the quota, if known + if (!_quotaUpdated) { + auto quotaIt = propagator()->_folderQuota.find(QFileInfo(_item->_file).path()); + if (quotaIt != propagator()->_folderQuota.end()) { + quotaIt.value() -= _item->_size; + } + _quotaUpdated = true; + } + + if (_item->_remotePerm.isNull()) { + qCWarning(lcPropagateUpload) << "PropagateUploadCommon::finalize: Missing permissions for" << propagator()->fullRemotePath(_item->_file); + auto *permCheck = + new PropfindJob(propagator()->account(), propagator()->webDavUrl(), propagator()->fullRemotePath(_item->_file), PropfindJob::Depth::Zero, this); + addChildJob(permCheck); + permCheck->setProperties({ "http://owncloud.org/ns:permissions" }); + connect(permCheck, &PropfindJob::directoryListingIterated, this, [this](const QString &, const QMap &map) { + _item->_remotePerm = RemotePermissions::fromServerString(map.value(QStringLiteral("permissions"))); + Q_ASSERT(!_item->_remotePerm.isNull()); + finalize(); + }); + connect(permCheck, &PropfindJob::finishedWithError, this, + [this] { done(SyncFileItem::FatalError, tr("The server did not provide the file permissions")); }); + permCheck->start(); + return; + } + + // Update the database entry + const auto result = propagator()->updateMetadata(*_item); + if (!result) { + done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error())); + return; + } + + // Files that were new on the remote shouldn't have online-only pin state + // even if their parent folder is online-only. + if (_item->instruction() & (CSYNC_INSTRUCTION_NEW | CSYNC_INSTRUCTION_TYPE_CHANGE)) { + Vfs *vfs = propagator()->syncOptions().vfs(); + const auto pin = vfs->pinState(_item->_file); + if (pin && *pin == PinState::OnlineOnly) { + std::ignore = vfs->setPinState(_item->_file, PinState::Unspecified); + } + } + + // Remove from the progress database: + propagator()->_journal->setUploadInfo(_item->_file, SyncJournalDb::UploadInfo()); + propagator()->_journal->commit(QStringLiteral("upload file start")); + + done(SyncFileItem::Success); +} + +void PropagateUploadCommon::abortNetworkJobs( + PropagatorJob::AbortType abortType, + const std::function &mayAbortJob) +{ + if (_aborting) + return; + _aborting = true; + + // Count the number of jobs that need aborting, and Q_EMIT the overall + // abort signal when they're all done. + QSharedPointer runningCount(new int(0)); + auto oneAbortFinished = [this, runningCount]() { + (*runningCount)--; + if (*runningCount == 0) { + Q_EMIT this->abortFinished(); + } + }; + + // Abort all running jobs, except for explicitly excluded ones + // perform actions on a copy as aborted jobs will be removed from _childJobs + const auto children = _childJobs; + for (auto *job : children) { + auto reply = job->reply(); + if (!reply || !reply->isRunning()) + continue; + + (*runningCount)++; + + // If a job should not be aborted that means we'll never abort before + // the hard abort timeout signal comes as runningCount will never go to + // zero. + // We may however finish before that if the un-abortable job completes + // normally. + if (!mayAbortJob(job)) + continue; + + // Abort the job + if (abortType == AbortType::Asynchronous) { + // Connect to finished signal of job reply to asynchronously finish the abort + connect(reply, &QNetworkReply::finished, this, oneAbortFinished); + } + job->abort(); + } + + if (*runningCount == 0 && abortType == AbortType::Asynchronous) + Q_EMIT abortFinished(); +} +} diff --git a/src/libsync/propagateuploadcommon.h b/src/libsync/propagateuploadcommon.h new file mode 100644 index 00000000000..110638b4b6c --- /dev/null +++ b/src/libsync/propagateuploadcommon.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "owncloudpropagator.h" +#include "networkjobs.h" + +#include + +namespace OCC { + +Q_DECLARE_LOGGING_CATEGORY(lcPropagateUpload) + +/** + * @brief The PropagateUploadFileCommon class is the code common between all chunking algorithms + * @ingroup libsync + * + * State Machine: + * + * +---> start() --> (delete job) -------+ + * | | + * +--> slotComputeContentChecksum() <---+ + * | + * v + * slotComputeTransmissionChecksum() + * | + * v + * slotStartUpload() -> doStartUpload() + * . + * . + * v + * finalize() or abortWithError() + */ +class PropagateUploadCommon : public PropagateItemJob +{ + Q_OBJECT + +protected: + static const QString fileChangedMessage(); + + QVector _jobs; /// network jobs that are currently in transit + bool _finished; /// Tells that all the jobs have been finished + bool _deleteExisting; + + /** Whether an abort is currently ongoing. + * + * Important to avoid duplicate aborts since each finishing PUTFileJob might + * trigger an abort on error. + */ + bool _aborting; + + QByteArray _transmissionChecksumHeader; + +public: + PropagateUploadCommon(OwncloudPropagator *propagator, const SyncFileItemPtr &item) + : PropagateItemJob(propagator, item) + , _finished(false) + , _deleteExisting(false) + , _aborting(false) + { + } + + /** + * Whether an existing entity with the same name may be deleted before + * the upload. + * + * Default: false. + */ + void setDeleteExisting(bool enabled); + + void start() override; + + bool isLikelyFinishedQuickly() override { return _item->_size < propagator()->smallFileSize(); } + +private Q_SLOTS: + void slotComputeContentChecksum(); + // Content checksum computed, compute the transmission checksum + void slotComputeTransmissionChecksum(CheckSums::Algorithm contentChecksumType, const QByteArray &contentChecksum); + // transmission checksum computed, prepare the upload + void slotStartUpload(CheckSums::Algorithm transmissionChecksumType, const QByteArray &transmissionChecksum); + +public: + virtual void doStartUpload() = 0; + + void finalize(); + void abortWithError(SyncFileItem::Status status, const QString &error); + + /*** + * Add job to the list of children + * The job is automatically removed from the children once it's done. + * It is important that this function is called before any connects. + */ + void addChildJob(AbstractNetworkJob *job); + + const auto &childJobs() const + { + return _childJobs; + } + + +protected: + void done(SyncFileItem::Status status, const QString &errorString = QString()) override; + + /** + * Aborts all running network jobs, except for the ones that mayAbortJob + * returns false on and, for async aborts, emits abortFinished when done. + */ + void abortNetworkJobs( + AbortType abortType, + const std::function &mayAbortJob); + + /** + * Checks whether the current error is one that should reset the whole + * transfer if it happens too often. If so: Bump UploadInfo::errorCount + * and maybe perform the reset. + */ + void checkResettingErrors(); + + /** + * Error handling functionality that is shared between jobs. + */ + void commonErrorHandling(AbstractNetworkJob *job); + + /** + * Increases the timeout for the final MOVE/PUT for large files. + * + * This is an unfortunate workaround since the drawback is not being able to + * detect real disconnects in a timely manner. Shall go away when the server + * response starts coming quicker, or there is some sort of async api. + * + * See #6527, enterprise#2480 + */ + static void adjustLastJobTimeout(AbstractNetworkJob *job, qint64 fileSize); + + /** Bases headers that need to be sent on the PUT, or in the MOVE for chunking-ng */ + QMap headers(); + +private: + bool _quotaUpdated = false; + + std::unordered_set _childJobs; /// network jobs that are currently in transit +}; +} diff --git a/src/libsync/propagateuploadfile.cpp b/src/libsync/propagateuploadfile.cpp new file mode 100644 index 00000000000..a2f7bb43bf7 --- /dev/null +++ b/src/libsync/propagateuploadfile.cpp @@ -0,0 +1,212 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "propagateuploadfile.h" +#include "account.h" +#include "common/checksums.h" +#include "common/syncjournaldb.h" +#include "filesystem.h" +#include "owncloudpropagator_p.h" +#include "propagatorjobs.h" +#include "putfilejob.h" +#include "syncengine.h" +#include "uploaddevice.h" + +#include +#include +#include + +#include + +namespace OCC { + +void PropagateUploadFile::doStartUpload() +{ + const QString fileName = propagator()->fullLocalPath(_item->_file); + // If the file is currently locked, we want to retry the sync + // when it becomes available again. + if (FileSystem::isFileLocked(fileName, FileSystem::LockMode::SharedRead)) { + Q_EMIT propagator()->seenLockedFile(fileName, FileSystem::LockMode::SharedRead); + abortWithError(SyncFileItem::SoftError, tr("%1 the file is currently in use").arg(QDir::toNativeSeparators(fileName))); + return; + } + + const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file); + + // If there is only one chunk, write the checksum in the database, so if the PUT is sent + // to the server, but the connection drops before we get the etag, we can check the checksum + // in reconcile (issue #5106) + auto pi = _item->toUploadInfo(); + pi._chunk = 0; + pi._transferid = 0; // We set a null transfer id because it is not chunked. + pi._errorCount = 0; + propagator()->_journal->setUploadInfo(_item->_file, pi); + propagator()->_journal->commit(QStringLiteral("Upload info")); + + propagator()->reportProgress(*_item, 0); + startUpload(); +} + +void PropagateUploadFile::startUpload() +{ + if (propagator()->_abortRequested) + return; + + qint64 fileSize = _item->_size; + auto headers = PropagateUploadCommon::headers(); + headers[QByteArrayLiteral("OC-Total-Length")] = QByteArray::number(fileSize); + + QString path = _item->_file; + + if (!_transmissionChecksumHeader.isEmpty()) { + qCInfo(lcPropagateUpload) << propagator()->fullRemotePath(path) << _transmissionChecksumHeader; + headers[checkSumHeaderC] = _transmissionChecksumHeader; + } + + const QString fileName = propagator()->fullLocalPath(_item->_file); + auto device = std::make_unique(fileName, 0, fileSize); + if (!device->open(QIODevice::ReadOnly)) { + qCWarning(lcPropagateUpload) << "Could not prepare upload device: " << device->errorString(); + // Soft error because this is likely caused by the user modifying his files while syncing + abortWithError(SyncFileItem::SoftError, device->errorString()); + return; + } + + // TODO: review usage of scoped pointer vs qt memory management + // job takes ownership of device via a QScopedPointer. Job deletes itself when finishing + PUTFileJob *job = new PUTFileJob(propagator()->account(), propagator()->webDavUrl(), propagator()->fullRemotePath(path), std::move(device), headers, this); + addChildJob(job); + connect(job, &PUTFileJob::finishedSignal, this, &PropagateUploadFile::slotPutFinished); + connect(job, &PUTFileJob::uploadProgress, this, &PropagateUploadFile::slotUploadProgress); + adjustLastJobTimeout(job, fileSize); + job->start(); + propagator()->_activeJobList.append(this); + + propagator()->scheduleNextJob(); +} + +void PropagateUploadFile::slotPutFinished() +{ + PUTFileJob *job = qobject_cast(sender()); + Q_ASSERT(job); + + propagator()->_activeJobList.removeOne(this); + + if (_finished) { + // We have sent the finished signal already. We don't need to handle any remaining jobs + return; + } + + _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + _item->_responseTimeStamp = job->responseTimestamp(); + _item->_requestId = job->requestId(); + QNetworkReply::NetworkError err = job->reply()->error(); + if (err != QNetworkReply::NoError) { + commonErrorHandling(job); + return; + } + + if (_item->_httpErrorCode == 202) { + done(SyncFileItem::NormalError, tr("The server did ask for a removed legacy feature(polling)")); + return; + } + + // Check the file again post upload. + // Two cases must be considered separately: If the upload is finished, + // the file is on the server and has a changed ETag. In that case, + // the etag has to be properly updated in the client journal, and because + // of that we can bail out here with an error. But we can reschedule a + // sync ASAP. + // But if the upload is ongoing, because not all chunks were uploaded + // yet, the upload can be stopped and an error can be displayed, because + // the server hasn't registered the new file yet. + QString etag = getEtagFromReply(job->reply()); + _finished = etag.length() > 0; + + // Check if the file still exists + const QString fullFilePath(propagator()->fullLocalPath(_item->_file)); + if (!FileSystem::fileExists(fullFilePath)) { + if (!_finished) { + abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync.")); + return; + } + propagator()->_anotherSyncNeeded = true; + } + + // Check whether the file changed since discovery. + if (FileSystem::fileChanged(QFileInfo{fullFilePath}, _item->_size, _item->_modtime)) { + propagator()->_anotherSyncNeeded = true; + if (!_finished) { + abortWithError(SyncFileItem::Message, fileChangedMessage()); + return; + } + } + + if (!_finished) { + if (!childJobs().empty()) { + // just wait for the other job to finish. + return; + } + done(SyncFileItem::NormalError, tr("The server did not acknowledge the file upload. (No e-tag was present)")); + return; + } + + // the file id should only be empty for new files up- or downloaded + QByteArray fid = job->reply()->rawHeader("OC-FileID"); + if (!fid.isEmpty()) { + if (!_item->_fileId.isEmpty() && _item->_fileId != fid) { + qCWarning(lcPropagateUpload) << "File ID changed!" << _item->_fileId << fid; + } + _item->_fileId = fid; + } + + _item->_etag = etag; + + if (job->reply()->rawHeader("X-OC-MTime") != "accepted") { + // X-OC-MTime is supported since owncloud 5.0. But not when chunking. + // Normally Owncloud 6 always puts X-OC-MTime + qCWarning(lcPropagateUpload) << "Server does not support X-OC-MTime" << job->reply()->rawHeader("X-OC-MTime"); + // Well, the mtime was not set + } + + finalize(); +} + + +void PropagateUploadFile::slotUploadProgress(qint64 sent, qint64 total) +{ + // Completion is signaled with sent=0, total=0; avoid accidentally + // resetting progress due to sent bytes being zero by ignoring it. + // finishedSignal() is bound to be emitted soon anyway. + // See https://bugreports.qt.io/browse/QTBUG-44782. + if (sent == 0 && total == 0) { + return; + } + + propagator()->reportProgress(*_item, sent); +} + +void PropagateUploadFile::abort(PropagatorJob::AbortType abortType) +{ + abortNetworkJobs(abortType, [abortType](AbstractNetworkJob *job) { + if (PUTFileJob *putJob = qobject_cast(job)) { + if (abortType == AbortType::Asynchronous && putJob->device()->atEnd()) { + return false; + } + } + return true; + }); +} + +} diff --git a/src/libsync/propagateuploadfile.h b/src/libsync/propagateuploadfile.h new file mode 100644 index 00000000000..f449dda747a --- /dev/null +++ b/src/libsync/propagateuploadfile.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "owncloudpropagator.h" +#include "networkjobs.h" +#include "propagateuploadcommon.h" + +#include + +namespace OCC { + +/** + * @ingroup libsync + * + * Propagation job, simple PUT upload. + * + */ +class PropagateUploadFile : public PropagateUploadCommon +{ + Q_OBJECT + +public: + PropagateUploadFile(OwncloudPropagator *propagator, const SyncFileItemPtr &item) + : PropagateUploadCommon(propagator, item) + { + } + + void doStartUpload() override; +public Q_SLOTS: + void abort(PropagatorJob::AbortType abortType) override; +private Q_SLOTS: + void startUpload(); + void slotPutFinished(); + void slotUploadProgress(qint64, qint64); +}; +} diff --git a/src/libsync/propagateuploadng.cpp b/src/libsync/propagateuploadng.cpp deleted file mode 100644 index 5201e325a19..00000000000 --- a/src/libsync/propagateuploadng.cpp +++ /dev/null @@ -1,591 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 "config.h" -#include "propagateupload.h" -#include "owncloudpropagator_p.h" -#include "networkjobs.h" -#include "account.h" -#include "common/syncjournaldb.h" -#include "common/syncjournalfilerecord.h" -#include "common/utility.h" -#include "filesystem.h" -#include "propagatorjobs.h" -#include "syncengine.h" -#include "propagateremotemove.h" -#include "propagateremotedelete.h" -#include "common/asserts.h" - -#include -#include -#include -#include - -#include -#include -#include - -namespace OCC { - -QUrl PropagateUploadFileNG::chunkUrl(qint64 chunkOffset) -{ - QString path = QLatin1String("remote.php/dav/uploads/") - + propagator()->account()->davUser() - + QLatin1Char('/') + QString::number(_transferId); - if (chunkOffset != -1) { - // We need to do add leading 0 because the server orders the chunk alphabetically - path += QLatin1Char('/') + QString::number(chunkOffset).rightJustified(16, QLatin1Char('0')); // 1e16 is 10 petabyte - } - return Utility::concatUrlPath(propagator()->account()->url(), path); -} - - -/* -State machine: - - +---> doStartUpload() - doStartUploadNext() - Check the db: is there an entry? - + + - |no |yes - | v - v PROPFIND - startNewUpload() <-+ +-------------------------------------+ - + | + | - MKCOL + slotPropfindFinishedWithError() slotPropfindFinished() - + Is there stale files to remove? - slotMkColFinished() + + - + no yes - | + + - | | DeleteJob - | | + - +-----+^------------------------------------------------------+^--+ slotDeleteJobFinished() - | - | - | - +----> startNextChunk() +-> finished? +- - ^ + | - +---------------+ | - | - +----------------------------------------+ - | - +-> MOVE +-----> moveJobFinished() +--> finalize() - */ - -PropagateUploadFileNG::PropagateUploadFileNG(OwncloudPropagator *propagator, const SyncFileItemPtr &item) - : PropagateUploadFileCommon(propagator, item) - , _bytesToUpload(item->_size) -{ -} -void PropagateUploadFileNG::doStartUpload() -{ - propagator()->_activeJobList.append(this); - - UploadRangeInfo rangeinfo = { 0, _item->_size }; - _rangesToUpload.append(rangeinfo); - _bytesToUpload = _item->_size; - doStartUploadNext(); -} - - -void PropagateUploadFileNG::doStartUploadNext() -{ - const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file); - if (progressInfo._valid && progressInfo.isChunked() && progressInfo._modtime == _item->_modtime - && progressInfo._size == _item->_size) { - _transferId = progressInfo._transferid; - auto url = chunkUrl(); - auto job = new LsColJob(propagator()->account(), url, this); - _jobs.append(job); - job->setProperties(QList() << "resourcetype" - << "getcontentlength"); - connect(job, &LsColJob::finishedWithoutError, this, &PropagateUploadFileNG::slotPropfindFinished); - connect(job, &LsColJob::finishedWithError, - this, &PropagateUploadFileNG::slotPropfindFinishedWithError); - connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); - connect(job, &LsColJob::directoryListingIterated, - this, &PropagateUploadFileNG::slotPropfindIterate); - job->start(); - return; - } else if (progressInfo._valid && progressInfo.isChunked()) { - // The upload info is stale. remove the stale chunks on the server - _transferId = progressInfo._transferid; - // Fire and forget. Any error will be ignored. - (new DeleteJob(propagator()->account(), chunkUrl(), this))->start(); - // startNewUpload will reset the _transferId and the UploadInfo in the db. - } - - startNewUpload(); -} - -void PropagateUploadFileNG::slotPropfindIterate(const QString &name, const QMap &properties) -{ - if (name == chunkUrl().path()) { - return; // skip the info about the path itself - } - bool ok = false; - QString chunkName = name.mid(name.lastIndexOf(QLatin1Char('/')) + 1); - qint64 chunkOffset = chunkName.toLongLong(&ok); - if (ok) { - ServerChunkInfo chunkinfo = { properties[QStringLiteral("getcontentlength")].toLongLong(), chunkName }; - _serverChunks[chunkOffset] = chunkinfo; - } -} - - -bool PropagateUploadFileNG::markRangeAsDone(qint64 start, qint64 size) -{ - bool found = false; - for (auto iter = _rangesToUpload.begin(); iter != _rangesToUpload.end(); ++iter) { - /* Only remove if they start at exactly the same chunk */ - if (iter->start == start && iter->size >= size) { - found = true; - iter->start += size; - iter->size -= size; - if (iter->size <= 0) { - _rangesToUpload.erase(iter); - break; - } - } - } - - return found; -} - -void PropagateUploadFileNG::slotPropfindFinished() -{ - auto job = qobject_cast(sender()); - slotJobDestroyed(job); // remove it from the _jobs list - propagator()->_activeJobList.removeOne(this); - - _currentChunkOffset = 0; - _sent = 0; - - // here is a copy because we might need to remove item(s) during iteration - const auto serverChunks = _serverChunks; - for (auto it = serverChunks.cbegin(); it != serverChunks.cend(); ++it) { - const auto &chunkOffset = it.key(); - const auto &chunkSize = it.value().size; - if (markRangeAsDone(chunkOffset, chunkSize)) { - qCDebug(lcPropagateUploadNG) << "Reusing existing data:" << chunkOffset << chunkSize; - _sent += chunkSize; - _serverChunks.remove(chunkOffset); - } else { - qCDebug(lcPropagateUploadNG) << "Discarding existing data:" << chunkOffset << chunkSize; - } - } - - if (_sent > _bytesToUpload) { - // Normally this can't happen because the size is xor'ed with the transfer id, and it is - // therefore impossible that there is more data on the server than on the file. - qCCritical(lcPropagateUploadNG) << "Inconsistency while resuming " << _item->_file - << ": the size on the server (" << _sent << ") is bigger than the size of the file (" - << _item->_size << ")"; - - // Wipe the old chunking data. - // Fire and forget. Any error will be ignored. - (new DeleteJob(propagator()->account(), chunkUrl(), this))->start(); - - propagator()->_activeJobList.append(this); - startNewUpload(); - return; - } - - qCInfo(lcPropagateUploadNG) << "Resuming " << _item->_file << "; sent =" << _sent << "; total=" << _bytesToUpload; - - if (!_serverChunks.isEmpty()) { - qCInfo(lcPropagateUploadNG) << "To Delete" << _serverChunks.keys(); - propagator()->_activeJobList.append(this); - _removeJobError = false; - - // Make sure that if there is a "hole" and then a few more chunks, on the server - // we should remove the later chunks. Otherwise when we do dynamic chunk sizing, we may end up - // with corruptions if there are too many chunks, or if we abort and there are still stale chunks. - for (auto it = _serverChunks.begin(); it != _serverChunks.end(); ++it) { - auto job = new DeleteJob(propagator()->account(), Utility::concatUrlPath(chunkUrl(), it->originalName), this); - QObject::connect(job, &DeleteJob::finishedSignal, this, &PropagateUploadFileNG::slotDeleteJobFinished); - _jobs.append(job); - job->start(); - } - _serverChunks.clear(); - return; - } - - startNextChunk(); -} - -void PropagateUploadFileNG::slotPropfindFinishedWithError() -{ - auto job = qobject_cast(sender()); - _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - _item->_responseTimeStamp = job->responseTimestamp(); - _item->_requestId = job->requestId(); - slotJobDestroyed(job); // remove it from the _jobs list - - QNetworkReply::NetworkError err = job->reply()->error(); - auto status = classifyError(err, _item->_httpErrorCode, &propagator()->_anotherSyncNeeded); - if (status == SyncFileItem::FatalError) { - propagator()->_activeJobList.removeOne(this); - abortWithError(status, job->errorStringParsingBody()); - return; - } - startNewUpload(); -} - -void PropagateUploadFileNG::slotDeleteJobFinished() -{ - auto job = qobject_cast(sender()); - OC_ASSERT(job); - _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - _item->_responseTimeStamp = job->responseTimestamp(); - _item->_requestId = job->requestId(); - slotJobDestroyed(job); - - QNetworkReply::NetworkError err = job->reply()->error(); - if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) { - const int httpStatus = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - SyncFileItem::Status status = classifyError(err, httpStatus); - if (status == SyncFileItem::FatalError) { - abortWithError(status, job->errorString()); - return; - } else { - qCWarning(lcPropagateUploadNG) << "DeleteJob errored out" << job->errorString() << job->reply()->url(); - _removeJobError = true; - // Let the other jobs finish - } - } - - // If no more Delete jobs are running, we can continue - bool runningDeleteJobs = false; - for (auto *otherJob : qAsConst(_jobs)) { - if (qobject_cast(otherJob)) - runningDeleteJobs = true; - } - if (!runningDeleteJobs) { - propagator()->_activeJobList.removeOne(this); - if (_removeJobError) { - // There was an error removing some files, just start over - startNewUpload(); - } else { - startNextChunk(); - } - } -} - -void PropagateUploadFileNG::startNewUpload() -{ - OC_ASSERT(propagator()->_activeJobList.count(this) == 1); - _transferId = QRandomGenerator::global()->generate(); - _sent = 0; - - propagator()->reportProgress(*_item, 0); - - SyncJournalDb::UploadInfo pi; - pi._valid = true; - pi._transferid = _transferId; - pi._modtime = _item->_modtime; - pi._contentChecksum = _item->_checksumHeader; - pi._size = _item->_size; - propagator()->_journal->setUploadInfo(_item->_file, pi); - propagator()->_journal->commit(QStringLiteral("Upload info")); - QMap headers; - headers["OC-Total-Length"] = QByteArray::number(_item->_size); - auto job = new MkColJob(propagator()->account(), chunkUrl(), headers, this); - connect(job, &MkColJob::finishedWithError, - this, &PropagateUploadFileNG::slotMkColFinished); - connect(job, &MkColJob::finishedWithoutError, - this, &PropagateUploadFileNG::slotMkColFinished); - connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); - job->start(); -} - -void PropagateUploadFileNG::slotMkColFinished() -{ - propagator()->_activeJobList.removeOne(this); - auto job = qobject_cast(sender()); - _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - _item->_responseTimeStamp = job->responseTimestamp(); - _item->_requestId = job->requestId(); - slotJobDestroyed(job); // remove it from the _jobs list - - QNetworkReply::NetworkError err = job->reply()->error(); - if (err != QNetworkReply::NoError || _item->_httpErrorCode != 201) { - _item->_requestId = job->requestId(); - SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode, - &propagator()->_anotherSyncNeeded); - abortWithError(status, job->errorStringParsingBody()); - return; - } - - startNextChunk(); -} - -void PropagateUploadFileNG::doFinalMove() -{ - // Still not finished all ranges. - if (!_rangesToUpload.isEmpty()) - return; - - OC_ENFORCE_X(_jobs.isEmpty(), "MOVE for upload even though jobs are still running"); - - _finished = true; - - // Finish with a MOVE - QString destination = QDir::cleanPath(propagator()->account()->davUrl().path() - + propagator()->fullRemotePath(_item->_file)); - auto headers = PropagateUploadFileCommon::headers(); - - // "If-Match applies to the source, but we are interested in comparing the etag of the destination - auto ifMatch = headers.take(QByteArrayLiteral("If-Match")); - if (!ifMatch.isEmpty()) { - headers[QByteArrayLiteral("If")] = "<" + QUrl::toPercentEncoding(destination, "/") + "> ([" + ifMatch + "])"; - } - if (!_transmissionChecksumHeader.isEmpty()) { - headers[checkSumHeaderC] = _transmissionChecksumHeader; - } - headers[QByteArrayLiteral("OC-Total-Length")] = QByteArray::number(_bytesToUpload); - headers[QByteArrayLiteral("OC-Total-File-Length")] = QByteArray::number(_item->_size); - - const QUrl source = Utility::concatUrlPath(chunkUrl(), QStringLiteral("/.file")); - - auto job = new MoveJob(propagator()->account(), source, destination, headers, this); - _jobs.append(job); - connect(job, &MoveJob::finishedSignal, this, &PropagateUploadFileNG::slotMoveJobFinished); - connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); - propagator()->_activeJobList.append(this); - adjustLastJobTimeout(job, _item->_size); - job->start(); - return; -} - -void PropagateUploadFileNG::startNextChunk() -{ - if (propagator()->_abortRequested) - return; - - OC_ENFORCE_X(_bytesToUpload >= _sent, "Sent data exceeds file size"); - - // All ranges complete! - if (_rangesToUpload.isEmpty()) { - doFinalMove(); - return; - } - - _currentChunkOffset = _rangesToUpload.first().start; - _currentChunkSize = qMin(propagator()->_chunkSize, _rangesToUpload.first().size); - - const QString fileName = propagator()->fullLocalPath(_item->_file); - // If the file is currently locked, we want to retry the sync - // when it becomes available again. - const auto lockMode = propagator()->syncOptions().requiredLockMode(); - if (FileSystem::isFileLocked(fileName, lockMode)) { - emit propagator()->seenLockedFile(fileName, lockMode); - abortWithError(SyncFileItem::SoftError, tr("%1 the file is currently in use").arg(fileName)); - return; - } - auto device = std::make_unique(fileName, _currentChunkOffset, _currentChunkSize, - &propagator()->_bandwidthManager); - if (!device->open(QIODevice::ReadOnly)) { - qCWarning(lcPropagateUploadNG) << "Could not prepare upload device: " << device->errorString(); - // Soft error because this is likely caused by the user modifying his files while syncing - abortWithError(SyncFileItem::SoftError, device->errorString()); - return; - } - - QMap headers; - headers["OC-Chunk-Offset"] = QByteArray::number(_currentChunkOffset); - - QUrl url = chunkUrl(_currentChunkOffset); - - // job takes ownership of device via a QScopedPointer. Job deletes itself when finishing - auto devicePtr = device.get(); // for connections later - PUTFileJob *job = new PUTFileJob(propagator()->account(), url, std::move(device), headers, 0, this); - _jobs.append(job); - connect(job, &PUTFileJob::finishedSignal, this, &PropagateUploadFileNG::slotPutFinished); - connect(job, &PUTFileJob::uploadProgress, - this, &PropagateUploadFileNG::slotUploadProgress); - connect(job, &PUTFileJob::uploadProgress, - devicePtr, &UploadDevice::slotJobUploadProgress); - connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); - job->start(); - propagator()->_activeJobList.append(this); -} - -void PropagateUploadFileNG::slotPutFinished() -{ - PUTFileJob *job = qobject_cast(sender()); - OC_ASSERT(job); - - _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - _item->_responseTimeStamp = job->responseTimestamp(); - _item->_requestId = job->requestId(); - - slotJobDestroyed(job); // remove it from the _jobs list - - propagator()->_activeJobList.removeOne(this); - - if (_finished) { - // We have sent the finished signal already. We don't need to handle any remaining jobs - return; - } - - QNetworkReply::NetworkError err = job->reply()->error(); - - if (err != QNetworkReply::NoError) { - commonErrorHandling(job); - return; - } - - // Mark the range as uploaded - markRangeAsDone(_currentChunkOffset, _currentChunkSize); - _sent += _currentChunkSize; - - OC_ENFORCE_X(_sent <= _bytesToUpload, "can't send more than size"); - - // Adjust the chunk size for the time taken. - // - // Dynamic chunk sizing is enabled if the server configured a - // target duration for each chunk upload. - auto targetDuration = propagator()->syncOptions()._targetChunkUploadDuration; - if (targetDuration.count() > 0) { - auto uploadTime = ++job->msSinceStart(); // add one to avoid div-by-zero - qint64 predictedGoodSize = (_currentChunkSize * targetDuration) / uploadTime; - - // The whole targeting is heuristic. The predictedGoodSize will fluctuate - // quite a bit because of external factors (like available bandwidth) - // and internal factors (like number of parallel uploads). - // - // We use an exponential moving average here as a cheap way of smoothing - // the chunk sizes a bit. - qint64 targetSize = propagator()->_chunkSize / 2 + predictedGoodSize / 2; - - // Adjust the dynamic chunk size _chunkSize used for sizing of the item's chunks to be send - propagator()->_chunkSize = qBound( - propagator()->syncOptions()._minChunkSize, - targetSize, - propagator()->syncOptions()._maxChunkSize); - - qCInfo(lcPropagateUploadNG) << "Chunked upload of" << _currentChunkSize << "bytes took" << uploadTime.count() - << "ms, desired is" << targetDuration.count() << "ms, expected good chunk size is" - << predictedGoodSize << "bytes and nudged next chunk size to " - << propagator()->_chunkSize << "bytes"; - } - - _finished = _sent == _bytesToUpload; - - // Check if the file still exists - const QString fullFilePath(propagator()->fullLocalPath(_item->_file)); - if (!FileSystem::fileExists(fullFilePath)) { - if (!_finished) { - abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync.")); - return; - } else { - propagator()->_anotherSyncNeeded = true; - } - } - - // Check whether the file changed since discovery. - if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) { - propagator()->_anotherSyncNeeded = true; - if (!_finished) { - abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync.")); - return; - } - } - - if (!_finished) { - // Deletes an existing blacklist entry on successful chunk upload - if (_item->_hasBlacklistEntry) { - propagator()->_journal->wipeErrorBlacklistEntry(_item->_file); - _item->_hasBlacklistEntry = false; - } - - // Reset the error count on successful chunk upload - auto uploadInfo = propagator()->_journal->getUploadInfo(_item->_file); - uploadInfo._errorCount = 0; - propagator()->_journal->setUploadInfo(_item->_file, uploadInfo); - propagator()->_journal->commit(QStringLiteral("Upload info")); - } - startNextChunk(); -} - -void PropagateUploadFileNG::slotMoveJobFinished() -{ - propagator()->_activeJobList.removeOne(this); - auto job = qobject_cast(sender()); - slotJobDestroyed(job); // remove it from the _jobs list - QNetworkReply::NetworkError err = job->reply()->error(); - _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - _item->_responseTimeStamp = job->responseTimestamp(); - _item->_requestId = job->requestId(); - - if (err != QNetworkReply::NoError) { - commonErrorHandling(job); - return; - } - - if (_item->_httpErrorCode == 202) { - done(SyncFileItem::NormalError, tr("The server did ask for a removed legacy feature(polling)")); - return; - } - - if (_item->_httpErrorCode != 201 && _item->_httpErrorCode != 204) { - abortWithError(SyncFileItem::NormalError, tr("Unexpected return code from server (%1)").arg(_item->_httpErrorCode)); - return; - } - - QByteArray fid = job->reply()->rawHeader("OC-FileID"); - if (fid.isEmpty()) { - qCWarning(lcPropagateUploadNG) << "Server did not return a OC-FileID" << _item->_file; - abortWithError(SyncFileItem::NormalError, tr("Missing File ID from server")); - return; - } else { - // the old file id should only be empty for new files uploaded - if (!_item->_fileId.isEmpty() && _item->_fileId != fid) { - qCWarning(lcPropagateUploadNG) << "File ID changed!" << _item->_fileId << fid; - } - _item->_fileId = fid; - } - - _item->_etag = getEtagFromReply(job->reply()); - if (_item->_etag.isEmpty()) { - qCWarning(lcPropagateUploadNG) << "Server did not return an ETAG" << _item->_file; - abortWithError(SyncFileItem::NormalError, tr("Missing ETag from server")); - return; - } - finalize(); -} - -void PropagateUploadFileNG::slotUploadProgress(qint64 sent, qint64 total) -{ - // Completion is signaled with sent=0, total=0; avoid accidentally - // resetting progress due to the sent being zero by ignoring it. - // finishedSignal() is bound to be emitted soon anyway. - // See https://bugreports.qt.io/browse/QTBUG-44782. - if (sent == 0 && total == 0) { - return; - } - propagator()->reportProgress(*_item, _sent + sent); -} - -void PropagateUploadFileNG::abort(PropagatorJob::AbortType abortType) -{ - abortNetworkJobs( - abortType, - [abortType](AbstractNetworkJob *job) { - return abortType != AbortType::Asynchronous || !qobject_cast(job); - }); -} - -} diff --git a/src/libsync/propagateuploadtus.cpp b/src/libsync/propagateuploadtus.cpp index f57c35d371a..2adcb4ab7a1 100644 --- a/src/libsync/propagateuploadtus.cpp +++ b/src/libsync/propagateuploadtus.cpp @@ -12,7 +12,9 @@ * for more details. */ +#include "propagateuploadtus.h" #include "account.h" +#include "capabilities.h" #include "common/asserts.h" #include "common/checksums.h" #include "common/syncjournaldb.h" @@ -22,24 +24,13 @@ #include "httplogger.h" #include "networkjobs.h" #include "owncloudpropagator_p.h" -#include "propagateremotedelete.h" -#include "propagateupload.h" -#include "propagateuploadtus.h" -#include "propagatorjobs.h" #include "syncengine.h" -#include #include -#include -#include -#include + #include namespace { -QUrl uploadURL(const OCC::AccountPtr &account) -{ - return OCC::Utility::concatUrlPath(account->url(), QStringLiteral("remote.php/dav/files/%1/").arg(account->davUser())); -} QByteArray uploadOffset() { @@ -61,13 +52,12 @@ UploadDevice *PropagateUploadFileTUS::prepareDevice(const quint64 &chunkSize) const QString localFileName = propagator()->fullLocalPath(_item->_file); // If the file is currently locked, we want to retry the sync // when it becomes available again. - const auto lockMode = propagator()->syncOptions().requiredLockMode(); - if (FileSystem::isFileLocked(localFileName, lockMode)) { - emit propagator()->seenLockedFile(localFileName, lockMode); + if (FileSystem::isFileLocked(localFileName, FileSystem::LockMode::SharedRead)) { + Q_EMIT propagator()->seenLockedFile(localFileName, FileSystem::LockMode::SharedRead); abortWithError(SyncFileItem::SoftError, tr("%1 the file is currently in use").arg(localFileName)); return nullptr; } - auto device = std::make_unique(localFileName, _currentOffset, chunkSize, &propagator()->_bandwidthManager); + auto device = std::make_unique(localFileName, _currentOffset, chunkSize); if (!device->open(QIODevice::ReadOnly)) { qCWarning(lcPropagateUploadTUS) << "Could not prepare upload device: " << device->errorString(); @@ -82,20 +72,25 @@ UploadDevice *PropagateUploadFileTUS::prepareDevice(const quint64 &chunkSize) SimpleNetworkJob *PropagateUploadFileTUS::makeCreationWithUploadJob(QNetworkRequest *request, UploadDevice *device) { Q_ASSERT(propagator()->account()->capabilities().tusSupport().extensions.contains(QStringLiteral("creation-with-upload"))); - // in difference to the old protocol the algrithm and the value are space seperated - const auto checkSum = _transmissionChecksumHeader.replace(':', ' ').toBase64(); - qCDebug(lcPropagateUploadTUS) << "FullPath:" << propagator()->fullRemotePath(_item->_file); - request->setRawHeader(QByteArrayLiteral("Upload-Metadata"), "filename " + propagator()->fullRemotePath(_item->_file).toUtf8().toBase64() + ",checksum " + checkSum); + + const auto checksumHeader = ChecksumHeader::parseChecksumHeader(_transmissionChecksumHeader); + + QByteArrayList encodedMetaData; + auto addMetaData = [&encodedMetaData](const QByteArray &key, const QByteArray &value) { encodedMetaData << key + ' ' + value.toBase64(); }; + addMetaData(QByteArrayLiteral("filename"), propagator()->fullRemotePath(_item->_file).toUtf8()); + // in difference to the old protocol the algorithm and the value are space seperated + addMetaData(QByteArrayLiteral("checksum"), Utility::enumToString(checksumHeader.type()).toUtf8() + ' ' + checksumHeader.checksum()); + addMetaData(QByteArrayLiteral("mtime"), QByteArray::number(static_cast(_item->_modtime))); + + request->setRawHeader(QByteArrayLiteral("Upload-Metadata"), encodedMetaData.join(',')); request->setRawHeader(QByteArrayLiteral("Upload-Length"), QByteArray::number(_item->_size)); - auto job = new SimpleNetworkJob(propagator()->account(), this); - job->prepareRequest("POST", uploadURL(propagator()->account()), *request, device); - return job; + return new SimpleNetworkJob(propagator()->account(), propagator()->webDavUrl(), {}, "POST", device, *request, this); } QNetworkRequest PropagateUploadFileTUS::prepareRequest(const quint64 &chunkSize) { QNetworkRequest request; - const auto headers = PropagateUploadFileCommon::headers(); + const auto headers = PropagateUploadCommon::headers(); for (auto it = headers.cbegin(); it != headers.cend(); ++it) { request.setRawHeader(it.key(), it.value()); } @@ -108,31 +103,46 @@ QNetworkRequest PropagateUploadFileTUS::prepareRequest(const quint64 &chunkSize) } PropagateUploadFileTUS::PropagateUploadFileTUS(OwncloudPropagator *propagator, const SyncFileItemPtr &item) - : PropagateUploadFileCommon(propagator, item) + : PropagateUploadCommon(propagator, item) { } void PropagateUploadFileTUS::doStartUpload() { + if (_transmissionChecksumHeader.isEmpty()) { + abortWithError(SyncFileItem::SoftError, tr("Checksum computation failed")); + return; + } propagator()->reportProgress(*_item, 0); - startNextChunk(); propagator()->_activeJobList.append(this); + + const auto info = propagator()->_journal->getUploadInfo(_item->_file); + if (info.validate(_item->_size, _item->_modtime, _item->_checksumHeader)) { + _location = info._url; + Q_ASSERT(_location.isValid()); + auto job = new SimpleNetworkJob(propagator()->account(), _location, {}, "HEAD", nullptr, {}, this); + connect(job, &SimpleNetworkJob::finishedSignal, this, &PropagateUploadFileTUS::slotChunkFinished); + job->start(); + } else { + startNextChunk(); + } } void PropagateUploadFileTUS::startNextChunk() { if (propagator()->_abortRequested) return; + const quint64 chunkSize = [&] { auto chunkSize = _item->_size - _currentOffset; if (propagator()->account()->capabilities().tusSupport().max_chunk_size) { - chunkSize = qMin(chunkSize - _currentOffset, propagator()->account()->capabilities().tusSupport().max_chunk_size); + chunkSize = std::min(chunkSize, propagator()->account()->capabilities().tusSupport().max_chunk_size); } return chunkSize; }(); QNetworkRequest req = prepareRequest(chunkSize); - auto device = prepareDevice(chunkSize); + UploadDevice *device = prepareDevice(chunkSize); if (!device) { return; } @@ -140,32 +150,31 @@ void PropagateUploadFileTUS::startNextChunk() SimpleNetworkJob *job; if (_currentOffset != 0) { qCDebug(lcPropagateUploadTUS) << "Starting to patch upload:" << propagator()->fullRemotePath(_item->_file); - job = new SimpleNetworkJob(propagator()->account(), this); - job->prepareRequest("PATCH", _location, req, device); + job = new SimpleNetworkJob(propagator()->account(), _location, {}, "PATCH", device, req, this); } else { - OC_ASSERT(_location.isEmpty()); + Q_ASSERT(_location.isEmpty()); qCDebug(lcPropagateUploadTUS) << "Starting creation with upload:" << propagator()->fullRemotePath(_item->_file); job = makeCreationWithUploadJob(&req, device); } + + job->setPriority(QNetworkRequest::LowPriority); qCDebug(lcPropagateUploadTUS) << "Offset:" << _currentOffset << _currentOffset / (_item->_size + 1) * 100 << "Chunk:" << chunkSize << chunkSize / (_item->_size + 1) * 100; - _jobs.append(job); - connect(job->reply(), &QNetworkReply::uploadProgress, device, &UploadDevice::slotJobUploadProgress); - connect(job->reply(), &QNetworkReply::uploadProgress, this,[this](qint64 bytesSent, qint64){ - propagator()->reportProgress(*_item, _currentOffset + bytesSent); - - }); + addChildJob(job); connect(job, &SimpleNetworkJob::finishedSignal, this, &PropagateUploadFileTUS::slotChunkFinished); - connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); + job->addNewReplyHook([this](QNetworkReply *reply) { + connect(reply, &QNetworkReply::uploadProgress, this, [this](qint64 bytesSent, qint64) { + propagator()->reportProgress(*_item, _currentOffset + bytesSent); + }); + }); job->start(); } void PropagateUploadFileTUS::slotChunkFinished() { SimpleNetworkJob *job = qobject_cast(sender()); - OC_ASSERT(job); - slotJobDestroyed(job); // remove it from the _jobs list + Q_ASSERT(job); qCDebug(lcPropagateUploadTUS) << propagator()->fullRemotePath(_item->_file) << HttpLogger::requestVerb(*job->reply()); _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); @@ -177,14 +186,12 @@ void PropagateUploadFileTUS::slotChunkFinished() // try to get the offset if possible, only try once if (err == QNetworkReply::TimeoutError && !_location.isEmpty() && HttpLogger::requestVerb(*job->reply()) != "HEAD") { - qCWarning(lcPropagateUploadTUS) << propagator()->fullRemotePath(_item->_file) << "Encountered a timeout -> get progrss for" << _location; + qCWarning(lcPropagateUploadTUS) << propagator()->fullRemotePath(_item->_file) << "Encountered a timeout -> get progress for" << _location; QNetworkRequest req; setTusVersionHeader(req); - auto updateJob = new SimpleNetworkJob(propagator()->account(), this); - updateJob->prepareRequest("HEAD", _location, req); - _jobs.append(updateJob); + auto updateJob = new SimpleNetworkJob(propagator()->account(), propagator()->webDavUrl(), _location.path(), "HEAD", {}, req, this); + addChildJob(updateJob); connect(updateJob, &SimpleNetworkJob::finishedSignal, this, &PropagateUploadFileTUS::slotChunkFinished); - connect(updateJob, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); updateJob->start(); return; @@ -216,22 +223,29 @@ void PropagateUploadFileTUS::slotChunkFinished() } // Check whether the file changed since discovery. - if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) { + if (FileSystem::fileChanged(QFileInfo{fullFilePath}, _item->_size, _item->_modtime)) { propagator()->_anotherSyncNeeded = true; if (!_finished) { - abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync.")); + abortWithError(SyncFileItem::Message, fileChangedMessage()); // FIXME: the legacy code was retrying for a few seconds. // and also checking that after the last chunk, and removed the file in case of INSTRUCTION_NEW return; } } if (!_finished) { + // we just started the upload + if (HttpLogger::requestVerb(*job->reply()) == QByteArrayLiteral("POST")) { + // add the new location + auto info = _item->toUploadInfo(); + info._url = _location; + propagator()->_journal->setUploadInfo(_item->_file, info); + } startNextChunk(); return; } // ==== handling when the upload is finished: - const QByteArray etag = getEtagFromReply(job->reply()); + const QString etag = getEtagFromReply(job->reply()); const QByteArray remPerms = job->reply()->rawHeader("OC-Perm"); if (!remPerms.isEmpty()) { _item->_remotePerm = RemotePermissions::fromServerString(QString::fromUtf8(remPerms)); @@ -241,27 +255,26 @@ void PropagateUploadFileTUS::slotChunkFinished() if (!_finished) { // Either the ETag or the remote Permissions were not in the headers of the reply. // Start a PROPFIND to fetch these data from the server. - auto check = new PropfindJob(propagator()->account(), propagator()->fullRemotePath(_item->_file)); - _jobs.append(check); + auto check = + new PropfindJob(propagator()->account(), propagator()->webDavUrl(), propagator()->fullRemotePath(_item->_file), PropfindJob::Depth::Zero, this); + addChildJob(check); check->setProperties({ "http://owncloud.org/ns:fileid", "http://owncloud.org/ns:permissions", "getetag" }); - connect(check, &PropfindJob::result, this, [this, check](const QMap &map) { + connect(check, &PropfindJob::directoryListingIterated, this, [this](const QString &, const QMap &map) { _finished = true; _item->_remotePerm = RemotePermissions::fromServerString(map.value(QStringLiteral("permissions"))); - finalize(Utility::normalizeEtag(map.value(QStringLiteral("getetag")).toUtf8()), map.value(QStringLiteral("fileid")).toUtf8()); - slotJobDestroyed(check); + finalize(Utility::normalizeEtag(map.value(QStringLiteral("getetag"))), map.value(QStringLiteral("fileid")).toUtf8()); }); - connect(check, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); check->start(); return; } + _item->_remotePerm = RemotePermissions::fromServerString(QString::fromUtf8(job->reply()->rawHeader("Oc-Perm"))); // the file id should only be empty for new files up- or downloaded finalize(etag, job->reply()->rawHeader("OC-FileID")); } -void PropagateUploadFileTUS::finalize(const QByteArray &etag, const QByteArray &fileId) +void PropagateUploadFileTUS::finalize(const QString &etag, const QByteArray &fileId) { OC_ASSERT(_finished); - qCDebug(lcPropagateUploadTUS) << _item->_etag << etag << fileId; _item->_etag = etag; if (!fileId.isEmpty()) { if (!_item->_fileId.isEmpty() && _item->_fileId != fileId) { @@ -270,7 +283,7 @@ void PropagateUploadFileTUS::finalize(const QByteArray &etag, const QByteArray & _item->_fileId = fileId; } propagator()->_activeJobList.removeOne(this); - PropagateUploadFileCommon::finalize(); + PropagateUploadCommon::finalize(); } void PropagateUploadFileTUS::abort(PropagatorJob::AbortType abortType) diff --git a/src/libsync/propagateuploadtus.h b/src/libsync/propagateuploadtus.h index 59d18fdaf9b..717c9120062 100644 --- a/src/libsync/propagateuploadtus.h +++ b/src/libsync/propagateuploadtus.h @@ -14,12 +14,13 @@ #pragma once -#include "propagateupload.h" +#include "propagateuploadfile.h" +#include "uploaddevice.h" namespace OCC { Q_DECLARE_LOGGING_CATEGORY(lcPropagateUploadTUS) -class PropagateUploadFileTUS : public PropagateUploadFileCommon +class PropagateUploadFileTUS : public PropagateUploadCommon { Q_OBJECT @@ -30,7 +31,7 @@ class PropagateUploadFileTUS : public PropagateUploadFileCommon void startNextChunk(); void slotChunkFinished(); - void finalize(const QByteArray &etag, const QByteArray &fileId); + void finalize(const QString &etag, const QByteArray &fileId); quint64 _currentOffset = 0; QUrl _location; diff --git a/src/libsync/propagateuploadv1.cpp b/src/libsync/propagateuploadv1.cpp deleted file mode 100644 index f30da247e0c..00000000000 --- a/src/libsync/propagateuploadv1.cpp +++ /dev/null @@ -1,373 +0,0 @@ -/* - * Copyright (C) by Olivier Goffart - * - * 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 "config.h" -#include "propagateupload.h" -#include "owncloudpropagator_p.h" -#include "networkjobs.h" -#include "account.h" -#include "common/syncjournaldb.h" -#include "common/syncjournalfilerecord.h" -#include "common/utility.h" -#include "filesystem.h" -#include "propagatorjobs.h" -#include "common/checksums.h" -#include "syncengine.h" -#include "propagateremotedelete.h" -#include "common/asserts.h" - -#include -#include -#include -#include - -#include -#include -#include - -namespace OCC { - -void PropagateUploadFileV1::doStartUpload() -{ - if (!propagator()->account()->capabilities().bigfilechunkingEnabled()) - { - _chunkCount = 1; - } else { - _chunkCount = int(std::ceil(_item->_size / double(chunkSize()))); - } - _startChunk = 0; - _transferId = uint(QRandomGenerator::global()->generate()) ^ uint(_item->_modtime) ^ (uint(_item->_size) << 16); - - const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file); - - if (progressInfo._valid && progressInfo.isChunked() && progressInfo._modtime == _item->_modtime && progressInfo._size == _item->_size - && (progressInfo._contentChecksum == _item->_checksumHeader || progressInfo._contentChecksum.isEmpty() || _item->_checksumHeader.isEmpty())) { - _startChunk = progressInfo._chunk; - _transferId = progressInfo._transferid; - qCInfo(lcPropagateUploadV1) << _item->_file << ": Resuming from chunk " << _startChunk; - } else if (_chunkCount <= 1 && !_item->_checksumHeader.isEmpty()) { - // If there is only one chunk, write the checksum in the database, so if the PUT is sent - // to the server, but the connection drops before we get the etag, we can check the checksum - // in reconcile (issue #5106) - SyncJournalDb::UploadInfo pi; - pi._valid = true; - pi._chunk = 0; - pi._transferid = 0; // We set a null transfer id because it is not chunked. - pi._modtime = _item->_modtime; - pi._errorCount = 0; - pi._contentChecksum = _item->_checksumHeader; - pi._size = _item->_size; - propagator()->_journal->setUploadInfo(_item->_file, pi); - propagator()->_journal->commit(QStringLiteral("Upload info")); - } - - _currentChunk = 0; - - propagator()->reportProgress(*_item, 0); - startNextChunk(); -} - -void PropagateUploadFileV1::startNextChunk() -{ - if (propagator()->_abortRequested) - return; - - if (!_jobs.isEmpty() && _currentChunk + _startChunk >= _chunkCount - 1) { - // Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that - // https://github.com/owncloud/core/issues/11106 - // We return now and when the _jobs are finished we will proceed with the last chunk - // NOTE: Some other parts of the code such as slotUploadProgress also assume that the last chunk - // is sent last. - return; - } - qint64 fileSize = _item->_size; - auto headers = PropagateUploadFileCommon::headers(); - headers[QByteArrayLiteral("OC-Total-Length")] = QByteArray::number(fileSize); - headers[QByteArrayLiteral("OC-Chunk-Size")] = QByteArray::number(chunkSize()); - - QString path = _item->_file; - - qint64 chunkStart = 0; - qint64 currentChunkSize = fileSize; - bool isFinalChunk = false; - if (_chunkCount > 1) { - int sendingChunk = (_currentChunk + _startChunk) % _chunkCount; - // XOR with chunk size to make sure everything goes well if chunk size changes between runs - uint transid = _transferId ^ uint(chunkSize()); - qCInfo(lcPropagateUploadV1) << "Upload chunk" << sendingChunk << "of" << _chunkCount << "transferid(remote)=" << transid; - path += QStringLiteral("-chunking-%1-%2-%3").arg(transid).arg(_chunkCount).arg(sendingChunk); - - headers[QByteArrayLiteral("OC-Chunked")] = QByteArrayLiteral("1"); - - chunkStart = chunkSize() * sendingChunk; - currentChunkSize = chunkSize(); - if (sendingChunk == _chunkCount - 1) { // last chunk - currentChunkSize = (fileSize % chunkSize()); - if (currentChunkSize == 0) { // if the last chunk pretends to be 0, its actually the full chunk size. - currentChunkSize = chunkSize(); - } - isFinalChunk = true; - } - } else { - // if there's only one chunk, it's the final one - isFinalChunk = true; - } - qCDebug(lcPropagateUploadV1) << _chunkCount << isFinalChunk << chunkStart << currentChunkSize; - - if (isFinalChunk && !_transmissionChecksumHeader.isEmpty()) { - qCInfo(lcPropagateUploadV1) << propagator()->fullRemotePath(path) << _transmissionChecksumHeader; - headers[checkSumHeaderC] = _transmissionChecksumHeader; - } - - const QString fileName = propagator()->fullLocalPath(_item->_file); - // If the file is currently locked, we want to retry the sync - // when it becomes available again. - const auto lockMode = propagator()->syncOptions().requiredLockMode(); - if (FileSystem::isFileLocked(fileName, lockMode)) { - emit propagator()->seenLockedFile(fileName, lockMode); - abortWithError(SyncFileItem::SoftError, tr("%1 the file is currently in use").arg(fileName)); - return; - } - auto device = std::make_unique(fileName, chunkStart, currentChunkSize, - &propagator()->_bandwidthManager); - if (!device->open(QIODevice::ReadOnly)) { - qCWarning(lcPropagateUploadV1) << "Could not prepare upload device: " << device->errorString(); - // Soft error because this is likely caused by the user modifying his files while syncing - abortWithError(SyncFileItem::SoftError, device->errorString()); - return; - } - - // job takes ownership of device via a QScopedPointer. Job deletes itself when finishing - auto devicePtr = device.get(); // for connections later - PUTFileJob *job = new PUTFileJob(propagator()->account(), propagator()->fullRemotePath(path), std::move(device), headers, _currentChunk, this); - _jobs.append(job); - connect(job, &PUTFileJob::finishedSignal, this, &PropagateUploadFileV1::slotPutFinished); - connect(job, &PUTFileJob::uploadProgress, this, &PropagateUploadFileV1::slotUploadProgress); - connect(job, &PUTFileJob::uploadProgress, devicePtr, &UploadDevice::slotJobUploadProgress); - connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed); - if (isFinalChunk) - adjustLastJobTimeout(job, fileSize); - job->start(); - propagator()->_activeJobList.append(this); - _currentChunk++; - - bool parallelChunkUpload = true; - - if (propagator()->account()->capabilities().chunkingParallelUploadDisabled()) { - // Server may also disable parallel chunked upload for any higher version - parallelChunkUpload = false; - } else { - QByteArray env = qgetenv("OWNCLOUD_PARALLEL_CHUNK"); - if (!env.isEmpty()) { - parallelChunkUpload = env != "false" && env != "0"; - } else { - int versionNum = propagator()->account()->serverVersionInt(); - if (versionNum < Account::makeServerVersion(8, 0, 3)) { - // Disable parallel chunk upload severs older than 8.0.3 to avoid too many - // internal sever errors (#2743, #2938) - parallelChunkUpload = false; - } - } - } - - - if (_currentChunk + _startChunk >= _chunkCount - 1) { - // Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that - // https://github.com/owncloud/core/issues/11106 - parallelChunkUpload = false; - } - - if (parallelChunkUpload && (propagator()->_activeJobList.count() < propagator()->maximumActiveTransferJob()) - && _currentChunk < _chunkCount) { - startNextChunk(); - } - if (!parallelChunkUpload || _chunkCount - _currentChunk <= 0) { - propagator()->scheduleNextJob(); - } -} - -void PropagateUploadFileV1::slotPutFinished() -{ - PUTFileJob *job = qobject_cast(sender()); - OC_ASSERT(job); - - slotJobDestroyed(job); // remove it from the _jobs list - - propagator()->_activeJobList.removeOne(this); - - if (_finished) { - // We have sent the finished signal already. We don't need to handle any remaining jobs - return; - } - - _item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - _item->_responseTimeStamp = job->responseTimestamp(); - _item->_requestId = job->requestId(); - QNetworkReply::NetworkError err = job->reply()->error(); - if (err != QNetworkReply::NoError) { - commonErrorHandling(job); - return; - } - - if (_item->_httpErrorCode == 202) { - done(SyncFileItem::NormalError, tr("The server did ask for a removed legacy feature(polling)")); - return; - } - - // Check the file again post upload. - // Two cases must be considered separately: If the upload is finished, - // the file is on the server and has a changed ETag. In that case, - // the etag has to be properly updated in the client journal, and because - // of that we can bail out here with an error. But we can reschedule a - // sync ASAP. - // But if the upload is ongoing, because not all chunks were uploaded - // yet, the upload can be stopped and an error can be displayed, because - // the server hasn't registered the new file yet. - QByteArray etag = getEtagFromReply(job->reply()); - _finished = etag.length() > 0; - - // Check if the file still exists - const QString fullFilePath(propagator()->fullLocalPath(_item->_file)); - if (!FileSystem::fileExists(fullFilePath)) { - if (!_finished) { - abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync.")); - return; - } else { - propagator()->_anotherSyncNeeded = true; - } - } - - // Check whether the file changed since discovery. - if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) { - propagator()->_anotherSyncNeeded = true; - if (!_finished) { - abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync.")); - // FIXME: the legacy code was retrying for a few seconds. - // and also checking that after the last chunk, and removed the file in case of INSTRUCTION_NEW - return; - } - } - - if (!_finished) { - // Proceed to next chunk. - if (_currentChunk >= _chunkCount) { - if (!_jobs.empty()) { - // just wait for the other job to finish. - return; - } - done(SyncFileItem::NormalError, tr("The server did not acknowledge the last chunk. (No e-tag was present)")); - return; - } - - // Deletes an existing blacklist entry on successful chunk upload - if (_item->_hasBlacklistEntry) { - propagator()->_journal->wipeErrorBlacklistEntry(_item->_file); - _item->_hasBlacklistEntry = false; - } - - SyncJournalDb::UploadInfo pi; - pi._valid = true; - auto currentChunk = job->_chunk; - for (auto *job : qAsConst(_jobs)) { - // Take the minimum finished one - if (auto putJob = qobject_cast(job)) { - currentChunk = qMin(currentChunk, putJob->_chunk - 1); - } - } - pi._chunk = (currentChunk + _startChunk + 1) % _chunkCount; // next chunk to start with - pi._transferid = _transferId; - pi._modtime = _item->_modtime; - pi._errorCount = 0; // successful chunk upload resets - pi._contentChecksum = _item->_checksumHeader; - pi._size = _item->_size; - propagator()->_journal->setUploadInfo(_item->_file, pi); - propagator()->_journal->commit(QStringLiteral("Upload info")); - startNextChunk(); - return; - } - // the following code only happens after all chunks were uploaded. - - // the file id should only be empty for new files up- or downloaded - QByteArray fid = job->reply()->rawHeader("OC-FileID"); - if (!fid.isEmpty()) { - if (!_item->_fileId.isEmpty() && _item->_fileId != fid) { - qCWarning(lcPropagateUploadV1) << "File ID changed!" << _item->_fileId << fid; - } - _item->_fileId = fid; - } - - _item->_etag = etag; - - if (job->reply()->rawHeader("X-OC-MTime") != "accepted") { - // X-OC-MTime is supported since owncloud 5.0. But not when chunking. - // Normally Owncloud 6 always puts X-OC-MTime - qCWarning(lcPropagateUploadV1) << "Server does not support X-OC-MTime" << job->reply()->rawHeader("X-OC-MTime"); - // Well, the mtime was not set - } - - finalize(); -} - - -void PropagateUploadFileV1::slotUploadProgress(qint64 sent, qint64 total) -{ - // Completion is signaled with sent=0, total=0; avoid accidentally - // resetting progress due to the sent being zero by ignoring it. - // finishedSignal() is bound to be emitted soon anyway. - // See https://bugreports.qt.io/browse/QTBUG-44782. - if (sent == 0 && total == 0) { - return; - } - - int progressChunk = _currentChunk + _startChunk - 1; - if (progressChunk >= _chunkCount) - progressChunk = _currentChunk - 1; - - // amount is the number of bytes already sent by all the other chunks that were sent - // not including this one. - // FIXME: this assumes all chunks have the same size, which is true only if the last chunk - // has not been finished (which should not happen because the last chunk is sent sequentially) - qint64 amount = progressChunk * chunkSize(); - - sender()->setProperty("byteWritten", sent); - if (_jobs.count() > 1) { - amount -= (_jobs.count() - 1) * chunkSize(); - for (auto *j : qAsConst(_jobs)) { - amount += j->property("byteWritten").toULongLong(); - } - } else { - // sender() is the only current job, no need to look at the byteWritten properties - amount += sent; - } - propagator()->reportProgress(*_item, amount); -} - -void PropagateUploadFileV1::abort(PropagatorJob::AbortType abortType) -{ - abortNetworkJobs( - abortType, - [this, abortType](AbstractNetworkJob *job) { - if (PUTFileJob *putJob = qobject_cast(job)){ - if (abortType == AbortType::Asynchronous - && _chunkCount > 0 - && (((_currentChunk + _startChunk) % _chunkCount) == 0) - && putJob->device()->atEnd()) { - return false; - } - } - return true; - }); -} - -} diff --git a/src/libsync/propagatorjobs.cpp b/src/libsync/propagatorjobs.cpp index d81af64bb55..0a6224d93c0 100644 --- a/src/libsync/propagatorjobs.cpp +++ b/src/libsync/propagatorjobs.cpp @@ -14,21 +14,17 @@ */ #include "propagatorjobs.h" -#include "owncloudpropagator.h" -#include "owncloudpropagator_p.h" -#include "propagateremotemove.h" -#include "common/utility.h" #include "common/syncjournaldb.h" #include "common/syncjournalfilerecord.h" #include "filesystem.h" -#include +#include "propagateremotemove.h" +#include +#include #include -#include -#include +#include #include -#include #include -#include +#include #include @@ -47,42 +43,57 @@ Q_LOGGING_CATEGORY(lcPropagateLocalRename, "sync.propagator.localrename", QtInfo * * \a path is relative to propagator()->_localDir + _item->_file and should start with a slash */ -bool PropagateLocalRemove::removeRecursively(const QString &path) +bool PropagateLocalRemove::removeRecursively(const QString &absolute) { - QString absolute = propagator()->fullLocalPath(_item->_file + path); - QStringList errors; - QList> deleted; - bool success = FileSystem::removeRecursively( + FileSystem::RemoveEntryList removed; + FileSystem::RemoveEntryList locked; + FileSystem::RemoveErrorList errors; + const bool success = FileSystem::removeRecursively( absolute, - [&deleted](const QString &path, bool isDir) { - // by prepending, a folder deletion may be followed by content deletions - deleted.prepend(qMakePair(path, isDir)); - }, + &removed, + &locked, &errors); if (!success) { // We need to delete the entries from the database now from the deleted vector. // Do it while avoiding redundant delete calls to the journal. QString deletedDir; - for (const auto &it : qAsConst(deleted)) { - if (!it.first.startsWith(propagator()->localPath())) + for (const auto &it : std::as_const(removed)) { + if (!it.path.startsWith(propagator()->localPath())) continue; - if (!deletedDir.isEmpty() && it.first.startsWith(deletedDir)) + if (!deletedDir.isEmpty() && it.path.startsWith(deletedDir)) continue; - if (it.second) { - deletedDir = it.first; + if (it.isDir) { + deletedDir = it.path; } - propagator()->_journal->deleteFileRecord(it.first.mid(propagator()->localPath().size()), it.second); + propagator()->_journal->deleteFileRecord(it.path.mid(propagator()->localPath().size()), it.isDir); + } + if (!errors.empty()) { + QStringList errorList; + errorList.reserve(errors.size()); + for (const auto &err : errors) { + errorList.append(tr("%1 failed with: %2").arg(QDir::toNativeSeparators(err.entry.path), err.error)); + } + done(SyncFileItem::NormalError, errorList.join(QStringLiteral(", "))); + return false; + } else if (!locked.empty()) { + QStringList errorList; + errorList.reserve(errors.size()); + for (const auto &l : locked) { + // unlock is handled in hack in `void Folder::slotWatchedPathChanged` + Q_EMIT propagator()->seenLockedFile(l.path, FileSystem::LockMode::Exclusive); + errorList.append(tr("%1 the file is currently in use").arg(QDir::toNativeSeparators(l.path))); + } + done(SyncFileItem::SoftError, errorList.join(QStringLiteral(", "))); + return false; } - - _error = errors.join(QStringLiteral(", ")); } return success; } void PropagateLocalRemove::start() { - _moveToTrash = propagator()->syncOptions()._moveFilesToTrash; + _moveToTrash = propagator()->moveToTrash(); if (propagator()->_abortRequested) return; @@ -95,26 +106,34 @@ void PropagateLocalRemove::start() return; } - QString removeError; - if (_moveToTrash) { - if ((QDir(filename).exists() || FileSystem::fileExists(filename)) - && !FileSystem::moveToTrash(filename, &removeError)) { - done(SyncFileItem::NormalError, removeError); + if (FileSystem::fileExists(filename)) { + if (FileSystem::isFileLocked(filename, FileSystem::LockMode::Exclusive)) { + Q_EMIT propagator()->seenLockedFile(filename, FileSystem::LockMode::Exclusive); + done(SyncFileItem::SoftError, tr("%1 the file is currently in use").arg(QDir::toNativeSeparators(filename))); return; } - } else { - if (_item->isDirectory()) { - if (QDir(filename).exists() && !removeRecursively(QString())) { - done(SyncFileItem::NormalError, _error); - return; + + bool ok = false; + QString removeError; + if (_moveToTrash) { + ok = QFile(filename).moveToTrash(); + if (!ok) { + removeError = tr("Could not move '%1' to the trash bin").arg(filename); } } else { - if (FileSystem::fileExists(filename) - && !FileSystem::remove(filename, &removeError)) { - done(SyncFileItem::NormalError, removeError); - return; + if (_item->isDirectory()) { + // removeRecursively will call done on error + if (!(ok = removeRecursively(filename))) { + return; + } + } else { + ok = FileSystem::remove(filename, &removeError); } } + if (!ok) { + done(SyncFileItem::NormalError, removeError); + return; + } } propagator()->reportProgress(*_item, 0); propagator()->_journal->deleteFileRecord(_item->_originalFile, _item->isDirectory()); @@ -135,6 +154,7 @@ void PropagateLocalMkdir::start() QFileInfo fi(newDirStr); if (fi.exists() && fi.isFile()) { if (_deleteExistingFile) { + qDebug(lcPropagateLocalMkdir) << "deleting existing file" << newDirStr << "to replace it with a dir"; QString removeError; if (!FileSystem::remove(newDirStr, &removeError)) { done(SyncFileItem::NormalError, @@ -142,9 +162,9 @@ void PropagateLocalMkdir::start() .arg(newDirStr, removeError)); return; } - } else if (_item->_instruction == CSYNC_INSTRUCTION_CONFLICT) { + } else if (_item->instruction() == CSYNC_INSTRUCTION_CONFLICT) { QString error; - if (!propagator()->createConflict(_item, _associatedComposite, &error)) { + if (!propagator()->createConflict(_item, &error)) { done(SyncFileItem::SoftError, error); return; } @@ -156,9 +176,8 @@ void PropagateLocalMkdir::start() done(SyncFileItem::NormalError, tr("Can not create local folder %1 because of a local file name clash with %2").arg(newDirStr, QDir::toNativeSeparators(clash.get()))); return; } - emit propagator()->touchedFile(newDirStr); - QDir localDir(propagator()->localPath()); - if (!localDir.mkpath(_item->_file)) { + + if (!FileSystem::mkpath(propagator()->localPath(), _item->_file)) { done(SyncFileItem::NormalError, tr("could not create folder %1").arg(newDirStr)); return; } @@ -169,7 +188,7 @@ void PropagateLocalMkdir::start() // so the database is aware that this folder exists even if the sync is aborted // before the correct etag is stored. SyncFileItem newItem(*_item); - newItem._etag = "_invalid_"; + newItem._etag = QStringLiteral("_invalid_"); const auto result = propagator()->updateMetadata(newItem); if (!result) { done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error())); @@ -180,9 +199,7 @@ void PropagateLocalMkdir::start() } propagator()->_journal->commit(QStringLiteral("localMkdir")); - auto resultStatus = _item->_instruction == CSYNC_INSTRUCTION_CONFLICT - ? SyncFileItem::Conflict - : SyncFileItem::Success; + auto resultStatus = _item->instruction() == CSYNC_INSTRUCTION_CONFLICT ? SyncFileItem::Conflict : SyncFileItem::Success; done(resultStatus); } @@ -193,7 +210,7 @@ void PropagateLocalMkdir::setDeleteExistingFile(bool enabled) void PropagateLocalRename::start() { - if (propagator()->_abortRequested) + if (propagator()->_abortRequested || !propagator()->syncOptions().isValid()) return; QString existingFile = propagator()->fullLocalPath(propagator()->adjustRenamedPath(_item->_file)); @@ -205,10 +222,10 @@ void PropagateLocalRename::start() propagator()->reportProgress(*_item, 0); qCDebug(lcPropagateLocalRename) << "MOVE " << existingFile << " => " << targetFile; - if (QString::compare(_item->_file, _item->_renameTarget, Qt::CaseInsensitive) != 0 - && propagator()->localFileNameClash(_item->_renameTarget)) { - // Only use localFileNameClash for the destination if we know that the source was not - // the one conflicting (renaming A.txt -> a.txt is OK) + if (_item->_file.normalized(QString::NormalizationForm_C) + != _item->_renameTarget.normalized(QString::NormalizationForm_C) // Normalization-only changes are okay + && QString::compare(_item->_file, _item->_renameTarget, Qt::CaseInsensitive) != 0 // Case-only changes are ok + && propagator()->localFileNameClash(_item->_renameTarget)) { // Anything else: check for a name-clash // Fixme: the file that is the reason for the clash could be named here, // it would have to come out the localFileNameClash function @@ -219,12 +236,10 @@ void PropagateLocalRename::start() return; } if (FileSystem::isFileLocked(existingFile, FileSystem::LockMode::Exclusive)) { - emit propagator()->seenLockedFile(existingFile, FileSystem::LockMode::Exclusive); + Q_EMIT propagator()->seenLockedFile(existingFile, FileSystem::LockMode::Exclusive); done(SyncFileItem::SoftError, tr("Could not rename %1 to %2, the file is currently in use").arg(existingFile, targetFile)); return; } - emit propagator()->touchedFile(existingFile); - emit propagator()->touchedFile(targetFile); QString renameError; if (!FileSystem::rename(existingFile, targetFile, &renameError)) { done(SyncFileItem::NormalError, renameError); @@ -236,9 +251,9 @@ void PropagateLocalRename::start() propagator()->_journal->getFileRecord(_item->_originalFile, &oldRecord); propagator()->_journal->deleteFileRecord(_item->_originalFile); - auto &vfs = propagator()->syncOptions()._vfs; + Vfs *vfs = propagator()->syncOptions().vfs(); auto pinState = vfs->pinState(_item->_originalFile); - vfs->setPinState(_item->_originalFile, PinState::Inherited); + std::ignore = vfs->setPinState(_item->_originalFile, PinState::Inherited); const auto oldFile = _item->_file; diff --git a/src/libsync/propagatorjobs.h b/src/libsync/propagatorjobs.h index b4c37cd94cb..a4d6d05eb6b 100644 --- a/src/libsync/propagatorjobs.h +++ b/src/libsync/propagatorjobs.h @@ -28,7 +28,7 @@ static const char checkSumHeaderC[] = "OC-Checksum"; static const char contentMd5HeaderC[] = "Content-MD5"; /** - * @brief Declaration of the other propagation jobs + * @brief Remove (or move to the trash) a file or folder that was removed remotely * @ingroup libsync */ class PropagateLocalRemove : public PropagateItemJob @@ -42,13 +42,12 @@ class PropagateLocalRemove : public PropagateItemJob void start() override; private: - bool removeRecursively(const QString &path); - QString _error; + bool removeRecursively(const QString &absolute); bool _moveToTrash; }; /** - * @brief The PropagateLocalMkdir class + * @brief Make a local directory after discovering it on the server * @ingroup libsync */ class PropagateLocalMkdir : public PropagateItemJob @@ -75,7 +74,7 @@ class PropagateLocalMkdir : public PropagateItemJob }; /** - * @brief The PropagateLocalRename class + * @brief Rename a local file/directory after discovering a rename on the server * @ingroup libsync */ class PropagateLocalRename : public PropagateItemJob diff --git a/src/libsync/putfilejob.cpp b/src/libsync/putfilejob.cpp new file mode 100644 index 00000000000..b23e1260172 --- /dev/null +++ b/src/libsync/putfilejob.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "account.h" +#include "filesystem.h" +#include "networkjobs.h" +#include "owncloudpropagator_p.h" +#include "propagateremotedelete.h" +#include "propagateuploadfile.h" +#include "putfilejob.h" +#include "syncengine.h" + +#include "common/asserts.h" +#include "common/utility.h" + +#include "libsync/theme.h" + +#include + +using namespace std::chrono_literals; + +namespace OCC { + +Q_LOGGING_CATEGORY(lcPutJob, "sync.networkjob.put", QtInfoMsg) + +PUTFileJob::PUTFileJob( + Account *account, const QUrl &url, const QString &path, std::unique_ptr &&device, const QMap &headers, QObject *parent) + : AbstractNetworkJob(account, url, path, parent) + , _device(device.release()) + , _headers(headers) +{ + _device->setParent(this); + // Long uploads must not block non-propagation jobs. + setPriority(QNetworkRequest::LowPriority); +} + +PUTFileJob::~PUTFileJob() +{ +} + +void PUTFileJob::start() +{ + QNetworkRequest req; + for (auto it = _headers.cbegin(); it != _headers.cend(); ++it) { + req.setRawHeader(it.key(), it.value()); + } + sendRequest("PUT", req, _device); + _requestTimer.start(); + AbstractNetworkJob::start(); +} + +void PUTFileJob::finished() +{ + _device->close(); + + qCInfo(lcPutJob) << "PUT of" << reply()->request().url().toString() << "FINISHED WITH STATUS" << replyStatusString() + << reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() + << reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); +} + +void PUTFileJob::newReplyHook(QNetworkReply *reply) +{ + connect(reply, &QNetworkReply::uploadProgress, this, &PUTFileJob::uploadProgress); +} +} diff --git a/src/libsync/putfilejob.h b/src/libsync/putfilejob.h new file mode 100644 index 00000000000..3c06c03a28e --- /dev/null +++ b/src/libsync/putfilejob.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "networkjobs.h" + +#include + +namespace OCC { + +Q_DECLARE_LOGGING_CATEGORY(lcPutJob) + +/** + * @brief The PUTFileJob class + * @ingroup libsync + */ +class PUTFileJob : public AbstractNetworkJob +{ + Q_OBJECT + +private: + QIODevice *_device; + QMap _headers; + QString _errorString; + QElapsedTimer _requestTimer; + +public: + explicit PUTFileJob( + Account *account, const QUrl &url, const QString &path, std::unique_ptr &&device, const HeaderMap &headers, QObject *parent = nullptr); + ~PUTFileJob() override; + + void start() override; + + void finished() override; + + QIODevice *device() + { + return _device; + } + + QString errorString() + { + return _errorString.isEmpty() ? AbstractNetworkJob::errorString() : _errorString; + } + + std::chrono::milliseconds msSinceStart() const + { + return std::chrono::milliseconds(_requestTimer.elapsed()); + } + +protected: + void newReplyHook(QNetworkReply *reply) override; + +Q_SIGNALS: + void uploadProgress(qint64, qint64); + +}; + +} diff --git a/sync-exclude.lst b/src/libsync/sync-exclude.lst similarity index 89% rename from sync-exclude.lst rename to src/libsync/sync-exclude.lst index fb7e62c4012..7f0fc4e9843 100644 --- a/sync-exclude.lst +++ b/src/libsync/sync-exclude.lst @@ -38,4 +38,8 @@ *.unison .nfs* +# Relevant for Infinite Scale +].space +*.psec + My Saved Places. diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 990eb040aa9..7ce3ac1a4d0 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -15,44 +15,24 @@ #include "syncengine.h" #include "account.h" -#include "owncloudpropagator.h" +#include "common/asserts.h" +#include "common/syncfilestatus.h" #include "common/syncjournaldb.h" #include "common/syncjournalfilerecord.h" -#include "discoveryphase.h" -#include "creds/abstractcredentials.h" -#include "common/syncfilestatus.h" +#include "configfile.h" #include "csync_exclude.h" -#include "filesystem.h" -#include "propagateremotedelete.h" -#include "propagatedownload.h" -#include "common/asserts.h" #include "discovery.h" -#include "common/vfs.h" - -#ifdef Q_OS_WIN -#include -#else -#include -#endif +#include "discoveryphase.h" +#include "owncloudpropagator.h" -#include -#include #include -#include -#include #include #include -#include -#include +#include #include -#include #include #include -#include -#include -#include -#include using namespace std::chrono_literals; @@ -60,68 +40,41 @@ namespace OCC { Q_LOGGING_CATEGORY(lcEngine, "sync.engine", QtInfoMsg) -/** When the client touches a file, block change notifications for this duration (ms) - * - * On Linux and Windows the file watcher can't distinguish a change that originates - * from the client (like a download during a sync operation) and an external change. - * To work around that, all files the client touches are recorded and file change - * notifications for these are blocked for some time. This value controls for how - * long. - * - * Reasons this delay can't be very small: - * - it takes time for the change notification to arrive and to be processed by the client - * - some time could pass between the client recording that a file will be touched - * and its filesystem operation finishing, triggering the notification - */ -static const std::chrono::milliseconds s_touchedFilesMaxAgeMs(3 * 1000); - // doc in header -std::chrono::milliseconds SyncEngine::minimumFileAgeForUpload(2000); +std::chrono::seconds SyncEngine::minimumFileAgeForUpload(2s); -SyncEngine::SyncEngine(AccountPtr account, const QString &localPath, - const QString &remotePath, OCC::SyncJournalDb *journal) - : _account(account) +SyncEngine::SyncEngine(Account *account, const QUrl &baseUrl, const QString &localPath, const QString &remotePath, OCC::SyncJournalDb *journal, QObject *parent) + : QObject(parent) + , _account(account) + , _baseUrl(baseUrl) , _needsUpdate(false) , _syncRunning(false) , _localPath(localPath) , _remotePath(remotePath) , _journal(journal) , _progressInfo(new ProgressInfo) - , _hasNoneFiles(false) - , _hasRemoveFile(false) - , _uploadLimit(0) - , _downloadLimit(0) - , _anotherSyncNeeded(NoFollowUpSync) -{ - qRegisterMetaType("SyncFileItem"); - qRegisterMetaType("SyncFileItemPtr"); - qRegisterMetaType("SyncFileItem::Status"); - qRegisterMetaType("SyncFileStatus"); - qRegisterMetaType("SyncFileItemSet"); - qRegisterMetaType("SyncFileItem::Direction"); - +{ // Everything in the SyncEngine expects a trailing slash for the localPath. OC_ASSERT(localPath.endsWith(QLatin1Char('/'))); - _excludedFiles.reset(new ExcludedFiles); + _excludedFiles = new ExcludedFiles(this); - _syncFileStatusTracker.reset(new SyncFileStatusTracker(this)); - - _clearTouchedFilesTimer.setSingleShot(true); - _clearTouchedFilesTimer.setInterval(30s); - connect(&_clearTouchedFilesTimer, &QTimer::timeout, this, &SyncEngine::slotClearTouchedFiles); + // hmmm....I'd like to pass the excluded files too, because the tracker needs to know if a file is excluded or not, + // but that also requires knowing if hidden files are excluded which is a dynamic prop of the syncEngine...so + // for now leave it alone + _syncFileStatusTracker = new SyncFileStatusTracker(_localPath, journal, this); + connect(_syncFileStatusTracker, &SyncFileStatusTracker::fileStatusChanged, this, &SyncEngine::fileStatusChanged); } SyncEngine::~SyncEngine() { _goingDown = true; - abort(); - _excludedFiles.reset(); + abort(tr("application exit", "abort reason")); } /** * Check if the item is in the blacklist. - * If it should not be sync'ed because of the blacklist, update the item with the error instruction + * If it should not be synced because of the blacklist, update the item with the error instruction * and proper error message, and return true. * If the item is not in the blacklist, or the blacklist is stale, return false. */ @@ -165,7 +118,7 @@ bool SyncEngine::checkErrorBlacklisting(SyncFileItem &item) if (item._etag.isEmpty() || entry._lastTryEtag.isEmpty()) { qCInfo(lcEngine) << item._file << "one ETag is empty, no blacklisting"; return false; - } else if (item._etag != entry._lastTryEtag) { + } else if (item._etag.toUtf8() != entry._lastTryEtag) { qCInfo(lcEngine) << item._file << " is blacklisted, but has changed etag!"; return false; } @@ -175,21 +128,24 @@ bool SyncEngine::checkErrorBlacklisting(SyncFileItem &item) qCInfo(lcEngine) << "Item is on blacklist: " << entry._file << "retries:" << entry._retryCount << "for another" << waitSeconds << "s"; - // We need to indicate that we skip this file due to blacklisting // for reporting and for making sure we don't update the blacklist // entry yet. // Classification is this _instruction and _status - item._instruction = CSYNC_INSTRUCTION_IGNORE; - item._status = SyncFileItem::BlacklistedError; + item.setInstruction(CSYNC_INSTRUCTION_IGNORE); + if (entry._errorCategory == SyncJournalErrorBlacklistRecord::Category::LocalSoftError) { + item._status = SyncFileItem::SoftError; + item._errorString = entry._errorString; + } else { + item._status = SyncFileItem::BlacklistedError; - auto waitSecondsStr = Utility::durationToDescriptiveString1(1000 * waitSeconds); - item._errorString = tr("%1 (skipped due to earlier error, trying again in %2)").arg(entry._errorString, waitSecondsStr); + auto waitSecondsStr = Utility::durationToDescriptiveString1(std::chrono::seconds(waitSeconds)); + item._errorString = tr("%1 (skipped due to earlier error, trying again in %2)").arg(entry._errorString, waitSecondsStr); - if (entry._errorCategory == SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage) { - slotInsufficientRemoteStorage(); + if (entry._errorCategory == SyncJournalErrorBlacklistRecord::Category::InsufficientRemoteStorage) { + slotInsufficientRemoteStorage(); + } } - return true; } @@ -203,12 +159,10 @@ static bool isFileTransferInstruction(SyncInstructions instruction) void SyncEngine::deleteStaleDownloadInfos(const SyncFileItemSet &syncItems) { - // Find all downloadinfo paths that we want to preserve. + // Find all download paths that we want to preserve. QSet download_file_paths; for (const auto &it : syncItems) { - if (it->_direction == SyncFileItem::Down - && it->_type == ItemTypeFile - && isFileTransferInstruction(it->_instruction)) { + if (it->_direction == SyncFileItem::Down && it->_type == ItemTypeFile && isFileTransferInstruction(it->instruction())) { download_file_paths.insert(it->_file); } } @@ -228,25 +182,14 @@ void SyncEngine::deleteStaleUploadInfos(const SyncFileItemSet &syncItems) // Find all blacklisted paths that we want to preserve. QSet upload_file_paths; for (const auto &it : syncItems) { - if (it->_direction == SyncFileItem::Up - && it->_type == ItemTypeFile - && isFileTransferInstruction(it->_instruction)) { + if (it->_direction == SyncFileItem::Up && it->_type == ItemTypeFile && isFileTransferInstruction(it->instruction())) { upload_file_paths.insert(it->_file); } } // Delete from journal. - const auto &ids = _journal->deleteStaleUploadInfos(upload_file_paths); - - // Delete the stales chunk on the server. - if (account()->capabilities().chunkingNg()) { - for (auto transferId : ids) { - if (!transferId) - continue; // Was not a chunked upload - QUrl url = Utility::concatUrlPath(account()->url(), QLatin1String("remote.php/dav/uploads/") + account()->davUser() + QLatin1Char('/') + QString::number(transferId)); - (new DeleteJob(account(), url, this))->start(); - } - } + _journal->deleteStaleUploadInfos(upload_file_paths); + } void SyncEngine::deleteStaleErrorBlacklistEntries(const SyncFileItemSet &syncItems) @@ -279,8 +222,8 @@ void SyncEngine::conflictRecordMaintenance() // If so, add them now. // // This happens when the conflicts table is new or when conflict files - // are downlaoded but the server doesn't send conflict headers. - for (const auto &path : qAsConst(_seenConflictFiles)) { + // are downloaded but the server doesn't send conflict headers. + for (const auto &path : std::as_const(_seenConflictFiles)) { OC_ASSERT(Utility::isConflictFile(path)); auto bapath = path.toUtf8(); @@ -304,33 +247,13 @@ void SyncEngine::conflictRecordMaintenance() void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item) { + if (!_account) + return; + if (Utility::isConflictFile(item->_file)) _seenConflictFiles.insert(item->_file); - if (item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA && !item->isDirectory()) { - _hasNoneFiles = true; - } else if (item->_instruction == CSYNC_INSTRUCTION_NONE) { - _hasNoneFiles = true; - if (_account->capabilities().uploadConflictFiles() && Utility::isConflictFile(item->_file)) { - // For uploaded conflict files, files with no action performed on them should - // be displayed: but we mustn't overwrite the instruction if something happens - // to the file! - item->_errorString = tr("Unresolved conflict."); - item->_instruction = CSYNC_INSTRUCTION_IGNORE; - item->_status = SyncFileItem::Conflict; - } + if (item->instruction() == CSYNC_INSTRUCTION_NONE) return; - } else if (item->_instruction == CSYNC_INSTRUCTION_REMOVE && !item->_isSelectiveSync) { - _hasRemoveFile = true; - } else if (item->_instruction == CSYNC_INSTRUCTION_RENAME) { - _hasNoneFiles = true; // If a file (or every file) has been renamed, it means not al files where deleted - } else if (item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE - || item->_instruction == CSYNC_INSTRUCTION_SYNC) { - if (item->_direction == SyncFileItem::Up) { - // An upload of an existing file means that the file was left unchanged on the server - // This counts as a NONE for detecting if all the files on the server were changed - _hasNoneFiles = true; - } - } // check for blacklisting of this item. // if the item is on blacklist, the instruction was set to ERROR @@ -341,7 +264,8 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item) const auto it = _syncItems.find(item); if (it != _syncItems.cend()) { const auto &item2 = it->get(); - qCWarning(lcEngine) << "We already have an item for " << item2->_file << ":" << item2->_instruction << item2->_direction << "|" << item->_instruction << item->_direction; + qCWarning(lcEngine) << "We already have an item for " << item2->_file << ":" << item2->instruction() << item2->_direction << "|" + << item->instruction() << item->_direction; return false; } return true; @@ -362,39 +286,56 @@ void SyncEngine::startSync() return; } + // ugh. need to do this "first" so that we don't crash on _duration.stop() in finalize + _duration.reset(); + + if (!_account || !_syncOptions.isValid()) { + finalize(false); + return; + } + + if (!preSyncChecks()) { + finalize(false); + return; + } + _syncRunning = true; - _anotherSyncNeeded = NoFollowUpSync; - _clearTouchedFilesTimer.stop(); - _hasNoneFiles = false; - _hasRemoveFile = false; + // I think these probably belong in finalize but lower prio at the moment + _anotherSyncNeeded = false; _seenConflictFiles.clear(); - _progressInfo->reset(); + _syncItems.clear(); + _needsUpdate = false; + + // Functionality like selective sync might have set up etag storage + // filtering via schedulePathForRemoteDiscovery(). This *is* the next sync, so + // undo the filter to allow this sync to retrieve and store the correct etags. + _journal->clearEtagStorageFilter(); + _lastLocalDiscoveryStyle = _localDiscoveryStyle; + startDiscovery(); +} + +bool SyncEngine::preSyncChecks() +{ if (!QFileInfo::exists(_localPath)) { - _anotherSyncNeeded = DelayedFollowUp; // No _tr, it should only occur in non-mirall - Q_EMIT syncError(QStringLiteral("Unable to find local sync folder.")); - finalize(false); - return; + Q_EMIT syncError(tr("Unable to sync: the local folder is missing.")); + // todo: consider if we can/should somehow disable the folder in this case + return false; } - // Check free size on disk first. const qint64 minFree = criticalFreeSpaceLimit(); const qint64 freeBytes = Utility::freeDiskSpace(_localPath); if (freeBytes >= 0) { if (freeBytes < minFree) { - qCWarning(lcEngine()) << "Too little space available at" << _localPath << ". Have" - << freeBytes << "bytes and require at least" << minFree << "bytes"; - _anotherSyncNeeded = DelayedFollowUp; - Q_EMIT syncError(tr("Only %1 are available, need at least %2 to start", - "Placeholders are postfixed with file sizes using Utility::octetsToString()") - .arg( - Utility::octetsToString(freeBytes), - Utility::octetsToString(minFree))); - finalize(false); - return; + qCWarning(lcEngine()) << "Too little space available at" << _localPath << ". Have" << freeBytes << "bytes and require at least" << minFree + << "bytes"; + Q_EMIT syncError( + tr("Only %1 are available, need at least %2 to start", "Placeholders are postfixed with file sizes using Utility::octetsToString()") + .arg(Utility::octetsToString(freeBytes), Utility::octetsToString(minFree))); + return false; } else { qCInfo(lcEngine) << "There are" << Utility::octetsToString(freeBytes) << "available at" << _localPath; } @@ -402,43 +343,32 @@ void SyncEngine::startSync() qCWarning(lcEngine) << "Could not determine free space available at" << _localPath; } - _syncItems.clear(); - _needsUpdate = false; - - if (!_journal->exists()) { - qCInfo(lcEngine) << "New sync (no sync journal exists)"; - } else { - qCInfo(lcEngine) << "Sync with existing sync journal"; - } - - qCInfo(lcEngine) << "Using Qt " << qVersion() << " SSL library " << QSslSocket::sslLibraryVersionString() << " on " << Utility::platformName(); - - // This creates the DB if it does not exist yet. if (!_journal->open()) { - qCWarning(lcEngine) << "No way to create a sync journal!"; + qCWarning(lcEngine) << "Could not create a sync journal!"; Q_EMIT syncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder.")); - finalize(false); - return; - // database creation error! + return false; } - // Functionality like selective sync might have set up etag storage - // filtering via schedulePathForRemoteDiscovery(). This *is* the next sync, so - // undo the filter to allow this sync to retrieve and store the correct etags. - _journal->clearEtagStorageFilter(); - - _excludedFiles->setExcludeConflictFiles(!_account->capabilities().uploadConflictFiles()); - - _lastLocalDiscoveryStyle = _localDiscoveryStyle; + return true; +} - if (_syncOptions._vfs->mode() == Vfs::WithSuffix && _syncOptions._vfs->fileSuffix().isEmpty()) { - Q_EMIT syncError(tr("Using virtual files with suffix, but suffix is not set")); +void SyncEngine::startDiscovery() +{ + if (!_journal || !_journal->isOpen()) { + qCWarning(lcEngine) << "db is closed or deleted on start discovery"; + Q_EMIT syncError(tr("Cannot open the sync journal.")); finalize(false); return; } + qCInfo(lcEngine) << "#### Discovery start ####################################################" << _duration.duration(); + qCInfo(lcEngine) << "Server" << _account->capabilities().status().versionString() << (_account->isHttp2Supported() ? "Using HTTP/2" : ""); + _progressInfo->_status = ProgressInfo::Discovery; + Q_EMIT transmissionProgress(*_progressInfo); + + bool ok; - auto selectiveSyncBlackList = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok); + auto selectiveSyncBlackList = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, ok); if (ok) { bool usingSelectiveSync = (!selectiveSyncBlackList.isEmpty()); qCInfo(lcEngine) << (usingSelectiveSync ? "Using Selective Sync" : "NOT Using Selective Sync"); @@ -448,124 +378,67 @@ void SyncEngine::startSync() finalize(false); return; } + auto selectiveSyncWhiteList = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, ok); + if (!ok) { + qCWarning(lcEngine) << "Unable to read selective sync list, aborting."; + Q_EMIT syncError(tr("Unable to read from the sync journal.")); + finalize(false); + return; + } - _stopWatch.start(); - _progressInfo->_status = ProgressInfo::Starting; - emit transmissionProgress(*_progressInfo); - - qCInfo(lcEngine) << "#### Discovery start ####################################################"; - qCInfo(lcEngine) << "Server" << account()->serverVersion() - << (account()->isHttp2Supported() ? "Using HTTP/2" : ""); - _progressInfo->_status = ProgressInfo::Discovery; - emit transmissionProgress(*_progressInfo); + Q_ASSERT(_discoveryPhase == nullptr); - // TODO: add a constructor to DiscoveryPhase - // pass a syncEngine object rather than copying everyhting to another object - _discoveryPhase.reset(new DiscoveryPhase); - _discoveryPhase->_account = _account; - _discoveryPhase->_excludes = _excludedFiles.data(); + _discoveryPhase = new DiscoveryPhase(_account, syncOptions(), _baseUrl, this); + _discoveryPhase->_excludes = _excludedFiles; _discoveryPhase->_statedb = _journal; _discoveryPhase->_localDir = _localPath; if (!_discoveryPhase->_localDir.endsWith(QLatin1Char('/'))) - _discoveryPhase->_localDir+=QLatin1Char('/'); + _discoveryPhase->_localDir += QLatin1Char('/'); _discoveryPhase->_remoteFolder = _remotePath; if (!_discoveryPhase->_remoteFolder.endsWith(QLatin1Char('/'))) - _discoveryPhase->_remoteFolder+=QLatin1Char('/'); - _discoveryPhase->_syncOptions = _syncOptions; + _discoveryPhase->_remoteFolder += QLatin1Char('/'); _discoveryPhase->_shouldDiscoverLocaly = [this](const QString &s) { return shouldDiscoverLocally(s); }; _discoveryPhase->setSelectiveSyncBlackList(selectiveSyncBlackList); - _discoveryPhase->setSelectiveSyncWhiteList(_journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, &ok)); - if (!ok) { - qCWarning(lcEngine) << "Unable to read selective sync list, aborting."; - Q_EMIT syncError(tr("Unable to read from the sync journal.")); - finalize(false); - return; - } - - // Check for invalid character in old server version - QString invalidFilenamePattern = _account->capabilities().invalidFilenameRegex(); - if (invalidFilenamePattern.isNull() - && _account->serverVersionInt() < Account::makeServerVersion(8, 1, 0)) { - // Server versions older than 8.1 don't support some characters in filenames. - // If the capability is not set, default to a pattern that avoids uploading - // files with names that contain these. - // It's important to respect the capability also for older servers -- the - // version check doesn't make sense for custom servers. - invalidFilenamePattern = QLatin1String("[\\\\:?*\"<>|]"); - } - if (!invalidFilenamePattern.isEmpty()) - _discoveryPhase->_invalidFilenameRx = QRegExp(invalidFilenamePattern); + _discoveryPhase->setSelectiveSyncWhiteList(selectiveSyncWhiteList); _discoveryPhase->_serverBlacklistedFiles = _account->capabilities().blacklistedFiles(); _discoveryPhase->_ignoreHiddenFiles = ignoreHiddenFiles(); - connect(_discoveryPhase.data(), &DiscoveryPhase::itemDiscovered, this, &SyncEngine::slotItemDiscovered); - connect(_discoveryPhase.data(), &DiscoveryPhase::newBigFolder, this, &SyncEngine::newBigFolder); - connect(_discoveryPhase.data(), &DiscoveryPhase::fatalError, this, [this](const QString &errorString) { + const QString invalidFilenamePattern = _account->capabilities().invalidFilenameRegex(); + if (!invalidFilenamePattern.isEmpty()) { + _discoveryPhase->_invalidFilenameRx = QRegularExpression(invalidFilenamePattern); + } + + connect(_discoveryPhase, &DiscoveryPhase::silentlyExcluded, _syncFileStatusTracker, &SyncFileStatusTracker::slotAddExcluded); + connect(_discoveryPhase, &DiscoveryPhase::excluded, _syncFileStatusTracker, &SyncFileStatusTracker::slotAddExcluded); + + connect(_discoveryPhase, &DiscoveryPhase::itemDiscovered, this, &SyncEngine::slotItemDiscovered); + connect(_discoveryPhase, &DiscoveryPhase::fatalError, this, [this](const QString &errorString) { Q_EMIT syncError(errorString); finalize(false); }); - connect(_discoveryPhase.data(), &DiscoveryPhase::finished, this, &SyncEngine::slotDiscoveryFinished); - connect(_discoveryPhase.data(), &DiscoveryPhase::silentlyExcluded, - _syncFileStatusTracker.data(), &SyncFileStatusTracker::slotAddSilentlyExcluded); - connect(_discoveryPhase.data(), &DiscoveryPhase::excluded, - _syncFileStatusTracker.data(), &SyncFileStatusTracker::slotAddSilentlyExcluded); - connect(_discoveryPhase.data(), &DiscoveryPhase::excluded, - this, &SyncEngine::excluded); - - auto discoveryJob = new ProcessDirectoryJob( - _discoveryPhase.data(), PinState::AlwaysLocal, _discoveryPhase.data()); - _discoveryPhase->startJob(discoveryJob); - connect(discoveryJob, &ProcessDirectoryJob::etag, this, &SyncEngine::slotRootEtagReceived); -} - -void SyncEngine::slotFolderDiscovered(bool local, const QString &folder) -{ - // Don't wanna overload the UI - if (!_lastUpdateProgressCallbackCall.isValid()) { - _lastUpdateProgressCallbackCall.start(); // first call - } else if (_lastUpdateProgressCallbackCall.elapsed() < 200) { - return; - } else { - _lastUpdateProgressCallbackCall.start(); - } + connect(_discoveryPhase, &DiscoveryPhase::excluded, this, &SyncEngine::excluded); + connect(_discoveryPhase, &DiscoveryPhase::finished, this, &SyncEngine::slotDiscoveryFinished); - if (local) { - _progressInfo->_currentDiscoveredLocalFolder = folder; - _progressInfo->_currentDiscoveredRemoteFolder.clear(); - } else { - _progressInfo->_currentDiscoveredRemoteFolder = folder; - _progressInfo->_currentDiscoveredLocalFolder.clear(); - } - emit transmissionProgress(*_progressInfo); -} -void SyncEngine::slotRootEtagReceived(const QByteArray &e, const QDateTime &time) -{ - if (_remoteRootEtag.isEmpty()) { - qCDebug(lcEngine) << "Root etag:" << e; - _remoteRootEtag = e; - emit rootEtag(_remoteRootEtag, time); - } -} + auto discoveryJob = new ProcessDirectoryJob(_discoveryPhase, PinState::AlwaysLocal, _discoveryPhase); + connect(discoveryJob, &ProcessDirectoryJob::etag, this, &SyncEngine::slotRootEtagReceived); -void SyncEngine::slotNewItem(const SyncFileItemPtr &item) -{ - _progressInfo->adjustTotalsForFile(*item); + _discoveryPhase->startJob(discoveryJob); } void SyncEngine::slotDiscoveryFinished() { if (!_discoveryPhase) { - // There was an error that was already taken care of + // There was an abort or error that was already finalized return; } - qCInfo(lcEngine) << "#### Discovery end #################################################### " << _stopWatch.addLapTime(QStringLiteral("Discovery Finished")) << "ms"; + qCInfo(lcEngine) << "#### Discovery end ####################################################" << _duration.duration(); // Sanity check - if (!_journal->open()) { - qCWarning(lcEngine) << "Bailing out, DB failure"; - Q_EMIT syncError(tr("Cannot open the sync journal")); + if (!_journal || !_journal->isOpen()) { + qCWarning(lcEngine) << "db is closed or deleted on discovery finished"; + Q_EMIT syncError(tr("Cannot open the sync journal.")); finalize(false); return; } else { @@ -576,181 +449,98 @@ void SyncEngine::slotDiscoveryFinished() _progressInfo->_currentDiscoveredRemoteFolder.clear(); _progressInfo->_currentDiscoveredLocalFolder.clear(); _progressInfo->_status = ProgressInfo::Reconcile; - emit transmissionProgress(*_progressInfo); + Q_EMIT transmissionProgress(*_progressInfo); - // qCInfo(lcEngine) << "Permissions of the root folder: " << _csync_ctx->remote.root_perms.toString(); - auto finish = [this]{ + auto databaseFingerprint = _journal->dataFingerprint(); + // If databaseFingerprint is empty, this means that there was no information in the database + // (for example, upgrading from a previous version, or first sync, or server not supporting fingerprint) + if (!databaseFingerprint.isEmpty() && _discoveryPhase && _discoveryPhase->_dataFingerprint != databaseFingerprint) { + qCInfo(lcEngine) << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryPhase->_dataFingerprint; + restoreOldFiles(_syncItems); + } + if (_discoveryPhase->_anotherSyncNeeded) { + _anotherSyncNeeded = true; + } + startPropagation(); +} - auto databaseFingerprint = _journal->dataFingerprint(); - // If databaseFingerprint is empty, this means that there was no information in the database - // (for example, upgrading from a previous version, or first sync, or server not supporting fingerprint) - if (!databaseFingerprint.isEmpty() && _discoveryPhase - && _discoveryPhase->_dataFingerprint != databaseFingerprint) { - qCInfo(lcEngine) << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryPhase->_dataFingerprint; - restoreOldFiles(_syncItems); - } +void SyncEngine::startPropagation() +{ + if (!_journal || !_journal->isOpen()) { + qCWarning(lcEngine) << "db is closed or deleted on start propagation"; + Q_EMIT syncError(tr("Cannot open the sync journal.")); + finalize(false); + return; + } - if (_discoveryPhase->_anotherSyncNeeded && _anotherSyncNeeded == NoFollowUpSync) { - _anotherSyncNeeded = ImmediateFollowUp; - } + Q_ASSERT(std::is_sorted(_syncItems.begin(), _syncItems.end())); - Q_ASSERT(std::is_sorted(_syncItems.begin(), _syncItems.end())); - - const auto regex = syncOptions().fileRegex(); - if (regex.isValid()) { - QSet names; - for (auto &i : _syncItems) { - if (regex.match(i->_file).hasMatch()) { - int index = -1; - QStringRef ref; - do { - ref = i->_file.midRef(0, index); - names.insert(ref); - index = ref.lastIndexOf(QLatin1Char('/')); - } while (index > 0); - } - } - //std::erase_if c++20 - // https://en.cppreference.com/w/cpp/container/set/erase_if - const auto erase_if = [](auto &c, const auto &pred) { - auto old_size = c.size(); - for (auto i = c.begin(), last = c.end(); i != last;) { - if (pred(*i)) { - i = c.erase(i); - } else { - ++i; - } - } - return old_size - c.size(); - }; - erase_if(_syncItems, [&names](const SyncFileItemPtr &i) { - return !names.contains(QStringRef { &i->_file }); - }); - } + qCInfo(lcEngine) << "#### Reconcile (aboutToPropagate) ####################################################" << _duration.duration(); - qCInfo(lcEngine) << "#### Reconcile (aboutToPropagate) #################################################### " << _stopWatch.addLapTime(QStringLiteral("Reconcile (aboutToPropagate)")) << "ms"; + _localDiscoveryPaths.clear(); - _localDiscoveryPaths.clear(); + // To announce the beginning of the sync + _syncFileStatusTracker->updateAboutToPropagate(_syncItems); + Q_EMIT aboutToPropagate(_syncItems); - // To announce the beginning of the sync - emit aboutToPropagate(_syncItems); + qCInfo(lcEngine) << "#### Reconcile (aboutToPropagate OK) ####################################################" << _duration.duration(); - qCInfo(lcEngine) << "#### Reconcile (aboutToPropagate OK) #################################################### "<< _stopWatch.addLapTime(QStringLiteral("Reconcile (aboutToPropagate OK)")) << "ms"; + // it's important to do this before ProgressInfo::start(), to announce start of new sync + _progressInfo->_status = ProgressInfo::Propagation; + Q_EMIT transmissionProgress(*_progressInfo); + _progressInfo->startEstimateUpdates(); - // it's important to do this before ProgressInfo::start(), to announce start of new sync - _progressInfo->_status = ProgressInfo::Propagation; - emit transmissionProgress(*_progressInfo); - _progressInfo->startEstimateUpdates(); + // do a database commit + _journal->commit(QStringLiteral("post treewalk")); - // post update phase script: allow to tweak stuff by a custom script in debug mode. - if (!qEnvironmentVariableIsEmpty("OWNCLOUD_POST_UPDATE_SCRIPT")) { -#ifndef NDEBUG - const QString script = qEnvironmentVariable("OWNCLOUD_POST_UPDATE_SCRIPT"); + // the propagator should already be cleaned up as part of the finalize impl after last sync completed. + // If it's still around, something is out of whack + Q_ASSERT(_propagator == nullptr); - qCDebug(lcEngine) << "Post Update Script: " << script; - QProcess::execute(script, {}); -#else - qCWarning(lcEngine) << "**** Attention: POST_UPDATE_SCRIPT installed, but not executed because compiled with NDEBUG"; -#endif - } + // set up a new propagator and complete the sync + _propagator = new OwncloudPropagator(_account, syncOptions(), _baseUrl, _localPath, _remotePath, _journal, this); + _propagator->setMoveToTrash(_moveToTrash); + connect(_propagator, &OwncloudPropagator::itemCompleted, this, &SyncEngine::slotItemCompleted); + connect(_propagator, &OwncloudPropagator::progress, this, &SyncEngine::slotProgress); + connect(_propagator, &OwncloudPropagator::updateFileTotal, this, &SyncEngine::updateFileTotal); + connect(_propagator, &OwncloudPropagator::finished, this, &SyncEngine::slotPropagationFinished, Qt::QueuedConnection); + connect(_propagator, &OwncloudPropagator::seenLockedFile, this, &SyncEngine::seenLockedFile); + connect(_propagator, &OwncloudPropagator::insufficientLocalStorage, this, &SyncEngine::slotInsufficientLocalStorage); + connect(_propagator, &OwncloudPropagator::insufficientRemoteStorage, this, &SyncEngine::slotInsufficientRemoteStorage); + connect(_propagator, &OwncloudPropagator::newItem, this, &SyncEngine::slotNewItem); - // do a database commit - _journal->commit(QStringLiteral("post treewalk")); - - _propagator = QSharedPointer( - new OwncloudPropagator(_account, _localPath, _remotePath, _journal)); - _propagator->setSyncOptions(_syncOptions); - connect(_propagator.data(), &OwncloudPropagator::itemCompleted, - this, &SyncEngine::slotItemCompleted); - connect(_propagator.data(), &OwncloudPropagator::progress, - this, &SyncEngine::slotProgress); - connect(_propagator.data(), &OwncloudPropagator::updateFileTotal, - this, &SyncEngine::updateFileTotal); - connect(_propagator.data(), &OwncloudPropagator::finished, this, &SyncEngine::slotPropagationFinished, Qt::QueuedConnection); - connect(_propagator.data(), &OwncloudPropagator::seenLockedFile, this, &SyncEngine::seenLockedFile); - connect(_propagator.data(), &OwncloudPropagator::touchedFile, this, &SyncEngine::slotAddTouchedFile); - connect(_propagator.data(), &OwncloudPropagator::insufficientLocalStorage, this, &SyncEngine::slotInsufficientLocalStorage); - connect(_propagator.data(), &OwncloudPropagator::insufficientRemoteStorage, this, &SyncEngine::slotInsufficientRemoteStorage); - connect(_propagator.data(), &OwncloudPropagator::newItem, this, &SyncEngine::slotNewItem); - - // apply the network limits to the propagator - setNetworkLimits(_uploadLimit, _downloadLimit); - - deleteStaleDownloadInfos(_syncItems); - deleteStaleUploadInfos(_syncItems); - deleteStaleErrorBlacklistEntries(_syncItems); - _journal->commit(QStringLiteral("post stale entry removal")); - - // Emit the started signal only after the propagator has been set up. - if (_needsUpdate) - Q_EMIT started(); - - _propagator->start(std::move(_syncItems)); - - qCInfo(lcEngine) << "#### Post-Reconcile end #################################################### " << _stopWatch.addLapTime(QStringLiteral("Post-Reconcile Finished")) << "ms"; - }; - - if (!_hasNoneFiles && _hasRemoveFile) { - qCInfo(lcEngine) << "All the files are going to be changed, asking the user"; - int side = 0; // > 0 means more deleted on the server. < 0 means more deleted on the client - for (const auto &it : qAsConst(_syncItems)) { - if (it->_instruction == CSYNC_INSTRUCTION_REMOVE) { - side += it->_direction == SyncFileItem::Down ? 1 : -1; - } - } + deleteStaleDownloadInfos(_syncItems); + deleteStaleUploadInfos(_syncItems); + deleteStaleErrorBlacklistEntries(_syncItems); + _journal->commit(QStringLiteral("post stale entry removal")); - QPointer guard = new QObject(); - QPointer self = this; - auto callback = [this, self, finish, guard](bool cancel) -> void { - // use a guard to ensure its only called once... - // qpointer to self to ensure we still exist - if (!guard || !self) { - return; - } - guard->deleteLater(); - if (cancel) { - qCInfo(lcEngine) << "User aborted sync"; - finalize(false); - return; - } else { - finish(); - } - }; - emit aboutToRemoveAllFiles(side >= 0 ? SyncFileItem::Down : SyncFileItem::Up, callback); - return; + // Emit the started signal only after the propagator has been set up. + if (_needsUpdate) { + _syncFileStatusTracker->updateSyncRunningChanged(); + Q_EMIT started(); } - finish(); -} - -void SyncEngine::setNetworkLimits(int upload, int download) -{ - _uploadLimit = upload; - _downloadLimit = download; - if (!_propagator) - return; + _propagator->start(std::move(_syncItems)); - _propagator->_uploadLimit = upload; - _propagator->_downloadLimit = download; - if (upload != 0 || download != 0) { - qCInfo(lcEngine) << "Network Limits (down/up) " << upload << download; - } + qCInfo(lcEngine) << "#### Post-Reconcile end ####################################################" << _duration.duration(); } -void SyncEngine::slotItemCompleted(const SyncFileItemPtr &item) -{ - _progressInfo->setProgressComplete(*item); - - emit transmissionProgress(*_progressInfo); - emit itemCompleted(item); -} void SyncEngine::slotPropagationFinished(bool success) { - if (_propagator->_anotherSyncNeeded && _anotherSyncNeeded == NoFollowUpSync) { - _anotherSyncNeeded = ImmediateFollowUp; + // this is quite important to check, as if the folder is going down the abort may still be finishing but the journal has already been closed or even + // deleted. + if (!_journal || !_journal->isOpen()) { + qCWarning(lcEngine) << "db is closed or deleted on propagation finished"; + Q_EMIT syncError(tr("Cannot open the sync journal.")); + finalize(false); + return; + } + + if (_propagator->_anotherSyncNeeded) { + _anotherSyncNeeded = true; } if (success && _discoveryPhase) { @@ -759,6 +549,12 @@ void SyncEngine::slotPropagationFinished(bool success) conflictRecordMaintenance(); + // update placeholders for files that where marked as dirty in a previous run + qCInfo(lcEngine) << "Updating files marked as dirty"; + for (const auto &record : _journal->getFileRecordsWithDirtyPlaceholders()) { + _propagator->updateMetadata(*SyncFileItem::fromSyncJournalFileRecord(record)); + } + _journal->deleteStaleFlagsEntries(); _journal->commit(QStringLiteral("All Finished."), false); @@ -767,43 +563,135 @@ void SyncEngine::slotPropagationFinished(bool success) // so we don't count this twice (like Recent Files) _progressInfo->_lastCompletedItem = SyncFileItem(); _progressInfo->_status = ProgressInfo::Done; - emit transmissionProgress(*_progressInfo); + Q_EMIT transmissionProgress(*_progressInfo); finalize(success); } void SyncEngine::finalize(bool success) { - qCInfo(lcEngine) << "Sync run took " << _stopWatch.addLapTime(QStringLiteral("Sync Finished")) << "ms"; - _stopWatch.stop(); + qDebug() << "Sync run took" << _duration.duration() << " for folder: " << _localPath; - if (_discoveryPhase) { - _discoveryPhase.take()->deleteLater(); + if (!_goingDown) { + _duration.stop(); + if (_discoveryPhase) { + _discoveryPhase->disconnect(); + _discoveryPhase->deleteLater(); + } + + _syncRunning = false; + // need to reality check this: is there some universe where the dying sync engine needs to notify "finished"? + Q_EMIT finished(success); + _syncFileStatusTracker->updateSyncFinished(); + + // emit finished must happen before delete of propagator. I don't know why but it does seem to break things + // if it's moved around. + if (_propagator) { + _propagator->disconnect(); + _propagator->deleteLater(); + } + + _seenConflictFiles.clear(); + _uniqueErrors.clear(); + _localDiscoveryPaths.clear(); + _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly; } - _syncRunning = false; - emit finished(success); +} - // Delete the propagator only after emitting the signal. - _propagator.clear(); - _seenConflictFiles.clear(); - _uniqueErrors.clear(); - _localDiscoveryPaths.clear(); - _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly; +void SyncEngine::abort(const QString &reason) +{ + bool aborting = false; + bool finalizeLater = false; + if (_propagator) { + aborting = true; + // If we're already in the propagation phase, aborting that is sufficient + _propagator->abort(); + // unfortunately we can't run finalize right away though, because it deletes the _propagator which may still be finishing the abort! + // the finalize now happens in slotPropagationFinished + finalizeLater = true; + } else if (_discoveryPhase) { + aborting = true; + // this appears to be: ensure discovery can't finish which might trigger the propagator + // _discoveryPhase is fully cleaned up in finalize + disconnect(_discoveryPhase, nullptr, this, nullptr); + } + if (aborting) { + qCInfo(lcEngine) << "Aborting sync, stated reason:" << reason; + if (!_goingDown) { + Q_EMIT syncError(tr("Aborted due to %1").arg(reason)); + } + // I don't think this is correct. As originally implemented the first call to finalize deleted the discovery and propagator. + // the problem with this is that the propagator abort requires any already running jobs to finish. deleting the propagator + // before the propagator finishes is risky at best! + if (!finalizeLater) + finalize(false); + } +} - _clearTouchedFilesTimer.start(); +void SyncEngine::slotRootEtagReceived(const QString &e, const QDateTime &time) +{ + if (_remoteRootEtag.isEmpty()) { + qCDebug(lcEngine) << "Root etag:" << e; + _remoteRootEtag = e; + Q_EMIT rootEtagDiscovered(_remoteRootEtag, time); + } +} + +void SyncEngine::slotNewItem(const SyncFileItemPtr &item) +{ + _progressInfo->adjustTotalsForFile(*item); +} + +void SyncEngine::slotFolderDiscovered(bool local, const QString &folder) +{ + // Don't want to overload the UI + if (!_lastUpdateProgressCallbackCall.isValid()) { + _lastUpdateProgressCallbackCall.start(); // first call + } else if (_lastUpdateProgressCallbackCall.elapsed() < 200) { + return; + } else { + _lastUpdateProgressCallbackCall.start(); + } + + if (local) { + _progressInfo->_currentDiscoveredLocalFolder = folder; + _progressInfo->_currentDiscoveredRemoteFolder.clear(); + } else { + _progressInfo->_currentDiscoveredRemoteFolder = folder; + _progressInfo->_currentDiscoveredLocalFolder.clear(); + } + Q_EMIT transmissionProgress(*_progressInfo); } void SyncEngine::slotProgress(const SyncFileItem &item, qint64 current) { _progressInfo->setProgressItem(item, current); - emit transmissionProgress(*_progressInfo); + Q_EMIT transmissionProgress(*_progressInfo); } void SyncEngine::updateFileTotal(const SyncFileItem &item, qint64 newSize) { _progressInfo->updateTotalsForFile(item, newSize); - emit transmissionProgress(*_progressInfo); + Q_EMIT transmissionProgress(*_progressInfo); +} + +void SyncEngine::slotItemCompleted(const SyncFileItemPtr &item) +{ + Q_ASSERT([&] { + // ensure the item is not marked as finished twice + const bool finished = item->_finished; + item->_finished = true; + return !finished; + }()); + + _progressInfo->setProgressComplete(*item); + + Q_EMIT transmissionProgress(*_progressInfo); + + _syncFileStatusTracker->updateItemCompleted(item); + Q_EMIT itemCompleted(item); } + void SyncEngine::restoreOldFiles(SyncFileItemSet &syncItems) { /* When the server is trying to send us lots of file in the past, this means that a backup @@ -816,14 +704,14 @@ void SyncEngine::restoreOldFiles(SyncFileItemSet &syncItems) if ((*it)->_direction != SyncFileItem::Down) continue; - switch ((*it)->_instruction) { + switch ((*it)->instruction()) { case CSYNC_INSTRUCTION_SYNC: qCWarning(lcEngine) << "restoreOldFiles: RESTORING" << (*it)->_file; - (*it)->_instruction = CSYNC_INSTRUCTION_CONFLICT; + (*it)->setInstruction(CSYNC_INSTRUCTION_CONFLICT); break; case CSYNC_INSTRUCTION_REMOVE: qCWarning(lcEngine) << "restoreOldFiles: RESTORING" << (*it)->_file; - (*it)->_instruction = CSYNC_INSTRUCTION_NEW; + (*it)->setInstruction(CSYNC_INSTRUCTION_NEW); (*it)->_direction = SyncFileItem::Up; break; case CSYNC_INSTRUCTION_RENAME: @@ -837,59 +725,18 @@ void SyncEngine::restoreOldFiles(SyncFileItemSet &syncItems) } } -void SyncEngine::slotAddTouchedFile(const QString &fn) -{ - QElapsedTimer now; - now.start(); - const QString file = QDir::cleanPath(fn); - - // Iterate from the oldest and remove anything older than 15 seconds. - while (true) { - // don't use a loop as we do erase in here - // don't use remove_if as we can stop as soon as we find the first new item - auto start = _touchedFiles.cbegin(); - if (start == _touchedFiles.cend()) { - break; - } - // Compare to our new QElapsedTimer instead of using elapsed(). - // This avoids querying the current time from the OS for every loop. - const auto elapsed = std::chrono::milliseconds(now.msecsSinceReference() - start->first.msecsSinceReference()); - if (elapsed <= s_touchedFilesMaxAgeMs) { - // We found the first path younger than the maximum age, keep the rest. - break; - } - _touchedFiles.erase(start); - } - - // This should be the largest QElapsedTimer yet, use constEnd() as hint. - _touchedFiles.insert(_touchedFiles.cend(), { now, file }); -} - -void SyncEngine::slotClearTouchedFiles() -{ - _touchedFiles.clear(); -} - -bool SyncEngine::wasFileTouched(const QString &fn) const -{ - // Start from the end (most recent) and look for our path. Check the time just in case. - for (auto it = _touchedFiles.crbegin(); it != _touchedFiles.crend(); ++it) { - if (it->second == fn) - return std::chrono::milliseconds(it->first.elapsed()) <= s_touchedFilesMaxAgeMs; - } - return false; -} - -AccountPtr SyncEngine::account() const -{ - return _account; -} - void SyncEngine::setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::set paths) { _localDiscoveryStyle = style; _localDiscoveryPaths = std::move(paths); + + // add running upload to the local discovery + const auto info = _journal->getUploadInfos(); + for (const auto &i : info) { + _localDiscoveryPaths.insert(i._path); + } + // Normalize to make sure that no path is a contained in another. // Note: for simplicity, this code consider anything less than '/' as a path separator, so for // example, this will remove "foo.bar" if "foo" is in the list. This will mean we might have @@ -909,8 +756,9 @@ void SyncEngine::setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::setsize() > path.size() && it->at(path.size()) == QLatin1Char('/')) return true; ++it; @@ -945,66 +793,19 @@ bool SyncEngine::shouldDiscoverLocally(const QString &path) const return false; } -void SyncEngine::wipeVirtualFiles(const QString &localPath, SyncJournalDb &journal, Vfs &vfs) -{ - qCInfo(lcEngine) << "Wiping virtual files inside" << localPath; - journal.getFilesBelowPath(QByteArray(), [&](const SyncJournalFileRecord &rec) { - if (rec._type != ItemTypeVirtualFile && rec._type != ItemTypeVirtualFileDownload) - return; - - qCDebug(lcEngine) << "Removing db record for" << rec._path; - journal.deleteFileRecord(QString::fromUtf8(rec._path)); - - // If the local file is a dehydrated placeholder, wipe it too. - // Otherwise leave it to allow the next sync to have a new-new conflict. - QString localFile = localPath + QString::fromUtf8(rec._path); - if (QFile::exists(localFile) && vfs.isDehydratedPlaceholder(localFile)) { - qCDebug(lcEngine) << "Removing local dehydrated placeholder" << rec._path; - FileSystem::remove(localFile); - } - }); - - journal.forceRemoteDiscoveryNextSync(); - - // Postcondition: No ItemTypeVirtualFile / ItemTypeVirtualFileDownload left in the db. - // But hydrated placeholders may still be around. -} - -void SyncEngine::abort() -{ - if (_propagator) - qCInfo(lcEngine) << "Aborting sync"; - - if (_propagator) { - // If we're already in the propagation phase, aborting that is sufficient - _propagator->abort(); - } else if (_discoveryPhase) { - // Delete the discovery and all child jobs after ensuring - // it can't finish and start the propagator - disconnect(_discoveryPhase.data(), nullptr, this, nullptr); - _discoveryPhase.take()->deleteLater(); - - if (!_goingDown) { - Q_EMIT syncError(tr("Aborted")); - } - finalize(false); - } -} - void SyncEngine::slotSummaryError(const QString &message) { if (_uniqueErrors.contains(message)) return; _uniqueErrors.insert(message); - emit syncError(message, ErrorCategory::Normal); + Q_EMIT syncError(message, ErrorCategory::Normal); } void SyncEngine::slotInsufficientLocalStorage() { - slotSummaryError( - tr("Disk space is low: Downloads that would reduce free space " - "below %1 were skipped.") + slotSummaryError(tr("Disk space is low: Downloads that would reduce free space " + "below %1 were skipped.") .arg(Utility::octetsToString(freeSpaceLimit()))); } @@ -1015,7 +816,47 @@ void SyncEngine::slotInsufficientRemoteStorage() return; _uniqueErrors.insert(msg); - emit syncError(msg, ErrorCategory::InsufficientRemoteStorage); + Q_EMIT syncError(msg, ErrorCategory::InsufficientRemoteStorage); +} + +bool SyncEngine::isExcluded(QStringView filePath) const +{ + Q_ASSERT(QDir::isAbsolutePath(filePath.toString())); + return _excludedFiles->isExcluded(filePath, localPath(), ignoreHiddenFiles()); +} + +bool SyncEngine::loadDefaultExcludes() +{ + ConfigFile::setupDefaultExcludeFilePaths(*_excludedFiles); + return _excludedFiles->reloadExcludeFiles(); +} + +void SyncEngine::clearManualExcludes() +{ + _excludedFiles->clearManualExcludes(); +} + +SyncFileStatus SyncEngine::fileStatus(const QString &relativePath) +{ + if (!_syncFileStatusTracker) + return {}; + return _syncFileStatusTracker->fileStatus(relativePath); +} + +void SyncEngine::pathTouched(const QString &fileName) +{ + if (_syncFileStatusTracker) + _syncFileStatusTracker->pathTouched(fileName); +} + +bool SyncEngine::reloadExcludes() +{ + return _excludedFiles->reloadExcludeFiles(); +} + +void SyncEngine::addManualExclude(const QString &filePath) +{ + _excludedFiles->addManualExclude(filePath); } } // namespace OCC diff --git a/src/libsync/syncengine.h b/src/libsync/syncengine.h index 118aa888ca1..4ccb0b79134 100644 --- a/src/libsync/syncengine.h +++ b/src/libsync/syncengine.h @@ -15,7 +15,13 @@ #pragma once -#include +#include "account.h" +#include "common/checksums.h" +#include "common/chronoelapsedtimer.h" +#include "discoveryphase.h" +#include "progressdispatcher.h" +#include "syncfileitem.h" +#include "syncfilestatustracker.h" #include #include @@ -23,18 +29,10 @@ #include #include #include -#include -#include +#include -#include "csync/csync_exclude.h" - -#include "syncfileitem.h" -#include "progressdispatcher.h" -#include "common/utility.h" -#include "syncfilestatustracker.h" -#include "accountfwd.h" -#include "discoveryphase.h" -#include "common/checksums.h" +#include +#include class QProcess; @@ -45,12 +43,6 @@ class SyncJournalDb; class OwncloudPropagator; class ProcessDirectoryJob; -enum AnotherSyncNeeded { - NoFollowUpSync, - ImmediateFollowUp, // schedule this again immediately (limited amount of times) - DelayedFollowUp // regularly schedule this folder again (around 1/minute, unlimited) -}; - /** * @brief The SyncEngine class * @ingroup libsync @@ -58,35 +50,44 @@ enum AnotherSyncNeeded { class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject { Q_OBJECT + public: - SyncEngine(AccountPtr account, const QString &localPath, - const QString &remotePath, SyncJournalDb *journal); + SyncEngine(Account *account, const QUrl &baseUrl, const QString &localPath, const QString &remotePath, SyncJournalDb *journal, QObject *parent); ~SyncEngine() override; Q_INVOKABLE void startSync(); - void setNetworkLimits(int upload, int download); - /* Abort the sync. Called from the main thread */ - void abort(); + /* Abort the sync. Called from the main thread via the folder list pause sync action, but could also come from folder delete or shutdown */ + void abort(const QString &reason); bool isSyncRunning() const { return _syncRunning; } - SyncOptions syncOptions() const { return _syncOptions; } - void setSyncOptions(const SyncOptions &options) { _syncOptions = options; } + const SyncOptions &syncOptions() const { return _syncOptions; } + void setSyncOptions(const SyncOptions &options) + { + Q_ASSERT(options.isValid()); + _syncOptions = options; + } bool ignoreHiddenFiles() const { return _ignore_hidden_files; } void setIgnoreHiddenFiles(bool ignore) { _ignore_hidden_files = ignore; } - ExcludedFiles &excludedFiles() { return *_excludedFiles; } - Utility::StopWatch &stopWatch() { return _stopWatch; } - SyncFileStatusTracker &syncFileStatusTracker() { return *_syncFileStatusTracker; } + void setMoveToTrash(bool trashIt) { _moveToTrash = trashIt; } + bool moveToTrash() const { return _moveToTrash; } - /* Returns whether another sync is needed to complete the sync */ - AnotherSyncNeeded isAnotherSyncNeeded() { return _anotherSyncNeeded; } + // file path must be absolute! + bool isExcluded(QStringView filePath) const; + void addManualExclude(const QString &filePath); + bool loadDefaultExcludes(); + bool reloadExcludes(); + void clearManualExcludes(); + + SyncFileStatus fileStatus(const QString &relativePath); + void pathTouched(const QString &fileName); - bool wasFileTouched(const QString &fn) const; - AccountPtr account() const; - SyncJournalDb *journal() const { return _journal; } + /* Returns whether another sync is needed to complete the sync */ + bool isAnotherSyncNeeded() { return _anotherSyncNeeded; } + QString localPath() const { return _localPath; } /** Duration in ms that uploads should be delayed after a file change @@ -99,7 +100,7 @@ class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject * To avoid that, uploads of files where the distance between the mtime and the * current time is less than this duration are skipped. */ - static std::chrono::milliseconds minimumFileAgeForUpload; + static std::chrono::seconds minimumFileAgeForUpload; /** * Control whether local discovery should read from filesystem or db. @@ -126,24 +127,16 @@ class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject /** Access the last sync run's local discovery style */ LocalDiscoveryStyle lastLocalDiscoveryStyle() const { return _lastLocalDiscoveryStyle; } - /** Removes all virtual file db entries and dehydrated local placeholders. - * - * Particularly useful when switching off vfs mode or switching to a - * different kind of vfs. - * - * Note that *hydrated* placeholder files might still be left. These will - * get cleaned up by Vfs::unregisterFolder(). - */ - static void wipeVirtualFiles(const QString &localPath, SyncJournalDb &journal, Vfs &vfs); - auto getPropagator() { return _propagator; } // for the test +Q_SIGNALS: + + void fileStatusChanged(const QString &systemFileName, SyncFileStatus fileStatus); -signals: // During update, before reconcile - void rootEtag(const QByteArray &, const QDateTime &); + void rootEtagDiscovered(const QString &, const QDateTime &); // after the above signals. with the items that actually need propagating - void aboutToPropagate(SyncFileItemSet &); + void aboutToPropagate(const SyncFileItemSet &items); // after each item completed by a job (successful or not) void itemCompleted(const SyncFileItemPtr &); @@ -152,30 +145,20 @@ class OWNCLOUDSYNC_EXPORT SyncEngine : public QObject /// We've produced a new sync error of a type. void syncError(const QString &message, ErrorCategory category = ErrorCategory::Normal); - void excluded(const QString &path, CSYNC_EXCLUDE_TYPE reason); + void excluded(const QString &path); void finished(bool success); void started(); - /** - * Emited when the sync engine detects that all the files have been removed or change. - * This usually happen when the server was reset or something. - * Call abort(true) slot connected from this signal to abort the sync. - */ - void aboutToRemoveAllFiles(SyncFileItem::Direction direction, std::function abort); - - // A new folder was discovered and was not synced because of the confirmation feature - void newBigFolder(const QString &folder, bool isExternal); - /** Emitted when propagation has problems with a locked file. * * Forwarded from OwncloudPropagator::seenLockedFile. */ void seenLockedFile(const QString &fileName, FileSystem::LockMode mode); -private slots: +private Q_SLOTS: void slotFolderDiscovered(bool local, const QString &folder); - void slotRootEtagReceived(const QByteArray &, const QDateTime &time); + void slotRootEtagReceived(const QString &, const QDateTime &time); /** When the discovery phase discovers an item */ void slotItemDiscovered(const SyncFileItemPtr &item); @@ -194,12 +177,6 @@ private slots: void slotProgress(const SyncFileItem &item, qint64 curent); void updateFileTotal(const SyncFileItem &item, qint64 newSize); - /** Records that a file was touched by a job. */ - void slotAddTouchedFile(const QString &fn); - - /** Wipes the _touchedFiles hash */ - void slotClearTouchedFiles(); - /** Emit a summary error, unless it was seen before */ void slotSummaryError(const QString &message); @@ -222,74 +199,72 @@ private slots: // Removes stale and adds missing conflict records after sync void conflictRecordMaintenance(); - // cleanup and emit the finished signal + // do a preflight check to be sure we can really sync + bool preSyncChecks(); + // start discovery + void startDiscovery(); + // start propagation + void startPropagation(); + + // cleanup and Q_EMIT the finished signal void finalize(bool success); // Must only be acessed during update and reconcile SyncFileItemSet _syncItems; - AccountPtr _account; + // the ever present account pointer primarily used for running jobs + QPointer _account; + + const QUrl _baseUrl; + bool _needsUpdate; bool _syncRunning; QString _localPath; QString _remotePath; - QByteArray _remoteRootEtag; - SyncJournalDb *_journal; - QScopedPointer _discoveryPhase; - QSharedPointer _propagator; + QString _remoteRootEtag; - // List of all files with conflicts - QSet _seenConflictFiles; + // this is owned by the folder + QPointer _journal; - QScopedPointer _progressInfo; + // both of these are parented/owned by the engine but are "rebuilt" on every sync + // todo: investigate to determine whether we can improve this pointer handling but simply clearing/resetting the + // states between runs (similar to ProgressInfo::reset, which contrary to the naming, is not related to smartpointers) + QPointer _discoveryPhase; + QPointer _propagator; - QScopedPointer _excludedFiles; - QScopedPointer _syncFileStatusTracker; - Utility::StopWatch _stopWatch; + // these pointers are all parented/owned by the engine + ProgressInfo *_progressInfo = nullptr; + ExcludedFiles *_excludedFiles = nullptr; + SyncFileStatusTracker *_syncFileStatusTracker = nullptr; - /** - * check if we are allowed to propagate everything, and if we are not, adjust the instructions - * to recover - */ - void checkForPermission(SyncFileItemSet &syncItems); - RemotePermissions getPermissions(const QString &file) const; + // List of all files with conflicts + QSet _seenConflictFiles; + Utility::ChronoElapsedTimer _duration; /** * Instead of downloading files from the server, upload the files to the server */ void restoreOldFiles(SyncFileItemSet &syncItems); - // true if there is at least one file which was not changed on the server - bool _hasNoneFiles; - - // true if there is at leasr one file with instruction REMOVE - bool _hasRemoveFile; - // If ignored files should be ignored bool _ignore_hidden_files = false; - - - int _uploadLimit; - int _downloadLimit; + // should deleted files be moved to trash + bool _moveToTrash = false; SyncOptions _syncOptions; - AnotherSyncNeeded _anotherSyncNeeded; - - /** Stores the time since a job touched a file. */ - std::multimap _touchedFiles; + bool _anotherSyncNeeded = false; QElapsedTimer _lastUpdateProgressCallbackCall; - /** For clearing the _touchedFiles variable after sync finished */ - QTimer _clearTouchedFilesTimer; - /** List of unique errors that occurred in a sync run. */ QSet _uniqueErrors; /** The kind of local discovery the last sync run used */ LocalDiscoveryStyle _lastLocalDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly; LocalDiscoveryStyle _localDiscoveryStyle = LocalDiscoveryStyle::FilesystemOnly; + + // must be ordered std::set _localDiscoveryPaths; // destructor called @@ -297,3 +272,9 @@ private slots: }; } +static const int syncFileItemTypeId = qRegisterMetaType("SyncFileItem"); +static const int syncFileItemPtrTypeId = qRegisterMetaType("SyncFileItemPtr"); +static const int syncFileItemStatusTypeId = qRegisterMetaType("SyncFileItem::Status"); +static const int syncFileStatusTypeId = qRegisterMetaType("SyncFileStatus"); +static const int syncFileItemSetTypeId = qRegisterMetaType("SyncFileItemSet"); +static const int syncFileItemDirectionTypeId = qRegisterMetaType("SyncFileItem::Direction"); diff --git a/src/libsync/syncfileitem.cpp b/src/libsync/syncfileitem.cpp index 1ca7c25eae1..59842b71b8a 100644 --- a/src/libsync/syncfileitem.cpp +++ b/src/libsync/syncfileitem.cpp @@ -17,10 +17,10 @@ #include "common/utility.h" #include "filesystem.h" -#include "csync/vio/csync_vio_local.h" - #include +#include + namespace OCC { Q_LOGGING_CATEGORY(lcFileItem, "sync.fileitem", QtInfoMsg) @@ -38,7 +38,7 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri if (rec._type == ItemTypeVirtualFileDehydration) rec._type = ItemTypeVirtualFile; - rec._etag = _etag; + rec._etag = _etag.toUtf8(); rec._fileId = _fileId; rec._fileSize = _size; rec._remotePerm = _remotePerm; @@ -66,7 +66,7 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec item->_inode = rec._inode; item->_modtime = rec._modtime; item->_type = rec._type; - item->_etag = rec._etag; + item->_etag = QString::fromUtf8(rec._etag); item->_fileId = rec._fileId; item->_size = rec._fileSize; item->_remotePerm = rec._remotePerm; @@ -75,6 +75,19 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec return item; } +SyncInstruction SyncFileItem::instruction() const +{ + return _instruction; +} + +void SyncFileItem::setInstruction(SyncInstruction instruction) +{ + // only one instruction is allowed + // with c++23 this can be made a static_assert + Q_ASSERT(std::bitset(instruction).count() == 1); + _instruction = instruction; +} + template <> QString Utility::enumToDisplayName(SyncFileItem::Status s) { @@ -101,9 +114,29 @@ QString Utility::enumToDisplayName(SyncFileItem::Status s) return QCoreApplication::translate("SyncFileItem::Status", "Blacklisted"); case OCC::SyncFileItem::Excluded: return QCoreApplication::translate("SyncFileItem::Status", "Excluded"); + case OCC::SyncFileItem::Message: + return QCoreApplication::translate("SyncFileItem::Status", "Message"); + case OCC::SyncFileItem::FilenameReserved: + return QCoreApplication::translate("SyncFileItem::Status", "Filename Reserved"); case OCC::SyncFileItem::StatusCount: Q_UNREACHABLE(); } Q_UNREACHABLE(); } } + +QDebug operator<<(QDebug debug, const OCC::SyncFileItem *item) +{ + if (!item) { + debug << "OCC::SyncFileItem(0x0)"; + } else { + QDebugStateSaver saver(debug); + debug.setAutoInsertSpaces(false); + debug << "OCC::SyncFileItem(file=" << item->_file; + if (!item->_renameTarget.isEmpty()) { + debug << ", destination=" << item->destination(); + } + debug << ", type=" << item->_type << ", instruction=" << item->instruction() << ", status=" << item->_status << ")"; + } + return debug; +} diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index dd6fe55a8f7..9b9cfeb0cc2 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -23,10 +23,11 @@ #include -#include -#include +#include "common/syncjournaldb.h" +#include "common/utility.h" +#include "csync.h" -#include +#include "owncloudlib.h" namespace OCC { @@ -42,14 +43,14 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem { Q_GADGET public: - enum Direction { + enum Direction : uint8_t { None = 0, Up, Down }; Q_ENUM(Direction) - enum Status { // stored in 4 bits + enum Status : uint8_t { // stored in 4 bits NoStatus, FatalError, ///< Error that causes the sync to stop @@ -93,12 +94,43 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem */ Excluded, + /** + * Similar to SoftError but will not cause any error handling + */ + Message, + + /** + * See CSYNC_FILE_EXCLUDE_RESERVED + */ + FilenameReserved, + /** For use in an array or vector for the number of items in this enum. */ StatusCount }; Q_ENUM(Status) + static constexpr std::array ErrorStatusItems = { + FatalError, + NormalError, + SoftError, + DetailError, + }; + + static constexpr std::array OtherDisplayableStatusItems = { + Conflict, + FileIgnored, + Restoration, + BlacklistedError, + Excluded, + Message, + FilenameReserved, + }; + + // We don't include NoStatus or Success in here + static_assert( + ErrorStatusItems.size() + OtherDisplayableStatusItems.size() == StatusCount - 2, "ErrorStatusItems or OtherDisplayableStatusItems is incomplete"); + SyncJournalFileRecord toSyncJournalFileRecordWithInode(const QString &localFileName) const; /** Creates a basic SyncFileItem from a DB record @@ -119,12 +151,12 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem , _isSelectiveSync(false) , _httpErrorCode(0) , _affectedItems(1) - , _instruction(CSYNC_INSTRUCTION_NONE) , _modtime(0) , _size(0) , _inode(0) , _previousSize(0) , _previousModtime(0) + , _relevantDirectoyInstruction(false) { } @@ -234,19 +266,19 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem */ QString _originalFile; - ItemType _type BITFIELD(3); - Direction _direction BITFIELD(3); - bool _serverHasIgnoredFiles BITFIELD(1); + ItemType _type; + Direction _direction; + bool _serverHasIgnoredFiles; /// Whether there's an entry in the blacklist table. /// Note: that entry may have retries left, so this can be true /// without the status being FileIgnored. - bool _hasBlacklistEntry BITFIELD(1); + bool _hasBlacklistEntry; // Variables useful to report to the user - Status _status BITFIELD(4); - bool _isRestoration BITFIELD(1); // The original operation was forbidden, and this is a restoration - bool _isSelectiveSync BITFIELD(1); // The file is removed or ignored because it is in the selective sync list + Status _status; + bool _isRestoration; // The original operation was forbidden, and this is a restoration + bool _isSelectiveSync; // The file is removed or ignored because it is in the selective sync list quint16 _httpErrorCode; RemotePermissions _remotePerm; QString _errorString; // Contains a string only in case of error @@ -257,9 +289,8 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem // usually this value is 1, but for removes on dirs, it might be much higher. // Variables used by the propagator - SyncInstructions _instruction; time_t _modtime; - QByteArray _etag; + QString _etag; qint64 _size; quint64 _inode; QByteArray _fileId; @@ -278,6 +309,25 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem QString _directDownloadUrl; QString _directDownloadCookies; + + bool _relevantDirectoyInstruction = false; + bool _finished = false; + + auto toUploadInfo() const + { + SyncJournalDb::UploadInfo out; + out._valid = true; + out._modtime = _modtime; + out._contentChecksum = _checksumHeader; + out._size = _size; + return out; + } + + SyncInstruction instruction() const; + void setInstruction(SyncInstruction instruction); + +private: + SyncInstruction _instruction = CSYNC_INSTRUCTION_NONE; }; @@ -293,8 +343,15 @@ inline bool operator<(const SyncFileItemPtr &item1, const SyncFileItemPtr &item2 using SyncFileItemSet = std::set; } +Q_DECLARE_METATYPE(OCC::SyncFileItemSet) Q_DECLARE_METATYPE(OCC::SyncFileItem) Q_DECLARE_METATYPE(OCC::SyncFileItemPtr) +OWNCLOUDSYNC_EXPORT QDebug operator<<(QDebug debug, const OCC::SyncFileItem *item); +inline QDebug operator<<(QDebug debug, const OCC::SyncFileItemPtr &item) +{ + return debug << item.data(); +} + #endif // SYNCFILEITEM_H diff --git a/src/libsync/syncfilestatustracker.cpp b/src/libsync/syncfilestatustracker.cpp index 46ac2ea3920..66d0594170c 100644 --- a/src/libsync/syncfilestatustracker.cpp +++ b/src/libsync/syncfilestatustracker.cpp @@ -14,11 +14,10 @@ */ #include "syncfilestatustracker.h" -#include "syncengine.h" +#include "common/asserts.h" #include "common/syncjournaldb.h" #include "common/syncjournalfilerecord.h" -#include "common/asserts.h" -#include "csync_exclude.h" +#include "syncengine.h" #include #include @@ -30,7 +29,7 @@ Q_LOGGING_CATEGORY(lcStatusTracker, "sync.statustracker", QtInfoMsg) bool SyncFileStatusTracker::PathComparator::operator()( const QString& lhs, const QString& rhs ) const { // This will make sure that the std::map is ordered and queried case-insensitively on macOS and Windows. - // we want don't want to pay for the runtime check on every comparison. + // we don't want to pay for the runtime check on every comparison. static const auto sensitivity = Utility::fsCaseSensitivity(); return lhs.compare(rhs, sensitivity) < 0; } @@ -72,34 +71,24 @@ SyncFileStatus::SyncFileStatusTag SyncFileStatusTracker::lookupProblem(const QSt static inline bool hasErrorStatus(const SyncFileItem &item) { const auto status = item._status; - return item._instruction == CSYNC_INSTRUCTION_ERROR - || status == SyncFileItem::NormalError - || status == SyncFileItem::FatalError - || status == SyncFileItem::DetailError - || status == SyncFileItem::BlacklistedError - || item._hasBlacklistEntry; + return item.instruction() == CSYNC_INSTRUCTION_ERROR || status == SyncFileItem::NormalError || status == SyncFileItem::FatalError + || status == SyncFileItem::DetailError || status == SyncFileItem::BlacklistedError || item._hasBlacklistEntry; } static inline bool hasExcludedStatus(const SyncFileItem &item) { const auto status = item._status; - return item._instruction == CSYNC_INSTRUCTION_IGNORE - || status == SyncFileItem::FileIgnored - || status == SyncFileItem::Conflict + return item.instruction() == CSYNC_INSTRUCTION_IGNORE || status == SyncFileItem::FileIgnored || status == SyncFileItem::Conflict || status == SyncFileItem::Restoration; } -SyncFileStatusTracker::SyncFileStatusTracker(SyncEngine *syncEngine) - : _syncEngine(syncEngine) +SyncFileStatusTracker::SyncFileStatusTracker(const QString &folderPath, SyncJournalDb *journal, SyncEngine *syncEngine) + : QObject(syncEngine) + , _syncEngine(syncEngine) + , _folderPath(folderPath) + , _journal(journal) , _caseSensitivity(Utility::fsCaseSensitivity()) -{ - connect(syncEngine, &SyncEngine::aboutToPropagate, - this, &SyncFileStatusTracker::slotAboutToPropagate); - connect(syncEngine, &SyncEngine::itemCompleted, - this, &SyncFileStatusTracker::slotItemCompleted); - connect(syncEngine, &SyncEngine::finished, this, &SyncFileStatusTracker::slotSyncFinished); - connect(syncEngine, &SyncEngine::started, this, &SyncFileStatusTracker::slotSyncEngineRunningChanged); - connect(syncEngine, &SyncEngine::finished, this, &SyncFileStatusTracker::slotSyncEngineRunningChanged); +{ } SyncFileStatus SyncFileStatusTracker::fileStatus(const QString &relativePath) @@ -111,7 +100,7 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString &relativePath) return resolveSyncAndErrorStatus(QString(), NotShared); } - const QString absolutePath = _syncEngine->localPath() + relativePath; + const QString absolutePath = _folderPath + relativePath; if (!QFileInfo::exists(absolutePath)) { return SyncFileStatus(SyncFileStatus::StatusNone); @@ -122,12 +111,16 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString &relativePath) // that the status of CSYNC_FILE_EXCLUDE_LIST excludes will change if the user // update the exclude list at runtime and doing it statically here removes // our ability to notify changes through the fileStatusChanged signal, - // it's an acceptable compromize to treat all exclude types the same. + // it's an acceptable compromise to treat all exclude types the same. // Update: This extra check shouldn't hurt even though silently excluded files - // are now available via slotAddSilentlyExcluded(). - if (_syncEngine->excludedFiles().isExcluded(_syncEngine->syncOptions()._vfs->underlyingFileName(absolutePath), - _syncEngine->localPath(), - _syncEngine->ignoreHiddenFiles())) { + // are now available via slotAddExcluded(). + + // actually it does "hurt", to the extent that we have a sync engine member and public getter on the excludes + // just to support stuff like this, all at the expense of decent encapsulation! + if (!_syncEngine) + return {}; + + if (_syncEngine->isExcluded(absolutePath)) { return SyncFileStatus(SyncFileStatus::StatusExcluded); } @@ -136,7 +129,7 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString &relativePath) // First look it up in the database to know if it's shared SyncJournalFileRecord rec; - if (_syncEngine->journal()->getFileRecord(relativePath, &rec) && rec.isValid()) { + if (_journal && _journal->getFileRecord(relativePath, &rec) && rec.isValid()) { return resolveSyncAndErrorStatus(relativePath, rec._remotePerm.hasPermission(RemotePermissions::IsShared) ? Shared : NotShared); } @@ -144,21 +137,19 @@ SyncFileStatus SyncFileStatusTracker::fileStatus(const QString &relativePath) return resolveSyncAndErrorStatus(relativePath, NotShared, PathUnknown); } -void SyncFileStatusTracker::slotPathTouched(const QString &fileName) +void SyncFileStatusTracker::pathTouched(const QString &fileName) { - QString folderPath = _syncEngine->localPath(); - - OC_ASSERT(fileName.startsWith(folderPath)); - QString localPath = fileName.mid(folderPath.size()); + OC_ASSERT(fileName.startsWith(_folderPath)); + QString localPath = fileName.mid(_folderPath.size()); _dirtyPaths.insert(localPath); - emit fileStatusChanged(fileName, SyncFileStatus::StatusSync); + Q_EMIT fileStatusChanged(fileName, SyncFileStatus::StatusSync); } -void SyncFileStatusTracker::slotAddSilentlyExcluded(const QString &folderPath) +void SyncFileStatusTracker::slotAddExcluded(const QString &folderPath) { _syncProblems[folderPath] = SyncFileStatus::StatusExcluded; - emit fileStatusChanged(getSystemDestination(folderPath), resolveSyncAndErrorStatus(folderPath, NotShared)); + Q_EMIT fileStatusChanged(getSystemDestination(folderPath), resolveSyncAndErrorStatus(folderPath, NotShared)); } void SyncFileStatusTracker::incSyncCountAndEmitStatusChanged(const QString &relativePath, SharedFlag sharedFlag) @@ -169,7 +160,7 @@ void SyncFileStatusTracker::incSyncCountAndEmitStatusChanged(const QString &rela SyncFileStatus status = sharedFlag == UnknownShared ? fileStatus(relativePath) : resolveSyncAndErrorStatus(relativePath, sharedFlag); - emit fileStatusChanged(getSystemDestination(relativePath), status); + Q_EMIT fileStatusChanged(getSystemDestination(relativePath), status); // We passed from OK to SYNC, increment the parent to keep it marked as // SYNC while we propagate ourselves and our own children. @@ -192,7 +183,7 @@ void SyncFileStatusTracker::decSyncCountAndEmitStatusChanged(const QString &rela SyncFileStatus status = sharedFlag == UnknownShared ? fileStatus(relativePath) : resolveSyncAndErrorStatus(relativePath, sharedFlag); - emit fileStatusChanged(getSystemDestination(relativePath), status); + Q_EMIT fileStatusChanged(getSystemDestination(relativePath), status); // We passed from SYNC to OK, decrement our parent. OC_ASSERT(!relativePath.endsWith(QLatin1Char('/'))); @@ -204,16 +195,17 @@ void SyncFileStatusTracker::decSyncCountAndEmitStatusChanged(const QString &rela } } -void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemSet &items) +void SyncFileStatusTracker::updateAboutToPropagate(const SyncFileItemSet &items) { OC_ASSERT(_syncCount.isEmpty()); ProblemsMap oldProblems; std::swap(_syncProblems, oldProblems); - for (const auto &item : qAsConst(items)) { - qCDebug(lcStatusTracker) << "Investigating" << item->destination() << item->_status << item->_instruction; + for (const auto &item : std::as_const(items)) { + qCDebug(lcStatusTracker) << "Investigating" << item->destination() << item->_status << item->instruction(); _dirtyPaths.remove(item->destination()); + _dirtyPaths.remove(item->_originalFile); if (hasErrorStatus(*item)) { _syncProblems[item->destination()] = SyncFileStatus::StatusError; @@ -223,14 +215,11 @@ void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemSet &items) } SharedFlag sharedFlag = item->_remotePerm.hasPermission(RemotePermissions::IsShared) ? Shared : NotShared; - if (item->_instruction != CSYNC_INSTRUCTION_NONE - && item->_instruction != CSYNC_INSTRUCTION_UPDATE_METADATA - && item->_instruction != CSYNC_INSTRUCTION_IGNORE - && item->_instruction != CSYNC_INSTRUCTION_ERROR) { + if (item->instruction() & ~(CSYNC_INSTRUCTION_NONE | CSYNC_INSTRUCTION_UPDATE_METADATA | CSYNC_INSTRUCTION_IGNORE | CSYNC_INSTRUCTION_ERROR)) { // Mark this path as syncing for instructions that will result in propagation. incSyncCountAndEmitStatusChanged(item->destination(), sharedFlag); } else { - emit fileStatusChanged(getSystemDestination(item->destination()), resolveSyncAndErrorStatus(item->destination(), sharedFlag)); + Q_EMIT fileStatusChanged(getSystemDestination(item->destination()), resolveSyncAndErrorStatus(item->destination(), sharedFlag)); } } @@ -240,7 +229,7 @@ void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemSet &items) QSet oldDirtyPaths; std::swap(_dirtyPaths, oldDirtyPaths); for (auto it = oldDirtyPaths.constBegin(); it != oldDirtyPaths.constEnd(); ++it) - emit fileStatusChanged(getSystemDestination(*it), fileStatus(*it)); + Q_EMIT fileStatusChanged(getSystemDestination(*it), fileStatus(*it)); // Make sure to push any status that might have been resolved indirectly since the last sync // (like an error file being deleted from disk) @@ -251,13 +240,13 @@ void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemSet &items) SyncFileStatus::SyncFileStatusTag severity = it->second; if (severity == SyncFileStatus::StatusError) invalidateParentPaths(path); - emit fileStatusChanged(getSystemDestination(path), fileStatus(path)); + Q_EMIT fileStatusChanged(getSystemDestination(path), fileStatus(path)); } } -void SyncFileStatusTracker::slotItemCompleted(const SyncFileItemPtr &item) +void SyncFileStatusTracker::updateItemCompleted(const SyncFileItemPtr &item) { - qCDebug(lcStatusTracker) << "Item completed" << item->destination() << item->_status << item->_instruction; + qCDebug(lcStatusTracker) << "Item completed" << item->destination() << item->_status << item->instruction(); if (hasErrorStatus(*item)) { _syncProblems[item->destination()] = SyncFileStatus::StatusError; @@ -269,29 +258,27 @@ void SyncFileStatusTracker::slotItemCompleted(const SyncFileItemPtr &item) } SharedFlag sharedFlag = item->_remotePerm.hasPermission(RemotePermissions::IsShared) ? Shared : NotShared; - if (item->_instruction != CSYNC_INSTRUCTION_NONE - && item->_instruction != CSYNC_INSTRUCTION_UPDATE_METADATA - && item->_instruction != CSYNC_INSTRUCTION_IGNORE - && item->_instruction != CSYNC_INSTRUCTION_ERROR) { - // decSyncCount calls *must* be symetric with incSyncCount calls in slotAboutToPropagate + if (item->instruction() & ~(CSYNC_INSTRUCTION_NONE | CSYNC_INSTRUCTION_UPDATE_METADATA | CSYNC_INSTRUCTION_IGNORE | CSYNC_INSTRUCTION_ERROR)) { + // decSyncCount calls *must* be symmetric with incSyncCount calls in slotAboutToPropagate decSyncCountAndEmitStatusChanged(item->destination(), sharedFlag); } else { - emit fileStatusChanged(getSystemDestination(item->destination()), resolveSyncAndErrorStatus(item->destination(), sharedFlag)); + Q_EMIT fileStatusChanged(getSystemDestination(item->destination()), resolveSyncAndErrorStatus(item->destination(), sharedFlag)); } } -void SyncFileStatusTracker::slotSyncFinished() +void SyncFileStatusTracker::updateSyncFinished() { - // Clear the sync counts to reduce the impact of unsymetrical inc/dec calls (e.g. when directory job abort) + // Clear the sync counts to reduce the impact of unsymmetrical inc/dec calls (e.g. when directory job abort) QHash oldSyncCount; std::swap(_syncCount, oldSyncCount); for (auto it = oldSyncCount.begin(); it != oldSyncCount.end(); ++it) - emit fileStatusChanged(getSystemDestination(it.key()), fileStatus(it.key())); + Q_EMIT fileStatusChanged(getSystemDestination(it.key()), fileStatus(it.key())); + updateSyncRunningChanged(); } -void SyncFileStatusTracker::slotSyncEngineRunningChanged() +void SyncFileStatusTracker::updateSyncRunningChanged() { - emit fileStatusChanged(getSystemDestination(QString()), resolveSyncAndErrorStatus(QString(), NotShared)); + Q_EMIT fileStatusChanged(getSystemDestination(QString()), resolveSyncAndErrorStatus(QString(), NotShared)); } SyncFileStatus SyncFileStatusTracker::resolveSyncAndErrorStatus(const QString &relativePath, SharedFlag sharedFlag, PathKnownFlag isPathKnown) @@ -322,13 +309,13 @@ void SyncFileStatusTracker::invalidateParentPaths(const QString &path) QStringList splitPath = path.split(QLatin1Char('/'), Qt::SkipEmptyParts); for (int i = 0; i < splitPath.size(); ++i) { QString parentPath = QStringList(splitPath.mid(0, i)).join(QLatin1Char('/')); - emit fileStatusChanged(getSystemDestination(parentPath), fileStatus(parentPath)); + Q_EMIT fileStatusChanged(getSystemDestination(parentPath), fileStatus(parentPath)); } } QString SyncFileStatusTracker::getSystemDestination(const QString &relativePath) { - QString systemPath = _syncEngine->localPath() + relativePath; + QString systemPath = _folderPath + relativePath; // SyncEngine::localPath() has a trailing slash, make sure to remove it if the // destination is empty. if (systemPath.endsWith(QLatin1Char('/'))) { diff --git a/src/libsync/syncfilestatustracker.h b/src/libsync/syncfilestatustracker.h index 7a89bab23e5..f9a21db8e64 100644 --- a/src/libsync/syncfilestatustracker.h +++ b/src/libsync/syncfilestatustracker.h @@ -16,11 +16,12 @@ #ifndef SYNCFILESTATUSTRACKER_H #define SYNCFILESTATUSTRACKER_H -// #include "ownsql.h" -#include "syncfileitem.h" #include "common/syncfilestatus.h" -#include +#include "common/syncjournaldb.h" +#include "syncfileitem.h" +#include #include +#include namespace OCC { @@ -35,23 +36,24 @@ class OWNCLOUDSYNC_EXPORT SyncFileStatusTracker : public QObject { Q_OBJECT public: - explicit SyncFileStatusTracker(SyncEngine *syncEngine); + // sync engine is always parent + explicit SyncFileStatusTracker(const QString &folderPath, SyncJournalDb *journal, SyncEngine *syncEngine); + SyncFileStatus fileStatus(const QString &relativePath); + void pathTouched(const QString &fileName); + + void updateAboutToPropagate(const SyncFileItemSet &items); + void updateItemCompleted(const SyncFileItemPtr &item); + void updateSyncFinished(); + void updateSyncRunningChanged(); -public slots: - void slotPathTouched(const QString &fileName); +public Q_SLOTS: // path relative to folder - void slotAddSilentlyExcluded(const QString &folderPath); + void slotAddExcluded(const QString &folderPath); -signals: +Q_SIGNALS: void fileStatusChanged(const QString &systemFileName, SyncFileStatus fileStatus); -private slots: - void slotAboutToPropagate(SyncFileItemSet &items); - void slotItemCompleted(const SyncFileItemPtr &item); - void slotSyncFinished(); - void slotSyncEngineRunningChanged(); - private: struct PathComparator { bool operator()( const QString& lhs, const QString& rhs ) const; @@ -71,7 +73,11 @@ private slots: void incSyncCountAndEmitStatusChanged(const QString &relativePath, SharedFlag sharedState); void decSyncCountAndEmitStatusChanged(const QString &relativePath, SharedFlag sharedState); - SyncEngine *_syncEngine; + // sync engine is also the parent. We *only* have this as a member to ask whether the path is excluded or not. + // consider replacing the sync engine as member with an instance of the exluded files list + QPointer _syncEngine; + QString _folderPath; + QPointer _journal; ProblemsMap _syncProblems; QSet _dirtyPaths; diff --git a/src/libsync/syncoptions.cpp b/src/libsync/syncoptions.cpp index 588a7c63436..39ed5a8047a 100644 --- a/src/libsync/syncoptions.cpp +++ b/src/libsync/syncoptions.cpp @@ -19,8 +19,8 @@ using namespace OCC; -SyncOptions::SyncOptions() - : _vfs(new VfsOff) +SyncOptions::SyncOptions(Vfs *vfs) + : _vfs(vfs) { } @@ -28,48 +28,7 @@ SyncOptions::~SyncOptions() { } -void SyncOptions::fillFromEnvironmentVariables() +bool SyncOptions::isValid() const { - QByteArray chunkSizeEnv = qgetenv("OWNCLOUD_CHUNK_SIZE"); - if (!chunkSizeEnv.isEmpty()) - _initialChunkSize = chunkSizeEnv.toUInt(); - - QByteArray minChunkSizeEnv = qgetenv("OWNCLOUD_MIN_CHUNK_SIZE"); - if (!minChunkSizeEnv.isEmpty()) - _minChunkSize = minChunkSizeEnv.toUInt(); - - QByteArray maxChunkSizeEnv = qgetenv("OWNCLOUD_MAX_CHUNK_SIZE"); - if (!maxChunkSizeEnv.isEmpty()) - _maxChunkSize = maxChunkSizeEnv.toUInt(); - - QByteArray targetChunkUploadDurationEnv = qgetenv("OWNCLOUD_TARGET_CHUNK_UPLOAD_DURATION"); - if (!targetChunkUploadDurationEnv.isEmpty()) - _targetChunkUploadDuration = std::chrono::milliseconds(targetChunkUploadDurationEnv.toUInt()); - - int maxParallel = qgetenv("OWNCLOUD_MAX_PARALLEL").toInt(); - if (maxParallel > 0) - _parallelNetworkJobs = maxParallel; -} - -void SyncOptions::verifyChunkSizes() -{ - _minChunkSize = qMin(_minChunkSize, _initialChunkSize); - _maxChunkSize = qMax(_maxChunkSize, _initialChunkSize); -} - -QRegularExpression SyncOptions::fileRegex() const -{ - return _fileRegex; -} - -void SyncOptions::setFilePattern(const QString &pattern) -{ - // full match or a path ending with this pattern - setPathPattern(QStringLiteral("(^|/|\\\\)") + pattern + QLatin1Char('$')); -} - -void SyncOptions::setPathPattern(const QString &pattern) -{ - _fileRegex.setPatternOptions(Utility::fsCasePreserving() ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption); - _fileRegex.setPattern(pattern); + return !_vfs.isNull(); } diff --git a/src/libsync/syncoptions.h b/src/libsync/syncoptions.h index 14f508521ee..77e55ec2b15 100644 --- a/src/libsync/syncoptions.h +++ b/src/libsync/syncoptions.h @@ -18,6 +18,7 @@ #include "common/filesystembase.h" #include "common/vfs.h" +#include #include #include #include @@ -33,93 +34,29 @@ namespace OCC { class OWNCLOUDSYNC_EXPORT SyncOptions { public: - SyncOptions(); + // the VFS pointer must be stored in a QPointer as it may go out of scope from "above" + // it's owned by the Folder so if the folder dies, so does the vfs pointer. + // use isValid to see if the options should still be used, or test the vfs pointer directly to see if it's null or not. + explicit SyncOptions(Vfs *vfs = nullptr); ~SyncOptions(); - /** Maximum size (in Bytes) a folder can have without asking for confirmation. - * -1 means infinite */ - qint64 _newBigFolderSizeLimit = -1; - - /** If a confirmation should be asked for external storages */ - bool _confirmExternalStorage = false; - - /** If remotely deleted files are needed to move to trash */ - bool _moveFilesToTrash = false; - - /** Create a virtual file for new files instead of downloading. May not be null */ - QSharedPointer _vfs; - - /** The initial un-adjusted chunk size in bytes for chunked uploads, both - * for old and new chunking algorithm, which classifies the item to be chunked - * - * In chunkingNG, when dynamic chunk size adjustments are done, this is the - * starting value and is then gradually adjusted within the - * minChunkSize / maxChunkSize bounds. - */ - qint64 _initialChunkSize = 10 * 1000 * 1000; // 10MB - - /** The minimum chunk size in bytes for chunked uploads */ - qint64 _minChunkSize = 1 * 1000 * 1000; // 1MB - - /** The maximum chunk size in bytes for chunked uploads */ - qint64 _maxChunkSize = 100 * 1000 * 1000; // 100MB - - /** The target duration of chunk uploads for dynamic chunk sizing. - * - * Set to 0 it will disable dynamic chunk sizing. - */ - std::chrono::milliseconds _targetChunkUploadDuration = std::chrono::minutes(1); + Vfs *vfs() const { return _vfs; } /** The maximum number of active jobs in parallel */ int _parallelNetworkJobs = 6; - /** Reads settings from env vars where available. - * - * Currently reads _initialChunkSize, _minChunkSize, _maxChunkSize, - * _targetChunkUploadDuration, _parallelNetworkJobs. - */ - void fillFromEnvironmentVariables(); - - /** Ensure min <= initial <= max - * - * Previously min/max chunk size values didn't exist, so users might - * have setups where the chunk size exceeds the new min/max default - * values. To cope with this, adjust min/max to always include the - * initial chunk size value. - */ - void verifyChunkSizes(); - - - /** A regular expression to match file names - * If no pattern is provided the default is an invalid regular expression. - */ - QRegularExpression fileRegex() const; - - /** - * A pattern like *.txt, matching only file names - */ - void setFilePattern(const QString &pattern); - - /** - * A pattern like /own.*\/.*txt matching the full path - */ - void setPathPattern(const QString &pattern); - - /** - * The required file lock mode + * @brief isValid indicates if the options are complete + * @return true if vfs is non-null, else false */ - FileSystem::LockMode requiredLockMode() const - { - return _vfs->mode() == Vfs::WindowsCfApi ? FileSystem::LockMode::Exclusive : FileSystem::LockMode::Shared; - } + bool isValid() const; private: - /** - * Only sync files that mathc the expression - * Invalid pattern by default. + /** Create a virtual file for new files instead of downloading. If vfs is null, isValid will return false indicating you should not use it + * implementation note: of course you can pass a nullptr to the ctr or it can be deleted from above so this is the "sanest" way to + * handle it. */ - QRegularExpression _fileRegex = QRegularExpression(QStringLiteral("(")); + QPointer _vfs; }; } diff --git a/src/libsync/syncresult.cpp b/src/libsync/syncresult.cpp index df3f334d668..798e4f78be8 100644 --- a/src/libsync/syncresult.cpp +++ b/src/libsync/syncresult.cpp @@ -34,44 +34,36 @@ void SyncResult::reset() *this = SyncResult(); } -QString SyncResult::statusString() const +template <> +QString Utility::enumToDisplayName(SyncResult::Status status) { - QString re; - Status stat = status(); - - switch (stat) { - case Undefined: - re = QStringLiteral("Undefined"); - break; - case NotYetStarted: - re = QStringLiteral("Not yet Started"); - break; - case SyncRunning: - re = QStringLiteral("Sync Running"); - break; - case Success: - re = QStringLiteral("Success"); - break; - case Error: - re = QStringLiteral("Error"); - break; - case SetupError: - re = QStringLiteral("SetupError"); - break; - case SyncPrepare: - re = QStringLiteral("SyncPrepare"); - break; - case Problem: - re = QStringLiteral("Success, some files were ignored."); - break; - case SyncAbortRequested: - re = QStringLiteral("Sync Request aborted by user"); - break; - case Paused: - re = QStringLiteral("Sync Paused"); - break; + switch (status) { + case SyncResult::Status::Undefined: + return QStringLiteral("Undefined"); + case SyncResult::Status::NotYetStarted: + return QStringLiteral("Awaiting sync"); + case SyncResult::Status::SyncRunning: + return QStringLiteral("Sync running"); + case SyncResult::Status::Success: + return QStringLiteral("Success"); + case SyncResult::Status::Error: + return QStringLiteral("Error"); + case SyncResult::Status::SetupError: + return QStringLiteral("Setup error"); + case SyncResult::Status::SyncPrepare: + return QStringLiteral("Preparing to sync"); + case SyncResult::Status::Problem: + return QStringLiteral("Success, some files were ignored."); + case SyncResult::Status::SyncAbortRequested: + return QStringLiteral("Aborting sync"); + case SyncResult::Status::Paused: + return QStringLiteral("Sync paused"); + case SyncResult::Status::Offline: + return QStringLiteral("Offline"); + case SyncResult::Status::Unavailable: + return "Unavailable"; } - return re; + Q_UNREACHABLE(); } void SyncResult::setStatus(Status stat) @@ -107,16 +99,6 @@ void SyncResult::clearErrors() _errors.clear(); } -void SyncResult::setFolder(const QString &folder) -{ - _folder = folder; -} - -QString SyncResult::folder() const -{ - return _folder; -} - void SyncResult::processCompletedItem(const SyncFileItemPtr &item) { if (Progress::isWarningKind(item->_status)) { @@ -124,10 +106,8 @@ void SyncResult::processCompletedItem(const SyncFileItemPtr &item) _foundFilesNotSynced = true; } - if (item->isDirectory() && (item->_instruction == CSYNC_INSTRUCTION_NEW - || item->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE - || item->_instruction == CSYNC_INSTRUCTION_REMOVE - || item->_instruction == CSYNC_INSTRUCTION_RENAME)) { + if (item->isDirectory() + && (item->instruction() & (CSYNC_INSTRUCTION_NEW | CSYNC_INSTRUCTION_TYPE_CHANGE | CSYNC_INSTRUCTION_REMOVE | CSYNC_INSTRUCTION_RENAME))) { _folderStructureWasChanged = true; } @@ -140,7 +120,7 @@ void SyncResult::processCompletedItem(const SyncFileItemPtr &item) _firstItemError = item; } } else if (item->_status == SyncFileItem::Conflict) { - if (item->_instruction == CSYNC_INSTRUCTION_CONFLICT) { + if (item->instruction() == CSYNC_INSTRUCTION_CONFLICT) { _numNewConflictItems++; if (!_firstNewConflictItem) { _firstNewConflictItem = item; @@ -150,7 +130,7 @@ void SyncResult::processCompletedItem(const SyncFileItemPtr &item) } } else { if (!item->hasErrorStatus() && item->_status != SyncFileItem::FileIgnored && item->_direction == SyncFileItem::Down) { - switch (item->_instruction) { + switch (item->instruction()) { case CSYNC_INSTRUCTION_NEW: case CSYNC_INSTRUCTION_TYPE_CHANGE: _numNewItems++; @@ -177,7 +157,7 @@ void SyncResult::processCompletedItem(const SyncFileItemPtr &item) // nothing. break; } - } else if (item->_instruction == CSYNC_INSTRUCTION_IGNORE && item->_hasBlacklistEntry) { + } else if (item->instruction() == CSYNC_INSTRUCTION_IGNORE && item->_hasBlacklistEntry) { if (item->_hasBlacklistEntry) { _numBlacklistErrors++; } diff --git a/src/libsync/syncresult.h b/src/libsync/syncresult.h index 11f0dee9f2f..83b80ba3cc3 100644 --- a/src/libsync/syncresult.h +++ b/src/libsync/syncresult.h @@ -19,6 +19,7 @@ #include #include +#include "common/utility.h" #include "owncloudlib.h" #include "syncfileitem.h" @@ -35,18 +36,7 @@ class OWNCLOUDSYNC_EXPORT SyncResult // the order of the values markes their importance // higher values take prcedence when computing the // overall status - enum Status { - Undefined, - NotYetStarted, - Success, - SyncPrepare, - SyncRunning, - SyncAbortRequested, - Paused, - Problem, - Error, - SetupError - }; + enum Status { Undefined, Success, NotYetStarted, SyncPrepare, SyncRunning, SyncAbortRequested, Paused, Offline, Problem, Error, SetupError, Unavailable }; Q_ENUM(Status); SyncResult() = default; @@ -60,10 +50,7 @@ class OWNCLOUDSYNC_EXPORT SyncResult void setStatus(Status); Status status() const; - QString statusString() const; QDateTime syncTime() const; - void setFolder(const QString &folder); - QString folder() const; bool foundFilesNotSynced() const { return _foundFilesNotSynced; } bool folderStructureWasChanged() const { return _folderStructureWasChanged; } @@ -93,7 +80,6 @@ class OWNCLOUDSYNC_EXPORT SyncResult Status _status = Undefined; SyncFileItemSet _syncItems; QDateTime _syncTime; - QString _folder; /** * when the sync tool support this... */ @@ -120,6 +106,9 @@ class OWNCLOUDSYNC_EXPORT SyncResult friend class TrayOverallStatusResult; }; + +template <> +OWNCLOUDSYNC_EXPORT QString Utility::enumToDisplayName(SyncResult::Status status); } #endif diff --git a/src/libsync/theme.cpp b/src/libsync/theme.cpp index 24feb5a5b0a..8e675269ece 100644 --- a/src/libsync/theme.cpp +++ b/src/libsync/theme.cpp @@ -13,48 +13,23 @@ */ #include "theme.h" -#include "common/depreaction.h" #include "common/utility.h" #include "common/version.h" #include "common/vfs.h" #include "config.h" -#include "configfile.h" -#include -#ifndef TOKEN_AUTH_ONLY -#include -#include -#include -#endif +#include "resources/resources.h" + #include +#include -#include "owncloudtheme.h" +#include "themewatcher.h" #ifdef THEME_INCLUDE #include THEME_INCLUDE #endif namespace { -QString vanillaThemePath() -{ - return QStringLiteral(":/client/ownCloud/theme"); -} - -QString brandThemePath() -{ - return QStringLiteral(":/client/" APPLICATION_SHORTNAME "/theme"); -} - -QString darkTheme() -{ - return QStringLiteral("dark"); -} - -QString coloredTheme() -{ - return QStringLiteral("colored"); -} - QString whiteTheme() { return QStringLiteral("white"); @@ -65,15 +40,14 @@ QString blackTheme() return QStringLiteral("black"); } -constexpr bool strings_equal(char const *a, char const *b) +QString darkTheme() { - return *a == *b && (*a == '\0' || strings_equal(a + 1, b + 1)); + return QStringLiteral("dark"); } -constexpr bool isVanilla() +QString coloredTheme() { - // TODO: c++17 stringview - return strings_equal(APPLICATION_SHORTNAME, "ownCloud"); + return QStringLiteral("colored"); } } namespace OCC { @@ -84,52 +58,20 @@ Theme *Theme::instance() { if (!_instance) { _instance = new THEME_CLASS; + auto *watcher = new Resources::ThemeWatcher(_instance); + connect(watcher, &Resources::ThemeWatcher::themeChanged, _instance, &Theme::themeChanged); } return _instance; } -Theme::~Theme() +Theme *Theme::create(QQmlEngine *qmlEngine, QJSEngine *) { + Q_ASSERT(qmlEngine->thread() == Theme::instance()->thread()); + QJSEngine::setObjectOwnership(Theme::instance(), QJSEngine::CppOwnership); + return instance(); } -QString Theme::statusHeaderText(SyncResult::Status status) const -{ - QString resultStr; - - switch (status) { - case SyncResult::Undefined: - resultStr = QCoreApplication::translate("theme", "Status undefined"); - break; - case SyncResult::NotYetStarted: - resultStr = QCoreApplication::translate("theme", "Waiting to start sync"); - break; - case SyncResult::SyncRunning: - resultStr = QCoreApplication::translate("theme", "Sync is running"); - break; - case SyncResult::Success: - resultStr = QCoreApplication::translate("theme", "Sync Success"); - break; - case SyncResult::Problem: - resultStr = QCoreApplication::translate("theme", "Sync Success, some files were ignored."); - break; - case SyncResult::Error: - resultStr = QCoreApplication::translate("theme", "Sync Error"); - break; - case SyncResult::SetupError: - resultStr = QCoreApplication::translate("theme", "Setup Error"); - break; - case SyncResult::SyncPrepare: - resultStr = QCoreApplication::translate("theme", "Preparing to sync"); - break; - case SyncResult::SyncAbortRequested: - resultStr = QCoreApplication::translate("theme", "Aborting..."); - break; - case SyncResult::Paused: - resultStr = QCoreApplication::translate("theme", "Sync is paused"); - break; - } - return resultStr; -} +Theme::~Theme() { } QString Theme::appNameGUI() const { @@ -141,44 +83,55 @@ QString Theme::appName() const return QStringLiteral(APPLICATION_SHORTNAME); } -QString Theme::configFileName() const +QString Theme::orgDomainName() const { - return QStringLiteral(APPLICATION_EXECUTABLE ".cfg"); + return QStringLiteral(APPLICATION_REV_DOMAIN); } -#ifndef TOKEN_AUTH_ONLY - -QIcon Theme::applicationIcon() const +QString Theme::vendor() const { - return themeUniversalIcon(QStringLiteral(APPLICATION_ICON_NAME "-icon")); + return QStringLiteral(APPLICATION_VENDOR); } -QIcon Theme::aboutIcon() const +QString Theme::configFileName() const { - return applicationIcon(); + return QStringLiteral(APPLICATION_EXECUTABLE ".cfg"); } -bool Theme::isUsingDarkTheme() const +QIcon Theme::applicationIcon() const { - //TODO: replace by a command line switch - static bool forceDark = qEnvironmentVariableIntValue("OWNCLOUD_FORCE_DARK_MODE") != 0; - return forceDark || QPalette().base().color().lightnessF() <= 0.5; + return Resources::themeUniversalIcon(applicationIconName() + QStringLiteral("-icon")); } -bool Theme::allowDarkTheme() const +QString Theme::applicationIconName() const { - return _hasBrandedColored == _hasBrandedDark; + return QStringLiteral(APPLICATION_ICON_NAME); } - -QIcon Theme::themeUniversalIcon(const QString &name, Theme::IconType iconType) const +QIcon Theme::aboutIcon() const { - return loadIcon(QStringLiteral("universal"), name, iconType); + return applicationIcon(); } -QIcon Theme::themeTrayIcon(const QString &name, bool sysTrayMenuVisible, IconType iconType) const +QIcon Theme::themeTrayIcon(const SyncResult &result, [[maybe_unused]] bool sysTrayMenuVisible, Resources::IconType iconType) const { - auto icon = loadIcon(systrayIconFlavor(_mono, sysTrayMenuVisible), name, iconType); + auto systrayIconFlavor = [&]() { + QString flavor; + if (_mono) { + flavor = Utility::hasDarkSystray() ? whiteTheme() : blackTheme(); + +#ifdef Q_OS_MAC + if (sysTrayMenuVisible) { + flavor = whiteTheme(); + } +#endif + } else { + // we have a dark sys tray and the theme has support for that + flavor = (Utility::hasDarkSystray() && Resources::hasDarkTheme()) ? darkTheme() : coloredTheme(); + } + return flavor; + }; + auto icon = Resources::loadIcon(systrayIconFlavor(), QStringLiteral("state-%1").arg(syncStateIconName(result)), iconType); #ifdef Q_OS_MAC // This defines the icon as a template and enables automatic macOS color handling // See https://bugreports.qt.io/browse/QTBUG-42109 @@ -187,116 +140,14 @@ QIcon Theme::themeTrayIcon(const QString &name, bool sysTrayMenuVisible, IconTyp return icon; } -QIcon Theme::themeIcon(const QString &name, Theme::IconType iconType) const -{ - return loadIcon((isUsingDarkTheme() && allowDarkTheme()) ? darkTheme() : coloredTheme(), name, iconType); -} -/* - * helper to load a icon from either the icon theme the desktop provides or from - * the apps Qt resources. - */ -QIcon Theme::loadIcon(const QString &flavor, const QString &name, IconType iconType) const -{ - // prevent recusion - const bool useCoreIcon = (iconType == IconType::VanillaIcon) || isVanilla(); - const QString path = useCoreIcon ? vanillaThemePath() : brandThemePath(); - const QString key = name + QLatin1Char(',') + flavor; - QIcon &cached = _iconCache[key]; // Take reference, this will also "set" the cache entry - if (cached.isNull()) { - if (isVanilla() && QIcon::hasThemeIcon(name)) { - // use from theme - return cached = QIcon::fromTheme(name); - } - const QString svg = QStringLiteral("%1/%2/%3.svg").arg(path, flavor, name); - if (QFile::exists(svg)) { - return cached = QIcon(svg); - } - - const QString png = QStringLiteral("%1/%2/%3.png").arg(path, flavor, name); - if (QFile::exists(png)) { - return cached = QIcon(png); - } - - const QList sizes {16, 22, 32, 48, 64, 128, 256, 512, 1024}; - QString previousIcon; - for (int size : sizes) { - QString pixmapName = QStringLiteral("%1/%2/%3-%4.png").arg(path, flavor, name, QString::number(size)); - if (QFile::exists(pixmapName)) { - previousIcon = pixmapName; - cached.addFile(pixmapName, { size, size }); - } else if (size >= 128) { - if (!previousIcon.isEmpty()) { - qWarning() << "Upsacling:" << previousIcon << "to" << size; - cached.addPixmap(QPixmap(previousIcon).scaled({ size, size }, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - } - } - } - } - if (cached.isNull()) { - if (!useCoreIcon && iconType == IconType::BrandedIconWithFallbackToVanillaIcon) { - return loadIcon(flavor, name, IconType::VanillaIcon); - } - qWarning() << "Failed to locate the icon" << name; - } - return cached; -} - -bool Theme::hasTheme(IconType type, const QString &theme) const -{ - const auto key = qMakePair(type != IconType::VanillaIcon, theme); - auto it = _themeCache.constFind(key); - if (it == _themeCache.cend()) { - return _themeCache[key] = QFileInfo(QStringLiteral("%1/%2/").arg(type == IconType::VanillaIcon ? vanillaThemePath() : brandThemePath(), theme)).isDir(); - } - return it.value(); -} - -QString Theme::systrayIconFlavor(bool mono, bool sysTrayMenuVisible) const -{ - Q_UNUSED(sysTrayMenuVisible) - QString flavor; - if (mono) { - flavor = Utility::hasDarkSystray() ? whiteTheme() : blackTheme(); - -#ifdef Q_OS_MAC - if (sysTrayMenuVisible) { - flavor = QLatin1String("white"); - } -#endif - } else { - // we have a dark sys tray and the theme has support for that - flavor = (Utility::hasDarkSystray() && allowDarkTheme()) ? darkTheme() : coloredTheme(); - } - return flavor; -} - -#endif - Theme::Theme() : QObject(nullptr) { } -// If this option returns true, the client only supports one folder to sync. -// The Add-Button is removed accordingly. -bool Theme::singleSyncFolder() const -{ - return false; -} - -bool Theme::multiAccount() const -{ - return true; -} - -QString Theme::defaultServerFolder() const -{ - return QStringLiteral("/"); -} - QString Theme::helpUrl() const { - return QStringLiteral("https://doc.owncloud.org/desktop/%1.%2/").arg(OCC::Version::version().majorVersion()).arg(OCC::Version::version().microVersion()); + return QStringLiteral("https://doc.owncloud.com/desktop/latest/"); } QString Theme::conflictHelpUrl() const @@ -314,81 +165,30 @@ QString Theme::overrideServerUrl() const return QString(); } -QString Theme::overrideServerUrlV2() const -{ - static const auto serverOverride = qEnvironmentVariable("OWNCLOUD_OVERRIDE_SERVER_URL"); - if (serverOverride.isEmpty()) { - OC_DISABLE_DEPRECATED_WARNING - return overrideServerUrl(); - OC_ENABLE_DEPRECATED_WARNING - } - return serverOverride; -} - -QString Theme::forceConfigAuthType() const +QString Theme::overrideServerPath() const { - return QString(); -} - - -QString Theme::defaultClientFolder() const -{ - return appName(); + return {}; } void Theme::setSystrayUseMonoIcons(bool mono) { _mono = mono; - emit systrayUseMonoIconsChanged(mono); + Q_EMIT systrayUseMonoIconsChanged(mono); } -bool Theme::systrayUseMonoIcons() const +QUrl Theme::updateCheckUrl() const { - return _mono; -} - -bool Theme::monoIconsAvailable() const -{ - // mono icons are only supported in vanilla and if a customer provides them - // no fallback to vanilla - return hasTheme(IconType::BrandedIcon, systrayIconFlavor(true)); -} - -QString Theme::updateCheckUrl() const -{ - return QStringLiteral("https://updates.owncloud.com/client/"); -} - -qint64 Theme::newBigFolderSizeLimit() const -{ - // Default to 500MB - return 500; -} - -bool Theme::wizardHideExternalStorageConfirmationCheckbox() const -{ - return false; -} - -bool Theme::wizardSkipAdvancedPage() const -{ - return false; -} - -bool Theme::wizardHideFolderSizeLimitCheckbox() const -{ - return false; +#ifndef APPLICATION_UPDATE_URL + return QUrl(QString()); +#else + return QUrl(QStringLiteral(APPLICATION_UPDATE_URL)); +#endif } QString Theme::gitSHA1(VersionFormat format) const { -#ifdef GIT_SHA1 - const auto gitSha = QStringLiteral(GIT_SHA1); - const auto gitShahSort = gitSha.left(6); - if (!aboutShowCopyright()) { - return gitShahSort; - } - const auto gitUrl = QStringLiteral("https://github.com/owncloud/client/commit/%1").arg(gitSha); + const QString gitShahSort = Version::gitSha().left(6); + const auto gitUrl = QStringLiteral("https://github.com/owncloud/client/commit/%1").arg(Version::gitSha()); switch (format) { case Theme::VersionFormat::OneLiner: Q_FALLTHROUGH(); @@ -399,7 +199,6 @@ QString Theme::gitSHA1(VersionFormat format) const case Theme::VersionFormat::RichText: return QStringLiteral("%3").arg(gitUrl, gitShahSort); } -#endif return QString(); } @@ -419,121 +218,91 @@ QString Theme::aboutVersions(Theme::VersionFormat format) const Q_UNREACHABLE(); }(); const QString qtVersion = QString::fromUtf8(qVersion()); - const QString qtVersionString = (QLatin1String(QT_VERSION_STR) == qtVersion ? qtVersion : QCoreApplication::translate("ownCloudTheme::qtVer", "%1 (Built against Qt %1)").arg(qtVersion, QStringLiteral(QT_VERSION_STR))); + const QString qtVersionString = (QLatin1String(QT_VERSION_STR) == qtVersion + ? qtVersion + : QCoreApplication::translate("ownCloudTheme::qtVer", "%1 (Built against Qt %2)").arg(qtVersion, QStringLiteral(QT_VERSION_STR))); QString _version = Version::displayString(); QString gitUrl; -#ifdef GIT_SHA1 - if (format != Theme::VersionFormat::Url) { - _version = QCoreApplication::translate("ownCloudTheme::versionWithSha", "%1 %2").arg(_version, gitSHA1(format)); - } else { - gitUrl = gitSHA1(format) + br; + // just use the version::gitSha if (!Version::gitSha().isEmpty()) + { + if (format != Theme::VersionFormat::Url) { + _version = QCoreApplication::translate("ownCloudTheme::versionWithSha", "%1 %2").arg(_version, gitSHA1(format)); + } else { + gitUrl = gitSHA1(format) + br; + } } -#endif + QStringList sysInfo = {QStringLiteral("OS: %1-%2 (build arch: %3, CPU arch: %4)") + .arg(QSysInfo::productType(), QSysInfo::kernelVersion(), QSysInfo::buildCpuArchitecture(), Utility::currentCpuArch())}; + // may be called by both GUI and CLI, but we can display QPA only for the former + if (auto guiApp = qobject_cast(qApp)) { + sysInfo << QStringLiteral("QPA: %1").arg(guiApp->platformName()); + } + return QCoreApplication::translate("ownCloudTheme::aboutVersions()", - "%1 %2 %3%8" - "%9" - "Libraries Qt %4, %5%8" - "Using virtual files plugin: %6%8" - "%7") - .arg(appName(), - _version, - QStringLiteral(__DATE__ " " __TIME__), - qtVersionString, - QSslSocket::sslLibraryVersionString(), - Vfs::modeToString(bestAvailableVfsMode()), - QSysInfo::productType() % QLatin1Char('-') % QSysInfo::kernelVersion(), - br, - gitUrl); + "%1 %2%7" + "%8" + "Libraries Qt %3, %4%7" + "Using virtual files plugin: %5%7" + "%6") + .arg(appName(), _version, qtVersionString, QSslSocket::sslLibraryVersionString(), + Utility::enumToString(VfsPluginManager::instance().bestAvailableVfsMode()), sysInfo.join(br), br, gitUrl); } QString Theme::about() const { // Ideally, the vendor should be "ownCloud GmbH", but it cannot be changed without - // changing the location of the settings and other registery keys. - const QString vendor = isVanilla() ? QStringLiteral("ownCloud GmbH") : QStringLiteral(APPLICATION_VENDOR); + // changing the location of the settings and other registry keys. + const QString vendor = Resources::isVanillaTheme() ? QStringLiteral("ownCloud GmbH") : QStringLiteral(APPLICATION_VENDOR); return tr("

    Version %1. For more information visit https://%3

    " - "

    For known issues and help, please visit: https://central.owncloud.org

    " + "

    For known issues and help, please visit: https://central.owncloud.com

    " "

    By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, " - " Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.

    " - "

    Copyright ownCloud GmbH

    " + " Jan-Christoph Borchardt, Thomas Müller,
    " + "Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.

    " + "

    Copyright ownCloud GmbH (A Kiteworks Company)

    " "

    Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.
    " "%5 and the %5 logo are registered trademarks of %4 in the " "United States, other countries, or both.

    " "

    %6

    ") - .arg(Utility::escape(Version::displayString()), - Utility::escape(QStringLiteral("https://" APPLICATION_DOMAIN)), - Utility::escape(QStringLiteral(APPLICATION_DOMAIN)), - Utility::escape(vendor), - Utility::escape(appNameGUI()), + .arg(Utility::escape(Version::displayString()), Utility::escape(QStringLiteral("https://" APPLICATION_DOMAIN)), + Utility::escape(QStringLiteral(APPLICATION_DOMAIN)), Utility::escape(vendor), Utility::escape(appNameGUI()), aboutVersions(Theme::VersionFormat::RichText)); } -bool Theme::aboutShowCopyright() const +QString Theme::syncStateIconName(const SyncResult &result) const { - return true; -} - -#ifndef TOKEN_AUTH_ONLY -QIcon Theme::syncStateIcon(SyncResult::Status status, bool sysTray, bool sysTrayMenuVisible) const -{ - return syncStateIcon(SyncResult { status }, sysTray, sysTrayMenuVisible); -} -QIcon Theme::syncStateIcon(const SyncResult &result, bool sysTray, bool sysTrayMenuVisible) const -{ - QString statusIcon; - switch (result.status()) { case SyncResult::NotYetStarted: - Q_FALLTHROUGH(); + [[fallthrough]]; case SyncResult::SyncRunning: - statusIcon = QStringLiteral("state-sync"); - break; + return QStringLiteral("sync"); case SyncResult::SyncAbortRequested: - Q_FALLTHROUGH(); + [[fallthrough]]; case SyncResult::Paused: - statusIcon = QStringLiteral("state-pause"); - break; + return QStringLiteral("pause"); case SyncResult::SyncPrepare: - Q_FALLTHROUGH(); + [[fallthrough]]; case SyncResult::Success: - if (result.hasUnresolvedConflicts()) { - return syncStateIcon(SyncResult { SyncResult::Problem }, sysTray, sysTrayMenuVisible); + if (!result.hasUnresolvedConflicts()) { + return QStringLiteral("ok"); } - statusIcon = QStringLiteral("state-ok"); - break; + [[fallthrough]]; case SyncResult::Problem: - Q_FALLTHROUGH(); + [[fallthrough]]; case SyncResult::Undefined: // this can happen if no sync connections are configured. - statusIcon = QStringLiteral("state-information"); - break; + return QStringLiteral("information"); + case SyncResult::Offline: + return QStringLiteral("offline"); case SyncResult::Error: - Q_FALLTHROUGH(); + [[fallthrough]]; + case SyncResult::Unavailable: + [[fallthrough]]; case SyncResult::SetupError: - // FIXME: Use state-problem once we have an icon. - statusIcon = QStringLiteral("state-error"); - } - if (sysTray) { - return themeTrayIcon(statusIcon, sysTrayMenuVisible); - } else { - return themeIcon(statusIcon); - } -} - -QIcon Theme::folderDisabledIcon() const -{ - return themeIcon(QLatin1String("state-pause")); -} - -QIcon Theme::folderOfflineIcon(bool sysTray, bool sysTrayMenuVisible) const -{ - const auto statusIcon = QLatin1String("state-offline"); - if (sysTray) { - return themeTrayIcon(statusIcon, sysTrayMenuVisible); - } else { - return themeIcon(statusIcon); + // FIXME: Use problem once we have an icon. + return QStringLiteral("error"); } + Q_UNREACHABLE(); } QColor Theme::wizardHeaderTitleColor() const @@ -541,11 +310,6 @@ QColor Theme::wizardHeaderTitleColor() const return qApp->palette().text().color(); } -QColor Theme::wizardHeaderSubTitleColor() const -{ - return wizardHeaderTitleColor(); -} - QColor Theme::wizardHeaderBackgroundColor() const { return QColor(); @@ -556,30 +320,9 @@ QIcon Theme::wizardHeaderLogo() const return applicationIcon(); } -QPixmap Theme::wizardHeaderBanner(const QSize &size) const +QIcon Theme::wizardFooterLogo() const { - const QColor c = wizardHeaderBackgroundColor(); - if (!c.isValid()) - return QPixmap(); - QPixmap pix(size); - pix.fill(c); - return pix; -} -#endif - -QString Theme::webDavPath() const -{ - return QStringLiteral("remote.php/webdav/"); -} - -bool Theme::linkSharing() const -{ - return true; -} - -bool Theme::userGroupSharing() const -{ - return true; + return QIcon(); } bool Theme::forceSystemNetworkProxy() const @@ -587,37 +330,6 @@ bool Theme::forceSystemNetworkProxy() const return false; } -Theme::UserIDType Theme::userIDType() const -{ - return UserIDType::UserIDUserName; -} - -QString Theme::customUserID() const -{ - return QString(); -} - -QString Theme::userIDHint() const -{ - return QString(); -} - - -QString Theme::wizardUrlPostfix() const -{ - return QString(); -} - -QString Theme::wizardUrlHint() const -{ - return QString(); -} - -QString Theme::quotaBaseFolder() const -{ - return QStringLiteral("/"); -} - QString Theme::oauthClientId() const { return QStringLiteral("xdXOt13JKxym1B1QcEncf2XDkLAexMBFwiT9j6EfhhHFJhs2KM9jbjTmf8JBXE69"); @@ -628,14 +340,10 @@ QString Theme::oauthClientSecret() const return QStringLiteral("UBntmLjC2yYCeHwsyj73Uwo9TAaecAetRwMw0xYcvNL9yRdLSUi0hUAHfvCHFeFh"); } -QString Theme::oauthLocalhost() const -{ - return QStringLiteral("http://localhost"); -} - -QPair Theme::oauthOverrideAuthUrl() const +QVector Theme::oauthPorts() const { - return {}; + // zero means a random port + return {0}; } QString Theme::openIdConnectScopes() const @@ -648,39 +356,48 @@ QString Theme::openIdConnectPrompt() const return QStringLiteral("select_account consent"); } -QString Theme::versionSwitchOutput() const +QString Theme::wizardUrlPlaceholder() const +{ + return QString(); +} + +bool Theme::forceVirtualFilesOption() const { - return aboutVersions(Theme::VersionFormat::Url); + return false; } -bool Theme::showVirtualFilesOption() const +QVector> Theme::urlActions() const { - return enableExperimentalFeatures(); + return {}; } -bool Theme::forceVirtualFilesOption() const +bool Theme::moveToTrashDefaultValue() const { return false; } -bool Theme::enableExperimentalFeatures() const +bool Theme::allowSystemConfigOverrides() const { - return ConfigFile().showExperimentalOptions(); + return false; } -bool Theme::connectionValidatorClearCookies() const +bool Theme::syncNewlyDiscoveredSpaces() const { return false; } -bool Theme::enableSocketApiIconSupport() const +bool Theme::spacesAreCalledFolders() const { - return true; + return false; } -bool Theme::warnOnMultipleDb() const +bool Theme::withCrashReporter() const { - return isVanilla(); +#ifdef WITH_CRASHREPORTER + return true; +#else + return false; +#endif } } // end namespace client diff --git a/src/libsync/theme.h b/src/libsync/theme.h index 89000974d15..0c16b977080 100644 --- a/src/libsync/theme.h +++ b/src/libsync/theme.h @@ -12,31 +12,29 @@ * for more details. */ -#ifndef _THEME_H -#define _THEME_H +#pragma once -#include -#include +#include "common/utility.h" +#include "resources/resources.h" #include "syncresult.h" -class QIcon; -class QString; -class QObject; -class QPixmap; -class QColor; -class QPaintDevice; +#include +#include +#include +#include namespace OCC { - class SyncResult; /** * @brief The Theme class * @ingroup libsync */ + class OWNCLOUDSYNC_EXPORT Theme : public QObject { Q_OBJECT + public: enum class VersionFormat { Plain, @@ -48,13 +46,14 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject /* returns a singleton instance. */ static Theme *instance(); + static Theme *create(QQmlEngine *qmlEngine, QJSEngine *); ~Theme() override; /** * @brief appNameGUI - Human readable application name. * - * Use and redefine this if the human readable name contains spaces, + * Use and redefine this if the human-readable name contains spaces, * special chars and such. * * By default, the name is derived from the APPLICATION_NAME @@ -72,15 +71,19 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject * name in the GUI, redefine appNameGUI. * * By default, the name is derived from the APPLICATION_SHORTNAME - * cmake variable, and should be the same. This method is only - * reimplementable for legacy reasons. + * cmake variable, and should be the same. This method exists only for + * legacy reasons. * * Warning: Do not modify this value, as many things, e.g. settings * depend on it! You most likely want to modify \ref appNameGUI(). * * @return QString with app name. */ - virtual QString appName() const; + QString appName() const; + + QString orgDomainName() const; + + QString vendor() const; /** * @brief configFileName @@ -88,51 +91,19 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject */ virtual QString configFileName() const; -#ifndef TOKEN_AUTH_ONLY /** - * Wehther we allow a fallback to a vanilla icon + * get a sync state icon */ - enum class IconType { - BrandedIcon, - BrandedIconWithFallbackToVanillaIcon, - VanillaIcon - }; - /** - * get an sync state icon - */ - QIcon syncStateIcon(const SyncResult &status, bool sysTray = false, bool sysTrayMenuVisible = false) const; - QIcon syncStateIcon(SyncResult::Status result, bool sysTray = false, bool sysTrayMenuVisible = false) const; + QIcon themeTrayIcon(const SyncResult &result, bool sysTrayMenuVisible = false, + Resources::IconType iconType = Resources::IconType::BrandedIconWithFallbackToVanillaIcon) const; + QString syncStateIconName(const SyncResult &result) const; - virtual QIcon folderDisabledIcon() const; - virtual QIcon folderOfflineIcon(bool sysTray = false, bool sysTrayMenuVisible = false) const; virtual QIcon applicationIcon() const; + virtual QString applicationIconName() const; virtual QIcon aboutIcon() const; - /** - * Whether use the dark icon theme - * The function also ensures the theme supports the dark theme - */ - bool isUsingDarkTheme() const; - - /** - * Whether the branding allows the dark theme - */ - bool allowDarkTheme() const; -#endif - - virtual QString statusHeaderText(SyncResult::Status) const; - - /** - * Characteristics: bool if more than one sync folder is allowed - */ - virtual bool singleSyncFolder() const; - - /** - * When true, client works with multiple accounts. - */ - virtual bool multiAccount() const; /** * URL to documentation. @@ -161,66 +132,35 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject * Setting a value here will pre-define the server url. * * The respective UI controls will be disabled - * Deprecated: Use overrideServerUrlV2 as it allows overrides + * NOT deprecated. overrideServerUrlV2 is gone as "overrides" (for what purpose? smells like a backdoor) should happen in theme only, + * or via the managed device config */ - Q_DECL_DEPRECATED_X("Use overrideServerUrlV2") - virtual QString overrideServerUrl() const; - - /** Same as overrideServerUrl allows override by - * setting $OWNCLOUD_OVERRIDE_SERVER_URL - */ - QString overrideServerUrlV2() const; - /** - * This is only usefull when previous version had a different overrideServerUrl - * with a different auth type in that case You should then specify "http" or "shibboleth". - * Normaly this should be left empty. - */ - virtual QString forceConfigAuthType() const; - - /** - * The default folder name without path on the server at setup time. - */ - virtual QString defaultServerFolder() const; - - /** - * The default folder name without path on the client side at setup time. - */ - virtual QString defaultClientFolder() const; + virtual QString overrideServerUrl() const; /** - * Override to encforce a particular locale, i.e. "de" or "pt_BR" + * If set to a non-empty string, the path part of the URL will be overwritten with this path. + * This can be used to set the end-point to a fixed location, and thereby shorten the URL that + * is given to the users. + * + * For example, if the URL for the product always contains `/dav` as the path, and setting that + * here, and the URL given to the user is `example.com`, the branded application will contact + * `example.com/dav`. */ - virtual QString enforcedLocale() const { return QString(); } + virtual QString overrideServerPath() const; -#ifndef TOKEN_AUTH_ONLY - /** colored, white or black */ - QString systrayIconFlavor(bool mono, bool sysTrayMenuVisible = false) const; - /** @return color for the setup wizard */ + /** @return color for the setup wizard. This is effectively the text color for the wizard pages*/ virtual QColor wizardHeaderTitleColor() const; - virtual QColor wizardHeaderSubTitleColor() const; - /** @return color for the setup wizard. */ + /** @return color for the setup wizard. This is effectively the background color for each page*/ virtual QColor wizardHeaderBackgroundColor() const; /** @return logo for the setup wizard. */ virtual QIcon wizardHeaderLogo() const; - /** - * The default implementation creates a - * background based on - * \ref wizardHeaderTitleColor(). - * - * @return banner for the setup wizard. - */ - virtual QPixmap wizardHeaderBanner(const QSize &size) const; -#endif - - /** - * The SHA sum of the released git commit - */ - QString gitSHA1(VersionFormat format = VersionFormat::Plain) const; + /** @return logo that is used below the main wizard page content. */ + virtual QIcon wizardFooterLogo() const; /** * The used library versions @@ -231,66 +171,18 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject * About dialog contents */ virtual QString about() const; - virtual bool aboutShowCopyright() const; /** * Define if the systray icons should be using mono design */ void setSystrayUseMonoIcons(bool mono); - /** - * Retrieve wether to use mono icons for systray - */ - bool systrayUseMonoIcons() const; - - /** - * Check if mono icons are available - */ - bool monoIconsAvailable() const; /** * @brief Where to check for new Updates. */ - virtual QString updateCheckUrl() const; - - /** - * Default option for the newBigFolderSizeLimit. - * Size in MB of the maximum size of folder before we ask the confirmation. - * Set -1 to never ask confirmation. 0 to ask confirmation for every folder. - **/ - virtual qint64 newBigFolderSizeLimit() const; - - /** - * Hide the checkbox that says "Ask for confirmation before synchronizing folders larger than X MB" - * in the account wizard - */ - virtual bool wizardHideFolderSizeLimitCheckbox() const; - /** - * Hide the checkbox that says "Ask for confirmation before synchronizing external storages" - * in the account wizard - */ - virtual bool wizardHideExternalStorageConfirmationCheckbox() const; + QUrl updateCheckUrl() const; - /** - * Skip the advanced page and create a sync with the default settings - */ - virtual bool wizardSkipAdvancedPage() const; - - /** - * Alternative path on the server that provides access to the webdav capabilities - * - * Attention: Make sure that this string does NOT have a leading slash and that - * it has a trailing slash, for example "remote.php/webdav/". - */ - virtual QString webDavPath() const; - - /** - * @brief Sharing options - * - * Allow link sharing and or user/group sharing - */ - virtual bool linkSharing() const; - virtual bool userGroupSharing() const; /** * If this returns true, the user cannot configure the proxy in the network settings. @@ -300,65 +192,10 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject virtual bool forceSystemNetworkProxy() const; /** - * @brief How to handle the userID - * - * @value UserIDUserName Wizard asks for user name as ID - * @value UserIDEmail Wizard asks for an email as ID - * @value UserIDCustom Specify string in \ref customUserID - */ - enum UserIDType { UserIDUserName = 0, - UserIDEmail, - UserIDCustom }; - - /** @brief What to display as the userID (e.g. in the wizards) - * - * @return UserIDType::UserIDUserName, unless reimplemented - */ - virtual UserIDType userIDType() const; - - /** - * @brief Allows to customize the type of user ID (e.g. user name, email) - * - * @note This string cannot be translated, but is still useful for - * referencing brand name IDs (e.g. "ACME ID", when using ACME.) - * - * @return An empty string, unless reimplemented - */ - virtual QString customUserID() const; - - /** - * @brief Demo string to be displayed when no text has been - * entered for the user id (e.g. mylogin@company.com) - * - * @return An empty string, unless reimplemented - */ - virtual QString userIDHint() const; - - /** - * @brief Postfix that will be enforced in a URL. e.g. - * ".myhosting.com". - * - * @return An empty string, unless reimplemented - */ - virtual QString wizardUrlPostfix() const; - - /** - * @brief String that will be shown as long as no text has been entered by the user. - * - * @return An empty string, unless reimplemented - */ - virtual QString wizardUrlHint() const; - - /** - * @brief the server folder that should be queried for the quota information - * - * This can be configured to show the quota infromation for a different - * folder than the root. This is the folder on which the client will do - * PROPFIND calls to get "quota-available-bytes" and "quota-used-bytes" - * - * Defaults: "/" + * @brief wizardUrlPlaceholder provides placeholder text for the URL field in the new account wizard + * @return empty string unless overridden */ - virtual QString quotaBaseFolder() const; + virtual QString wizardUrlPlaceholder() const; /** * The OAuth client_id, secret pair. @@ -368,108 +205,86 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject virtual QString oauthClientSecret() const; /** - * Defaults to http://localhost due to historic reasons, - * can be set to http://127.0.0.1 reasons. - * This option is only available with oauth2 not with OpenID Connect. - */ - virtual QString oauthLocalhost() const; - - /** - * By default the client tries to get the OAuth access endpoint and the OAuth token endpoint from /.well-known/openid-configuration - * Setting this allow authentication without a well known url - * - * @return QPair + * List of ports to use for the local redirect server */ - virtual QPair oauthOverrideAuthUrl() const; + virtual QVector oauthPorts() const; /** - * Returns the required opeidconnect scopes + * Returns the required OpenIDConnect scopes */ virtual QString openIdConnectScopes() const; /** - * Returns the openidconnect promt type + * Returns the OpenIDConnect prompt type * It is supposed to be "consent select_account". - * For Konnect it currently needs to be select_account, + * For "Konnect" it currently needs to be select_account, * which is the current default. */ virtual QString openIdConnectPrompt() const; - /** - * @brief What should be output for the --version command line switch. - * - * By default, it's a combination of appName(), version(), the GIT SHA1 and some - * important dependency versions. - */ - virtual QString versionSwitchOutput() const; /** - * @brief Whether to show the option to create folders using "virtual files". - * - * By default, this is the same as enableExperimentalFreatures() + * On windows, force the user to use vfs, by disabling options to turn it off. + * Default: false */ - virtual bool showVirtualFilesOption() const; - virtual bool forceVirtualFilesOption() const; /** - * @brief Whether to show options considered as experimental. - * - * By default, the options are not shown unless experimental options are - * manually enabled in the configuration file. - */ - virtual bool enableExperimentalFeatures() const; + * Returns a list of IconName, Name, Url triplets that will be displayed as buttons or menu items in the main view + * For each url an optional icon can be provided in the form of #IconName.svg or multiple #IconName-#resolution.png like for the other theme icons. + * if the icon name is empty or "wrong" there will be no icon on the action, just text + * */ + virtual QVector> urlActions() const; /** - * Whether to clear cookies before checking status.php - * This is used with F5 BIG-IP seups. + * Set the default value for move to trash option + * Default: false */ - virtual bool connectionValidatorClearCookies() const; - + virtual bool moveToTrashDefaultValue() const; /** - * Enables the response of V2/GET_CLIENT_ICON, default true. - * See #9167 + * @brief Allow the system configuration to override theme values. + * @default false */ - virtual bool enableSocketApiIconSupport() const; + virtual bool allowSystemConfigOverrides() const; + /** + * @brief Automatically add sync connections for newly discovered Spaces. + * + * Default: false + * See #11749 + */ + virtual bool syncNewlyDiscoveredSpaces() const; /** - * Warn if we find multiple db files in the sync root. - * This can indicate that the sync dir is shared between multiple clients or accounts + * Whether to call spaces "Spaces" in the UI, or call them "Folders" + * + * Default: false */ - virtual bool warnOnMultipleDb() const; + virtual bool spacesAreCalledFolders() const; + bool withCrashReporter() const; protected: -#ifndef TOKEN_AUTH_ONLY - QIcon themeUniversalIcon(const QString &name, IconType iconType = IconType::BrandedIcon) const; - QIcon themeTrayIcon(const QString &name, bool sysTrayMenuVisible = false, IconType iconType = IconType::BrandedIconWithFallbackToVanillaIcon) const; - QIcon themeIcon(const QString &name, IconType iconType = IconType::BrandedIconWithFallbackToVanillaIcon) const; -#endif Theme(); -signals: + /** + * The SHA sum of the released git commit + */ + QString gitSHA1(VersionFormat format = VersionFormat::Plain) const; + + +Q_SIGNALS: void systrayUseMonoIconsChanged(bool); + void themeChanged(); + private: Theme(Theme const &); Theme &operator=(Theme const &); - QIcon loadIcon(const QString &flavor, const QString &name, IconType iconType) const; - // whether or not a theme is available - bool hasTheme(IconType type, const QString &theme) const; - static Theme *_instance; bool _mono = false; -#ifndef TOKEN_AUTH_ONLY - mutable QMap _iconCache; - // <, bool - // caches the availability of themes for branded and unbranded themes - mutable QMap, bool> _themeCache; - const bool _hasBrandedColored = hasTheme(IconType::BrandedIcon, QStringLiteral("colored")); - const bool _hasBrandedDark = hasTheme(IconType::BrandedIcon, QStringLiteral("dark")); -#endif }; + } -#endif // _THEME_H diff --git a/src/libsync/uploaddevice.cpp b/src/libsync/uploaddevice.cpp new file mode 100644 index 00000000000..f482c270a43 --- /dev/null +++ b/src/libsync/uploaddevice.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 "account.h" +#include "filesystem.h" +#include "propagateuploadfile.h" +#include "syncengine.h" +#include "uploaddevice.h" + +#include "common/asserts.h" +#include "common/utility.h" + +#include "libsync/theme.h" + +#include + +#include + +using namespace std::chrono_literals; + +namespace OCC { + +UploadDevice::UploadDevice(const QString &fileName, qint64 start, qint64 size) + : _file(fileName) + , _start(start) + , _size(size) + , _read(0) +{ +} + +bool UploadDevice::open(QIODevice::OpenMode mode) +{ + if (mode & QIODevice::WriteOnly) + return false; + + // Get the file size now: _file.fileName() is no longer reliable + // on all platforms after openAndSeekFileSharedRead(). + auto fileDiskSize = FileSystem::getSize(QFileInfo{_file.fileName()}); + + QString openError; + if (!FileSystem::openAndSeekFileSharedRead(&_file, &openError, _start)) { + setErrorString(openError); + return false; + } + + _size = qBound(0ll, _size, fileDiskSize - _start); + _read = 0; + + return QIODevice::open(mode); +} + +void UploadDevice::close() +{ + _file.close(); + QIODevice::close(); +} + +qint64 UploadDevice::writeData(const char *, qint64) +{ + OC_ASSERT_X(false, "write to read only device"); + return 0; +} + +qint64 UploadDevice::readData(char *data, qint64 maxLen) +{ + if (_size - _read <= 0) { + return -1; + } + maxLen = qMin(maxLen, _size - _read); + if (maxLen <= 0) { + return 0; + } + + auto c = _file.read(data, maxLen); + if (c < 0) { + setErrorString(_file.errorString()); + return -1; + } + _read += c; + return c; +} + +bool UploadDevice::atEnd() const +{ + return _read >= _size; +} + +qint64 UploadDevice::size() const +{ + return _size; +} + +qint64 UploadDevice::bytesAvailable() const +{ + return _size - _read + QIODevice::bytesAvailable(); +} + +// random access, we can seek +bool UploadDevice::isSequential() const +{ + return false; +} + +bool UploadDevice::seek(qint64 pos) +{ + if (!QIODevice::seek(pos)) { + return false; + } + if (pos < 0 || pos > _size) { + return false; + } + _read = pos; + _file.seek(_start + pos); + return true; +} +} diff --git a/src/libsync/uploaddevice.h b/src/libsync/uploaddevice.h new file mode 100644 index 00000000000..55cd5174db7 --- /dev/null +++ b/src/libsync/uploaddevice.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) by Olivier Goffart + * + * 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 + +namespace OCC { + +/** + * @brief The UploadDevice class + * @ingroup libsync + */ +class UploadDevice : public QIODevice +{ + Q_OBJECT +public: + UploadDevice(const QString &fileName, qint64 start, qint64 size); + + bool open(QIODevice::OpenMode mode) override; + void close() override; + + qint64 writeData(const char *, qint64) override; + qint64 readData(char *data, qint64 maxLen) override; + bool atEnd() const override; + qint64 size() const override; + qint64 bytesAvailable() const override; + bool isSequential() const override; + bool seek(qint64 pos) override; + + Q_SIGNALS: + + private: + /// The local file to read data from + QFile _file; + + /// Start of the file data to use + qint64 _start = 0; + /// Amount of file data after _start to use + qint64 _size = 0; + /// Position between _start and _start+_size + qint64 _read = 0; +}; +} \ No newline at end of file diff --git a/src/libsync/version.h b/src/libsync/version.h new file mode 100644 index 00000000000..4ec22aacbc8 --- /dev/null +++ b/src/libsync/version.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 +#pragma message("Warning: Legacy version.h was included, this is only meant as a workaround for the legacy branding mechanism. Please directly include 'common/version.h'") + +#include "common/version.h" diff --git a/src/libsync/vfs/CMakeLists.txt b/src/libsync/vfs/CMakeLists.txt deleted file mode 100644 index dadf1eed977..00000000000 --- a/src/libsync/vfs/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -# Globbing for plugins has a problem with in-source builds -# that create directories for the build. - -foreach(vfsPlugin ${VIRTUAL_FILE_SYSTEM_PLUGINS}) - set(vfsPluginPath ${vfsPlugin}) - set(vfsPluginPathOut ${vfsPlugin}) - get_filename_component(vfsPluginName ${vfsPlugin} NAME) - if (NOT IS_ABSOLUTE ${vfsPlugin}) - set(vfsPluginPath "${CMAKE_CURRENT_LIST_DIR}/${vfsPlugin}") - else() - get_filename_component(vfsPluginPathOut ${vfsPluginPath} DIRECTORY) - endif() - if(NOT IS_DIRECTORY ${vfsPluginPath}) - continue() - endif() - - add_subdirectory(${vfsPluginPath} ${vfsPluginPathOut}) - - if(BUILD_TESTING AND IS_DIRECTORY "${vfsPluginPath}/test") - add_subdirectory("${vfsPluginPath}/test" "${vfsPluginName}_test") - message(STATUS "Added vfsPlugin with tests: ${vfsPluginName}") - else() - message(STATUS "Added vfsPlugin without tests: ${vfsPluginName}") - endif() -endforeach() diff --git a/src/libsync/vfs/suffix/CMakeLists.txt b/src/libsync/vfs/suffix/CMakeLists.txt deleted file mode 100644 index bdc33cac410..00000000000 --- a/src/libsync/vfs/suffix/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_library(owncloud_vfs_suffix MODULE - vfs_suffix.cpp -) - -set_target_properties(owncloud_vfs_suffix PROPERTIES OUTPUT_NAME "${synclib_NAME}_vfs_suffix") - -target_link_libraries(owncloud_vfs_suffix - libsync -) -INSTALL(TARGETS owncloud_vfs_suffix DESTINATION "${KDE_INSTALL_PLUGINDIR}") - diff --git a/src/libsync/vfs/suffix/vfs_suffix.cpp b/src/libsync/vfs/suffix/vfs_suffix.cpp deleted file mode 100644 index 1641c83831b..00000000000 --- a/src/libsync/vfs/suffix/vfs_suffix.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) by Christian Kamm - * - * 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 "vfs_suffix.h" - -#include - -#include "syncfileitem.h" -#include "filesystem.h" -#include "common/syncjournaldb.h" - -namespace OCC { - -VfsSuffix::VfsSuffix(QObject *parent) - : Vfs(parent) -{ -} - -VfsSuffix::~VfsSuffix() -{ -} - -Vfs::Mode VfsSuffix::mode() const -{ - return WithSuffix; -} - -QString VfsSuffix::fileSuffix() const -{ - return QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX); -} - -void VfsSuffix::startImpl(const VfsSetupParams ¶ms) -{ - // It is unsafe for the database to contain any ".owncloud" file entries - // that are not marked as a virtual file. These could be real .owncloud - // files that were synced before vfs was enabled. - QByteArrayList toWipe; - params.journal->getFilesBelowPath("", [&toWipe](const SyncJournalFileRecord &rec) { - if (!rec.isVirtualFile() && rec._path.endsWith(APPLICATION_DOTVIRTUALFILE_SUFFIX)) - toWipe.append(rec._path); - }); - for (const auto &path : toWipe) { - params.journal->deleteFileRecord(QString::fromUtf8(path)); - } - Q_EMIT started(); -} - -void VfsSuffix::stop() -{ -} - -void VfsSuffix::unregisterFolder() -{ -} - -bool VfsSuffix::isHydrating() const -{ - return false; -} - -Result VfsSuffix::updateMetadata(const SyncFileItem &item, const QString &filePath, const QString &) -{ - FileSystem::setModTime(filePath, item._modtime); - return Vfs::ConvertToPlaceholderResult::Ok; -} - -Result VfsSuffix::createPlaceholder(const SyncFileItem &item) -{ - // The concrete shape of the placeholder is also used in isDehydratedPlaceholder() below - const QString fn = _setupParams.filesystemPath + item._file; - Q_ASSERT(fn.endsWith(fileSuffix())); - - QFile file(fn); - if (file.exists() && file.size() > 1 - && !FileSystem::verifyFileUnchanged(fn, item._size, item._modtime)) { - return QStringLiteral("Cannot create a placeholder because a file with the placeholder name already exist"); - } - - if (!file.open(QFile::ReadWrite | QFile::Truncate)) - return file.errorString(); - - file.write(" "); - file.close(); - FileSystem::setModTime(fn, item._modtime); - return {}; -} - -Result VfsSuffix::dehydratePlaceholder(const SyncFileItem &item) -{ - SyncFileItem virtualItem(item); - virtualItem._file = item._renameTarget; - auto r = createPlaceholder(virtualItem); - if (!r) - return r; - - if (item._file != item._renameTarget) { // can be the same when renaming foo -> foo.owncloud to dehydrate - QFile::remove(_setupParams.filesystemPath + item._file); - } - - // Move the item's pin state - auto pin = _setupParams.journal->internalPinStates().rawForPath(item._file.toUtf8()); - if (pin && *pin != PinState::Inherited) { - setPinState(item._renameTarget, *pin); - setPinState(item._file, PinState::Inherited); - } - - // Ensure the pin state isn't contradictory - pin = pinState(item._renameTarget); - if (pin && *pin == PinState::AlwaysLocal) - setPinState(item._renameTarget, PinState::Unspecified); - return {}; -} - -bool VfsSuffix::isDehydratedPlaceholder(const QString &filePath) -{ - if (!filePath.endsWith(fileSuffix())) - return false; - QFileInfo fi(filePath); - return fi.exists() && fi.size() == 1; -} - -bool VfsSuffix::statTypeVirtualFile(csync_file_stat_t *stat, void *) -{ - if (stat->path.endsWith(fileSuffix().toUtf8())) { - stat->type = ItemTypeVirtualFile; - return true; - } - return false; -} - -Vfs::AvailabilityResult VfsSuffix::availability(const QString &folderPath) -{ - return availabilityInDb(folderPath); -} - -QString VfsSuffix::underlyingFileName(const QString &fileName) const -{ - { - if (fileName.endsWith(fileSuffix())) { - return fileName.chopped(fileSuffix().size()); - } - return fileName; - } -} - -} // namespace OCC diff --git a/src/libsync/vfs/suffix/vfs_suffix.h b/src/libsync/vfs/suffix/vfs_suffix.h deleted file mode 100644 index f73a964b5d1..00000000000 --- a/src/libsync/vfs/suffix/vfs_suffix.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) by Christian Kamm - * - * 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 -#include - -#include "common/vfs.h" -#include "common/plugin.h" - -namespace OCC { - -class VfsSuffix : public Vfs -{ - Q_OBJECT - -public: - explicit VfsSuffix(QObject *parent = nullptr); - ~VfsSuffix() override; - - Mode mode() const override; - QString fileSuffix() const override; - QString underlyingFileName(const QString &fileName) const override; - - - void stop() override; - void unregisterFolder() override; - - bool socketApiPinStateActionsShown() const override { return true; } - bool isHydrating() const override; - - - Result createPlaceholder(const SyncFileItem &item) override; - Result dehydratePlaceholder(const SyncFileItem &item) override; - - bool needsMetadataUpdate(const SyncFileItem &) override { return false; } - bool isDehydratedPlaceholder(const QString &filePath) override; - bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) override; - - bool setPinState(const QString &folderPath, PinState state) override - { return setPinStateInDb(folderPath, state); } - Optional pinState(const QString &folderPath) override - { return pinStateInDb(folderPath); } - AvailabilityResult availability(const QString &folderPath) override; - -public slots: - void fileStatusChanged(const QString &, SyncFileStatus) override {} - -protected: - Result updateMetadata(const SyncFileItem &item, const QString &filePath, const QString &replacesFile) override; - void startImpl(const VfsSetupParams ¶ms) override; -}; - -class SuffixVfsPluginFactory : public QObject, public DefaultPluginFactory -{ - Q_OBJECT - Q_PLUGIN_METADATA(IID "org.owncloud.PluginFactory" FILE "vfspluginmetadata.json") - Q_INTERFACES(OCC::PluginFactory) -}; - -} // namespace OCC diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt new file mode 100644 index 00000000000..276f2be0557 --- /dev/null +++ b/src/plugins/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(vfs) diff --git a/src/plugins/vfs/CMakeLists.txt b/src/plugins/vfs/CMakeLists.txt new file mode 100644 index 00000000000..6a2935120af --- /dev/null +++ b/src/plugins/vfs/CMakeLists.txt @@ -0,0 +1,28 @@ +# Globbing for plugins has a problem with in-source builds +# that create directories for the build. + + +include(OCAddVfsPlugin) + +foreach(vfsPlugin ${VIRTUAL_FILE_SYSTEM_PLUGINS}) + set(vfsPluginPath ${vfsPlugin}) + set(vfsPluginPathOut ${vfsPlugin}) + get_filename_component(vfsPluginName ${vfsPlugin} NAME) + if (NOT IS_ABSOLUTE ${vfsPlugin}) + set(vfsPluginPath "${CMAKE_CURRENT_LIST_DIR}/${vfsPlugin}") + else() + set(vfsPluginPathOut ${PROJECT_BINARY_DIR}/plugins/${vfsPluginName}) + endif() + if(NOT IS_DIRECTORY ${vfsPluginPath}) + continue() + endif() + + add_subdirectory(${vfsPluginPath} ${vfsPluginPathOut}) + + if(BUILD_TESTING AND IS_DIRECTORY "${vfsPluginPath}/test") + add_subdirectory("${vfsPluginPath}/test" "${vfsPluginName}_test") + message(STATUS "Added vfsPlugin with tests: ${vfsPluginName}") + else() + message(STATUS "Added vfsPlugin without tests: ${vfsPluginName}") + endif() +endforeach() diff --git a/src/plugins/vfs/off/CMakeLists.txt b/src/plugins/vfs/off/CMakeLists.txt new file mode 100644 index 00000000000..dda7be9f76c --- /dev/null +++ b/src/plugins/vfs/off/CMakeLists.txt @@ -0,0 +1 @@ +add_vfs_plugin(NAME off SRC vfs_off.cpp) \ No newline at end of file diff --git a/src/plugins/vfs/off/vfs_off.cpp b/src/plugins/vfs/off/vfs_off.cpp new file mode 100644 index 00000000000..216eab5b3a4 --- /dev/null +++ b/src/plugins/vfs/off/vfs_off.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "vfs_off.h" + +#include "filesystem.h" +#include "syncfileitem.h" + +using namespace OCC; + +VfsOff::VfsOff(QObject *parent) + : Vfs(parent) +{ +} + +VfsOff::~VfsOff() = default; + +Vfs::Mode VfsOff::mode() const +{ + return Vfs::Off; +} + +void VfsOff::stop() { } + +void VfsOff::unregisterFolder() { } + +bool VfsOff::socketApiPinStateActionsShown() const +{ + return false; +} + +Result VfsOff::createPlaceholder(const SyncFileItem &) +{ + return {}; +} + +bool VfsOff::needsMetadataUpdate(const SyncFileItem &) +{ + return false; +} + +bool VfsOff::isDehydratedPlaceholder(const QString &) +{ + return false; +} + +bool VfsOff::statTypeVirtualFile(csync_file_stat_t *, void *) +{ + return false; +} + +bool VfsOff::setPinState(const QString &, PinState) +{ + return true; +} + +Optional VfsOff::pinState(const QString &) +{ + return PinState::AlwaysLocal; +} + +Vfs::AvailabilityResult VfsOff::availability(const QString &) +{ + return VfsItemAvailability::AlwaysLocal; +} + +void VfsOff::startImpl(const VfsSetupParams &) +{ + Q_EMIT started(); +} + +Result VfsOff::updateMetadata(const SyncFileItem &item, const QString &filePath, const QString &replacesFile) +{ + Q_UNUSED(replacesFile) + + if (!item.isDirectory()) { + const bool isReadOnly = !item._remotePerm.isNull() && !item._remotePerm.hasPermission(RemotePermissions::CanWrite); + FileSystem::setFileReadOnlyWeak(filePath, isReadOnly); + } + return { ConvertToPlaceholderResult::Ok }; +} + +void VfsOff::onFileStatusChanged(const QString &, SyncFileStatus) +{ +} diff --git a/src/plugins/vfs/off/vfs_off.h b/src/plugins/vfs/off/vfs_off.h new file mode 100644 index 00000000000..4f4245ead3d --- /dev/null +++ b/src/plugins/vfs/off/vfs_off.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) by Christian Kamm + * + * 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 +#include + +#include "common/plugin.h" +#include "common/vfs.h" + +namespace OCC { + +class VfsOff : public Vfs +{ + Q_OBJECT + +public: + VfsOff(QObject *parent = nullptr); + ~VfsOff() override; + + Mode mode() const override; + + void stop() override; + void unregisterFolder() override; + + bool socketApiPinStateActionsShown() const override; + + Result createPlaceholder(const SyncFileItem &) override; + + bool needsMetadataUpdate(const SyncFileItem &) override; + bool isDehydratedPlaceholder(const QString &) override; + bool statTypeVirtualFile(csync_file_stat_t *, void *) override; + + bool setPinState(const QString &, PinState) override; + Optional pinState(const QString &) override; + AvailabilityResult availability(const QString &) override; + +public Q_SLOTS: + void onFileStatusChanged(const QString &, SyncFileStatus) override; + +protected: + Result updateMetadata(const SyncFileItem &, const QString &, const QString &) override; + void startImpl(const VfsSetupParams &) override; +}; + + +class OffVfsPluginFactory : public QObject, public DefaultPluginFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.owncloud.PluginFactory" FILE "vfspluginmetadata.json") + Q_INTERFACES(OCC::PluginFactory) +}; + +} // namespace OCC diff --git a/src/plugins/vfs/win/CMakeLists.txt b/src/plugins/vfs/win/CMakeLists.txt new file mode 100755 index 00000000000..de8d452eefb --- /dev/null +++ b/src/plugins/vfs/win/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# This file was originally licensed under ownCloud Commercial License. +# see +# As of 2025, it is relicensed under GPL 2.0 or later. +# +# SPDX-License-Identifier: GPL-2.0-or-later +if(WIN32) + add_library(vfs_plugin_win_static OBJECT + vfs_win.cpp + hydrationdevice.cpp + validationdevice.cpp + callbacks.cpp + utility.cpp + ) + + apply_common_target_settings(vfs_plugin_win_static) + + target_link_libraries(vfs_plugin_win_static PUBLIC + libsync + cldapi + Propsys + WindowsApp + ) + add_vfs_plugin(NAME wincfapi SRC vfs_win_plugin_factory.cpp LIBS vfs_plugin_win_static) +endif() diff --git a/src/plugins/vfs/win/callbacks.cpp b/src/plugins/vfs/win/callbacks.cpp new file mode 100755 index 00000000000..52374b468e6 --- /dev/null +++ b/src/plugins/vfs/win/callbacks.cpp @@ -0,0 +1,158 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "callbacks.h" +#include "libsync/syncengine.h" + +#include "vfs_win_p.h" + +#define ENUM_CASE_NAME(X) \ + case X: \ + return QString::fromUtf8(#X); \ + break; + +namespace { +QString dehydrationReasonToName(CF_CALLBACK_DEHYDRATION_REASON reason) +{ + switch (reason) { + ENUM_CASE_NAME(CF_CALLBACK_DEHYDRATION_REASON_NONE); + ENUM_CASE_NAME(CF_CALLBACK_DEHYDRATION_REASON_USER_MANUAL); + ENUM_CASE_NAME(CF_CALLBACK_DEHYDRATION_REASON_SYSTEM_LOW_SPACE); + ENUM_CASE_NAME(CF_CALLBACK_DEHYDRATION_REASON_SYSTEM_INACTIVITY); + ENUM_CASE_NAME(CF_CALLBACK_DEHYDRATION_REASON_SYSTEM_OS_UPGRADE); + } + Q_UNREACHABLE(); +} +} + +namespace OCC { + +// WARNING: Callbacks in threadpool: multiple and parallel calls are possible +void CALLBACK callbackFetchData(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params) +{ + qCInfo(lcVfs) << Q_FUNC_INFO; + const auto callbackContext = reinterpret_cast(info->CallbackContext); + // const auto transferKey = info->TransferKey.QuadPart; + const auto opdata = CfAPIUtil::CfOpdata(info); + + // Note: Resuming works as expected: the file is already partially hydrated + // we will see resumeStart > 0 here. + auto resumeStart = params->FetchData.RequiredFileOffset.QuadPart; + auto resumeEnd = resumeStart + params->FetchData.RequiredLength.QuadPart; + + if (params->FetchData.OptionalFileOffset.QuadPart == resumeEnd) { + resumeEnd += params->FetchData.OptionalLength.QuadPart; + qCInfo(lcVfs) << Q_FUNC_INFO << "Performing an optional extended hydration"; + } else if (OC_ENSURE(params->FetchData.OptionalFileOffset.QuadPart == 0)) { + // noop + } + + const QString path = CfCallbackInfoHelper::getFullNormalizedPath(info); + auto fileId = QByteArray(reinterpret_cast(info->FileIdentity), info->FileIdentityLength); + qCInfo(lcVfs) << "fetch data request" << callbackContext->q->params().filesystemPath << path << fileId + << params->FetchData.RequiredFileOffset.QuadPart << params->FetchData.RequiredLength.QuadPart + << params->FetchData.OptionalFileOffset.QuadPart << params->FetchData.OptionalLength.QuadPart + << "Resume:" << resumeStart << "to" << resumeEnd << "size:" << (resumeEnd - resumeStart); + + // Jump to gui thread + QMetaObject::invokeMethod(callbackContext, [callbackContext, resumeStart, resumeEnd, path, fileId, opdata]() { + callbackContext->startFetchData(opdata, path, resumeStart, resumeEnd, fileId); + }, Qt::QueuedConnection); +} + +void CALLBACK callbackCancelFetchData(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params) +{ + Q_UNUSED(params) + + qCInfo(lcVfs) << Q_FUNC_INFO; + const auto callbackContext = reinterpret_cast(info->CallbackContext); + const auto transferKey = info->TransferKey.QuadPart; + const QString path = CfCallbackInfoHelper::getFullNormalizedPath(info); + qCInfo(lcVfs) << "fetch data request abort for" << path; + + // Jump to gui thread + QMetaObject::invokeMethod(callbackContext, [callbackContext, transferKey, path]() { + callbackContext->cancelFetchData(transferKey, path); + }, Qt::QueuedConnection); +} + +void CALLBACK callbackValidateData(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params) +{ + qCInfo(lcVfs) << Q_FUNC_INFO; + // TODO: Even if this fails the local file will be read as a hydrated placeholder afterwards! That's annoying! + + const auto callbackContext = reinterpret_cast(info->CallbackContext); + auto opdata = CfAPIUtil::CfOpdata(info); + auto offset = params->ValidateData.RequiredFileOffset.QuadPart; + auto length = params->ValidateData.RequiredLength.QuadPart; + auto fileSize = info->FileSize.QuadPart; + + const QString path = CfCallbackInfoHelper::getFullNormalizedPath(info); + qCInfo(lcVfs) << "request to validate data" << path << offset << length; + + // Jump to gui thread + QMetaObject::invokeMethod(callbackContext, [callbackContext, opdata, offset, length, fileSize, path]() mutable { + callbackContext->startValidateData(opdata, path, offset, length, fileSize); + }, Qt::QueuedConnection); +} + +void CALLBACK callbackNotifyDehydrate(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params) +{ + const QString path = CfCallbackInfoHelper::getFullNormalizedPath(info); + qCInfo(lcVfs) << Q_FUNC_INFO << "dehydration of" << path << "requested, reason " << dehydrationReasonToName(params->Dehydrate.Reason); +} + +void CALLBACK callbackNotifyDehydrateCompletion(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params) +{ + const QString path = CfCallbackInfoHelper::getFullNormalizedPath(info); + qCInfo(lcVfs) << Q_FUNC_INFO << "dehydration of" << path << "completed, reason " << dehydrationReasonToName(params->DehydrateCompletion.Reason); +} + +void CALLBACK callbackFetchPlaceholders(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params) +{ + const QString path = CfCallbackInfoHelper::getFullNormalizedPath(info); + Q_UNUSED(params); + qCInfo(lcVfs) << Q_FUNC_INFO << path; +} + +void CALLBACK callbackCancelFetchPlaceholders(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params) +{ + Q_UNUSED(params) + + const QString path = CfCallbackInfoHelper::getFullNormalizedPath(info); + qCInfo(lcVfs) << Q_FUNC_INFO << path; +} + +void CALLBACK callbackRename(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params) +{ + const auto callbackContext = reinterpret_cast(info->CallbackContext); + const QString path = CfCallbackInfoHelper::getFullNormalizedPath(info); + const QString newPath = CfCallbackInfoHelper::getFullPath(info, params->Rename.TargetPath); + CF_OPERATION_PARAMETERS opParams = {}; + opParams.ParamSize = CF_SIZE_OF_OP_PARAM(AckRename); + opParams.AckRename.CompletionStatus = STATUS_SUCCESS; + + CF_SYNC_STATUS *syncStatus = nullptr; + qCInfo(lcVfs) << Q_FUNC_INFO << path << "to" << newPath; + VfsWin *vfs = callbackContext->q; + if (vfs->isDehydratedPlaceholder(path) && vfs->params().syncEngine()->isExcluded(newPath)) { + qCWarning(lcVfs) << "Preventing rename of" << path << "to" << newPath << "as it would create a stray placeholder"; + opParams.AckRename.CompletionStatus = STATUS_CLOUD_FILE_NOT_SUPPORTED; + } + const auto opdata = CfAPIUtil::CfOpdata(info); + auto opInfo = opdata.toOpinfo(); + opInfo.Type = CF_OPERATION_TYPE_ACK_RENAME; + opInfo.SyncStatus = syncStatus; + + HRESULT ok = CfExecute(&opInfo, &opParams); + if (FAILED(ok)) { + qCWarning(lcVfs) << "CfExecute error on signaling fetch error" << Utility::formatWinError(ok); + return; + } +} + +} // OCC namespace diff --git a/src/plugins/vfs/win/callbacks.h b/src/plugins/vfs/win/callbacks.h new file mode 100755 index 00000000000..82613ef0532 --- /dev/null +++ b/src/plugins/vfs/win/callbacks.h @@ -0,0 +1,21 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "cfapi_includes.h" + +namespace OCC { +void CALLBACK callbackFetchData(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params); +void CALLBACK callbackCancelFetchData(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params); +void CALLBACK callbackValidateData(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params); +void CALLBACK callbackNotifyDehydrate(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params); +void CALLBACK callbackNotifyDehydrateCompletion(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params); +void CALLBACK callbackFetchPlaceholders(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params); +void CALLBACK callbackCancelFetchPlaceholders(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params); +void CALLBACK callbackRename(const CF_CALLBACK_INFO *info, const CF_CALLBACK_PARAMETERS *params); +} diff --git a/src/plugins/vfs/win/cfapi_includes.h b/src/plugins/vfs/win/cfapi_includes.h new file mode 100644 index 00000000000..d89a55a6f07 --- /dev/null +++ b/src/plugins/vfs/win/cfapi_includes.h @@ -0,0 +1,21 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#ifdef _WINDOWS_ +#error Windows.h was already included +#endif +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS + +#include +#include +#include +#include + +#include diff --git a/src/plugins/vfs/win/hydrationdevice.cpp b/src/plugins/vfs/win/hydrationdevice.cpp new file mode 100755 index 00000000000..011ff50bdcf --- /dev/null +++ b/src/plugins/vfs/win/hydrationdevice.cpp @@ -0,0 +1,96 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "hydrationdevice.h" + +#include "common/filesystembase.h" +#include "common/utility.h" + +using namespace OCC::FileSystem::SizeLiterals; + +namespace { +/** + * The only requirement is that both offset and length are 4KB aligned unless the range described ends on the logical file size (EoF), + * in which case, the length is not required to be 4KB aligned as long as the resulting range ends on or beyond the logical file size. + * + * https://learn.microsoft.com/en-us/windows/win32/api/cfapi/ns-cfapi-cf_operation_parameters + */ +constexpr qsizetype minChunkSizeC = 4_KiB * 1000; +} + +namespace OCC { + +PlaceholderHydrationDevice::PlaceholderHydrationDevice( + CfAPIUtil::CfOpdata opdata, + quint64 startOffset, + quint64 size) + : _opdata(opdata) + , _offset(startOffset) + , _size(size) +{ + // reserve a buffer with twice the size + _buffer.reserve(minChunkSizeC * 2); + _progressCompleted.QuadPart = startOffset; +} + +qint64 PlaceholderHydrationDevice::writeData(const char *data, qint64 maxSize) +{ + _buffer.append(data, maxSize); + + // Feed data if it has an acceptable size or if it's the final chunk. + while (!_buffer.isEmpty() && (_buffer.size() >= minChunkSizeC || _buffer.size() == _size - _offset)) { + auto feedAmount = std::min(_buffer.size(), minChunkSizeC); + + auto opInfo = _opdata.toOpinfo(); + opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA; + opInfo.SyncStatus = nullptr; // can convey extra user-visible text through this + + CF_OPERATION_PARAMETERS opParams = {}; + opParams.ParamSize = CF_SIZE_OF_OP_PARAM(TransferData); + opParams.TransferData.Flags = CF_OPERATION_TRANSFER_DATA_FLAG_NONE; + opParams.TransferData.Offset.QuadPart = _offset; + opParams.TransferData.Length.QuadPart = feedAmount; + opParams.TransferData.CompletionStatus = STATUS_SUCCESS; + opParams.TransferData.Buffer = _buffer.constData(); + + HRESULT ok = CfExecute(&opInfo, &opParams); + if (FAILED(ok)) { + qCWarning(lcVfs) << "CfExecute error in writeData" << Utility::formatWinError(ok); + return -1; + } + + _offset += feedAmount; + // inplace righ() + const auto newSize = _buffer.size() - feedAmount; + memcpy(_buffer.data(), _buffer.data() + feedAmount, newSize); + _buffer.resize(newSize); + } + + // refresh Windows Copy Dialog progress anyhow + + LARGE_INTEGER progressTotal = {}; + progressTotal.QuadPart = _size; + + _progressCompleted.QuadPart += maxSize; + + + HRESULT ok = CfReportProviderProgress(_opdata.connectionKey, _opdata.transferKey, progressTotal, _progressCompleted); + if (FAILED(ok)) { + qCWarning(lcVfs) << "CfReportProviderProgress error in writeData" << Utility::formatWinError(ok); + } + return maxSize; +} + +void PlaceholderHydrationDevice::close() +{ + if (_buffer.size() > 0) { + qCCritical(lcVfs) << "Closed PlaceholderHydrationDevice while buffer still filled" << _buffer.size() << _offset << _size; + } + QIODevice::close(); +} + +} diff --git a/src/plugins/vfs/win/hydrationdevice.h b/src/plugins/vfs/win/hydrationdevice.h new file mode 100755 index 00000000000..1f230934811 --- /dev/null +++ b/src/plugins/vfs/win/hydrationdevice.h @@ -0,0 +1,59 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "utility.h" + +namespace OCC { + +// Device that feeds data to Windows via CfExecute instead of storing to a file +class PlaceholderHydrationDevice : public QIODevice +{ + Q_OBJECT + +public: + PlaceholderHydrationDevice( + CfAPIUtil::CfOpdata opdata, + quint64 startOffset, + quint64 size); + + quint64 transferedAmount() const + { + return _offset; + } + + quint64 fileSize() const + { + return _size; + } + +protected: + qint64 readData(char *, qint64) override + { + // can't read data + return -1; + } + + qint64 writeData(const char *data, qint64 maxSize) override; + + void close() override; + +private: + CfAPIUtil::CfOpdata _opdata; + + qsizetype _offset = 0; + const qsizetype _size = 0; + + QByteArray _buffer; + + // for the progress bar + LARGE_INTEGER _progressCompleted = {}; +}; + +} diff --git a/src/plugins/vfs/win/utility.cpp b/src/plugins/vfs/win/utility.cpp new file mode 100644 index 00000000000..77571af59d1 --- /dev/null +++ b/src/plugins/vfs/win/utility.cpp @@ -0,0 +1,129 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "utility.h" + +#include +#include + +using namespace OCC; + +namespace { +// Taken from the CloudMirror example from Microsoft +std::unique_ptr getTokenInformation() +{ + std::unique_ptr tokenInfo; + + // get the tokenHandle from current thread/process if it's null + auto tokenHandle { GetCurrentThreadEffectiveToken() }; // Pseudo token, don't free. + + DWORD tokenInfoSize { 0 }; + if (!::GetTokenInformation(tokenHandle, TokenUser, nullptr, 0, &tokenInfoSize)) { + if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + tokenInfo.reset(reinterpret_cast(new char[tokenInfoSize])); + if (!::GetTokenInformation(tokenHandle, TokenUser, tokenInfo.get(), tokenInfoSize, &tokenInfoSize)) { + qCInfo(lcVfs) << "GetTokenInformation failed"; + } + } else { + qCInfo(lcVfs) << "GetTokenInformation failed"; + } + } + return tokenInfo; +} + +// Taken from the CloudMirror example from Microsoft +QString convertSidToQString(_In_ PSID sid) +{ + winrt::com_array stringBuffer; + if (ConvertSidToStringSidW(sid, winrt::put_abi(stringBuffer))) { + // winrt::put_abi does not set the size for stringBuffer + return QString::fromWCharArray(stringBuffer.data()); + } else { + return {}; + } +}; +} // anonymous namespace + +CfAPIUtil::CfOpdata::CfOpdata() + : correlationVector(nullptr) +{ +} + +CfAPIUtil::CfOpdata::CfOpdata(const CF_CALLBACK_INFO *info) + : connectionKey(info->ConnectionKey) + , transferKey(info->TransferKey) + , correlationVector(info->CorrelationVector) +{ +} + +CF_OPERATION_INFO CfAPIUtil::CfOpdata::toOpinfo() const +{ + auto opInfo = CF_OPERATION_INFO(); + opInfo.StructSize = sizeof(opInfo); + opInfo.ConnectionKey = connectionKey; + opInfo.TransferKey = transferKey; + opInfo.CorrelationVector = correlationVector; + return opInfo; +} + +Result OCC::CfAPIUtil::getFileMetadata(const Utility::Handle &fileHandle, FILE_BASIC_INFO *info) +{ + if (!GetFileInformationByHandleEx(fileHandle, FileBasicInfo, info, sizeof(*info))) { + return GetLastError(); + } + return {}; +} + +Result OCC::CfAPIUtil::getFileMetadata(const QString &filePath, FILE_BASIC_INFO *info) +{ + auto fileHandle = OCC::CfAPIUtil::getFileHandle(filePath, 0); + if (!fileHandle) { + return fileHandle.error(); + } + return OCC::CfAPIUtil::getFileMetadata(*fileHandle, info); +} + +Result OCC::CfAPIUtil::getFileHandle(const QString &filePath, DWORD access) +{ + DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + DWORD creationDisp = OPEN_EXISTING; + DWORD attributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS; + + auto longPath = FileSystem::longWinPath(filePath).toStdWString(); + + HANDLE fileHandle = CreateFileW( + longPath.data(), + access, + shareMode, + NULL, + creationDisp, + attributes, + NULL); + auto out = Utility::Handle(fileHandle); + if (out) { + return std::move(out); + } + return HRESULT_FROM_WIN32(GetLastError()); +} + +QString CfAPIUtil::getSidAsString() +{ + std::unique_ptr tokenInfo(getTokenInformation()); + return convertSidToQString(tokenInfo->User.Sid); +} + +template <> +Result CfAPIUtil::getInfo(HANDLE handle) +{ + return getInfo(handle, CF_PLACEHOLDER_INFO_BASIC); +} + +template <> +Result CfAPIUtil::getInfo(HANDLE handle) +{ + return getInfo(handle, CF_PLACEHOLDER_INFO_STANDARD); +} diff --git a/src/plugins/vfs/win/utility.h b/src/plugins/vfs/win/utility.h new file mode 100755 index 00000000000..7706c2d340a --- /dev/null +++ b/src/plugins/vfs/win/utility.h @@ -0,0 +1,120 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include "cfapi_includes.h" + +#include "common/filesystembase.h" +#include "common/result.h" +#include "common/utility.h" +#include "common/utility_win.h" + +#include +#include + +#include + +using namespace OCC::FileSystem::SizeLiterals; + +Q_DECLARE_LOGGING_CATEGORY(lcVfs) + + +// Horrible macro magic to help to initialise CF_OPERATION_PARAMETERS.ParamSize +// see: https://github.com/microsoft/Windows-classic-samples/blob/27ffb0811ca761741502feaefdb591aebf592193/Samples/CloudMirror/CloudMirror/FileCopierWithProgress.cpp#L38 +#define FIELD_SIZE(type, field) (sizeof(((type *)0)->field)) +#define CF_SIZE_OF_OP_PARAM(field) (FIELD_OFFSET(CF_OPERATION_PARAMETERS, field) + FIELD_SIZE(CF_OPERATION_PARAMETERS, field)) +namespace OCC::CfAPIUtil { + +/// Holds data that callbacks provide and that needs to be passed into CfExecute +struct CfOpdata +{ + CfOpdata(); + + explicit CfOpdata(const CF_CALLBACK_INFO *info); + + CF_OPERATION_INFO toOpinfo() const; + + CF_CONNECTION_KEY connectionKey; + LARGE_INTEGER transferKey; + PCORRELATION_VECTOR correlationVector; +}; + +inline QString hstringToQString(const winrt::hstring &str) +{ + const std::wstring_view view { str }; + // qt6 will take size_t + return QString::fromWCharArray(view.data(), view.size()); +} + + +Result getFileHandle(const QString &filePath, DWORD access); + +template +[[nodiscard]] Result getInfo(HANDLE handle, CF_PLACEHOLDER_INFO_CLASS type) +{ + auto info = INFO(); + DWORD resultsize = 0; + HRESULT ok = CfGetPlaceholderInfo(handle, type, &info, sizeof(info), &resultsize); + // INFO has a variable lenght, we are however only interested in the fixed size members + if (SUCCEEDED(ok) || ok == HRESULT_FROM_WIN32(ERROR_MORE_DATA)) + return std::move(info); + return ok; +} + + +template +[[nodiscard]] Result getInfo(HANDLE handle) +{ + static_assert(std::is_same::value, "Unreachable"); +} + +template <> +[[nodiscard]] Result getInfo(HANDLE handle); + +template <> +[[nodiscard]] Result getInfo(HANDLE handle); + + +template +[[nodiscard]] Result getInfo(const QString &fileName) +{ + auto handle = getFileHandle(fileName, 0); + if (!handle) { + // If the source file doesn't exist, there's nothing to propagate + if (handle.error() != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { + qCWarning(lcVfs) << Q_FUNC_INFO << ": couldn't get handle for" << fileName << Utility::formatWinError(handle.error()); + } + return handle.error(); + } + return getInfo(handle.get()); +} + +[[nodiscard]] Result getFileMetadata(const Utility::Handle &fileHandle, FILE_BASIC_INFO *info); + +[[nodiscard]] Result getFileMetadata(const QString &filePath, FILE_BASIC_INFO *info); + +QString getSidAsString(); + +/** + * + * Assert that we follow https://learn.microsoft.com/en-us/windows/win32/api/cfapi/ns-cfapi-cf_operation_parameters + * We either folow the alignment or we passed the whole file size. + * @param size The size of the complete file + * @param offset Offset for the current chunk + * @param length Length of the current chunk + */ +inline void assert_4_KiB_chunk(size_t size, size_t offset, size_t length) +{ + // the offset must be 0 or a multiple of 4_KiB + Q_ASSERT(offset % 4_KiB == 0); + Q_ASSERT( + // length is a multiple of 4_KiB + (length % 4_KiB == 0) + // we queried the rest of the file, here the 4_KiB rule does not apply + || (offset + length == size)); +} +} diff --git a/src/plugins/vfs/win/validationdevice.cpp b/src/plugins/vfs/win/validationdevice.cpp new file mode 100755 index 00000000000..b77da1017d1 --- /dev/null +++ b/src/plugins/vfs/win/validationdevice.cpp @@ -0,0 +1,63 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "validationdevice.h" +#include "utility.h" + +#include "common/filesystembase.h" +#include "common/utility.h" + +using namespace OCC::FileSystem::SizeLiterals; + +namespace OCC { + +qint64 PlaceholderValidationDevice::size() const +{ + return _size; +} + +qint64 PlaceholderValidationDevice::pos() const +{ + return _offset; +} + +qint64 PlaceholderValidationDevice::readData(char *data, qint64 maxSize) +{ + if (_offset >= _size) + return -1; + if (maxSize == 0) + return 0; + auto opInfo = _opdata.toOpinfo(); + opInfo.Type = CF_OPERATION_TYPE_RETRIEVE_DATA; + opInfo.SyncStatus = nullptr; // can convey extra user-visible text through this + + CF_OPERATION_PARAMETERS opParams = {}; + opParams.ParamSize = CF_SIZE_OF_OP_PARAM(RetrieveData); + opParams.RetrieveData.Flags = CF_OPERATION_RETRIEVE_DATA_FLAG_NONE; + opParams.RetrieveData.Buffer = data; + opParams.RetrieveData.Offset.QuadPart = _offset; + // ensure length is a multiple of 4_KiB + opParams.RetrieveData.Length.QuadPart = std::min(maxSize - (maxSize % 4_KiB), _size - _offset); + CfAPIUtil::assert_4_KiB_chunk(size(), opParams.RetrieveData.Offset.QuadPart, opParams.RetrieveData.Length.QuadPart); + + HRESULT ok = CfExecute(&opInfo, &opParams); + if (FAILED(ok)) { + qCWarning(lcVfs) << "CfExecute error in readData" << Utility::formatWinError(ok); + return -1; + } + + auto readAmount = opParams.RetrieveData.ReturnedLength.QuadPart; + _offset += readAmount; + return readAmount; +} + +bool PlaceholderValidationDevice::atEnd() const +{ + return _offset >= _size; +} + +} diff --git a/src/plugins/vfs/win/validationdevice.h b/src/plugins/vfs/win/validationdevice.h new file mode 100755 index 00000000000..d52b78c892b --- /dev/null +++ b/src/plugins/vfs/win/validationdevice.h @@ -0,0 +1,57 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "utility.h" + +namespace OCC { + +// Device that reads via FETCH_DATA to feed data into checksum validation functions +class PlaceholderValidationDevice : public QIODevice +{ + Q_OBJECT + +public: + PlaceholderValidationDevice( + CfAPIUtil::CfOpdata opdata, + quint64 startOffset, + quint64 size) + : _opdata(opdata) + , _offset(startOffset) + , _size(size) + { + } + + qint64 size() const override; + qint64 pos() const override; + bool atEnd() const override; + +protected: + qint64 readData(char *data, qint64 maxSize) override; + + bool isSequential() const override + { + return true; + } + + qint64 writeData(const char *, qint64) override + { + // can't write data + return -1; + } + +private: + CfAPIUtil::CfOpdata _opdata; + + quint64 _offset = 0; + quint64 _size = 0; +}; + +} diff --git a/src/plugins/vfs/win/vfs_win.cpp b/src/plugins/vfs/win/vfs_win.cpp new file mode 100755 index 00000000000..b5c24310e0c --- /dev/null +++ b/src/plugins/vfs/win/vfs_win.cpp @@ -0,0 +1,1221 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +// this include should come before every other include +#include "cfapi_includes.h" + +#include "vfs_win.h" +#include "vfs_win_p.h" + + +#include +#include +#include +#include + +#include + +#include // needed for AddFolderToSearchIndexer + +#include "account.h" +#include "common/checksums.h" +#include "common/result.h" +#include "common/utility.h" +#include "libsync/filesystem.h" +#include "owncloudpropagator_p.h" // for parseEtag() +#include "propagatedownload.h" +#include "propagatorjobs.h" + +#include "csync.h" + +#include +#include +#include +#include +#include +#include + +#include "utility.h" +#include "validationdevice.h" +#include "hydrationdevice.h" +#include "callbacks.h" + + +using namespace OCC::FileSystem::SizeLiterals; + +namespace { +const wchar_t* MSSearchIndex = L"SystemIndex"; +} + +Q_LOGGING_CATEGORY(lcVfs, "sync.vfs.win", QtDebugMsg) + +using namespace OCC; +using namespace OCC::CfAPIUtil; + +namespace { + +std::wstring convertFullNativePathToFullyDecodedUrlW(const QString &path) +{ + // We assume that the path is in the form "c:\My Documents", so valid native path without any encoded characters. + // Then the fully decoded URL is "file:///c:\My Documents". + // Note: we are not using QUrl to first encode the path, and then fully-decode it. The problem with fully decoding + // some URLs is cannot be reliably represented as a fully-decoded string (see QUrl documentation). However, + // we already have a valid local path that is not encoded, we can use that directly. + return QStringLiteral("file:///%1").arg(path).toStdWString(); +} + +// Tell windows that the hydration needs to be restarted, possibly with a new size +void restartHydration(const CfOpdata &opdata, const QString &filePath = {}, const qint64 &updatedFilesize = -1) +{ + auto opInfo = opdata.toOpinfo(); + opInfo.Type = CF_OPERATION_TYPE_RESTART_HYDRATION; + + CF_OPERATION_PARAMETERS opParams = {}; + opParams.ParamSize = CF_SIZE_OF_OP_PARAM(RestartHydration); + opParams.RestartHydration.Flags = CF_OPERATION_RESTART_HYDRATION_FLAG_NONE; + + // If we restart because there's a new size, send it along + CF_FS_METADATA metadata; + if (!filePath.isEmpty() && updatedFilesize != -1) { + opParams.RestartHydration.FsMetadata = &metadata; + metadata.FileSize.QuadPart = updatedFilesize; + const auto result = getFileMetadata(filePath, &metadata.BasicInfo); + if (!result) { + qCWarning(lcVfs) << "CfExecute error on restart hydration" << Utility::formatWinError(result.error()); + return; + } + } + + HRESULT ok = CfExecute(&opInfo, &opParams); + if (FAILED(ok)) { + qCWarning(lcVfs) << "CfExecute error on restart hydration" << Utility::formatWinError(ok); + return; + } +} + +// Takes oldFile's pin state and sets it on newFile which is a new hydrated file +void propagatePinState(const QString &newFile, const QString &oldFile) +{ + const auto prevInfo = getInfo(oldFile); + if (!prevInfo) { + if (prevInfo.error() != HRESULT_FROM_WIN32(ERROR_NOT_A_CLOUD_FILE)) + qCWarning(lcVfs) << Q_FUNC_INFO << ": couldn't get info for" << oldFile << Utility::formatWinError(prevInfo.error()); + return; + } + // It doesn't make sense to set EXCLUDED or UNPINNED on a new hydrated file + CF_PIN_STATE state = prevInfo->PinState; + if (state == CF_PIN_STATE_EXCLUDED || state == CF_PIN_STATE_UNPINNED) + state = CF_PIN_STATE_UNSPECIFIED; + + const auto handle = getFileHandle(newFile, WRITE_DAC); + OC_ASSERT(handle); + const HRESULT ok = CfSetPinState(*handle, state, CF_SET_PIN_FLAG_NONE, nullptr); + if (FAILED(ok)) { + qCWarning(lcVfs) << Q_FUNC_INFO << ": couldn't write pin state to file" << Utility::formatWinError(ok); + } +} + + +OCC::Result convertToPlaceholder(const QString &filename, const OCC::SyncFileItem &item, const QString &replacesFile) +{ + const QString longPath = FileSystem::longWinPath(filename); + // Check whether it already is a placeholder + auto info = getInfo(longPath); + if (info) { + // already a cloud file + return OCC::Vfs::ConvertToPlaceholderResult::Ok; + } else if (info.error() == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { + return OCC::Vfs::ConvertToPlaceholderResult::Ok; + } else if (info.error() != HRESULT_FROM_WIN32(ERROR_NOT_A_CLOUD_FILE)) { + qCWarning(lcVfs) << Q_FUNC_INFO << ": couldn't get info for" << filename << Utility::formatWinError(info.error()); + } + + // Get exclusive access + Utility::Handle oplockHandle { INVALID_HANDLE_VALUE, &CfCloseHandle }; + HRESULT ok = CfOpenFileWithOplock( + reinterpret_cast(longPath.utf16()), + CF_OPEN_FILE_FLAG_EXCLUSIVE, + &oplockHandle.handle()); + if (FAILED(ok)) { + const QString error = QStringLiteral("CfOpenWithOplock error %1 %2").arg(filename, Utility::formatWinError(ok)); + qCWarning(lcVfs) << error; + if (ok == HRESULT_FROM_WIN32(ERROR_CANNOT_BREAK_OPLOCK) || ok == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)) { + return OCC::Vfs::ConvertToPlaceholderResult::Locked; + } + return error; + } + + // Convert it + USN usn; + ok = CfConvertToPlaceholder( + oplockHandle, + item._fileId.constData(), + item._fileId.size(), + CF_CONVERT_FLAG_MARK_IN_SYNC, + &usn, + nullptr); + if (FAILED(ok)) { + const QString error = QStringLiteral("CfConvertToPlaceholder error %1 %2").arg(filename, Utility::formatWinError(ok)); + qCWarning(lcVfs) << error; + return error; + } + + // Propagate pin state, if any + if (!replacesFile.isEmpty()) { + propagatePinState(longPath, replacesFile); + } + return OCC::Vfs::ConvertToPlaceholderResult::Ok; +} + +winrt::com_ptr getSearchCrawlScopeManager() +{ + winrt::com_ptr searchCrawlScopeManager; + + try { + winrt::com_ptr searchManager; + winrt::check_hresult(CoCreateInstance(__uuidof(CSearchManager), NULL, CLSCTX_SERVER, __uuidof(&searchManager), searchManager.put_void())); + + winrt::com_ptr searchCatalogManager; + winrt::check_hresult(searchManager->GetCatalog(MSSearchIndex, searchCatalogManager.put())); + + winrt::check_hresult(searchCatalogManager->GetCrawlScopeManager(searchCrawlScopeManager.put())); + } catch (...) { + qCWarning(lcVfs) << "Failed to get CrawlScopeManager:" << Utility::formatWinError(winrt::to_hresult()); + } + return searchCrawlScopeManager; +} +} + +// Tell windows that the fetch_data request has failed +void HydrationContext::transferDataError(NTSTATUS status) +{ + auto opInfo = opdata.toOpinfo(); + opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA; + + CF_OPERATION_PARAMETERS opParams = {}; + opParams.ParamSize = CF_SIZE_OF_OP_PARAM(TransferData); + opParams.TransferData.CompletionStatus = status; + + // the error affects the whole file, not a transfer chunk + opParams.TransferData.Offset.QuadPart = 0; + opParams.TransferData.Length.QuadPart = hydrationDevice->fileSize(); + assert_4_KiB_chunk(hydrationDevice->fileSize(), opParams.TransferData.Offset.QuadPart, opParams.TransferData.Length.QuadPart); + + HRESULT ok = CfExecute(&opInfo, &opParams); + if (FAILED(ok)) { + qCWarning(lcVfs) << "CfExecute error on signaling fetch error" << Utility::formatWinError(ok); + return; + } +} + +void VfsWinPrivate::startFetchData(CfOpdata opdata, const QString &targetPath, quint64 fetchStart, quint64 fetchEnd, const QByteArray &fileId) +{ + // Get account-relative path to file + auto ¶ms = q->params(); + auto journal = params.journal; + SyncJournalFileRecord record; + journal->getFileRecordsByFileId(fileId, [&](const SyncJournalFileRecord &r) { + Q_ASSERT(!record.isValid()); + record = r; + // TODO: How about multiple results? + }); + if (!record.isValid()) { + qCWarning(lcVfs) << "Failed to find record for" << fileId; + return; + } + QString accountRelativePath = params.remotePath + QString::fromUtf8(record._path); + + QMap headers; + + // Make sure we hydrate only with the expected amount of data: we get into + // trouble if the remote and local file size don't match up: either Windows + // will wait for more data that isn't coming, or Windows will reject us + // feeding more data than it expects. + if (static_cast(record._fileSize) != fetchEnd) { + qCInfo(lcVfs) << "DB size and placeholder size don't match up, restarting…" + << accountRelativePath << record._fileSize << fetchEnd; + restartHydration(opdata, targetPath, record._fileSize); + return; + } + + auto hydrationContext = new HydrationContext(q); + + auto hydrationDevice = new PlaceholderHydrationDevice(opdata, fetchStart, fetchEnd); + hydrationDevice->setParent(hydrationContext); + hydrationDevice->open(QIODevice::WriteOnly); + + + // We don't mind if we download a newer revision of the file: intentionally use empty etag + auto getJob = new GETFileJob(params.account, params.baseUrl(), accountRelativePath, hydrationDevice, headers, {}, fetchStart, hydrationContext); + getJob->setExpectedContentLength(fetchEnd - fetchStart); + getJob->setPriority(QNetworkRequest::HighPriority); + + // TODO: This could be a resume where the hydration context already exists? Unsure. + // this case would work as-is, but maybe it'd be good to keep track of retried hydrations. + hydrationContext->journal = journal; + hydrationContext->opdata = opdata; + hydrationContext->record = record; + hydrationContext->filesystemPath = targetPath; // ignore the filename from the db record, it might have been renamed (and we fetched the record based on the fileId). + hydrationContext->hydrationDevice = hydrationDevice; + hydrationContext->downloadJob = getJob; + setHydrationContext(opdata.transferKey.QuadPart, hydrationContext); + + QObject::connect(getJob, &GETFileJob::finishedSignal, hydrationContext, &HydrationContext::downloadJobDone); + getJob->start(); +} + +void HydrationContext::downloadJobDone() +{ + if (!journal) { + qCWarning(lcVfs) << "journal was destroyed before hydration finished"; + delete this; + return; + } + auto job = downloadJob; + if (!job) { + qCWarning(lcVfs) << "GET job destroyed before downloadJobDone called"; + delete this; + return; + } + + // The job is done and will deleteLater() itself. Also, we might wipe this + // hydrationContext on error. If the job is parented to this it would be + // deleted then and we'd call deleteLater() on a dead instance. + downloadJob->setParent(nullptr); + + // Handle errors first: + if (job->reply()->error() != QNetworkReply::NoError || job->errorStatus() != SyncFileItem::NoStatus) { + if (isAborted) { + qCDebug(lcVfs) << "GET job was aborted"; + return; + } + + // TODO: more elaborate error handling needed + qCInfo(lcVfs) << "GET job error" << record._path << "with error code" << job->httpStatusCode() << job->errorString(); + + if (job->httpStatusCode() == 404) { + qCDebug(lcVfs) << "Reporting file not in sync"; + transferDataError(STATUS_CLOUD_FILE_NOT_IN_SYNC); + // TODO: schedule a sync + } else if (job->contentLength() != -1 && job->contentLength() != job->expectedContentLength()) { + Q_ASSERT(job->resumeStart() == 0); + const auto newFileSize = job->resumeStart() + job->contentLength(); + if (newFileSize == 0 && record._fileSize != 0) { + // the file might now be a folder + qCWarning(lcVfs) << "Metadata appears to be outdated, abort hydration" << record._path; + Q_EMIT pluginInstance()->needSync(); + transferDataError(STATUS_CLOUD_FILE_NOT_IN_SYNC); + } else { + qCInfo(lcVfs) << "Data size and placeholder size don't match up, restarting…" << record._path; + record._fileSize = newFileSize; + journal->setFileRecord(record); + restartHydration(opdata, filesystemPath, record._fileSize); + } + } else { + qCDebug(lcVfs) << "Reporting generic download failed"; + transferDataError(STATUS_CLOUD_FILE_UNSUCCESSFUL); + } + + delete this; + return; + } + + // Download was successful! + + // Update etag and mtime, similar to PropagateDownload + if (!job->etag().isEmpty()) { + Q_ASSERT(job->etag() == Utility::normalizeEtag(job->etag())); + record._etag = job->etag().toUtf8(); + } + if (job->lastModified()) { + record._modtime = job->lastModified(); + } + + // Find checksum header + // TODO: Duplicated with PropagateDownload + transmissionChecksumHeader = findBestChecksum(job->reply()->rawHeader(checkSumHeaderC)); + const auto contentMd5Header = job->reply()->rawHeader(contentMd5HeaderC); + if (transmissionChecksumHeader.isEmpty() && !contentMd5Header.isEmpty()) { + transmissionChecksumHeader = "MD5:" + contentMd5Header; + } + + // signal that validation may start now + isDownloadDone = true; + Q_EMIT downloadDone(); + // TODO: maybe also start a timer, so we can release the hydration in case the + // windows validation callback never fires? +} + +void VfsWinPrivate::cancelFetchData(TransferKey transferKey, const QString &path) +{ + auto context = hydrationContext(transferKey); + if (!context) { + qCWarning(lcVfs) << "trying to cancel unknown hydration" << path << transferKey; + return; + } + + qCInfo(lcVfs) << "request to cancel hydration of" << context->filesystemPath; + + // Abort the download job + context->abort(); + + // It's possible it's being used from another thread currently + delete context; +} + +void VfsWinPrivate::startValidateData(CfOpdata opdata, const QString &path, quint64 offset, quint64 length, quint64 fileSize) +{ + auto context = hydrationContext(opdata.transferKey.QuadPart); + if (!context) { + // TODO: better error signaling? + qCWarning(lcVfs) << "don't know about the hydration, restarting" << path; + restartHydration(opdata); + return; + } + context->opdata = opdata; + + if (offset != 0 || length != fileSize) { + qCWarning(lcVfs) << "we don't support partial validation, validate the whole file"; + } + + // Make sure fetching is entirely done (including the finished() signal) + // before going to the validation step + auto continueValidation = [context, fileSize]() { + context->ensureContentChecksum(fileSize); + }; + if (context->isDownloadDone) { + continueValidation(); + } else { + QObject::connect(context, &HydrationContext::downloadDone, context, continueValidation); + } +} + +HydrationContext::HydrationContext(VfsWin *parent) + : QObject(parent) +{ + +} + +HydrationContext::~HydrationContext() +{ +} + +void HydrationContext::ensureContentChecksum(quint64 fileSize) +{ + const auto checksumType = pluginInstance()->params().account->capabilities().preferredUploadChecksumType(); + // If we can reuse the transmission checksum as the content checksum, skip ahead + const auto checksumHeader = ChecksumHeader::parseChecksumHeader(transmissionChecksumHeader); + if (!transmissionChecksumHeader.isEmpty() && checksumHeader.type() == checksumType) { + record._checksumHeader = transmissionChecksumHeader; + validateTransmissionChecksum(fileSize); + return; + } + + // Otherwise, compute the content checksum, then proceed + + auto device = std::make_unique(opdata, 0, fileSize); + device->open(QIODevice::ReadOnly); + + auto compute = new ComputeChecksum(this); + compute->setChecksumType(checksumType); + connect(compute, &ComputeChecksum::done, this, [this, fileSize](CheckSums::Algorithm checksumType, const QByteArray &checksum) { + record._checksumHeader = ChecksumHeader(checksumType, checksum).makeChecksumHeader(); + validateTransmissionChecksum(fileSize); + }); + compute->start(std::move(device)); +} + +void HydrationContext::validateTransmissionChecksum(quint64 fileSize) +{ + auto opInfo = opdata.toOpinfo(); + opInfo.Type = CF_OPERATION_TYPE_ACK_DATA; + opInfo.SyncStatus = nullptr; // can convey extra user-visible text through this + + CF_OPERATION_PARAMETERS opParams = {}; + opParams.ParamSize = CF_SIZE_OF_OP_PARAM(AckData); + opParams.AckData.Flags = CF_OPERATION_ACK_DATA_FLAG_NONE; + opParams.AckData.CompletionStatus = STATUS_SUCCESS; + + // we validate the whole file, not a singe chunk + opParams.AckData.Offset.QuadPart = 0; + opParams.AckData.Length.QuadPart = fileSize; + assert_4_KiB_chunk(fileSize, opParams.AckData.Offset.QuadPart, opParams.AckData.Length.QuadPart); + + auto device = std::make_unique(opdata, 0, fileSize); + device->open(QIODevice::ReadOnly); + + auto validator = new ValidateChecksumHeader(this); + QObject::connect(validator, &ValidateChecksumHeader::validated, this, [this, opInfo, opParams](CheckSums::Algorithm checksumType, const QByteArray &checksum) mutable { + Q_UNUSED(checksumType); + Q_UNUSED(checksum); + qCInfo(lcVfs) << "validation success:" << record._path; + + HRESULT ok = CfExecute(&opInfo, &opParams); + if (FAILED(ok)) { + qCWarning(lcVfs) << "CfExecute error" << Utility::formatWinError(ok); + delete this; + return; + } + + // Set placeholder file mtime to db mtime + FileSystem::setModTime(filesystemPath, record._modtime); + + // Update the inode, similar to SyncFileItem::toSyncJournalFileRecordWithInode() + FileSystem::getInode(filesystemPath, &record._inode); + + // The file is no longer virtual + record._type = ItemTypeFile; + + // Update db entry + if (journal) + journal->setFileRecord(record); + + pluginInstance()->onFileStatusChanged(filesystemPath, SyncFileStatus::StatusUpToDate); + delete this; + }); + QObject::connect(validator, &ValidateChecksumHeader::validationFailed, this, [this, opInfo, opParams](const QString &msg) mutable { + qCInfo(lcVfs) << "validation failed:" << record._path << msg; + + opParams.AckData.CompletionStatus = STATUS_CLOUD_FILE_VALIDATION_FAILED; + HRESULT ok = CfExecute(&opInfo, &opParams); + if (FAILED(ok)) { + qCWarning(lcVfs) << "CfExecute error" << Utility::formatWinError(ok); + delete this; + return; + } + + delete this; + }); + validator->start(std::move(device), transmissionChecksumHeader); +} + +VfsWin::VfsWin(QObject *parent) + : Vfs(parent) + , d_ptr(new VfsWinPrivate(this)) +{ +} + +VfsWin::~VfsWin() +{ + if (d_ptr->addToSearchIndexerThread) { + d_ptr->addToSearchIndexerThread->wait(); + } +} + +Vfs::Mode VfsWin::mode() const +{ + return WindowsCfApi; +} + +// the canonical sync root path +QString VfsWinPrivate::syncRootPath() const +{ + return QDir::toNativeSeparators(Utility::stripTrailingSlash(q->params().filesystemPath)); +} + +QMutex VfsWinPrivate::registrationMutex; + +// Register as a sync provider with cfapi as well as with the explorer integration +void VfsWinPrivate::registerFolder(const VfsSetupParams ¶ms) +{ + const QString syncRoot = syncRootPath(); + const auto syncRootW = syncRoot.toStdWString(); + _registrationId = [¶ms, &syncRoot] { + QString id; + + // For the StorageProviderSyncRootManager the id seems to be the primary key. + // There's probably a length limit though, so make reasonably sure folders have + // unique ids by using a hash. + auto hash = QCryptographicHash::hash(syncRoot.toUtf8(), QCryptographicHash::Sha1); + QString sid = getSidAsString(); + + if (sid.isEmpty()) { + // Something went wrong with the secure token API. Fall back: + id = params.providerName + QLatin1Char('_') + QString::fromUtf8(hash.toBase64()); + } else { + // The format is described in: https://learn.microsoft.com/en-us/uwp/api/windows.storage.provider.storageprovidersyncrootinfo.id?view=winrt-22621#windows-storage-provider-storageprovidersyncrootinfo-id + id = QStringLiteral("%1!%2!%3").arg(params.providerName, sid, QString::fromUtf8(hash.toBase64())); + } + + return id.toStdWString(); + }(); + + AddFolderToSearchIndexer(syncRoot); + // + // First set up common policy information for both + // + using namespace winrt::Windows::Storage; + using namespace winrt::Windows::Storage::Provider; + StorageProviderSyncRootInfo providerInfo; + // The calls below are "putters". They set the values on the `providerInfo`, but they do no + // additional processing. The "getters" have the same method name, but take no parameters. + + // hydrate fully when hydration is requested, before handing data to the user - also means validation will always see the whole file + providerInfo.HydrationPolicy(StorageProviderHydrationPolicy::Full); + providerInfo.HydrationPolicyModifier( + StorageProviderHydrationPolicyModifier::ValidationRequired + | StorageProviderHydrationPolicyModifier::AutoDehydrationAllowed); + // the whole tree is always available, no dynamic ls queries + providerInfo.PopulationPolicy(StorageProviderPopulationPolicy::AlwaysFull); + + // maybe more? + providerInfo.InSyncPolicy(StorageProviderInSyncPolicy::FileLastWriteTime); + + // no hardlinks + providerInfo.HardlinkPolicy(StorageProviderHardlinkPolicy::None); + // + // Windows::Storage::Provider registration + // + + providerInfo.AllowPinning(true); + + // It's unclear what Context is for + Streams::DataWriter contextWriter; + contextWriter.WriteString(params.providerName.toStdWString()); + providerInfo.Context(contextWriter.DetachBuffer()); + + const auto providerNameW = [params] { + QString out = params.multipleAccountsRegistered ? tr("%1 - %2").arg(params.providerDisplayName, params.account->displayNameWithHost()) + : params.providerDisplayName; + if (params.remotePath != QLatin1Char('/')) { + out = tr("%1 - %2").arg(out, params.remotePath); + } + return out.toStdWString(); + }(); + const auto providerVersionW = params.providerVersion.toString().toStdWString(); + providerInfo.DisplayNameResource(providerNameW.c_str()); + providerInfo.Version(providerVersionW.c_str()); + + // A path to an icon resource for the custom state of a file or folder. + const auto applicationPathW = QCoreApplication::instance()->applicationFilePath().toStdWString(); + providerInfo.IconResource(applicationPathW.c_str()); + + // An identifier for the sync root. + providerInfo.Id(_registrationId.c_str()); + + // The protection mode of the sync root registration. + providerInfo.ProtectionMode(StorageProviderProtectionMode::Unknown); + + // GUID that represents the ID of the storage provider. + // This is a randomly generated guid. + // Disabled because using the ProviderId getter/setter causes crashes +// providerInfo.ProviderId(winrt::guid( +// 0x3210d540, 0xaa0f, 0x428b, {0x91, 0xd2, 0x04, 0xcf, 0x37, 0x9c, 0xea, 0xe8})); + + // A Uri to a cloud storage recycle bin. + //providerInfo.RecycleBinUri(L"recycle bin"); + + providerInfo.ShowSiblingsAsGroup(true); + + // Prepage the key from the shell property store we'll use to determine + // availability. + PROPERTYKEY key; + auto hr = PSGetPropertyKeyFromName(L"System.StorageProviderState", &key); + if (FAILED(hr)) { + Q_EMIT q->error(tr("Could not find StorageProviderState property %1").arg(Utility::formatWinError(hr))); + return; + } else { + storageProviderStateKey = std::make_unique(key); + } + + using namespace winrt::Windows::Foundation; + auto getfolderop = StorageFolder::GetFolderFromPathAsync(syncRootW); + _registrationState = RegistrationState::Registering; + getfolderop.Completed([providerInfo, syncRoot, syncRootW, params, this](const IAsyncOperation &result, AsyncStatus status) { + QScopeGuard markRegistrationEnded([this, syncRoot]() { _registrationState = RegistrationState::FinishedRegistration; }); + + if (status != AsyncStatus::Completed) { + qCWarning(lcVfs) << "Could not find StorageFolder for" << syncRoot << "error:" << Utility::formatWinError(result.ErrorCode()); + Q_EMIT q->error(tr("Could not find StorageFolder for %1 error: %2").arg(syncRoot, Utility::formatWinError(result.ErrorCode()))); + return; + } + + StorageFolder f(nullptr); + try { + f = result.GetResults(); + providerInfo.Path(f); + } catch (winrt::hresult_error const& ex) { + qCWarning(lcVfs) << "Could not retrieve StorageFolder for" << syncRoot << "error:" << ex.code() << hstringToQString(ex.message()); + Q_EMIT q->error(tr("Could not retrieve StorageFolder for %1 %2 (0x%3)").arg(syncRoot, hstringToQString(ex.message()), QString::number(ex.code(), 16))); + return; + } + + try { + auto previousInfo = StorageProviderSyncRootManager::GetSyncRootInformationForFolder(f); + const QString oldId = hstringToQString(previousInfo.Id()); + if (!oldId.isEmpty()) { + const QString context = [&] { + auto cxt = previousInfo.Context(); + if (cxt) { + return QString::fromUtf8(reinterpret_cast(cxt.data()), cxt.Length()); + } + return QString {}; + }(); + + qCInfo(lcVfs) << "Found a registered sync root for" << QJsonObject { // + { QStringLiteral("SyncRoot"), syncRoot }, { QStringLiteral("Id"), oldId }, // + { QStringLiteral("Path"), hstringToQString(previousInfo.Path().Path()) }, // + { QStringLiteral("DisplayName"), hstringToQString(previousInfo.DisplayNameResource()) }, // + { QStringLiteral("Version"), hstringToQString(previousInfo.Version()) }, // + { QStringLiteral("Context"), context } + }; + if (providerInfo.Id() != previousInfo.Id()) { + qCInfo(lcVfs) << "New id" << hstringToQString(providerInfo.Id()) << "Old id" << oldId; + // old setups used owncloud for every branding + if (oldId.startsWith(QLatin1String("owncloud_")) || oldId.startsWith(params.providerName)) { + if (previousInfo.Path().Path() != providerInfo.Path().Path()) { + qCWarning(lcVfs) << "Path did not match" << hstringToQString(previousInfo.Path().Path()) << "!=" << hstringToQString(providerInfo.Path().Path()); + Q_EMIT q->error(tr("%1 is managed by another sync client").arg(hstringToQString(previousInfo.Path().Path()))); + return; + } + } else { + qCWarning(lcVfs) << "Id did not match" << hstringToQString(previousInfo.Id()) << "!=" << oldId; + Q_EMIT q->error(tr("The folder is used by a different client: %1").arg(hstringToQString(previousInfo.DisplayNameResource()))); + return; + } + _registrationId = previousInfo.Id(); + providerInfo.Id(previousInfo.Id()); + qCInfo(lcVfs) << "Keeping legacy id" << _registrationId; + } + const QString oldVersion = hstringToQString(previousInfo.Version()); + if (QVersionNumber::fromString(oldVersion) > params.providerVersion) { + qCWarning(lcVfs) << "Downgrading the folder from" << oldVersion << "to" << params.providerVersion << "is not supported"; + Q_EMIT q->error(tr("Downgrading the folder from %1 to %2 is not supported").arg(oldVersion, params.providerVersion.toString())); + return; + } + } + } catch (winrt::hresult_error const& ex) { + qCDebug(lcVfs) << "Could not check for previous StorageFolder for" << syncRoot << ":" << ex.code() << hstringToQString(ex.message()); + } + + try { + QMutexLocker registrationLock(®istrationMutex); + StorageProviderSyncRootManager::Register(providerInfo); + } catch (winrt::hresult_error const& ex) { + qCWarning(lcVfs) << "Error registering StorageProvider for" << params.filesystemPath << QString::number(ex.code()) << hstringToQString(ex.message()); + Q_EMIT q->error(tr("Error registering StorageProvider for %1: %2 (0x%3)").arg(params.filesystemPath, hstringToQString(ex.message()), QString::number(ex.code(), 16))); + return; + } + + // + // Connect to the sync root: Must be done before the process can interact with it + // also registers callbacks + // + // clang-format off + const CF_CALLBACK_REGISTRATION callbacks[] = { + { CF_CALLBACK_TYPE_FETCH_DATA, &callbackFetchData }, + { CF_CALLBACK_TYPE_CANCEL_FETCH_DATA, &callbackCancelFetchData }, + { CF_CALLBACK_TYPE_VALIDATE_DATA, &callbackValidateData }, + { CF_CALLBACK_TYPE_NOTIFY_DEHYDRATE, &callbackNotifyDehydrate }, + { CF_CALLBACK_TYPE_NOTIFY_DEHYDRATE_COMPLETION, &callbackNotifyDehydrateCompletion }, + // If we supported on-demand directory population we'd need to properly implement these: + // at the moment we generate the full directory tree + { CF_CALLBACK_TYPE_FETCH_PLACEHOLDERS, &callbackFetchPlaceholders }, + { CF_CALLBACK_TYPE_CANCEL_FETCH_PLACEHOLDERS, &callbackCancelFetchPlaceholders }, + { CF_CALLBACK_TYPE_NOTIFY_RENAME, &callbackRename }, + // There are more interesting callbacks for delete, open, close + CF_CALLBACK_REGISTRATION_END + }; + // clang-format on + + HRESULT ok; + { + QMutexLocker registrationLock(®istrationMutex); + ok = CfConnectSyncRoot(syncRootW.c_str(), callbacks, + this, // use VfsWinPrivate as callback context + CF_CONNECT_FLAG_BLOCK_SELF_IMPLICIT_HYDRATION // don't let antivirus implicitly hydrate (or something, a bit unclear) + | CF_CONNECT_FLAG_REQUIRE_FULL_FILE_PATH, // request that callbacks get the full path + &_connectionKey); + } + if (FAILED(ok)) { + Q_EMIT q->error(tr("Unable to connect sync root: %1 error: %2").arg(syncRoot, Utility::formatWinError(ok))); + return; + } + // We have not discovered what the status ends up being used for + //CfUpdateSyncProviderStatus(d->_connectionKey, CF_PROVIDER_STATUS_IDLE); + if (addToSearchIndexerThread) { + addToSearchIndexerThread->wait(); + } + Q_EMIT q->started(); + }); +} + +// If the local (client) folder where the cloud file placeholders are created +// is not under the User folder (i.e. Documents, Photos, etc), then it is required +// to add the folder to the Search Indexer. This is because the properties for +// the cloud file state/progress are cached in the indexer, and if the folder isn't +// indexed, attempts to get the properties on items will not return the expected values. +void VfsWinPrivate::AddFolderToSearchIndexer(const QString& folder) +{ + class Worker : public QThread + { + public: + Worker(const QString &folder) + : _folder(folder) + { + } + + void run() override + { + const std::wstring url(convertFullNativePathToFullyDecodedUrlW(_folder)); + + winrt::com_ptr searchCrawlScopeManager = getSearchCrawlScopeManager(); + + if (searchCrawlScopeManager) { + try { + winrt::check_hresult(searchCrawlScopeManager->AddDefaultScopeRule(url.data(), TRUE, FOLLOW_FLAGS::FF_INDEXCOMPLEXURLS)); + winrt::check_hresult(searchCrawlScopeManager->SaveAll()); + + qCDebug(lcVfs) << "Successfully called AddFolderToSearchIndexer on" << url; + } catch (...) { + // winrt::to_hresult() will eat the exception if it is a result of winrt::check_hresult, + // otherwise the exception will get rethrown and this method will crash out as it should + qCWarning(lcVfs) << "Failed on call to AddFolderToSearchIndexer for" << url << " with" << Utility::formatWinError(winrt::to_hresult()); + } + } + } + + private: + QString _folder; + }; + + addToSearchIndexerThread.reset(new Worker(folder)); + addToSearchIndexerThread->start(); +} + +// Note: on Windows this is done asynchornously. +void VfsWin::startImpl(const VfsSetupParams ¶ms) +{ + Q_D(VfsWin); + d->registerFolder(params); +} + +void VfsWin::stop() +{ + Q_D(VfsWin); + + // The last bit of registering a folder is done asynchronously. This might still + // be pending or running, so make sure it has ended. + // todo: #30 + QEventLoop wait; + while (d->_registrationState == VfsWinPrivate::RegistrationState::Registering) { + wait.processEvents(QEventLoop::AllEvents, 100); + } + + QMutexLocker registrationLock(&d->registrationMutex); + CfDisconnectSyncRoot(d->_connectionKey); +} + +void VfsWin::unregisterFolder() +{ + Q_D(VfsWin); + + if (d->_registrationState == VfsWinPrivate::Unregistered) + return; + + // First step: remove the folder from the search index + const std::wstring url(convertFullNativePathToFullyDecodedUrlW(d->syncRootPath())); + winrt::com_ptr searchCrawlScopeManager = getSearchCrawlScopeManager(); + + if (searchCrawlScopeManager) { + try { + winrt::check_hresult(searchCrawlScopeManager->RemoveDefaultScopeRule(url.data())); + } catch (...) { + qCWarning(lcVfs) << "Failed to remove DefaultSearchScopeRule for" << url << "with" << Utility::formatWinError(winrt::to_hresult()); + } + } else { + qCWarning(lcVfs) << "Unable to get the SearchCrawlScopeManager to remove the path" << url; + } + + // Final step: unregister the path. We're now completely done with it. + using SyncRootManager = winrt::Windows::Storage::Provider::StorageProviderSyncRootManager; + try { + QMutexLocker registrationLock(&d->registrationMutex); + SyncRootManager::Unregister(d->_registrationId); + } catch (winrt::hresult_error const &ex) { + qCWarning(lcVfs) << "Could not Unregister() sync root:" << hstringToQString(ex.message()); + } +} + +OCC::Result VfsWin::updateMetadata(const SyncFileItem &item, const QString &filePath, const QString &replacesFile) +{ + { + const auto result = convertToPlaceholder(filePath, item, replacesFile); + if (!result || result.get() != OCC::Vfs::ConvertToPlaceholderResult::Ok) { + return result; + } + } + // File system metadata to be updated for the placeholder. Values of 0 for the metadata indicate there are no updates. + CF_FS_METADATA metadata = {}; + if (item._direction == SyncFileItem::Down) { + metadata.FileSize.QuadPart = item._size; + Utility::UnixTimeToLargeIntegerFiletime(item._modtime, &metadata.BasicInfo.ChangeTime); + metadata.BasicInfo.LastWriteTime = metadata.BasicInfo.ChangeTime; + metadata.BasicInfo.LastAccessTime = metadata.BasicInfo.ChangeTime; + + if (!item.isDirectory()) { + if (!item._remotePerm.isNull() && !item._remotePerm.hasPermission(RemotePermissions::CanWrite)) { + CF_FS_METADATA metadataOld = {}; + auto result = getFileMetadata(filePath, &metadataOld.BasicInfo); + if (!result) { + return OCC::Utility::formatWinError(result.error()); + } + metadata.BasicInfo.FileAttributes = metadataOld.BasicInfo.FileAttributes & FILE_ATTRIBUTE_READONLY; + } + } + } + // get handle + auto fileHandle = getFileHandle(filePath, WRITE_DAC); + if (!fileHandle) { + return OCC::Utility::formatWinError(fileHandle.error()); + } + + CF_UPDATE_FLAGS flags = CF_UPDATE_FLAG_NONE; + // dehydrate the file and mark it as done + if (!item.isDirectory() && item._type == ItemTypeVirtualFileDehydration) { + flags |= CF_UPDATE_FLAG_MARK_IN_SYNC | CF_UPDATE_FLAG_DEHYDRATE; + } + + const auto ok = CfUpdatePlaceholder( + *fileHandle, &metadata, + item._fileId.constData(), static_cast(item._fileId.size()), // fileid + nullptr, 0, // no dehydration + flags, + nullptr, // no usn + nullptr); // no overlapped + + if (FAILED(ok)) { + const auto error = Utility::formatWinError(ok); + qCWarning(lcVfs) << Q_FUNC_INFO << filePath << error; + return error; + } + return ConvertToPlaceholderResult::Ok; +} + +Result VfsWinPrivate::createPlaceholderInternal(const SyncFileItem &item, const FILE_BASIC_INFO &basicInfo) +{ + const auto path = std::filesystem::path(syncRootPath().toStdWString()) / item._file.toStdWString(); + const auto name = path.filename().wstring(); + const auto dirPath = path.parent_path().wstring(); + + CF_PLACEHOLDER_CREATE_INFO placeholder = {}; + placeholder.RelativeFileName = name.data(); + placeholder.FsMetadata.FileSize.QuadPart = static_cast(item._size); + placeholder.FsMetadata.BasicInfo = basicInfo; + placeholder.FileIdentity = item._fileId.constData(); + placeholder.FileIdentityLength = static_cast(item._fileId.size()); + placeholder.Flags = CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC; + // Result and CreateUsn are filled later + + DWORD nProcessed = 0; + HRESULT ok = CfCreatePlaceholders( + dirPath.data(), + &placeholder, + 1, + CF_CREATE_FLAG_NONE, + &nProcessed); + if (FAILED(ok)) { + const auto error = QStringLiteral("CfCreatePlaceholders error %1 : %2 for %3").arg(Utility::formatWinError(ok), Utility::formatWinError(placeholder.Result), item._file); + qCWarning(lcVfs) << error; + return error; + } + + // It's unclear how the PINNED/UNPINNED file attribute in FsMetadata.BasicInfo + // interacts with the pin state that is inherited through the parent folder for + // new files. + // Since it never makes sense for a placeholder to inherit the PINNED attribute + // from its parent we remove it here if necessary. Inheriting UNSPECIFIED or + // UNPINNED makes sense. + FILE_BASIC_INFO fileInfo; + if (getFileMetadata(q->params().filesystemPath + item._file, &fileInfo)) { + if (fileInfo.FileAttributes & FILE_ATTRIBUTE_PINNED) { + if (!q->setPinState(item._file, PinState::Unspecified)) { + return QStringLiteral("Failed to set pin state"); + } + } + } + return {}; +} + +Result VfsWin::createPlaceholder(const SyncFileItem &item) +{ + Q_D(VfsWin); + + auto basicInfo = FILE_BASIC_INFO(); + basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; + Utility::UnixTimeToLargeIntegerFiletime(item._modtime, &basicInfo.ChangeTime); + basicInfo.LastAccessTime = basicInfo.ChangeTime; + basicInfo.LastWriteTime = basicInfo.ChangeTime; + basicInfo.CreationTime = basicInfo.ChangeTime; + + return d->createPlaceholderInternal(item, basicInfo); +} + +bool VfsWin::needsMetadataUpdate(const SyncFileItem &item) +{ + QString fsPath = params().filesystemPath + item._file; + auto handle = getFileHandle(fsPath, 0); + if (!handle) + return false; + return !getInfo(*handle); +} + +bool VfsWin::isDehydratedPlaceholder(const QString &filePath) +{ + auto handle = getFileHandle(filePath, 0); + if (!handle) { + return false; + } + FILE_ATTRIBUTE_TAG_INFO info = {}; + if (!GetFileInformationByHandleEx(*handle, FileAttributeTagInfo, &info, sizeof(info))) { + return false; + } + CF_PLACEHOLDER_STATE state = CfGetPlaceholderStateFromAttributeTag(info.FileAttributes, info.ReparseTag); + if (state == CF_PLACEHOLDER_STATE_NO_STATES) { + // This is not a placeholder + return false; + } + // no partial data, for us that's a download in progress + return state & (CF_PLACEHOLDER_STATE_PARTIAL | CF_PLACEHOLDER_STATE_PARTIALLY_ON_DISK); +} + +bool VfsWin::statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) +{ + auto ffd = reinterpret_cast(stat_data); + + // Directories always go though as directories, even if they are placeholder-directories + if (ffd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + stat->type = ItemTypeDirectory; + return true; + } + + const CF_PLACEHOLDER_STATE placeholder = CfGetPlaceholderStateFromFindData(ffd); + const bool pinBit = ffd->dwFileAttributes & FILE_ATTRIBUTE_PINNED; + const bool unpinBit = ffd->dwFileAttributes & FILE_ATTRIBUTE_UNPINNED; + if (placeholder & CF_PLACEHOLDER_STATE_PLACEHOLDER) { + if (placeholder & CF_PLACEHOLDER_STATE_PARTIAL) { + if (pinBit && !unpinBit) { + stat->type = ItemTypeVirtualFileDownload; + } else { + stat->type = ItemTypeVirtualFile; + } + } else { + if (unpinBit && !pinBit) { + stat->type = ItemTypeVirtualFileDehydration; + } else { + stat->type = ItemTypeFile; + } + } + return true; + } + return false; +} + +bool VfsWin::setPinState(const QString &relFilePath, PinState state) +{ + const QString fsPath = params().filesystemPath + relFilePath; + + auto handle = getFileHandle(fsPath, WRITE_DAC); + if (!handle) { + if (state == PinState::Inherited && (handle.error() == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) || handle.error() == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))) { + // PropagateLocalRename calls the function for the no longer exisitng file path + return true; + } + qCWarning(lcVfs) << Q_FUNC_INFO << ": could not make handle for" << fsPath << Utility::formatWinError(handle.error()); + return false; + } + + CF_PIN_STATE cfState = CF_PIN_STATE_UNSPECIFIED; + switch (state) { + case PinState::Inherited: + cfState = CF_PIN_STATE_INHERIT; + break; + case PinState::AlwaysLocal: + cfState = CF_PIN_STATE_PINNED; + break; + case PinState::OnlineOnly: + cfState = CF_PIN_STATE_UNPINNED; + break; + case PinState::Unspecified: + cfState = CF_PIN_STATE_UNSPECIFIED; + break; + } + + const HRESULT ok = CfSetPinState(*handle, cfState, CF_SET_PIN_FLAG_RECURSE, nullptr); + if (FAILED(ok)) { + qCWarning(lcVfs) << Q_FUNC_INFO << ": could not set pin state on" << fsPath << Utility::formatWinError(ok); + return false; + } + return true; +} + +Optional VfsWin::pinState(const QString &relFilePath) +{ + QString fsPath = params().filesystemPath + relFilePath; + + // TODO: These functions need tests + + FILE_BASIC_INFO fileInfo; + if (!getFileMetadata(fsPath, &fileInfo)) { + return {}; + } + + bool pinBit = fileInfo.FileAttributes & FILE_ATTRIBUTE_PINNED; + bool unpinBit = fileInfo.FileAttributes & FILE_ATTRIBUTE_UNPINNED; + if (pinBit && !unpinBit) + return PinState::AlwaysLocal; + if (unpinBit && !pinBit) + return PinState::OnlineOnly; + return PinState::Unspecified; +} + +Vfs::AvailabilityResult VfsWin::availability(const QString &folderPath) +{ + Q_D(VfsWin); + + auto hydrationStatus = params().journal->hasHydratedOrDehydratedFiles(folderPath.toUtf8()); + if (!hydrationStatus) + return AvailabilityError::DbError; + if (hydrationStatus->hasDehydrated) { + if (hydrationStatus->hasHydrated) { + return VfsItemAvailability::Mixed; + } else { + // We don't have the info needed to ever mark something as OnlineOnly + return VfsItemAvailability::AllDehydrated; + } + } + if (!hydrationStatus->hasHydrated) + return AvailabilityError::NoSuchItem; + + // Determine whether it's recursively pinned or not. + // To do that, we query the shell property system, which has an indexer to determine + // that fact for the icons that the Explorer shows. + + if (!d->storageProviderStateKey) { + // If the key couldn't be retrieved there already was a warning during setup. + return VfsItemAvailability::AllHydrated; + } + + QString fsPath = params().filesystemPath + folderPath; + // WARNING: The property system doesn't like the new paths that + // FileSystem::longWinPath() would generate + auto nFsPath = QDir::toNativeSeparators(fsPath).toStdWString(); + + // Read the combined availability property from the shell property model + // First, get a property store for the item + IPropertyStore *pps = nullptr; + auto hr = SHGetPropertyStoreFromParsingName( + nFsPath.c_str(), NULL, GPS_BESTEFFORT | GPS_DELAYCREATION, IID_PPV_ARGS(&pps)); + if (FAILED(hr)) { + qCWarning(lcVfs) << "Could not create property store for item" + << fsPath << Utility::formatWinError(hr); + return VfsItemAvailability::AllHydrated; + } + auto releasePps = qScopeGuard([&] { pps->Release(); }); + + // And then read and interpret the relevant property + PROPVARIANT value = {}; + hr = pps->GetValue(*d->storageProviderStateKey, &value); + if (FAILED(hr)) { + qCWarning(lcVfs) << "Could not access StorageProviderState property" + << fsPath << Utility::formatWinError(hr); + return VfsItemAvailability::AllHydrated; + } + if (value.vt == VT_UI4) { + enum WinShellAvailabilities : unsigned short { + WinShellAvailableLocally = 2, + WinShellAlwaysAvailableLocally = 3, + }; + + if (value.uiVal == WinShellAlwaysAvailableLocally) + return VfsItemAvailability::AlwaysLocal; + } else if (value.vt != VT_EMPTY) { + qCWarning(lcVfs) << "Unexpected StorageProviderState property type" + << fsPath << value.vt; + } + return VfsItemAvailability::AllHydrated; +} + +static Result getBasicPlaceholderInfo(const QString &systemFileName) +{ + auto handle = getFileHandle(systemFileName, 0); + if (!handle) { + return handle.error(); + } + + return getInfo(*handle); +} + +void VfsWin::onFileStatusChanged(const QString &systemFileName, SyncFileStatus status) +{ + auto placeholderInfo = getBasicPlaceholderInfo(systemFileName); + if (!placeholderInfo) { + // This slot will often be called for nonexistant files, that's not an error. + if (placeholderInfo.error() == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || placeholderInfo.error() == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) { + return; + } else if (placeholderInfo.error() != HRESULT_FROM_WIN32(ERROR_NOT_A_CLOUD_FILE)) { + qCWarning(lcVfs) << Q_FUNC_INFO << ": could not retrieve placeholder info for" << systemFileName + << Utility::formatWinError(placeholderInfo.error()); + } + return; + } + + // Check if something changed + const bool isExcluded = status.tag() == SyncFileStatus::StatusExcluded; + const bool wasExcluded = placeholderInfo->PinState == CF_PIN_STATE_EXCLUDED; + const bool isInSync = status.tag() == SyncFileStatus::StatusUpToDate; + const bool wasInSync = placeholderInfo->InSyncState == CF_IN_SYNC_STATE_IN_SYNC; + + if (isExcluded && wasExcluded) { + // Still excluded, so nothing changed + return; + } else if (!isExcluded && !wasExcluded && isInSync == wasInSync) { + // exclusion state the same (not excluded), and sync state the same, so nothing changed + return; + } + // else: either the exclusion state or the in-sync state is not the same. + + // We need to write new metadata, so open a writable handle to the file + auto handle = getFileHandle(systemFileName, WRITE_DAC); + if (!handle) { + qCCritical(lcVfs) << "Error setting file sync status: could not make writable handle for" << systemFileName << ":" + << Utility::formatWinError(handle.error()); + return; + } + + // Set inclusion state: + if (isExcluded != wasExcluded) { + const CF_PIN_STATE newPinState = isExcluded ? CF_PIN_STATE_EXCLUDED : CF_PIN_STATE_INHERIT; + const HRESULT res = CfSetPinState(*handle, newPinState, CF_SET_PIN_FLAG_RECURSE, nullptr); + if (FAILED(res)) { + qCCritical(lcVfs) << "Error setting sync exclusion state for" << systemFileName << ":" << Utility::formatWinError(res); + return; + } + } + + if (isExcluded) { + // We don't need to set the sync state for an excluded file + return; + } + + const CF_IN_SYNC_STATE newSyncState = isInSync ? CF_IN_SYNC_STATE_IN_SYNC : CF_IN_SYNC_STATE_NOT_IN_SYNC; + const HRESULT res = CfSetInSyncState(*handle, newSyncState, CF_SET_IN_SYNC_FLAG_NONE, nullptr); + if (FAILED(res)) { + qCCritical(lcVfs) << "Error setting file in-sync state for" << systemFileName << ":" << Utility::formatWinError(res); + } +} + + +QString CfCallbackInfoHelper::getFullPath(const CF_CALLBACK_INFO *info, const wchar_t *path) +{ + return QDir::cleanPath(QString::fromWCharArray(info->VolumeDosName) + QString::fromWCharArray(path)); +} + +QString CfCallbackInfoHelper::getFullNormalizedPath(const CF_CALLBACK_INFO *info) +{ + return CfCallbackInfoHelper::getFullPath(info, info->NormalizedPath); +} diff --git a/src/plugins/vfs/win/vfs_win.h b/src/plugins/vfs/win/vfs_win.h new file mode 100755 index 00000000000..e8bff4d6b8f --- /dev/null +++ b/src/plugins/vfs/win/vfs_win.h @@ -0,0 +1,61 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "common/vfs.h" +#include "common/plugin.h" + +namespace OCC { + +class VfsWinPrivate; + +class VfsWin : public Vfs +{ + Q_OBJECT + Q_DECLARE_PRIVATE(VfsWin) + const QScopedPointer d_ptr; + +public: + explicit VfsWin(QObject *parent); + ~VfsWin(); + + Mode mode() const override; + + void stop() override; + void unregisterFolder() override; + + [[nodiscard]] bool socketApiPinStateActionsShown() const override { return false; } + + [[nodiscard]] Result createPlaceholder(const SyncFileItem &item) override; + + /// Returns true for files that aren't placeholders yet + [[nodiscard]] bool needsMetadataUpdate(const SyncFileItem &item) override; + + [[nodiscard]] bool isDehydratedPlaceholder(const QString &filePath) override; + [[nodiscard]] bool statTypeVirtualFile(csync_file_stat_t *stat, void *stat_data) override; + + // Pin states exist as file attributes on disk + [[nodiscard]] bool setPinState(const QString &relFilePath, PinState state) override; + [[nodiscard]] Optional pinState(const QString &filePath) override; + + [[nodiscard]] AvailabilityResult availability(const QString &folderPath) override; + +public Q_SLOTS: + void onFileStatusChanged(const QString &systemFileName, SyncFileStatus status) override; + +protected: + [[nodiscard]] Result updateMetadata(const SyncFileItem &item, const QString &filePath, const QString &replacesFile = {}) override; + void startImpl(const VfsSetupParams ¶ms) override; +}; + + + +} // namespace OCC diff --git a/src/plugins/vfs/win/vfs_win_p.h b/src/plugins/vfs/win/vfs_win_p.h new file mode 100755 index 00000000000..4df8c93e319 --- /dev/null +++ b/src/plugins/vfs/win/vfs_win_p.h @@ -0,0 +1,143 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "vfs_win.h" +#include "utility.h" +#include "hydrationdevice.h" + +#include "propagatedownload.h" +#include "common/syncjournalfilerecord.h" + +#include + +namespace OCC { + +/** Information that is passed between fetch-data and validate-data callbacks + * + * For example, when the validation callback is triggered, we need to know the + * checksum header that was retrieved while downloading the file's data. + */ +class HydrationContext : public QObject +{ + Q_OBJECT +public: + HydrationContext(VfsWin *parent); + ~HydrationContext(); + + // TODO: prefix member vars + CfAPIUtil::CfOpdata opdata; + QPointer journal; + SyncJournalFileRecord record; + QString filesystemPath; + QByteArray transmissionChecksumHeader; + PlaceholderHydrationDevice *hydrationDevice = nullptr; + QPointer downloadJob; + bool isDownloadDone = false; + bool isAborted = false; + + void abort() { + isAborted = true; + if (downloadJob && downloadJob->reply()) { + downloadJob->reply()->abort(); + } + } + + VfsWin *pluginInstance() const + { + return qobject_cast(parent()); + } + +public Q_SLOTS: + /// Takes care of setting record._checksumHeader, then proceeds to validateTransmissionChecksum() + void ensureContentChecksum(quint64 fileSize); + + /// Validates the transmission checksum and wraps up + void validateTransmissionChecksum(quint64 fileSize); + + /// Extract important data from download job response, then signals downloadDone() + void downloadJobDone(); + + /// Send an error code to Windows + void transferDataError(NTSTATUS status); + +Q_SIGNALS: + void downloadDone(); +}; + +using TransferKey = quint64; + +// This is also used as the call context when registering a sync root +class VfsWinPrivate : public QObject +{ + Q_OBJECT + +public: + VfsWinPrivate(VfsWin *q) + : q(q) + , _registrationState(RegistrationState::Unregistered) + { + } + + enum RegistrationState { Unregistered, Registering, FinishedRegistration }; + + VfsWin *q; + QAtomicInt _registrationState; + CF_CONNECTION_KEY _connectionKey; + static QMutex registrationMutex; + + // System.StorageProviderState key + std::unique_ptr storageProviderStateKey; + + std::wstring _registrationId; + + QScopedPointer addToSearchIndexerThread; + + HydrationContext *hydrationContext(const TransferKey key) + { + return _hydrations.value(key); + } + + void setHydrationContext(const TransferKey key, HydrationContext *context) + { + _hydrations.insert(key, context); + connect(context, &HydrationContext::destroyed, this, [key, this] { + _hydrations.remove(key); + }); + } + + void registerFolder(const VfsSetupParams ¶ms); + + void AddFolderToSearchIndexer(const QString& folder); + + // The path used for sync root registration + QString syncRootPath() const; + + [[nodiscard]] Result createPlaceholderInternal(const SyncFileItem &item, const FILE_BASIC_INFO &basicInfo); + + // The following are triggered from callbacks + void startFetchData(CfAPIUtil::CfOpdata opdata, const QString &targetPath, quint64 fetchStart, quint64 fetchEnd, + const QByteArray &fileId); + void cancelFetchData(TransferKey transferKey, const QString &path); + void startValidateData(CfAPIUtil::CfOpdata opdata, const QString &path, quint64 offset, quint64 length, + quint64 fileSize); + +private: + // Multiple hydrations can run at the same time and need to exchange data + // between the fetch-data phase and the validate-data phase. They store their + // info here. Must only be accessed from the gui thread! + QHash _hydrations; +}; + +namespace CfCallbackInfoHelper { + + QString getFullPath(const CF_CALLBACK_INFO *info, const wchar_t *path); + QString getFullNormalizedPath(const CF_CALLBACK_INFO *info); + +} // CfCallbackInfoHelper namespace +} // OCC namespace diff --git a/src/plugins/vfs/win/vfs_win_plugin_factory.cpp b/src/plugins/vfs/win/vfs_win_plugin_factory.cpp new file mode 100644 index 00000000000..02a4466c2bf --- /dev/null +++ b/src/plugins/vfs/win/vfs_win_plugin_factory.cpp @@ -0,0 +1,24 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "vfs_win.h" +#include "common/vfs.h" + +#include + + +namespace OCC { + +class SuffixVfsPluginFactory : public QObject, public DefaultPluginFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.owncloud.PluginFactory" FILE "vfspluginmetadata.json") + Q_INTERFACES(OCC::PluginFactory) +}; + +} + +#include "vfs_win_plugin_factory.moc" diff --git a/src/resources/CMakeLists.txt b/src/resources/CMakeLists.txt new file mode 100644 index 00000000000..adbd58671ec --- /dev/null +++ b/src/resources/CMakeLists.txt @@ -0,0 +1,53 @@ +add_library(owncloudResources SHARED + client.qrc + core_theme.qrc + loadresources.cpp + resources.cpp + qmlresources.cpp + themewatcher.cpp + template.cpp +) + +generate_theme(owncloudResources OWNCLOUD_SIDEBAR_ICONS) + +# make them available to the whole project +set(OWNCLOUD_SIDEBAR_ICONS ${OWNCLOUD_SIDEBAR_ICONS} CACHE INTERNAL "Sidebar icons" FORCE) + +target_link_libraries(owncloudResources PUBLIC Qt::Core Qt::Gui Qt::Quick) +apply_common_target_settings(owncloudResources) +target_include_directories(owncloudResources PUBLIC $ $) +target_compile_definitions(owncloudResources PRIVATE APPLICATION_SHORTNAME="${APPLICATION_SHORTNAME}") + +set_target_properties(owncloudResources PROPERTIES + OUTPUT_NAME "ownCloudResources" + AUTOUIC ON + AUTORCC ON + VERSION ${MIRALL_VERSION} + SOVERSION ${MIRALL_SOVERSION} +) + +generate_export_header(owncloudResources + EXPORT_MACRO_NAME OWNCLOUDRESOURCES_EXPORT + EXPORT_FILE_NAME owncloudresources.h + STATIC_DEFINE OWNCLOUD_BUILT_AS_STATIC +) + +# Handle Translations, pick all client_* files from trans directory. +file(GLOB client_translations ${CMAKE_SOURCE_DIR}/translations/client_*.ts) +qt_add_lrelease(owncloudResources TS_FILES ${client_translations} QM_FILES_OUTPUT_VARIABLE client_compiled_translations) +add_resources_to_target( + TARGET owncloudResources + PREFIX translations + FILES "${client_compiled_translations}" +) + +ecm_add_qml_module(owncloudResources + URI org.ownCloud.resources + VERSION 1.0 + NAMESPACE OCC + GENERATE_PLUGIN_SOURCE +) + +ecm_finalize_qml_module(owncloudResources DESTINATION ${KDE_INSTALL_QMLDIR}) + +install(TARGETS owncloudResources EXPORT ${APPLICATION_SHORTNAME}Config ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/resources/client.qrc b/src/resources/client.qrc new file mode 100644 index 00000000000..8ac01f6d17d --- /dev/null +++ b/src/resources/client.qrc @@ -0,0 +1,32 @@ + + + font-awesome/folder-solid.svg + font-awesome/cog-solid.svg + font-awesome/ellipsis-h-solid.svg + font-awesome/trash-solid.svg + font-awesome/bell-solid.svg + font-awesome/power-off-solid.svg + font-awesome/plus-solid.svg + font-awesome/exclamation-triangle-solid.svg + font-awesome/ban-solid.svg + font-awesome/check-solid.svg + font-awesome/step-forward-solid.svg + font-awesome/copy-solid.svg + font-awesome/arrow-up-right-from-square-solid.svg + font-awesome/undo-solid.svg + wizard/style.qss + oauth/success.html + oauth/error.html + remixicon/space.svg + font-awesome/states/error.svg + font-awesome/states/information.svg + font-awesome/states/offline.svg + font-awesome/states/ok.svg + font-awesome/states/pause.svg + font-awesome/states/sync.svg + font-awesome/states/warning.svg + font-awesome/right-left-solid.svg + font-awesome/folder-solid-small.svg + otherCoreIcons/defaultSpaceImage.svg + + diff --git a/core_theme.qrc b/src/resources/core_theme.qrc similarity index 96% rename from core_theme.qrc rename to src/resources/core_theme.qrc index 1137eaeed63..0a3b8e95725 100644 --- a/core_theme.qrc +++ b/src/resources/core_theme.qrc @@ -21,6 +21,8 @@ theme/universal/oc-image-about.svg theme/universal/owncloud-icon.svg theme/universal/wizard_logo.svg + theme/universal/wizard_logo_dark.svg + theme/universal/wizard_footer_logo.svg theme/white/ui-dark-plain-monochrom-state-checkmark.svg theme/white/ui-dark-plain-monochrom-state-error.svg theme/white/ui-dark-plain-monochrom-state-info.svg diff --git a/resources/font-awesome/LICENSE.txt b/src/resources/font-awesome/LICENSE.txt similarity index 100% rename from resources/font-awesome/LICENSE.txt rename to src/resources/font-awesome/LICENSE.txt diff --git a/src/resources/font-awesome/arrow-up-right-from-square-solid.svg b/src/resources/font-awesome/arrow-up-right-from-square-solid.svg new file mode 100644 index 00000000000..af3ccdc526b --- /dev/null +++ b/src/resources/font-awesome/arrow-up-right-from-square-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/ban-solid.svg b/src/resources/font-awesome/ban-solid.svg new file mode 100644 index 00000000000..7bddc562f99 --- /dev/null +++ b/src/resources/font-awesome/ban-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/bell-solid.svg b/src/resources/font-awesome/bell-solid.svg new file mode 100644 index 00000000000..f2cec823728 --- /dev/null +++ b/src/resources/font-awesome/bell-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/check-solid.svg b/src/resources/font-awesome/check-solid.svg new file mode 100644 index 00000000000..17133fe5a40 --- /dev/null +++ b/src/resources/font-awesome/check-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/cog-solid.svg b/src/resources/font-awesome/cog-solid.svg new file mode 100644 index 00000000000..4ab7013e83e --- /dev/null +++ b/src/resources/font-awesome/cog-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/copy-solid.svg b/src/resources/font-awesome/copy-solid.svg new file mode 100644 index 00000000000..38e80f1259a --- /dev/null +++ b/src/resources/font-awesome/copy-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/ellipsis-h-solid.svg b/src/resources/font-awesome/ellipsis-h-solid.svg new file mode 100644 index 00000000000..a2657770402 --- /dev/null +++ b/src/resources/font-awesome/ellipsis-h-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/exclamation-triangle-solid.svg b/src/resources/font-awesome/exclamation-triangle-solid.svg new file mode 100644 index 00000000000..fec62065a7c --- /dev/null +++ b/src/resources/font-awesome/exclamation-triangle-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/folder-solid-small.svg b/src/resources/font-awesome/folder-solid-small.svg new file mode 100644 index 00000000000..d7d8251c11a --- /dev/null +++ b/src/resources/font-awesome/folder-solid-small.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/resources/font-awesome/folder-solid.svg b/src/resources/font-awesome/folder-solid.svg new file mode 100644 index 00000000000..71a2bcce8fe --- /dev/null +++ b/src/resources/font-awesome/folder-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/plus-solid.svg b/src/resources/font-awesome/plus-solid.svg new file mode 100644 index 00000000000..34a21f236a9 --- /dev/null +++ b/src/resources/font-awesome/plus-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/power-off-solid.svg b/src/resources/font-awesome/power-off-solid.svg new file mode 100644 index 00000000000..0a6cae87572 --- /dev/null +++ b/src/resources/font-awesome/power-off-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/right-left-solid.svg b/src/resources/font-awesome/right-left-solid.svg new file mode 100644 index 00000000000..217d9cdcb38 --- /dev/null +++ b/src/resources/font-awesome/right-left-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/states/error.svg b/src/resources/font-awesome/states/error.svg new file mode 100644 index 00000000000..b3d3df0f005 --- /dev/null +++ b/src/resources/font-awesome/states/error.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/resources/font-awesome/states/information.svg b/src/resources/font-awesome/states/information.svg new file mode 100644 index 00000000000..dcfaf8be37c --- /dev/null +++ b/src/resources/font-awesome/states/information.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/resources/font-awesome/states/offline.svg b/src/resources/font-awesome/states/offline.svg new file mode 100644 index 00000000000..abc02d581b9 --- /dev/null +++ b/src/resources/font-awesome/states/offline.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/states/ok.svg b/src/resources/font-awesome/states/ok.svg new file mode 100644 index 00000000000..bc0d701899b --- /dev/null +++ b/src/resources/font-awesome/states/ok.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/resources/font-awesome/states/pause.svg b/src/resources/font-awesome/states/pause.svg new file mode 100644 index 00000000000..102d4c1448a --- /dev/null +++ b/src/resources/font-awesome/states/pause.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/resources/font-awesome/states/sync.svg b/src/resources/font-awesome/states/sync.svg new file mode 100644 index 00000000000..0a24fec7b77 --- /dev/null +++ b/src/resources/font-awesome/states/sync.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/resources/font-awesome/states/warning.svg b/src/resources/font-awesome/states/warning.svg new file mode 100644 index 00000000000..fcd4818ae03 --- /dev/null +++ b/src/resources/font-awesome/states/warning.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/resources/font-awesome/step-forward-solid.svg b/src/resources/font-awesome/step-forward-solid.svg new file mode 100644 index 00000000000..44d065300a9 --- /dev/null +++ b/src/resources/font-awesome/step-forward-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/trash-solid.svg b/src/resources/font-awesome/trash-solid.svg new file mode 100644 index 00000000000..12bf3f484db --- /dev/null +++ b/src/resources/font-awesome/trash-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/font-awesome/undo-solid.svg b/src/resources/font-awesome/undo-solid.svg new file mode 100644 index 00000000000..77c9781c642 --- /dev/null +++ b/src/resources/font-awesome/undo-solid.svg @@ -0,0 +1 @@ + diff --git a/src/resources/loadresources.cpp b/src/resources/loadresources.cpp new file mode 100644 index 00000000000..978d8afc222 --- /dev/null +++ b/src/resources/loadresources.cpp @@ -0,0 +1,38 @@ +#include "loadresources.h" + +#include + +using namespace OCC; + +void static load_rc() +{ + Q_INIT_RESOURCE(owncloudResources_translations); + Q_INIT_RESOURCE(client); + Q_INIT_RESOURCE(core_theme); +#ifdef BRANDING_AVAILABLE + Q_INIT_RESOURCE(theme); +#endif +} + +void static unload_rc() +{ + Q_CLEANUP_RESOURCE(owncloudResources_translations); + Q_CLEANUP_RESOURCE(client); + Q_CLEANUP_RESOURCE(core_theme); +#ifdef BRANDING_AVAILABLE + Q_CLEANUP_RESOURCE(theme); +#endif +} + +ResourcesLoader::ResourcesLoader() +{ + // Q_INIT_RESOURCE must not be called in a namespace + ::load_rc(); +} + + +ResourcesLoader::~ResourcesLoader() +{ + // Q_CLEANUP_RESOURCE must not be called in a namespace + ::unload_rc(); +} \ No newline at end of file diff --git a/src/resources/loadresources.h b/src/resources/loadresources.h new file mode 100644 index 00000000000..30f0c64af60 --- /dev/null +++ b/src/resources/loadresources.h @@ -0,0 +1,12 @@ +#pragma once + +#include "owncloudresources.h" + +namespace OCC { +class OWNCLOUDRESOURCES_EXPORT ResourcesLoader +{ +public: + ResourcesLoader(); + ~ResourcesLoader(); +}; +} \ No newline at end of file diff --git a/src/resources/oauth/error.html b/src/resources/oauth/error.html new file mode 100644 index 00000000000..cd3840f16f7 --- /dev/null +++ b/src/resources/oauth/error.html @@ -0,0 +1,52 @@ + + + + + +@{TITLE} + + + + +
    +
    + +

    @{TITLE}

    +

    @{MESSAGE}

    +
    +
    + + diff --git a/src/resources/oauth/success.html b/src/resources/oauth/success.html new file mode 100644 index 00000000000..a08ec4f531b --- /dev/null +++ b/src/resources/oauth/success.html @@ -0,0 +1,52 @@ + + + + + +@{TITLE} + + + + +
    +
    + +

    @{TITLE}

    +

    @{MESSAGE}

    +
    +
    + + diff --git a/src/resources/otherCoreIcons/Frame 60x60 Square 30x30.svg b/src/resources/otherCoreIcons/Frame 60x60 Square 30x30.svg new file mode 100644 index 00000000000..dfd988425da --- /dev/null +++ b/src/resources/otherCoreIcons/Frame 60x60 Square 30x30.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/resources/otherCoreIcons/defaultSpaceImage.svg b/src/resources/otherCoreIcons/defaultSpaceImage.svg new file mode 100644 index 00000000000..85ea977fcc3 --- /dev/null +++ b/src/resources/otherCoreIcons/defaultSpaceImage.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/resources/qmlresources.cpp b/src/resources/qmlresources.cpp new file mode 100644 index 00000000000..245bebe0baa --- /dev/null +++ b/src/resources/qmlresources.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "resources/qmlresources.h" + +#include "resources/resources.h" + +namespace { +constexpr QSize minIconSize{16, 16}; +} + +using namespace OCC; +QUrl Resources::QMLResources::resourcePath2(const QString &provider, const QString &icon, bool enabled, const QVariantMap &properies) +{ + auto map = + QVariantMap{{QStringLiteral("enabled"), enabled}, {QStringLiteral("icon"), icon}, {QStringLiteral("systemtheme"), Resources::isUsingDarkTheme()}}; + map.insert(properies); + const auto data = QJsonDocument::fromVariant(map).toJson(); + return QUrl(QStringLiteral("image://%1/%2").arg(provider, QString::fromUtf8(data.toBase64()))); +} + +QUrl Resources::QMLResources::resourcePath(const QString &theme, const QString &icon, bool enabled) +{ + return resourcePath2(QStringLiteral("ownCloud"), icon, enabled, {{QStringLiteral("theme"), theme}}); +} + +Resources::QMLResources::Icon Resources::QMLResources::parseIcon(const QString &id) +{ + const auto data = QJsonDocument::fromJson(QByteArray::fromBase64(id.toUtf8())).object(); + return Icon{data.value(QLatin1String("theme")).toString(), data.value(QLatin1String("icon")).toString(), data.value(QLatin1String("enabled")).toBool()}; +} + +QPixmap Resources::pixmap(const QSize &requestedSize, const QIcon &icon, QIcon::Mode mode, QSize *outSize) +{ + Q_ASSERT(requestedSize.isValid()); + QSize actualSize = requestedSize.isValid() ? requestedSize : icon.availableSizes().constFirst(); + if (outSize) { + *outSize = actualSize; + } + return icon.pixmap(actualSize.expandedTo(minIconSize), mode); +} diff --git a/src/resources/qmlresources.h b/src/resources/qmlresources.h new file mode 100644 index 00000000000..1b5125d221b --- /dev/null +++ b/src/resources/qmlresources.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "resources/owncloudresources.h" + +#include +#include + +namespace OCC { +namespace Resources { + + class OWNCLOUDRESOURCES_EXPORT QMLResources : public QObject + { + Q_OBJECT + QML_SINGLETON + QML_ELEMENT + public: + using QObject::QObject; + struct Icon + { + QString theme; + QString iconName; + bool enabled; + }; + Q_INVOKABLE static QUrl resourcePath(const QString &theme, const QString &icon, bool enabled); + Q_INVOKABLE static QUrl resourcePath2(const QString &provider, const QString &icon, bool enabled, const QVariantMap &properies = {}); + + static Icon parseIcon(const QString &id); + }; + + QPixmap OWNCLOUDRESOURCES_EXPORT pixmap(const QSize &requestedSize, const QIcon &icon, QIcon::Mode mode, QSize *outSize); +} // Resources +} // OCC diff --git a/src/resources/remixicon/License.txt b/src/resources/remixicon/License.txt new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/src/resources/remixicon/License.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/resources/remixicon/space.svg b/src/resources/remixicon/space.svg new file mode 100644 index 00000000000..b1a5b26221f --- /dev/null +++ b/src/resources/remixicon/space.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/resources/resources.cpp b/src/resources/resources.cpp new file mode 100644 index 00000000000..30ccd0922c2 --- /dev/null +++ b/src/resources/resources.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "resources/resources.h" +#include "resources/qmlresources.h" +#include "resources/template.h" +#include "resources/themewatcher.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace OCC; +using namespace Resources; + +Q_LOGGING_CATEGORY(lcResources, "sync.resources", QtInfoMsg) + +namespace { +struct IconCache +{ + IconCache() + { + auto *watcher = new ThemeWatcher(qApp); + QObject::connect(watcher, &ThemeWatcher::themeChanged, [this]() { _cache.clear(); }); + + // this is the currently "correct" way to listen for system color changes - I'm leaving it out + // for the time being + // this generally works to clear the old icons, but places where the icon is already set, eg + // the toolbar actions and connection status, need to be reset to what is in the new cache. + // this will not work here, as we need to be sure the cache has been cleared *before* anyone + // asks for the icon again. Conceptually though, it's correct to do it this way. + // QObject::connect(qGuiApp->styleHints(), &QStyleHints::colorSchemeChanged, [this]() { _cache.clear(); }); + } + QMap _cache; +}; +Q_GLOBAL_STATIC(IconCache, iconCache) + +QString vanillaThemePath() +{ + return QStringLiteral(":/client/ownCloud/theme"); +} + +QString brandThemePath() +{ + return QStringLiteral(":/client/" APPLICATION_SHORTNAME "/theme"); +} + +QString darkTheme() +{ + return QStringLiteral("dark"); +} + +QString coloredTheme() +{ + return QStringLiteral("colored"); +} + +QString whiteTheme() +{ + return QStringLiteral("white"); +} + +bool hasTheme(IconType type, const QString &theme) +{ + // <, bool + // caches the availability of themes for branded and unbranded themes + static QMap, bool> _themeCache; + const auto key = qMakePair(type != IconType::VanillaIcon, theme); + auto it = _themeCache.constFind(key); + if (it == _themeCache.cend()) { + return _themeCache[key] = QFileInfo(QStringLiteral("%1/%2/").arg(type == IconType::VanillaIcon ? vanillaThemePath() : brandThemePath(), theme)).isDir(); + } + return it.value(); +} + +} + +bool OCC::Resources::hasDarkTheme() +{ + static bool _hasBrandedColored = hasTheme(IconType::BrandedIcon, coloredTheme()); + static bool _hasBrandedDark = hasTheme(IconType::BrandedIcon, darkTheme()); + return _hasBrandedColored == _hasBrandedDark; +} + +bool Resources::hasMonoTheme() +{ + // mono icons are only supported in vanilla and if a customer provides them + // no fallback to vanilla + return hasTheme(Resources::IconType::BrandedIcon, whiteTheme()); +} + +bool Resources::isVanillaTheme() +{ + return std::string_view(APPLICATION_SHORTNAME) == "ownCloud"; +} + +bool OCC::Resources::isUsingDarkTheme() +{ + static bool forceDark = qEnvironmentVariableIntValue("OWNCLOUD_FORCE_DARK_MODE") != 0; + return forceDark || QPalette().base().color().lightnessF() <= 0.5; +} + +QIcon OCC::Resources::getCoreIcon(const QString &iconName) +{ + if (iconName.isEmpty()) { + return {}; + } + QIcon &cached = iconCache->_cache[iconName]; // Take reference, this will also "set" the cache entry + if (cached.isNull()) { + const QString iconPath = QStringLiteral(":/client/resources/core/%1.svg").arg(iconName); + Q_ASSERT(QFileInfo::exists(iconPath)); + const QString color = isUsingDarkTheme() ? QStringLiteral("#ADACAB") : QStringLiteral("#435671"); + QByteArray data = Template::renderTemplateFromFile(iconPath, {{QStringLiteral("color"), color}}).toUtf8(); + QBuffer buffer(&data); + QImageReader iconReader(&buffer, "svg"); + return cached = QPixmap::fromImageReader(&iconReader); + } + return cached; + // " + // + // " +} + +/* + * helper to load a icon from either the icon theme the desktop provides or from + * the apps Qt resources. + */ +QIcon OCC::Resources::loadIcon(const QString &flavor, const QString &name, IconType iconType) +{ + // prevent recusion + const bool useCoreIcon = (iconType == IconType::VanillaIcon) || isVanillaTheme(); + const QString path = QStringLiteral("%1/%2/%3").arg(useCoreIcon ? vanillaThemePath() : brandThemePath(), flavor, name); + QIcon &cached = iconCache->_cache[path]; // Take reference, this will also "set" the cache entry + if (cached.isNull()) { + if (isVanillaTheme() && QIcon::hasThemeIcon(name)) { + // use from theme + return cached = QIcon::fromTheme(name); + } + const QString svg = QStringLiteral("%1.svg").arg(path); + if (QFile::exists(svg)) { + return cached = QIcon(svg); + } + + const QString png = QStringLiteral("%1.png").arg(path); + if (QFile::exists(png)) { + return cached = QIcon(png); + } + + const QList sizes{16, 22, 32, 48, 64, 128, 256, 512, 1024}; + QString previousIcon; + for (int size : sizes) { + QString pixmapName = QStringLiteral("%1-%2.png").arg(path, QString::number(size)); + if (QFile::exists(pixmapName)) { + previousIcon = pixmapName; + cached.addFile(pixmapName, {size, size}); + } else if (size >= 128) { + if (!previousIcon.isEmpty()) { + qCWarning(lcResources) << "Upscaling:" << previousIcon << "to" << size; + cached.addPixmap(QPixmap(previousIcon).scaled({size, size}, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + } + } + } + } + if (cached.isNull()) { + if (!useCoreIcon && iconType == IconType::BrandedIconWithFallbackToVanillaIcon) { + return loadIcon(flavor, name, IconType::VanillaIcon); + } + qCWarning(lcResources) << "Failed to locate the icon" << path; + } + return cached; +} + +QIcon OCC::Resources::themeIcon(const QString &name, IconType iconType) +{ + return loadIcon((Resources::isUsingDarkTheme() && hasDarkTheme()) ? darkTheme() : coloredTheme(), name, iconType); +} + +QIcon OCC::Resources::themeUniversalIcon(const QString &name, IconType iconType) +{ + return loadIcon(QStringLiteral("universal"), name, iconType); +} + +QIcon OCC::Resources::buildAvatar(const QString &initials, QUuid accountUid) +{ + QIcon &cached = iconCache->_cache[accountUid.toString()]; // Take reference, this will also "set" the cache entry + if (cached.isNull()) { + // for now we are going for a color scheme that mimics our other icons. + // the logic: + // it is extremely difficult to "pick" random colors that are going to look good and have + // sufficient contrast with dark and light mode on all platforms + // additionally, a random colorful icon looks a bit out of place given we have no other "custom" colors + // in the app outside of the account wizard - I think using the same gray we do for other icons is harmonious, at least + QColor badgeColor = isUsingDarkTheme() ? "#ADACAB" : "#435671"; + + QPalette pal = qGuiApp->palette(); + + // I really don't think this needs to be larger than 64x64 ever...let's see how it goes + QPixmap pix(64, 64); + pix.fill(Qt::transparent); + + QPainter painter; + painter.begin(&pix); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setBrush(badgeColor); + painter.setPen(Qt::NoPen); + painter.drawEllipse(pix.rect()); + + QFont font = painter.font(); + font.setPixelSize(32); + font.setBold(true); + painter.setFont(font); + // use base color to simulate transparent text that reveals what is behind it, as we have in other icons...ie the base color + painter.setPen(pal.color(QPalette::Base)); + painter.drawText(pix.rect(), Qt::AlignCenter, initials); + painter.end(); + cached = pix; + } + return cached; +} + + +// todo: all of this will die soon :) +CoreImageProvider::CoreImageProvider() + : QQuickImageProvider(QQuickImageProvider::Pixmap) +{ +} +QPixmap CoreImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + const auto qmlIcon = QMLResources::parseIcon(id); + + QIcon icon; + if (qmlIcon.theme == QLatin1String("core")) { + icon = getCoreIcon(qmlIcon.iconName); + } else if (qmlIcon.theme == QLatin1String("universal")) { + icon = themeUniversalIcon(qmlIcon.iconName); + } else { + icon = themeIcon(qmlIcon.iconName); + } + return Resources::pixmap(requestedSize, icon, qmlIcon.enabled ? QIcon::Normal : QIcon::Disabled, size); +} diff --git a/src/resources/resources.h b/src/resources/resources.h new file mode 100644 index 00000000000..31768f86381 --- /dev/null +++ b/src/resources/resources.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "resources/owncloudresources.h" + +#include +#include +#include + +namespace OCC::Resources { +Q_NAMESPACE +/** + * Wehther we allow a fallback to a vanilla icon + */ +enum class IconType { BrandedIcon, BrandedIconWithFallbackToVanillaIcon, VanillaIcon }; +Q_ENUM_NS(IconType); + + +/** + * + * @return Whether we are using the vanilla theme + */ +bool OWNCLOUDRESOURCES_EXPORT isVanillaTheme(); + +/** + * Whether use the dark icon theme + * The function also ensures the theme supports the dark theme + */ +bool OWNCLOUDRESOURCES_EXPORT isUsingDarkTheme(); + +bool OWNCLOUDRESOURCES_EXPORT hasDarkTheme(); + +/** Whether the theme provides monochrome tray icons + */ +bool OWNCLOUDRESOURCES_EXPORT hasMonoTheme(); + +QIcon OWNCLOUDRESOURCES_EXPORT getCoreIcon(const QString &icon_name); + +QIcon OWNCLOUDRESOURCES_EXPORT loadIcon(const QString &flavor, const QString &name, IconType iconType); +QIcon OWNCLOUDRESOURCES_EXPORT themeIcon(const QString &name, IconType iconType = IconType::BrandedIconWithFallbackToVanillaIcon); +QIcon OWNCLOUDRESOURCES_EXPORT buildAvatar(const QString &initials, QUuid accountUid); +/** + * Returns a universal (non color schema aware) icon. + */ +QIcon OWNCLOUDRESOURCES_EXPORT themeUniversalIcon(const QString &name, IconType iconType = IconType::BrandedIcon); + +class OWNCLOUDRESOURCES_EXPORT CoreImageProvider : public QQuickImageProvider +{ + Q_OBJECT +public: + CoreImageProvider(); + + QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; +}; +} diff --git a/src/resources/template.cpp b/src/resources/template.cpp new file mode 100644 index 00000000000..73bf0f22be9 --- /dev/null +++ b/src/resources/template.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "template.h" + +#include "common/asserts.h" + +#include +#include +#include +#include + +using namespace OCC::Resources; + +Q_LOGGING_CATEGORY(lcResourcesTeplate, "sync.resoruces.template", QtInfoMsg) + +QString Template::renderTemplateFromFile(const QString &filePath, const QMap &values) +{ + return renderTemplate( + [&] { + QFile f(filePath); + OC_ASSERT(f.open(QFile::ReadOnly)); + return QString::fromUtf8(f.readAll()); + }(), + values); +} + +QString Template::renderTemplate(QString &&templ, const QMap &values) +{ + static const QRegularExpression pattern(QStringLiteral("@{([^{}]+)}")); + const auto replace = [&templ, &values](QRegularExpressionMatchIterator it) { + while (it.hasNext()) { + const auto match = it.next(); + Q_ASSERT(match.lastCapturedIndex() == 1); + Q_ASSERT([&] { + if (!values.contains(match.captured(1))) { + qWarning(lcResourcesTeplate) << "Unknown key:" << match.captured(1); + return false; + } + return true; + }()); + templ.replace(match.captured(0), values.value(match.captured(1))); + } + }; + + auto matches = pattern.globalMatch(templ); + do { + replace(matches); + // the placeholder can again contain a placeholder + matches = pattern.globalMatch(templ); + } while (matches.hasNext()); + + return templ; +} diff --git a/src/resources/template.h b/src/resources/template.h new file mode 100644 index 00000000000..a2def523301 --- /dev/null +++ b/src/resources/template.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "resources/owncloudresources.h" + +#include + +namespace OCC { +namespace Resources { + namespace Template { + + /** + * Replace all occurrences of @{} values in template with the values from values + */ + OWNCLOUDRESOURCES_EXPORT QString renderTemplateFromFile(const QString &filePath, const QMap &values); + OWNCLOUDRESOURCES_EXPORT QString renderTemplate(QString &&templ, const QMap &values); + }; +} +} diff --git a/theme/black/ui-light-plain-monochrom-state-checkmark.svg b/src/resources/theme/black/ui-light-plain-monochrom-state-checkmark.svg similarity index 100% rename from theme/black/ui-light-plain-monochrom-state-checkmark.svg rename to src/resources/theme/black/ui-light-plain-monochrom-state-checkmark.svg diff --git a/theme/black/ui-light-plain-monochrom-state-downloaded.svg b/src/resources/theme/black/ui-light-plain-monochrom-state-downloaded.svg similarity index 100% rename from theme/black/ui-light-plain-monochrom-state-downloaded.svg rename to src/resources/theme/black/ui-light-plain-monochrom-state-downloaded.svg diff --git a/theme/black/ui-light-plain-monochrom-state-error.svg b/src/resources/theme/black/ui-light-plain-monochrom-state-error.svg similarity index 100% rename from theme/black/ui-light-plain-monochrom-state-error.svg rename to src/resources/theme/black/ui-light-plain-monochrom-state-error.svg diff --git a/theme/black/ui-light-plain-monochrom-state-info.svg b/src/resources/theme/black/ui-light-plain-monochrom-state-info.svg similarity index 100% rename from theme/black/ui-light-plain-monochrom-state-info.svg rename to src/resources/theme/black/ui-light-plain-monochrom-state-info.svg diff --git a/theme/black/ui-light-plain-monochrom-state-offline.svg b/src/resources/theme/black/ui-light-plain-monochrom-state-offline.svg similarity index 100% rename from theme/black/ui-light-plain-monochrom-state-offline.svg rename to src/resources/theme/black/ui-light-plain-monochrom-state-offline.svg diff --git a/theme/black/ui-light-plain-monochrom-state-pause.svg b/src/resources/theme/black/ui-light-plain-monochrom-state-pause.svg similarity index 100% rename from theme/black/ui-light-plain-monochrom-state-pause.svg rename to src/resources/theme/black/ui-light-plain-monochrom-state-pause.svg diff --git a/theme/black/ui-light-plain-monochrom-state-sync.svg b/src/resources/theme/black/ui-light-plain-monochrom-state-sync.svg similarity index 100% rename from theme/black/ui-light-plain-monochrom-state-sync.svg rename to src/resources/theme/black/ui-light-plain-monochrom-state-sync.svg diff --git a/theme/black/ui-light-plain-monochrom-state-warning.svg b/src/resources/theme/black/ui-light-plain-monochrom-state-warning.svg similarity index 100% rename from theme/black/ui-light-plain-monochrom-state-warning.svg rename to src/resources/theme/black/ui-light-plain-monochrom-state-warning.svg diff --git a/theme/colored/1024-owncloud-icon-sidebar.png b/src/resources/theme/colored/1024-owncloud-icon-sidebar.png similarity index 100% rename from theme/colored/1024-owncloud-icon-sidebar.png rename to src/resources/theme/colored/1024-owncloud-icon-sidebar.png diff --git a/theme/colored/1024-owncloud-icon.png b/src/resources/theme/colored/1024-owncloud-icon.png similarity index 100% rename from theme/colored/1024-owncloud-icon.png rename to src/resources/theme/colored/1024-owncloud-icon.png diff --git a/theme/colored/128-owncloud-icon-sidebar.png b/src/resources/theme/colored/128-owncloud-icon-sidebar.png similarity index 100% rename from theme/colored/128-owncloud-icon-sidebar.png rename to src/resources/theme/colored/128-owncloud-icon-sidebar.png diff --git a/theme/colored/128-owncloud-icon.png b/src/resources/theme/colored/128-owncloud-icon.png similarity index 100% rename from theme/colored/128-owncloud-icon.png rename to src/resources/theme/colored/128-owncloud-icon.png diff --git a/theme/colored/16-owncloud-icon-sidebar.png b/src/resources/theme/colored/16-owncloud-icon-sidebar.png similarity index 100% rename from theme/colored/16-owncloud-icon-sidebar.png rename to src/resources/theme/colored/16-owncloud-icon-sidebar.png diff --git a/theme/colored/16-owncloud-icon.png b/src/resources/theme/colored/16-owncloud-icon.png similarity index 100% rename from theme/colored/16-owncloud-icon.png rename to src/resources/theme/colored/16-owncloud-icon.png diff --git a/theme/colored/24-owncloud-icon.png b/src/resources/theme/colored/24-owncloud-icon.png similarity index 100% rename from theme/colored/24-owncloud-icon.png rename to src/resources/theme/colored/24-owncloud-icon.png diff --git a/theme/colored/256-owncloud-icon-sidebar.png b/src/resources/theme/colored/256-owncloud-icon-sidebar.png similarity index 100% rename from theme/colored/256-owncloud-icon-sidebar.png rename to src/resources/theme/colored/256-owncloud-icon-sidebar.png diff --git a/theme/colored/256-owncloud-icon.png b/src/resources/theme/colored/256-owncloud-icon.png similarity index 100% rename from theme/colored/256-owncloud-icon.png rename to src/resources/theme/colored/256-owncloud-icon.png diff --git a/theme/colored/32-owncloud-icon-sidebar.png b/src/resources/theme/colored/32-owncloud-icon-sidebar.png similarity index 100% rename from theme/colored/32-owncloud-icon-sidebar.png rename to src/resources/theme/colored/32-owncloud-icon-sidebar.png diff --git a/theme/colored/32-owncloud-icon.png b/src/resources/theme/colored/32-owncloud-icon.png similarity index 100% rename from theme/colored/32-owncloud-icon.png rename to src/resources/theme/colored/32-owncloud-icon.png diff --git a/theme/colored/48-owncloud-icon.png b/src/resources/theme/colored/48-owncloud-icon.png similarity index 100% rename from theme/colored/48-owncloud-icon.png rename to src/resources/theme/colored/48-owncloud-icon.png diff --git a/theme/colored/512-owncloud-icon-sidebar.png b/src/resources/theme/colored/512-owncloud-icon-sidebar.png similarity index 100% rename from theme/colored/512-owncloud-icon-sidebar.png rename to src/resources/theme/colored/512-owncloud-icon-sidebar.png diff --git a/theme/colored/64-owncloud-icon-sidebar.png b/src/resources/theme/colored/64-owncloud-icon-sidebar.png similarity index 100% rename from theme/colored/64-owncloud-icon-sidebar.png rename to src/resources/theme/colored/64-owncloud-icon-sidebar.png diff --git a/theme/colored/64-owncloud-icon.png b/src/resources/theme/colored/64-owncloud-icon.png similarity index 100% rename from theme/colored/64-owncloud-icon.png rename to src/resources/theme/colored/64-owncloud-icon.png diff --git a/theme/colored/sidebar.svg b/src/resources/theme/colored/sidebar.svg similarity index 100% rename from theme/colored/sidebar.svg rename to src/resources/theme/colored/sidebar.svg diff --git a/theme/colored/ui-light-plain-color-state-checkmark.svg b/src/resources/theme/colored/ui-light-plain-color-state-checkmark.svg similarity index 100% rename from theme/colored/ui-light-plain-color-state-checkmark.svg rename to src/resources/theme/colored/ui-light-plain-color-state-checkmark.svg diff --git a/theme/colored/ui-light-plain-color-state-error.svg b/src/resources/theme/colored/ui-light-plain-color-state-error.svg similarity index 100% rename from theme/colored/ui-light-plain-color-state-error.svg rename to src/resources/theme/colored/ui-light-plain-color-state-error.svg diff --git a/theme/colored/ui-light-plain-color-state-info.svg b/src/resources/theme/colored/ui-light-plain-color-state-info.svg similarity index 100% rename from theme/colored/ui-light-plain-color-state-info.svg rename to src/resources/theme/colored/ui-light-plain-color-state-info.svg diff --git a/theme/colored/ui-light-plain-color-state-offline.svg b/src/resources/theme/colored/ui-light-plain-color-state-offline.svg similarity index 100% rename from theme/colored/ui-light-plain-color-state-offline.svg rename to src/resources/theme/colored/ui-light-plain-color-state-offline.svg diff --git a/theme/colored/ui-light-plain-color-state-pause.svg b/src/resources/theme/colored/ui-light-plain-color-state-pause.svg similarity index 100% rename from theme/colored/ui-light-plain-color-state-pause.svg rename to src/resources/theme/colored/ui-light-plain-color-state-pause.svg diff --git a/theme/colored/ui-light-plain-color-state-sync.svg b/src/resources/theme/colored/ui-light-plain-color-state-sync.svg similarity index 100% rename from theme/colored/ui-light-plain-color-state-sync.svg rename to src/resources/theme/colored/ui-light-plain-color-state-sync.svg diff --git a/theme/dark/ui-dark-plain-color-state-checkmark.svg b/src/resources/theme/dark/ui-dark-plain-color-state-checkmark.svg similarity index 100% rename from theme/dark/ui-dark-plain-color-state-checkmark.svg rename to src/resources/theme/dark/ui-dark-plain-color-state-checkmark.svg diff --git a/theme/dark/ui-dark-plain-color-state-error.svg b/src/resources/theme/dark/ui-dark-plain-color-state-error.svg similarity index 100% rename from theme/dark/ui-dark-plain-color-state-error.svg rename to src/resources/theme/dark/ui-dark-plain-color-state-error.svg diff --git a/theme/dark/ui-dark-plain-color-state-info.svg b/src/resources/theme/dark/ui-dark-plain-color-state-info.svg similarity index 100% rename from theme/dark/ui-dark-plain-color-state-info.svg rename to src/resources/theme/dark/ui-dark-plain-color-state-info.svg diff --git a/theme/dark/ui-dark-plain-color-state-offline.svg b/src/resources/theme/dark/ui-dark-plain-color-state-offline.svg similarity index 100% rename from theme/dark/ui-dark-plain-color-state-offline.svg rename to src/resources/theme/dark/ui-dark-plain-color-state-offline.svg diff --git a/theme/dark/ui-dark-plain-color-state-pause.svg b/src/resources/theme/dark/ui-dark-plain-color-state-pause.svg similarity index 100% rename from theme/dark/ui-dark-plain-color-state-pause.svg rename to src/resources/theme/dark/ui-dark-plain-color-state-pause.svg diff --git a/theme/dark/ui-dark-plain-color-state-sync.svg b/src/resources/theme/dark/ui-dark-plain-color-state-sync.svg similarity index 100% rename from theme/dark/ui-dark-plain-color-state-sync.svg rename to src/resources/theme/dark/ui-dark-plain-color-state-sync.svg diff --git a/theme/universal/oc-image-about.svg b/src/resources/theme/universal/oc-image-about.svg similarity index 100% rename from theme/universal/oc-image-about.svg rename to src/resources/theme/universal/oc-image-about.svg diff --git a/theme/universal/owncloud-icon.svg b/src/resources/theme/universal/owncloud-icon.svg similarity index 100% rename from theme/universal/owncloud-icon.svg rename to src/resources/theme/universal/owncloud-icon.svg diff --git a/src/resources/theme/universal/wizard_footer_logo.svg b/src/resources/theme/universal/wizard_footer_logo.svg new file mode 100644 index 00000000000..385d98effd8 --- /dev/null +++ b/src/resources/theme/universal/wizard_footer_logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/theme/universal/wizard_logo.svg b/src/resources/theme/universal/wizard_logo.svg similarity index 100% rename from theme/universal/wizard_logo.svg rename to src/resources/theme/universal/wizard_logo.svg diff --git a/src/resources/theme/universal/wizard_logo_dark.svg b/src/resources/theme/universal/wizard_logo_dark.svg new file mode 100644 index 00000000000..385d98effd8 --- /dev/null +++ b/src/resources/theme/universal/wizard_logo_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/theme/white/ui-dark-plain-monochrom-state-checkmark.svg b/src/resources/theme/white/ui-dark-plain-monochrom-state-checkmark.svg similarity index 100% rename from theme/white/ui-dark-plain-monochrom-state-checkmark.svg rename to src/resources/theme/white/ui-dark-plain-monochrom-state-checkmark.svg diff --git a/theme/white/ui-dark-plain-monochrom-state-error.svg b/src/resources/theme/white/ui-dark-plain-monochrom-state-error.svg similarity index 100% rename from theme/white/ui-dark-plain-monochrom-state-error.svg rename to src/resources/theme/white/ui-dark-plain-monochrom-state-error.svg diff --git a/theme/white/ui-dark-plain-monochrom-state-info.svg b/src/resources/theme/white/ui-dark-plain-monochrom-state-info.svg similarity index 100% rename from theme/white/ui-dark-plain-monochrom-state-info.svg rename to src/resources/theme/white/ui-dark-plain-monochrom-state-info.svg diff --git a/theme/white/ui-dark-plain-monochrom-state-offline.svg b/src/resources/theme/white/ui-dark-plain-monochrom-state-offline.svg similarity index 100% rename from theme/white/ui-dark-plain-monochrom-state-offline.svg rename to src/resources/theme/white/ui-dark-plain-monochrom-state-offline.svg diff --git a/theme/white/ui-dark-plain-monochrom-state-pause.svg b/src/resources/theme/white/ui-dark-plain-monochrom-state-pause.svg similarity index 100% rename from theme/white/ui-dark-plain-monochrom-state-pause.svg rename to src/resources/theme/white/ui-dark-plain-monochrom-state-pause.svg diff --git a/theme/white/ui-dark-plain-monochrom-state-sync.svg b/src/resources/theme/white/ui-dark-plain-monochrom-state-sync.svg similarity index 100% rename from theme/white/ui-dark-plain-monochrom-state-sync.svg rename to src/resources/theme/white/ui-dark-plain-monochrom-state-sync.svg diff --git a/src/resources/themewatcher.cpp b/src/resources/themewatcher.cpp new file mode 100644 index 00000000000..fa0f5d87f47 --- /dev/null +++ b/src/resources/themewatcher.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "themewatcher.h" + +#include +#include + +namespace OCC { +namespace Resources { + ThemeWatcher::ThemeWatcher(QObject *parent) + : QObject(parent) + { + qApp->installEventFilter(this); + } + + bool ThemeWatcher::eventFilter(QObject *watched, QEvent *event) + { + if (event->type() == QEvent::ThemeChange) { + Q_EMIT themeChanged(); + } + return QObject::eventFilter(watched, event); + } +} // Resources +} // OCC diff --git a/src/resources/themewatcher.h b/src/resources/themewatcher.h new file mode 100644 index 00000000000..44b05cb3dc2 --- /dev/null +++ b/src/resources/themewatcher.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 "resources/owncloudresources.h" + +#include + +namespace OCC { +namespace Resources { + + class OWNCLOUDRESOURCES_EXPORT ThemeWatcher : public QObject + { + Q_OBJECT + public: + ThemeWatcher(QObject *parent = nullptr); + + Q_SIGNALS: + void themeChanged(); + + protected: + bool eventFilter(QObject *watched, QEvent *event) override; + }; + +} // Resources +} // OCC diff --git a/src/resources/wizard/style.qss b/src/resources/wizard/style.qss new file mode 100644 index 00000000000..364a0e180e1 --- /dev/null +++ b/src/resources/wizard/style.qss @@ -0,0 +1,87 @@ +/* + it turned out to be easier to apply styles as "locally" as possible, i.e., just apply where necessary + + we use a dedicated widget as a container for the pages within the wizard window + styles will be inherited by all child widgets, so we'd have to guess the original values for all elements which we + do not want to style + + the navigation/pagination part is located outside the content widget, so it won't be affected by this stylesheet + especially since we prefix all rules with #contentWidget + + note that @\{...\} are variables replaced by our own code when loading the stylesheet + they represent theme-specific colors + */ + +/* + unfortunately, if we want to change the background color altogether, we have to set it it on the top level element + and reset it to the original value where needed (some group boxes) + */ +#contentWidget +{ + background-color: @{WIZARD_BACKGROUND_COLOR}; +} + +/* selectively set the font color for most labels and one group box title */ +#contentWidget #welcomeTextLabel, +#contentWidget #serverUrlLabel, +#contentWidget #youreAllSetLabel, +#contentWidget #enterYourCredentialsLabel, +#contentWidget #enterYourUsernameLabel, +#contentWidget #urlLabel, +#contentWidget #usernameLabel, +#contentWidget #passwordLabel, +#contentWidget #topLabel, +#contentWidget #usernameLabel, +#contentWidget #passwordLabel, +#contentWidget #advancedConfigGroupBox::title, +#contentWidget #instancesListLabel +{ + color: @{WIZARD_FONT_COLOR}; +} + +/* + the group box also has got a content widget, which we can easily hide from C++ when it is not checked + a side effect of this is that it makes setting the original background color a lot easier, since we do not have to + set the margins/paddings of the parent group box + */ +#contentWidget #advancedConfigGroupBoxContentWidget +{ + /* + fortunately, we can just reuse the colors from the original theme using the QPalette macro + this is significantly better than having to set a specific color in some way + */ + background-color: palette(window); +} + +#contentWidget #advancedConfigGroupBox +{ + /* + the border would be hidden by the content widget anyway, and the content widget background color typically + provides sufficient contrast + */ + border: 0; +} + +#contentWidget #advancedConfigGroupBox:checked { + /* + this value is combined with some suitable top margin on the layout in Qt Designer + (must be non-0, otherwise the stylesheet value does not have any effect) + */ + margin-top: 16px; +} + +/* server URL page */ +#contentWidget QLabel#welcomeTextLabel { font-size: 30px; } +#contentWidget QLabel#serverUrlLabel { font-size: 16px; } + +/* credentials pages (common) */ +#contentWidget QLabel#urlLabel { font-size: 16px; } + +/* basic auth credentials page */ +#contentWidget QLabel#enterYourCredentialsLabel { font-size: 16px; } + +/* OAuth2 credentials page */ +#contentWidget QLabel#pleaseLogIntoLabel { font-size: 16px; } + +/* account configured page */ +#contentWidget QLabel#youreAllSetLabel { font-size: 30px; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c7c4c74864c..d78765e0b84 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,15 +2,17 @@ include(owncloud_add_test.cmake) add_subdirectory(testutils) +owncloud_add_test(JHash) + owncloud_add_test(OwncloudPropagator) owncloud_add_test(OwnSql) owncloud_add_test(SyncJournalDB) owncloud_add_test(SyncFileItem) owncloud_add_test(ConcatUrl) -owncloud_add_test(Cookies) owncloud_add_test(XmlParse) +owncloud_add_test(OAuthHtmlPage ../src/gui/creds/oauthhtmlpage.cpp) owncloud_add_test(ChecksumValidator) - +owncloud_add_test(ConnectionValidator) # TODO: we need keychain access for this test if (NOT APPLE OR NOT DEFINED ENV{GITHUB_ACTION}) @@ -20,25 +22,34 @@ endif() owncloud_add_test(ExcludedFiles) owncloud_add_test(Utility) +owncloud_add_test(AppConfig ../src/libsync/owncloudtheme.cpp) + owncloud_add_test(SyncEngine) -owncloud_add_test(SyncVirtualFiles) owncloud_add_test(SyncMove) +add_dependencies(SyncMoveTest test_helper) owncloud_add_test(SyncDelete) owncloud_add_test(SyncConflict) owncloud_add_test(SyncFileStatusTracker) owncloud_add_test(Download) -owncloud_add_test(ChunkingNg) owncloud_add_test(UploadReset) -owncloud_add_test(AllFilesDeleted) owncloud_add_test(Blacklist) owncloud_add_test(LocalDiscovery) owncloud_add_test(RemoteDiscovery) owncloud_add_test(Permissions) -owncloud_add_test(SelectiveSync) owncloud_add_test(DatabaseError) owncloud_add_test(LockedFiles) - -owncloud_add_test(FolderMigration) +owncloud_add_test(Adapters + ../src/gui/networkadapters/determineauthtypeadapter.cpp + ../src/gui/networkadapters/discoverwebfingerserviceadapter.cpp + ../src/gui/networkadapters/fetchcapabilitiesadapter.cpp +# ../src/gui/networkadapters/resolveurladapter.cpp +# ../src/gui/tlserrordialog.cpp + ../src/gui/networkadapters/webfingerlookupadapter.cpp +) + +if (WITH_AUTO_UPDATER) + owncloud_add_test(Updater) +endif() owncloud_add_test(FolderWatcher) @@ -46,23 +57,26 @@ if( UNIX AND NOT APPLE ) owncloud_add_test(InotifyWatcher) endif(UNIX AND NOT APPLE) - -owncloud_add_test(LongPath) -owncloud_add_benchmark(LargeSync) +owncloud_add_test(FileSystem) owncloud_add_test(FolderMan) -owncloud_add_test(OAuth) +owncloud_add_test(OAuth ../src/gui/creds/oauth.cpp ../src/gui/networkadapters/userinfoadapter.cpp ../src/gui/creds/oauthhtmlpage.cpp) configure_file(test_journal.db "${PROJECT_BINARY_DIR}/bin/test_journal.db" COPYONLY) - owncloud_add_test(JobQueue) -find_package(CMocka) -if (CMOCKA_FOUND) - include(AddCMockaTest) - add_subdirectory(csync) -endif (CMOCKA_FOUND) - add_subdirectory(modeltests) + +if(WIN32) + find_file(cldapi_FOUND CldApi.dll) + if (cldapi_FOUND) + include("${CMAKE_SOURCE_DIR}/test/owncloud_add_test.cmake") + owncloud_add_test(WinVfs) + target_link_libraries(WinVfsTest vfs_plugin_win_static) + add_dependencies(WinVfsTest vfs_wincfapi) + else() + message(WARNING "Failed to detect CldApi.dll: Skipping WinVfsTest") + endif() +endif() diff --git a/test/benchmarks/benchlargesync.cpp b/test/benchmarks/benchlargesync.cpp deleted file mode 100644 index 05b7c1b9d9d..00000000000 --- a/test/benchmarks/benchlargesync.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * - */ - -#include "testutils/syncenginetestutils.h" -#include - -using namespace OCC; - -int numDirs = 0; -int numFiles = 0; - -template -void addBunchOfFiles(int depth, const QString &path, FileModifier &fi) { - for (int fileNum = 1; fileNum <= filesPerDir; ++fileNum) { - QString name = QStringLiteral("file") + QString::number(fileNum); - fi.insert(path.isEmpty() ? name : path + "/" + name); - numFiles++; - } - if (depth >= maxDepth) - return; - for (char dirNum = 1; dirNum <= dirPerDir; ++dirNum) { - QString name = QStringLiteral("dir") + QString::number(dirNum); - QString subPath = path.isEmpty() ? name : path + "/" + name; - fi.mkdir(subPath); - numDirs++; - addBunchOfFiles(depth + 1, subPath, fi); - } -} - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - addBunchOfFiles<10, 8, 4>(0, "", fakeFolder.localModifier()); - - qDebug() << "NUMFILES" << numFiles; - qDebug() << "NUMDIRS" << numDirs; - QElapsedTimer timer; - timer.start(); - bool result1 = fakeFolder.syncOnce(); - qDebug() << "FIRST SYNC: " << result1 << timer.restart(); - bool result2 = fakeFolder.syncOnce(); - qDebug() << "SECOND SYNC: " << result2 << timer.restart(); - return (result1 && result2) ? 0 : -1; -} diff --git a/test/csync/AUTHORS b/test/csync/AUTHORS deleted file mode 100644 index 5fd81c08e36..00000000000 --- a/test/csync/AUTHORS +++ /dev/null @@ -1,3 +0,0 @@ -Andreas Schneider -Klaas Freitag -Olivier Goffart diff --git a/test/csync/CMakeLists.txt b/test/csync/CMakeLists.txt deleted file mode 100644 index bd7a8a1eac0..00000000000 --- a/test/csync/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -include(ECMMarkAsTest) -include_directories( - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_BINARY_DIR} - ${CMOCKA_INCLUDE_DIR} -) - -include_directories(${CHECK_INCLUDE_DIRS}) -# create test library -add_library(torture STATIC torture.c cmdline.c) -target_link_libraries(torture ${CMOCKA_LIBRARIES}) -set_target_properties(torture PROPERTIES C_STANDARD 99 AUTOMOC OFF) - -set(TEST_TARGET_LIBRARIES torture Qt5::Core csync) - -# create tests - -# std - -add_cmocka_test(check_std_c_jhash std_tests/check_std_c_jhash.c ${TEST_TARGET_LIBRARIES}) - -# vio -add_cmocka_test(check_vio_ext vio_tests/check_vio_ext.cpp ${TEST_TARGET_LIBRARIES}) - diff --git a/test/csync/COPYING b/test/csync/COPYING deleted file mode 100644 index 4362b49151d..00000000000 --- a/test/csync/COPYING +++ /dev/null @@ -1,502 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/test/csync/cmdline.c b/test/csync/cmdline.c deleted file mode 100644 index 2c9abe019a8..00000000000 --- a/test/csync/cmdline.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * libcsync -- a library to sync a directory with another - * - * Copyright (c) 2008-2013 by Andreas Schneider - * - * 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 "csync/config_csync.h" -#include "torture.h" - -#ifdef HAVE_ARGP_H -#include - -const char *argp_program_version = "csync test 0.2"; -const char *argp_program_bug_address = ""; - -static char **cmdline; - -/* Program documentation. */ -static char doc[] = "csync test"; - -/* The options we understand. */ -static struct argp_option options[] = { - { - .name = "verbose", - .key = 'v', - .arg = NULL, - .flags = 0, - .doc = "Make csync test more verbose", - .group = 0 - }, - {NULL, 0, NULL, 0, NULL, 0} -}; - -/* Parse a single option. */ -static error_t parse_opt (int key, char *arg, struct argp_state *state) { - /* Get the input argument from argp_parse, which we - * know is a pointer to our arguments structure. - */ - struct argument_s *arguments = state->input; - - /* arg is currently not used */ - (void) arg; - - switch (key) { - case 'v': - arguments->verbose++; - break; - case ARGP_KEY_ARG: - /* End processing here. */ - cmdline = &state->argv [state->next - 1]; - state->next = state->argc; - break; - default: - return ARGP_ERR_UNKNOWN; - } - - return 0; -} - -/* Our argp parser. */ -/* static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; */ -static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; -#endif /* HAVE_ARGP_H */ - -void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments) { - /* - * Parse our arguments; every option seen by parse_opt will - * be reflected in arguments. - */ -#ifdef HAVE_ARGP_H - argp_parse(&argp, argc, argv, 0, 0, arguments); -#else - (void) argc; - (void) argv; - (void) arguments; -#endif /* HAVE_ARGP_H */ -} diff --git a/test/csync/std_tests/check_std_c_jhash.c b/test/csync/std_tests/check_std_c_jhash.c deleted file mode 100644 index 67103372a3f..00000000000 --- a/test/csync/std_tests/check_std_c_jhash.c +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Tests are taken form lookup2.c and lookup8.c - * by Bob Jenkins, December 1996, Public Domain. - * - * See http://burtleburtle.net/bob/hash/evahash.html - */ -#include "torture.h" - -#include "common/c_jhash.h" - -#define HASHSTATE 1 -#define HASHLEN 1 -#define MAXPAIR 80 -#define MAXLEN 70 - -static void check_c_jhash_trials(void **state) -{ - uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1]; - uint32_t c[HASHSTATE], d[HASHSTATE], i, j=0, k, l, m, z; - uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE]; - uint32_t x[HASHSTATE],y[HASHSTATE]; - uint32_t hlen; - - (void) state; /* unused */ - - for (hlen=0; hlen < MAXLEN; ++hlen) { - z=0; - for (i=0; i>(8-j)); - c[0] = c_jhash(a, hlen, m); - b[i] ^= ((k+1)<>(8-j)); - d[0] = c_jhash(b, hlen, m); - /* check every bit is 1, 0, set, and not set at least once */ - for (l=0; lz) z=k; - if (k==MAXPAIR) { - print_error("Some bit didn't change: "); - print_error("%.8x %.8x %.8x %.8x %.8x %.8x ", - e[0], f[0], g[0], h[0], x[0], y[0]); - print_error("i %d j %d m %d len %d\n",i,j,m,hlen); - } - if (z == MAXPAIR) { - if (z < MAXPAIR) { - assert_true(z < MAXPAIR); - // print_error("%u trials needed, should be less than 40\n", z/2); - return; - } - } - } - } - } - } -} - -static void check_c_jhash_alignment_problems(void **state) -{ - uint32_t test; - uint8_t buf[MAXLEN+20], *b; - uint32_t len; - uint8_t q[] = "This is the time for all good men to come to the aid of their country"; - uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country"; - uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country"; - uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country"; - uint32_t h,i,j,ref,x,y; - - (void) state; /* unused */ - - test = c_jhash(q, sizeof(q)-1, (uint32_t)0); - assert_true(test == c_jhash(qq+1, sizeof(q)-1, (uint32_t)0)); - assert_true(test == c_jhash(qq+1, sizeof(q)-1, (uint32_t)0)); - assert_true(test == c_jhash(qqq+2, sizeof(q)-1, (uint32_t)0)); - assert_true(test == c_jhash(qqqq+3, sizeof(q)-1, (uint32_t)0)); - for (h=0, b=buf+1; h<8; ++h, ++b) { - for (i=0; i>(8-j)); - c[0] = c_jhash64(a, hlen, m); - b[i] ^= ((k+1)<>(8-j)); - d[0] = c_jhash64(b, hlen, m); - /* check every bit is 1, 0, set, and not set at least once */ - for (l=0; lz) z=k; - if (k==MAXPAIR) { -#if 0 - print_error("Some bit didn't change: "); - print_error("%.8llx %.8llx %.8llx %.8llx %.8llx %.8llx ", - (long long unsigned int) e[0], - (long long unsigned int) f[0], - (long long unsigned int) g[0], - (long long unsigned int) h[0], - (long long unsigned int) x[0], - (long long unsigned int) y[0]); - print_error("i %d j %d m %d len %d\n", - (uint32_t)i,(uint32_t)j,(uint32_t)m,(uint32_t)hlen); -#endif - } - if (z == MAXPAIR) { - if (z < MAXPAIR) { -#if 0 - print_error("%lu trials needed, should be less than 40", z/2); -#endif - assert_true(z < MAXPAIR); - } - return; - } - } - } - } - } -} - -static void check_c_jhash64_alignment_problems(void **state) -{ - uint8_t buf[MAXLEN+20], *b; - uint64_t len; - uint8_t q[] = "This is the time for all good men to come to the aid of their country"; - uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country"; - uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country"; - uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country"; - uint8_t o[] = "xxxxThis is the time for all good men to come to the aid of their country"; - uint8_t oo[] = "xxxxxThis is the time for all good men to come to the aid of their country"; - uint8_t ooo[] = "xxxxxxThis is the time for all good men to come to the aid of their country"; - uint8_t oooo[] = "xxxxxxxThis is the time for all good men to come to the aid of their country"; - uint64_t h,i,j,ref,t,x,y; - - (void) state; /* unused */ - - h = c_jhash64(q+0, (uint64_t)(sizeof(q)-1), (uint64_t)0); - t = h; - assert_true(t == h); - // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); - h = c_jhash64(qq+1, (uint64_t)(sizeof(q)-1), (uint64_t)0); - assert_true(t == h); - // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); - h = c_jhash64(qqq+2, (uint64_t)(sizeof(q)-1), (uint64_t)0); - assert_true(t == h); - // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); - h = c_jhash64(qqqq+3, (uint64_t)(sizeof(q)-1), (uint64_t)0); - assert_true(t == h); - // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); - h = c_jhash64(o+4, (uint64_t)(sizeof(q)-1), (uint64_t)0); - assert_true(t == h); - // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); - h = c_jhash64(oo+5, (uint64_t)(sizeof(q)-1), (uint64_t)0); - assert_true(t == h); - // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); - h = c_jhash64(ooo+6, (uint64_t)(sizeof(q)-1), (uint64_t)0); - assert_true(t == h); - // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); - h = c_jhash64(oooo+7, (uint64_t)(sizeof(q)-1), (uint64_t)0); - assert_true(t == h); - // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); - for (h=0, b=buf+1; h<8; ++h, ++b) { - for (i=0; i - * - * 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 "csync/config_csync.h" - -#include "torture.h" - -static int verbosity; - -int torture_csync_verbosity(void) -{ - return verbosity; -} - -int main(int argc, char **argv) -{ - struct argument_s arguments; - - arguments.verbose = 0; - torture_cmdline_parse(argc, argv, &arguments); - verbosity = arguments.verbose; - - return torture_run_tests(); -} - diff --git a/test/csync/torture.h b/test/csync/torture.h deleted file mode 100644 index 045518ecc0d..00000000000 --- a/test/csync/torture.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * libcsync -- a library to sync a directory with another - * - * Copyright (c) 2008-2013 by Andreas Schneider - * - * 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 _TORTURE_H -#define _TORTURE_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -#include - -/* Used by main to communicate with parse_opt. */ -struct argument_s { - char *args[2]; - int verbose; -}; - -void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments); - -int torture_csync_verbosity(void); - -/* - * This function must be defined in every unit test file. - */ -int torture_run_tests(void); - -#ifdef __cplusplus -} -#endif - -#endif /* _TORTURE_H */ diff --git a/test/csync/valgrind-csync.supp b/test/csync/valgrind-csync.supp deleted file mode 100644 index c4d10e40cad..00000000000 --- a/test/csync/valgrind-csync.supp +++ /dev/null @@ -1,61 +0,0 @@ -### SQLITE3 - -{ - Invalid read of size 4 in sqlite3 - Memcheck:Addr4 - obj:/usr/lib*/libsqlite3.so* - fun:sqlite3_close - fun:csync_statedb_close -} - -{ - sqlite3 jump on uninit value - Memcheck:Cond - obj:/usr/lib*/libsqlite3.so* - fun:sqlite3_vmprintf - fun:sqlite3_mprintf -} - -{ - sqlite3 uninit value - Memcheck:Value8 - obj:/usr/lib*/libsqlite3.so* - fun:sqlite3_vmprintf - fun:sqlite3_mprintf -} - -### GLIBC - -{ - glibc dlopen leak - Memcheck:Leak - fun:calloc - obj:/lib*/libdl-2.*.so - fun:dlopen -} - -{ - glibc dlopen leak - Memcheck:Leak - fun:calloc - fun:_dlerror_run - fun:dlopen@@GLIBC_2.2.5 - fun:csync_vio_init -} - -{ - glibc dlerror leak - Memcheck:Leak - fun:realloc - fun:vasprintf - fun:asprintf - fun:dlerror -} - -{ - glibc fnmatch invalid read - Memcheck:Addr1 - fun:__GI_strlen - fun:fnmatch@@GLIBC_* - fun:csync_fnmatch -} diff --git a/test/csync/vio_tests/check_vio_ext.cpp b/test/csync/vio_tests/check_vio_ext.cpp deleted file mode 100644 index 34d3213f978..00000000000 --- a/test/csync/vio_tests/check_vio_ext.cpp +++ /dev/null @@ -1,366 +0,0 @@ -/* - * libcsync -- a library to sync a directory with another - * - * Copyright (c) 2015-2013 by Klaas Freitag - * - * 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 -#include -#include -#include -#include -#include - -#include "csync.h" -#include "vio/csync_vio_local.h" - -#include - -static const auto CSYNC_TEST_DIR = []{ return QStringLiteral("%1/csync_test").arg(QDir::tempPath());}(); - -#include "torture.h" - -namespace { -int oc_mkdir(const QString &path) -{ - return QDir(path).mkpath(path) ? 0 : -1; -} - -} -#define WD_BUFFER_SIZE 255 - -static mbchar_t wd_buffer[WD_BUFFER_SIZE]; - -typedef struct { - QByteArray result; - QByteArray ignored_dir; -} statevar; - -/* remove the complete test dir */ -static int wipe_testdir() -{ - QDir tmp(CSYNC_TEST_DIR); - if (tmp.exists()) - { - return tmp.removeRecursively() ? 0 : 1; - } - return 0; -} - -static int setup_testenv(void **state) { - int rc; - - rc = wipe_testdir(); - assert_int_equal(rc, 0); - - auto dir = CSYNC_TEST_DIR; - rc = oc_mkdir(dir); - assert_int_equal(rc, 0); - - assert_non_null(_tgetcwd(wd_buffer, WD_BUFFER_SIZE)); - -#ifdef Q_OS_WIN - rc = _tchdir(dir.toStdWString().data()); -#else - rc = _tchdir(dir.toLocal8Bit().constData()); -#endif - assert_int_equal(rc, 0); - - /* --- initialize csync */ - statevar *mystate = new statevar; - *state = mystate; - return 0; -} - -static void output( const char *text ) -{ - printf("%s\n", text); -} - -static int teardown(void **state) { - int rc; - - output("================== Tearing down!\n"); - - rc = _tchdir(wd_buffer); - assert_int_equal(rc, 0); - - rc = wipe_testdir(); - assert_int_equal(rc, 0); - - delete reinterpret_cast(*state); - return 0; -} - -/* This function takes a relative path, prepends it with the CSYNC_TEST_DIR - * and creates each sub directory. - */ -static void create_dirs( const char *path ) -{ - int rc; - auto _mypath = QStringLiteral("%1/%2").arg(CSYNC_TEST_DIR, QString::fromUtf8(path)).toUtf8(); - char *mypath = _mypath.data(); - - char *p = mypath + CSYNC_TEST_DIR.size() + 1; /* start behind the offset */ - int i = 0; - - assert_non_null(path); - - while( *(p+i) ) { - if( *(p+i) == '/' ) { - p[i] = '\0'; - - auto mb_dir = QString::fromUtf8(mypath); - rc = oc_mkdir(mb_dir); - if(rc) - { - rc = errno; - } - assert_int_equal(rc, 0); - p[i] = '/'; - } - i++; - } -} - -/* - * This function uses the vio_opendir, vio_readdir and vio_closedir functions - * to traverse a file tree that was created before by the create_dir function. - * - * It appends a listing to the result member of the incoming struct in *state - * that can be compared later to what was expected in the calling functions. - * - * The int parameter cnt contains the number of seen files (not dirs) in the - * whole tree. - * - */ -static void traverse_dir(void **state, const QString &dir, int *cnt) -{ - csync_vio_handle_t *dh; - std::unique_ptr dirent; - statevar *sv = (statevar*) *state; - QByteArray subdir; - QByteArray subdir_out; - int rc; - int is_dir; - - dh = csync_vio_local_opendir(dir); - assert_non_null(dh); - - OCC::Vfs *vfs = nullptr; - while( (dirent = csync_vio_local_readdir(dh, vfs)) ) { - assert_non_null(dirent.get()); - if (!dirent->original_path.isEmpty()) { - sv->ignored_dir = dirent->original_path; - continue; - } - - assert_false(dirent->path.isEmpty()); - - if( dirent->path == ".." || dirent->path == "." ) { - continue; - } - - is_dir = (dirent->type == ItemTypeDirectory) ? 1:0; - - subdir = dir.toUtf8() + "/" + dirent->path; - subdir_out = (is_dir ? " ":" ") + subdir; - - if( is_dir ) { - if( sv->result.isNull() ) { - sv->result = subdir_out; - } else { - sv->result += subdir_out; - } - } else { - *cnt = *cnt +1; - } - output(subdir_out.constData()); - if( is_dir ) { - traverse_dir(state, QString::fromUtf8(subdir), cnt); - } - } - - rc = csync_vio_local_closedir(dh); - assert_int_equal(rc, 0); - -} - -static void create_file( const char *path, const char *name, const char *content) -{ - QFile file(QStringLiteral("%1/%2%3").arg(CSYNC_TEST_DIR, QString::fromUtf8(path), QString::fromUtf8(name))); - assert_int_equal(1, file.open(QIODevice::WriteOnly | QIODevice::NewOnly)); - file.write(content); -} - -static void check_readdir_shorttree(void **state) -{ - statevar *sv = (statevar*) *state; - - const char *t1 = "alibaba/und/die/vierzig/räuber/"; - create_dirs( t1 ); - int files_cnt = 0; - - traverse_dir(state, CSYNC_TEST_DIR, &files_cnt); - - assert_string_equal(sv->result.constData(), - QString::fromUtf8(" %1/alibaba" - " %1/alibaba/und" - " %1/alibaba/und/die" - " %1/alibaba/und/die/vierzig" - " %1/alibaba/und/die/vierzig/räuber") - .arg(CSYNC_TEST_DIR) - .toUtf8() - .constData()); - assert_int_equal(files_cnt, 0); -} - -static void check_readdir_with_content(void **state) -{ - statevar *sv = (statevar*) *state; - int files_cnt = 0; - - const char *t1 = "warum/nur/40/Räuber/"; - create_dirs( t1 ); - - create_file( t1, "Räuber Max.txt", "Der Max ist ein schlimmer finger"); - create_file( t1, "пя́тница.txt", "Am Freitag tanzt der Ürk"); - - - traverse_dir(state, CSYNC_TEST_DIR, &files_cnt); - - assert_string_equal(sv->result.constData(), - QString::fromUtf8(" %1/warum" - " %1/warum/nur" - " %1/warum/nur/40" - " %1/warum/nur/40/Räuber") - .arg(CSYNC_TEST_DIR) - .toUtf8() - .constData()); - /* " %1/warum/nur/40/Räuber/Räuber Max.txt" - " %1/warum/nur/40/Räuber/пя́тница.txt"; */ - assert_int_equal(files_cnt, 2); /* Two files in the sub dir */ -} - -static void check_readdir_longtree(void **state) -{ - statevar *sv = (statevar*) *state; - - /* Strange things here: Compilers only support strings with length of 4k max. - * The expected result string is longer, so it needs to be split up in r1, r2 and r3 - */ - - /* create the test tree */ - const char *t1 = "vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER/ZWEI/Butteln/VOLL RUM/"; - create_dirs( t1 ); - - const auto r1 = QString::fromUtf8( -" %1/vierzig" -" %1/vierzig/mann" -" %1/vierzig/mann/auf" -" %1/vierzig/mann/auf/des" -" %1/vierzig/mann/auf/des/toten" -" %1/vierzig/mann/auf/des/toten/Mann" -" %1/vierzig/mann/auf/des/toten/Mann/kiste" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum").arg(CSYNC_TEST_DIR); - - - const auto r2 = QString::fromUtf8( -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH").arg(CSYNC_TEST_DIR); - - - const auto r3 = QString::fromUtf8( -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER/ZWEI" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER/ZWEI/Butteln" -" %1/vierzig/mann/auf/des/toten/Mann/kiste/ooooooooooooooooooooooh/and/ne/bottle/voll/rum/und/so/singen/wir/VIERZIG/MANN/AUF/DES/TOTEN/MANNS/KISTE/OOOOOOOOH/AND/NE/BOTTLE/VOLL/RUM/undnochmalallezusammen/VierZig/MannaufDesTotenManns/KISTE/ooooooooooooooooooooooooooohhhhhh/und/BESSER/ZWEI/Butteln/VOLL RUM").arg(CSYNC_TEST_DIR); - - /* assemble the result string ... */ - const auto result = (r1 + r2 + r3).toUtf8(); - int files_cnt = 0; - traverse_dir(state, CSYNC_TEST_DIR, &files_cnt); - assert_int_equal(files_cnt, 0); - /* and compare. */ - assert_string_equal(sv->result.constData(), result.constData()); -} - -// https://github.com/owncloud/client/issues/3128 https://github.com/owncloud/client/issues/2777 -static void check_readdir_bigunicode(void **state) -{ - statevar *sv = (statevar*) *state; -// 1: ? ASCII: 239 - EF -// 2: ? ASCII: 187 - BB -// 3: ? ASCII: 191 - BF -// 4: ASCII: 32 - 20 - - QString p = QStringLiteral("%1/%2").arg(CSYNC_TEST_DIR, QStringLiteral("goodone/")); - int rc = oc_mkdir(p); - assert_int_equal(rc, 0); - - p = QStringLiteral("%1/goodone/ugly\xEF\xBB\xBF\x32.txt").arg(CSYNC_TEST_DIR); // file with encoding error - - rc = oc_mkdir(p); - - assert_int_equal(rc, 0); - - int files_cnt = 0; - traverse_dir(state, CSYNC_TEST_DIR, &files_cnt); - const auto expected_result = QStringLiteral(" %1/goodone" - " %1/goodone/ugly\xEF\xBB\xBF\x32.txt") - .arg(CSYNC_TEST_DIR); - assert_string_equal(sv->result.constData(), expected_result.toUtf8().constData()); - - assert_int_equal(files_cnt, 0); -} - -int torture_run_tests(void) -{ - const struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(check_readdir_shorttree, setup_testenv, teardown), - cmocka_unit_test_setup_teardown(check_readdir_with_content, setup_testenv, teardown), - cmocka_unit_test_setup_teardown(check_readdir_longtree, setup_testenv, teardown), - cmocka_unit_test_setup_teardown(check_readdir_bigunicode, setup_testenv, teardown), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); -} diff --git a/test/gui/.gitignore b/test/gui/.gitignore new file mode 100644 index 00000000000..7648ceb913e --- /dev/null +++ b/test/gui/.gitignore @@ -0,0 +1,2 @@ +shared/scripts/custom_lib +reports diff --git a/test/gui/.pylintrc b/test/gui/.pylintrc new file mode 100644 index 00000000000..11c27e4440c --- /dev/null +++ b/test/gui/.pylintrc @@ -0,0 +1,87 @@ +[MASTER] +init-hook='import sys; sys.path.append("./shared/scripts")' + +[MAIN] +enable=all +disable= + missing-docstring, + global-statement, + locally-disabled, + suppressed-message, + unused-variable, + too-many-public-methods, + bare-except, + fixme, +fail-under=10 +ignore-paths=^tst_.*/test.py$, + shared/scripts/names.py, + shared/scripts/custom_lib +ignored-modules= + squish, + squishinfo, + object, + objectmaphelper, + test, + requests, + psutil, + urllib3, + custom_lib.syncstate, +load-plugins= + pylint.extensions.check_elif, + pylint.extensions.bad_builtin, + pylint.extensions.code_style, + pylint.extensions.overlapping_exceptions, + pylint.extensions.typing, + pylint.extensions.redefined_variable_type, +py-version=3.10 +unsafe-load-any-extension=no + +[BASIC] +attr-naming-style=snake_case +; TODO: enable it later +; class-attribute-naming-style=snake_case +class-const-naming-style=UPPER_CASE +class-naming-style=PascalCase +const-naming-style=UPPER_CASE +function-naming-style=snake_case +inlinevar-naming-style=snake_case +method-naming-style=snake_case +module-naming-style=any +variable-naming-style=snake_case + +[DESIGN] +max-args=8 + +[VARIABLES] +additional-builtins= + squish, + squishinfo, + test, + testSettings, + OnFeatureStart, + OnFeatureEnd, + OnScenarioStart, + OnScenarioEnd, + QApplication, + Given, + When, + Then, + Step, +allow-global-unused-variables=no +dummy-variables-rgx=^hook$|^_*$|^step$ +ignored-argument-names=^context$|^_*$ + +[STRING] +check-quote-consistency=yes + +[FORMAT] +indent-string=' ' +max-line-length=120 + +[TYPECHECK] +ignored-classes=object,test + +[SIMILARITIES] +ignore-comments=yes +ignore-docstrings=yes +ignore-imports=yes diff --git a/test/gui/Makefile b/test/gui/Makefile index 6838763100c..4f8db033971 100644 --- a/test/gui/Makefile +++ b/test/gui/Makefile @@ -1,28 +1,24 @@ -# Main Makefile for formatting python code for gui test -# -# Requirements to run make here: -# - black -# -# Installing black can be done via https://black.readthedocs.io/en/stable/getting_started.html -# configuration for black can be found inside pyproject.toml file under [tools.black] section. -# -# Usage: -# make test-python-style -# -auto format all the python files inside `test/gui` -# make test-python-style-check -# -check which files will be reformatted -# make test-python-style-diff -# -see what changes will be done -# +PYTHON_LINT_PATHS:="./**/*.py" -.PHONY: test-python-style -test-python-style: +.PHONY: install +install: install + python3.10 -m pip install -r requirements.txt + playwright install chromium + +.PHONY: python-lint +python-lint: + black --check --diff . + python3.10 -m pylint --rcfile ./.pylintrc $(PYTHON_LINT_PATHS) + +.PHONY: python-lint-fix +python-lint-fix: black . + python3.10 -m pylint --rcfile ./.pylintrc $(PYTHON_LINT_PATHS) -.PHONY: test-python-style-diff -test-python-style-diff: - black --diff . +.PHONY: gherkin-lint +gherkin-lint: + gherlint -c ./config/.gherlintrc.json . -.PHONY: test-python-style-check -test-python-style-check: - black --check . +.PHONY: gherkin-lint-fix +gherkin-lint-fix: + gherlint --fix -c ./config/.gherlintrc.json . diff --git a/test/gui/TestLogParser.py b/test/gui/TestLogParser.py deleted file mode 100644 index b10dc19b391..00000000000 --- a/test/gui/TestLogParser.py +++ /dev/null @@ -1,83 +0,0 @@ -# # Input parameters -# name of the file that contains the data to be parsed -import json -import sys - -# Method that tries to find the data using nested loops -def traverse_loop(data): - failing_tests = [] - - # Loop level 0 where we loop through all the feature files inside the only test suites that we have. - for feature_file in data["tests"][0]["tests"]: - - # Loop through all the features in the feature file - for feature in feature_file['tests']: - - # Loop through all the scenarios in the feature - for scenario in feature['tests']: - - # If the scenario is not skipped, then loop through all the steps in the scenario - if "tests" in scenario: - - for test_step in scenario['tests']: - - # If the test step fails then it contains further "tests" object - # So loop through all the errors in the test step - if "tests" in test_step: - - for error in test_step['tests']: - - # Sometimes, the step with assertions operations also contains this "tests" object. - # And we do not consider it to be an error, if there is a result key with value "PASS" - if 'result' in error and error['result'] == 'PASS': - continue - - # Append the information of failing tests into the list of failing tests - # If the error detail is missing(occurs mainly in runtime error) then we display the entire error object. - test = { - "Feature File": str(feature_file['name']), - "Feature": str(feature['name']), - "Scenario": str(scenario['name']), - "Test Step": str(test_step['name']), - "Error Details": str(error['detail']) - if ('detail' in error) - else "Error details not found", - } - - failing_tests.append(test) - - return failing_tests - - -def filter_redundancy(raw_data): - unique_scenarios = [] - filtered_data = [] - - for scenario in raw_data: - if scenario['Scenario'] not in unique_scenarios: - unique_scenarios.append(scenario['Scenario']) - filtered_data.append(scenario) - else: - if scenario['Error Details'] != "Error details not found": - for repeated_scenario in filtered_data: - if repeated_scenario['Scenario'] == scenario['Scenario']: - repeated_scenario['Error Details'] = scenario['Error Details'] - - return filtered_data - - -f = open(str(sys.argv[1])) - -# returns JSON object as a dictionary -data = json.load(f) - -# Traverse through the json file to look for failing tests -failing_tests_raw = traverse_loop(data) - -# Remove duplicate nodes, if exists -# This step is necessary because sometimes the data in failing_tests_raw is redundant. -failing_tests = filter_redundancy(failing_tests_raw) - -print(json.dumps(failing_tests, indent=4, sort_keys=True)) - -f.close() diff --git a/test/gui/ci/client_server.ini b/test/gui/ci/client_server.ini new file mode 100644 index 00000000000..f3ea6aaa7b8 --- /dev/null +++ b/test/gui/ci/client_server.ini @@ -0,0 +1,3 @@ +[General] +AUT/kiteworks = "/__w/client/client/build/bin" +AUTPMTimeout = "15000" diff --git a/test/gui/ci/log_reports.sh b/test/gui/ci/log_reports.sh new file mode 100644 index 00000000000..cab3ad4a4ce --- /dev/null +++ b/test/gui/ci/log_reports.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +# $1 - GUI_TEST_REPORT_DIR +# $2 - DRONE_REPO +# $3 - DRONE_BUILD_NUMBER +# $4 - server type ('oc10' or 'ocis') + +REPORT_DIR="$1" +REPO="$2" +BUILD_NUMBER="$3" +SERVER_TYPE="$4" + +LOG_URL_PATH="https://cache.owncloud.com/public/${REPO}/${BUILD_NUMBER}/${SERVER_TYPE}/guiReportUpload" +CURL="curl --write-out %{http_code} --silent --output /dev/null" + +# $1 - log file path +# $2 - heading +function check_log() { + if [[ -f "${REPORT_DIR}/$1" ]]; then + LOG_FILE_URL="${LOG_URL_PATH}/$1" + STATUS_CODE=$($CURL "$LOG_FILE_URL") + if [[ "$STATUS_CODE" == "200" ]]; then + echo -e "$2: $LOG_FILE_URL" + fi + fi +} + +echo -e "Please, find the logs in the following links:\n" + +# check gui report +check_log "index.html" "GUI Report" +# check server log +check_log "serverlog.log" "Client log" +# check stacktrace +check_log "stacktrace.log" "Stacktrace" + +# check screenshots +if [[ -d "${REPORT_DIR}/screenshots" ]]; then + echo -e "Screenshots:" + for i in "${REPORT_DIR}"/screenshots/*.png; do + filename=$(basename "$i") + if [ "$filename" != "*.png" ]; then + echo -e "\t - ${LOG_URL_PATH}/screenshots/$filename" + fi + done +fi + +# check screenrecords +if [[ -d "${REPORT_DIR}/screenrecords" ]]; then + echo -e "Videos:" + for i in "${REPORT_DIR}"/screenrecords/*.mp4; do + filename=$(basename "$i") + if [ "$filename" != "*.mp4" ]; then + echo -e "\t - ${LOG_URL_PATH}/screenrecords/$filename" + fi + done +fi diff --git a/test/gui/ci/server.ini b/test/gui/ci/server.ini new file mode 100644 index 00000000000..7cae07f8155 --- /dev/null +++ b/test/gui/ci/server.ini @@ -0,0 +1,3 @@ +[General] +AUT/owncloud = "/__w/client/client/build/bin" +AUTPMTimeout = "15000" diff --git a/test/gui/config.sample.ini b/test/gui/config.sample.ini index 6f0c18bdd54..bcee9497964 100644 --- a/test/gui/config.sample.ini +++ b/test/gui/config.sample.ini @@ -1,7 +1,17 @@ [DEFAULT] BACKEND_HOST= SECURE_BACKEND_HOST= -MIDDLEWARE_URL= CLIENT_ROOT_SYNC_PATH= -CLIENT_SYNC_TIMEOUT= -CLIENT_LOG_FILE= \ No newline at end of file +MAX_SYNC_TIMEOUT= +MIN_SYNC_TIMEOUT= +LOWEST_SYNC_TIMEOUT= +CLIENT_LOG_FILE= +TEMP_FOLDER_PATH= +CLIENT_CONFIG_DIR= +GUI_TEST_REPORT_DIR= +OCIS=false +RECORD_VIDEO_ON_FAILURE=false +; provide a json file path relative to this config file +PREDEFINED_USERS= +CLIENT_NAME= +PERSONAL_SYNC_FOLDER= diff --git a/test/gui/config.xml b/test/gui/config.xml deleted file mode 100644 index 6ab8665c5f9..00000000000 --- a/test/gui/config.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/test/gui/config/.gherlintrc.json b/test/gui/config/.gherlintrc.json new file mode 100644 index 00000000000..0a9a1890892 --- /dev/null +++ b/test/gui/config/.gherlintrc.json @@ -0,0 +1,14 @@ +{ + "rules": { + "indentation": ["error", 4], + "no_repetitive_step_keyword": "error", + "require_step": "error", + "no_trailing_whitespace": "error", + "require_scenario": "error", + "no_then_as_first_step": "error", + "only_given_step_in_background": "error", + "newline_before_scenario": ["error", 2, true], + "lowercase_title": "off", + "no_but_in_given_when": "error" + } +} diff --git a/test/gui/drone/server.ini b/test/gui/drone/server.ini deleted file mode 100644 index e19973377c7..00000000000 --- a/test/gui/drone/server.ini +++ /dev/null @@ -1,3 +0,0 @@ -[General] -AUT/owncloud = "/drone/src/build-GUI-tests/bin" -AUTPMTimeout = "15000" diff --git a/test/gui/envs.txt b/test/gui/envs.txt new file mode 100644 index 00000000000..8d35c7da6d4 --- /dev/null +++ b/test/gui/envs.txt @@ -0,0 +1 @@ +XDG_CONFIG_HOME=/tmp/owncloudtest/.config diff --git a/test/gui/requirements.txt b/test/gui/requirements.txt new file mode 100644 index 00000000000..4f609d60175 --- /dev/null +++ b/test/gui/requirements.txt @@ -0,0 +1,7 @@ +requests==2.32.*; python_version >= "3.10" +PyGObject==3.42.*; sys_platform != 'win32' and python_version >= "3.10" +psutil==5.9.*; python_version >= "3.10" +black==24.3.*; python_version >= "3.10" +pylint==3.2.*; python_version >= "3.10" +pywin32==305; sys_platform == 'win32' and python_version >= "3.10" +pytest-playwright==0.7.0; python_version >= "3.10" diff --git a/test/gui/shared/files-for-upload/simple.pdf b/test/gui/shared/files-for-upload/simple.pdf new file mode 100644 index 00000000000..305e543db83 Binary files /dev/null and b/test/gui/shared/files-for-upload/simple.pdf differ diff --git a/test/gui/shared/files-for-upload/test_video.mp4 b/test/gui/shared/files-for-upload/test_video.mp4 new file mode 100644 index 00000000000..1fc478842f5 Binary files /dev/null and b/test/gui/shared/files-for-upload/test_video.mp4 differ diff --git a/test/gui/shared/files-for-upload/testavatar.jpeg b/test/gui/shared/files-for-upload/testavatar.jpeg new file mode 100644 index 00000000000..1d140b76801 Binary files /dev/null and b/test/gui/shared/files-for-upload/testavatar.jpeg differ diff --git a/test/gui/shared/files-for-upload/testavatar.jpg b/test/gui/shared/files-for-upload/testavatar.jpg new file mode 100644 index 00000000000..fb9e54042cb Binary files /dev/null and b/test/gui/shared/files-for-upload/testavatar.jpg differ diff --git a/test/gui/shared/files-for-upload/testavatar.png b/test/gui/shared/files-for-upload/testavatar.png new file mode 100644 index 00000000000..bd9c3cb1e0d Binary files /dev/null and b/test/gui/shared/files-for-upload/testavatar.png differ diff --git a/test/gui/shared/files-for-upload/testimage.mp3 b/test/gui/shared/files-for-upload/testimage.mp3 new file mode 100644 index 00000000000..d2eea3cc4e5 Binary files /dev/null and b/test/gui/shared/files-for-upload/testimage.mp3 differ diff --git a/test/gui/shared/scripts/bdd_hooks.py b/test/gui/shared/scripts/bdd_hooks.py index 848b64580cd..139bc0fa105 100644 --- a/test/gui/shared/scripts/bdd_hooks.py +++ b/test/gui/shared/scripts/bdd_hooks.py @@ -16,94 +16,279 @@ # See the section 'Performing Actions During Test Execution Via Hooks' in the Squish # manual for a complete reference of the available API. import shutil -import urllib.request import os -import builtins +from datetime import datetime +from types import SimpleNamespace +from helpers.StacktraceHelper import get_core_dumps, generate_stacktrace +from helpers.SyncHelper import close_socket_connection, clear_waited_after_sync +from helpers.SpaceHelper import delete_project_spaces +from helpers.api.provisioning import ( + delete_created_users, + create_user, +) +from helpers.SetupClientHelper import wait_until_app_killed +from helpers.ConfigHelper import ( + init_config, + get_config, + set_config, + clear_scenario_config, + is_windows, + is_linux, + is_owncloud_client, +) +from helpers.FilesHelper import prefix_path_namespace, cleanup_created_paths +from helpers.ReportHelper import save_video_recording, take_screenshot, is_video_enabled +from helpers.UserHelper import init_predefined_users +from helpers.api.external_api import ( + delete_all_resources, + permanently_delete_all_resources, + delete_all_spaces, + permanently_delete_all_spaces, +) +from pageObjects.Toolbar import Toolbar +from pageObjects.AccountSetting import AccountSetting +from pageObjects.AccountConnectionWizard import AccountConnectionWizard + +# Squish test settings: +# This controls whether a test (scenario) should stop execution on failure or not +# If set to True, the scenario will stop on the first step failure and remaining steps will not be executed +# If set to False, the scenario will continue to execute all steps and report all failures at the end +testSettings.throwOnFailure = True + +# this will reset in every test suite +PREVIOUS_FAIL_RESULT_COUNT = 0 +PREVIOUS_ERROR_RESULT_COUNT = 0 + + +# runs before a feature +# Order: 1 +@OnFeatureStart +def hook(context): + init_config() + if not is_owncloud_client(): + init_predefined_users() + + +# runs before every scenario +# Order: 1 @OnScenarioStart def hook(context): - from configparser import ConfigParser - - CONFIG_ENV_MAP = { - 'localBackendUrl': 'BACKEND_HOST', - 'secureLocalBackendUrl': 'SECURE_BACKEND_HOST', - 'clientSyncTimeout': 'CLIENT_SYNC_TIMEOUT', - 'middlewareUrl': 'MIDDLEWARE_URL', - 'clientConfigFile': 'CLIENT_LOG_FILE', - 'clientRootSyncPath': 'CLIENT_ROOT_SYNC_PATH', - } - - DEFAULT_CONFIG = { - 'localBackendUrl': 'https://localhost:9200/', - 'secureLocalBackendUrl': 'https://localhost:9200/', - 'clientSyncTimeout': 60, - 'middlewareUrl': 'http://localhost:3000/', - 'clientConfigFile': '-', - 'clientRootSyncPath': '/tmp/client-bdd/', - } - - # read configs from environment variables - context.userData = {} - for key, value in CONFIG_ENV_MAP.items(): - context.userData[key] = os.environ.get(value, '') - - # try reading configs from config.ini - cfg = ConfigParser() - try: - cfg.read('../config.ini') - for key, value in context.userData.items(): - if value == '': - context.userData[key] = cfg.get('DEFAULT', CONFIG_ENV_MAP[key]) - except Exception as err: - print(err) - - # Set the default values if empty - for key, value in context.userData.items(): - if value == '': - context.userData[key] = DEFAULT_CONFIG[key] - elif key == 'clientSyncTimeout': - context.userData[key] = builtins.int(value) - elif key == 'clientRootSyncPath': - # make sure there is always one trailing slash - context.userData[key] = value.rstrip('/') + '/' - - # initially set user sync path to root + clear_scenario_config() + # create admin user + if not is_owncloud_client(): + create_user("admin") + + +# runs before every scenario +# Order: 2 +@OnScenarioStart +def hook(context): + # set owncloud config file path + config_dir = get_config("clientConfigDir") + if os.path.exists(config_dir): + if len(os.listdir(config_dir)) and is_windows(): + raise FileExistsError( + "Looks like you have previous client config in '" + + config_dir + + "'\n[DANGER] Delete it and try again." + + "\n[DANGER] Removing config file will make client to lost the previously added accounts." + ) + # clean previous configs + shutil.rmtree(config_dir) + os.makedirs(config_dir, 0o0755) + set_config( + "clientConfigFile", + os.path.join(config_dir, get_config("client_name").lower() + ".cfg"), + ) + + # create reports dir if not exists + test_report_dir = get_config("guiTestReportDir") + if not os.path.exists(test_report_dir): + os.makedirs(test_report_dir) + + # log tests scenario title on serverlog file + if os.getenv("CI"): + with open(test_report_dir + "/serverlog.log", "a", encoding="utf-8") as f: + f.write( + str((datetime.now()).strftime("%H:%M:%S:%f")) + + "\tBDD Scenario: " + + context.title + + "\n" + ) + # this path will be changed according to the user added to the client # e.g.: /tmp/client-bdd/Alice - context.userData['currentUserSyncPath'] = context.userData['clientRootSyncPath'] + set_config("currentUserSyncPath", "") + + root_sync_dir = get_config("clientRootSyncPath") + if not os.path.exists(root_sync_dir): + os.makedirs(root_sync_dir) - if not os.path.exists(context.userData['clientRootSyncPath']): - os.makedirs(context.userData['clientRootSyncPath']) + tmp_dir = get_config("tempFolderPath") + if not os.path.exists(tmp_dir): + os.makedirs(tmp_dir) - req = urllib.request.Request( - os.path.join(context.userData['middlewareUrl'], 'init'), - headers={"Content-Type": "application/json"}, - method='POST', + # sync connection folder display name + set_config( + "syncConnectionName", + get_config("personal_sync_folder"), ) - try: - urllib.request.urlopen(req) - except urllib.error.HTTPError as e: - raise Exception( - "Step execution through test middleware failed. Error: " + e.read().decode() - ) +# determines if the test scenario failed or not +# Currently, this workaround is needed because we cannot find out +# a way to determine the pass/fail status of currently running test scenario. +# And, resultCount("errors") and resultCount("fails") +# return the total number of error/failed test scenarios of a test suite. +def scenario_failed(): + return ( + test.resultCount("fails") - PREVIOUS_FAIL_RESULT_COUNT > 0 + or test.resultCount("errors") - PREVIOUS_ERROR_RESULT_COUNT > 0 + ) + + +def scenario_title_to_filename(title): + # scenario name can have "/" which is invalid filename + return title.replace(" ", "_").replace("/", "_").strip(".") + + +# runs after every scenario +# Order: 1 @OnScenarioEnd def hook(context): + clear_waited_after_sync() + close_socket_connection() + + # generate screenshot and video reports + if is_linux(): + filename = scenario_title_to_filename(context.title) + if scenario_failed(): + take_screenshot(f"{filename}.png") + + if is_video_enabled(): + save_video_recording(f"{filename}.mp4", scenario_failed()) + + # teardown accounts and configs + teardown_client() + + # search coredumps after every test scenario + # CI pipeline might fail although all tests are passing + if coredumps := get_core_dumps(): + try: + generate_stacktrace(context.title, coredumps) + test.log("Stacktrace generated!") + except OSError as err: + test.log("Exception occured:" + str(err)) + elif scenario_failed(): + test.log("No coredump found!") + + global PREVIOUS_FAIL_RESULT_COUNT, PREVIOUS_ERROR_RESULT_COUNT + PREVIOUS_FAIL_RESULT_COUNT = test.resultCount("fails") + PREVIOUS_ERROR_RESULT_COUNT = test.resultCount("errors") + + +# runs after every scenario +# Order: 2 +# server cleanup +@OnScenarioEnd +def hook(context): + if not is_owncloud_client(): + delete_all_resources() + permanently_delete_all_resources() + delete_all_spaces() + permanently_delete_all_spaces() + delete_project_spaces() + delete_created_users() + + +def get_active_widget(): + ch = object.children(squish.waitForObject(AccountSetting.DIALOG_STACK, 500)) + for obj in ch: + if hasattr(obj, "objectName") and obj.objectName and obj.objectName != "page": + return obj + + # return empty object if not found + return SimpleNamespace(objectName="") + + +def teardown_client(): + # Cleanup user accounts from UI for Windows platform + # It is not needed for Linux so skipping it in order to save CI time + if is_windows(): + # remove account from UI + # In Windows, removing only config and sync folders won't help + # so to work around that, remove the account connection + close_dialogs() + close_widgets() + active_widget = get_active_widget() + if active_widget.objectName and active_widget.objectName != "SetupWizardWidget": + accounts, selectors = Toolbar.get_accounts() + for display_name in selectors: + _, account_objects = Toolbar.get_accounts() + squish.mouseClick(squish.waitForObject(account_objects[display_name])) + AccountSetting.remove_account_connection() + if accounts: + squish.waitForObject(AccountConnectionWizard.SERVER_ADDRESS_BOX) + # Detach (i.e. potentially terminate) all AUTs at the end of a scenario - for ctx in applicationContextList(): + for ctx in squish.applicationContextList(): + # get pid before detaching + pid = ctx.pid ctx.detach() - snooze(5) # ToDo wait smarter till the app died + wait_until_app_killed(pid) + + # clean up config files + shutil.rmtree(get_config("clientConfigDir")) - # delete local files/folders - for filename in os.listdir(context.userData['clientRootSyncPath']): - test.log("Deleting: " + filename) - file_path = os.path.join(context.userData['clientRootSyncPath'], filename) + # delete test files/folders + for entry in os.scandir(get_config("clientRootSyncPath")): try: - if os.path.isfile(file_path) or os.path.islink(file_path): - os.unlink(file_path) - elif os.path.isdir(file_path): - shutil.rmtree(file_path) - except Exception as e: - print('Failed to delete %s. Reason: %s' % (file_path, e)) + if entry.is_file() or entry.is_symlink(): + test.log("Deleting file: " + entry.name) + os.unlink(prefix_path_namespace(entry.path)) + elif entry.is_dir(): + test.log("Deleting folder: " + entry.name) + shutil.rmtree(prefix_path_namespace(entry.path)) + except OSError as e: + test.log(f"Failed to delete '{entry.name}'.\nReason: {e}.") + # cleanup paths created outside of the temporary directory during the test + cleanup_created_paths() + + +def close_dialogs(): + # close the current active dailog if it's not a main client window + while True: + active_window = QApplication.activeModalWidget() + if str(active_window) == "": + break + test.log(f"Closing '{active_window.objectName}' window") + if not active_window.close(): + confirm_dialog = QApplication.activeModalWidget() + if confirm_dialog.visible: + squish.clickButton( + squish.waitForObject(AccountSetting.CONFIRMATION_YES_BUTTON) + ) + + +def close_widgets(): + try: + ch = object.children(squish.waitForObject(AccountSetting.DIALOG_STACK, 500)) + for obj in ch: + if ( + hasattr(obj, "objectName") + and obj.objectName + and obj.objectName != "page" + ): + obj.close() + # if the dialog has a confirmation dialog, confirm it + confirm_dialog = QApplication.activeModalWidget() + if str(confirm_dialog) != "" and confirm_dialog.visible: + squish.clickButton( + squish.waitForObject(AccountSetting.CONFIRMATION_YES_BUTTON) + ) + except LookupError: + # nothing to close if DIALOG_STACK is not found + # required for client versions <= 5 + pass diff --git a/test/gui/shared/scripts/helpers/ConfigHelper.py b/test/gui/shared/scripts/helpers/ConfigHelper.py new file mode 100644 index 00000000000..1a1882a124c --- /dev/null +++ b/test/gui/shared/scripts/helpers/ConfigHelper.py @@ -0,0 +1,199 @@ +import os +import platform +import builtins +from tempfile import gettempdir +from configparser import ConfigParser +from pathlib import Path + +CURRENT_DIR = Path(__file__).resolve().parent + + +def read_env_file(): + envs = {} + script_path = os.path.dirname(os.path.realpath(__file__)) + env_path = os.path.abspath(os.path.join(script_path, '..', '..', '..', 'envs.txt')) + with open(env_path, 'rt', encoding='UTF-8') as f: + for line in f: + if not line.strip(): + continue + if line.startswith('#'): + continue + key, value = line.split('=', 1) + envs[key] = value.strip() + return envs + + +def get_config_from_env_file(env): + envs = read_env_file() + if env in envs: + return envs[env] + raise KeyError(f'Environment "{env}" not found in envs.txt') + + +def is_windows(): + return platform.system() == 'Windows' + + +def is_linux(): + return platform.system() == 'Linux' + + +def get_win_user_home(): + return os.environ.get('UserProfile') + + +def get_client_root_path(): + if is_windows(): + return os.path.join(get_win_user_home(), 'owncloudtest') + return os.path.join(gettempdir(), 'owncloudtest') + + +def get_test_config_file_path(): + script_path = os.path.dirname(os.path.realpath(__file__)) + return os.path.abspath(os.path.join(script_path, '..', '..', '..', 'config.ini')) + + +def get_client_name(): + if os.environ.get('CLIENT_NAME'): + return os.environ.get('CLIENT_NAME') + cfg = ConfigParser() + if cfg.read(get_test_config_file_path()) and cfg.get('DEFAULT', 'CLIENT_NAME'): + return cfg.get('DEFAULT', 'CLIENT_NAME') + return 'ownCloud' + + +def get_config_home(): + if is_windows(): + # There is no way to set custom config path in windows + # TODO: set to different path if option is available + return os.path.join( + get_win_user_home(), 'AppData', 'Roaming', get_client_name() + ) + return os.path.join(get_config_from_env_file('XDG_CONFIG_HOME'), get_client_name()) + + +def get_default_home_dir(): + if is_windows(): + return get_win_user_home() + return os.environ.get('HOME') + + +# map environment variables to config keys +CONFIG_ENV_MAP = { + 'localBackendUrl': 'BACKEND_HOST', + 'secureLocalBackendUrl': 'SECURE_BACKEND_HOST', + 'maxSyncTimeout': 'MAX_SYNC_TIMEOUT', + 'minSyncTimeout': 'MIN_SYNC_TIMEOUT', + 'lowestSyncTimeout': 'LOWEST_SYNC_TIMEOUT', + 'clientLogFile': 'CLIENT_LOG_FILE', + 'clientRootSyncPath': 'CLIENT_ROOT_SYNC_PATH', + 'tempFolderPath': 'TEMP_FOLDER_PATH', + 'clientConfigDir': 'CLIENT_CONFIG_DIR', + 'guiTestReportDir': 'GUI_TEST_REPORT_DIR', + 'predefined_users': 'PREDEFINED_USERS', + 'personal_sync_folder': 'PERSONAL_SYNC_FOLDER', + 'ocis': 'OCIS', + 'record_video_on_failure': 'RECORD_VIDEO_ON_FAILURE', +} + +DEFAULT_PATH_CONFIG = { + 'custom_lib': os.path.abspath('../shared/scripts/custom_lib'), + 'home_dir': get_default_home_dir(), + # allow to record first 5 videos + 'video_record_limit': 5, +} + +# default config values +CONFIG = { + 'localBackendUrl': 'https://localhost:9200/', + 'secureLocalBackendUrl': 'https://localhost:9200/', + 'maxSyncTimeout': 60, + 'minSyncTimeout': 5, + 'lowestSyncTimeout': 1, + 'clientLogFile': '-', + 'clientRootSyncPath': get_client_root_path(), + 'tempFolderPath': os.path.join(get_client_root_path(), 'temp'), + 'clientConfigDir': get_config_home(), + 'guiTestReportDir': os.path.abspath('../reports'), + 'predefined_users': '', + 'client_name': get_client_name(), + 'personal_sync_folder': 'Personal', + 'ocis': False, + 'record_video_on_failure': False, + 'files_for_upload': os.path.join(CURRENT_DIR.parent.parent, 'files-for-upload'), +} +CONFIG.update(DEFAULT_PATH_CONFIG) + +READONLY_CONFIG = list(CONFIG_ENV_MAP.keys()) + list(DEFAULT_PATH_CONFIG.keys()) + +SCENARIO_CONFIGS = {} + + +def read_cfg_file(cfg_path): + cfg = ConfigParser() + if cfg.read(cfg_path): + for key, _ in CONFIG.items(): + if key in CONFIG_ENV_MAP: + if value := cfg.get('DEFAULT', CONFIG_ENV_MAP[key]): + if key in ('ocis', 'record_video_on_failure'): + CONFIG[key] = value == 'true' + else: + CONFIG[key] = value + + +def init_config(): + # try reading configs from config.ini + try: + read_cfg_file(get_test_config_file_path()) + except: + pass + + # read and override configs from environment variables + for key, value in CONFIG_ENV_MAP.items(): + if os.environ.get(value): + if key in ('ocis', 'record_video_on_failure'): + CONFIG[key] = os.environ.get(value) == 'true' + else: + CONFIG[key] = os.environ.get(value) + + # Set the default values if empty + for key, value in CONFIG.items(): + if key in ('maxSyncTimeout', 'minSyncTimeout'): + CONFIG[key] = builtins.int(value) + elif key in ('localBackendUrl', 'secureLocalBackendUrl'): + # make sure there is always one trailing slash + CONFIG[key] = value.rstrip('/') + '/' + elif key in ( + 'clientRootSyncPath', + 'tempFolderPath', + 'clientConfigDir', + 'guiTestReportDir', + ): + # make sure there is always one trailing slash + if is_windows(): + value = value.replace('/', '\\') + CONFIG[key] = value.rstrip('\\') + '\\' + else: + CONFIG[key] = value.rstrip('/') + '/' + + +def get_config(key): + return CONFIG[key] + + +def set_config(key, value): + if key in READONLY_CONFIG: + raise KeyError(f'Cannot set read-only config: {key}') + # save the initial config value + if key not in SCENARIO_CONFIGS: + SCENARIO_CONFIGS[key] = CONFIG.get(key) + CONFIG[key] = value + + +def clear_scenario_config(): + for key, value in SCENARIO_CONFIGS.items(): + CONFIG[key] = value + + +def is_owncloud_client(): + return get_config('client_name') == 'ownCloud' diff --git a/test/gui/shared/scripts/helpers/FilesHelper.py b/test/gui/shared/scripts/helpers/FilesHelper.py index 10d29cddad5..8b2405be8a5 100644 --- a/test/gui/shared/scripts/helpers/FilesHelper.py +++ b/test/gui/shared/scripts/helpers/FilesHelper.py @@ -1,11 +1,134 @@ -def buildConflictedRegex(filename): - if '.' in filename: +import os +import re +import ctypes +import shutil + +from helpers.ConfigHelper import is_windows, get_config + + +def build_conflicted_regex(filename): + if "." in filename: # TODO: improve this for complex filenames - namepart = filename.split('.')[0] - extpart = filename.split('.')[1] - return '%s \(conflicted copy \d{4}-\d{2}-\d{2} \d{6}\)\.%s' % ( + namepart = filename.split(".")[0] + extpart = filename.split(".")[1] + # pylint: disable=anomalous-backslash-in-string + return "%s \(conflicted copy \d{4}-\d{2}-\d{2} \d{6}\)\.%s" % ( namepart, extpart, ) - else: - return '%s \(conflicted copy \d{4}-\d{2}-\d{2} \d{6}\)' % (filename) + # pylint: disable=anomalous-backslash-in-string + return "%s \(conflicted copy \d{4}-\d{2}-\d{2} \d{6}\)" % filename + + +def sanitize_path(path): + return path.replace("//", "/") + + +def prefix_path_namespace(path): + if is_windows(): + # https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#win32-file-namespaces + # disable string parsing + # - long path + # - trailing whitespaces + return f"\\\\?\\{path}" + return path + + +def can_read(resource): + read = False + try: + with open(resource, encoding="utf-8") as f: + read = True + except: + pass + return read and os.access(resource, os.R_OK) + + +def can_write(resource): + write = False + try: + with open(resource, "w", encoding="utf-8") as f: + write = True + except: + pass + return write and os.access(resource, os.W_OK) + + +def read_file_content(file): + with open(file, "r", encoding="utf-8") as f: + content = f.read() + return content + + +def is_empty_sync_folder(folder): + ignore_files = ["Desktop.ini"] + for item in os.listdir(folder): + # do not count the hidden files as they are ignored by the client + if not item.startswith(".") and not item in ignore_files: + return False + return True + + +def get_size_in_bytes(size): + match = re.match(r"(\d+)((?: )?[KkMmGgBb]{0,2})?", str(size)) + units = ["b", "kb", "mb", "gb"] + multiplier = 1024 + if match: + size_num = int(match.group(1)) + size_unit = match.group(2) + + if not (size_unit := size_unit.lower()): + return size_num + if size_unit in units: + if size_unit == "b": + return size_num + if size_unit == "kb": + return size_num * multiplier + if size_unit == "mb": + return size_num * (multiplier**2) + if size_unit == "gb": + return size_num * (multiplier**3) + + raise ValueError("Invalid size: " + size) + + +def get_file_size_on_disk(resource_path): + file_size_high = ctypes.c_ulonglong(0) + if is_windows(): + return ctypes.windll.kernel32.GetCompressedFileSizeW( + ctypes.c_wchar_p(resource_path), ctypes.pointer(file_size_high) + ) + raise OSError("'get_file_size_on_disk' function is only supported for Windows OS.") + + +def get_file_size(resource_path): + return os.stat(resource_path).st_size + + +# temp paths created outside the temporary directory during the test +CREATED_PATHS = [] + + +def remember_path(path): + CREATED_PATHS.append(path) + + +def cleanup_created_paths(): + global CREATED_PATHS + for path in CREATED_PATHS: + if os.path.exists(path): + if os.path.isdir(path): + shutil.rmtree(prefix_path_namespace(path)) + else: + os.unlink(prefix_path_namespace(path)) + CREATED_PATHS = [] + + +def get_file_for_upload(file_name): + base_upload_dir = get_config("files_for_upload") + return os.path.join(base_upload_dir, file_name) + + +def write_file(resource, content): + with open(prefix_path_namespace(resource), "w", encoding="utf-8") as f: + f.write(content) diff --git a/test/gui/shared/scripts/helpers/ReportHelper.py b/test/gui/shared/scripts/helpers/ReportHelper.py new file mode 100644 index 00000000000..c7eff43a0b1 --- /dev/null +++ b/test/gui/shared/scripts/helpers/ReportHelper.py @@ -0,0 +1,71 @@ +import os +import glob +import shutil +import test +import squish +import squishinfo + +from helpers.ConfigHelper import get_config +from helpers.FilesHelper import prefix_path_namespace + + +def get_screenrecords_path(): + return os.path.join(get_config("guiTestReportDir"), "screenrecords") + + +def get_screenshots_path(): + return os.path.join(get_config("guiTestReportDir"), "screenshots") + + +def is_video_enabled(): + return get_config("record_video_on_failure") and not reached_video_limit() + + +def reached_video_limit(): + video_report_dir = get_screenrecords_path() + if not os.path.exists(video_report_dir): + return False + entries = [f for f in os.scandir(video_report_dir) if f.is_file()] + return len(entries) >= get_config("video_record_limit") + + +def save_video_recording(filename, test_failed): + try: + # do not throw if stopVideoCapture() fails + test.stopVideoCapture() + except: + test.log("Failed to stop screen recording") + + if not (video_dir := squishinfo.resultDir): + video_dir = squishinfo.testCase + else: + test_case = "/".join(squishinfo.testCase.split("/")[-2:]) + video_dir = os.path.join(video_dir, test_case) + video_dir = os.path.join(video_dir, "attachments") + + # if the test failed + # move videos to the screenrecords directory + if test_failed: + video_files = glob.glob(f"{video_dir}/**/*.mp4", recursive=True) + screenrecords_dir = get_screenrecords_path() + if not os.path.exists(screenrecords_dir): + os.makedirs(screenrecords_dir) + # reverse the list to get the latest video first + video_files.reverse() + for idx, video in enumerate(video_files): + if idx: + file_parts = filename.rsplit(".", 1) + filename = f"{file_parts[0]}_{idx+1}.{file_parts[1]}" + shutil.move(video, os.path.join(screenrecords_dir, filename)) + # remove the video directory + shutil.rmtree(prefix_path_namespace(video_dir)) + + +def take_screenshot(filename): + directory = get_screenshots_path() + if not os.path.exists(directory): + os.makedirs(directory) + try: + squish.saveDesktopScreenshot(os.path.join(directory, filename)) + except: + test.log("Failed to save screenshot") diff --git a/test/gui/shared/scripts/helpers/SetupClientHelper.py b/test/gui/shared/scripts/helpers/SetupClientHelper.py index 0d62e17b2da..02d01ec9e43 100644 --- a/test/gui/shared/scripts/helpers/SetupClientHelper.py +++ b/test/gui/shared/scripts/helpers/SetupClientHelper.py @@ -1,125 +1,208 @@ +import os +import subprocess from urllib.parse import urlparse -import squish from os import makedirs from os.path import exists, join +import test +import psutil +import squish - -confdir = '/tmp/bdd-tests-owncloud-client/' -confFilePath = confdir + 'owncloud.cfg' +from helpers.ConfigHelper import get_config, set_config, is_windows +from helpers.ReportHelper import is_video_enabled +from helpers.UserHelper import get_username_for_user, get_password_for_user -def substituteInLineCodes(context, value): - value = value.replace('%local_server%', context.userData['localBackendUrl']) - value = value.replace( - '%secure_local_server%', context.userData['secureLocalBackendUrl'] - ) +def substitute_inline_codes(value): + value = value.replace('%local_server%', get_config('localBackendUrl')) + value = value.replace('%secure_local_server%', get_config('secureLocalBackendUrl')) + value = value.replace('%client_root_sync_path%', get_config('clientRootSyncPath')) + value = value.replace('%current_user_sync_path%', get_config('currentUserSyncPath')) value = value.replace( - '%client_root_sync_path%', context.userData['clientRootSyncPath'] - ) - value = value.replace( - '%current_user_sync_path%', context.userData['currentUserSyncPath'] - ) - value = value.replace( - '%local_server_hostname%', urlparse(context.userData['localBackendUrl']).netloc + '%local_server_hostname%', urlparse(get_config('localBackendUrl')).netloc ) + value = value.replace('%home%', get_config('home_dir')) return value -def getClientDetails(context): - clientDetails = {'server': '', 'user': '', 'password': ''} - for row in context.table[0:]: - row[1] = substituteInLineCodes(context, row[1]) +def get_client_details(details): + client_details = { + 'server': '', + 'user': '', + 'password': '', + 'sync_folder': '', + 'oauth': False, + } + for row in details: + row[1] = substitute_inline_codes(row[1]) if row[0] == 'server': - clientDetails.update({'server': row[1]}) + client_details.update({'server': row[1]}) elif row[0] == 'user': - clientDetails.update({'user': row[1]}) - elif row[0] == 'password': - clientDetails.update({'password': row[1]}) - return clientDetails + client_details.update({'user': get_username_for_user(row[1])}) + client_details.update({'password': get_password_for_user(row[1])}) + elif row[0] == 'sync_folder': + client_details.update({'sync_folder': row[1]}) + return client_details -def createUserSyncPath(context, username): - userSyncPath = join(context.userData['clientRootSyncPath'], username) - if not exists(userSyncPath): - makedirs(userSyncPath) +def create_user_sync_path(username): + # '' at the end adds '/' to the path + user_sync_path = join(get_config('clientRootSyncPath'), username, '') - setCurrentUserSyncPath(context, username) - return userSyncPath + if not exists(user_sync_path): + makedirs(user_sync_path) + set_current_user_sync_path(user_sync_path) + return user_sync_path.replace('\\', '/') -def getUserSyncPath(context, username): - return createUserSyncPath(context, username) +def create_space_path(space='Personal'): + space_path = join(get_config('currentUserSyncPath'), space, '') + if not exists(space_path): + makedirs(space_path) + return space_path.replace('\\', '/') -def setCurrentUserSyncPath(context, user): - syncPath = join(context.userData['clientRootSyncPath'], user, '') - context.userData['currentUserSyncPath'] = syncPath +def set_current_user_sync_path(sync_path): + set_config('currentUserSyncPath', sync_path) -def getResourcePath(context, resource, user=None): - resource == resource.strip('/') - if not user == None: - return join(context.userData['clientRootSyncPath'], user, resource) - else: - return join(context.userData['currentUserSyncPath'], resource) + +def get_resource_path(resource='', user='', space=''): + sync_path = get_config('currentUserSyncPath') + if user: + sync_path = user + space = space or get_config('syncConnectionName') + sync_path = join(sync_path, space) + sync_path = join(get_config('clientRootSyncPath'), sync_path) + resource = resource.replace(sync_path, '').strip('/').strip('\\') + if is_windows(): + resource = resource.replace('/', '\\') + return join( + sync_path, + resource, + ) + + +def get_temp_resource_path(resource_name): + return join(get_config('tempFolderPath'), resource_name) -def startClient(context): +def get_current_user_sync_path(): + return get_config('currentUserSyncPath') + + +def start_client(): + check_keyring() + squish.startApplication( - "owncloud -s" - + " --logfile " - + context.userData['clientConfigFile'] - + " --logdebug" - + " --logflush" - + " --confdir " - + confdir + f'{get_config("client_name").lower()} -s' + + f' --logfile {get_config("clientLogFile")}' + + ' --logdebug' + + ' --logflush' ) + if is_video_enabled(): + test.startVideoCapture() + else: + test.log( + f'Video recordings reached the maximum limit of {get_config("video_record_limit")}.' + + 'Skipping video recording...' + ) + + +def is_app_killed(pid): + try: + psutil.Process(pid) + return False + except psutil.NoSuchProcess: + return True + + +def wait_until_app_killed(pid=0): + timeout = 5 * 1000 + killed = squish.waitFor( + lambda: is_app_killed(pid), + timeout, + ) + if not killed: + test.log(f'Application was not terminated within {timeout} milliseconds') + + +# sometimes the keyring is locked during the test execution, and we need to unlock it +def check_keyring(): + if is_windows(): + return + + if is_keyring_locked(): + test.log('[INFO] Keyring is locked or service is down. Unlocking...') + wait_until_keyring_unlocked() + + +def unlock_keyring() -> bool | None: + if is_windows(): + return None + + password = os.getenv('VNC_PW') + command = f'echo -n "{password}" | gnome-keyring-daemon -r --unlock' + stdout, stderr, returncode = run_sys_command(command, True) + + output = '' + if stdout: + output = stdout.decode('utf-8') + if stderr: + output = stderr.decode('utf-8') + test.log(f'[INFO] Keyring unlock output: {output}') + # wait for keyring to unlock squish.snooze(1) + if returncode: + return False + + return not is_keyring_locked() -def getPollingInterval(): - pollingInterval = '''[ownCloud] - remotePollInterval={pollingInterval} - ''' - args = {'pollingInterval': 5000} - pollingInterval = pollingInterval.format(**args) - return pollingInterval - - -def setUpClient(context, username, displayName, confFilePath): - userSetting = ''' - [Accounts] - 0/Folders/1/ignoreHiddenFiles=true - 0/Folders/1/localPath={client_sync_path} - 0/Folders/1/paused=false - 0/Folders/1/targetPath=/ - 0/Folders/1/version=2 - 0/Folders/1/virtualFilesMode=off - 0/dav_user={davUserName} - 0/display-name={displayUserName} - 0/http_oauth=false - 0/http_user={davUserName} - 0/url={local_server} - 0/user={displayUserFirstName} - 0/version=1 - version=2 - ''' - userFirstName = username.split() - userSetting = userSetting + getPollingInterval() - - syncPath = createUserSyncPath(context, userFirstName[0]) - - args = { - 'displayUserName': displayName, - 'davUserName': userFirstName[0].lower(), - 'displayUserFirstName': userFirstName[0], - 'client_sync_path': syncPath, - 'local_server': context.userData['localBackendUrl'], - } - userSetting = userSetting.format(**args) - configFile = open(confFilePath, "w") - configFile.write(userSetting) - configFile.close() - startClient(context) +def wait_until_keyring_unlocked(): + if is_windows(): + return + + timeout = 10 + unlocked = squish.waitFor( + lambda: unlock_keyring(), # pylint: disable=unnecessary-lambda + timeout * 1000, + ) + if not unlocked: + test.fail(f'Timeout. Keyring was not unlocked within {timeout} seconds') + + +def is_keyring_locked() -> bool | None: + if is_windows(): + return None + + stdout, stderr, _ = run_sys_command( + [ + 'busctl', + '--user', + 'get-property', + 'org.freedesktop.secrets', + '/org/freedesktop/secrets/collection/login', + 'org.freedesktop.Secret.Collection', + 'Locked', + ] + ) + output = '' + if stdout: + output = stdout.decode('utf-8') + if stderr: + output = stderr.decode('utf-8') + test.log(f'[INFO] Keyring locked status: {output}') + return not output.strip().endswith('false') + + +def run_sys_command(command=None, shell=False): + cmd = subprocess.run( + command, + shell=shell, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + return cmd.stdout, cmd.stderr, cmd.returncode diff --git a/test/gui/shared/scripts/helpers/SpaceHelper.py b/test/gui/shared/scripts/helpers/SpaceHelper.py new file mode 100644 index 00000000000..8bae24b56a2 --- /dev/null +++ b/test/gui/shared/scripts/helpers/SpaceHelper.py @@ -0,0 +1,149 @@ +import json +from urllib import parse + +from helpers.ConfigHelper import get_config +from helpers.api.utils import url_join +import helpers.api.http_helper as request + +created_spaces = {} +user_spaces = {} +space_role = ['manager', 'editor', 'viewer'] + + +def get_space_endpint(): + return url_join(get_config('localBackendUrl'), 'graph', 'v1.0', 'drives') + + +def get_dav_endpint(): + return url_join(get_config('localBackendUrl'), 'dav', 'spaces') + + +def get_share_endpint(): + return url_join( + get_config('localBackendUrl'), + 'ocs/v2.php/apps', + 'files_sharing/api/v1/shares', + ) + + +def create_space(space_name): + body = json.dumps({'name': space_name}) + response = request.post(get_space_endpint(), body) + request.assert_http_status(response, 201, f'Failed to create space {space_name}') + # save created space + resp_object = response.json() + created_spaces[space_name] = resp_object['id'] + + +def fetch_spaces(user=None, query=''): + if query: + query = '?' + query + url = get_space_endpint() + query + response = request.get(url=url, user=user) + request.assert_http_status(response, 200, 'Failed to get spaces') + return response.json()['value'] + + +def get_project_spaces(user=None): + search_query = '$filter=driveType eq \'project\'' + return fetch_spaces(query=search_query, user=user) + + +def get_space_id(space_name, user=None): + spaces = {**created_spaces, **user_spaces} + if not space_name in spaces.keys(): + return fetch_space_id(space_name, user) + return spaces.get(space_name) + + +def fetch_space_id(space_name, user=None): + spaces = fetch_spaces(user=user) + space_id = None + for space in spaces: + if space['name'] == space_name: + user_spaces[space_name] = space['id'] + space_id = space['id'] + break + return space_id + + +def delete_project_spaces(): + global created_spaces, user_spaces + for _, space_id in created_spaces.items(): + disable_project_space(space_id) + delete_project_space(space_id) + created_spaces = {} + user_spaces = {} + + +def disable_project_space(space_id): + url = url_join(get_space_endpint(), space_id) + response = request.delete(url) + request.assert_http_status(response, 204, f'Failed to disable space {space_id}') + + +def delete_project_space(space_id): + url = url_join(get_space_endpint(), space_id) + response = request.delete(url, {'Purge': 'T'}) + request.assert_http_status(response, 204, f'Failed to delete space {space_id}') + + +def create_space_folder(space_name, folder_name): + space_id = get_space_id(space_name) + url = url_join(get_dav_endpint(), space_id, folder_name) + response = request.mkcol(url) + request.assert_http_status( + response, + 201, + f'Failed to create folder "{folder_name}" in space "{space_name}"', + ) + + +def create_space_file(space_name, file_name, content): + space_id = get_space_id(space_name) + url = url_join(get_dav_endpint(), space_id, file_name) + response = request.put(url, content) + if response.status_code not in (201, 204): + raise AssertionError( + f"Creating file '{file_name}' in space '{space_name}' failed with {response.status_code}\n" + + response.text + ) + + +def add_user_to_space(user, space_name, role): + role = role.lower() + if not role in space_role: + raise ValueError(f"Cannot set the role '{role}' to a space") + + space_id = get_space_id(space_name) + url = get_share_endpint() + body = parse.urlencode( + { + 'space_ref': space_id, + 'shareType': 7, + 'shareWith': user, + 'role': role, + } + ) + headers = {'Content-Type': 'application/x-www-form-urlencoded'} + response = request.post(url, body, headers) + request.assert_http_status( + response, 200, f'Failed to add user "{user}" to space "{space_name}"' + ) + + +def get_file_content(space_name, file_name, user=None): + space_id = get_space_id(space_name, user) + url = url_join(get_dav_endpint(), space_id, file_name) + response = request.get(url=url, user=user) + request.assert_http_status(response, 200, f'Failed to get file "{file_name}"') + return response.text + + +def resource_exists(space_name, resource, user=None): + space_id = get_space_id(space_name, user) + url = url_join(get_dav_endpint(), space_id, resource) + response = request.get(url=url, user=user) + if response.status_code == 200: + return True + return False diff --git a/test/gui/shared/scripts/helpers/StacktraceHelper.py b/test/gui/shared/scripts/helpers/StacktraceHelper.py new file mode 100644 index 00000000000..9592589f8c5 --- /dev/null +++ b/test/gui/shared/scripts/helpers/StacktraceHelper.py @@ -0,0 +1,73 @@ +import os +import subprocess +import glob +import re +from datetime import datetime +from helpers.ConfigHelper import is_windows + + +def get_core_dumps(): + # TODO: find a way to use coredump in windows + if is_windows(): + return False + # read coredump location + with open('/proc/sys/kernel/core_pattern', 'r', encoding='utf-8') as f: + coredump_path = f.read().strip('\n') + + # yields something like: /tmp/core-*-*-*-* + coredump_file_pattern = re.sub(r'%[a-zA-Z]{1}', '*', coredump_path) + return glob.glob(coredump_file_pattern) + + +def generate_stacktrace(scenario_title, coredumps): + message = ['###########################################'] + message.append(f'Scenario: {scenario_title}') + + for coredump_file in coredumps: + message.append(parse_stacktrace(coredump_file)) + + message.append('###########################################') + message.append('') + stacktrace = '\n'.join(message) + + stacktrace_file = os.environ.get('STACKTRACE_FILE', '../stacktrace.log') + # save stacktrace to a file + with open(stacktrace_file, 'a', encoding='utf-8') as f: + f.write(stacktrace) + + +def parse_stacktrace(coredump_file): + message = [] + if coredump_file: + coredump_filename = os.path.basename(coredump_file) + # example coredump file: core-1648445754-1001-11-!drone!src!build-GUI-tests!bin!owncloud + patterns = coredump_filename.split('-') + app_binary = '-'.join(patterns[4:]).replace('!', '/') + + message.append('-------------------------------------------') + message.append(f'Executable: {app_binary}') + message.append(f'Timestamp: {str(datetime.fromtimestamp(float(patterns[1])))}') + message.append(f'Process ID: {patterns[2]}') + message.append(f'Signal Number: {patterns[3]}') + message.append('-------------------------------------------') + message.append('<<<<< STACKTRACE START >>>>>') + message.append( + subprocess.run( + [ + 'gdb', + app_binary, + coredump_file, + '-batch', + '-ex', + 'bt full', + ], + stdout=subprocess.PIPE, + check=False, + ).stdout.decode('utf-8') + ) + message.append('<<<<< STACKTRACE END >>>>>') + + # remove coredump file + os.unlink(coredump_file) + + return '\n'.join(message) diff --git a/test/gui/shared/scripts/helpers/SyncHelper.py b/test/gui/shared/scripts/helpers/SyncHelper.py new file mode 100644 index 00000000000..a17fe214c2e --- /dev/null +++ b/test/gui/shared/scripts/helpers/SyncHelper.py @@ -0,0 +1,340 @@ +import os +import re +import sys +import test +import squish +import helpers.api.http_helper as request + +from helpers.ConfigHelper import get_config, is_linux, is_windows +from helpers.FilesHelper import sanitize_path + +if is_windows(): + from helpers.WinPipeHelper import WinPipeConnect as SocketConnect +else: + # NOTE: 'syncstate.py' was removed from client + # and is now available at https://github.com/owncloud/client-desktop-shell-integration-nautilus + # check if 'syncstate.py' is available, if not, download it + custom_lib = get_config('custom_lib') + syncstate_lib_file = os.path.join(custom_lib, 'syncstate.py') + os.makedirs(custom_lib, exist_ok=True) + + if not os.path.exists(syncstate_lib_file): + response = request.get( + ( + 'https://raw.githubusercontent.com/' + 'owncloud/client-desktop-shell-integration-nautilus/master/src/syncstate.py' + ) + ) + assert response.status_code == 200, 'Failed to get content of syncstate file' + with open(syncstate_lib_file, 'w', encoding='utf-8') as f: + f.write(response.text) + + # the script needs to use the system-wide python + # to switch from the built-in interpreter + # see https://kb.froglogic.com/squish/howto/using-external-python-interpreter-squish-6-6/ + # if the IDE fails to reference the script, + # add the folder in Edit->Preferences->PyDev->Interpreters->Libraries + sys.path.append(custom_lib) + from custom_lib.syncstate import SocketConnect + +# socket messages +socket_messages = [] +SOCKET_CONNECT = None +# Whether wait has been made or not after account is set up +# This is useful for waiting only for the first time +WAITED_AFTER_SYNC = False + +# File syncing in client has the following status +SYNC_STATUS = { + 'SYNC': 'STATUS:SYNC', # sync in progress + 'OK': 'STATUS:OK', # sync completed + 'ERROR': 'STATUS:ERROR', # sync error + 'IGNORE': 'STATUS:IGNORE', # sync ignored + 'NOP': 'STATUS:NOP', # not in sync yet + 'REGISTER': 'REGISTER_PATH', + 'UNREGISTER': 'UNREGISTER_PATH', + 'UPDATE': 'UPDATE_VIEW', +} + +SYNC_PATTERNS = { + # default sync patterns for the initial sync (after adding account) + # the pattern can be of TWO types depending on the available resources (files/folders) + 'initial': [ + # when adding account via New Account wizard + [ + SYNC_STATUS['REGISTER'], + SYNC_STATUS['UPDATE'], + SYNC_STATUS['UPDATE'], + ], + # when syncing empty account (hidden files are ignored) + [SYNC_STATUS['UPDATE'], SYNC_STATUS['OK']], + # when syncing an account that has some files/folders + [SYNC_STATUS['SYNC'], SYNC_STATUS['OK']], + ], + 'root_synced': [ + [ + SYNC_STATUS['SYNC'], + SYNC_STATUS['OK'], + SYNC_STATUS['OK'], + SYNC_STATUS['OK'], + SYNC_STATUS['UPDATE'], + ], + [ + SYNC_STATUS['SYNC'], + SYNC_STATUS['UPDATE'], + SYNC_STATUS['OK'], + SYNC_STATUS['OK'], + SYNC_STATUS['OK'], + SYNC_STATUS['UPDATE'], + ], + ], + 'single_synced': [SYNC_STATUS['SYNC'], SYNC_STATUS['OK']], + 'error': [SYNC_STATUS['ERROR']], +} + +# checking initial sync of Personal sync shows these sync status +if is_windows(): + SYNC_PATTERNS['initial'].append([SYNC_STATUS['UPDATE'], SYNC_STATUS['UPDATE']]) + + +def get_socket_connection(): + global SOCKET_CONNECT + if not SOCKET_CONNECT or not SOCKET_CONNECT.connected: + SOCKET_CONNECT = SocketConnect() + return SOCKET_CONNECT + + +def delete_socket_connection(): + global SOCKET_CONNECT + SOCKET_CONNECT = None + + +def read_socket_messages(): + messages = [] + socket_connect = get_socket_connection() + socket_connect.read_socket_data_with_timeout(0.1) + for line in socket_connect.get_available_responses(): + messages.append(line) + return messages + + +def read_and_update_socket_messages(): + messages = read_socket_messages() + return update_socket_messages(messages) + + +def update_socket_messages(messages): + socket_messages.extend(filter_sync_messages(messages)) + return socket_messages + + +def clear_socket_messages(resource=''): + global socket_messages + if resource: + resource_messages = set(filter_messages_for_item(socket_messages, resource)) + socket_messages = [ + msg for msg in socket_messages if msg not in resource_messages + ] + else: + socket_messages.clear() + + +def close_socket_connection(): + socket_messages.clear() + if SOCKET_CONNECT: + SOCKET_CONNECT.connected = False + if is_windows(): + SOCKET_CONNECT.close_conn() + elif is_linux(): + SOCKET_CONNECT._sock.close() # pylint: disable=protected-access + + +def get_initial_sync_patterns(): + return SYNC_PATTERNS['initial'] + + +def get_synced_pattern(resource=''): + # get only the resource path + sync_path = get_config('currentUserSyncPath') + if get_config('ocis'): + sync_path = os.path.join(sync_path, get_config('syncConnectionName')) + + if resource := resource.replace(sync_path, '').strip('\\').strip('/'): + return SYNC_PATTERNS['single_synced'] + return SYNC_PATTERNS['root_synced'] + + +# generate sync pattern from the socket messages +# +# returns List +# e.g: ['UPDATE_VIEW', 'STATUS:OK'] +def generate_sync_pattern_from_messages(messages): + pattern = [] + if not messages: + return pattern + + sync_messages = filter_sync_messages(messages) + for message in sync_messages: + # E.g; from "STATUS:OK:/tmp/client-bdd/Alice/" + # excludes ":/tmp/client-bdd/Alice/" + # adds only "STATUS:OK" to the pattern list + if match := re.search(':(/|[A-Z]{1}:\\\\).*', message): + (end, _) = match.span() + # shared resources will have status like "STATUS:OK+SWM" + status = message[:end].replace('+SWM', '') + pattern.append(status) + return pattern + + +# strip out the messages that are not related to sync +def filter_sync_messages(messages): + start_idx = 0 + if 'GET_STRINGS:END' in messages: + start_idx = messages.index('GET_STRINGS:END') + 1 + return messages[start_idx:] + + +def filter_messages_for_item(messages, item): + filtered_messages = [] + for msg in messages: + msg = msg.rstrip('/').rstrip('\\') + item = item.rstrip('/').rstrip('\\') + if msg.endswith(item) and not msg.startswith(SYNC_STATUS['NOP']): + filtered_messages.append(msg) + return filtered_messages + + +def listen_sync_status_for_item(item, resource_type='FOLDER'): + if (resource_type := resource_type.upper()) not in ('FILE', 'FOLDER'): + raise ValueError('resource_type must be "FILE" or "FOLDER"') + socket_connect = get_socket_connection() + item = item.rstrip('\\') + socket_connect.sendCommand(f'RETRIEVE_{resource_type}_STATUS:{item}\n') + + +def get_current_sync_status(resource, resource_type): + listen_sync_status_for_item(resource, resource_type) + messages = filter_messages_for_item(read_socket_messages(), resource) + # return the last message from the list + return messages[-1] + + +def wait_for_resource_to_sync(resource, resource_type='FOLDER', patterns=None): + listen_sync_status_for_item(resource, resource_type) + + timeout = get_config('maxSyncTimeout') * 1000 + + if patterns is None: + patterns = get_synced_pattern(resource) + + synced = squish.waitFor( + lambda: has_sync_pattern(patterns, resource), + timeout, + ) + clear_socket_messages(resource) + if not synced: + # if the sync pattern doesn't match then check the last sync status + # and pass the step if the last sync status is STATUS:OK + status = get_current_sync_status(resource, resource_type) + if status.startswith(SYNC_STATUS['OK']): + test.log( + '[WARN] Failed to match sync pattern for resource: ' + + resource + + f'\nBut its last status is "{SYNC_STATUS["OK"]}"' + + '. So passing the step.' + ) + return + raise TimeoutError( + 'Timeout while waiting for sync to complete for ' + + str(timeout) + + ' milliseconds' + ) + + +def wait_for_initial_sync_to_complete(path): + wait_for_resource_to_sync( + path, + 'FOLDER', + get_initial_sync_patterns(), + ) + + +def has_sync_pattern(patterns, resource=None): + if isinstance(patterns[0], str): + patterns = [patterns] + messages = read_and_update_socket_messages() + if resource: + messages = filter_messages_for_item(messages, resource) + for pattern in patterns: + pattern_len = len(pattern) + for idx, _ in enumerate(messages): + actual_pattern = generate_sync_pattern_from_messages( + messages[idx : idx + pattern_len] + ) + if len(actual_pattern) < pattern_len: + break + if pattern_len == len(actual_pattern) and pattern == actual_pattern: + return True + # 100 milliseconds polling interval + squish.snooze(0.1) + return False + + +# Using socket API to check file sync status +def has_sync_status(item_name, status): + sync_messages = read_and_update_socket_messages() + sync_messages = filter_messages_for_item(sync_messages, item_name) + for line in sync_messages: + line = line.rstrip('/').rstrip('\\') + item_name = item_name.rstrip('/').rstrip('\\') + if line.startswith(status) and line.endswith(item_name): + return True + return False + + +# useful for checking sync status such as 'error', 'ignore' +# but not quite so reliable for checking 'ok' sync status +def wait_for_resource_to_have_sync_status( + resource, resource_type, status=SYNC_STATUS['OK'], timeout=None +): + resource = sanitize_path(resource) + + listen_sync_status_for_item(resource, resource_type) + + if not timeout: + timeout = get_config('maxSyncTimeout') * 1000 + + result = squish.waitFor( + lambda: has_sync_status(resource, status), + timeout, + ) + + if not result: + if status == SYNC_STATUS['ERROR']: + expected = 'have sync error' + elif status == SYNC_STATUS['IGNORE']: + expected = 'be sync ignored' + else: + expected = 'be synced' + raise ValueError( + f'Expected {resource_type} "{resource}" to {expected}, but not.' + ) + + +def wait_for_resource_to_have_sync_error(resource, resource_type): + wait_for_resource_to_have_sync_status(resource, resource_type, SYNC_STATUS['ERROR']) + + +# performing actions immediately after completing the sync from the server does not work +# The test should wait for a while before performing the action +# issue: https://github.com/owncloud/client/issues/8832 +def wait_for_client_to_be_ready(): + global WAITED_AFTER_SYNC + if not WAITED_AFTER_SYNC: + squish.snooze(get_config('minSyncTimeout')) + WAITED_AFTER_SYNC = True + + +def clear_waited_after_sync(): + global WAITED_AFTER_SYNC + WAITED_AFTER_SYNC = False diff --git a/test/gui/shared/scripts/helpers/UserHelper.py b/test/gui/shared/scripts/helpers/UserHelper.py new file mode 100644 index 00000000000..4e335a03243 --- /dev/null +++ b/test/gui/shared/scripts/helpers/UserHelper.py @@ -0,0 +1,100 @@ +from base64 import b64encode +from typing import NamedTuple +import json +import os + +from helpers.ConfigHelper import get_config + + +class User(NamedTuple): + username: str + password: str + displayname: str + email: str + + +test_users = { + "admin": User( + username="admin", + password="admin", + displayname="adminUsername", + email="admin@example.org", + ), + "Alice": User( + username="Alice", + password="1234", + displayname="Alice Hansen", + email="alice@example.org", + ), + "Brian": User( + username="Brian", + password="AaBb2Cc3Dd4", + displayname="Brian Murphy", + email="brian@example.org", + ), + "Carol": User( + username="Carol", + password="1234", + displayname="Carol King", + email="carol@example.org", + ), + "David": User( + username="David", + password="1234", + displayname="David Lopez", + email="david@example.org", + ), +} + + +def get_default_password(): + return "1234" + + +def basic_auth_header(user=None, password=None): + if not user and not password: + user = "admin" + password = "admin" + elif not user == "public" and not password: + password = get_password_for_user(user) + + token = b64encode((f"{user}:{password}").encode()).decode() + return {"Authorization": "Basic " + token} + + +def get_user_info(username, attribute): + if username in test_users: + return getattr(test_users[username], attribute) + if attribute == "password": + return get_default_password() + raise ValueError(f"Invalid user attribute: {attribute}") + + +def get_displayname_for_user(username): + return get_user_info(username, "displayname") + + +def get_password_for_user(username): + return get_user_info(username, "password") + + +def get_username_for_user(username): + return get_user_info(username, "username") + + +def init_predefined_users(): + with open( + os.path.abspath("../" + get_config("predefined_users")), encoding="utf-8" + ) as f: + users = json.load(f) + for key, value in users.items(): + test_users.update( + { + key: User( + username=value["username"], + password=value["password"], + displayname=value["displayname"], + email=value["email"], + ) + } + ) diff --git a/test/gui/shared/scripts/helpers/WebUIHelper.py b/test/gui/shared/scripts/helpers/WebUIHelper.py new file mode 100644 index 00000000000..b998d7616d9 --- /dev/null +++ b/test/gui/shared/scripts/helpers/WebUIHelper.py @@ -0,0 +1,51 @@ +import squish +from playwright.sync_api import sync_playwright + +from helpers.ConfigHelper import is_owncloud_client + +envs = {} + + +def get_clipboard_text(): + try: + return squish.getClipboardText() + except: + # Retry after 2 seconds + squish.snooze(2) + return squish.getClipboardText() + + +def authorize_via_webui(username, password): + global envs + envs = { + 'OC_USERNAME': username.strip('"'), + 'OC_PASSWORD': password.strip('"'), + 'OC_AUTH_URL': get_clipboard_text(), + } + + with sync_playwright() as p: + browser = p.chromium.launch() + context = browser.new_context(ignore_https_errors=True) + page = context.new_page() + page.goto(envs['OC_AUTH_URL']) + oidc_login(page) + context.close() + browser.close() + + +def oidc_login(page): + if not is_owncloud_client(): + page.fill('#email', envs['OC_USERNAME']) + page.click('span:text-is("Next")') + page.fill('#password', envs['OC_PASSWORD']) + page.click('span:text-is("Sign in")') + page.wait_for_selector('text=Successfully signed in') + else: + # login + page.fill('#oc-login-username', envs['OC_USERNAME']) + page.fill('#oc-login-password', envs['OC_PASSWORD']) + page.click('button[type=submit]') + # allow permissions + page.click('button >> text=Allow') + # confirm successful login + page.wait_for_selector('text=Successfully signed in') diff --git a/test/gui/shared/scripts/helpers/WinPipeHelper.py b/test/gui/shared/scripts/helpers/WinPipeHelper.py new file mode 100644 index 00000000000..e82e4fca069 --- /dev/null +++ b/test/gui/shared/scripts/helpers/WinPipeHelper.py @@ -0,0 +1,138 @@ +import os +import time + +# pylint: disable=import-error +import win32pipe +import win32file +import winerror +import pywintypes +import win32event + +TIMEOUT = 100 +DEFAULT_BUFLEN = 4096 +CLIENT_MESSAGES = [ + 'STATUS', + 'REGISTER_PATH', + 'UNREGISTER_PATH', + 'UPDATE_VIEW', + 'GET_STRINGS', + 'STRING', + 'VERSION', + 'SHARE', + 'GET_MENU_ITEMS', + 'MENU_ITEM', +] + + +def get_pipe_path(): + pipename = r'\\.\\pipe\\' + pipename = os.path.join(pipename, 'ownCloud-' + os.getenv('USERNAME')) + return pipename + + +class WinPipeConnect: + def __init__(self): + self.connected = False + self._pipe = None + self._remainder = ''.encode('utf-8') + self._overlapped = pywintypes.OVERLAPPED() + self._overlapped.hEvent = win32event.CreateEvent(None, 1, 0, None) + self.connect_to_pipe_server() + + def connect_to_pipe_server(self): + try: + pipename = get_pipe_path() + + self._pipe = win32file.CreateFile( + pipename, + win32file.GENERIC_READ | win32file.GENERIC_WRITE, + 0, + None, + win32file.OPEN_EXISTING, + win32file.FILE_FLAG_OVERLAPPED, + None, + ) + + if self._pipe == win32file.INVALID_HANDLE_VALUE: + win32pipe.CloseHandle(self._pipe) + raise OSError('Invalid _pipe value') + + self.connected = True + + self.sendCommand('VERSION:\n') + self.sendCommand('GET_STRINGS:\n') + except Exception as e: # pylint: disable=broad-except + print(f'Could not connect to named pipe {pipename}\n' + str(e)) + win32file.CloseHandle(self._pipe) + + def sendCommand(self, cmd): # pylint: disable=invalid-name + if self.connected: + w_res, _ = win32file.WriteFile( + self._pipe, cmd.encode('utf-8'), self._overlapped + ) + if w_res == winerror.ERROR_IO_PENDING: + res = win32event.WaitForSingleObject(self._overlapped.hEvent, TIMEOUT) + if res != win32event.WAIT_OBJECT_0: + print('Sending timed out!') + return False + if not win32file.GetOverlappedResult( + self._pipe, self._overlapped, False + ): + print('GetOverlappedResult failed') + return False + else: + print('Cannot send, not connected!') + return False + return True + + # Reads data that becomes available. + # New responses can be accessed with get_available_responses(). + def read_socket_data_with_timeout(self, timeout): + messages = b'' + start_time = time.time() + while True: # pylint: disable=too-many-nested-blocks + if (time.time() - start_time) >= timeout: + self._remainder += messages + break + + peek_bytes = win32pipe.PeekNamedPipe(self._pipe, DEFAULT_BUFLEN)[1] + if isinstance(peek_bytes, int) and peek_bytes > 0: + _, message_mem = win32file.ReadFile( + self._pipe, DEFAULT_BUFLEN, self._overlapped + ) + if message_mem: + m_bytes = bytes(message_mem) + if b'\n' in m_bytes: + for m in m_bytes.split(b'\n'): + try: + # append decodable and client specific bytes + m.decode('utf-8') + start = m.split(b':', 1)[0] + if start.decode('utf-8') in CLIENT_MESSAGES: + messages += m + b'\n' + except: + pass + + else: + res = win32event.WaitForSingleObject( + self._overlapped.hEvent, int(timeout * 1000) + ) + if res != win32event.WAIT_OBJECT_0: + print('Reading timed out!') + return False + if not win32file.GetOverlappedResult( + self._pipe, self._overlapped, False + ): + return False + return True + + # Parses response lines out of collected data, returns list of strings + def get_available_responses(self): + if (end := self._remainder.rfind(b'\n')) == -1: + return [] + data = self._remainder[:end] + self._remainder = self._remainder[end + 1 :] + return data.decode('utf-8').split('\n') + + def close_conn(self): + win32file.CloseHandle(self._pipe) diff --git a/test/gui/shared/scripts/helpers/api/external_api.py b/test/gui/shared/scripts/helpers/api/external_api.py new file mode 100644 index 00000000000..4c37058b607 --- /dev/null +++ b/test/gui/shared/scripts/helpers/api/external_api.py @@ -0,0 +1,437 @@ +import os +import json + +import helpers.api.http_helper as request +from helpers.api.utils import url_join +from helpers.ConfigHelper import get_config +from helpers.FilesHelper import write_file +from helpers.UserHelper import get_username_for_user +from helpers.api.provisioning import created_users +from helpers.FilesHelper import get_file_for_upload + +tokens = {} +personal_sync_folder_id = {} +created_spaces = {} +available_roles = { + 'viewer': 6, + 'downloader': 2, + 'uploader': 7, + 'collaborator': 3, + 'manager': 4, +} + + +def get_headers(user, extra_headers=None): + token = generate_xsrf_token(user) + headers = { + 'x-accellion-version': '28', + 'x-xsrf-token': token['x_xsrf_token'], + 'cookie': f'ACC-XSRF-TOKEN={token["acc_xsrf_token"]}; web_token={token["web_token"]}', + } + if extra_headers: + headers.update(extra_headers) + return headers + + +def generate_xsrf_token(user): + if user in tokens: + return tokens[user] + url = url_join(get_config('localBackendUrl'), 'rest/users/actions/login') + headers = {'content-type': 'application/json', 'x-accellion-version': '28'} + for created_user in created_users.values(): + if created_user['username'] == user: + password = created_user['password'] + break + body = json.dumps({'username': user, 'password': password}) + response = request.send_request(url, 'POST', body, headers) + assert response.status_code == 200, 'Failed to get the token' + + acc_xsrf_token = None + web_token = None + x_xsrf_token = None + for cookie in response.cookies: + if cookie.name == 'ACC-XSRF-TOKEN': + acc_xsrf_token = cookie.value + x_xsrf_token = cookie.value + elif cookie.name == 'web_token': + web_token = cookie.value + tokens[user] = { + 'acc_xsrf_token': acc_xsrf_token, + 'web_token': web_token, + 'x_xsrf_token': x_xsrf_token, + } + return tokens[user] + + +def resource_exists(user, resource, space=None): + user = get_username_for_user(user) + resource_name = os.path.basename(resource) + folder_path = os.path.dirname(resource) + if space: + folder_id = get_space_id(user, space) + else: + folder_id = get_folder_id_by_path(user, folder_path) + return get_resource(user, resource_name, folder_id) is not None + + +def get_file_content(user, resource, space=None): + user = get_username_for_user(user) + r = get_resource(user, resource, space=space) + url = url_join(get_config('localBackendUrl'), f'rest/files/{r["id"]}/content') + headers = get_headers(user) + response = request.get(url, headers=headers) + assert response.status_code == 200, f'Could not get content of file: {resource}' + return response.text + + +def create_folder(user, folder_name): + user = get_username_for_user(user) + folder_to_create = os.path.basename(folder_name) + folder_path = os.path.dirname(folder_name) + folder_id = get_folder_id_by_path(user, folder_path) + url = url_join( + get_config('localBackendUrl'), + f'rest/folders/{folder_id}/folders', + ) + headers = get_headers(user, {'content-type': 'application/json'}) + body = json.dumps( + { + 'description': '', + 'fileLifetime': '0', + 'name': folder_to_create, + 'syncable': '1', + } + ) + response = request.post(url, body, headers) + assert ( + response.status_code == 201 + ), f'Could not create the folder: {folder_name} for user {user}' + + +def get_folder_id_by_path(user, folder_path=None): + parts = folder_path.split(os.sep) + current_folder_id = get_personal_sync_folder_id(user) + if not folder_path: + return current_folder_id + + for part in parts: + if folder := get_resource(user, part, current_folder_id, 'd'): + current_folder_id = folder['id'] + return current_folder_id + + +def upload_file(user, file_name, file_content=None): + user = get_username_for_user(user) + filename = os.path.basename(file_name) + if file_content: + # create file in temp folder before uploading + file_path = f'{get_config("tempFolderPath")}/{filename}' + write_file(file_path, file_content) + else: + file_path = get_file_for_upload(filename) + file_size = os.path.getsize(file_path) + folder_path = os.path.dirname(file_name) + folder_id = get_folder_id_by_path(user, folder_path) + + # initiate upload + upload_uri = initiate_upload(user, filename, file_size, folder_id) + + # finalize upload + finalize_upload(user, filename, file_path, file_size, upload_uri) + + +def initiate_upload(user, file_name, file_size, folder_id): + url = url_join( + get_config('localBackendUrl'), + f'rest/folders/{folder_id}/actions/initiateUpload', + ) + headers = get_headers(user, {'content-type': 'application/json'}) + json_body = json.dumps( + {'filename': file_name, 'totalSize': file_size, 'totalChunks': 1} + ) + + response = request.post(url, body=json_body, headers=headers) + assert ( + response.status_code == 201 + ), f'Failed to initiate upload for file {file_name}.' + + data = json.loads(response.text) + return data['uri'] + + +def finalize_upload(user, file_name, file_path, file_size, upload_uri): + url = url_join(get_config('localBackendUrl'), upload_uri) + headers = get_headers(user) + with open(file_path, 'rb') as f: + files = {'content': (file_name, f, 'application/octet-stream')} + body = { + 'compressionMode': 'NORMAL', + 'compressionSize': file_size, + 'originalSize': file_size, + 'index': 0, + } + response = request.post(url, body=body, headers=headers, files=files) + assert response.status_code == 201, f'Failed to upload file {file_name}.' + + +def delete_resource(user, resource): + user = get_username_for_user(user) + r = get_resource(user, resource) + if r['type'] == 'd': + uri = 'folders' + else: + uri = 'files' + url = url_join( + get_config('localBackendUrl'), + f'rest/{uri}?partialSuccess=true&id:in={r["id"]}', + ) + headers = get_headers(user) + response = request.delete(url, headers) + assert response.status_code == 204, f'Cound not delete resource: {resource}' + + +def get_personal_sync_folder_id(user): + if user in personal_sync_folder_id: + return personal_sync_folder_id[user] + + url = url_join(get_config('localBackendUrl'), 'rest/folders/top') + headers = get_headers(user) + response = request.get(url, headers) + assert ( + response.status_code == 200 + ), f'Failed to get the personal sync folder id for user {user}' + data = json.loads(response.text) + + for folder in data['data']: + if folder['name'] == get_config('personal_sync_folder'): + personal_sync_folder_id[user] = folder['id'] + return personal_sync_folder_id[user] + raise ValueError(f'Cound not find personal sync folder id for user {user}') + + +# return resources inside sync folder +# status=False returns resources that are not deleted +# status=True returns resources that are deleted and are stored in deleted content +def get_resources_inside_folder(user, status=False, folder_id=None): + if not folder_id: + folder_id = get_personal_sync_folder_id(user) + url = url_join( + get_config('localBackendUrl'), + f'rest/folders/{folder_id}/children?deleted={status}', + ) + headers = get_headers(user) + response = request.get(url, headers) + assert response.status_code == 200, 'Failed to get the resources inside folder' + + data = json.loads(response.text) + return data['data'] + + +def get_resource_ids(user, status=False): + resources = get_resources_inside_folder(user, status) + folder_ids = [] + file_ids = [] + for resource in resources: + if resource['type'] == 'd': + folder_ids.append(resource['id']) + if resource['type'] == 'f': + file_ids.append(resource['id']) + return folder_ids, file_ids + + +def delete_all_resources(): + for user in created_users.values(): + folder_ids, file_ids = get_resource_ids(user['username']) + headers = get_headers(user['username']) + # Delete all folders + if len(folder_ids) > 0: + url = url_join( + get_config('localBackendUrl'), + f'rest/folders?partialSuccess=true&id:in={",".join(folder_ids)}', + ) + response = request.delete(url, headers) + assert response.status_code == 204, 'Failed to delete all folders.' + + # Delete all files + if len(file_ids) > 0: + url = url_join( + get_config('localBackendUrl'), + f'rest/files?partialSuccess=true&id:in={",".join(file_ids)}', + ) + response = request.delete(url, headers) + assert response.status_code == 204, 'Failed to delete all files.' + + +def permanently_delete_all_resources(): + for user in created_users.values(): + folder_ids, file_ids = get_resource_ids(user['username'], True) + headers = get_headers(user['username']) + # Permanently delete all folders + if len(folder_ids) > 0: + url = url_join( + get_config('localBackendUrl'), + f'rest/folders/actions/permanent?partialSuccess=true&id:in={",".join(folder_ids)}', + ) + response = request.delete(url, headers) + assert ( + response.status_code == 204 + ), 'Failed to permanently delete all folders.' + + # Permanently delete all files + if len(file_ids) > 0: + url = url_join( + get_config('localBackendUrl'), + f'rest/files/actions/permanent?partialSuccess=true&id:in={",".join(file_ids)}', + ) + response = request.delete(url, headers) + assert ( + response.status_code == 204 + ), 'Failed to permanently delete all files.' + + +def get_folder_items_count(user, folder_name): + user = get_username_for_user(user) + folder_id = get_folder_id_by_path(user, folder_name) + resources = get_resources_inside_folder(user, folder_id=folder_id) + return str(len(resources)) + + +def get_resource(user, name, folder_id=None, resource_type=None, space=None): + if space: + folder_id = get_space_id(user, space) + resources = get_resources_inside_folder(user, folder_id=folder_id) + for resource in resources: + if resource['name'] == name and ( + resource_type is None or resource['type'] == resource_type + ): + return resource + return None + + +def create_space(space_name, user='admin'): + user = get_username_for_user(user) + url = url_join(get_config('localBackendUrl'), 'rest/folders/0/folders') + headers = get_headers(user, {'content-type': 'application/json'}) + body = json.dumps( + { + 'name': space_name, + 'parent': '0', + 'description': '', + 'expire': '0', + 'fileLifetime': '0', + 'syncable': '1', + 'secure': '0', + } + ) + response = request.post(url, body, headers) + assert ( + response.status_code == 201 + ), f'Could not create space {space_name} for user {user}' + if user not in created_spaces: + created_spaces[user] = [] + created_spaces[user].append(space_name) + + +def get_spaces(user, deleted=False): + url = url_join(get_config('localBackendUrl'), f'rest/folders/top?deleted={deleted}') + headers = get_headers(user) + response = request.get(url, headers) + assert response.status_code == 200, f'Failed to get spaces for user {user}' + data = json.loads(response.text) + return data['data'] + + +def get_space_id(user, space_name): + spaces = get_spaces(user) + for space in spaces: + if space['name'] == space_name: + return space['id'] + raise ValueError(f'Could not get space id for space {space_name}') + + +def get_deleted_space_ids(user): + spaces = get_spaces(user, True) + deleted_space_ids = [] + for space in spaces: + deleted_space_ids.append(space['id']) + return deleted_space_ids + + +def delete_all_spaces(): + for user in created_users.values(): + if (user := user['username']) in created_spaces: + for space in created_spaces[user]: + space_id = get_space_id(user, space) + url = url_join( + get_config('localBackendUrl'), + f'rest/folders?partialSuccess=true&id:in={space_id}', + ) + headers = get_headers(user) + response = request.delete(url, headers) + assert response.status_code == 204, f'Could not delete space: {space}' + + +def permanently_delete_all_spaces(): + for user in created_users.values(): + if (user := user['username']) in created_spaces: + deleted_space_ids = get_deleted_space_ids(user) + headers = get_headers(user) + url = url_join( + get_config('localBackendUrl'), + f'rest/folders/actions/permanent?partialSuccess=true&id:in={",".join(deleted_space_ids)}', + ) + response = request.delete(url, headers) + assert ( + response.status_code == 204 + ), 'Failed to permanently delete all spaces' + created_spaces.clear() + + +def create_space_folder(space, folder_name, user='admin'): + user = get_username_for_user(user) + space_id = get_space_id(user, space) + url = url_join(get_config('localBackendUrl'), f'rest/folders/{space_id}/folders') + headers = get_headers(user, {'content-type': 'application/json'}) + body = json.dumps( + { + 'description': '', + 'fileLifetime': '0', + 'name': folder_name, + 'syncable': '1', + } + ) + response = request.post(url, body, headers) + assert ( + response.status_code == 201 + ), f'Could not create the folder: {folder_name} in space {space}' + + +def upload_space_file(space, file_name, file_content, user='admin'): + user = get_username_for_user(user) + file_path = f'{get_config("tempFolderPath")}/{file_name}' + write_file(file_path, file_content) + file_size = os.path.getsize(file_path) + space_id = get_space_id(user, space) + # initiate upload + upload_uri = initiate_upload(user, file_name, file_size, space_id) + # finalize upload + finalize_upload(user, file_name, file_path, file_size, upload_uri) + + +def add_user_to_space(user, space_name, role, space_owner='admin'): + space_owner = get_username_for_user(space_owner) + user = get_username_for_user(user) + role_id = available_roles[role] + space_id = get_space_id(space_owner, space_name) + url = url_join( + get_config('localBackendUrl'), + f'rest/folders/{space_id}/members?updateIfExists=true&partialSuccess=true', + ) + headers = get_headers(space_owner, {'content-type': 'application/json'}) + body = json.dumps( + {'roleId': role_id, 'notify': True, 'notifyFileAdded': False, 'emails': [user]} + ) + response = request.post(url, body, headers) + assert ( + response.status_code == 201 + ), f'Failed to add user {user} to space {space_name}' diff --git a/test/gui/shared/scripts/helpers/api/http_helper.py b/test/gui/shared/scripts/helpers/api/http_helper.py new file mode 100644 index 00000000000..86255f14c2e --- /dev/null +++ b/test/gui/shared/scripts/helpers/api/http_helper.py @@ -0,0 +1,63 @@ +import requests +import urllib3 + +from helpers.UserHelper import basic_auth_header +from helpers.ConfigHelper import get_config + +urllib3.disable_warnings() + + +def send_request( + url, method, body=None, headers=None, user=None, password=None, files=None +): + auth_header = basic_auth_header(user, password) + if not headers: + headers = {} + headers.update(auth_header) + return requests.request( + method, + url, + data=body, + headers=headers, + verify=False, + # in seconds + # e.g.: 60 + timeout=get_config("maxSyncTimeout"), + files=files, + ) + + +def get(url, headers=None, user=None, password=None): + return send_request( + url=url, method="GET", headers=headers, user=user, password=password + ) + + +def post(url, body=None, headers=None, user=None, files=None): + return send_request(url, "POST", body, headers, user, files=files) + + +def put(url, body=None, headers=None, user=None): + return send_request(url, "PUT", body, headers, user) + + +def delete(url, headers=None, user=None): + return send_request(url=url, method="DELETE", headers=headers, user=user) + + +def mkcol(url, headers=None, user=None): + return send_request(url=url, method="MKCOL", headers=headers, user=user) + + +def propfind(url, body=None, headers=None, user=None, password=None): + return send_request(url, "PROPFIND", body, headers, user, password) + + +def assert_http_status(response, expected_code, message=""): + response_body = "" + if response.text: + response_body = response.text + + assert ( + response.status_code == expected_code + ), f"{message}\nRequest failed with status code '{response.status_code}'\n{response_body}" diff --git a/test/gui/shared/scripts/helpers/api/ocis.py b/test/gui/shared/scripts/helpers/api/ocis.py new file mode 100644 index 00000000000..ef45b79b975 --- /dev/null +++ b/test/gui/shared/scripts/helpers/api/ocis.py @@ -0,0 +1,45 @@ +import json + +import helpers.api.http_helper as request +from helpers.api.utils import url_join +from helpers.ConfigHelper import get_config + + +def get_graph_url(): + return url_join(get_config("localBackendUrl"), "graph", "v1.0") + + +def get_user_id(user): + url = url_join(get_graph_url(), "users", user) + response = request.get(url) + request.assert_http_status(response, 200, f"Failed to get user '{user}'") + resp_object = response.json() + return resp_object["id"] + + +def create_user(username, password, displayname, email): + url = url_join(get_graph_url(), "users") + body = json.dumps( + { + "onPremisesSamAccountName": username, + "passwordProfile": {"password": password}, + "displayName": displayname, + "mail": email, + } + ) + response = request.post(url, body) + request.assert_http_status(response, 201, f"Failed to create user '{username}'") + resp_object = response.json() + return { + "id": resp_object["id"], + "username": username, + "password": password, + "displayname": resp_object["displayName"], + "email": resp_object["mail"], + } + + +def delete_user(user_id): + url = url_join(get_graph_url(), "users", user_id) + response = request.delete(url) + request.assert_http_status(response, 204, "Failed to delete user") diff --git a/test/gui/shared/scripts/helpers/api/provisioning.py b/test/gui/shared/scripts/helpers/api/provisioning.py new file mode 100644 index 00000000000..43782d201d6 --- /dev/null +++ b/test/gui/shared/scripts/helpers/api/provisioning.py @@ -0,0 +1,42 @@ +from helpers.api import ocis +from helpers.ConfigHelper import is_owncloud_client +from helpers import UserHelper + +created_users = {} + + +def create_user(username): + if not is_owncloud_client(): + user = UserHelper.test_users[username] + created_users[username] = { + 'username': user.username, + 'password': user.password, + 'displayname': user.displayname, + 'email': user.email, + } + return + + if username in UserHelper.test_users: + user = UserHelper.test_users[username] + else: + user = UserHelper.User( + username=username, + displayname=username, + email=f'{username}@mail.com', + password=UserHelper.get_default_password(), + ) + + user_info = ocis.create_user( + user.username, + user.password, + user.displayname, + user.email, + ) + created_users[username] = user_info + + +def delete_created_users(): + for username, user_info in list(created_users.items()): + if is_owncloud_client(): + ocis.delete_user(user_info['id']) + del created_users[username] diff --git a/test/gui/shared/scripts/helpers/api/utils.py b/test/gui/shared/scripts/helpers/api/utils.py new file mode 100644 index 00000000000..6e859f02e1d --- /dev/null +++ b/test/gui/shared/scripts/helpers/api/utils.py @@ -0,0 +1,8 @@ +from posixpath import join + + +def url_join(*args): + paths = [] + for path in list(args): + paths.append(path.strip("/")) + return join("", *paths) diff --git a/test/gui/shared/scripts/helpers/api/webdav_helper.py b/test/gui/shared/scripts/helpers/api/webdav_helper.py new file mode 100644 index 00000000000..703aad8bd20 --- /dev/null +++ b/test/gui/shared/scripts/helpers/api/webdav_helper.py @@ -0,0 +1,79 @@ +from urllib.parse import quote +import xml.etree.ElementTree as ET + +import helpers.api.http_helper as request +from helpers.api.utils import url_join +from helpers.ConfigHelper import get_config +from helpers.FilesHelper import get_file_for_upload + + +def get_webdav_url(): + return url_join(get_config('localBackendUrl'), 'remote.php/dav/files') + + +def get_resource_path(user, resource): + resource = resource.strip('/').replace('\\', '/') + encoded_resource_path = [quote(path, safe='') for path in resource.split('/')] + encoded_resource_path = '/'.join(encoded_resource_path) + url = url_join(get_webdav_url(), user, encoded_resource_path) + return url + + +def resource_exists(user, resource): + response = request.propfind(get_resource_path(user, resource), user=user) + if response.status_code == 207: + return True + if response.status_code == 404: + return False + raise AssertionError(f'Server returned status code: {response.status_code}') + + +def get_file_content(user, resource): + response = request.get(get_resource_path(user, resource), user=user) + return response.text + + +def get_folder_items_count(user, folder_name): + folder_name = folder_name.strip('/') + path = get_resource_path(user, folder_name) + xml_response = request.propfind(path, user=user) + total_items = 0 + root_element = ET.fromstring(xml_response.content) + for response_element in root_element: + for href_element in response_element: + # The first item is folder itself so excluding it + if href_element.tag == '{DAV:}href' and not href_element.text.endswith( + f'{user}/{folder_name}/' + ): + total_items += 1 + return str(total_items) + + +def create_folder(user, folder_name): + url = get_resource_path(user, folder_name) + response = request.mkcol(url, user=user) + assert ( + response.status_code == 201 + ), f'Could not create the folder: {folder_name} for user {user}' + + +def create_file(user, file_name, contents): + url = get_resource_path(user, file_name) + response = request.put(url, body=contents, user=user) + assert response.status_code in [ + 201, + 204, + ], f"Could not create file '{file_name}' for user {user}" + + +def upload_file(user, file_name, destination): + file_path = get_file_for_upload(file_name) + with open(file_path, 'rb') as file: + contents = file.read() + create_file(user, destination, contents) + + +def delete_resource(user, resource): + url = get_resource_path(user, resource) + response = request.delete(url, user=user) + assert response.status_code == 204, f"Could not delete folder '{resource}'" diff --git a/test/gui/shared/scripts/names.py b/test/gui/shared/scripts/names.py index 490b11b1a42..d9aadd691d1 100644 --- a/test/gui/shared/scripts/names.py +++ b/test/gui/shared/scripts/names.py @@ -1,117 +1,87 @@ # encoding: UTF-8 +# fmt: off from objectmaphelper import * settings_OCC_SettingsDialog = {"name": "Settings", "type": "OCC::SettingsDialog", "visible": 1} owncloudWizard_OCC_OwncloudWizard = {"name": "owncloudWizard", "type": "OCC::OwncloudWizard", "visible": 1} -owncloudWizard_label_2_QLabel = {"name": "label_2", "type": "QLabel", "visible": 1, "window": owncloudWizard_OCC_OwncloudWizard} -leUrl_OCC_PostfixLineEdit = {"buddy": owncloudWizard_label_2_QLabel, "name": "leUrl", "type": "OCC::PostfixLineEdit", "visible": 1} -owncloudWizard_qt_passive_wizardbutton1_QPushButton = {"name": "__qt__passive_wizardbutton1", "type": "QPushButton", "visible": 1, "window": owncloudWizard_OCC_OwncloudWizard} -owncloudWizard_lLocal_QLabel = {"name": "lLocal", "type": "QLabel", "visible": 1, "window": owncloudWizard_OCC_OwncloudWizard} -pbSelectLocalFolder_QPushButton = {"buddy": owncloudWizard_lLocal_QLabel, "name": "pbSelectLocalFolder", "type": "QPushButton", "visible": 1} qFileDialog_QFileDialog = {"name": "QFileDialog", "type": "QFileDialog", "visible": 1} -qFileDialog_splitter_QSplitter = {"name": "splitter", "type": "QSplitter", "visible": 1, "window": qFileDialog_QFileDialog} -splitter_frame_QFrame = {"container": qFileDialog_splitter_QSplitter, "name": "frame", "type": "QFrame", "visible": 1} -frame_stackedWidget_QStackedWidget = {"container": splitter_frame_QFrame, "name": "stackedWidget", "type": "QStackedWidget", "visible": 1} -stackedWidget_treeView_QTreeView = {"container": frame_stackedWidget_QStackedWidget, "name": "treeView", "type": "QTreeView", "visible": 1} -qFileDialog_newFolderButton_QToolButton = {"name": "newFolderButton", "type": "QToolButton", "visible": 1, "window": qFileDialog_QFileDialog} -treeView_QExpandingLineEdit = {"columnIndex": 0, "container": stackedWidget_treeView_QTreeView, "rowIndex": 2, "type": "QExpandingLineEdit", "unnamed": 1, "visible": 1} -qFileDialog_Choose_QPushButton = {"text": "Choose", "type": "QPushButton", "unnamed": 1, "visible": 1, "window": qFileDialog_QFileDialog} -enter_Password_QInputDialog = {"type": "QInputDialog", "unnamed": 1, "visible": 1, "windowTitle": "Enter Password"} -enter_Password_Cancel_QPushButton = {"text": "Cancel", "type": "QPushButton", "unnamed": 1, "visible": 1, "window": enter_Password_QInputDialog} -settings_settingsdialog_toolbutton_admin_localhost_QToolButton = {"name": "settingsdialog_toolbutton_admin@localhost", "type": "QToolButton", "visible": 1, "window": settings_OCC_SettingsDialog} settings_stack_QStackedWidget = {"name": "stack", "type": "QStackedWidget", "visible": 1, "window": settings_OCC_SettingsDialog} -stack_accountToolbox_QToolButton = {"container": settings_stack_QStackedWidget, "name": "_accountToolbox", "type": "QToolButton", "visible": 1} -settings_QMenu = {"type": "QMenu", "unnamed": 1, "visible": 1, "window": settings_OCC_SettingsDialog} -owncloudWizard_usernameLabel_QLabel = {"name": "usernameLabel", "type": "QLabel", "visible": 1, "window": owncloudWizard_OCC_OwncloudWizard} -owncloudWizard_passwordLabel_QLabel = {"name": "passwordLabel", "type": "QLabel", "visible": 1, "window": owncloudWizard_OCC_OwncloudWizard} +settings_dialogStack_QStackedWidget = {"name": "dialogStack", "type": "QStackedWidget", "visible": 1, "window": settings_OCC_SettingsDialog} qFileDialog_fileNameLabel_QLabel = {"name": "fileNameLabel", "type": "QLabel", "visible": 1, "window": qFileDialog_QFileDialog} -leUsername_QLineEdit = {"buddy": owncloudWizard_usernameLabel_QLabel, "name": "leUsername", "type": "QLineEdit", "visible": 1} -owncloudWizard_cbSyncFromScratch_QRadioButton = {"name": "cbSyncFromScratch", "type": "QRadioButton", "visible": 1, "window": owncloudWizard_OCC_OwncloudWizard} -lePassword_QLineEdit = {"buddy": owncloudWizard_passwordLabel_QLabel, "name": "lePassword", "type": "QLineEdit", "visible": 1} -fileNameEdit_QLineEdit = {"buddy": qFileDialog_fileNameLabel_QLabel, "name": "fileNameEdit", "type": "QLineEdit", "visible": 1} -settings_settingsdialog_toolbutton_artur_jankaritech_ocloud_de_QToolButton = {"name": "settingsdialog_toolbutton_artur@jankaritech.ocloud.de", "type": "QToolButton", "visible": 1, "window": settings_OCC_SettingsDialog} -stack_folderList_QTreeView = {"container": settings_stack_QStackedWidget, "name": "_folderList", "type": "QTreeView", "visible": 1} sharingDialog_OCC_ShareDialog = {"name": "SharingDialog", "type": "OCC::ShareDialog", "visible": 1} sharingDialog_qt_tabwidget_stackedwidget_QStackedWidget = {"name": "qt_tabwidget_stackedwidget", "type": "QStackedWidget", "visible": 1, "window": sharingDialog_OCC_ShareDialog} qt_tabwidget_stackedwidget_SharingDialogUG_OCC_ShareUserGroupWidget = {"container": sharingDialog_qt_tabwidget_stackedwidget_QStackedWidget, "name": "SharingDialogUG", "type": "OCC::ShareUserGroupWidget", "visible": 1} sharingDialogUG_scrollArea_QScrollArea = {"container": qt_tabwidget_stackedwidget_SharingDialogUG_OCC_ShareUserGroupWidget, "name": "scrollArea", "type": "QScrollArea", "visible": 1} -scrollArea_deleteShareButton_QToolButton = {"container": sharingDialogUG_scrollArea_QScrollArea, "name": "deleteShareButton", "type": "QToolButton", "visible": 1} settings_settingsdialog_toolbutton_Quit_ownCloud_QToolButton = {"name": "settingsdialog_toolbutton_Quit ownCloud", "type": "QToolButton", "visible": 1, "window": settings_OCC_SettingsDialog} -quit_ownCloud_QMessageBox = {"type": "QMessageBox", "unnamed": 1, "visible": 1, "windowTitle": "Quit ownCloud"} -quit_ownCloud_Yes_QPushButton = {"text": "Yes", "type": "QPushButton", "unnamed": 1, "visible": 1, "window": quit_ownCloud_QMessageBox} -qFileDialog_lookInCombo_QComboBox = {"name": "lookInCombo", "type": "QComboBox", "visible": 1, "window": qFileDialog_QFileDialog} -splitter_sidebar_QSidebar = {"container": qFileDialog_splitter_QSplitter, "name": "sidebar", "type": "QSidebar", "visible": 1} settings_settingsdialog_toolbutton_Settings_QToolButton = {"name": "settingsdialog_toolbutton_Settings", "type": "QToolButton", "visible": 1, "window": settings_OCC_SettingsDialog} -stack_scrollArea_QScrollArea = {"container": settings_stack_QStackedWidget, "name": "scrollArea", "type": "QScrollArea", "visible": 1} -scrollArea_QScrollBar = {"container": stack_scrollArea_QScrollArea, "type": "QScrollBar", "unnamed": 1, "visible": 1} -scrollArea_groupBox_QGroupBox = {"container": stack_scrollArea_QScrollArea, "name": "groupBox", "type": "QGroupBox", "visible": 1} -groupBox_proxyGroupBox_QGroupBox = {"container": scrollArea_groupBox_QGroupBox, "name": "proxyGroupBox", "type": "QGroupBox", "visible": 1} -proxyGroupBox_manualProxyRadioButton_QRadioButton = {"container": groupBox_proxyGroupBox_QGroupBox, "name": "manualProxyRadioButton", "type": "QRadioButton", "visible": 1} -proxyGroupBox_hostLineEdit_QLineEdit = {"container": groupBox_proxyGroupBox_QGroupBox, "name": "hostLineEdit", "type": "QLineEdit", "visible": 1} -proxyGroupBox_authRequiredcheckBox_QCheckBox = {"container": groupBox_proxyGroupBox_QGroupBox, "name": "authRequiredcheckBox", "type": "QCheckBox", "visible": 1} -proxyGroupBox_labelLocalhost_QLabel = {"container": groupBox_proxyGroupBox_QGroupBox, "name": "labelLocalhost", "type": "QLabel", "visible": 1} -groupBox_uploadBox_QGroupBox = {"container": scrollArea_groupBox_QGroupBox, "name": "uploadBox", "type": "QGroupBox", "visible": 1} -uploadBox_autoUploadLimitRadioButton_QRadioButton = {"container": groupBox_uploadBox_QGroupBox, "name": "autoUploadLimitRadioButton", "type": "QRadioButton", "visible": 1} stack_qt_tabwidget_stackedwidget_QStackedWidget = {"container": settings_stack_QStackedWidget, "name": "qt_tabwidget_stackedwidget", "type": "QStackedWidget", "visible": 1} -qt_tabwidget_stackedwidget_OCC_ActivityWidget_OCC_ActivityWidget = {"container": stack_qt_tabwidget_stackedwidget_QStackedWidget, "name": "OCC__ActivityWidget", "type": "OCC::ActivityWidget", "visible": 1} -oCC_ActivityWidget_activityList_QListView = {"container": qt_tabwidget_stackedwidget_OCC_ActivityWidget_OCC_ActivityWidget, "name": "_activityList", "type": "QListView", "visible": 1} -o_activityList_QScrollBar = {"container": oCC_ActivityWidget_activityList_QListView, "type": "QScrollBar", "unnamed": 1, "visible": 1} -oCC_ActivityWidget_notifyScroll_QScrollArea = {"container": qt_tabwidget_stackedwidget_OCC_ActivityWidget_OCC_ActivityWidget, "name": "_notifyScroll", "type": "QScrollArea", "visible": 1} -o_notifyScroll_QScrollBar = {"container": oCC_ActivityWidget_notifyScroll_QScrollArea, "type": "QScrollBar", "unnamed": 1, "visible": 1} -o_notifyScroll_Accept_QPushButton = {"container": oCC_ActivityWidget_notifyScroll_QScrollArea, "text": "Accept", "type": "QPushButton", "unnamed": 1, "visible": 1} -o_notifyScroll_timeLabel_QLabel = {"container": oCC_ActivityWidget_notifyScroll_QScrollArea, "name": "_timeLabel", "type": "QLabel", "visible": 1} -o_notifyScroll_subjectLabel_QLabel = {"container": oCC_ActivityWidget_notifyScroll_QScrollArea, "name": "_subjectLabel", "type": "QLabel", "visible": 1} -scrollArea_OCC_ShareUserLine_OCC_ShareUserLine = {"container": sharingDialogUG_scrollArea_QScrollArea, "name": "OCC__ShareUserLine", "type": "OCC::ShareUserLine", "visible": 1} -o_folderList_QModelIndex = {"column": 0, "container": stack_folderList_QTreeView, "text": "", "type": "QModelIndex"} -qt_tabwidget_stackedwidget_OCC_IssuesWidget_OCC_IssuesWidget = {"container": stack_qt_tabwidget_stackedwidget_QStackedWidget, "name": "OCC__IssuesWidget", "type": "OCC::IssuesWidget", "visible": 1} -oCC_IssuesWidget_treeWidget_QTreeWidget = {"container": qt_tabwidget_stackedwidget_OCC_IssuesWidget_OCC_IssuesWidget, "name": "_treeWidget", "type": "QTreeWidget", "visible": 1} -o_treeWidget_lorem_conflicted_copy_2020_12_14_133239_txt_QModelIndex = {"column": 1, "container": oCC_IssuesWidget_treeWidget_QTreeWidget, "text": RegularExpression("lorem (conflicted copy 2020-12-14 133239).txt"), "type": "QModelIndex"} -scrollArea_sharedWith_QLabel = {"container": sharingDialogUG_scrollArea_QScrollArea, "name": "sharedWith", "type": "QLabel", "visible": 1} -scrollArea_permissionsEdit_QCheckBox = {"container": sharingDialogUG_scrollArea_QScrollArea, "name": "permissionsEdit", "type": "QCheckBox", "visible": 1} -scrollArea_permissionShare_QCheckBox = {"container": sharingDialogUG_scrollArea_QScrollArea, "name": "permissionShare", "type": "QCheckBox", "visible": 1} -stack_qt_tabwidget_tabbar_QTabBar = {"container": settings_stack_QStackedWidget, "name": "qt_tabwidget_tabbar", "type": "QTabBar", "visible": 1} -o_treeWidget_Conflict_Server_version_downloaded_local_copy_renamed_and_not_uploaded_QModelIndex = {"column": 3, "container": oCC_IssuesWidget_treeWidget_QTreeWidget, "text": "Conflict: Server version downloaded, local copy renamed and not uploaded.", "type": "QModelIndex"} -qt_tabwidget_stackedwidget_OCC_ProtocolWidget_OCC_ProtocolWidget = {"container": stack_qt_tabwidget_stackedwidget_QStackedWidget, "name": "OCC__ProtocolWidget", "type": "OCC::ProtocolWidget", "visible": 1} -oCC_ProtocolWidget_treeWidget_QTreeWidget = {"container": qt_tabwidget_stackedwidget_OCC_ProtocolWidget_OCC_ProtocolWidget, "name": "_treeWidget", "type": "QTreeWidget", "visible": 1} -o_treeWidget_lorem_txt_QModelIndex = {"column": 1, "container": oCC_ProtocolWidget_treeWidget_QTreeWidget, "text": "lorem.txt", "type": "QModelIndex"} +qt_tabwidget_stackedwidget_OCC_SyncErrorWidget_OCC_SyncErrorWidget = {"container": stack_qt_tabwidget_stackedwidget_QStackedWidget, "name": "OCC__SyncErrorWidget", "type": "OCC::SyncErrorWidget", "visible": 1} sharingDialog_qt_tabwidget_tabbar_QTabBar = {"name": "qt_tabwidget_tabbar", "type": "QTabBar", "visible": 1, "window": sharingDialog_OCC_ShareDialog} qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget = {"container": sharingDialog_qt_tabwidget_stackedwidget_QStackedWidget, "name": "OCC__ShareLinkWidget", "type": "OCC::ShareLinkWidget", "visible": 1} oCC_ShareLinkWidget_checkBox_password_QCheckBox = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "checkBox_password", "type": "QCheckBox", "visible": 1} oCC_ShareLinkWidget_widget_editing_QWidget = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "widget_editing", "type": "QWidget", "visible": 1} oCC_ShareLinkWidget_checkBox_password_QProgressIndicator = {"aboveWidget": oCC_ShareLinkWidget_widget_editing_QWidget, "container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "leftWidget": oCC_ShareLinkWidget_checkBox_password_QCheckBox, "type": "QProgressIndicator", "unnamed": 1, "visible": 1} -sharingDialog_label_name_QLabel = {"name": "label_name", "type": "QLabel", "visible": 1, "window": sharingDialog_OCC_ShareDialog} oCC_ShareLinkWidget_linkShares_QTableWidget = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "linkShares", "type": "QTableWidget", "visible": 1} -linkShares_QToolButton = {"container": oCC_ShareLinkWidget_linkShares_QTableWidget, "text": "...", "type": "QToolButton", "unnamed": 1, "visible": 1} oCC_ShareLinkWidget_lineEdit_password_QLineEdit = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "lineEdit_password", "type": "QLineEdit", "visible": 1} -oCC_ShareLinkWidget_calendar_QDateEdit = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "calendar", "type": "QDateEdit", "visible": 1} -oCC_ShareLinkWidget_qt_calendar_prevmonth_QPrevNextCalButton = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "qt_calendar_prevmonth", "type": "QPrevNextCalButton", "visible": 1} -oCC_ShareLinkWidget_qt_calendar_calendarview_QCalendarView = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "qt_calendar_calendarview", "type": "QCalendarView", "visible": 1} -oCC_ShareLinkWidget_qt_spinbox_lineedit_QLineEdit = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "qt_spinbox_lineedit", "type": "QLineEdit", "visible": 1} -linkShares_0_0_QModelIndex = {"column": 0, "container": oCC_ShareLinkWidget_linkShares_QTableWidget, "row": 0, "type": "QModelIndex"} -oCC_ShareLinkWidget_radio_readOnly_QRadioButton = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "radio_readOnly", "type": "QRadioButton", "visible": 1} -oCC_ShareLinkWidget_radio_readWrite_QRadioButton = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "radio_readWrite", "type": "QRadioButton", "visible": 1} -oCC_ShareLinkWidget_radio_uploadOnly_QRadioButton = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "radio_uploadOnly", "type": "QRadioButton", "visible": 1} oCC_ShareLinkWidget_checkBox_expire_QCheckBox = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "checkBox_expire", "type": "QCheckBox", "visible": 1} oCC_ShareLinkWidget_checkBox_expire_QProgressIndicator = {"aboveWidget": oCC_ShareLinkWidget_lineEdit_password_QLineEdit, "container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "leftWidget": oCC_ShareLinkWidget_checkBox_expire_QCheckBox, "type": "QProgressIndicator", "unnamed": 1, "visible": 1} -oCC_IssuesWidget_tableView_QTableView = {"container": qt_tabwidget_stackedwidget_OCC_IssuesWidget_OCC_IssuesWidget, "name": "_tableView", "type": "QTableView", "visible": 1} -o_tableView_0_1_QModelIndex = {"column": 1, "container": oCC_IssuesWidget_tableView_QTableView, "row": 0, "type": "QModelIndex"} settings_settingsdialog_toolbutton_Add_account_QToolButton = {"name": "settingsdialog_toolbutton_Add account", "type": "QToolButton", "visible": 1, "window": settings_OCC_SettingsDialog} settings_settingsdialog_toolbutton_Activity_QToolButton = {"name": "settingsdialog_toolbutton_Activity", "type": "QToolButton", "visible": 1, "window": settings_OCC_SettingsDialog} -sharingDialog_Close_QPushButton = {"text": "Close", "type": "QPushButton", "unnamed": 1, "visible": 1, "window": sharingDialog_OCC_ShareDialog} -stack_Enable_experimental_placeholder_mode_QPushButton = {"container": settings_stack_QStackedWidget, "text": "Enable experimental placeholder mode", "type": "QPushButton", "unnamed": 1, "visible": 1} -disable_virtual_file_support_QMessageBox = {"type": "QMessageBox", "unnamed": 1, "visible": 1, "windowTitle": "Disable virtual file support?"} -disable_virtual_file_support_Disable_support_QPushButton = {"text": "Disable support", "type": "QPushButton", "unnamed": 1, "visible": 1, "window": disable_virtual_file_support_QMessageBox} -error_QMessageBox = {"type": "QMessageBox", "unnamed": 1, "visible": 1, "windowTitle": "Error"} -oCC_SslErrorDialog_OCC_SslErrorDialog = {"name": "OCC__SslErrorDialog", "type": "OCC::SslErrorDialog", "visible": 1} -oCC_SslErrorDialog_cbTrustConnect_QCheckBox = {"name": "_cbTrustConnect", "type": "QCheckBox", "visible": 1, "window": oCC_SslErrorDialog_OCC_SslErrorDialog} -oCC_SslErrorDialog_OK_QPushButton = {"text": "OK", "type": "QPushButton", "unnamed": 1, "visible": 1, "window": oCC_SslErrorDialog_OCC_SslErrorDialog} -owncloudWizard_urlLabel_QLabel = {"name": "urlLabel", "type": "QLabel", "visible": 1, "window": owncloudWizard_OCC_OwncloudWizard} +disable_virtual_file_support_QMessageBox = {"name": "DisableVFSMessageBox", "type": "QMessageBox", "visible": 1} owncloudWizard_urlLabel_QLabel = {"name": "urlLabel", "type": "QLabel", "visible": 1, "window": owncloudWizard_OCC_OwncloudWizard} -choose_What_to_Sync_OCC_SelectiveSyncDialog = {"type": "OCC::SelectiveSyncDialog", "unnamed": 1, "visible": 1, "windowTitle": "Choose What to Sync"} -choose_What_to_Sync_Deselect_remote_folders_you_do_not_wish_to_synchronize_QLabel = {"text": "Deselect remote folders you do not wish to synchronize.", "type": "QLabel", "unnamed": 1, "visible": 1, "window": choose_What_to_Sync_OCC_SelectiveSyncDialog} -choose_What_To_Synchronize_QTreeWidget = {"aboveWidget": choose_What_to_Sync_Deselect_remote_folders_you_do_not_wish_to_synchronize_QLabel, "type": "QTreeWidget", "unnamed": 1, "visible": 1, "window": choose_What_to_Sync_OCC_SelectiveSyncDialog} -deselect_remote_folders_you_do_not_wish_to_synchronize_QHeaderView = {"container": choose_What_To_Synchronize_QTreeWidget, "orientation": 1, "type": "QHeaderView", "unnamed": 1, "visible": 1} -linkShares_QToolButton_2 = {"container": oCC_ShareLinkWidget_linkShares_QTableWidget, "occurrence": 2, "type": "QToolButton", "unnamed": 1, "visible": 1} -oCC_ShareLinkWidget_Delete_QPushButton = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "text": "Delete", "type": "QPushButton", "unnamed": 1, "visible": 1} -oCC_ShareLinkWidget_pushButton_setPassword_QPushButton = {"container": qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, "name": "pushButton_setPassword", "type": "QPushButton", "visible": 1} +setupWizardWindow_OCC_Wizard_SetupWizardWindow = {"name": "SetupWizardWidget", "type": "OCC::Wizard::SetupWizardWidget", "visible": 1} +setupWizardWindow_contentWidget_QStackedWidget = {"name": "contentWidget", "type": "QStackedWidget", "visible": 1, "window": setupWizardWindow_OCC_Wizard_SetupWizardWindow} +contentWidget_advancedConfigGroupBox_QGroupBox = {"container": setupWizardWindow_contentWidget_QStackedWidget, "name": "advancedConfigGroupBox", "type": "QGroupBox", "visible": 1} +advancedConfigGroupBox_localDirectoryGroupBox_QGroupBox = {"container": contentWidget_advancedConfigGroupBox_QGroupBox, "name": "localDirectoryGroupBox", "type": "QGroupBox", "visible": 1} +advancedConfigGroupBox_syncModeGroupBox_QGroupBox = {"container": contentWidget_advancedConfigGroupBox_QGroupBox, "name": "syncModeGroupBox", "type": "QGroupBox", "visible": 1} +add_Folder_Sync_Connection_OCC_FolderWizard = {"name":"AddFolderSyncWizard", "type": "OCC::FolderWizard", "visible": 1} +add_Folder_Sync_Connection_groupBox_QGroupBox = {"name": "groupBox", "type": "QGroupBox", "visible": 1, "window": add_Folder_Sync_Connection_OCC_FolderWizard} +add_Folder_Sync_Connection_Deselect_remote_folders_you_do_not_wish_to_synchronize_QLabel = {"type": "QLabel", "unnamed": 1, "visible": 1, "window": add_Folder_Sync_Connection_OCC_FolderWizard} +folder_Sync_Connection_Deselect_remote_folders_QTreeWidget = {"aboveWidget": add_Folder_Sync_Connection_Deselect_remote_folders_you_do_not_wish_to_synchronize_QLabel, "type": "QTreeWidget", "unnamed": 1, "visible": 1, "window": add_Folder_Sync_Connection_OCC_FolderWizard} +deselect_remote_folders_you_do_not_wish_to_synchronize_ownCloud_QModelIndex = {"column": 0, "container": folder_Sync_Connection_Deselect_remote_folders_QTreeWidget, "type": "QModelIndex"} +loginRequiredDialog_OCC_LoginRequiredDialog = {"name": "LoginRequiredDialog", "type": "OCC::LoginRequiredDialog", "visible": 1} +loginRequiredDialog_contentWidget_QStackedWidget = {"name": "contentWidget", "type": "QStackedWidget", "visible": 1, "window": loginRequiredDialog_OCC_LoginRequiredDialog} +contentWidget_contentWidget_QStackedWidget = {"container": setupWizardWindow_contentWidget_QStackedWidget, "name": "contentWidget", "type": "QStackedWidget", "visible": 1} +add_Folder_Sync_Connection_tableView_QTableView = {"name": "tableView","type": "QTableView","visible": 1,"window": add_Folder_Sync_Connection_OCC_FolderWizard} +stack_scrollArea_QScrollArea = {"container": settings_stack_QStackedWidget, "name": "scrollArea", "type": "QScrollArea", "visible": 1} +stack_stackedWidget_QStackedWidget = {"container": settings_stack_QStackedWidget, "name": "stackedWidget", "type": "QStackedWidget", "visible": 1} +stackedWidget_quickWidget_QQuickWidget = {"container": stack_stackedWidget_QStackedWidget, "name": "quickWidget", "type": "QQuickWidget", "visible": 1} +quickWidget_scrollView_ScrollView = {"container": stackedWidget_quickWidget_QQuickWidget, "id": "scrollView", "type": "ScrollView", "unnamed": 1, "visible": True} +scrollView_ListView = {"container": quickWidget_scrollView_ScrollView, "type": "ListView", "unnamed": 1, "visible": True} +dialogStack_quickWidget_QQuickWidget = {"container": settings_dialogStack_QStackedWidget, "name": "quickWidget", "type": "QQuickWidget", "visible": 1} +groupBox_folderTreeWidget_QTreeWidget = {"container": add_Folder_Sync_Connection_groupBox_QGroupBox, "name": "folderTreeWidget", "type": "QTreeWidget", "visible": 1} +confirm_Folder_Sync_Connection_Removal_QMessageBox = {"name":"ConfirmFolderSyncRemovalMessageBox", "type": "QMessageBox", "visible": 1} +stackedWidget_quickWidget_OCC_QmlUtils_OCQuickWidget = {"container": stack_stackedWidget_QStackedWidget, "name": "accountFoldersView", "type": "OCC::AccountFoldersView", "visible": 1} +qt_tabwidget_stackedwidget_OCC_LocalActivityWidget_OCC_LocalActivityWidget = {"container": stack_qt_tabwidget_stackedwidget_QStackedWidget, "name": "OCC__LocalActivityWidget", "type": "OCC::LocalActivityWidget", "visible": 1} +oCC_LocalActivityWidget_tableView_QTableView = {"container": qt_tabwidget_stackedwidget_OCC_LocalActivityWidget_OCC_LocalActivityWidget, "name": "_tableView", "type": "QTableView", "visible": 1} +oCC_SyncErrorWidget_tableView_QTableView = {"container": qt_tabwidget_stackedwidget_OCC_SyncErrorWidget_OCC_SyncErrorWidget, "name": "_tableView", "type": "QTableView", "visible": 1} +dialogStack_quickWidget_OCC_QmlUtils_OCQuickWidget = {"container": settings_dialogStack_QStackedWidget, "name": "quickWidget", "type": "OCC::QmlUtils::OCQuickWidget", "visible": 1} +contentWidget_OCC_QmlUtils_OCQuickWidget = {"container": contentWidget_contentWidget_QStackedWidget, "type": "OCC::QmlUtils::OCQuickWidget", "unnamed": 1, "visible": 1} +stackedWidget_Add_Folder_Sync_Connection_QGroupBox = {"container": stack_stackedWidget_QStackedWidget, "title": "Add Folder Sync Connection", "type": "QGroupBox", "unnamed": 1, "visible": 1} +stackedWidget_groupBox_QGroupBox = {"container": settings_stack_QStackedWidget, "name": "groupBox", "type": "QGroupBox", "visible": 1} +groupBox_OCC_QmlUtils_OCQuickWidget = {"container": stackedWidget_groupBox_QGroupBox, "type": "OCC::QmlUtils::OCQuickWidget", "unnamed": 1, "visible": 1} +welcome_to_ownCloud_OCC_NewAccountWizard = {"name": "NewAccountWizard", "type": "OCC::NewAccountWizard", "visible": 1} +server_address_QLabel = {"type": "QLabel", "unnamed": 1, "visible": 1, "window": welcome_to_ownCloud_OCC_NewAccountWizard} +stackedWidget_accountFoldersTreeView_QTreeView = {"container": stack_stackedWidget_QStackedWidget, "name": "accountFoldersTreeView", "type": "QTreeView", "visible": 1} +confirm_Folder_Sync_Connection_Removal_QMessageBox_2 = {"type": "QMessageBox", "unnamed": 1, "visible": 1, "windowTitle": "Confirm Folder Sync Connection Removal"} +confirmRemoveFolderSyncDialog_QMessageBox = {"name": "confirmRemoveFolderSyncDialog", "type": "QMessageBox", "visible": 1} +confirmRemoveFolderSyncDialog_removeFolderSyncButton_QPushButton = {"name": "removeFolderSyncButton", "type": "QPushButton", "visible": 1, "window": confirmRemoveFolderSyncDialog_QMessageBox} +settings_folderOptionsMenu_QMenu = {"name": "folderOptionsMenu", "type": "QMenu", "visible": 1, "window": settings_OCC_SettingsDialog} +confirmDisableVfsDialog_QMessageBox = {"name": "confirmDisableVfsDialog", "type": "QMessageBox", "visible": 1} +mainWindow_OCC_MainWindow = {"name": "MainWindow", "type": "OCC::MainWindow", "visible": 1} +mainWindow_QMenu = {"type": "QMenu", "unnamed": 1, "visible": 1, "window": mainWindow_OCC_MainWindow} +mainWindow_tableView_QTableView = {"name": "_tableView", "type": "QTableView", "visible": 1, "window": mainWindow_OCC_MainWindow} +mainWindow_folderOptionsMenu_QMenu = {"name": "folderOptionsMenu", "type": "QMenu", "visible": 1, "window": mainWindow_OCC_MainWindow} +mainWindow_stackedWidget_QStackedWidget = {"name": "stackedWidget", "type": "QStackedWidget", "visible": 1, "window": mainWindow_OCC_MainWindow} +stackedWidget_accountFoldersTreeView_QTreeView_2 = {"container": mainWindow_stackedWidget_QStackedWidget, "name": "accountFoldersTreeView", "type": "QTreeView", "visible": 1} +stackedWidget_groupBox_QGroupBox_2 = {"container": mainWindow_stackedWidget_QStackedWidget, "name": "groupBox", "type": "QGroupBox", "visible": 1} +groupBox_quickWidget_OCC_QmlUtils_OCQuickWidget = {"container": stackedWidget_groupBox_QGroupBox_2, "name": "quickWidget", "type": "OCC::QmlUtils::OCQuickWidget", "visible": 1} +quickWidget_scrollView_ScrollView_2 = {"container": groupBox_quickWidget_OCC_QmlUtils_OCQuickWidget, "id": "scrollView", "type": "ScrollView", "unnamed": 1, "visible": True} +mainWindow_scrollArea_QScrollArea = {"name": "scrollArea", "type": "QScrollArea", "visible": 1, "window": mainWindow_OCC_MainWindow} +scrollArea_generalGroupBox_QGroupBox = {"container": mainWindow_scrollArea_QScrollArea, "name": "generalGroupBox", "type": "QGroupBox", "visible": 1} +scrollArea_advancedGroupBox_QGroupBox = {"container": mainWindow_scrollArea_QScrollArea, "name": "advancedGroupBox", "type": "QGroupBox", "visible": 1} +scrollArea_groupBox_QGroupBox = {"container": mainWindow_scrollArea_QScrollArea, "name": "groupBox", "type": "QGroupBox", "visible": 1} +mainWindow_qt_tabwidget_stackedwidget_QStackedWidget = {"name": "qt_tabwidget_stackedwidget", "type": "QStackedWidget", "visible": 1, "window": mainWindow_OCC_MainWindow} +confirmRemoveAccountDialog_QMessageBox = {"name": "confirmRemoveAccountDialog", "type": "QMessageBox", "visible": 1} +mainWindow_manageAccountMenu_QMenu = {"name": "manageAccountMenu", "type": "QMenu", "visible": 1, "window": mainWindow_OCC_MainWindow} diff --git a/test/gui/shared/scripts/pageObjects/AccountConnectionWizard.py b/test/gui/shared/scripts/pageObjects/AccountConnectionWizard.py index aaef3decd7b..7369f7267b9 100644 --- a/test/gui/shared/scripts/pageObjects/AccountConnectionWizard.py +++ b/test/gui/shared/scripts/pageObjects/AccountConnectionWizard.py @@ -1,160 +1,327 @@ import names import squish -from helpers.SetupClientHelper import getClientDetails, createUserSyncPath -import test + +from pageObjects.EnterPassword import EnterPassword + +from helpers.WebUIHelper import authorize_via_webui +from helpers.SetupClientHelper import ( + create_user_sync_path, + get_temp_resource_path, + set_current_user_sync_path, +) +from helpers.SyncHelper import listen_sync_status_for_item class AccountConnectionWizard: - SERVER_ADDRESS_BOX = names.leUrl_OCC_PostfixLineEdit - NEXT_BUTTON = names.owncloudWizard_qt_passive_wizardbutton1_QPushButton - USERNAME_BOX = names.leUsername_QLineEdit - PASSWORD_BOX = names.lePassword_QLineEdit - SELECT_LOCAL_FOLDER = names.pbSelectLocalFolder_QPushButton - DIRECTORY_NAME_BOX = names.fileNameEdit_QLineEdit - CHOOSE_BUTTON = names.qFileDialog_Choose_QPushButton + SERVER_ADDRESS_BOX = { + "name": "ServerAddressLineEdit", + "type": "QLineEdit", + "visible": 1, + "window": names.welcome_to_ownCloud_OCC_NewAccountWizard, + } + NEXT_BUTTON = { + "container": names.settings_dialogStack_QStackedWidget, + "name": "nextButton", + "type": "QPushButton", + "visible": 1, + } + SIGN_IN_BUTTON = { + "name": "__qt__passive_wizardbutton1", + "type": "QPushButton", + "visible": 1, + "window": names.welcome_to_ownCloud_OCC_NewAccountWizard, + } FINISH_BUTTON = { "name": "qt_wizard_finish", "type": "QPushButton", "visible": 1, - "window": names.owncloudWizard_OCC_OwncloudWizard, + "window": names.welcome_to_ownCloud_OCC_NewAccountWizard, + } + ADVANCED_CONFIGURATION_BUTTON = { + "name": "__qt__passive_wizardbutton6", + "type": "QPushButton", + "visible": 1, + "window": names.welcome_to_ownCloud_OCC_NewAccountWizard, + } + USERNAME_BOX = { + "container": names.contentWidget_OCC_QmlUtils_OCQuickWidget, + "id": "userNameField", + "type": "TextField", + "visible": True, + } + PASSWORD_BOX = { + "container": names.contentWidget_OCC_QmlUtils_OCQuickWidget, + "id": "passwordField", + "type": "TextField", + "visible": True, + } + SELECT_LOCAL_FOLDER = { + "type": "QLineEdit", + "unnamed": 1, + "visible": 1, + "window": names.welcome_to_ownCloud_OCC_NewAccountWizard, + } + DIRECTORY_NAME_BOX = { + "name": "BrowseButton", + "type": "QPushButton", + "visible": 1, + "window": names.welcome_to_ownCloud_OCC_NewAccountWizard, } - ERROR_OK_BUTTON = { - "text": "OK", + CHOOSE_BUTTON = { "type": "QPushButton", "unnamed": 1, "visible": 1, - "window": names.error_QMessageBox, + "window": names.qFileDialog_QFileDialog, } ERROR_LABEL = { - "name": "errorLabel", + "name": "errorMessageLabel", "type": "QLabel", "visible": 1, - "window": names.owncloudWizard_OCC_OwncloudWizard, + "window": names.setupWizardWindow_OCC_Wizard_SetupWizardWindow, } - CREDENTIAL_PAGE = { - "name": "OwncloudHttpCredsPage", - "type": "OCC::OwncloudHttpCredsPage", + BASIC_CREDENTIAL_PAGE = { + "container": names.contentWidget_contentWidget_QStackedWidget, + "type": "OCC::Wizard::BasicCredentialsSetupWizardPage", "visible": 1, - "window": names.owncloudWizard_OCC_OwncloudWizard, } - ADVANCE_SETUP_PAGE = { - "name": "OwncloudAdvancedSetupPage", - "type": "OCC::OwncloudAdvancedSetupPage", + OAUTH_CREDENTIAL_PAGE = { + "type": "QWizardPage", + "unnamed": 1, "visible": 1, - "window": names.owncloudWizard_OCC_OwncloudWizard, + "window": names.welcome_to_ownCloud_OCC_NewAccountWizard, } - MANUAL_SYNC_FOLDER_OPTION = { - "name": "rManualFolder", + COPY_URL_TO_CLIPBOARD_BUTTON = { + "name": "CopyUrlToClipboardButton", + "type": "QPushButton", + "visible": 1, + } + CONF_SYNC_MANUALLY_RADIO_BUTTON = { + "name": "SelectiveSyncRadioButton", "type": "QRadioButton", "visible": 1, - "window": names.owncloudWizard_OCC_OwncloudWizard, + "window": names.welcome_to_ownCloud_OCC_NewAccountWizard, } - CHOOSE_WHAT_TO_SYNC_BUTTON = { - "name": "bSelectiveSync", - "type": "QPushButton", + ADVANCED_CONFIGURATION_CHECKBOX = { + "container": names.setupWizardWindow_contentWidget_QStackedWidget, + "name": "advancedConfigGroupBox", + "type": "QGroupBox", "visible": 1, - "window": names.owncloudWizard_OCC_OwncloudWizard, } - SELECTIVE_SYNC_DIALOG = names.choose_What_to_Sync_OCC_SelectiveSyncDialog - SYNC_DIALOG_FOLDER_TREE = names.choose_What_To_Synchronize_QTreeWidget - SYNC_DIALOG_ROOT_FOLDER = { - "column": 0, - "container": SYNC_DIALOG_FOLDER_TREE, - "text": "/", - "type": "QModelIndex", + DIRECTORY_NAME_EDIT_BOX = { + "buddy": names.qFileDialog_fileNameLabel_QLabel, + "name": "fileNameEdit", + "type": "QLineEdit", + "visible": 1, } - SYNC_DIALOG_OK_BUTTON = { - "text": "OK", - "type": "QPushButton", - "unnamed": 1, + VIRTUAL_FILE_RADIO_BUTTON = { + "container": names.advancedConfigGroupBox_syncModeGroupBox_QGroupBox, + "name": "useVfsRadioButton", + "type": "QRadioButton", "visible": 1, - "window": SELECTIVE_SYNC_DIALOG, + } + SYNC_EVERYTHING_RADIO_BUTTON = { + "name": "SyncAllRadioButton", + "type": "QRadioButton", + "visible": 1, + "window": names.welcome_to_ownCloud_OCC_NewAccountWizard, } - def __init__(self): - pass - - def sanitizeFolderPath(self, folderPath): - return folderPath.rstrip("/") - - def addServer(self, context): - clientDetails = getClientDetails(context) - squish.mouseClick(squish.waitForObject(self.SERVER_ADDRESS_BOX)) + @staticmethod + def add_server(server_url): + squish.mouseClick( + squish.waitForObject(AccountConnectionWizard.SERVER_ADDRESS_BOX) + ) squish.type( - squish.waitForObject(self.SERVER_ADDRESS_BOX), clientDetails['server'] + squish.waitForObject(AccountConnectionWizard.SERVER_ADDRESS_BOX), + server_url, ) - squish.clickButton(squish.waitForObject(self.NEXT_BUTTON)) + AccountConnectionWizard.sign_in() - def addUserCreds(self, context): - clientDetails = getClientDetails(context) + @staticmethod + def accept_certificate(): + squish.clickButton(squish.waitForObject(EnterPassword.ACCEPT_CERTIFICATE_YES)) - squish.type(squish.waitForObject(self.USERNAME_BOX), clientDetails['user']) - squish.type(squish.waitForObject(self.USERNAME_BOX), "") - squish.type(squish.waitForObject(self.PASSWORD_BOX), clientDetails['password']) - squish.clickButton(squish.waitForObject(self.NEXT_BUTTON)) + @staticmethod + def add_user_credentials(username, password): + AccountConnectionWizard.oidc_login(username, password) - def selectSyncFolder(self, context): - clientDetails = getClientDetails(context) - # create sync folder for user - syncPath = createUserSyncPath(context, clientDetails['user']) + @staticmethod + def oidc_login(username, password): + AccountConnectionWizard.browser_login(username, password) - try: - squish.clickButton(squish.waitForObject(self.ERROR_OK_BUTTON)) - except LookupError: - pass - squish.clickButton(squish.waitForObject(self.SELECT_LOCAL_FOLDER)) - squish.mouseClick(squish.waitForObject(self.DIRECTORY_NAME_BOX)) - squish.type(squish.waitForObject(self.DIRECTORY_NAME_BOX), syncPath) - squish.clickButton(squish.waitForObject(self.CHOOSE_BUTTON)) - test.compare( - str(squish.waitForObjectExists(self.SELECT_LOCAL_FOLDER).text), - self.sanitizeFolderPath(syncPath), + @staticmethod + def browser_login(username, password): + # wait 500ms for copy button to fully load + squish.snooze(1 / 2) + squish.mouseClick( + squish.waitForObject(AccountConnectionWizard.COPY_URL_TO_CLIPBOARD_BUTTON) ) + authorize_via_webui(username, password) - def connectAccount(self): - squish.clickButton(squish.waitForObject(self.FINISH_BUTTON)) + @staticmethod + def next_step(): + squish.clickButton( + squish.waitForObjectExists(AccountConnectionWizard.NEXT_BUTTON) + ) - def addAccount(self, context): - self.addServer(context) - self.addUserCreds(context) - self.selectSyncFolder(context) - self.connectAccount() + @staticmethod + def finish(): + squish.clickButton( + squish.waitForObjectExists(AccountConnectionWizard.FINISH_BUTTON) + ) - def openSyncDialog(self): - squish.clickButton(squish.waitForObject(self.CHOOSE_WHAT_TO_SYNC_BUTTON)) + @staticmethod + def sign_in(): + squish.clickButton( + squish.waitForObjectExists(AccountConnectionWizard.SIGN_IN_BUTTON) + ) - def selectManualSyncFolder(self): - squish.clickButton(squish.waitForObject(self.MANUAL_SYNC_FOLDER_OPTION)) + @staticmethod + def select_sync_folder(user): + # create sync folder for user + sync_path = create_user_sync_path(user) - def selectFoldersToSync(self, context): - self.openSyncDialog() + AccountConnectionWizard.select_advanced_config() - # first deselect all squish.mouseClick( - squish.waitForObject(self.SYNC_DIALOG_ROOT_FOLDER), - 11, - 11, - squish.Qt.NoModifier, - squish.Qt.LeftButton, + squish.waitForObject(AccountConnectionWizard.DIRECTORY_NAME_BOX) ) - for row in context.table[1:]: - squish.mouseClick( - squish.waitForObjectItem(self.SYNC_DIALOG_FOLDER_TREE, "/." + row[0]), - 11, - 11, - squish.Qt.NoModifier, - squish.Qt.LeftButton, - ) - squish.clickButton(squish.waitForObject(self.SYNC_DIALOG_OK_BUTTON)) + squish.type( + squish.waitForObject(AccountConnectionWizard.DIRECTORY_NAME_EDIT_BOX), + sync_path, + ) + squish.clickButton(squish.waitForObject(AccountConnectionWizard.CHOOSE_BUTTON)) + return sync_path + + @staticmethod + def set_temp_folder_as_sync_folder(folder_name): + sync_path = get_temp_resource_path(folder_name) - def sortBy(self, headerText): + # clear the current path squish.mouseClick( + squish.waitForObject(AccountConnectionWizard.SELECT_LOCAL_FOLDER) + ) + + squish.waitForObject(AccountConnectionWizard.SELECT_LOCAL_FOLDER).setText("") + + squish.type( + squish.waitForObject(AccountConnectionWizard.SELECT_LOCAL_FOLDER), + sync_path, + ) + set_current_user_sync_path(sync_path) + return sync_path + + @staticmethod + def add_account(account_details): + AccountConnectionWizard.add_account_information(account_details) + AccountConnectionWizard.finish() + + @staticmethod + def add_account_information(account_details): + if account_details["server"]: + AccountConnectionWizard.add_server(account_details["server"]) + AccountConnectionWizard.accept_certificate() + if account_details["user"]: + AccountConnectionWizard.add_user_credentials( + account_details["user"], + account_details["password"], + ) + sync_path = "" + if account_details["sync_folder"]: + AccountConnectionWizard.select_advanced_config() + sync_path = AccountConnectionWizard.set_temp_folder_as_sync_folder( + account_details["sync_folder"] + ) + elif account_details["user"]: + sync_path = AccountConnectionWizard.select_sync_folder( + account_details["user"] + ) + if sync_path: + # listen for sync status + listen_sync_status_for_item(sync_path) + + @staticmethod + def select_manual_sync_folder_option(): + squish.clickButton( squish.waitForObject( - { - "container": names.deselect_remote_folders_you_do_not_wish_to_synchronize_QHeaderView, - "text": headerText, - "type": "HeaderViewItem", - "visible": True, - } + AccountConnectionWizard.CONF_SYNC_MANUALLY_RADIO_BUTTON ) ) + + @staticmethod + def select_vfs_option(): + squish.clickButton( + squish.waitForObject(AccountConnectionWizard.VIRTUAL_FILE_RADIO_BUTTON) + ) + + @staticmethod + def select_download_everything_option(): + squish.clickButton( + squish.waitForObject(AccountConnectionWizard.SYNC_EVERYTHING_RADIO_BUTTON) + ) + + @staticmethod + def get_error_message(): + return str(squish.waitForObjectExists(AccountConnectionWizard.ERROR_LABEL).text) + + @staticmethod + def is_new_connection_window_visible(): + visible = False + try: + squish.waitForObject(AccountConnectionWizard.SERVER_ADDRESS_BOX) + visible = True + except: + pass + return visible + + @staticmethod + def is_credential_window_visible(): + visible = False + try: + squish.waitForObject(AccountConnectionWizard.OAUTH_CREDENTIAL_PAGE) + visible = True + except: + pass + return visible + + @staticmethod + def select_advanced_config(): + squish.clickButton( + squish.waitForObjectExists( + AccountConnectionWizard.ADVANCED_CONFIGURATION_BUTTON + ) + ) + + @staticmethod + def can_change_local_sync_dir(): + can_change = False + try: + squish.waitForObjectExists(AccountConnectionWizard.SELECT_LOCAL_FOLDER) + squish.clickButton( + squish.waitForObject(AccountConnectionWizard.DIRECTORY_NAME_BOX) + ) + squish.waitForObjectExists(AccountConnectionWizard.CHOOSE_BUTTON) + can_change = True + except: + pass + return can_change + + @staticmethod + def is_sync_everything_option_checked(): + return squish.waitForObjectExists( + AccountConnectionWizard.SYNC_EVERYTHING_RADIO_BUTTON + ).checked + + @staticmethod + def is_vfs_option_checked(): + return squish.waitForObjectExists( + AccountConnectionWizard.VIRTUAL_FILE_RADIO_BUTTON + ).checked + + @staticmethod + def get_local_sync_path(): + return str( + squish.waitForObjectExists( + AccountConnectionWizard.SELECT_LOCAL_FOLDER + ).displayText + ) diff --git a/test/gui/shared/scripts/pageObjects/AccountSetting.py b/test/gui/shared/scripts/pageObjects/AccountSetting.py new file mode 100644 index 00000000000..dbbb759504b --- /dev/null +++ b/test/gui/shared/scripts/pageObjects/AccountSetting.py @@ -0,0 +1,149 @@ +import names +import squish + + +class AccountSetting: + MANAGE_ACCOUNT_BUTTON = { + "container": names.mainWindow_OCC_MainWindow, + "name": "manageAccountButton", + "type": "QPushButton", + "visible": 1, + } + ACCOUNT_MENU = { + "container": names.mainWindow_manageAccountMenu_QMenu, + "type": "QAction", + "visible": True, + } + CONFIRM_REMOVE_CONNECTION_BUTTON = { + "name": "removeAccountButton", + "type": "QPushButton", + "visible": 1, + "window": names.confirmRemoveAccountDialog_QMessageBox, + } + ACCOUNT_CONNECTION_LABEL = { + "container": names.mainWindow_OCC_MainWindow, + "name": "connectionStatusLabel", + "type": "QLabel", + "visible": 1, + } + LOG_BROWSER_WINDOW = { + "name": "OCC__LogBrowser", + "type": "OCC::LogBrowser", + "visible": 1, + } + ACCOUNT_LOADING = { + "window": names.mainWindow_OCC_MainWindow, + "name": "loadingPage", + "type": "QWidget", + "visible": 0, + } + DIALOG_STACK = { + "name": "dialogStack", + "type": "QStackedWidget", + "visible": 1, + "window": names.mainWindow_OCC_MainWindow, + } + CONFIRMATION_YES_BUTTON = {"type": "QPushButton", "visible": 1} + + @staticmethod + def account_action(action, name): + squish.clickButton(squish.waitForObject(AccountSetting.MANAGE_ACCOUNT_BUTTON)) + action_selector = AccountSetting.ACCOUNT_MENU.copy() + action_selector.update({"text": action, "name": name}) + squish.activateItem(squish.waitForObject(action_selector)) + + @staticmethod + def remove_account_connection(): + AccountSetting.account_action("Remove", "removeAction") + squish.clickButton( + squish.waitForObject(AccountSetting.CONFIRM_REMOVE_CONNECTION_BUTTON) + ) + + @staticmethod + def logout(): + AccountSetting.account_action("Log out", "logInOutAction") + + @staticmethod + def login(): + AccountSetting.account_action("Log in", "logInOutAction") + + @staticmethod + def get_account_connection_label(): + return str( + squish.waitForObjectExists(AccountSetting.ACCOUNT_CONNECTION_LABEL).text + ) + + @staticmethod + def is_connecting(): + return "Connecting to" in AccountSetting.get_account_connection_label() + + @staticmethod + def is_user_signed_out(): + return "Signed out" in AccountSetting.get_account_connection_label() + + @staticmethod + def is_user_signed_in(): + return "Connected" in AccountSetting.get_account_connection_label() + + @staticmethod + def wait_until_connection_is_configured(timeout=5000): + result = squish.waitFor( + AccountSetting.is_connecting, + timeout, + ) + + if not result: + raise TimeoutError( + "Timeout waiting for connection to be configured for " + + str(timeout) + + " milliseconds" + ) + + @staticmethod + def wait_until_account_is_connected(timeout=5000): + result = squish.waitFor( + AccountSetting.is_user_signed_in, + timeout, + ) + + if not result: + raise TimeoutError( + "Timeout waiting for the account to be connected for " + + str(timeout) + + " milliseconds" + ) + return result + + @staticmethod + def wait_until_sync_folder_is_configured(timeout=5000): + result = squish.waitFor( + lambda: not squish.waitForObjectExists( + AccountSetting.ACCOUNT_LOADING + ).visible, + timeout, + ) + + if not result: + raise TimeoutError( + "Timeout waiting for sync folder to be connected for " + + str(timeout) + + " milliseconds" + ) + return result + + @staticmethod + def press_key(key): + key = key.replace('"', "") + key = f"<{key}>" + squish.nativeType(key) + + @staticmethod + def is_log_dialog_visible(): + visible = False + try: + visible = squish.waitForObjectExists( + AccountSetting.LOG_BROWSER_WINDOW + ).visible + except: + pass + return visible diff --git a/test/gui/shared/scripts/pageObjects/AccountStatus.py b/test/gui/shared/scripts/pageObjects/AccountStatus.py deleted file mode 100644 index dda7ec92cb5..00000000000 --- a/test/gui/shared/scripts/pageObjects/AccountStatus.py +++ /dev/null @@ -1,48 +0,0 @@ -import names -import squish - - -class AccountStatus: - ACCOUNT_BUTTON = { - "container": names.settings_stack_QStackedWidget, - "name": "_accountToolbox", - "type": "QToolButton", - "visible": 1, - } - ACCOUNT_MENU = { - "type": "QMenu", - "unnamed": 1, - "visible": 1, - "window": names.settings_OCC_SettingsDialog, - } - SIGNED_OUT_TEXT_BAR = { - "container": names.settings_stack_QStackedWidget, - "name": "connectLabel", - "type": "QLabel", - "visible": 1, - } - - REMOVE_CONNECTION_BUTTON = { - "container": names.settings_stack_QStackedWidget, - "text": "Remove connection", - "type": "QPushButton", - "unnamed": 1, - "visible": 1, - } - - def accountAction(self, action): - squish.sendEvent( - "QMouseEvent", - squish.waitForObject(self.ACCOUNT_BUTTON), - squish.QEvent.MouseButtonPress, - 0, - 0, - squish.Qt.LeftButton, - 0, - 0, - ) - squish.activateItem(squish.waitForObjectItem(self.ACCOUNT_MENU, action)) - - def removeConnection(self): - self.accountAction("Remove") - squish.clickButton(squish.waitForObject(self.REMOVE_CONNECTION_BUTTON)) diff --git a/test/gui/shared/scripts/pageObjects/Activity.py b/test/gui/shared/scripts/pageObjects/Activity.py index 518fa7b14a8..496cf8f4c34 100644 --- a/test/gui/shared/scripts/pageObjects/Activity.py +++ b/test/gui/shared/scripts/pageObjects/Activity.py @@ -1,81 +1,303 @@ import names import squish from objectmaphelper import RegularExpression -from helpers.FilesHelper import buildConflictedRegex + +from helpers.FilesHelper import build_conflicted_regex +from helpers.ConfigHelper import get_config class Activity: + TAB_CONTAINER = { + "container": names.settings_dialogStack_QStackedWidget, + "type": "QTabWidget", + "visible": 1, + } SUBTAB_CONTAINER = { - "container": names.settings_stack_QStackedWidget, + "container": names.settings_dialogStack_QStackedWidget, "name": "qt_tabwidget_tabbar", "type": "QTabBar", "visible": 1, } - - SUBTAB = { - "container": names.settings_stack_QStackedWidget, - "type": "QTabWidget", + NOT_SYNCED_TABLE = { + "container": names.mainWindow_OCC_MainWindow, + "name": "_tableView", + "type": "QTableView", + "visible": 1, + } + LOCAL_ACTIVITY_FILTER_BUTTON = { + "name": "_filterButton", + "type": "QPushButton", + "visible": 1, + "window": names.mainWindow_OCC_MainWindow, + } + SYNCED_ACTIVITY_FILTER_OPTION_SELECTOR = { + "type": "QMenu", "unnamed": 1, "visible": 1, + "window": names.mainWindow_OCC_MainWindow, + } + SYNCED_ACTIVITY_TABLE = { + "window": names.mainWindow_OCC_MainWindow, + "name": "_tableView", + "type": "QTableView", + "visible": 1, } + NOT_SYNCED_FILTER_BUTTON = { + "container": names.mainWindow_OCC_MainWindow, + "name": "_filterButton", + "type": "QPushButton", + "visible": 1, + } + NOT_SYNCED_FILTER_OPTION_SELECTOR = { + "type": "QMenu", + "unnamed": 1, + "visible": 1, + "window": names.mainWindow_OCC_MainWindow, + } + SYNCED_ACTIVITY_TABLE_HEADER_SELECTOR = { + "container": names.mainWindow_tableView_QTableView, + "name": "ActivityListHeaderV2", + "orientation": 1, + "type": "OCC::ExpandingHeaderView", + "visible": 1, + } + NOT_SYNCED_ACTIVITY_TABLE_HEADER_SELECTOR = { + "container": names.mainWindow_tableView_QTableView, + "name": "ActivityErrorListHeaderV2", + "orientation": 1, + "type": "OCC::ExpandingHeaderView", + "visible": 1, + } + + @staticmethod + def get_tab_object(tab_index): + return { + "container": Activity.SUBTAB_CONTAINER, + "index": tab_index, + "type": "TabItem", + } - def clickTab(self, tabName): - # It might take some time for all files to sync except the expected number of unsynced files - squish.snooze(10) + @staticmethod + def get_tab_text(tab_index): + return squish.waitForObjectExists(Activity.get_tab_object(tab_index)).text - tabFound = False + @staticmethod + def get_not_synced_file_selector(resource): + return { + "column": 1, + "container": Activity.NOT_SYNCED_TABLE, + "text": resource, + "type": "QModelIndex", + } + + @staticmethod + def get_not_synced_status(row): + return squish.waitForObjectExists( + { + "column": 6, + "row": row, + "container": Activity.NOT_SYNCED_TABLE, + "type": "QModelIndex", + } + ).text + + @staticmethod + def click_tab(tab_name): + tab_found = False + + # NOTE: Some activity tabs are loaded dynamically + # and the tab index changes after all the tabs are loaded properly + # So wait for a second to let the UI render the tabs properly + # before trying to click the tab + squish.snooze(get_config("lowestSyncTimeout")) # Selecting tab by name fails for "Not Synced" when there are no unsynced files # Because files count will be appended like "Not Synced (2)" # So to overcome this the following approach has been implemented - tabCount = squish.waitForObjectExists(self.SUBTAB_CONTAINER).count - for index in range(tabCount): - tabText = squish.waitForObjectExists( - { - "container": names.stack_qt_tabwidget_tabbar_QTabBar, - "index": index, - "type": "TabItem", - } - ).text + tab_count = squish.waitForObjectExists(Activity.SUBTAB_CONTAINER).count + tabs = [] + for index in range(tab_count): + tab_text = Activity.get_tab_text(index) + tabs.append(tab_text) - if tabName in tabText: - tabFound = True - squish.clickTab(squish.waitForObject(self.SUBTAB), tabText) + if tab_name in tab_text: + tab_found = True + # click_tab becomes flaky with "Not Synced" tab + # because the tab text changes. e.g. "Not Synced (2)" + # squish.click_tab(Activity.TAB_CONTAINER, tab_text) + + # NOTE: If only the objectOrName is specified, + # the object is clicked in the middle by the Qt::LeftButton button + # and with no keyboard modifiers pressed. + squish.mouseClick( + squish.waitForObjectExists(Activity.get_tab_object(index)) + ) break - if not tabFound: - raise Exception("Tab not found: " + tabName) + if not tab_found: + raise LookupError( + "Tab not found: " + + tab_name + + " in " + + str(tabs) + + ". Tabs count: " + + str(tab_count) + ) - def checkFileExist(self, filename): - squish.waitForObject(names.settings_OCC_SettingsDialog) + @staticmethod + def check_file_exist(filename): squish.waitForObjectExists( - { - "column": 1, - "container": names.oCC_IssuesWidget_tableView_QTableView, - "text": RegularExpression(buildConflictedRegex(filename)), - "type": "QModelIndex", - } + Activity.get_not_synced_file_selector( + RegularExpression(build_conflicted_regex(filename)) + ) ) - def checkBlackListedFileExist(self, filename): - squish.waitForObject(names.settings_OCC_SettingsDialog) + @staticmethod + def is_resource_blacklisted(filename): + result = squish.waitFor( + lambda: Activity.has_sync_status(filename, "Blacklisted"), + get_config("maxSyncTimeout") * 1000, + ) + return result + + @staticmethod + def is_resource_ignored(filename): + result = squish.waitFor( + lambda: Activity.has_sync_status(filename, "File Ignored"), + get_config("maxSyncTimeout") * 1000, + ) + return result - # The blacklisted file does not have text like (conflicted copy) appended to it in the not synced table. - fileRow = squish.waitForObject( + @staticmethod + def is_resource_excluded(filename): + result = squish.waitFor( + lambda: Activity.has_sync_status(filename, "Excluded"), + get_config("maxSyncTimeout") * 1000, + ) + return result + + @staticmethod + def has_sync_status(filename, status): + try: + file_row = squish.waitForObject( + Activity.get_not_synced_file_selector(filename), + get_config("lowestSyncTimeout") * 1000, + )["row"] + if Activity.get_not_synced_status(file_row) == status: + return True + return False + except: + return False + + @staticmethod + def select_synced_filter(sync_filter): + squish.clickButton(squish.waitForObject(Activity.LOCAL_ACTIVITY_FILTER_BUTTON)) + squish.activateItem( + squish.waitForObjectItem( + Activity.SYNCED_ACTIVITY_FILTER_OPTION_SELECTOR, sync_filter + ) + ) + + @staticmethod + def get_synced_file_selector(resource): + return { + "column": Activity.get_synced_table_column_number_by_name("File"), + "container": Activity.SYNCED_ACTIVITY_TABLE, + "text": resource, + "type": "QModelIndex", + } + + @staticmethod + def get_synced_table_column_number_by_name(column_name): + return squish.waitForObject( { - "column": 1, - "container": names.oCC_IssuesWidget_tableView_QTableView, - "text": filename, - "type": "QModelIndex", + "container": Activity.SYNCED_ACTIVITY_TABLE_HEADER_SELECTOR, + "text": column_name, + "type": "HeaderViewItem", + "visible": True, } - )["row"] + )["section"] - squish.waitForObjectExists( + @staticmethod + def check_synced_table(resource, action, account): + try: + file_row = squish.waitForObject( + Activity.get_synced_file_selector(resource), + get_config("lowestSyncTimeout") * 1000, + )["row"] + squish.waitForObjectExists( + { + "column": Activity.get_synced_table_column_number_by_name("Action"), + "row": file_row, + "container": Activity.SYNCED_ACTIVITY_TABLE, + "text": action, + "type": "QModelIndex", + } + ) + squish.waitForObjectExists( + { + "column": Activity.get_synced_table_column_number_by_name( + "Account" + ), + "row": file_row, + "container": Activity.SYNCED_ACTIVITY_TABLE, + "text": account, + "type": "QModelIndex", + } + ) + return True + except: + return False + + @staticmethod + def select_not_synced_filter(filter_option): + squish.clickButton(squish.waitForObject(Activity.NOT_SYNCED_FILTER_BUTTON)) + squish.activateItem( + squish.waitForObjectItem( + Activity.NOT_SYNCED_FILTER_OPTION_SELECTOR, filter_option + ) + ) + + @staticmethod + def get_not_synced_table_column_number_by_name(column_name): + return squish.waitForObject( { - "column": 6, - "row": fileRow, - "container": names.oCC_IssuesWidget_tableView_QTableView, - "text": "Blacklisted", - "type": "QModelIndex", + "container": Activity.NOT_SYNCED_ACTIVITY_TABLE_HEADER_SELECTOR, + "text": column_name, + "type": "HeaderViewItem", + "visible": True, } - ) + )["section"] + + @staticmethod + def check_not_synced_table(resource, status, account): + try: + file_row = squish.waitForObject( + Activity.get_not_synced_file_selector(resource), + get_config("lowestSyncTimeout") * 1000, + )["row"] + squish.waitForObjectExists( + { + "column": Activity.get_not_synced_table_column_number_by_name( + "Status" + ), + "row": file_row, + "container": Activity.NOT_SYNCED_TABLE, + "text": status, + "type": "QModelIndex", + } + ) + squish.waitForObjectExists( + { + "column": Activity.get_not_synced_table_column_number_by_name( + "Account" + ), + "row": file_row, + "container": Activity.NOT_SYNCED_TABLE, + "text": account, + "type": "QModelIndex", + } + ) + return True + except: + return False diff --git a/test/gui/shared/scripts/pageObjects/EnterPassword.py b/test/gui/shared/scripts/pageObjects/EnterPassword.py index 5311fe65421..b060c9d7d2d 100644 --- a/test/gui/shared/scripts/pageObjects/EnterPassword.py +++ b/test/gui/shared/scripts/pageObjects/EnterPassword.py @@ -2,28 +2,80 @@ import squish +from helpers.WebUIHelper import authorize_via_webui +from helpers.ConfigHelper import get_config + + class EnterPassword: - PASSWORD_BOX = { - "container": names.enter_Password_QInputDialog, + LOGIN_CONTAINER = { + "name": "LoginRequiredDialog", + "type": "OCC::LoginRequiredDialog", + "visible": 1, + } + LOGIN_USER_LABEL = { + "container": names.groupBox_OCC_QmlUtils_OCQuickWidget, + "type": "Label", + "visible": True, + } + USERNAME_BOX = { + "name": "usernameLineEdit", "type": "QLineEdit", - "unnamed": 1, "visible": 1, + "window": LOGIN_CONTAINER, + } + PASSWORD_BOX = { + "container": names.groupBox_OCC_QmlUtils_OCQuickWidget, + "id": "passwordField", + "type": "TextField", + "visible": True, + } + LOGIN_BUTTON = { + "container": names.groupBox_OCC_QmlUtils_OCQuickWidget, + "id": "loginButton", + "type": "Button", + "visible": True, } - OK_BUTTON = { - "text": "OK", + LOGOUT_BUTTON = { + "container": names.groupBox_OCC_QmlUtils_OCQuickWidget, + "id": "logOutButton", + "type": "Button", + "visible": True, + } + COPY_URL_TO_CLIPBOARD_BUTTON = { + "container": names.stackedWidget_groupBox_QGroupBox_2, "type": "QPushButton", "unnamed": 1, "visible": 1, - "window": names.enter_Password_QInputDialog, } + TLS_CERT_WINDOW = { + "name": "OCC__TlsErrorDialog", + "type": "OCC::TlsErrorDialog", + "visible": 1, + } + ACCEPT_CERTIFICATE_YES = { + "type": "QPushButton", + "visible": 1, + "window": TLS_CERT_WINDOW, + } + + def __init__(self, occurrence=1): + if occurrence > 1 and get_config("ocis"): + self.TLS_CERT_WINDOW.update({"occurrence": occurrence}) + + def get_username(self): + # Parse username from the login label: + label = str(squish.waitForObjectExists(self.LOGIN_USER_LABEL).text) + username = label.split(" ", maxsplit=2)[1] + return username.capitalize() + + def oidc_relogin(self, username, password): + # wait 500ms for copy button to fully load + squish.snooze(1 / 2) + squish.mouseClick(squish.waitForObject(self.COPY_URL_TO_CLIPBOARD_BUTTON)) + authorize_via_webui(username, password) - def __init__(self): - pass + def relogin(self, username, password): + self.oidc_relogin(username, password) - def enterPassword(self, password): - try: - squish.waitForObject(self.PASSWORD_BOX, 10000) - squish.type(squish.waitForObject(self.PASSWORD_BOX), password) - squish.clickButton(squish.waitForObject(self.OK_BUTTON)) - except LookupError: - pass + def accept_certificate(self): + squish.clickButton(squish.waitForObject(self.ACCEPT_CERTIFICATE_YES)) diff --git a/test/gui/shared/scripts/pageObjects/PublicLinkDialog.py b/test/gui/shared/scripts/pageObjects/PublicLinkDialog.py deleted file mode 100644 index ea7af0ea319..00000000000 --- a/test/gui/shared/scripts/pageObjects/PublicLinkDialog.py +++ /dev/null @@ -1,196 +0,0 @@ -import names -import squish -import test -import datetime - - -class PublicLinkDialog: - PUBLIC_LINKS_TAB = { - "container": names.sharingDialog_qt_tabwidget_tabbar_QTabBar, - "text": "Public Links", - "type": "TabItem", - } - ITEM_TO_SHARE = { - "name": "label_name", - "type": "QLabel", - "visible": 1, - "window": names.sharingDialog_OCC_ShareDialog, - } - PASSWORD_CHECKBOX = { - "container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, - "name": "checkBox_password", - "type": "QCheckBox", - "visible": 1, - } - CREATE_SHARE_BUTTON = { - "container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, - "name": "createShareButton", - "type": "QPushButton", - "visible": 1, - } - - PUBLIC_LINK_NAME = { - "column": 0, - "container": names.oCC_ShareLinkWidget_linkShares_QTableWidget, - "row": 0, - "type": "QModelIndex", - } - EXPIRATION_DATE_FIELD = { - "container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, - "name": "qt_spinbox_lineedit", - "type": "QLineEdit", - "visible": 1, - } - READ_ONLY_RADIO_BUTTON = { - "container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, - "name": "radio_readOnly", - "type": "QRadioButton", - "visible": 1, - } - READ_WRITE_RADIO_BUTTON = { - "container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, - "name": "radio_readWrite", - "type": "QRadioButton", - "visible": 1, - } - UPLOAD_ONLY_RADIO_BUTTON = { - "container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget, - "name": "radio_uploadOnly", - "type": "QRadioButton", - "visible": 1, - } - - def openPublicLinkDialog(self): - squish.mouseClick( - squish.waitForObject(self.PUBLIC_LINKS_TAB), - 0, - 0, - squish.Qt.NoModifier, - squish.Qt.LeftButton, - ) - - def createPublicLink(self, context, resource, password='', permissions=''): - radioObjectName = '' - if permissions: - radioObjectName = self.getRadioObjectForPermssion(permissions) - - test.compare( - str(squish.waitForObjectExists(self.ITEM_TO_SHARE).text), - resource.replace(context.userData['currentUserSyncPath'], ''), - ) - - if radioObjectName: - test.compare( - str(squish.waitForObjectExists(radioObjectName).text), permissions - ) - squish.clickButton(squish.waitForObject(radioObjectName)) - - if password: - squish.clickButton(squish.waitForObject(self.PASSWORD_CHECKBOX)) - squish.mouseClick( - squish.waitForObject(self.PASSWORD_CHECKBOX), - 0, - 0, - squish.Qt.NoModifier, - squish.Qt.LeftButton, - ) - squish.type( - squish.waitForObject(self.PASSWORD_CHECKBOX), - password, - ) - - squish.clickButton(squish.waitForObject(self.CREATE_SHARE_BUTTON)) - squish.waitFor( - lambda: ( - squish.waitForObject(names.linkShares_0_0_QModelIndex).displayText - == "Public link" - ) - ) - - def togglesPassword(self): - squish.clickButton(squish.waitForObject(self.PASSWORD_CHECKBOX)) - - def setExpirationDate(self, context, publicLinkName, resource): - test.compare( - str(squish.waitForObjectExists(self.ITEM_TO_SHARE).text), - resource, - ) - test.compare( - str(squish.waitForObjectExists(self.PUBLIC_LINK_NAME).text), - publicLinkName, - ) - expDate = [] - for row in context.table: - if row[0] == 'expireDate': - expDate = datetime.datetime.strptime(row[1], '%Y-%m-%d') - expYear = expDate.year - 2000 - squish.mouseClick( - squish.waitForObject(self.EXPIRATION_DATE_FIELD), - 0, - 0, - squish.Qt.NoModifier, - squish.Qt.LeftButton, - ) - squish.nativeType("") - squish.nativeType("") - squish.nativeType(expDate.month) - squish.nativeType(expDate.day) - squish.nativeType(expYear) - squish.nativeType("") - squish.testSettings.silentVerifications = True - squish.waitFor( - lambda: (test.xvp("publicLinkExpirationProgressIndicatorInvisible")) - ) - waitFor(lambda: (test.vp("publicLinkExpirationProgressIndicatorInvisible"))) - squish.testSettings.silentVerifications = False - test.compare( - str(squish.waitForObjectExists(self.EXPIRATION_DATE_FIELD).displayText), - str(expDate.month) + "/" + str(expDate.day) + "/" + str(expYear), - ) - - def getRadioObjectForPermssion(self, permissions): - radioObjectName = '' - if permissions == 'Download / View' or permissions == 'Viewer': - radioObjectName = self.READ_ONLY_RADIO_BUTTON - elif permissions == 'Download / View / Edit' or permissions == 'Editor': - radioObjectName = self.READ_WRITE_RADIO_BUTTON - elif permissions == 'Upload only (File Drop)' or permissions == 'Contributor': - radioObjectName = self.UPLOAD_ONLY_RADIO_BUTTON - else: - raise Exception("No such radio object found for given permission") - - return radioObjectName - - def createPublicLinkWithRole(self, role): - radioObjectName = self.getRadioObjectForPermssion(role) - - squish.clickButton(squish.waitForObject(radioObjectName)) - squish.clickButton(squish.waitForObject(self.CREATE_SHARE_BUTTON)) - squish.waitFor( - lambda: ( - squish.findObject(names.linkShares_0_0_QModelIndex).displayText - == "Public link" - ) - ) - - def changePassword(self, publicLinkName, password): - test.compare( - str(squish.waitForObjectExists(self.PUBLIC_LINK_NAME).text), - publicLinkName, - ) - squish.mouseClick( - squish.waitForObject(names.oCC_ShareLinkWidget_lineEdit_password_QLineEdit), - 0, - 0, - squish.Qt.NoModifier, - squish.Qt.LeftButton, - ) - squish.type( - squish.waitForObject(names.oCC_ShareLinkWidget_lineEdit_password_QLineEdit), - password, - ) - squish.clickButton( - squish.waitForObject( - names.oCC_ShareLinkWidget_pushButton_setPassword_QPushButton - ) - ) diff --git a/test/gui/shared/scripts/pageObjects/Settings.py b/test/gui/shared/scripts/pageObjects/Settings.py new file mode 100644 index 00000000000..77f428a2a2b --- /dev/null +++ b/test/gui/shared/scripts/pageObjects/Settings.py @@ -0,0 +1,124 @@ +import names +import squish + +from pageObjects.Toolbar import Toolbar + + +class Settings: + GENERAL_CHECKBOX_OPTION_ITEM = { + "container": names.scrollArea_generalGroupBox_QGroupBox, + "type": "QCheckBox", + "visible": 1, + } + ADVANCED_CHECKBOX_OPTION_ITEM = { + "container": names.scrollArea_advancedGroupBox_QGroupBox, + "type": "QCheckBox", + "visible": 1, + } + NETWORK_OPTION_ITEM = { + "container": names.scrollArea_groupBox_QGroupBox, + "type": "QGroupBox", + "visible": 1, + } + ABOUT_BUTTON = { + "container": names.mainWindow_QMenu, + "name": "aboutAction", + "type": "QAction", + "visible": True, + } + ABOUT_DIALOG = { + "container": names.mainWindow_qt_tabwidget_stackedwidget_QStackedWidget, + "name": "tab", + "type": "QWidget", + "visible": 1, + } + ABOUT_DIALOG_OK_BUTTON = { + "type": "QPushButton", + "unnamed": 1, + "visible": 1, + "window": ABOUT_DIALOG, + } + CLOSE_BUTTON = { + "name": "closeButton", + "type": "QPushButton", + "visible": 1, + "window": names.mainWindow_OCC_MainWindow, + } + + GENERAL_OPTIONS_MAP = { + "Start on Login": "autostartCheckBox", + "Use Monochrome Icons in the system tray": "monoIconsCheckBox", + "Language": "languageDropdown", + "Show desktop Notifications": "desktopNotificationsCheckBox", + } + ADVANCED_OPTION_MAP = { + "Sync hidden files": "syncHiddenFilesCheckBox", + "Show crash reporter": "", + "Edit ignored files": "ignoredFilesButton", + "Log settings": "logSettingsButton", + "Ask for confirmation before synchronizing folders larger than 500 MB": "newFolderLimitCheckBox", + "Ask for confirmation before synchronizing external storages": "newExternalStorage", + } + NETWORK_OPTION_MAP = { + "Proxy Settings": "proxyGroupBox", + "Download Bandwidth": "downloadBox", + "Upload Bandwidth": "uploadBox", + } + + @staticmethod + def get_general_checkbox_option_selector(name): + selector = Settings.GENERAL_CHECKBOX_OPTION_ITEM.copy() + selector.update({"name": name}) + if name == "languageDropdown": + selector.update({"type": "QComboBox"}) + return selector + + @staticmethod + def get_advanced_checkbox_option_selector(name): + selector = Settings.ADVANCED_CHECKBOX_OPTION_ITEM.copy() + selector.update({"name": name}) + if name in ("ignoredFilesButton", "logSettingsButton"): + selector.update({"type": "QPushButton"}) + return selector + + @staticmethod + def get_network_option_selector(name): + selector = Settings.NETWORK_OPTION_ITEM.copy() + selector.update({"name": name}) + return selector + + @staticmethod + def check_general_option(option): + selector = Settings.GENERAL_OPTIONS_MAP[option] + squish.waitForObjectExists( + Settings.get_general_checkbox_option_selector(selector) + ) + + @staticmethod + def check_advanced_option(option): + selector = Settings.ADVANCED_OPTION_MAP[option] + squish.waitForObjectExists( + Settings.get_advanced_checkbox_option_selector(selector) + ) + + @staticmethod + def check_network_option(option): + selector = Settings.NETWORK_OPTION_MAP[option] + squish.waitForObjectExists(Settings.get_network_option_selector(selector)) + + @staticmethod + def open_about_button(): + squish.mouseClick(squish.waitForObject(Toolbar.TOOLBAR_MORE_BUTTON)) + squish.mouseClick(squish.waitForObject(Settings.ABOUT_BUTTON)) + + @staticmethod + def wait_for_about_dialog_to_be_visible(): + squish.waitForObjectExists(Settings.ABOUT_DIALOG) + + @staticmethod + def close_about_dialog(): + squish.clickButton(squish.waitForObjectExists(Settings.ABOUT_DIALOG_OK_BUTTON)) + + @staticmethod + def close_settings_tab(): + squish.clickButton(squish.waitForObjectExists(Settings.CLOSE_BUTTON)) diff --git a/test/gui/shared/scripts/pageObjects/SharingDialog.py b/test/gui/shared/scripts/pageObjects/SharingDialog.py deleted file mode 100644 index a8a4f1ea521..00000000000 --- a/test/gui/shared/scripts/pageObjects/SharingDialog.py +++ /dev/null @@ -1,132 +0,0 @@ -import names -import squish -import test - - -class SharingDialog: - - ITEM_TO_SHARE = { - "name": "label_name", - "type": "QLabel", - "visible": 1, - "window": names.sharingDialog_OCC_ShareDialog, - } - SHARE_WITH_COLLABORATOR_INPUT_FIELD = { - "container": names.qt_tabwidget_stackedwidget_SharingDialogUG_OCC_ShareUserGroupWidget, - "name": "shareeLineEdit", - "type": "QLineEdit", - "visible": 1, - } - SUGGESTED_COLLABORATOR = {"type": "QListView", "unnamed": 1, "visible": 1} - EDIT_PERMISSIONS_CHECKBOX = { - "container": names.sharingDialogUG_scrollArea_QScrollArea, - "name": "permissionsEdit", - "type": "QCheckBox", - "visible": 1, - } - SHARE_PERMISSIONS_CHECKBOX = { - "container": names.sharingDialogUG_scrollArea_QScrollArea, - "name": "permissionShare", - "type": "QCheckBox", - "visible": 1, - } - SHARING_DIALOG_CLOSE_BUTTON = { - "text": "Close", - "type": "QPushButton", - "unnamed": 1, - "visible": 1, - "window": names.sharingDialog_OCC_ShareDialog, - } - SHARED_WITH_ = { - "container": names.sharingDialogUG_scrollArea_QScrollArea, - "name": "sharedWith", - "type": "QLabel", - "visible": 1, - } - SHARING_DIALOG = { - "type": "QLabel", - "unnamed": 1, - "visible": 1, - "window": names.sharingDialog_OCC_ShareDialog, - } - SHARING_DIALOG_ERROR = {"name": "errorLabel", "type": "QLabel", "visible": 1} - - def getAvailablePermission(self): - editChecked = squish.waitForObjectExists(self.EDIT_PERMISSIONS_CHECKBOX).checked - shareChecked = squish.waitForObjectExists( - self.SHARE_PERMISSIONS_CHECKBOX - ).checked - - return editChecked, shareChecked - - def addCollaborator(self, receiver, permissions, isGroup=False): - self.selectCollaborator(receiver, isGroup) - permissionsList = permissions.split(",") - - editChecked, shareChecked = self.getAvailablePermission() - - if ('edit' in permissionsList and editChecked == False) or ( - 'edit' not in permissionsList and editChecked == True - ): - squish.clickButton(squish.waitForObject(self.EDIT_PERMISSIONS_CHECKBOX)) - if ('share' in permissionsList and shareChecked == False) or ( - 'share' not in permissionsList and shareChecked == True - ): - squish.clickButton(squish.waitForObject(self.SHARE_PERMISSIONS_CHECKBOX)) - - squish.clickButton(squish.waitForObject(self.SHARING_DIALOG_CLOSE_BUTTON)) - - def getSharingDialogMessage(self): - return str(squish.waitForObjectExists(self.SHARING_DIALOG).text) - - def getErrorText(self): - return str(squish.waitForObjectExists(self.SHARING_DIALOG_ERROR).text) - - def selectCollaborator(self, receiver, isGroup=False): - postFixInSuggestion = "" - if isGroup: - postFixInSuggestion = " (group)" - - squish.mouseClick( - squish.waitForObject(self.SHARE_WITH_COLLABORATOR_INPUT_FIELD), - 0, - 0, - squish.Qt.NoModifier, - squish.Qt.LeftButton, - ) - squish.type( - squish.waitForObject(self.SHARE_WITH_COLLABORATOR_INPUT_FIELD), - receiver, - ) - squish.mouseClick( - squish.waitForObjectItem( - self.SUGGESTED_COLLABORATOR, receiver + postFixInSuggestion - ), - 0, - 0, - squish.Qt.NoModifier, - squish.Qt.LeftButton, - ) - - def removePermissions(self, permissions): - removePermissionsList = permissions.split(",") - ( - isEditPermissionAvailable, - isSharePermissionAvailable, - ) = self.getAvailablePermission() - - if 'share' in removePermissionsList and isSharePermissionAvailable: - squish.clickButton( - squish.waitForObject(names.scrollArea_permissionShare_QCheckBox) - ) - - if 'edit' in removePermissionsList and isEditPermissionAvailable: - squish.clickButton( - squish.waitForObject(names.scrollArea_permissionsEdit_QCheckBox) - ) - - def verifyResource(self, resource): - test.compare( - str(squish.waitForObjectExists(self.ITEM_TO_SHARE).text), - resource, - ) diff --git a/test/gui/shared/scripts/pageObjects/SyncConnection.py b/test/gui/shared/scripts/pageObjects/SyncConnection.py new file mode 100644 index 00000000000..3b1b6900eff --- /dev/null +++ b/test/gui/shared/scripts/pageObjects/SyncConnection.py @@ -0,0 +1,197 @@ +import names +import squish +import object # pylint: disable=redefined-builtin + +from helpers.ConfigHelper import get_config + + +class SyncConnection: + FOLDER_SYNC_CONNECTION_TREE = { + "container": names.mainWindow_stackedWidget_QStackedWidget, + "name": "accountFoldersTreeView", + "type": "QTreeView", + "visible": True, + } + FOLDER_SYNC_CONNECTION = { + "container": names.stackedWidget_groupBox_QGroupBox, + "type": "QTreeWidget", + "unnamed": 1, + "visible": 1, + } + FOLDER_SYNC_CONNECTION_LABEL = { + "container": names.stackedWidget_accountFoldersTreeView_QTreeView_2, + "type": "QModelIndex", + "column": 0, + } + FOLDER_SYNC_CONNECTION_MENU_BUTTON = { + "columnIndex": 1, + "container": names.stackedWidget_accountFoldersTreeView_QTreeView_2, + "name": "buttonDelegateButton", + "type": "QPushButton", + "visible": 1, + } + DISABLE_VFS_CONFIRMATION_BUTTON = { + "name": "disableVfsButton", + "type": "QAction", + "visible": 1, + "window": names.confirmDisableVfsDialog_QMessageBox, + } + SELECTIVE_SYNC_OK_BUTTON = { + "container": names.settings_stack_QStackedWidget, + "type": "QPushButton", + "visible": 1, + } + CANCEL_FOLDER_SYNC_CONNECTION_DIALOG = { + "name": "cancelRemoveFolderSyncButton", + "type": "QPushButton", + "visible": 1, + "window": names.confirmRemoveFolderSyncDialog_QMessageBox, + } + REMOVE_FOLDER_SYNC_CONNECTION_BUTTON = { + "name": "removeFolderSyncButton", + "type": "QPushButton", + "visible": 1, + "window": names.confirmRemoveFolderSyncDialog_QMessageBox, + } + FORCE_SYNC_ACTION_MENU_OPTION = { + "container": names.mainWindow_folderOptionsMenu_QMenu, + "name": "forceSyncAction", + "type": "QAction", + "visible": True, + } + PAUSE_SYNC_ACTION_MENU_OPTION = { + "container": names.mainWindow_folderOptionsMenu_QMenu, + "name": "pauseSyncAction", + "type": "QAction", + "visible": True, + } + SELECTIVE_SYNC_ACTION_MENU_OPTION = { + "container": names.settings_folderOptionsMenu_QMenu, + "name": "selectiveSyncAction", + "type": "QAction", + "visible": True, + } + REMOVE_FOLDER_SYNC_ACTION_MENU_OPTION = { + "container": names.mainWindow_folderOptionsMenu_QMenu, + "name": "removeFolderSyncAction", + "type": "QAction", + "visible": True, + } + ENABLE_VFS_ACTION_MENU_OPTION = { + "container": names.settings_folderOptionsMenu_QMenu, + "name": "enableVfsAction", + "type": "QAction", + "visible": True, + } + + @staticmethod + def open_menu(sync_folder=""): + if get_config("ocis") and not sync_folder: + sync_folder = get_config("personal_sync_folder") + elif not sync_folder: + sync_folder = "ownCloud" + selector = SyncConnection.FOLDER_SYNC_CONNECTION_LABEL.copy() + selector.update({"text": sync_folder}) + + folder_sync_connection = squish.waitForObject(selector) + squish.mouseClick(folder_sync_connection) + + menu_button_selector = SyncConnection.FOLDER_SYNC_CONNECTION_MENU_BUTTON.copy() + menu_button_selector.update({"rowIndex": folder_sync_connection.row}) + menu_button = squish.waitForObject(menu_button_selector) + squish.mouseClick(menu_button) + + @staticmethod + def perform_action(action, sync_folder=""): + SyncConnection.open_menu(sync_folder) + squish.activateItem(squish.waitForObject(action)) + + @staticmethod + def force_sync(): + SyncConnection.perform_action(SyncConnection.FORCE_SYNC_ACTION_MENU_OPTION) + + @staticmethod + def pause_sync(): + SyncConnection.perform_action(SyncConnection.PAUSE_SYNC_ACTION_MENU_OPTION) + + @staticmethod + def resume_sync(): + SyncConnection.perform_action(SyncConnection.PAUSE_SYNC_ACTION_MENU_OPTION) + + @staticmethod + def enable_vfs(): + SyncConnection.perform_action(SyncConnection.ENABLE_VFS_ACTION_MENU_OPTION) + + @staticmethod + def disable_vfs(): + SyncConnection.perform_action(SyncConnection.ENABLE_VFS_ACTION_MENU_OPTION) + squish.clickButton( + squish.waitForObject(SyncConnection.DISABLE_VFS_CONFIRMATION_BUTTON) + ) + + @staticmethod + def has_menu_item(item): + return squish.waitForObjectItem(names.settings_folderOptionsMenu_QMenu, item) + + @staticmethod + def menu_item_exists(menu_item): + obj = names.settings_folderOptionsMenu_QMenu.copy() + obj.update({"type": "QAction", "text": menu_item}) + return object.exists(obj) + + @staticmethod + def choose_what_to_sync(): + SyncConnection.open_menu() + SyncConnection.perform_action(SyncConnection.SELECTIVE_SYNC_ACTION_MENU_OPTION) + + @staticmethod + def unselect_folder_in_selective_sync(folder_name): + sync_folders = object.children( + squish.waitForObject(SyncConnection.FOLDER_SYNC_CONNECTION) + ) + for sync_folder in sync_folders: + # TODO: allow selective sync in other sync folders as well + if hasattr(sync_folder, "text") and sync_folder.text == "Personal": + items = object.children(sync_folder) + for item in items: + if hasattr(item, "text") and item.text: + # remove item size suffix + # example: folder1 (13 B) => folder1 + item_name = item.text.rsplit(" ", 1) + if item_name[0] == folder_name: + # NOTE: checkbox does not have separate object + # click on (11,11) which is a checkbox to unselect the folder + squish.mouseClick( + item, + 11, + 11, + squish.Qt.NoModifier, + squish.Qt.LeftButton, + ) + break + squish.clickButton( + squish.waitForObject(SyncConnection.SELECTIVE_SYNC_OK_BUTTON) + ) + + @staticmethod + def get_folder_connection_count(): + tree = squish.waitForObject(SyncConnection.FOLDER_SYNC_CONNECTION_TREE) + return tree.model().rowCount() + + @staticmethod + def remove_folder_sync_connection(sync_folder=""): + SyncConnection.perform_action( + SyncConnection.REMOVE_FOLDER_SYNC_ACTION_MENU_OPTION, sync_folder + ) + + @staticmethod + def cancel_folder_sync_connection_removal(): + squish.clickButton( + squish.waitForObject(SyncConnection.CANCEL_FOLDER_SYNC_CONNECTION_DIALOG) + ) + + @staticmethod + def confirm_folder_sync_connection_removal(): + squish.clickButton( + squish.waitForObject(SyncConnection.REMOVE_FOLDER_SYNC_CONNECTION_BUTTON) + ) diff --git a/test/gui/shared/scripts/pageObjects/SyncConnectionWizard.py b/test/gui/shared/scripts/pageObjects/SyncConnectionWizard.py new file mode 100644 index 00000000000..b767d0e9213 --- /dev/null +++ b/test/gui/shared/scripts/pageObjects/SyncConnectionWizard.py @@ -0,0 +1,333 @@ +from os import path +import names +import squish + +from helpers.SetupClientHelper import ( + get_current_user_sync_path, +) +from helpers.ConfigHelper import get_config + + +class SyncConnectionWizard: + ADD_FOLDER_SYNC_CONNECTION_WIZARD = { + "name": "FolderWizardSourcePage", + "type": "OCC::FolderWizardLocalPath", + "visible": 1, + "window": names.add_Folder_Sync_Connection_OCC_FolderWizard, + } + CHOOSE_LOCAL_SYNC_FOLDER = { + "name": "localFolderLineEdit", + "type": "QLineEdit", + "visible": 1, + "window": names.add_Folder_Sync_Connection_OCC_FolderWizard, + } + BACK_BUTTON = { + "window": names.add_Folder_Sync_Connection_OCC_FolderWizard, + "type": "QPushButton", + "visible": 1, + } + NEXT_BUTTON = { + "window": names.add_Folder_Sync_Connection_OCC_FolderWizard, + "name": "__qt__passive_wizardbutton1", + "type": "QPushButton", + "visible": 1, + } + SELECTIVE_SYNC_ROOT_FOLDER = { + "column": 0, + "container": names.folder_Sync_Connection_Deselect_remote_folders_QTreeWidget, + "type": "QModelIndex", + } + SYNC_DIALOG_FOLDER_TREE = { + "column": 0, + "container": names.deselect_remote_folders_you_do_not_wish_to_synchronize_ownCloud_QModelIndex, + "type": "QModelIndex", + } + ADD_SYNC_CONNECTION_BUTTON = { + "name": "qt_wizard_finish", + "type": "QPushButton", + "visible": 1, + "window": names.add_Folder_Sync_Connection_OCC_FolderWizard, + } + REMOTE_FOLDER_TREE = { + "container": names.add_Folder_Sync_Connection_groupBox_QGroupBox, + "name": "folderTreeWidget", + "type": "QTreeWidget", + "visible": 1, + } + VFS_CHECKBOX = { + "type": "QCheckBox", + "unnamed": 1, + "visible": 1, + "window": names.add_Folder_Sync_Connection_OCC_FolderWizard, + } + SELECTIVE_SYNC_TREE_HEADER = { + "container": names.folder_Sync_Connection_Deselect_remote_folders_QTreeWidget, + "orientation": 1, + "type": "QHeaderView", + "unnamed": 1, + "visible": 1, + } + CANCEL_FOLDER_SYNC_CONNECTION_WIZARD = { + "window": names.add_Folder_Sync_Connection_OCC_FolderWizard, + "name": "qt_wizard_cancel", + "type": "QPushButton", + "visible": 1, + } + SPACE_NAME_SELECTOR = { + "container": names.quickWidget_scrollView_ScrollView_2, + "type": "Label", + "visible": True, + } + CREATE_REMOTE_FOLDER_BUTTON = { + "container": names.add_Folder_Sync_Connection_groupBox_QGroupBox, + "name": "addFolderButton", + "type": "QPushButton", + "visible": 1, + } + REFRESH_BUTTON = { + "container": names.add_Folder_Sync_Connection_groupBox_QGroupBox, + "name": "refreshButton", + "type": "QPushButton", + "visible": 1, + } + REMOTE_FOLDER_SELECTION_INPUT = { + "name": "folderEntry", + "type": "QLineEdit", + "visible": 1, + "window": names.add_Folder_Sync_Connection_OCC_FolderWizard, + } + ADD_FOLDER_SYNC_BUTTON = { + "checkable": False, + "container": names.mainWindow_stackedWidget_QStackedWidget, + "name": "addFolderSyncButton", + "type": "QPushButton", + "visible": True, + } + WARN_LABEL = { + "window": names.add_Folder_Sync_Connection_OCC_FolderWizard, + "name": "warnLabel", + "type": "QLabel", + "visible": 1, + } + + @staticmethod + def set_sync_path_ocis(sync_path): + if not sync_path: + sync_path = path.join( + get_current_user_sync_path(), get_config("syncConnectionName") + ) + squish.type( + squish.waitForObject(SyncConnectionWizard.CHOOSE_LOCAL_SYNC_FOLDER), + "", + ) + squish.type( + SyncConnectionWizard.CHOOSE_LOCAL_SYNC_FOLDER, + sync_path, + ) + SyncConnectionWizard.next_step() + + @staticmethod + def set_sync_path(sync_path=""): + SyncConnectionWizard.set_sync_path_ocis(sync_path) + + @staticmethod + def next_step(): + squish.clickButton(squish.waitForObject(SyncConnectionWizard.NEXT_BUTTON)) + + @staticmethod + def back(): + squish.clickButton(squish.waitForObject(SyncConnectionWizard.BACK_BUTTON)) + + @staticmethod + def select_remote_destination_folder(folder): + squish.mouseClick( + squish.waitForObjectItem(SyncConnectionWizard.REMOTE_FOLDER_TREE, folder) + ) + SyncConnectionWizard.next_step() + + @staticmethod + def deselect_all_remote_folders(): + # NOTE: checkbox does not have separate object + # click on (11,11) which is a checkbox + squish.mouseClick( + squish.waitForObject(SyncConnectionWizard.SELECTIVE_SYNC_ROOT_FOLDER), + 11, + 11, + squish.Qt.NoModifier, + squish.Qt.LeftButton, + ) + + @staticmethod + def enable_disable_vfs_support(action="disable"): + if action not in ["enable", "disable"]: + raise ValueError("Invalid action: " + action) + + checked = squish.waitForObjectExists(SyncConnectionWizard.VFS_CHECKBOX).checked + if (action == "enable") == checked: + return + squish.clickButton(squish.waitForObject(SyncConnectionWizard.VFS_CHECKBOX)) + + @staticmethod + def select_folders_to_sync(folders): + # first deselect all + SyncConnectionWizard.deselect_all_remote_folders() + for folder in folders: + folder_levels = folder.strip("/").split("/") + parent_selector = None + for sub_folder in folder_levels: + if not parent_selector: + SyncConnectionWizard.SYNC_DIALOG_FOLDER_TREE["text"] = sub_folder + parent_selector = SyncConnectionWizard.SYNC_DIALOG_FOLDER_TREE + selector = parent_selector + else: + selector = { + "column": "0", + "container": parent_selector, + "text": sub_folder, + "type": "QModelIndex", + } + if ( + len(folder_levels) == 1 + or folder_levels.index(sub_folder) == len(folder_levels) - 1 + ): + # NOTE: checkbox does not have separate object + # click on (11,11) which is a checkbox to unselect the folder + squish.mouseClick( + squish.waitForObject(selector), + 11, + 11, + squish.Qt.NoModifier, + squish.Qt.LeftButton, + ) + else: + squish.doubleClick(squish.waitForObject(selector)) + + @staticmethod + def sort_by(header_text): + squish.mouseClick( + squish.waitForObject( + { + "container": SyncConnectionWizard.SELECTIVE_SYNC_TREE_HEADER, + "text": header_text, + "type": "HeaderViewItem", + "visible": True, + } + ) + ) + + @staticmethod + def add_sync_connection(): + squish.clickButton( + squish.waitForObject(SyncConnectionWizard.ADD_SYNC_CONNECTION_BUTTON) + ) + + @staticmethod + def get_item_name_from_row(row_index): + folder_row = { + "row": row_index, + "container": SyncConnectionWizard.SELECTIVE_SYNC_ROOT_FOLDER, + "type": "QModelIndex", + } + return str(squish.waitForObjectExists(folder_row).displayText) + + @staticmethod + def is_root_folder_checked(): + state = squish.waitForObject(SyncConnectionWizard.SELECTIVE_SYNC_ROOT_FOLDER)[ + "checkState" + ] + return state == "checked" + + @staticmethod + def cancel_folder_sync_connection_wizard(): + squish.clickButton( + squish.waitForObject( + SyncConnectionWizard.CANCEL_FOLDER_SYNC_CONNECTION_WIZARD + ) + ) + + @staticmethod + def select_space(space_name): + selector = SyncConnectionWizard.SPACE_NAME_SELECTOR.copy() + selector["text"] = space_name + squish.mouseClick(squish.waitForObject(selector)) + + @staticmethod + def sync_space(space_name): + SyncConnectionWizard.select_space(space_name) + SyncConnectionWizard.next_step() + SyncConnectionWizard.set_sync_path( + path.join(get_current_user_sync_path(), space_name) + ) + SyncConnectionWizard.add_sync_connection() + + @staticmethod + def refresh_remote(): + squish.clickButton(squish.waitForObject(SyncConnectionWizard.REFRESH_BUTTON)) + + @staticmethod + def generate_remote_folder_selector(folder_name, parent_container=None): + if not parent_container: + parent_container = { + "container": names.groupBox_folderTreeWidget_QTreeWidget, + "text": "ownCloud", + "type": "QModelIndex", + } + return { + "container": parent_container, + "text": folder_name, + "type": "QModelIndex", + } + + @staticmethod + def has_remote_folder(folder_name): + folder_tree = folder_name.strip("/").split("/") + parent_container = None + + for folder in folder_tree: + folder_selector = SyncConnectionWizard.generate_remote_folder_selector( + folder, parent_container + ) + try: + if parent_container: + squish.doubleClick(parent_container) + + squish.waitForObject(folder_selector) + + parent_container = folder_selector + except: + return False, None + return True, parent_container + + @staticmethod + def is_remote_folder_selected(folder_selector): + return squish.waitForObjectExists(folder_selector).selected + + @staticmethod + def get_local_sync_path(): + return str( + squish.waitForObjectExists( + SyncConnectionWizard.CHOOSE_LOCAL_SYNC_FOLDER + ).displayText + ) + + @staticmethod + def get_warn_label(): + return str(squish.waitForObjectExists(SyncConnectionWizard.WARN_LABEL).text) + + @staticmethod + def is_add_sync_folder_button_enabled(): + return squish.waitForObjectExists( + SyncConnectionWizard.ADD_FOLDER_SYNC_BUTTON + ).enabled + + @staticmethod + def is_space_available(space_name): + selector = SyncConnectionWizard.SPACE_NAME_SELECTOR.copy() + selector["text"] = space_name + space_available = False + try: + squish.waitForObject(selector) + space_available = True + except: + pass + return space_available diff --git a/test/gui/shared/scripts/pageObjects/SyncWizard.py b/test/gui/shared/scripts/pageObjects/SyncWizard.py deleted file mode 100644 index 8dcf9433628..00000000000 --- a/test/gui/shared/scripts/pageObjects/SyncWizard.py +++ /dev/null @@ -1,17 +0,0 @@ -import names -import squish - - -class SyncWizard: - ACTION_MENU = { - "container": names.settings_stack_QStackedWidget, - "name": "_folderList", - "type": "QTreeView", - "visible": 1, - } - - def performAction(self, action): - squish.openContextMenu( - squish.waitForObjectItem(self.ACTION_MENU, "_1"), 0, 0, squish.Qt.NoModifier - ) - squish.activateItem(squish.waitForObjectItem(names.settings_QMenu, action)) diff --git a/test/gui/shared/scripts/pageObjects/Toolbar.py b/test/gui/shared/scripts/pageObjects/Toolbar.py index 291e38df236..cd2e14142ef 100644 --- a/test/gui/shared/scripts/pageObjects/Toolbar.py +++ b/test/gui/shared/scripts/pageObjects/Toolbar.py @@ -1,34 +1,195 @@ import names import squish +import object # pylint: disable=redefined-builtin +from objectmaphelper import RegularExpression + +from helpers.SetupClientHelper import wait_until_app_killed +from helpers.ConfigHelper import get_config class Toolbar: - ACTIVITY_BUTTON = { - "name": "settingsdialog_toolbutton_Activity", + TOOLBAR_ROW = { + "name": "mainWindowToolbar", + "type": "QToolBar", + "visible": 1, + "window": names.mainWindow_OCC_MainWindow, + } + ACCOUNT_BUTTON = { "type": "QToolButton", + "unnamed": 1, "visible": 1, - "window": names.settings_OCC_SettingsDialog, + "window": names.mainWindow_OCC_MainWindow, } ADD_ACCOUNT_BUTTON = { - "name": "settingsdialog_toolbutton_Add account", + "container": names.mainWindow_QMenu, + "name": "addAcountAction", + "type": "QAction", + "visible": True, + } + ACTIVITY_BUTTON = { + "text": "Activity", + "type": "QToolButton", + "unnamed": 1, + "visible": 1, + "window": names.mainWindow_OCC_MainWindow, + } + ERRORS_BUTTON = { + "text": RegularExpression(r"^Errors: [0-9]+$"), + "type": "QToolButton", + "unnamed": 1, + "visible": 1, + "window": names.mainWindow_OCC_MainWindow, + } + SETTINGS_BUTTON = { + "container": names.mainWindow_QMenu, + "name": "settingsAction", + "type": "QAction", + "visible": True, + } + QUIT_BUTTON = { + "container": names.mainWindow_QMenu, + "name": "quitAction", + "type": "QAction", + "visible": True, + } + QUIT_CONFIRMATION_DIALOG = { + "type": "QMessageBox", + "unnamed": 1, + "visible": 1, + } + CONFIRM_QUIT_BUTTON = { + "type": "QPushButton", + "unnamed": 1, + "visible": 1, + "window": QUIT_CONFIRMATION_DIALOG, + } + TOOLBAR_MORE_BUTTON = { + "text": "More", "type": "QToolButton", "visible": 1, - "window": names.settings_OCC_SettingsDialog, + "window": names.mainWindow_OCC_MainWindow, } - def clickActivity(self): - squish.clickButton(squish.waitForObject(self.ACTIVITY_BUTTON)) + TOOLBAR_ITEMS = ["Add Account", "Activity", "Settings", "Quit"] + + @staticmethod + def get_item_selector(item_name): + return { + "window": names.mainWindow_OCC_MainWindow, + "text": item_name, + "type": "QToolButton", + "visible": True, + } + + @staticmethod + def has_item(item_name, timeout=get_config("minSyncTimeout") * 1000): + try: + squish.waitForObject(Toolbar.get_item_selector(item_name), timeout) + return True + except: + return False + + @staticmethod + def open_activity(): + squish.mouseClick(squish.waitForObject(Toolbar.ACTIVITY_BUTTON)) + + @staticmethod + def open_errors(): + squish.mouseClick(squish.waitForObject(Toolbar.ERRORS_BUTTON)) - def clickAddAccount(self): - squish.clickButton(squish.waitForObject(self.ADD_ACCOUNT_BUTTON)) + @staticmethod + def open_new_account_setup(): + squish.mouseClick(squish.waitForObject(Toolbar.TOOLBAR_MORE_BUTTON)) + squish.mouseClick(squish.waitForObject(Toolbar.ADD_ACCOUNT_BUTTON)) - def getDisplayedAccountText(self, displayname, host): + @staticmethod + def open_account(displayname): + _, selector = Toolbar.get_account(displayname) + squish.mouseClick(squish.waitForObject(selector)) + + @staticmethod + def get_displayed_account_text(displayname, host): return str( squish.waitForObjectExists( - { - "name": "settingsdialog_toolbutton_" + displayname + "@" + host, - "type": "QToolButton", - "visible": 1, - } + Toolbar.get_item_selector(displayname + "\n" + host) ).text ) + + @staticmethod + def open_settings_tab(): + squish.mouseClick(squish.waitForObject(Toolbar.TOOLBAR_MORE_BUTTON)) + squish.mouseClick(squish.waitForObject(Toolbar.SETTINGS_BUTTON)) + + @staticmethod + def quit_owncloud(): + squish.mouseClick(squish.waitForObject(Toolbar.TOOLBAR_MORE_BUTTON)) + squish.mouseClick(squish.waitForObject(Toolbar.QUIT_BUTTON)) + squish.clickButton(squish.waitForObject(Toolbar.CONFIRM_QUIT_BUTTON)) + for ctx in squish.applicationContextList(): + pid = ctx.pid + ctx.detach() + wait_until_app_killed(pid) + + @staticmethod + def get_accounts(): + accounts = {} + selectors = {} + children_obj = object.children(squish.waitForObjectExists(Toolbar.TOOLBAR_ROW)) + account_idx = 1 + for obj in children_obj: + + if hasattr(obj, "defaultAction") and hasattr( + obj.defaultAction, "objectName" + ): + object_name = str(getattr(obj.defaultAction, "objectName", "")) + if not object_name.startswith("accountAction_"): + continue + hostname = str(obj.text).strip() + tooltip = str(obj.toolTip).strip() + lines = [line.strip() for line in tooltip.splitlines() if line.strip()] + username = lines[0] if lines else "" + account_info = { + "displayname": username, + "username": username, + "hostname": hostname, + "initials": "", + "current": getattr(obj, "checked", False), + } + account_locator = Toolbar.ACCOUNT_BUTTON.copy() + if account_idx > 1: + account_locator.update({"occurrence": account_idx}) + account_locator.update({"text": hostname}) + + accounts[username] = account_info + selectors[username] = obj + account_idx += 1 + return accounts, selectors + + @staticmethod + def get_account(display_name): + accounts, selectors = Toolbar.get_accounts() + return accounts.get(display_name), selectors.get(display_name) + + @staticmethod + def get_active_account(): + accounts, selectors = Toolbar.get_accounts() + for account, info in accounts.items(): + if info["current"]: + return info, selectors[account] + return None, None + + @staticmethod + def account_has_focus(display_name): + account, selector = Toolbar.get_account(display_name) + return account["current"] and squish.waitForObject(selector).checked + + @staticmethod + def account_exists(display_name): + account, selector = Toolbar.get_account(display_name) + if ( + account is None + or selector is None + and account["displayname"] != display_name + ): + raise LookupError(f'Account "{display_name}" does not exist') + squish.waitForObject(selector) diff --git a/test/gui/shared/steps/account_context.py b/test/gui/shared/steps/account_context.py new file mode 100644 index 00000000000..72f38a9a00f --- /dev/null +++ b/test/gui/shared/steps/account_context.py @@ -0,0 +1,298 @@ +from pageObjects.AccountConnectionWizard import AccountConnectionWizard +from pageObjects.SyncConnectionWizard import SyncConnectionWizard +from pageObjects.EnterPassword import EnterPassword +from pageObjects.Toolbar import Toolbar +from pageObjects.AccountSetting import AccountSetting + +from helpers.SetupClientHelper import ( + start_client, + substitute_inline_codes, + get_client_details, + get_resource_path, +) +from helpers.UserHelper import get_displayname_for_user, get_password_for_user +from helpers.SyncHelper import ( + wait_for_initial_sync_to_complete, + listen_sync_status_for_item, + delete_socket_connection, +) +from helpers.ConfigHelper import get_config, is_windows, is_linux, is_owncloud_client + + +@When('the user adds the following user credentials:') +def step(context): + account_details = get_client_details(context.table) + AccountConnectionWizard.add_user_credentials( + account_details['user'], account_details['password'] + ) + + +@Then('the account with displayname "|any|" and host "|any|" should be displayed') +def step(context, displayname, _): + if not is_owncloud_client(): + # get displayname from first name + displayname = get_displayname_for_user(displayname.split()[0].strip()) + displayname = substitute_inline_codes(displayname) + Toolbar.account_exists(displayname) + + +@Then('the account with displayname "|any|" and host "|any|" should not be displayed') +def step(context, displayname, host): + displayname = substitute_inline_codes(displayname) + host = substitute_inline_codes(host) + account_title = displayname + '\n' + host + timeout = get_config('lowestSyncTimeout') * 1000 + + test.compare( + False, + Toolbar.has_item(account_title, timeout), + f"Expected account '{displayname}' to be removed", + ) + + +@Given('user "|any|" has set up a client with default settings') +def step(context, username): + password = get_password_for_user(username) + account_details = get_client_details( + [['server', '%local_server%'], ['user', username], ['password', password]] + ) + start_client() + AccountConnectionWizard.add_account(account_details) + # wait for files to sync + wait_for_initial_sync_to_complete(get_resource_path('/', account_details['user'])) + + +@Given('the user has set up the following accounts with default settings:') +def step(context): + start_client() + for idx, row in enumerate(context.table): + username = row[0] + account_details = get_client_details( + [['server', '%local_server%'], ['user', username]] + ) + if idx > 0: + Toolbar.open_new_account_setup() + AccountConnectionWizard.add_account(account_details) + # wait for files to sync + wait_for_initial_sync_to_complete( + get_resource_path('/', account_details['user']) + ) + + +@Given('the user has started the client') +def step(context): + start_client() + + +@When('the user starts the client') +def step(context): + start_client() + + +@When('user "|any|" starts the client') +def step(context, user): + start_client() + listen_sync_status_for_item(get_resource_path('/', user)) + + +@When('the user opens the add-account dialog') +def step(context): + Toolbar.open_new_account_setup() + + +@When('the user adds the following account:') +def step(context): + account_details = get_client_details(context.table) + AccountConnectionWizard.add_account(account_details) + # wait for files to sync + wait_for_initial_sync_to_complete(get_resource_path('/', account_details['user'])) + + +@Given('the user has entered the following account information:') +def step(context): + account_details = get_client_details(context.table) + AccountConnectionWizard.add_account_information(account_details) + + +@When('the user "|any|" logs out using the client-UI') +def step(context, _): + AccountSetting.logout() + + +@Then('user "|any|" should be signed out') +def step(context, username): + test.compare( + AccountSetting.is_user_signed_out(), + True, + f'User "{username}" is signed out', + ) + + +@Given('user "|any|" has logged out from the client-UI') +def step(context, username): + AccountSetting.logout() + if not AccountSetting.is_user_signed_out(): + raise LookupError(f'Failed to logout user {username}') + + +@When('user "|any|" logs in using the client-UI') +def step(context, username): + AccountSetting.login() + password = get_password_for_user(username) + enter_password = EnterPassword() + enter_password.relogin(username, password) + + # wait for files to sync + wait_for_initial_sync_to_complete(get_resource_path('/', username)) + + +@Then('user "|any|" should be connected to the server') +def step(context, _): + AccountSetting.wait_until_account_is_connected() + AccountSetting.wait_until_sync_folder_is_configured() + + +@When('the user removes the connection for user "|any|" and host |any|') +def step(context, username, _): + displayname = get_displayname_for_user(username) + displayname = substitute_inline_codes(displayname) + + Toolbar.open_account(displayname) + AccountSetting.remove_account_connection() + + +@Then('connection wizard should be visible') +def step(context): + test.compare( + AccountConnectionWizard.is_new_connection_window_visible(), + True, + 'Connection window is visible', + ) + + +@When('the user accepts the certificate') +def step(context): + AccountConnectionWizard.accept_certificate() + + +@When('the user adds the server "|any|"') +def step(context, server): + server_url = substitute_inline_codes(server) + AccountConnectionWizard.add_server(server_url) + + +@When('the user selects manual sync folder option in advanced section') +def step(context): + AccountConnectionWizard.select_manual_sync_folder_option() + AccountConnectionWizard.finish() + + +@Then('credentials wizard should be visible') +def step(context): + test.compare( + AccountConnectionWizard.is_credential_window_visible(), + True, + 'Credentials wizard is visible', + ) + + +@When('the user selects vfs option in advanced section') +def step(context): + AccountConnectionWizard.select_vfs_option() + AccountConnectionWizard.finish() + + +@When('the user selects download everything option in advanced section') +def step(context): + AccountConnectionWizard.select_download_everything_option() + AccountConnectionWizard.finish() + + +@When('the user opens the advanced configuration') +def step(context): + AccountConnectionWizard.select_advanced_config() + + +@Then('the user should be able to choose the local download directory') +def step(context): + test.compare(True, AccountConnectionWizard.can_change_local_sync_dir()) + + +@Then('the download everything option should be selected by default for Linux') +def step(context): + if is_linux(): + test.compare( + True, + AccountConnectionWizard.is_sync_everything_option_checked(), + 'Sync everything option is checked', + ) + + +@Then('the VFS option should be selected by default for Windows') +def step(context): + if is_windows(): + test.compare( + True, + AccountConnectionWizard.is_vfs_option_checked(), + 'VFS option is checked', + ) + + +@When(r'^the user presses the "([^"]*)" key(?:s)?', regexp=True) +def step(context, key): + AccountSetting.press_key(key) + + +@Then('the log dialog should be opened') +def step(context): + test.compare(True, AccountSetting.is_log_dialog_visible(), 'Log dialog is opened') + + +@Step('the user cancels the sync connection wizard') +def step(context): + SyncConnectionWizard.cancel_folder_sync_connection_wizard() + + +@When('the user quits the client') +def step(context): + Toolbar.quit_owncloud() + delete_socket_connection() + + +@Then('"|any|" account should be opened') +def step(context, displayname): + displayname = substitute_inline_codes(displayname) + if not Toolbar.account_has_focus(displayname): + raise LookupError(f"Account '{displayname}' should be opened, but it is not") + + +@Then( + r'the default local sync path should contain "([^"]*)" in the (configuration|sync connection) wizard', + regexp=True, +) +def step(context, sync_path, wizard): + sync_path = substitute_inline_codes(sync_path) + if is_windows(): + sync_path = sync_path.replace('/', '\\') + + actual_sync_path = '' + if wizard == 'configuration': + actual_sync_path = AccountConnectionWizard.get_local_sync_path() + else: + actual_sync_path = SyncConnectionWizard.get_local_sync_path() + + test.compare( + actual_sync_path, + sync_path, + 'Compare sync path contains the expected path', + ) + + +@Then('the warning "|any|" should appear in the sync connection wizard') +def step(context, warn_message): + actual_message = SyncConnectionWizard.get_warn_label() + test.compare( + True, + warn_message in actual_message, + 'Contains warning message', + ) diff --git a/test/gui/shared/steps/file_context.py b/test/gui/shared/steps/file_context.py new file mode 100644 index 00000000000..3397044a78c --- /dev/null +++ b/test/gui/shared/steps/file_context.py @@ -0,0 +1,393 @@ +# -*- coding: utf-8 -*- +import os +import re +import builtins +import shutil +import zipfile +from os.path import isfile, join, isdir +import squish + +from helpers.SetupClientHelper import get_resource_path, get_temp_resource_path +from helpers.SyncHelper import wait_for_client_to_be_ready +from helpers.ConfigHelper import get_config, is_owncloud_client +from helpers.FilesHelper import ( + build_conflicted_regex, + sanitize_path, + can_read, + can_write, + read_file_content, + get_size_in_bytes, + prefix_path_namespace, + remember_path, + write_file, +) +from helpers.UserHelper import get_username_for_user + + +def folder_exists(folder_path, timeout=1000): + return squish.waitFor( + lambda: isdir(sanitize_path(folder_path)), + timeout, + ) + + +def file_exists(file_path, timeout=1000): + return squish.waitFor( + lambda: isfile(sanitize_path(file_path)), + timeout, + ) + + +# To create folders in a temporary directory, we set is_temp_folder True +# And if is_temp_folder is True, the create_folder function create folders in tempFolderPath +def create_folder(foldername, username=None, is_temp_folder=False): + if is_temp_folder: + folder_path = join(get_config('tempFolderPath'), foldername) + else: + folder_path = get_resource_path(foldername, username) + os.makedirs(prefix_path_namespace(folder_path)) + + +def rename_file_folder(source, destination): + source = get_resource_path(source) + destination = get_resource_path(destination) + os.rename(source, destination) + + +def create_file_with_size(filename, filesize, is_temp_folder=False): + if is_temp_folder: + file = join(get_config('tempFolderPath'), filename) + else: + file = get_resource_path(filename) + with open(prefix_path_namespace(file), 'wb') as f: + f.seek(get_size_in_bytes(filesize) - 1) + f.write(b'\0') + + +def wait_and_write_file(path, content): + wait_for_client_to_be_ready() + write_file(path, content) + + +def wait_and_try_to_write_file(resource, content): + wait_for_client_to_be_ready() + try: + write_file(resource, content) + except: + pass + + +def create_zip(resources, zip_file_name, cwd=''): + os.chdir(cwd) + with zipfile.ZipFile(zip_file_name, 'w') as zipped_file: + for resource in resources: + zipped_file.write(resource) + + +def extract_zip(zip_file_path, destination_dir): + with zipfile.ZipFile(zip_file_path, 'r') as zip_file: + zip_file.extractall(destination_dir) + + +def add_copy_suffix(resource_path, resource_type): + if resource_type == 'file': + source_dir = resource_path.rsplit('.', 1) + return source_dir[0] + ' - Copy.' + source_dir[-1] + return resource_path + ' - Copy' + + +@When( + 'user "|any|" creates a file "|any|" with the following content inside the sync folder' +) +def step(context, username, filename): + if not is_owncloud_client(): + username = get_username_for_user(username) + file_content = '\n'.join(context.multiLineText) + file = get_resource_path(filename, username) + wait_and_write_file(file, file_content) + + +@When('user "|any|" creates a folder "|any|" inside the sync folder') +def step(context, username, foldername): + if not is_owncloud_client(): + username = get_username_for_user(username) + wait_for_client_to_be_ready() + create_folder(foldername, username) + + +@Given('user "|any|" has created a folder "|any|" inside the sync folder') +def step(context, username, foldername): + if not is_owncloud_client(): + username = get_username_for_user(username) + create_folder(foldername, username) + + +@When('user "|any|" creates a file "|any|" with size "|any|" inside the sync folder') +def step(context, _, filename, filesize): + create_file_with_size(filename, filesize) + + +@When(r'the user copies the (file|folder) "([^"]*)" to "([^"]*)"', regexp=True) +def step(context, resource_type, source_dir, destination_dir): + source = get_resource_path(source_dir) + destination = get_resource_path(destination_dir) + if source == destination and destination_dir != '/': + destination = add_copy_suffix(source, resource_type) + if resource_type == 'folder': + return shutil.copytree(source, destination) + return shutil.copy2(source, destination) + + +@When(r'the user renames a (?:file|folder) "([^"]*)" to "([^"]*)"', regexp=True) +def step(context, source, destination): + wait_for_client_to_be_ready() + rename_file_folder(source, destination) + + +@Then('the file "|any|" should exist on the file system with the following content') +def step(context, file_path): + expected = '\n'.join(context.multiLineText) + file_path = get_resource_path(file_path) + with open(file_path, 'r', encoding='utf-8') as f: + contents = f.read() + test.compare( + expected, + contents.rstrip(), + 'file expected to exist with content ' + + expected + + ' but does not have the expected content', + ) + + +@Then(r'^the (file|folder) "([^"]*)" should exist on the file system$', regexp=True) +def step(context, resource_type, resource): + resource_path = get_resource_path(resource) + resource_exists = False + if resource_type == 'file': + resource_exists = file_exists( + resource_path, get_config('maxSyncTimeout') * 1000 + ) + else: + resource_exists = folder_exists( + resource_path, get_config('maxSyncTimeout') * 1000 + ) + + test.compare( + True, + resource_exists, + f'Assert {resource_type} "{resource}" exists on the system', + ) + + +@Then(r'^the (file|folder) "([^"]*)" should not exist on the file system$', regexp=True) +def step(context, resource_type, resource): + resource_path = get_resource_path(resource) + resource_exists = False + if resource_type == 'file': + resource_exists = file_exists(resource_path, 1000) + else: + resource_exists = folder_exists(resource_path, 1000) + + test.compare( + False, + resource_exists, + f'Assert {resource_type} "{resource}" doesn\'t exist on the system', + ) + + +@Given('the user has changed the content of local file "|any|" to:') +def step(context, filename): + file_content = '\n'.join(context.multiLineText) + wait_and_write_file(get_resource_path(filename), file_content) + + +@Then( + 'a conflict file for "|any|" should exist on the file system with the following content' +) +def step(context, filename): + expected = '\n'.join(context.multiLineText) + + onlyfiles = [ + f for f in os.listdir(get_resource_path()) if isfile(get_resource_path(f)) + ] + found = False + pattern = re.compile(build_conflicted_regex(filename)) + for file in onlyfiles: + if pattern.match(file): + with open(get_resource_path(file), 'r', encoding='utf-8') as f: + if f.read() == expected: + found = True + break + + if not found: + raise AssertionError('Conflict file not found with given name') + + +@When('the user overwrites the file "|any|" with content "|any|"') +def step(context, resource, content): + resource = get_resource_path(resource) + wait_and_write_file(resource, content) + + +@When('the user tries to overwrite the file "|any|" with content "|any|"') +def step(context, resource, content): + resource = get_resource_path(resource) + wait_and_try_to_write_file(resource, content) + + +@When('user "|any|" tries to overwrite the file "|any|" with content "|any|"') +def step(context, user, resource, content): + resource = get_resource_path(resource, user) + wait_and_try_to_write_file(resource, content) + + +@When(r'the user deletes the (file|folder) "([^"]*)"', regexp=True) +def step(context, item_type, resource): + wait_for_client_to_be_ready() + + resource_path = sanitize_path(get_resource_path(resource)) + if item_type == 'file': + os.remove(resource_path) + else: + shutil.rmtree(resource_path) + + +@When('user "|any|" creates the following files inside the sync folder:') +def step(context, username): + if not is_owncloud_client(): + username = get_username_for_user(username) + wait_for_client_to_be_ready() + + for row in context.table[1:]: + file = get_resource_path(row[0], username) + write_file(file, '') + + +@Given('the user has created a folder "|any|" in temp folder') +def step(context, folder_name): + create_folder(folder_name, is_temp_folder=True) + + +@Given( + 'the user has created "|any|" files each of size "|any|" bytes inside folder "|any|" in temp folder' +) +def step(context, file_number, file_size, folder_name): + current_sync_path = get_temp_resource_path(folder_name) + if folder_exists(current_sync_path): + file_size = builtins.int(file_size) + for i in range(0, builtins.int(file_number)): + file_name = f'file{i}.txt' + create_file_with_size(join(current_sync_path, file_name), file_size, True) + else: + raise FileNotFoundError( + f"Folder '{folder_name}' does not exist in the temp folder" + ) + + +@When( + r'user "([^"]*)" moves (folder|file) "([^"]*)" from the temp folder into the sync folder', + regexp=True, +) +def step(context, username, _, resource_name): + if get_config('predefined_users'): + username = get_username_for_user(username) + source_dir = join(get_config('tempFolderPath'), resource_name) + destination_dir = get_resource_path('/', username) + shutil.move(source_dir, destination_dir) + + +@When( + r'user "([^"]*)" moves (?:folder|file) "([^"]*)" to the temp folder', + regexp=True, +) +def step(context, _, resource_name): + source_dir = get_resource_path(resource_name) + destination_dir = get_temp_resource_path(resource_name) + shutil.move(source_dir, destination_dir) + + +@When( + r'user "([^"]*)" moves (?:file|folder) "([^"]*)" to "([^"]*)" in the sync folder', + regexp=True, +) +def step(context, username, source, destination): + wait_for_client_to_be_ready() + source_dir = get_resource_path(source, username) + if destination in (None, '/'): + destination = '' + destination_dir = get_resource_path(destination, username) + shutil.move(source_dir, destination_dir) + + +@Then('user "|any|" should be able to open the file "|any|" on the file system') +def step(context, user, file_name): + if not is_owncloud_client(): + user = get_username_for_user(user) + file_path = get_resource_path(file_name, user) + if not os.path.exists(file_path): + raise ValueError(f'Could not find resource {file_path}') + test.compare(can_read(file_path), True, 'File should be readable') + + +@Then('as "|any|" the file "|any|" should have content "|any|" on the file system') +def step(context, user, file_name, content): + if not is_owncloud_client(): + user = get_username_for_user(user) + file_path = get_resource_path(file_name, user) + file_content = read_file_content(file_path) + test.compare(file_content, content, 'Comparing file content') + + +@Then('user "|any|" should not be able to edit the file "|any|" on the file system') +def step(context, user, file_name): + if not is_owncloud_client(): + user = get_username_for_user(user) + file_path = get_resource_path(file_name, user) + if not os.path.exists(file_path): + raise ValueError(f'Could not find resource {file_path}') + test.compare(not can_write(file_path), True, 'File should not be writable') + + +@Given( + 'the user has created a zip file "|any|" with the following resources in the temp folder' +) +def step(context, zip_file_name): + resource_list = [] + + for row in context.table[1:]: + resource_list.append(row[0]) + resource = join(get_config('tempFolderPath'), row[0]) + if row[1] == 'folder': + os.makedirs(resource) + elif row[1] == 'file': + content = '' + if len(row) > 2 and row[2]: + content = row[2] + write_file(resource, content) + create_zip(resource_list, zip_file_name, get_config('tempFolderPath')) + + +@When('user "|any|" unzips the zip file "|any|" inside the sync root') +def step(context, username, zip_file_name): + if get_config('predefined_users'): + username = get_username_for_user(username) + destination_dir = get_resource_path('/', username) + zip_file_path = join(destination_dir, zip_file_name) + extract_zip(zip_file_path, destination_dir) + + +@When('user "|any|" copies file "|any|" to temp folder') +def step(context, username, source): + wait_for_client_to_be_ready() + source_dir = get_resource_path(source, username) + destination_dir = get_temp_resource_path(source) + shutil.copy2(source_dir, destination_dir) + + +@Given('the user has created folder "|any|" in the default home path') +def step(context, folder_name): + folder_path = join(get_config('home_dir'), folder_name) + os.makedirs(prefix_path_namespace(folder_path)) + remember_path(folder_path) + # when account is added, folder with suffix will be created + remember_path(f'{folder_path} (2)') diff --git a/test/gui/shared/steps/server_context.py b/test/gui/shared/steps/server_context.py new file mode 100644 index 00000000000..f73104edd33 --- /dev/null +++ b/test/gui/shared/steps/server_context.py @@ -0,0 +1,113 @@ +from helpers.api import provisioning, webdav_helper as webdav +from helpers.ConfigHelper import is_owncloud_client +import helpers.api.external_api as ext + +from pageObjects.Toolbar import Toolbar + + +@Then( + r'^as "([^"].*)" (?:file|folder) "([^"].*)" should not exist in the server', + regexp=True, +) +def step(context, user_name, resource_name): + if not is_owncloud_client(): + resource_exists = ext.resource_exists(user_name, resource_name) + else: + resource_exists = webdav.resource_exists(user_name, resource_name) + test.compare( + resource_exists, + False, + f"Resource '{resource_name}' should not exist, but does", + ) + + +@Then( + r'^as "([^"].*)" (?:file|folder) "([^"].*)" should exist in the server', regexp=True +) +def step(context, user_name, resource_name): + if not is_owncloud_client(): + resource_exists = ext.resource_exists(user_name, resource_name) + else: + resource_exists = webdav.resource_exists(user_name, resource_name) + test.compare( + resource_exists, + True, + f"Resource '{resource_name}' should exist, but does not", + ) + + +@Then('as "|any|" the file "|any|" should have the content "|any|" in the server') +def step(context, user_name, file_name, content): + if not is_owncloud_client(): + text_content = ext.get_file_content(user_name, file_name) + else: + text_content = webdav.get_file_content(user_name, file_name) + test.compare( + text_content, + content, + f"File '{file_name}' should have content '{content}' but found '{text_content}'", + ) + + +@Then( + r'as user "([^"].*)" folder "([^"].*)" should contain "([^"].*)" items in the server', + regexp=True, +) +def step(context, user_name, folder_name, items_number): + if not is_owncloud_client(): + total_items = ext.get_folder_items_count(user_name, folder_name) + else: + total_items = webdav.get_folder_items_count(user_name, folder_name) + test.compare( + total_items, items_number, f'Folder should contain {items_number} items' + ) + + +@Given('user "|any|" has created folder "|any|" in the server') +def step(context, user, folder_name): + if not is_owncloud_client(): + ext.create_folder(user, folder_name) + else: + webdav.create_folder(user, folder_name) + + +@Given('user "|any|" has uploaded file with content "|any|" to "|any|" in the server') +def step(context, user, file_content, file_name): + if not is_owncloud_client(): + ext.upload_file(user, file_name, file_content) + else: + webdav.create_file(user, file_name, file_content) + + +@When('the user clicks on the settings tab') +def step(context): + Toolbar.open_settings_tab() + + +@When('user "|any|" uploads file with content "|any|" to "|any|" in the server') +def step(context, user, file_content, file_name): + if not is_owncloud_client(): + ext.upload_file(user, file_name, file_content) + else: + webdav.create_file(user, file_name, file_content) + + +@When('user "|any|" deletes the folder "|any|" in the server') +def step(context, user, folder_name): + if not is_owncloud_client(): + ext.delete_resource(user, folder_name) + else: + webdav.delete_resource(user, folder_name) + + +@Given('user "|any|" has been created in the server with default attributes') +def step(context, user): + provisioning.create_user(user) + + +@Given('user "|any|" has uploaded file "|any|" to "|any|" in the server') +def step(context, user, file_name, destination): + if not is_owncloud_client(): + ext.upload_file(user, destination) + else: + webdav.upload_file(user, file_name, destination) diff --git a/test/gui/shared/steps/spaces_context.py b/test/gui/shared/steps/spaces_context.py new file mode 100644 index 00000000000..57e6d19befd --- /dev/null +++ b/test/gui/shared/steps/spaces_context.py @@ -0,0 +1,73 @@ +from helpers.SpaceHelper import ( + create_space, + create_space_folder, + create_space_file, + add_user_to_space, + get_file_content, + resource_exists, +) +from helpers.ConfigHelper import is_owncloud_client +import helpers.api.external_api as ext + + +@Given('the administrator has created a space "|any|"') +def step(context, space_name): + if not is_owncloud_client(): + ext.create_space(space_name) + else: + create_space(space_name) + + +@Given('the administrator has created a folder "|any|" in space "|any|"') +def step(context, folder_name, space_name): + if not is_owncloud_client(): + ext.create_space_folder(space_name, folder_name) + else: + create_space_folder(space_name, folder_name) + + +@Given( + 'the administrator has uploaded a file "|any|" with content "|any|" inside space "|any|"' +) +def step(context, file_name, content, space_name): + if not is_owncloud_client(): + ext.upload_space_file(space_name, file_name, content) + else: + create_space_file(space_name, file_name, content) + + +@Given('the administrator has added user "|any|" to space "|any|" with role "|any|"') +def step(context, user, space_name, role): + if not is_owncloud_client(): + ext.add_user_to_space(user, space_name, role) + else: + add_user_to_space(user, space_name, role) + + +@Then( + 'as "|any|" the file "|any|" in the space "|any|" should have content "|any|" in the server' +) +def step(context, user, file_name, space_name, content): + if not is_owncloud_client(): + downloaded_content = ext.get_file_content(user, file_name, space_name) + else: + downloaded_content = get_file_content(space_name, file_name, user) + test.compare(downloaded_content, content, 'Comparing file content') + + +@Then( + r'as "([^"]*)" the space "([^"]*)" (should|should not) have (?:folder|file) "([^"]*)" in the server', + regexp=True, +) +def step(context, user, space_name, should_or_should_not, resource_name): + expected = should_or_should_not == 'should' + if not is_owncloud_client(): + exists = ext.resource_exists(user, resource_name, space_name) + else: + exists = resource_exists(space_name, resource_name, user) + test.compare(exists, expected, 'Resource exists') + + +@When('user "|any|" uploads a file "|any|" with content "|any|" inside space "|any|"') +def step(context, user, file_name, file_content, space_name): + ext.upload_space_file(space_name, file_name, file_content, user) diff --git a/test/gui/shared/steps/steps.py b/test/gui/shared/steps/steps.py deleted file mode 100644 index 73f4471e828..00000000000 --- a/test/gui/shared/steps/steps.py +++ /dev/null @@ -1,1106 +0,0 @@ -# -*- coding: utf-8 -*- -import names -import os -import sys -from os import listdir -from os.path import isfile, join, isdir -import re -import urllib.request -import json -import requests -import shutil - -from objectmaphelper import RegularExpression -from pageObjects.AccountConnectionWizard import AccountConnectionWizard -from helpers.SetupClientHelper import * -from helpers.FilesHelper import buildConflictedRegex -from pageObjects.EnterPassword import EnterPassword -from pageObjects.PublicLinkDialog import PublicLinkDialog -from pageObjects.SharingDialog import SharingDialog -from pageObjects.SyncWizard import SyncWizard -from pageObjects.Toolbar import Toolbar -from pageObjects.Activity import Activity -from pageObjects.AccountStatus import AccountStatus - -# the script needs to use the system wide python -# to switch from the built-in interpreter see https://kb.froglogic.com/squish/howto/using-external-python-interpreter-squish-6-6/ -# if the IDE fails to reference the script, add the folder in Edit->Preferences->PyDev->Interpreters->Libraries -sys.path.append(os.path.realpath('../../../shell_integration/nautilus/')) -import syncstate -import functools - - -confdir = '/tmp/bdd-tests-owncloud-client/' -confFilePath = confdir + 'owncloud.cfg' -socketConnect = None - -stateDataFromMiddleware = None - - -def getTestStateFromMiddleware(context): - global stateDataFromMiddleware - if stateDataFromMiddleware is None: - res = requests.get( - os.path.join(context.userData['middlewareUrl'], 'state'), - headers={"Content-Type": "application/json"}, - ) - try: - stateDataFromMiddleware = res.json() - except ValueError: - raise Exception("Could not get created users information from middleware") - - return stateDataFromMiddleware - - -@OnScenarioStart -def hook(context): - try: - os.makedirs(confdir, 0o0755) - except: - pass - try: - os.remove(confFilePath) - except: - pass - - -@Given(r'the user has added (the first|another) account with', regexp=True) -def step(context, accountType): - newAccount = AccountConnectionWizard() - if accountType == 'another': - toolbar = Toolbar() - toolbar.clickAddAccount() - - newAccount.addAccount(context) - - -@When('the user adds the following wrong user credentials:') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.addUserCreds(context) - - -@Then('an account should be displayed with the displayname |any| and host |any|') -def step(context, displayname, host): - displayname = substituteInLineCodes(context, displayname) - host = substituteInLineCodes(context, host) - test.compare( - str( - waitForObjectExists( - { - "name": "settingsdialog_toolbutton_" + displayname + "@" + host, - "type": "QToolButton", - "visible": 1, - } - ).text - ), - displayname + "\n" + host, - ) - - -def getDisplaynameForUser(context, username): - usersDataFromMiddleware = getTestStateFromMiddleware(context) - return usersDataFromMiddleware['created_users'][username]['displayname'] - - -def getPasswordForUser(context, username): - usersDataFromMiddleware = getTestStateFromMiddleware(context) - return usersDataFromMiddleware['created_users'][username]['password'] - - -@Given('user "|any|" has set up a client with default settings') -def step(context, username): - password = getPasswordForUser(context, username) - displayName = getDisplaynameForUser(context, username) - setUpClient(context, username, displayName, confFilePath) - enterUserPassword = EnterPassword() - enterUserPassword.enterPassword(password) - - -@Given('the user has started the client') -def step(context): - startClient(context) - - -@When(r'the user adds (the first|another) account with', regexp=True) -def step(context, accountType): - newAccount = AccountConnectionWizard() - if accountType == 'another': - toolbar = Toolbar() - toolbar.clickAddAccount() - - newAccount.addAccount(context) - - -@Given('the user has added the following account information:') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.addServer(context) - newAccount.addUserCreds(context) - newAccount.selectSyncFolder(context) - - -def isItemSynced(type, itemName): - if type != 'FILE' and type != 'FOLDER': - raise Exception("type must be 'FILE' or 'FOLDER'") - socketConnect = syncstate.SocketConnect() - socketConnect.sendCommand("RETRIEVE_" + type + "_STATUS:" + itemName + "\n") - - if not socketConnect.read_socket_data_with_timeout(0.1): - return False - for line in socketConnect.get_available_responses(): - if line.startswith('STATUS:OK') and line.endswith(itemName): - return True - elif line.endswith(itemName): - return False - - -def isFolderSynced(folderName): - return isItemSynced('FOLDER', folderName) - - -def isFileSynced(fileName): - return isItemSynced('FILE', fileName) - - -def waitForFileToBeSynced(context, fileName): - waitFor( - lambda: isFileSynced( - sanitizePath(context.userData['currentUserSyncPath'] + fileName) - ), - context.userData['clientSyncTimeout'] * 1000, - ) - - -def waitForFolderToBeSynced(context, folderName): - waitFor( - lambda: isFolderSynced( - sanitizePath(context.userData['currentUserSyncPath'] + folderName) - ), - context.userData['clientSyncTimeout'] * 1000, - ) - - -def folderExists(folderPath, timeout=1000): - return waitFor( - lambda: isdir(sanitizePath(folderPath)), - timeout, - ) - - -def fileExists(filePath, timeout=1000): - return waitFor( - lambda: isfile(sanitizePath(filePath)), - timeout, - ) - - -def sanitizePath(path): - return path.replace('//', '/') - - -def shareResource(resource): - socketConnect = syncstate.SocketConnect() - socketConnect.sendCommand("SHARE:" + resource + "\n") - if not socketConnect.read_socket_data_with_timeout(0.1): - return False - for line in socketConnect.get_available_responses(): - if line.startswith('SHARE:OK') and line.endswith(resource): - return True - elif line.endswith(resource): - return False - - -def executeStepThroughMiddleware(context, step): - body = {"step": step} - if hasattr(context, "table"): - body["table"] = context.table - - params = json.dumps(body).encode('utf8') - - req = urllib.request.Request( - context.userData['middlewareUrl'] + 'execute', - data=params, - headers={"Content-Type": "application/json"}, - method='POST', - ) - try: - urllib.request.urlopen(req) - except urllib.error.HTTPError as e: - raise Exception( - "Step execution through test middleware failed. Error: " + e.read().decode() - ) - - -@When( - 'the user adds "|any|" as collaborator of resource "|any|" with permissions "|any|" using the client-UI' -) -def step(context, receiver, resource, permissions): - openSharingDialog(context, resource) - shareItem = SharingDialog() - shareItem.addCollaborator(receiver, permissions) - - -@When( - 'the user adds group "|any|" as collaborator of resource "|any|" with permissions "|any|" using the client-UI' -) -def step(context, receiver, resource, permissions): - openSharingDialog(context, resource) - shareItem = SharingDialog() - shareItem.addCollaborator(receiver, permissions, True) - - -@Then( - 'user "|any|" should be listed in the collaborators list for file "|any|" with permissions "|any|" on the client-UI' -) -def step(context, receiver, resource, permissions): - collaboratorShouldBeListed(context, receiver, resource, permissions) - - -@Then( - 'group "|any|" should be listed in the collaborators list for file "|any|" with permissions "|any|" on the client-UI' -) -def step(context, receiver, resource, permissions): - receiver += " (group)" - collaboratorShouldBeListed(context, receiver, resource, permissions) - - -def collaboratorShouldBeListed(context, receiver, resource, permissions): - resource = getResourcePath(context, resource) - socketConnect = syncstate.SocketConnect() - socketConnect.sendCommand("SHARE:" + resource + "\n") - permissionsList = permissions.split(',') - test.compare( - str(waitForObjectExists(names.scrollArea_sharedWith_QLabel).text), receiver - ) - test.compare( - waitForObjectExists(names.scrollArea_permissionsEdit_QCheckBox).checked, - ('edit' in permissionsList), - ) - test.compare( - waitForObjectExists(names.scrollArea_permissionShare_QCheckBox).checked, - ('share' in permissionsList), - ) - - -@When('the user waits for the files to sync') -def step(context): - waitForFolderToBeSynced(context, '/') - - -@When('the user waits for file "|any|" to be synced') -def step(context, fileName): - waitForFileToBeSynced(context, fileName) - - -@When('the user waits for folder "|any|" to be synced') -def step(context, folderName): - waitForFolderToBeSynced(context, folderName) - - -@Given('the user has waited for file "|any|" to be synced') -def step(context, fileName): - waitForFileToBeSynced(context, fileName) - - -@Given( - 'user "|any|" has created a file "|any|" with the following content inside the sync folder' -) -def step(context, username, filename): - createFile(context, filename, username) - - -@When( - 'user "|any|" creates a file "|any|" with the following content inside the sync folder' -) -def step(context, username, filename): - createFile(context, filename, username) - - -def createFile(context, filename, username=None): - fileContent = "\n".join(context.multiLineText) - syncPath = None - if username: - syncPath = getUserSyncPath(context, username) - else: - syncPath = context.userData['currentUserSyncPath'] - f = open(join(syncPath, filename), "w") - f.write(fileContent) - f.close() - - -@When('user "|any|" creates a folder "|any|" inside the sync folder') -def step(context, username, foldername): - createFolder(context, foldername, username) - - -@Given('user "|any|" has created a folder "|any|" inside the sync folder') -def step(context, username, foldername): - createFolder(context, foldername, username) - - -def createFolder(context, foldername, username=None): - syncPath = None - if username: - syncPath = getUserSyncPath(context, username) - else: - syncPath = context.userData['currentUserSyncPath'] - path = join(syncPath, foldername) - os.makedirs(path) - - -@When('the user copies the folder "|any|" to "|any|"') -def step(context, sourceFolder, destinationFolder): - source_dir = join(context.userData['currentUserSyncPath'], sourceFolder) - destination_dir = join(context.userData['currentUserSyncPath'], destinationFolder) - shutil.copytree(source_dir, destination_dir) - - -@Given(r"^(.*) on the server (.*)$", regexp=True) -def step(context, stepPart1, stepPart2): - executeStepThroughMiddleware(context, "Given " + stepPart1 + " " + stepPart2) - global usersDataFromMiddleware - usersDataFromMiddleware = None - - -@Given(r"^(.*) on the server$", regexp=True) -def step(context, stepPart1): - executeStepThroughMiddleware(context, "Given " + stepPart1) - global usersDataFromMiddleware - usersDataFromMiddleware = None - - -@Then(r"^(.*) on the server (.*)$", regexp=True) -def step(context, stepPart1, stepPart2): - executeStepThroughMiddleware(context, "Then " + stepPart1 + " " + stepPart2) - - -@Then(r"^(.*) on the server$", regexp=True) -def step(context, stepPart1): - executeStepThroughMiddleware(context, "Then " + stepPart1) - - -@Then('the file "|any|" should exist on the file system with the following content') -def step(context, filePath): - expected = "\n".join(context.multiLineText) - filePath = context.userData['currentUserSyncPath'] + filePath - f = open(filePath, 'r') - contents = f.read() - test.compare( - expected, - contents, - "file expected to exist with content " - + expected - + " but does not have the expected content", - ) - - -@Then(r'^the (file|folder) "([^"]*)" should exist on the file system$', regexp=True) -def step(context, resourceType, resource): - resourcePath = join(context.userData['currentUserSyncPath'], resource) - resourceExists = False - if resourceType == 'file': - resourceExists = fileExists( - resourcePath, context.userData['clientSyncTimeout'] * 1000 - ) - elif resourceType == 'folder': - resourceExists = folderExists( - resourcePath, context.userData['clientSyncTimeout'] * 1000 - ) - else: - raise Exception("Unsupported resource type '" + resourceType + "'") - - test.compare( - True, - resourceExists, - "Assert " + resourceType + " '" + resource + "' exists on the system", - ) - - -@Then(r'^the (file|folder) "([^"]*)" should not exist on the file system$', regexp=True) -def step(context, resourceType, resource): - resourcePath = join(context.userData['currentUserSyncPath'], resource) - resourceExists = False - if resourceType == 'file': - resourceExists = fileExists(resourcePath, 1000) - elif resourceType == 'folder': - resourceExists = folderExists(resourcePath, 1000) - else: - raise Exception("Unsupported resource type '" + resourceType + "'") - - test.compare( - False, - resourceExists, - "Assert " + resourceType + " '" + resource + "' doesn't exist on the system", - ) - - -@Given('the user has paused the file sync') -def step(context): - waitForFolderToBeSynced(context, '/') - syncWizard = SyncWizard() - syncWizard.performAction("Pause sync") - - -@Given('the user has changed the content of local file "|any|" to:') -def step(context, filename): - fileContent = "\n".join(context.multiLineText) - f = open(context.userData['currentUserSyncPath'] + filename, "w") - f.write(fileContent) - f.close() - - -@When('the user resumes the file sync on the client') -def step(context): - syncWizard = SyncWizard() - syncWizard.performAction("Resume sync") - - -@When('the user triggers force sync on the client') -def step(context): - mouseClick( - waitForObjectItem(names.stack_folderList_QTreeView, "_1"), - 720, - 36, - Qt.NoModifier, - Qt.LeftButton, - ) - activateItem(waitForObjectItem(names.settings_QMenu, "Force sync now")) - - -@Then( - 'a conflict file for "|any|" should exist on the file system with the following content' -) -def step(context, filename): - expected = "\n".join(context.multiLineText) - - namepart = filename.split('.')[0] - extpart = filename.split('.')[1] - onlyfiles = [ - f - for f in listdir(context.userData['currentUserSyncPath']) - if isfile(join(context.userData['currentUserSyncPath'], f)) - ] - found = False - pattern = re.compile(buildConflictedRegex(filename)) - for file in onlyfiles: - if pattern.match(file): - f = open(context.userData['currentUserSyncPath'] + file, 'r') - contents = f.read() - if contents == expected: - found = True - break - - if not found: - raise Exception("Conflict file not found with given name") - - -@When('the user clicks on the activity tab') -def step(context): - toolbar = Toolbar() - toolbar.clickActivity() - - -@Then('a conflict warning should be shown for |integer| files') -def step(context, files): - clickTab(waitForObject(names.stack_QTabWidget), "Not Synced ({})".format(files)) - test.compare( - waitForObjectExists( - names.oCC_IssuesWidget_treeWidget_QTreeWidget - ).topLevelItemCount, - files, - ) - test.compare( - waitForObjectExists(names.oCC_IssuesWidget_treeWidget_QTreeWidget).visible, True - ) - test.compare( - waitForObjectExists( - names.o_treeWidget_Conflict_Server_version_downloaded_local_copy_renamed_and_not_uploaded_QModelIndex - ).displayText, - "Conflict: Server version downloaded, local copy renamed and not uploaded.", - ) - - -@Then('the table of conflict warnings should include file "|any|"') -def step(context, filename): - activity = Activity() - activity.checkFileExist(filename) - - -@Then('the file "|any|" should be blacklisted') -def step(context, filename): - activity = Activity() - activity.checkBlackListedFileExist(filename) - - -@When('the user selects "|any|" tab in the activity') -def step(context, tabName): - activity = Activity() - activity.clickTab(tabName) - - -def openSharingDialog(context, resource, itemType='file'): - resource = getResourcePath(context, resource) - - if itemType == 'folder': - waitFor( - lambda: isFolderSynced(resource), - context.userData['clientSyncTimeout'] * 1000, - ) - elif itemType == 'file': - waitFor( - lambda: isFileSynced(resource), context.userData['clientSyncTimeout'] * 1000 - ) - else: - raise Exception("No such item type for resource") - - waitFor( - lambda: shareResource(resource), context.userData['clientSyncTimeout'] * 1000 - ) - - -@When('the user opens the public links dialog of "|any|" using the client-UI') -def step(context, resource): - openSharingDialog(context, resource) - publicLinkDialog = PublicLinkDialog() - publicLinkDialog.openPublicLinkDialog() - - -@When("the user toggles the password protection using the client-UI") -def step(context): - publicLinkDialog = PublicLinkDialog() - publicLinkDialog.togglesPassword() - - -@Then('the password progress indicator should not be visible in the client-UI') -def step(context): - waitFor(lambda: (test.vp("publicLinkPasswordProgressIndicatorInvisible"))) - - -@Then( - 'the password progress indicator should not be visible in the client-UI - expected to fail' -) -def step(context): - waitFor(lambda: (test.xvp("publicLinkPasswordProgressIndicatorInvisible"))) - - -@When('the user opens the sharing dialog of "|any|" using the client-UI') -def step(context, resource): - openSharingDialog(context, resource, 'folder') - - -def getSharingDialogText(): - shareItem = SharingDialog() - errorText = shareItem.getSharingDialogMessage() - return errorText - - -@Then('the text "|any|" should be displayed in the sharing dialog') -def step(context, fileShareContext): - errorText = getSharingDialogText() - test.compare( - errorText, - fileShareContext, - ) - - -@Then('the error text "|any|" should be displayed in the sharing dialog') -def step(context, fileShareContext): - errorText = getSharingDialogText() - test.compare( - errorText, - fileShareContext, - ) - - -def createPublicLinkShare(context, resource, password='', permissions=''): - resource = getResourcePath(context, resource) - openSharingDialog(context, resource) - publicLinkDialog = PublicLinkDialog() - publicLinkDialog.openPublicLinkDialog() - publicLinkDialog.createPublicLink(context, resource, password, permissions) - - -@When( - 'the user creates a new public link for file "|any|" without password using the client-UI' -) -def step(context, resource): - createPublicLinkShare(context, resource) - - -@When( - 'the user creates a new public link for file "|any|" with password "|any|" using the client-UI' -) -def step(context, resource, password): - createPublicLinkShare(context, resource, password) - - -@When('the user edits the public link named "|any|" of file "|any|" changing following') -def step(context, publicLinkName, resource): - publicLinkDialog = PublicLinkDialog() - publicLinkDialog.setExpirationDate(context, publicLinkName, resource) - - -@When( - 'the user creates a new public link with permissions "|any|" for folder "|any|" without password using the client-UI' -) -def step(context, permissions, resource): - createPublicLinkShare(context, resource, '', permissions) - - -@When( - 'the user creates a new public link with permissions "|any|" for folder "|any|" with password "|any|" using the client-UI' -) -def step(context, permissions, resource, password): - createPublicLinkShare(context, resource, password, permissions) - - -def createPublicShareWithRole(context, resource, role): - resource = sanitizePath(substituteInLineCodes(context, resource)) - openSharingDialog(context, resource) - publicLinkDialog = PublicLinkDialog() - publicLinkDialog.openPublicLinkDialog() - publicLinkDialog.createPublicLinkWithRole(role) - - -@When( - 'the user creates a new public link for folder "|any|" using the client-UI with these details:' -) -def step(context, resource): - role = '' - for row in context.table: - if row[0] == 'role': - role = row[1] - break - - if role == '': - raise Exception("No role has been found") - else: - createPublicShareWithRole(context, resource, role) - - -@When('the user logs out of the client-UI') -def step(context): - accountStatus = AccountStatus() - accountStatus.accountAction("Log out") - - -def isUserSignedOut(context, username): - displayname = getDisplaynameForUser(context, username) - server = context.userData['localBackendUrl'] - accountStatus = AccountStatus() - test.compare( - str(waitForObjectExists(accountStatus.SIGNED_OUT_TEXT_BAR).text), - 'Signed out from ' - + server - + ' as ' - + displayname - + '.', - ) - - -def isUserSignedIn(context, username): - displayname = getDisplaynameForUser(context, username) - server = context.userData['localBackendUrl'] - accountStatus = AccountStatus() - - test.compare( - str(waitForObjectExists(accountStatus.SIGNED_OUT_TEXT_BAR).text), - 'Connected ' - + 'to ' - + server - + ' as ' - + displayname - + '.', - ) - - -@Then('user "|any|" should be signed out') -def step(context, username): - isUserSignedOut(context, username) - - -@Given('user "|any|" has logged out of the client-UI') -def step(context, username): - waitForFolderToBeSynced(context, '/') - # TODO: find some way to dynamically to check if files are synced - # It might take some time for all files to sync - snooze(5) - accountStatus = AccountStatus() - accountStatus.accountAction("Log out") - isUserSignedOut(context, username) - - -@When('user "|any|" logs in to the client-UI') -def step(context, username): - accountStatus = AccountStatus() - accountStatus.accountAction("Log in") - password = getPasswordForUser(context, username) - enterUserPassword = EnterPassword() - enterUserPassword.enterPassword(password) - - -@Then('user "|any|" should be connect to the client-UI') -def step(context, username): - # TODO: find some way to dynamically to check if files are synced - # It might take some time for all files to sync and connect to ther server - snooze(5) - isUserSignedIn(context, username) - - -@When('the user removes the connection for user "|any|" and host |any|') -def step(context, username, host): - displayname = getDisplaynameForUser(context, username) - displayname = substituteInLineCodes(context, displayname) - host = substituteInLineCodes(context, host) - - clickButton( - waitForObject( - { - "name": "settingsdialog_toolbutton_" + displayname + "@" + host, - "type": "QToolButton", - "visible": 1, - } - ) - ) - - waitForFolderToBeSynced(context, '/') - accountStatus = AccountStatus() - accountStatus.removeConnection() - - -@Then('an account with the displayname |any| and host |any| should not be displayed') -def step(context, displayname, host): - displayname = substituteInLineCodes(context, displayname) - host = substituteInLineCodes(context, host) - toolbar = Toolbar() - displayedAccountText = toolbar.getDisplayedAccountText(displayname, host) - - test.compare( - displayedAccountText, - displayname + "\n" + host, - ) - - -@Then('connection wizard should be visible') -def step(context): - test.compare( - str(waitForObjectExists(names.owncloudWizard_label_2_QLabel).text), - 'Ser&ver Address', - ) - waitForObject(AccountConnectionWizard.SERVER_ADDRESS_BOX) - - -@Then("the following tabs in the toolbar should match the default baseline") -def step(context): - for tabName in context.table: - test.vp(tabName[0]) - - -@When( - 'the user removes permissions "|any|" for user "|any|" of resource "|any|" using the client-UI' -) -def step(context, permissions, receiver, resource): - openSharingDialog(context, resource) - test.compare( - str(waitForObjectExists(names.scrollArea_sharedWith_QLabel).text), receiver - ) - - shareItem = SharingDialog() - shareItem.removePermissions(permissions) - - -@When("the user closes the sharing dialog") -def step(context): - clickButton(waitForObject(names.sharingDialog_Close_QPushButton)) - - -@Then( - '"|any|" permissions should not be displayed for user "|any|" for resource "|any|" on the client-UI' -) -def step(context, permissions, user, resource): - permissionsList = permissions.split(',') - - shareItem = SharingDialog() - shareItem.verifyResource(resource) - editChecked, shareChecked = shareItem.getAvailablePermission() - - if 'edit' in permissionsList: - test.compare(editChecked, False) - - if 'share' in permissionsList: - test.compare(shareChecked, False) - - -@Then('the error "|any|" should be displayed') -def step(context, errorMessage): - sharingDialog = SharingDialog() - test.compare(sharingDialog.getErrorText(), errorMessage) - - -@When( - 'the user tires to share resource "|any|" with the group "|any|" using the client-UI' -) -def step(context, resource, group): - openSharingDialog(context, resource) - - sharingDialog = SharingDialog() - sharingDialog.selectCollaborator(group, True) - - -@When('the user overwrites the file "|any|" with content "|any|"') -def step(context, resource, content): - print("starting file overwrite") - waitForFileToBeSynced(context, resource) - waitForFolderToBeSynced(context, '/') - - # overwriting the file immediately after it has been synced from the server seems to have some problem. - # The client does not see the change although the changes have already been made thus we are having a race condition - # So for now we add 5sec static wait - # an issue https://github.com/owncloud/client/issues/8832 has been created for it - - snooze(5) - - f = open(context.userData['currentUserSyncPath'] + resource, "w") - f.write(content) - f.close() - - print("file has been overwritten") - waitForFileToBeSynced(context, resource) - - -def enableVFSSupport(vfsBtnText): - # The enabling/disabling VFS button do not have it's own object - # But it is inside the "stack_folderList_QTreeView" object. - # So we are clicking at (718, 27) of "stack_folderList_QTreeView" object to enable/disable VFS - mouseClick( - waitForObjectItem(names.stack_folderList_QTreeView, "_1"), - 718, - 27, - Qt.NoModifier, - Qt.LeftButton, - ) - activateItem(waitForObjectItem(names.settings_QMenu, vfsBtnText)) - clickButton( - waitForObject(names.stack_Enable_experimental_placeholder_mode_QPushButton) - ) - - -@When("the user enables virtual file support") -def step(context): - enableVFSSupport("Enable virtual file support (experimental)...") - - -@Then('the "|any|" button should be available') -def step(context, btnText): - # The enabling/disabling VFS button do not have it's own object - # But it is inside the "stack_folderList_QTreeView" object. - # So we are clicking at (718, 27) of "stack_folderList_QTreeView" object to enable/disable VFS - mouseClick( - waitForObjectItem(names.stack_folderList_QTreeView, "_1"), - 718, - 27, - Qt.NoModifier, - Qt.LeftButton, - ) - waitForObjectItem(names.settings_QMenu, btnText) - - -@Given("the user has enabled virtual file support") -def step(context): - enableVFSSupport("Enable virtual file support (experimental)...") - - -@When("the user disables virtual file support") -def step(context): - # The enabling/disabling VFS button do not have it's own object - # But it is inside the "stack_folderList_QTreeView" object. - # So we are clicking at (718, 27) of "stack_folderList_QTreeView" object to enable/disable VFS - mouseClick( - waitForObjectItem(names.stack_folderList_QTreeView, "_1"), - 733, - 27, - Qt.NoModifier, - Qt.LeftButton, - ) - activateItem( - waitForObjectItem(names.settings_QMenu, "Disable virtual file support...") - ) - clickButton( - waitForObject(names.disable_virtual_file_support_Disable_support_QPushButton) - ) - - -@When('the user accepts the certificate') -def step(context): - clickButton(waitForObject(names.oCC_SslErrorDialog_cbTrustConnect_QCheckBox)) - clickButton(waitForObject(names.oCC_SslErrorDialog_OK_QPushButton)) - - -@Then('the lock shown should be closed') -def step(context): - test.vp("urlLock") - - -@Then('error "|any|" should be displayed') -def step(context, errorMsg): - newAccount = AccountConnectionWizard() - test.compare(str(waitForObjectExists(newAccount.ERROR_LABEL).text), errorMsg) - - -@When(r'the user deletes the (file|folder) "([^"]*)"', regexp=True) -def step(context, itemType, resource): - resourcePath = sanitizePath(context.userData['currentUserSyncPath'] + resource) - if itemType == 'file': - os.remove(resourcePath) - elif itemType == 'folder': - shutil.rmtree(resourcePath) - else: - raise Exception("No such item type for resource") - - -@When( - 'the user unshares the resource "|any|" for collaborator "|any|" using the client-UI' -) -def step(context, resource, receiver): - openSharingDialog(context, resource) - test.compare( - str(waitForObjectExists(names.scrollArea_sharedWith_QLabel).text), receiver - ) - clickButton(waitForObject(names.scrollArea_deleteShareButton_QToolButton)) - - -@Given('the user has added the following server address:') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.addServer(context) - test.compare( - waitForObjectExists(newAccount.CREDENTIAL_PAGE).visible, - True, - "Assert credentials page is visible", - ) - - -@When('the user adds the following server address:') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.addServer(context) - - -@Given('the user has added the following user credentials:') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.addUserCreds(context) - test.compare( - waitForObjectExists(newAccount.ADVANCE_SETUP_PAGE).visible, - True, - "Assert setup page is visible", - ) - - -@Given('the user has opened chose_what_to_sync dialog') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.openSyncDialog() - test.compare( - waitForObjectExists(newAccount.SELECTIVE_SYNC_DIALOG).visible, - True, - "Assert selective sync dialog is visible", - ) - - -@When('the user opens chose_what_to_sync dialog') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.openSyncDialog() - - -@When('the user selects the following folders to sync:') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.selectFoldersToSync(context) - - -@When('the user selects manual sync folder option') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.selectManualSyncFolder() - - -@When('the user connects the account') -def step(context): - newAccount = AccountConnectionWizard() - newAccount.connectAccount() - - -@When('the user sorts the folder list by "|any|"') -def step(context, headerText): - headerText = headerText.capitalize() - if headerText in ["Size", "Name"]: - newAccount = AccountConnectionWizard() - newAccount.sortBy(headerText) - else: - raise Exception("Sorting by '" + headerText + "' is not supported.") - - -@Then('the dialog chose_what_to_sync should be visible') -def step(context): - newAccount = AccountConnectionWizard() - test.compare( - waitForObjectExists(newAccount.SELECTIVE_SYNC_DIALOG).visible, - True, - "Assert selective sync dialog is visible", - ) - - -@Then('the sync all checkbox should be checked') -def step(context): - newAccount = AccountConnectionWizard() - test.compare( - waitForObjectExists(newAccount.SYNC_DIALOG_ROOT_FOLDER).checkState, - "checked", - "Assert sync all checkbox is checked", - ) - - -@Then("the folders should be in the following order:") -def step(context): - newAccount = AccountConnectionWizard() - rowIndex = 0 - for row in context.table[1:]: - FOLDER_TREE_ROW = { - "row": rowIndex, - "container": newAccount.SYNC_DIALOG_ROOT_FOLDER, - "type": "QModelIndex", - } - expectedFolder = row[0] - actualFolder = waitForObjectExists(FOLDER_TREE_ROW).displayText - test.compare(actualFolder, expectedFolder) - - rowIndex += 1 - - -@When('the user deletes the public link for file "|any|"') -def step(context, resource): - openSharingDialog(context, resource) - publicLinkDialog = PublicLinkDialog() - publicLinkDialog.openPublicLinkDialog() - - test.compare( - str(waitForObjectExists(publicLinkDialog.ITEM_TO_SHARE).text), - resource.replace(context.userData['currentUserSyncPath'], ''), - ) - clickButton(waitForObject(names.linkShares_QToolButton_2)) - clickButton(waitForObject(names.oCC_ShareLinkWidget_Delete_QPushButton)) - - -@When( - 'the user changes the password of public link "|any|" to "|any|" using the client-UI' -) -def step(context, publicLinkName, password): - publicLinkDialog = PublicLinkDialog() - publicLinkDialog.changePassword(publicLinkName, password) diff --git a/test/gui/shared/steps/sync_context.py b/test/gui/shared/steps/sync_context.py new file mode 100644 index 00000000000..35fcc0e8a3e --- /dev/null +++ b/test/gui/shared/steps/sync_context.py @@ -0,0 +1,377 @@ +import re +import squish + +from pageObjects.SyncConnectionWizard import SyncConnectionWizard +from pageObjects.SyncConnection import SyncConnection +from pageObjects.Toolbar import Toolbar +from pageObjects.Activity import Activity +from pageObjects.Settings import Settings + +from helpers.ConfigHelper import get_config, is_windows, set_config, is_owncloud_client +from helpers.SyncHelper import ( + wait_for_resource_to_sync, + wait_for_resource_to_have_sync_error, + wait_for_initial_sync_to_complete, +) +from helpers.SetupClientHelper import ( + get_temp_resource_path, + set_current_user_sync_path, + substitute_inline_codes, + get_resource_path, +) +from helpers.UserHelper import get_displayname_for_user, get_username_for_user + + +@When('using sync connection folder "|any|"') +def step(context, sync_folder): + if not is_owncloud_client(): + set_config( + 'syncConnectionName', + f'{sync_folder} (Shared by {get_username_for_user("admin")})', + ) + else: + set_config('syncConnectionName', sync_folder) + # wait for files to sync + wait_for_initial_sync_to_complete(get_resource_path('/', space=sync_folder)) + + +@Given('the user has paused the file sync') +def step(context): + SyncConnection.pause_sync() + + +@When('the user resumes the file sync on the client') +def step(context): + SyncConnection.resume_sync() + + +@When('the user force syncs the files') +def step(context): + SyncConnection.force_sync() + + +@When('the user waits for the files to sync') +def step(context): + wait_for_resource_to_sync(get_resource_path('/')) + + +@When(r'the user waits for (file|folder) "([^"]*)" to be synced', regexp=True) +def step(context, resource_type, resource): + resource = get_resource_path(resource) + wait_for_resource_to_sync(resource, resource_type) + + +@When(r'the user waits for (file|folder) "([^"]*)" to have sync error', regexp=True) +def step(context, resource_type, resource): + resource = get_resource_path(resource) + wait_for_resource_to_have_sync_error(resource, resource_type) + + +@When( + r'user "([^"]*)" waits for (file|folder) "([^"]*)" to have sync error', regexp=True +) +def step(context, username, resource_type, resource): + resource = get_resource_path(resource, username) + wait_for_resource_to_have_sync_error(resource, resource_type) + + +@When('the user enables virtual file support') +def step(context): + SyncConnection.enable_vfs() + # TODO: remove snooze with proper wait + # let the client re-sync + squish.snooze(get_config('minSyncTimeout')) + + +@Then('the "|any|" button should be available') +def step(context, item): + SyncConnection.open_menu() + SyncConnection.has_menu_item(item) + + +@Then('the "|any|" button should not be available') +def step(context, item): + SyncConnection.open_menu() + test.compare( + SyncConnection.menu_item_exists(item), + False, + f'Menu item "{item}" does not exist.', + ) + # Close the opened menu as it remains open + # This will prevent the menu to close in the next step while trying to open the menu + squish.mouseClick(squish.waitForObject(SyncConnection.FOLDER_SYNC_CONNECTION_LABEL)) + + +@When('the user disables virtual file support') +def step(context): + SyncConnection.disable_vfs() + # TODO: remove snooze with proper wait + # let the client re-sync + squish.snooze(get_config('minSyncTimeout')) + + +@When('the user clicks on the activity tab') +def step(context): + Toolbar.open_activity() + + +@When('the user clicks on the errors tab') +def step(context): + Toolbar.open_errors() + + +@Then('the table of conflict warnings should include file "|any|"') +def step(context, filename): + Activity.check_file_exist(filename) + + +@Then('the file "|any|" should be blacklisted') +def step(context, filename): + test.compare( + True, Activity.is_resource_blacklisted(filename), 'File is Blacklisted' + ) + + +@Then('the file "|any|" should be ignored') +def step(context, filename): + test.compare(True, Activity.is_resource_ignored(filename), 'File is Ignored') + + +@Then('the file "|any|" should be excluded') +def step(context, filename): + test.compare(True, Activity.is_resource_excluded(filename), 'File is Excluded') + + +@Then('the toolbar should have the following tabs:') +def step(context): + for tab_name in context.table: + test.compare( + Toolbar.has_item(tab_name[0]), + True, + f'Toolbar tab "{tab_name[0]}" exists.', + ) + + +@When('the user selects the following folders to sync:') +def step(context): + folders = [] + for row in context.table[1:]: + folders.append(row[0]) + SyncConnectionWizard.select_folders_to_sync(folders) + SyncConnectionWizard.add_sync_connection() + + +@When('the user sorts the folder list by "|any|"') +def step(context, header_text): + if (header_text := header_text.capitalize()) in ['Size', 'Name']: + SyncConnectionWizard.sort_by(header_text) + else: + raise ValueError("Sorting by '" + header_text + "' is not supported.") + + +@Then('the sync all checkbox should be checked') +def step(context): + test.compare( + SyncConnectionWizard.is_root_folder_checked(), + True, + 'Sync all checkbox is checked', + ) + + +@Then('the folders should be in the following order:') +def step(context): + row_index = 0 + for row in context.table[1:]: + expected_folder = row[0] + actual_folder = SyncConnectionWizard.get_item_name_from_row(row_index) + test.compare(actual_folder, expected_folder) + + row_index += 1 + + +@When('the user selects "|any|" space in sync connection wizard') +def step(context, space_name): + if get_config('ocis'): + if not is_owncloud_client(): + space_name = get_config('personal_sync_folder') + SyncConnectionWizard.select_space(space_name) + SyncConnectionWizard.next_step() + set_config('syncConnectionName', space_name) + + +@When('the user sets the sync path in sync connection wizard') +def step(context): + SyncConnectionWizard.set_sync_path() + + +@When( + 'the user sets the temp folder "|any|" as local sync path in sync connection wizard' +) +def step(context, folder_name): + sync_path = get_temp_resource_path(folder_name) + SyncConnectionWizard.set_sync_path(sync_path) + # empty connection name when using temporary locations + set_config('syncConnectionName', '') + set_current_user_sync_path(sync_path) + + +@When('the user syncs the "|any|" space') +def step(context, space_name): + if space_name == 'Personal' and not is_owncloud_client(): + space_name = get_config('personal_sync_folder') + SyncConnectionWizard.sync_space(space_name) + + +@Then('the settings tab should have the following options in the general section:') +def step(context): + for item in context.table: + Settings.check_general_option(item[0]) + + +@Then('the settings tab should have the following options in the advanced section:') +def step(context): + for item in context.table: + Settings.check_advanced_option(item[0]) + + +@Then('the settings tab should have the following options in the network section:') +def step(context): + for item in context.table: + Settings.check_network_option(item[0]) + + +@When('the user closes the settings tab') +def step(context): + Settings.close_settings_tab() + + +@When('the user opens the about dialog') +def step(context): + Settings.open_about_button() + + +@Then('the about dialog should be opened') +def step(context): + Settings.wait_for_about_dialog_to_be_visible() + + +@When('the user adds the folder sync connection') +def step(context): + SyncConnectionWizard.add_sync_connection() + + +@When('user unselects all the remote folders') +def step(context): + SyncConnectionWizard.deselect_all_remote_folders() + + +@When('the user |word| VFS support for Windows') +def step(context, action): + if is_windows(): + action = action.rstrip('s') + SyncConnectionWizard.enable_disable_vfs_support(action) + + +@When('user unselects a folder "|any|" in selective sync') +def step(context, folder_name): + SyncConnection.choose_what_to_sync() + SyncConnection.unselect_folder_in_selective_sync(folder_name) + + +@Then('the sync folder list should be empty') +def step(context): + test.compare( + 0, + SyncConnection.get_folder_connection_count(), + 'Sync connections should be empty', + ) + + +@When('the user navigates back in the sync connection wizard') +def step(context): + SyncConnectionWizard.back() + + +@When('the user selects remove folder sync connection option') +def step(context): + SyncConnection.remove_folder_sync_connection() + + +@When('the user cancels the folder sync connection removal dialog') +def step(context): + SyncConnection.cancel_folder_sync_connection_removal() + + +@When('the user removes the "|any|" folder sync connection') +def step(context, sync_folder): + SyncConnection.remove_folder_sync_connection(sync_folder) + SyncConnection.confirm_folder_sync_connection_removal() + + +@Then('the file "|any|" should have status "|any|" in the activity tab') +def step(context, file_name, status): + Activity.has_sync_status(file_name, status) + + +@Then('the button to open sync connection wizard should be disabled') +def step(context): + test.compare( + False, + SyncConnectionWizard.is_add_sync_folder_button_enabled(), + 'Button to open sync connection wizard should be disabled', + ) + + +@When('the user checks the activities of account "|any|"') +def step(context, account): + account = substitute_inline_codes(account) + Activity.select_synced_filter(account) + + +@Then('the following activities should be displayed in synced table') +def step(context): + for row in context.table[1:]: + resource = row[0] + action = row[1] + account = substitute_inline_codes(row[2]) + test.compare( + Activity.check_synced_table(resource, action, account), + True, + 'Resource should be displayed in the synced table', + ) + + +@Then( + r'the following activities (should|should not) be displayed in not synced table', + regexp=True, +) +def step(context, should_or_should_not): + expected = should_or_should_not == 'should' + for row in context.table[1:]: + resource = row[0] + status = row[1] + account = substitute_inline_codes(row[2]) + if not is_owncloud_client(): + # get the displayname of user from account string + # and replace the existing displayname with predefined user's displayname from the account string + displayname = get_displayname_for_user(account.split()[0].strip()) + account = re.sub(r'^[^@]+', displayname, account) + test.compare( + Activity.check_not_synced_table(resource, status, account), + expected, + 'Resource should be displayed in the not synced table', + ) + + +@When('the user unchecks the "|any|" filter') +def step(context, filter_option): + Activity.select_not_synced_filter(filter_option) + + +@Then('the space "|any|" should not exist in the sync folder list') +def step(context, space_name): + test.compare( + SyncConnectionWizard.is_space_available(space_name), + False, + f'Expected {space_name} to be not synced.', + ) diff --git a/test/gui/shared/steps/vfs_context.py b/test/gui/shared/steps/vfs_context.py new file mode 100644 index 00000000000..febd6c52140 --- /dev/null +++ b/test/gui/shared/steps/vfs_context.py @@ -0,0 +1,23 @@ +from helpers.FilesHelper import get_file_size_on_disk, get_file_size +from helpers.SetupClientHelper import get_resource_path + + +@Then('the placeholder of file "|any|" should exist on the file system') +def step(context, file_name): + resource_path = get_resource_path(file_name) + size_on_disk = get_file_size_on_disk(resource_path) + test.compare( + size_on_disk, 0, f"Size of the placeholder on the disk is: '{size_on_disk}'" + ) + + +@Then('the file "|any|" should be downloaded') +def step(context, file_name): + resource_path = get_resource_path(file_name) + size_on_disk = get_file_size_on_disk(resource_path) + file_size = get_file_size(resource_path) + test.compare( + size_on_disk, + file_size, + f"Original file size '{file_size}' is not equal to its size on disk '{size_on_disk}'", + ) diff --git a/test/gui/suite.conf b/test/gui/suite.conf index 5b8da565965..e1a13cdb266 100644 --- a/test/gui/suite.conf +++ b/test/gui/suite.conf @@ -1,9 +1,8 @@ AUT=owncloud --showsettings -ENVVARS=envvars +ENVVARS=envs.txt HOOK_SUB_PROCESSES=false -IMPLICITAUTSTART=0 +IMPLICITAUTSTART=false LANGUAGE=Python OBJECTMAPSTYLE=script -TEST_CASES=tst_addAccount tst_sharing tst_syncing tst_loginLogout tst_removeAccountConnection tst_checkAlltabs tst_vfs tst_deletFilesFolders tst_editFiles VERSION=3 WRAPPERS=Qt diff --git a/test/gui/tst_activity/test.feature b/test/gui/tst_activity/test.feature new file mode 100644 index 00000000000..c79b9638f6b --- /dev/null +++ b/test/gui/tst_activity/test.feature @@ -0,0 +1,50 @@ +Feature: filter activity for user + As a user + I want to filter activity + So that I can view activity of specific user + + + Scenario: filter synced activities + Given user "Alice" has been created in the server with default attributes + And user "Brian" has been created in the server with default attributes + And user "Alice" has created folder "simple-folder" in the server + And the user has set up the following accounts with default settings: + | Alice | + | Brian | + When the user clicks on the activity tab + And the user checks the activities of account "Alice Hansen@%local_server_hostname%" + Then the following activities should be displayed in synced table + | resource | action | account | + | simple-folder | Downloaded | Alice Hansen@%local_server_hostname% | + + @skipOnWindows @predefined_users + Scenario: filter not synced activities (Linux only) + Given user "Alice" has been created in the server with default attributes + And user "Alice" has set up a client with default settings + And user "Alice" has created a folder "Folder1" inside the sync folder + When user "Alice" creates the following files inside the sync folder: + | files | + | /.htaccess | + | /Folder1/a\\a.txt | + And the user clicks on the errors tab + Then the file "Folder1/a\\a.txt" should be blacklisted + And the file ".htaccess" should be excluded + When the user unchecks the "Excluded" filter + Then the following activities should be displayed in not synced table + | resource | status | account | + | Folder1/a\\a.txt | Blacklisted | Alice Hansen@%local_server_hostname% | + + @skipOnLinux @predefined_users + Scenario: filter not synced activities (Windows only) + Given user "Alice" has been created in the server with default attributes + And user "Alice" has set up a client with default settings + And user "Alice" has created a folder "Folder1" inside the sync folder + When user "Alice" creates the following files inside the sync folder: + | files | + | /.htaccess | + And the user clicks on the errors tab + Then the file ".htaccess" should be excluded + When the user unchecks the "Excluded" filter + Then the following activities should not be displayed in not synced table + | resource | status | account | + | .htaccess | Excluded | Alice Hansen@%local_server_hostname% | diff --git a/test/gui/tst_activity/test.py b/test/gui/tst_activity/test.py new file mode 100644 index 00000000000..83b0a5275a4 --- /dev/null +++ b/test/gui/tst_activity/test.py @@ -0,0 +1,8 @@ +source(findFile('scripts', 'python/bdd.py')) + +setupHooks('../shared/scripts/bdd_hooks.py') +collectStepDefinitions('./steps', '../shared/steps') + + +def main(): + runFeatureFile('test.feature') diff --git a/test/gui/tst_addAccount/test.feature b/test/gui/tst_addAccount/test.feature index e82941c5b6f..98eeba8300f 100644 --- a/test/gui/tst_addAccount/test.feature +++ b/test/gui/tst_addAccount/test.feature @@ -1,48 +1,83 @@ Feature: adding accounts - As a user I want to be able join multiple owncloud servers to the client So that I can sync data with various organisations - Background: - Given user "Alice" has been created on the server with default attributes and without skeleton files + Given user "Alice" has been created in the server with default attributes + + @predefined_users + Scenario: Check default options in advanced configuration + Given the user has started the client + And the user has entered the following account information: + | server | %local_server% | + | user | Alice | + | password | 1234 | + Then the download everything option should be selected by default for Linux + And the VFS option should be selected by default for Windows + And the user should be able to choose the local download directory + @predefined_users Scenario: Adding normal Account Given the user has started the client - When the user adds the first account with + When the user adds the following account: | server | %local_server% | | user | Alice | | password | 1234 | - Then an account should be displayed with the displayname Alice Hansen and host %local_server_hostname% + Then the account with displayname "Alice Hansen" and host "%local_server_hostname%" should be displayed Scenario: Adding multiple accounts - Given user "Brian" has been created on the server with default attributes and without skeleton files + Given user "Brian" has been created in the server with default attributes And user "Alice" has set up a client with default settings - When the user adds another account with + When the user opens the add-account dialog + And the user adds the following account: | server | %local_server% | | user | Brian | | password | AaBb2Cc3Dd4 | - Then an account should be displayed with the displayname Alice Hansen and host %local_server_hostname% - And an account should be displayed with the displayname Brian Murphy and host %local_server_hostname% - - - Scenario: Adding account with wrong credentials - Given the user has started the client - And the user has added the following server address: - | server | %local_server% | - When the user adds the following wrong user credentials: - | user | Alice | - | password | 12345 | - Then error "The provided credentials are not correct" should be displayed + Then "Brian Murphy" account should be opened + And the account with displayname "Alice Hansen" and host "%local_server_hostname%" should be displayed + And the account with displayname "Brian Murphy" and host "%local_server_hostname%" should be displayed Scenario: Adding account with self signed certificate for the first time Given the user has started the client - When the user adds the following server address: - | server | %secure_local_server% | + When the user adds the server "%secure_local_server%" And the user accepts the certificate - Then the lock shown should be closed + Then credentials wizard should be visible + @skipOnLinux + Scenario: Adding account with vfs disabled (Windows only) + Given the user has started the client + And the user has entered the following account information: + | server | %local_server% | + | user | Alice | + | password | 1234 | + When the user selects download everything option in advanced section + Then the "Enable virtual file support" button should be available + @predefined_users + Scenario: Add space manually from sync connection window + Given user "Alice" has created folder "simple-folder" in the server + And the user has started the client + And the user has entered the following account information: + | server | %local_server% | + | user | Alice | + | password | 1234 | + When the user selects manual sync folder option in advanced section + And the user syncs the "Personal" space + Then the folder "simple-folder" should exist on the file system + + + Scenario: Check for suffix when sync path exists + Given the user has created folder "ownCloud" in the default home path + And the user has started the client + And the user has entered the following account information: + | server | %local_server% | + When the user adds the following user credentials: + | user | Alice | + | password | 1234 | + And the user opens the advanced configuration + Then the default local sync path should contain "%home%/ownCloud (2)" in the configuration wizard + When the user selects download everything option in advanced section + Then the button to open sync connection wizard should be disabled diff --git a/test/gui/tst_addAccount/test.py b/test/gui/tst_addAccount/test.py index d6224b99e57..83b0a5275a4 100644 --- a/test/gui/tst_addAccount/test.py +++ b/test/gui/tst_addAccount/test.py @@ -5,5 +5,4 @@ def main(): - testSettings.throwOnFailure = True runFeatureFile('test.feature') diff --git a/test/gui/tst_addAccount/verificationPoints/urlLock b/test/gui/tst_addAccount/verificationPoints/urlLock deleted file mode 100644 index 0faaa167cc2..00000000000 --- a/test/gui/tst_addAccount/verificationPoints/urlLock +++ /dev/null @@ -1,6 +0,0 @@ - - - -iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAV1BMVEVDVnFEV3JFV3JFWHNGWHNGWXNJW3VTZH1UZH1VZn5XZ39ZaYFkc4lnd4xxf5N5hpiCjZ+LlqaSnKuTnqyWoK2YorCmrrqss77EyM/V2Nza3N/a3eDv7++tKicuAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAZElEQVQYla2O3QqAIAxGp2ZpmuZfWvn+z1kqBOVtB8a+HRgb5JtzW8Zx2c6S4a6koaJTE8eKqTBGULweVURJ1J7zroiMVYR5cmXbTXOownPui2i9F5YheEDMwgAvPuNfdFe+f1xFYQkhIG7dgQAAAABJRU5ErkJggg== - - diff --git a/test/gui/tst_checkAlltabs/test.feature b/test/gui/tst_checkAlltabs/test.feature index fdc87daffba..f28a9b4db54 100644 --- a/test/gui/tst_checkAlltabs/test.feature +++ b/test/gui/tst_checkAlltabs/test.feature @@ -1,15 +1,34 @@ +@predefined_users Feature: Visually check all tabs - As a user I want to visually check all tabs in client - So that I can performe all the actions related to client + So that I can perform all the actions related to client Scenario: Tabs in toolbar looks correct - Given user "Alice" has been created on the server with default attributes and without skeleton files + Given user "Alice" has been created in the server with default attributes + And user "Alice" has set up a client with default settings + Then the toolbar should have the following tabs: + | Activity | + | Errors: 0 | + | More | + + + Scenario: Verify various setting options in Settings tab + Given user "Alice" has been created in the server with default attributes And user "Alice" has set up a client with default settings - Then the following tabs in the toolbar should match the default baseline - | AddAccount | - | Activity | - | Settings | - | QuitOwncloud | + When the user clicks on the settings tab + Then the settings tab should have the following options in the general section: + | Start on Login | + | Use Monochrome Icons in the system tray | + | Show desktop Notifications | + | Language | + And the settings tab should have the following options in the advanced section: + | Sync hidden files | + | Edit ignored files | + | Log settings | + And the settings tab should have the following options in the network section: + | Proxy Settings | + When the user closes the settings tab + And the user opens the about dialog + Then the about dialog should be opened diff --git a/test/gui/tst_checkAlltabs/test.py b/test/gui/tst_checkAlltabs/test.py index d6224b99e57..83b0a5275a4 100644 --- a/test/gui/tst_checkAlltabs/test.py +++ b/test/gui/tst_checkAlltabs/test.py @@ -5,5 +5,4 @@ def main(): - testSettings.throwOnFailure = True runFeatureFile('test.feature') diff --git a/test/gui/tst_checkAlltabs/verificationPoints/Activity b/test/gui/tst_checkAlltabs/verificationPoints/Activity deleted file mode 100644 index fcd14212209..00000000000 --- a/test/gui/tst_checkAlltabs/verificationPoints/Activity +++ /dev/null @@ -1,16 +0,0 @@ - - - - -MTk1 - - -MA== - - -QWN0aXZpdHk= - - -dHJ1ZQ== - - diff --git a/test/gui/tst_checkAlltabs/verificationPoints/AddAccount b/test/gui/tst_checkAlltabs/verificationPoints/AddAccount deleted file mode 100644 index b5242c8586b..00000000000 --- a/test/gui/tst_checkAlltabs/verificationPoints/AddAccount +++ /dev/null @@ -1,16 +0,0 @@ - - - - -MA== - - -MA== - - -QWRkIGFjY291bnQ= - - -dHJ1ZQ== - - diff --git a/test/gui/tst_checkAlltabs/verificationPoints/QuitOwncloud b/test/gui/tst_checkAlltabs/verificationPoints/QuitOwncloud deleted file mode 100644 index 2298a4f7cdf..00000000000 --- a/test/gui/tst_checkAlltabs/verificationPoints/QuitOwncloud +++ /dev/null @@ -1,16 +0,0 @@ - - - - -Njkx - - -MA== - - -UXVpdCBvd25DbG91ZA== - - -dHJ1ZQ== - - diff --git a/test/gui/tst_checkAlltabs/verificationPoints/Settings b/test/gui/tst_checkAlltabs/verificationPoints/Settings deleted file mode 100644 index 708af14bcd2..00000000000 --- a/test/gui/tst_checkAlltabs/verificationPoints/Settings +++ /dev/null @@ -1,16 +0,0 @@ - - - - -MjYy - - -MA== - - -U2V0dGluZ3M= - - -dHJ1ZQ== - - diff --git a/test/gui/tst_deletFilesFolders/test.feature b/test/gui/tst_deletFilesFolders/test.feature index 57ef3f4ac14..26831eb20b8 100644 --- a/test/gui/tst_deletFilesFolders/test.feature +++ b/test/gui/tst_deletFilesFolders/test.feature @@ -1,32 +1,48 @@ +@predefined_users Feature: deleting files and folders - As a user I want to delete files and folders So that I can keep my filing system clean and tidy + Background: + Given user "Alice" has been created in the server with default attributes - Background: - Given user "Alice" has been created on the server with default attributes and without skeleton files - + @issue-9439 Scenario Outline: Delete a file - Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "" on the server + Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "" in the server And user "Alice" has set up a client with default settings - When the user waits for file "" to be synced - And the user deletes the file "" - Then as "Alice" file "" should not exist on the server + When the user deletes the file "" + And the user waits for the files to sync + Then as "Alice" file "" should not exist in the server Examples: | fileName | | textfile0.txt | | textfile0-with-name-more-than-20-characters | + | ~`!@#$^&()-_=+{[}];',textfile.txt | - + @issue-9439 Scenario Outline: Delete a folder - Given user "Alice" has created folder "" on the server + Given user "Alice" has created folder "" in the server And user "Alice" has set up a client with default settings - When the user waits for folder "" to be synced - And the user deletes the folder "" - Then as "Alice" file "" should not exist on the server + When the user deletes the folder "" + And the user waits for the files to sync + Then as "Alice" file "" should not exist in the server Examples: | folderName | | simple-empty-folder | | simple-folder-with-name-more-than-20-characters | + + + Scenario: Delete a file and a folder + Given user "Alice" has uploaded file with content "test file 1" to "textfile1.txt" in the server + And user "Alice" has uploaded file with content "test file 2" to "textfile2.txt" in the server + And user "Alice" has created folder "test-folder1" in the server + And user "Alice" has created folder "test-folder2" in the server + And user "Alice" has set up a client with default settings + When the user deletes the file "textfile1.txt" + And the user deletes the folder "test-folder1" + And the user waits for the files to sync + Then as "Alice" file "textfile1.txt" should not exist in the server + And as "Alice" folder "test-folder1" should not exist in the server + And as "Alice" file "textfile2.txt" should exist in the server + And as "Alice" folder "test-folder2" should exist in the server diff --git a/test/gui/tst_deletFilesFolders/test.py b/test/gui/tst_deletFilesFolders/test.py index d6224b99e57..83b0a5275a4 100644 --- a/test/gui/tst_deletFilesFolders/test.py +++ b/test/gui/tst_deletFilesFolders/test.py @@ -5,5 +5,4 @@ def main(): - testSettings.throwOnFailure = True runFeatureFile('test.feature') diff --git a/test/gui/tst_editFiles/test.feature b/test/gui/tst_editFiles/test.feature index 79376aeaf75..d0dd2e463c4 100644 --- a/test/gui/tst_editFiles/test.feature +++ b/test/gui/tst_editFiles/test.feature @@ -1,16 +1,15 @@ Feature: edit files - As a user I want to be able to edit the file content So that I can modify and change file data - Background: - Given user "Alice" has been created on the server with default attributes and without skeleton files - + Given user "Alice" has been created in the server with default attributes - Scenario: Modify orignal content of a file with special character - Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "S@mpleFile!With,$pecial?Characters.txt" on the server + @predefined_users + Scenario: Modify original content of a file with special character + Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "S@mpleFile!With,$pecial&Characters.txt" in the server And user "Alice" has set up a client with default settings - When the user overwrites the file "S@mpleFile!With,$pecial?Characters.txt" with content "overwrite ownCloud test text file" - Then as "Alice" the file "S@mpleFile!With,$pecial?Characters.txt" on the server should have the content "overwrite ownCloud test text file" \ No newline at end of file + When the user overwrites the file "S@mpleFile!With,$pecial&Characters.txt" with content "overwrite ownCloud test text file" + And the user waits for file "S@mpleFile!With,$pecial&Characters.txt" to be synced + Then as "Alice" the file "S@mpleFile!With,$pecial&Characters.txt" should have the content "overwrite ownCloud test text file" in the server diff --git a/test/gui/tst_editFiles/test.py b/test/gui/tst_editFiles/test.py index d6224b99e57..83b0a5275a4 100644 --- a/test/gui/tst_editFiles/test.py +++ b/test/gui/tst_editFiles/test.py @@ -5,5 +5,4 @@ def main(): - testSettings.throwOnFailure = True runFeatureFile('test.feature') diff --git a/test/gui/tst_loginLogout/test.feature b/test/gui/tst_loginLogout/test.feature index 21e3ddb23e7..9fbe5a51da9 100644 --- a/test/gui/tst_loginLogout/test.feature +++ b/test/gui/tst_loginLogout/test.feature @@ -3,16 +3,21 @@ Feature: Logout users I want to be able to login and logout of my account So that I can protect my work and identity and be assured of privacy - Background: - Given user "Alice" has been created on the server with default attributes and without skeleton files + Background: + Given user "Alice" has been created in the server with default attributes - Scenario: logging out - Given user "Alice" has set up a client with default settings - When the user logs out of the client-UI - Then user "Alice" should be signed out + @predefined_users + Scenario: logging out + Given user "Alice" has set up a client with default settings + When the user "Alice" logs out using the client-UI + Then user "Alice" should be signed out - Scenario: login after loggin out - Given user "Alice" has set up a client with default settings - And user "Alice" has logged out of the client-UI - When user "Alice" logs in to the client-UI - Then user "Alice" should be connect to the client-UI + + Scenario: login after logging out + Given user "Alice" has set up a client with default settings + And user "Alice" has logged out from the client-UI + When user "Alice" logs in using the client-UI + Then user "Alice" should be connected to the server + When the user quits the client + And the user starts the client + Then user "Alice" should be connected to the server diff --git a/test/gui/tst_loginLogout/test.py b/test/gui/tst_loginLogout/test.py index d6224b99e57..83b0a5275a4 100644 --- a/test/gui/tst_loginLogout/test.py +++ b/test/gui/tst_loginLogout/test.py @@ -5,5 +5,4 @@ def main(): - testSettings.throwOnFailure = True runFeatureFile('test.feature') diff --git a/test/gui/tst_moveFilesFolders/test.feature b/test/gui/tst_moveFilesFolders/test.feature new file mode 100644 index 00000000000..b7d046afb2f --- /dev/null +++ b/test/gui/tst_moveFilesFolders/test.feature @@ -0,0 +1,54 @@ +Feature: move file and folder + As a user + I want to be able to move a folder and a file + So that I can organize my files and folders + + Background: + Given user "Alice" has been created in the server with default attributes + And user "Alice" has created folder "folder1" in the server + And user "Alice" has created folder "folder1/folder2" in the server + And user "Alice" has created folder "folder1/folder2/folder3" in the server + And user "Alice" has created folder "folder1/folder2/folder3/folder4" in the server + And user "Alice" has created folder "folder1/folder2/folder3/folder4/folder5" in the server + + + Scenario: Move folder and file from level 5 sub-folder to sync root + Given user "Alice" has created folder "folder1/folder2/folder3/folder4/folder5/test-folder" in the server + And user "Alice" has uploaded file with content "ownCloud" to "folder1/folder2/folder3/folder4/folder5/lorem.txt" in the server + And user "Alice" has set up a client with default settings + When user "Alice" moves file "folder1/folder2/folder3/folder4/folder5/lorem.txt" to "/" in the sync folder + And user "Alice" moves folder "folder1/folder2/folder3/folder4/folder5/test-folder" to "/" in the sync folder + And the user waits for the files to sync + Then as "Alice" the file "lorem.txt" should have the content "ownCloud" in the server + And as "Alice" folder "test-folder" should exist in the server + And as "Alice" file "folder1/folder2/folder3/folder4/folder5/lorem.txt" should not exist in the server + And as "Alice" folder "folder1/folder2/folder3/folder4/folder5/test-folder" should not exist in the server + + + Scenario: Move two folders and a file down to the level 5 sub-folder + And user "Alice" has created folder "test-folder1" in the server + And user "Alice" has created folder "test-folder2" in the server + And user "Alice" has uploaded file with content "ownCloud test" to "testFile.txt" in the server + And user "Alice" has set up a client with default settings + When user "Alice" moves folder "test-folder1" to "folder1/folder2/folder3/folder4/folder5" in the sync folder + And user "Alice" moves folder "test-folder2" to "folder1/folder2/folder3/folder4/folder5" in the sync folder + And user "Alice" moves file "testFile.txt" to "folder1/folder2/folder3/folder4/folder5" in the sync folder + And the user waits for the files to sync + Then as "Alice" the file "folder1/folder2/folder3/folder4/folder5/testFile.txt" should have the content "ownCloud test" in the server + And as "Alice" folder "folder1/folder2/folder3/folder4/folder5/test-folder1" should exist in the server + And as "Alice" folder "folder1/folder2/folder3/folder4/folder5/test-folder2" should exist in the server + And as "Alice" file "testFile.txt" should not exist in the server + And as "Alice" folder "test-folder1" should not exist in the server + And as "Alice" folder "test-folder2" should not exist in the server + + + Scenario: Rename a file and a folder + Given user "Alice" has uploaded file with content "test file 1" to "textfile.txt" in the server + And user "Alice" has set up a client with default settings + When the user renames a file "textfile.txt" to "lorem.txt" + And the user renames a folder "folder1" to "FOLDER" + And the user waits for the files to sync + Then as "Alice" the file "lorem.txt" should have the content "test file 1" in the server + And as "Alice" folder "FOLDER" should exist in the server + But as "Alice" file "textfile.txt" should not exist in the server + And as "Alice" folder "folder1" should not exist in the server diff --git a/test/gui/tst_moveFilesFolders/test.py b/test/gui/tst_moveFilesFolders/test.py new file mode 100644 index 00000000000..83b0a5275a4 --- /dev/null +++ b/test/gui/tst_moveFilesFolders/test.py @@ -0,0 +1,8 @@ +source(findFile('scripts', 'python/bdd.py')) + +setupHooks('../shared/scripts/bdd_hooks.py') +collectStepDefinitions('./steps', '../shared/steps') + + +def main(): + runFeatureFile('test.feature') diff --git a/test/gui/tst_removeAccountConnection/test.feature b/test/gui/tst_removeAccountConnection/test.feature index 2b78d04d9f3..5acd1bc2f7a 100644 --- a/test/gui/tst_removeAccountConnection/test.feature +++ b/test/gui/tst_removeAccountConnection/test.feature @@ -1,24 +1,22 @@ Feature: remove account connection - As a user I want to remove my account So that I won't be using any client-UI services - Scenario: remove an account connection - Given user "Alice" has been created on the server with default attributes and without skeleton files - And user "Brian" has been created on the server with default attributes and without skeleton files - And user "Alice" has set up a client with default settings - And the user has added another account with - | server | %local_server% | - | user | Brian | - | password | AaBb2Cc3Dd4 | - When the user removes the connection for user "Brian" and host %local_server_hostname% - Then an account should be displayed with the displayname Alice Hansen and host %local_server_hostname% - + Scenario: remove an account connection + Given user "Alice" has been created in the server with default attributes + And user "Brian" has been created in the server with default attributes + And the user has set up the following accounts with default settings: + | Alice | + | Brian | + When the user removes the connection for user "Brian" and host %local_server_hostname% + Then the account with displayname "Brian Murphy" and host "%local_server_hostname%" should not be displayed + But the account with displayname "Alice Hansen" and host "%local_server_hostname%" should be displayed - Scenario: remove the only account connection - Given user "Alice" has been created on the server with default attributes and without skeleton files - And user "Alice" has set up a client with default settings - When the user removes the connection for user "Alice" and host %local_server_hostname% - Then connection wizard should be visible + @predefined_users + Scenario: remove the only account connection + Given user "Alice" has been created in the server with default attributes + And user "Alice" has set up a client with default settings + When the user removes the connection for user "Alice" and host %local_server_hostname% + Then connection wizard should be visible diff --git a/test/gui/tst_removeAccountConnection/test.py b/test/gui/tst_removeAccountConnection/test.py index d6224b99e57..83b0a5275a4 100644 --- a/test/gui/tst_removeAccountConnection/test.py +++ b/test/gui/tst_removeAccountConnection/test.py @@ -5,5 +5,4 @@ def main(): - testSettings.throwOnFailure = True runFeatureFile('test.feature') diff --git a/test/gui/tst_sharing/test.feature b/test/gui/tst_sharing/test.feature deleted file mode 100644 index a05d8232b88..00000000000 --- a/test/gui/tst_sharing/test.feature +++ /dev/null @@ -1,284 +0,0 @@ -Feature: Sharing - - As a user - I want to share files and folders with other users - So that those users can access the files and folders - - - Background: - Given user "Alice" has been created on the server with default attributes and without skeleton files - - @smokeTest - Scenario: simple sharing - Given user "Brian" has been created on the server with default attributes and without skeleton files - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - And user "Alice" has set up a client with default settings - When the user adds "Brian Murphy" as collaborator of resource "textfile0.txt" with permissions "edit,share" using the client-UI - Then user "Brian Murphy" should be listed in the collaborators list for file "textfile0.txt" with permissions "edit,share" on the client-UI - - - Scenario: Share files/folders with special characters in their name - Given user "Brian" has been created on the server with default attributes and without skeleton files - And user "Alice" has created folder "SampleFolder,With,$pecial?Characters" on the server - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/$ample1?.txt" on the server - And user "Alice" has set up a client with default settings - When the user adds "Brian Murphy" as collaborator of resource "SampleFolder,With,$pecial?Characters" with permissions "edit,share" using the client-UI - And the user adds "Brian Murphy" as collaborator of resource "$ample1?.txt" with permissions "edit,share" using the client-UI - Then user "Brian Murphy" should be listed in the collaborators list for file "SampleFolder,With,$pecial?Characters" with permissions "edit,share" on the client-UI - And user "Brian Murphy" should be listed in the collaborators list for file "$ample1?.txt" with permissions "edit,share" on the client-UI - - @issue-7459 - Scenario: Progress indicator should not be visible after unselecting the password protection checkbox while sharing through public link - Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - And user "Alice" has set up a client with default settings - When the user opens the public links dialog of "textfile0.txt" using the client-UI - And the user toggles the password protection using the client-UI - And the user toggles the password protection using the client-UI - Then the password progress indicator should not be visible in the client-UI - expected to fail - - - Scenario: Collaborator should not see to whom a file is shared. - Given user "Brian" has been created on the server with default attributes and without skeleton files - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - And user "Alice" has shared file "/textfile0.txt" on the server with user "Brian" with "read, share" permission - And user "Brian" has set up a client with default settings - When the user opens the sharing dialog of "textfile0.txt" using the client-UI - Then the error text "The item is not shared with any users or groups" should be displayed in the sharing dialog - - - Scenario: Group sharing - Given group "grp1" has been created on the server - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - And user "Alice" has set up a client with default settings - When the user adds group "grp1" as collaborator of resource "textfile0.txt" with permissions "edit,share" using the client-UI - Then group "grp1" should be listed in the collaborators list for file "textfile0.txt" with permissions "edit,share" on the client-UI - - - Scenario: User (non-author) can not share to a group to which the file is already shared - Given user "Brian" has been created on the server with default attributes and without skeleton files - And group "grp1" has been created on the server - And user "Brian" on the server has been added to group "grp1" - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - And user "Alice" has shared file "/textfile0.txt" on the server with user "Brian" with "read, share, update" permission - And user "Alice" has shared file "/textfile0.txt" on the server with group "grp1" with "read, share, update" permission - And user "Brian" has set up a client with default settings - When the user tires to share resource "textfile0.txt" with the group "grp1" using the client-UI - Then the error "Path already shared with this group" should be displayed - - - Scenario: sharee edits content of a file inside of a shared folder shared by sharer - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "simple-folder/textfile.txt" on the server - And user "Brian" has been created on the server with default attributes and without skeleton files - And user "Alice" has shared folder "simple-folder" on the server with user "Brian" with "all" permissions - And user "Brian" has set up a client with default settings - When the user overwrites the file "simple-folder/textfile.txt" with content "overwrite ownCloud test text file" - Then as "Brian" the file "simple-folder/textfile.txt" on the server should have the content "overwrite ownCloud test text file" - And as "Alice" the file "simple-folder/textfile.txt" on the server should have the content "overwrite ownCloud test text file" - - - Scenario: sharee edits content of a file shared by sharer - Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile.txt" on the server - And user "Brian" has been created on the server with default attributes and without skeleton files - And user "Alice" has shared file "textfile.txt" on the server with user "Brian" with "all" permissions - And user "Brian" has set up a client with default settings - When the user overwrites the file "textfile.txt" with content "overwrite ownCloud test text file" - Then as "Brian" the file "textfile.txt" on the server should have the content "overwrite ownCloud test text file" - And as "Alice" the file "textfile.txt" on the server should have the content "overwrite ownCloud test text file" - - - Scenario: unshare a shared file - Given user "Brian" has been created on the server with default attributes and without skeleton files - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" on the server - And user "Alice" has shared file "textfile0.txt" on the server with user "Brian" with "all" permissions - And user "Alice" has set up a client with default settings - When the user unshares the resource "textfile0.txt" for collaborator "Brian Murphy" using the client-UI - Then the text "The item is not shared with any users or groups" should be displayed in the sharing dialog - And as "Brian" file "textfile0.txt" on the server should not exist - - - Scenario: unshare a shared folder - Given user "Brian" has been created on the server with default attributes and without skeleton files - And user "Alice" has created folder "simple-folder" on the server - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "textfile0.txt" on the server - And user "Alice" has shared folder "simple-folder" on the server with user "Brian" with "all" permissions - And user "Alice" has set up a client with default settings - When the user unshares the resource "simple-folder" for collaborator "Brian Murphy" using the client-UI - Then the text "The item is not shared with any users or groups" should be displayed in the sharing dialog - And as "Brian" folder "simple-folder" on the server should not exist - - @issue-7423 - Scenario: unshare a reshared file - Given the setting "shareapi_auto_accept_share" on the server of app "core" has been set to "no" - And the administrator on the server has set the default folder for received shares to "Shares" - And user "Alice" has created folder "simple-folder" on the server - And user "Brian" has been created on the server with default attributes and without skeleton files - And user "Carol" has been created on the server with default attributes and without skeleton files - And user "Alice" has shared folder "simple-folder" on the server with user "Brian" - And user "Brian" has accepted the share "simple-folder" on the server offered by user "Alice" - And user "Brian" has shared folder "Shares/simple-folder" on the server with user "Carol" - And user "Brian" has set up a client with default settings - And user "Alice" has updated the share permissions on the server for folder "/simple-folder" to "read" for user "Brian" - When the user opens the sharing dialog of "Shares/simple-folder" using the client-UI - Then the error text "The file can not be shared because it was shared without sharing permission." should be displayed in the sharing dialog - - @smokeTest - Scenario: simple sharing of a file by public link without password - Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - And user "Alice" has set up a client with default settings - When the user creates a new public link for file "textfile0.txt" without password using the client-UI - Then as user "Alice" the file "textfile0.txt" should have a public link on the server - And the public should be able to download the file "textfile0.txt" without password from the last created public link by "Alice" on the server - - - Scenario: sharing of a file by public link and deleting the link - Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - And user "Alice" has set up a client with default settings - And user "Alice" has created a public link on the server with following settings - | path | textfile0.txt | - | name | Public-link | - When the user deletes the public link for file "textfile0.txt" - Then as user "Alice" the file "/textfile0.txt" should not have any public link on the server - - - Scenario Outline: simple sharing of a file by public link with password - Given user "Alice" has set up a client with default settings - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - When the user creates a new public link for file "textfile0.txt" with password "" using the client-UI - Then as user "Alice" the file "textfile0.txt" should have a public link on the server - And the public should be able to download the file "textfile0.txt" with password "" from the last created public link by "Alice" on the server - Examples: - | password | - |password1234| - | p@$s!23 | - - - Scenario: sharing of a file by public link with password and changing the password - Given user "Alice" has set up a client with default settings - And user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - And user "Alice" has created a public link on the server with following settings - | path | textfile0.txt | - | name | Public-link | - | password | 1234 | - When the user opens the public links dialog of "textfile0.txt" using the client-UI - And the user changes the password of public link "Public-link" to "password1234" using the client-UI - Then as user "Alice" the file "textfile0.txt" should have a public link on the server - And the public should be able to download the file "textfile0.txt" with password "password1234" from the last created public link by "Alice" on the server - - - @issue-8733 - Scenario: user changes the expiration date of an already existing public link using webUI - Given user "Alice" has uploaded file with content "ownCloud test text file 0" to "/textfile0.txt" on the server - And user "Alice" has set up a client with default settings - And user "Alice" has created a public link on the server with following settings - | path | textfile0.txt | - | name | Public link | - | expireDate | 2031-10-14 | - When the user opens the public links dialog of "textfile0.txt" using the client-UI - And the user edits the public link named "Public link" of file "textfile0.txt" changing following - | expireDate | 2038-07-21 | - Then the fields of the last public link share response of user "Alice" should include on the server - | expireDate | 2038-07-21 | - - @smokeTest - Scenario: simple sharing of a folder by public link without password - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has set up a client with default settings - When the user creates a new public link with permissions "Download / View" for folder "simple-folder" without password using the client-UI - Then as user "Alice" the folder "simple-folder" should have a public link on the server - And the public should be able to download the folder "lorem.txt" without password from the last created public link by "Alice" on the server - - - Scenario: simple sharing of a folder by public link with password - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has set up a client with default settings - When the user creates a new public link with permissions "Download / View" for folder "simple-folder" with password "pass123" using the client-UI - Then as user "Alice" the folder "simple-folder" should have a public link on the server - And the public should be able to download the folder "lorem.txt" with password "pass123" from the last created public link by "Alice" on the server - - @issue-8733 - Scenario: user changes the expiration date of an already existing public link for folder using client-UI - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has set up a client with default settings - And user "Alice" has created a public link on the server with following settings - | path | simple-folder | - | name | Public link | - | expireDate | 2031-10-14 | - | permissions | read, update, create, delete | - When the user opens the public links dialog of "simple-folder" using the client-UI - And the user edits the public link named "Public link" of file "simple-folder" changing following - | expireDate | 2038-07-21 | - Then the fields of the last public link share response of user "Alice" on the server should include - | expireDate | 2038-07-21 | - - - Scenario Outline: simple sharing of folder by public link with different roles - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has set up a client with default settings - When the user creates a new public link for folder "simple-folder" using the client-UI with these details: - | role | | - Then user "Alice" on the server should have a share with these details: - | field | value | - | share_type | public_link | - | uid_owner | Alice | - | permissions | | - | path | /simple-folder | - | name | Public link | - Examples: - | role | permissions | - | Viewer | read | - | Editor | read, update, create, delete | - | Contributor | create | - - - Scenario: sharing by public link with "Uploader" role - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has created file "simple-folder/lorem.txt" on the server - And user "Alice" has set up a client with default settings - When the user creates a new public link for folder "simple-folder" using the client-UI with these details: - | role | Contributor | - Then user "Alice" on the server should have a share with these details: - | field | value | - | share_type | public_link | - | uid_owner | Alice | - | permissions | create | - | path | /simple-folder | - | name | Public link | - And the public should not be able to download the file "lorem.txt" from the last created public link by "Alice" on the server - - - Scenario Outline: change collaborator permissions of a file & folder - Given the setting "shareapi_auto_accept_share" on the server of app "core" has been set to "yes" - And the administrator on the server has set the default folder for received shares to "Shares" - And user "Alice" has created folder "simple-folder" on the server - And user "Alice" has created file "lorem.txt" on the server - And user "Brian" has been created on the server with default attributes and without skeleton files - And user "Alice" has shared folder "simple-folder" on the server with user "Brian" with "all" permissions - And user "Alice" has shared file "lorem.txt" on the server with user "Brian" with "all" permissions - And user "Alice" has set up a client with default settings - When the user removes permissions "" for user "Brian Murphy" of resource "simple-folder" using the client-UI - Then "" permissions should not be displayed for user "Brian Murphy" for resource "simple-folder" on the client-UI - When the user closes the sharing dialog - And the user removes permissions "" for user "Brian Murphy" of resource "lorem.txt" using the client-UI - Then "" permissions should not be displayed for user "Brian Murphy" for resource "lorem.txt" on the client-UI - And user "Alice" on the server should have a share with these details: - | field | value | - | uid_owner | Alice | - | share_with | Brian | - | share_type | user | - | file_target | /Shares/simple-folder | - | item_type | folder | - | permissions | | - And user "Alice" on the server should have a share with these details: - | field | value | - | uid_owner | Alice | - | share_with | Brian | - | share_type | user | - | file_target | /Shares/lorem.txt | - | item_type | file | - | permissions | | - Examples: - | permissions | expected-folder-permission | expected-file-permission | - | edit | read, share | read, share | - | share | read, update, create, delete | read,update | - | edit,share | read | read | diff --git a/test/gui/tst_sharing/test.py b/test/gui/tst_sharing/test.py deleted file mode 100644 index d6224b99e57..00000000000 --- a/test/gui/tst_sharing/test.py +++ /dev/null @@ -1,9 +0,0 @@ -source(findFile('scripts', 'python/bdd.py')) - -setupHooks('../shared/scripts/bdd_hooks.py') -collectStepDefinitions('./steps', '../shared/steps') - - -def main(): - testSettings.throwOnFailure = True - runFeatureFile('test.feature') diff --git a/test/gui/tst_spaces/test.feature b/test/gui/tst_spaces/test.feature new file mode 100644 index 00000000000..0c96663a716 --- /dev/null +++ b/test/gui/tst_spaces/test.feature @@ -0,0 +1,150 @@ +Feature: Project spaces + As a user + I want to sync project space + So that I can do view and manage the space + + Background: + Given user "Alice" has been created in the server with default attributes + And the administrator has created a space "Project101" + + + Scenario: User with Viewer role can open the file + Given the administrator has created a folder "planning" in space "Project101" + And the administrator has uploaded a file "testfile.txt" with content "some content" inside space "Project101" + And the administrator has added user "Alice" to space "Project101" with role "viewer" + And user "Alice" has set up a client with default settings + When using sync connection folder "Project101" + And the user waits for file "testfile.txt" to be synced + Then user "Alice" should be able to open the file "testfile.txt" on the file system + And as "Alice" the file "testfile.txt" should have content "some content" on the file system + + @skipOnWindows + Scenario: User with Viewer role cannot edit the file (Linux only) + Given the administrator has created a folder "planning" in space "Project101" + And the administrator has uploaded a file "testfile.txt" with content "some content" inside space "Project101" + And the administrator has added user "Alice" to space "Project101" with role "viewer" + And user "Alice" has set up a client with default settings + When using sync connection folder "Project101" + And the user waits for file "testfile.txt" to be synced + Then user "Alice" should not be able to edit the file "testfile.txt" on the file system + And as "Alice" the file "testfile.txt" in the space "Project101" should have content "some content" in the server + + @skipOnLinux + Scenario: User with Viewer role cannot edit the file (Windows only) + Given the administrator has created a folder "planning" in space "Project101" + And the administrator has uploaded a file "testfile.txt" with content "some content" inside space "Project101" + And the administrator has added user "Alice" to space "Project101" with role "viewer" + And user "Alice" has set up a client with default settings + When using sync connection folder "Project101" + And the user waits for file "testfile.txt" to be synced + And the user overwrites the file "testfile.txt" with content "overwrite some content" + And the user clicks on the errors tab + Then the file "testfile.txt" should be blacklisted + And as "Alice" the file "testfile.txt" in the space "Project101" should have content "some content" in the server + + + Scenario: User with Editor role can edit the file + Given the administrator has created a folder "planning" in space "Project101" + And the administrator has uploaded a file "testfile.txt" with content "some content" inside space "Project101" + And the administrator has added user "Alice" to space "Project101" with role "editor" + And user "Alice" has set up a client with default settings + When using sync connection folder "Project101" + And the user waits for file "testfile.txt" to be synced + And the user overwrites the file "testfile.txt" with content "some content edited" + And the user waits for file "testfile.txt" to be synced + Then as "Alice" the file "testfile.txt" in the space "Project101" should have content "some content edited" in the server + + + Scenario: User with Manager role can add files and folders + Given the administrator has added user "Alice" to space "Project101" with role "manager" + And user "Alice" has set up a client with default settings + When using sync connection folder "Project101" + And user "Alice" creates a file "localFile.txt" with the following content inside the sync folder + """ + test content + """ + And user "Alice" creates a folder "localFolder" inside the sync folder + And the user waits for the files to sync + Then as "Alice" the file "localFile.txt" in the space "Project101" should have content "test content" in the server + And as "Alice" the space "Project101" should have folder "localFolder" in the server + + @predefined_users @skip + Scenario: User with viewer role cannot sync space to the desktop client + Given the administrator has added user "Alice" to space "Project101" with role "viewer" + And the user has started the client + When the user adds the following account: + | server | %local_server% | + | user | Alice | + | password | 1234 | + Then the space "Project101" should not exist in the sync folder list + + @predefined_users @skip + Scenario: User with downloader role cannot add or delete file + Given the administrator has created a folder "planning" in space "Project101" + And the administrator has uploaded a file "testfile.txt" with content "some content" inside space "Project101" + And the administrator has added user "Alice" to space "Project101" with role "downloader" + And user "Alice" has set up a client with default settings + When using sync connection folder "Project101" + Then user "Alice" should be able to open the file "testfile.txt" on the file system + And as "Alice" the file "testfile.txt" should have content "some content" on the file system + When user "Alice" creates a file "lorem.txt" with the following content inside the sync folder + """ + test content + """ + And user "Alice" creates a folder "localFolder" inside the sync folder + And the user deletes the file "testfile.txt" + And the user deletes the folder "planning" + Then as "Alice" the space "Project101" should not have file "lorem.txt" in the server + And as "Alice" the space "Project101" should not have folder "localFolder" in the server + And as "Alice" the space "Project101" should have file "testfile.txt" in the server + And as "Alice" the space "Project101" should have file "planning" in the server + + @predefined_users @skip + Scenario: User with uploader role can edit/upload files + Given the administrator has uploaded a file "testfile.txt" with content "some content" inside space "Project101" + And the administrator has added user "Alice" to space "Project101" with role "uploader" + And user "Alice" has set up a client with default settings + When using sync connection folder "Project101" + Then the file "testfile.txt" should not exist on the file system + And the folder "planning" should not exist on the file system + When the user overwrites the file "testfile.txt" with content "some content edited" + And the user waits for file "testfile.txt" to be synced + Then as "Alice" the file "testfile.txt" in the space "Project101" should have content "some content edited" in the server + When user "Alice" creates a file "lorem.txt" with the following content inside the sync folder + """ + test content + """ + And the user waits for file "lorem.txt" to be synced + Then as "Alice" the file "lorem.txt" in the space "Project101" should have content "test content" in the server + + @predefined_users @skip + Scenario Outline: User with collaborator and manager role can add, edit and delete files and folders in space + Given the administrator has uploaded a file "testfile.txt" with content "some content" inside space "Project101" + And the administrator has created a folder "planning" in space "Project101" + And the administrator has added user "Alice" to space "Project101" with role "" + And user "Alice" has set up a client with default settings + When using sync connection folder "Project101" + Then user "Alice" should be able to open the file "testfile.txt" on the file system + And as "Alice" the file "testfile.txt" should have content "some content" on the file system + When the user overwrites the file "testfile.txt" with content "some content edited" + And the user waits for file "testfile.txt" to be synced + Then as "Alice" the file "testfile.txt" in the space "Project101" should have content "some content edited" in the server + When user "Alice" creates a file "lorem.txt" with the following content inside the sync folder + """ + test content + """ + And user "Alice" creates a folder "localFolder" inside the sync folder + And the user waits for the files to sync + Then as "Alice" the file "lorem.txt" in the space "Project101" should have content "test content" in the server + And as "Alice" the space "Project101" should have folder "localFolder" in the server + When the user deletes the file "testfile.txt" + And the user deletes the folder "planning" + And the user waits for the files to sync + Then as "Alice" the space "Project101" should not have file "testfile.txt" in the server + And as "Alice" the space "Project101" should not have folder "planning" in the server + Examples: + | role | + | collaborator | + | manager | + + diff --git a/test/gui/tst_spaces/test.py b/test/gui/tst_spaces/test.py new file mode 100644 index 00000000000..83b0a5275a4 --- /dev/null +++ b/test/gui/tst_spaces/test.py @@ -0,0 +1,8 @@ +source(findFile('scripts', 'python/bdd.py')) + +setupHooks('../shared/scripts/bdd_hooks.py') +collectStepDefinitions('./steps', '../shared/steps') + + +def main(): + runFeatureFile('test.feature') diff --git a/test/gui/tst_syncing/test.feature b/test/gui/tst_syncing/test.feature index b3d71d52384..effbffb1742 100644 --- a/test/gui/tst_syncing/test.feature +++ b/test/gui/tst_syncing/test.feature @@ -1,13 +1,12 @@ Feature: Syncing files - As a user I want to be able to sync my local folders to to my owncloud server so that I dont have to upload and download files manually Background: - Given user "Alice" has been created on the server with default attributes and without skeleton files + Given user "Alice" has been created in the server with default attributes - @smokeTest + @smokeTest @issue-9281 @predefined_users Scenario: Syncing a file to the server Given user "Alice" has set up a client with default settings When user "Alice" creates a file "lorem-for-upload.txt" with the following content inside the sync folder @@ -15,15 +14,16 @@ Feature: Syncing files test content """ And the user waits for file "lorem-for-upload.txt" to be synced - Then as "Alice" the file "lorem-for-upload.txt" on the server should have the content "test content" - + And the user clicks on the activity tab + Then the file "lorem-for-upload.txt" should have status "Uploaded" in the activity tab + And as "Alice" the file "lorem-for-upload.txt" should have the content "test content" in the server + @predefined_users Scenario: Syncing all files and folders from the server - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has created folder "large-folder" on the server - And user "Alice" has uploaded file on the server with content "test content" to "uploaded-lorem.txt" + Given user "Alice" has created folder "simple-folder" in the server + And user "Alice" has created folder "large-folder" in the server + And user "Alice" has uploaded file with content "test content" to "uploaded-lorem.txt" in the server And user "Alice" has set up a client with default settings - When the user waits for the files to sync Then the file "uploaded-lorem.txt" should exist on the file system And the file "uploaded-lorem.txt" should exist on the file system with the following content """ @@ -32,21 +32,18 @@ Feature: Syncing files And the folder "simple-folder" should exist on the file system And the folder "large-folder" should exist on the file system - + @issue-9733 @predefined_users Scenario: Syncing a file from the server and creating a conflict - Given user "Alice" has uploaded file on the server with content "server content" to "/conflict.txt" + Given user "Alice" has uploaded file with content "server content" to "/conflict.txt" in the server And user "Alice" has set up a client with default settings - And the user has waited for file "conflict.txt" to be synced And the user has paused the file sync And the user has changed the content of local file "conflict.txt" to: """ client content """ - And user "Alice" has uploaded file on the server with content "changed server content" to "/conflict.txt" + And user "Alice" has uploaded file with content "changed server content" to "/conflict.txt" in the server When the user resumes the file sync on the client - And the user clicks on the activity tab - And the user selects "Not Synced" tab in the activity - # Then a conflict warning should be shown for 1 files + And the user clicks on the errors tab Then the table of conflict warnings should include file "conflict.txt" And the file "conflict.txt" should exist on the file system with the following content """ @@ -57,65 +54,81 @@ Feature: Syncing files client content """ - + @predefined_users Scenario: Sync all is selected by default - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has created folder "large-folder" on the server + Given user "Alice" has created folder "simple-folder" in the server + And user "Alice" has created folder "large-folder" in the server + And user "Alice" has uploaded file with content "test content" to "testFile.txt" in the server + And user "Alice" has uploaded file with content "lorem content" to "lorem.txt" in the server And the user has started the client - And the user has added the following account information: + And the user has entered the following account information: | server | %local_server% | | user | Alice | | password | 1234 | - When the user opens chose_what_to_sync dialog - Then the dialog chose_what_to_sync should be visible - And the sync all checkbox should be checked - + When the user selects manual sync folder option in advanced section + And the user selects "Personal" space in sync connection wizard + And the user sets the sync path in sync connection wizard + And the user navigates back in the sync connection wizard + And the user sets the temp folder "localSyncFolder" as local sync path in sync connection wizard + And the user disables VFS support for Windows + Then the sync all checkbox should be checked + When user unselects all the remote folders + And the user adds the folder sync connection + And the user waits for the files to sync + Then the file "testFile.txt" should exist on the file system + And the file "lorem.txt" should exist on the file system + But the folder "simple-folder" should not exist on the file system + And the folder "large-folder" should not exist on the file system + @predefined_users Scenario: Sync only one folder from the server - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has created folder "large-folder" on the server + Given user "Alice" has created folder "simple-folder" in the server + And user "Alice" has created folder "large-folder" in the server And the user has started the client - And the user has added the following account information: + And the user has entered the following account information: | server | %local_server% | | user | Alice | | password | 1234 | - When the user selects the following folders to sync: + When the user selects manual sync folder option in advanced section + And the user selects "Personal" space in sync connection wizard + And the user sets the sync path in sync connection wizard + And the user disables VFS support for Windows + And the user selects the following folders to sync: | folder | | simple-folder | - And the user connects the account - And the user waits for the files to sync Then the folder "simple-folder" should exist on the file system But the folder "large-folder" should not exist on the file system + When user "Alice" uploads file with content "some content" to "simple-folder/lorem.txt" in the server + And the user waits for file "simple-folder/lorem.txt" to be synced + Then the file "simple-folder/lorem.txt" should exist on the file system + When user "Alice" creates a file "simple-folder/localFile.txt" with the following content inside the sync folder + """ + test content + """ + And the user waits for file "simple-folder/localFile.txt" to be synced + Then as "Alice" file "simple-folder/localFile.txt" should exist in the server + When user "Alice" uploads file with content "ownCloud" to "large-folder/lorem.txt" in the server + And the user deletes the folder "simple-folder" + And the user waits for the files to sync + Then the file "large-folder/lorem.txt" should not exist on the file system + And as "Alice" folder "simple-folder" should not exist in the server - - Scenario: Connect account with manual sync folder option - Given user "Alice" has created folder "simple-folder" on the server - And user "Alice" has created folder "large-folder" on the server - And user "Alice" has uploaded file on the server with content "test content" to "lorem.txt" - And the user has started the client - And the user has added the following account information: - | server | %local_server% | - | user | Alice | - | password | 1234 | - When the user selects manual sync folder option - And the user connects the account - Then the folder "simple-folder" should not exist on the file system - But the folder "large-folder" should not exist on the file system - And the file "lorem.txt" should not exist on the file system - - + @issue-9733 Scenario: sort folders list by name and size - Given user "Alice" has created folder "123Folder" on the server - And user "Alice" has uploaded file on the server with content "small" to "123Folder/lorem.txt" - And user "Alice" has created folder "aFolder" on the server - And user "Alice" has uploaded file on the server with content "more contents" to "aFolder/lorem.txt" - And user "Alice" has created folder "bFolder" on the server + Given user "Alice" has created folder "123Folder" in the server + And user "Alice" has uploaded file with content "small" to "123Folder/lorem.txt" in the server + And user "Alice" has created folder "aFolder" in the server + And user "Alice" has uploaded file with content "more contents" to "aFolder/lorem.txt" in the server + And user "Alice" has created folder "bFolder" in the server And the user has started the client - And the user has added the following account information: + And the user has entered the following account information: | server | %local_server% | | user | Alice | | password | 1234 | - When the user opens chose_what_to_sync dialog + When the user selects manual sync folder option in advanced section + And the user selects "Personal" space in sync connection wizard + And the user sets the sync path in sync connection wizard + And the user disables VFS support for Windows # folders are sorted by name in ascending order by default Then the folders should be in the following order: | folder | @@ -143,21 +156,42 @@ Feature: Syncing files | aFolder | | 123Folder | | bFolder | + And the user cancels the sync connection wizard - + @predefined_users Scenario Outline: Syncing a folder to the server Given user "Alice" has set up a client with default settings - When user "Alice" creates a folder "" inside the sync folder - And the user waits for folder "" to be synced - Then as "Alice" folder "" should exist on the server + When user "Alice" creates a folder inside the sync folder + And the user waits for folder to be synced + Then as "Alice" folder should exist in the server Examples: - | foldername | - | myFolder | - | really long folder name with some spaces and special char such as $%ñ& | + | foldername | + | "myFolder" | + | "really long folder name with some spaces and special char such as $%ñ&" | + @skipOnWindows @predefined_users + Scenario Outline: Syncing a folder having space at the end (Linux only) + Given user "Alice" has set up a client with default settings + When user "Alice" creates a folder inside the sync folder + And the user waits for folder to be synced + Then as "Alice" folder should exist in the server + Examples: + | foldername | + | "folder with space at end " | + @skipOnLinux + Scenario: Try to sync files having space at the end (Windows only) + Given user "Alice" has uploaded file with content "lorem epsum" to "trailing-space.txt " in the server + And user "Alice" has set up a client with default settings + When user "Alice" creates a folder "folder with space at end " inside the sync folder + And the user force syncs the files + And the user clicks on the errors tab + Then the file "trailing-space.txt " should be ignored + And the file "folder with space at end " should be ignored + + @predefined_users Scenario: Many subfolders can be synced - Given user "Alice" has created folder "parent" on the server + Given user "Alice" has created folder "parent" in the server And user "Alice" has set up a client with default settings When user "Alice" creates a folder "parent/subfolderEmpty1" inside the sync folder And user "Alice" creates a folder "parent/subfolderEmpty2" inside the sync folder @@ -189,70 +223,279 @@ Feature: Syncing files """ test content """ - And the user waits for folder "parent" to be synced - Then as "Alice" folder "parent/subfolderEmpty1" should exist on the server - And as "Alice" folder "parent/subfolderEmpty2" should exist on the server - And as "Alice" folder "parent/subfolderEmpty3" should exist on the server - And as "Alice" folder "parent/subfolderEmpty4" should exist on the server - And as "Alice" folder "parent/subfolderEmpty5" should exist on the server - And as "Alice" folder "parent/subfolder1" should exist on the server - And as "Alice" folder "parent/subfolder2" should exist on the server - And as "Alice" folder "parent/subfolder3" should exist on the server - And as "Alice" folder "parent/subfolder4" should exist on the server - And as "Alice" folder "parent/subfolder5" should exist on the server - + And the user waits for the files to sync + Then as "Alice" folder "parent/subfolderEmpty1" should exist in the server + And as "Alice" folder "parent/subfolderEmpty2" should exist in the server + And as "Alice" folder "parent/subfolderEmpty3" should exist in the server + And as "Alice" folder "parent/subfolderEmpty4" should exist in the server + And as "Alice" folder "parent/subfolderEmpty5" should exist in the server + And as "Alice" folder "parent/subfolder1" should exist in the server + And as "Alice" folder "parent/subfolder2" should exist in the server + And as "Alice" folder "parent/subfolder3" should exist in the server + And as "Alice" folder "parent/subfolder4" should exist in the server + And as "Alice" folder "parent/subfolder5" should exist in the server + @predefined_users Scenario: Both original and copied folders can be synced Given user "Alice" has set up a client with default settings - And user "Alice" has created a folder "original" inside the sync folder - And user "Alice" has created a file "original/test.txt" with the following content inside the sync folder + When user "Alice" creates a folder "original" inside the sync folder + And the user copies the folder "original" to "copied" + And the user waits for folder "copied" to be synced + Then as "Alice" folder "original" should exist in the server + And as "Alice" folder "copied" should exist in the server + + @issue-9281 @predefined_users + Scenario: Verify that you can create a subfolder with long name + Given user "Alice" has created folder "Folder1" in the server + And user "Alice" has set up a client with default settings + When user "Alice" creates a folder "Folder1/really long folder name with some spaces and special char such as $%ñ&" inside the sync folder + And the user waits for folder "Folder1/really long folder name with some spaces and special char such as $%ñ&" to be synced + Then the folder "Folder1/really long folder name with some spaces and special char such as $%ñ&" should exist on the file system + And as "Alice" folder "Folder1/really long folder name with some spaces and special char such as $%ñ&" should exist in the server + + @predefined_users + Scenario: Verify pre existing folders in local (Desktop client) are copied over to the server + Given user "Alice" has set up a client with default settings + When the user quits the client + Given user "Alice" has created a folder "Folder1" inside the sync folder + And user "Alice" has created a folder "Folder1/subFolder1" inside the sync folder + And user "Alice" has created a folder "Folder1/subFolder1/subFolder2" inside the sync folder + When user "Alice" starts the client + And the user waits for the files to sync + Then as "Alice" folder "Folder1" should exist in the server + And as "Alice" folder "Folder1/subFolder1" should exist in the server + And as "Alice" folder "Folder1/subFolder1/subFolder2" should exist in the server + + @skipOnWindows @predefined_users + Scenario: Filenames that are rejected by the server are reported (Linux only) + Given user "Alice" has created folder "Folder1" in the server + And user "Alice" has set up a client with default settings + When user "Alice" creates a file "Folder1/a\\a.txt" with the following content inside the sync folder """ test content """ - When the user copies the folder "original" to "copied" - And the user waits for folder "copied" to be synced - Then as "Alice" folder "original" should exist on the server - And as "Alice" folder "copied" should exist on the server + And the user clicks on the activity tab + Then the file "Folder1/a\\a.txt" should exist on the file system + When the user clicks on the errors tab + Then the file "Folder1/a\\a.txt" should be blacklisted + + @predefined_users + Scenario Outline: Sync long nested folder + Given user "Alice" has created folder "" in the server + And user "Alice" has set up a client with default settings + When user "Alice" creates a folder "/" inside the sync folder + And user "Alice" creates a folder "//" inside the sync folder + And user "Alice" creates a folder "///" inside the sync folder + And user "Alice" creates a folder "////" inside the sync folder + And the user waits for folder "////" to be synced + Then as "Alice" folder "/" should exist in the server + And as "Alice" folder "//" should exist in the server + And as "Alice" folder "///" should exist in the server + And as "Alice" folder "////" should exist in the server + Examples: + | foldername | + | An empty folder which name is obviously more than 59 characters | + @skipOnWindows @predefined_users + Scenario: Invalid system names are synced (Linux only) + Given user "Alice" has created folder "CON" in the server + And user "Alice" has created folder "test%" in the server + And user "Alice" has uploaded file with content "server content" to "/PRN" in the server + And user "Alice" has uploaded file with content "server content" to "/foo%" in the server + And user "Alice" has set up a client with default settings + Then the folder "CON" should exist on the file system + And the folder "test%" should exist on the file system + And the file "PRN" should exist on the file system + And the file "foo%" should exist on the file system + And as "Alice" folder "CON" should exist in the server + And as "Alice" folder "test%" should exist in the server + And as "Alice" file "/PRN" should exist in the server + And as "Alice" file "/foo%" should exist in the server - Scenario: Verify that you can create a subfolder with long name + @skipOnLinux + Scenario: Sync invalid system names (Windows only) + Given user "Alice" has created folder "CON" in the server + And user "Alice" has created folder "test%" in the server + And user "Alice" has uploaded file with content "server content" to "/PRN" in the server + And user "Alice" has uploaded file with content "server content" to "/foo%" in the server + And user "Alice" has set up a client with default settings + Then the folder "test%" should exist on the file system + And the file "foo%" should exist on the file system + But the folder "CON" should not exist on the file system + And the file "PRN" should not exist on the file system + + @predefined_users + Scenario: various types of files can be synced from server to client + Given user "Alice" has created folder "simple-folder" in the server + And user "Alice" has uploaded file "testavatar.png" to "simple-folder/testavatar.png" in the server + And user "Alice" has uploaded file "testavatar.jpg" to "simple-folder/testavatar.jpg" in the server + And user "Alice" has uploaded file "testavatar.jpeg" to "simple-folder/testavatar.jpeg" in the server + And user "Alice" has uploaded file "testimage.mp3" to "simple-folder/testimage.mp3" in the server + And user "Alice" has uploaded file "test_video.mp4" to "simple-folder/test_video.mp4" in the server + And user "Alice" has uploaded file "simple.pdf" to "simple-folder/simple.pdf" in the server + And user "Alice" has set up a client with default settings + Then the folder "simple-folder" should exist on the file system + And the file "simple-folder/testavatar.png" should exist on the file system + And the file "simple-folder/testavatar.jpg" should exist on the file system + And the file "simple-folder/testavatar.jpeg" should exist on the file system + And the file "simple-folder/testimage.mp3" should exist on the file system + And the file "simple-folder/test_video.mp4" should exist on the file system + And the file "simple-folder/simple.pdf" should exist on the file system + + @predefined_users + Scenario: various types of files can be synced from client to server Given user "Alice" has set up a client with default settings - And user "Alice" has created a folder "Folder1" inside the sync folder - When user "Alice" creates a folder "Folder1/really long folder name with some spaces and special char such as $%ñ&" inside the sync folder - And user "Alice" creates a file "Folder1/really long folder name with some spaces and special char such as $%ñ&/test.txt" with the following content inside the sync folder + When user "Alice" creates the following files inside the sync folder: + | files | + | /testavatar.png | + | /testavatar.jpg | + | /testavatar.jpeg | + | /testaudio.mp3 | + | /test_video.mp4 | + | /simple.txt | + And the user waits for the files to sync + Then as "Alice" file "testavatar.png" should exist in the server + And as "Alice" file "testavatar.jpg" should exist in the server + And as "Alice" file "testavatar.jpeg" should exist in the server + And as "Alice" file "testaudio.mp3" should exist in the server + And as "Alice" file "test_video.mp4" should exist in the server + And as "Alice" file "simple.txt" should exist in the server + + @predefined_users + Scenario Outline: File with long name can be synced + Given user "Alice" has set up a client with default settings + When user "Alice" creates a file "" with the following content inside the sync folder """ test content """ - And the user waits for file "Folder1/really long folder name with some spaces and special char such as $%ñ&/test.txt" to be synced - Then as "Alice" folder "Folder1" should exist on the server - And as "Alice" folder "Folder1/really long folder name with some spaces and special char such as $%ñ&" should exist on the server - And the file "Folder1/really long folder name with some spaces and special char such as $%ñ&/test.txt" should exist on the file system with the following content + And the user waits for file "" to be synced + Then as "Alice" file "" should exist in the server + Examples: + | filename | + | thisIsAVeryLongFileNameToCheckThatItWorks-thisIsAVeryLongFileNameToCheckThatItWorks-thisIsAVeryLongFileNameToCheckThatItWorks-thisIsAVeryLongFileNameToCheckThatItWorks-thisIsAVeryLongFileNameToCheckThatItWorks-thisIs.txt | + + + Scenario: Syncing file of 1 GB size + Given user "Alice" has set up a client with default settings + When user "Alice" creates a file "newfile.txt" with size "1GB" inside the sync folder + And the user waits for file "newfile.txt" to be synced + Then as "Alice" file "newfile.txt" should exist in the server + + @predefined_users + Scenario: File with spaces in the name can sync + Given user "Alice" has set up a client with default settings + When user "Alice" creates a file "file with space.txt" with the following content inside the sync folder """ - test content + test contents """ - And as "Alice" the file "Folder1/really long folder name with some spaces and special char such as $%ñ&/test.txt" on the server should have the content "test content" + And the user waits for file "file with space.txt" to be synced + Then as "Alice" file "file with space.txt" should exist in the server - Scenario: Verify pre existing folders in local (Desktop client) are copied over to the server - Given user "Alice" has created a folder "Folder1" inside the sync folder - And user "Alice" has created a folder "Folder1/subFolder1" inside the sync folder - And user "Alice" has created a folder "Folder1/subFolder1/subFolder2" inside the sync folder + Scenario: Syncing folders each having 500 files + Given the user has created a folder "folder1" in temp folder + And the user has created "500" files each of size "1048576" bytes inside folder "folder1" in temp folder + And the user has created a folder "folder2" in temp folder + And the user has created "500" files each of size "1048576" bytes inside folder "folder2" in temp folder And user "Alice" has set up a client with default settings - When the user waits for folder "Folder1" to be synced - Then as "Alice" folder "Folder1" should exist on the server - And as "Alice" folder "Folder1/subFolder1" should exist on the server - And as "Alice" folder "Folder1/subFolder1/subFolder2" should exist on the server + When user "Alice" moves folder "folder1" from the temp folder into the sync folder + And user "Alice" moves folder "folder2" from the temp folder into the sync folder + And the user waits for folder "folder1" to be synced + And the user waits for folder "folder2" to be synced + Then as "Alice" folder "folder1" should exist in the server + And as user "Alice" folder "folder1" should contain "500" items in the server + And as "Alice" folder "folder2" should exist in the server + And as user "Alice" folder "folder2" should contain "500" items in the server + @predefined_users + Scenario: Skip sync folder configuration + Given the user has started the client + And the user has entered the following account information: + | server | %local_server% | + | user | Alice | + | password | 1234 | + When the user selects manual sync folder option in advanced section + And the user cancels the sync connection wizard + Then the account with displayname "Alice Hansen" and host "%local_server_hostname%" should be displayed + And the sync folder list should be empty - Scenario: Filenames that are rejected by the server are reported - Given user "Alice" has set up a client with default settings - And user "Alice" has created a folder "Folder1" inside the sync folder - When user "Alice" creates a file "Folder1/a\\a.txt" with the following content inside the sync folder + @predefined_users + Scenario: extract a zip file in the sync folder + Given the user has created a zip file "archive.zip" with the following resources in the temp folder + | resource | type | content | + | folder1 | folder | | + | folder2 | folder | | + | file1.txt | file | Test file1 | + | file2.txt | file | Test file2 | + And user "Alice" has set up a client with default settings + When user "Alice" moves file "archive.zip" from the temp folder into the sync folder + And the user waits for the files to sync + And user "Alice" unzips the zip file "archive.zip" inside the sync root + And the user waits for the files to sync + Then as "Alice" folder "folder1" should exist in the server + And as "Alice" folder "folder2" should exist in the server + And as "Alice" the file "file1.txt" should have the content "Test file1" in the server + And as "Alice" the file "file2.txt" should have the content "Test file2" in the server + + @predefined_users + Scenario: sync remote folder to a local sync folder having special characters + Given user "Alice" has created folder "~`!@#$^&()-_=+{[}];',)" in the server + And user "Alice" has created folder "simple-folder" in the server + And user "Alice" has created folder "test-folder" in the server + And user "Alice" has created folder "test-folder/sub-folder1" in the server + And user "Alice" has created folder "test-folder/sub-folder2" in the server + And user "Alice" has created folder "~test%" in the server + And the user has created a folder "~`!@#$^&()-_=+{[}];',)PRN%" in temp folder + And the user has started the client + And the user has entered the following account information: + | server | %local_server% | + | user | Alice | + | password | 1234 | + When the user selects manual sync folder option in advanced section + And the user selects "Personal" space in sync connection wizard + And the user sets the temp folder "~`!@#$^&()-_=+{[}];',)PRN%" as local sync path in sync connection wizard + And the user disables VFS support for Windows + And the user selects the following folders to sync: + | folder | + | ~`!@#$^&()-_=+{[}];',) | + | simple-folder | + | test-folder/sub-folder2 | + Then the folder "~`!@#$^&()-_=+{[}];',)" should exist on the file system + And the folder "simple-folder" should exist on the file system + But the folder "~test%" should not exist on the file system + When user "Alice" deletes the folder "simple-folder" in the server + And the user waits for the files to sync + Then the folder "simple-folder" should not exist on the file system + And the folder "test-folder/sub-folder2" should exist on the file system + And the folder "test-folder/sub-folder1" should not exist on the file system + + @issue-11814 + Scenario: remove folder sync connection + Given user "Alice" has created folder "simple-folder" in the server + And user "Alice" has set up a client with default settings + When the user selects remove folder sync connection option + And the user cancels the folder sync connection removal dialog + And the user removes the "Shares" folder sync connection + And the user removes the "Personal" folder sync connection + Then the sync folder list should be empty + And the folder "simple-folder" should exist on the file system + And as "Alice" folder "simple-folder" should exist in the server + + @issue-12333 + Scenario: sync resources having European characters + Given user "Alice" has created folder "źäćöśęüóńł" in the server + And user "Alice" has uploaded file with content "test content" to "źäćöśęüóńł.txt" in the server + And user "Alice" has set up a client with default settings + When user "Alice" creates a folder "äöüąćęłńóśź" inside the sync folder + And user "Alice" creates a file "ąćęłńóśźäöü.txt" with the following content inside the sync folder + """ + test content + """ + And the user waits for the files to sync + Then as "Alice" folder "äöüąćęłńóśź" should exist in the server + And as "Alice" file "ąćęłńóśźäöü.txt" should exist in the server + And the folder "źäćöśęüóńł" should exist on the file system + And the file "źäćöśęüóńł.txt" should exist on the file system with the following content """ test content """ - And the user waits for folder "Folder1" to be synced - Then as "Alice" folder "Folder1" should exist on the server - When the user clicks on the activity tab - And the user selects "Not Synced" tab in the activity - Then the file "Folder1/a\\a.txt" should be blacklisted diff --git a/test/gui/tst_syncing/test.py b/test/gui/tst_syncing/test.py index d6224b99e57..83b0a5275a4 100644 --- a/test/gui/tst_syncing/test.py +++ b/test/gui/tst_syncing/test.py @@ -5,5 +5,4 @@ def main(): - testSettings.throwOnFailure = True runFeatureFile('test.feature') diff --git a/test/gui/tst_syncing/verificationPoints/VP1 b/test/gui/tst_syncing/verificationPoints/VP1 deleted file mode 100644 index 9f6b4789224..00000000000 --- a/test/gui/tst_syncing/verificationPoints/VP1 +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/test/gui/tst_syncing/verificationPoints/VP2 b/test/gui/tst_syncing/verificationPoints/VP2 deleted file mode 100644 index 9f6b4789224..00000000000 --- a/test/gui/tst_syncing/verificationPoints/VP2 +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/test/gui/tst_syncing/verificationPoints/conflict-warning b/test/gui/tst_syncing/verificationPoints/conflict-warning deleted file mode 100644 index 9f6b4789224..00000000000 --- a/test/gui/tst_syncing/verificationPoints/conflict-warning +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/test/gui/tst_vfs/test.feature b/test/gui/tst_vfs/test.feature index 8bcb23bd0a0..9f57f9a192e 100644 --- a/test/gui/tst_vfs/test.feature +++ b/test/gui/tst_vfs/test.feature @@ -1,20 +1,86 @@ +@skipOnLinux Feature: Enable/disable virtual file support - As a user I want to enable virtual file support So that I can synchronize virtual files with local folder - Scenario: Enable VFS - Given user "Alice" has been created on the server with default attributes and without skeleton files + Scenario: Disable/Enable VFS + Given user "Alice" has been created in the server with default attributes + And user "Alice" has uploaded file with content "ownCloud" to "testFile.txt" in the server + And user "Alice" has created folder "folder1" in the server + And user "Alice" has uploaded file with content "some contents" to "folder1/lorem.txt" in the server + And user "Alice" has created folder "folder2" in the server + And user "Alice" has uploaded file with content "content" to "folder2/lorem.txt" in the server And user "Alice" has set up a client with default settings + Then the placeholder of file "testFile.txt" should exist on the file system + And the placeholder of file "folder1/lorem.txt" should exist on the file system + And the placeholder of file "folder2/lorem.txt" should exist on the file system + And the "Choose what to sync" button should not be available + When the user disables virtual file support + Then the "Activate virtual files" button should be available + And the file "testFile.txt" should be downloaded + And the file "folder1/lorem.txt" should be downloaded + And the file "folder2/lorem.txt" should be downloaded + When user unselects a folder "folder1" in selective sync + And the user waits for the files to sync + Then the folder "folder1" should not exist on the file system + And the file "folder2/lorem.txt" should exist on the file system When the user enables virtual file support - Then the "Disable virtual file support..." button should be available + Then the "Deactivate virtual files" button should be available + And the placeholder of file "folder1/lorem.txt" should exist on the file system + And the file "testFile.txt" should be downloaded + And the file "folder2/lorem.txt" should be downloaded - Scenario: Disable VFS - Given user "Alice" has been created on the server with default attributes and without skeleton files + Scenario: Copy and paste virtual file + Given user "Alice" has been created in the server with default attributes + And user "Alice" has uploaded file with content "sample file" to "sampleFile.txt" in the server + And user "Alice" has uploaded file with content "lorem file" to "lorem.txt" in the server + And user "Alice" has uploaded file with content "test file" to "testFile.txt" in the server + And user "Alice" has created folder "Folder" in the server And user "Alice" has set up a client with default settings - And the user has enabled virtual file support - When the user disables virtual file support - Then the "Enable virtual file support (experimental)..." button should be available \ No newline at end of file + Then the placeholder of file "lorem.txt" should exist on the file system + And the placeholder of file "sampleFile.txt" should exist on the file system + And the placeholder of file "testFile.txt" should exist on the file system + When user "Alice" copies file "sampleFile.txt" to temp folder + And the user copies the file "lorem.txt" to "Folder" + And the user copies the file "testFile.txt" to "testFile.txt" + And the user waits for file "Folder/lorem.txt" to be synced + Then the file "sampleFile.txt" should be downloaded + And the file "Folder/lorem.txt" should be downloaded + And the file "lorem.txt" should be downloaded + And the file "testFile.txt" should be downloaded + And the file "testFile - Copy.txt" should be downloaded + And as "Alice" file "Folder/lorem.txt" should exist in the server + And as "Alice" file "lorem.txt" should exist in the server + And as "Alice" file "sampleFile.txt" should exist in the server + And as "Alice" file "testFile.txt" should exist in the server + And as "Alice" file "testFile - Copy.txt" should exist in the server + + + Scenario: Move virtual file + Given user "Alice" has been created in the server with default attributes + And user "Alice" has uploaded file with content "lorem file" to "lorem.txt" in the server + And user "Alice" has uploaded file with content "some contents" to "sampleFile.txt" in the server + And user "Alice" has created folder "Folder" in the server + And user "Alice" has set up a client with default settings + When user "Alice" moves file "lorem.txt" to "Folder" in the sync folder + And user "Alice" moves file "sampleFile.txt" to the temp folder + And the user waits for file "Folder/lorem.txt" to be synced + Then the placeholder of file "Folder/lorem.txt" should exist on the file system + And as "Alice" file "Folder/lorem.txt" should exist in the server + And as "Alice" file "lorem.txt" should not exist in the server + And as "Alice" file "sampleFile.txt" should not exist in the server + + + Scenario: Disable/Enable VFS quickly + Given user "Alice" has been created in the server with default attributes + And user "Alice" has set up a client with default settings + When user "Alice" creates a file "newfile.txt" with size "100MB" inside the sync folder + And the user waits for file "newfile.txt" to be synced + And the user disables virtual file support + And the user enables virtual file support + And the user disables virtual file support + And the user enables virtual file support + Then the "Deactivate virtual files" button should be available diff --git a/test/gui/tst_vfs/test.py b/test/gui/tst_vfs/test.py index d6224b99e57..83b0a5275a4 100644 --- a/test/gui/tst_vfs/test.py +++ b/test/gui/tst_vfs/test.py @@ -5,5 +5,4 @@ def main(): - testSettings.throwOnFailure = True runFeatureFile('test.feature') diff --git a/test/modeltests/CMakeLists.txt b/test/modeltests/CMakeLists.txt index e2e5d59341c..cbf4cb52d80 100644 --- a/test/modeltests/CMakeLists.txt +++ b/test/modeltests/CMakeLists.txt @@ -1,2 +1 @@ -owncloud_add_test(ActivityModel) owncloud_add_test(ProtocolModel) diff --git a/test/modeltests/testactivitymodel.cpp b/test/modeltests/testactivitymodel.cpp deleted file mode 100644 index fa9b7d2d62c..00000000000 --- a/test/modeltests/testactivitymodel.cpp +++ /dev/null @@ -1,49 +0,0 @@ - -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * - */ - -#include "gui/models/activitylistmodel.h" -#include "gui/accountmanager.h" - -#include "testutils/testutils.h" - -#include -#include - -namespace OCC { - -class TestActivityModel : public QObject -{ - Q_OBJECT - -private Q_SLOTS: - void testInsert() - { - auto model = new ActivityListModel(this); - - new QAbstractItemModelTester(model, this); - - auto acc1 = TestUtils::createDummyAccount(); - auto acc2 = TestUtils::createDummyAccount(); - - model->setActivityList({ - Activity { Activity::ActivityType, 1, acc1, "test", "test", "foo.cpp", QUrl::fromUserInput("https://owncloud.com"), QDateTime::currentDateTime() }, - Activity { Activity::ActivityType, 2, acc1, "test", "test", "foo.cpp", QUrl::fromUserInput("https://owncloud.com"), QDateTime::currentDateTime() }, - Activity { Activity::ActivityType, 4, acc2, "test", "test", "foo.cpp", QUrl::fromUserInput("https://owncloud.com"), QDateTime::currentDateTime() }, - }); - model->setActivityList({ - Activity { Activity::ActivityType, 1, acc2, "test", "test", "foo.cpp", QUrl::fromUserInput("https://owncloud.com"), QDateTime::currentDateTime() }, - Activity { Activity::ActivityType, 2, acc1, "test", "test", "foo.cpp", QUrl::fromUserInput("https://owncloud.com"), QDateTime::currentDateTime() }, - Activity { Activity::ActivityType, 4, acc2, "test", "test", "foo.cpp", QUrl::fromUserInput("https://owncloud.com"), QDateTime::currentDateTime() }, - }); - model->slotRemoveAccount(AccountManager::instance()->accounts().first().data()); - } -}; -} - -QTEST_GUILESS_MAIN(OCC::TestActivityModel) -#include "testactivitymodel.moc" diff --git a/test/modeltests/testprotocolmodel.cpp b/test/modeltests/testprotocolmodel.cpp index 583b941c4de..4239a45d475 100644 --- a/test/modeltests/testprotocolmodel.cpp +++ b/test/modeltests/testprotocolmodel.cpp @@ -13,9 +13,10 @@ #include "testutils/testutils.h" -#include #include +#include #include +#include namespace OCC { @@ -30,19 +31,20 @@ private Q_SLOTS: new QAbstractItemModelTester(model, this); - QTemporaryDir dir; + auto dir = TestUtils::createTempDir(); - auto account = TestUtils::createDummyAccount(); + auto newAccountState = createDummyAccount(); - AccountStatePtr newAccountState(new AccountState(account)); const QDir d(dir.path()); - QVERIFY(d.mkdir("foo")); - QVERIFY(d.mkdir("bar")); - const QString foo = dir.path() + QStringLiteral("/foo"); - const QString bar = dir.path() + QStringLiteral("/bar"); - QVERIFY(TestUtils::folderMan()->addFolder(newAccountState.data(), TestUtils::createDummyFolderDefinition(foo))); - QVERIFY(TestUtils::folderMan()->addFolder(newAccountState.data(), TestUtils::createDummyFolderDefinition(bar))); - + QVERIFY(d.mkdir(QStringLiteral("foo"))); + QVERIFY(d.mkdir(QStringLiteral("bar"))); + auto foo = TestUtils::folderMan()->addFolder( + newAccountState.get(), TestUtils::createDummyFolderDefinition(newAccountState->account(), dir.path() + QStringLiteral("/foo"))); + auto bar = TestUtils::folderMan()->addFolder( + newAccountState.get(), TestUtils::createDummyFolderDefinition(newAccountState->account(), dir.path() + QStringLiteral("/bar"))); + + QVERIFY(foo); + QVERIFY(bar); // populate with dummy data // -1 to test the ring buffer window roll over diff --git a/test/owncloud_add_test.cmake b/test/owncloud_add_test.cmake index 8f4ecfd7c2a..db4be0d9052 100644 --- a/test/owncloud_add_test.cmake +++ b/test/owncloud_add_test.cmake @@ -1,36 +1,31 @@ -find_package(Qt5 COMPONENTS Core Test Xml Network REQUIRED) +include(OCApplyCommonSettings) +find_package(Qt6 COMPONENTS Test REQUIRED) include(ECMAddTests) function(owncloud_add_test test_class) set(OWNCLOUD_TEST_CLASS ${test_class}) string(TOLOWER "${OWNCLOUD_TEST_CLASS}" OWNCLOUD_TEST_CLASS_LOWERCASE) + set(SRC_PATH test${OWNCLOUD_TEST_CLASS_LOWERCASE}.cpp) + if (IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/test${OWNCLOUD_TEST_CLASS_LOWERCASE}/) + set(SRC_PATH test${OWNCLOUD_TEST_CLASS_LOWERCASE}/${SRC_PATH}) + endif() - ecm_add_test(test${OWNCLOUD_TEST_CLASS_LOWERCASE}.cpp + ecm_add_test(${SRC_PATH} ${ARGN} TEST_NAME "${OWNCLOUD_TEST_CLASS}Test" LINK_LIBRARIES - owncloudCore syncenginetestutils testutilsloader Qt5::Test + owncloudGui syncenginetestutils testutilsloader Qt::Test ) - target_compile_definitions(${OWNCLOUD_TEST_CLASS}Test PRIVATE OWNCLOUD_BIN_PATH="${CMAKE_BINARY_DIR}/bin" SOURCEDIR="${PROJECT_SOURCE_DIR}") + apply_common_target_settings(${OWNCLOUD_TEST_CLASS}Test) + target_compile_definitions(${OWNCLOUD_TEST_CLASS}Test PRIVATE SOURCEDIR="${PROJECT_SOURCE_DIR}" QT_FORCE_ASSERTS) target_include_directories(${OWNCLOUD_TEST_CLASS}Test PRIVATE "${CMAKE_SOURCE_DIR}/test/") if (UNIX AND NOT APPLE) set_property(TEST ${OWNCLOUD_TEST_CLASS}Test PROPERTY ENVIRONMENT "QT_QPA_PLATFORM=offscreen") endif() -endfunction() - -macro(owncloud_add_benchmark test_class) - set(OWNCLOUD_TEST_CLASS ${test_class}) - string(TOLOWER "${OWNCLOUD_TEST_CLASS}" OWNCLOUD_TEST_CLASS_LOWERCASE) - - add_executable(${OWNCLOUD_TEST_CLASS}Bench benchmarks/bench${OWNCLOUD_TEST_CLASS_LOWERCASE}.cpp ${additional_cpp}) - ecm_mark_nongui_executable(${OWNCLOUD_TEST_CLASS}Bench) - target_link_libraries(${OWNCLOUD_TEST_CLASS}Bench - libsync - syncenginetestutils - Qt5::Core Qt5::Test Qt5::Xml Qt5::Network - ) - target_compile_definitions(${OWNCLOUD_TEST_CLASS}Bench PRIVATE OWNCLOUD_BIN_PATH="${CMAKE_BINARY_DIR}/bin") -endmacro() + foreach(arg IN LISTS ARGN) + target_sources(${OWNCLOUD_TEST_CLASS}Test PRIVATE ${arg}) + endforeach() +endfunction() diff --git a/test/testadapters.cpp b/test/testadapters.cpp new file mode 100644 index 00000000000..144e7ab4800 --- /dev/null +++ b/test/testadapters.cpp @@ -0,0 +1,281 @@ +/* + * Copyright (C) Erik Verbruggen + * + * 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 + +#include "../src/gui/networkadapters/determineauthtypeadapter.h" +#include "../src/gui/networkadapters/discoverwebfingerserviceadapter.h" +#include "../src/gui/networkadapters/fetchcapabilitiesadapter.h" +// #include "../src/gui/networkadapters/resolveurladapter.h" +#include "../src/gui/networkadapters/webfingerlookupadapter.h" +#include "testutils/syncenginetestutils.h" + +using namespace OCC; + +/** + * @brief A reply object carrying json payload, with the headers set accordingly, and a 200 Ok + * response code. + */ +class AdapterJSonReply : public FakeReply +{ + Q_OBJECT + +public: + AdapterJSonReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &payload, QObject *parent) + : FakeReply(parent) + , payload(payload) + { + setRequest(request); + setUrl(request.url()); + setOperation(op); + open(QIODevice::ReadOnly); + + QMetaObject::invokeMethod(this, &AdapterJSonReply::respond, Qt::QueuedConnection); + } + + Q_INVOKABLE void respond() + { + setHeader(QNetworkRequest::ContentLengthHeader, payload.size()); + setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); + Q_EMIT metaDataChanged(); + if (bytesAvailable()) + Q_EMIT readyRead(); + checkedFinished(); + } + + qint64 bytesAvailable() const override + { + return payload.size(); + } + + qint64 readData(char *data, qint64 maxlen) override + { + qint64 len = std::min(qint64 { payload.size() }, maxlen); + std::copy(payload.cbegin(), payload.cbegin() + len, data); + payload.remove(0, static_cast(len)); + return len; + } + +private: + QByteArray payload; +}; + +/* + * Note: in this class we cannot use QCOMPARE/QVERIFY/etc, so we use asserts to validate the requests. + */ +class TestAdaptersAM : public FakeAM +{ + Q_OBJECT + +public: + TestAdaptersAM(const QUrl &urlWithoutPath, const QString &authToken = {}) + : FakeAM(nullptr) + , _urlWithoutPath(urlWithoutPath) + , _authToken(authToken) + { + setOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) -> QNetworkReply * { + return this->handleRequest(op, req, device); + }); + } + +protected: + virtual QNetworkReply *handleRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) + { + Q_UNUSED(device) + + if (op == QNetworkAccessManager::GetOperation) { + QString reqPath = req.url().path(); + if (reqPath == QStringLiteral("/.well-known/webfinger")) { + const QString resource = QUrlQuery(req.url()).queryItemValue(QStringLiteral("resource")); + if (resource == _urlWithoutPath.toString()) { + return handleWebFingerDiscoveryRequest(op, req); + } else if (resource == QStringLiteral("acct:me@%1").arg(_urlWithoutPath.host())) { + return handleWebFingerLookupRequest(op, req); + } + } else if (reqPath == QStringLiteral("/ocs/v2.php/cloud/capabilities")) { + return handleCapabilitiesRequest(op, req); + } + } else if (op == QNetworkAccessManager::CustomOperation && req.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray() == "PROPFIND") { + auto reply = new FakeErrorReply(op, req, this, 401, {}); + reply->setRawHeader(QByteArrayLiteral("www-authenticate"), QByteArrayLiteral("Bearer realm=\"ownCloudTest\"")); + return reply; + } + + Q_UNREACHABLE(); // Bomb out, the adapter send a strange request operation or path + } + + virtual QNetworkReply *handleWebFingerDiscoveryRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req) + { + return new AdapterJSonReply(op, req, {ValidWebFingerResponse}, this); + } + + virtual QNetworkReply *handleWebFingerLookupRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req) + { + verifyAuthentication(req); + const QString expectedResource = QStringLiteral("acct:me@%1").arg(_urlWithoutPath.host()); + Q_ASSERT(QUrlQuery(req.url()).queryItemValue(QStringLiteral("resource")) == expectedResource); + + return new AdapterJSonReply(op, req, {ValidWebFingerLookupResponse}, this); + } + + virtual QNetworkReply *handleCapabilitiesRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req) + { + verifyAuthentication(req); + Q_ASSERT(QUrlQuery(req.url()).queryItemValue(QStringLiteral("format")) == QStringLiteral("json")); + + return new AdapterJSonReply(op, req, ValidFetchCapabilitiesResponse, this); + } + + QNetworkReply *errorResponse(QNetworkAccessManager::Operation op, const QNetworkRequest &req, int httpErrorCode = 404) + { + return new FakeErrorReply(op, req, this, httpErrorCode); + } + + void verifyAuthentication(const QNetworkRequest &req) + { + Q_ASSERT(req.rawHeader("Authorization") == QByteArrayLiteral("Bearer ").append(_authToken.toUtf8())); + } + + static QByteArray ValidWebFingerResponse; + static QByteArray ValidWebFingerLookupResponse; + static QByteArray ValidFetchCapabilitiesResponse; + +private: + const QUrl _urlWithoutPath; + const QString _authToken; +}; + +QByteArray TestAdaptersAM::ValidWebFingerResponse = QByteArrayLiteral( + "{\"subject\":\"https://demo.test\",\"links\":[{\"rel\":\"http://openid.net/specs/connect/1.0/issuer\",\"href\":\"https://demo.test/realms/test\"}]}"); +QByteArray TestAdaptersAM::ValidWebFingerLookupResponse = + QByteArrayLiteral("{\"links\":[{\"rel\":\"http://webfinger.owncloud/rel/server-instance\",\"href\":\"one.demo.test\"},{\"rel\":\"http://webfinger.owncloud/" + "rel/server-instance\",\"href\":\"two.demo.test\"}]}"); +QByteArray TestAdaptersAM::ValidFetchCapabilitiesResponse = QByteArrayLiteral( + "{\"ocs\":{\"data\":{\"capabilities\":{\"checksums\":{\"preferredUploadType\":\"SHA3-256\",\"supportedTypes\":[\"SHA3-256\"]},\"core\":{\"pollinterval\":" + "30000,\"status\":{\"edition\":\"Enterprise\",\"installed\":true,\"maintenance\":false,\"needsDbUpgrade\":false,\"product\":\"test\",\"productname\":" + "\"test\",\"productversion\":\"8.4.1\",\"version\":\"10.0.11.5\",\"versionstring\":\"10.0.11\"},\"support-url-signing\":false},\"dav\":{\"chunking\":\"\"," + "\"chunkingParallelUploadDisabled\":false,\"reports\":null,\"trashbin\":\"\"},\"files\":{\"app_providers\":null,\"archivers\":null,\"bigfilechunking\":" + "false,\"blacklisted_files\":null,\"favorites\":false,\"permanent_deletion\":false,\"privateLinks\":true,\"tus_support\":null,\"undelete\":false," + "\"versioning\":false},\"files_sharing\":{\"allow_custom\":false,\"api_enabled\":false,\"auto_accept_share\":false,\"can_rename\":false,\"default_" + "permissions\":0,\"deny_access\":false,\"federation\":null,\"group_sharing\":false,\"public\":null,\"resharing\":false,\"resharing_default\":false," + "\"search_min_length\":0,\"share_with_group_members_only\":false,\"share_with_membership_groups_only\":false,\"user\":{\"profile_picture\":true,\"send_" + "mail\":false,\"settings\":null},\"user_enumeration\":null},\"notifications\":{},\"spaces\":{\"enabled\":true,\"has_multiple_personal_spaces\":true," + "\"projects\":true,\"version\":\"1.0.0\"}},\"version\":{\"edition\":\"Enterprise\",\"major\":10,\"micro\":11,\"minor\":0,\"product\":\"test\",\"string\":" + "\"10.0.11\"}},\"meta\":{\"message\":\"OK\",\"status\":\"ok\",\"statuscode\":100}}}"); + + +class FailureAM : public TestAdaptersAM +{ +public: + FailureAM(const QUrl &urlWithoutPath) + : TestAdaptersAM(urlWithoutPath) + { + } + +protected: + QNetworkReply *handleRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) override + { + Q_UNUSED(device); + + // Send the reply + // no well-known endpoints defined + return errorResponse(op, req); + } +}; + +class TestAdapters : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testWebFingerDiscovery_Failure() + { + FailureAM fakeAM(testUrl()); + + DiscoverWebFingerServiceAdapter adapter(&fakeAM, testUrl()); + DiscoverWebFingerServiceResult result = adapter.getResult(); + QVERIFY(!result.error.isEmpty()); + QVERIFY(result.href.isEmpty()); + } + + void testWebFingerDiscovery_Success() + { + TestAdaptersAM fakeAM(testUrl()); + + DiscoverWebFingerServiceAdapter adapter(&fakeAM, testUrl()); + DiscoverWebFingerServiceResult result = adapter.getResult(); + QVERIFY(result.error.isEmpty()); + QCOMPARE(result.href, QStringLiteral("https://demo.test/realms/test")); + } + + void testWebFingerDiscovery_BrokenJSON() + { + class BrokenAM : public TestAdaptersAM + { + public: + BrokenAM() + : TestAdaptersAM(testUrl()) + { + } + + protected: + QNetworkReply *handleWebFingerDiscoveryRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &req) override + { + return new AdapterJSonReply(op, req, ValidWebFingerResponse.first(80), this); + } + } fakeAM; + + DiscoverWebFingerServiceAdapter adapter(&fakeAM, testUrl()); + DiscoverWebFingerServiceResult result = adapter.getResult(); + QVERIFY(!result.error.isEmpty()); + QVERIFY(result.href.isEmpty()); + } + + void testDetermineAuthType_OAUTH() + { + TestAdaptersAM fakeAM(testUrl()); + + DetermineAuthTypeAdapter adapter(&fakeAM, testUrl()); + DetermineAuthTypeResult result = adapter.getResult(); + QVERIFY(result.error.isEmpty()); + QCOMPARE(result.type, AuthenticationType::OAuth); + } + + void testFetchCapabilitiesAdapter_Success() + { + TestAdaptersAM fakeAM(testUrl(), testToken()); + + FetchCapabilitiesAdapter adapter(&fakeAM, testToken(), testUrl(), nullptr); + FetchCapabilitiesResult result = adapter.getResult(); + QVERIFY(result.error.isEmpty()); + } + + void testWebFingerLookupAdapter_Success() + { + TestAdaptersAM fakeAM(testUrl(), testToken()); + + WebFingerLookupAdapter adapter(&fakeAM, testToken(), testUrl(), nullptr); + WebFingerLookupResult result = adapter.getResult(); + QVERIFY(result.success()); + QCOMPARE(result.urls.size(), 2); + } + +private: + static QUrl testUrl() { return QUrl(QStringLiteral("https://demo.test")); } + static QString testToken() { return QStringLiteral("theToken"); } +}; + +QTEST_GUILESS_MAIN(TestAdapters) +#include "testadapters.moc" diff --git a/test/testallfilesdeleted.cpp b/test/testallfilesdeleted.cpp deleted file mode 100644 index e3ad69995f0..00000000000 --- a/test/testallfilesdeleted.cpp +++ /dev/null @@ -1,330 +0,0 @@ -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * - */ - -#include -#include "testutils/syncenginetestutils.h" -#include - -using namespace OCC; - - -static void changeAllFileId(FileInfo &info) { - info.fileId = generateFileId(); - if (!info.isDir) - return; - info.etag = generateEtag(); - for (auto it = info.children.begin(); it != info.children.end(); ++it) { - changeAllFileId(*it); - } -} - -/* - * This test ensure that the SyncEngine::aboutToRemoveAllFiles is correctly called and that when - * we the user choose to remove all files SyncJournalDb::clearFileTable makes works as expected - */ -class TestAllFilesDeleted : public QObject -{ - Q_OBJECT - -private slots: - - void testAllFilesDeletedKeep_data() - { - QTest::addColumn("deleteOnRemote"); - QTest::newRow("local") << false; - QTest::newRow("remote") << true; - - } - - /* - * In this test, all files are deleted in the client, or the server, and we simulate - * that the users press "keep" - */ - void testAllFilesDeletedKeep() - { - QFETCH(bool, deleteOnRemote); - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - - //Just set a blacklist so we can check it is still there. This directory does not exists but - // that does not matter for our purposes. - QStringList selectiveSyncBlackList = { "Q/" }; - fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, - selectiveSyncBlackList); - - auto initialState = fakeFolder.currentLocalState(); - int aboutToRemoveAllFilesCalled = 0; - QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles, - [&](SyncFileItem::Direction dir, std::function callback) { - QCOMPARE(aboutToRemoveAllFilesCalled, 0); - aboutToRemoveAllFilesCalled++; - QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up); - callback(true); - fakeFolder.syncEngine().journal()->clearFileTable(); // That's what Folder is doing - }); - - auto &modifier = deleteOnRemote ? fakeFolder.remoteModifier() : fakeFolder.localModifier(); - const auto &children = fakeFolder.currentRemoteState().children; - for (auto it = children.cbegin(); it != children.cend(); ++it) - modifier.remove(it.key()); - - QVERIFY(!fakeFolder.syncOnce()); // Should fail because we cancel the sync - QCOMPARE(aboutToRemoveAllFilesCalled, 1); - - // Next sync should recover all files - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), initialState); - QCOMPARE(fakeFolder.currentRemoteState(), initialState); - - // The selective sync blacklist should be not have been deleted. - bool ok = true; - QCOMPARE(fakeFolder.syncEngine().journal()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok), - selectiveSyncBlackList); - } - - void testAllFilesDeletedDelete_data() - { - testAllFilesDeletedKeep_data(); - } - - /* - * This test is like the previous one but we simulate that the user presses "delete" - */ - void testAllFilesDeletedDelete() - { - QFETCH(bool, deleteOnRemote); - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - - int aboutToRemoveAllFilesCalled = 0; - QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles, - [&](SyncFileItem::Direction dir, std::function callback) { - QCOMPARE(aboutToRemoveAllFilesCalled, 0); - aboutToRemoveAllFilesCalled++; - QCOMPARE(dir, deleteOnRemote ? SyncFileItem::Down : SyncFileItem::Up); - callback(false); - }); - - auto &modifier = deleteOnRemote ? fakeFolder.remoteModifier() : fakeFolder.localModifier(); - const auto &children = fakeFolder.currentRemoteState().children; - for (auto it = children.cbegin(); it != children.cend(); ++it) - modifier.remove(it.key()); - - QVERIFY(fakeFolder.syncOnce()); // Should succeed, and all files must then be deleted - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentLocalState().children.count(), 0); - - // Try another sync to be sure. - - QVERIFY(fakeFolder.syncOnce()); // Should succeed (doing nothing) - QCOMPARE(aboutToRemoveAllFilesCalled, 1); // should not have been called. - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentLocalState().children.count(), 0); - } - - void testNotDeleteMetaDataChange() { - /** - * This test make sure that we don't popup a file deleted message if all the metadata have - * been updated (for example when the server is upgraded or something) - **/ - - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - // We never remove all files. - QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles, - [&] { QVERIFY(false); }); - QVERIFY(fakeFolder.syncOnce()); - - const auto &children = fakeFolder.currentRemoteState().children; - for (auto it = children.cbegin(); it != children.cend(); ++it) - fakeFolder.syncJournal().avoidRenamesOnNextSync(it.key()); // clears all the fileid and inodes. - fakeFolder.localModifier().remove("A/a1"); - auto expectedState = fakeFolder.currentLocalState(); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), expectedState); - QCOMPARE(fakeFolder.currentRemoteState(), expectedState); - - fakeFolder.remoteModifier().remove("B/b1"); - changeAllFileId(fakeFolder.remoteModifier()); - expectedState = fakeFolder.currentRemoteState(); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), expectedState); - QCOMPARE(fakeFolder.currentRemoteState(), expectedState); - } - - void testResetServer() - { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - - int aboutToRemoveAllFilesCalled = 0; - QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles, - [&](SyncFileItem::Direction dir, std::function callback) { - QCOMPARE(aboutToRemoveAllFilesCalled, 0); - aboutToRemoveAllFilesCalled++; - QCOMPARE(dir, SyncFileItem::Down); - callback(false); - }); - - // Some small changes - fakeFolder.localModifier().mkdir("Q"); - fakeFolder.localModifier().insert("Q/q1"); - fakeFolder.localModifier().appendByte("B/b1"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(aboutToRemoveAllFilesCalled, 0); - - // Do some change localy - fakeFolder.localModifier().appendByte("A/a1"); - - // reset the server. - fakeFolder.remoteModifier() = FileInfo::A12_B12_C12_S12(); - - // Now, aboutToRemoveAllFiles with down as a direction - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(aboutToRemoveAllFilesCalled, 1); - - } - - void testDataFingetPrint_data() - { - QTest::addColumn("hasInitialFingerPrint"); - QTest::newRow("initial finger print") << true; - QTest::newRow("no initial finger print") << false; - } - - void testDataFingetPrint() - { - QFETCH(bool, hasInitialFingerPrint); - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.remoteModifier().setContents("C/c1", 'N'); - fakeFolder.remoteModifier().setModTime("C/c1", QDateTime::currentDateTimeUtc().addDays(-2)); - fakeFolder.remoteModifier().remove("C/c2"); - if (hasInitialFingerPrint) { - fakeFolder.remoteModifier().extraDavProperties = "initial_finger_print"; - } else { - //Server support finger print, but none is set. - fakeFolder.remoteModifier().extraDavProperties = ""; - } - - int fingerprintRequests = 0; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation, const QNetworkRequest &request, QIODevice *stream) -> QNetworkReply * { - auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute); - if (verb == "PROPFIND") { - auto data = stream->readAll(); - if (data.contains("data-fingerprint")) { - if (request.url().path().endsWith("webdav/")) - ++fingerprintRequests; - else - fingerprintRequests = -10000; // fingerprint queried on incorrect path - } - } - return nullptr; - }); - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fingerprintRequests, 1); - // First sync, we did not change the finger print, so the file should be downloaded as normal - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->contentChar, 'N'); - QVERIFY(!fakeFolder.currentRemoteState().find("C/c2")); - - /* Simulate a backup restoration */ - - // A/a1 is an old file - fakeFolder.remoteModifier().setContents("A/a1", 'O'); - fakeFolder.remoteModifier().setModTime("A/a1", QDateTime::currentDateTimeUtc().addDays(-2)); - // B/b1 did not exist at the time of the backup - fakeFolder.remoteModifier().remove("B/b1"); - // B/b2 was uploaded by another user in the mean time. - fakeFolder.remoteModifier().setContents("B/b2", 'N'); - fakeFolder.remoteModifier().setModTime("B/b2", QDateTime::currentDateTimeUtc().addDays(2)); - - // C/c3 was removed since we made the backup - fakeFolder.remoteModifier().insert("C/c3_removed"); - // C/c4 was moved to A/a2 since we made the backup - fakeFolder.remoteModifier().rename("A/a2", "C/old_a2_location"); - - // The admin sets the data-fingerprint property - fakeFolder.remoteModifier().extraDavProperties = "new_finger_print"; - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fingerprintRequests, 2); - auto currentState = fakeFolder.currentLocalState(); - // Altough the local file is kept as a conflict, the server file is downloaded - QCOMPARE(currentState.find("A/a1")->contentChar, 'O'); - auto conflict = findConflict(currentState, "A/a1"); - QVERIFY(conflict); - QCOMPARE(conflict->contentChar, 'W'); - fakeFolder.localModifier().remove(conflict->path()); - // b1 was restored (re-uploaded) - QVERIFY(currentState.find("B/b1")); - - // b2 has the new content (was not restored), since its mode time goes forward in time - QCOMPARE(currentState.find("B/b2")->contentChar, 'N'); - conflict = findConflict(currentState, "B/b2"); - QVERIFY(conflict); // Just to be sure, we kept the old file in a conflict - QCOMPARE(conflict->contentChar, 'W'); - fakeFolder.localModifier().remove(conflict->path()); - - // We actually do not remove files that technically should have been removed (we don't want data-loss) - QVERIFY(currentState.find("C/c3_removed")); - QVERIFY(currentState.find("C/old_a2_location")); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } - - void testSingleFileRenamed() { - FakeFolder fakeFolder{FileInfo{}}; - - int aboutToRemoveAllFilesCalled = 0; - QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles, - [&](SyncFileItem::Direction , std::function ) { - aboutToRemoveAllFilesCalled++; - QFAIL("should not be called"); - }); - - // add a single file - fakeFolder.localModifier().insert("hello.txt"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(aboutToRemoveAllFilesCalled, 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // rename it - fakeFolder.localModifier().rename("hello.txt", "goodbye.txt"); - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(aboutToRemoveAllFilesCalled, 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } - - void testSelectiveSyncNoPopup() { - // Unselecting all folder should not cause the popup to be shown - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - - int aboutToRemoveAllFilesCalled = 0; - QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToRemoveAllFiles, - [&](SyncFileItem::Direction , std::function) { - aboutToRemoveAllFilesCalled++; - QFAIL("should not be called"); - }); - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(aboutToRemoveAllFilesCalled, 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, - QStringList() << "A/" << "B/" << "C/" << "S/"); - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), FileInfo{}); // all files should be one localy - QCOMPARE(fakeFolder.currentRemoteState(), FileInfo::A12_B12_C12_S12()); // Server not changed - QCOMPARE(aboutToRemoveAllFilesCalled, 0); // But we did not show the popup - } - - -}; - -QTEST_GUILESS_MAIN(TestAllFilesDeleted) -#include "testallfilesdeleted.moc" diff --git a/test/testappconfig.cpp b/test/testappconfig.cpp new file mode 100644 index 00000000000..cc46c76f946 --- /dev/null +++ b/test/testappconfig.cpp @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "libsync/owncloudtheme.h" +#include "libsync/config/appconfig.h" + +static const std::string_view iniData{ + "[Setup]\n" + "ServerUrl=https://cloud.example.com\n" + "AllowServerUrlChange=false\n" + "MoveToTrash=true\n" + "\n" + "[Updater]\n" + "SkipUpdateCheck=true\n" + "\n" + "[OpenIDConnect]\n" + "ClientId=your-client-id\n" + "ClientSecret=your-client-secret\n" + "Ports=8080,8443\n" + "Scopes=openid offline_access email profile\n" + "Prompt=select_account consent\n" +}; + +class TestAppConfig : public QObject +{ + Q_OBJECT + + QString _configPath; + + bool createConfigFile(const QByteArray &data) { + auto theme = OCC::ownCloudTheme(); + _configPath = OCC::AppConfig::configPath(QOperatingSystemVersion::Unknown, theme); + + QString parent = QFileInfo(_configPath).path(); + if (!QDir().mkpath(parent)) + return false; + + QFile iniFile(_configPath); + if (!iniFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + qDebug()<{8080, 8443})); + QCOMPARE(oidCfg.scopes(), "openid offline_access email profile"); + QCOMPARE(oidCfg.prompt(), "select_account consent"); + } +}; + +QTEST_GUILESS_MAIN(TestAppConfig) +#include "testappconfig.moc" diff --git a/test/testblacklist.cpp b/test/testblacklist.cpp index 3a4fe93be80..027b73330d9 100644 --- a/test/testblacklist.cpp +++ b/test/testblacklist.cpp @@ -14,7 +14,7 @@ using namespace OCC; SyncJournalFileRecord journalRecord(FakeFolder &folder, const QByteArray &path) { SyncJournalFileRecord rec; - folder.syncJournal().getFileRecord(path, &rec); + folder.syncJournal()->getFileRecord(path, &rec); return rec; } @@ -22,7 +22,24 @@ class TestBlacklist : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } + void testBlacklistBasic_data() { QTest::addColumn("remote"); @@ -32,16 +49,18 @@ private slots: void testBlacklistBasic() { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); QFETCH(bool, remote); - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); ItemCompletedSpy completeSpy(fakeFolder); auto &modifier = remote ? fakeFolder.remoteModifier() : fakeFolder.localModifier(); int counter = 0; - const QByteArray testFileName = QByteArrayLiteral("A/new"); + const QString testFileName = QStringLiteral("A/new"); QByteArray reqId; fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) -> QNetworkReply * { if (req.url().path().endsWith(testFileName)) { @@ -64,37 +83,46 @@ private slots: // The first sync and the download will fail - the item will be blacklisted modifier.insert(testFileName); fakeFolder.serverErrorPaths().append(testFileName, 500); // will be blacklisted - QVERIFY(!fakeFolder.syncOnce()); + const bool syncResult = fakeFolder.applyLocalModificationsAndSync(); + if (vfsMode == Vfs::WindowsCfApi && filesAreDehydrated && remote) { + // With dehydrated files, only a PROPFIND is done, but not a GET request. + // And it is the GET request that fails, and causes a blacklist entry, all "syncs" will succeed. + QVERIFY(syncResult); + QSKIP("The remainder of this test is not applicable for dehydrated files"); + } + + QVERIFY(!syncResult); { auto it = completeSpy.findItem(testFileName); QVERIFY(it); QCOMPARE(it->_status, SyncFileItem::NormalError); // initial error visible - QCOMPARE(it->_instruction, CSYNC_INSTRUCTION_NEW); + QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(entry.isValid()); - QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Normal); + QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal); QCOMPARE(entry._retryCount, 1); QCOMPARE(counter, 1); QVERIFY(entry._ignoreDuration > 0); QCOMPARE(entry._requestId, reqId); - if (remote) + if (remote) { QCOMPARE(journalRecord(fakeFolder, "A")._etag, initialEtag); + } } cleanup(); // Ignored during the second run - but soft errors are also errors - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); { auto it = completeSpy.findItem(testFileName); QVERIFY(it); QCOMPARE(it->_status, SyncFileItem::BlacklistedError); - QCOMPARE(it->_instruction, CSYNC_INSTRUCTION_IGNORE); // no retry happened! + QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_IGNORE); // no retry happened! - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(entry.isValid()); - QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Normal); + QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal); QCOMPARE(entry._retryCount, 1); QCOMPARE(counter, 1); QVERIFY(entry._ignoreDuration > 0); @@ -107,21 +135,21 @@ private slots: // Let's expire the blacklist entry to verify it gets retried { - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); entry._ignoreDuration = 1; entry._lastTryTime -= 1; - fakeFolder.syncJournal().setErrorBlacklistEntry(entry); + fakeFolder.syncJournal()->setErrorBlacklistEntry(entry); } - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); { auto it = completeSpy.findItem(testFileName); QVERIFY(it); QCOMPARE(it->_status, SyncFileItem::BlacklistedError); // blacklisted as it's just a retry - QCOMPARE(it->_instruction, CSYNC_INSTRUCTION_NEW); // retry! + QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); // retry! - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(entry.isValid()); - QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Normal); + QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal); QCOMPARE(entry._retryCount, 2); QCOMPARE(counter, 2); QVERIFY(entry._ignoreDuration > 0); @@ -132,18 +160,22 @@ private slots: } cleanup(); + // Try to make sure that the modifications below do are not within the same second as the + // previous sync attempt was done, otherwise the changes go undetected. + QThread::sleep(1); + // When the file changes a retry happens immediately modifier.appendByte(testFileName); - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); { auto it = completeSpy.findItem(testFileName); QVERIFY(it); QCOMPARE(it->_status, SyncFileItem::BlacklistedError); - QCOMPARE(it->_instruction, CSYNC_INSTRUCTION_NEW); // retry! + QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); // retry! - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(entry.isValid()); - QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Normal); + QCOMPARE(entry._errorCategory, SyncJournalErrorBlacklistRecord::Category::Normal); QCOMPARE(entry._retryCount, 3); QCOMPARE(counter, 3); QVERIFY(entry._ignoreDuration > 0); @@ -157,24 +189,24 @@ private slots: // When the error goes away and the item is retried, the sync succeeds fakeFolder.serverErrorPaths().clear(); { - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); entry._ignoreDuration = 1; entry._lastTryTime -= 1; - fakeFolder.syncJournal().setErrorBlacklistEntry(entry); + fakeFolder.syncJournal()->setErrorBlacklistEntry(entry); } - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); { auto it = completeSpy.findItem(testFileName); QVERIFY(it); QCOMPARE(it->_status, SyncFileItem::Success); - QCOMPARE(it->_instruction, CSYNC_INSTRUCTION_NEW); + QCOMPARE(it->instruction(), CSYNC_INSTRUCTION_NEW); - auto entry = fakeFolder.syncJournal().errorBlacklistEntry(testFileName); + auto entry = fakeFolder.syncJournal()->errorBlacklistEntry(testFileName); QVERIFY(!entry.isValid()); QCOMPARE(counter, 4); if (remote) - QCOMPARE(journalRecord(fakeFolder, "A")._etag, fakeFolder.currentRemoteState().find("A")->etag); + QCOMPARE(journalRecord(fakeFolder, "A")._etag, fakeFolder.currentRemoteState().find(QStringLiteral("A"))->etag); } cleanup(); diff --git a/test/testchecksumvalidator.cpp b/test/testchecksumvalidator.cpp index 72e7e9dff4c..27a9065ef59 100644 --- a/test/testchecksumvalidator.cpp +++ b/test/testchecksumvalidator.cpp @@ -23,21 +23,22 @@ using namespace OCC::Utility; { Q_OBJECT private: - QTemporaryDir _root; + const QTemporaryDir _root = TestUtils::createTempDir(); QString _testfile; QString _expectedError; QByteArray _expected; - QByteArray _expectedType; + CheckSums::Algorithm _expectedType; bool _successDown; bool _errorSeen; - public slots: + public Q_SLOTS: - void slotUpValidated(const QByteArray& type, const QByteArray& checksum) { - qDebug() << "Checksum: " << checksum; - QVERIFY(_expected == checksum ); - QVERIFY(_expectedType == type ); - } + void slotUpValidated(CheckSums::Algorithm type, const QByteArray &checksum) + { + qDebug() << "Checksum: " << checksum; + QCOMPARE(_expected, checksum); + QVERIFY(_expectedType == type); + } void slotDownValidated() { _successDown = true; @@ -53,7 +54,7 @@ using namespace OCC::Utility; QProcess md5; QStringList args; args.append(file); - md5.start(cmd, args); + md5.start(QString::fromUtf8(cmd), args); QByteArray sumShell; qDebug() << "File: "<< file; @@ -65,23 +66,23 @@ using namespace OCC::Utility; return sumShell; } - private slots: +private Q_SLOTS: void initTestCase() { - _testfile = _root.path()+"/csFile"; + _testfile = _root.path() + QStringLiteral("/csFile"); TestUtils::writeRandomFile(_testfile); } void testMd5Calc() { - QString file( _root.path() + "/file_a.bin"); + QString file(_root.path() + QStringLiteral("/file_a.bin")); QVERIFY(TestUtils::writeRandomFile(file)); QFileInfo fi(file); QVERIFY(fi.exists()); QFile fileDevice(file); fileDevice.open(QIODevice::ReadOnly); - QByteArray sum = calcMd5(&fileDevice); + QByteArray sum = ComputeChecksum::computeNow(&fileDevice, CheckSums::Algorithm::MD5); fileDevice.close(); QByteArray sSum = shellSum("md5sum", file); @@ -94,14 +95,14 @@ using namespace OCC::Utility; void testSha1Calc() { - QString file( _root.path() + "/file_b.bin"); + QString file(_root.path() + QStringLiteral("/file_b.bin")); TestUtils::writeRandomFile(file); QFileInfo fi(file); QVERIFY(fi.exists()); QFile fileDevice(file); fileDevice.open(QIODevice::ReadOnly); - QByteArray sum = calcSha1(&fileDevice); + QByteArray sum = ComputeChecksum::computeNow(&fileDevice, CheckSums::Algorithm::SHA1); fileDevice.close(); QByteArray sSum = shellSum("sha1sum", file); @@ -114,14 +115,14 @@ using namespace OCC::Utility; void testUploadChecksummingAdler() { ComputeChecksum *vali = new ComputeChecksum(this); - _expectedType = "Adler32"; + _expectedType = CheckSums::Algorithm::ADLER32; vali->setChecksumType(_expectedType); connect(vali, &ComputeChecksum::done, this, &TestChecksumValidator::slotUpValidated); - auto file = new QFile(_testfile, vali); - file->open(QIODevice::ReadOnly); - _expected = calcAdler32(file); + auto file = QFile(_testfile); + file.open(QIODevice::ReadOnly); + _expected = ComputeChecksum::computeNow(&file, CheckSums::Algorithm::ADLER32); qDebug() << "XX Expected Checksum: " << _expected; vali->start(_testfile); @@ -135,13 +136,13 @@ using namespace OCC::Utility; void testUploadChecksummingMd5() { ComputeChecksum *vali = new ComputeChecksum(this); - _expectedType = OCC::checkSumMD5C; + _expectedType = OCC::CheckSums::Algorithm::MD5; vali->setChecksumType(_expectedType); connect(vali, &ComputeChecksum::done, this, &TestChecksumValidator::slotUpValidated); - auto file = new QFile(_testfile, vali); - file->open(QIODevice::ReadOnly); - _expected = calcMd5(file); + auto file = QFile(_testfile); + file.open(QIODevice::ReadOnly); + _expected = ComputeChecksum::computeNow(&file, CheckSums::Algorithm::MD5); vali->start(_testfile); QEventLoop loop; @@ -153,13 +154,13 @@ using namespace OCC::Utility; void testUploadChecksummingSha1() { ComputeChecksum *vali = new ComputeChecksum(this); - _expectedType = OCC::checkSumSHA1C; + _expectedType = OCC::CheckSums::Algorithm::SHA1; vali->setChecksumType(_expectedType); connect(vali, &ComputeChecksum::done, this, &TestChecksumValidator::slotUpValidated); - auto file = new QFile(_testfile, vali); - file->open(QIODevice::ReadOnly); - _expected = calcSha1(file); + auto file = QFile(_testfile); + file.open(QIODevice::ReadOnly); + _expected = ComputeChecksum::computeNow(&file, CheckSums::Algorithm::SHA1); vali->start(_testfile); @@ -175,15 +176,15 @@ using namespace OCC::Utility; connect(vali, &ValidateChecksumHeader::validated, this, &TestChecksumValidator::slotDownValidated); connect(vali, &ValidateChecksumHeader::validationFailed, this, &TestChecksumValidator::slotDownError); - auto file = new QFile(_testfile, vali); - file->open(QIODevice::ReadOnly); - _expected = calcAdler32(file); + auto file = QFile(_testfile); + file.open(QIODevice::ReadOnly); + _expected = ComputeChecksum::computeNow(&file, CheckSums::Algorithm::ADLER32); - QByteArray adler = checkSumAdlerC; + QByteArray adler = OCC::CheckSums::toString(OCC::CheckSums::Algorithm::ADLER32).data(); adler.append(":"); adler.append(_expected); - file->seek(0); + file.seek(0); _successDown = false; vali->start(_testfile, adler); @@ -191,13 +192,13 @@ using namespace OCC::Utility; _expectedError = QStringLiteral("The downloaded file does not match the checksum, it will be resumed. '543345' != '%1'").arg(QString::fromUtf8(_expected)); _errorSeen = false; - file->seek(0); + file.seek(0); vali->start(_testfile, "Adler32:543345"); QTRY_VERIFY(_errorSeen); - _expectedError = QLatin1String("The checksum header contained an unknown checksum type 'Klaas32'"); + _expectedError = QStringLiteral("The checksum header contained an unknown checksum type 'Klaas32'"); _errorSeen = false; - file->seek(0); + file.seek(0); vali->start(_testfile, "Klaas32:543345"); QTRY_VERIFY(_errorSeen); diff --git a/test/testchunkingng.cpp b/test/testchunkingng.cpp deleted file mode 100644 index 084acc3e43b..00000000000 --- a/test/testchunkingng.cpp +++ /dev/null @@ -1,649 +0,0 @@ -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * - */ - -#include -#include "testutils/syncenginetestutils.h" -#include - -using namespace std::chrono_literals; -using namespace OCC; - -namespace { - -const QVariantMap TestCapabilities = { - { "dav", QVariantMap { { "chunking", "1.0" } } }, - { "checksums", QVariantMap { { "preferredUploadType", "md5" } } } -}; - - -/* Upload a 1/3 of a file of given size. - * fakeFolder needs to be synchronized */ -void partialUpload(FakeFolder &fakeFolder, const QString &name, qint64 size) -{ - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.uploadState().children.count(), 0); // The state should be clean - - fakeFolder.localModifier().insert(name, size); - // Abort when the upload is at 1/3 - qint64 sizeWhenAbort = -1; - auto con = QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress, - [&](const ProgressInfo &progress) { - if (progress.completedSize() > (progress.totalSize() /3 )) { - sizeWhenAbort = progress.completedSize(); - fakeFolder.syncEngine().abort(); - } - }); - - QVERIFY(!fakeFolder.syncOnce()); // there should have been an error - QObject::disconnect(con); - QVERIFY(sizeWhenAbort > 0); - QVERIFY(sizeWhenAbort < size); - - QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking - auto upStateChildren = fakeFolder.uploadState().children.first().children; - QCOMPARE(sizeWhenAbort, std::accumulate(upStateChildren.cbegin(), upStateChildren.cend(), 0, - [](int s, const FileInfo &i) { return s + i.size; })); -} - -// Reduce max chunk size a bit so we get more chunks -void setChunkSize(SyncEngine &engine, qint64 size) -{ - SyncOptions options; - options._maxChunkSize = size; - options._initialChunkSize = size; - options._minChunkSize = size; - engine.setSyncOptions(options); -} -} - -class TestChunkingNG : public QObject -{ - Q_OBJECT - -private slots: - - void testFileUpload() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - const int size = 10 * 1000 * 1000; // 10 MB - - fakeFolder.localModifier().insert("A/a0", size); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); - - // Check that another upload of the same file also work. - fakeFolder.localModifier().appendByte("A/a0"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.uploadState().children.count(), 2); // the transfer was done with chunking - } - - // Test resuming when there's a confusing chunk added - void testResume1() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const int size = 10 * 1000 * 1000; // 10 MB - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - partialUpload(fakeFolder, "A/a0", size); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - auto chunkingId = fakeFolder.uploadState().children.first().name; - const auto &chunkMap = fakeFolder.uploadState().children.first().children; - qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.size; }); - QVERIFY(uploadedSize > 2 * 1000 * 1000); // at least 2 MB - - // Add a fake chunk to make sure it gets deleted - fakeFolder.uploadState().children.first().insert("10000", size); - - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::PutOperation) { - // Test that we properly resuming and are not sending past data again. - Q_ASSERT(request.rawHeader("OC-Chunk-Offset").toLongLong() >= uploadedSize); - } else if (op == QNetworkAccessManager::DeleteOperation) { - Q_ASSERT(request.url().path().endsWith("/10000")); - } - return nullptr; - }); - - QVERIFY(fakeFolder.syncOnce()); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); - // The same chunk id was re-used - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - QCOMPARE(fakeFolder.uploadState().children.first().name, chunkingId); - } - - // Test resuming when one of the uploaded chunks got removed - void testResume2() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - const int size = 150 * 1000 * 1000; // 30 MB - partialUpload(fakeFolder, "A/a0", size); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - auto chunkingId = fakeFolder.uploadState().children.first().name; - const auto &chunkMap = fakeFolder.uploadState().children.first().children; - qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.size; }); - QVERIFY(uploadedSize > 2 * 1000 * 1000); // at least 50 MB - QVERIFY(chunkMap.size() >= 3); // at least three chunks - - QStringList chunksToDelete; - - // Remove the second chunk, so all further chunks will be deleted and resent - auto firstChunk = chunkMap.first(); - auto secondChunk = *(chunkMap.begin() + 1); - for (auto it = chunkMap.cbegin() + 2; it != chunkMap.cend(); ++it) { - chunksToDelete.append(it.key()); - } - fakeFolder.uploadState().children.first().remove(secondChunk.name); - - QStringList deletedPaths; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::PutOperation) { - // Test that we properly resuming, not resending the first chunk - Q_ASSERT(request.rawHeader("OC-Chunk-Offset").toLongLong() >= firstChunk.size); - } else if (op == QNetworkAccessManager::DeleteOperation) { - deletedPaths.append(request.url().path()); - } - return nullptr; - }); - - QVERIFY(fakeFolder.syncOnce()); - - for (const auto& toDelete : chunksToDelete) { - bool wasDeleted = false; - for (const auto& deleted : deletedPaths) { - if (deleted.mid(deleted.lastIndexOf('/') + 1) == toDelete) { - wasDeleted = true; - break; - } - } - QVERIFY(wasDeleted); - } - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); - // The same chunk id was re-used - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - QCOMPARE(fakeFolder.uploadState().children.first().name, chunkingId); - } - - // Test resuming when all chunks are already present - void testResume3() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const int size = 30 * 1000 * 1000; // 30 MB - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - partialUpload(fakeFolder, "A/a0", size); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - auto chunkingId = fakeFolder.uploadState().children.first().name; - const auto &chunkMap = fakeFolder.uploadState().children.first().children; - qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.size; }); - QVERIFY(uploadedSize > 5 * 1000 * 1000); // at least 5 MB - - // Add a chunk that makes the file completely uploaded - fakeFolder.uploadState().children.first().insert( - QString::number(uploadedSize).rightJustified(16, '0'), size - uploadedSize); - - bool sawPut = false; - bool sawDelete = false; - bool sawMove = false; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::PutOperation) { - sawPut = true; - } else if (op == QNetworkAccessManager::DeleteOperation) { - sawDelete = true; - } else if (request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") { - sawMove = true; - } - return nullptr; - }); - - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(sawMove); - QVERIFY(!sawPut); - QVERIFY(!sawDelete); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); - // The same chunk id was re-used - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - QCOMPARE(fakeFolder.uploadState().children.first().name, chunkingId); - } - - // Test resuming (or rather not resuming!) for the error case of the sum of - // chunk sizes being larger than the file size - void testResume4() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const int size = 30 * 1000 * 1000; // 300 MB - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - partialUpload(fakeFolder, "A/a0", size); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - auto chunkingId = fakeFolder.uploadState().children.first().name; - const auto &chunkMap = fakeFolder.uploadState().children.first().children; - qint64 uploadedSize = std::accumulate(chunkMap.begin(), chunkMap.end(), 0LL, [](qint64 s, const FileInfo &f) { return s + f.size; }); - QVERIFY(uploadedSize > 5 * 1000 * 1000); // at least 5 MB - - // Add a chunk that makes the file more than completely uploaded - fakeFolder.uploadState().children.first().insert( - QString::number(uploadedSize).rightJustified(16, '0'), size - uploadedSize + 100); - - QVERIFY(fakeFolder.syncOnce()); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - } - - // Check what happens when we abort during the final MOVE and the - // the final MOVE takes longer than the abort-delay - void testLateAbortHard() - { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "chunking", "1.0" } } }, { "checksums", QVariantMap{ { "supportedTypes", QStringList() << "SHA1" } } } }); - const int size = 15 * 1000 * 1000; - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - // Make the MOVE never reply, but trigger a client-abort and apply the change remotely - QObject parent; - QByteArray moveChecksumHeader; - int nGET = 0; - const auto responseDelay = 24h; // bigger than abort-wait timeout - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") { - QTimer::singleShot(50ms, &parent, [&]() { fakeFolder.syncEngine().abort(); }); - moveChecksumHeader = request.rawHeader("OC-Checksum"); - return new DelayedReply(responseDelay, fakeFolder.uploadState(), fakeFolder.remoteModifier(), op, request, &parent); - } else if (op == QNetworkAccessManager::GetOperation) { - nGET++; - } - return nullptr; - }); - - - // Test 1: NEW file aborted - fakeFolder.localModifier().insert("A/a0", size); - QVERIFY(!fakeFolder.syncOnce()); // error: abort! - - // Now the next sync gets a NEW/NEW conflict and since there's no checksum - // it just becomes a UPDATE_METADATA - auto checkIsUpdateMetaData = [&](SyncFileItemSet &items) { - QCOMPARE(items.size(), 2); - auto it = items.cbegin(); - QCOMPARE(it->get()->_file, QLatin1String("A")); - QCOMPARE(it->get()->_instruction, CSYNC_INSTRUCTION_UPDATE_METADATA); - it++; - QCOMPARE(it->get()->_file, QLatin1String("A/a0")); - QCOMPARE(it->get()->_instruction, CSYNC_INSTRUCTION_UPDATE_METADATA); - QCOMPARE(it->get()->_etag, fakeFolder.remoteModifier().find("A/a0")->etag); - }; - auto checkEtagUpdated = [&](const SyncFileItemPtr &item) { - if (item->_file == QLatin1String("A/a0")) { - SyncJournalFileRecord record; - QVERIFY(fakeFolder.syncJournal().getFileRecord(QByteArray("A/a0"), &record)); - QCOMPARE(record._etag, fakeFolder.remoteModifier().find("A/a0")->etag); - QCOMPARE(item->_etag, fakeFolder.remoteModifier().find("A/a0")->etag); - } - }; - auto connection1 = connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, checkIsUpdateMetaData); - auto connection2 = connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, checkEtagUpdated); - QVERIFY(fakeFolder.syncOnce()); - disconnect(connection1); - disconnect(connection2); - QCOMPARE(nGET, 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - - // Test 2: modified file upload aborted - fakeFolder.localModifier().appendByte("A/a0"); - QVERIFY(!fakeFolder.syncOnce()); // error: abort! - - // An EVAL/EVAL conflict is also UPDATE_METADATA when there's no checksums - connection1 = connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, checkIsUpdateMetaData); - connection2 = connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, checkEtagUpdated); - QVERIFY(fakeFolder.syncOnce()); - disconnect(connection1); - disconnect(connection2); - QCOMPARE(nGET, 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - - // Test 3: modified file upload aborted, with good checksums - fakeFolder.localModifier().appendByte("A/a0"); - QVERIFY(!fakeFolder.syncOnce()); // error: abort! - - // Set the remote checksum -- the test setup doesn't do it automatically - QVERIFY(!moveChecksumHeader.isEmpty()); - fakeFolder.remoteModifier().find("A/a0")->checksums = moveChecksumHeader; - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(nGET, 0); // no new download, just a metadata update! - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - - // Test 4: New file, that gets deleted locally before the next sync - fakeFolder.localModifier().insert("A/a3", size); - QVERIFY(!fakeFolder.syncOnce()); // error: abort! - fakeFolder.localModifier().remove("A/a3"); - - // bug: in this case we must expect a re-download of A/A3 - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(nGET, 1); - QVERIFY(fakeFolder.currentLocalState().find("A/a3")); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } - - // Check what happens when we abort during the final MOVE and the - // the final MOVE is short enough for the abort-delay to help - void testLateAbortRecoverable() - { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "chunking", "1.0" } } }, { "checksums", QVariantMap{ { "supportedTypes", QStringList() << "SHA1" } } } }); - const int size = 15 * 1000 * 1000; - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - // Make the MOVE never reply, but trigger a client-abort and apply the change remotely - QObject parent; - const auto responseDelay = 200ms; // smaller than abort-wait timeout - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") { - QTimer::singleShot(50ms, &parent, [&]() { fakeFolder.syncEngine().abort(); }); - return new DelayedReply(responseDelay, fakeFolder.uploadState(), fakeFolder.remoteModifier(), op, request, &parent); - } - return nullptr; - }); - - // Test 1: NEW file aborted - fakeFolder.localModifier().insert("A/a0", size); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // Test 2: modified file upload aborted - fakeFolder.localModifier().appendByte("A/a0"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } - - // We modify the file locally after it has been partially uploaded - void testRemoveStale1() { - - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const int size = 10 * 1000 * 1000; // 10 MB - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - partialUpload(fakeFolder, "A/a0", size); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - auto chunkingId = fakeFolder.uploadState().children.first().name; - - - fakeFolder.localModifier().setContents("A/a0", 'B'); - fakeFolder.localModifier().appendByte("A/a0"); - - QVERIFY(fakeFolder.syncOnce()); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size + 1); - // A different chunk id was used, and the previous one is removed - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - QVERIFY(fakeFolder.uploadState().children.first().name != chunkingId); - } - - // We remove the file locally after it has been partially uploaded - void testRemoveStale2() { - - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const int size = 10 * 1000 * 1000; // 10 MB - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - partialUpload(fakeFolder, "A/a0", size); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - - fakeFolder.localModifier().remove("A/a0"); - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.uploadState().children.count(), 0); - } - - - void testCreateConflictWhileSyncing() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const int size = 10 * 1000 * 1000; // 10 MB - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - // Put a file on the server and download it. - fakeFolder.remoteModifier().insert("A/a0", size); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // Modify the file localy and start the upload - fakeFolder.localModifier().setContents("A/a0", 'B'); - fakeFolder.localModifier().appendByte("A/a0"); - - // But in the middle of the sync, modify the file on the server - QMetaObject::Connection con = QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress, - [&](const ProgressInfo &progress) { - if (progress.completedSize() > (progress.totalSize() / 2 )) { - fakeFolder.remoteModifier().setContents("A/a0", 'C'); - QObject::disconnect(con); - } - }); - - QVERIFY(!fakeFolder.syncOnce()); - // There was a precondition failed error, this means wen need to sync again - QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), ImmediateFollowUp); - - QCOMPARE(fakeFolder.uploadState().children.count(), 1); // We did not clean the chunks at this point - - // Now we will download the server file and create a conflict - QVERIFY(fakeFolder.syncOnce()); - auto localState = fakeFolder.currentLocalState(); - - // A0 is the one from the server - QCOMPARE(localState.find("A/a0")->size, size); - QCOMPARE(localState.find("A/a0")->contentChar, 'C'); - - // There is a conflict file with our version - auto &stateAChildren = localState.find("A")->children; - auto it = std::find_if(stateAChildren.cbegin(), stateAChildren.cend(), [&](const FileInfo &fi) { - return fi.name.startsWith("a0 (conflicted copy"); - }); - QVERIFY(it != stateAChildren.cend()); - QCOMPARE(it->contentChar, 'B'); - QCOMPARE(it->size, size+1); - - // Remove the conflict file so the comparison works! - fakeFolder.localModifier().remove("A/" + it->name); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - QCOMPARE(fakeFolder.uploadState().children.count(), 0); // The last sync cleaned the chunks - } - - void testModifyLocalFileWhileUploading() { - - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const int size = 10 * 1000 * 1000; // 100 MB - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - fakeFolder.localModifier().insert("A/a0", size); - - // middle of the sync, modify the file - QMetaObject::Connection con = QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress, - [&](const ProgressInfo &progress) { - if (progress.completedSize() > (progress.totalSize() / 2 )) { - fakeFolder.localModifier().setContents("A/a0", 'B'); - fakeFolder.localModifier().appendByte("A/a0"); - QObject::disconnect(con); - } - }); - - QVERIFY(!fakeFolder.syncOnce()); - - // There should be a followup sync - QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), ImmediateFollowUp); - - QCOMPARE(fakeFolder.uploadState().children.count(), 1); // We did not clean the chunks at this point - auto chunkingId = fakeFolder.uploadState().children.first().name; - - // Now we make a new sync which should upload the file for good. - QVERIFY(fakeFolder.syncOnce()); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size+1); - - // A different chunk id was used, and the previous one is removed - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - QVERIFY(fakeFolder.uploadState().children.first().name != chunkingId); - } - - - void testResumeServerDeletedChunks() { - - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const int size = 30 * 1000 * 1000; // 30 MB - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - partialUpload(fakeFolder, "A/a0", size); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - auto chunkingId = fakeFolder.uploadState().children.first().name; - - // Delete the chunks on the server - fakeFolder.uploadState().children.clear(); - QVERIFY(fakeFolder.syncOnce()); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); - - // A different chunk id was used - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - QVERIFY(fakeFolder.uploadState().children.first().name != chunkingId); - } - - // Check what happens when the connection is dropped on the PUT (non-chunking) or MOVE (chunking) - // for on the issue #5106 - void connectionDroppedBeforeEtagRecieved_data() - { - QTest::addColumn("chunking"); - QTest::newRow("big file") << true; - QTest::newRow("small file") << false; - } - void connectionDroppedBeforeEtagRecieved() - { - QFETCH(bool, chunking); - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "chunking", "1.0" } } }, { "checksums", QVariantMap{ { "supportedTypes", QStringList() << "SHA1" } } } }); - const int size = chunking ? 1 * 1000 * 1000 : 300; - setChunkSize(fakeFolder.syncEngine(), 300 * 1000); - - // Make the MOVE never reply, but trigger a client-abort and apply the change remotely - QByteArray checksumHeader; - int nGET = 0; - QScopedValueRollback setHttpTimeout(AbstractNetworkJob::httpTimeout, 1s); - const auto responseDelay = 24h; // much bigger than http timeout (so a timeout will occur) - // This will perform the operation on the server, but the reply will not come to the client - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * { - if (!chunking) { - Q_ASSERT(!request.url().path().contains("/uploads/") - && "Should not touch uploads endpoint when not chunking"); - } - if (!chunking && op == QNetworkAccessManager::PutOperation) { - checksumHeader = request.rawHeader("OC-Checksum"); - return new DelayedReply(responseDelay, fakeFolder.remoteModifier(), op, request, outgoingData->readAll(), &fakeFolder.syncEngine()); - } else if (chunking && request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") { - checksumHeader = request.rawHeader("OC-Checksum"); - return new DelayedReply(responseDelay, fakeFolder.uploadState(), fakeFolder.remoteModifier(), op, request, &fakeFolder.syncEngine()); - } else if (op == QNetworkAccessManager::GetOperation) { - nGET++; - } - return nullptr; - }); - - // Test 1: a NEW file - fakeFolder.localModifier().insert("A/a0", size); - QVERIFY(!fakeFolder.syncOnce()); // timeout! - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // but the upload succeeded - QVERIFY(!checksumHeader.isEmpty()); - fakeFolder.remoteModifier().find("A/a0")->checksums = checksumHeader; // The test system don't do that automatically - // Should be resolved properly - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(nGET, 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // Test 2: Modify the file further - fakeFolder.localModifier().appendByte("A/a0"); - QVERIFY(!fakeFolder.syncOnce()); // timeout! - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // but the upload succeeded - fakeFolder.remoteModifier().find("A/a0")->checksums = checksumHeader; - // modify again, should not cause conflict - fakeFolder.localModifier().appendByte("A/a0"); - QVERIFY(!fakeFolder.syncOnce()); // now it's trying to upload the modified file - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - fakeFolder.remoteModifier().find("A/a0")->checksums = checksumHeader; - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(nGET, 0); - } - - void testPercentEncoding() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const int size = 5 * 1000 * 1000; - setChunkSize(fakeFolder.syncEngine(), 1 * 1000 * 1000); - - fakeFolder.localModifier().insert("A/file % \u20ac", size); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // Only the second upload contains an "If" header - fakeFolder.localModifier().appendByte("A/file % \u20ac"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } - - // Test uploading large files (2.5GiB) - void testVeryBigFiles() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().account()->setCapabilities(TestCapabilities); - const qint64 size = 2.5 * 1024 * 1024 * 1024; // 2.5 GiB - - // Partial upload of big files - partialUpload(fakeFolder, "A/a0", size); - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - auto chunkingId = fakeFolder.uploadState().children.first().name; - - // Now resume - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size); - - // The same chunk id was re-used - QCOMPARE(fakeFolder.uploadState().children.count(), 1); - QCOMPARE(fakeFolder.uploadState().children.first().name, chunkingId); - - - // Upload another file again, this time without interruption - fakeFolder.localModifier().appendByte("A/a0"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size + 1); - } - - -}; - -QTEST_GUILESS_MAIN(TestChunkingNG) -#include "testchunkingng.moc" diff --git a/test/testconcaturl.cpp b/test/testconcaturl.cpp index 16f76ed34e2..c63b440452b 100644 --- a/test/testconcaturl.cpp +++ b/test/testconcaturl.cpp @@ -23,15 +23,15 @@ static QueryItems make() return QueryItems(); } -static QueryItems make(QString key, QString value) +static QueryItems make(const QString &key, const QString &value) { QueryItems q; q.append(qMakePair(key, value)); return q; } -static QueryItems make(QString key1, QString value1, - QString key2, QString value2) +static QueryItems make(const QString &key1, const QString &value1, + const QString &key2, const QString &value2) { QueryItems q; q.append(qMakePair(key1, value1)); @@ -42,19 +42,19 @@ static QueryItems make(QString key1, QString value1, class TestConcatUrl: public QObject { Q_OBJECT -private slots: +private Q_SLOTS: void testFolder() { QFETCH(QString, base); QFETCH(QString, concat); QFETCH(QueryItems, query); QFETCH(QString, expected); - QUrl baseUrl("http://example.com" + base); + QUrl baseUrl(QStringLiteral("http://example.com") + base); QUrlQuery urlQuery; urlQuery.setQueryItems(query); QUrl resultUrl = Utility::concatUrlPath(baseUrl, concat, urlQuery); QString result = QString::fromUtf8(resultUrl.toEncoded()); - QString expectedFull = "http://example.com" + expected; + QString expectedFull = QStringLiteral("http://example.com") + expected; QCOMPARE(result, expectedFull); } @@ -88,16 +88,16 @@ private slots: // Tests about query args QTest::newRow("query1") - << "/baa" - << "/foo" - << make("a=a", "b=b", - "c", "d") - << "/baa/foo?a%3Da=b%3Db&c=d"; + << "/baa" + << "/foo" + << make(QStringLiteral("a=a"), QStringLiteral("b=b"), + QStringLiteral("c"), QStringLiteral("d")) + << "/baa/foo?a%3Da=b%3Db&c=d"; QTest::newRow("query2") - << "" - << "" - << make("foo", "bar") - << "?foo=bar"; + << "" + << "" + << make(QStringLiteral("foo"), QStringLiteral("bar")) + << "?foo=bar"; } }; diff --git a/test/testconnectionvalidator/capabilities.json.in b/test/testconnectionvalidator/capabilities.json.in new file mode 100644 index 00000000000..9f056b19278 --- /dev/null +++ b/test/testconnectionvalidator/capabilities.json.in @@ -0,0 +1,179 @@ +{ + "ocs": { + "meta": { + "status": "ok", + "statuscode": 100, + "message": "OK" + }, + "data": { + "capabilities": { + "core": { + "pollinterval": 60, + "webdav-root": "remote.php/webdav", + "status": { + "installed": true, + "maintenance": false, + "needsDbUpgrade": false, + "version": "@{version}", + "versionstring": "10.11.0", + "edition": "Community", + "productname": "Infinite Scale", + "product": "Infinite Scale", + "productversion": "@{productversion}" + }, + "support-url-signing": true, + "support-sse": true + }, + "checksums": { + "supportedTypes": [ + "sha1", + "md5", + "adler32" + ], + "preferredUploadType": "sha1" + }, + "files": { + "privateLinks": true, + "bigfilechunking": false, + "undelete": true, + "versioning": true, + "favorites": false, + "full_text_search": false, + "tags": true, + "blacklisted_files": [], + "tus_support": { + "version": "1.0.0", + "resumable": "1.0.0", + "extension": "creation,creation-with-upload", + "max_chunk_size": 100000000, + "http_method_override": "" + }, + "archivers": [ + { + "enabled": true, + "version": "2.0.0", + "formats": [ + "tar", + "zip" + ], + "archiver_url": "/archiver", + "max_num_files": "10000", + "max_size": "1073741824" + } + ], + "app_providers": [ + { + "enabled": true, + "version": "1.1.0", + "apps_url": "/app/list", + "open_url": "/app/open", + "open_web_url": "/app/open-with-web", + "new_url": "/app/new" + } + ] + }, + "dav": { + "chunking": "", + "trashbin": "1.0", + "reports": [ + "search-files" + ], + "chunkingParallelUploadDisabled": false + }, + "files_sharing": { + "api_enabled": true, + "resharing": true, + "group_sharing": true, + "sharing_roles": true, + "deny_access": false, + "auto_accept_share": true, + "share_with_group_members_only": true, + "share_with_membership_groups_only": true, + "search_min_length": 3, + "default_permissions": 22, + "user_enumeration": { + "enabled": true, + "group_members_only": true + }, + "federation": { + "outgoing": false, + "incoming": false + }, + "public": { + "enabled": true, + "send_mail": true, + "social_share": true, + "upload": true, + "multiple": true, + "supports_upload_only": true, + "password": { + "enforced_for": { + "read_only": false, + "read_write": false, + "read_write_delete": false, + "upload_only": false + }, + "enforced": false + }, + "expire_date": { + "enabled": false + }, + "can_edit": true, + "alias": true + }, + "user": { + "send_mail": true, + "profile_picture": false, + "settings": [ + { + "enabled": true, + "version": "1.0.0" + } + ], + "expire_date": { + "enabled": true + } + } + }, + "spaces": { + "version": "1.0.0", + "enabled": true, + "projects": true, + "share_jail": true, + "max_quota": 0 + }, + "graph": { + "personal-data-export": true, + "users": { + "read_only_attributes": [ + "user.onPremisesSamAccountName", + "user.displayName", + "user.mail", + "user.passwordProfile", + "user.appRoleAssignments" + ], + "create_disabled": true, + "delete_disabled": true, + "change_password_self_disabled": true + } + }, + "notifications": { + "ocs-endpoints": [ + "list", + "get", + "delete" + ] + } + }, + "version": { + "major": 10, + "minor": 11, + "micro": 0, + "string": "10.11.0", + "edition": "Community", + "product": "Infinite Scale", + "productversion": "4.0.5" + } + } + } +} diff --git a/test/testconnectionvalidator/status.php.json.in b/test/testconnectionvalidator/status.php.json.in new file mode 100644 index 00000000000..060ca6b4e5c --- /dev/null +++ b/test/testconnectionvalidator/status.php.json.in @@ -0,0 +1,11 @@ +{ + "installed": true, + "maintenance": @{maintenance}, + "needsDbUpgrade": false, + "version": "10.11.0.0", + "versionstring": "10.11.0", + "edition": "Community", + "productname": "Infinite Scale", + "product": "Infinite Scale", + "productversion": "4.0.5" +} diff --git a/test/testconnectionvalidator/testconnectionvalidator.cpp b/test/testconnectionvalidator/testconnectionvalidator.cpp new file mode 100644 index 00000000000..284229f5dee --- /dev/null +++ b/test/testconnectionvalidator/testconnectionvalidator.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) by Hannah von Reth + * + * 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 + +#include "gui/connectionvalidator.h" +#include "libsync/abstractnetworkjob.h" +#include "libsync/httplogger.h" +#include "resources/template.h" + +#include "testutils/syncenginetestutils.h" +#include "testutils/testutils.h" + +using namespace std::chrono_literals; + +using namespace OCC; + +class TestConnectionValidator : public QObject +{ + Q_OBJECT + + enum class FailStage { Invalid, StatusPhp, AuthValidation, Capabilities, UserInfo }; + + // we can't use QMap direclty with QFETCH + using Values = QMap; + auto getPayload(const QString &payloadName) + { + QFile f(QStringLiteral(SOURCEDIR "/test/testconnectionvalidator/%1").arg(payloadName)); + Q_ASSERT(f.open(QIODevice::ReadOnly)); + return f.readAll(); + } + + auto getPayloadTemplated(const QString &payloadName, const QMap &values) + { + return Resources::Template::renderTemplate(QString::fromUtf8(getPayload(payloadName)), values).toUtf8(); + } + +private Q_SLOTS: + + + void initTestCase() { AbstractNetworkJob::httpTimeout = 1s; } + + void testStatusPhp_data() + { + QTest::addColumn("failStage"); + QTest::addColumn("values"); + QTest::addColumn("status"); + + const auto defaultValue = Values{{QStringLiteral("maintenance"), QStringLiteral("false")}, {QStringLiteral("version"), QStringLiteral("10.11.0.0")}, + {QStringLiteral("productversion"), QStringLiteral("4.0.5")}}; + + QTest::newRow("stauts.php maintenance") << FailStage::StatusPhp << [value = defaultValue]() mutable { + value[QStringLiteral("maintenance")] = QStringLiteral("true"); + return value; + }() << ConnectionValidator::MaintenanceMode; + QTest::newRow("stauts.php ServiceUnavailable") << FailStage::StatusPhp << defaultValue << ConnectionValidator::StatusNotFound; + QTest::newRow("stauts.php UnsupportedClient") << FailStage::StatusPhp << defaultValue << ConnectionValidator::ClientUnsupported; + + QTest::newRow("auth 401") << FailStage::AuthValidation << defaultValue << ConnectionValidator::CredentialsWrong; + QTest::newRow("auth timeout") << FailStage::AuthValidation << defaultValue << ConnectionValidator::Timeout; + QTest::newRow("auth ServiceUnavailable") << FailStage::AuthValidation << defaultValue << ConnectionValidator::ServiceUnavailable; + QTest::newRow("auth UnsupportedClient") << FailStage::AuthValidation << defaultValue << ConnectionValidator::ClientUnsupported; + + QTest::newRow("capabilites timeout") << FailStage::Capabilities << defaultValue << ConnectionValidator::CredentialsWrong; + QTest::newRow("capabilites 401") << FailStage::Capabilities << defaultValue << ConnectionValidator::Timeout; + QTest::newRow("capabilites unsupported server") << FailStage::Capabilities << [value = defaultValue]() mutable { + value[QStringLiteral("version")] = QStringLiteral("7.0"); + value[QStringLiteral("productversion")] = QString(); + return value; + }() << ConnectionValidator::ServerVersionMismatch; + + + QTest::newRow("user info timeout") << FailStage::UserInfo << defaultValue << ConnectionValidator::Timeout; + QTest::newRow("user info 401") << FailStage::UserInfo << defaultValue << ConnectionValidator::CredentialsWrong; + QTest::newRow("success") << FailStage::UserInfo << defaultValue << ConnectionValidator::Connected; + } + + void testStatusPhp() + { + QFETCH(FailStage, failStage); + QFETCH(Values, values); + QFETCH(ConnectionValidator::Status, status); + + auto reachedStage = FailStage::Invalid; + FakeFolder fakeFolder({}); + + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { + const auto path = request.url().path(); + const auto verb = HttpLogger::requestVerb(op, request); + if (op == QNetworkAccessManager::GetOperation) { + if (path.endsWith(QLatin1String("status.php"))) { + reachedStage = FailStage::StatusPhp; + if (failStage == FailStage::StatusPhp) { + if (status == ConnectionValidator::Timeout) { + return new FakeHangingReply(op, request, this); + } else if (status == ConnectionValidator::StatusNotFound) { + return new FakeErrorReply(op, request, this, 500); + } else if (status == ConnectionValidator::ClientUnsupported) { + return new FakeErrorReply(op, request, this, 403); + } + } + return new FakePayloadReply(op, request, getPayloadTemplated(QStringLiteral("status.php.json.in"), values), this); + } else if (path.endsWith(QLatin1String("capabilities"))) { + reachedStage = FailStage::Capabilities; + if (failStage == FailStage::Capabilities) { + if (status == ConnectionValidator::CredentialsWrong) { + return new FakeErrorReply(op, request, this, 401); + } else if (status == ConnectionValidator::Timeout) { + return new FakeHangingReply(op, request, this); + } + } + return new FakePayloadReply(op, request, getPayloadTemplated(QStringLiteral("capabilities.json.in"), values), this); + } else if (path.endsWith(QLatin1String("user"))) { + reachedStage = FailStage::UserInfo; + if (failStage == FailStage::UserInfo) { + if (status == ConnectionValidator::CredentialsWrong) { + return new FakeErrorReply(op, request, this, 401); + } else if (status == ConnectionValidator::Timeout) { + return new FakeHangingReply(op, request, this); + } + } + return new FakePayloadReply(op, request, getPayload(QStringLiteral("user.json")), this); + } + } else if (failStage == FailStage::AuthValidation && verb == "PROPFIND") { + reachedStage = FailStage::AuthValidation; + if (status == ConnectionValidator::CredentialsWrong) { + return new FakeErrorReply(op, request, this, 401); + } else if (status == ConnectionValidator::Timeout) { + return new FakeHangingReply(op, request, this); + } else if (status == ConnectionValidator::ServiceUnavailable) { + return new FakeErrorReply(op, request, this, 503); + } else if (status == ConnectionValidator::ClientUnsupported) { + return new FakeErrorReply(op, request, this, 403); + } + } + return nullptr; + }); + + ConnectionValidator val(fakeFolder.account(), nullptr); + val.checkServer(ConnectionValidator::ValidationMode::ValidateAuthAndUpdate); + + QSignalSpy spy(&val, &ConnectionValidator::connectionResult); + QVERIFY(spy.wait()); + QCOMPARE(spy.first().first().value(), status); + QCOMPARE(reachedStage, failStage); + } +}; + +QTEST_MAIN(TestConnectionValidator) +#include "testconnectionvalidator.moc" diff --git a/test/testconnectionvalidator/user.json b/test/testconnectionvalidator/user.json new file mode 100644 index 00000000000..fa09570bce8 --- /dev/null +++ b/test/testconnectionvalidator/user.json @@ -0,0 +1,17 @@ +{ + "ocs": { + "meta": { + "status": "ok", + "statuscode": 200, + "message": "OK", + "totalitems": "", + "itemsperpage": "" + }, + "data": { + "id": "admin", + "display-name": "test", + "email": "test@test.com", + "language": "en" + } + } +} diff --git a/test/testcookies.cpp b/test/testcookies.cpp deleted file mode 100644 index a5053abd9c1..00000000000 --- a/test/testcookies.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - This software is in the public domain, furnished "as is", without technical - support, and with no warranty, express or implied, as to its usefulness for - any purpose. -*/ - -#include - -#include "libsync/cookiejar.h" - -using namespace OCC; - -class TestCookies : public QObject -{ - Q_OBJECT - -private slots: - void testCookies() - { - QTemporaryDir tmp; - const QString nonexistingPath = tmp.filePath("someNonexistingDir/test.db"); - QNetworkCookie cookieA = QNetworkCookie("foo", "bar"); - // tomorrow rounded - cookieA.setExpirationDate(QDateTime(QDateTime::currentDateTimeUtc().addDays(1).date())); - const QList cookies = {cookieA, QNetworkCookie("foo2", "bar")}; - CookieJar jar; - jar.setAllCookies(cookies); - QCOMPARE(cookies, jar.allCookies()); - QVERIFY(jar.save(tmp.filePath("test.db"))); - // ensure we are able to create a cookie jar in a non exisitning folder (mkdir) - QVERIFY(jar.save(nonexistingPath)); - - CookieJar jar2; - QVERIFY(jar2.restore(nonexistingPath)); - // here we should have only cookieA as the second one was a session cookie - QCOMPARE(QList{cookieA}, jar2.allCookies()); - - } - -}; - -QTEST_APPLESS_MAIN(TestCookies) -#include "testcookies.moc" diff --git a/test/testcredentialmanager.cpp b/test/testcredentialmanager.cpp index 9afea5ef019..1eef7483988 100644 --- a/test/testcredentialmanager.cpp +++ b/test/testcredentialmanager.cpp @@ -44,7 +44,8 @@ private Q_SLOTS: QTest::newRow("bool") << QVariant::fromValue(true); QTest::newRow("int") << QVariant::fromValue(1); - QTest::newRow("map") << QVariant::fromValue(QVariantMap { { "foo", QColor(Qt::red) }, { "bar", "42" } }); + QTest::newRow("map") << QVariant::fromValue( + QVariantMap{{QStringLiteral("foo"), QColor(Qt::red).name()}, {QStringLiteral("bar"), QStringLiteral("42")}}); } void testSetGet() @@ -66,6 +67,7 @@ private Q_SLOTS: #endif QCOMPARE(setJob->error(), QKeychain::NoError); auto getJob = creds->get(key); + QVERIFY(getJob != nullptr); setFallbackEnabled(getJob->_job); connect(getJob, &CredentialJob::finished, this, [getJob, data, creds, this] { QCOMPARE(getJob->error(), QKeychain::NoError); @@ -76,7 +78,7 @@ private Q_SLOTS: } connect(jobs[0], &QKeychain::Job::finished, this, [creds, data, jobs, this] { QCOMPARE(jobs[0]->error(), QKeychain::NoError); - QVERIFY(creds->knownKeys().isEmpty()); + QVERIFY(creds->isEmpty()); _finished = true; }); }); @@ -111,8 +113,8 @@ private Q_SLOTS: QTest::qWait(1000); for (const auto s : spies) { QCOMPARE(s->count(), 1); - s->deleteLater(); } + qDeleteAll(spies); spies.clear(); { auto jobs = creds->clear(QStringLiteral("foo")); @@ -130,8 +132,8 @@ private Q_SLOTS: QTest::qWait(1000); for (const auto s : spies) { QCOMPARE(s->count(), 1); - s->deleteLater(); } + qDeleteAll(spies); } } }; diff --git a/test/testdatabaseerror.cpp b/test/testdatabaseerror.cpp index 871514a502a..1073e724600 100644 --- a/test/testdatabaseerror.cpp +++ b/test/testdatabaseerror.cpp @@ -16,53 +16,84 @@ class TestDatabaseError : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } + void testDatabaseError() { /* This test will make many iteration, at each iteration, the iᵗʰ database access will fail. * The test ensure that if there is a failure, the next sync recovers. And if there was * no error, then everything was sync'ed properly. */ + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + if (vfsMode == Vfs::WindowsCfApi) { + QSKIP("Known to be broken, see https://github.com/owncloud/client-desktop-vfs-win/issues/22"); + } + FileInfo finalState; + + auto checkAgainstFinalState = [&finalState](const FileInfo ¤tRemoteState) { + // The final state should be the same for every iteration, EXCEPT for the lastModified times: + // these will differ (well, increase), because files are re-created with every iteration. + return currentRemoteState.equals(finalState, FileInfo::IgnoreLastModified); + }; + for (int count = 0; true; ++count) { qInfo() << "Starting Iteration" << count; - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); // Do a couple of changes - fakeFolder.remoteModifier().insert("A/a0"); - fakeFolder.remoteModifier().appendByte("A/a1"); - fakeFolder.remoteModifier().remove("A/a2"); - fakeFolder.remoteModifier().rename("S/s1", "S/s1_renamed"); - fakeFolder.remoteModifier().mkdir("D"); - fakeFolder.remoteModifier().mkdir("D/subdir"); - fakeFolder.remoteModifier().insert("D/subdir/file"); - fakeFolder.localModifier().insert("B/b0"); - fakeFolder.localModifier().appendByte("B/b1"); - fakeFolder.remoteModifier().remove("B/b2"); - fakeFolder.localModifier().mkdir("NewDir"); - fakeFolder.localModifier().rename("C", "NewDir/C"); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a0")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.remoteModifier().remove(QStringLiteral("A/a2")); + fakeFolder.remoteModifier().rename(QStringLiteral("S/s1"), QStringLiteral("S/s1_renamed")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("D")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("D/subdir")); + fakeFolder.remoteModifier().insert(QStringLiteral("D/subdir/file")); + fakeFolder.localModifier().insert(QStringLiteral("B/b0")); + fakeFolder.localModifier().appendByte(QStringLiteral("B/b1")); + fakeFolder.remoteModifier().remove(QStringLiteral("B/b2")); + fakeFolder.localModifier().mkdir(QStringLiteral("NewDir")); + fakeFolder.localModifier().rename(QStringLiteral("C"), QStringLiteral("NewDir/C")); // Set the counter - fakeFolder.syncJournal().autotestFailCounter = count; + fakeFolder.syncJournal()->autotestFailCounter = count; // run the sync - bool result = fakeFolder.syncOnce(); + bool result = fakeFolder.applyLocalModificationsAndSync(); qInfo() << "Result of iteration" << count << "was" << result; - if (fakeFolder.syncJournal().autotestFailCounter >= 0) { + if (fakeFolder.syncJournal()->autotestFailCounter >= 0) { // No error was thrown, we are finished QVERIFY(result); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.currentRemoteState(), finalState); + QVERIFY(checkAgainstFinalState(fakeFolder.currentRemoteState())); return; } if (!result) { - fakeFolder.syncJournal().autotestFailCounter = -1; + fakeFolder.syncJournal()->autotestFailCounter = -1; // esnure we actually sync and are not blocked by ignored files... - fakeFolder.syncJournal().wipeErrorBlacklist(); + fakeFolder.syncJournal()->wipeErrorBlacklist(); // Try again QVERIFY(fakeFolder.syncOnce()); } @@ -71,8 +102,7 @@ private slots: if (count == 0) { finalState = fakeFolder.currentRemoteState(); } else { - // the final state should be the same for every iteration - QCOMPARE(fakeFolder.currentRemoteState(), finalState); + QVERIFY(checkAgainstFinalState(fakeFolder.currentRemoteState())); } } } diff --git a/test/testdownload.cpp b/test/testdownload.cpp index d67901b33bf..1d552ee06cf 100644 --- a/test/testdownload.cpp +++ b/test/testdownload.cpp @@ -5,29 +5,38 @@ * */ -#include + +#include "owncloudpropagator.h" +#include "syncengine.h" #include "testutils/syncenginetestutils.h" -#include -#include + +#include + +#ifndef Q_OS_WIN +#include +#endif using namespace std::chrono_literals; using namespace OCC; -static constexpr qint64 stopAfter = 3'123'668; +static constexpr auto stopAfter = 3_MiB; -/* A FakeGetReply that sends max 'fakeSize' bytes, but whose ContentLength has the corect size */ +/** A FakeGetReply that sends max 'fakeSize' bytes, but whose ContentLength has the corect size */ class BrokenFakeGetReply : public FakeGetReply { Q_OBJECT public: using FakeGetReply::FakeGetReply; - int fakeSize = stopAfter; + qint64 fakeSize = stopAfter; qint64 bytesAvailable() const override { - if (aborted) - return 0; - return std::min(size, fakeSize) + QIODevice::bytesAvailable(); + switch (state) { + case State::Ok: + return std::min(size, fakeSize) + QIODevice::bytesAvailable(); + default: + return FakeGetReply::bytesAvailable(); + } } qint64 readData(char *data, qint64 maxlen) override @@ -56,79 +65,177 @@ class TestDownload : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } void testResume() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().setIgnoreHiddenFiles(true); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); - auto size = 30 * 1000 * 1000; - fakeFolder.remoteModifier().insert("A/a0", size); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + fakeFolder.syncEngine()->setIgnoreHiddenFiles(true); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + auto size = 30_MiB; + fakeFolder.remoteModifier().insert(QStringLiteral("A/a0"), size); // First, download only the first 3 MB of the file fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/a0")) { + if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith(QLatin1String("A/a0"))) { return new BrokenFakeGetReply(fakeFolder.remoteModifier(), op, request, this); } return nullptr; }); - QVERIFY(!fakeFolder.syncOnce()); // The sync must fail because not all the file was downloaded - QCOMPARE(getItem(completeSpy, "A/a0")->_status, SyncFileItem::SoftError); - QCOMPARE(getItem(completeSpy, "A/a0")->_errorString, QString("The file could not be downloaded completely.")); - QVERIFY(fakeFolder.syncEngine().isAnotherSyncNeeded()); + if (filesAreDehydrated) { + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // The sync should succeed, because there are no downloads, and only the placeholders get created + } else { + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); // The sync should fail because not all the files were downloaded + QCOMPARE(getItem(completeSpy, QStringLiteral("A/a0"))->_status, SyncFileItem::SoftError); + QCOMPARE(getItem(completeSpy, QStringLiteral("A/a0"))->_errorString, QStringLiteral("The file could not be downloaded completely.")); + QVERIFY(fakeFolder.syncEngine()->isAnotherSyncNeeded()); + + // Now, we need to restart, this time, it should resume. + QByteArray rangeRequest; + QByteArray rangeReply; + OperationCounter counter; + + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *device) -> QNetworkReply * { + counter.serverOverride(op, request, device); + if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith(QLatin1String("A/a0"))) { + rangeRequest = request.rawHeader("Range"); + auto reply = new FakeGetReply(fakeFolder.remoteModifier(), op, request, this); + connect(reply, &FakeGetReply::metaDataChanged, reply, [&, reply] { + rangeReply = reply->rawHeader("Content-Range"); + }); + return reply; + } + return nullptr; + }); + fakeFolder.syncJournal()->wipeErrorBlacklist(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // now this should succeed + QCOMPARE(rangeRequest, QByteArrayLiteral("bytes=") + QByteArray::number(stopAfter) + '-'); + QCOMPARE(rangeReply, QByteArrayLiteral("bytes ") + QByteArray::number(stopAfter) + '-'); + QCOMPARE(counter.nGET, 1); + + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } + } + - // Now, we need to restart, this time, it should resume. - QByteArray ranges; + void testResumeInNextSync() + { + /* + * Same as testResume but we simulate a new sync from the Folder class, with a partial discovery. + */ + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + fakeFolder.syncEngine()->setIgnoreHiddenFiles(true); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + constexpr auto size = 30_MiB; + fakeFolder.remoteModifier().insert(QStringLiteral("A/a0"), size); + + // First, download only the first 3 MB of the file fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/a0")) { - ranges = request.rawHeader("Range"); + if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith(QLatin1String("A/a0"))) { + return new BrokenFakeGetReply(fakeFolder.remoteModifier(), op, request, this); } return nullptr; }); - QVERIFY(fakeFolder.syncOnce()); // now this succeeds - QCOMPARE(ranges, QByteArray("bytes=" + QByteArray::number(stopAfter) + "-")); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + if (filesAreDehydrated) { + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // The sync should succeed, because there are no downloads, and only the placeholders get created + } else { + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); // The sync should fail because not all the files were downloaded + QCOMPARE(getItem(completeSpy, QStringLiteral("A/a0"))->_status, SyncFileItem::SoftError); + QCOMPARE(getItem(completeSpy, QStringLiteral("A/a0"))->_errorString, QStringLiteral("The file could not be downloaded completely.")); + QVERIFY(fakeFolder.syncEngine()->isAnotherSyncNeeded()); + + QByteArray ranges; + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { + if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith(QLatin1String("A/a0"))) { + ranges = request.rawHeader("Range"); + } + return nullptr; + }); + fakeFolder.syncJournal()->wipeErrorBlacklist(); + // perform a partial sync + fakeFolder.syncEngine()->setLocalDiscoveryOptions(OCC::LocalDiscoveryStyle::DatabaseAndFilesystem, {}); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // now this should succeed + QCOMPARE(ranges, QByteArray("bytes=" + QByteArray::number(stopAfter) + "-")); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } } void testErrorMessage () { // This test's main goal is to test that the error string from the server is shown in the UI - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().setIgnoreHiddenFiles(true); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); - auto size = 3'500'000; - fakeFolder.remoteModifier().insert("A/broken", size); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); - QByteArray serverMessage = "The file was not downloaded because the tests wants so!"; + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + fakeFolder.syncEngine()->setIgnoreHiddenFiles(true); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + const auto size = 3_MiB + 500_KiB; + fakeFolder.remoteModifier().insert(QStringLiteral("A/broken"), size); + + QString serverMessage = QStringLiteral("The file was not downloaded because the tests wants so!"); // First, download only the first 3 MB of the file fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/broken")) { + if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith(QLatin1String("A/broken"))) { return new FakeErrorReply(op, request, this, 400, "\n" "\n" "Sabre\\DAV\\Exception\\Forbidden\n" - ""+serverMessage+"\n" - ""); + "" + + serverMessage.toUtf8() + + "\n" + ""); } return nullptr; }); bool timedOut = false; - QTimer::singleShot(10s, &fakeFolder.syncEngine(), [&]() { timedOut = true; fakeFolder.syncEngine().abort(); }); - QVERIFY(!fakeFolder.syncOnce()); // Fail because A/broken - QVERIFY(!timedOut); - QCOMPARE(getItem(completeSpy, "A/broken")->_status, SyncFileItem::NormalError); - QVERIFY(getItem(completeSpy, "A/broken")->_errorString.contains(serverMessage)); + QTimer::singleShot(10s, fakeFolder.syncEngine(), [&]() { + timedOut = true; + fakeFolder.syncEngine()->abort({}); + }); + if (filesAreDehydrated) { + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // Success, because files are never downloaded + } else { + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); // Fail because A/broken + QVERIFY(!timedOut); + QCOMPARE(getItem(completeSpy, QStringLiteral("A/broken"))->_status, SyncFileItem::NormalError); + QVERIFY(getItem(completeSpy, QStringLiteral("A/broken"))->_errorString.contains(serverMessage)); + } } void serverMaintenence() { // Server in maintenance must abort the sync. - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.remoteModifier().insert("A/broken"); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + fakeFolder.remoteModifier().insert(QStringLiteral("A/broken")); fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { if (op == QNetworkAccessManager::GetOperation) { return new FakeErrorReply(op, request, this, 503, @@ -141,88 +248,32 @@ private slots: return nullptr; }); - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); - QVERIFY(!fakeFolder.syncOnce()); // Fail because A/broken - // FatalError means the sync was aborted, which is what we want - QCOMPARE(getItem(completeSpy, "A/broken")->_status, SyncFileItem::FatalError); - QVERIFY(getItem(completeSpy, "A/broken")->_errorString.contains("System in maintenance mode")); - } - - void testMoveFailsInAConflict() { -#ifdef Q_OS_WIN - QSKIP("Not run on windows because permission on directory does not do what is expected"); -#else - if (getuid() == 0) { - QSKIP("The permissions have no effect on the root user"); + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + if (filesAreDehydrated) { + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // Success, because files are never downloaded + } else { + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); // Fail because A/broken + // FatalError means the sync was aborted, which is what we want + QCOMPARE(getItem(completeSpy, QStringLiteral("A/broken"))->_status, SyncFileItem::FatalError); + QVERIFY(getItem(completeSpy, QStringLiteral("A/broken"))->_errorString.contains(QStringLiteral("System in maintenance mode"))); } -#endif - // Test for https://github.com/owncloud/client/issues/7015 - // We want to test the case in which the renaming of the original to the conflict file succeeds, - // but renaming the temporary file fails. - // This tests uses the fact that a "touchedFile" notification will be sent at the right moment. - // Note that there will be first a notification on the file and the conflict file before. - - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().setIgnoreHiddenFiles(true); - fakeFolder.remoteModifier().setContents("A/a1", 'A'); - fakeFolder.localModifier().setContents("A/a1", 'B'); - - bool propConnected = false; - QString conflictFile; - auto transProgress = connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress, - [&](const ProgressInfo &pi) { - auto propagator = fakeFolder.syncEngine().getPropagator(); - if (pi.status() != ProgressInfo::Propagation || propConnected || !propagator) - return; - propConnected = true; - connect(propagator.data(), &OwncloudPropagator::touchedFile, [&](const QString &s) { - if (s.contains("conflicted copy")) { - QCOMPARE(conflictFile, QString()); - conflictFile = s; - return; - } - if (!conflictFile.isEmpty()) { - // Check that the temporary file is still there - QCOMPARE(QDir(fakeFolder.localPath() + "A/").entryList({"*.~*"}, QDir::Files | QDir::Hidden).count(), 1); - // Set the permission to read only on the folder, so the rename of the temporary file will fail - QFile(fakeFolder.localPath() + "A/").setPermissions(QFile::Permissions(0x5555)); - } - }); - }); - - QVERIFY(!fakeFolder.syncOnce()); // The sync must fail because the rename failed - QVERIFY(!conflictFile.isEmpty()); - - // restore permissions - QFile(fakeFolder.localPath() + "A/").setPermissions(QFile::Permissions(0x7777)); - - QObject::disconnect(transProgress); - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::GetOperation) - QTest::qFail("There shouldn't be any download", __FILE__, __LINE__); - return nullptr; - }); - QVERIFY(fakeFolder.syncOnce()); - - // The a1 file is still tere and have the right content - QVERIFY(fakeFolder.currentRemoteState().find("A/a1")); - QCOMPARE(fakeFolder.currentRemoteState().find("A/a1")->contentChar, 'A'); - - QVERIFY(QFile::remove(conflictFile)); // So the comparison succeeds; - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } - void testHttp2Resend() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.remoteModifier().insert("A/resendme", 300); + void testHttp2Resend() + { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + fakeFolder.remoteModifier().insert(QStringLiteral("A/resendme"), 300_B); - QByteArray serverMessage = "Needs to be resend on a new connection!"; + QString serverMessage = QStringLiteral("Needs to be resend on a new connection!"); int resendActual = 0; int resendExpected = 2; // First, download only the first 3 MB of the file fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/resendme") && resendActual < resendExpected) { + if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith(QLatin1String("A/resendme")) && resendActual < resendExpected) { auto errorReply = new FakeErrorReply(op, request, this, 400, "ignore this body"); errorReply->setError(QNetworkReply::ContentReSendError, serverMessage); errorReply->setAttribute(QNetworkRequest::Http2WasUsedAttribute, true); @@ -233,19 +284,23 @@ private slots: return nullptr; }); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(resendActual, 2); - - fakeFolder.remoteModifier().appendByte("A/resendme"); - resendActual = 0; - resendExpected = 10; - - QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted); - QVERIFY(!fakeFolder.syncOnce()); - QCOMPARE(resendActual, 4); // the 4th fails because it only resends 3 times - QCOMPARE(getItem(completeSpy, "A/resendme")->_status, SyncFileItem::NormalError); - QVERIFY(getItem(completeSpy, "A/resendme")->_errorString.contains(serverMessage)); + if (filesAreDehydrated) { + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // Success, because files are never downloaded + } else { + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QCOMPARE(resendActual, 2); + + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/resendme")); + resendActual = 0; + resendExpected = 10; + + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(resendActual, 6); // AbstractNetworkJob::MaxRetryCount + 1 + QCOMPARE(getItem(completeSpy, QStringLiteral("A/resendme"))->_status, SyncFileItem::NormalError); + QVERIFY(getItem(completeSpy, QStringLiteral("A/resendme"))->_errorString.contains(serverMessage)); + } } }; diff --git a/test/testexcludedfiles.cpp b/test/testexcludedfiles.cpp index 276d415fe2f..84d5cbd6297 100644 --- a/test/testexcludedfiles.cpp +++ b/test/testexcludedfiles.cpp @@ -9,11 +9,13 @@ #include #include "csync_exclude.h" +#include "testutils.h" using namespace OCC; -#define EXCLUDE_LIST_FILE SOURCEDIR "/sync-exclude.lst" - +namespace { +const QString excludeListFileC = QStringLiteral(":/libsync/sync-exclude.lst"); +} // The tests were converted from the old CMocka framework, that's why there is a global static QScopedPointer excludedFiles; @@ -25,15 +27,15 @@ static void setup() { static void setup_init() { setup(); - excludedFiles->addExcludeFilePath(EXCLUDE_LIST_FILE); + excludedFiles->addExcludeFilePath(excludeListFileC); QVERIFY(excludedFiles->reloadExcludeFiles()); /* and add some unicode stuff */ - excludedFiles->addManualExclude("*.💩"); // is this source file utf8 encoded? - excludedFiles->addManualExclude("пятницы.*"); - excludedFiles->addManualExclude("*/*.out"); - excludedFiles->addManualExclude("latex*/*.run.xml"); - excludedFiles->addManualExclude("latex/*/*.tex.tmp"); + excludedFiles->addManualExclude(QStringLiteral("*.💩")); // is this source file utf8 encoded? + excludedFiles->addManualExclude(QStringLiteral("пятницы.*")); + excludedFiles->addManualExclude(QStringLiteral("*/*.out")); + excludedFiles->addManualExclude(QStringLiteral("latex*/*.run.xml")); + excludedFiles->addManualExclude(QStringLiteral("latex/*/*.tex.tmp")); QVERIFY(excludedFiles->reloadExcludeFiles()); } @@ -42,27 +44,15 @@ class TestExcludedFiles: public QObject { Q_OBJECT - static auto check_file_full(const QString &path) - { - return excludedFiles->fullPatternMatch(&path, ItemTypeFile); - } + static auto check_file_full(const QString &path) { return excludedFiles->fullPatternMatch(path, ItemTypeFile); } - static auto check_dir_full(const QString &path) - { - return excludedFiles->fullPatternMatch(&path, ItemTypeDirectory); - } + static auto check_dir_full(const QString &path) { return excludedFiles->fullPatternMatch(path, ItemTypeDirectory); } - static auto check_file_traversal(const QString &path) - { - return excludedFiles->traversalPatternMatch(&path, ItemTypeFile); - } + static auto check_file_traversal(const QString &path) { return excludedFiles->traversalPatternMatch(path, ItemTypeFile); } - static auto check_dir_traversal(const QString &path) - { - return excludedFiles->traversalPatternMatch(&path, ItemTypeDirectory); - } + static auto check_dir_traversal(const QString &path) { return excludedFiles->traversalPatternMatch(path, ItemTypeDirectory); } -private slots: +private Q_SLOTS: void testFun() { ExcludedFiles excluded; @@ -70,7 +60,7 @@ private slots: bool keepHidden = false; auto check_isExcluded = [&](const QString &a, bool keepHidden, bool create = true) { - QTemporaryDir tmp; + auto tmp = OCC::TestUtils::createTempDir(); Q_ASSERT(tmp.isValid()); auto createTree = [&](const QString &path) { @@ -78,7 +68,7 @@ private slots: const auto fileName = parts.back(); parts.pop_back(); // remove file name QString workPath = tmp.path() + QLatin1Char('/'); - for (const auto &p : qAsConst(parts)) { + for (const auto &p : std::as_const(parts)) { QDir dir(workPath); dir.mkdir(p); workPath += p + QLatin1Char('/'); @@ -92,7 +82,7 @@ private slots: if (create) { createTree(a); } - return excluded.isExcluded(tmp.path() + a, tmp.path() + QStringLiteral("/a"), keepHidden); + return excluded.isExcluded(QString{tmp.path() + a}, QString{tmp.path() + QStringLiteral("/a")}, keepHidden); }; QVERIFY(!check_isExcluded(QStringLiteral("/a/b"), keepHidden)); @@ -100,7 +90,7 @@ private slots: QVERIFY(!check_isExcluded(QStringLiteral("/a/.b"), keepHidden)); QVERIFY(check_isExcluded(QStringLiteral("/a/.b"), excludeHidden)); - excluded.addExcludeFilePath(EXCLUDE_LIST_FILE); + excluded.addExcludeFilePath(excludeListFileC); excluded.reloadExcludeFiles(); QVERIFY(!check_isExcluded(QStringLiteral("/a/b"), keepHidden)); @@ -118,440 +108,432 @@ private slots: void check_csync_exclude_add() { setup(); - excludedFiles->addManualExclude("/tmp/check_csync1/*"); - QCOMPARE(check_file_full("/tmp/check_csync1/foo"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("/tmp/check_csync2/foo"), CSYNC_NOT_EXCLUDED); - QVERIFY(excludedFiles->_allExcludes.contains("/tmp/check_csync1/*")); - - QVERIFY(excludedFiles->_fullRegexFile.pattern().contains("csync1")); - QVERIFY(excludedFiles->_fullTraversalRegexFile.pattern().contains("csync1")); - QVERIFY(!excludedFiles->_bnameTraversalRegexFile.pattern().contains("csync1")); - - excludedFiles->addManualExclude("foo"); - QVERIFY(excludedFiles->_bnameTraversalRegexFile.pattern().contains("foo")); - QVERIFY(excludedFiles->_fullRegexFile.pattern().contains("foo")); - QVERIFY(!excludedFiles->_fullTraversalRegexFile.pattern().contains("foo")); + excludedFiles->addManualExclude(QStringLiteral("/tmp/check_csync1/*")); + QCOMPARE(check_file_full(QStringLiteral("/tmp/check_csync1/foo")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("/tmp/check_csync2/foo")), CSYNC_NOT_EXCLUDED); + QVERIFY(excludedFiles->_allExcludes.contains(QStringLiteral("/tmp/check_csync1/*"))); + + QVERIFY(excludedFiles->_fullRegexFile.pattern().contains(QStringLiteral("csync1"))); + QVERIFY(excludedFiles->_fullTraversalRegexFile.pattern().contains(QStringLiteral("csync1"))); + QVERIFY(!excludedFiles->_bnameTraversalRegexFile.pattern().contains(QStringLiteral("csync1"))); + + excludedFiles->addManualExclude(QStringLiteral("foo")); + QVERIFY(excludedFiles->_bnameTraversalRegexFile.pattern().contains(QStringLiteral("foo"))); + QVERIFY(excludedFiles->_fullRegexFile.pattern().contains(QStringLiteral("foo"))); + QVERIFY(!excludedFiles->_fullTraversalRegexFile.pattern().contains(QStringLiteral("foo"))); } void check_csync_excluded() { setup_init(); - QCOMPARE(check_file_full(""), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("/"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("A"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("krawel_krawel"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full(".kde/share/config/kwin.eventsrc"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full(".directory/cache-maximegalon/cache1.txt"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_full("mozilla/.directory"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QString()), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("/")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("A")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("krawel_krawel")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral(".kde/share/config/kwin.eventsrc")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral(".directory/cache-maximegalon/cache1.txt")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral("mozilla/.directory")), CSYNC_FILE_EXCLUDE_LIST); /* * Test for patterns in subdirs. '.beagle' is defined as a pattern and has * to be found in top dir as well as in directories underneath. */ - QCOMPARE(check_dir_full(".apdisk"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_full("foo/.apdisk"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_full("foo/bar/.apdisk"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral(".apdisk")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral("foo/.apdisk")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral("foo/bar/.apdisk")), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full(".java"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral(".java")), CSYNC_NOT_EXCLUDED); /* Files in the ignored dir .java will also be ignored. */ - QCOMPARE(check_file_full(".apdisk/totally_amazing.jar"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral(".apdisk/totally_amazing.jar")), CSYNC_FILE_EXCLUDE_LIST); /* and also in subdirs */ - QCOMPARE(check_file_full("projects/.apdisk/totally_amazing.jar"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("projects/.apdisk/totally_amazing.jar")), CSYNC_FILE_EXCLUDE_LIST); /* csync-journal is ignored in general silently. */ - QCOMPARE(check_file_full(".csync_journal.db"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_full(".csync_journal.db.ctmp"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_full("subdir/.csync_journal.db"), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral(".csync_journal.db")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral(".csync_journal.db.ctmp")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("subdir/.csync_journal.db")), CSYNC_FILE_SILENTLY_EXCLUDED); /* also the new form of the database name */ - QCOMPARE(check_file_full("._sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_full("._sync_5bdd60bdfcfa.db.ctmp"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_full("._sync_5bdd60bdfcfa.db-shm"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_full("subdir/._sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("._sync_5bdd60bdfcfa.db")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("._sync_5bdd60bdfcfa.db.ctmp")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("._sync_5bdd60bdfcfa.db-shm")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("subdir/._sync_5bdd60bdfcfa.db")), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_full(".sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_full(".sync_5bdd60bdfcfa.db.ctmp"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_full(".sync_5bdd60bdfcfa.db-shm"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_full("subdir/.sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral(".sync_5bdd60bdfcfa.db")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral(".sync_5bdd60bdfcfa.db.ctmp")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral(".sync_5bdd60bdfcfa.db-shm")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("subdir/.sync_5bdd60bdfcfa.db")), CSYNC_FILE_SILENTLY_EXCLUDED); /* pattern ]*.directory - ignore and remove */ - QCOMPARE(check_file_full("my.~directory"), CSYNC_FILE_EXCLUDE_AND_REMOVE); - QCOMPARE(check_file_full("/a_folder/my.~directory"), CSYNC_FILE_EXCLUDE_AND_REMOVE); + QCOMPARE(check_file_full(QStringLiteral("my.~directory")), CSYNC_FILE_EXCLUDE_AND_REMOVE); + QCOMPARE(check_file_full(QStringLiteral("/a_folder/my.~directory")), CSYNC_FILE_EXCLUDE_AND_REMOVE); /* Not excluded because the pattern .netscape/cache requires directory. */ - QCOMPARE(check_file_full(".netscape/cache"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral(".netscape/cache")), CSYNC_NOT_EXCLUDED); /* Not excluded */ - QCOMPARE(check_file_full("unicode/中文.hé"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("unicode/中文.hé")), CSYNC_NOT_EXCLUDED); /* excluded */ - QCOMPARE(check_file_full("unicode/пятницы.txt"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("unicode/中文.💩"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("unicode/пятницы.txt")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("unicode/中文.💩")), CSYNC_FILE_EXCLUDE_LIST); /* path wildcards */ - QCOMPARE(check_file_full("foobar/my_manuscript.out"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("latex_tmp/my_manuscript.run.xml"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("foobar/my_manuscript.out")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("latex_tmp/my_manuscript.run.xml")), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("word_tmp/my_manuscript.run.xml"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("word_tmp/my_manuscript.run.xml")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("latex/my_manuscript.tex.tmp"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("latex/my_manuscript.tex.tmp")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("latex/songbook/my_manuscript.tex.tmp"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("latex/songbook/my_manuscript.tex.tmp")), CSYNC_FILE_EXCLUDE_LIST); - #ifdef _WIN32 - QCOMPARE(check_file_full("file_trailing_space "), CSYNC_FILE_EXCLUDE_TRAILING_SPACE); - QCOMPARE(check_file_full("file_trailing_dot."), CSYNC_FILE_EXCLUDE_INVALID_CHAR); - QCOMPARE(check_file_full("AUX"), CSYNC_FILE_EXCLUDE_INVALID_CHAR); - QCOMPARE(check_file_full("file_invalid_char<"), CSYNC_FILE_EXCLUDE_INVALID_CHAR); - QCOMPARE(check_file_full("file_invalid_char\n"), CSYNC_FILE_EXCLUDE_INVALID_CHAR); - #endif +#ifdef _WIN32 + QCOMPARE(check_file_full(QStringLiteral("file_trailing_space ")), CSYNC_FILE_EXCLUDE_TRAILING_SPACE); + QCOMPARE(check_file_full(QStringLiteral("file_trailing_dot.")), CSYNC_FILE_EXCLUDE_INVALID_CHAR); + QCOMPARE(check_file_full(QStringLiteral("AUX")), CSYNC_FILE_EXCLUDE_INVALID_CHAR); + QCOMPARE(check_file_full(QStringLiteral("file_invalid_char<")), CSYNC_FILE_EXCLUDE_INVALID_CHAR); + QCOMPARE(check_file_full(QStringLiteral("file_invalid_char\n")), CSYNC_FILE_EXCLUDE_INVALID_CHAR); +#endif /* ? character */ - excludedFiles->addManualExclude("bond00?"); + excludedFiles->addManualExclude(QStringLiteral("bond00?")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_file_full("bond00"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("bond007"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("bond0071"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("bond00")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("bond007")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("bond0071")), CSYNC_NOT_EXCLUDED); /* brackets */ - excludedFiles->addManualExclude("a [bc] d"); + excludedFiles->addManualExclude(QStringLiteral("a [bc] d")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_file_full("a d d"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("a d"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("a b d"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("a c d"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("a d d")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("a d")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("a b d")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("a c d")), CSYNC_FILE_EXCLUDE_LIST); #ifndef Q_OS_WIN // Because of CSYNC_FILE_EXCLUDE_INVALID_CHAR on windows /* escapes */ - excludedFiles->addManualExclude("a \\*"); - excludedFiles->addManualExclude("b \\?"); - excludedFiles->addManualExclude("c \\[d]"); + excludedFiles->addManualExclude(QStringLiteral("a \\*")); + excludedFiles->addManualExclude(QStringLiteral("b \\?")); + excludedFiles->addManualExclude(QStringLiteral("c \\[d]")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_file_full("a \\*"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("a bc"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("a *"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("b \\?"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("b f"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("b ?"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("c \\[d]"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("c d"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_full("c [d]"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("a \\*")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("a bc")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("a *")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("b \\?")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("b f")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("b ?")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("c \\[d]")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("c d")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("c [d]")), CSYNC_FILE_EXCLUDE_LIST); #endif } void check_csync_excluded_traversal() { setup_init(); - QCOMPARE(check_file_traversal(""), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("/"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QString()), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("/")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("A"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("A")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("krawel_krawel"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal(".kde/share/config/kwin.eventsrc"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_dir_traversal("mozilla/.directory"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("krawel_krawel")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral(".kde/share/config/kwin.eventsrc")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_dir_traversal(QStringLiteral("mozilla/.directory")), CSYNC_FILE_EXCLUDE_LIST); /* * Test for patterns in subdirs. '.beagle' is defined as a pattern and has * to be found in top dir as well as in directories underneath. */ - QCOMPARE(check_dir_traversal(".apdisk"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_traversal("foo/.apdisk"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_traversal("foo/bar/.apdisk"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral(".apdisk")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral("foo/.apdisk")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral("foo/bar/.apdisk")), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal(".java"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral(".java")), CSYNC_NOT_EXCLUDED); /* csync-journal is ignored in general silently. */ - QCOMPARE(check_file_traversal(".csync_journal.db"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal(".csync_journal.db.ctmp"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal("subdir/.csync_journal.db"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal("/two/subdir/.csync_journal.db"), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral(".csync_journal.db")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral(".csync_journal.db.ctmp")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("subdir/.csync_journal.db")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("/two/subdir/.csync_journal.db")), CSYNC_FILE_SILENTLY_EXCLUDED); /* also the new form of the database name */ - QCOMPARE(check_file_traversal("._sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal("._sync_5bdd60bdfcfa.db.ctmp"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal("._sync_5bdd60bdfcfa.db-shm"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal("subdir/._sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("._sync_5bdd60bdfcfa.db")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("._sync_5bdd60bdfcfa.db.ctmp")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("._sync_5bdd60bdfcfa.db-shm")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("subdir/._sync_5bdd60bdfcfa.db")), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal(".sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal(".sync_5bdd60bdfcfa.db.ctmp"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal(".sync_5bdd60bdfcfa.db-shm"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal("subdir/.sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral(".sync_5bdd60bdfcfa.db")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral(".sync_5bdd60bdfcfa.db.ctmp")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral(".sync_5bdd60bdfcfa.db-shm")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("subdir/.sync_5bdd60bdfcfa.db")), CSYNC_FILE_SILENTLY_EXCLUDED); /* Other builtin excludes */ - QCOMPARE(check_file_traversal("foo/Desktop.ini"), CSYNC_FILE_SILENTLY_EXCLUDED); - QCOMPARE(check_file_traversal("Desktop.ini"), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("foo/Desktop.ini")), CSYNC_FILE_SILENTLY_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("Desktop.ini")), CSYNC_FILE_SILENTLY_EXCLUDED); /* pattern ]*.directory - ignore and remove */ - QCOMPARE(check_file_traversal("my.~directory"), CSYNC_FILE_EXCLUDE_AND_REMOVE); - QCOMPARE(check_file_traversal("/a_folder/my.~directory"), CSYNC_FILE_EXCLUDE_AND_REMOVE); + QCOMPARE(check_file_traversal(QStringLiteral("my.~directory")), CSYNC_FILE_EXCLUDE_AND_REMOVE); + QCOMPARE(check_file_traversal(QStringLiteral("/a_folder/my.~directory")), CSYNC_FILE_EXCLUDE_AND_REMOVE); /* Not excluded because the pattern .netscape/cache requires directory. */ - QCOMPARE(check_file_traversal(".netscape/cache"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral(".netscape/cache")), CSYNC_NOT_EXCLUDED); /* Not excluded */ - QCOMPARE(check_file_traversal("unicode/中文.hé"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("unicode/中文.hé")), CSYNC_NOT_EXCLUDED); /* excluded */ - QCOMPARE(check_file_traversal("unicode/пятницы.txt"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("unicode/中文.💩"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("unicode/пятницы.txt")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("unicode/中文.💩")), CSYNC_FILE_EXCLUDE_LIST); /* path wildcards */ - QCOMPARE(check_file_traversal("foobar/my_manuscript.out"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("latex_tmp/my_manuscript.run.xml"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("word_tmp/my_manuscript.run.xml"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("latex/my_manuscript.tex.tmp"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("latex/songbook/my_manuscript.tex.tmp"), CSYNC_FILE_EXCLUDE_LIST); - - #ifdef _WIN32 - QCOMPARE(check_file_traversal("file_trailing_space "), CSYNC_FILE_EXCLUDE_TRAILING_SPACE); - QCOMPARE(check_file_traversal("file_trailing_dot."), CSYNC_FILE_EXCLUDE_INVALID_CHAR); - QCOMPARE(check_file_traversal("AUX"), CSYNC_FILE_EXCLUDE_INVALID_CHAR); - QCOMPARE(check_file_traversal("file_invalid_char<"), CSYNC_FILE_EXCLUDE_INVALID_CHAR); - QCOMPARE(check_file_traversal("file_invalid_char\n"), CSYNC_FILE_EXCLUDE_INVALID_CHAR); - #endif + QCOMPARE(check_file_traversal(QStringLiteral("foobar/my_manuscript.out")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("latex_tmp/my_manuscript.run.xml")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("word_tmp/my_manuscript.run.xml")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("latex/my_manuscript.tex.tmp")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("latex/songbook/my_manuscript.tex.tmp")), CSYNC_FILE_EXCLUDE_LIST); + +#ifdef _WIN32 + QCOMPARE(check_file_traversal(QStringLiteral("file_trailing_space ")), CSYNC_FILE_EXCLUDE_TRAILING_SPACE); + QCOMPARE(check_file_traversal(QStringLiteral("file_trailing_dot.")), CSYNC_FILE_EXCLUDE_INVALID_CHAR); + QCOMPARE(check_file_traversal(QStringLiteral("AUX")), CSYNC_FILE_EXCLUDE_INVALID_CHAR); + QCOMPARE(check_file_traversal(QStringLiteral("file_invalid_char<")), CSYNC_FILE_EXCLUDE_INVALID_CHAR); + QCOMPARE(check_file_traversal(QStringLiteral("file_invalid_char\n")), CSYNC_FILE_EXCLUDE_INVALID_CHAR); +#endif /* From here the actual traversal tests */ - excludedFiles->addManualExclude("/exclude"); + excludedFiles->addManualExclude(QStringLiteral("/exclude")); excludedFiles->reloadExcludeFiles(); /* Check toplevel dir, the pattern only works for toplevel dir. */ - QCOMPARE(check_dir_traversal("/exclude"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_traversal("/foo/exclude"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_dir_traversal(QStringLiteral("/exclude")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral("/foo/exclude")), CSYNC_NOT_EXCLUDED); /* check for a file called exclude. Must still work */ - QCOMPARE(check_file_traversal("/exclude"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("/foo/exclude"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("/exclude")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("/foo/exclude")), CSYNC_NOT_EXCLUDED); /* Add an exclude for directories only: excl/ */ - excludedFiles->addManualExclude("excl/"); + excludedFiles->addManualExclude(QStringLiteral("excl/")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_dir_traversal("/excl"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_traversal("meep/excl"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral("/excl")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral("meep/excl")), CSYNC_FILE_EXCLUDE_LIST); // because leading dirs aren't checked! - QCOMPARE(check_file_traversal("meep/excl/file"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("/excl"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("meep/excl/file")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("/excl")), CSYNC_NOT_EXCLUDED); - excludedFiles->addManualExclude("/excludepath/withsubdir"); + excludedFiles->addManualExclude(QStringLiteral("/excludepath/withsubdir")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_dir_traversal("/excludepath/withsubdir"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("/excludepath/withsubdir"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_traversal("/excludepath/withsubdir2"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_dir_traversal(QStringLiteral("/excludepath/withsubdir")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("/excludepath/withsubdir")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral("/excludepath/withsubdir2")), CSYNC_NOT_EXCLUDED); // because leading dirs aren't checked! - QCOMPARE(check_dir_traversal("/excludepath/withsubdir/foo"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_dir_traversal(QStringLiteral("/excludepath/withsubdir/foo")), CSYNC_NOT_EXCLUDED); /* Check ending of pattern */ - QCOMPARE(check_file_traversal("/exclude"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("/excludeX"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("exclude"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("/exclude")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("/excludeX")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("exclude")), CSYNC_NOT_EXCLUDED); - excludedFiles->addManualExclude("exclude"); + excludedFiles->addManualExclude(QStringLiteral("exclude")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_file_traversal("exclude"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("exclude")), CSYNC_FILE_EXCLUDE_LIST); /* ? character */ - excludedFiles->addManualExclude("bond00?"); + excludedFiles->addManualExclude(QStringLiteral("bond00?")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_file_traversal("bond00"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("bond007"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("bond0071"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("bond00")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("bond007")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("bond0071")), CSYNC_NOT_EXCLUDED); /* brackets */ - excludedFiles->addManualExclude("a [bc] d"); + excludedFiles->addManualExclude(QStringLiteral("a [bc] d")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_file_traversal("a d d"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("a d"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("a b d"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("a c d"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("a d d")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("a d")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("a b d")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("a c d")), CSYNC_FILE_EXCLUDE_LIST); #ifndef Q_OS_WIN // Because of CSYNC_FILE_EXCLUDE_INVALID_CHAR on windows /* escapes */ - excludedFiles->addManualExclude("a \\*"); - excludedFiles->addManualExclude("b \\?"); - excludedFiles->addManualExclude("c \\[d]"); + excludedFiles->addManualExclude(QStringLiteral("a \\*")); + excludedFiles->addManualExclude(QStringLiteral("b \\?")); + excludedFiles->addManualExclude(QStringLiteral("c \\[d]")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_file_traversal("a \\*"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("a bc"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("a *"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("b \\?"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("b f"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("b ?"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("c \\[d]"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("c d"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("c [d]"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("a \\*")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("a bc")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("a *")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("b \\?")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("b f")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("b ?")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("c \\[d]")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("c d")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("c [d]")), CSYNC_FILE_EXCLUDE_LIST); #endif } void check_csync_dir_only() { setup(); - excludedFiles->addManualExclude("filedir"); - excludedFiles->addManualExclude("dir/"); - - QCOMPARE(check_file_traversal("other"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("filedir"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("dir"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("s/other"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("s/filedir"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("s/dir"), CSYNC_NOT_EXCLUDED); - - QCOMPARE(check_dir_traversal("other"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_dir_traversal("filedir"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_traversal("dir"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_traversal("s/other"), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_dir_traversal("s/filedir"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_traversal("s/dir"), CSYNC_FILE_EXCLUDE_LIST); - - QCOMPARE(check_dir_full("filedir/foo"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("filedir/foo"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_full("dir/foo"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("dir/foo"), CSYNC_FILE_EXCLUDE_LIST); + excludedFiles->addManualExclude(QStringLiteral("filedir")); + excludedFiles->addManualExclude(QStringLiteral("dir/")); + + QCOMPARE(check_file_traversal(QStringLiteral("other")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("filedir")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("dir")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("s/other")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("s/filedir")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("s/dir")), CSYNC_NOT_EXCLUDED); + + QCOMPARE(check_dir_traversal(QStringLiteral("other")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_dir_traversal(QStringLiteral("filedir")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral("dir")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral("s/other")), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_dir_traversal(QStringLiteral("s/filedir")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_traversal(QStringLiteral("s/dir")), CSYNC_FILE_EXCLUDE_LIST); + + QCOMPARE(check_dir_full(QStringLiteral("filedir/foo")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("filedir/foo")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral("dir/foo")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("dir/foo")), CSYNC_FILE_EXCLUDE_LIST); } void check_csync_pathes() { setup_init(); - excludedFiles->addManualExclude("/exclude"); + excludedFiles->addManualExclude(QStringLiteral("/exclude")); excludedFiles->reloadExcludeFiles(); /* Check toplevel dir, the pattern only works for toplevel dir. */ - QCOMPARE(check_dir_full("/exclude"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral("/exclude")), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_full("/foo/exclude"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_dir_full(QStringLiteral("/foo/exclude")), CSYNC_NOT_EXCLUDED); /* check for a file called exclude. Must still work */ - QCOMPARE(check_file_full("/exclude"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("/exclude")), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("/foo/exclude"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("/foo/exclude")), CSYNC_NOT_EXCLUDED); /* Add an exclude for directories only: excl/ */ - excludedFiles->addManualExclude("excl/"); + excludedFiles->addManualExclude(QStringLiteral("excl/")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_dir_full("/excl"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_full("meep/excl"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("meep/excl/file"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral("/excl")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral("meep/excl")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("meep/excl/file")), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("/excl"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_full(QStringLiteral("/excl")), CSYNC_NOT_EXCLUDED); - excludedFiles->addManualExclude("/excludepath/withsubdir"); + excludedFiles->addManualExclude(QStringLiteral("/excludepath/withsubdir")); excludedFiles->reloadExcludeFiles(); - QCOMPARE(check_dir_full("/excludepath/withsubdir"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_full("/excludepath/withsubdir"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral("/excludepath/withsubdir")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_full(QStringLiteral("/excludepath/withsubdir")), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_dir_full("/excludepath/withsubdir2"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_dir_full(QStringLiteral("/excludepath/withsubdir2")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_dir_full("/excludepath/withsubdir/foo"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_dir_full(QStringLiteral("/excludepath/withsubdir/foo")), CSYNC_FILE_EXCLUDE_LIST); } void check_csync_wildcards() { setup(); - excludedFiles->addManualExclude("a/foo*bar"); - excludedFiles->addManualExclude("b/foo*bar*"); - excludedFiles->addManualExclude("c/foo?bar"); - excludedFiles->addManualExclude("d/foo?bar*"); - excludedFiles->addManualExclude("e/foo?bar?"); - excludedFiles->addManualExclude("g/bar*"); - excludedFiles->addManualExclude("h/bar?"); + excludedFiles->addManualExclude(QStringLiteral("a/foo*bar")); + excludedFiles->addManualExclude(QStringLiteral("b/foo*bar*")); + excludedFiles->addManualExclude(QStringLiteral("c/foo?bar")); + excludedFiles->addManualExclude(QStringLiteral("d/foo?bar*")); + excludedFiles->addManualExclude(QStringLiteral("e/foo?bar?")); + excludedFiles->addManualExclude(QStringLiteral("g/bar*")); + excludedFiles->addManualExclude(QStringLiteral("h/bar?")); excludedFiles->setWildcardsMatchSlash(false); - QCOMPARE(check_file_traversal("a/fooXYZbar"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("a/fooX/Zbar"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("a/fooXYZbar")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("a/fooX/Zbar")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("b/fooXYZbarABC"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("b/fooX/ZbarABC"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("b/fooXYZbarABC")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("b/fooX/ZbarABC")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("c/fooXbar"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("c/foo/bar"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("c/fooXbar")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("c/foo/bar")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("d/fooXbarABC"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("d/foo/barABC"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("d/fooXbarABC")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("d/foo/barABC")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("e/fooXbarA"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("e/foo/barA"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("e/fooXbarA")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("e/foo/barA")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("g/barABC"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("g/XbarABC"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("g/barABC")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("g/XbarABC")), CSYNC_NOT_EXCLUDED); - QCOMPARE(check_file_traversal("h/barZ"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("h/XbarZ"), CSYNC_NOT_EXCLUDED); + QCOMPARE(check_file_traversal(QStringLiteral("h/barZ")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("h/XbarZ")), CSYNC_NOT_EXCLUDED); excludedFiles->setWildcardsMatchSlash(true); - QCOMPARE(check_file_traversal("a/fooX/Zbar"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("b/fooX/ZbarABC"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("c/foo/bar"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("d/foo/barABC"), CSYNC_FILE_EXCLUDE_LIST); - QCOMPARE(check_file_traversal("e/foo/barA"), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("a/fooX/Zbar")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("b/fooX/ZbarABC")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("c/foo/bar")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("d/foo/barABC")), CSYNC_FILE_EXCLUDE_LIST); + QCOMPARE(check_file_traversal(QStringLiteral("e/foo/barA")), CSYNC_FILE_EXCLUDE_LIST); } void check_csync_regex_translation() { setup(); - QByteArray storage; - auto translate = [&storage](const char *pattern) { - storage = ExcludedFiles::convertToRegexpSyntax(pattern, false).toUtf8(); - return storage.constData(); - }; - - QCOMPARE(translate(""), ""); - QCOMPARE(translate("abc"), "abc"); - QCOMPARE(translate("a*c"), "a[^/]*c"); - QCOMPARE(translate("a?c"), "a[^/]c"); - QCOMPARE(translate("a[xyz]c"), "a[xyz]c"); - QCOMPARE(translate("a[xyzc"), "a\\[xyzc"); - QCOMPARE(translate("a[!xyz]c"), "a[^xyz]c"); - QCOMPARE(translate("a\\*b\\?c\\[d\\\\e"), "a\\*b\\?c\\[d\\\\e"); - QCOMPARE(translate("a.c"), "a\\.c"); - QCOMPARE(translate("?𠜎?"), "[^/]\\𠜎[^/]"); // 𠜎 is 4-byte utf8 + auto translate = [](const QString &pattern) { return ExcludedFiles::convertToRegexpSyntax(pattern, false).toUtf8(); }; + + QCOMPARE(translate(QString()), ""); + QCOMPARE(translate(QStringLiteral("abc")), "abc"); + QCOMPARE(translate(QStringLiteral("a*c")), "a[^/]*c"); + QCOMPARE(translate(QStringLiteral("a?c")), "a[^/]c"); + QCOMPARE(translate(QStringLiteral("a[xyz]c")), "a[xyz]c"); + QCOMPARE(translate(QStringLiteral("a[xyzc")), "a\\[xyzc"); + QCOMPARE(translate(QStringLiteral("a[!xyz]c")), "a[^xyz]c"); + QCOMPARE(translate(QStringLiteral("a\\*b\\?c\\[d\\\\e")), "a\\*b\\?c\\[d\\\\e"); + QCOMPARE(translate(QStringLiteral("a.c")), "a\\.c"); + QCOMPARE(translate(QStringLiteral("?𠜎?")), "[^/]\\𠜎[^/]"); // 𠜎 is 4-byte utf8 } void check_csync_bname_trigger() { setup(); bool wildcardsMatchSlash = false; - QByteArray storage; - auto translate = [&storage, &wildcardsMatchSlash](const char *pattern) { - storage = ExcludedFiles::extractBnameTrigger(pattern, wildcardsMatchSlash).toUtf8(); - return storage.constData(); - }; + auto translate = [&wildcardsMatchSlash](const QString &pattern) { return ExcludedFiles::extractBnameTrigger(pattern, wildcardsMatchSlash).toUtf8(); }; - QCOMPARE(translate(""), ""); - QCOMPARE(translate("a/b/"), ""); - QCOMPARE(translate("a/b/c"), "c"); - QCOMPARE(translate("c"), "c"); - QCOMPARE(translate("a/foo*"), "foo*"); - QCOMPARE(translate("a/abc*foo*"), "abc*foo*"); + QCOMPARE(translate(QString()), ""); + QCOMPARE(translate(QStringLiteral("a/b/")), ""); + QCOMPARE(translate(QStringLiteral("a/b/c")), "c"); + QCOMPARE(translate(QStringLiteral("c")), "c"); + QCOMPARE(translate(QStringLiteral("a/foo*")), "foo*"); + QCOMPARE(translate(QStringLiteral("a/abc*foo*")), "abc*foo*"); wildcardsMatchSlash = true; - QCOMPARE(translate(""), ""); - QCOMPARE(translate("a/b/"), ""); - QCOMPARE(translate("a/b/c"), "c"); - QCOMPARE(translate("c"), "c"); - QCOMPARE(translate("*"), "*"); - QCOMPARE(translate("a/foo*"), "foo*"); - QCOMPARE(translate("a/abc?foo*"), "*foo*"); - QCOMPARE(translate("a/abc*foo*"), "*foo*"); - QCOMPARE(translate("a/abc?foo?"), "*foo?"); - QCOMPARE(translate("a/abc*foo?*"), "*foo?*"); - QCOMPARE(translate("a/abc*/foo*"), "foo*"); + QCOMPARE(translate(QString()), ""); + QCOMPARE(translate(QStringLiteral("a/b/")), ""); + QCOMPARE(translate(QStringLiteral("a/b/c")), "c"); + QCOMPARE(translate(QStringLiteral("c")), "c"); + QCOMPARE(translate(QStringLiteral("*")), "*"); + QCOMPARE(translate(QStringLiteral("a/foo*")), "foo*"); + QCOMPARE(translate(QStringLiteral("a/abc?foo*")), "*foo*"); + QCOMPARE(translate(QStringLiteral("a/abc*foo*")), "*foo*"); + QCOMPARE(translate(QStringLiteral("a/abc?foo?")), "*foo?"); + QCOMPARE(translate(QStringLiteral("a/abc*foo?*")), "*foo?*"); + QCOMPARE(translate(QStringLiteral("a/abc*/foo*")), "foo*"); } void check_csync_is_windows_reserved_word() { auto csync_is_windows_reserved_word = [](const char *fn) { QString s = QString::fromLatin1(fn); - extern bool csync_is_windows_reserved_word(const QStringRef &filename); - return csync_is_windows_reserved_word(&s); + extern bool csync_is_windows_reserved_word(QStringView filename); + return csync_is_windows_reserved_word(s); }; QVERIFY(csync_is_windows_reserved_word("CON")); @@ -597,8 +579,8 @@ private slots: QBENCHMARK { for (int i = 0; i < N; ++i) { - totalRc += check_dir_full("/this/is/quite/a/long/path/with/many/components"); - totalRc += check_file_full("/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/29"); + totalRc += check_dir_full(QStringLiteral("/this/is/quite/a/long/path/with/many/components")); + totalRc += check_file_full(QStringLiteral("/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/29")); } QCOMPARE(totalRc, 0); // mainly to avoid optimization } @@ -611,8 +593,8 @@ private slots: QBENCHMARK { for (int i = 0; i < N; ++i) { - totalRc += check_dir_traversal("/this/is/quite/a/long/path/with/many/components"); - totalRc += check_file_traversal("/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/29"); + totalRc += check_dir_traversal(QStringLiteral("/this/is/quite/a/long/path/with/many/components")); + totalRc += check_file_traversal(QStringLiteral("/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/29")); } QCOMPARE(totalRc, 0); // mainly to avoid optimization } diff --git a/test/testfilesystem.cpp b/test/testfilesystem.cpp new file mode 100644 index 00000000000..e2fb5da7659 --- /dev/null +++ b/test/testfilesystem.cpp @@ -0,0 +1,211 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2013 by Klaas Freitag + * + * 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 "common/filesystembase.h" +#include "csync/csync.h" +#include "csync/vio/csync_vio_local.h" +#include "testutils/testutils.h" + +#include +#include + + +class TestFileSystem : public QObject +{ + Q_OBJECT + +private Q_SLOTS: +#ifdef Q_OS_WIN + void check_long_win_path() + { + { + const auto path = QStringLiteral("C://DATA/FILES/MUSIC/MY_MUSIC.mp3"); // check a short path + const auto exp_path = QStringLiteral("\\\\?\\C:\\\\DATA\\FILES\\MUSIC\\MY_MUSIC.mp3"); + QString new_short = OCC::FileSystem::longWinPath(path); + QCOMPARE(new_short, exp_path); + } + + { + const auto path = QStringLiteral("\\\\foo\\bar/MY_MUSIC.mp3"); + const auto exp_path = QStringLiteral("\\\\foo\\bar\\MY_MUSIC.mp3"); + QString new_short = OCC::FileSystem::longWinPath(path); + QCOMPARE(new_short, exp_path); + } + + { + const auto path = QStringLiteral("//foo\\bar/MY_MUSIC.mp3"); + const auto exp_path = QStringLiteral("\\\\foo\\bar\\MY_MUSIC.mp3"); + QString new_short = OCC::FileSystem::longWinPath(path); + QCOMPARE(new_short, exp_path); + } + + { + const auto path = QStringLiteral("\\foo\\bar"); + const auto exp_path = QStringLiteral("\\\\?\\foo\\bar"); + QString new_short = OCC::FileSystem::longWinPath(path); + QCOMPARE(new_short, exp_path); + } + + { + const auto path = QStringLiteral("/foo/bar"); + const auto exp_path = QStringLiteral("\\\\?\\foo\\bar"); + QString new_short = OCC::FileSystem::longWinPath(path); + QCOMPARE(new_short, exp_path); + } + + const auto longPath = QStringLiteral("D://alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" + "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" + "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" + "olonglonglonglong/file.txt"); + const auto longPathConv = QStringLiteral("\\\\?\\D:\\\\alonglonglonglong\\blonglonglonglong\\clonglonglonglong\\dlonglonglonglong\\" + "elonglonglonglong\\flonglonglonglong\\glonglonglonglong\\hlonglonglonglong\\ilonglonglonglong\\" + "jlonglonglonglong\\klonglonglonglong\\llonglonglonglong\\mlonglonglonglong\\nlonglonglonglong\\" + "olonglonglonglong\\file.txt"); + + QString new_long = OCC::FileSystem::longWinPath(longPath); + // printf( "XXXXXXXXXXXX %s %d\n", new_long, mem_reserved); + + QCOMPARE(new_long, longPathConv); + + // printf( "YYYYYYYYYYYY %ld\n", strlen(new_long)); + QCOMPARE(new_long.length(), 286); + } +#endif + + + void testLongPathStat_data() + { + QTest::addColumn("name"); + + QTest::newRow("long") << QStringLiteral("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" + "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" + "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" + "olonglonglonglong/file.txt"); + QTest::newRow("long emoji") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" + "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" + "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" + "olonglonglonglong/file🐷.txt"); + QTest::newRow("long russian") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" + "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" + "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" + "olonglonglonglong/собственное.txt"); + QTest::newRow("long arabic") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" + "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" + "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" + "olonglonglonglong/السحاب.txt"); + QTest::newRow("long chinese") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" + "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" + "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" + "olonglonglonglong/自己的云.txt"); + } + + void testLongPathStat() + { + auto tmp = OCC::TestUtils::createTempDir(); + QFETCH(QString, name); + const QFileInfo longPath(tmp.path() + name); + + const auto data = QByteArrayLiteral("hello"); + qDebug() << longPath; + QVERIFY(longPath.dir().mkpath(QStringLiteral("."))); + + QFile file(longPath.filePath()); + QVERIFY(file.open(QFile::WriteOnly)); + QVERIFY(file.write(data.constData()) == data.size()); + file.close(); + + csync_file_stat_t buf; + QVERIFY(csync_vio_local_stat(longPath.filePath(), &buf) != -1); + QVERIFY(buf.size == data.size()); + QVERIFY(buf.size == longPath.size()); + + QVERIFY(tmp.remove()); + } + + void testMkdir_data() + { + QTest::addColumn("name"); + + const unsigned char a_umlaut_composed_bytes[] = {0xc3, 0xa4, 0x00}; + const QString a_umlaut_composed = QString::fromUtf8(reinterpret_cast(a_umlaut_composed_bytes)); + const QString a_umlaut_decomposed = a_umlaut_composed.normalized(QString::NormalizationForm_D); + + QTest::newRow("a-umlaut composed") << a_umlaut_composed; + QTest::newRow("a-umlaut decomposed") << a_umlaut_decomposed; + } + + // This is not a full test, it is meant to verify that no nomalization changes are done. + // The implementation of `OCC::FileSystem::mkpath` relies on either Qt (Windows) or + // `std::filesystem` (POSIX), which should be covered by tests of their own. + void testMkdir() + { + QFETCH(QString, name); + + auto tmp = OCC::TestUtils::createTempDir(); + QVERIFY(OCC::FileSystem::mkpath(tmp.path(), name)); + csync_file_stat_t buf; + auto dh = csync_vio_local_opendir(tmp.path()); + QVERIFY(dh); + while (auto fs = csync_vio_local_readdir(dh, nullptr)) { + QCOMPARE(fs->path, name); + QCOMPARE(fs->type, ItemTypeDirectory); + } + csync_vio_local_closedir(dh); + } + + void testRename_data() + { + QTest::addColumn("name"); + + const unsigned char a_umlaut_composed_bytes[] = {0xc3, 0xa4, 0x00}; + const QString a_umlaut_composed = QString::fromUtf8(reinterpret_cast(a_umlaut_composed_bytes)); + const QString a_umlaut_decomposed = a_umlaut_composed.normalized(QString::NormalizationForm_D); + + QTest::newRow("a-umlaut composed") << a_umlaut_composed; + QTest::newRow("a-umlaut decomposed") << a_umlaut_decomposed; + } + + // This is not a full test, it is meant to verify that no nomalization changes are done. + void testRename() + { + QFETCH(QString, name); + + auto tmp = OCC::TestUtils::createTempDir(); + QFile f(tmp.path() + u"/abc"); + QVERIFY(f.open(QFile::WriteOnly)); + QByteArray data("abc"); + QCOMPARE(f.write(data), data.size()); + f.close(); + + QString err; + QVERIFY(OCC::FileSystem::uncheckedRenameReplace(f.fileName(), tmp.path() + u'/' + name, &err)); + + csync_file_stat_t buf; + auto dh = csync_vio_local_opendir(tmp.path()); + QVERIFY(dh); + while (auto fs = csync_vio_local_readdir(dh, nullptr)) { + QCOMPARE(fs->path, name); + QCOMPARE(fs->type, ItemTypeFile); + } + csync_vio_local_closedir(dh); + } +}; + +QTEST_GUILESS_MAIN(TestFileSystem) +#include "testfilesystem.moc" diff --git a/test/testfolder.cpp b/test/testfolder.cpp deleted file mode 100644 index 986c588ebdb..00000000000 --- a/test/testfolder.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * - */ - -#include - -#include "common/utility.h" -#include "folder.h" - -using namespace OCC; - -class TestFolder: public QObject -{ - Q_OBJECT -private slots: - void testFolder() - { - QFETCH(QString, folder); - QFETCH(QString, expectedFolder); - Folder *f = new Folder("alias", folder, "http://foo.bar.net"); - QCOMPARE(f->path(), expectedFolder); - delete f; - } - - void testFolder_data() - { - QTest::addColumn("folder"); - QTest::addColumn("expectedFolder"); - - QTest::newRow("unixcase") << "/foo/bar" << "/foo/bar"; - QTest::newRow("doubleslash") << "/foo//bar" << "/foo/bar"; - QTest::newRow("tripleslash") << "/foo///bar" << "/foo/bar"; - QTest::newRow("mixedslash") << "/foo/\\bar" << "/foo/bar"; - QTest::newRow("windowsfwslash") << "C:/foo/bar" << "C:/foo/bar"; - QTest::newRow("windowsbwslash") << "C:\\foo" << "C:/foo"; - QTest::newRow("windowsbwslash2") << "C:\\foo\\bar" << "C:/foo/bar"; - } - -}; - -QTEST_APPLESS_MAIN(TestFolder) -#include "testfolder.moc" diff --git a/test/testfolderman.cpp b/test/testfolderman.cpp index 47213ed6b9a..0576f49cd65 100644 --- a/test/testfolderman.cpp +++ b/test/testfolderman.cpp @@ -11,191 +11,279 @@ #include "common/utility.h" #include "folderman.h" -#include "account.h" #include "accountstate.h" -#include "configfile.h" +#include "guiutility.h" #include "testutils/testutils.h" +#include + +#ifndef Q_OS_WIN +#include +#else +#include "common/utility_win.h" +#endif + using namespace OCC; class TestFolderMan: public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + /* + * Note: this is only meant for folders/spaces, not `SpacesSyncRoot` testing! That is done in `testSpacesSyncRootAndFolderCreation`. + */ void testCheckPathValidityForNewFolder() { #ifdef Q_OS_WIN - Utility::NtfsPermissionLookupRAII ntfs_perm; + QNtfsPermissionCheckGuard ntfs_perm; #endif - QTemporaryDir dir; + auto dir = TestUtils::createTempDir(); QVERIFY(dir.isValid()); QDir dir2(dir.path()); - QVERIFY(dir2.mkpath("sub/ownCloud1/folder/f")); - QVERIFY(dir2.mkpath("ownCloud2")); - QVERIFY(dir2.mkpath("sub/free")); - QVERIFY(dir2.mkpath("free2/sub")); + QVERIFY(dir2.mkpath(QStringLiteral("sub/ownCloud1/folder/f"))); + QVERIFY(dir2.mkpath(QStringLiteral("ownCloud2"))); + QVERIFY(dir2.mkpath(QStringLiteral("sub/free"))); + QVERIFY(dir2.mkpath(QStringLiteral("free2/sub"))); { - QFile f(dir.path() + "/sub/file.txt"); + QFile f(dir.path() + QStringLiteral("/sub/file.txt")); f.open(QFile::WriteOnly); f.write("hello"); } QString dirPath = dir2.canonicalPath(); - AccountPtr account = TestUtils::createDummyAccount(); - AccountStatePtr newAccountState(new AccountState(account)); + auto newAccountState = createDummyAccount(); + FolderMan *folderman = TestUtils::folderMan(); QCOMPARE(folderman, FolderMan::instance()); - QVERIFY(folderman->addFolder(newAccountState.data(), TestUtils::createDummyFolderDefinition(dirPath + "/sub/ownCloud1"))); - QVERIFY(folderman->addFolder(newAccountState.data(), TestUtils::createDummyFolderDefinition(dirPath + "/ownCloud2"))); - + QVERIFY(folderman->addFolder( + newAccountState.get(), TestUtils::createDummyFolderDefinition(newAccountState->account(), dirPath + QStringLiteral("/sub/ownCloud1")))); + QVERIFY(folderman->addFolder( + newAccountState.get(), TestUtils::createDummyFolderDefinition(newAccountState->account(), dirPath + QStringLiteral("/ownCloud2")))); - // those should be allowed - // QString FolderMan::checkPathValidityForNewFolder(const QString& path, const QUrl &serverUrl, bool forNewDirectory) + const auto type = FolderMan::NewFolderType::SpacesFolder; + const QUuid uuid = newAccountState->account()->uuid(); - QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/sub/free"), QString()); - QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/free2/"), QString()); + QCOMPARE(folderman->checkPathValidity(dirPath + QStringLiteral("/sub/free"), type, uuid), QString()); + QCOMPARE(folderman->checkPathValidity(dirPath + QStringLiteral("/free2/"), type, uuid), QString()); // Not an existing directory -> Ok - QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/sub/bliblablu"), QString()); - QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/sub/free/bliblablu"), QString()); - // QCOMPARE(folderman->checkPathValidityForNewFolder(dirPath + "/sub/bliblablu/some/more"), QString()); + QCOMPARE(folderman->checkPathValidity(dirPath + QStringLiteral("/sub/bliblablu"), type, uuid), QString()); + QCOMPARE(folderman->checkPathValidity(dirPath + QStringLiteral("/sub/free/bliblablu"), type, uuid), QString()); // A file -> Error - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/file.txt").isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/sub/file.txt"), type, uuid).isNull()); // The following both fail because they refer to the same account - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/").isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/sub/ownCloud1"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/ownCloud2/"), type, uuid).isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath).isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/folder").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/folder/f").isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath, type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/sub/ownCloud1/folder"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/sub/ownCloud1/folder/f"), type, uuid).isNull()); #ifndef Q_OS_WIN // no links on windows, no permissions // make a bunch of links - QVERIFY(QFile::link(dirPath + "/sub/free", dirPath + "/link1")); - QVERIFY(QFile::link(dirPath + "/sub", dirPath + "/link2")); - QVERIFY(QFile::link(dirPath + "/sub/ownCloud1", dirPath + "/link3")); - QVERIFY(QFile::link(dirPath + "/sub/ownCloud1/folder", dirPath + "/link4")); + QVERIFY(QFile::link(dirPath + QStringLiteral("/sub/free"), dirPath + QStringLiteral("/link1"))); + QVERIFY(QFile::link(dirPath + QStringLiteral("/sub"), dirPath + QStringLiteral("/link2"))); + QVERIFY(QFile::link(dirPath + QStringLiteral("/sub/ownCloud1"), dirPath + QStringLiteral("/link3"))); + QVERIFY(QFile::link(dirPath + QStringLiteral("/sub/ownCloud1/folder"), dirPath + QStringLiteral("/link4"))); // Ok - QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link1").isNull()); - QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link2/free").isNull()); + QVERIFY(folderman->checkPathValidity(dirPath + QStringLiteral("/link1"), type, uuid).isNull()); + QVERIFY(folderman->checkPathValidity(dirPath + QStringLiteral("/link2/free"), type, uuid).isNull()); // Not Ok - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link2").isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/link2"), type, uuid).isNull()); // link 3 points to an existing sync folder. To make it fail, the account must be the same - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link3").isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/link3"), type, uuid).isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link4").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link3/folder").isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/link4"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/link3/folder"), type, uuid).isNull()); // test some non existing sub path (error) - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/some/sub/path").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/blublu").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/sub/ownCloud1/folder/g/h").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/link3/folder/neu_folder").isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/sub/ownCloud1/some/sub/path"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/ownCloud2/blublu"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/sub/ownCloud1/folder/g/h"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/link3/folder/neu_folder"), type, uuid).isNull()); // Subfolder of links - QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link1/subfolder").isNull()); - QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/link2/free/subfolder").isNull()); + QVERIFY(folderman->checkPathValidity(dirPath + QStringLiteral("/link1/subfolder"), type, uuid).isNull()); + QVERIFY(folderman->checkPathValidity(dirPath + QStringLiteral("/link2/free/subfolder"), type, uuid).isNull()); if (getuid() != 0) { // Should not have the rights - QVERIFY(!folderman->checkPathValidityForNewFolder("/").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder("/usr/bin/somefolder").isNull()); + QVERIFY(!folderman->checkPathValidity(QStringLiteral("/"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(QStringLiteral("/usr/bin/somefolder"), type, uuid).isNull()); } #endif -#ifdef Q_OS_WIN // drive-letter tests - if (!QFileInfo("v:/").exists()) { - QVERIFY(!folderman->checkPathValidityForNewFolder("v:").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder("v:/").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder("v:/foo").isNull()); - } - if (QFileInfo("c:/").isWritable()) { - QVERIFY(folderman->checkPathValidityForNewFolder("c:").isNull()); - QVERIFY(folderman->checkPathValidityForNewFolder("c:/").isNull()); - QVERIFY(folderman->checkPathValidityForNewFolder("c:/foo").isNull()); + if (Utility::isWindows()) { // drive-letter tests + if (!QFileInfo::exists("v:/")) { + QVERIFY(!folderman->checkPathValidity(QStringLiteral("v:"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(QStringLiteral("v:/"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(QStringLiteral("v:/foo"), type, uuid).isNull()); + } + if (QFileInfo(QStringLiteral("c:/")).isWritable()) { + QVERIFY(folderman->checkPathValidity(QStringLiteral("c:"), type, uuid).isNull()); + QVERIFY(folderman->checkPathValidity(QStringLiteral("c:/"), type, uuid).isNull()); + QVERIFY(folderman->checkPathValidity(QStringLiteral("c:/foo"), type, uuid).isNull()); + } } -#endif // Invalid paths - QVERIFY(!folderman->checkPathValidityForNewFolder("").isNull()); + QVERIFY(!folderman->checkPathValidity({}, type, uuid).isNull()); // REMOVE ownCloud2 from the filesystem, but keep a folder sync'ed to it. - QDir(dirPath + "/ownCloud2/").removeRecursively(); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/blublu").isNull()); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/ownCloud2/sub/subsub/sub").isNull()); + QDir(dirPath + QStringLiteral("/ownCloud2/")).removeRecursively(); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/ownCloud2/blublu"), type, uuid).isNull()); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/ownCloud2/sub/subsub/sub"), type, uuid).isNull()); { // check for rejection of a directory with `.sync_*.db` - QVERIFY(dir2.mkpath("db-check1")); - QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/db-check1").isNull()); - QFile f(dirPath + "/db-check1/.sync_something.db"); + QVERIFY(dir2.mkpath(QStringLiteral("db-check1"))); + QVERIFY(folderman->checkPathValidity(dirPath + QStringLiteral("/db-check1"), type, uuid).isNull()); + QFile f(dirPath + QStringLiteral("/db-check1/.sync_something.db")); QVERIFY(f.open(QFile::Truncate | QFile::WriteOnly)); f.close(); - QVERIFY(QFileInfo::exists(dirPath + "/db-check1/.sync_something.db")); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/db-check1").isNull()); + QVERIFY(QFileInfo::exists(dirPath + QStringLiteral("/db-check1/.sync_something.db"))); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/db-check1"), type, uuid).isNull()); } { // check for rejection of a directory with `._sync_*.db` - QVERIFY(dir2.mkpath("db-check2")); - QVERIFY(folderman->checkPathValidityForNewFolder(dirPath + "/db-check2").isNull()); - QFile f(dirPath + "/db-check2/._sync_something.db"); + QVERIFY(dir2.mkpath(QStringLiteral("db-check2"))); + QVERIFY(folderman->checkPathValidity(dirPath + QStringLiteral("/db-check2"), type, uuid).isNull()); + QFile f(dirPath + QStringLiteral("/db-check2/._sync_something.db")); QVERIFY(f.open(QFile::Truncate | QFile::WriteOnly)); f.close(); - QVERIFY(QFileInfo::exists(dirPath + "/db-check2/._sync_something.db")); - QVERIFY(!folderman->checkPathValidityForNewFolder(dirPath + "/db-check2").isNull()); + QVERIFY(QFileInfo::exists(dirPath + QStringLiteral("/db-check2/._sync_something.db"))); + QVERIFY(!folderman->checkPathValidity(dirPath + QStringLiteral("/db-check2"), type, uuid).isNull()); } } + /* + * Note: this is only meant for folders/spaces, not `SpacesSyncRoot` testing! That is done in `testSpacesSyncRootAndFolderCreation`. + */ void testFindGoodPathForNewSyncFolder() { // SETUP - QTemporaryDir dir; + auto dir = TestUtils::createTempDir(); QVERIFY(dir.isValid()); QDir dir2(dir.path()); - QVERIFY(dir2.mkpath("sub/ownCloud1/folder/f")); - QVERIFY(dir2.mkpath("ownCloud")); - QVERIFY(dir2.mkpath("ownCloud2")); - QVERIFY(dir2.mkpath("ownCloud2/foo")); - QVERIFY(dir2.mkpath("sub/free")); - QVERIFY(dir2.mkpath("free2/sub")); + + // Folder in config and on disk: + // todo: where is this added "to the config" and what is the "config" we are even talking about here? + QVERIFY(dir2.mkpath(QStringLiteral("sub/ownCloud1/folder/f"))); + // Folders only on disk, not in configuration: + QVERIFY(dir2.mkpath(QStringLiteral("ownCloud"))); + QVERIFY(dir2.mkpath(QStringLiteral("ownCloud2"))); + QVERIFY(dir2.mkpath(QStringLiteral("ownCloud2/foo"))); + QVERIFY(dir2.mkpath(QStringLiteral("sub/free"))); + QVERIFY(dir2.mkpath(QStringLiteral("free2/sub"))); QString dirPath = dir2.canonicalPath(); - AccountPtr account = TestUtils::createDummyAccount(); + auto newAccountState = createDummyAccount(); - AccountStatePtr newAccountState(new AccountState(account)); FolderMan *folderman = TestUtils::folderMan(); - QVERIFY(folderman->addFolder(newAccountState.data(), TestUtils::createDummyFolderDefinition(dirPath + "/sub/ownCloud/"))); - QVERIFY(folderman->addFolder(newAccountState.data(), TestUtils::createDummyFolderDefinition(dirPath + "/ownCloud2/"))); - // TEST + // todo: addFolder needs to be made private to FolderMan because there are other important impls "around" it depending on how the folder + // is being created. + // The only reason it's not currently private is because it's used in the tests, which is dubious. + // AT THE VERY LEAST, the folder needs to exist on disk *before* calling addFolder and with new refactoring, addFolder will fail if that is not the case + // historically, calling addFolder without an existing local dir resulted in a hideously broken Folder instance that is just waiting to crash ;) + QVERIFY(dir2.mkpath(QStringLiteral("sub/ownCloud/"))); + QVERIFY(folderman->addFolder( + newAccountState.get(), TestUtils::createDummyFolderDefinition(newAccountState->account(), dirPath + QStringLiteral("/sub/ownCloud/")))); - QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/oc"), - QString(dirPath + "/oc")); - QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud"), - QString(dirPath + "/ownCloud3")); - QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2"), - QString(dirPath + "/ownCloud22")); - QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2/foo"), - QString(dirPath + "/ownCloud2/foo")); - QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2/bar"), - QString(dirPath + "/ownCloud2/bar")); - QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/sub"), - QString(dirPath + "/sub2")); + QVERIFY(dir2.mkpath(QStringLiteral("ownCloud (2)/"))); + QVERIFY(folderman->addFolder( + newAccountState.get(), TestUtils::createDummyFolderDefinition(newAccountState->account(), dirPath + QStringLiteral("/ownCloud (2)/")))); + + // Test todo: verify that addFolder with path that has no existing local folder fails, and do some findGoodPathForNewSyncFolder tests around + // that too? With new updates, the folder will not be created, hence will not exist in the FolderMan folder container(s) which does change + // the results of the function under test, afaik + + // TEST + const auto folderType = FolderMan::NewFolderType::SpacesFolder; + const auto uuid = newAccountState->account()->uuid(); + + QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("oc"), folderType, uuid), dirPath + QStringLiteral("/oc")); + QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud"), folderType, uuid), dirPath + QStringLiteral("/ownCloud (3)")); + QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud2"), folderType, uuid), dirPath + QStringLiteral("/ownCloud2 (2)")); + QCOMPARE( + folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud (2)"), folderType, uuid), dirPath + QStringLiteral("/ownCloud (2) (2)")); + QCOMPARE( + folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud2/foo"), folderType, uuid), dirPath + QStringLiteral("/ownCloud2_foo")); + QCOMPARE( + folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud2/bar"), folderType, uuid), dirPath + QStringLiteral("/ownCloud2_bar")); + QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("sub"), folderType, uuid), dirPath + QStringLiteral("/sub (2)")); // REMOVE ownCloud2 from the filesystem, but keep a folder sync'ed to it. // We should still not suggest this folder as a new folder. - QDir(dirPath + "/ownCloud2/").removeRecursively(); - QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud"), - QString(dirPath + "/ownCloud3")); - QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath + "/ownCloud2"), - QString(dirPath + "/ownCloud22")); + // todo: add a verify here to check if the folder exists in the first place before removing it + QDir(dirPath + QStringLiteral("/ownCloud (2)/")).removeRecursively(); + QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud"), folderType, uuid), dirPath + QStringLiteral("/ownCloud (3)")); + QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud2"), folderType, uuid), + QString(dirPath + QStringLiteral("/ownCloud2 (2)"))); + QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("ownCloud (2)"), folderType, uuid), + QString(dirPath + QStringLiteral("/ownCloud (2) (2)"))); + + // make sure people can't do evil stuff + QCOMPARE( + folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral("../../../Bo/b"), folderType, uuid), QString(dirPath + QStringLiteral("/___Bo_b"))); + + // normalise the name + QCOMPARE(folderman->findGoodPathForNewSyncFolder(dirPath, QStringLiteral(" Bo:*<>!b "), folderType, uuid), + QString(dirPath + QStringLiteral("/Bo____!b"))); + } + + void testSpacesSyncRootAndFolderCreation() + { + const QString someDeepPath = "/Some/Deep/Path"; + + auto dir = TestUtils::createTempDir(); + QVERIFY(dir.isValid()); + QDir dir2(dir.path()); + + // Create a sync root for another account + QVERIFY(dir2.mkpath(QStringLiteral("AnotherSpacesSyncRoot") + someDeepPath)); + const auto anotherUuid = QUuid::createUuid(); + Utility::markDirectoryAsSyncRoot(dir2.filePath(QStringLiteral("AnotherSpacesSyncRoot")), anotherUuid); + + FolderMan *folderman = TestUtils::folderMan(); + QCOMPARE(folderman, FolderMan::instance()); + + QString dirPath = dir2.canonicalPath(); + const auto ourUuid = QUuid::createUuid(); + + // Spaces Sync Root in another Spaces Sync Root should fail + QVERIFY(!folderman->checkPathValidity( + dirPath + QStringLiteral("/AnotherSpacesSyncRoot/OurSpacesSyncRoot"), FolderMan::NewFolderType::SpacesSyncRoot, ourUuid) + .isNull()); + // Also deep inside + QVERIFY(!folderman->checkPathValidity( + dirPath + QStringLiteral("/AnotherSpacesSyncRoot") + someDeepPath + "/OurSpacesSyncRoot", FolderMan::NewFolderType::SpacesSyncRoot, ourUuid) + .isNull()); + // Spaces Sync Root one level up should be fine + QVERIFY(folderman->checkPathValidity(dirPath + QStringLiteral("/OurSpacesSyncRoot"), FolderMan::NewFolderType::SpacesSyncRoot, ourUuid).isNull()); + + // Create the sync root so we can test Spaces Folder creation below + QVERIFY(dir2.mkpath(QStringLiteral("OurSpacesSyncRoot"))); + Utility::markDirectoryAsSyncRoot(dir2.filePath(QStringLiteral("OurSpacesSyncRoot")), ourUuid); + + // A folder for a Space in a sync root for another account should fail + QVERIFY( + !folderman->checkPathValidity(dirPath + QStringLiteral("/AnotherSpacesSyncRoot/OurSpacesFolder"), FolderMan::NewFolderType::SpacesFolder, ourUuid) + .isNull()); + // Also deep inside + QVERIFY(!folderman->checkPathValidity( + dirPath + QStringLiteral("/AnotherSpacesSyncRoot") + someDeepPath + "/OurSpacesFolder", FolderMan::NewFolderType::SpacesFolder, ourUuid) + .isNull()); + // But in our sync root that should just be fine + QVERIFY(folderman->checkPathValidity(dirPath + QStringLiteral("/OurSpacesSyncRoot/OurSpacesFolder"), FolderMan::NewFolderType::SpacesFolder, ourUuid) + .isNull()); } }; diff --git a/test/testfoldermigration.cpp b/test/testfoldermigration.cpp deleted file mode 100644 index 24eb290f87f..00000000000 --- a/test/testfoldermigration.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * - */ - -#include - -#include "accountmanager.h" -#include "common/utility.h" -#include "configfile.h" -#include "folderman.h" - -using namespace OCC; - -namespace { -auto Settings_2_4() -{ - return QStringLiteral(R"([Accounts] - version=2 - 0\Folders\1\localPath= - 0\Folders\1\journalPath=._sync_2215dfc5505b.db - 0\url=https://demo.owncloud.com - 0\Folders\1\targetPath=/ - 0\http_oauth=true - 0\serverVersion=10.8.0.4 - 0\Folders\1\paused=false - 0\http_user=admin - 0\Folders\1\ignoreHiddenFiles=true - 0\authType=http - 0\user=admin)"); -} - -} - -class TestFolderMigration : public QObject -{ - Q_OBJECT -private: - auto writeSettings(const QTemporaryDir &tmp, const QString &content) - { - QFile settingsFile(ConfigFile::configFile()); - OC_ENFORCE(settingsFile.open(QFile::WriteOnly)); - settingsFile.write(content.toUtf8()); - settingsFile.close(); - - auto settings = ConfigFile::settingsWithGroup(QStringLiteral("Accounts")); - settings->setValue("0/Folders/1/localPath", tmp.path()); - qDebug() << settings->childGroups() << settings->childKeys(); - - - return settings; - } -private slots: - void testFolderMigrationMissingJurnalPath_data() - { - QTest::addColumn("journalPaths"); - QTest::addColumn("url"); - - QTest::newRow("2.4") << QStringList { "._sync_2215dfc5505b.db" } << "https://demo.owncloud.com"; - QTest::newRow("2.4 url") << QStringList { "._sync_2215dfc5505b.db" } << "https://demo.owncloud.com/"; - QTest::newRow("2.6") << QStringList { ".sync_2215dfc5505b.db" } << "https://demo.owncloud.com"; - QTest::newRow("2.6 url") << QStringList { ".sync_2215dfc5505b.db" } << "https://demo.owncloud.com/"; - QTest::newRow("2.6 multi") << QStringList { ".sync_2215dfc5505b.db", "._sync_2215dfc5505b.db" } << "https://demo.owncloud.com"; - QTest::newRow("2.9") << QStringList { ".sync_journal.db" } << "https://demo.owncloud.com"; - QTest::newRow("2.9 url") << QStringList { ".sync_journal.db" } << "https://demo.owncloud.com/"; - QTest::newRow("2.9 multi") << QStringList { ".sync_journal.db", ".sync_2215dfc5505b.db", "._sync_2215dfc5505b.db" } << "https://demo.owncloud.com"; - } - - void testFolderMigrationMissingJurnalPath() - { - QFETCH(QStringList, journalPaths); - QFETCH(QString, url); - QTemporaryDir tmp; - const auto settings = writeSettings(tmp, Settings_2_4()); - settings->setValue("0/url", url); - settings->remove("0/Folders/1/journalPath"); - QVERIFY(!settings->value("0/Folders/1/journalPath").isValid()); - - for (const auto &journalPath : journalPaths) { - QFile syncDb(tmp.filePath(journalPath)); - QVERIFY(syncDb.open(QFile::WriteOnly)); - syncDb.write("foo"); - syncDb.close(); - } - - AccountManager::instance()->restore(); - - settings->beginGroup("0/Folders"); - TestUtils::folderMan()->setupFoldersHelper(*settings.get(), AccountManager::instance()->accounts().first(), {}, true, false); - settings->endGroup(); - - QCOMPARE(journalPaths.first(), settings->value("0/Folders/1/journalPath")); - delete TestUtils::folderMan(); - } -}; - -QTEST_MAIN(TestFolderMigration) -#include "testfoldermigration.moc" diff --git a/test/testfolderwatcher.cpp b/test/testfolderwatcher.cpp index 889b5b2af1b..d3afba4c13d 100644 --- a/test/testfolderwatcher.cpp +++ b/test/testfolderwatcher.cpp @@ -7,19 +7,35 @@ #include +#include "common/chronoelapsedtimer.h" #include "common/utility.h" #include "folderwatcher.h" #include "testutils/testutils.h" +using namespace std::chrono_literals; + +namespace { +class FolderWatcherForTests : public OCC::FolderWatcher +{ + Q_OBJECT +public: + using OCC::FolderWatcher::FolderWatcher; + + FolderWatcherForTests() + : FolderWatcher(nullptr) { }; + + bool pathIsIgnored(const QString &) const override { return false; } +}; + void touch(const QString &file) { #ifdef Q_OS_WIN OCC::TestUtils::writeRandomFile(file); #else QString cmd; - cmd = QString("touch %1").arg(file); + cmd = QStringLiteral("touch %1").arg(file); qDebug() << "Command: " << cmd; - system(cmd.toLocal8Bit()); + system(cmd.toLocal8Bit().constData()); #endif } @@ -29,9 +45,9 @@ void mkdir(const QString &file) QDir dir; dir.mkdir(file); #else - QString cmd = QString("mkdir %1").arg(file); + QString cmd = QStringLiteral("mkdir %1").arg(file); qDebug() << "Command: " << cmd; - system(cmd.toLocal8Bit()); + system(cmd.toLocal8Bit().constData()); #endif } @@ -41,9 +57,9 @@ void rmdir(const QString &file) QDir dir; dir.rmdir(file); #else - QString cmd = QString("rmdir %1").arg(file); + QString cmd = QStringLiteral("rmdir %1").arg(file); qDebug() << "Command: " << cmd; - system(cmd.toLocal8Bit()); + system(cmd.toLocal8Bit().constData()); #endif } @@ -52,9 +68,9 @@ void rm(const QString &file) #ifdef Q_OS_WIN QFile::remove(file); #else - QString cmd = QString("rm %1").arg(file); + QString cmd = QStringLiteral("rm %1").arg(file); qDebug() << "Command: " << cmd; - system(cmd.toLocal8Bit()); + system(cmd.toLocal8Bit().constData()); #endif } @@ -63,11 +79,12 @@ void mv(const QString &file1, const QString &file2) #ifdef Q_OS_WIN QFile::rename(file1, file2); #else - QString cmd = QString("mv %1 %2").arg(file1, file2); + QString cmd = QStringLiteral("mv %1 %2").arg(file1, file2); qDebug() << "Command: " << cmd; - system(cmd.toLocal8Bit()); + system(cmd.toLocal8Bit().constData()); #endif } +} using namespace OCC; @@ -75,24 +92,25 @@ class TestFolderWatcher : public QObject { Q_OBJECT - QTemporaryDir _root; + const QTemporaryDir _root = TestUtils::createTempDir(); QString _rootPath; - QScopedPointer _watcher; + QScopedPointer _watcher; QScopedPointer _pathChangedSpy; bool waitForPathChanged(const QString &path) { - QElapsedTimer t; - t.start(); - while (t.elapsed() < 5000) { + Utility::ChronoElapsedTimer t; + // Current interval is 10s, if we didn't get a change in 15s something did not work + // https://github.com/owncloud/client/blob/82b5a1e1bb2b05503e0774d3be5e328c4cd207e2/src/gui/folderwatcher.cpp#L40-L40 + while (t.duration() < 15s) { // Check if it was already reported as changed by the watcher for (int i = 0; i < _pathChangedSpy->size(); ++i) { const auto &args = _pathChangedSpy->at(i); - if (args.first().toString() == path) + if (args.first().value>().contains(path)) return true; } // Wait a bit and test again (don't bother checking if we timed out or not) - _pathChangedSpy->wait(200); + _pathChangedSpy->wait(1000); } return false; } @@ -109,17 +127,17 @@ class TestFolderWatcher : public QObject _rootPath = rootDir.canonicalPath(); qDebug() << "creating test directory tree in " << _rootPath; - rootDir.mkpath("a1/b1/c1"); - rootDir.mkpath("a1/b1/c2"); - rootDir.mkpath("a1/b2/c1"); - rootDir.mkpath("a1/b3/c3"); - rootDir.mkpath("a2/b3/c3"); - TestUtils::writeRandomFile(_rootPath + "/a1/random.bin"); - TestUtils::writeRandomFile(_rootPath + "/a1/b2/todelete.bin"); - TestUtils::writeRandomFile(_rootPath + "/a2/renamefile"); - TestUtils::writeRandomFile(_rootPath + "/a1/movefile"); - - _watcher.reset(new FolderWatcher); + rootDir.mkpath(QStringLiteral("a1/b1/c1")); + rootDir.mkpath(QStringLiteral("a1/b1/c2")); + rootDir.mkpath(QStringLiteral("a1/b2/c1")); + rootDir.mkpath(QStringLiteral("a1/b3/c3")); + rootDir.mkpath(QStringLiteral("a2/b3/c3")); + TestUtils::writeRandomFile(_rootPath + QStringLiteral("/a1/random.bin")); + TestUtils::writeRandomFile(_rootPath + QStringLiteral("/a1/b2/todelete.bin")); + TestUtils::writeRandomFile(_rootPath + QStringLiteral("/a2/renamefile")); + TestUtils::writeRandomFile(_rootPath + QStringLiteral("/a1/movefile")); + + _watcher.reset(new FolderWatcherForTests()); _watcher->init(_rootPath); _pathChangedSpy.reset(new QSignalSpy(_watcher.data(), &FolderWatcher::pathChanged)); } @@ -129,11 +147,11 @@ class TestFolderWatcher : public QObject int n = 0; const auto &entryList = QDir(path).entryList(QDir::Dirs | QDir::NoDotAndDotDot); for (const auto &sub : entryList) - n += 1 + countFolders(path + '/' + sub); + n += 1 + countFolders(path + QLatin1Char('/') + sub); return n; } -private slots: +private Q_SLOTS: void init() { _pathChangedSpy->clear(); @@ -146,40 +164,40 @@ private slots: } void testACreate() { // create a new file - QString file(_rootPath + "/foo.txt"); + QString file(_rootPath + QStringLiteral("/foo.txt")); QString cmd; - cmd = QString("echo \"xyz\" > %1").arg(file); + cmd = QStringLiteral("echo \"xyz\" > %1").arg(file); qDebug() << "Command: " << cmd; - system(cmd.toLocal8Bit()); + system(cmd.toLocal8Bit().constData()); QVERIFY(waitForPathChanged(file)); } void testATouch() { // touch an existing file. - QString file(_rootPath + "/a1/random.bin"); + QString file(_rootPath + QStringLiteral("/a1/random.bin")); touch(file); QVERIFY(waitForPathChanged(file)); } void testCreateADir() { - QString file(_rootPath+"/a1/b1/new_dir"); + QString file(_rootPath + QStringLiteral("/a1/b1/new_dir")); mkdir(file); QVERIFY(waitForPathChanged(file)); // Notifications from that new folder arrive too - QString file2(_rootPath + "/a1/b1/new_dir/contained"); + QString file2(_rootPath + QStringLiteral("/a1/b1/new_dir/contained")); touch(file2); QVERIFY(waitForPathChanged(file2)); } void testRemoveADir() { - QString file(_rootPath+"/a1/b3/c3"); + QString file(_rootPath + QStringLiteral("/a1/b3/c3")); rmdir(file); QVERIFY(waitForPathChanged(file)); } void testRemoveAFile() { - QString file(_rootPath+"/a1/b2/todelete.bin"); + QString file(_rootPath + QStringLiteral("/a1/b2/todelete.bin")); QVERIFY(QFile::exists(file)); rm(file); QVERIFY(!QFile::exists(file)); @@ -188,8 +206,8 @@ private slots: } void testRenameAFile() { - QString file1(_rootPath+"/a2/renamefile"); - QString file2(_rootPath+"/a2/renamefile.renamed"); + QString file1(_rootPath + QStringLiteral("/a2/renamefile")); + QString file2(_rootPath + QStringLiteral("/a2/renamefile.renamed")); QVERIFY(QFile::exists(file1)); mv(file1, file2); QVERIFY(QFile::exists(file2)); @@ -199,8 +217,8 @@ private slots: } void testMoveAFile() { - QString old_file(_rootPath+"/a1/movefile"); - QString new_file(_rootPath+"/a2/movefile.renamed"); + QString old_file(_rootPath + QStringLiteral("/a1/movefile")); + QString new_file(_rootPath + QStringLiteral("/a2/movefile.renamed")); QVERIFY(QFile::exists(old_file)); mv(old_file, new_file); QVERIFY(QFile::exists(new_file)); @@ -210,8 +228,8 @@ private slots: } void testRenameDirectorySameBase() { - QString old_file(_rootPath+"/a1/b1"); - QString new_file(_rootPath+"/a1/brename"); + QString old_file(_rootPath + QStringLiteral("/a1/b1")); + QString new_file(_rootPath + QStringLiteral("/a1/brename")); QVERIFY(QFile::exists(old_file)); mv(old_file, new_file); QVERIFY(QFile::exists(new_file)); @@ -221,18 +239,18 @@ private slots: // Verify that further notifications end up with the correct paths - QString file(_rootPath+"/a1/brename/c1/random.bin"); + QString file(_rootPath + QStringLiteral("/a1/brename/c1/random.bin")); touch(file); QVERIFY(waitForPathChanged(file)); - QString dir(_rootPath+"/a1/brename/newfolder"); + QString dir(_rootPath + QStringLiteral("/a1/brename/newfolder")); mkdir(dir); QVERIFY(waitForPathChanged(dir)); } void testRenameDirectoryDifferentBase() { - QString old_file(_rootPath+"/a1/brename"); - QString new_file(_rootPath+"/bren"); + QString old_file(_rootPath + QStringLiteral("/a1/brename")); + QString new_file(_rootPath + QStringLiteral("/bren")); QVERIFY(QFile::exists(old_file)); mv(old_file, new_file); QVERIFY(QFile::exists(new_file)); @@ -242,11 +260,11 @@ private slots: // Verify that further notifications end up with the correct paths - QString file(_rootPath+"/bren/c1/random.bin"); + QString file(_rootPath + QStringLiteral("/bren/c1/random.bin")); touch(file); QVERIFY(waitForPathChanged(file)); - QString dir(_rootPath+"/bren/newfolder2"); + QString dir(_rootPath + QStringLiteral("/bren/newfolder2")); mkdir(dir); QVERIFY(waitForPathChanged(dir)); } diff --git a/test/testinotifywatcher.cpp b/test/testinotifywatcher.cpp index ee858c98c71..f0424ccb287 100644 --- a/test/testinotifywatcher.cpp +++ b/test/testinotifywatcher.cpp @@ -19,20 +19,17 @@ class TestInotifyWatcher: public FolderWatcherPrivate private: QString _root; -private slots: +private Q_SLOTS: void initTestCase() { - qsrand(QTime::currentTime().msec()); - - _root = QDir::tempPath() + "/" + "test_" + QString::number(qrand()); + _root = QDir::tempPath() + QStringLiteral("/test_") + QString::number(QRandomGenerator::global()->generate()); qDebug() << "creating test directory tree in " << _root; QDir rootDir(_root); - rootDir.mkpath(_root + "/a1/b1/c1"); - rootDir.mkpath(_root + "/a1/b1/c2"); - rootDir.mkpath(_root + "/a1/b2/c1"); - rootDir.mkpath(_root + "/a1/b3/c3"); - rootDir.mkpath(_root + "/a2/b3/c3"); - + rootDir.mkpath(_root + QStringLiteral("/a1/b1/c1")); + rootDir.mkpath(_root + QStringLiteral("/a1/b1/c2")); + rootDir.mkpath(_root + QStringLiteral("/a1/b2/c1")); + rootDir.mkpath(_root + QStringLiteral("/a1/b3/c3")); + rootDir.mkpath(_root + QStringLiteral("/a2/b3/c3")); } // Test the recursive path listing function findFoldersBelow @@ -40,23 +37,23 @@ private slots: QStringList dirs; bool ok = findFoldersBelow(QDir(_root), dirs); - QVERIFY( dirs.indexOf(_root + "/a1")>-1); - QVERIFY( dirs.indexOf(_root + "/a1/b1")>-1); - QVERIFY( dirs.indexOf(_root + "/a1/b1/c1")>-1); - QVERIFY( dirs.indexOf(_root + "/a1/b1/c2")>-1); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a1")) > -1); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a1/b1")) > -1); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a1/b1/c1")) > -1); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a1/b1/c2")) > -1); - QVERIFY(TestUtils::writeRandomFile(_root + "/a1/rand1.dat")); - QVERIFY(TestUtils::writeRandomFile(_root + "/a1/b1/rand2.dat")); - QVERIFY(TestUtils::writeRandomFile(_root + "/a1/b1/c1/rand3.dat")); + QVERIFY(TestUtils::writeRandomFile(_root + QStringLiteral("/a1/rand1.dat"))); + QVERIFY(TestUtils::writeRandomFile(_root + QStringLiteral("/a1/b1/rand2.dat"))); + QVERIFY(TestUtils::writeRandomFile(_root + QStringLiteral("/a1/b1/c1/rand3.dat"))); - QVERIFY( dirs.indexOf(_root + "/a1/b2")>-1); - QVERIFY( dirs.indexOf(_root + "/a1/b2/c1")>-1); - QVERIFY( dirs.indexOf(_root + "/a1/b3")>-1); - QVERIFY( dirs.indexOf(_root + "/a1/b3/c3")>-1); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a1/b2")) > -1); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a1/b2/c1")) > -1); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a1/b3")) > -1); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a1/b3/c3")) > -1); - QVERIFY( dirs.indexOf(_root + "/a2")); - QVERIFY( dirs.indexOf(_root + "/a2/b3")); - QVERIFY( dirs.indexOf(_root + "/a2/b3/c3")); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a2"))); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a2/b3"))); + QVERIFY(dirs.indexOf(_root + QStringLiteral("/a2/b3/c3"))); QVERIFY2(dirs.count() == 11, "Directory count wrong."); @@ -65,7 +62,7 @@ private slots: void cleanupTestCase() { if( _root.startsWith(QDir::tempPath() )) { - system( QString("rm -rf %1").arg(_root).toLocal8Bit() ); + system(QStringLiteral("rm -rf %1").arg(_root).toLocal8Bit().constData()); } } }; diff --git a/test/testjhash.cpp b/test/testjhash.cpp new file mode 100644 index 00000000000..5dd84e611f3 --- /dev/null +++ b/test/testjhash.cpp @@ -0,0 +1,302 @@ +/* + * Tests are taken form lookup2.c and lookup8.c + * by Bob Jenkins, December 1996, Public Domain. + * + * See http://burtleburtle.net/bob/hash/evahash.html + */ + +#include + +#include "common/c_jhash.h" + +#define HASHSTATE 1 +#define HASHLEN 1 +#define MAXPAIR 80 +#define MAXLEN 70 + +inline std::uint8_t operator"" _u8(unsigned long long value) +{ + return static_cast(value); +} + +class TestJHash : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void check_c_jhash_trials() + { + uint8_t qa[MAXLEN + 1], qb[MAXLEN + 2], *a = &qa[0], *b = &qb[1]; + uint64_t c[HASHSTATE], d[HASHSTATE], i, j = 0, k, l, m, z; + uint64_t e[HASHSTATE], f[HASHSTATE], g[HASHSTATE], h[HASHSTATE]; + uint64_t x[HASHSTATE], y[HASHSTATE]; + uint64_t hlen; + + for (hlen = 0; hlen < MAXLEN; ++hlen) { + z = 0; + for (i = 0; i < hlen; ++i) { /*----------------------- for each input byte, */ + for (j = 0; j < 8; ++j) { /*------------------------ for each input bit, */ + for (m = 1; m < 8; ++m) { /*------------ for serveral possible initvals, */ + for (l = 0; l < HASHSTATE; ++l) + e[l] = f[l] = g[l] = h[l] = x[l] = y[l] = ~0ull; + + /*---- check that every output bit is affected by that input bit */ + for (k = 0; k < MAXPAIR; k += 2) { + uint64_t finished = 1; + /* keys have one bit different */ + for (l = 0; l < hlen + 1; ++l) { + a[l] = b[l] = 0; + } + /* have a and b be two keys differing in only one bit */ + a[i] ^= (k << j); + a[i] ^= (k >> (8 - j)); + c[0] = c_jhash64(a, hlen, m); + b[i] ^= ((k + 1) << j); + b[i] ^= ((k + 1) >> (8 - j)); + d[0] = c_jhash64(b, hlen, m); + /* check every bit is 1, 0, set, and not set at least once */ + for (l = 0; l < HASHSTATE; ++l) { + e[l] &= (c[l] ^ d[l]); + f[l] &= ~(c[l] ^ d[l]); + g[l] &= c[l]; + h[l] &= ~c[l]; + x[l] &= d[l]; + y[l] &= ~d[l]; + if (e[l] | f[l] | g[l] | h[l] | x[l] | y[l]) + finished = 0; + } + if (finished) + break; + } + if (k > z) + z = k; + if (k == MAXPAIR) { + auto format = [](int i) { + return QString::number(i, '0', 8); + }; + QFAIL(qPrintable( + QStringLiteral("Some bit didn't change: %1 %2 %3 %4 %5 %6 ").arg(format(e[0]), format(f[0]), format(g[0]), format(h[0]), format(x[0]), format(y[0])) // + + QStringLiteral("i %1 j %2 m %3 len %4").arg(QString::number(i), QString::number(j), QString::number(m), QString::number(hlen)))); + } + if (z == MAXPAIR) { + if (z < MAXPAIR) { + QVERIFY(z < MAXPAIR); + // print_error("%u trials needed, should be less than 40\n", z/2); + return; + } + } + } + } + } + } + } + + void check_c_jhash_alignment_problems() + { + uint64_t test; + uint8_t buf[MAXLEN + 20], *b; + uint64_t len; + uint8_t q[] = "This is the time for all good men to come to the aid of their country"; + uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country"; + uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country"; + uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country"; + uint64_t h, i, j, ref, x, y; + + + test = c_jhash64(q, sizeof(q) - 1, 0); + QCOMPARE(test, c_jhash64(qq + 1, sizeof(q) - 1, 0)); + QCOMPARE(test, c_jhash64(qq + 1, sizeof(q) - 1, 0)); + QCOMPARE(test, c_jhash64(qqq + 2, sizeof(q) - 1, 0)); + QCOMPARE(test, c_jhash64(qqqq + 3, sizeof(q) - 1, 0)); + for (h = 0, b = buf + 1; h < 8; ++h, ++b) { + for (i = 0; i < MAXLEN; ++i) { + len = i; + for (j = 0; j < i; ++j) + *(b + j) = 0; + + /* these should all be equal */ + ref = c_jhash64(b, len, 1); + *(b + i) = ~0_u8; + *(b - 1) = ~0_u8; + x = c_jhash64(b, len, 1); + y = c_jhash64(b, len, 1); + QVERIFY(!(ref != x) || (ref != y)); + } + } + } + + void check_c_jhash_null_strings() + { + uint8_t buf[1]; + uint64_t h, i, t; + + + buf[0] = ~0_u8; + for (i = 0, h = 0; i < 8; ++i) { + t = h; + h = c_jhash64(buf, 0, h); + QVERIFY(t != h); + // print_error("0-byte-string check failed: t = %.8x, h = %.8x", t, h); + } + } + + void check_c_jhash64_trials() + { + uint8_t qa[MAXLEN + 1], qb[MAXLEN + 2]; + uint8_t *a, *b; + uint64_t c[HASHSTATE], d[HASHSTATE], i, j = 0, k, l, m, z; + uint64_t e[HASHSTATE], f[HASHSTATE], g[HASHSTATE], h[HASHSTATE]; + uint64_t x[HASHSTATE], y[HASHSTATE]; + uint64_t hlen; + + a = &qa[0]; + b = &qb[1]; + + for (hlen = 0; hlen < MAXLEN; ++hlen) { + z = 0; + for (i = 0; i < hlen; ++i) { /*----------------------- for each byte, */ + for (j = 0; j < 8; ++j) { /*------------------------ for each bit, */ + for (m = 0; m < 8; ++m) { /*-------- for serveral possible levels, */ + for (l = 0; l < HASHSTATE; ++l) + e[l] = f[l] = g[l] = h[l] = x[l] = y[l] = ~0ul; + + /*---- check that every input bit affects every output bit */ + for (k = 0; k < MAXPAIR; k += 2) { + uint64_t finished = 1; + /* keys have one bit different */ + for (l = 0; l < hlen + 1; ++l) { + a[l] = b[l] = 0; + } + /* have a and b be two keys differing in only one bit */ + a[i] ^= (k << j); + a[i] ^= (k >> (8 - j)); + c[0] = c_jhash64(a, hlen, m); + b[i] ^= ((k + 1) << j); + b[i] ^= ((k + 1) >> (8 - j)); + d[0] = c_jhash64(b, hlen, m); + /* check every bit is 1, 0, set, and not set at least once */ + for (l = 0; l < HASHSTATE; ++l) { + e[l] &= (c[l] ^ d[l]); + f[l] &= ~(c[l] ^ d[l]); + g[l] &= c[l]; + h[l] &= ~c[l]; + x[l] &= d[l]; + y[l] &= ~d[l]; + if (e[l] | f[l] | g[l] | h[l] | x[l] | y[l]) + finished = 0; + } + if (finished) + break; + } + if (k > z) + z = k; + if (k == MAXPAIR) { +#if 0 + print_error("Some bit didn't change: "); + print_error("%.8llx %.8llx %.8llx %.8llx %.8llx %.8llx ", + (long long unsigned int) e[0], + (long long unsigned int) f[0], + (long long unsigned int) g[0], + (long long unsigned int) h[0], + (long long unsigned int) x[0], + (long long unsigned int) y[0]); + print_error("i %d j %d m %d len %d\n", + i,j,m,hlen); +#endif + } + if (z == MAXPAIR) { + if (z < MAXPAIR) { +#if 0 + print_error("%lu trials needed, should be less than 40", z/2); +#endif + QVERIFY(z < MAXPAIR); + } + return; + } + } + } + } + } + } + + void check_c_jhash64_alignment_problems(void **state) + { + uint8_t buf[MAXLEN + 20], *b; + uint64_t len; + uint8_t q[] = "This is the time for all good men to come to the aid of their country"; + uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country"; + uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country"; + uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country"; + uint8_t o[] = "xxxxThis is the time for all good men to come to the aid of their country"; + uint8_t oo[] = "xxxxxThis is the time for all good men to come to the aid of their country"; + uint8_t ooo[] = "xxxxxxThis is the time for all good men to come to the aid of their country"; + uint8_t oooo[] = "xxxxxxxThis is the time for all good men to come to the aid of their country"; + uint64_t h, i, j, ref, t, x, y; + + (void)state; /* unused */ + + h = c_jhash64(q + 0, (sizeof(q) - 1), 0); + t = h; + QCOMPARE(t, h); + // , "%.8lx%.8lx\n", h, (h>>32)); + h = c_jhash64(qq + 1, (sizeof(q) - 1), 0); + QCOMPARE(t, h); + // , "%.8lx%.8lx\n", h, (h>>32)); + h = c_jhash64(qqq + 2, (sizeof(q) - 1), 0); + QCOMPARE(t, h); + // , "%.8lx%.8lx\n", h, (h>>32)); + h = c_jhash64(qqqq + 3, (sizeof(q) - 1), 0); + QCOMPARE(t, h); + // , "%.8lx%.8lx\n", h, (h>>32)); + h = c_jhash64(o + 4, (sizeof(q) - 1), 0); + QCOMPARE(t, h); + // , "%.8lx%.8lx\n", h, (h>>32)); + h = c_jhash64(oo + 5, (sizeof(q) - 1), 0); + QCOMPARE(t, h); + // , "%.8lx%.8lx\n", h, (h>>32)); + h = c_jhash64(ooo + 6, (sizeof(q) - 1), 0); + QCOMPARE(t, h); + // , "%.8lx%.8lx\n", h, (h>>32)); + h = c_jhash64(oooo + 7, (sizeof(q) - 1), 0); + QCOMPARE(t, h); + // , "%.8lx%.8lx\n", h, (h>>32)); + for (h = 0, b = buf + 1; h < 8; ++h, ++b) { + for (i = 0; i < MAXLEN; ++i) { + len = i; + for (j = 0; j < i; ++j) + *(b + j) = 0; + + /* these should all be equal */ + ref = c_jhash64(b, len, 1); + *(b + i) = ~0_u8; + *(b - 1) = ~0_u8; + x = c_jhash64(b, len, 1); + y = c_jhash64(b, len, 1); + QVERIFY(!(ref != x) || (ref != y)); +#if 0 + print_error("alignment error: %.8lx %.8lx %.8lx %ld %ld\n", ref, x, y, h, i); +#endif + } + } + } + + void check_c_jhash64_null_strings() + { + uint8_t buf[1]; + uint64_t h, i, t; + + buf[0] = ~0_u8; + for (i = 0, h = 0; i < 8; ++i) { + t = h; + h = c_jhash64(buf, 0, h); + QVERIFY(t != h); +#if 0 + print_error("0-byte-string check failed: t = %.8lx, h = %.8lx", t, h); +#endif + } + } +}; + + +QTEST_APPLESS_MAIN(TestJHash) +#include "testjhash.moc" diff --git a/test/testjobqueue.cpp b/test/testjobqueue.cpp index 7fca5408246..a73e7782214 100644 --- a/test/testjobqueue.cpp +++ b/test/testjobqueue.cpp @@ -19,8 +19,9 @@ class TestJob : public AbstractNetworkJob { // AbstractNetworkJob interface public: - TestJob(AccountPtr account) - : AbstractNetworkJob(account, QStringLiteral("/A/a1")) + // TODO: davurl + TestJob(Account *account) + : AbstractNetworkJob(account, account->davUrl(), QStringLiteral("/A/a1")) { } @@ -39,14 +40,13 @@ class TestJob : public AbstractNetworkJob buf->setData(xml); buf->open(QIODevice::ReadOnly); // assumes ownership - sendRequest("PROPFIND", makeDavUrl(path()), req, buf); + sendRequest("PROPFIND", req, buf); AbstractNetworkJob::start(); } protected: - bool finished() override + void finished() override { - return true; } }; diff --git a/test/testlocaldiscovery.cpp b/test/testlocaldiscovery.cpp index 643a3c96aec..7b9d3543822 100644 --- a/test/testlocaldiscovery.cpp +++ b/test/testlocaldiscovery.cpp @@ -5,10 +5,13 @@ * */ -#include #include "testutils/syncenginetestutils.h" -#include +#include "testutils/testutils.h" + #include +#include + +#include using namespace OCC; @@ -16,192 +19,340 @@ class TestLocalDiscovery : public QObject { Q_OBJECT -private slots: - // Check correct behavior when local discovery is partially drawn from the db +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } + + /// Check correct behavior when local discovery is partially drawn from the db void testLocalDiscoveryStyle() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - LocalDiscoveryTracker tracker; - connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); - connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); + LocalDiscoveryTracker tracker(nullptr); + connect(fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); + connect(fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); // More subdirectories are useful for testing - fakeFolder.localModifier().mkdir("A/X"); - fakeFolder.localModifier().mkdir("A/Y"); - fakeFolder.localModifier().insert("A/X/x1"); - fakeFolder.localModifier().insert("A/Y/y1"); - tracker.addTouchedPath("A/X"); + fakeFolder.localModifier().mkdir(QStringLiteral("A/X")); + fakeFolder.localModifier().mkdir(QStringLiteral("A/Y")); + fakeFolder.localModifier().insert(QStringLiteral("A/X/x1")); + fakeFolder.localModifier().insert(QStringLiteral("A/Y/y1")); + tracker.addTouchedPath(QStringLiteral("A/X")); tracker.startSyncFullDiscovery(); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QVERIFY(tracker.localDiscoveryPaths().empty()); // Test begins - fakeFolder.localModifier().insert("A/a3"); - fakeFolder.localModifier().insert("A/X/x2"); - fakeFolder.localModifier().insert("A/Y/y2"); - fakeFolder.localModifier().insert("B/b3"); - fakeFolder.remoteModifier().insert("C/c3"); - fakeFolder.remoteModifier().appendByte("C/c1"); - tracker.addTouchedPath("A/X"); - - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.localModifier().insert(QStringLiteral("A/a3")); + fakeFolder.localModifier().insert(QStringLiteral("A/X/x2")); + fakeFolder.localModifier().insert(QStringLiteral("A/Y/y2")); + fakeFolder.localModifier().insert(QStringLiteral("B/b3")); + fakeFolder.remoteModifier().insert(QStringLiteral("C/c3")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c1")); + tracker.addTouchedPath(QStringLiteral("A/X")); + + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(fakeFolder.currentRemoteState().find("A/a3")); - QVERIFY(fakeFolder.currentRemoteState().find("A/X/x2")); - QVERIFY(!fakeFolder.currentRemoteState().find("A/Y/y2")); - QVERIFY(!fakeFolder.currentRemoteState().find("B/b3")); - QVERIFY(fakeFolder.currentLocalState().find("C/c3")); - QCOMPARE(fakeFolder.syncEngine().lastLocalDiscoveryStyle(), LocalDiscoveryStyle::DatabaseAndFilesystem); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("A/a3"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("A/X/x2"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("A/Y/y2"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("B/b3"))); + QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("C/c3"))); + QCOMPARE(fakeFolder.syncEngine()->lastLocalDiscoveryStyle(), LocalDiscoveryStyle::DatabaseAndFilesystem); QVERIFY(tracker.localDiscoveryPaths().empty()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(fakeFolder.syncEngine().lastLocalDiscoveryStyle(), LocalDiscoveryStyle::FilesystemOnly); + QCOMPARE(fakeFolder.syncEngine()->lastLocalDiscoveryStyle(), LocalDiscoveryStyle::FilesystemOnly); QVERIFY(tracker.localDiscoveryPaths().empty()); } void testLocalDiscoveryDecision() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - auto &engine = fakeFolder.syncEngine(); - - QVERIFY(engine.shouldDiscoverLocally("")); - QVERIFY(engine.shouldDiscoverLocally("A")); - QVERIFY(engine.shouldDiscoverLocally("A/X")); - - fakeFolder.syncEngine().setLocalDiscoveryOptions( - LocalDiscoveryStyle::DatabaseAndFilesystem, - { "A/X", "A/X space", "A/X/beta", "foo bar space/touch", "foo/", "zzz", "zzzz" }); - - QVERIFY(engine.shouldDiscoverLocally("")); - QVERIFY(engine.shouldDiscoverLocally("A")); - QVERIFY(engine.shouldDiscoverLocally("A/X")); - QVERIFY(!engine.shouldDiscoverLocally("B")); - QVERIFY(!engine.shouldDiscoverLocally("A B")); - QVERIFY(!engine.shouldDiscoverLocally("B/X")); - QVERIFY(engine.shouldDiscoverLocally("foo bar space")); - QVERIFY(engine.shouldDiscoverLocally("foo")); - QVERIFY(!engine.shouldDiscoverLocally("foo bar")); - QVERIFY(!engine.shouldDiscoverLocally("foo bar/touch")); - // These are within "A/X" so they should be discovered - QVERIFY(engine.shouldDiscoverLocally("A/X/alpha")); - QVERIFY(engine.shouldDiscoverLocally("A/X beta")); - QVERIFY(engine.shouldDiscoverLocally("A/X/Y")); - QVERIFY(engine.shouldDiscoverLocally("A/X space")); - QVERIFY(engine.shouldDiscoverLocally("A/X space/alpha")); - QVERIFY(!engine.shouldDiscoverLocally("A/Xylo/foo")); - QVERIFY(engine.shouldDiscoverLocally("zzzz/hello")); - QVERIFY(!engine.shouldDiscoverLocally("zzza/hello")); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + auto engine = fakeFolder.syncEngine(); + + QVERIFY(engine->shouldDiscoverLocally(QString())); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X"))); + + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, + {QStringLiteral("A/X"), QStringLiteral("A/X space"), QStringLiteral("A/X/beta"), QStringLiteral("foo bar space/touch"), QStringLiteral("foo/"), + QStringLiteral("zzz"), QStringLiteral("zzzz")}); + + QVERIFY(engine->shouldDiscoverLocally(QString())); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("B"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("A B"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("B/X"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("foo bar space"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("foo"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("foo bar"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("foo bar/touch"))); + // These are within QStringLiteral("A/X") so they should be discovered + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X/alpha"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X beta"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X/Y"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X space"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("A/X space/alpha"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("A/Xylo/foo"))); + QVERIFY(engine->shouldDiscoverLocally(QStringLiteral("zzzz/hello"))); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("zzza/hello"))); QEXPECT_FAIL("", "There is a possibility of false positives if the set contains a path " "which is a prefix, and that prefix is followed by a character less than '/'", Continue); - QVERIFY(!engine.shouldDiscoverLocally("A/X o")); + QVERIFY(!engine->shouldDiscoverLocally(QStringLiteral("A/X o"))); - fakeFolder.syncEngine().setLocalDiscoveryOptions( - LocalDiscoveryStyle::DatabaseAndFilesystem, - {}); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {}); - QVERIFY(!engine.shouldDiscoverLocally("")); + QVERIFY(!engine->shouldDiscoverLocally(QString())); } // Check whether item success and item failure adjusts the // tracker correctly. void testTrackerItemCompletion() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - LocalDiscoveryTracker tracker; - connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); - connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); + LocalDiscoveryTracker tracker(nullptr); + connect(fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); + connect(fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); auto trackerContains = [&](const char *path) { - return tracker.localDiscoveryPaths().find(path) != tracker.localDiscoveryPaths().end(); + return tracker.localDiscoveryPaths().find(QString::fromLatin1(path)) != tracker.localDiscoveryPaths().end(); }; - tracker.addTouchedPath("A/spurious"); + tracker.addTouchedPath(QStringLiteral("A/spurious")); - fakeFolder.localModifier().insert("A/a3"); - tracker.addTouchedPath("A/a3"); + fakeFolder.localModifier().insert(QStringLiteral("A/a3")); + tracker.addTouchedPath(QStringLiteral("A/a3")); - fakeFolder.localModifier().insert("A/a4"); - fakeFolder.serverErrorPaths().append("A/a4"); + fakeFolder.localModifier().insert(QStringLiteral("A/a4")); + fakeFolder.serverErrorPaths().append(QStringLiteral("A/a4")); // We're not adding a4 as touched, it's in the same folder as a3 and will be seen. // And due to the error it should be added to the explicit list while a3 gets removed. - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(fakeFolder.currentRemoteState().find("A/a3")); - QVERIFY(!fakeFolder.currentRemoteState().find("A/a4")); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("A/a3"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("A/a4"))); QVERIFY(!trackerContains("A/a3")); QVERIFY(trackerContains("A/a4")); QVERIFY(trackerContains("A/spurious")); // not removed since overall sync not successful - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly); tracker.startSyncFullDiscovery(); - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(!fakeFolder.currentRemoteState().find("A/a4")); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("A/a4"))); QVERIFY(trackerContains("A/a4")); // had an error, still here QVERIFY(!trackerContains("A/spurious")); // removed due to full discovery fakeFolder.serverErrorPaths().clear(); - fakeFolder.syncJournal().wipeErrorBlacklist(); - tracker.addTouchedPath("A/newspurious"); // will be removed due to successful sync + fakeFolder.syncJournal()->wipeErrorBlacklist(); + tracker.addTouchedPath(QStringLiteral("A/newspurious")); // will be removed due to successful sync - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(fakeFolder.currentRemoteState().find("A/a4")); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("A/a4"))); QVERIFY(tracker.localDiscoveryPaths().empty()); } void testDirectoryAndSubDirectory() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); - fakeFolder.localModifier().mkdir("A/newDir"); - fakeFolder.localModifier().mkdir("A/newDir/subDir"); - fakeFolder.localModifier().insert("A/newDir/subDir/file", 10); + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - auto expectedState = fakeFolder.currentLocalState(); + fakeFolder.localModifier().mkdir(QStringLiteral("A/newDir")); + fakeFolder.localModifier().mkdir(QStringLiteral("A/newDir/subDir")); + fakeFolder.localModifier().insert(QStringLiteral("A/newDir/subDir/file"), 10_B); // Only "A" was modified according to the file system tracker - fakeFolder.syncEngine().setLocalDiscoveryOptions( - LocalDiscoveryStyle::DatabaseAndFilesystem, - { "A" }); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("A")}); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - QCOMPARE(fakeFolder.currentLocalState(), expectedState); - QCOMPARE(fakeFolder.currentRemoteState(), expectedState); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("A/newDir/subDir/file"))); } // Tests the behavior of invalid filename detection void testServerBlacklist() { - FakeFolder fakeFolder { FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - fakeFolder.syncEngine().account()->setCapabilities({ { "files", - QVariantMap { { "blacklisted_files", QVariantList { ".foo", "bar" } } } } }); - fakeFolder.localModifier().insert("C/.foo"); - fakeFolder.localModifier().insert("C/bar"); - fakeFolder.localModifier().insert("C/moo"); - fakeFolder.localModifier().insert("C/.moo"); + auto cap = TestUtils::testCapabilities(); + cap.insert( + {{QStringLiteral("files"), QVariantMap{{QStringLiteral("blacklisted_files"), QVariantList{QStringLiteral(".foo"), QStringLiteral("bar")}}}}}); + fakeFolder.account()->setCapabilities({fakeFolder.account()->url(), cap}); + fakeFolder.localModifier().insert(QStringLiteral("C/.foo")); + fakeFolder.localModifier().insert(QStringLiteral("C/bar")); + fakeFolder.localModifier().insert(QStringLiteral("C/moo")); + fakeFolder.localModifier().insert(QStringLiteral("C/.moo")); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("C/moo"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("C/.moo"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("C/.foo"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("C/bar"))); + } + + void testNameNormalization_data() + { + QTest::addColumn("correct"); + QTest::addColumn("incorrect"); + + const unsigned char a_umlaut_composed_bytes[] = {0xc3, 0xa4, 0x00}; + const QString a_umlaut_composed = QString::fromUtf8(reinterpret_cast(a_umlaut_composed_bytes)); + const QString a_umlaut_decomposed = a_umlaut_composed.normalized(QString::NormalizationForm_D); + QTest::newRow("a_umlaut decomposed") << a_umlaut_decomposed << a_umlaut_composed; + QTest::newRow("a_umlaut composed") << a_umlaut_composed << a_umlaut_decomposed; + } + + // Test that when a file/directory name on the remote is encoded in NFC, the local name is encoded + // in the same way, and that a subsequent sync does not change anything. And the same for NFD. + void testNameNormalization() + { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + QFETCH(QString, correct); + QFETCH(QString, incorrect); + + // Create an empty remote folder + FakeFolder fakeFolder({FileInfo{}}, vfsMode, filesAreDehydrated); + OperationCounter counter(fakeFolder); + + // Create a file with an a-umlout in the "correct" normalization: + fakeFolder.remoteModifier().mkdir(QStringLiteral("P")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("P/A")); + fakeFolder.remoteModifier().insert(QStringLiteral("P/A/") + correct); + + // Same for a directory, holding a "normal" file: + fakeFolder.remoteModifier().mkdir(QStringLiteral("P/B") + correct); + fakeFolder.remoteModifier().insert(QStringLiteral("P/B") + correct + QStringLiteral("/b")); + + LocalDiscoveryTracker tracker(nullptr); + connect(fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); + connect(fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); + + // First sync: discover that there are files/directories on the server that are not yet synced to the local end + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + // Check that locally we have the file and the directory with the correct names: + { + auto localState = fakeFolder.currentLocalState(); + QVERIFY(localState.find(QStringLiteral("P/A/") + correct) != nullptr); // check if the file exists + QVERIFY(localState.find(QStringLiteral("P/B") + correct + QStringLiteral("/b")) != nullptr); // check if the file exists + } + + counter.reset(); + + qDebug() << "*** MARK"; // Log marker to check if a PUT/DELETE shows up in the second sync + + // Force a full local discovery on the next sync, which forces a walk of the (local) file system, reading back names (and file sizes/mtimes/etc.)... + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, {QStringLiteral("P")}); + tracker.startSyncFullDiscovery(); + + // ... and start the second sync: + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + // If the normalization of the file/directory name did not change, no rename/move/etc. should have been detected, so check that the client didn't issue + // any of these operations: + QCOMPARE(counter.nDELETE, 0); + QCOMPARE(counter.nMOVE, 0); + QCOMPARE(counter.nPUT, 0); + + // Check that the remote names are unchanged, and that no "incorrect" names have been introduced: + FileInfo &remoteState = fakeFolder.currentRemoteState(); + QVERIFY(remoteState.find(QStringLiteral("P/A/") + correct) != nullptr); // check if the file still exists in the original normalization + QVERIFY(remoteState.find(QStringLiteral("P/A/") + incorrect) == nullptr); // there should NOT be a file with another normalization + QVERIFY(remoteState.find(QStringLiteral("P/B") + correct + QStringLiteral("/b")) + != nullptr); // check if the directory still exists in the original normalization + QVERIFY(remoteState.find(QStringLiteral("P/B") + incorrect + QStringLiteral("/b")) + == nullptr); // there should NOT be a directory with another normalization + } + + void testLocalNameNormalizationChange() + { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + // Create an empty remote folder + FakeFolder fakeFolder({FileInfo{}}, vfsMode, filesAreDehydrated); + OperationCounter counter(fakeFolder); + + const unsigned char a_umlaut_composed_bytes[] = {0xc3, 0xa4, 0x00}; + const QString a_umlaut_composed = QString::fromUtf8(reinterpret_cast(a_umlaut_composed_bytes)); + const QString a_umlaut_decomposed = a_umlaut_composed.normalized(QString::NormalizationForm_D); + + // OC10 stores names in composed form only, oCIS might have a mix, but NFC is common. + fakeFolder.remoteModifier().insert(a_umlaut_composed); + + // Download the file + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + // With client version 5 on mac, the file name would be decomposed by Qt. Simulate that: + QString err; + bool result = FileSystem::uncheckedRenameReplace(fakeFolder.localPath() + a_umlaut_composed, fakeFolder.localPath() + a_umlaut_decomposed, &err); + QVERIFY(result); + QVERIFY(err.isEmpty()); + + // Now nothing should happen... + counter.reset(); QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentRemoteState().find("C/moo")); - QVERIFY(fakeFolder.currentRemoteState().find("C/.moo")); - QVERIFY(!fakeFolder.currentRemoteState().find("C/.foo")); - QVERIFY(!fakeFolder.currentRemoteState().find("C/bar")); + + QCOMPARE(counter.nGET, 0); + QCOMPARE(counter.nDELETE, 0); + QCOMPARE(counter.nMOVE, 0); + QCOMPARE(counter.nPUT, 0); + + // Check that nothing changed on the server: + QVERIFY(fakeFolder.currentRemoteState().find(a_umlaut_composed) != nullptr); + QVERIFY(fakeFolder.currentRemoteState().find(a_umlaut_decomposed) == nullptr); + + // Check that locally the file is renamed back to the same composition as on the server + QVERIFY(fakeFolder.currentLocalState().find(a_umlaut_composed) != nullptr); + QVERIFY(fakeFolder.currentLocalState().find(a_umlaut_decomposed) == nullptr); } }; diff --git a/test/testlockedfiles.cpp b/test/testlockedfiles.cpp index 1df501afde3..dde5d51250e 100644 --- a/test/testlockedfiles.cpp +++ b/test/testlockedfiles.cpp @@ -5,32 +5,29 @@ * */ -#include -#include "testutils/syncenginetestutils.h" +#include "localdiscoverytracker.h" #include "lockwatcher.h" -#include -#include +#include "syncengine.h" +#include "testutils.h" +#include "testutils/syncenginetestutils.h" + +#include using namespace std::chrono_literals; using namespace OCC; #ifdef Q_OS_WIN +#include "common/utility_win.h" // pass combination of FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_SHARE_DELETE -HANDLE makeHandle(const QString &file, int shareMode) +Utility::Handle makeHandle(const QString &file, int shareMode) { const auto fName = FileSystem::longWinPath(file); const wchar_t *wuri = reinterpret_cast(fName.utf16()); - auto handle = CreateFileW( - wuri, - GENERIC_READ | GENERIC_WRITE, - shareMode, - NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); + auto handle = CreateFileW(wuri, GENERIC_READ | GENERIC_WRITE, shareMode, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle == INVALID_HANDLE_VALUE) { qWarning() << GetLastError(); } - return handle; + return Utility::Handle(handle); } #endif @@ -38,10 +35,27 @@ class TestLockedFiles : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } + void testBasicLockFileWatcher() { - QTemporaryDir tmp; + auto temporaryDir = TestUtils::createTempDir(); int count = 0; QString file; @@ -49,13 +63,14 @@ private slots: watcher.setCheckInterval(std::chrono::milliseconds(50)); connect(&watcher, &LockWatcher::fileUnlocked, &watcher, [&](const QString &f) { ++count; file = f; }); - const QString tmpFile = tmp.path() + QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" - "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" - "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" - "olonglonglonglong/file🐷.txt"); + const QString tmpFile = temporaryDir.path() + + QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" + "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" + "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" + "olonglonglonglong/file🐷.txt"); { // use a long file path to ensure we handle that correctly - QVERIFY(QFileInfo(tmpFile).dir().mkpath(".")); + QVERIFY(QFileInfo(tmpFile).dir().mkpath(QStringLiteral("."))); QFile tmp(tmpFile); QVERIFY(tmp.open(QFile::WriteOnly)); QVERIFY(tmp.write("ownCLoud")); @@ -64,7 +79,7 @@ private slots: QVERIFY(!FileSystem::isFileLocked(tmpFile, FileSystem::LockMode::Shared)); watcher.addFile(tmpFile, FileSystem::LockMode::Shared); - QVERIFY(watcher.contains(tmpFile)); + QVERIFY(watcher.contains(tmpFile, FileSystem::LockMode::Shared)); QEventLoop loop; QTimer::singleShot(120ms, &loop, [&] { loop.exit(); }); @@ -72,7 +87,7 @@ private slots: QCOMPARE(count, 1); QCOMPARE(file, tmpFile); - QVERIFY(!watcher.contains(tmpFile)); + QVERIFY(!watcher.contains(tmpFile, FileSystem::LockMode::Shared)); #ifdef Q_OS_WIN auto h = makeHandle(tmpFile, 0); @@ -86,7 +101,7 @@ private slots: QCOMPARE(count, 0); QVERIFY(file.isEmpty()); - QVERIFY(watcher.contains(tmpFile)); + QVERIFY(watcher.contains(tmpFile, FileSystem::LockMode::Shared)); CloseHandle(h); QVERIFY(!FileSystem::isFileLocked(tmpFile, FileSystem::LockMode::Shared)); @@ -96,48 +111,59 @@ private slots: QCOMPARE(count, 1); QCOMPARE(file, tmpFile); - QVERIFY(!watcher.contains(tmpFile)); + QVERIFY(!watcher.contains(tmpFile, FileSystem::LockMode::Shared)); #endif - QVERIFY(tmp.remove()); + QVERIFY(temporaryDir.remove()); } #ifdef Q_OS_WIN void testLockedFilePropagation() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + + // This test can only work when the file is locally available... + if (filesAreDehydrated) { + // ... so force hydration in case of dehydrated vfs + fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + } QStringList seenLockedFiles; - connect(&fakeFolder.syncEngine(), &SyncEngine::seenLockedFile, &fakeFolder.syncEngine(), - [&](const QString &file) { seenLockedFiles.append(file); }); + connect(fakeFolder.syncEngine(), &SyncEngine::seenLockedFile, fakeFolder.syncEngine(), [&](const QString &file) { seenLockedFiles.append(file); }); - LocalDiscoveryTracker tracker; - connect(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); - connect(&fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); + LocalDiscoveryTracker tracker(nullptr); + connect(fakeFolder.syncEngine(), &SyncEngine::itemCompleted, &tracker, &LocalDiscoveryTracker::slotItemCompleted); + connect(fakeFolder.syncEngine(), &SyncEngine::finished, &tracker, &LocalDiscoveryTracker::slotSyncFinished); auto hasLocalDiscoveryPath = [&](const QString &path) { auto &paths = tracker.localDiscoveryPaths(); - return paths.find(path.toUtf8()) != paths.end(); + return paths.find(path) != paths.end(); }; // // Local change, attempted upload, but file is locked! // - fakeFolder.localModifier().appendByte("A/a1"); - tracker.addTouchedPath("A/a1"); - auto h1 = makeHandle(fakeFolder.localPath() + "A/a1", 0); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + tracker.addTouchedPath(QStringLiteral("A/a1")); + auto h1 = makeHandle(fakeFolder.localPath() + QStringLiteral("A/a1"), 0); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(seenLockedFiles.contains(fakeFolder.localPath() + "A/a1")); + QVERIFY(seenLockedFiles.contains(fakeFolder.localPath() + QStringLiteral("A/a1"))); QVERIFY(seenLockedFiles.size() == 1); - QVERIFY(hasLocalDiscoveryPath("A/a1")); + QVERIFY(hasLocalDiscoveryPath(QStringLiteral("A/a1"))); CloseHandle(h1); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.syncJournal()->wipeErrorBlacklist(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); seenLockedFiles.clear(); @@ -146,21 +172,23 @@ private slots: // // Remote change, attempted download, but file is locked! // - fakeFolder.remoteModifier().appendByte("A/a1"); - auto h2 = makeHandle(fakeFolder.localPath() + "A/a1", 0); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + auto h2 = makeHandle(fakeFolder.localPath() + QStringLiteral("A/a1"), 0); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(seenLockedFiles.contains(fakeFolder.localPath() + "A/a1")); + QVERIFY(seenLockedFiles.contains(fakeFolder.localPath() + QStringLiteral("A/a1"))); QVERIFY(seenLockedFiles.size() == 1); CloseHandle(h2); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); + fakeFolder.syncEngine()->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, tracker.localDiscoveryPaths()); tracker.startSyncPartialDiscovery(); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.syncJournal()->wipeErrorBlacklist(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } #endif diff --git a/test/testlongpath.cpp b/test/testlongpath.cpp deleted file mode 100644 index 7e912ae1578..00000000000 --- a/test/testlongpath.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - * libcsync -- a library to sync a directory with another - * - * Copyright (c) 2013 by Klaas Freitag - * - * 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 "common/filesystembase.h" -#include "csync/csync.h" -#include "csync/vio/csync_vio_local.h" - -#include -#include - - -class TestLongWindowsPath : public QObject -{ - Q_OBJECT - -private Q_SLOTS: -#ifdef Q_OS_WIN - void check_long_win_path() - { - { - const auto path = QStringLiteral("C://DATA/FILES/MUSIC/MY_MUSIC.mp3"); // check a short path - const auto exp_path = QStringLiteral("\\\\?\\C:\\\\DATA\\FILES\\MUSIC\\MY_MUSIC.mp3"); - QString new_short = OCC::FileSystem::pathtoUNC(path); - QCOMPARE(new_short, exp_path); - } - - { - const auto path = QStringLiteral("\\\\foo\\bar/MY_MUSIC.mp3"); - const auto exp_path = QStringLiteral("\\\\foo\\bar\\MY_MUSIC.mp3"); - QString new_short = OCC::FileSystem::pathtoUNC(path); - QCOMPARE(new_short, exp_path); - } - - { - const auto path = QStringLiteral("//foo\\bar/MY_MUSIC.mp3"); - const auto exp_path = QStringLiteral("\\\\foo\\bar\\MY_MUSIC.mp3"); - QString new_short = OCC::FileSystem::pathtoUNC(path); - QCOMPARE(new_short, exp_path); - } - - { - const auto path = QStringLiteral("\\foo\\bar"); - const auto exp_path = QStringLiteral("\\\\?\\foo\\bar"); - QString new_short = OCC::FileSystem::pathtoUNC(path); - QCOMPARE(new_short, exp_path); - } - - { - const auto path = QStringLiteral("/foo/bar"); - const auto exp_path = QStringLiteral("\\\\?\\foo\\bar"); - QString new_short = OCC::FileSystem::pathtoUNC(path); - QCOMPARE(new_short, exp_path); - } - - const auto longPath = QStringLiteral("D://alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" - "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" - "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" - "olonglonglonglong/file.txt"); - const auto longPathConv = QStringLiteral("\\\\?\\D:\\\\alonglonglonglong\\blonglonglonglong\\clonglonglonglong\\dlonglonglonglong\\" - "elonglonglonglong\\flonglonglonglong\\glonglonglonglong\\hlonglonglonglong\\ilonglonglonglong\\" - "jlonglonglonglong\\klonglonglonglong\\llonglonglonglong\\mlonglonglonglong\\nlonglonglonglong\\" - "olonglonglonglong\\file.txt"); - - QString new_long = OCC::FileSystem::pathtoUNC(longPath); - // printf( "XXXXXXXXXXXX %s %d\n", new_long, mem_reserved); - - QCOMPARE(new_long, longPathConv); - - // printf( "YYYYYYYYYYYY %ld\n", strlen(new_long)); - QCOMPARE(new_long.length(), 286); - } -#endif - - - void testLongPathStat_data() - { - QTest::addColumn("name"); - - QTest::newRow("long") << QStringLiteral("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" - "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" - "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" - "olonglonglonglong/file.txt"); - QTest::newRow("long emoji") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" - "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" - "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" - "olonglonglonglong/file🐷.txt"); - QTest::newRow("long russian") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" - "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" - "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" - "olonglonglonglong/собственное.txt"); - QTest::newRow("long arabic") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" - "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" - "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" - "olonglonglonglong/السحاب.txt"); - QTest::newRow("long chinese") << QString::fromUtf8("/alonglonglonglong/blonglonglonglong/clonglonglonglong/dlonglonglonglong/" - "elonglonglonglong/flonglonglonglong/glonglonglonglong/hlonglonglonglong/ilonglonglonglong/" - "jlonglonglonglong/klonglonglonglong/llonglonglonglong/mlonglonglonglong/nlonglonglonglong/" - "olonglonglonglong/自己的云.txt"); - } - - void testLongPathStat() - { - QTemporaryDir tmp; - QFETCH(QString, name); - const QFileInfo longPath(tmp.path() + name); - - const auto data = QByteArrayLiteral("hello"); - qDebug() << longPath; - QVERIFY(longPath.dir().mkpath(".")); - - QFile file(longPath.filePath()); - QVERIFY(file.open(QFile::WriteOnly)); - QVERIFY(file.write(data.constData()) == data.size()); - file.close(); - - csync_file_stat_t buf; - QVERIFY(csync_vio_local_stat(longPath.filePath(), &buf) != -1); - QVERIFY(buf.size == data.size()); - QVERIFY(buf.size == longPath.size()); - - QVERIFY(tmp.remove()); - } -}; - -QTEST_GUILESS_MAIN(TestLongWindowsPath) -#include "testlongpath.moc" diff --git a/test/testnetrcparser.cpp b/test/testnetrcparser.cpp deleted file mode 100644 index 1c8724e664f..00000000000 --- a/test/testnetrcparser.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * */ - -#include - -#include "cmd/netrcparser.h" - -using namespace OCC; - -namespace { - -const char testfileC[] = "netrctest"; -const char testfileWithDefaultC[] = "netrctestDefault"; -const char testfileEmptyC[] = "netrctestEmpty"; - -} - -class TestNetrcParser : public QObject -{ - Q_OBJECT - -private slots: - void initTestCase() { - QFile netrc(testfileC); - QVERIFY(netrc.open(QIODevice::WriteOnly)); - netrc.write("machine foo login bar password baz\n"); - netrc.write("machine broken login bar2 dontbelonghere password baz2 extratokens dontcare andanother\n"); - netrc.write("machine\nfunnysplit\tlogin bar3 password baz3\n"); - netrc.write("machine frob login \"user with spaces\" password 'space pwd'\n"); - QFile netrcWithDefault(testfileWithDefaultC); - QVERIFY(netrcWithDefault.open(QIODevice::WriteOnly)); - netrcWithDefault.write("machine foo login bar password baz\n"); - netrcWithDefault.write("default login user password pass\n"); - QFile netrcEmpty(testfileEmptyC); - QVERIFY(netrcEmpty.open(QIODevice::WriteOnly)); - } - - void cleanupTestCase() { - QVERIFY(QFile::remove(testfileC)); - QVERIFY(QFile::remove(testfileWithDefaultC)); - QVERIFY(QFile::remove(testfileEmptyC)); - } - - void testValidNetrc() { - NetrcParser parser(testfileC); - QVERIFY(parser.parse()); - QCOMPARE(parser.find("foo"), qMakePair(QString("bar"), QString("baz"))); - QCOMPARE(parser.find("broken"), qMakePair(QString("bar2"), QString())); - QCOMPARE(parser.find("funnysplit"), qMakePair(QString("bar3"), QString("baz3"))); - QCOMPARE(parser.find("frob"), qMakePair(QString("user with spaces"), QString("space pwd"))); - } - - void testEmptyNetrc() { - NetrcParser parser(testfileEmptyC); - QVERIFY(!parser.parse()); - QCOMPARE(parser.find("foo"), qMakePair(QString(), QString())); - } - - void testValidNetrcWithDefault() { - NetrcParser parser(testfileWithDefaultC); - QVERIFY(parser.parse()); - QCOMPARE(parser.find("foo"), qMakePair(QString("bar"), QString("baz"))); - QCOMPARE(parser.find("dontknow"), qMakePair(QString("user"), QString("pass"))); - } - - void testInvalidNetrc() { - NetrcParser parser("/invalid"); - QVERIFY(!parser.parse()); - } -}; - -QTEST_APPLESS_MAIN(TestNetrcParser) -#include "testnetrcparser.moc" diff --git a/test/testoauth.cpp b/test/testoauth.cpp index d6500d4337f..b4f48f78b14 100644 --- a/test/testoauth.cpp +++ b/test/testoauth.cpp @@ -8,10 +8,12 @@ #include #include -#include "libsync/creds/oauth.h" +#include "common/asserts.h" +#include "creds/oauth.h" #include "testutils/syncenginetestutils.h" #include "theme.h" -#include "common/asserts.h" + +#include using namespace std::chrono_literals; using namespace OCC; @@ -19,13 +21,16 @@ using namespace OCC; class DesktopServiceHook : public QObject { Q_OBJECT -signals: +Q_SIGNALS: void hooked(const QUrl &); + public: - DesktopServiceHook() { QDesktopServices::setUrlHandler("oauthtest", this, "hooked"); } + DesktopServiceHook() { QDesktopServices::setUrlHandler(QStringLiteral("oauthtest"), this, "hooked"); } + ~DesktopServiceHook() { QDesktopServices::unsetUrlHandler(QStringLiteral("oauthtest")); } }; -static const QUrl sOAuthTestServer("oauthtest://someserver/owncloud"); +static const QUrl sOpenIdBaseURL(QStringLiteral("oauthtest://auth.example.com")); +static const QUrl sOpenIdAuthURL(QStringLiteral("oauthtest://auth.example.com/realms/test/protocol/openid-connect/auth")); class FakePostReply : public QNetworkReply @@ -44,27 +49,35 @@ class FakePostReply : public QNetworkReply setOperation(op); open(QIODevice::ReadOnly); payload->open(QIODevice::ReadOnly); - QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &FakePostReply::respond, Qt::QueuedConnection); } Q_INVOKABLE virtual void respond() { if (aborted) { - setError(OperationCanceledError, "Operation Canceled"); - emit metaDataChanged(); - emit finished(); + setError(OperationCanceledError, QStringLiteral("Operation Canceled")); + Q_EMIT metaDataChanged(); + checkedFinished(); return; } setHeader(QNetworkRequest::ContentLengthHeader, payload->size()); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); - emit metaDataChanged(); + Q_EMIT metaDataChanged(); if (bytesAvailable()) - emit readyRead(); - emit finished(); + Q_EMIT readyRead(); + checkedFinished(); } void abort() override { aborted = true; } + + void checkedFinished() { + if (!isFinished()) { + setFinished(true); + Q_EMIT finished(); + } + } + qint64 bytesAvailable() const override { if (aborted) return 0; @@ -92,62 +105,90 @@ class OAuthTestCase : public QObject { Q_OBJECT DesktopServiceHook desktopServiceHook; + +protected: + QString _expectedClientId = Theme::instance()->oauthClientId(); + public: enum State { StartState, StatusPhpState, BrowserOpened, TokenAsked, + UserInfoFetched, CustomState } state = StartState; Q_ENUM(State); - // for oauth2 we use localhost, for oidc we use 127.0.0.1 - QString localHost = QStringLiteral("localhost"); + QString localHost = QStringLiteral("127.0.0.1"); bool replyToBrowserOk = false; bool gotAuthOk = false; virtual bool done() const { return replyToBrowserOk && gotAuthOk; } - FakeQNAM *fakeQnam = nullptr; + FakeAM *fakeAm = nullptr; QNetworkAccessManager realQNAM; QPointer browserReply = nullptr; - QString code = generateEtag(); - OCC::AccountPtr account; - - QScopedPointer oauth; - - virtual void test() { - fakeQnam = new FakeQNAM({}); - account = OCC::Account::create(); - account->setUrl(sOAuthTestServer); - account->setCredentials(new FakeCredentials{fakeQnam}); - fakeQnam->setParent(this); - fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) { - if (req.url().path().endsWith(".well-known/openid-configuration")) { + QString code = QString::fromUtf8(generateEtag()); + OCC::Account *account; + + std::unique_ptr oauth; + + virtual std::unique_ptr prepareOauth() + { + fakeAm = new FakeAM(nullptr); + account = new Account(QUuid::createUuid(), "admin", sOpenIdBaseURL); + + // the account seizes ownership over the qnam in account->setCredentials(...) by keeping a shared pointer on it + // therefore, we should never call fakeAm->setThis(...) + // Lisa todo: ??? + account->setCredentials(new FakeCredentials(account, fakeAm, account)); + fakeAm->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) { + if (req.url().path().endsWith(QLatin1String(".well-known/openid-configuration"))) { return this->wellKnownReply(op, req); - } else if (req.url().path().endsWith("status.php")) { + } + if (req.url().path().endsWith(QLatin1String("status.php"))) { return this->statusPhpReply(op, req); } + if (req.url().path().endsWith(QLatin1String("ocs/v2.php/cloud/user")) && req.url().query() == QLatin1String("format=json")) { + return this->userInfoReply(op, req); + } OC_ASSERT(device); - OC_ASSERT(device->bytesAvailable() > 0); // OAuth2 always sends around POST data. - return this->tokenReply(op, req); + OC_ASSERT(device && device->bytesAvailable() > 0); // OAuth2 always sends around POST data. + return this->tokenReply(op, req, device); }); - QObject::connect(&desktopServiceHook, &DesktopServiceHook::hooked, - this, &OAuthTestCase::openBrowserHook); + QObject::connect(&desktopServiceHook, &DesktopServiceHook::hooked, this, &OAuthTestCase::openBrowserHook); + + AppConfig config; + auto out = std::make_unique(account, config.openIdConfig(), nullptr); + QObject::connect(out.get(), &OAuth::result, this, &OAuthTestCase::oauthResult); + return out; + } + + virtual void test() + { + runTest(); - oauth.reset(new OAuth(account.data(), nullptr)); - QObject::connect(oauth.data(), &OAuth::result, this, &OAuthTestCase::oauthResult); - oauth->startAuthentication(); QTRY_VERIFY(done()); } + virtual void runTest() + { + oauth = prepareOauth(); + oauth->startAuthentication(); + + QSignalSpy spy(oauth.get(), &OCC::OAuth::authorisationLinkChanged); + if (spy.wait()) { + oauth->openBrowser(); + } + } + virtual void openBrowserHook(const QUrl &url) { QCOMPARE(state, StatusPhpState); state = BrowserOpened; - QCOMPARE(url.path(), sOAuthTestServer.path() + QStringLiteral("/index.php/apps/oauth2/authorize")); - QVERIFY(url.toString().startsWith(sOAuthTestServer.toString())); + QCOMPARE(url.path(), sOpenIdAuthURL.path()); + QVERIFY(url.toString().startsWith(sOpenIdBaseURL.toString())); QUrlQuery query(url); QCOMPARE(query.queryItemValue(QStringLiteral("response_type")), QLatin1String("code")); - QCOMPARE(query.queryItemValue(QStringLiteral("client_id")), Theme::instance()->oauthClientId()); + QCOMPARE(query.queryItemValue(QStringLiteral("client_id")), _expectedClientId); QUrl redirectUri(query.queryItemValue(QStringLiteral("redirect_uri"))); QCOMPARE(redirectUri.host(), localHost); redirectUri.setQuery(QStringLiteral("code=%1&state=%2").arg(code, query.queryItemValue(QStringLiteral("state")))); @@ -155,30 +196,32 @@ class OAuthTestCase : public QObject } virtual QNetworkReply *createBrowserReply(const QNetworkRequest &request) { - browserReply = realQNAM.get(request); + auto r = request; + // don't follow the redirect to owncloud://success + r.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy); + browserReply = realQNAM.get(r); QObject::connect(browserReply, &QNetworkReply::finished, this, &OAuthTestCase::browserReplyFinished); return browserReply; } virtual void browserReplyFinished() { QCOMPARE(sender(), browserReply.data()); - QCOMPARE(state, TokenAsked); + QCOMPARE(state, UserInfoFetched); browserReply->deleteLater(); QCOMPARE(QNetworkReply::NoError, browserReply->error()); QCOMPARE(browserReply->rawHeader("Location"), QByteArray("owncloud://success")); replyToBrowserOk = true; } - virtual QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req) + virtual QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req, [[maybe_unused]] QIODevice *device) { OC_ASSERT(state == BrowserOpened); state = TokenAsked; OC_ASSERT(op == QNetworkAccessManager::PostOperation); - OC_ASSERT(req.url().toString().startsWith(sOAuthTestServer.toString())); - OC_ASSERT(req.url().path() == sOAuthTestServer.path() + "/index.php/apps/oauth2/api/v1/token"); + auto payload = std::make_unique(); payload->setData(tokenReplyPayload()); - return new FakePostReply(op, req, std::move(payload), fakeQnam); + return new FakePostReply(op, req, std::move(payload), fakeAm); } virtual QNetworkReply *statusPhpReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req) @@ -186,49 +229,74 @@ class OAuthTestCase : public QObject OC_ASSERT(state == StartState); state = StatusPhpState; OC_ASSERT(op == QNetworkAccessManager::GetOperation); - OC_ASSERT(req.url().toString().startsWith(sOAuthTestServer.toString())); - OC_ASSERT(req.url().path() == sOAuthTestServer.path() + "/status.php"); + OC_ASSERT(req.url().toString().startsWith(sOpenIdBaseURL.toString())); + OC_ASSERT(req.url().path() == sOpenIdBaseURL.path() + QStringLiteral("/status.php")); auto payload = std::make_unique(); payload->setData(statusPhpPayload()); - return new FakePostReply(op, req, std::move(payload), fakeQnam); + return new FakePostReply(op, req, std::move(payload), fakeAm); } - virtual QNetworkReply *wellKnownReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req) + virtual QNetworkReply *userInfoReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req) { - return new FakeErrorReply(op, req, fakeQnam, 404); + OC_ASSERT(state == TokenAsked); + state = UserInfoFetched; + OC_ASSERT(op == QNetworkAccessManager::GetOperation); + OC_ASSERT(req.url().toString().startsWith(sOpenIdBaseURL.toString())); + OC_ASSERT(req.url().path() == sOpenIdBaseURL.path() + QStringLiteral("/ocs/v2.php/cloud/user")); + OC_ASSERT(req.url().query() == QStringLiteral("format=json")); + auto payload = std::make_unique(); + payload->setData(userInfoPayload()); + return new FakePostReply(op, req, std::move(payload), fakeAm); } - virtual QByteArray tokenReplyPayload() const { - // the dummy server provides the user admin + virtual QNetworkReply * wellKnownReply(QNetworkAccessManager::Operation op, const QNetworkRequest & req) { + OC_ASSERT(op == QNetworkAccessManager::GetOperation); QJsonDocument jsondata(QJsonObject{ - { "access_token", "123" }, - { "refresh_token" , "456" }, - { "message_url", "owncloud://success"}, - { "user_id", "admin" }, - { "token_type", "Bearer" } + {QStringLiteral("authorization_endpoint"), sOpenIdAuthURL.toString()}, + {QStringLiteral("token_endpoint"), QStringLiteral("oauthtest://openidserver/token_endpoint")}, + {QStringLiteral("token_endpoint_auth_methods_supported"), QJsonArray{QStringLiteral("client_secret_post")}}, }); + return new FakePayloadReply(op, req, jsondata.toJson(), fakeAm); + } + + virtual QByteArray tokenReplyPayload() const { + // the dummy server provides the user admin + QJsonDocument jsondata(QJsonObject{{QStringLiteral("access_token"), QStringLiteral("123")}, {QStringLiteral("refresh_token"), QStringLiteral("456")}, + {QStringLiteral("message_url"), QStringLiteral("owncloud://success")}, {QStringLiteral("user_id"), QStringLiteral("admin")}, + {QStringLiteral("token_type"), QStringLiteral("Bearer")}}); return jsondata.toJson(); } virtual QByteArray statusPhpPayload() const { - QJsonDocument jsondata(QJsonObject { - { "installed", true }, - { "maintenance", false }, - { "needsDbUpgrade", false }, - { "version", "10.5.0.10" }, - { "versionstring", "10.5.0" }, - { "edition", "Enterprise" }, - { "productname", "ownCloud" } }); + QJsonDocument jsondata( + QJsonObject{{QStringLiteral("installed"), true}, {QStringLiteral("maintenance"), false}, {QStringLiteral("needsDbUpgrade"), false}, + {QStringLiteral("version"), QStringLiteral("10.5.0.10")}, {QStringLiteral("versionstring"), QStringLiteral("10.5.0")}, + {QStringLiteral("edition"), QStringLiteral("Enterprise")}, {QStringLiteral("productname"), QStringLiteral("ownCloud")}}); return jsondata.toJson(); } - virtual void oauthResult(OAuth::Result result, const QString &user, const QString &token , const QString &refreshToken) { - QCOMPARE(state, TokenAsked); + virtual QByteArray userInfoPayload() const + { + // the dummy server provides the user admin + // we don't provide "meta" at the moment, since it is not used + QJsonDocument jsonData(QJsonObject{{QStringLiteral("ocs"), + QJsonObject{{QStringLiteral("data"), + QJsonObject{ + {QStringLiteral("display-name"), QStringLiteral("Admin")}, + {QStringLiteral("id"), QStringLiteral("admin")}, + {QStringLiteral("email"), QStringLiteral("admin@admin.admin")}, + + }}}}}); + return jsonData.toJson(); + } + + virtual void oauthResult(OAuth::Result result, const QString &token, const QString &refreshToken) + { QCOMPARE(result, OAuth::LoggedIn); - QCOMPARE(user, QString("admin")); - QCOMPARE(token, QString("123")); - QCOMPARE(refreshToken, QString("456")); + QCOMPARE(state, UserInfoFetched); + QCOMPARE(token, QStringLiteral("123")); + QCOMPARE(refreshToken, QStringLiteral("456")); gotAuthOk = true; } }; @@ -237,7 +305,7 @@ class TestOAuth: public QObject { Q_OBJECT -private slots: +private Q_SLOTS: void testBasic() { OAuthTestCase test; @@ -250,13 +318,9 @@ private slots: struct Test : OAuthTestCase { QByteArray tokenReplyPayload() const override { // the dummy server provides the user admin - QJsonDocument jsondata(QJsonObject{ - { "access_token", "123" }, - { "refresh_token" , "456" }, - { "message_url", "owncloud://success"}, - { "user_id", "wrong_user" }, - { "token_type", "Bearer" } - }); + QJsonDocument jsondata(QJsonObject{{QStringLiteral("access_token"), QStringLiteral("123")}, + {QStringLiteral("refresh_token"), QStringLiteral("456")}, {QStringLiteral("message_url"), QStringLiteral("owncloud://success")}, + {QStringLiteral("user_id"), QStringLiteral("wrong_user")}, {QStringLiteral("token_type"), QStringLiteral("Bearer")}}); return jsondata.toJson(); } @@ -279,21 +343,18 @@ private slots: void testCloseBrowserDontCrash() { struct Test : OAuthTestCase { - QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest & req) override + QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req, [[maybe_unused]] QIODevice *device) override { OC_ASSERT(browserReply); // simulate the fact that the browser is closing the connection browserReply->abort(); - // don't process network events, as it messes up the execution order and - // causes an Qt internal crash - QCoreApplication::processEvents(QEventLoop::ExcludeSocketNotifiers); OC_ASSERT(state == BrowserOpened); state = TokenAsked; auto payload = std::make_unique(); payload->setData(tokenReplyPayload()); - return new SlowFakePostReply(op, req, std::move(payload), fakeQnam); + return new SlowFakePostReply(op, req, std::move(payload), fakeAm); } void browserReplyFinished() override @@ -325,7 +386,7 @@ private slots: }; for (const auto &x : payloads) { auto socket = new QTcpSocket(this); - socket->connectToHost("localhost", port); + socket->connectToHost(QStringLiteral("localhost"), port); QVERIFY(socket->waitForConnected()); socket->write(x); } @@ -340,17 +401,18 @@ private slots: return nullptr; } - QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req) override + QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) override { if (state == CustomState) return new FakeErrorReply{op, req, this, 500}; - return OAuthTestCase::tokenReply(op, req); + return OAuthTestCase::tokenReply(op, req, device); } - void oauthResult(OAuth::Result result, const QString &user, const QString &token , - const QString &refreshToken) override { - if (state != CustomState) - return OAuthTestCase::oauthResult(result, user, token, refreshToken); + void oauthResult(OAuth::Result result, const QString &token, const QString &refreshToken) override + { + if (state != CustomState) { + return OAuthTestCase::oauthResult(result, token, refreshToken); + } QCOMPARE(result, OAuth::Error); } } test; @@ -361,39 +423,61 @@ private slots: struct Test : OAuthTestCase { Test() { - localHost = QLatin1String("127.0.0.1"); - } - - QNetworkReply * wellKnownReply(QNetworkAccessManager::Operation op, const QNetworkRequest & req) override { - OC_ASSERT(op == QNetworkAccessManager::GetOperation); - QJsonDocument jsondata(QJsonObject{ - { "authorization_endpoint", QJsonValue( - "oauthtest://openidserver" + sOAuthTestServer.path() + "/index.php/apps/oauth2/authorize") }, - { "token_endpoint" , "oauthtest://openidserver/token_endpoint" } - }); - return new FakePayloadReply(op, req, jsondata.toJson(), fakeQnam); + localHost = QStringLiteral("127.0.0.1"); } void openBrowserHook(const QUrl & url) override { - OC_ASSERT(url.host() == "openidserver"); + OC_ASSERT(url.host() == QStringLiteral("auth.example.com")); QUrl url2 = url; - url2.setHost(sOAuthTestServer.host()); + url2.setHost(sOpenIdBaseURL.host()); OAuthTestCase::openBrowserHook(url2); } - QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest & request) override + /* + QNetworkReply *tokenReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *device) override { OC_ASSERT(browserReply); - OC_ASSERT(request.url().toString().startsWith("oauthtest://openidserver/token_endpoint")); + OC_ASSERT(request.url().toString().startsWith(QStringLiteral("oauthtest://openidserver/token_endpoint"))); auto req = request; - req.setUrl(request.url().toString().replace("oauthtest://openidserver/token_endpoint", - sOAuthTestServer.toString() + "/index.php/apps/oauth2/api/v1/token")); - return OAuthTestCase::tokenReply(op, req); - } + req.setUrl(QUrl(request.url().toString().replace(QLatin1String("oauthtest://openidserver/token_endpoint"), + sOpenIdBaseURL.toString() + QStringLiteral("/index.php/apps/oauth2/api/v1/token")))); + return OAuthTestCase::tokenReply(op, req, device); + }*/ } test; test.test(); } + void testWellKnown307() { + struct Test : OAuthTestCase { + Test() + { + localHost = QStringLiteral("127.0.0.1"); + } + + QNetworkReply *wellKnownReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req) override + { + return new FakeErrorReply(op, req, fakeAm, 404); + } + + void oauthResult(OAuth::Result result, const QString &token, const QString &refreshToken) override + { + QCOMPARE(result, OAuth::ErrorIdPUnreachable); + QCOMPARE(state, StatusPhpState); + QCOMPARE(token, QStringLiteral("")); + QCOMPARE(refreshToken, QStringLiteral("")); + } + + void openBrowserHook(const QUrl & url) override { + OC_ASSERT(url.host() == QStringLiteral("openidserver")); + QUrl url2 = url; + url2.setHost(sOpenIdBaseURL.host()); + OAuthTestCase::openBrowserHook(url2); + } + } test; + test.runTest(); + QTRY_VERIFY(test.gotAuthOk == false); + QTRY_VERIFY(test.replyToBrowserOk == false); + } void testTimeout() { @@ -404,19 +488,19 @@ private slots: Test() : rollback(AbstractNetworkJob::httpTimeout, 1s) { - localHost = QLatin1String("127.0.0.1"); + localHost = QStringLiteral("127.0.0.1"); } QNetworkReply *statusPhpReply(QNetworkAccessManager::Operation op, const QNetworkRequest &req) override { OC_ASSERT(op == QNetworkAccessManager::GetOperation); - return new FakeHangingReply(op, req, fakeQnam); + return new FakeHangingReply(op, req, fakeAm); } - void oauthResult(OAuth::Result result, const QString &user, const QString &token, const QString &refreshToken) override + void oauthResult(OAuth::Result result, const QString &token, const QString &refreshToken) override { - Q_UNUSED(user); Q_UNUSED(token); + Q_UNUSED(refreshToken); QCOMPARE(state, StartState); QCOMPARE(result, OAuth::Error); @@ -429,5 +513,5 @@ private slots: }; -QTEST_GUILESS_MAIN(TestOAuth) +QTEST_MAIN(TestOAuth) #include "testoauth.moc" diff --git a/test/testoauthhtmlpage.cpp b/test/testoauthhtmlpage.cpp new file mode 100644 index 00000000000..52e98ad356f --- /dev/null +++ b/test/testoauthhtmlpage.cpp @@ -0,0 +1,41 @@ +/* +* This software is in the public domain, furnished "as is", without technical + * support, and with no warranty, express or implied, as to its usefulness for + * any purpose. + * + */ + +#include + +#include "creds/oauthhtmlpage.h" + +using namespace OCC; + +class TestOAuthHtmlPage : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testSuccessPage() { + const QString title = tr("Successfully signed in"); + const QString message = tr("You can close this window."); + + auto oauth = OAuthHtmlPage::buildPage(true, title, message); + QVERIFY(oauth.contains(title)); + QVERIFY(oauth.contains(message)); + QVERIFY(oauth.contains(QStringLiteral("49851C"))); + } + + void testErrorPage() { + const QString title = tr("Login error"); + const QString message = tr("The reply from the server did not contain all expected fields\n:\tError: Missing field access_token"); + + auto oauth = OAuthHtmlPage::buildPage(false, title, message); + QVERIFY(oauth.contains(title)); + QVERIFY(oauth.contains(message)); + QVERIFY(oauth.contains(QStringLiteral("E50101"))); + } +}; + +QTEST_MAIN(TestOAuthHtmlPage) +#include "testoauthhtmlpage.moc" diff --git a/test/testowncloudpropagator.cpp b/test/testowncloudpropagator.cpp index ef53a3672b1..6e1be209aeb 100644 --- a/test/testowncloudpropagator.cpp +++ b/test/testowncloudpropagator.cpp @@ -7,8 +7,9 @@ #include #include -#include "propagatedownload.h" #include "owncloudpropagator_p.h" +#include "propagatedownload.h" +#include "qchar.h" using namespace OCC; namespace OCC { @@ -19,7 +20,7 @@ class TestOwncloudPropagator : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: void testUpdateErrorFromSession() { // OwncloudPropagator propagator( NULL, QLatin1String("test1"), QLatin1String("test2"), new ProgressDatabase); @@ -31,51 +32,37 @@ private slots: QString fn; // without dir for (int i = 1; i <= 1000; i++) { - fn+="F"; + fn += QLatin1String("F"); QString tmpFileName = createDownloadTmpFileName(fn); - if (tmpFileName.contains('/')) { - tmpFileName = tmpFileName.mid(tmpFileName.lastIndexOf('/')+1); + if (tmpFileName.contains(QLatin1Char('/'))) { + tmpFileName = tmpFileName.mid(tmpFileName.lastIndexOf(QLatin1Char('/')) + 1); } QVERIFY( tmpFileName.length() > 0); QVERIFY( tmpFileName.length() <= 254); } // with absolute dir - fn = "/Users/guruz/ownCloud/rocks/GPL"; + fn = QStringLiteral("/Users/guruz/ownCloud/rocks/GPL"); for (int i = 1; i < 1000; i++) { - fn+="F"; + fn += QLatin1Char('F'); QString tmpFileName = createDownloadTmpFileName(fn); - if (tmpFileName.contains('/')) { - tmpFileName = tmpFileName.mid(tmpFileName.lastIndexOf('/')+1); + if (tmpFileName.contains(QLatin1Char('/'))) { + tmpFileName = tmpFileName.mid(tmpFileName.lastIndexOf(QLatin1Char('/')) + 1); } QVERIFY( tmpFileName.length() > 0); QVERIFY( tmpFileName.length() <= 254); } // with relative dir - fn = "rocks/GPL"; + fn = QStringLiteral("rocks/GPL"); for (int i = 1; i < 1000; i++) { - fn+="F"; + fn += QLatin1String("F"); QString tmpFileName = createDownloadTmpFileName(fn); - if (tmpFileName.contains('/')) { - tmpFileName = tmpFileName.mid(tmpFileName.lastIndexOf('/')+1); + if (tmpFileName.contains(QLatin1Char('/'))) { + tmpFileName = tmpFileName.mid(tmpFileName.lastIndexOf(QLatin1Char('/')) + 1); } QVERIFY( tmpFileName.length() > 0); QVERIFY( tmpFileName.length() <= 254); } } - - void testParseEtag() - { - typedef QPair Test; - QList tests; - tests.append(Test("\"abcd\"", "abcd")); - tests.append(Test("\"\"", "")); - tests.append(Test("\"fii\"-gzip", "fii")); - tests.append(Test("W/\"foo\"", "foo")); - - for (const auto &test : qAsConst(tests)) { - QCOMPARE(parseEtag(test.first), QByteArray(test.second)); - } - } }; QTEST_APPLESS_MAIN(TestOwncloudPropagator) diff --git a/test/testownsql.cpp b/test/testownsql.cpp index 167c53be88a..3dcf8a7b1d3 100644 --- a/test/testownsql.cpp +++ b/test/testownsql.cpp @@ -4,22 +4,23 @@ * any purpose. * */ +#include "common/ownsql.h" +#include "testutils.h" + #include #include -#include "common/ownsql.h" - using namespace OCC; class TestOwnSql : public QObject { Q_OBJECT - QTemporaryDir _tempDir; + const QTemporaryDir _tempDir = TestUtils::createTempDir(); -private slots: +private Q_SLOTS: void testOpenDb() { - QFileInfo fi( _tempDir.path() + "/testdb.sqlite" ); + QFileInfo fi(_tempDir.path() + QStringLiteral("/testdb.sqlite")); QVERIFY( !fi.exists() ); // must not exist _db.openOrCreateReadWrite(fi.filePath()); fi.refresh(); @@ -58,8 +59,8 @@ private slots: SqlQuery q(_db); q.prepare(sql); q.bindValue(1, 2); - q.bindValue(2, "Brucely Lafayette"); - q.bindValue(3, "Nurderway5, New York"); + q.bindValue(2, QStringLiteral("Brucely Lafayette")); + q.bindValue(3, QStringLiteral("Nurderway5, New York")); q.bindValue(4, 1403101224); QVERIFY(q.exec()); } @@ -107,8 +108,8 @@ private slots: SqlQuery q(_db); q.prepare(sql); q.bindValue(1, 3); - q.bindValue(2, QString::fromUtf8("пятницы")); - q.bindValue(3, QString::fromUtf8("проспект")); + q.bindValue(2, QStringLiteral("пятницы")); + q.bindValue(3, QStringLiteral("проспект")); q.bindValue(4, 1403002224); QVERIFY(q.exec()); } diff --git a/test/testpermissions.cpp b/test/testpermissions.cpp index b1dc393786b..166ceaa407e 100644 --- a/test/testpermissions.cpp +++ b/test/testpermissions.cpp @@ -13,7 +13,7 @@ using namespace OCC; static void applyPermissionsFromName(FileInfo &info) { - static QRegularExpression rx("_PERM_([^_]*)_[^/]*$"); + static QRegularExpression rx(QStringLiteral("_PERM_([^_]*)_[^/]*$")); auto m = rx.match(info.name); if (m.hasMatch()) { info.permissions = RemotePermissions::fromServerString(m.captured(1)); @@ -25,20 +25,21 @@ static void applyPermissionsFromName(FileInfo &info) { // Check if the expected rows in the DB are non-empty. Note that in some cases they might be, then we cannot use this function // https://github.com/owncloud/client/issues/2038 -static void assertCsyncJournalOk(SyncJournalDb &journal) +static void assertCsyncJournalOk(SyncJournalDb *journal) { // The DB is openend in locked mode: close to allow us to access. - journal.close(); + journal->close(); SqlDatabase db; - QVERIFY(db.openReadOnly(journal.databaseFilePath())); + QVERIFY(db.openReadOnly(journal->databaseFilePath())); SqlQuery q("SELECT count(*) from metadata where length(fileId) == 0", db); QVERIFY(q.exec()); QVERIFY(q.next().hasData); QCOMPARE(q.intValue(0), 0); #if defined(Q_OS_WIN) // Make sure the file does not appear in the FileInfo - FileSystem::setFileHidden(journal.databaseFilePath() + "-shm", true); + FileSystem::setFileHidden(journal->databaseFilePath() + QStringLiteral("-shm"), true); #endif + journal->allowReopen(); } SyncFileItemPtr findDiscoveryItem(const SyncFileItemSet &spy, const QString &path) @@ -54,64 +55,87 @@ bool itemInstruction(const ItemCompletedSpy &spy, const QString &path, const Syn { auto item = spy.findItem(path); Q_ASSERT(!item.isNull()); - return item->_instruction == instr; + return item->instruction() == instr; } bool discoveryInstruction(const SyncFileItemSet &spy, const QString &path, const SyncInstructions instr) { auto item = findDiscoveryItem(spy, path); - return item->_instruction == instr; + return item->instruction() == instr; } class TestPermissions : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } void t7pl() { - FakeFolder fakeFolder{ FileInfo() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + if (vfsMode == Vfs::WindowsCfApi) { + QSKIP("Known issue: the winvfs plug-in fails to set file permissions"); + } + + FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Some of this test depends on the order of discovery. With threading // that order becomes effectively random, but we want to make sure to test // all cases and thus disable threading. - auto syncOpts = fakeFolder.syncEngine().syncOptions(); + auto syncOpts = fakeFolder.syncEngine()->syncOptions(); syncOpts._parallelNetworkJobs = 1; - fakeFolder.syncEngine().setSyncOptions(syncOpts); + fakeFolder.syncEngine()->setSyncOptions(syncOpts); - const int cannotBeModifiedSize = 133; - const int canBeModifiedSize = 144; + const auto cannotBeModifiedSize = 133_B; + const auto canBeModifiedSize = 144_B; //create some files auto insertIn = [&](const QString &dir) { - fakeFolder.remoteModifier().insert(dir + "normalFile_PERM_WVND_.data", 100 ); - fakeFolder.remoteModifier().insert(dir + "cannotBeRemoved_PERM_WVN_.data", 101 ); - fakeFolder.remoteModifier().insert(dir + "canBeRemoved_PERM_D_.data", 102 ); - fakeFolder.remoteModifier().insert(dir + "cannotBeModified_PERM_DVN_.data", cannotBeModifiedSize , 'A'); - fakeFolder.remoteModifier().insert(dir + "canBeModified_PERM_W_.data", canBeModifiedSize ); + fakeFolder.remoteModifier().insert(dir + QStringLiteral("normalFile_PERM_WVND_.data"), 100_B); + fakeFolder.remoteModifier().insert(dir + QStringLiteral("cannotBeRemoved_PERM_WVN_.data"), 101_B); + fakeFolder.remoteModifier().insert(dir + QStringLiteral("canBeRemoved_PERM_D_.data"), 102_B); + fakeFolder.remoteModifier().insert(dir + QStringLiteral("cannotBeModified_PERM_DVN_.data"), cannotBeModifiedSize, 'A'); + fakeFolder.remoteModifier().insert(dir + QStringLiteral("canBeModified_PERM_W_.data"), canBeModifiedSize); }; //put them in some directories - fakeFolder.remoteModifier().mkdir("normalDirectory_PERM_CKDNV_"); - insertIn("normalDirectory_PERM_CKDNV_/"); - fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_" ); - insertIn("readonlyDirectory_PERM_M_/" ); - fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_"); - fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"); - fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data", 100); + fakeFolder.remoteModifier().mkdir(QStringLiteral("normalDirectory_PERM_CKDNV_")); + insertIn(QStringLiteral("normalDirectory_PERM_CKDNV_/")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_")); + insertIn(QStringLiteral("readonlyDirectory_PERM_M_/")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")); + fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"), 100_B); applyPermissionsFromName(fakeFolder.remoteModifier()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); assertCsyncJournalOk(fakeFolder.syncJournal()); qInfo("Do some changes and see how they propagate"); //1. remove the file than cannot be removed // (they should be recovered) - fakeFolder.localModifier().remove("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data"); - fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"); + fakeFolder.localModifier().remove(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data")); + fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data")); //2. remove the file that can be removed // (they should properly be gone) @@ -120,8 +144,8 @@ private slots: QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner); fakeFolder.localModifier().remove(file); }; - removeReadOnly("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data"); - removeReadOnly("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data"); + removeReadOnly(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data")); + removeReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data")); //3. Edit the files that cannot be modified // (they should be recovered, and a conflict shall be created) @@ -130,56 +154,57 @@ private slots: QFile(fakeFolder.localPath() + file).setPermissions(QFile::WriteOwner | QFile::ReadOwner); fakeFolder.localModifier().appendByte(file); }; - editReadOnly("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data"); - editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"); + editReadOnly(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data")); + editReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); //4. Edit other files // (they should be uploaded) - fakeFolder.localModifier().appendByte("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data"); - fakeFolder.localModifier().appendByte("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data"); + fakeFolder.localModifier().appendByte(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data")); + fakeFolder.localModifier().appendByte(QStringLiteral("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data")); //5. Create a new file in a read write folder // (should be uploaded) - fakeFolder.localModifier().insert("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data", 106 ); + fakeFolder.localModifier().insert(QStringLiteral("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data"), 106_B); applyPermissionsFromName(fakeFolder.remoteModifier()); //do the sync - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); auto currentLocalState = fakeFolder.currentLocalState(); //1. // File should be recovered - QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data")); - QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data")); + QVERIFY(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeRemoved_PERM_WVN_.data"))); + QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"))); //2. // File should be deleted - QVERIFY(!currentLocalState.find("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data")); - QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data")); + QVERIFY(!currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeRemoved_PERM_D_.data"))); + QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/canBeRemoved_PERM_D_.data"))); //3. // File should be recovered - QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data")->size, cannotBeModifiedSize); - QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")->size, cannotBeModifiedSize); + QCOMPARE(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data"))->contentSize, cannotBeModifiedSize); + QCOMPARE(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"))->contentSize, cannotBeModifiedSize); // and conflict created - auto c1 = findConflict(currentLocalState, "normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data"); + auto c1 = findConflict(currentLocalState, QStringLiteral("normalDirectory_PERM_CKDNV_/cannotBeModified_PERM_DVN_.data")); QVERIFY(c1); - QCOMPARE(c1->size, cannotBeModifiedSize + 1); - auto c2 = findConflict(currentLocalState, "readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"); + QCOMPARE(c1->contentSize, cannotBeModifiedSize + 1); + auto c2 = findConflict(currentLocalState, QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); QVERIFY(c2); - QCOMPARE(c2->size, cannotBeModifiedSize + 1); + QCOMPARE(c2->contentSize, cannotBeModifiedSize + 1); // remove the conflicts for the next state comparison fakeFolder.localModifier().remove(c1->path()); fakeFolder.localModifier().remove(c2->path()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); //4. File should be updated, that's tested by assertLocalAndRemoteDir - QCOMPARE(currentLocalState.find("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data")->size, canBeModifiedSize + 1); - QCOMPARE(currentLocalState.find("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data")->size, canBeModifiedSize + 1); + QCOMPARE(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/canBeModified_PERM_W_.data"))->contentSize, canBeModifiedSize + 1); + QCOMPARE(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/canBeModified_PERM_W_.data"))->contentSize, canBeModifiedSize + 1); //5. // the file should be in the server and local - QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data")); + QVERIFY(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/newFile_PERM_WDNV_.data"))); // Both side should still be the same QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -188,21 +213,22 @@ private slots: //6. Create a new file in a read only folder // (they should not be uploaded) - fakeFolder.localModifier().insert("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data", 105 ); + fakeFolder.localModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"), 105_B); applyPermissionsFromName(fakeFolder.remoteModifier()); // error: can't upload to readonly - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); currentLocalState = fakeFolder.currentLocalState(); //6. // The file should not exist on the remote, but still be there - QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data")); - QVERIFY(!fakeFolder.currentRemoteState().find("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data")); + QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"))); // remove it so next test succeed. - fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data"); + fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/newFile_PERM_WDNV_.data")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // Both side should still be the same QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -210,22 +236,22 @@ private slots: //###################################################################### qInfo( "remove the read only directory" ); // -> It must be recovered - fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_"); + fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_")); applyPermissionsFromName(fakeFolder.remoteModifier()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); currentLocalState = fakeFolder.currentLocalState(); - QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data")); - QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_")); + QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeRemoved_PERM_WVN_.data"))); + QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"))); // the subdirectory had delete permissions, so the contents were deleted - QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")); + QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // restore - fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"); - fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"); + fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")); + fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")); applyPermissionsFromName(fakeFolder.remoteModifier()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -234,27 +260,27 @@ private slots: //Missing directory should be restored //new directory should be uploaded - fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"); + fakeFolder.localModifier().rename(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"), QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_")); applyPermissionsFromName(fakeFolder.remoteModifier()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); currentLocalState = fakeFolder.currentLocalState(); // old name restored - QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_")); + QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"))); // contents moved (had move permissions) - QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")); - QVERIFY(!currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")); + QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"))); + QVERIFY(!currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); // new still exist (and is uploaded) - QVERIFY(currentLocalState.find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")); + QVERIFY(currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // restore for further tests - fakeFolder.remoteModifier().mkdir("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_"); - fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"); + fakeFolder.remoteModifier().mkdir(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_")); + fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data")); applyPermissionsFromName(fakeFolder.remoteModifier()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -263,43 +289,45 @@ private slots: // do a sync to update the database applyPermissionsFromName(fakeFolder.remoteModifier()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); - QVERIFY(fakeFolder.currentLocalState().find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" )); + QVERIFY( + fakeFolder.currentLocalState().find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); //1. rename a directory in a read only folder //Missing directory should be restored //new directory should stay but not be uploaded - fakeFolder.localModifier().rename("readonlyDirectory_PERM_M_/subdir_PERM_CK_", "readonlyDirectory_PERM_M_/newname_PERM_CK_" ); + fakeFolder.localModifier().rename(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"), QStringLiteral("readonlyDirectory_PERM_M_/newname_PERM_CK_")); //2. move a directory from read to read only (move the directory from previous step) - fakeFolder.localModifier().rename("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_", "readonlyDirectory_PERM_M_/moved_PERM_CK_" ); + fakeFolder.localModifier().rename(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"), QStringLiteral("readonlyDirectory_PERM_M_/moved_PERM_CK_")); // error: can't upload to readonly! - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); currentLocalState = fakeFolder.currentLocalState(); //1. // old name restored - QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_" )); + QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_"))); // including contents - QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" )); + QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/subdir_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); // new still exist - QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/newname_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" )); + QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/newname_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); // but is not on server: so remove it localy for the future comarison - fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/newname_PERM_CK_"); + fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/newname_PERM_CK_")); //2. // old removed - QVERIFY(!currentLocalState.find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_")); + QVERIFY(!currentLocalState.find(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"))); // but still on the server: the rename causing an error meant the deletes didn't execute - QVERIFY(fakeFolder.currentRemoteState().find("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_")); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"))); // new still there - QVERIFY(currentLocalState.find("readonlyDirectory_PERM_M_/moved_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data" )); + QVERIFY(currentLocalState.find(QStringLiteral("readonlyDirectory_PERM_M_/moved_PERM_CK_/subsubdir_PERM_CKDNV_/normalFile_PERM_WVND_.data"))); //but not on server - fakeFolder.localModifier().remove("readonlyDirectory_PERM_M_/moved_PERM_CK_"); - fakeFolder.remoteModifier().remove("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_"); + fakeFolder.localModifier().remove(QStringLiteral("readonlyDirectory_PERM_M_/moved_PERM_CK_")); + fakeFolder.remoteModifier().remove(QStringLiteral("normalDirectory_PERM_CKDNV_/subdir_PERM_CKDNV_")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -307,32 +335,33 @@ private slots: //###################################################################### qInfo( "multiple restores of a file create different conflict files" ); - fakeFolder.remoteModifier().insert("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"); + fakeFolder.remoteModifier().insert(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); applyPermissionsFromName(fakeFolder.remoteModifier()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"); - fakeFolder.localModifier().setContents("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data", 's'); + editReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); + fakeFolder.localModifier().setContents(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"), FileModifier::DefaultFileSize, 's'); //do the sync applyPermissionsFromName(fakeFolder.remoteModifier()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); QThread::sleep(1); // make sure changes have different mtime - editReadOnly("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"); - fakeFolder.localModifier().setContents("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data", 'd'); + editReadOnly(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")); + fakeFolder.localModifier().setContents(QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"), FileModifier::DefaultFileSize, 'd'); //do the sync applyPermissionsFromName(fakeFolder.remoteModifier()); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); assertCsyncJournalOk(fakeFolder.syncJournal()); // there should be two conflict files currentLocalState = fakeFolder.currentLocalState(); int count = 0; - while (auto i = findConflict(currentLocalState, "readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data")) { + while (auto i = findConflict(currentLocalState, QStringLiteral("readonlyDirectory_PERM_M_/cannotBeModified_PERM_DVN_.data"))) { QVERIFY((i->contentChar == 's') || (i->contentChar == 'd')); fakeFolder.localModifier().remove(i->path()); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); currentLocalState = fakeFolder.currentLocalState(); count++; } @@ -350,166 +379,177 @@ private slots: // What happens if the source can't be moved or the target can't be created? void testForbiddenMoves() { - FakeFolder fakeFolder{FileInfo{}}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + if (vfsMode == Vfs::WindowsCfApi) { + QSKIP("Known issue: the winvfs plug-in fails to set file permissions"); + } + + FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated); // Some of this test depends on the order of discovery. With threading // that order becomes effectively random, but we want to make sure to test // all cases and thus disable threading. - auto syncOpts = fakeFolder.syncEngine().syncOptions(); + auto syncOpts = fakeFolder.syncEngine()->syncOptions(); syncOpts._parallelNetworkJobs = 1; - fakeFolder.syncEngine().setSyncOptions(syncOpts); + fakeFolder.syncEngine()->setSyncOptions(syncOpts); auto &lm = fakeFolder.localModifier(); auto &rm = fakeFolder.remoteModifier(); - rm.mkdir("allowed"); - rm.mkdir("norename"); - rm.mkdir("nomove"); - rm.mkdir("nocreatefile"); - rm.mkdir("nocreatedir"); - rm.mkdir("zallowed"); // order of discovery matters - - rm.mkdir("allowed/sub"); - rm.mkdir("allowed/sub2"); - rm.insert("allowed/file"); - rm.insert("allowed/sub/file"); - rm.insert("allowed/sub2/file"); - rm.mkdir("norename/sub"); - rm.insert("norename/file"); - rm.insert("norename/sub/file"); - rm.mkdir("nomove/sub"); - rm.insert("nomove/file"); - rm.insert("nomove/sub/file"); - rm.mkdir("zallowed/sub"); - rm.mkdir("zallowed/sub2"); - rm.insert("zallowed/file"); - rm.insert("zallowed/sub/file"); - rm.insert("zallowed/sub2/file"); - - setAllPerm(rm.find("norename"), RemotePermissions::fromServerString("WDVCK")); - setAllPerm(rm.find("nomove"), RemotePermissions::fromServerString("WDNCK")); - setAllPerm(rm.find("nocreatefile"), RemotePermissions::fromServerString("WDNVK")); - setAllPerm(rm.find("nocreatedir"), RemotePermissions::fromServerString("WDNVC")); - - QVERIFY(fakeFolder.syncOnce()); + rm.mkdir(QStringLiteral("allowed")); + rm.mkdir(QStringLiteral("norename")); + rm.mkdir(QStringLiteral("nomove")); + rm.mkdir(QStringLiteral("nocreatefile")); + rm.mkdir(QStringLiteral("nocreatedir")); + rm.mkdir(QStringLiteral("zallowed")); // order of discovery matters + + rm.mkdir(QStringLiteral("allowed/sub")); + rm.mkdir(QStringLiteral("allowed/sub2")); + rm.insert(QStringLiteral("allowed/file")); + rm.insert(QStringLiteral("allowed/sub/file")); + rm.insert(QStringLiteral("allowed/sub2/file")); + rm.mkdir(QStringLiteral("norename/sub")); + rm.insert(QStringLiteral("norename/file")); + rm.insert(QStringLiteral("norename/sub/file")); + rm.mkdir(QStringLiteral("nomove/sub")); + rm.insert(QStringLiteral("nomove/file")); + rm.insert(QStringLiteral("nomove/sub/file")); + rm.mkdir(QStringLiteral("zallowed/sub")); + rm.mkdir(QStringLiteral("zallowed/sub2")); + rm.insert(QStringLiteral("zallowed/file")); + rm.insert(QStringLiteral("zallowed/sub/file")); + rm.insert(QStringLiteral("zallowed/sub2/file")); + + setAllPerm(rm.find(QStringLiteral("norename")), RemotePermissions::fromServerString(QStringLiteral("WDVCK"))); + setAllPerm(rm.find(QStringLiteral("nomove")), RemotePermissions::fromServerString(QStringLiteral("WDNCK"))); + setAllPerm(rm.find(QStringLiteral("nocreatefile")), RemotePermissions::fromServerString(QStringLiteral("WDNVK"))); + setAllPerm(rm.find(QStringLiteral("nocreatedir")), RemotePermissions::fromServerString(QStringLiteral("WDNVC"))); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // Renaming errors - lm.rename("norename/file", "norename/file_renamed"); - lm.rename("norename/sub", "norename/sub_renamed"); + lm.rename(QStringLiteral("norename/file"), QStringLiteral("norename/file_renamed")); + lm.rename(QStringLiteral("norename/sub"), QStringLiteral("norename/sub_renamed")); // Moving errors - lm.rename("nomove/file", "allowed/file_moved"); - lm.rename("nomove/sub", "allowed/sub_moved"); + lm.rename(QStringLiteral("nomove/file"), QStringLiteral("allowed/file_moved")); + lm.rename(QStringLiteral("nomove/sub"), QStringLiteral("allowed/sub_moved")); // Createfile errors - lm.rename("allowed/file", "nocreatefile/file"); - lm.rename("zallowed/file", "nocreatefile/zfile"); - lm.rename("allowed/sub", "nocreatefile/sub"); // TODO: probably forbidden because it contains file children? + lm.rename(QStringLiteral("allowed/file"), QStringLiteral("nocreatefile/file")); + lm.rename(QStringLiteral("zallowed/file"), QStringLiteral("nocreatefile/zfile")); + lm.rename(QStringLiteral("allowed/sub"), QStringLiteral("nocreatefile/sub")); // TODO: probably forbidden because it contains file children? // Createdir errors - lm.rename("allowed/sub2", "nocreatedir/sub2"); - lm.rename("zallowed/sub2", "nocreatedir/zsub2"); + lm.rename(QStringLiteral("allowed/sub2"), QStringLiteral("nocreatedir/sub2")); + lm.rename(QStringLiteral("zallowed/sub2"), QStringLiteral("nocreatedir/zsub2")); // also hook into discovery!! SyncFileItemSet discovery; - connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, this, [&discovery](auto v) { discovery = v; }); + connect(fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, this, [&discovery](auto v) { discovery = v; }); ItemCompletedSpy completeSpy(fakeFolder); - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); // if renaming doesn't work, just delete+create - QVERIFY(itemInstruction(completeSpy, "norename/file", CSYNC_INSTRUCTION_REMOVE)); - QVERIFY(completeSpy.findItem("norename/sub").isNull()); - QVERIFY(discoveryInstruction(discovery, "norename/sub", CSYNC_INSTRUCTION_REMOVE)); - QVERIFY(itemInstruction(completeSpy, "norename/file_renamed", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemInstruction(completeSpy, "norename/sub_renamed", CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/file"), CSYNC_INSTRUCTION_REMOVE)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/sub"), CSYNC_INSTRUCTION_REMOVE)); + QVERIFY(discoveryInstruction(discovery, QStringLiteral("norename/sub"), CSYNC_INSTRUCTION_REMOVE)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/file_renamed"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/sub_renamed"), CSYNC_INSTRUCTION_NEW)); // the contents can _move_ - QVERIFY(itemInstruction(completeSpy, "norename/sub_renamed/file", CSYNC_INSTRUCTION_RENAME)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("norename/sub_renamed/file"), CSYNC_INSTRUCTION_RENAME)); // simiilarly forbidding moves becomes delete+create - QVERIFY(itemInstruction(completeSpy, "nomove/file", CSYNC_INSTRUCTION_REMOVE)); - QVERIFY(completeSpy.findItem("norename/sub").isNull()); - QVERIFY(discoveryInstruction(discovery, "nomove/sub", CSYNC_INSTRUCTION_REMOVE)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nomove/file"), CSYNC_INSTRUCTION_REMOVE)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nomove/sub"), CSYNC_INSTRUCTION_REMOVE)); + QVERIFY(discoveryInstruction(discovery, QStringLiteral("nomove/sub"), CSYNC_INSTRUCTION_REMOVE)); // nomove/sub/file is removed as part of the dir - QVERIFY(itemInstruction(completeSpy, "allowed/file_moved", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemInstruction(completeSpy, "allowed/sub_moved", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemInstruction(completeSpy, "allowed/sub_moved/file", CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/file_moved"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub_moved"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub_moved/file"), CSYNC_INSTRUCTION_NEW)); // when moving to an invalid target, the targets should be an error - QVERIFY(itemInstruction(completeSpy, "nocreatefile/file", CSYNC_INSTRUCTION_ERROR)); - QVERIFY(itemInstruction(completeSpy, "nocreatefile/zfile", CSYNC_INSTRUCTION_ERROR)); - QVERIFY(itemInstruction(completeSpy, "nocreatefile/sub", CSYNC_INSTRUCTION_RENAME)); // TODO: What does a real server say? - QVERIFY(itemInstruction(completeSpy, "nocreatedir/sub2", CSYNC_INSTRUCTION_ERROR)); - QVERIFY(itemInstruction(completeSpy, "nocreatedir/zsub2", CSYNC_INSTRUCTION_ERROR)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/file"), CSYNC_INSTRUCTION_ERROR)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/zfile"), CSYNC_INSTRUCTION_ERROR)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/sub"), CSYNC_INSTRUCTION_RENAME)); // TODO: What does a real server say? + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/sub2"), CSYNC_INSTRUCTION_ERROR)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/zsub2"), CSYNC_INSTRUCTION_ERROR)); // and the sources of the invalid moves should be restored, not deleted // (depending on the order of discovery a follow-up sync is needed) - QVERIFY(completeSpy.findItem("allowed/file").isNull()); - QVERIFY(completeSpy.findItem("allowed/sub2").isNull()); - QVERIFY(itemInstruction(completeSpy, "zallowed/file", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemInstruction(completeSpy, "zallowed/sub2", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemInstruction(completeSpy, "zallowed/sub2/file", CSYNC_INSTRUCTION_NEW)); - QCOMPARE(fakeFolder.syncEngine().isAnotherSyncNeeded(), ImmediateFollowUp); + QVERIFY(completeSpy.findItem(QStringLiteral("allowed/file")).isNull()); + QVERIFY(completeSpy.findItem(QStringLiteral("allowed/sub2")).isNull()); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/file"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/sub2"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("zallowed/sub2/file"), CSYNC_INSTRUCTION_NEW)); + QCOMPARE(fakeFolder.syncEngine()->isAnotherSyncNeeded(), true); // A follow-up sync will restore allowed/file and allowed/sub2 and maintain the nocreatedir/file errors completeSpy.clear(); - QCOMPARE(fakeFolder.syncJournal().wipeErrorBlacklist(), 4); - QVERIFY(!fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.syncJournal()->wipeErrorBlacklist(), 4); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(itemInstruction(completeSpy, "nocreatefile/file", CSYNC_INSTRUCTION_ERROR)); - QVERIFY(itemInstruction(completeSpy, "nocreatefile/zfile", CSYNC_INSTRUCTION_ERROR)); - QVERIFY(itemInstruction(completeSpy, "nocreatedir/sub2", CSYNC_INSTRUCTION_ERROR)); - QVERIFY(itemInstruction(completeSpy, "nocreatedir/zsub2", CSYNC_INSTRUCTION_ERROR)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/file"), CSYNC_INSTRUCTION_ERROR)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatefile/zfile"), CSYNC_INSTRUCTION_ERROR)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/sub2"), CSYNC_INSTRUCTION_ERROR)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("nocreatedir/zsub2"), CSYNC_INSTRUCTION_ERROR)); - QVERIFY(itemInstruction(completeSpy, "allowed/file", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemInstruction(completeSpy, "allowed/sub2", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemInstruction(completeSpy, "allowed/sub2/file", CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/file"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub2"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("allowed/sub2/file"), CSYNC_INSTRUCTION_NEW)); auto cls = fakeFolder.currentLocalState(); - QVERIFY(cls.find("allowed/file")); - QVERIFY(cls.find("allowed/sub2")); - QVERIFY(cls.find("zallowed/file")); - QVERIFY(cls.find("zallowed/sub2")); - QVERIFY(cls.find("zallowed/sub2/file")); + QVERIFY(cls.find(QStringLiteral("allowed/file"))); + QVERIFY(cls.find(QStringLiteral("allowed/sub2"))); + QVERIFY(cls.find(QStringLiteral("zallowed/file"))); + QVERIFY(cls.find(QStringLiteral("zallowed/sub2"))); + QVERIFY(cls.find(QStringLiteral("zallowed/sub2/file"))); } // Test for issue #7293 void testAllowedMoveForbiddenDelete() { - FakeFolder fakeFolder{FileInfo{}}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + if (vfsMode == Vfs::WindowsCfApi) { + QSKIP("Known issue: the winvfs plug-in fails to set file permissions"); + } + + FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated); // Some of this test depends on the order of discovery. With threading // that order becomes effectively random, but we want to make sure to test // all cases and thus disable threading. - auto syncOpts = fakeFolder.syncEngine().syncOptions(); + auto syncOpts = fakeFolder.syncEngine()->syncOptions(); syncOpts._parallelNetworkJobs = 1; - fakeFolder.syncEngine().setSyncOptions(syncOpts); + fakeFolder.syncEngine()->setSyncOptions(syncOpts); auto &lm = fakeFolder.localModifier(); auto &rm = fakeFolder.remoteModifier(); - rm.mkdir("changeonly"); - rm.mkdir("changeonly/sub1"); - rm.insert("changeonly/sub1/file1"); - rm.insert("changeonly/sub1/filetorname1a"); - rm.insert("changeonly/sub1/filetorname1z"); - rm.mkdir("changeonly/sub2"); - rm.insert("changeonly/sub2/file2"); - rm.insert("changeonly/sub2/filetorname2a"); - rm.insert("changeonly/sub2/filetorname2z"); + rm.mkdir(QStringLiteral("changeonly")); + rm.mkdir(QStringLiteral("changeonly/sub1")); + rm.insert(QStringLiteral("changeonly/sub1/file1")); + rm.insert(QStringLiteral("changeonly/sub1/filetorname1a")); + rm.insert(QStringLiteral("changeonly/sub1/filetorname1z")); + rm.mkdir(QStringLiteral("changeonly/sub2")); + rm.insert(QStringLiteral("changeonly/sub2/file2")); + rm.insert(QStringLiteral("changeonly/sub2/filetorname2a")); + rm.insert(QStringLiteral("changeonly/sub2/filetorname2z")); - setAllPerm(rm.find("changeonly"), RemotePermissions::fromServerString("NSV")); + setAllPerm(rm.find(QStringLiteral("changeonly")), RemotePermissions::fromServerString(QStringLiteral("NSV"))); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - lm.rename("changeonly/sub1/filetorname1a", "changeonly/sub1/aaa1_renamed"); - lm.rename("changeonly/sub1/filetorname1z", "changeonly/sub1/zzz1_renamed"); + lm.rename(QStringLiteral("changeonly/sub1/filetorname1a"), QStringLiteral("changeonly/sub1/aaa1_renamed")); + lm.rename(QStringLiteral("changeonly/sub1/filetorname1z"), QStringLiteral("changeonly/sub1/zzz1_renamed")); - lm.rename("changeonly/sub2/filetorname2a", "changeonly/sub2/aaa2_renamed"); - lm.rename("changeonly/sub2/filetorname2z", "changeonly/sub2/zzz2_renamed"); + lm.rename(QStringLiteral("changeonly/sub2/filetorname2a"), QStringLiteral("changeonly/sub2/aaa2_renamed")); + lm.rename(QStringLiteral("changeonly/sub2/filetorname2z"), QStringLiteral("changeonly/sub2/zzz2_renamed")); - lm.rename("changeonly/sub1", "changeonly/aaa"); - lm.rename("changeonly/sub2", "changeonly/zzz"); + lm.rename(QStringLiteral("changeonly/sub1"), QStringLiteral("changeonly/aaa")); + lm.rename(QStringLiteral("changeonly/sub2"), QStringLiteral("changeonly/zzz")); - auto expectedState = fakeFolder.currentLocalState(); - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), expectedState); - QCOMPARE(fakeFolder.currentRemoteState(), expectedState); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } }; diff --git a/test/testplan.txt b/test/testplan.txt index 31528893686..d4a2205320c 100644 --- a/test/testplan.txt +++ b/test/testplan.txt @@ -46,7 +46,7 @@ Test Plan 3. SSL => With version 1.0.1 oCC supports SSL connections. -+ Pre-Req: Have a SSL ready host with unsigned certificate. ++ Pre-Req: Have an SSL ready host with unsigned certificate. 3.1 SSL connection - Start ownCloud configuration and enter the SSL url with https://... diff --git a/test/testremotediscovery.cpp b/test/testremotediscovery.cpp index b8174faefa6..92198a126a9 100644 --- a/test/testremotediscovery.cpp +++ b/test/testremotediscovery.cpp @@ -47,7 +47,23 @@ class TestRemoteDiscovery : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } void testRemoteDiscoveryError_data() { @@ -56,46 +72,49 @@ private slots: QTest::addColumn("expectedErrorString"); QTest::addColumn("syncSucceeds"); - QString itemErrorMessage = "Internal Server Fake Error"; + QString itemErrorMessage = QStringLiteral("Fake internal server error"); QTest::newRow("400") << 400 << itemErrorMessage << false; - QTest::newRow("401") << 401 << itemErrorMessage << false; - QTest::newRow("403") << 403 << itemErrorMessage << true; - QTest::newRow("404") << 404 << itemErrorMessage << true; + QTest::newRow("401") << 401 << QStringLiteral("Fake credentials error") << false; + QTest::newRow("403") << 403 << QStringLiteral("Fake access denied error") << true; + QTest::newRow("404") << 404 << QStringLiteral("Fake file not found error") << true; QTest::newRow("500") << 500 << itemErrorMessage << true; QTest::newRow("503") << 503 << itemErrorMessage << true; // 200 should be an error since propfind should return 207 QTest::newRow("200") << 200 << itemErrorMessage << false; QTest::newRow("InvalidXML") << +InvalidXML << "Unknown error" << false; - QTest::newRow("Timeout") << +Timeout << "Operation canceled" << false; + QTest::newRow("Timeout") << +Timeout << "Connection timed out" << false; } // Check what happens when there is an error. void testRemoteDiscoveryError() { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + QFETCH(int, errorKind); QFETCH(QString, expectedErrorString); QFETCH(bool, syncSucceeds); - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); // Do Some change as well - fakeFolder.localModifier().insert("A/z1"); - fakeFolder.localModifier().insert("B/z1"); - fakeFolder.localModifier().insert("C/z1"); - fakeFolder.remoteModifier().insert("A/z2"); - fakeFolder.remoteModifier().insert("B/z2"); - fakeFolder.remoteModifier().insert("C/z2"); + fakeFolder.localModifier().insert(QStringLiteral("A/z1")); + fakeFolder.localModifier().insert(QStringLiteral("B/z1")); + fakeFolder.localModifier().insert(QStringLiteral("C/z1")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/z2")); + fakeFolder.remoteModifier().insert(QStringLiteral("B/z2")); + fakeFolder.remoteModifier().insert(QStringLiteral("C/z2")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); auto oldLocalState = fakeFolder.currentLocalState(); auto oldRemoteState = fakeFolder.currentRemoteState(); - QString errorFolder = "webdav/B"; - QString fatalErrorPrefix = "Server replied with an error while reading directory 'B' : "; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) - -> QNetworkReply *{ - if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "PROPFIND" && req.url().path().endsWith(errorFolder)) { + QString errorFolder = fakeFolder.account()->davPath() + QStringLiteral("B"); + QString fatalErrorPrefix = QStringLiteral("Server replied with an error while reading directory 'B' : "); + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) -> QNetworkReply * { + if (req.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray() == "PROPFIND" && req.url().path().endsWith(errorFolder)) { if (errorKind == InvalidXML) { return new FakeBrokenXmlPropfindReply(fakeFolder.remoteModifier(), op, req, this); } else if (errorKind == Timeout) { @@ -111,65 +130,67 @@ private slots: QScopedValueRollback setHttpTimeout(AbstractNetworkJob::httpTimeout, errorKind == Timeout ? 1s : 10000s); ItemCompletedSpy completeSpy(fakeFolder); - QSignalSpy errorSpy(&fakeFolder.syncEngine(), &SyncEngine::syncError); - QCOMPARE(fakeFolder.syncOnce(), syncSucceeds); + QSignalSpy errorSpy(fakeFolder.syncEngine(), &SyncEngine::syncError); + QCOMPARE(fakeFolder.applyLocalModificationsAndSync(), syncSucceeds); // The folder B should not have been sync'ed (and in particular not removed) - QCOMPARE(oldLocalState.children["B"], fakeFolder.currentLocalState().children["B"]); - QCOMPARE(oldRemoteState.children["B"], fakeFolder.currentRemoteState().children["B"]); + QCOMPARE(oldLocalState.children[QStringLiteral("B")], fakeFolder.currentLocalState().children[QStringLiteral("B")]); + QCOMPARE(oldRemoteState.children[QStringLiteral("B")], fakeFolder.currentRemoteState().children[QStringLiteral("B")]); if (!syncSucceeds) { QCOMPARE(errorSpy.size(), 1); QCOMPARE(errorSpy[0][0].toString(), QString(fatalErrorPrefix + expectedErrorString)); } else { - QCOMPARE(completeSpy.findItem("B")->_instruction, CSYNC_INSTRUCTION_IGNORE); - QVERIFY(completeSpy.findItem("B")->_errorString.contains(expectedErrorString)); + QCOMPARE(completeSpy.findItem(QStringLiteral("B"))->instruction(), CSYNC_INSTRUCTION_IGNORE); + QVERIFY(completeSpy.findItem(QStringLiteral("B"))->_errorString.contains(expectedErrorString)); // The other folder should have been sync'ed as the sync just ignored the faulty dir - QCOMPARE(fakeFolder.currentRemoteState().children["A"], fakeFolder.currentLocalState().children["A"]); - QCOMPARE(fakeFolder.currentRemoteState().children["C"], fakeFolder.currentLocalState().children["C"]); - QCOMPARE(completeSpy.findItem("A/z1")->_instruction, CSYNC_INSTRUCTION_NEW); + QCOMPARE(fakeFolder.currentRemoteState().children[QStringLiteral("A")], fakeFolder.currentLocalState().children[QStringLiteral("A")]); + QCOMPARE(fakeFolder.currentRemoteState().children[QStringLiteral("C")], fakeFolder.currentLocalState().children[QStringLiteral("C")]); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/z1"))->instruction(), CSYNC_INSTRUCTION_NEW); } // // Check the same discovery error on the sync root // - errorFolder = "webdav/"; - fatalErrorPrefix = "Server replied with an error while reading directory '' : "; + errorFolder = fakeFolder.account()->davPath(); + fatalErrorPrefix = QStringLiteral("Server replied with an error while reading directory '/' : "); errorSpy.clear(); - QVERIFY(!fakeFolder.syncOnce()); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(errorSpy.size(), 1); QCOMPARE(errorSpy[0][0].toString(), QString(fatalErrorPrefix + expectedErrorString)); } void testMissingData() { - FakeFolder fakeFolder{ FileInfo() }; - fakeFolder.remoteModifier().insert("good"); - fakeFolder.remoteModifier().insert("noetag"); - fakeFolder.remoteModifier().find("noetag")->etag.clear(); - fakeFolder.remoteModifier().insert("nofileid"); - fakeFolder.remoteModifier().find("nofileid")->fileId.clear(); - fakeFolder.remoteModifier().mkdir("nopermissions"); - fakeFolder.remoteModifier().insert("nopermissions/A"); - - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) - -> QNetworkReply *{ - if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "PROPFIND" && req.url().path().endsWith("nopermissions")) + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo(), vfsMode, filesAreDehydrated); + fakeFolder.remoteModifier().insert(QStringLiteral("good")); + fakeFolder.remoteModifier().insert(QStringLiteral("noetag")); + fakeFolder.remoteModifier().find(QStringLiteral("noetag"))->etag.clear(); + fakeFolder.remoteModifier().insert(QStringLiteral("nofileid")); + fakeFolder.remoteModifier().find(QStringLiteral("nofileid"))->fileId.clear(); + fakeFolder.remoteModifier().mkdir(QStringLiteral("nopermissions")); + fakeFolder.remoteModifier().insert(QStringLiteral("nopermissions/A")); + + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) -> QNetworkReply * { + if (req.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray() == "PROPFIND" && req.url().path().endsWith(QLatin1String("nopermissions"))) return new MissingPermissionsPropfindReply(fakeFolder.remoteModifier(), op, req, this); return nullptr; }); ItemCompletedSpy completeSpy(fakeFolder); - QVERIFY(!fakeFolder.syncOnce()); - - QCOMPARE(completeSpy.findItem("good")->_instruction, CSYNC_INSTRUCTION_NEW); - QCOMPARE(completeSpy.findItem("noetag")->_instruction, CSYNC_INSTRUCTION_ERROR); - QCOMPARE(completeSpy.findItem("nofileid")->_instruction, CSYNC_INSTRUCTION_ERROR); - QCOMPARE(completeSpy.findItem("nopermissions")->_instruction, CSYNC_INSTRUCTION_NEW); - QCOMPARE(completeSpy.findItem("nopermissions/A")->_instruction, CSYNC_INSTRUCTION_ERROR); - QVERIFY(completeSpy.findItem("noetag")->_errorString.contains("etag")); - QVERIFY(completeSpy.findItem("nofileid")->_errorString.contains("file id")); - QVERIFY(completeSpy.findItem("nopermissions/A")->_errorString.contains("permissions")); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); + + QCOMPARE(completeSpy.findItem(QStringLiteral("good"))->instruction(), CSYNC_INSTRUCTION_NEW); + QCOMPARE(completeSpy.findItem(QStringLiteral("noetag"))->instruction(), CSYNC_INSTRUCTION_ERROR); + QCOMPARE(completeSpy.findItem(QStringLiteral("nofileid"))->instruction(), CSYNC_INSTRUCTION_ERROR); + QCOMPARE(completeSpy.findItem(QStringLiteral("nopermissions"))->instruction(), CSYNC_INSTRUCTION_NEW); + QCOMPARE(completeSpy.findItem(QStringLiteral("nopermissions/A"))->instruction(), CSYNC_INSTRUCTION_ERROR); + QVERIFY(completeSpy.findItem(QStringLiteral("noetag"))->_errorString.contains(QStringLiteral("etag"))); + QVERIFY(completeSpy.findItem(QStringLiteral("nofileid"))->_errorString.contains(QStringLiteral("id"))); + QVERIFY(completeSpy.findItem(QStringLiteral("nopermissions/A"))->_errorString.contains(QStringLiteral("permissions"))); } }; diff --git a/test/testselectivesync.cpp b/test/testselectivesync.cpp deleted file mode 100644 index 67f44d3fc9f..00000000000 --- a/test/testselectivesync.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * - */ - -#include -#include "testutils/syncenginetestutils.h" -#include - -using namespace OCC; - - -class TestSelectiveSync : public QObject -{ - Q_OBJECT - -private slots: - - - void testSelectiveSyncBigFolders() - { - FakeFolder fakeFolder { FileInfo::A12_B12_C12_S12() }; - SyncOptions options; - options._newBigFolderSizeLimit = 20000; // 20 K - fakeFolder.syncEngine().setSyncOptions(options); - - QStringList sizeRequests; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation, const QNetworkRequest &req, QIODevice *device) - -> QNetworkReply * { - // Record what path we are querying for the size - if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "PROPFIND") { - if (device->readAll().contains("extraDavProperties = "20020"; - fakeFolder.remoteModifier().find("A/newBigDir/subDir")->extraDavProperties = "20020"; - fakeFolder.remoteModifier().find("B/newSmallDir")->extraDavProperties = "10"; - fakeFolder.remoteModifier().find("B/newSmallDir/subDir")->extraDavProperties = "10"; - - QVERIFY(fakeFolder.syncOnce()); - - QCOMPARE(newBigFolder.count(), 1); - QCOMPARE(newBigFolder.first()[0].toString(), QString("A/newBigDir")); - QCOMPARE(newBigFolder.first()[1].toBool(), false); - newBigFolder.clear(); - - QCOMPARE(sizeRequests.count(), 2); // "A/newBigDir" and "B/newSmallDir"; - QCOMPARE(sizeRequests.filter("/subDir").count(), 0); // at no point we should request the size of the subdirs - sizeRequests.clear(); - - auto oldSync = fakeFolder.currentLocalState(); - // syncing again should do the same - fakeFolder.syncEngine().journal()->schedulePathForRemoteDiscovery(QString("A/newBigDir")); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), oldSync); - QCOMPARE(newBigFolder.count(), 1); // (since we don't have a real Folder, the files were not added to any list) - newBigFolder.clear(); - QCOMPARE(sizeRequests.count(), 1); // "A/newBigDir"; - sizeRequests.clear(); - - // Simulate that we accept all files by seting a wildcard white list - fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, - QStringList() << QLatin1String("/")); - fakeFolder.syncEngine().journal()->schedulePathForRemoteDiscovery(QString("A/newBigDir")); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(newBigFolder.count(), 0); - QCOMPARE(sizeRequests.count(), 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } -}; - -QTEST_GUILESS_MAIN(TestSelectiveSync) -#include "testselectivesync.moc" diff --git a/test/testsyncconflict.cpp b/test/testsyncconflict.cpp index dde1eb751b6..25efa009e8c 100644 --- a/test/testsyncconflict.cpp +++ b/test/testsyncconflict.cpp @@ -5,318 +5,195 @@ * */ -#include -#include "testutils/syncenginetestutils.h" #include +#include "testutils/syncenginetestutils.h" +#include "testutils/testutils.h" + +#include + using namespace OCC; +namespace { bool itemSuccessful(const ItemCompletedSpy &spy, const QString &path, const SyncInstructions instr) { auto item = spy.findItem(path); - return item->_status == SyncFileItem::Success && item->_instruction == instr; + Q_ASSERT(item); + return item->_status == SyncFileItem::Success && item->instruction() == instr; } bool itemConflict(const ItemCompletedSpy &spy, const QString &path) { auto item = spy.findItem(path); - return item->_status == SyncFileItem::Conflict && item->_instruction == CSYNC_INSTRUCTION_CONFLICT; -} - -bool itemSuccessfulMove(const ItemCompletedSpy &spy, const QString &path) -{ - return itemSuccessful(spy, path, CSYNC_INSTRUCTION_RENAME); + Q_ASSERT(item); + return item->_status == SyncFileItem::Conflict && item->instruction() == CSYNC_INSTRUCTION_CONFLICT; } QStringList findConflicts(const FileInfo &dir) { QStringList conflicts; for (const auto &item : dir.children) { - if (item.name.contains("(conflicted copy")) { + if (item.name.contains(QLatin1String("(conflicted copy"))) { conflicts.append(item.path()); } } return conflicts; } -bool expectAndWipeConflict(FileModifier &local, FileInfo state, const QString path) +bool expectAndWipeConflict(FakeFolder &fFolder, const QString &path) { PathComponents pathComponents(path); - auto base = state.find(pathComponents.parentDirComponents()); - if (!base) + auto localState = fFolder.currentLocalState(); + auto base = localState.find(pathComponents.parentDirComponents()); + if (!base) { return false; - for (const auto &item : qAsConst(base->children)) { - if (item.name.startsWith(pathComponents.fileName()) && item.name.contains("(conflicted copy")) { - local.remove(item.path()); + } + + for (const auto &item : std::as_const(base->children)) { + if (item.name.startsWith(pathComponents.fileName()) && item.name.contains(QLatin1String("(conflicted copy"))) { + fFolder.localModifier().remove(item.path()); + OC_ASSERT(fFolder.applyLocalModificationsWithoutSync()); return true; } } + return false; } SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path) { SyncJournalFileRecord record; - folder.syncJournal().getFileRecord(path, &record); + folder.syncJournal()->getFileRecord(path, &record); return record; } +} + class TestSyncConflict : public QObject { Q_OBJECT -private slots: - void testNoUpload() +private Q_SLOTS: + void initTestCase_data() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); - fakeFolder.localModifier().setContents("A/a1", 'L'); - fakeFolder.remoteModifier().setContents("A/a1", 'R'); - fakeFolder.localModifier().appendByte("A/a2"); - fakeFolder.remoteModifier().appendByte("A/a2"); - fakeFolder.remoteModifier().appendByte("A/a2"); - QVERIFY(fakeFolder.syncOnce()); - - // Verify that the conflict names don't have the user name - const auto &conflicts = findConflicts(fakeFolder.currentLocalState().children["A"]); - for (const auto &name : conflicts) { - QVERIFY(!name.contains(fakeFolder.syncEngine().account()->davDisplayName())); - } + QTest::newRow("Vfs::Off") << Vfs::Off << false; - QVERIFY(expectAndWipeConflict(fakeFolder.localModifier(), fakeFolder.currentLocalState(), "A/a1")); - QVERIFY(expectAndWipeConflict(fakeFolder.localModifier(), fakeFolder.currentLocalState(), "A/a2")); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } } - void testUploadAfterDownload() + void testNoUpload() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", true } }); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); - QMap conflictMap; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::PutOperation) { - if (request.rawHeader("OC-Conflict") == "1") { - auto baseFileId = request.rawHeader("OC-ConflictBaseFileId"); - auto components = request.url().toString().split('/'); - QString conflictFile = components.mid(components.size() - 2).join('/'); - conflictMap[baseFileId] = conflictFile; - [&] { - QVERIFY(!baseFileId.isEmpty()); - QCOMPARE(request.rawHeader("OC-ConflictInitialBasePath"), - Utility::conflictFileBaseNameFromPattern(conflictFile.toUtf8())); - }(); - } - } - return nullptr; - }); - - fakeFolder.localModifier().setContents("A/a1", 'L'); - fakeFolder.remoteModifier().setContents("A/a1", 'R'); - fakeFolder.localModifier().appendByte("A/a2"); - fakeFolder.remoteModifier().appendByte("A/a2"); - fakeFolder.remoteModifier().appendByte("A/a2"); - QVERIFY(fakeFolder.syncOnce()); - auto local = fakeFolder.currentLocalState(); - auto remote = fakeFolder.currentRemoteState(); - QCOMPARE(local, remote); - - auto a1FileId = fakeFolder.remoteModifier().find("A/a1")->fileId; - auto a2FileId = fakeFolder.remoteModifier().find("A/a2")->fileId; - QVERIFY(conflictMap.contains(a1FileId)); - QVERIFY(conflictMap.contains(a2FileId)); - QCOMPARE(conflictMap.size(), 2); - QCOMPARE(Utility::conflictFileBaseNameFromPattern(conflictMap[a1FileId].toUtf8()), QByteArray("A/a1")); - - // Check that the conflict file contains the username - QVERIFY(conflictMap[a1FileId].contains(QString("(conflicted copy %1 ").arg(fakeFolder.syncEngine().account()->davDisplayName()))); - - QCOMPARE(remote.find(conflictMap[a1FileId])->contentChar, 'L'); - QCOMPARE(remote.find("A/a1")->contentChar, 'R'); - - QCOMPARE(remote.find(conflictMap[a2FileId])->size, 5); - QCOMPARE(remote.find("A/a2")->size, 6); - } - - void testSeparateUpload() - { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", true } }); + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QMap conflictMap; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::PutOperation) { - if (request.rawHeader("OC-Conflict") == "1") { - auto baseFileId = request.rawHeader("OC-ConflictBaseFileId"); - auto components = request.url().toString().split('/'); - QString conflictFile = components.mid(components.size() - 2).join('/'); - conflictMap[baseFileId] = conflictFile; - [&] { - QVERIFY(!baseFileId.isEmpty()); - QCOMPARE(request.rawHeader("OC-ConflictInitialBasePath"), - Utility::conflictFileBaseNameFromPattern(conflictFile.toUtf8())); - }(); - } + fakeFolder.localModifier().setContents(QStringLiteral("A/a1"), FileModifier::DefaultFileSize, 'L'); + fakeFolder.remoteModifier().setContents(QStringLiteral("A/a1"), FileModifier::DefaultFileSize, 'R'); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a2")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a2")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a2")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + const auto &conflicts = findConflicts(fakeFolder.currentLocalState().children[QStringLiteral("A")]); + if (filesAreDehydrated) { + // There should be no conflicts: before a modification is done to a local file, + // it will be downloaded from the remote first. + QCOMPARE(conflicts.size(), 0); + } else { + // Verify that the conflict names don't have the user name + for (const auto &name : conflicts) { + QVERIFY(!name.contains(fakeFolder.account()->davDisplayName())); } - return nullptr; - }); - - // Explicitly add a conflict file to simulate the case where the upload of the - // file didn't finish in the same sync run that the conflict was created. - // To do that we need to create a mock conflict record. - auto a1FileId = fakeFolder.remoteModifier().find("A/a1")->fileId; - QString conflictName = QLatin1String("A/a1 (conflicted copy me 1234)"); - fakeFolder.localModifier().insert(conflictName, 64, 'L'); - ConflictRecord conflictRecord; - conflictRecord.path = conflictName.toUtf8(); - conflictRecord.baseFileId = a1FileId; - conflictRecord.initialBasePath = "A/a1"; - fakeFolder.syncJournal().setConflictRecord(conflictRecord); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(conflictMap.size(), 1); - QCOMPARE(conflictMap[a1FileId], conflictName); - QCOMPARE(fakeFolder.currentRemoteState().find(conflictMap[a1FileId])->contentChar, 'L'); - conflictMap.clear(); - - // Now the user can locally alter the conflict file and it will be uploaded - // as usual. - fakeFolder.localModifier().setContents(conflictName, 'P'); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(conflictMap.size(), 1); - QCOMPARE(conflictMap[a1FileId], conflictName); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - conflictMap.clear(); - - // Similarly, remote modifications of conflict files get propagated downwards - fakeFolder.remoteModifier().setContents(conflictName, 'Q'); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QVERIFY(conflictMap.isEmpty()); - - // Conflict files for conflict files! - auto a1ConflictFileId = fakeFolder.remoteModifier().find(conflictName)->fileId; - fakeFolder.remoteModifier().appendByte(conflictName); - fakeFolder.remoteModifier().appendByte(conflictName); - fakeFolder.localModifier().appendByte(conflictName); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(conflictMap.size(), 1); - QVERIFY(conflictMap.contains(a1ConflictFileId)); - QCOMPARE(fakeFolder.currentRemoteState().find(conflictName)->size, 66); - QCOMPARE(fakeFolder.currentRemoteState().find(conflictMap[a1ConflictFileId])->size, 65); - conflictMap.clear(); - } - // What happens if we download a conflict file? Is the metadata set up correctly? - void testDownloadingConflictFile() - { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", true } }); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // With no headers from the server - fakeFolder.remoteModifier().insert("A/a1 (conflicted copy 1234)"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - auto conflictRecord = fakeFolder.syncJournal().conflictRecord("A/a1 (conflicted copy 1234)"); - QVERIFY(conflictRecord.isValid()); - QCOMPARE(conflictRecord.baseFileId, fakeFolder.remoteModifier().find("A/a1")->fileId); - QCOMPARE(conflictRecord.initialBasePath, QByteArray("A/a1")); - - // Now with server headers - QObject parent; - auto a2FileId = fakeFolder.remoteModifier().find("A/a2")->fileId; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { - if (op == QNetworkAccessManager::GetOperation) { - auto reply = new FakeGetReply(fakeFolder.remoteModifier(), op, request, &parent); - reply->setRawHeader("OC-Conflict", "1"); - reply->setRawHeader("OC-ConflictBaseFileId", a2FileId); - reply->setRawHeader("OC-ConflictBaseMtime", "1234"); - reply->setRawHeader("OC-ConflictBaseEtag", "etag"); - reply->setRawHeader("OC-ConflictInitialBasePath", "A/original"); - return reply; - } - return nullptr; - }); - fakeFolder.remoteModifier().insert("A/really-a-conflict"); // doesn't look like a conflict, but headers say it is - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - conflictRecord = fakeFolder.syncJournal().conflictRecord("A/really-a-conflict"); - QVERIFY(conflictRecord.isValid()); - QCOMPARE(conflictRecord.baseFileId, a2FileId); - QCOMPARE(conflictRecord.baseModtime, 1234); - QCOMPARE(conflictRecord.baseEtag, QByteArray("etag")); - QCOMPARE(conflictRecord.initialBasePath, QByteArray("A/original")); + QVERIFY(expectAndWipeConflict(fakeFolder, QStringLiteral("A/a1"))); + QVERIFY(expectAndWipeConflict(fakeFolder, QStringLiteral("A/a2"))); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } } // Check that conflict records are removed when the file is gone void testConflictRecordRemoval1() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", true } }); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Make conflict records ConflictRecord conflictRecord; conflictRecord.path = "A/a1"; - fakeFolder.syncJournal().setConflictRecord(conflictRecord); + fakeFolder.syncJournal()->setConflictRecord(conflictRecord); conflictRecord.path = "A/a2"; - fakeFolder.syncJournal().setConflictRecord(conflictRecord); + fakeFolder.syncJournal()->setConflictRecord(conflictRecord); // A nothing-to-sync keeps them alive - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QVERIFY(fakeFolder.syncJournal().conflictRecord("A/a1").isValid()); - QVERIFY(fakeFolder.syncJournal().conflictRecord("A/a2").isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord("A/a1").isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord("A/a2").isValid()); // When the file is removed, the record is removed too - fakeFolder.localModifier().remove("A/a2"); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.localModifier().remove(QStringLiteral("A/a2")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QVERIFY(fakeFolder.syncJournal().conflictRecord("A/a1").isValid()); - QVERIFY(!fakeFolder.syncJournal().conflictRecord("A/a2").isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord("A/a1").isValid()); + QVERIFY(!fakeFolder.syncJournal()->conflictRecord("A/a2").isValid()); } - // Same test, but with uploadConflictFiles == false void testConflictRecordRemoval2() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", false } }); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Create two conflicts - fakeFolder.localModifier().appendByte("A/a1"); - fakeFolder.localModifier().appendByte("A/a1"); - fakeFolder.remoteModifier().appendByte("A/a1"); - fakeFolder.localModifier().appendByte("A/a2"); - fakeFolder.localModifier().appendByte("A/a2"); - fakeFolder.remoteModifier().appendByte("A/a2"); - QVERIFY(fakeFolder.syncOnce()); - - const auto &conflicts = findConflicts(fakeFolder.currentLocalState().children["A"]); - QByteArray a1conflict; - QByteArray a2conflict; - for (const auto & conflict : conflicts) { - if (conflict.contains("a1")) - a1conflict = conflict.toUtf8(); - if (conflict.contains("a2")) - a2conflict = conflict.toUtf8(); - } + fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a2")); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a2")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a2")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + const auto &conflicts = findConflicts(fakeFolder.currentLocalState().children[QStringLiteral("A")]); + if (filesAreDehydrated) { + // There should be no conflicts: before a modification is done to a local file, + // it will be downloaded from the remote first. + QCOMPARE(conflicts.size(), 0); + } else { + QString a1conflict; + QString a2conflict; + for (const auto &conflict : conflicts) { + if (conflict.contains(QLatin1String("a1"))) + a1conflict = conflict; + if (conflict.contains(QLatin1String("a2"))) + a2conflict = conflict; + } - // A nothing-to-sync keeps them alive - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.syncJournal().conflictRecord(a1conflict).isValid()); - QVERIFY(fakeFolder.syncJournal().conflictRecord(a2conflict).isValid()); + // A nothing-to-sync keeps them alive + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord(a1conflict.toUtf8()).isValid()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord(a2conflict.toUtf8()).isValid()); - // When the file is removed, the record is removed too - fakeFolder.localModifier().remove(a2conflict); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.syncJournal().conflictRecord(a1conflict).isValid()); - QVERIFY(!fakeFolder.syncJournal().conflictRecord(a2conflict).isValid()); + // When the file is removed, the record is removed too + fakeFolder.localModifier().remove(a2conflict); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(fakeFolder.syncJournal()->conflictRecord(a1conflict.toUtf8()).isValid()); + QVERIFY(!fakeFolder.syncJournal()->conflictRecord(a2conflict.toUtf8()).isValid()); + } } void testConflictFileBaseName_data() @@ -401,8 +278,10 @@ private slots: void testLocalDirRemoteFileConflict() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", true } }); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); auto cleanup = [&]() { @@ -411,151 +290,156 @@ private slots: cleanup(); // 1) a NEW/NEW conflict - fakeFolder.localModifier().mkdir("Z"); - fakeFolder.localModifier().mkdir("Z/subdir"); - fakeFolder.localModifier().insert("Z/foo"); - fakeFolder.remoteModifier().insert("Z", 63); + fakeFolder.localModifier().mkdir(QStringLiteral("Z")); + fakeFolder.localModifier().mkdir(QStringLiteral("Z/subdir")); + fakeFolder.localModifier().insert(QStringLiteral("Z/foo")); + fakeFolder.remoteModifier().insert(QStringLiteral("Z"), 63_B); // 2) local file becomes a dir; remote file changes - fakeFolder.localModifier().remove("A/a1"); - fakeFolder.localModifier().mkdir("A/a1"); - fakeFolder.localModifier().insert("A/a1/bar"); - fakeFolder.remoteModifier().appendByte("A/a1"); + fakeFolder.localModifier().remove(QStringLiteral("A/a1")); + fakeFolder.localModifier().mkdir(QStringLiteral("A/a1")); + fakeFolder.localModifier().insert(QStringLiteral("A/a1/bar")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); // 3) local dir gets a new file; remote dir becomes a file - fakeFolder.localModifier().insert("B/zzz"); - fakeFolder.remoteModifier().remove("B"); - fakeFolder.remoteModifier().insert("B", 31); + fakeFolder.localModifier().insert(QStringLiteral("B/zzz")); + fakeFolder.remoteModifier().remove(QStringLiteral("B")); + fakeFolder.remoteModifier().insert(QStringLiteral("B"), 31_B); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); auto conflicts = findConflicts(fakeFolder.currentLocalState()); - conflicts += findConflicts(fakeFolder.currentLocalState().children["A"]); + conflicts += findConflicts(fakeFolder.currentLocalState().children[QStringLiteral("A")]); QCOMPARE(conflicts.size(), 3); std::sort(conflicts.begin(), conflicts.end()); - auto conflictRecords = fakeFolder.syncJournal().conflictRecordPaths(); + auto conflictRecords = fakeFolder.syncJournal()->conflictRecordPaths(); QCOMPARE(conflictRecords.size(), 3); std::sort(conflictRecords.begin(), conflictRecords.end()); // 1) - QVERIFY(itemConflict(completeSpy, "Z")); - QCOMPARE(fakeFolder.currentLocalState().find("Z")->size, 63); - QVERIFY(conflicts[2].contains("Z")); + QVERIFY(itemConflict(completeSpy, QStringLiteral("Z"))); + QCOMPARE(fakeFolder.currentLocalState().find(QStringLiteral("Z"))->contentSize, 63); + QVERIFY(conflicts[2].contains(QStringLiteral("Z"))); QCOMPARE(conflicts[2].toUtf8(), conflictRecords[2]); QVERIFY(QFileInfo(fakeFolder.localPath() + conflicts[2]).isDir()); - QVERIFY(QFile::exists(fakeFolder.localPath() + conflicts[2] + "/foo")); + QVERIFY(QFile::exists(fakeFolder.localPath() + conflicts[2] + QStringLiteral("/foo"))); // 2) - QVERIFY(itemConflict(completeSpy, "A/a1")); - QCOMPARE(fakeFolder.currentLocalState().find("A/a1")->size, 5); - QVERIFY(conflicts[0].contains("A/a1")); + QVERIFY(itemConflict(completeSpy, QStringLiteral("A/a1"))); + QCOMPARE(fakeFolder.currentLocalState().find(QStringLiteral("A/a1"))->contentSize, 5); + QVERIFY(conflicts[0].contains(QStringLiteral("A/a1"))); QCOMPARE(conflicts[0].toUtf8(), conflictRecords[0]); QVERIFY(QFileInfo(fakeFolder.localPath() + conflicts[0]).isDir()); - QVERIFY(QFile::exists(fakeFolder.localPath() + conflicts[0] + "/bar")); + QVERIFY(QFile::exists(fakeFolder.localPath() + conflicts[0] + QStringLiteral("/bar"))); // 3) - QVERIFY(itemConflict(completeSpy, "B")); - QCOMPARE(fakeFolder.currentLocalState().find("B")->size, 31); - QVERIFY(conflicts[1].contains("B")); + QVERIFY(itemConflict(completeSpy, QStringLiteral("B"))); + if (filesAreDehydrated) { + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(fakeFolder.localPath() + QStringLiteral("B"))); + } else { + QCOMPARE(fakeFolder.currentLocalState().find(QStringLiteral("B"))->contentSize, 31); + } + QVERIFY(conflicts[1].contains(QStringLiteral("B"))); QCOMPARE(conflicts[1].toUtf8(), conflictRecords[1]); QVERIFY(QFileInfo(fakeFolder.localPath() + conflicts[1]).isDir()); - QVERIFY(QFile::exists(fakeFolder.localPath() + conflicts[1] + "/zzz")); - - // The contents of the conflict directories will only be uploaded after - // another sync. - QVERIFY(fakeFolder.syncEngine().isAnotherSyncNeeded() == ImmediateFollowUp); - cleanup(); - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(itemSuccessful(completeSpy, conflicts[0], CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemSuccessful(completeSpy, conflicts[0] + "/bar", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemSuccessful(completeSpy, conflicts[1], CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemSuccessful(completeSpy, conflicts[1] + "/zzz", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemSuccessful(completeSpy, conflicts[2], CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemSuccessful(completeSpy, conflicts[2] + "/foo", CSYNC_INSTRUCTION_NEW)); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QVERIFY(QFile::exists(fakeFolder.localPath() + conflicts[1] + QStringLiteral("/zzz"))); } void testLocalFileRemoteDirConflict() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.syncEngine().account()->setCapabilities({ { "uploadConflictFiles", true } }); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); // 1) a NEW/NEW conflict - fakeFolder.remoteModifier().mkdir("Z"); - fakeFolder.remoteModifier().mkdir("Z/subdir"); - fakeFolder.remoteModifier().insert("Z/foo"); - fakeFolder.localModifier().insert("Z"); + fakeFolder.remoteModifier().mkdir(QStringLiteral("Z")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("Z/subdir")); + fakeFolder.remoteModifier().insert(QStringLiteral("Z/foo")); + fakeFolder.localModifier().insert(QStringLiteral("Z")); // 2) local dir becomes file: remote dir adds file - fakeFolder.localModifier().remove("A"); - fakeFolder.localModifier().insert("A", 63); - fakeFolder.remoteModifier().insert("A/bar"); + fakeFolder.localModifier().remove(QStringLiteral("A")); + fakeFolder.localModifier().insert(QStringLiteral("A"), 63_B); + fakeFolder.remoteModifier().insert(QStringLiteral("A/bar")); // 3) local file changes; remote file becomes dir - fakeFolder.localModifier().appendByte("B/b1"); - fakeFolder.remoteModifier().remove("B/b1"); - fakeFolder.remoteModifier().mkdir("B/b1"); - fakeFolder.remoteModifier().insert("B/b1/zzz"); - - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.localModifier().appendByte(QStringLiteral("B/b1")); + fakeFolder.remoteModifier().remove(QStringLiteral("B/b1")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("B/b1")); + fakeFolder.remoteModifier().insert(QStringLiteral("B/b1/zzz")); + + if (filesAreDehydrated) { + // the dehydrating the placeholder failed as the metadata is out of sync + QSignalSpy spy(fakeFolder.vfs(), &Vfs::needSync); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(spy.count() == 1); + QVERIFY(fakeFolder.syncOnce()); + // reapply change and try again + fakeFolder.localModifier().appendByte(QStringLiteral("B/b1")); + // writing to a file fails! + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); + // TODO: check error type + return; + } + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); auto conflicts = findConflicts(fakeFolder.currentLocalState()); - conflicts += findConflicts(fakeFolder.currentLocalState().children["B"]); + conflicts += findConflicts(fakeFolder.currentLocalState().children[QStringLiteral("B")]); QCOMPARE(conflicts.size(), 3); std::sort(conflicts.begin(), conflicts.end()); - auto conflictRecords = fakeFolder.syncJournal().conflictRecordPaths(); + auto conflictRecords = fakeFolder.syncJournal()->conflictRecordPaths(); QCOMPARE(conflictRecords.size(), 3); std::sort(conflictRecords.begin(), conflictRecords.end()); // 1) - QVERIFY(itemConflict(completeSpy, "Z")); - QVERIFY(conflicts[2].contains("Z")); + QVERIFY(itemConflict(completeSpy, QStringLiteral("Z"))); + QVERIFY(conflicts[2].contains(QStringLiteral("Z"))); QCOMPARE(conflicts[2].toUtf8(), conflictRecords[2]); // 2) - QVERIFY(itemConflict(completeSpy, "A")); - QVERIFY(conflicts[0].contains("A")); + QVERIFY(itemConflict(completeSpy, QStringLiteral("A"))); + QVERIFY(conflicts[0].contains(QStringLiteral("A"))); QCOMPARE(conflicts[0].toUtf8(), conflictRecords[0]); // 3) - QVERIFY(itemConflict(completeSpy, "B/b1")); - QVERIFY(conflicts[1].contains("B/b1")); + QVERIFY(itemConflict(completeSpy, QStringLiteral("B/b1"))); + QVERIFY(conflicts[1].contains(QStringLiteral("B/b1"))); QCOMPARE(conflicts[1].toUtf8(), conflictRecords[1]); - - // Also verifies that conflicts were uploaded - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void testTypeConflictWithMove() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); // the remote becomes a file, but a file inside the dir has moved away! - fakeFolder.remoteModifier().remove("A"); - fakeFolder.remoteModifier().insert("A"); - fakeFolder.localModifier().rename("A/a1", "a1"); + fakeFolder.remoteModifier().remove(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A")); + fakeFolder.localModifier().rename(QStringLiteral("A/a1"), QStringLiteral("a1")); // same, but with a new file inside the dir locally - fakeFolder.remoteModifier().remove("B"); - fakeFolder.remoteModifier().insert("B"); - fakeFolder.localModifier().rename("B/b1", "b1"); - fakeFolder.localModifier().insert("B/new"); + fakeFolder.remoteModifier().remove(QStringLiteral("B")); + fakeFolder.remoteModifier().insert(QStringLiteral("B")); + fakeFolder.localModifier().rename(QStringLiteral("B/b1"), QStringLiteral("b1")); + fakeFolder.localModifier().insert(QStringLiteral("B/new")); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(itemSuccessful(completeSpy, "A", CSYNC_INSTRUCTION_TYPE_CHANGE)); - QVERIFY(itemConflict(completeSpy, "B")); + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("A"), CSYNC_INSTRUCTION_TYPE_CHANGE)); + QVERIFY(itemConflict(completeSpy, QStringLiteral("B"))); auto conflicts = findConflicts(fakeFolder.currentLocalState()); std::sort(conflicts.begin(), conflicts.end()); QVERIFY(conflicts.size() == 2); - QVERIFY(conflicts[0].contains("A (conflicted copy")); - QVERIFY(conflicts[1].contains("B (conflicted copy")); - for (const auto &conflict : qAsConst(conflicts)) + QVERIFY(conflicts[0].contains(QStringLiteral("A (conflicted copy"))); + QVERIFY(conflicts[1].contains(QStringLiteral("B (conflicted copy"))); + for (const auto &conflict : std::as_const(conflicts)) QDir(fakeFolder.localPath() + conflict).removeRecursively(); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); @@ -564,65 +448,95 @@ private slots: void testTypeChange() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); // dir becomes file - fakeFolder.remoteModifier().remove("A"); - fakeFolder.remoteModifier().insert("A"); - fakeFolder.localModifier().remove("B"); - fakeFolder.localModifier().insert("B"); + fakeFolder.remoteModifier().remove(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A")); + fakeFolder.localModifier().remove(QStringLiteral("B")); + fakeFolder.localModifier().insert(QStringLiteral("B")); // file becomes dir - fakeFolder.remoteModifier().remove("C/c1"); - fakeFolder.remoteModifier().mkdir("C/c1"); - fakeFolder.remoteModifier().insert("C/c1/foo"); - fakeFolder.localModifier().remove("C/c2"); - fakeFolder.localModifier().mkdir("C/c2"); - fakeFolder.localModifier().insert("C/c2/bar"); + fakeFolder.remoteModifier().remove(QStringLiteral("C/c1")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("C/c1")); + fakeFolder.remoteModifier().insert(QStringLiteral("C/c1/foo")); + fakeFolder.localModifier().remove(QStringLiteral("C/c2")); + fakeFolder.localModifier().mkdir(QStringLiteral("C/c2")); + fakeFolder.localModifier().insert(QStringLiteral("C/c2/bar")); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(itemSuccessful(completeSpy, "A", CSYNC_INSTRUCTION_TYPE_CHANGE)); - QVERIFY(itemSuccessful(completeSpy, "B", CSYNC_INSTRUCTION_TYPE_CHANGE)); - QVERIFY(itemSuccessful(completeSpy, "C/c1", CSYNC_INSTRUCTION_TYPE_CHANGE)); - QVERIFY(itemSuccessful(completeSpy, "C/c2", CSYNC_INSTRUCTION_TYPE_CHANGE)); + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("A"), CSYNC_INSTRUCTION_TYPE_CHANGE)); + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("B"), CSYNC_INSTRUCTION_TYPE_CHANGE)); + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("C/c1"), CSYNC_INSTRUCTION_TYPE_CHANGE)); + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("C/c2"), CSYNC_INSTRUCTION_TYPE_CHANGE)); // A becomes a conflict because we don't delete folders with files // inside of them! const auto &conflicts = findConflicts(fakeFolder.currentLocalState()); QVERIFY(conflicts.size() == 1); - QVERIFY(conflicts[0].contains("A (conflicted copy")); - for (const auto &conflict : conflicts) + QVERIFY(conflicts[0].contains(QStringLiteral("A (conflicted copy"))); + for (const auto &conflict : conflicts) { QDir(fakeFolder.localPath() + conflict).removeRecursively(); + } + + QCOMPARE(fakeFolder.syncEngine()->isAnotherSyncNeeded(), true); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } + - QVERIFY(fakeFolder.syncEngine().isAnotherSyncNeeded() == ImmediateFollowUp); - QVERIFY(fakeFolder.syncOnce()); + void testTypeChangeEmptyFolderToFile() + { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder({}, vfsMode, filesAreDehydrated); + + fakeFolder.remoteModifier().mkdir(QStringLiteral("TestFolder")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + ItemCompletedSpy completeSpy(fakeFolder); + fakeFolder.remoteModifier().remove(QStringLiteral("TestFolder")); + fakeFolder.remoteModifier().insert(QStringLiteral("TestFolder")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + // TestFolder should now be a file, if we are using placeholders, this file must not be hydrated + if (filesAreDehydrated) { + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(fakeFolder.localPath() + QStringLiteral("TestFolder"))); + } + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("TestFolder"), CSYNC_INSTRUCTION_TYPE_CHANGE)); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } // Test what happens if we remove entries both on the server, and locally void testRemoveRemove() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.remoteModifier().remove("A"); - fakeFolder.localModifier().remove("A"); - fakeFolder.remoteModifier().remove("B/b1"); - fakeFolder.localModifier().remove("B/b1"); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - auto expectedState = fakeFolder.currentLocalState(); + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + fakeFolder.remoteModifier().remove(QStringLiteral("A")); + fakeFolder.localModifier().remove(QStringLiteral("A")); + fakeFolder.remoteModifier().remove(QStringLiteral("B/b1")); + fakeFolder.localModifier().remove(QStringLiteral("B/b1")); + + auto expectedState = fakeFolder.currentRemoteState(); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), expectedState); QCOMPARE(fakeFolder.currentRemoteState(), expectedState); - QVERIFY(dbRecord(fakeFolder, "B/b2").isValid()); + QVERIFY(dbRecord(fakeFolder, QStringLiteral("B/b2")).isValid()); - QVERIFY(!dbRecord(fakeFolder, "B/b1").isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/a1").isValid()); - QVERIFY(!dbRecord(fakeFolder, "A").isValid()); + QVERIFY(!dbRecord(fakeFolder, QStringLiteral("B/b1")).isValid()); + QVERIFY(!dbRecord(fakeFolder, QStringLiteral("A/a1")).isValid()); + QVERIFY(!dbRecord(fakeFolder, QStringLiteral("A")).isValid()); } }; diff --git a/test/testsyncdelete.cpp b/test/testsyncdelete.cpp index 780b5e684bb..3258c119153 100644 --- a/test/testsyncdelete.cpp +++ b/test/testsyncdelete.cpp @@ -15,46 +15,68 @@ class TestSyncDelete : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } void testDeleteDirectoryWithNewFile() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); // Remove a directory on the server with new files on the client - fakeFolder.remoteModifier().remove("A"); - fakeFolder.localModifier().insert("A/hello.txt"); + fakeFolder.remoteModifier().remove(QStringLiteral("A")); + fakeFolder.localModifier().insert(QStringLiteral("A/hello.txt")); // Symetry - fakeFolder.localModifier().remove("B"); - fakeFolder.remoteModifier().insert("B/hello.txt"); + fakeFolder.localModifier().remove(QStringLiteral("B")); + fakeFolder.remoteModifier().insert(QStringLiteral("B/hello.txt")); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // A/a1 must be gone because the directory was removed on the server, but hello.txt must be there - QVERIFY(!fakeFolder.currentRemoteState().find("A/a1")); - QVERIFY(fakeFolder.currentRemoteState().find("A/hello.txt")); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("A/a1"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("A/hello.txt"))); // Symetry - QVERIFY(!fakeFolder.currentRemoteState().find("B/b1")); - QVERIFY(fakeFolder.currentRemoteState().find("B/hello.txt")); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("B/b1"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("B/hello.txt"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void issue1329() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - fakeFolder.localModifier().remove("B"); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.localModifier().remove(QStringLiteral("B")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Add a directory that was just removed in the previous sync: - fakeFolder.localModifier().mkdir("B"); - fakeFolder.localModifier().insert("B/b1"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentRemoteState().find("B/b1")); + fakeFolder.localModifier().mkdir(QStringLiteral("B")); + fakeFolder.localModifier().insert(QStringLiteral("B/b1")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("B/b1"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } }; diff --git a/test/testsyncengine.cpp b/test/testsyncengine.cpp index 811c635cc9f..e60cbd0e04e 100644 --- a/test/testsyncengine.cpp +++ b/test/testsyncengine.cpp @@ -5,17 +5,21 @@ * */ -#include -#include "testutils/syncenginetestutils.h" #include +#include "testutils/syncenginetestutils.h" +#include "testutils/testutils.h" + +#include + using namespace std::chrono_literals; +using namespace OCC::FileSystem::SizeLiterals; using namespace OCC; bool itemDidComplete(const ItemCompletedSpy &spy, const QString &path) { if (auto item = spy.findItem(path)) { - return item->_instruction != CSYNC_INSTRUCTION_NONE && item->_instruction != CSYNC_INSTRUCTION_UPDATE_METADATA; + return item->instruction() & ~(CSYNC_INSTRUCTION_NONE | CSYNC_INSTRUCTION_UPDATE_METADATA); } return false; } @@ -23,7 +27,7 @@ bool itemDidComplete(const ItemCompletedSpy &spy, const QString &path) bool itemInstruction(const ItemCompletedSpy &spy, const QString &path, const SyncInstructions instr) { auto item = spy.findItem(path); - return item->_instruction == instr; + return item->instruction() == instr; } bool itemDidCompleteSuccessfully(const ItemCompletedSpy &spy, const QString &path) @@ -38,146 +42,186 @@ class TestSyncEngine : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } + void testFileDownload() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); - fakeFolder.remoteModifier().insert("A/a0"); - fakeFolder.syncOnce(); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a0")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a0")); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("A/a0"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void testFileUpload() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); - fakeFolder.localModifier().insert("A/a0"); - fakeFolder.syncOnce(); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a0")); + fakeFolder.localModifier().insert(QStringLiteral("A/a0")); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("A/a0"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void testDirDownload() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); - fakeFolder.remoteModifier().mkdir("Y"); - fakeFolder.remoteModifier().mkdir("Z"); - fakeFolder.remoteModifier().insert("Z/d0"); - fakeFolder.syncOnce(); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Y")); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z")); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z/d0")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("Y")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("Z")); + fakeFolder.remoteModifier().insert(QStringLiteral("Z/d0")); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("Y"))); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("Z"))); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("Z/d0"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void testDirUpload() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); - fakeFolder.localModifier().mkdir("Y"); - fakeFolder.localModifier().mkdir("Z"); - fakeFolder.localModifier().insert("Z/d0"); - fakeFolder.syncOnce(); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Y")); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z")); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "Z/d0")); + fakeFolder.localModifier().mkdir(QStringLiteral("Y")); + fakeFolder.localModifier().mkdir(QStringLiteral("Z")); + fakeFolder.localModifier().insert(QStringLiteral("Z/d0")); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("Y"))); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("Z"))); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("Z/d0"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void testLocalDelete() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); - fakeFolder.remoteModifier().remove("A/a1"); - fakeFolder.syncOnce(); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a1")); + fakeFolder.remoteModifier().remove(QStringLiteral("A/a1")); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("A/a1"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void testRemoteDelete() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); - fakeFolder.localModifier().remove("A/a1"); - fakeFolder.syncOnce(); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "A/a1")); + fakeFolder.localModifier().remove(QStringLiteral("A/a1")); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("A/a1"))); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void testEmlLocalChecksum() { - FakeFolder fakeFolder{FileInfo{}}; - fakeFolder.localModifier().insert("a1.eml", 64, 'A'); - fakeFolder.localModifier().insert("a2.eml", 64, 'A'); - fakeFolder.localModifier().insert("a3.eml", 64, 'A'); - fakeFolder.localModifier().insert("b3.txt", 64, 'A'); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo {}, vfsMode, filesAreDehydrated); + fakeFolder.account()->setCapabilities({fakeFolder.account()->url(), TestUtils::testCapabilities(CheckSums::Algorithm::SHA1)}); + fakeFolder.localModifier().insert(QStringLiteral("a1.eml"), 64_B, 'A'); + fakeFolder.localModifier().insert(QStringLiteral("a2.eml"), 64_B, 'A'); + fakeFolder.localModifier().insert(QStringLiteral("a3.eml"), 64_B, 'A'); + fakeFolder.localModifier().insert(QStringLiteral("b3.txt"), 64_B, 'A'); // Upload and calculate the checksums - // fakeFolder.syncOnce(); - fakeFolder.syncOnce(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - auto getDbChecksum = [&](QString path) { + auto getDbChecksum = [&](const QString &path) { SyncJournalFileRecord record; - fakeFolder.syncJournal().getFileRecord(path, &record); + fakeFolder.syncJournal()->getFileRecord(path, &record); return record._checksumHeader; }; // printf 'A%.0s' {1..64} | sha1sum - QByteArray referenceChecksum("SHA1:30b86e44e6001403827a62c58b08893e77cf121f"); - QCOMPARE(getDbChecksum("a1.eml"), referenceChecksum); - QCOMPARE(getDbChecksum("a2.eml"), referenceChecksum); - QCOMPARE(getDbChecksum("a3.eml"), referenceChecksum); - QCOMPARE(getDbChecksum("b3.txt"), referenceChecksum); + QCOMPARE(getDbChecksum(QStringLiteral("a1.eml")), referenceChecksum); + QCOMPARE(getDbChecksum(QStringLiteral("a2.eml")), referenceChecksum); + QCOMPARE(getDbChecksum(QStringLiteral("a3.eml")), referenceChecksum); + QCOMPARE(getDbChecksum(QStringLiteral("b3.txt")), referenceChecksum); + + // Make sure that the lastModified time caused by the setContent calls below is actually different: + QThread::sleep(1); ItemCompletedSpy completeSpy(fakeFolder); // Touch the file without changing the content, shouldn't upload - fakeFolder.localModifier().setContents("a1.eml", 'A'); + fakeFolder.localModifier().setContents(QStringLiteral("a1.eml"), 64_B, 'A'); // Change the content/size - fakeFolder.localModifier().setContents("a2.eml", 'B'); - fakeFolder.localModifier().appendByte("a3.eml"); - fakeFolder.localModifier().appendByte("b3.txt"); - fakeFolder.syncOnce(); - - QCOMPARE(getDbChecksum("a1.eml"), referenceChecksum); - QCOMPARE(getDbChecksum("a2.eml"), QByteArray("SHA1:84951fc23a4dafd10020ac349da1f5530fa65949")); - QCOMPARE(getDbChecksum("a3.eml"), QByteArray("SHA1:826b7e7a7af8a529ae1c7443c23bf185c0ad440c")); - QCOMPARE(getDbChecksum("b3.eml"), getDbChecksum("a3.txt")); - - QVERIFY(!itemDidComplete(completeSpy, "a1.eml")); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a2.eml")); - QVERIFY(itemDidCompleteSuccessfully(completeSpy, "a3.eml")); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + fakeFolder.localModifier().setContents(QStringLiteral("a2.eml"), 64_B, 'B'); + fakeFolder.localModifier().appendByte(QStringLiteral("a3.eml"), 'X'); + fakeFolder.localModifier().appendByte(QStringLiteral("b3.txt"), 'X'); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QCOMPARE(getDbChecksum(QStringLiteral("a1.eml")), referenceChecksum); + QCOMPARE(getDbChecksum(QStringLiteral("a2.eml")), QByteArray("SHA1:84951fc23a4dafd10020ac349da1f5530fa65949")); + QCOMPARE(getDbChecksum(QStringLiteral("a3.eml")), QByteArray("SHA1:c119308d57884896cd86a7050e449aaba24b1fee")); + QCOMPARE(getDbChecksum(QStringLiteral("b3.eml")), getDbChecksum(QStringLiteral("a3.txt"))); + + QVERIFY(!itemDidComplete(completeSpy, QStringLiteral("a1.eml"))); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("a2.eml"))); + QVERIFY(itemDidCompleteSuccessfully(completeSpy, QStringLiteral("a3.eml"))); + + // The local and remote state now differ: the local mtime for `a1.eml` is bigger (newer) than on the server, because + // the upload was skipped (same checksum). So first verify that this is the case: + QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("a1.eml"))->lastModified() + > fakeFolder.currentRemoteState().find(QStringLiteral("a1.eml"))->lastModified()); + // And then check if everything else actually is the same: + QVERIFY(fakeFolder.currentLocalState().equals(fakeFolder.currentRemoteState(), FileInfo::IgnoreLastModified)); } void testSelectiveSyncBug() { // issue owncloud/enterprise#1965: files from selective-sync ignored // folders are uploaded anyway is some circumstances. - FakeFolder fakeFolder{FileInfo{ QString(), { - FileInfo { QStringLiteral("parentFolder"), { - FileInfo{ QStringLiteral("subFolderA"), { - { QStringLiteral("fileA.txt"), 400 }, - { QStringLiteral("fileB.txt"), 400, 'o' }, - FileInfo { QStringLiteral("subsubFolder"), { - { QStringLiteral("fileC.txt"), 400 }, - { QStringLiteral("fileD.txt"), 400, 'o' } - }}, - FileInfo{ QStringLiteral("anotherFolder"), { - FileInfo { QStringLiteral("emptyFolder"), { } }, - FileInfo { QStringLiteral("subsubFolder"), { - { QStringLiteral("fileE.txt"), 400 }, - { QStringLiteral("fileF.txt"), 400, 'o' } - }} - }} - }}, - FileInfo{ QStringLiteral("subFolderB"), {} } - }} - }}}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + if (vfsMode == Vfs::WindowsCfApi) { + QSKIP("selective sync is not supported with winvfs"); + } + + FakeFolder fakeFolder(FileInfo { QString(), { FileInfo { QStringLiteral("parentFolder"), { FileInfo { QStringLiteral("subFolderA"), { { QStringLiteral("fileA.txt"), 400 }, { QStringLiteral("fileB.txt"), 400, 'o' }, FileInfo { QStringLiteral("subsubFolder"), { { QStringLiteral("fileC.txt"), 400 }, { QStringLiteral("fileD.txt"), 400, 'o' } } }, FileInfo { QStringLiteral("anotherFolder"), { FileInfo { QStringLiteral("emptyFolder"), {} }, FileInfo { QStringLiteral("subsubFolder"), { { QStringLiteral("fileE.txt"), 400 }, { QStringLiteral("fileF.txt"), 400, 'o' } } } } } } }, FileInfo { QStringLiteral("subFolderB"), {} } } } } }, vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); auto expectedServerState = fakeFolder.currentRemoteState(); // Remove subFolderA with selectiveSync: - fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, - {"parentFolder/subFolderA/"}); - fakeFolder.syncEngine().journal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/")); + fakeFolder.syncJournal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {QStringLiteral("parentFolder/subFolderA/")}); + fakeFolder.syncJournal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/")); auto getEtag = [&](const QByteArray &file) { SyncJournalFileRecord rec; - fakeFolder.syncJournal().getFileRecord(file, &rec); + fakeFolder.syncJournal()->getFileRecord(file, &rec); return rec._etag; }; QVERIFY(getEtag("parentFolder") == "_invalid_"); @@ -186,82 +230,110 @@ private slots: // But touch local file before the next sync, such that the local folder // can't be removed - fakeFolder.localModifier().setContents("parentFolder/subFolderA/fileB.txt", 'n'); - fakeFolder.localModifier().setContents("parentFolder/subFolderA/subsubFolder/fileD.txt", 'n'); - fakeFolder.localModifier().setContents("parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt", 'n'); + fakeFolder.localModifier().setContents(QStringLiteral("parentFolder/subFolderA/fileB.txt"), FileModifier::DefaultFileSize, 'n'); + fakeFolder.localModifier().setContents(QStringLiteral("parentFolder/subFolderA/subsubFolder/fileD.txt"), FileModifier::DefaultFileSize, 'n'); + fakeFolder.localModifier().setContents(QStringLiteral("parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt"), FileModifier::DefaultFileSize, 'n'); // Several follow-up syncs don't change the state at all, // in particular the remote state doesn't change and fileB.txt // isn't uploaded. for (int i = 0; i < 3; ++i) { - fakeFolder.syncOnce(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); { // Nothing changed on the server QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState); // The local state should still have subFolderA auto local = fakeFolder.currentLocalState(); - QVERIFY(local.find("parentFolder/subFolderA")); - QVERIFY(!local.find("parentFolder/subFolderA/fileA.txt")); - QVERIFY(local.find("parentFolder/subFolderA/fileB.txt")); - QVERIFY(!local.find("parentFolder/subFolderA/subsubFolder/fileC.txt")); - QVERIFY(local.find("parentFolder/subFolderA/subsubFolder/fileD.txt")); - QVERIFY(!local.find("parentFolder/subFolderA/anotherFolder/subsubFolder/fileE.txt")); - QVERIFY(local.find("parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt")); - QVERIFY(!local.find("parentFolder/subFolderA/anotherFolder/emptyFolder")); - QVERIFY(local.find("parentFolder/subFolderB")); + QVERIFY(local.find(QStringLiteral("parentFolder/subFolderA"))); + QVERIFY(!local.find(QStringLiteral("parentFolder/subFolderA/fileA.txt"))); + QVERIFY(local.find(QStringLiteral("parentFolder/subFolderA/fileB.txt"))); + QVERIFY(!local.find(QStringLiteral("parentFolder/subFolderA/subsubFolder/fileC.txt"))); + QVERIFY(local.find(QStringLiteral("parentFolder/subFolderA/subsubFolder/fileD.txt"))); + QVERIFY(!local.find(QStringLiteral("parentFolder/subFolderA/anotherFolder/subsubFolder/fileE.txt"))); + QVERIFY(local.find(QStringLiteral("parentFolder/subFolderA/anotherFolder/subsubFolder/fileF.txt"))); + QVERIFY(!local.find(QStringLiteral("parentFolder/subFolderA/anotherFolder/emptyFolder"))); + QVERIFY(local.find(QStringLiteral("parentFolder/subFolderB"))); } } } void abortAfterFailedMkdir() { - FakeFolder fakeFolder{FileInfo{}}; - QSignalSpy finishedSpy(&fakeFolder.syncEngine(), &SyncEngine::finished); - fakeFolder.serverErrorPaths().append("NewFolder"); - fakeFolder.localModifier().mkdir("NewFolder"); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo {}, vfsMode, filesAreDehydrated); + QSignalSpy finishedSpy(fakeFolder.syncEngine(), &SyncEngine::finished); + fakeFolder.serverErrorPaths().append(QStringLiteral("NewFolder")); + fakeFolder.localModifier().mkdir(QStringLiteral("NewFolder")); // This should be aborted and would otherwise fail in FileInfo::create. - fakeFolder.localModifier().insert("NewFolder/NewFile"); - fakeFolder.syncOnce(); + fakeFolder.localModifier().insert(QStringLiteral("NewFolder/NewFile"), FileModifier::DefaultFileSize, 'n'); + + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(finishedSpy.size(), 1); QCOMPARE(finishedSpy.first().first().toBool(), false); } - /** Verify that an incompletely propagated directory doesn't have the server's - * etag stored in the database yet. */ - void testDirEtagAfterIncompleteSync() { - FakeFolder fakeFolder{FileInfo{}}; - QSignalSpy finishedSpy(&fakeFolder.syncEngine(), &SyncEngine::finished); - fakeFolder.serverErrorPaths().append("NewFolder/foo"); - fakeFolder.remoteModifier().mkdir("NewFolder"); - fakeFolder.remoteModifier().insert("NewFolder/foo"); - QVERIFY(!fakeFolder.syncOnce()); + /** + * Verify that an incompletely propagated directory doesn't have the server's + * etag stored in the database yet. + */ + void testDirEtagAfterIncompleteSync() + { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo {}, vfsMode, filesAreDehydrated); + QSignalSpy finishedSpy(fakeFolder.syncEngine(), &SyncEngine::finished); + fakeFolder.serverErrorPaths().append(QStringLiteral("NewFolder/foo")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("NewFolder")); + fakeFolder.remoteModifier().insert(QStringLiteral("NewFolder/foo")); + bool syncSuccess = fakeFolder.applyLocalModificationsAndSync(); + if (filesAreDehydrated) { + QVERIFY(syncSuccess); + } else { + QVERIFY(!syncSuccess); + } SyncJournalFileRecord rec; - fakeFolder.syncJournal().getFileRecord(QByteArrayLiteral("NewFolder"), &rec); + fakeFolder.syncJournal()->getFileRecord(QByteArrayLiteral("NewFolder"), &rec); QVERIFY(rec.isValid()); - QCOMPARE(rec._etag, QByteArrayLiteral("_invalid_")); + if (filesAreDehydrated) { + // No error, failure occurs only with a GET, so etag should be valid (i.e. NOT invalid): + QVERIFY(rec._etag != QByteArrayLiteral("_invalid_")); + } else { + // Download failed, check for invalid etag: + QCOMPARE(rec._etag, QByteArrayLiteral("_invalid_")); + } QVERIFY(!rec._fileId.isEmpty()); } void testDirDownloadWithError() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + if (filesAreDehydrated) { + QSKIP("Nothing gets downloaded, so no error, so skip this test"); + } + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); ItemCompletedSpy completeSpy(fakeFolder); - fakeFolder.remoteModifier().mkdir("Y"); - fakeFolder.remoteModifier().mkdir("Y/Z"); - fakeFolder.remoteModifier().insert("Y/Z/d0"); - fakeFolder.remoteModifier().insert("Y/Z/d1"); - fakeFolder.remoteModifier().insert("Y/Z/d2"); - fakeFolder.remoteModifier().insert("Y/Z/d3"); - fakeFolder.remoteModifier().insert("Y/Z/d4"); - fakeFolder.remoteModifier().insert("Y/Z/d5"); - fakeFolder.remoteModifier().insert("Y/Z/d6"); - fakeFolder.remoteModifier().insert("Y/Z/d7"); - fakeFolder.remoteModifier().insert("Y/Z/d8"); - fakeFolder.remoteModifier().insert("Y/Z/d9"); - fakeFolder.serverErrorPaths().append("Y/Z/d2", 503); - fakeFolder.serverErrorPaths().append("Y/Z/d3", 503); - QVERIFY(!fakeFolder.syncOnce()); + fakeFolder.remoteModifier().mkdir(QStringLiteral("Y")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("Y/Z")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d0")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d1")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d2")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d3")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d4")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d5")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d6")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d7")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d8")); + fakeFolder.remoteModifier().insert(QStringLiteral("Y/Z/d9")); + fakeFolder.serverErrorPaths().append(QStringLiteral("Y/Z/d2"), 503); + fakeFolder.serverErrorPaths().append(QStringLiteral("Y/Z/d3"), 503); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QCoreApplication::processEvents(); // should not crash QSet seen; @@ -270,9 +342,9 @@ private slots: qDebug() << item->_file << item->isDirectory() << item->_status; QVERIFY(!seen.contains(item->_file)); // signal only sent once per item seen.insert(item->_file); - if (item->_file == "Y/Z/d2") { + if (item->_file == QLatin1String("Y/Z/d2")) { QVERIFY(item->_status == SyncFileItem::NormalError); - } else if (item->_file == "Y/Z/d3") { + } else if (item->_file == QLatin1String("Y/Z/d3")) { QVERIFY(item->_status != SyncFileItem::Success); } else if (!item->isDirectory()) { QVERIFY(item->_status == SyncFileItem::Success); @@ -284,57 +356,63 @@ private slots: { QTest::addColumn("sameMtime"); QTest::addColumn("checksums"); - - QTest::addColumn("expectedGET"); + QTest::addColumn("expectedGEThydrated"); + QTest::addColumn("expectedGETdehydrated"); QTest::newRow("Same mtime, but no server checksum -> ignored in reconcile") << true << QByteArray() - << 0; + << 0 // hydrated + << 1; // dehydrated QTest::newRow("Same mtime, weak server checksum differ -> downloaded") << true << QByteArray("Adler32:bad") - << 1; + << 1 // hydrated + << 1; // dehydrated; QTest::newRow("Same mtime, matching weak checksum -> skipped") << true << QByteArray("Adler32:2a2010d") - << 0; + << 0 // hydrated + << 1; // dehydrated; QTest::newRow("Same mtime, strong server checksum differ -> downloaded") << true << QByteArray("SHA1:bad") - << 1; + << 1 // hydrated + << 1; // dehydrated; QTest::newRow("Same mtime, matching strong checksum -> skipped") << true << QByteArray("SHA1:56900fb1d337cf7237ff766276b9c1e8ce507427") - << 0; - + << 0 // hydrated + << 1; // dehydrated; QTest::newRow("mtime changed, but no server checksum -> download") << false << QByteArray() - << 1; + << 1 // hydrated + << 1; // dehydrated; QTest::newRow("mtime changed, weak checksum match -> download anyway") << false << QByteArray("Adler32:2a2010d") - << 1; + << 1 // hydrated + << 1; // dehydrated; QTest::newRow("mtime changed, strong checksum match -> skip") << false << QByteArray("SHA1:56900fb1d337cf7237ff766276b9c1e8ce507427") - << 0; + << 0 // hydrated + << 1; // dehydrated; } void testFakeConflict() { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); QFETCH(bool, sameMtime); QFETCH(QByteArray, checksums); - QFETCH(int, expectedGET); + QFETCH(int, expectedGEThydrated); + QFETCH(int, expectedGETdehydrated); - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + int expectedGET = filesAreDehydrated ? expectedGETdehydrated : expectedGEThydrated; - int nGET = 0; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) { - if (op == QNetworkAccessManager::GetOperation) - ++nGET; - return nullptr; - }); + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + OperationCounter counter(fakeFolder); // For directly editing the remote checksum FileInfo &remoteInfo = fakeFolder.remoteModifier(); @@ -343,25 +421,30 @@ private slots: auto mtime = QDateTime::currentDateTimeUtc().addDays(-4); mtime.setMSecsSinceEpoch(mtime.toMSecsSinceEpoch() / 1000 * 1000); - fakeFolder.localModifier().setContents("A/a1", 'C'); - fakeFolder.localModifier().setModTime("A/a1", mtime); - fakeFolder.remoteModifier().setContents("A/a1", 'C'); - if (!sameMtime) + const auto a1size = fakeFolder.currentRemoteState().find(QStringLiteral("A/a1"))->contentSize; + + // In the dehydrated case, executing this `setContents` will cause a hydration of the file, so there will always be 1 GET request. + fakeFolder.localModifier().setContents(QStringLiteral("A/a1"), a1size, 'C'); + + fakeFolder.localModifier().setModTime(QStringLiteral("A/a1"), mtime); + fakeFolder.remoteModifier().setContents(QStringLiteral("A/a1"), a1size, 'C'); + if (!sameMtime) { mtime = mtime.addDays(1); - fakeFolder.remoteModifier().setModTime("A/a1", mtime); - remoteInfo.find("A/a1")->checksums = checksums; - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(nGET, expectedGET); + } + fakeFolder.remoteModifier().setModTime(QStringLiteral("A/a1"), mtime); + remoteInfo.find(QStringLiteral("A/a1"))->checksums = checksums; + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(counter.nGET, expectedGET); // check that mtime in journal and filesystem agree - QString a1path = fakeFolder.localPath() + "A/a1"; + QString a1path = fakeFolder.localPath() + QStringLiteral("A/a1"); SyncJournalFileRecord a1record; - fakeFolder.syncJournal().getFileRecord(QByteArray("A/a1"), &a1record); + fakeFolder.syncJournal()->getFileRecord(QByteArray("A/a1"), &a1record); QCOMPARE(a1record._modtime, (qint64)FileSystem::getModTime(a1path)); // Extra sync reads from db, no difference - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(nGET, expectedGET); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(counter.nGET, expectedGET); } /** @@ -370,6 +453,13 @@ private slots: */ void testSyncFileItemProperties() { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + if (vfsMode == Vfs::WindowsCfApi && filesAreDehydrated) { + QSKIP("This test expects files to exist in the sync folder before a sync."); + } + auto initialMtime = QDateTime::currentDateTimeUtc().addDays(-7); auto changedMtime = QDateTime::currentDateTimeUtc().addDays(-4); auto changedMtime2 = QDateTime::currentDateTimeUtc().addDays(-3); @@ -381,40 +471,39 @@ private slots: // Ensure the initial mtimes are as expected auto initialFileInfo = FileInfo::A12_B12_C12_S12(); - initialFileInfo.setModTime("A/a1", initialMtime); - initialFileInfo.setModTime("B/b1", initialMtime); - initialFileInfo.setModTime("C/c1", initialMtime); - - FakeFolder fakeFolder{ initialFileInfo }; + initialFileInfo.setModTime(QStringLiteral("A/a1"), initialMtime); + initialFileInfo.setModTime(QStringLiteral("B/b1"), initialMtime); + initialFileInfo.setModTime(QStringLiteral("C/c1"), initialMtime); + FakeFolder fakeFolder(initialFileInfo, vfsMode, filesAreDehydrated); // upload a - fakeFolder.localModifier().appendByte("A/a1"); - fakeFolder.localModifier().setModTime("A/a1", changedMtime); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.localModifier().setModTime(QStringLiteral("A/a1"), changedMtime); // download b - fakeFolder.remoteModifier().appendByte("B/b1"); - fakeFolder.remoteModifier().setModTime("B/b1", changedMtime); + fakeFolder.remoteModifier().appendByte(QStringLiteral("B/b1")); + fakeFolder.remoteModifier().setModTime(QStringLiteral("B/b1"), changedMtime); // conflict c - fakeFolder.localModifier().appendByte("C/c1"); - fakeFolder.localModifier().appendByte("C/c1"); - fakeFolder.localModifier().setModTime("C/c1", changedMtime); - fakeFolder.remoteModifier().appendByte("C/c1"); - fakeFolder.remoteModifier().setModTime("C/c1", changedMtime2); + fakeFolder.localModifier().appendByte(QStringLiteral("C/c1")); + fakeFolder.localModifier().appendByte(QStringLiteral("C/c1")); + fakeFolder.localModifier().setModTime(QStringLiteral("C/c1"), changedMtime); + fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c1")); + fakeFolder.remoteModifier().setModTime(QStringLiteral("C/c1"), changedMtime2); - connect(&fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](SyncFileItemSet &items) { + connect(fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, [&](const SyncFileItemSet &items) { SyncFileItemPtr a1, b1, c1; for (auto &item : items) { - if (item->_file == "A/a1") + if (item->_file == QLatin1String("A/a1")) a1 = item; - if (item->_file == "B/b1") + if (item->_file == QLatin1String("B/b1")) b1 = item; - if (item->_file == "C/c1") + if (item->_file == QLatin1String("C/c1")) c1 = item; } // a1: should have local size and modtime QVERIFY(a1); - QCOMPARE(a1->_instruction, CSYNC_INSTRUCTION_SYNC); + QCOMPARE(a1->instruction(), CSYNC_INSTRUCTION_SYNC); QCOMPARE(a1->_direction, SyncFileItem::Up); QCOMPARE(a1->_size, qint64(5)); @@ -424,7 +513,7 @@ private slots: // b2: should have remote size and modtime QVERIFY(b1); - QCOMPARE(b1->_instruction, CSYNC_INSTRUCTION_SYNC); + QCOMPARE(b1->instruction(), CSYNC_INSTRUCTION_SYNC); QCOMPARE(b1->_direction, SyncFileItem::Down); QCOMPARE(b1->_size, qint64(17)); QCOMPARE(Utility::qDateTimeFromTime_t(b1->_modtime), changedMtime); @@ -433,7 +522,7 @@ private slots: // c1: conflicts are downloads, so remote size and modtime QVERIFY(c1); - QCOMPARE(c1->_instruction, CSYNC_INSTRUCTION_CONFLICT); + QCOMPARE(c1->instruction(), CSYNC_INSTRUCTION_CONFLICT); QCOMPARE(c1->_direction, SyncFileItem::None); QCOMPARE(c1->_size, qint64(25)); QCOMPARE(Utility::qDateTimeFromTime_t(c1->_modtime), changedMtime2); @@ -441,7 +530,7 @@ private slots: QCOMPARE(Utility::qDateTimeFromTime_t(c1->_previousModtime), changedMtime); }); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); } /** @@ -449,12 +538,15 @@ private slots: */ void testInsufficientRemoteStorage() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); // Disable parallel uploads - SyncOptions syncOptions; + SyncOptions syncOptions = fakeFolder.syncEngine()->syncOptions(); syncOptions._parallelNetworkJobs = 0; - fakeFolder.syncEngine().setSyncOptions(syncOptions); + fakeFolder.syncEngine()->setSyncOptions(syncOptions); // Produce an error based on upload size int remoteQuota = 1000; @@ -471,23 +563,23 @@ private slots: return nullptr; }); - fakeFolder.localModifier().insert("A/big", 800); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.localModifier().insert(QStringLiteral("A/big"), 800_B); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(nPUT, 1); QCOMPARE(n507, 0); nPUT = 0; - fakeFolder.localModifier().insert("A/big1", 500); // ok - fakeFolder.localModifier().insert("A/big2", 1200); // 507 (quota guess now 1199) - fakeFolder.localModifier().insert("A/big3", 1200); // skipped - fakeFolder.localModifier().insert("A/big4", 1500); // skipped - fakeFolder.localModifier().insert("A/big5", 1100); // 507 (quota guess now 1099) - fakeFolder.localModifier().insert("A/big6", 900); // ok (quota guess now 199) - fakeFolder.localModifier().insert("A/big7", 200); // skipped - fakeFolder.localModifier().insert("A/big8", 199); // ok (quota guess now 0) - - fakeFolder.localModifier().insert("B/big8", 1150); // 507 - QVERIFY(!fakeFolder.syncOnce()); + fakeFolder.localModifier().insert(QStringLiteral("A/big1"), 500_B); // ok + fakeFolder.localModifier().insert(QStringLiteral("A/big2"), 1200_B); // 507 (quota guess now 1199) + fakeFolder.localModifier().insert(QStringLiteral("A/big3"), 1200_B); // skipped + fakeFolder.localModifier().insert(QStringLiteral("A/big4"), 1500_B); // skipped + fakeFolder.localModifier().insert(QStringLiteral("A/big5"), 1100_B); // 507 (quota guess now 1099) + fakeFolder.localModifier().insert(QStringLiteral("A/big6"), 900_B); // ok (quota guess now 199) + fakeFolder.localModifier().insert(QStringLiteral("A/big7"), 200_B); // skipped + fakeFolder.localModifier().insert(QStringLiteral("A/big8"), 199_B); // ok (quota guess now 0) + + fakeFolder.localModifier().insert(QStringLiteral("B/big8"), 1150_B); // 507 + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(nPUT, 6); QCOMPARE(n507, 3); } @@ -495,7 +587,10 @@ private slots: // Checks whether downloads with bad checksums are accepted void testChecksumValidation() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); QObject parent; QByteArray checksumValue; @@ -514,244 +609,276 @@ private slots: }); // Basic case - fakeFolder.remoteModifier().create("A/a3", 16, 'A'); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.remoteModifier().create(QStringLiteral("A/a3"), 16, 'A'); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Bad OC-Checksum checksumValue = "SHA1:bad"; - fakeFolder.remoteModifier().create("A/a4", 16, 'A'); - QVERIFY(!fakeFolder.syncOnce()); + fakeFolder.remoteModifier().create(QStringLiteral("A/a4"), 16, 'A'); + bool syncSucceeded = fakeFolder.applyLocalModificationsAndSync(); + if (filesAreDehydrated) { + // In the dehydrated case, files are never downloaded, so checksums are not computed. Only placeholders are created. + QVERIFY(syncSucceeded); + } else { + // Any case where files are actually downloaded (no vfs, local files are hydrated, etc), the checksum calculation should fail. + QVERIFY(!syncSucceeded); + } // Good OC-Checksum checksumValue = "SHA1:19b1928d58a2030d08023f3d7054516dbc186f20"; // printf 'A%.0s' {1..16} | sha1sum - - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.syncJournal()->wipeErrorBlacklist(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); checksumValue = QByteArray(); // Bad Content-MD5 contentMd5Value = "bad"; - fakeFolder.remoteModifier().create("A/a5", 16, 'A'); - QVERIFY(!fakeFolder.syncOnce()); + fakeFolder.remoteModifier().create(QStringLiteral("A/a5"), 16, 'A'); + syncSucceeded = fakeFolder.applyLocalModificationsAndSync(); + if (filesAreDehydrated) { + // In the dehydrated case, files are never downloaded, so checksums are not computed. Only placeholders are created. + QVERIFY(syncSucceeded); + } else { + // Any case where files are actually downloaded (no vfs, local files are hydrated, etc), the checksum calculation should fail. + QVERIFY(!syncSucceeded); + } // Good Content-MD5 contentMd5Value = "d8a73157ce10cd94a91c2079fc9a92c8"; // printf 'A%.0s' {1..16} | md5sum - - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.syncJournal()->wipeErrorBlacklist(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Invalid OC-Checksum is ignored checksumValue = "garbage"; // contentMd5Value is still good - fakeFolder.remoteModifier().create("A/a6", 16, 'A'); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.remoteModifier().create(QStringLiteral("A/a6"), 16, 'A'); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); contentMd5Value = "bad"; - fakeFolder.remoteModifier().create("A/a7", 16, 'A'); - QVERIFY(!fakeFolder.syncOnce()); + fakeFolder.remoteModifier().create(QStringLiteral("A/a7"), 16, 'A'); + syncSucceeded = fakeFolder.applyLocalModificationsAndSync(); + if (filesAreDehydrated) { + // In the dehydrated case, files are never downloaded, so checksums are not computed. Only placeholders are created. + QVERIFY(syncSucceeded); + } else { + // Any case where files are actually downloaded (no vfs, local files are hydrated, etc), the checksum calculation should fail. + QVERIFY(!syncSucceeded); + } contentMd5Value.clear(); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.syncJournal()->wipeErrorBlacklist(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // OC-Checksum contains Unsupported checksums checksumValue = "Unsupported:XXXX SHA1:invalid Invalid:XxX"; - fakeFolder.remoteModifier().create("A/a8", 16, 'A'); - QVERIFY(!fakeFolder.syncOnce()); // Since the supported SHA1 checksum is invalid, no download + fakeFolder.remoteModifier().create(QStringLiteral("A/a8"), 16, 'A'); + syncSucceeded = fakeFolder.applyLocalModificationsAndSync(); + if (filesAreDehydrated) { + // In the dehydrated case, files are never downloaded, so checksums are not computed. Only placeholders are created. + QVERIFY(syncSucceeded); + } else { + // Since the supported SHA1 checksum is invalid, no download + QVERIFY(!syncSucceeded); + } checksumValue = "Unsupported:XXXX SHA1:19b1928d58a2030d08023f3d7054516dbc186f20 Invalid:XxX"; - QVERIFY(fakeFolder.syncOnce()); // The supported SHA1 checksum is valid now, so the file are downloaded + fakeFolder.syncJournal()->wipeErrorBlacklist(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // The supported SHA1 checksum is valid now, so the file are downloaded QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } // Tests the behavior of invalid filename detection void testInvalidFilenameRegex() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); -#ifndef Q_OS_WIN // We can't have local file with these character - // For current servers, no characters are forbidden - fakeFolder.syncEngine().account()->setServerVersion("10.0.0"); - fakeFolder.localModifier().insert("A/\\:?*\"<>|.txt"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); - // For legacy servers, some characters were forbidden by the client - fakeFolder.syncEngine().account()->setServerVersion("8.0.0"); - fakeFolder.localModifier().insert("B/\\:?*\"<>|.txt"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentRemoteState().find("B/\\:?*\"<>|.txt")); -#endif + if (!Utility::isWindows()) { // We can't have local file with these character + fakeFolder.localModifier().insert(QStringLiteral("A/\\:?*\"<>|.txt")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } // We can override that by setting the capability - fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "invalidFilenameRegex", "" } } } }); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Check that new servers also accept the capability - fakeFolder.syncEngine().account()->setServerVersion("10.0.0"); - fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ { "invalidFilenameRegex", "my[fgh]ile" } } } }); - fakeFolder.localModifier().insert("C/myfile.txt"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentRemoteState().find("C/myfile.txt")); + auto invalidFilenameRegexCapabilities = [](const QString ®ex) { + auto cap = TestUtils::testCapabilities(); + auto dav = cap[QStringLiteral("dav")].toMap(); + dav.insert({{QStringLiteral("invalidFilenameRegex"), regex}}); + cap[QStringLiteral("dav")] = dav; + return cap; + }; + fakeFolder.account()->setCapabilities({fakeFolder.account()->url(), invalidFilenameRegexCapabilities(QStringLiteral("my[fgh]ile"))}); + fakeFolder.localModifier().insert(QStringLiteral("C/myfile.txt")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("C/myfile.txt"))); } void testDiscoveryHiddenFile() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - QVERIFY(fakeFolder.syncOnce()); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // We can't depend on currentLocalState for hidden files since // it should rightfully skip things like download temporaries - auto localFileExists = [&](QString name) { + auto localFileExists = [&](const QString &name) { return QFileInfo::exists(fakeFolder.localPath() + name); }; - fakeFolder.syncEngine().setIgnoreHiddenFiles(true); - fakeFolder.remoteModifier().insert("A/.hidden"); - fakeFolder.localModifier().insert("B/.hidden"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!localFileExists("A/.hidden")); - QVERIFY(!fakeFolder.currentRemoteState().find("B/.hidden")); - - fakeFolder.syncEngine().setIgnoreHiddenFiles(false); - fakeFolder.syncJournal().forceRemoteDiscoveryNextSync(); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(localFileExists("A/.hidden")); - QVERIFY(fakeFolder.currentRemoteState().find("B/.hidden")); - } - - void testNoLocalEncoding() - { - const auto utf8Locale = QTextCodec::codecForLocale(); - if (utf8Locale->mibEnum() != 106) { - QSKIP(qUtf8Printable(QStringLiteral("Test only works for UTF8 locale, but current locale is %1").arg(QString::fromUtf8(utf8Locale->name())))); - } - - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // Utf8 locale can sync both - fakeFolder.remoteModifier().insert("A/tößt"); - fakeFolder.remoteModifier().insert("A/t𠜎t"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/tößt")); - QVERIFY(fakeFolder.currentLocalState().find("A/t𠜎t")); - -#if !defined(Q_OS_MAC) && !defined(Q_OS_WIN) - // Try again with a locale that can represent ö but not 𠜎 (4-byte utf8). - auto codec = QTextCodec::codecForName("ISO-8859-15"); - QVERIFY(codec); - QTextCodec::setCodecForLocale(codec); - QCOMPARE(QTextCodec::codecForLocale()->mibEnum(), 111); - - fakeFolder.remoteModifier().insert("B/tößt"); - fakeFolder.remoteModifier().insert("B/t𠜎t"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("B/tößt")); - QVERIFY(!fakeFolder.currentLocalState().find("B/t𠜎t")); - QVERIFY(!fakeFolder.currentLocalState().find("B/t?t")); - QVERIFY(!fakeFolder.currentLocalState().find("B/t??t")); - QVERIFY(!fakeFolder.currentLocalState().find("B/t???t")); - QVERIFY(!fakeFolder.currentLocalState().find("B/t????t")); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentRemoteState().find("B/tößt")); - QVERIFY(fakeFolder.currentRemoteState().find("B/t𠜎t")); - - // Try again with plain ascii - codec = QTextCodec::codecForName("ASCII"); - if (codec) { - QTextCodec::setCodecForLocale(codec); - QCOMPARE(QTextCodec::codecForLocale()->mibEnum(), 3); - - fakeFolder.remoteModifier().insert("C/tößt"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("C/tößt")); - QVERIFY(!fakeFolder.currentLocalState().find("C/t??t")); - QVERIFY(!fakeFolder.currentLocalState().find("C/t????t")); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentRemoteState().find("C/tößt")); - } else { - qDebug() << "Skipping test for ASCII, ASCII is not available, available encodings are:" << QTextCodec::availableCodecs(); - } - - QTextCodec::setCodecForLocale(utf8Locale); -#endif + fakeFolder.syncEngine()->setIgnoreHiddenFiles(true); + fakeFolder.remoteModifier().insert(QStringLiteral("A/.hidden")); + fakeFolder.localModifier().insert(QStringLiteral("B/.hidden")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(!localFileExists(QStringLiteral("A/.hidden"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("B/.hidden"))); + + fakeFolder.syncEngine()->setIgnoreHiddenFiles(false); + fakeFolder.syncJournal()->forceRemoteDiscoveryNextSync(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(localFileExists(QStringLiteral("A/.hidden"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("B/.hidden"))); } // Aborting has had bugs when there are parallel upload jobs void testUploadV1Multiabort() { - FakeFolder fakeFolder{ FileInfo{} }; - SyncOptions options; - options._initialChunkSize = 10; - options._maxChunkSize = 10; - options._minChunkSize = 10; - fakeFolder.syncEngine().setSyncOptions(options); - - QObject parent; - int nPUT = 0; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo {}, vfsMode, filesAreDehydrated); + // I'm pretty sure the next two lines are COMPLETELY USELESS + // SyncOptions options = fakeFolder.syncEngine()->syncOptions(); + // fakeFolder.syncEngine()->setSyncOptions(options); + auto cap = TestUtils::testCapabilities(); + // unset chunking v1 + cap.remove(QStringLiteral("dav")); + fakeFolder.account()->setCapabilities({fakeFolder.account()->url(), cap}); + + // possible memory problems on linux start here + auto counter = std::make_unique(); + fakeFolder.setServerOverride([counter = counter.get(), fakeFolder = &fakeFolder]( + QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *device) -> QNetworkReply * { + counter->serverOverride(op, request, device); if (op == QNetworkAccessManager::PutOperation) { - ++nPUT; - return new FakeHangingReply(op, request, &parent); + return new FakeHangingReply(op, request, fakeFolder); } return nullptr; }); - fakeFolder.localModifier().insert("file", 100, 'W'); - QTimer::singleShot(100ms, &fakeFolder.syncEngine(), [&]() { fakeFolder.syncEngine().abort(); }); - QVERIFY(!fakeFolder.syncOnce()); - - QCOMPARE(nPUT, 3); + fakeFolder.localModifier().insert(QStringLiteral("file1"), 1_MiB, 'W'); + fakeFolder.localModifier().insert(QStringLiteral("file2"), 1_MiB, 'W'); + fakeFolder.localModifier().insert(QStringLiteral("file3"), 1_MiB, 'W'); + // wait until the sync engine is ready + // wait a second and abort + connect(fakeFolder.syncEngine(), &SyncEngine::aboutToPropagate, fakeFolder.syncEngine(), + [&]() { QTimer::singleShot(1s, fakeFolder.syncEngine(), [&]() { fakeFolder.syncEngine()->abort({}); }); }); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); + // for debugging memory problem on linux: I get output to here, including one nameless abort (above) then the abort associated with + // syncengine dtr + QCOMPARE(counter->nPUT, 3); } #ifndef Q_OS_WIN void testPropagatePermissions() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - auto perm = QFileDevice::Permission(0x7704); // user/owner: rwx, group: r, other: - - QFile::setPermissions(fakeFolder.localPath() + "A/a1", perm); - QFile::setPermissions(fakeFolder.localPath() + "A/a2", perm); - fakeFolder.syncOnce(); // get the metadata-only change out of the way - fakeFolder.remoteModifier().appendByte("A/a1"); - fakeFolder.remoteModifier().appendByte("A/a2"); - fakeFolder.localModifier().appendByte("A/a2"); - fakeFolder.localModifier().appendByte("A/a2"); - fakeFolder.syncOnce(); // perms should be preserved - QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1").permissions(), perm); - QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a2").permissions(), perm); - - auto conflictName = fakeFolder.syncJournal().conflictRecord(fakeFolder.syncJournal().conflictRecordPaths().first()).path; - QVERIFY(conflictName.contains("A/a2")); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + auto perm = QFileDevice::Permission(0x7740); // user/owner: rwx, group: r, other: - + QFile::setPermissions(fakeFolder.localPath() + QStringLiteral("A/a1"), perm); + QFile::setPermissions(fakeFolder.localPath() + QStringLiteral("A/a2"), perm); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // get the metadata-only change out of the way + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a2")); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a2")); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a2")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // perms should be preserved + QCOMPARE(QFileInfo(fakeFolder.localPath() + QStringLiteral("A/a1")).permissions(), perm); + QCOMPARE(QFileInfo(fakeFolder.localPath() + QStringLiteral("A/a2")).permissions(), perm); + + const QString conflictName = QString::fromUtf8(fakeFolder.syncJournal()->conflictRecord(fakeFolder.syncJournal()->conflictRecordPaths().first()).path); + QVERIFY(conflictName.contains(QStringLiteral("A/a2"))); QCOMPARE(QFileInfo(fakeFolder.localPath() + conflictName).permissions(), perm); } #endif void testEmptyLocalButHasRemote() { - FakeFolder fakeFolder{ FileInfo{} }; - fakeFolder.remoteModifier().mkdir("foo"); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + FakeFolder fakeFolder(FileInfo {}, vfsMode, filesAreDehydrated); + fakeFolder.remoteModifier().mkdir(QStringLiteral("foo")); - QVERIFY(fakeFolder.currentLocalState().find("foo")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("foo"))); } // Check that server mtime is set on directories on initial propagation void testDirectoryInitialMtime() { - FakeFolder fakeFolder{ FileInfo{} }; - fakeFolder.remoteModifier().mkdir("foo"); - fakeFolder.remoteModifier().insert("foo/bar"); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo {}, vfsMode, filesAreDehydrated); + fakeFolder.remoteModifier().mkdir(QStringLiteral("foo")); + fakeFolder.remoteModifier().insert(QStringLiteral("foo/bar")); auto datetime = QDateTime::currentDateTime(); datetime.setSecsSinceEpoch(datetime.toSecsSinceEpoch()); // wipe ms - fakeFolder.remoteModifier().find("foo")->lastModified = datetime; + fakeFolder.remoteModifier().find(QStringLiteral("foo"))->setLastModified(datetime); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(QFileInfo(fakeFolder.localPath() + "foo").lastModified(), datetime); + QCOMPARE(QFileInfo(fakeFolder.localPath() + QStringLiteral("foo")).lastModified(), datetime); + } + + + void testProceedWithIndependentDelets() + { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + // during an upload of B/newFile an error occurs that aborts all delete jobs + fakeFolder.localModifier().insert(QStringLiteral("B/newFile")); + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { + if (op == QNetworkAccessManager::PutOperation) { + return new FakeErrorReply(op, request, this, 507); + } + return nullptr; + }); + + // this will go through + fakeFolder.remoteModifier().insert(QStringLiteral("C/newRemoteFile")); + + // those will fail https://github.com/owncloud/client/issues/9311 + fakeFolder.remoteModifier().remove(QStringLiteral("A")); + fakeFolder.remoteModifier().remove(QStringLiteral("S")); + + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); + + // the download succeeded + QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("C/newRemoteFile"))); + + // the deletes happened as they are independent of the error in B + QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("A"))); + QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("S"))); } }; diff --git a/test/testsyncfileitem.cpp b/test/testsyncfileitem.cpp index 8b4732f7074..d99416860e8 100644 --- a/test/testsyncfileitem.cpp +++ b/test/testsyncfileitem.cpp @@ -14,7 +14,7 @@ class TestSyncFileItem : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: void initTestCase() { } @@ -32,18 +32,18 @@ private slots: QTest::addColumn("b"); QTest::addColumn("c"); - QTest::newRow("a1") << createItem("client") << createItem("client/build") << createItem("client-build") ; - QTest::newRow("a2") << createItem("test/t1") << createItem("test/t2") << createItem("test/t3") ; - QTest::newRow("a3") << createItem("ABCD") << createItem("abcd") << createItem("zzzz"); + QTest::newRow("a1") << createItem(QStringLiteral("client")) << createItem(QStringLiteral("client/build")) << createItem(QStringLiteral("client-build")); + QTest::newRow("a2") << createItem(QStringLiteral("test/t1")) << createItem(QStringLiteral("test/t2")) << createItem(QStringLiteral("test/t3")); + QTest::newRow("a3") << createItem(QStringLiteral("ABCD")) << createItem(QStringLiteral("abcd")) << createItem(QStringLiteral("zzzz")); SyncFileItem movedItem1; - movedItem1._file = "folder/source/file.f"; - movedItem1._renameTarget = "folder/destination/file.f"; - movedItem1._instruction = CSYNC_INSTRUCTION_RENAME; + movedItem1._file = QStringLiteral("folder/source/file.f"); + movedItem1._renameTarget = QStringLiteral("folder/destination/file.f"); + movedItem1.setInstruction(CSYNC_INSTRUCTION_RENAME); - QTest::newRow("move1") << createItem("folder/destination") << movedItem1 << createItem("folder/destination-2"); - QTest::newRow("move2") << createItem("folder/destination/1") << movedItem1 << createItem("folder/source"); - QTest::newRow("move3") << createItem("abc") << movedItem1 << createItem("ijk"); + QTest::newRow("move1") << createItem(QStringLiteral("folder/destination")) << movedItem1 << createItem(QStringLiteral("folder/destination-2")); + QTest::newRow("move2") << createItem(QStringLiteral("folder/destination/1")) << movedItem1 << createItem(QStringLiteral("folder/source")); + QTest::newRow("move3") << createItem(QStringLiteral("abc")) << movedItem1 << createItem(QStringLiteral("ijk")); } void testComparator() { diff --git a/test/testsyncfilestatustracker.cpp b/test/testsyncfilestatustracker.cpp index d0dc3a01ac0..9574052100d 100644 --- a/test/testsyncfilestatustracker.cpp +++ b/test/testsyncfilestatustracker.cpp @@ -13,26 +13,31 @@ using namespace OCC; class StatusPushSpy : public QSignalSpy { - SyncEngine &_syncEngine; + SyncEngine *_syncEngine; + public: - StatusPushSpy(SyncEngine &syncEngine) - : QSignalSpy(&syncEngine.syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged) + StatusPushSpy(SyncEngine *syncEngine) + : QSignalSpy(syncEngine, &SyncEngine::fileStatusChanged) , _syncEngine(syncEngine) - { } + { + } - SyncFileStatus statusOf(const QString &relativePath) const { - QFileInfo file(_syncEngine.localPath(), relativePath); + SyncFileStatus statusOf(const QString &relativePath) const + { + const QFileInfo file(_syncEngine->localPath(), relativePath); // Start from the end to get the latest status for (auto it = crbegin(); it != crend(); ++it) { - if (QFileInfo(it->at(0).toString()) == file) + const auto info = QFileInfo(it->at(0).toString()); + if (info == file) { return it->at(1).value(); + } } return SyncFileStatus(); } bool statusEmittedBefore(const QString &firstPath, const QString &secondPath) const { - QFileInfo firstFile(_syncEngine.localPath(), firstPath); - QFileInfo secondFile(_syncEngine.localPath(), secondPath); + QFileInfo firstFile(_syncEngine->localPath(), firstPath); + QFileInfo secondFile(_syncEngine->localPath(), secondPath); // Start from the end to get the latest status int i = size() - 1; for (; i >= 0; --i) { @@ -49,6 +54,11 @@ class StatusPushSpy : public QSignalSpy } }; +// +// IMPORTANT: these tests are only run with VFS off, because the tests assume a very controlled order of status changes. +// When using some form of VFS, the event loop has to run in order to fulfill requests from the OS, and therefore things +// like stopping execution in `execUntilBeforePropagation()` are very hard to do. +// class TestSyncFileStatusTracker : public QObject { Q_OBJECT @@ -58,254 +68,268 @@ class TestSyncFileStatusTracker : public QObject QDirIterator it(root, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (it.hasNext()) { QString filePath = it.next().mid(root.size()); - SyncFileStatus pushedStatus = statusSpy.statusOf(filePath); + auto pushedStatus = statusSpy.statusOf(filePath); if (pushedStatus != SyncFileStatus()) { - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(filePath), pushedStatus); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(filePath), pushedStatus); } } } -private slots: +private Q_SLOTS: void parentsGetSyncStatusUploadDownload() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.localModifier().appendByte("B/b1"); - fakeFolder.remoteModifier().appendByte("C/c1"); + fakeFolder.localModifier().appendByte(QStringLiteral("B/b1")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c1")); StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("B/b2"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("C/c2"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("C/c2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void parentsGetSyncStatusNewFileUploadDownload() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.localModifier().insert("B/b0"); - fakeFolder.remoteModifier().insert("C/c0"); + fakeFolder.localModifier().insert(QStringLiteral("B/b0")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + fakeFolder.remoteModifier().insert(QStringLiteral("C/c0")); StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("C/c0"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("B/b1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("C/c1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c0")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("C/c0"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c0")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void parentsGetSyncStatusNewDirDownload() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.remoteModifier().mkdir("D"); - fakeFolder.remoteModifier().insert("D/d0"); + fakeFolder.remoteModifier().mkdir(QStringLiteral("D")); + fakeFolder.remoteModifier().insert(QStringLiteral("D/d0")); StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusSync)); - fakeFolder.execUntilItemCompleted("D"); + statusSpy.clear(); + fakeFolder.execUntilItemCompleted(QStringLiteral("D")); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusNone)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusNone)); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void parentsGetSyncStatusNewDirUpload() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.localModifier().mkdir("D"); - fakeFolder.localModifier().insert("D/d0"); + fakeFolder.localModifier().mkdir(QStringLiteral("D")); + fakeFolder.localModifier().insert(QStringLiteral("D/d0")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusSync)); - fakeFolder.execUntilItemCompleted("D"); + statusSpy.clear(); + fakeFolder.execUntilItemCompleted(QStringLiteral("D")); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("D"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("D/d0"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D")), SyncFileStatus(SyncFileStatus::StatusNone)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("D/d0")), SyncFileStatus(SyncFileStatus::StatusNone)); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void parentsGetSyncStatusDeleteUpDown() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.remoteModifier().remove("B/b1"); - fakeFolder.localModifier().remove("C/c1"); + fakeFolder.remoteModifier().remove(QStringLiteral("B/b1")); + fakeFolder.localModifier().remove(QStringLiteral("C/c1")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync)); // Discovered as remotely removed, pending for local removal. - QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("B/b2"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("C/c2"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("C/c2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void warningStatusForExcludedFile() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().excludedFiles().addManualExclude("A/a1"); - fakeFolder.syncEngine().excludedFiles().addManualExclude("B"); - fakeFolder.localModifier().appendByte("A/a1"); - fakeFolder.localModifier().appendByte("B/b1"); + fakeFolder.syncEngine()->addManualExclude(QStringLiteral("A/a1")); + fakeFolder.syncEngine()->addManualExclude(QStringLiteral("B")); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.localModifier().appendByte(QStringLiteral("B/b1")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusExcluded)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusExcluded)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusExcluded)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusExcluded)); QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue); - QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusExcluded)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusExcluded)); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusExcluded)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusExcluded)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusExcluded)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusExcluded)); QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue); - QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusExcluded)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusExcluded)); QEXPECT_FAIL("", "csync will stop at ignored directories without traversing children, so we don't currently push the status for newly ignored children of an ignored directory.", Continue); - QCOMPARE(statusSpy.statusOf("B/b2"), SyncFileStatus(SyncFileStatus::StatusExcluded)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b2")), SyncFileStatus(SyncFileStatus::StatusExcluded)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); // Clears the exclude expr above - fakeFolder.syncEngine().excludedFiles().clearManualExcludes(); + fakeFolder.syncEngine()->clearManualExcludes(); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusSync)); statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void warningStatusForExcludedFile_CasePreserving() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.syncEngine().excludedFiles().addManualExclude("B"); - fakeFolder.serverErrorPaths().append("A/a1"); - fakeFolder.localModifier().appendByte("A/a1"); + fakeFolder.syncEngine()->addManualExclude(QStringLiteral("B")); + fakeFolder.serverErrorPaths().append(QStringLiteral("A/a1")); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); fakeFolder.syncOnce(); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(""), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("B"), SyncFileStatus(SyncFileStatus::StatusExcluded)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusExcluded)); // Should still get the status for different casing on macOS and Windows. - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("a"), SyncFileStatus(Utility::fsCasePreserving() ? SyncFileStatus::StatusWarning : SyncFileStatus::StatusNone)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/A1"), SyncFileStatus(Utility::fsCasePreserving() ? SyncFileStatus::StatusError : SyncFileStatus::StatusNone)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("b"), SyncFileStatus(Utility::fsCasePreserving() ? SyncFileStatus::StatusExcluded : SyncFileStatus::StatusNone)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("a")), + SyncFileStatus(Utility::fsCasePreservingButCaseInsensitive() ? SyncFileStatus::StatusWarning : SyncFileStatus::StatusNone)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/A1")), + SyncFileStatus(Utility::fsCasePreservingButCaseInsensitive() ? SyncFileStatus::StatusError : SyncFileStatus::StatusNone)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("b")), + SyncFileStatus(Utility::fsCasePreservingButCaseInsensitive() ? SyncFileStatus::StatusExcluded : SyncFileStatus::StatusNone)); } void parentsGetWarningStatusForError() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.serverErrorPaths().append("A/a1"); - fakeFolder.serverErrorPaths().append("B/b0"); - fakeFolder.localModifier().appendByte("A/a1"); - fakeFolder.localModifier().insert("B/b0"); + fakeFolder.serverErrorPaths().append(QStringLiteral("A/a1")); + fakeFolder.serverErrorPaths().append(QStringLiteral("B/b0")); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.localModifier().insert(QStringLiteral("B/b0")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusSync)); statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a2"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError)); statusSpy.clear(); // Remove the error and start a second sync, the blacklist should kick in @@ -314,68 +338,69 @@ private slots: fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); // A/a1 and B/b0 should be on the black list for the next few seconds - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError)); statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a2"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError)); statusSpy.clear(); // Start a third sync, this time together with a real file to sync - fakeFolder.localModifier().appendByte("C/c1"); + fakeFolder.localModifier().appendByte(QStringLiteral("C/c1")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); // The root should show SYNC even though there is an error underneath, // since C/c1 is syncing and the SYNC status has priority. - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusSync)); statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a2"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a2")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); // Another sync after clearing the blacklist entry, everything should return to order. - fakeFolder.syncEngine().journal()->wipeErrorBlacklistEntry("A/a1"); - fakeFolder.syncEngine().journal()->wipeErrorBlacklistEntry("B/b0"); + fakeFolder.syncJournal()->wipeErrorBlacklistEntry(QStringLiteral("A/a1")); + fakeFolder.syncJournal()->wipeErrorBlacklistEntry(QStringLiteral("B/b0")); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusSync)); statusSpy.clear(); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("A/a1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B/b0"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b0")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } @@ -387,24 +412,25 @@ private slots: {QStringLiteral("a"), 4}, {QStringLiteral("a1"), 4} }}}}}; - fakeFolder.serverErrorPaths().append("A/a1"); - fakeFolder.localModifier().appendByte("A/a1"); + fakeFolder.serverErrorPaths().append(QStringLiteral("A/a1")); + fakeFolder.localModifier().appendByte(QStringLiteral("A/a1")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); // The SyncFileStatusTraker won't push any status for all of them, test with a pull. - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); fakeFolder.execUntilFinished(); // We use string matching for paths in the implementation, // an error should affect only parents and not every path that starts with the problem path. - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus(""), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a1"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(fakeFolder.syncEngine().syncFileStatusTracker().fileStatus("A/a"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a1")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(fakeFolder.syncEngine()->fileStatus(QStringLiteral("A/a")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); } // Even for status pushes immediately following each other, macOS @@ -412,21 +438,22 @@ private slots: // children are marked as OK before their parents do. void childOKEmittedBeforeParent() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.localModifier().appendByte("B/b1"); - fakeFolder.remoteModifier().appendByte("C/c1"); + fakeFolder.localModifier().appendByte(QStringLiteral("B/b1")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c1")); StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.syncOnce(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QVERIFY(statusSpy.statusEmittedBefore("B/b1", "B")); - QVERIFY(statusSpy.statusEmittedBefore("C/c1", "C")); - QVERIFY(statusSpy.statusEmittedBefore("B", "")); - QVERIFY(statusSpy.statusEmittedBefore("C", "")); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B/b1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("C"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("C/c1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QVERIFY(statusSpy.statusEmittedBefore(QStringLiteral("B/b1"), QStringLiteral("B"))); + QVERIFY(statusSpy.statusEmittedBefore(QStringLiteral("C/c1"), QStringLiteral("C"))); + QVERIFY(statusSpy.statusEmittedBefore(QStringLiteral("B"), QString())); + QVERIFY(statusSpy.statusEmittedBefore(QStringLiteral("C"), QString())); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("C/c1")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); } void sharedStatus() { @@ -434,43 +461,44 @@ private slots: sharedUpToDateStatus.setShared(true); FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.remoteModifier().insert("S/s0"); - fakeFolder.remoteModifier().appendByte("S/s1"); - fakeFolder.remoteModifier().insert("B/b3"); - fakeFolder.remoteModifier().find("B/b3")->extraDavProperties = "0"; - fakeFolder.remoteModifier().find("A/a1")->isShared = true; // becomes shared - fakeFolder.remoteModifier().find("A", true); // change the etags of the parent + fakeFolder.remoteModifier().insert(QStringLiteral("S/s0")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("S/s1")); + fakeFolder.remoteModifier().insert(QStringLiteral("B/b3")); + fakeFolder.remoteModifier().find(QStringLiteral("B/b3"))->extraDavProperties = "0"; + fakeFolder.remoteModifier().find(QStringLiteral("A/a1"))->isShared = true; // becomes shared + fakeFolder.remoteModifier().find(QStringLiteral("A"), true); // change the etags of the parent StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); fakeFolder.execUntilBeforePropagation(); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); // We don't care about the shared flag for the sync status, // Mac and Windows won't show it and we can't know it for new files. - QCOMPARE(statusSpy.statusOf("S").tag(), SyncFileStatus::StatusSync); - QCOMPARE(statusSpy.statusOf("S/s0").tag(), SyncFileStatus::StatusSync); - QCOMPARE(statusSpy.statusOf("S/s1").tag(), SyncFileStatus::StatusSync); + QCOMPARE(statusSpy.statusOf(QStringLiteral("S")).tag(), SyncFileStatus::StatusSync); + QCOMPARE(statusSpy.statusOf(QStringLiteral("S/s0")).tag(), SyncFileStatus::StatusSync); + QCOMPARE(statusSpy.statusOf(QStringLiteral("S/s1")).tag(), SyncFileStatus::StatusSync); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("S"), sharedUpToDateStatus); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("S")), sharedUpToDateStatus); QEXPECT_FAIL("", "We currently only know if a new file is shared on the second sync, after a PROPFIND.", Continue); - QCOMPARE(statusSpy.statusOf("S/s0"), sharedUpToDateStatus); - QCOMPARE(statusSpy.statusOf("S/s1"), sharedUpToDateStatus); - QCOMPARE(statusSpy.statusOf("B/b1").shared(), false); - QCOMPARE(statusSpy.statusOf("B/b3"), sharedUpToDateStatus); - QCOMPARE(statusSpy.statusOf("A/a1"), sharedUpToDateStatus); + QCOMPARE(statusSpy.statusOf(QStringLiteral("S/s0")), sharedUpToDateStatus); + QCOMPARE(statusSpy.statusOf(QStringLiteral("S/s1")), sharedUpToDateStatus); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1")).shared(), false); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b3")), sharedUpToDateStatus); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), sharedUpToDateStatus); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); } void renameError() { FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - fakeFolder.serverErrorPaths().append("A/a1"); - fakeFolder.localModifier().rename("A/a1", "A/a1m"); - fakeFolder.localModifier().rename("B/b1", "B/b1m"); + fakeFolder.serverErrorPaths().append(QStringLiteral("A/a1")); + fakeFolder.localModifier().rename(QStringLiteral("A/a1"), QStringLiteral("A/a1m")); + fakeFolder.localModifier().rename(QStringLiteral("B/b1"), QStringLiteral("B/b1m")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); StatusPushSpy statusSpy(fakeFolder.syncEngine()); fakeFolder.scheduleSync(); @@ -478,21 +506,21 @@ private slots: verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf("A/a1m"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("A/a1"), statusSpy.statusOf("A/a1notexist")); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusSync)); - QCOMPARE(statusSpy.statusOf("B/b1m"), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1m")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), statusSpy.statusOf(QStringLiteral("A/a1notexist"))); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusSync)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1m")), SyncFileStatus(SyncFileStatus::StatusSync)); fakeFolder.execUntilFinished(); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf("A/a1m"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(statusSpy.statusOf("A/a1"), statusSpy.statusOf("A/a1notexist")); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); - QCOMPARE(statusSpy.statusOf("B/b1m"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1m")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), statusSpy.statusOf(QStringLiteral("A/a1notexist"))); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1m")), SyncFileStatus(SyncFileStatus::StatusUpToDate)); statusSpy.clear(); QVERIFY(!fakeFolder.syncOnce()); @@ -500,12 +528,12 @@ private slots: statusSpy.clear(); QVERIFY(!fakeFolder.syncOnce()); verifyThatPushMatchesPull(fakeFolder, statusSpy); - QCOMPARE(statusSpy.statusOf("A/a1m"), SyncFileStatus(SyncFileStatus::StatusError)); - QCOMPARE(statusSpy.statusOf("A/a1"), statusSpy.statusOf("A/a1notexist")); - QCOMPARE(statusSpy.statusOf("A"), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf(""), SyncFileStatus(SyncFileStatus::StatusWarning)); - QCOMPARE(statusSpy.statusOf("B"), SyncFileStatus(SyncFileStatus::StatusNone)); - QCOMPARE(statusSpy.statusOf("B/b1m"), SyncFileStatus(SyncFileStatus::StatusNone)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1m")), SyncFileStatus(SyncFileStatus::StatusError)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A/a1")), statusSpy.statusOf(QStringLiteral("A/a1notexist"))); + QCOMPARE(statusSpy.statusOf(QStringLiteral("A")), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QString()), SyncFileStatus(SyncFileStatus::StatusWarning)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B")), SyncFileStatus(SyncFileStatus::StatusNone)); + QCOMPARE(statusSpy.statusOf(QStringLiteral("B/b1m")), SyncFileStatus(SyncFileStatus::StatusNone)); statusSpy.clear(); } diff --git a/test/testsyncjournaldb.cpp b/test/testsyncjournaldb.cpp index ae786a42b49..c098df6a8b5 100644 --- a/test/testsyncjournaldb.cpp +++ b/test/testsyncjournaldb.cpp @@ -21,17 +21,17 @@ class TestSyncJournalDB : public QObject public: TestSyncJournalDB() - : _db((_tempDir.path() + "/sync.db")) + : _db((_tempDir.path() + QStringLiteral("/sync.db")), nullptr) { QVERIFY(_tempDir.isValid()); } - qint64 dropMsecs(QDateTime time) + qint64 dropMsecs(const QDateTime &time) { return Utility::qDateTimeToTime_t(time); } -private slots: +private Q_SLOTS: void initTestCase() { @@ -67,8 +67,8 @@ private slots: QVERIFY(storedRecord == record); // Update checksum - record._checksumHeader = "Adler32:newchecksum"; - _db.updateFileRecordChecksum("foo", "newchecksum", "Adler32"); + record._checksumHeader = "ADLER32:newchecksum"; + _db.updateFileRecordChecksum(QStringLiteral("foo"), "newchecksum", CheckSums::fromByteArray("Adler32")); QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &storedRecord)); QVERIFY(storedRecord == record); @@ -85,7 +85,7 @@ private slots: QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &storedRecord)); QVERIFY(storedRecord == record); - QVERIFY(_db.deleteFileRecord("foo")); + QVERIFY(_db.deleteFileRecord(QStringLiteral("foo"))); QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo"), &record)); QVERIFY(!record.isValid()); } @@ -103,9 +103,9 @@ private slots: SyncJournalFileRecord storedRecord; QVERIFY(_db.getFileRecord(QByteArrayLiteral("foo-checksum"), &storedRecord)); - QVERIFY(storedRecord._path == record._path); - QVERIFY(storedRecord._remotePerm == record._remotePerm); - QVERIFY(storedRecord._checksumHeader == record._checksumHeader); + QCOMPARE(storedRecord._path, record._path); + QCOMPARE(storedRecord._remotePerm, record._remotePerm); + QCOMPARE(storedRecord._checksumHeader, record._checksumHeader); // qDebug()<< "OOOOO " << storedRecord._modtime.toTime_t() << record._modtime.toTime_t(); @@ -131,27 +131,27 @@ private slots: void testDownloadInfo() { typedef SyncJournalDb::DownloadInfo Info; - Info record = _db.getDownloadInfo("nonexistant"); + Info record = _db.getDownloadInfo(QStringLiteral("nonexistant")); QVERIFY(!record._valid); record._errorCount = 5; record._etag = "ABCDEF"; record._valid = true; - record._tmpfile = "/tmp/foo"; - _db.setDownloadInfo("foo", record); + record._tmpfile = QStringLiteral("/tmp/foo"); + _db.setDownloadInfo(QStringLiteral("foo"), record); - Info storedRecord = _db.getDownloadInfo("foo"); + Info storedRecord = _db.getDownloadInfo(QStringLiteral("foo")); QVERIFY(storedRecord == record); - _db.setDownloadInfo("foo", Info()); - Info wipedRecord = _db.getDownloadInfo("foo"); + _db.setDownloadInfo(QStringLiteral("foo"), Info()); + Info wipedRecord = _db.getDownloadInfo(QStringLiteral("foo")); QVERIFY(!wipedRecord._valid); } void testUploadInfo() { typedef SyncJournalDb::UploadInfo Info; - Info record = _db.getUploadInfo("nonexistant"); + Info record = _db.getUploadInfo(QStringLiteral("nonexistant")); QVERIFY(!record._valid); record._errorCount = 5; @@ -160,13 +160,13 @@ private slots: record._size = 12894789147; record._modtime = dropMsecs(QDateTime::currentDateTime()); record._valid = true; - _db.setUploadInfo("foo", record); + _db.setUploadInfo(QStringLiteral("foo"), record); - Info storedRecord = _db.getUploadInfo("foo"); + Info storedRecord = _db.getUploadInfo(QStringLiteral("foo")); QVERIFY(storedRecord == record); - _db.setUploadInfo("foo", Info()); - Info wipedRecord = _db.getUploadInfo("foo"); + _db.setUploadInfo(QStringLiteral("foo"), Info()); + Info wipedRecord = _db.getUploadInfo(QStringLiteral("foo")); QVERIFY(!wipedRecord._valid); } @@ -281,12 +281,12 @@ private slots: << "foo bla bar/file" << "fo_" << "fo_/file"; - for (const auto &elem : qAsConst(elements)) + for (const auto &elem : std::as_const(elements)) makeEntry(elem); auto checkElements = [&]() { bool ok = true; - for (const auto &elem : qAsConst(elements)) { + for (const auto &elem : std::as_const(elements)) { SyncJournalFileRecord record; _db.getFileRecord(elem, &record); if (!record.isValid()) { @@ -297,17 +297,17 @@ private slots: return ok; }; - _db.deleteFileRecord("moo", true); + _db.deleteFileRecord(QStringLiteral("moo"), true); elements.removeAll("moo"); elements.removeAll("moo/file"); QVERIFY(checkElements()); - _db.deleteFileRecord("fo_", true); + _db.deleteFileRecord(QStringLiteral("fo_"), true); elements.removeAll("fo_"); elements.removeAll("fo_/file"); QVERIFY(checkElements()); - _db.deleteFileRecord("foo%bar", true); + _db.deleteFileRecord(QStringLiteral("foo%bar"), true); elements.removeAll("foo%bar"); QVERIFY(checkElements()); } diff --git a/test/testsyncmove.cpp b/test/testsyncmove.cpp index 047e5149bca..d05ba0437c9 100644 --- a/test/testsyncmove.cpp +++ b/test/testsyncmove.cpp @@ -5,46 +5,25 @@ * */ -#include #include "testutils/syncenginetestutils.h" +#include "testutils/testutils.h" + +#include #include +#include using namespace OCC; - -struct OperationCounter { - int nGET = 0; - int nPUT = 0; - int nMOVE = 0; - int nDELETE = 0; - - void reset() { *this = {}; } - - auto functor() { - return [&](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) { - if (op == QNetworkAccessManager::GetOperation) - ++nGET; - if (op == QNetworkAccessManager::PutOperation) - ++nPUT; - if (op == QNetworkAccessManager::DeleteOperation) - ++nDELETE; - if (req.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") - ++nMOVE; - return nullptr; - }; - } -}; - bool itemSuccessful(const ItemCompletedSpy &spy, const QString &path, const SyncInstructions instr) { auto item = spy.findItem(path); - return item->_status == SyncFileItem::Success && item->_instruction == instr; + return item->_status == SyncFileItem::Success && item->instruction() == instr; } bool itemConflict(const ItemCompletedSpy &spy, const QString &path) { auto item = spy.findItem(path); - return item->_status == SyncFileItem::Conflict && item->_instruction == CSYNC_INSTRUCTION_CONFLICT; + return item->_status == SyncFileItem::Conflict && item->instruction() == CSYNC_INSTRUCTION_CONFLICT; } bool itemSuccessfulMove(const ItemCompletedSpy &spy, const QString &path) @@ -56,21 +35,21 @@ QStringList findConflicts(const FileInfo &dir) { QStringList conflicts; for (const auto &item : dir.children) { - if (item.name.contains("(conflicted copy")) { + if (item.name.contains(QLatin1String("(conflicted copy"))) { conflicts.append(item.path()); } } return conflicts; } -bool expectAndWipeConflict(FileModifier &local, FileInfo state, const QString path) +bool expectAndWipeConflict(FileModifier &local, FileInfo state, const QString &path) { PathComponents pathComponents(path); auto base = state.find(pathComponents.parentDirComponents()); if (!base) return false; - for (const auto &item : qAsConst(base->children)) { - if (item.name.startsWith(pathComponents.fileName()) && item.name.contains("(conflicted copy")) { + for (const auto &item : std::as_const(base->children)) { + if (item.name.startsWith(pathComponents.fileName()) && item.name.contains(QLatin1String("(conflicted copy"))) { local.remove(item.path()); return true; } @@ -82,83 +61,105 @@ class TestSyncMove : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); + + QTest::newRow("Vfs::Off") << Vfs::Off << false; + + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; + + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } + } + void testRemoteChangeInMovedFolder() { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + // issue #5192 - FakeFolder fakeFolder{ FileInfo{ QString(), { FileInfo{ QStringLiteral("folder"), { FileInfo{ QStringLiteral("folderA"), { { QStringLiteral("file.txt"), 400 } } }, QStringLiteral("folderB") } } } } }; + FakeFolder fakeFolder(FileInfo { QString(), { FileInfo { QStringLiteral("folder"), { FileInfo { QStringLiteral("folderA"), { { QStringLiteral("file.txt"), 400 } } }, QStringLiteral("folderB") } } } }, vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); // Edit a file in a moved directory. - fakeFolder.remoteModifier().setContents("folder/folderA/file.txt", 'a'); - fakeFolder.remoteModifier().rename("folder/folderA", "folder/folderB/folderA"); - fakeFolder.syncOnce(); + fakeFolder.remoteModifier().setContents(QStringLiteral("folder/folderA/file.txt"), FileInfo::DefaultFileSize, 'a'); + fakeFolder.remoteModifier().rename(QStringLiteral("folder/folderA"), QStringLiteral("folder/folderB/folderA")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); auto oldState = fakeFolder.currentLocalState(); - QVERIFY(oldState.find("folder/folderB/folderA/file.txt")); - QVERIFY(!oldState.find("folder/folderA/file.txt")); + QVERIFY(oldState.find(QStringLiteral("folder/folderB/folderA/file.txt"))); + QVERIFY(!oldState.find(QStringLiteral("folder/folderA/file.txt"))); // This sync should not remove the file - fakeFolder.syncOnce(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(fakeFolder.currentLocalState(), oldState); } void testSelectiveSyncMovedFolder() { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + // issue #5224 - FakeFolder fakeFolder{ FileInfo{ QString(), { FileInfo{ QStringLiteral("parentFolder"), { FileInfo{ QStringLiteral("subFolderA"), { { QStringLiteral("fileA.txt"), 400 } } }, FileInfo{ QStringLiteral("subFolderB"), { { QStringLiteral("fileB.txt"), 400 } } } } } } } }; + FakeFolder fakeFolder(FileInfo { QString(), { FileInfo { QStringLiteral("parentFolder"), { FileInfo { QStringLiteral("subFolderA"), { { QStringLiteral("fileA.txt"), 400 } } }, FileInfo { QStringLiteral("subFolderB"), { { QStringLiteral("fileB.txt"), 400 } } } } } } }, vfsMode, filesAreDehydrated); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); auto expectedServerState = fakeFolder.currentRemoteState(); // Remove subFolderA with selectiveSync: - fakeFolder.syncEngine().journal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, - { "parentFolder/subFolderA/" }); - fakeFolder.syncEngine().journal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/")); + fakeFolder.syncJournal()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, {QStringLiteral("parentFolder/subFolderA/")}); + fakeFolder.syncJournal()->schedulePathForRemoteDiscovery(QByteArrayLiteral("parentFolder/subFolderA/")); - fakeFolder.syncOnce(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); { // Nothing changed on the server QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState); // The local state should not have subFolderA auto remoteState = fakeFolder.currentRemoteState(); - remoteState.remove("parentFolder/subFolderA"); + remoteState.remove(QStringLiteral("parentFolder/subFolderA")); QCOMPARE(fakeFolder.currentLocalState(), remoteState); } // Rename parentFolder on the server - fakeFolder.remoteModifier().rename("parentFolder", "parentFolderRenamed"); + fakeFolder.remoteModifier().rename(QStringLiteral("parentFolder"), QStringLiteral("parentFolderRenamed")); expectedServerState = fakeFolder.currentRemoteState(); - fakeFolder.syncOnce(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); { QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState); auto remoteState = fakeFolder.currentRemoteState(); // The subFolderA should still be there on the server. - QVERIFY(remoteState.find("parentFolderRenamed/subFolderA/fileA.txt")); + QVERIFY(remoteState.find(QStringLiteral("parentFolderRenamed/subFolderA/fileA.txt"))); // But not on the client because of the selective sync - remoteState.remove("parentFolderRenamed/subFolderA"); + remoteState.remove(QStringLiteral("parentFolderRenamed/subFolderA")); QCOMPARE(fakeFolder.currentLocalState(), remoteState); } // Rename it again, locally this time. - fakeFolder.localModifier().rename("parentFolderRenamed", "parentThirdName"); - fakeFolder.syncOnce(); + fakeFolder.localModifier().rename(QStringLiteral("parentFolderRenamed"), QStringLiteral("parentThirdName")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); { auto remoteState = fakeFolder.currentRemoteState(); // The subFolderA should still be there on the server. - QVERIFY(remoteState.find("parentThirdName/subFolderA/fileA.txt")); + QVERIFY(remoteState.find(QStringLiteral("parentThirdName/subFolderA/fileA.txt"))); // But not on the client because of the selective sync - remoteState.remove("parentThirdName/subFolderA"); + remoteState.remove(QStringLiteral("parentThirdName/subFolderA")); QCOMPARE(fakeFolder.currentLocalState(), remoteState); expectedServerState = fakeFolder.currentRemoteState(); ItemCompletedSpy completeSpy(fakeFolder); - fakeFolder.syncOnce(); // This sync should do nothing + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // This sync should do nothing QCOMPARE(completeSpy.count(), 0); QCOMPARE(fakeFolder.currentRemoteState(), expectedServerState); @@ -168,87 +169,122 @@ private slots: void testLocalMoveDetection() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - - int nPUT = 0; - int nDELETE = 0; - fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) { - if (op == QNetworkAccessManager::PutOperation) - ++nPUT; - if (op == QNetworkAccessManager::DeleteOperation) - ++nDELETE; - return nullptr; - }); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + fakeFolder.account()->setCapabilities({fakeFolder.account()->url(), TestUtils::testCapabilities(CheckSums::Algorithm::ADLER32)}); + + OperationCounter counter(fakeFolder); // For directly editing the remote checksum FileInfo &remoteInfo = fakeFolder.remoteModifier(); - // Simple move causing a remote rename - fakeFolder.localModifier().rename("A/a1", "A/a1m"); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.localModifier().rename(QStringLiteral("A/a1"), QStringLiteral("A/a1m")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), remoteInfo); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo)); - QCOMPARE(nPUT, 0); + QCOMPARE(counter.nGET, 0); + QCOMPARE(counter.nPUT, 0); + QCOMPARE(counter.nMOVE, 1); + QCOMPARE(counter.nDELETE, 0); + counter.reset(); - // Move-and-change, causing a upload and delete - fakeFolder.localModifier().rename("A/a2", "A/a2m"); - fakeFolder.localModifier().appendByte("A/a2m"); - QVERIFY(fakeFolder.syncOnce()); + // Move-and-change, mtime+size, causing a upload and delete + QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("A/a2"))->isDehydratedPlaceholder + == filesAreDehydrated); // no-one touched it, so the hydration state should be the same as the initial state + auto mt = fakeFolder.currentLocalState().find(QStringLiteral("A/a2"))->lastModified(); + QVERIFY(mt.toSecsSinceEpoch() + 1 < QDateTime::currentDateTime().toSecsSinceEpoch()); + fakeFolder.localModifier().rename(QStringLiteral("A/a2"), QStringLiteral("A/a2m")); + fakeFolder.localModifier().setContents(QStringLiteral("A/a2m"), fakeFolder.remoteModifier().contentSize + 1, 'x'); + fakeFolder.localModifier().setModTime(QStringLiteral("A/a2m"), mt.addSecs(1)); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(!fakeFolder.currentLocalState() + .find(QStringLiteral("A/a2m")) + ->isDehydratedPlaceholder); // We overwrote all data in the file, so whatever the state was before, it is no longer dehydrated QCOMPARE(fakeFolder.currentLocalState(), remoteInfo); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo)); - QCOMPARE(nPUT, 1); - QCOMPARE(nDELETE, 1); - + QCOMPARE(counter.nGET, filesAreDehydrated ? 1 : 0); // on winvfs, with a dehydrated file, the os will try to hydrate the file before we write to it. When the file is hydrated, it doesn't need to be fetched. + QCOMPARE(counter.nMOVE, 0); // we cannot detect moves (and we didn't implement it yet in winvfs), so ... + QCOMPARE(counter.nDELETE, 1); // ... the file just disappears, and ... + QCOMPARE(counter.nPUT, 1); // ... another file (with just 1 byte difference) appears somewhere else. Coincidence. + counter.reset(); // Move-and-change, mtime+content only - fakeFolder.localModifier().rename("B/b1", "B/b1m"); - fakeFolder.localModifier().setContents("B/b1m", 'C'); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.localModifier().rename(QStringLiteral("B/b1"), QStringLiteral("B/b1m")); + fakeFolder.localModifier().setContents(QStringLiteral("B/b1m"), FileModifier::DefaultFileSize, 'C'); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), remoteInfo); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo)); - QCOMPARE(nPUT, 2); - QCOMPARE(nDELETE, 2); + QCOMPARE(counter.nPUT, 1); + QCOMPARE(counter.nDELETE, 1); + counter.reset(); // Move-and-change, size+content only - auto mtime = fakeFolder.remoteModifier().find("B/b2")->lastModified; - fakeFolder.localModifier().rename("B/b2", "B/b2m"); - fakeFolder.localModifier().appendByte("B/b2m"); - fakeFolder.localModifier().setModTime("B/b2m", mtime); - QVERIFY(fakeFolder.syncOnce()); + auto mtime = fakeFolder.remoteModifier().find(QStringLiteral("B/b2"))->lastModified(); + fakeFolder.localModifier().rename(QStringLiteral("B/b2"), QStringLiteral("B/b2m")); + fakeFolder.localModifier().appendByte(QStringLiteral("B/b2m")); + fakeFolder.localModifier().setModTime(QStringLiteral("B/b2m"), mtime); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), remoteInfo); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo)); - QCOMPARE(nPUT, 3); - QCOMPARE(nDELETE, 3); - - // Move-and-change, content only -- c1 has no checksum, so we fail to detect this! - // NOTE: This is an expected failure. - mtime = fakeFolder.remoteModifier().find("C/c1")->lastModified; - fakeFolder.localModifier().rename("C/c1", "C/c1m"); - fakeFolder.localModifier().setContents("C/c1m", 'C'); - fakeFolder.localModifier().setModTime("C/c1m", mtime); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(nPUT, 3); - QCOMPARE(nDELETE, 3); - QVERIFY(!(fakeFolder.currentLocalState() == remoteInfo)); - - // cleanup, and upload a file that will have a checksum in the db - fakeFolder.localModifier().remove("C/c1m"); - fakeFolder.localModifier().insert("C/c3"); - QVERIFY(fakeFolder.syncOnce()); + if (vfsMode == Vfs::Off) { + QCOMPARE(counter.nGET, 0); // b2m is detected as a *new* file, so we don't need to fetch the contents + } else { + QCOMPARE(counter.nGET, 1); // b2 is accessed, so we get a callback to download the file + } + QCOMPARE(counter.nMOVE, 0); // content differs, so not a move + QCOMPARE(counter.nPUT, 1); // upload b2m + QCOMPARE(counter.nDELETE, 1); // delete b2 + counter.reset(); + + // WinVFS handles this just fine. + if (vfsMode == Vfs::Off) { + // Move-and-change, content only -- c1 has no checksum, so we fail to detect this! + // NOTE: This is an expected failure. + mtime = fakeFolder.remoteModifier().find(QStringLiteral("C/c1"))->lastModified(); + auto size = fakeFolder.currentRemoteState().find(QStringLiteral("C/c1"))->contentSize; + fakeFolder.localModifier().rename(QStringLiteral("C/c1"), QStringLiteral("C/c1m")); + fakeFolder.localModifier().setContents(QStringLiteral("C/c1m"), size, 'C'); + fakeFolder.localModifier().setModTime(QStringLiteral("C/c1m"), mtime); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(counter.nPUT, 0); + QCOMPARE(counter.nDELETE, 0); + QVERIFY(fakeFolder.currentLocalState() != remoteInfo); + counter.reset(); + + // cleanup, and upload a file that will have a checksum in the db + fakeFolder.localModifier().remove(QStringLiteral("C/c1m")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(counter.nDELETE, 1); + counter.reset(); + } else { + // no rename happened, nothing to clean up + } + + fakeFolder.localModifier().insert( + QStringLiteral("C/c3"), 13_B, 'E'); // 13, because c1 (and c2) have a size of 24 bytes, so this file is clearly different + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), remoteInfo); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo)); - QCOMPARE(nPUT, 4); - QCOMPARE(nDELETE, 4); + QCOMPARE(counter.nGET, 0); + QCOMPARE(counter.nMOVE, 0); + QCOMPARE(counter.nPUT, 1); + QCOMPARE(counter.nDELETE, 0); + counter.reset(); // Move-and-change, content only, this time while having a checksum - mtime = fakeFolder.remoteModifier().find("C/c3")->lastModified; - fakeFolder.localModifier().rename("C/c3", "C/c3m"); - fakeFolder.localModifier().setContents("C/c3m", 'C'); - fakeFolder.localModifier().setModTime("C/c3m", mtime); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(nPUT, 5); - QCOMPARE(nDELETE, 5); + mtime = fakeFolder.remoteModifier().find(QStringLiteral("C/c3"))->lastModified(); + fakeFolder.localModifier().rename(QStringLiteral("C/c3"), QStringLiteral("C/c3m")); + fakeFolder.localModifier().setContents(QStringLiteral("C/c3m"), FileModifier::DefaultFileSize, 'C'); + fakeFolder.localModifier().setModTime(QStringLiteral("C/c3m"), mtime); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(counter.nGET, 0); + QCOMPARE(counter.nMOVE, 0); + QCOMPARE(counter.nPUT, 1); + QCOMPARE(counter.nDELETE, 1); QCOMPARE(fakeFolder.currentLocalState(), remoteInfo); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(remoteInfo)); + counter.reset(); } void testDuplicateFileId_data() @@ -268,62 +304,67 @@ private slots: // case. void testDuplicateFileId() { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); QFETCH(QString, prefix); - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + if (filesAreDehydrated) { + QSKIP("This test expects to be able to modify local files on disk, which does not work with dehydrated files."); + } + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); auto &remote = fakeFolder.remoteModifier(); - remote.mkdir("A/W"); - remote.insert("A/W/w1"); - remote.mkdir("A/Q"); + remote.mkdir(QStringLiteral("A/W")); + remote.insert(QStringLiteral("A/W/w1")); + remote.mkdir(QStringLiteral("A/Q")); // Duplicate every entry in A under O/A remote.mkdir(prefix); - remote.children[prefix].addChild(remote.children["A"]); + remote.children[prefix].addChild(remote.children[QStringLiteral("A")]); // This already checks that the rename detection doesn't get // horribly confused if we add new files that have the same // fileid as existing ones - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - OperationCounter counter; - fakeFolder.setServerOverride(counter.functor()); + OperationCounter counter(fakeFolder); // Try a remote file move - remote.rename("A/a1", "A/W/a1m"); - remote.rename(prefix + "/A/a1", prefix + "/A/W/a1m"); - QVERIFY(fakeFolder.syncOnce()); + remote.rename(QStringLiteral("A/a1"), QStringLiteral("A/W/a1m")); + remote.rename(prefix + QStringLiteral("/A/a1"), prefix + QStringLiteral("/A/W/a1m")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nGET, 0); // And a remote directory move - remote.rename("A/W", "A/Q/W"); - remote.rename(prefix + "/A/W", prefix + "/A/Q/W"); - QVERIFY(fakeFolder.syncOnce()); + remote.rename(QStringLiteral("A/W"), QStringLiteral("A/Q/W")); + remote.rename(prefix + QStringLiteral("/A/W"), prefix + QStringLiteral("/A/Q/W")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nGET, 0); // Partial file removal (in practice, A/a2 may be moved to O/a2, but we don't care) - remote.rename(prefix + "/A/a2", prefix + "/a2"); - remote.remove("A/a2"); - QVERIFY(fakeFolder.syncOnce()); + remote.rename(prefix + QStringLiteral("/A/a2"), prefix + QStringLiteral("/a2")); + remote.remove(QStringLiteral("A/a2")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nGET, 0); // Local change plus remote move at the same time - fakeFolder.localModifier().appendByte(prefix + "/a2"); - remote.rename(prefix + "/a2", prefix + "/a3"); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.localModifier().appendByte(prefix + QStringLiteral("/a2")); + remote.rename(prefix + QStringLiteral("/a2"), prefix + QStringLiteral("/a3")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nGET, 1); counter.reset(); // remove localy, and remote move at the same time - fakeFolder.localModifier().remove("A/Q/W/a1m"); - remote.rename("A/Q/W/a1m", "A/Q/W/a1p"); - remote.rename(prefix + "/A/Q/W/a1m", prefix + "/A/Q/W/a1p"); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.localModifier().remove(QStringLiteral("A/Q/W/a1m")); + remote.rename(QStringLiteral("A/Q/W/a1m"), QStringLiteral("A/Q/W/a1p")); + remote.rename(prefix + QStringLiteral("/A/Q/W/a1m"), prefix + QStringLiteral("/A/Q/W/a1p")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nGET, 1); counter.reset(); @@ -331,124 +372,150 @@ private slots: void testMovePropagation() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); auto &local = fakeFolder.localModifier(); auto &remote = fakeFolder.remoteModifier(); - OperationCounter counter; - fakeFolder.setServerOverride(counter.functor()); + OperationCounter counter(fakeFolder); + + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + counter.reset(); // Move { - counter.reset(); - local.rename("A/a1", "A/a1m"); - remote.rename("B/b1", "B/b1m"); + local.rename(QStringLiteral("A/a1"), QStringLiteral("A/a1m")); + remote.rename(QStringLiteral("B/b1"), QStringLiteral("B/b1m")); ItemCompletedSpy completeSpy(fakeFolder); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nGET, 0); QCOMPARE(counter.nPUT, 0); QCOMPARE(counter.nMOVE, 1); QCOMPARE(counter.nDELETE, 0); - QVERIFY(itemSuccessfulMove(completeSpy, "A/a1m")); - QVERIFY(itemSuccessfulMove(completeSpy, "B/b1m")); - QCOMPARE(completeSpy.findItem("A/a1m")->_file, QStringLiteral("A/a1")); - QCOMPARE(completeSpy.findItem("A/a1m")->_renameTarget, QStringLiteral("A/a1m")); - QCOMPARE(completeSpy.findItem("B/b1m")->_file, QStringLiteral("B/b1")); - QCOMPARE(completeSpy.findItem("B/b1m")->_renameTarget, QStringLiteral("B/b1m")); + QVERIFY(itemSuccessfulMove(completeSpy, QStringLiteral("A/a1m"))); + QVERIFY(itemSuccessfulMove(completeSpy, QStringLiteral("B/b1m"))); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1m"))->_file, QStringLiteral("A/a1")); + QCOMPARE(completeSpy.findItem(QStringLiteral("A/a1m"))->_renameTarget, QStringLiteral("A/a1m")); + QCOMPARE(completeSpy.findItem(QStringLiteral("B/b1m"))->_file, QStringLiteral("B/b1")); + QCOMPARE(completeSpy.findItem(QStringLiteral("B/b1m"))->_renameTarget, QStringLiteral("B/b1m")); + counter.reset(); } // Touch+Move on same side - counter.reset(); - local.rename("A/a2", "A/a2m"); - local.setContents("A/a2m", 'A'); - remote.rename("B/b2", "B/b2m"); - remote.setContents("B/b2m", 'A'); - QVERIFY(fakeFolder.syncOnce()); + local.rename(QStringLiteral("A/a2"), QStringLiteral("A/a2m")); + local.setContents(QStringLiteral("A/a2m"), FileModifier::DefaultFileSize, 'A'); + remote.rename(QStringLiteral("B/b2"), QStringLiteral("B/b2m")); + remote.setContents(QStringLiteral("B/b2m"), FileModifier::DefaultFileSize, 'A'); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); QCOMPARE(counter.nGET, 1); QCOMPARE(counter.nPUT, 1); QCOMPARE(counter.nMOVE, 0); QCOMPARE(counter.nDELETE, 1); - QCOMPARE(remote.find("A/a2m")->contentChar, 'A'); - QCOMPARE(remote.find("B/b2m")->contentChar, 'A'); + QCOMPARE(remote.find(QStringLiteral("A/a2m"))->contentChar, 'A'); + QCOMPARE(remote.find(QStringLiteral("B/b2m"))->contentChar, 'A'); + counter.reset(); // Touch+Move on opposite sides - counter.reset(); - local.rename("A/a1m", "A/a1m2"); - remote.setContents("A/a1m", 'B'); - remote.rename("B/b1m", "B/b1m2"); - local.setContents("B/b1m", 'B'); - QVERIFY(fakeFolder.syncOnce()); + local.rename(QStringLiteral("A/a1m"), QStringLiteral("A/a1m2")); + remote.setContents(QStringLiteral("A/a1m"), FileModifier::DefaultFileSize, 'B'); + remote.rename(QStringLiteral("B/b1m"), QStringLiteral("B/b1m2")); + local.setContents(QStringLiteral("B/b1m"), FileModifier::DefaultFileSize, 'B'); + + if (filesAreDehydrated) { + // When a placeholder is accessed, a hydration request is made. This will fail, because + // the file (b1m) is no longer available on the server. We then return an error, so the + // helper will fail to do the local modifications. + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); + QSKIP("Behaviour for dehydrated files is different at this point compared to no-VFS"); + } + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); QCOMPARE(counter.nGET, 2); QCOMPARE(counter.nPUT, 2); QCOMPARE(counter.nMOVE, 0); QCOMPARE(counter.nDELETE, 0); + // All these files existing afterwards is debatable. Should we propagate // the rename in one direction and grab the new contents in the other? // Currently there's no propagation job that would do that, and this does // at least not lose data. - QCOMPARE(remote.find("A/a1m")->contentChar, 'B'); - QCOMPARE(remote.find("B/b1m")->contentChar, 'B'); - QCOMPARE(remote.find("A/a1m2")->contentChar, 'W'); - QCOMPARE(remote.find("B/b1m2")->contentChar, 'W'); + QVERIFY(remote.find(QStringLiteral("A/a1m"))->contentChar == 'B'); + QVERIFY(remote.find(QStringLiteral("B/b1m"))->contentChar == 'B'); + QVERIFY(remote.find(QStringLiteral("A/a1m2"))->contentChar == 'W'); + QVERIFY(remote.find(QStringLiteral("B/b1m2"))->contentChar == 'W'); + QCOMPARE(remote.find(QStringLiteral("A/a1m"))->contentChar, 'B'); + QCOMPARE(remote.find(QStringLiteral("B/b1m"))->contentChar, 'B'); + QCOMPARE(remote.find(QStringLiteral("A/a1m2"))->contentChar, 'W'); + QCOMPARE(remote.find(QStringLiteral("B/b1m2"))->contentChar, 'W'); + counter.reset(); // Touch+create on one side, move on the other { - counter.reset(); - local.appendByte("A/a1m"); - local.insert("A/a1mt"); - remote.rename("A/a1m", "A/a1mt"); - remote.appendByte("B/b1m"); - remote.insert("B/b1mt"); - local.rename("B/b1m", "B/b1mt"); + local.appendByte(QStringLiteral("A/a1m")); + local.insert(QStringLiteral("A/a1mt")); + remote.rename(QStringLiteral("A/a1m"), QStringLiteral("A/a1mt")); + remote.appendByte(QStringLiteral("B/b1m")); + remote.insert(QStringLiteral("B/b1mt")); + local.rename(QStringLiteral("B/b1m"), QStringLiteral("B/b1mt")); ItemCompletedSpy completeSpy(fakeFolder); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), "A/a1mt")); - QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), "B/b1mt")); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + // First check the counters: QCOMPARE(counter.nGET, 3); QCOMPARE(counter.nPUT, 1); QCOMPARE(counter.nMOVE, 0); QCOMPARE(counter.nDELETE, 0); - QVERIFY(itemSuccessful(completeSpy, "A/a1m", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemSuccessful(completeSpy, "B/b1m", CSYNC_INSTRUCTION_NEW)); - QVERIFY(itemConflict(completeSpy, "A/a1mt")); - QVERIFY(itemConflict(completeSpy, "B/b1mt")); + // Ok, now we can remove the conflicting files. This needs disk access, so it might trigger server interaction. (Hence checking the counters before we do this.) + QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), QStringLiteral("A/a1mt"))); + QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), QStringLiteral("B/b1mt"))); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + // Now we can compare the clean-up states: + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("A/a1m"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("B/b1m"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(itemConflict(completeSpy, QStringLiteral("A/a1mt"))); + QVERIFY(itemConflict(completeSpy, QStringLiteral("B/b1mt"))); + counter.reset(); } // Create new on one side, move to new on the other { - counter.reset(); - local.insert("A/a1N", 13); - remote.rename("A/a1mt", "A/a1N"); - remote.insert("B/b1N", 13); - local.rename("B/b1mt", "B/b1N"); + local.insert(QStringLiteral("A/a1N"), 13_B); + remote.rename(QStringLiteral("A/a1mt"), QStringLiteral("A/a1N")); + remote.insert(QStringLiteral("B/b1N"), 13_B); + local.rename(QStringLiteral("B/b1mt"), QStringLiteral("B/b1N")); ItemCompletedSpy completeSpy(fakeFolder); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), "A/a1N")); - QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), "B/b1N")); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + // First check the counters: QCOMPARE(counter.nGET, 2); QCOMPARE(counter.nPUT, 0); QCOMPARE(counter.nMOVE, 0); QCOMPARE(counter.nDELETE, 1); - QVERIFY(itemSuccessful(completeSpy, "A/a1mt", CSYNC_INSTRUCTION_REMOVE)); - QVERIFY(itemSuccessful(completeSpy, "B/b1mt", CSYNC_INSTRUCTION_REMOVE)); - QVERIFY(itemConflict(completeSpy, "A/a1N")); - QVERIFY(itemConflict(completeSpy, "B/b1N")); + // Ok, now we can remove the conflicting files. This needs disk access, so it might trigger server interaction. (Hence checking the counters before we do this.) + QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), QStringLiteral("A/a1N"))); + QVERIFY(expectAndWipeConflict(local, fakeFolder.currentLocalState(), QStringLiteral("B/b1N"))); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + // Now we can compare the clean-up states: + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("A/a1mt"), CSYNC_INSTRUCTION_REMOVE)); + QVERIFY(itemSuccessful(completeSpy, QStringLiteral("B/b1mt"), CSYNC_INSTRUCTION_REMOVE)); + QVERIFY(itemConflict(completeSpy, QStringLiteral("A/a1N"))); + QVERIFY(itemConflict(completeSpy, QStringLiteral("B/b1N"))); + counter.reset(); } // Local move, remote move - counter.reset(); - local.rename("C/c1", "C/c1mL"); - remote.rename("C/c1", "C/c1mR"); - QVERIFY(fakeFolder.syncOnce()); + local.rename(QStringLiteral("C/c1"), QStringLiteral("C/c1mL")); + remote.rename(QStringLiteral("C/c1"), QStringLiteral("C/c1mR")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // end up with both files QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); @@ -459,9 +526,9 @@ private slots: // Rename/rename conflict on a folder counter.reset(); - remote.rename("C", "CMR"); - local.rename("C", "CML"); - QVERIFY(fakeFolder.syncOnce()); + remote.rename(QStringLiteral("C"), QStringLiteral("CMR")); + local.rename(QStringLiteral("C"), QStringLiteral("CML")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); // End up with both folders QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); @@ -469,118 +536,122 @@ private slots: QCOMPARE(counter.nPUT, 3); QCOMPARE(counter.nMOVE, 0); QCOMPARE(counter.nDELETE, 0); + counter.reset(); // Folder move { - counter.reset(); - local.rename("A", "AM"); - remote.rename("B", "BM"); + local.rename(QStringLiteral("A"), QStringLiteral("AM")); + remote.rename(QStringLiteral("B"), QStringLiteral("BM")); ItemCompletedSpy completeSpy(fakeFolder); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); QCOMPARE(counter.nGET, 0); QCOMPARE(counter.nPUT, 0); QCOMPARE(counter.nMOVE, 1); QCOMPARE(counter.nDELETE, 0); - QVERIFY(itemSuccessfulMove(completeSpy, "AM")); - QVERIFY(itemSuccessfulMove(completeSpy, "BM")); - QCOMPARE(completeSpy.findItem("AM")->_file, QStringLiteral("A")); - QCOMPARE(completeSpy.findItem("AM")->_renameTarget, QStringLiteral("AM")); - QCOMPARE(completeSpy.findItem("BM")->_file, QStringLiteral("B")); - QCOMPARE(completeSpy.findItem("BM")->_renameTarget, QStringLiteral("BM")); + QVERIFY(itemSuccessfulMove(completeSpy, QStringLiteral("AM"))); + QVERIFY(itemSuccessfulMove(completeSpy, QStringLiteral("BM"))); + QCOMPARE(completeSpy.findItem(QStringLiteral("AM"))->_file, QStringLiteral("A")); + QCOMPARE(completeSpy.findItem(QStringLiteral("AM"))->_renameTarget, QStringLiteral("AM")); + QCOMPARE(completeSpy.findItem(QStringLiteral("BM"))->_file, QStringLiteral("B")); + QCOMPARE(completeSpy.findItem(QStringLiteral("BM"))->_renameTarget, QStringLiteral("BM")); + counter.reset(); } // Folder move with contents touched on the same side { - counter.reset(); - local.setContents("AM/a2m", 'C'); + local.setContents(QStringLiteral("AM/a2m"), FileModifier::DefaultFileSize, 'C'); // We must change the modtime for it is likely that it did not change between sync. // (Previous version of the client (<=2.5) would not need this because it was always doing // checksum comparison for all renames. But newer version no longer does it if the file is // renamed because the parent folder is renamed) - local.setModTime("AM/a2m", QDateTime::currentDateTimeUtc().addDays(3)); - local.rename("AM", "A2"); - remote.setContents("BM/b2m", 'C'); - remote.rename("BM", "B2"); + local.setModTime(QStringLiteral("AM/a2m"), QDateTime::currentDateTimeUtc().addDays(3)); + local.rename(QStringLiteral("AM"), QStringLiteral("A2")); + remote.setContents(QStringLiteral("BM/b2m"), FileModifier::DefaultFileSize, 'C'); + remote.rename(QStringLiteral("BM"), QStringLiteral("B2")); ItemCompletedSpy completeSpy(fakeFolder); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); QCOMPARE(counter.nGET, 1); QCOMPARE(counter.nPUT, 1); QCOMPARE(counter.nMOVE, 1); QCOMPARE(counter.nDELETE, 0); - QCOMPARE(remote.find("A2/a2m")->contentChar, 'C'); - QCOMPARE(remote.find("B2/b2m")->contentChar, 'C'); - QVERIFY(itemSuccessfulMove(completeSpy, "A2")); - QVERIFY(itemSuccessfulMove(completeSpy, "B2")); + QCOMPARE(remote.find(QStringLiteral("A2/a2m"))->contentChar, 'C'); + QCOMPARE(remote.find(QStringLiteral("B2/b2m"))->contentChar, 'C'); + QVERIFY(itemSuccessfulMove(completeSpy, QStringLiteral("A2"))); + QVERIFY(itemSuccessfulMove(completeSpy, QStringLiteral("B2"))); + counter.reset(); } // Folder rename with contents touched on the other tree - counter.reset(); - remote.setContents("A2/a2m", 'D'); + remote.setContents(QStringLiteral("A2/a2m"), FileModifier::DefaultFileSize, 'D'); // setContents alone may not produce updated mtime if the test is fast // and since we don't use checksums here, that matters. - remote.appendByte("A2/a2m"); - local.rename("A2", "A3"); - local.setContents("B2/b2m", 'D'); - local.appendByte("B2/b2m"); - remote.rename("B2", "B3"); - QVERIFY(fakeFolder.syncOnce()); + remote.appendByte(QStringLiteral("A2/a2m")); + local.rename(QStringLiteral("A2"), QStringLiteral("A3")); + local.setContents(QStringLiteral("B2/b2m"), FileModifier::DefaultFileSize, 'D'); + local.appendByte(QStringLiteral("B2/b2m"), 'D'); + remote.rename(QStringLiteral("B2"), QStringLiteral("B3")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); QCOMPARE(counter.nGET, 1); QCOMPARE(counter.nPUT, 1); QCOMPARE(counter.nMOVE, 1); QCOMPARE(counter.nDELETE, 0); - QCOMPARE(remote.find("A3/a2m")->contentChar, 'D'); - QCOMPARE(remote.find("B3/b2m")->contentChar, 'D'); + QCOMPARE(remote.find(QStringLiteral("A3/a2m"))->contentChar, 'D'); + QCOMPARE(remote.find(QStringLiteral("B3/b2m"))->contentChar, 'D'); + counter.reset(); // Folder rename with contents touched on both ends - counter.reset(); - remote.setContents("A3/a2m", 'R'); - remote.appendByte("A3/a2m"); - local.setContents("A3/a2m", 'L'); - local.appendByte("A3/a2m"); - local.appendByte("A3/a2m"); - local.rename("A3", "A4"); - remote.setContents("B3/b2m", 'R'); - remote.appendByte("B3/b2m"); - local.setContents("B3/b2m", 'L'); - local.appendByte("B3/b2m"); - local.appendByte("B3/b2m"); - remote.rename("B3", "B4"); - QVERIFY(fakeFolder.syncOnce()); + remote.setContents(QStringLiteral("A3/a2m"), FileModifier::DefaultFileSize, 'R'); + remote.appendByte(QStringLiteral("A3/a2m"), 'R'); + local.setContents(QStringLiteral("A3/a2m"), FileModifier::DefaultFileSize, 'L'); + local.appendByte(QStringLiteral("A3/a2m"), 'L'); + local.appendByte(QStringLiteral("A3/a2m"), 'L'); + local.rename(QStringLiteral("A3"), QStringLiteral("A4")); + remote.setContents(QStringLiteral("B3/b2m"), FileModifier::DefaultFileSize, 'R'); + remote.appendByte(QStringLiteral("B3/b2m"), 'R'); + local.setContents(QStringLiteral("B3/b2m"), FileModifier::DefaultFileSize, 'L'); + local.appendByte(QStringLiteral("B3/b2m"), 'L'); + local.appendByte(QStringLiteral("B3/b2m"), 'L'); + remote.rename(QStringLiteral("B3"), QStringLiteral("B4")); + QThread::sleep(1); // This test is timing-sensitive. No idea why, it's probably the modtime on the client side. + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + qDebug() << counter; + QCOMPARE(counter.nGET, 2); + QCOMPARE(counter.nPUT, 0); + QCOMPARE(counter.nMOVE, 1); + QCOMPARE(counter.nDELETE, 0); auto currentLocal = fakeFolder.currentLocalState(); - auto conflicts = findConflicts(currentLocal.children["A4"]); + auto conflicts = findConflicts(currentLocal.children[QStringLiteral("A4")]); QCOMPARE(conflicts.size(), 1); - for (const auto &c : qAsConst(conflicts)) { + for (const auto &c : std::as_const(conflicts)) { + qDebug() << c; QCOMPARE(currentLocal.find(c)->contentChar, 'L'); local.remove(c); } - conflicts = findConflicts(currentLocal.children["B4"]); + conflicts = findConflicts(currentLocal.children[QStringLiteral("B4")]); QCOMPARE(conflicts.size(), 1); - for (const auto &c : qAsConst(conflicts)) { + for (const auto &c : std::as_const(conflicts)) { QCOMPARE(currentLocal.find(c)->contentChar, 'L'); local.remove(c); } + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); - QCOMPARE(counter.nGET, 2); - QCOMPARE(counter.nPUT, 0); - QCOMPARE(counter.nMOVE, 1); - QCOMPARE(counter.nDELETE, 0); - QCOMPARE(remote.find("A4/a2m")->contentChar, 'R'); - QCOMPARE(remote.find("B4/b2m")->contentChar, 'R'); + QCOMPARE(remote.find(QStringLiteral("A4/a2m"))->contentChar, 'R'); + QCOMPARE(remote.find(QStringLiteral("B4/b2m"))->contentChar, 'R'); + counter.reset(); // Rename a folder and rename the contents at the same time - counter.reset(); - local.rename("A4/a2m", "A4/a2m2"); - local.rename("A4", "A5"); - remote.rename("B4/b2m", "B4/b2m2"); - remote.rename("B4", "B5"); - QVERIFY(fakeFolder.syncOnce()); + local.rename(QStringLiteral("A4/a2m"), QStringLiteral("A4/a2m2")); + local.rename(QStringLiteral("A4"), QStringLiteral("A5")); + remote.rename(QStringLiteral("B4/b2m"), QStringLiteral("B4/b2m2")); + remote.rename(QStringLiteral("B4"), QStringLiteral("B5")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); QCOMPARE(counter.nGET, 0); @@ -592,17 +663,19 @@ private slots: // These renames can be troublesome on windows void testRenameCaseOnly() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); auto &local = fakeFolder.localModifier(); auto &remote = fakeFolder.remoteModifier(); - OperationCounter counter; - fakeFolder.setServerOverride(counter.functor()); + OperationCounter counter(fakeFolder); - local.rename("A/a1", "A/A1"); - remote.rename("A/a2", "A/A2"); + local.rename(QStringLiteral("A/a1"), QStringLiteral("A/A1")); + remote.rename(QStringLiteral("A/a2"), QStringLiteral("A/A2")); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentLocalState(), remote); QCOMPARE(printDbData(fakeFolder.dbState()), printDbData(fakeFolder.currentRemoteState())); QCOMPARE(counter.nGET, 0); @@ -614,22 +687,35 @@ private slots: // Check interaction of moves with file type changes void testMoveAndTypeChange() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); auto &local = fakeFolder.localModifier(); auto &remote = fakeFolder.remoteModifier(); // Touch on one side, rename and mkdir on the other { - local.appendByte("A/a1"); - remote.rename("A/a1", "A/a1mq"); - remote.mkdir("A/a1"); - remote.appendByte("B/b1"); - local.rename("B/b1", "B/b1mq"); - local.mkdir("B/b1"); + local.appendByte(QStringLiteral("A/a1")); + remote.rename(QStringLiteral("A/a1"), QStringLiteral("A/a1mq")); + remote.mkdir(QStringLiteral("A/a1")); + remote.appendByte(QStringLiteral("B/b1")); + local.rename(QStringLiteral("B/b1"), QStringLiteral("B/b1mq")); + local.mkdir(QStringLiteral("B/b1")); ItemCompletedSpy completeSpy(fakeFolder); - QVERIFY(fakeFolder.syncOnce()); - // BUG: This doesn't behave right - //QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + if (filesAreDehydrated) { + // the dehydrating the placeholder failed as the metadata is out of sync + QSignalSpy spy(fakeFolder.vfs(), &Vfs::needSync); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(spy.count() == 1); + QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } else { + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(findConflicts(fakeFolder.currentLocalState().children[QStringLiteral("A")]).size(), 1); + QCOMPARE(findConflicts(fakeFolder.currentLocalState().children[QStringLiteral("B")]).size(), 1); + } } } @@ -637,89 +723,88 @@ private slots: // When a file is moved and the server mtime was not in sync, the local mtime should be kept void testMoveAndMTimeChange() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - OperationCounter counter; - fakeFolder.setServerOverride(counter.functor()); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + OperationCounter counter(fakeFolder); // Changing the mtime on the server (without invalidating the etag) - fakeFolder.remoteModifier().find("A/a1")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-50000); - fakeFolder.remoteModifier().find("A/a2")->lastModified = QDateTime::currentDateTimeUtc().addSecs(-40000); + fakeFolder.remoteModifier().find(QStringLiteral("A/a1"))->setLastModified(QDateTime::currentDateTime().addSecs(-50000)); + fakeFolder.remoteModifier().find(QStringLiteral("A/a2"))->setLastModified(QDateTime::currentDateTime().addSecs(-40000)); // Move a few files - fakeFolder.remoteModifier().rename("A/a1", "A/a1_server_renamed"); - fakeFolder.localModifier().rename("A/a2", "A/a2_local_renamed"); + fakeFolder.remoteModifier().rename(QStringLiteral("A/a1"), QStringLiteral("A/a1_server_renamed")); + fakeFolder.localModifier().rename(QStringLiteral("A/a2"), QStringLiteral("A/a2_local_renamed")); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(counter.nGET, 0); QCOMPARE(counter.nPUT, 0); QCOMPARE(counter.nMOVE, 1); QCOMPARE(counter.nDELETE, 0); // Another sync should do nothing - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(counter.nGET, 0); QCOMPARE(counter.nPUT, 0); QCOMPARE(counter.nMOVE, 1); QCOMPARE(counter.nDELETE, 0); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + // Check that everything other than the mtime is still equal: + QVERIFY(fakeFolder.currentLocalState().equals(fakeFolder.currentRemoteState(), FileInfo::IgnoreLastModified)); } // Test for https://github.com/owncloud/client/issues/6694 void testInvertFolderHierarchy() { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - fakeFolder.remoteModifier().mkdir("A/Empty"); - fakeFolder.remoteModifier().mkdir("A/Empty/Foo"); - fakeFolder.remoteModifier().mkdir("C/AllEmpty"); - fakeFolder.remoteModifier().mkdir("C/AllEmpty/Bar"); - fakeFolder.remoteModifier().insert("A/Empty/f1"); - fakeFolder.remoteModifier().insert("A/Empty/Foo/f2"); - fakeFolder.remoteModifier().mkdir("C/AllEmpty/f3"); - fakeFolder.remoteModifier().mkdir("C/AllEmpty/Bar/f4"); - QVERIFY(fakeFolder.syncOnce()); - - OperationCounter counter; - fakeFolder.setServerOverride(counter.functor()); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + fakeFolder.remoteModifier().mkdir(QStringLiteral("A/Empty")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("A/Empty/Foo")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("C/AllEmpty")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("C/AllEmpty/Bar")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/Empty/f1")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/Empty/Foo/f2")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("C/AllEmpty/f3")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("C/AllEmpty/Bar/f4")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + OperationCounter counter(fakeFolder); // "Empty" is after "A", alphabetically - fakeFolder.localModifier().rename("A/Empty", "Empty"); - fakeFolder.localModifier().rename("A", "Empty/A"); + fakeFolder.localModifier().rename(QStringLiteral("A/Empty"), QStringLiteral("Empty")); + fakeFolder.localModifier().rename(QStringLiteral("A"), QStringLiteral("Empty/A")); // "AllEmpty" is before "C", alphabetically - fakeFolder.localModifier().rename("C/AllEmpty", "AllEmpty"); - fakeFolder.localModifier().rename("C", "AllEmpty/C"); + fakeFolder.localModifier().rename(QStringLiteral("C/AllEmpty"), QStringLiteral("AllEmpty")); + fakeFolder.localModifier().rename(QStringLiteral("C"), QStringLiteral("AllEmpty/C")); - auto expectedState = fakeFolder.currentLocalState(); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), expectedState); - QCOMPARE(fakeFolder.currentRemoteState(), expectedState); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nDELETE, 0); QCOMPARE(counter.nGET, 0); QCOMPARE(counter.nPUT, 0); // Now, the revert, but "crossed" - fakeFolder.localModifier().rename("Empty/A", "A"); - fakeFolder.localModifier().rename("AllEmpty/C", "C"); - fakeFolder.localModifier().rename("Empty", "C/Empty"); - fakeFolder.localModifier().rename("AllEmpty", "A/AllEmpty"); - expectedState = fakeFolder.currentLocalState(); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), expectedState); - QCOMPARE(fakeFolder.currentRemoteState(), expectedState); + fakeFolder.localModifier().rename(QStringLiteral("Empty/A"), QStringLiteral("A")); + fakeFolder.localModifier().rename(QStringLiteral("AllEmpty/C"), QStringLiteral("C")); + fakeFolder.localModifier().rename(QStringLiteral("Empty"), QStringLiteral("C/Empty")); + fakeFolder.localModifier().rename(QStringLiteral("AllEmpty"), QStringLiteral("A/AllEmpty")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nDELETE, 0); QCOMPARE(counter.nGET, 0); QCOMPARE(counter.nPUT, 0); // Reverse on remote - fakeFolder.remoteModifier().rename("A/AllEmpty", "AllEmpty"); - fakeFolder.remoteModifier().rename("C/Empty", "Empty"); - fakeFolder.remoteModifier().rename("C", "AllEmpty/C"); - fakeFolder.remoteModifier().rename("A", "Empty/A"); - expectedState = fakeFolder.currentRemoteState(); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), expectedState); - QCOMPARE(fakeFolder.currentRemoteState(), expectedState); + fakeFolder.remoteModifier().rename(QStringLiteral("A/AllEmpty"), QStringLiteral("AllEmpty")); + fakeFolder.remoteModifier().rename(QStringLiteral("C/Empty"), QStringLiteral("Empty")); + fakeFolder.remoteModifier().rename(QStringLiteral("C"), QStringLiteral("AllEmpty/C")); + fakeFolder.remoteModifier().rename(QStringLiteral("A"), QStringLiteral("Empty/A")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nDELETE, 0); QCOMPARE(counter.nGET, 0); QCOMPARE(counter.nPUT, 0); @@ -734,64 +819,67 @@ private slots: void testDeepHierarchy() { + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); QFETCH(bool, local); - FakeFolder fakeFolder { FileInfo::A12_B12_C12_S12() }; - auto &modifier = local ? fakeFolder.localModifier() : fakeFolder.remoteModifier(); - - modifier.mkdir("FolA"); - modifier.mkdir("FolA/FolB"); - modifier.mkdir("FolA/FolB/FolC"); - modifier.mkdir("FolA/FolB/FolC/FolD"); - modifier.mkdir("FolA/FolB/FolC/FolD/FolE"); - modifier.insert("FolA/FileA.txt"); - modifier.insert("FolA/FolB/FileB.txt"); - modifier.insert("FolA/FolB/FolC/FileC.txt"); - modifier.insert("FolA/FolB/FolC/FolD/FileD.txt"); - modifier.insert("FolA/FolB/FolC/FolD/FolE/FileE.txt"); - QVERIFY(fakeFolder.syncOnce()); - - OperationCounter counter; - fakeFolder.setServerOverride(counter.functor()); - - modifier.insert("FolA/FileA2.txt"); - modifier.insert("FolA/FolB/FileB2.txt"); - modifier.insert("FolA/FolB/FolC/FileC2.txt"); - modifier.insert("FolA/FolB/FolC/FolD/FileD2.txt"); - modifier.insert("FolA/FolB/FolC/FolD/FolE/FileE2.txt"); - modifier.rename("FolA", "FolA_Renamed"); - modifier.rename("FolA_Renamed/FolB", "FolB_Renamed"); - modifier.rename("FolB_Renamed/FolC", "FolA"); - modifier.rename("FolA/FolD", "FolA/FolD_Renamed"); - modifier.mkdir("FolB_Renamed/New"); - modifier.rename("FolA/FolD_Renamed/FolE", "FolB_Renamed/New/FolE"); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + FileModifier &modifier = local ? fakeFolder.localModifier() : fakeFolder.remoteModifier(); + + modifier.mkdir(QStringLiteral("FolA")); + modifier.mkdir(QStringLiteral("FolA/FolB")); + modifier.mkdir(QStringLiteral("FolA/FolB/FolC")); + modifier.mkdir(QStringLiteral("FolA/FolB/FolC/FolD")); + modifier.mkdir(QStringLiteral("FolA/FolB/FolC/FolD/FolE")); + modifier.insert(QStringLiteral("FolA/FileA.txt")); + modifier.insert(QStringLiteral("FolA/FolB/FileB.txt")); + modifier.insert(QStringLiteral("FolA/FolB/FolC/FileC.txt")); + modifier.insert(QStringLiteral("FolA/FolB/FolC/FolD/FileD.txt")); + modifier.insert(QStringLiteral("FolA/FolB/FolC/FolD/FolE/FileE.txt")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + OperationCounter counter(fakeFolder); + + modifier.insert(QStringLiteral("FolA/FileA2.txt")); + modifier.insert(QStringLiteral("FolA/FolB/FileB2.txt")); + modifier.insert(QStringLiteral("FolA/FolB/FolC/FileC2.txt")); + modifier.insert(QStringLiteral("FolA/FolB/FolC/FolD/FileD2.txt")); + modifier.insert(QStringLiteral("FolA/FolB/FolC/FolD/FolE/FileE2.txt")); + modifier.rename(QStringLiteral("FolA"), QStringLiteral("FolA_Renamed")); + modifier.rename(QStringLiteral("FolA_Renamed/FolB"), QStringLiteral("FolB_Renamed")); + modifier.rename(QStringLiteral("FolB_Renamed/FolC"), QStringLiteral("FolA")); + modifier.rename(QStringLiteral("FolA/FolD"), QStringLiteral("FolA/FolD_Renamed")); + modifier.mkdir(QStringLiteral("FolB_Renamed/New")); + modifier.rename(QStringLiteral("FolA/FolD_Renamed/FolE"), QStringLiteral("FolB_Renamed/New/FolE")); auto expected = local ? fakeFolder.currentLocalState() : fakeFolder.currentRemoteState(); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), expected); - QCOMPARE(fakeFolder.currentRemoteState(), expected); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); QCOMPARE(counter.nDELETE, local ? 1 : 0); // FolC was is renamed to an existing name, so it is not considered as renamed // There was 5 inserts - QCOMPARE(counter.nGET, local ? 0 : 5); + QCOMPARE(counter.nGET, local || filesAreDehydrated ? 0 : 5); QCOMPARE(counter.nPUT, local ? 5 : 0); } void renameOnBothSides() { - FakeFolder fakeFolder { FileInfo::A12_B12_C12_S12() }; - OperationCounter counter; - fakeFolder.setServerOverride(counter.functor()); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + OperationCounter counter(fakeFolder); // Test that renaming a file within a directory that was renamed on the other side actually do a rename. // 1) move the folder alphabeticaly before - fakeFolder.remoteModifier().rename("A/a1", "A/a1m"); - fakeFolder.localModifier().rename("A", "_A"); - fakeFolder.localModifier().rename("B/b1", "B/b1m"); - fakeFolder.remoteModifier().rename("B", "_B"); + fakeFolder.remoteModifier().rename(QStringLiteral("A/a1"), QStringLiteral("A/a1m")); + fakeFolder.localModifier().rename(QStringLiteral("A"), QStringLiteral("_A")); + fakeFolder.localModifier().rename(QStringLiteral("B/b1"), QStringLiteral("B/b1m")); + fakeFolder.remoteModifier().rename(QStringLiteral("B"), QStringLiteral("_B")); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentRemoteState(), fakeFolder.currentRemoteState()); - QVERIFY(fakeFolder.currentRemoteState().find("_A/a1m")); - QVERIFY(fakeFolder.currentRemoteState().find("_B/b1m")); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("_A/a1m"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("_B/b1m"))); QCOMPARE(counter.nDELETE, 0); QCOMPARE(counter.nGET, 0); QCOMPARE(counter.nPUT, 0); @@ -799,14 +887,14 @@ private slots: counter.reset(); // 2) move alphabetically after - fakeFolder.remoteModifier().rename("_A/a2", "_A/a2m"); - fakeFolder.localModifier().rename("_B/b2", "_B/b2m"); - fakeFolder.localModifier().rename("_A", "S/A"); - fakeFolder.remoteModifier().rename("_B", "S/B"); - QVERIFY(fakeFolder.syncOnce()); + fakeFolder.remoteModifier().rename(QStringLiteral("_A/a2"), QStringLiteral("_A/a2m")); + fakeFolder.localModifier().rename(QStringLiteral("_B/b2"), QStringLiteral("_B/b2m")); + fakeFolder.localModifier().rename(QStringLiteral("_A"), QStringLiteral("S/A")); + fakeFolder.remoteModifier().rename(QStringLiteral("_B"), QStringLiteral("S/B")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentRemoteState(), fakeFolder.currentRemoteState()); - QVERIFY(fakeFolder.currentRemoteState().find("S/A/a2m")); - QVERIFY(fakeFolder.currentRemoteState().find("S/B/b2m")); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("S/A/a2m"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("S/B/b2m"))); QCOMPARE(counter.nDELETE, 0); QCOMPARE(counter.nGET, 0); QCOMPARE(counter.nPUT, 0); @@ -815,47 +903,86 @@ private slots: void moveFileToDifferentFolderOnBothSides() { - FakeFolder fakeFolder { FileInfo::A12_B12_C12_S12() }; - OperationCounter counter; - fakeFolder.setServerOverride(counter.functor()); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); + + FakeFolder fakeFolder(FileInfo::A12_B12_C12_S12(), vfsMode, filesAreDehydrated); + OperationCounter counter(fakeFolder); + + QCOMPARE(fakeFolder.currentLocalState().find(QStringLiteral("B/b1"))->isDehydratedPlaceholder, filesAreDehydrated); + QCOMPARE(fakeFolder.currentLocalState().find(QStringLiteral("B/b2"))->isDehydratedPlaceholder, filesAreDehydrated); // Test that moving a file within to different folder on both side does the right thing. - fakeFolder.remoteModifier().rename("B/b1", "A/b1"); - fakeFolder.localModifier().rename("B/b1", "C/b1"); + fakeFolder.remoteModifier().rename(QStringLiteral("B/b1"), QStringLiteral("A/b1")); + fakeFolder.localModifier().rename(QStringLiteral("B/b1"), QStringLiteral("C/b1")); - fakeFolder.localModifier().rename("B/b2", "A/b2"); - fakeFolder.remoteModifier().rename("B/b2", "C/b2"); + fakeFolder.localModifier().rename(QStringLiteral("B/b2"), QStringLiteral("A/b2")); + fakeFolder.remoteModifier().rename(QStringLiteral("B/b2"), QStringLiteral("C/b2")); - QVERIFY(fakeFolder.syncOnce()); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); QCOMPARE(fakeFolder.currentRemoteState(), fakeFolder.currentRemoteState()); - QVERIFY(fakeFolder.currentRemoteState().find("A/b1")); - QVERIFY(fakeFolder.currentRemoteState().find("C/b1")); - QVERIFY(fakeFolder.currentRemoteState().find("A/b2")); - QVERIFY(fakeFolder.currentRemoteState().find("C/b2")); + // The easy checks: the server always has the data, so it can successfully move the files: + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("A/b1"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("C/b2"))); + // Either the client has hydrated files, in which case it will upload the data to the target locations; + // or the files were dehydrated, so it has to remove the files. (No data-loss in the latter case: the + // files were dehydrated, so there was no data anyway.) + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("C/b1")) || filesAreDehydrated); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("A/b2")) || filesAreDehydrated); + QCOMPARE(counter.nMOVE, 0); // Unfortunately, we can't really make a move in this case - QCOMPARE(counter.nGET, 2); - QCOMPARE(counter.nPUT, 2); + QCOMPARE(counter.nGET, filesAreDehydrated ? 0 : 2); + QCOMPARE(counter.nPUT, filesAreDehydrated ? 0 : 2); QCOMPARE(counter.nDELETE, 0); counter.reset(); - } // Test that deletes don't run before renames void testRenameParallelism() { - FakeFolder fakeFolder{ FileInfo{} }; - fakeFolder.remoteModifier().mkdir("A"); - fakeFolder.remoteModifier().insert("A/file"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QFETCH_GLOBAL(Vfs::Mode, vfsMode); + QFETCH_GLOBAL(bool, filesAreDehydrated); - fakeFolder.localModifier().mkdir("B"); - fakeFolder.localModifier().rename("A/file", "B/file"); - fakeFolder.localModifier().remove("A"); + FakeFolder fakeFolder({ FileInfo {} }, vfsMode, filesAreDehydrated); + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/file")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + { + auto localState = fakeFolder.currentLocalState(); + FileInfo *localFile = localState.find(QStringLiteral("A/file")); + QVERIFY(localFile != nullptr); // check if the file exists + QCOMPARE(vfsMode == Vfs::Off || localFile->isDehydratedPlaceholder, vfsMode == Vfs::Off || filesAreDehydrated); + + auto remoteState = fakeFolder.currentRemoteState(); + FileInfo *remoteFile = remoteState.find(QStringLiteral("A/file")); + QVERIFY(remoteFile != nullptr); + QCOMPARE(localFile->lastModified(), remoteFile->lastModified()); + + QCOMPARE(localState, remoteState); + } + + fakeFolder.localModifier().mkdir(QStringLiteral("B")); + fakeFolder.localModifier().rename(QStringLiteral("A/file"), QStringLiteral("B/file")); + fakeFolder.localModifier().remove(QStringLiteral("A")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + { + auto localState = fakeFolder.currentLocalState(); + QVERIFY(localState.find(QStringLiteral("A/file")) == nullptr); // check if the file is gone + QVERIFY(localState.find(QStringLiteral("A")) == nullptr); // check if the directory is gone + FileInfo *localFile = localState.find(QStringLiteral("B/file")); + QVERIFY(localFile != nullptr); // check if the file exists + QCOMPARE(vfsMode == Vfs::Off || localFile->isDehydratedPlaceholder, vfsMode == Vfs::Off || filesAreDehydrated); + + auto remoteState = fakeFolder.currentRemoteState(); + FileInfo *remoteFile = remoteState.find(QStringLiteral("B/file")); + QVERIFY(remoteFile != nullptr); + QCOMPARE(localFile->lastModified(), remoteFile->lastModified()); + + QCOMPARE(localState, remoteState); + } } void testMovedWithError_data() @@ -863,76 +990,59 @@ private slots: QTest::addColumn("vfsMode"); QTest::newRow("Vfs::Off") << Vfs::Off; - QTest::newRow("Vfs::WithSuffix") << Vfs::WithSuffix; #ifdef Q_OS_WIN32 - if (isVfsPluginAvailable(Vfs::WindowsCfApi)) - { + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { QTest::newRow("Vfs::WindowsCfApi") << Vfs::WindowsCfApi; } else { - QWARN("Skipping Vfs::WindowsCfApi"); + qWarning("Skipping Vfs::WindowsCfApi"); } - #endif } void testMovedWithError() { QFETCH(Vfs::Mode, vfsMode); - const auto getName = [vfsMode] (const QString &s) - { - if (vfsMode == Vfs::WithSuffix) - { - return QStringLiteral("%1" APPLICATION_DOTVIRTUALFILE_SUFFIX).arg(s); - } - return s; - }; - const QString src = "folder/folderA/file.txt"; - const QString dest = "folder/folderB/file.txt"; + const QString src = QStringLiteral("folder/folderA/file.txt"); + const QString dest = QStringLiteral("folder/folderB/file.txt"); FakeFolder fakeFolder{ FileInfo{ QString(), { FileInfo{ QStringLiteral("folder"), { FileInfo{ QStringLiteral("folderA"), { { QStringLiteral("file.txt"), 400 } } }, QStringLiteral("folderB") } } } } }; - auto syncOpts = fakeFolder.syncEngine().syncOptions(); + auto syncOpts = fakeFolder.syncEngine()->syncOptions(); syncOpts._parallelNetworkJobs = 0; - fakeFolder.syncEngine().setSyncOptions(syncOpts); + fakeFolder.syncEngine()->setSyncOptions(syncOpts); QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); if (vfsMode != Vfs::Off) { - auto vfs = QSharedPointer(createVfsFromPlugin(vfsMode).release()); + auto vfs = VfsPluginManager::instance().createVfsFromPlugin(vfsMode, &fakeFolder); QVERIFY(vfs); + // todo: this is going to kill the parallel jobs count set above - I don't know if it matters so need to check fakeFolder.switchToVfs(vfs); - fakeFolder.syncJournal().internalPinStates().setForPath("", PinState::OnlineOnly); + fakeFolder.syncJournal()->internalPinStates().setForPath("", PinState::OnlineOnly); // make files virtual - fakeFolder.syncOnce(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); } fakeFolder.serverErrorPaths().append(src, 403); - fakeFolder.localModifier().rename(getName(src), getName(dest)); - QVERIFY(!fakeFolder.currentLocalState().find(getName(src))); - QVERIFY(fakeFolder.currentLocalState().find(getName(dest))); + fakeFolder.localModifier().rename(src, dest); QVERIFY(fakeFolder.currentRemoteState().find(src)); QVERIFY(!fakeFolder.currentRemoteState().find(dest)); // sync1 file gets detected as error, instruction is still NEW_FILE - fakeFolder.syncOnce(); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); // sync2 file is in error state, checkErrorBlacklisting sets instruction to IGNORED - fakeFolder.syncOnce(); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); if (vfsMode != Vfs::Off) { - fakeFolder.syncJournal().internalPinStates().setForPath("", PinState::AlwaysLocal); - fakeFolder.syncOnce(); + fakeFolder.syncJournal()->internalPinStates().setForPath("", PinState::AlwaysLocal); + QVERIFY(!fakeFolder.applyLocalModificationsAndSync()); } QVERIFY(!fakeFolder.currentLocalState().find(src)); - QVERIFY(fakeFolder.currentLocalState().find(getName(dest))); - if (vfsMode == Vfs::WithSuffix) - { - // the placeholder was not restored as it is still in error state - QVERIFY(!fakeFolder.currentLocalState().find(dest)); - } + QVERIFY(fakeFolder.currentLocalState().find(dest)); QVERIFY(fakeFolder.currentRemoteState().find(src)); QVERIFY(!fakeFolder.currentRemoteState().find(dest)); } diff --git a/test/testsyncvirtualfiles.cpp b/test/testsyncvirtualfiles.cpp deleted file mode 100644 index a55748c9fc3..00000000000 --- a/test/testsyncvirtualfiles.cpp +++ /dev/null @@ -1,1219 +0,0 @@ -/* - * This software is in the public domain, furnished "as is", without technical - * support, and with no warranty, express or implied, as to its usefulness for - * any purpose. - * - */ - -#include -#include "testutils/syncenginetestutils.h" -#include "common/vfs.h" -#include "config.h" -#include - -using namespace OCC; - -#define DVSUFFIX APPLICATION_DOTVIRTUALFILE_SUFFIX - -auto itemInstruction(const ItemCompletedSpy &spy, const QString &path) -{ - auto item = spy.findItem(path); - Q_ASSERT(!item.isNull()); - return item->_instruction; -} - -SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path) -{ - SyncJournalFileRecord record; - folder.syncJournal().getFileRecord(path, &record); - return record; -} - -void triggerDownload(FakeFolder &folder, const QByteArray &path) -{ - auto &journal = folder.syncJournal(); - SyncJournalFileRecord record; - journal.getFileRecord(path + DVSUFFIX, &record); - if (!record.isValid()) - return; - record._type = ItemTypeVirtualFileDownload; - journal.setFileRecord(record); - journal.schedulePathForRemoteDiscovery(record._path); -} - -void markForDehydration(FakeFolder &folder, const QByteArray &path) -{ - auto &journal = folder.syncJournal(); - SyncJournalFileRecord record; - journal.getFileRecord(path, &record); - if (!record.isValid()) - return; - record._type = ItemTypeVirtualFileDehydration; - journal.setFileRecord(record); - journal.schedulePathForRemoteDiscovery(record._path); -} - -QSharedPointer setupVfs(FakeFolder &folder) -{ - auto suffixVfs = QSharedPointer(createVfsFromPlugin(Vfs::WithSuffix).release()); - folder.switchToVfs(suffixVfs); - - // Using this directly doesn't recursively unpin everything and instead leaves - // the files in the hydration that that they start with - folder.syncJournal().internalPinStates().setForPath("", PinState::Unspecified); - - return suffixVfs; -} - -class TestSyncVirtualFiles : public QObject -{ - Q_OBJECT - -private slots: - void testVirtualFileLifecycle_data() - { - QTest::addColumn("doLocalDiscovery"); - - QTest::newRow("full local discovery") << true; - QTest::newRow("skip local discovery") << false; - } - - void testVirtualFileLifecycle() - { - QFETCH(bool, doLocalDiscovery); - - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - ItemCompletedSpy completeSpy(fakeFolder); - - auto cleanup = [&]() { - completeSpy.clear(); - if (!doLocalDiscovery) - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem); - }; - cleanup(); - - // Create a virtual file for a new remote file - fakeFolder.remoteModifier().mkdir("A"); - fakeFolder.remoteModifier().insert("A/a1", 64); - auto someDate = QDateTime(QDate(1984, 07, 30), QTime(1,3,2)); - fakeFolder.remoteModifier().setModTime("A/a1", someDate); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1" DVSUFFIX).lastModified(), someDate); - QVERIFY(fakeFolder.currentRemoteState().find("A/a1")); - QCOMPARE(itemInstruction(completeSpy, "A/a1" DVSUFFIX), CSYNC_INSTRUCTION_NEW); - QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._type, ItemTypeVirtualFile); - cleanup(); - - // Another sync doesn't actually lead to changes - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1" DVSUFFIX).lastModified(), someDate); - QVERIFY(fakeFolder.currentRemoteState().find("A/a1")); - QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._type, ItemTypeVirtualFile); - QVERIFY(completeSpy.isEmpty()); - cleanup(); - - // Not even when the remote is rediscovered - fakeFolder.syncJournal().forceRemoteDiscoveryNextSync(); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1" DVSUFFIX).lastModified(), someDate); - QVERIFY(fakeFolder.currentRemoteState().find("A/a1")); - QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._type, ItemTypeVirtualFile); - QVERIFY(completeSpy.isEmpty()); - cleanup(); - - // Neither does a remote change - fakeFolder.remoteModifier().appendByte("A/a1"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentRemoteState().find("A/a1")); - QCOMPARE(itemInstruction(completeSpy, "A/a1" DVSUFFIX), CSYNC_INSTRUCTION_UPDATE_METADATA); - QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._type, ItemTypeVirtualFile); - QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._fileSize, 65); - cleanup(); - - // If the local virtual file file is removed, it'll just be recreated - if (!doLocalDiscovery) - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, { "A" }); - fakeFolder.localModifier().remove("A/a1" DVSUFFIX); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentRemoteState().find("A/a1")); - QCOMPARE(itemInstruction(completeSpy, "A/a1" DVSUFFIX), CSYNC_INSTRUCTION_NEW); - QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._type, ItemTypeVirtualFile); - QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._fileSize, 65); - cleanup(); - - // Remote rename is propagated - fakeFolder.remoteModifier().rename("A/a1", "A/a1m"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1m")); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a1m" DVSUFFIX)); - QVERIFY(!fakeFolder.currentRemoteState().find("A/a1")); - QVERIFY(fakeFolder.currentRemoteState().find("A/a1m")); - QVERIFY( - itemInstruction(completeSpy, "A/a1m" DVSUFFIX) == CSYNC_INSTRUCTION_RENAME - || (itemInstruction(completeSpy, "A/a1m" DVSUFFIX) == CSYNC_INSTRUCTION_NEW - && itemInstruction(completeSpy, "A/a1" DVSUFFIX) == CSYNC_INSTRUCTION_REMOVE)); - QCOMPARE(dbRecord(fakeFolder, "A/a1m" DVSUFFIX)._type, ItemTypeVirtualFile); - cleanup(); - - // Remote remove is propagated - fakeFolder.remoteModifier().remove("A/a1m"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1m" DVSUFFIX)); - QVERIFY(!fakeFolder.currentRemoteState().find("A/a1m")); - QCOMPARE(itemInstruction(completeSpy, "A/a1m" DVSUFFIX), CSYNC_INSTRUCTION_REMOVE); - QVERIFY(!dbRecord(fakeFolder, "A/a1" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/a1m" DVSUFFIX).isValid()); - cleanup(); - - // Edge case: Local virtual file but no db entry for some reason - fakeFolder.remoteModifier().insert("A/a2", 64); - fakeFolder.remoteModifier().insert("A/a3", 64); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/a2" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a3" DVSUFFIX)); - cleanup(); - - fakeFolder.syncEngine().journal()->deleteFileRecord("A/a2" DVSUFFIX); - fakeFolder.syncEngine().journal()->deleteFileRecord("A/a3" DVSUFFIX); - fakeFolder.remoteModifier().remove("A/a3"); - fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/a2" DVSUFFIX)); - QCOMPARE(itemInstruction(completeSpy, "A/a2" DVSUFFIX), CSYNC_INSTRUCTION_UPDATE_METADATA); - QVERIFY(dbRecord(fakeFolder, "A/a2" DVSUFFIX).isValid()); - QVERIFY(!fakeFolder.currentLocalState().find("A/a3" DVSUFFIX)); - QCOMPARE(itemInstruction(completeSpy, "A/a3" DVSUFFIX), CSYNC_INSTRUCTION_REMOVE); - QVERIFY(!dbRecord(fakeFolder, "A/a3" DVSUFFIX).isValid()); - cleanup(); - } - - void testVirtualFileConflict() - { - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - ItemCompletedSpy completeSpy(fakeFolder); - - auto cleanup = [&]() { - completeSpy.clear(); - }; - cleanup(); - - // Create a virtual file for a new remote file - fakeFolder.remoteModifier().mkdir("A"); - fakeFolder.remoteModifier().insert("A/a1", 64); - fakeFolder.remoteModifier().insert("A/a2", 64); - fakeFolder.remoteModifier().mkdir("B"); - fakeFolder.remoteModifier().insert("B/b1", 64); - fakeFolder.remoteModifier().insert("B/b2", 64); - fakeFolder.remoteModifier().mkdir("C"); - fakeFolder.remoteModifier().insert("C/c1", 64); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("B/b2" DVSUFFIX)); - cleanup(); - - // A: the correct file and a conflicting file are added, virtual files stay - // B: same setup, but the virtual files are deleted by the user - // C: user adds a *directory* locally - fakeFolder.localModifier().insert("A/a1", 64); - fakeFolder.localModifier().insert("A/a2", 30); - fakeFolder.localModifier().insert("B/b1", 64); - fakeFolder.localModifier().insert("B/b2", 30); - fakeFolder.localModifier().remove("B/b1" DVSUFFIX); - fakeFolder.localModifier().remove("B/b2" DVSUFFIX); - fakeFolder.localModifier().mkdir("C/c1"); - fakeFolder.localModifier().insert("C/c1/foo"); - QVERIFY(fakeFolder.syncOnce()); - - // Everything is CONFLICT since mtimes are different even for a1/b1 - QCOMPARE(itemInstruction(completeSpy, "A/a1"), CSYNC_INSTRUCTION_CONFLICT); - QCOMPARE(itemInstruction(completeSpy, "A/a2"), CSYNC_INSTRUCTION_CONFLICT); - QCOMPARE(itemInstruction(completeSpy, "B/b1"), CSYNC_INSTRUCTION_CONFLICT); - QCOMPARE(itemInstruction(completeSpy, "B/b2"), CSYNC_INSTRUCTION_CONFLICT); - QCOMPARE(itemInstruction(completeSpy, "C/c1"), CSYNC_INSTRUCTION_CONFLICT); - - // no virtual file files should remain - QVERIFY(!fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/a2" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("B/b1" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("B/b2" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("C/c1" DVSUFFIX)); - - // conflict files should exist - QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 3); - - // nothing should have the virtual file tag - QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile); - QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile); - QCOMPARE(dbRecord(fakeFolder, "B/b1")._type, ItemTypeFile); - QCOMPARE(dbRecord(fakeFolder, "B/b2")._type, ItemTypeFile); - QCOMPARE(dbRecord(fakeFolder, "C/c1")._type, ItemTypeFile); - QVERIFY(!dbRecord(fakeFolder, "A/a1" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/a2" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "B/b1" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "B/b2" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "C/c1" DVSUFFIX).isValid()); - - cleanup(); - } - - void testWithNormalSync() - { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - ItemCompletedSpy completeSpy(fakeFolder); - - auto cleanup = [&]() { - completeSpy.clear(); - }; - cleanup(); - - // No effect sync - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - cleanup(); - - // Existing files are propagated just fine in both directions - fakeFolder.localModifier().appendByte("A/a1"); - fakeFolder.localModifier().insert("A/a3"); - fakeFolder.remoteModifier().appendByte("A/a2"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - cleanup(); - - // New files on the remote create virtual files - fakeFolder.remoteModifier().insert("A/new"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/new")); - QVERIFY(fakeFolder.currentLocalState().find("A/new" DVSUFFIX)); - QVERIFY(fakeFolder.currentRemoteState().find("A/new")); - QCOMPARE(itemInstruction(completeSpy, "A/new" DVSUFFIX), CSYNC_INSTRUCTION_NEW); - QCOMPARE(dbRecord(fakeFolder, "A/new" DVSUFFIX)._type, ItemTypeVirtualFile); - cleanup(); - } - - void testVirtualFileDownload() - { - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - ItemCompletedSpy completeSpy(fakeFolder); - - auto cleanup = [&]() { - completeSpy.clear(); - }; - cleanup(); - - // Create a virtual file for remote files - fakeFolder.remoteModifier().mkdir("A"); - fakeFolder.remoteModifier().insert("A/a1"); - fakeFolder.remoteModifier().insert("A/a2"); - fakeFolder.remoteModifier().insert("A/a3"); - fakeFolder.remoteModifier().insert("A/a4"); - fakeFolder.remoteModifier().insert("A/a5"); - fakeFolder.remoteModifier().insert("A/a6"); - fakeFolder.remoteModifier().insert("A/a7"); - fakeFolder.remoteModifier().insert("A/b1"); - fakeFolder.remoteModifier().insert("A/b2"); - fakeFolder.remoteModifier().insert("A/b3"); - fakeFolder.remoteModifier().insert("A/b4"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a2" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a3" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a4" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a5" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a6" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a7" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/b1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/b2" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/b3" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/b4" DVSUFFIX)); - cleanup(); - - // Download by changing the db entry - triggerDownload(fakeFolder, "A/a1"); - triggerDownload(fakeFolder, "A/a2"); - triggerDownload(fakeFolder, "A/a3"); - triggerDownload(fakeFolder, "A/a4"); - triggerDownload(fakeFolder, "A/a5"); - triggerDownload(fakeFolder, "A/a6"); - triggerDownload(fakeFolder, "A/a7"); - // Download by renaming locally - fakeFolder.localModifier().rename("A/b1" DVSUFFIX, "A/b1"); - fakeFolder.localModifier().rename("A/b2" DVSUFFIX, "A/b2"); - fakeFolder.localModifier().rename("A/b3" DVSUFFIX, "A/b3"); - fakeFolder.localModifier().rename("A/b4" DVSUFFIX, "A/b4"); - // Remote complications - fakeFolder.remoteModifier().appendByte("A/a2"); - fakeFolder.remoteModifier().remove("A/a3"); - fakeFolder.remoteModifier().rename("A/a4", "A/a4m"); - fakeFolder.remoteModifier().appendByte("A/b2"); - fakeFolder.remoteModifier().remove("A/b3"); - fakeFolder.remoteModifier().rename("A/b4", "A/b4m"); - // Local complications - fakeFolder.localModifier().insert("A/a5"); - fakeFolder.localModifier().insert("A/a6"); - fakeFolder.localModifier().remove("A/a6" DVSUFFIX); - fakeFolder.localModifier().rename("A/a7" DVSUFFIX, "A/a7"); - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(itemInstruction(completeSpy, "A/a1"), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(completeSpy.findItem("A/a1")->_type, ItemTypeVirtualFileDownload); - QCOMPARE(itemInstruction(completeSpy, "A/a2"), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(completeSpy.findItem("A/a2")->_type, ItemTypeVirtualFileDownload); - QCOMPARE(itemInstruction(completeSpy, "A/a3" DVSUFFIX), CSYNC_INSTRUCTION_REMOVE); - QCOMPARE(itemInstruction(completeSpy, "A/a4m"), CSYNC_INSTRUCTION_NEW); - QCOMPARE(itemInstruction(completeSpy, "A/a4" DVSUFFIX), CSYNC_INSTRUCTION_REMOVE); - QCOMPARE(itemInstruction(completeSpy, "A/a5"), CSYNC_INSTRUCTION_CONFLICT); - QCOMPARE(itemInstruction(completeSpy, "A/a5" DVSUFFIX), CSYNC_INSTRUCTION_REMOVE); - QCOMPARE(itemInstruction(completeSpy, "A/a6"), CSYNC_INSTRUCTION_CONFLICT); - QCOMPARE(itemInstruction(completeSpy, "A/a7"), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(itemInstruction(completeSpy, "A/b1"), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(itemInstruction(completeSpy, "A/b2"), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(itemInstruction(completeSpy, "A/b3"), CSYNC_INSTRUCTION_REMOVE); - QCOMPARE(itemInstruction(completeSpy, "A/b4m" DVSUFFIX), CSYNC_INSTRUCTION_NEW); - QCOMPARE(itemInstruction(completeSpy, "A/b4"), CSYNC_INSTRUCTION_REMOVE); - QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile); - QVERIFY(!dbRecord(fakeFolder, "A/a1" DVSUFFIX).isValid()); - QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile); - QVERIFY(!dbRecord(fakeFolder, "A/a3").isValid()); - QCOMPARE(dbRecord(fakeFolder, "A/a4m")._type, ItemTypeFile); - QCOMPARE(dbRecord(fakeFolder, "A/a5")._type, ItemTypeFile); - QCOMPARE(dbRecord(fakeFolder, "A/a6")._type, ItemTypeFile); - QCOMPARE(dbRecord(fakeFolder, "A/a7")._type, ItemTypeFile); - QCOMPARE(dbRecord(fakeFolder, "A/b1")._type, ItemTypeFile); - QVERIFY(!dbRecord(fakeFolder, "A/b1" DVSUFFIX).isValid()); - QCOMPARE(dbRecord(fakeFolder, "A/b2")._type, ItemTypeFile); - QVERIFY(!dbRecord(fakeFolder, "A/b3").isValid()); - QCOMPARE(dbRecord(fakeFolder, "A/b4m" DVSUFFIX)._type, ItemTypeVirtualFile); - QVERIFY(!dbRecord(fakeFolder, "A/a1" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/a2" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/a3" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/a4" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/a5" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/a6" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/a7" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/b1" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/b2" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/b3" DVSUFFIX).isValid()); - QVERIFY(!dbRecord(fakeFolder, "A/b4" DVSUFFIX).isValid()); - - triggerDownload(fakeFolder, "A/b4m"); - QVERIFY(fakeFolder.syncOnce()); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } - - void testVirtualFileDownloadResume() - { - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - ItemCompletedSpy completeSpy(fakeFolder); - - auto cleanup = [&]() { - completeSpy.clear(); - fakeFolder.syncJournal().wipeErrorBlacklist(); - }; - cleanup(); - - // Create a virtual file for remote files - fakeFolder.remoteModifier().mkdir("A"); - fakeFolder.remoteModifier().insert("A/a1"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - cleanup(); - - // Download by changing the db entry - triggerDownload(fakeFolder, "A/a1"); - fakeFolder.serverErrorPaths().append("A/a1", 500); - QVERIFY(!fakeFolder.syncOnce()); - QCOMPARE(itemInstruction(completeSpy, "A/a1"), CSYNC_INSTRUCTION_SYNC); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._type, ItemTypeVirtualFileDownload); - QVERIFY(!dbRecord(fakeFolder, "A/a1").isValid()); - cleanup(); - - fakeFolder.serverErrorPaths().clear(); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(itemInstruction(completeSpy, "A/a1"), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile); - QVERIFY(!dbRecord(fakeFolder, "A/a1" DVSUFFIX).isValid()); - } - - void testNewFilesNotVirtual() - { - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - fakeFolder.remoteModifier().mkdir("A"); - fakeFolder.remoteModifier().insert("A/a1"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - - fakeFolder.syncJournal().internalPinStates().setForPath("", PinState::AlwaysLocal); - - // Create a new remote file, it'll not be virtual - fakeFolder.remoteModifier().insert("A/a2"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/a2")); - QVERIFY(!fakeFolder.currentLocalState().find("A/a2" DVSUFFIX)); - } - - void testDownloadRecursive() - { - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // Create a virtual file for remote files - fakeFolder.remoteModifier().mkdir("A"); - fakeFolder.remoteModifier().mkdir("A/Sub"); - fakeFolder.remoteModifier().mkdir("A/Sub/SubSub"); - fakeFolder.remoteModifier().mkdir("A/Sub2"); - fakeFolder.remoteModifier().mkdir("B"); - fakeFolder.remoteModifier().mkdir("B/Sub"); - fakeFolder.remoteModifier().insert("A/a1"); - fakeFolder.remoteModifier().insert("A/a2"); - fakeFolder.remoteModifier().insert("A/Sub/a3"); - fakeFolder.remoteModifier().insert("A/Sub/a4"); - fakeFolder.remoteModifier().insert("A/Sub/SubSub/a5"); - fakeFolder.remoteModifier().insert("A/Sub2/a6"); - fakeFolder.remoteModifier().insert("B/b1"); - fakeFolder.remoteModifier().insert("B/Sub/b2"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a2" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a4" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a5" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("B/b1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(!fakeFolder.currentLocalState().find("A/a2")); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3")); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4")); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5")); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub2/a6")); - QVERIFY(!fakeFolder.currentLocalState().find("B/b1")); - QVERIFY(!fakeFolder.currentLocalState().find("B/Sub/b2")); - - - // Download All file in the directory A/Sub - // (as in Folder::downloadVirtualFile) - fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("A/Sub"); - - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a2" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("B/b1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(!fakeFolder.currentLocalState().find("A/a2")); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3")); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a4")); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a5")); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub2/a6")); - QVERIFY(!fakeFolder.currentLocalState().find("B/b1")); - QVERIFY(!fakeFolder.currentLocalState().find("B/Sub/b2")); - - // Add a file in a subfolder that was downloaded - // Currently, this continue to add it as a virtual file. - fakeFolder.remoteModifier().insert("A/Sub/SubSub/a7"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a7" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a7")); - - // Now download all files in "A" - fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("A"); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/a2" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub2/a6" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a7" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("B/b1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(fakeFolder.currentLocalState().find("A/a2")); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3")); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a4")); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a5")); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6")); - QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a7")); - QVERIFY(!fakeFolder.currentLocalState().find("B/b1")); - QVERIFY(!fakeFolder.currentLocalState().find("B/Sub/b2")); - - // Now download remaining files in "B" - fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("B"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - } - - void testRenameToVirtual() - { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - ItemCompletedSpy completeSpy(fakeFolder); - - auto cleanup = [&]() { - completeSpy.clear(); - }; - cleanup(); - - // If a file is renamed to .owncloud, it becomes virtual - fakeFolder.localModifier().rename("A/a1", "A/a1" DVSUFFIX); - // If a file is renamed to .owncloud, the rename propagates but the - // file isn't made virtual the first sync run. - fakeFolder.localModifier().rename("A/a2", "A/rand" DVSUFFIX); - // dangling virtual files are removed - fakeFolder.localModifier().insert("A/dangling" DVSUFFIX, 1, ' '); - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(!fakeFolder.currentLocalState().find("A/a1")); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)->size <= 1); - QVERIFY(fakeFolder.currentRemoteState().find("A/a1")); - QCOMPARE(itemInstruction(completeSpy, "A/a1" DVSUFFIX), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(dbRecord(fakeFolder, "A/a1" DVSUFFIX)._type, ItemTypeVirtualFile); - QVERIFY(!dbRecord(fakeFolder, "A/a1").isValid()); - - QVERIFY(!fakeFolder.currentLocalState().find("A/a2")); - QVERIFY(!fakeFolder.currentLocalState().find("A/a2" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/rand")); - QVERIFY(!fakeFolder.currentRemoteState().find("A/a2")); - QVERIFY(fakeFolder.currentRemoteState().find("A/rand")); - QCOMPARE(itemInstruction(completeSpy, "A/rand"), CSYNC_INSTRUCTION_RENAME); - QVERIFY(dbRecord(fakeFolder, "A/rand")._type == ItemTypeFile); - - QVERIFY(!fakeFolder.currentLocalState().find("A/dangling" DVSUFFIX)); - cleanup(); - } - - void testRenameVirtual() - { - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - ItemCompletedSpy completeSpy(fakeFolder); - - auto cleanup = [&]() { - completeSpy.clear(); - }; - cleanup(); - - fakeFolder.remoteModifier().insert("file1", 128, 'C'); - fakeFolder.remoteModifier().insert("file2", 256, 'C'); - fakeFolder.remoteModifier().insert("file3", 256, 'C'); - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(fakeFolder.currentLocalState().find("file1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("file2" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("file3" DVSUFFIX)); - cleanup(); - - fakeFolder.localModifier().rename("file1" DVSUFFIX, "renamed1" DVSUFFIX); - fakeFolder.localModifier().rename("file2" DVSUFFIX, "renamed2" DVSUFFIX); - triggerDownload(fakeFolder, "file2"); - triggerDownload(fakeFolder, "file3"); - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(!fakeFolder.currentLocalState().find("file1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("renamed1" DVSUFFIX)); - QVERIFY(!fakeFolder.currentRemoteState().find("file1")); - QVERIFY(fakeFolder.currentRemoteState().find("renamed1")); - QCOMPARE(itemInstruction(completeSpy, "renamed1" DVSUFFIX), CSYNC_INSTRUCTION_RENAME); - QVERIFY(dbRecord(fakeFolder, "renamed1" DVSUFFIX).isValid()); - - // file2 has a conflict between the download request and the rename: - // the rename wins, the download is ignored - QVERIFY(!fakeFolder.currentLocalState().find("file2")); - QVERIFY(!fakeFolder.currentLocalState().find("file2" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("renamed2" DVSUFFIX)); - QVERIFY(fakeFolder.currentRemoteState().find("renamed2")); - QCOMPARE(itemInstruction(completeSpy, "renamed2" DVSUFFIX), CSYNC_INSTRUCTION_RENAME); - QVERIFY(dbRecord(fakeFolder, "renamed2" DVSUFFIX)._type == ItemTypeVirtualFile); - - QCOMPARE(itemInstruction(completeSpy, "file3"), CSYNC_INSTRUCTION_SYNC); - QVERIFY(dbRecord(fakeFolder, "file3")._type == ItemTypeFile); - cleanup(); - - // Test rename while adding/removing vfs suffix - fakeFolder.localModifier().rename("renamed1" DVSUFFIX, "R1"); - // Contents of file2 could also change at the same time... - fakeFolder.localModifier().rename("file3", "R3" DVSUFFIX); - QVERIFY(fakeFolder.syncOnce()); - cleanup(); - } - - void testRenameVirtual2() - { - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - ItemCompletedSpy completeSpy(fakeFolder); - auto cleanup = [&]() { - completeSpy.clear(); - }; - cleanup(); - - fakeFolder.remoteModifier().insert("case3", 128, 'C'); - fakeFolder.remoteModifier().insert("case4", 256, 'C'); - fakeFolder.remoteModifier().insert("case5", 256, 'C'); - fakeFolder.remoteModifier().insert("case6", 256, 'C'); - QVERIFY(fakeFolder.syncOnce()); - - triggerDownload(fakeFolder, "case4"); - triggerDownload(fakeFolder, "case6"); - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(fakeFolder.currentLocalState().find("case3" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("case4")); - QVERIFY(fakeFolder.currentLocalState().find("case5" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("case6")); - cleanup(); - - // Case 1: foo -> bar (tested elsewhere) - // Case 2: foo.oc -> bar.oc (tested elsewhere) - - // Case 3: foo.oc -> bar (db unchanged) - fakeFolder.localModifier().rename("case3" DVSUFFIX, "case3-rename"); - - // Case 4: foo -> bar.oc (db unchanged) - fakeFolder.localModifier().rename("case4", "case4-rename" DVSUFFIX); - - // Case 5: foo.oc -> bar.oc (db hydrate) - fakeFolder.localModifier().rename("case5" DVSUFFIX, "case5-rename" DVSUFFIX); - triggerDownload(fakeFolder, "case5"); - - // Case 6: foo -> bar (db dehydrate) - fakeFolder.localModifier().rename("case6", "case6-rename"); - markForDehydration(fakeFolder, "case6"); - - QVERIFY(fakeFolder.syncOnce()); - - // Case 3: the rename went though, hydration is forgotten - QVERIFY(!fakeFolder.currentLocalState().find("case3")); - QVERIFY(!fakeFolder.currentLocalState().find("case3" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("case3-rename")); - QVERIFY(fakeFolder.currentLocalState().find("case3-rename" DVSUFFIX)); - QVERIFY(!fakeFolder.currentRemoteState().find("case3")); - QVERIFY(fakeFolder.currentRemoteState().find("case3-rename")); - QCOMPARE(itemInstruction(completeSpy, "case3-rename" DVSUFFIX), CSYNC_INSTRUCTION_RENAME); - QVERIFY(dbRecord(fakeFolder, "case3-rename" DVSUFFIX)._type == ItemTypeVirtualFile); - - // Case 4: the rename went though, dehydration is forgotten - QVERIFY(!fakeFolder.currentLocalState().find("case4")); - QVERIFY(!fakeFolder.currentLocalState().find("case4" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("case4-rename")); - QVERIFY(!fakeFolder.currentLocalState().find("case4-rename" DVSUFFIX)); - QVERIFY(!fakeFolder.currentRemoteState().find("case4")); - QVERIFY(fakeFolder.currentRemoteState().find("case4-rename")); - QCOMPARE(itemInstruction(completeSpy, "case4-rename"), CSYNC_INSTRUCTION_RENAME); - QVERIFY(dbRecord(fakeFolder, "case4-rename")._type == ItemTypeFile); - - // Case 5: the rename went though, hydration is forgotten - QVERIFY(!fakeFolder.currentLocalState().find("case5")); - QVERIFY(!fakeFolder.currentLocalState().find("case5" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("case5-rename")); - QVERIFY(fakeFolder.currentLocalState().find("case5-rename" DVSUFFIX)); - QVERIFY(!fakeFolder.currentRemoteState().find("case5")); - QVERIFY(fakeFolder.currentRemoteState().find("case5-rename")); - QCOMPARE(itemInstruction(completeSpy, "case5-rename" DVSUFFIX), CSYNC_INSTRUCTION_RENAME); - QVERIFY(dbRecord(fakeFolder, "case5-rename" DVSUFFIX)._type == ItemTypeVirtualFile); - - // Case 6: the rename went though, dehydration is forgotten - QVERIFY(!fakeFolder.currentLocalState().find("case6")); - QVERIFY(!fakeFolder.currentLocalState().find("case6" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("case6-rename")); - QVERIFY(!fakeFolder.currentLocalState().find("case6-rename" DVSUFFIX)); - QVERIFY(!fakeFolder.currentRemoteState().find("case6")); - QVERIFY(fakeFolder.currentRemoteState().find("case6-rename")); - QCOMPARE(itemInstruction(completeSpy, "case6-rename"), CSYNC_INSTRUCTION_RENAME); - QVERIFY(dbRecord(fakeFolder, "case6-rename")._type == ItemTypeFile); - } - - // Dehydration via sync works - void testSyncDehydration() - { - FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() }; - setupVfs(fakeFolder); - - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - ItemCompletedSpy completeSpy(fakeFolder); - auto cleanup = [&]() { - completeSpy.clear(); - }; - cleanup(); - - // - // Mark for dehydration and check - // - - markForDehydration(fakeFolder, "A/a1"); - - markForDehydration(fakeFolder, "A/a2"); - fakeFolder.remoteModifier().appendByte("A/a2"); - // expect: normal dehydration - - markForDehydration(fakeFolder, "B/b1"); - fakeFolder.remoteModifier().remove("B/b1"); - // expect: local removal - - markForDehydration(fakeFolder, "B/b2"); - fakeFolder.remoteModifier().rename("B/b2", "B/b3"); - // expect: B/b2 is gone, B/b3 is NEW placeholder - - markForDehydration(fakeFolder, "C/c1"); - fakeFolder.localModifier().appendByte("C/c1"); - // expect: no dehydration, upload of c1 - - markForDehydration(fakeFolder, "C/c2"); - fakeFolder.localModifier().appendByte("C/c2"); - fakeFolder.remoteModifier().appendByte("C/c2"); - fakeFolder.remoteModifier().appendByte("C/c2"); - // expect: no dehydration, conflict - - QVERIFY(fakeFolder.syncOnce()); - - auto isDehydrated = [&](const QString &path) { - QString placeholder = path + DVSUFFIX; - return !fakeFolder.currentLocalState().find(path) - && fakeFolder.currentLocalState().find(placeholder); - }; - auto hasDehydratedDbEntries = [&](const QString &path) { - SyncJournalFileRecord normal, suffix; - fakeFolder.syncJournal().getFileRecord(path, &normal); - fakeFolder.syncJournal().getFileRecord(path + DVSUFFIX, &suffix); - return !normal.isValid() && suffix.isValid() && suffix._type == ItemTypeVirtualFile; - }; - - QVERIFY(isDehydrated("A/a1")); - QVERIFY(hasDehydratedDbEntries("A/a1")); - QCOMPARE(itemInstruction(completeSpy, "A/a1" DVSUFFIX), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(completeSpy.findItem("A/a1" DVSUFFIX)->_type, ItemTypeVirtualFileDehydration); - QCOMPARE(completeSpy.findItem("A/a1" DVSUFFIX)->_file, QStringLiteral("A/a1")); - QCOMPARE(completeSpy.findItem("A/a1" DVSUFFIX)->_renameTarget, QStringLiteral("A/a1" DVSUFFIX)); - QVERIFY(isDehydrated("A/a2")); - QVERIFY(hasDehydratedDbEntries("A/a2")); - QCOMPARE(itemInstruction(completeSpy, "A/a2" DVSUFFIX), CSYNC_INSTRUCTION_SYNC); - QCOMPARE(completeSpy.findItem("A/a2" DVSUFFIX)->_type, ItemTypeVirtualFileDehydration); - - QVERIFY(!fakeFolder.currentLocalState().find("B/b1")); - QVERIFY(!fakeFolder.currentRemoteState().find("B/b1")); - QCOMPARE(itemInstruction(completeSpy, "B/b1"), CSYNC_INSTRUCTION_REMOVE); - - QVERIFY(!fakeFolder.currentLocalState().find("B/b2")); - QVERIFY(!fakeFolder.currentRemoteState().find("B/b2")); - QVERIFY(isDehydrated("B/b3")); - QVERIFY(hasDehydratedDbEntries("B/b3")); - QCOMPARE(itemInstruction(completeSpy, "B/b2"), CSYNC_INSTRUCTION_REMOVE); - QCOMPARE(itemInstruction(completeSpy, "B/b3" DVSUFFIX), CSYNC_INSTRUCTION_NEW); - - QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->size, 25); - QCOMPARE(itemInstruction(completeSpy, "C/c1"), CSYNC_INSTRUCTION_SYNC); - - QCOMPARE(fakeFolder.currentRemoteState().find("C/c2")->size, 26); - QCOMPARE(itemInstruction(completeSpy, "C/c2"), CSYNC_INSTRUCTION_CONFLICT); - cleanup(); - - auto expectedLocalState = fakeFolder.currentLocalState(); - auto expectedRemoteState = fakeFolder.currentRemoteState(); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), expectedLocalState); - QCOMPARE(fakeFolder.currentRemoteState(), expectedRemoteState); - } - - void testWipeVirtualSuffixFiles() - { - FakeFolder fakeFolder{ FileInfo{} }; - setupVfs(fakeFolder); - - // Create a suffix-vfs baseline - - fakeFolder.remoteModifier().mkdir("A"); - fakeFolder.remoteModifier().mkdir("A/B"); - fakeFolder.remoteModifier().insert("f1"); - fakeFolder.remoteModifier().insert("A/a1"); - fakeFolder.remoteModifier().insert("A/a3"); - fakeFolder.remoteModifier().insert("A/B/b1"); - fakeFolder.localModifier().mkdir("A"); - fakeFolder.localModifier().mkdir("A/B"); - fakeFolder.localModifier().insert("f2"); - fakeFolder.localModifier().insert("A/a2"); - fakeFolder.localModifier().insert("A/B/b2"); - - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(fakeFolder.currentLocalState().find("f1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a3" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/B/b1" DVSUFFIX)); - - // Make local changes to a3 - fakeFolder.localModifier().remove("A/a3" DVSUFFIX); - fakeFolder.localModifier().insert("A/a3" DVSUFFIX, 100); - - // Now wipe the virtuals - - SyncEngine::wipeVirtualFiles(fakeFolder.localPath(), fakeFolder.syncJournal(), *fakeFolder.syncEngine().syncOptions()._vfs); - - QVERIFY(!fakeFolder.currentLocalState().find("f1" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/a1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("A/a3" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/B/b1" DVSUFFIX)); - - fakeFolder.switchToVfs(QSharedPointer(new VfsOff)); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentRemoteState().find("A/a3" DVSUFFIX)); // regular upload - QVERIFY(fakeFolder.currentLocalState() != fakeFolder.currentRemoteState()); - } - - void testNewVirtuals() - { - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - auto setPin = [&] (const QByteArray &path, PinState state) { - fakeFolder.syncJournal().internalPinStates().setForPath(path, state); - }; - - fakeFolder.remoteModifier().mkdir("local"); - fakeFolder.remoteModifier().mkdir("online"); - fakeFolder.remoteModifier().mkdir("unspec"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - setPin("local", PinState::AlwaysLocal); - setPin("online", PinState::OnlineOnly); - setPin("unspec", PinState::Unspecified); - - // Test 1: root is Unspecified - fakeFolder.remoteModifier().insert("file1"); - fakeFolder.remoteModifier().insert("online/file1"); - fakeFolder.remoteModifier().insert("local/file1"); - fakeFolder.remoteModifier().insert("unspec/file1"); - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(fakeFolder.currentLocalState().find("file1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("online/file1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("local/file1")); - QVERIFY(fakeFolder.currentLocalState().find("unspec/file1" DVSUFFIX)); - - // Test 2: change root to AlwaysLocal - setPin("", PinState::AlwaysLocal); - - fakeFolder.remoteModifier().insert("file2"); - fakeFolder.remoteModifier().insert("online/file2"); - fakeFolder.remoteModifier().insert("local/file2"); - fakeFolder.remoteModifier().insert("unspec/file2"); - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(fakeFolder.currentLocalState().find("file2")); - QVERIFY(fakeFolder.currentLocalState().find("online/file2" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("local/file2")); - QVERIFY(fakeFolder.currentLocalState().find("unspec/file2" DVSUFFIX)); - - // root file1 was hydrated due to its new pin state - QVERIFY(fakeFolder.currentLocalState().find("file1")); - - // file1 is unchanged in the explicitly pinned subfolders - QVERIFY(fakeFolder.currentLocalState().find("online/file1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("local/file1")); - QVERIFY(fakeFolder.currentLocalState().find("unspec/file1" DVSUFFIX)); - - // Test 3: change root to OnlineOnly - setPin("", PinState::OnlineOnly); - - fakeFolder.remoteModifier().insert("file3"); - fakeFolder.remoteModifier().insert("online/file3"); - fakeFolder.remoteModifier().insert("local/file3"); - fakeFolder.remoteModifier().insert("unspec/file3"); - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(fakeFolder.currentLocalState().find("file3" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("online/file3" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("local/file3")); - QVERIFY(fakeFolder.currentLocalState().find("unspec/file3" DVSUFFIX)); - - // root file1 was dehydrated due to its new pin state - QVERIFY(fakeFolder.currentLocalState().find("file1" DVSUFFIX)); - - // file1 is unchanged in the explicitly pinned subfolders - QVERIFY(fakeFolder.currentLocalState().find("online/file1" DVSUFFIX)); - QVERIFY(fakeFolder.currentLocalState().find("local/file1")); - QVERIFY(fakeFolder.currentLocalState().find("unspec/file1" DVSUFFIX)); - } - - // Check what happens if vfs-suffixed files exist on the server or in the db - void testExtraFilesLocalDehydrated() - { - FakeFolder fakeFolder{ FileInfo() }; - setupVfs(fakeFolder); - - ItemCompletedSpy completeSpy(fakeFolder); - auto cleanup = [&]() { - completeSpy.clear(); - }; - cleanup(); - - // create a bunch of local virtual files, in some instances - // ignore remote files - fakeFolder.remoteModifier().mkdir("A"); - fakeFolder.remoteModifier().insert("A/file1", 30, 'A'); - fakeFolder.remoteModifier().insert("A/file2", 40, 'A'); - fakeFolder.remoteModifier().insert("A/file3", 60, 'A'); - fakeFolder.remoteModifier().insert("A/file3" DVSUFFIX, 70, 'A'); - fakeFolder.remoteModifier().insert("A/file4", 80, 'A'); - fakeFolder.remoteModifier().insert("A/file4" DVSUFFIX, 90, 'A'); - fakeFolder.remoteModifier().insert("A/file4" DVSUFFIX DVSUFFIX, 100, 'A'); - fakeFolder.remoteModifier().insert("A/file5", 110, 'A'); - fakeFolder.remoteModifier().insert("A/file6", 120, 'A'); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(!fakeFolder.currentLocalState().find("A/file1")); - QVERIFY(fakeFolder.currentLocalState().find("A/file1" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/file2")); - QVERIFY(fakeFolder.currentLocalState().find("A/file2" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/file3")); - QVERIFY(fakeFolder.currentLocalState().find("A/file3" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/file4")); - QVERIFY(fakeFolder.currentLocalState().find("A/file4" DVSUFFIX)); - QVERIFY(!fakeFolder.currentLocalState().find("A/file4" DVSUFFIX DVSUFFIX)); - QCOMPARE(itemInstruction(completeSpy, "A/file1" DVSUFFIX), CSYNC_INSTRUCTION_NEW); - QCOMPARE(itemInstruction(completeSpy, "A/file2" DVSUFFIX), CSYNC_INSTRUCTION_NEW); - QCOMPARE(itemInstruction(completeSpy, "A/file3" DVSUFFIX), CSYNC_INSTRUCTION_NEW); - cleanup(); - - // Create odd extra files locally and remotely - fakeFolder.localModifier().insert("A/file1", 10, 'A'); - fakeFolder.localModifier().insert("A/file2" DVSUFFIX DVSUFFIX, 10, 'A'); - fakeFolder.remoteModifier().insert("A/file5" DVSUFFIX, 10, 'A'); - fakeFolder.localModifier().insert("A/file6", 10, 'A'); - fakeFolder.remoteModifier().insert("A/file6" DVSUFFIX, 10, 'A'); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(itemInstruction(completeSpy, "A/file1"), CSYNC_INSTRUCTION_CONFLICT); - QCOMPARE(itemInstruction(completeSpy, "A/file1" DVSUFFIX), CSYNC_INSTRUCTION_REMOVE); // it's now a pointless real virtual file - QCOMPARE(itemInstruction(completeSpy, "A/file6"), CSYNC_INSTRUCTION_CONFLICT); - cleanup(); - } - - void testAvailability() - { - FakeFolder fakeFolder{ FileInfo() }; - auto vfs = setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - auto setPin = [&] (const QByteArray &path, PinState state) { - fakeFolder.syncJournal().internalPinStates().setForPath(path, state); - }; - - fakeFolder.remoteModifier().mkdir("local"); - fakeFolder.remoteModifier().mkdir("local/sub"); - fakeFolder.remoteModifier().mkdir("online"); - fakeFolder.remoteModifier().mkdir("online/sub"); - fakeFolder.remoteModifier().mkdir("unspec"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - setPin("local", PinState::AlwaysLocal); - setPin("online", PinState::OnlineOnly); - setPin("unspec", PinState::Unspecified); - - fakeFolder.remoteModifier().insert("file1"); - fakeFolder.remoteModifier().insert("online/file1"); - fakeFolder.remoteModifier().insert("online/file2"); - fakeFolder.remoteModifier().insert("local/file1"); - fakeFolder.remoteModifier().insert("local/file2"); - fakeFolder.remoteModifier().insert("unspec/file1"); - QVERIFY(fakeFolder.syncOnce()); - - // root is unspecified - QCOMPARE(*vfs->availability("file1" DVSUFFIX), VfsItemAvailability::AllDehydrated); - QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AlwaysLocal); - QCOMPARE(*vfs->availability("local/file1"), VfsItemAvailability::AlwaysLocal); - QCOMPARE(*vfs->availability("online"), VfsItemAvailability::OnlineOnly); - QCOMPARE(*vfs->availability("online/file1" DVSUFFIX), VfsItemAvailability::OnlineOnly); - QCOMPARE(*vfs->availability("unspec"), VfsItemAvailability::AllDehydrated); - QCOMPARE(*vfs->availability("unspec/file1" DVSUFFIX), VfsItemAvailability::AllDehydrated); - - // Subitem pin states can ruin "pure" availabilities - setPin("local/sub", PinState::OnlineOnly); - QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AllHydrated); - setPin("online/sub", PinState::Unspecified); - QCOMPARE(*vfs->availability("online"), VfsItemAvailability::AllDehydrated); - - triggerDownload(fakeFolder, "unspec/file1"); - setPin("local/file2", PinState::OnlineOnly); - setPin("online/file2" DVSUFFIX, PinState::AlwaysLocal); - QVERIFY(fakeFolder.syncOnce()); - - QCOMPARE(*vfs->availability("unspec"), VfsItemAvailability::AllHydrated); - QCOMPARE(*vfs->availability("local"), VfsItemAvailability::Mixed); - QCOMPARE(*vfs->availability("online"), VfsItemAvailability::Mixed); - - vfs->setPinState("local", PinState::AlwaysLocal); - vfs->setPinState("online", PinState::OnlineOnly); - QVERIFY(fakeFolder.syncOnce()); - - QCOMPARE(*vfs->availability("online"), VfsItemAvailability::OnlineOnly); - QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AlwaysLocal); - - auto r = vfs->availability("nonexistant"); - QVERIFY(!r); - QCOMPARE(r.error(), Vfs::AvailabilityError::NoSuchItem); - } - - void testPinStateLocals() - { - FakeFolder fakeFolder{ FileInfo() }; - auto vfs = setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - auto setPin = [&] (const QByteArray &path, PinState state) { - fakeFolder.syncJournal().internalPinStates().setForPath(path, state); - }; - - fakeFolder.remoteModifier().mkdir("local"); - fakeFolder.remoteModifier().mkdir("online"); - fakeFolder.remoteModifier().mkdir("unspec"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - setPin("local", PinState::AlwaysLocal); - setPin("online", PinState::OnlineOnly); - setPin("unspec", PinState::Unspecified); - - fakeFolder.localModifier().insert("file1"); - fakeFolder.localModifier().insert("online/file1"); - fakeFolder.localModifier().insert("online/file2"); - fakeFolder.localModifier().insert("local/file1"); - fakeFolder.localModifier().insert("unspec/file1"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // root is unspecified - QCOMPARE(*vfs->pinState("file1" DVSUFFIX), PinState::Unspecified); - QCOMPARE(*vfs->pinState("local/file1"), PinState::AlwaysLocal); - QCOMPARE(*vfs->pinState("online/file1"), PinState::Unspecified); - QCOMPARE(*vfs->pinState("unspec/file1"), PinState::Unspecified); - - // Sync again: bad pin states of new local files usually take effect on second sync - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // When a file in an online-only folder is renamed, it retains its pin - fakeFolder.localModifier().rename("online/file1", "online/file1rename"); - fakeFolder.remoteModifier().rename("online/file2", "online/file2rename"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(*vfs->pinState("online/file1rename"), PinState::Unspecified); - QCOMPARE(*vfs->pinState("online/file2rename"), PinState::Unspecified); - - // When a folder is renamed, the pin states inside should be retained - fakeFolder.localModifier().rename("online", "onlinerenamed1"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(*vfs->pinState("onlinerenamed1"), PinState::OnlineOnly); - QCOMPARE(*vfs->pinState("onlinerenamed1/file1rename"), PinState::Unspecified); - - fakeFolder.remoteModifier().rename("onlinerenamed1", "onlinerenamed2"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(*vfs->pinState("onlinerenamed2"), PinState::OnlineOnly); - QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::Unspecified); - - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - // When a file is deleted and later a new file has the same name, the old pin - // state isn't preserved. - QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::Unspecified); - fakeFolder.remoteModifier().remove("onlinerenamed2/file1rename"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::OnlineOnly); - fakeFolder.remoteModifier().insert("onlinerenamed2/file1rename"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::OnlineOnly); - QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename" DVSUFFIX), PinState::OnlineOnly); - - // When a file is hydrated or dehydrated due to pin state it retains its pin state - vfs->setPinState("onlinerenamed2/file1rename" DVSUFFIX, PinState::AlwaysLocal); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("onlinerenamed2/file1rename")); - QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::AlwaysLocal); - - vfs->setPinState("onlinerenamed2", PinState::Unspecified); - vfs->setPinState("onlinerenamed2/file1rename", PinState::OnlineOnly); - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("onlinerenamed2/file1rename" DVSUFFIX)); - QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename" DVSUFFIX), PinState::OnlineOnly); - } - - void testIncompatiblePins() - { - FakeFolder fakeFolder{ FileInfo() }; - auto vfs = setupVfs(fakeFolder); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - auto setPin = [&] (const QByteArray &path, PinState state) { - fakeFolder.syncJournal().internalPinStates().setForPath(path, state); - }; - - fakeFolder.remoteModifier().mkdir("local"); - fakeFolder.remoteModifier().mkdir("online"); - QVERIFY(fakeFolder.syncOnce()); - QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); - - setPin("local", PinState::AlwaysLocal); - setPin("online", PinState::OnlineOnly); - - fakeFolder.localModifier().insert("local/file1"); - fakeFolder.localModifier().insert("online/file1"); - QVERIFY(fakeFolder.syncOnce()); - - markForDehydration(fakeFolder, "local/file1"); - triggerDownload(fakeFolder, "online/file1"); - - // the sync sets the changed files pin states to unspecified - QVERIFY(fakeFolder.syncOnce()); - - QVERIFY(fakeFolder.currentLocalState().find("online/file1")); - QVERIFY(fakeFolder.currentLocalState().find("local/file1" DVSUFFIX)); - QCOMPARE(*vfs->pinState("online/file1"), PinState::Unspecified); - QCOMPARE(*vfs->pinState("local/file1" DVSUFFIX), PinState::Unspecified); - - // no change on another sync - QVERIFY(fakeFolder.syncOnce()); - QVERIFY(fakeFolder.currentLocalState().find("online/file1")); - QVERIFY(fakeFolder.currentLocalState().find("local/file1" DVSUFFIX)); - } -}; - -QTEST_GUILESS_MAIN(TestSyncVirtualFiles) -#include "testsyncvirtualfiles.moc" diff --git a/test/testupdater.cpp b/test/testupdater.cpp new file mode 100644 index 00000000000..b7234c5ea79 --- /dev/null +++ b/test/testupdater.cpp @@ -0,0 +1,58 @@ +/* + This software is in the public domain, furnished "as is", without technical + support, and with no warranty, express or implied, as to its usefulness for + any purpose. +*/ + +#include + +#include "updater/updater.h" +#include "updater/ocupdater.h" + +namespace OCC { + +class TestUpdater : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testDownload_data() + { + QTest::addColumn("url"); + QTest::addColumn("result"); + // a redirect to attic + QTest::newRow("redirect") << "https://download.owncloud.com/desktop/ownCloud/stable/6.0.3.18040/win/ownCloud-6.0.3.18040.x64.msi" << OCUpdater::DownloadComplete; + QTest::newRow("broken url") << "https://&" << OCUpdater::DownloadFailed; + } + + void testDownload() + { + QFETCH(QString, url); + QFETCH(OCUpdater::DownloadState, result); + UpdateInfo info; + info.setDownloadUrl(url); + info.setVersionString(QStringLiteral("ownCloud 2.2.4 (build 6408)")); + // ensure we do the update + info.setVersion(QStringLiteral("100.2.4.6408")); + auto *updater = new WindowsUpdater({}); + QSignalSpy downloadSpy(updater, &WindowsUpdater::downloadStateChanged); + updater->versionInfoArrived(info); + // we might have multiple state changes allow them to happen + for (int i = 0; i <= OCUpdater::UpdateOnlyAvailableThroughSystem; ++i) { + // wait up to a minute, we are actually downloading a file + if (!downloadSpy.wait(60000)) { + // we timed out + break; + } + if (updater->downloadState() == result) { + break; + } + } + QCOMPARE(updater->downloadState(), result); + updater->deleteLater(); + } +}; +} + +QTEST_MAIN(OCC::TestUpdater) +#include "testupdater.moc" diff --git a/test/testuploadreset.cpp b/test/testuploadreset.cpp index d954e7553b0..9d41fec4b0b 100644 --- a/test/testuploadreset.cpp +++ b/test/testuploadreset.cpp @@ -4,71 +4,36 @@ * any purpose. * */ - -#include -#include "testutils/syncenginetestutils.h" #include #include +#include "testutils/syncenginetestutils.h" +#include "testutils/testutils.h" + +#include + using namespace OCC; class TestUploadReset : public QObject { Q_OBJECT -private slots: - - // Verify that the chunked transfer eventually gets reset with the new chunking - void testFileUploadNg() { - FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()}; - - fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ - {"chunking", "1.0"}, - {"httpErrorCodesThatResetFailingChunkedUploads", QVariantList{500} } } } }); - - const int size = 100 * 1000 * 1000; // 100 MB - fakeFolder.localModifier().insert("A/a0", size); - QDateTime modTime = QDateTime::currentDateTime(); - fakeFolder.localModifier().setModTime("A/a0", modTime); - - // Create a transfer id, so we can make the final MOVE fail - SyncJournalDb::UploadInfo uploadInfo; - uploadInfo._transferid = 1; - uploadInfo._valid = true; - uploadInfo._modtime = Utility::qDateTimeToTime_t(modTime); - uploadInfo._size = size; - fakeFolder.syncEngine().journal()->setUploadInfo("A/a0", uploadInfo); - - fakeFolder.uploadState().mkdir("1"); - fakeFolder.serverErrorPaths().append("1/.file"); - - QVERIFY(!fakeFolder.syncOnce()); - - uploadInfo = fakeFolder.syncEngine().journal()->getUploadInfo("A/a0"); - QCOMPARE(uploadInfo._errorCount, 1); - QCOMPARE(uploadInfo._transferid, 1U); - - fakeFolder.syncEngine().journal()->wipeErrorBlacklist(); - QVERIFY(!fakeFolder.syncOnce()); - - uploadInfo = fakeFolder.syncEngine().journal()->getUploadInfo("A/a0"); - QCOMPARE(uploadInfo._errorCount, 2); - QCOMPARE(uploadInfo._transferid, 1U); - - fakeFolder.syncEngine().journal()->wipeErrorBlacklist(); - QVERIFY(!fakeFolder.syncOnce()); +private Q_SLOTS: + void initTestCase_data() + { + QTest::addColumn("vfsMode"); + QTest::addColumn("filesAreDehydrated"); - uploadInfo = fakeFolder.syncEngine().journal()->getUploadInfo("A/a0"); - QCOMPARE(uploadInfo._errorCount, 3); - QCOMPARE(uploadInfo._transferid, 1U); + QTest::newRow("Vfs::Off") << Vfs::Off << false; - fakeFolder.syncEngine().journal()->wipeErrorBlacklist(); - QVERIFY(!fakeFolder.syncOnce()); + if (VfsPluginManager::instance().isVfsPluginAvailable(Vfs::WindowsCfApi)) { + QTest::newRow("Vfs::WindowsCfApi dehydrated") << Vfs::WindowsCfApi << true; - uploadInfo = fakeFolder.syncEngine().journal()->getUploadInfo("A/a0"); - QCOMPARE(uploadInfo._errorCount, 0); - QCOMPARE(uploadInfo._transferid, 0U); - QVERIFY(!uploadInfo._valid); + // TODO: the hydrated version will fail due to an issue in the winvfs plugin, so leave it disabled for now. + // QTest::newRow("Vfs::WindowsCfApi hydrated") << Vfs::WindowsCfApi << false; + } else if (Utility::isWindows()) { + qWarning("Skipping Vfs::WindowsCfApi"); + } } }; diff --git a/test/testutility.cpp b/test/testutility.cpp index 94e5d6ff441..2191eb86ac0 100644 --- a/test/testutility.cpp +++ b/test/testutility.cpp @@ -7,9 +7,16 @@ #include #include +#include "filesystem.h" +#include "testutils/testutils.h" + #include "common/filesystembase.h" #include "common/utility.h" -#include "config.h" +#ifdef Q_OS_WIN +#include "common/utility_win.h" +#endif + +using namespace std::chrono_literals; using namespace OCC::Utility; @@ -21,43 +28,37 @@ class TestUtility : public QObject { Q_OBJECT -private slots: - void testFormatFingerprint() - { - QVERIFY2(formatFingerprint("68ac906495480a3404beee4874ed853a037a7a8f") - == "68:ac:90:64:95:48:0a:34:04:be:ee:48:74:ed:85:3a:03:7a:7a:8f", - "Utility::formatFingerprint() is broken"); - } +private Q_SLOTS: void testOctetsToString() { - QLocale::setDefault(QLocale("en")); - QCOMPARE(octetsToString(999) , QString("999 B")); - QCOMPARE(octetsToString(1024) , QString("1 KB")); - QCOMPARE(octetsToString(1364) , QString("1 KB")); - - QCOMPARE(octetsToString(9110) , QString("9 KB")); - QCOMPARE(octetsToString(9910) , QString("10 KB")); - QCOMPARE(octetsToString(10240) , QString("10 KB")); - - QCOMPARE(octetsToString(123456) , QString("121 KB")); - QCOMPARE(octetsToString(1234567) , QString("1.2 MB")); - QCOMPARE(octetsToString(12345678) , QString("12 MB")); - QCOMPARE(octetsToString(123456789) , QString("118 MB")); - QCOMPARE(octetsToString(1000LL*1000*1000 * 5) , QString("4.7 GB")); - - QCOMPARE(octetsToString(1), QString("1 B")); - QCOMPARE(octetsToString(2), QString("2 B")); - QCOMPARE(octetsToString(1024), QString("1 KB")); - QCOMPARE(octetsToString(1024*1024), QString("1 MB")); - QCOMPARE(octetsToString(1024LL*1024*1024), QString("1 GB")); + QLocale::setDefault(QLocale(QStringLiteral("en"))); + QCOMPARE(octetsToString(999), QString::fromLatin1("999 bytes")); + QCOMPARE(octetsToString(1024), QString::fromLatin1("1 kB")); + QCOMPARE(octetsToString(1364), QString::fromLatin1("1 kB")); + + QCOMPARE(octetsToString(9110), QString::fromLatin1("9 kB")); + QCOMPARE(octetsToString(9910), QString::fromLatin1("10 kB")); + QCOMPARE(octetsToString(10240), QString::fromLatin1("10 kB")); + + QCOMPARE(octetsToString(123456), QString::fromLatin1("121 kB")); + QCOMPARE(octetsToString(1234567), QString::fromLatin1("1.2 MB")); + QCOMPARE(octetsToString(12345678), QString::fromLatin1("11.8 MB")); + QCOMPARE(octetsToString(123456789), QString::fromLatin1("117.7 MB")); + QCOMPARE(octetsToString(1000LL * 1000 * 1000 * 5), QString::fromLatin1("4.66 GB")); + + QCOMPARE(octetsToString(1), QString::fromLatin1("1 bytes")); + QCOMPARE(octetsToString(2), QString::fromLatin1("2 bytes")); + QCOMPARE(octetsToString(1024), QString::fromLatin1("1 kB")); + QCOMPARE(octetsToString(1024 * 1024), QString::fromLatin1("1.0 MB")); + QCOMPARE(octetsToString(1024LL * 1024 * 1024), QString::fromLatin1("1.00 GB")); } void testLaunchOnStartup() { QString postfix = QString::number(QRandomGenerator::global()->generate()); - const QString appName = QString::fromLatin1("testLaunchOnStartup.%1").arg(postfix); - const QString guiName = "LaunchOnStartup GUI Name"; + const QString appName = QStringLiteral("testLaunchOnStartup.%1").arg(postfix); + const QString guiName = QStringLiteral("LaunchOnStartup GUI Name"); QVERIFY(hasLaunchOnStartup(appName) == false); setLaunchOnStartup(appName, guiName, true); @@ -66,77 +67,78 @@ private slots: QVERIFY(hasLaunchOnStartup(appName) == false); } - void testDurationToDescriptiveString() + void testDurationToDescriptiveString_data() { - QLocale::setDefault(QLocale("C")); - //NOTE: in order for the plural to work we would need to load the english translation - - quint64 sec = 1000; - quint64 hour = 3600 * sec; - - QDateTime current = QDateTime::currentDateTimeUtc(); - - QCOMPARE(durationToDescriptiveString2(0), QString("0 second(s)") ); - QCOMPARE(durationToDescriptiveString2(5), QString("0 second(s)") ); - QCOMPARE(durationToDescriptiveString2(1000), QString("1 second(s)") ); - QCOMPARE(durationToDescriptiveString2(1005), QString("1 second(s)") ); - QCOMPARE(durationToDescriptiveString2(56123), QString("56 second(s)") ); - QCOMPARE(durationToDescriptiveString2(90*sec), QString("1 minute(s) 30 second(s)") ); - QCOMPARE(durationToDescriptiveString2(3*hour), QString("3 hour(s)") ); - QCOMPARE(durationToDescriptiveString2(3*hour + 20*sec), QString("3 hour(s)") ); - QCOMPARE(durationToDescriptiveString2(3*hour + 70*sec), QString("3 hour(s) 1 minute(s)") ); - QCOMPARE(durationToDescriptiveString2(3*hour + 100*sec), QString("3 hour(s) 2 minute(s)") ); - QCOMPARE(durationToDescriptiveString2(current.msecsTo(current.addYears(4).addMonths(5).addDays(2).addSecs(23*60*60))), - QString("4 year(s) 5 month(s)") ); - QCOMPARE(durationToDescriptiveString2(current.msecsTo(current.addDays(2).addSecs(23*60*60))), - QString("2 day(s) 23 hour(s)") ); - - QCOMPARE(durationToDescriptiveString1(0), QString("0 second(s)") ); - QCOMPARE(durationToDescriptiveString1(5), QString("0 second(s)") ); - QCOMPARE(durationToDescriptiveString1(1000), QString("1 second(s)") ); - QCOMPARE(durationToDescriptiveString1(1005), QString("1 second(s)") ); - QCOMPARE(durationToDescriptiveString1(56123), QString("56 second(s)") ); - QCOMPARE(durationToDescriptiveString1(90*sec), QString("2 minute(s)") ); - QCOMPARE(durationToDescriptiveString1(3*hour), QString("3 hour(s)") ); - QCOMPARE(durationToDescriptiveString1(3*hour + 20*sec), QString("3 hour(s)") ); - QCOMPARE(durationToDescriptiveString1(3*hour + 70*sec), QString("3 hour(s)") ); - QCOMPARE(durationToDescriptiveString1(3*hour + 100*sec), QString("3 hour(s)") ); - QCOMPARE(durationToDescriptiveString1(current.msecsTo(current.addYears(4).addMonths(5).addDays(2).addSecs(23*60*60))), - QString("4 year(s)") ); - QCOMPARE(durationToDescriptiveString1(current.msecsTo(current.addDays(2).addSecs(23*60*60))), - QString("3 day(s)") ); - + QTest::addColumn("useTranslation"); + QTest::addColumn("input"); + QTest::addColumn("output1"); + QTest::addColumn("output2"); + + + const QDateTime current = QDateTime::currentDateTimeUtc(); + QTest::newRow("0ms") << false << std::chrono::milliseconds(0ms) << QStringLiteral("0 second(s)") << QString(); + QTest::newRow("5ms") << false << std::chrono::milliseconds(5ms) << QStringLiteral("0 second(s)") << QString(); + QTest::newRow("1s") << false << std::chrono::milliseconds(1s) << QStringLiteral("1 second(s)") << QString(); + QTest::newRow("1005ms") << false << std::chrono::milliseconds(1005ms) << QStringLiteral("1 second(s)") << QString(); + QTest::newRow("56123ms") << false << std::chrono::milliseconds(56123ms) << QStringLiteral("56 second(s)") << QString(); + QTest::newRow("90s") << false << std::chrono::milliseconds(90s) << QStringLiteral("2 minute(s)") << QStringLiteral("1 minute(s) 30 second(s)"); + QTest::newRow("3h") << false << std::chrono::milliseconds(3h) << QStringLiteral("3 hour(s)") << QString(); + QTest::newRow("3h + 20s") << false << std::chrono::milliseconds(3h + 20s) << QStringLiteral("3 hour(s)") << QString(); + QTest::newRow("3h + 70s") << false << std::chrono::milliseconds(3h + 70s) << QStringLiteral("3 hour(s)") << QStringLiteral("3 hour(s) 1 minute(s)"); + QTest::newRow("3h + 100s") << false << std::chrono::milliseconds(3h + 100s) << QStringLiteral("3 hour(s)") << QStringLiteral("3 hour(s) 2 minute(s)"); + QTest::newRow("4years") << false << std::chrono::milliseconds(current.addYears(4).addMonths(5).addDays(2).addSecs(23 * 60 * 60) - current) + << QStringLiteral("4 year(s)") << QStringLiteral("4 year(s) 5 month(s)"); + QTest::newRow("2days") << false << std::chrono::milliseconds(current.addDays(2).addSecs(23 * 60 * 60) - current) << QStringLiteral("3 day(s)") + << QStringLiteral("2 day(s) 23 hour(s)"); + + + QTest::newRow("0ms translated") << true << std::chrono::milliseconds(0ms) << QStringLiteral("0 seconds") << QString(); + QTest::newRow("5ms translated") << true << std::chrono::milliseconds(5ms) << QStringLiteral("0 seconds") << QString(); + QTest::newRow("1s translated") << true << std::chrono::milliseconds(1s) << QStringLiteral("1 second") << QString(); + QTest::newRow("1005ms translated") << true << std::chrono::milliseconds(1005ms) << QStringLiteral("1 second") << QString(); + QTest::newRow("56123ms translated") << true << std::chrono::milliseconds(56123ms) << QStringLiteral("56 seconds") << QString(); + QTest::newRow("90s translated") << true << std::chrono::milliseconds(90s) << QStringLiteral("2 minutes") << QStringLiteral("1 minute 30 seconds"); + QTest::newRow("3h translated") << true << std::chrono::milliseconds(3h) << QStringLiteral("3 hours") << QString(); + QTest::newRow("3h + 20s translated") << true << std::chrono::milliseconds(3h + 20s) << QStringLiteral("3 hours") << QString(); + QTest::newRow("3h + 70s translated") << true << std::chrono::milliseconds(3h + 70s) << QStringLiteral("3 hours") << QStringLiteral("3 hours 1 minute"); + QTest::newRow("3h + 100s translated") << true << std::chrono::milliseconds(3h + 100s) << QStringLiteral("3 hours") + << QStringLiteral("3 hours 2 minutes"); + QTest::newRow("4years translated") << true << std::chrono::milliseconds(current.addYears(4).addMonths(5).addDays(2).addSecs(23 * 60 * 60) - current) + << QStringLiteral("4 years") << QStringLiteral("4 years 5 months"); + QTest::newRow("2days translated") << true << std::chrono::milliseconds(current.addDays(2).addSecs(23 * 60 * 60) - current) << QStringLiteral("3 days") + << QStringLiteral("2 days 23 hours"); } - void testVersionOfInstalledBinary() + + void testDurationToDescriptiveString() { - if(isLinux()) { - // pass the cmd client from our build dir - // this is a bit inaccurate as it does not test the "real thing" - // but cmd and gui have the same --version handler by now - // and cmd works without X in CI - QString ver = versionOfInstalledBinary(QStringLiteral(OWNCLOUD_BIN_PATH "/" APPLICATION_EXECUTABLE "cmd")); - qDebug() << "Version of installed ownCloud Binary: " << ver; - QVERIFY(!ver.isEmpty()); - - QRegExp rx(APPLICATION_SHORTNAME " \\d+\\.\\d+\\.\\d+.*"); - QVERIFY(rx.exactMatch(ver)); - } else { - QVERIFY(versionOfInstalledBinary().isEmpty()); + QFETCH(bool, useTranslation); + QFETCH(std::chrono::milliseconds, input); + QFETCH(QString, output1); + QFETCH(QString, output2); + + QScopedPointer translator; + if (useTranslation) { + translator.reset(new QTranslator(QCoreApplication::instance())); + QVERIFY(translator->load(QStringLiteral("client_en.ts"), QStringLiteral(":/client/translations/"))); + QCoreApplication::instance()->installTranslator(translator.data()); } + + QCOMPARE(durationToDescriptiveString1(input), output1); + QCOMPARE(durationToDescriptiveString2(input), output2.isEmpty() ? output1 : output2); } void testTimeAgo() { // Both times in same timezone - QDateTime d1 = QDateTime::fromString("2015-01-24T09:20:30+01:00", Qt::ISODate); - QDateTime d2 = QDateTime::fromString("2015-01-23T09:20:30+01:00", Qt::ISODate); + QDateTime d1 = QDateTime::fromString(QStringLiteral("2015-01-24T09:20:30+01:00"), Qt::ISODate); + QDateTime d2 = QDateTime::fromString(QStringLiteral("2015-01-23T09:20:30+01:00"), Qt::ISODate); QString s = timeAgoInWords(d2, d1); QCOMPARE(s, QLatin1String("1 day(s) ago")); // Different timezones - QDateTime earlyTS = QDateTime::fromString("2015-01-24T09:20:30+01:00", Qt::ISODate); - QDateTime laterTS = QDateTime::fromString("2015-01-24T09:20:30-01:00", Qt::ISODate); + QDateTime earlyTS = QDateTime::fromString(QStringLiteral("2015-01-24T09:20:30+01:00"), Qt::ISODate); + QDateTime laterTS = QDateTime::fromString(QStringLiteral("2015-01-24T09:20:30-01:00"), Qt::ISODate); s = timeAgoInWords(earlyTS, laterTS); QCOMPARE(s, QLatin1String("2 hour(s) ago")); @@ -148,17 +150,17 @@ private slots: earlyTS = earlyTS.addSecs(-6); s = timeAgoInWords(earlyTS, laterTS ); - QCOMPARE(s, QLatin1String("Less than a minute ago")); + QCOMPARE(s, QLatin1String("less than a minute ago")); } void testFsCasePreserving() { - QVERIFY(isMac() || isWindows() ? fsCasePreserving() : ! fsCasePreserving()); + QVERIFY(isMac() || isWindows() ? fsCasePreservingButCaseInsensitive() : !fsCasePreservingButCaseInsensitive()); QScopedValueRollback scope(OCC::fsCasePreserving_override); OCC::fsCasePreserving_override = 1; - QVERIFY(fsCasePreserving()); + QVERIFY(fsCasePreservingButCaseInsensitive()); OCC::fsCasePreserving_override = 0; - QVERIFY(! fsCasePreserving()); + QVERIFY(!fsCasePreservingButCaseInsensitive()); } void testFileNamesEqual() @@ -166,25 +168,25 @@ private slots: QTemporaryDir dir; QVERIFY(dir.isValid()); QDir dir2(dir.path()); - QVERIFY(dir2.mkpath("test")); - if( !fsCasePreserving() ) { - QVERIFY(dir2.mkpath("TEST")); + QVERIFY(dir2.mkpath(QStringLiteral("1"))); + if (!fsCasePreservingButCaseInsensitive()) { + QVERIFY(dir2.mkpath(QStringLiteral("TEST"))); } - QVERIFY(dir2.mkpath("test/TESTI")); - QVERIFY(dir2.mkpath("TESTI")); + QVERIFY(dir2.mkpath(QStringLiteral("test/TESTI"))); + QVERIFY(dir2.mkpath(QStringLiteral("TESTI"))); QString a = dir.path(); QString b = dir.path(); QVERIFY(fileNamesEqual(a, b)); - QVERIFY(fileNamesEqual(a+"/test", b+"/test")); // both exist - QVERIFY(fileNamesEqual(a+"/test/TESTI", b+"/test/../test/TESTI")); // both exist + QVERIFY(fileNamesEqual(a + QStringLiteral("/test"), b + QStringLiteral("/test"))); // both exist + QVERIFY(fileNamesEqual(a + QStringLiteral("/test/TESTI"), b + QStringLiteral("/test/../test/TESTI"))); // both exist QScopedValueRollback scope(OCC::fsCasePreserving_override, true); - QVERIFY(fileNamesEqual(a+"/test", b+"/TEST")); // both exist + QVERIFY(fileNamesEqual(a + QStringLiteral("/test"), b + QStringLiteral("/TEST"))); // both exist - QVERIFY(!fileNamesEqual(a+"/test", b+"/test/TESTI")); // both are different + QVERIFY(!fileNamesEqual(a + QStringLiteral("/test"), b + QStringLiteral("/test/TESTI"))); // both are different dir.remove(); } @@ -203,6 +205,8 @@ private slots: add(QStringLiteral("/A/a"), QStringLiteral("/A"), true); add(QStringLiteral("/A/a"), QStringLiteral("/A/a"), true); add(QStringLiteral("/A/a"), QStringLiteral("/A/a/"), true); + add(QStringLiteral("/A/a/"), QStringLiteral("/A/a/"), true); + add(QStringLiteral("/A/a/"), QStringLiteral("/A/a"), true); add(QStringLiteral("C:/A/a"), QStringLiteral("C:/A"), true); add(QStringLiteral("C:/Aa"), QStringLiteral("C:/A"), false); add(QStringLiteral("C:/Aa"), QStringLiteral("C:/A"), false); @@ -219,6 +223,13 @@ private slots: add(QStringLiteral("https://foo/bar"), QStringLiteral("https://foo"), true); add(QStringLiteral("https://foo/bar"), QStringLiteral("http://foo"), false); add(QStringLiteral("https://foo/bar"), QStringLiteral("http://foo/foo"), false); +#ifdef Q_OS_WIN + // QDir::cleanPath converts \\ only on Windows + add(QStringLiteral("C:/Program Files/test)"), QStringLiteral("C:/Program Files"), true); + add(QStringLiteral(R"(C:\Program Files\test)"), QStringLiteral("C:/Program Files"), true); + add(QStringLiteral(R"(C:\Program Files\test\)"), QStringLiteral("C:/Program Files\\"), true); + add(QStringLiteral(R"(C:\Program Files\test\\\)"), QStringLiteral("C:/Program Files/test"), true); +#endif } void testIsChildOf() @@ -257,11 +268,10 @@ private slots: void testNormalizeEtag() { - QByteArray str; + ; #define CHECK_NORMALIZE_ETAG(TEST, EXPECT) \ - str = OCC::Utility::normalizeEtag(TEST); \ - QCOMPARE(str.constData(), EXPECT); \ + QCOMPARE(OCC::Utility::normalizeEtag(QStringLiteral(TEST)), QStringLiteral(EXPECT)); CHECK_NORMALIZE_ETAG("foo", "foo"); CHECK_NORMALIZE_ETAG("\"foo\"", "foo"); @@ -274,6 +284,68 @@ private slots: CHECK_NORMALIZE_ETAG("\"foo\"-gzip", "foo"); CHECK_NORMALIZE_ETAG("\"foo-gzip\"", "foo"); } + + void testFileMetaData() + { + using namespace OCC::TestUtils; + using namespace OCC::FileSystem; + + QTemporaryDir temp = createTempDir(); + QFile tempFile(temp.filePath(QStringLiteral("testfile"))); + QVERIFY(tempFile.open(QIODevice::WriteOnly)); + QByteArray data(64, 'X'); + QCOMPARE(tempFile.write(data), data.size()); + tempFile.close(); + + const auto fn = tempFile.fileName(); + const QString testKey = QStringLiteral("testKey"); + const QByteArray testValue("testValue"); + + QVERIFY(!Tags::get(fn, testKey).has_value()); + QVERIFY(Tags::set(fn, testKey, testValue)); + QCOMPARE(Tags::get(fn, testKey).value(), testValue); + QVERIFY(Tags::remove(fn, testKey)); + QVERIFY(!Tags::get(fn, testKey).has_value()); + } + + void testfileSystemForPath() + { +#ifndef Q_OS_WIN + { + QString fs = OCC::FileSystem::fileSystemForPath(QStringLiteral("/foo/bar")); + QCOMPARE_NE(fs, QString()); + } +#endif + } + + void testDirMetaData() + { + using namespace OCC::TestUtils; + using namespace OCC::FileSystem; + + QTemporaryDir tempDir = createTempDir(); + + const auto fn = tempDir.path(); + const QString testKey = QStringLiteral("testKey"); + const QByteArray testValue("testValue"); + + QVERIFY(!Tags::get(fn, testKey).has_value()); + QVERIFY(Tags::set(fn, testKey, testValue)); + QCOMPARE(Tags::get(fn, testKey).value(), testValue); + QVERIFY(Tags::remove(fn, testKey)); + QVERIFY(!Tags::get(fn, testKey).has_value()); + } +#ifdef Q_OS_WIN + void testUtilityUnixTimeToLargeIntegerFiletime() + { + // Sun Jan 31 2038 23:00:00 GMT+0000 + time_t y2k38 = 2148591600; + LARGE_INTEGER out; + OCC::Utility::UnixTimeToLargeIntegerFiletime(y2k38, &out); + // (2148591600 * 10000000) + 116444736000000000 + QCOMPARE(out.QuadPart, 137930652000000000); + } +#endif }; QTEST_GUILESS_MAIN(TestUtility) diff --git a/test/testutils/CMakeLists.txt b/test/testutils/CMakeLists.txt index a27fa720b6d..efa20a1fa21 100644 --- a/test/testutils/CMakeLists.txt +++ b/test/testutils/CMakeLists.txt @@ -1,12 +1,11 @@ +add_executable(test_helper test_helper.cpp) +target_link_libraries(test_helper PUBLIC Qt::Core libsync) + add_library(syncenginetestutils STATIC syncenginetestutils.cpp testutils.cpp) -target_link_libraries(syncenginetestutils PUBLIC owncloudCore Qt5::Test) +target_link_libraries(syncenginetestutils PUBLIC owncloudGui Qt::Test) +target_compile_definitions(syncenginetestutils PRIVATE TEST_HELPER_EXE="$") # testutilsloader.cpp uses Q_COREAPP_STARTUP_FUNCTION which can't used reliably in a static lib # therefore we compile it in the tests -if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0") - add_library(testutilsloader OBJECT testutilsloader.cpp) - target_link_libraries(testutilsloader PUBLIC owncloudCore) -else() - add_library(testutilsloader INTERFACE) - target_sources(testutilsloader INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/testutilsloader.cpp) -endif() +add_library(testutilsloader OBJECT testutilsloader.cpp) +target_link_libraries(testutilsloader PUBLIC owncloudGui owncloudResources) diff --git a/test/testutils/syncenginetestutils.cpp b/test/testutils/syncenginetestutils.cpp index cc7604e6a1d..f1d3f49a0a3 100644 --- a/test/testutils/syncenginetestutils.cpp +++ b/test/testutils/syncenginetestutils.cpp @@ -6,17 +6,18 @@ */ #include "syncenginetestutils.h" +#include "testutils.h" -#include "httplogger.h" #include "accessmanager.h" -#include "libsync/configfile.h" +#include "accountmanager.h" +#include "httplogger.h" +#include "libsync/syncresult.h" -using namespace std::chrono_literals; +#include +#include -PathComponents::PathComponents(const char *path) - : PathComponents { QString::fromUtf8(path) } -{ -} +using namespace std::chrono_literals; +using namespace std::chrono; PathComponents::PathComponents(const QString &path) : QStringList { path.split(QLatin1Char('/'), Qt::SkipEmptyParts) } @@ -40,76 +41,95 @@ PathComponents PathComponents::subComponents() const & void DiskFileModifier::remove(const QString &relativePath) { - QFileInfo fi { _rootDir.filePath(relativePath) }; - if (fi.isFile()) - QVERIFY(_rootDir.remove(relativePath)); - else - QVERIFY(QDir { fi.filePath() }.removeRecursively()); + _processArguments.append({QStringLiteral("remove"), relativePath}); } -void DiskFileModifier::insert(const QString &relativePath, qint64 size, char contentChar) +void DiskFileModifier::insert(const QString &relativePath, quint64 size, char contentChar) { - QFile file { _rootDir.filePath(relativePath) }; - QVERIFY(!file.exists()); - file.open(QFile::WriteOnly); - QByteArray buf(1024, contentChar); - for (int x = 0; x < size / buf.size(); ++x) { - file.write(buf); - } - file.write(buf.data(), size % buf.size()); - file.close(); - // Set the mtime 30 seconds in the past, for some tests that need to make sure that the mtime differs. - OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(QDateTime::currentDateTimeUtc().addSecs(-30))); - QCOMPARE(file.size(), size); -} + // In the test helper, the insert command is implemented by filling a QByteArray, and then + // writing that to disk. The size of the QByteArray is an int, so make sure that we don't end + // up with a negative int when converting from quint64 -> int. + Q_ASSERT(size <= std::numeric_limits::max()); -void DiskFileModifier::setContents(const QString &relativePath, char contentChar) -{ - QFile file { _rootDir.filePath(relativePath) }; - QVERIFY(file.exists()); - qint64 size = file.size(); - file.open(QFile::WriteOnly); - file.write(QByteArray {}.fill(contentChar, size)); + _processArguments.append({QStringLiteral("insert"), relativePath, QString::number(size), QChar::fromLatin1(contentChar)}); } -void DiskFileModifier::appendByte(const QString &relativePath, char contentChar) +void DiskFileModifier::setContents(const QString &relativePath, quint64 newSize, char contentChar) { - QFile file { _rootDir.filePath(relativePath) }; - QVERIFY(file.exists()); - file.open(QFile::ReadWrite); - QByteArray contents; - if (contentChar) - contents += contentChar; - else - contents = file.read(1); - file.seek(file.size()); - file.write(contents); + _processArguments.append({QStringLiteral("contents"), relativePath, QString::number(newSize), QChar::fromLatin1(contentChar)}); } -void DiskFileModifier::modifyByte(const QString &relativePath, quint64 offset, char contentChar) +void DiskFileModifier::appendByte(const QString &relativePath, char contentChar) { - QFile file { _rootDir.filePath(relativePath) }; - QVERIFY(file.exists()); - file.open(QFile::ReadWrite); - file.seek(offset); - file.write(&contentChar, 1); - file.close(); + _processArguments.append({QStringLiteral("appendbyte"), relativePath, QChar::fromLatin1(contentChar)}); } void DiskFileModifier::mkdir(const QString &relativePath) { - _rootDir.mkpath(relativePath); + _processArguments.append({QStringLiteral("mkdir"), relativePath}); } void DiskFileModifier::rename(const QString &from, const QString &to) { - QVERIFY(_rootDir.exists(from)); - QVERIFY(_rootDir.rename(from, to)); + _processArguments.append({QStringLiteral("rename"), from, to}); } void DiskFileModifier::setModTime(const QString &relativePath, const QDateTime &modTime) { - OCC::FileSystem::setModTime(_rootDir.filePath(relativePath), OCC::Utility::qDateTimeToTime_t(modTime)); + _processArguments.append({QStringLiteral("mtime"), relativePath, QString::number(modTime.toSecsSinceEpoch())}); +} + +class HelperProcess : public QProcess +{ +public: + HelperProcess(const QDir &rootDir, QStringList processArguments) // copy is on purpose + { + if (processArguments.isEmpty()) { // fast-path: + finished = true; + succeeded = true; + return; + } + + setProcessChannelMode(QProcess::MergedChannels); + processArguments.prepend(rootDir.absolutePath()); + QObject::connect(this, &QProcess::readyRead, [this]() { + while (canReadLine()) { + qDebug() << "helper output:" << readLine(); + } + }); + QObject::connect(this, QOverload::of(&QProcess::finished), + [this](int exitCode, QProcess::ExitStatus exitStatus) { + qDebug() << "helper finished:" << exitCode << exitStatus; + this->finished = true; + this->succeeded = exitStatus == QProcess::NormalExit && exitCode == 0; + }); + + qDebug() << "Starting helper:" << TEST_HELPER_EXE << processArguments; + start(QStringLiteral(TEST_HELPER_EXE), processArguments); + } + +public: + bool finished = false; + bool succeeded = true; +}; + +bool DiskFileModifier::applyModifications() +{ + if (_processArguments.isEmpty()) { + return true; + } + + HelperProcess helper(_rootDir, _processArguments); + helper.waitForStarted(); + _processArguments.clear(); // the helper has a copy, we clean it now for the next run + do { + std::this_thread::sleep_for(50ms); + QThread::currentThread()->eventDispatcher()->processEvents(QEventLoop::AllEvents); + } while (!helper.finished); + if (!helper.succeeded) { + qWarning() << Q_FUNC_INFO << "failed"; + } + return helper.succeeded; } FileInfo FileInfo::A12_B12_C12_S12() @@ -134,11 +154,12 @@ FileInfo::FileInfo(const QString &name, const std::initializer_list &c addChild(source); } -void FileInfo::addChild(const FileInfo &info) +FileInfo &FileInfo::addChild(const FileInfo &info) { - auto &dest = this->children[info.name] = info; + FileInfo &dest = this->children[info.name] = info; dest.parentPath = path(); dest.fixupParentPathRecursively(); + return dest; } void FileInfo::remove(const QString &relativePath) @@ -146,37 +167,34 @@ void FileInfo::remove(const QString &relativePath) const PathComponents pathComponents { relativePath }; FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents()); Q_ASSERT(parent); - parent->children.erase(std::find_if(parent->children.begin(), parent->children.end(), - [&pathComponents](const FileInfo &fi) { return fi.name == pathComponents.fileName(); })); + parent->children.remove(pathComponents.fileName()); } -void FileInfo::insert(const QString &relativePath, qint64 size, char contentChar) +void FileInfo::insert(const QString &relativePath, quint64 size, char contentChar) { create(relativePath, size, contentChar); } -void FileInfo::setContents(const QString &relativePath, char contentChar) +void FileInfo::setContents(const QString &relativePath, quint64 newSize, char contentChar) { FileInfo *file = findInvalidatingEtags(relativePath); Q_ASSERT(file); file->contentChar = contentChar; + file->contentSize = newSize; + if (!file->isDehydratedPlaceholder) { + file->fileSize = newSize; + } } void FileInfo::appendByte(const QString &relativePath, char contentChar) { - Q_UNUSED(contentChar); + Q_UNUSED(contentChar) FileInfo *file = findInvalidatingEtags(relativePath); Q_ASSERT(file); - file->size += 1; -} - -void FileInfo::modifyByte(const QString &relativePath, quint64 offset, char contentChar) -{ - Q_UNUSED(offset); - Q_UNUSED(contentChar); - FileInfo *file = findInvalidatingEtags(relativePath); - Q_ASSERT(file); - Q_ASSERT(!"unimplemented"); + if (!file->isDehydratedPlaceholder) { + file->contentSize += 1; + } + file->fileSize += 1; } void FileInfo::mkdir(const QString &relativePath) @@ -204,7 +222,7 @@ void FileInfo::setModTime(const QString &relativePath, const QDateTime &modTime) { FileInfo *file = findInvalidatingEtags(relativePath); Q_ASSERT(file); - file->lastModified = modTime; + file->setLastModified(modTime); } FileInfo *FileInfo::find(PathComponents pathComponents, const bool invalidateEtags) @@ -239,7 +257,7 @@ FileInfo *FileInfo::createDir(const QString &relativePath) return &child; } -FileInfo *FileInfo::create(const QString &relativePath, qint64 size, char contentChar) +FileInfo *FileInfo::create(const QString &relativePath, quint64 size, char contentChar) { const PathComponents pathComponents { relativePath }; FileInfo *parent = findInvalidatingEtags(pathComponents.parentDirComponents()); @@ -251,14 +269,52 @@ FileInfo *FileInfo::create(const QString &relativePath, qint64 size, char conten return &child; } -bool FileInfo::operator==(const FileInfo &other) const +bool FileInfo::equals(const FileInfo &other, CompareWhat compareWhat) const { - // Consider files to be equal between local<->remote as a user would. - return name == other.name - && isDir == other.isDir - && size == other.size - && contentChar == other.contentChar - && children == other.children; + // Only check the content and contentSize if both files are hydrated: + if (!isDehydratedPlaceholder && !other.isDehydratedPlaceholder) { + if (contentSize != other.contentSize || contentChar != other.contentChar) { + qDebug() << "1" << name << "!=" << other.name; + return false; + } + } + + // We need to check this before we use isDir in the next if-statement: + if (isDir != other.isDir) { + qDebug() << "2" << name << "!=" << other.name; + return false; + } + + if (compareWhat == CompareLastModified) { + // Don't check directory mtime: it might change when (unsynced) files get created. + if (!isDir && _lastModifiedInSecondsUTC != other._lastModifiedInSecondsUTC) { + qDebug() << "3" << name << "!=" << other.name; + return false; + } + } + + if (name != other.name || fileSize != other.fileSize) { + qDebug() << "4" << name << "!=" << other.name; + return false; + } + + if (children.size() != other.children.size()) { + qDebug() << "5" << name << "!=" << other.name; + return false; + } + + for (auto it = children.constBegin(), eit = children.constEnd(); it != eit; ++it) { + auto oit = other.children.constFind(it.key()); + if (oit == other.children.constEnd()) { + qDebug() << "6" << name << "!=" << other.name; + return false; + } else if (!it.value().equals(oit.value(), compareWhat)) { + qDebug() << "7" << name << "!=" << other.name; + return false; + } + } + + return true; } QString FileInfo::path() const @@ -302,10 +358,10 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces Q_ASSERT(!fileName.isNull()); // for root, it should be empty const FileInfo *fileInfo = remoteRootFileInfo.find(fileName); if (!fileInfo) { - QMetaObject::invokeMethod(this, "respond404", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &FakePropfindReply::respond404, Qt::QueuedConnection); return; } - const QString prefix = request.url().path().left(request.url().path().size() - fileName.size()); + const auto prefix = QUrl(request.url().path().left(request.url().path().size() - fileName.size())); // Don't care about the request and just return a full propfind const QString davUri { QStringLiteral("DAV:") }; @@ -331,11 +387,10 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces } else xml.writeEmptyElement(davUri, QStringLiteral("resourcetype")); - auto gmtDate = fileInfo.lastModified.toUTC(); - auto stringDate = QLocale::c().toString(gmtDate, QStringLiteral("ddd, dd MMM yyyy HH:mm:ss 'GMT'")); - xml.writeTextElement(davUri, QStringLiteral("getlastmodified"), stringDate); - xml.writeTextElement(davUri, QStringLiteral("getcontentlength"), QString::number(fileInfo.size)); - xml.writeTextElement(davUri, QStringLiteral("getetag"), QStringLiteral("\"%1\"").arg(QString::fromLatin1(fileInfo.etag))); + auto gmtDate = fileInfo.lastModifiedInUtc(); + xml.writeTextElement(davUri, QStringLiteral("getlastmodified"), OCC::Utility::formatRFC1123Date(gmtDate)); + xml.writeTextElement(davUri, QStringLiteral("getcontentlength"), QString::number(fileInfo.contentSize)); + xml.writeTextElement(davUri, QStringLiteral("getetag"), QStringLiteral("\"%1\"").arg(QString::fromUtf8(fileInfo.etag))); xml.writeTextElement(ocUri, QStringLiteral("permissions"), !fileInfo.permissions.isNull() ? QString(fileInfo.permissions.toString()) : fileInfo.isShared ? QStringLiteral("SRDNVCKW") : QStringLiteral("RDNVCKW")); xml.writeTextElement(ocUri, QStringLiteral("id"), QString::fromUtf8(fileInfo.fileId)); @@ -358,7 +413,7 @@ FakePropfindReply::FakePropfindReply(FileInfo &remoteRootFileInfo, QNetworkAcces xml.writeEndElement(); // multistatus xml.writeEndDocument(); - QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &FakePropfindReply::respond, Qt::QueuedConnection); } void FakePropfindReply::respond() @@ -366,19 +421,22 @@ void FakePropfindReply::respond() setHeader(QNetworkRequest::ContentLengthHeader, payload.size()); setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/xml; charset=utf-8")); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 207); - setFinished(true); - emit metaDataChanged(); + Q_EMIT metaDataChanged(); if (bytesAvailable()) - emit readyRead(); - emit finished(); + Q_EMIT readyRead(); + checkedFinished(); } void FakePropfindReply::respond404() { + if (isFinished()) { + return; + } + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 404); setError(InternalServerError, QStringLiteral("Not Found")); - emit metaDataChanged(); - emit finished(); + Q_EMIT metaDataChanged(); + checkedFinished(); } qint64 FakePropfindReply::bytesAvailable() const @@ -402,7 +460,7 @@ FakePutReply::FakePutReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager:: setOperation(op); open(QIODevice::ReadOnly); fileInfo = perform(remoteRootFileInfo, request, putPayload); - QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &FakePutReply::respond, Qt::QueuedConnection); } FileInfo *FakePutReply::perform(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload) @@ -411,33 +469,28 @@ FileInfo *FakePutReply::perform(FileInfo &remoteRootFileInfo, const QNetworkRequ Q_ASSERT(!fileName.isEmpty()); FileInfo *fileInfo = remoteRootFileInfo.find(fileName); if (fileInfo) { - fileInfo->size = putPayload.size(); + fileInfo->contentSize = putPayload.size(); fileInfo->contentChar = putPayload.at(0); } else { // Assume that the file is filled with the same character fileInfo = remoteRootFileInfo.create(fileName, putPayload.size(), putPayload.at(0)); } - fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong()); + fileInfo->fileSize = fileInfo->contentSize; // it's hydrated on the server, so these are the same + fileInfo->setLastModifiedFromSecondsUTC(request.rawHeader("X-OC-Mtime").toLongLong()); remoteRootFileInfo.find(fileName, /*invalidate_etags=*/true); return fileInfo; } void FakePutReply::respond() { - emit uploadProgress(fileInfo->size, fileInfo->size); + Q_EMIT uploadProgress(fileInfo->contentSize, fileInfo->contentSize); setRawHeader("OC-ETag", fileInfo->etag); setRawHeader("ETag", fileInfo->etag); setRawHeader("OC-FileID", fileInfo->fileId); setRawHeader("X-OC-MTime", "accepted"); // Prevents Q_ASSERT(!_runningNow) since we'll call PropagateItemJob::done twice in that case. setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); - emit metaDataChanged(); - emit finished(); -} - -void FakePutReply::abort() -{ - setError(OperationCanceledError, QStringLiteral("abort")); - emit finished(); + Q_EMIT metaDataChanged(); + checkedFinished(); } FakeMkcolReply::FakeMkcolReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent) @@ -456,15 +509,18 @@ FakeMkcolReply::FakeMkcolReply(FileInfo &remoteRootFileInfo, QNetworkAccessManag abort(); return; } - QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &FakeMkcolReply::respond, Qt::QueuedConnection); } void FakeMkcolReply::respond() { - setRawHeader("OC-FileId", fileInfo->fileId); - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201); - emit metaDataChanged(); - emit finished(); + if (error() == QNetworkReply::NoError) { + setRawHeader("OC-FileId", fileInfo->fileId); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201); + Q_EMIT metaDataChanged(); + } + + checkedFinished(); } FakeDeleteReply::FakeDeleteReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent) @@ -478,14 +534,15 @@ FakeDeleteReply::FakeDeleteReply(FileInfo &remoteRootFileInfo, QNetworkAccessMan QString fileName = getFilePathFromUrl(request.url()); Q_ASSERT(!fileName.isEmpty()); remoteRootFileInfo.remove(fileName); - QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &FakeDeleteReply::respond, Qt::QueuedConnection); } void FakeDeleteReply::respond() { setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 204); - emit metaDataChanged(); - emit finished(); + Q_EMIT metaDataChanged(); + + checkedFinished(); } FakeMoveReply::FakeMoveReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent) @@ -501,18 +558,22 @@ FakeMoveReply::FakeMoveReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager QString dest = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination"))); Q_ASSERT(!dest.isEmpty()); remoteRootFileInfo.rename(fileName, dest); - QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, &FakeMoveReply::respond, Qt::QueuedConnection); } void FakeMoveReply::respond() { - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201); - emit metaDataChanged(); - emit finished(); + if (error() == QNetworkReply::NoError) { + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201); + Q_EMIT metaDataChanged(); + } + + checkedFinished(); } FakeGetReply::FakeGetReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent) : FakeReply { parent } + , _range(parseRange(request)) { setRequest(request); setUrl(request.url()); @@ -523,219 +584,98 @@ FakeGetReply::FakeGetReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager:: Q_ASSERT(!fileName.isEmpty()); fileInfo = remoteRootFileInfo.find(fileName); if (!fileInfo) { - qDebug() << "meh;"; + qDebug() << "Could not find file" << fileName << "on the remote"; + state = State::FileNotFound; } - Q_ASSERT_X(fileInfo, Q_FUNC_INFO, "Could not find file on the remote"); QMetaObject::invokeMethod(this, &FakeGetReply::respond, Qt::QueuedConnection); } -void FakeGetReply::respond() +std::pair FakeGetReply::parseRange(const QNetworkRequest &request) { - if (aborted) { - setError(OperationCanceledError, QStringLiteral("Operation Canceled")); - emit metaDataChanged(); - emit finished(); - return; - } - payload = fileInfo->contentChar; - size = fileInfo->size; - setHeader(QNetworkRequest::ContentLengthHeader, size); - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); - setRawHeader("OC-ETag", fileInfo->etag); - setRawHeader("ETag", fileInfo->etag); - setRawHeader("OC-FileId", fileInfo->fileId); - emit metaDataChanged(); - if (bytesAvailable()) - emit readyRead(); - emit finished(); -} - -void FakeGetReply::abort() -{ - setError(OperationCanceledError, QStringLiteral("Operation Canceled")); - aborted = true; -} - -qint64 FakeGetReply::bytesAvailable() const -{ - if (aborted) - return 0; - return size + QIODevice::bytesAvailable(); -} - -qint64 FakeGetReply::readData(char *data, qint64 maxlen) -{ - qint64 len = std::min(qint64 { size }, maxlen); - std::fill_n(data, len, payload); - size -= len; - return len; -} - -FakeGetWithDataReply::FakeGetWithDataReply(FileInfo &remoteRootFileInfo, const QByteArray &data, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent) - : FakeReply { parent } -{ - setRequest(request); - setUrl(request.url()); - setOperation(op); - open(QIODevice::ReadOnly); - - Q_ASSERT(!data.isEmpty()); - payload = data; - QString fileName = getFilePathFromUrl(request.url()); - Q_ASSERT(!fileName.isEmpty()); - fileInfo = remoteRootFileInfo.find(fileName); - QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection); - if (request.hasRawHeader("Range")) { const QString range = QString::fromUtf8(request.rawHeader("Range")); - const QRegularExpression bytesPattern(QStringLiteral("bytes=(?\\d+)-(?\\d+)")); + const QRegularExpression bytesPattern(QStringLiteral(R"(bytes=(?P\d+)-(?P\d*))")); const QRegularExpressionMatch match = bytesPattern.match(range); if (match.hasMatch()) { const int start = match.captured(QStringLiteral("start")).toInt(); - const int end = match.captured(QStringLiteral("end")).toInt(); - payload = payload.mid(start, end - start + 1); + const QString end = match.captured(QStringLiteral("end")); + if (end.isEmpty()) { + // until the end + return { start, -1 }; + } else { + // to the end of the range + return { start, end.toLongLong() }; + } } } + return { 0, 0 }; } -void FakeGetWithDataReply::respond() -{ - if (aborted) { - setError(OperationCanceledError, QStringLiteral("Operation Canceled")); - emit metaDataChanged(); - emit finished(); - return; - } - setHeader(QNetworkRequest::ContentLengthHeader, payload.size()); - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); - setRawHeader("OC-ETag", fileInfo->etag); - setRawHeader("ETag", fileInfo->etag); - setRawHeader("OC-FileId", fileInfo->fileId); - emit metaDataChanged(); - if (bytesAvailable()) - emit readyRead(); - emit finished(); -} - -void FakeGetWithDataReply::abort() -{ - setError(OperationCanceledError, QStringLiteral("Operation Canceled")); - aborted = true; -} - -qint64 FakeGetWithDataReply::bytesAvailable() const -{ - if (aborted) - return 0; - return payload.size() - offset + QIODevice::bytesAvailable(); -} - -qint64 FakeGetWithDataReply::readData(char *data, qint64 maxlen) -{ - qint64 len = std::min(payload.size() - offset, quint64(maxlen)); - std::memcpy(data, payload.constData() + offset, len); - offset += len; - return len; -} - -FakeChunkMoveReply::FakeChunkMoveReply(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent) - : FakeReply { parent } -{ - setRequest(request); - setUrl(request.url()); - setOperation(op); - open(QIODevice::ReadOnly); - fileInfo = perform(uploadsFileInfo, remoteRootFileInfo, request); - if (!fileInfo) { - QTimer::singleShot(0, this, &FakeChunkMoveReply::respondPreconditionFailed); - } else { - QTimer::singleShot(0, this, &FakeChunkMoveReply::respond); - } -} - -FileInfo *FakeChunkMoveReply::perform(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo, const QNetworkRequest &request) +void FakeGetReply::respond() { - QString source = getFilePathFromUrl(request.url()); - Q_ASSERT(!source.isEmpty()); - Q_ASSERT(source.endsWith(QLatin1String("/.file"))); - source = source.left(source.length() - qstrlen("/.file")); - - auto sourceFolder = uploadsFileInfo.find(source); - Q_ASSERT(sourceFolder); - Q_ASSERT(sourceFolder->isDir); - int count = 0; - qlonglong size = 0; - qlonglong prev = 0; - char payload = '\0'; - - QString fileName = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination"))); - Q_ASSERT(!fileName.isEmpty()); - - const auto &sourceFolderChildren = sourceFolder->children; - // Compute the size and content from the chunks if possible - for (auto it = sourceFolderChildren.cbegin(); it != sourceFolderChildren.cend(); ++it) { - const auto &chunkNameLongLong = it.key().toLongLong(); - const auto &x = it.value(); - if (chunkNameLongLong != prev) + qDebug() << this << state; + if (state != State::Aborted) { + switch (state) { + case State::Aborted: + Q_UNREACHABLE(); + case State::FileNotFound: + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 404); + setError(ContentNotFoundError, QStringLiteral("File Not Found")); + Q_EMIT metaDataChanged(); break; - Q_ASSERT(!x.isDir); - Q_ASSERT(x.size > 0); // There should not be empty chunks - size += x.size; - Q_ASSERT(!payload || payload == x.contentChar); - payload = x.contentChar; - ++count; - prev = chunkNameLongLong + x.size; - } - Q_ASSERT(sourceFolderChildren.count() == count); // There should not be holes or extra files - - // NOTE: This does not actually assemble the file data from the chunks! - FileInfo *fileInfo = remoteRootFileInfo.find(fileName); - if (fileInfo) { - // The client should put this header - Q_ASSERT(request.hasRawHeader("If")); - - // And it should condition on the destination file - auto start = QByteArray("<" + request.rawHeader("Destination") + ">"); - Q_ASSERT(request.rawHeader("If").startsWith(start)); - - if (request.rawHeader("If") != start + " ([\"" + fileInfo->etag + "\"])") { - return nullptr; + case State::Ok: + payload = fileInfo->contentChar; + if (_range.second != 0) { + if (_range.second == -1) { + size = fileInfo->contentSize - _range.first; + } else { + size = _range.second - _range.first; + } + setRawHeader("Content-Range", QByteArrayLiteral("bytes ") + QByteArray::number(_range.first) + '-'); + } else { + size = fileInfo->contentSize; + } + setHeader(QNetworkRequest::ContentLengthHeader, size); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); + setRawHeader("OC-ETag", fileInfo->etag); + setRawHeader("ETag", fileInfo->etag); + setRawHeader("OC-FileId", fileInfo->fileId); + setRawHeader("X-OC-Mtime", QByteArray::number(fileInfo->lastModifiedInSecondsUTC())); + + Q_EMIT metaDataChanged(); + if (bytesAvailable()) { + Q_EMIT readyRead(); + } } - fileInfo->size = size; - fileInfo->contentChar = payload; - } else { - Q_ASSERT(!request.hasRawHeader("If")); - // Assume that the file is filled with the same character - fileInfo = remoteRootFileInfo.create(fileName, size, payload); + checkedFinished(); } - fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("X-OC-Mtime").toLongLong()); - remoteRootFileInfo.find(fileName, /*invalidate_etags=*/true); - - return fileInfo; } -void FakeChunkMoveReply::respond() +void FakeGetReply::abort() { - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201); - setRawHeader("OC-ETag", fileInfo->etag); - setRawHeader("ETag", fileInfo->etag); - setRawHeader("OC-FileId", fileInfo->fileId); - emit metaDataChanged(); - emit finished(); + state = State::Aborted; + FakeReply::abort(); } -void FakeChunkMoveReply::respondPreconditionFailed() +qint64 FakeGetReply::bytesAvailable() const { - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 412); - setError(InternalServerError, QStringLiteral("Precondition Failed")); - emit metaDataChanged(); - emit finished(); + switch (state) { + case State::Ok: + return size + QIODevice::bytesAvailable(); + case State::Aborted: + Q_FALLTHROUGH(); + case State::FileNotFound: + return 0; + } + Q_UNREACHABLE(); // Unreachable, but GCC on CentOS 7 does not understand that. } -void FakeChunkMoveReply::abort() +qint64 FakeGetReply::readData(char *data, qint64 maxlen) { - setError(OperationCanceledError, QStringLiteral("abort")); - emit finished(); + qint64 len = std::min(qint64 { size }, maxlen); + std::fill_n(data, len, payload); + size -= len; + return len; } FakePayloadReply::FakePayloadReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &body, QObject *parent) @@ -747,16 +687,17 @@ FakePayloadReply::FakePayloadReply(QNetworkAccessManager::Operation op, const QN setOperation(op); open(QIODevice::ReadOnly); QTimer::singleShot(10ms, this, &FakePayloadReply::respond); + setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); } void FakePayloadReply::respond() { - setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 200); - setHeader(QNetworkRequest::ContentLengthHeader, _body.size()); - emit metaDataChanged(); - emit readyRead(); - setFinished(true); - emit finished(); + if (error() == QNetworkReply::NoError) { + setHeader(QNetworkRequest::ContentLengthHeader, _body.size()); + Q_EMIT metaDataChanged(); + Q_EMIT readyRead(); + checkedFinished(); + } } qint64 FakePayloadReply::readData(char *buf, qint64 max) @@ -781,22 +722,33 @@ FakeErrorReply::FakeErrorReply(QNetworkAccessManager::Operation op, const QNetwo setOperation(op); open(QIODevice::ReadOnly); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpErrorCode); - setError(InternalServerError, QStringLiteral("Internal Server Fake Error")); + switch (httpErrorCode) { + case 401: + setError(AuthenticationRequiredError, QStringLiteral("Fake credentials error")); + break; + case 403: + setError(ContentAccessDenied, QStringLiteral("Fake access denied error")); + break; + case 404: + setError(UnknownServerError, QStringLiteral("Fake file not found error")); + break; + default: + setError(InternalServerError, QStringLiteral("Fake internal server error")); + } QMetaObject::invokeMethod(this, &FakeErrorReply::respond, Qt::QueuedConnection); } void FakeErrorReply::respond() { - emit metaDataChanged(); - emit readyRead(); + Q_EMIT metaDataChanged(); + Q_EMIT readyRead(); // finishing can come strictly after readyRead was called QTimer::singleShot(5ms, this, &FakeErrorReply::slotSetFinished); } void FakeErrorReply::slotSetFinished() { - setFinished(true); - emit finished(); + checkedFinished(); } qint64 FakeErrorReply::readData(char *buf, qint64 max) @@ -821,23 +773,50 @@ FakeHangingReply::FakeHangingReply(QNetworkAccessManager::Operation op, const QN open(QIODevice::ReadOnly); } -void FakeHangingReply::abort() +FakeAM::FakeAM(FileInfo initialRoot, QObject *parent) + : OCC::AccessManager(parent) + , _remoteRootFileInfo{std::move(initialRoot)} { - // Follow more or less the implementation of QNetworkReplyImpl::abort - close(); - setError(OperationCanceledError, tr("Operation canceled")); - emit error(OperationCanceledError); - setFinished(true); - emit finished(); + setCookieJar(new OCC::CookieJar); } -FakeQNAM::FakeQNAM(FileInfo initialRoot) - : _remoteRootFileInfo { std::move(initialRoot) } +FakeAM::FakeAM(QObject *parent) + : FakeAM({}, parent) { - setCookieJar(new OCC::CookieJar); } -QNetworkReply *FakeQNAM::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) +OCC::TestUtils::TestUtilsPrivate::AccountStateRaii createDummyAccountWithFileSupport(FileInfo intialRoot) +{ + // ensure we have an instance of folder man + std::ignore = OCC::TestUtils::folderMan(); + // don't use the account manager to create the account, it would try to use widgets + auto acc = new OCC::Account(QUuid::createUuid(), QStringLiteral("admin"), QUrl(QStringLiteral("http://localhost/owncloud"))); + + // todo: #41 + FakeAM *am = new FakeAM(intialRoot, nullptr); + auto creds = new FakeCredentials(acc, am, acc); + acc->setCredentials(creds); + acc->setDavDisplayName(QStringLiteral("fakename") + acc->uuid().toString(QUuid::WithoutBraces)); + acc->setCapabilities({acc->url(), OCC::TestUtils::testCapabilities()}); + + return {OCC::AccountManager::instance()->addAccount(acc), &OCC::TestUtils::TestUtilsPrivate::accountStateDeleter}; +} + +OCC::TestUtils::TestUtilsPrivate::AccountStateRaii createDummyAccount() +{ + // ensure we have an instance of folder man + std::ignore = OCC::TestUtils::folderMan(); + // don't use the account manager to create the account, it would try to use widgets + auto acc = new OCC::Account(QUuid::createUuid(), QStringLiteral("admin"), QUrl(QStringLiteral("http://localhost/owncloud"))); + auto creds = new FakeCredentials(acc, acc); + acc->setCredentials(creds); + acc->setDavDisplayName(QStringLiteral("fakename") + acc->uuid().toString(QUuid::WithoutBraces)); + acc->setCapabilities({acc->url(), OCC::TestUtils::testCapabilities()}); + + return {OCC::AccountManager::instance()->addAccount(acc), &OCC::TestUtils::TestUtilsPrivate::accountStateDeleter}; +} + +QNetworkReply *FakeAM::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) { QNetworkReply *reply = nullptr; auto newRequest = request; @@ -849,110 +828,146 @@ QNetworkReply *FakeQNAM::createRequest(QNetworkAccessManager::Operation op, cons } if (!reply) { const QString fileName = getFilePathFromUrl(newRequest.url()); - Q_ASSERT(!fileName.isNull()); - if (_errorPaths.contains(fileName)) { + if (fileName.isNull()) { + // we don't actually handle this case but thats ok for now + newRequest.setTransferTimeout(duration_cast(5min).count()); + qWarning() << "Ignoring request to" << newRequest.url(); + reply = new FakeHangingReply(op, newRequest, this); + } else if (_errorPaths.contains(fileName)) { reply = new FakeErrorReply { op, newRequest, this, _errorPaths[fileName] }; } } if (!reply) { - const bool isUpload = newRequest.url().path().startsWith(sUploadUrl.path()); - FileInfo &info = isUpload ? _uploadFileInfo : _remoteRootFileInfo; + FileInfo &info = _remoteRootFileInfo; - auto verb = newRequest.attribute(QNetworkRequest::CustomVerbAttribute); - if (verb == QLatin1String("PROPFIND")) - // Ignore outgoingData always returning somethign good enough, works for now. + const auto verb = newRequest.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray(); + if (verb == QByteArrayLiteral("PROPFIND")) + // Ignore outgoingData always returning something good enough, works for now. reply = new FakePropfindReply { info, op, newRequest, this }; - else if (verb == QLatin1String("GET") || op == QNetworkAccessManager::GetOperation) + else if (verb == QByteArrayLiteral("GET") || op == QNetworkAccessManager::GetOperation) reply = new FakeGetReply { info, op, newRequest, this }; - else if (verb == QLatin1String("PUT") || op == QNetworkAccessManager::PutOperation) + else if (verb == QByteArrayLiteral("PUT") || op == QNetworkAccessManager::PutOperation) reply = new FakePutReply { info, op, newRequest, outgoingData->readAll(), this }; - else if (verb == QLatin1String("MKCOL")) + else if (verb == QByteArrayLiteral("MKCOL")) reply = new FakeMkcolReply { info, op, newRequest, this }; - else if (verb == QLatin1String("DELETE") || op == QNetworkAccessManager::DeleteOperation) + else if (verb == QByteArrayLiteral("DELETE") || op == QNetworkAccessManager::DeleteOperation) reply = new FakeDeleteReply { info, op, newRequest, this }; - else if (verb == QLatin1String("MOVE") && !isUpload) + else if (verb == QByteArrayLiteral("MOVE")) reply = new FakeMoveReply { info, op, newRequest, this }; - else if (verb == QLatin1String("MOVE") && isUpload) - reply = new FakeChunkMoveReply { info, _remoteRootFileInfo, op, newRequest, this }; else { qDebug() << verb << outgoingData; Q_UNREACHABLE(); } } + // timeout would be handled by Qt + if (request.transferTimeout() != 0) { + QTimer::singleShot(request.transferTimeout(), reply, [reply] { + reply->abort(); + }); + } OCC::HttpLogger::logRequest(reply, op, outgoingData); return reply; } -FakeFolder::FakeFolder(const FileInfo &fileTemplate) +FakeFolder::FakeFolder(const FileInfo &fileTemplate, OCC::Vfs::Mode vfsMode, bool filesAreDehydrated) : _localModifier(_tempDir.path()) { // Needs to be done once - OCC::SyncEngine::minimumFileAgeForUpload = std::chrono::milliseconds(0); + OCC::SyncEngine::minimumFileAgeForUpload = 0s; QDir rootDir { _tempDir.path() }; qDebug() << "FakeFolder operating on" << rootDir; - toDisk(rootDir, fileTemplate); + toDisk(rootDir, filesAreDehydrated ? FileInfo() : fileTemplate); + + // todo: #41 + _accountState = createDummyAccountWithFileSupport(fileTemplate); + _fakeAm = dynamic_cast(_accountState->account()->accessManager()); + Q_ASSERT(_fakeAm); - _fakeQnam = new FakeQNAM(fileTemplate); - _account = OCC::Account::create(); - _account->setUrl(QUrl(QStringLiteral("http://admin:admin@localhost/owncloud"))); - _account->setCredentials(new FakeCredentials { _fakeQnam }); - _account->setDavDisplayName(QStringLiteral("fakename")); - _account->setServerVersion(QStringLiteral("10.0.0")); + _journalDb = new OCC::SyncJournalDb(localPath() + QStringLiteral(".sync_test.db"), this); + // TODO: davUrl + + _syncEngine = new OCC::SyncEngine(account(), account()->davUrl(), localPath(), QString(), _journalDb, this); + OCC::Vfs *vfs = OCC::VfsPluginManager::instance().createVfsFromPlugin(vfsMode, this); + Q_ASSERT(vfs && vfs->mode() == vfsMode); + + // make sure the sync engine options are *not* yet set - for the tests this happens in switchToVfs + Q_ASSERT(!_syncEngine->syncOptions().isValid()); - _journalDb.reset(new OCC::SyncJournalDb(localPath() + QStringLiteral(".sync_test.db"))); - _syncEngine.reset(new OCC::SyncEngine(_account, localPath(), QString(), _journalDb.get())); // Ignore temporary files from the download. (This is in the default exclude list, but we don't load it) - _syncEngine->excludedFiles().addManualExclude(QStringLiteral("]*.~*")); + _syncEngine->addManualExclude(QStringLiteral("]*.~*")); - // handle aboutToRemoveAllFiles with a timeout in case our test does not handle it - QObject::connect(_syncEngine.get(), &OCC::SyncEngine::aboutToRemoveAllFiles, _syncEngine.get(), [this](OCC::SyncFileItem::Direction, std::function callback) { - QTimer::singleShot(1s, _syncEngine.get(), [callback] { - callback(false); - }); - }); + // start the vfs instance + switchToVfs(vfs); - // Ensure we have a valid VfsOff instance "running" - switchToVfs(_syncEngine->syncOptions()._vfs); + if (vfsMode != OCC::Vfs::Off) { + const auto pinState = filesAreDehydrated ? OCC::PinState::OnlineOnly : OCC::PinState::AlwaysLocal; + syncJournal()->internalPinStates().setForPath("", pinState); + OC_ENFORCE(vfs->setPinState(QString(), pinState)); + } // A new folder will update the local file state database on first sync. // To have a state matching what users will encounter, we have to a sync // using an identical local/remote file tree first. - OC_ENFORCE(syncOnce()); + OC_ENFORCE(syncOnce()) +} + +FakeFolder::~FakeFolder() +{ + if (_syncEngine && _syncEngine->syncOptions().isValid()) { + _syncEngine->syncOptions().vfs()->stop(); + _syncEngine->syncOptions().vfs()->unregisterFolder(); + } } -void FakeFolder::switchToVfs(QSharedPointer vfs) +void FakeFolder::switchToVfs(OCC::Vfs *vfs) { + Q_ASSERT(vfs); + qDebug() << "switching vfs to " << vfs->mode(); + auto opts = _syncEngine->syncOptions(); - opts._vfs->stop(); - QObject::disconnect(_syncEngine.get(), nullptr, opts._vfs.data(), nullptr); + if (opts.isValid()) { + qDebug() << "last vfs was valid with mode: " << opts.vfs()->mode(); - opts._vfs = vfs; - _syncEngine->setSyncOptions(opts); + // we might get strange problems with "dead" vfs instances that have not been stopped because the FakeFolder switchToVfs is missing + // *a lot* of extra steps that normal Folder implements when switching the mode + if (_syncEngine->isSyncRunning()) { + qDebug() << "sync was still running when switch vfs called, exec until done"; + execUntilFinished(); + } + qDebug() << "killing old vfs"; + OCC::Vfs *vfsToDie = opts.vfs(); + vfsToDie->stop(); + vfsToDie->unregisterFolder(); + QObject::disconnect(_syncEngine, nullptr, vfsToDie, nullptr); + QObject::disconnect(vfsToDie); + // this is "nice" to avoid waiting for the parent folder to die + vfsToDie->deleteLater(); + } + + // todo: nooooooo - the opts should be treated as immutable. that change is coming so this will have to end sometime, starting now. + // opts._vfs = vfs; + qDebug() << "setting new sync options on engine with new vfs"; + _syncEngine->setSyncOptions(OCC::SyncOptions{vfs}); - OCC::VfsSetupParams vfsParams; + OCC::VfsSetupParams vfsParams(account(), account()->davUrl(), syncEngine()); vfsParams.filesystemPath = localPath(); vfsParams.remotePath = QLatin1Char('/'); - vfsParams.account = _account; - vfsParams.journal = _journalDb.get(); + vfsParams.journal = _journalDb; vfsParams.providerName = QStringLiteral("OC-TEST"); vfsParams.providerDisplayName = QStringLiteral("OC-TEST"); - vfsParams.providerVersion = QVersionNumber(0, 1); - QObject::connect(_syncEngine.get(), &QObject::destroyed, vfs.data(), [vfs]() { - vfs->stop(); - vfs->unregisterFolder(); - }); + vfsParams.providerVersion = QVersionNumber(0, 1, 0); + vfsParams.multipleAccountsRegistered = false; - QObject::connect(vfs.get(), &OCC::Vfs::error, vfs.get(), [](const QString &error) { - QFAIL(qUtf8Printable(error)); - }); - QSignalSpy spy(vfs.get(), &OCC::Vfs::started); + QObject::connect(_syncEngine, &OCC::SyncEngine::fileStatusChanged, vfs, &OCC::Vfs::onFileStatusChanged); + + QObject::connect(vfs, &OCC::Vfs::error, vfs, [](const QString &error) { Q_ASSERT_X(false, {}, qUtf8Printable(error)); }); + QSignalSpy spy(vfs, &OCC::Vfs::started); vfs->start(vfsParams); - // don't use QVERIFY outside of the test slot if (spy.isEmpty() && !spy.wait()) { - QFAIL("VFS Setup failed"); + Q_ASSERT_X(false, {}, "VFS Setup failed"); } } @@ -976,18 +991,18 @@ QString FakeFolder::localPath() const void FakeFolder::scheduleSync() { // Have to be done async, else, an error before exec() does not terminate the event loop. - QMetaObject::invokeMethod(_syncEngine.get(), "startSync", Qt::QueuedConnection); + QMetaObject::invokeMethod(_syncEngine, &OCC::SyncEngine::startSync, Qt::QueuedConnection); } void FakeFolder::execUntilBeforePropagation() { - QSignalSpy spy(_syncEngine.get(), &OCC::SyncEngine::aboutToPropagate); + QSignalSpy spy(_syncEngine, &OCC::SyncEngine::aboutToPropagate); QVERIFY(spy.wait()); } void FakeFolder::execUntilItemCompleted(const QString &relativePath) { - QSignalSpy spy(_syncEngine.get(), &OCC::SyncEngine::itemCompleted); + QSignalSpy spy(_syncEngine, &OCC::SyncEngine::itemCompleted); QElapsedTimer t; t.start(); while (t.elapsed() < 5000) { @@ -1002,6 +1017,16 @@ void FakeFolder::execUntilItemCompleted(const QString &relativePath) QVERIFY(false); } +bool FakeFolder::isDehydratedPlaceholder(const QString &filePath) +{ + return vfs()->isDehydratedPlaceholder(filePath); +} + +OCC::Vfs *FakeFolder::vfs() const +{ + return _syncEngine->syncOptions().vfs(); +} + void FakeFolder::toDisk(QDir &dir, const FileInfo &templateFi) { for (const auto &child : templateFi.children) { @@ -1013,37 +1038,64 @@ void FakeFolder::toDisk(QDir &dir, const FileInfo &templateFi) } else { QFile file { dir.filePath(child.name) }; file.open(QFile::WriteOnly); - file.write(QByteArray {}.fill(child.contentChar, child.size)); + file.write(QByteArray {}.fill(child.contentChar, child.contentSize)); file.close(); - OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(child.lastModified)); + OCC::FileSystem::setModTime(file.fileName(), child.lastModifiedInSecondsUTC()); } } } void FakeFolder::fromDisk(QDir &dir, FileInfo &templateFi) { - const auto infoList = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); - for (const auto &diskChild : infoList) { - if (diskChild.isDir()) { + auto dh = csync_vio_local_opendir(dir.absolutePath()); + if (!dh) { + return; + } + while (true) { + auto dirent = csync_vio_local_readdir(dh, nullptr); + if (!dirent) + break; + if (dirent->type == ItemTypeSkip) + continue; + if (dirent->is_hidden || dirent->path.startsWith(QStringLiteral(".sync_"))) + continue; + + QString absolutePathItem = dir.absolutePath() + QDir::separator() + dirent->path; + if (dirent->type == ItemTypeDirectory) { QDir subDir = dir; - subDir.cd(diskChild.fileName()); - FileInfo &subFi = templateFi.children[diskChild.fileName()] = FileInfo { diskChild.fileName() }; + subDir.cd(dirent->path); + FileInfo &subFi = templateFi.children[dirent->path] = FileInfo{dirent->path}; + subFi.setLastModified(QDateTime::fromSecsSinceEpoch(dirent->modtime, QTimeZone::utc())); fromDisk(subDir, subFi); } else { - QFile f { diskChild.filePath() }; - f.open(QFile::ReadOnly); - auto content = f.read(1); - if (content.size() == 0) { - qWarning() << "Empty file at:" << diskChild.filePath(); - continue; + FileInfo fi(dirent->path); + fi.isDir = false; + fi.fileSize = dirent->size; + fi.isDehydratedPlaceholder = isDehydratedPlaceholder(absolutePathItem); + fi.setLastModified(QDateTime::fromSecsSinceEpoch(dirent->modtime, QTimeZone::utc())); + if (fi.isDehydratedPlaceholder) { + fi.contentChar = '\0'; + fi.contentSize = 0; + } else { + QFile f{absolutePathItem}; + OC_ENFORCE(f.open(QFile::ReadOnly)); + auto content = f.read(1); + if (content.size() == 0) { + qWarning() << "Empty file at:" << dirent->path; + fi.contentChar = FileInfo::DefaultContentChar; + } else { + fi.contentChar = content.at(0); + } + fi.contentSize = fi.fileSize; } - char contentChar = content.at(0); - templateFi.children.insert(diskChild.fileName(), FileInfo { diskChild.fileName(), diskChild.size(), contentChar }); + + templateFi.children.insert(fi.name, fi); } } + csync_vio_local_closedir(dh); } -FileInfo &findOrCreateDirs(FileInfo &base, PathComponents components) +FileInfo &findOrCreateDirs(FileInfo &base, const PathComponents &components) { if (components.isEmpty()) return base; @@ -1067,11 +1119,11 @@ FileInfo FakeFolder::dbState() const auto &item = parentDir.children[name]; item.name = name; item.parentPath = parentDir.path(); - item.size = record._fileSize; + item.contentSize = record._fileSize; item.isDir = record._type == ItemTypeDirectory; item.permissions = record._remotePerm; item.etag = record._etag; - item.lastModified = OCC::Utility::qDateTimeFromTime_t(record._modtime); + item.setLastModifiedFromSecondsUTC(record._modtime); item.fileId = record._fileId; item.checksums = record._checksumHeader; // item.contentChar can't be set from the db @@ -1079,6 +1131,30 @@ FileInfo FakeFolder::dbState() const return result; } +bool FakeFolder::execUntilFinished() +{ + QSignalSpy spy(_syncEngine, &OCC::SyncEngine::finished); + bool ok = spy.wait(3600000); + Q_ASSERT(ok && "Sync timed out"); + return spy[0][0].toBool(); +} + +bool FakeFolder::syncOnce() +{ + QObject connectScope; + QList> errors; + connect(_syncEngine, &OCC::SyncEngine::syncError, &connectScope, + [&errors](const QString &message, OCC::ErrorCategory category) { errors << qMakePair(message, category); }); + OCC::SyncResult result; + connect(_syncEngine, &OCC::SyncEngine::itemCompleted, &connectScope, [&result](const OCC::SyncFileItemPtr &item) { result.processCompletedItem(item); }); + scheduleSync(); + const bool ok = execUntilFinished(); + if (!ok) { + qWarning() << Q_FUNC_INFO << "failed. Errors:" << errors << "Another sync needed:" << _syncEngine->isAnotherSyncNeeded() << result.errorStrings(); + } + return ok; +} + OCC::SyncFileItemPtr ItemCompletedSpy::findItem(const QString &path) const { for (const QList &args : *this) { @@ -1092,9 +1168,51 @@ OCC::SyncFileItemPtr ItemCompletedSpy::findItem(const QString &path) const FakeReply::FakeReply(QObject *parent) : QNetworkReply(parent) { - setRawHeader(QByteArrayLiteral("Date"), QDateTime::currentDateTimeUtc().toString(Qt::RFC2822Date).toUtf8()); + setRawHeader(QByteArrayLiteral("Date"), OCC::Utility::formatRFC1123Date(QDateTime::currentDateTimeUtc()).toUtf8()); + QTimer::singleShot(0, this, [this] { + // emulate the real world + // don't Q_EMIT if we where already aborted + if (!isFinished() && error() != QNetworkReply::OperationCanceledError) { + Q_EMIT requestSent(); + } + }); +} + +FakeReply::~FakeReply() { } + +void FakeReply::checkedFinished() +{ + // It can happen that this job is cancelled before the full data is read, and the job is aborted. This being the test framework, processing happens + // synchronously, so between the signals above and here, the job can get aborted, which emits the finished signal. So do NOT emit it again if + // this is the case. + if (!isFinished()) { + setFinished(true); + Q_EMIT finished(); + } +} + +void FakeReply::abort() +{ + if (!isFinished()) { + setError(OperationCanceledError, QStringLiteral("Operation Canceled")); + Q_EMIT metaDataChanged(); + checkedFinished(); + } } -FakeReply::~FakeReply() +QString getFilePathFromUrl(const QUrl &url) { + const QString path = url.path(); + // old school dav url + const QString sRootUrl = QStringLiteral("/owncloud/remote.php/webdav/"); + // more modern dav url including user name + const QString sRootUrl2 = QStringLiteral("/owncloud/remote.php/dav/files/admin/"); + + if (path.startsWith(sRootUrl)) { + return path.mid(sRootUrl.length()); + } + if (path.startsWith(sRootUrl2)) { + return path.mid(sRootUrl2.length()); + } + return {}; } diff --git a/test/testutils/syncenginetestutils.h b/test/testutils/syncenginetestutils.h index bacc43c636a..cc08738fc13 100644 --- a/test/testutils/syncenginetestutils.h +++ b/test/testutils/syncenginetestutils.h @@ -7,47 +7,41 @@ #pragma once #include "account.h" -#include "creds/abstractcredentials.h" -#include "logger.h" -#include "filesystem.h" -#include "folder.h" -#include "syncengine.h" +#include "common/filesystembase.h" #include "common/syncjournaldb.h" #include "common/syncjournalfilerecord.h" #include "common/vfs.h" +#include "creds/abstractcredentials.h" #include "csync_exclude.h" +#include "filesystem.h" +#include "folder.h" +#include "logger.h" +#include "syncengine.h" +#include "testutils.h" #include #include -#include #include +#include +#include #include #include -#include #include + +using namespace OCC::FileSystem::SizeLiterals; /* * TODO: In theory we should use QVERIFY instead of Q_ASSERT for testing, but this * only works when directly called from a QTest :-( */ +QString getFilePathFromUrl(const QUrl &url); -static const QUrl sRootUrl = QUrl::fromEncoded("owncloud://somehost/owncloud/remote.php/webdav/"); -static const QUrl sRootUrl2 = QUrl::fromEncoded("owncloud://somehost/owncloud/remote.php/dav/files/admin/"); -static const QUrl sUploadUrl = QUrl::fromEncoded("owncloud://somehost/owncloud/remote.php/dav/uploads/admin/"); +class FileInfo; -inline QString getFilePathFromUrl(const QUrl &url) -{ - QString path = url.path(); - if (path.startsWith(sRootUrl.path())) - return path.mid(sRootUrl.path().length()); - if (path.startsWith(sRootUrl2.path())) - return path.mid(sRootUrl2.path().length()); - if (path.startsWith(sUploadUrl.path())) - return path.mid(sUploadUrl.path().length()); - return {}; -} +OCC::TestUtils::TestUtilsPrivate::AccountStateRaii createDummyAccount(); +OCC::TestUtils::TestUtilsPrivate::AccountStateRaii createDummyAccountWithFileSupport(FileInfo intialRoot); inline QByteArray generateEtag() { @@ -61,7 +55,6 @@ inline QByteArray generateFileId() class PathComponents : public QStringList { public: - PathComponents(const char *path); PathComponents(const QString &path); PathComponents(const QStringList &pathComponents); @@ -76,23 +69,54 @@ class PathComponents : public QStringList QString fileName() const { return last(); } }; +/** + * @brief The FileModifier class defines the interface for both the local on-disk modifier and the + * remote in-memory modifier. + */ class FileModifier { public: + static constexpr auto DefaultFileSize = 64_B; + static constexpr char DefaultContentChar = 'X'; + virtual ~FileModifier() { } virtual void remove(const QString &relativePath) = 0; - virtual void insert(const QString &relativePath, qint64 size = 64, char contentChar = 'W') = 0; - virtual void setContents(const QString &relativePath, char contentChar) = 0; - virtual void appendByte(const QString &relativePath, char contentChar = 0) = 0; - virtual void modifyByte(const QString &relativePath, quint64 offset, char contentChar) = 0; + virtual void insert(const QString &relativePath, quint64 size = DefaultFileSize, char contentChar = DefaultContentChar) = 0; + + virtual void setContents(const QString &relativePath, quint64 newSize, char contentChar = DefaultContentChar) = 0; + + // prevent implicit cast to quint64 + template + void setContents(const QString &relativePath, T newSize, char contentChar = DefaultContentChar) = delete; + + // prevent implicit cast to quint64 + template + void insert(const QString &relativePath, T size, char contentChar = DefaultContentChar) = delete; + + virtual void appendByte(const QString &relativePath, char contentChar = DefaultContentChar) = 0; virtual void mkdir(const QString &relativePath) = 0; virtual void rename(const QString &relativePath, const QString &relativeDestinationDirectory) = 0; virtual void setModTime(const QString &relativePath, const QDateTime &modTime) = 0; }; +class FakeFolder; + +/** + * @brief The DiskFileModifier class is a FileModifier for files on the local disk. + * + * It uses a helper program (`test_helper`) to do the actual modifications. When running without a + * VFS, or when running with suffix-VFS, the changes are done before doing a sync. When running + * with a VFS, the OS will do callbacks to the test program. So in this case the modifications are + * done while the event loop is running, in order to process those OS callbacks. + * + * Note: these actions do NOT read content from files on disk: if the file would be dehydrated, + * reading will cause a re-hydration, which means network requests, and some tests explicitly check + * for the amount of requests. + */ class DiskFileModifier : public FileModifier { QDir _rootDir; + QStringList _processArguments; public: DiskFileModifier(const QString &rootDirPath) @@ -100,18 +124,43 @@ class DiskFileModifier : public FileModifier { } void remove(const QString &relativePath) override; - void insert(const QString &relativePath, qint64 size = 64, char contentChar = 'W') override; - void setContents(const QString &relativePath, char contentChar) override; - void appendByte(const QString &relativePath, char contentChar) override; - void modifyByte(const QString &relativePath, quint64 offset, char contentChar) override; + void insert(const QString &relativePath, quint64 size = DefaultFileSize, char contentChar = DefaultContentChar) override; + void setContents(const QString &relativePath, quint64 newSize, char contentChar = DefaultContentChar) override; + void appendByte(const QString &relativePath, char contentChar = DefaultContentChar) override; void mkdir(const QString &relativePath) override; void rename(const QString &from, const QString &to) override; void setModTime(const QString &relativePath, const QDateTime &modTime) override; + + Q_REQUIRED_RESULT bool applyModifications(); + + // prevent implicit cast to quint64 + template + void setContents(const QString &relativePath, T newSize, char contentChar = DefaultContentChar) = delete; + + // prevent implicit cast to quint64 + template + void insert(const QString &relativePath, T size, char contentChar = DefaultContentChar) = delete; }; +static inline qint64 defaultLastModified() +{ + auto precise = QDateTime::currentDateTimeUtc().addDays(-7); + time_t timeInSeconds = OCC::Utility::qDateTimeToTime_t(precise); + return timeInSeconds; +} + +/** + * @brief The FileInfo class represents the remotely stored (on-server) content. To be able to + * modify the content, this class is also the remote \c FileModifier. + */ class FileInfo : public FileModifier { + /// FIXME: we should make it explicit in the construtor if we're talking about a hydrated or a dehydrated file! + /// FIXME: this class is both a remote folder, as the root folder which in turn is the remote FileModifier. + /// This is a mess: unifying remote files/folders is ok, but because it's also the remote root (which + /// should implement the FileModifier), this means that each single remote file/folder also implements + /// this interface. public: static FileInfo A12_B12_C12_S12(); @@ -120,32 +169,32 @@ class FileInfo : public FileModifier : name { name } { } - FileInfo(const QString &name, qint64 size) + FileInfo(const QString &name, quint64 size) : name { name } , isDir { false } - , size { size } + , fileSize(size) + , contentSize { size } { } - FileInfo(const QString &name, qint64 size, char contentChar) + FileInfo(const QString &name, quint64 size, char contentChar) : name { name } , isDir { false } - , size { size } + , fileSize(size) + , contentSize { size } , contentChar { contentChar } { } FileInfo(const QString &name, const std::initializer_list &children); - void addChild(const FileInfo &info); + FileInfo &addChild(const FileInfo &info); void remove(const QString &relativePath) override; - void insert(const QString &relativePath, qint64 size = 64, char contentChar = 'W') override; + void insert(const QString &relativePath, quint64 size = DefaultFileSize, char contentChar = DefaultContentChar) override; - void setContents(const QString &relativePath, char contentChar) override; + void setContents(const QString &relativePath, quint64 newSize, char contentChar = DefaultContentChar) override; - void appendByte(const QString &relativePath, char contentChar = 0) override; - - void modifyByte(const QString &relativePath, quint64 offset, char contentChar) override; + void appendByte(const QString &relativePath, char contentChar = DefaultContentChar) override; void mkdir(const QString &relativePath) override; @@ -153,24 +202,58 @@ class FileInfo : public FileModifier void setModTime(const QString &relativePath, const QDateTime &modTime) override; + /// Return a pointer to the FileInfo, or a nullptr if it doesn't exist FileInfo *find(PathComponents pathComponents, const bool invalidateEtags = false); + FileInfo *find(const QString &pathComponents, const bool invalidateEtags = false) { return find(PathComponents{pathComponents}, invalidateEtags); } FileInfo *createDir(const QString &relativePath); - FileInfo *create(const QString &relativePath, qint64 size, char contentChar); + FileInfo *create(const QString &relativePath, quint64 size, char contentChar); bool operator<(const FileInfo &other) const { return name < other.name; } - bool operator==(const FileInfo &other) const; + enum CompareWhat { + CompareLastModified, + IgnoreLastModified, + }; + + bool operator==(const FileInfo &other) const { return equals(other, CompareLastModified); } + + bool equals(const FileInfo &other, CompareWhat compareWhat) const; bool operator!=(const FileInfo &other) const { return !operator==(other); } + QDateTime lastModified() const + { + return QDateTime::fromSecsSinceEpoch(_lastModifiedInSecondsUTC, QTimeZone::LocalTime); + } + + QDateTime lastModifiedInUtc() const + { + return QDateTime::fromSecsSinceEpoch(_lastModifiedInSecondsUTC, QTimeZone::UTC); + } + + void setLastModified(const QDateTime &t) + { + _lastModifiedInSecondsUTC = t.toSecsSinceEpoch(); + } + + qint64 lastModifiedInSecondsUTC() const + { + return _lastModifiedInSecondsUTC; + } + + void setLastModifiedFromSecondsUTC(qint64 utcSecs) + { + _lastModifiedInSecondsUTC = utcSecs; + } + QString path() const; QString absolutePath() const; @@ -180,13 +263,15 @@ class FileInfo : public FileModifier bool isDir = true; bool isShared = false; OCC::RemotePermissions permissions; // When uset, defaults to everything - QDateTime lastModified = QDateTime::currentDateTimeUtc().addDays(-7); + qint64 _lastModifiedInSecondsUTC = defaultLastModified(); QByteArray etag = generateEtag(); QByteArray fileId = generateFileId(); QByteArray checksums; QByteArray extraDavProperties; - qint64 size = 0; + quint64 fileSize = 0; + quint64 contentSize = 0; char contentChar = 'W'; + bool isDehydratedPlaceholder = false; // Sorted by name to be able to compare trees QMap children; @@ -196,24 +281,49 @@ class FileInfo : public FileModifier friend inline QDebug operator<<(QDebug dbg, const FileInfo &fi) { - return dbg << "{ " << fi.path() << ": " << fi.children; + return dbg.nospace().noquote() + << "{ '" << fi.path() << "': " + << ", isDir:" << fi.isDir + << QStringLiteral(", lastModified: %1 (%2)").arg(QString::number(fi._lastModifiedInSecondsUTC), fi.lastModifiedInUtc().toString()) + << ", fileSize:" << fi.fileSize + << ", contentSize:" << fi.contentSize + << QStringLiteral(", contentChar: 0x%1").arg(QString::number(int(fi.contentChar), 16)) + << ", isDehydratedPlaceholder:" << fi.isDehydratedPlaceholder + << ", children:" << fi.children + << " }"; } + + // prevent implicit cast to quint64 + template + void setContents(const QString &relativePath, T newSize, char contentChar = DefaultContentChar) = delete; + + // prevent implicit cast to quint64 + template + void insert(const QString &relativePath, T size, char contentChar = DefaultContentChar) = delete; }; class FakeReply : public QNetworkReply { Q_OBJECT + public: FakeReply(QObject *parent); - virtual ~FakeReply(); + virtual ~FakeReply() override; // useful to be public for testing + using QNetworkReply::setAttribute; using QNetworkReply::setRawHeader; + + void checkedFinished(); + + virtual void abort() override; }; class FakePropfindReply : public FakeReply + { Q_OBJECT + public: QByteArray payload; @@ -223,8 +333,6 @@ class FakePropfindReply : public FakeReply Q_INVOKABLE void respond404(); - void abort() override { } - qint64 bytesAvailable() const override; qint64 readData(char *data, qint64 maxlen) override; }; @@ -232,7 +340,6 @@ class FakePropfindReply : public FakeReply class FakePutReply : public FakeReply { Q_OBJECT - FileInfo *fileInfo; public: FakePutReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &putPayload, QObject *parent); @@ -241,105 +348,79 @@ class FakePutReply : public FakeReply Q_INVOKABLE virtual void respond(); - void abort() override; qint64 readData(char *, qint64) override { return 0; } + +private: + FileInfo *fileInfo; }; class FakeMkcolReply : public FakeReply { Q_OBJECT - FileInfo *fileInfo; public: FakeMkcolReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent); Q_INVOKABLE void respond(); - void abort() override { } qint64 readData(char *, qint64) override { return 0; } + +private: + FileInfo *fileInfo; }; class FakeDeleteReply : public FakeReply { Q_OBJECT + public: FakeDeleteReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent); Q_INVOKABLE void respond(); - void abort() override { } qint64 readData(char *, qint64) override { return 0; } }; class FakeMoveReply : public FakeReply { Q_OBJECT + public: FakeMoveReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent); Q_INVOKABLE void respond(); - void abort() override { } qint64 readData(char *, qint64) override { return 0; } }; class FakeGetReply : public FakeReply { Q_OBJECT + public: + enum class State { + Ok, + Aborted, + FileNotFound, + }; + Q_ENUM(State); + const FileInfo *fileInfo; char payload; - int size; - bool aborted = false; + qint64 size; + State state = State::Ok; + const std::pair _range; FakeGetReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent); - Q_INVOKABLE void respond(); - - void abort() override; - qint64 bytesAvailable() const override; - - qint64 readData(char *data, qint64 maxlen) override; -}; - -class FakeGetWithDataReply : public FakeReply -{ - Q_OBJECT -public: - const FileInfo *fileInfo; - QByteArray payload; - quint64 offset = 0; - bool aborted = false; - - FakeGetWithDataReply(FileInfo &remoteRootFileInfo, const QByteArray &data, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent); - - Q_INVOKABLE void respond(); + virtual void respond(); void abort() override; - qint64 bytesAvailable() const override; - - qint64 readData(char *data, qint64 maxlen) override; -}; - -class FakeChunkMoveReply : public FakeReply -{ - Q_OBJECT - FileInfo *fileInfo; - -public: - FakeChunkMoveReply(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo, - QNetworkAccessManager::Operation op, const QNetworkRequest &request, - QObject *parent); + virtual qint64 bytesAvailable() const override; - static FileInfo *perform(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo, const QNetworkRequest &request); - - Q_INVOKABLE virtual void respond(); + virtual qint64 readData(char *data, qint64 maxlen) override; - Q_INVOKABLE void respondPreconditionFailed(); - - void abort() override; - - qint64 readData(char *, qint64) override { return 0; } + static std::pair parseRange(const QNetworkRequest &request); }; class FakePayloadReply : public FakeReply @@ -349,9 +430,8 @@ class FakePayloadReply : public FakeReply FakePayloadReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &body, QObject *parent); - void respond(); + virtual void respond(); - void abort() override { } qint64 readData(char *buf, qint64 max) override; qint64 bytesAvailable() const override; QByteArray _body; @@ -371,11 +451,10 @@ class FakeErrorReply : public FakeReply using QNetworkReply::setAttribute; using QNetworkReply::setError; -public slots: +public Q_SLOTS: void slotSetFinished(); public: - void abort() override { } qint64 readData(char *buf, qint64 max) override; qint64 bytesAvailable() const override; @@ -389,7 +468,6 @@ class FakeHangingReply : public FakeReply public: FakeHangingReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent); - void abort() override; qint64 readData(char *, qint64) override { return 0; } }; @@ -415,7 +493,7 @@ class DelayedReply : public OriginalReply } }; -class FakeQNAM : public QNetworkAccessManager +class FakeAM : public OCC::AccessManager { public: using Override = std::function; @@ -429,7 +507,10 @@ class FakeQNAM : public QNetworkAccessManager Override _override; public: - FakeQNAM(FileInfo initialRoot); + FakeAM(QObject *parent); + FakeAM(FileInfo initialRoot, QObject *parent); + // void setRemoteState(FileInfo info); + //->use in FakeFolder after createDummyAccount around line 775 FileInfo ¤tRemoteState() { return _remoteRootFileInfo; } FileInfo &uploadState() { return _uploadFileInfo; } @@ -442,18 +523,29 @@ class FakeQNAM : public QNetworkAccessManager QIODevice *outgoingData = nullptr) override; }; + class FakeCredentials : public OCC::AbstractCredentials { - QNetworkAccessManager *_qnam; - public: - FakeCredentials(QNetworkAccessManager *qnam) - : _qnam { qnam } + // this impl is concerning. Unfortunately it's used so many places in tests it's very hard to align it with the real world impl and use of the + // AbstractCredentials which makes it pretty questionable at base. anyway, the abstract creds now want an account and a parent in the ctr as the setAccount + // member is removed, and yeah, it should have a parent to keep things clean, but refactoring the tests is going to be a very large job so that needs to + // happen another time. I find it very concerning that the tests work without any account at all. + FakeCredentials(OCC::Account *account, QObject *parent) + : OCC::AbstractCredentials(account, parent) { + _am = new FakeAM({}, this); } - QString authType() const override { return QStringLiteral("test"); } - QString user() const override { return QStringLiteral("admin"); } - QNetworkAccessManager *createQNAM() const override { return _qnam; } + + FakeCredentials(OCC::Account *account, FakeAM *am, QObject *parent) + : OCC::AbstractCredentials(account, parent) + , _am(am) + { + if (_am->parent() == nullptr) + _am->setParent(this); + } + + OCC::AccessManager *createAccessManager() const override { return _am; } bool ready() const override { return true; } void fetchFromKeychain() override { } void askFromUser() override { } @@ -461,46 +553,53 @@ class FakeCredentials : public OCC::AbstractCredentials void persist() override { } void invalidateToken() override { } void forgetSensitiveData() override { } + bool refreshAccessToken() override { return true; } + +private: + OCC::AccessManager *_am; }; -class FakeFolder +class FakeFolder : public QObject { - QTemporaryDir _tempDir; + Q_OBJECT + // oof, this has a default ctr but I don't see it ever instantiated? + FakeAM *_fakeAm = nullptr; + const QTemporaryDir _tempDir = OCC::TestUtils::createTempDir(); DiskFileModifier _localModifier; - // FIXME: Clarify ownership, double delete - FakeQNAM *_fakeQnam; - OCC::AccountPtr _account; - std::unique_ptr _journalDb; - std::unique_ptr _syncEngine; + OCC::TestUtils::TestUtilsPrivate::AccountStateRaii _accountState = + OCC::TestUtils::TestUtilsPrivate::AccountStateRaii{nullptr, &OCC::TestUtils::TestUtilsPrivate::accountStateDeleter}; + OCC::SyncJournalDb *_journalDb = nullptr; + OCC::SyncEngine *_syncEngine = nullptr; public: - FakeFolder(const FileInfo &fileTemplate); + FakeFolder(const FileInfo &fileTemplate, OCC::Vfs::Mode vfsMode = OCC::Vfs::Off, bool filesAreDehydrated = false); + ~FakeFolder(); - void switchToVfs(QSharedPointer vfs); + void switchToVfs(OCC::Vfs *vfs); - OCC::AccountPtr account() const { return _account; } - OCC::SyncEngine &syncEngine() const { return *_syncEngine; } - OCC::SyncJournalDb &syncJournal() const { return *_journalDb; } + OCC::Account *account() const { return _accountState->account(); } + OCC::SyncEngine *syncEngine() const { return _syncEngine; } + OCC::SyncJournalDb *syncJournal() const { return _journalDb; } FileModifier &localModifier() { return _localModifier; } - FileInfo &remoteModifier() { return _fakeQnam->currentRemoteState(); } + FileInfo &remoteModifier() { return _fakeAm->currentRemoteState(); } FileInfo currentLocalState(); - FileInfo currentRemoteState() { return _fakeQnam->currentRemoteState(); } - FileInfo &uploadState() { return _fakeQnam->uploadState(); } + FileInfo ¤tRemoteState() { return _fakeAm->currentRemoteState(); } + FileInfo &uploadState() { return _fakeAm->uploadState(); } FileInfo dbState() const; struct ErrorList { - FakeQNAM *_qnam; + FakeAM *_qnam; void append(const QString &path, int error = 500) { _qnam->errorPaths().insert(path, error); } void clear() { _qnam->errorPaths().clear(); } }; - ErrorList serverErrorPaths() { return { _fakeQnam }; } - void setServerOverride(const FakeQNAM::Override &override) { _fakeQnam->setOverride(override); } + ErrorList serverErrorPaths() { return { _fakeAm }; } + void setServerOverride(const FakeAM::Override &override) { _fakeAm->setOverride(override); } QString localPath() const; @@ -510,24 +609,30 @@ class FakeFolder void execUntilItemCompleted(const QString &relativePath); - bool execUntilFinished() + bool execUntilFinished(); + + bool syncOnce(); + + Q_REQUIRED_RESULT bool applyLocalModificationsAndSync() { - QSignalSpy spy(_syncEngine.get(), &OCC::SyncEngine::finished); - bool ok = spy.wait(3600000); - Q_ASSERT(ok && "Sync timed out"); - return spy[0][0].toBool(); + if (!_localModifier.applyModifications()) { + return false; + } + return syncOnce(); } - bool syncOnce() + Q_REQUIRED_RESULT bool applyLocalModificationsWithoutSync() { - scheduleSync(); - return execUntilFinished(); + return _localModifier.applyModifications(); } + bool isDehydratedPlaceholder(const QString &filePath); + OCC::Vfs *vfs() const; + private: static void toDisk(QDir &dir, const FileInfo &templateFi); - static void fromDisk(QDir &dir, FileInfo &templateFi); + void fromDisk(QDir &dir, FileInfo &templateFi); }; @@ -550,7 +655,7 @@ inline const FileInfo *findConflict(FileInfo &dir, const QString &filename) struct ItemCompletedSpy : QSignalSpy { explicit ItemCompletedSpy(FakeFolder &folder) - : QSignalSpy(&folder.syncEngine(), &OCC::SyncEngine::itemCompleted) + : QSignalSpy(folder.syncEngine(), &OCC::SyncEngine::itemCompleted) { } @@ -569,18 +674,19 @@ inline void addFiles(QStringList &dest, const FileInfo &fi) { if (fi.isDir) { dest += QStringLiteral("%1 - dir").arg(fi.path()); - for (const auto &fi : fi.children) - addFiles(dest, fi); + for (const auto &child : fi.children) + addFiles(dest, child); } else { - dest += QStringLiteral("%1 - %2 %3-bytes").arg(fi.path()).arg(fi.size).arg(fi.contentChar); + dest += QStringLiteral("%1 - %2 %3-bytes (%4)").arg(fi.path(), QString::number(fi.contentSize), QChar::fromLatin1(fi.contentChar), fi.lastModifiedInUtc().toString()); } } inline QString toStringNoElide(const FileInfo &fi) { QStringList files; - for (const auto &fi : fi.children) - addFiles(files, fi); + for (const auto &childInfo : fi.children) { + addFiles(files, childInfo); + } files.sort(); return QStringLiteral("FileInfo with %1 files(\n\t%2\n)").arg(files.size()).arg(files.join(QStringLiteral("\n\t"))); } @@ -594,18 +700,73 @@ inline void addFilesDbData(QStringList &dest, const FileInfo &fi) { // could include etag, permissions etc, but would need extra work if (fi.isDir) { - dest += QStringLiteral("%1 - %2 %3 %4").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.lastModified.toSecsSinceEpoch()), QString::fromUtf8(fi.fileId)); - for (const auto &fi : fi.children) - addFilesDbData(dest, fi); + dest += QStringLiteral("%1 - %2 %3 %4").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.lastModifiedInSecondsUTC()), QString::fromUtf8(fi.fileId)); + for (const auto &child : fi.children) + addFilesDbData(dest, child); } else { - dest += QStringLiteral("%1 - %2 %3 %4 %5").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.size), QString::number(fi.lastModified.toSecsSinceEpoch()), QString::fromUtf8(fi.fileId)); + dest += QStringLiteral("%1 - %2 %3 %4 %5").arg(fi.name, fi.isDir ? QStringLiteral("dir") : QStringLiteral("file"), QString::number(fi.contentSize), QString::number(fi.lastModifiedInSecondsUTC()), QString::fromUtf8(fi.fileId)); } } inline char *printDbData(const FileInfo &fi) { QStringList files; - for (const auto &fi : fi.children) - addFilesDbData(files, fi); + for (const auto &child : fi.children) + addFilesDbData(files, child); return QTest::toString(QStringLiteral("FileInfo with %1 files(%2)").arg(files.size()).arg(files.join(QStringLiteral(", ")))); } + +/** + * @brief Utility class that count the number of GET/PUT/MOVE/DELETE operations during a sync. + * + * This can be used for subsequent syncs, but the counters need to be reset. + */ +struct OperationCounter +{ + int nGET = 0; + int nPUT = 0; + int nMOVE = 0; + int nDELETE = 0; + + OperationCounter() { } + OperationCounter(const OperationCounter &) = delete; + OperationCounter(OperationCounter &&) = delete; + void operator=(OperationCounter const &) = delete; + void operator=(OperationCounter &&) = delete; + + void reset() + { + nGET = 0; + nPUT = 0; + nMOVE = 0; + nDELETE = 0; + } + + QNetworkReply *serverOverride(QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *) + { + if (op == QNetworkAccessManager::GetOperation) { + ++nGET; + } else if (op == QNetworkAccessManager::PutOperation) { + ++nPUT; + } else if (op == QNetworkAccessManager::DeleteOperation) { + ++nDELETE; + } else if (req.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray() == QByteArrayLiteral("MOVE")) { + ++nMOVE; + } + return nullptr; + } + auto functor() + { + return [this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) { return serverOverride(op, req, device); }; + } + + OperationCounter(FakeFolder &fakeFolder) + { + fakeFolder.setServerOverride(functor()); + } + + friend inline QDebug operator<<(QDebug dbg, const OperationCounter &oc) + { + return dbg << "nGET:" << oc.nGET << " nPUT:" << oc.nPUT << " nMOVE:" << oc.nMOVE << " nDELETE:" << oc.nDELETE; + } +}; diff --git a/test/testutils/test_helper.cpp b/test/testutils/test_helper.cpp new file mode 100644 index 00000000000..682e106fe46 --- /dev/null +++ b/test/testutils/test_helper.cpp @@ -0,0 +1,511 @@ +/* + * Copyright (C) by Erik Verbruggen + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include "common/utility.h" +#include "libsync/filesystem.h" + +#ifdef Q_OS_WIN +#include "common/utility_win.h" +#endif + + +/* + * IMPORTANT: the commands below do NOT read any data from files when modifying them! + * + * When a file is dehydrated, reading will cause a re-hydration (download), and many tests check + * for the number of network requests. + */ + +using namespace std; + +namespace { +bool writeToFile(std::string_view command, const QString &fileName, QIODevice::OpenMode mode, const QByteArray &data) +{ +#ifndef Q_OS_WIN + // Use io streams, so we can be sure no-one plays around with normalizing the file name. + + ios::openmode oMode = ios::binary | ios::out; + if (mode & QIODevice::Append) + oMode |= ios::app; + if (mode & QIODevice::Truncate) + oMode |= ios::trunc; + ofstream out(fileName.toUtf8().constData(), oMode); + out.write(data.constData(), data.size()); + out.close(); +#else + // Qt bug: [INSERT BUG HERE] + // When opening a cloud file results in a cfapi error, Qt does not report an error. + // Writes will succeed but not be committed to the file. + // Reads will always return 0 + + const QFileInfo info(fileName); + DWORD creation = OPEN_ALWAYS; + if (mode & QIODevice::Truncate) { + creation = TRUNCATE_EXISTING; + if (!info.exists()) { + cerr << "Error: truncating a non existing file '" << qPrintable(fileName) << "' for " << command << " command" << endl; + return false; + } + } else if (mode & QIODevice::Append) { + creation = OPEN_EXISTING; + if (!info.exists()) { + cerr << "Error: appending to non existing file '" << qPrintable(fileName) << "' for " << command << " command" << endl; + return false; + } + } + auto handle = CreateFileW(reinterpret_cast(fileName.utf16()), + GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, + {}, + creation, + FILE_ATTRIBUTE_NORMAL, + nullptr); + + if (handle == INVALID_HANDLE_VALUE) { + const auto error = OCC::Utility::formatWinError(GetLastError()); + cerr << "Error: cannot open file '" << qPrintable(fileName) << "' for " << command << " command: " + << qPrintable(error) << endl; + return false; + } + // cleanup the handle when leaving the scope + auto close = qScopeGuard([handle] { + CloseHandle(handle); + }); + if (mode & QFile::Append) { + LARGE_INTEGER pos; + pos.QuadPart = info.size(); + if (SetFilePointer(handle, pos.LowPart, &pos.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + cerr << "Error: cannot seek to EOF in '" << qPrintable(fileName) << "' for " << command << " command" << endl; + return false; + } + } + DWORD bytesWritten; + if (!WriteFile(handle, data.constData(), data.size(), &bytesWritten, nullptr) || bytesWritten != data.size()) { + const auto error = OCC::Utility::formatWinError(GetLastError()); + cerr << "Error: wrote " << bytesWritten << " bytes to '" << qPrintable(fileName) << "' instead of requested " << data.size() << " bytes. Error: " << qPrintable(error) << endl; + return false; + } +#endif + return true; +} +} + +/** + * @brief The abstract Command class. You know, from the pattern. + */ +class Command +{ +public: + Command(const QString &fileName) + : _fileName(fileName) + { + } + virtual ~Command() = 0; + + virtual bool execute(QDir &rootDir) const = 0; + +protected: + static QString parseFileName(QStringListIterator &it) + { + return it.next(); + } + + const QString _fileName; +}; + +Command::~Command() { } + +class SetMtimeCommand : public Command +{ +public: + static constexpr string_view name = "mtime"; + + SetMtimeCommand(const QString &fileName, qlonglong secs) + : Command(fileName) + , _secs(secs) + { + } + ~SetMtimeCommand() override { } + + bool execute(QDir &rootDir) const override + { + auto modTime = QDateTime::fromSecsSinceEpoch(_secs); + cerr << name << ", file: " << qPrintable(_fileName) << ", secs: " << _secs << endl; + return OCC::FileSystem::setModTime(rootDir.filePath(_fileName), OCC::Utility::qDateTimeToTime_t(modTime)); + } + + static Command *parse(QStringListIterator &it) + { + QString fileName = parseFileName(it); + if (fileName.isEmpty()) { + cerr << "Error: invalid filename for " << name << " command" << endl; + return {}; + } + + QString secsStr = it.next(); + bool ok = false; + auto secs = secsStr.toLongLong(&ok); + if (!ok) { + cerr << "Error: '" << qPrintable(secsStr) << "' is not a valid number (" << name << ")" << endl; + return {}; + } + + return new SetMtimeCommand(fileName, secs); + } + +private: + const qlonglong _secs; +}; + +class SetContentsCommand : public Command +{ +public: + static constexpr string_view name = "contents"; + + SetContentsCommand(const QString &fileName, qlonglong count, char ch) + : Command(fileName) + , _count(count) + , _ch(ch) + { + } + ~SetContentsCommand() override { } + + bool execute(QDir &rootDir) const override + { + cerr << name << endl; + int count = _count == -1 ? 32 : _count; + return writeToFile(name, rootDir.filePath(_fileName), QIODevice::WriteOnly | QIODevice::Truncate, QByteArray(count, _ch)); + } + + static Command *parse(QStringListIterator &it) + { + QString fileName = parseFileName(it); + if (fileName.isEmpty()) { + cerr << "Error: invalid filename for " << name << " command" << endl; + return {}; + } + + QString countStr = it.next(); + bool ok = false; + auto count = countStr.toLongLong(&ok); + if (!ok) { + cerr << "Error: '" << qPrintable(countStr) << "' is not a valid number (" << name << ")" << endl; + return {}; + } + + QString charStr = it.next(); + if (charStr.size() != 1) { + cerr << "Error: content for " << name << " command should be 1 character in size" << endl; + } + + return new SetContentsCommand(fileName, count, charStr.at(0).toLatin1()); + } + +private: + const qlonglong _count; + const char _ch; +}; + +class RenameCommand : public Command +{ +public: + static constexpr string_view name = "rename"; + + RenameCommand(const QString &fileName, const QString &newName) + : Command(fileName) + , _newName(newName) + { + } + ~RenameCommand() override { } + + bool execute(QDir &rootDir) const override + { + cerr << name << endl; + if (!rootDir.exists(_fileName)) { + cerr << "File does not exist: " << qPrintable(rootDir.absoluteFilePath(_fileName)) << endl; + return false; + } + + bool success = rootDir.rename(_fileName, _newName); + if (!success) { + cerr << "Rename of " << qPrintable(_fileName) << " failed" << endl; + } + return success; + } + + static Command *parse(QStringListIterator &it) + { + QString fileName = parseFileName(it); + if (fileName.isEmpty()) { + cerr << "Error: invalid filename for " << name << " command" << endl; + return {}; + } + + QString newName = it.next(); + if (newName.isEmpty()) { + cerr << "Error: invalid new name for " << name << " command" << endl; + return {}; + } + + return new RenameCommand(fileName, newName); + } + +private: + const QString _newName; +}; + +class AppendByteCommand : public Command +{ +public: + static constexpr string_view name = "appendbyte"; + + AppendByteCommand(const QString &fileName, char ch) + : Command(fileName) + , _ch(ch) + { + } + ~AppendByteCommand() override { } + + bool execute(QDir &rootDir) const override + { + cerr << name << endl; + + if (_ch == '\0') { + cerr << "Error: appending a NUL byte is probably a failure somewhere else." << endl; + return false; + } + cerr << ".... file: " << qPrintable(_fileName) << ", byte: " << _ch << endl; + return writeToFile(name, rootDir.filePath(_fileName), QIODevice::WriteOnly | QIODevice::Append, QByteArray(1, _ch)); + ; + } + + static Command *parse(QStringListIterator &it) + { + QString fileName = parseFileName(it); + if (fileName.isEmpty()) { + cerr << "Error: invalid filename for " << name << " command" << endl; + return {}; + } + + QString charStr = it.next(); + if (charStr.size() != 1) { + cerr << "Error: content for " << name << " command should be 1 character in size" << endl; + } + + return new AppendByteCommand(fileName, charStr.at(0).toLatin1()); + } + +private: + const char _ch; +}; + +class InsertCommand : public Command +{ +public: + static constexpr string_view name = "insert"; + + InsertCommand(const QString &fileName, qlonglong count, char ch) + : Command(fileName) + , _count(count) + , _ch(ch) + { + } + ~InsertCommand() override { } + + bool execute(QDir &rootDir) const override + { + cerr << name << " '" << qPrintable(_fileName) << "' with " + << _count << " " << _ch << " characters" << endl; + if (QFileInfo::exists(rootDir.filePath(_fileName))) { + cerr << "Error: file '" << qPrintable(_fileName) << "' for " << name << " command already exists" << endl; + return false; + } + + return writeToFile(name, rootDir.filePath(_fileName), QIODevice::WriteOnly, QByteArray(_count, _ch)); + } + + static Command *parse(QStringListIterator &it) + { + QString fileName = parseFileName(it); + if (fileName.isEmpty()) { + cerr << "Error: invalid filename for " << name << " command" << endl; + return {}; + } + + QString countStr = it.next(); + bool ok = false; + auto count = countStr.toLongLong(&ok); + if (!ok) { + cerr << "Error: '" << qPrintable(countStr) << "' is not a valid number (" << name << ")" << endl; + return {}; + } + + QString charStr = it.next(); + if (charStr.size() != 1) { + cerr << "Error: content for " << name << " command should be 1 character in size" << endl; + } + + return new InsertCommand(fileName, count, charStr.at(0).toLatin1()); + } + +private: + const qlonglong _count; + const char _ch; +}; + +class RemoveCommand : public Command +{ +public: + static constexpr string_view name = "remove"; + + RemoveCommand(const QString &fileName) + : Command(fileName) + { + } + ~RemoveCommand() override { } + + bool execute(QDir &rootDir) const override + { + cerr << name << endl; + QFileInfo fi(rootDir.filePath(_fileName)); + if (fi.isFile()) { + return rootDir.remove(_fileName); + } else { + return QDir(fi.filePath()).removeRecursively(); + } + } + + static Command *parse(QStringListIterator &it) + { + QString fileName = parseFileName(it); + if (fileName.isEmpty()) { + cerr << "Error: invalid filename for " << name << " command" << endl; + return {}; + } + + return new RemoveCommand(fileName); + } +}; + +class MkdirCommand : public Command +{ +public: + static constexpr string_view name = "mkdir"; + + MkdirCommand(const QString &fileName) + : Command(fileName) + { + } + ~MkdirCommand() override { } + + bool execute(QDir &rootDir) const override + { + cerr << name << " " << qPrintable(_fileName) << endl; + return rootDir.mkdir(_fileName); + } + + static Command *parse(QStringListIterator &it) + { + QString fileName = parseFileName(it); + if (fileName.isEmpty()) { + cerr << "Error: invalid directory name for " << name << " command" << endl; + return {}; + } + + return new MkdirCommand(fileName); + } +}; + +/** + * @brief parseArguments Creates a list of commands to be executed. + * + * Each command's `parse` method reads the required arguments from the command-line by using the + * iterator. + * + * @param it The iterator over the argument list + * @return a list of commands to execute + */ +vector parseArguments(QStringListIterator &it) +{ + map> parserFunctions = { + { SetMtimeCommand::name, SetMtimeCommand::parse }, + { SetContentsCommand::name, SetContentsCommand::parse }, + { RenameCommand::name, RenameCommand::parse }, + { AppendByteCommand::name, AppendByteCommand::parse }, + { InsertCommand::name, InsertCommand::parse }, + { RemoveCommand::name, RemoveCommand::parse }, + { MkdirCommand::name, MkdirCommand::parse }, + }; + + vector commands; + + while (it.hasNext()) { + const QString option = it.next(); + auto pf = parserFunctions.find(option.toStdString()); + if (pf == parserFunctions.end()) { + cerr << "Error: unknown command '" << qPrintable(option) << "'" << endl; + return {}; + } + + if (auto cmd = (pf->second)(it)) { + commands.emplace_back(cmd); + } else { + return {}; + } + } + + return commands; +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + QStringListIterator it(app.arguments()); + if (it.hasNext()) { + // skip program name + it.next(); + } + + QDir rootDir(it.next()); + + const auto commands = parseArguments(it); + if (commands.empty()) { + return -1; + } + + cerr << "Starting executing commands in '" << qPrintable(rootDir.absolutePath()) << "' ...:" << endl; + + for (const auto &cmd : commands) { + cerr << ".. Executing command: "; + if (!cmd->execute(rootDir)) { + return -2; + } + cerr << ".. command done." << endl; + } + + cerr << "Successfully executed all commands." << endl; + + qDeleteAll(commands); + + return 0; +} diff --git a/test/testutils/testutils.cpp b/test/testutils/testutils.cpp index 816f96f8915..cbfb8737077 100644 --- a/test/testutils/testutils.cpp +++ b/test/testutils/testutils.cpp @@ -1,56 +1,38 @@ #include "testutils.h" -#include "creds/httpcredentials.h" +#include "common/checksums.h" #include "gui/accountmanager.h" #include #include -namespace { -class HttpCredentialsTest : public OCC::HttpCredentials -{ -public: - HttpCredentialsTest(const QString &user, const QString &password) - : HttpCredentials(OCC::DetermineAuthTypeJob::AuthType::Basic, user, password) - { - } - - void askFromUser() override - { - } -}; -} - namespace OCC { namespace TestUtils { - AccountPtr createDummyAccount() + + // We have more than one of these? + FolderDefinition createDummyFolderDefinition(Account *account, const QString &path) { - // don't use the account manager to create the account, it would try to use widgets - auto acc = Account::create(); - HttpCredentialsTest *cred = new HttpCredentialsTest("testuser", "secret"); - acc->setCredentials(cred); - acc->setUrl(QUrl(QStringLiteral("http://localhost/owncloud"))); - acc->setDavDisplayName(QStringLiteral("fakename") + acc->uuid().toString()); - acc->setServerVersion(QStringLiteral("10.0.0")); - OCC::AccountManager::instance()->addAccount(acc); - return acc; + // TODO: legacy + // todo: #51 - make a real def, not this super minimal thing + // space Id can never be empty! + FolderDefinition d(account->davUrl(), QUuid::createUuid().toString()); + d.setLocalPath(path); + d.setTargetPath(path); + d.setJournalPath(SyncJournalDb::makeDbName(d.localPath())); + return d; } - FolderDefinition createDummyFolderDefinition(const QString &path) + QTemporaryDir createTempDir() { - OCC::FolderDefinition d; - d.localPath = path; - d.targetPath = path; - d.alias = path; - return d; + return QTemporaryDir { QStringLiteral("%1/ownCloud-unit-test-%2-XXXXXX").arg(QDir::tempPath(), qApp->applicationName()) }; } FolderMan *folderMan() { static QPointer man; if (!man) { - man = new FolderMan; + man = FolderMan::createInstance().release(); QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, man, &FolderMan::deleteLater); }; return man; @@ -82,5 +64,37 @@ namespace TestUtils { } return false; } + + const QVariantMap testCapabilities(CheckSums::Algorithm algo) + { + static const auto algorithmNames = [] { + QVariantList out; + for (const auto &a : CheckSums::All) { + out.append(Utility::enumToString(a.first)); + } + return out; + }(); + // clang-format off + return {{QStringLiteral("core"), + QVariantMap{{QStringLiteral("status"), + QVariantMap{{QStringLiteral("installed"), QStringLiteral("1")}, {QStringLiteral("maintenance"), QStringLiteral("0")}, + {QStringLiteral("needsDbUpgrade"), QStringLiteral("0")}, {QStringLiteral("version"), QStringLiteral("10.11.0.0")}, + {QStringLiteral("versionstring"), QStringLiteral("10.11.0")}, {QStringLiteral("edition"), QStringLiteral("Community")}, + {QStringLiteral("productname"), QStringLiteral("Infinite Scale")}, {QStringLiteral("product"), QStringLiteral("Infinite Scale")}, + {QStringLiteral("productversion"), QStringLiteral("2.0.0-beta1+7c2e3201b")}}}}}, + {QStringLiteral("files"), QVariantList{}}, + {QStringLiteral("spaces"), QVariantMap{{QStringLiteral("enabled"), QStringLiteral("true")}}}, + {QStringLiteral("dav"), QVariantMap{{QStringLiteral("chunking"), QStringLiteral("1.0")}}}, + {QStringLiteral("checksums"), QVariantMap{{QStringLiteral("preferredUploadType"), Utility::enumToString(algo)}, {QStringLiteral("supportedTypes"), algorithmNames}}} + }; + // clang-format on + } + + void TestUtilsPrivate::accountStateDeleter(OCC::AccountState *acc) + { + if (acc) { + OCC::AccountManager::instance()->deleteAccount(acc); + } + } } } diff --git a/test/testutils/testutils.h b/test/testutils/testutils.h index a7a2ae2eef8..dae21821fff 100644 --- a/test/testutils/testutils.h +++ b/test/testutils/testutils.h @@ -1,15 +1,37 @@ #pragma once #include "account.h" +#include "common/checksumalgorithms.h" #include "folder.h" #include "folderman.h" +#include +#include +#include + +#include + namespace OCC { namespace TestUtils { + namespace TestUtilsPrivate { + void accountStateDeleter(OCC::AccountState *acc); + + using AccountStateRaii = std::unique_ptr; + } + FolderMan *folderMan(); - FolderDefinition createDummyFolderDefinition(const QString &path); - AccountPtr createDummyAccount(); + FolderDefinition createDummyFolderDefinition(Account *account, const QString &path); + // TestUtilsPrivate::AccountStateRaii createDummyAccount(); bool writeRandomFile(const QString &fname, int size = -1); + + /*** + * Create a QTemporaryDir with a test specific name pattern + * ownCloud-unit-test-{TestName}-XXXXXX + * This allow to clean up after failed tests + */ + QTemporaryDir createTempDir(); + + const QVariantMap testCapabilities(CheckSums::Algorithm algo = CheckSums::Algorithm::DUMMY_FOR_TESTS); } } diff --git a/test/testutils/testutilsloader.cpp b/test/testutils/testutilsloader.cpp index efa4f1421fb..016ed935eb2 100644 --- a/test/testutils/testutilsloader.cpp +++ b/test/testutils/testutilsloader.cpp @@ -1,17 +1,28 @@ #include "configfile.h" +#include "gui/networkinformation.h" #include "logger.h" +#include "resources/loadresources.h" +#include "testutils.h" #include -#include namespace { -void setupLogger() +void setUpTests() { - static QTemporaryDir dir; - OCC::ConfigFile::setConfDir(dir.path()); // we don't want to pollute the user's config file + // load the resources + static const OCC::ResourcesLoader resources; + + static auto dir = OCC::TestUtils::createTempDir(); + OCC::ConfigFile::setConfDir(QStringLiteral("%1/config").arg(dir.path())); // we don't want to pollute the user's config file OCC::Logger::instance()->setLogFile(QStringLiteral("-")); OCC::Logger::instance()->addLogRule({ QStringLiteral("sync.httplogger=true") }); + OCC::Logger::instance()->setLogDebug(true); + + OCC::Account::setCommonCacheDirectory(QStringLiteral("%1/cache").arg(dir.path())); + + // ensure we have an instance of NetworkInformation + OCC::NetworkInformation::instance(); } -Q_COREAPP_STARTUP_FUNCTION(setupLogger); +Q_COREAPP_STARTUP_FUNCTION(setUpTests) } diff --git a/test/testwinvfs.cpp b/test/testwinvfs.cpp new file mode 100755 index 00000000000..bca2cbdf337 --- /dev/null +++ b/test/testwinvfs.cpp @@ -0,0 +1,798 @@ +/* + * This file was originally licensed under ownCloud Commercial License. + * see + * As of 2025, it is relicensed under GPL 2.0 or later. + */ +// SPDX-License-Identifier: GPL-2.0-or-later + +// this include should come before every other include +#include "../src/plugins/vfs/win/cfapi_includes.h" + +#include "../src/plugins/vfs/win/utility.h" +#include "../src/plugins/vfs/win/vfs_win.h" + +#include "testutils/syncenginetestutils.h" +#include + +#include + +#include + +using namespace std::chrono_literals; + +using namespace OCC; + +SyncFileItemPtr findItem(const QSignalSpy &spy, const QString &path) +{ + for (const QList &args : spy) { + auto item = args[0].value(); + if (item->destination() == path) + return item; + } + return SyncFileItemPtr(new SyncFileItem); +} + +bool itemInstruction(const QSignalSpy &spy, const QString &path, const SyncInstructions instr) +{ + auto item = findItem(spy, path); + return item->instruction() == instr; +} + +SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path) +{ + SyncJournalFileRecord record; + folder.syncJournal()->getFileRecord(path, &record); + return record; +} + +void markForDownload(FakeFolder &folder, const QByteArray &path) +{ + auto journal = folder.syncJournal(); + SyncJournalFileRecord record; + journal->getFileRecord(path, &record); + if (!record.isValid()) + return; + record._type = ItemTypeVirtualFileDownload; + journal->setFileRecord(record); + journal->schedulePathForRemoteDiscovery(record._path); +} + +bool isPlaceholder(const QString &path) +{ + auto info = CfAPIUtil::getInfo(path); + return bool(info); +} + +int64_t isPlaceholderWithOnDiskSize(const QString &path) +{ + auto info = CfAPIUtil::getInfo(path); + if (!info) { + return -1; + } + return info.get().OnDiskDataSize.QuadPart; +} + +bool hydratePlaceholderSynchronously(const QString &path) +{ + bool hydrationOk = false; + std::unique_ptr thread(QThread::create([&]() { + auto handle = CfAPIUtil::getFileHandle(path, 0); + if (!handle) + return; + + LARGE_INTEGER start; + LARGE_INTEGER end; + start.QuadPart = 0; + end.QuadPart = MAXLONGLONG; + const HRESULT ok = CfHydratePlaceholder(handle.get(), start, end, CF_HYDRATE_FLAG_NONE, nullptr); + if (FAILED(ok)) { + qWarning() << "CfHydratePlaceholder failed:" << Utility::formatWinError(ok); + hydrationOk = false; + } else { + hydrationOk = true; + } + })); + + QSignalSpy spy(thread.get(), &QThread::finished); + thread->start(); + OC_ENFORCE(spy.wait(std::chrono::duration_cast(1min).count())); + return hydrationOk; +} + +// Like VfsWin::setPinState but with control over recursion flag +bool setPinState(FakeFolder &folder, const QString &path, CF_PIN_STATE state, CF_SET_PIN_FLAGS flag) +{ + QString fsPath = folder.localPath() + path; + auto handle = CfAPIUtil::getFileHandle(fsPath, 0); + if (!handle) + return false; + + HRESULT ok = CfSetPinState(handle.get(), state, flag, nullptr); + return ok == 0; +} + + +class TestWinVfs : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + + void testPlaceholderCreationAndHydration() + { + FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; + fakeFolder.account()->setCapabilities({fakeFolder.account()->url(), TestUtils::testCapabilities(CheckSums::Algorithm::SHA1)}); + + QSignalSpy completeSpy(fakeFolder.syncEngine(), &SyncEngine::itemCompleted); + auto cleanup = [&]() { + completeSpy.clear(); + }; + cleanup(); + + auto someDate = QDateTime(QDate(1984, 07, 30), QTime(1,3,2)); + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a1"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a2"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a3"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a4"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a5"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a6"), 0_B); + fakeFolder.remoteModifier().setModTime(QStringLiteral("A/a1"), someDate); + fakeFolder.remoteModifier().setModTime(QStringLiteral("A/a2"), someDate); + fakeFolder.remoteModifier().setModTime(QStringLiteral("A/a3"), someDate); + QString localA1 = fakeFolder.localPath() + QStringLiteral("A/a1"); + QString localA2 = fakeFolder.localPath() + QStringLiteral("A/a2"); + QString localA3 = fakeFolder.localPath() + QStringLiteral("A/a3"); + QString localA4 = fakeFolder.localPath() + QStringLiteral("A/a4"); + QString localA5 = fakeFolder.localPath() + QStringLiteral("A/a5"); + QString localA6 = fakeFolder.localPath() + QStringLiteral("A/a6"); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(localA1)); + QCOMPARE(QFileInfo(localA1).lastModified(), someDate); + QCOMPARE(QFileInfo(localA1).size(), 5_MiB); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("A/a1"))); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a1"), CSYNC_INSTRUCTION_NEW)); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a1"))._type, ItemTypeVirtualFile); + QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("A/a6"))->isDehydratedPlaceholder); + // we assume further placeholders are created correctly + cleanup(); + + // We listen to the Vfs hydration signals to check. + + // + // Check that basic hydration works + // + OperationCounter counter(fakeFolder); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(localA1)); + QVERIFY(hydratePlaceholderSynchronously(localA1)); + QVERIFY(!fakeFolder.vfs()->isDehydratedPlaceholder(localA1)); + QCOMPARE(counter.nGET, 1); + + QCOMPARE(isPlaceholderWithOnDiskSize(localA1), 5_MiB); + QCOMPARE(QFileInfo(localA1).lastModified(), someDate); + QCOMPARE(QFileInfo(localA1).size(), 5_MiB); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a1"))._type, ItemTypeFile); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a1"))._checksumHeader, "SHA1:8886930866e6faf7558cda88305d92d13ee26cc9"); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a1")).get(), PinState::Unspecified); // since it was an implicit hydration + + // + // Is the remote etag propagated if it changed? + // + + fakeFolder.remoteModifier().setContents(QStringLiteral("A/a2"), 5_MiB, 'M'); + + counter.reset(); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(localA2)); + QVERIFY(hydratePlaceholderSynchronously(localA2)); + QVERIFY(!fakeFolder.vfs()->isDehydratedPlaceholder(localA2)); + QCOMPARE(counter.nGET, 1); + + QCOMPARE(isPlaceholderWithOnDiskSize(localA2), 5_MiB); + QCOMPARE(QFileInfo(localA2).lastModified(), someDate); + QCOMPARE(QFileInfo(localA2).size(), 5_MiB); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a2"))._type, ItemTypeFile); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a2"))._etag, fakeFolder.remoteModifier().find(QStringLiteral("A/a2"))->etag); + + // + // What if the remote file size changed? + // + + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a3")); + + counter.reset(); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(localA3)); + QVERIFY(hydratePlaceholderSynchronously(localA3)); + QVERIFY(!fakeFolder.vfs()->isDehydratedPlaceholder(localA3)); + QCOMPARE(counter.nGET, 2); // the download was restarted as we detected a size missmatch between the local and the remote size + + QCOMPARE(isPlaceholderWithOnDiskSize(localA3), 5_MiB + 1_B); + QCOMPARE(QFileInfo(localA3).lastModified(), someDate); + QCOMPARE(QFileInfo(localA3).size(), 5_MiB + 1_B); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a3"))._type, ItemTypeFile); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a3"))._etag, fakeFolder.remoteModifier().find(QStringLiteral("A/a3"))->etag); + + // + // Check if a 0 (zero) byte file can be a) a placeholder before hydration, + // and b) is considered *not* to be a placeholder after hydration. + // + QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("A/a6"))->isDehydratedPlaceholder); + QVERIFY(hydratePlaceholderSynchronously(localA6)); + QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("A/a6"))); // check that the file/placeholder (still) exists + QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("A/a6"))->isDehydratedPlaceholder); + + // + // Do network errors get signaled promptly? + // + + fakeFolder.serverErrorPaths().append(QStringLiteral("A/a4")); + QVERIFY(!hydratePlaceholderSynchronously(localA4)); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(localA4)); + + fakeFolder.serverErrorPaths().clear(); + QVERIFY(hydratePlaceholderSynchronously(localA4)); + + // + // Checksum validation errors are caught? + // + + QByteArray checksumValue; + QObject parent; + fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * { + if (op == QNetworkAccessManager::GetOperation) { + auto reply = new FakeGetReply(fakeFolder.remoteModifier(), op, request, &parent); + if (!checksumValue.isNull()) + reply->setRawHeader("OC-Checksum", checksumValue); + return reply; + } + return nullptr; + }); + + checksumValue = "SHA1:bad"; + + QVERIFY(!hydratePlaceholderSynchronously(localA5)); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(localA5)); + + // a second hydration attempt doesn't change anything + QVERIFY(!hydratePlaceholderSynchronously(localA5)); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(localA5)); + + checksumValue = "MD5:b8fc857a25e7958868c2f003d5e0952d"; + QVERIFY(hydratePlaceholderSynchronously(localA5)); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a5"))._checksumHeader, "SHA1:8886930866e6faf7558cda88305d92d13ee26cc9"); + + fakeFolder.setServerOverride(nullptr); + + // everything is hydrated and in sync + // this also checks the file contents (first byte at least) + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } + + // Normal hydration-through-sync still works + void testSyncHydration() + { + FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; + + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + auto cleanup = [&]() { + completeSpy.clear(); + }; + cleanup(); + + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a1"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a2"), 5_MiB, 'A'); + QString localA1 = fakeFolder.localPath() + QStringLiteral("A/a1"); + QString localA2 = fakeFolder.localPath() + QStringLiteral("A/a2"); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(localA1)); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a1"))._type, ItemTypeVirtualFile); + cleanup(); + + // + // Mark for download and check it works + // + + markForDownload(fakeFolder, "A/a1"); + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/a2"), PinState::AlwaysLocal)); + + CF_FS_METADATA metadata; + QVERIFY(CfAPIUtil::getFileMetadata(fakeFolder.localPath() + QStringLiteral("A/a2"), &metadata.BasicInfo)); + QVERIFY(metadata.BasicInfo.FileAttributes & FILE_ATTRIBUTE_PINNED); + QVERIFY(!(metadata.BasicInfo.FileAttributes & FILE_ATTRIBUTE_UNPINNED)); + + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a2")).get(), PinState::AlwaysLocal); + + QCOMPARE(isPlaceholderWithOnDiskSize(localA1), 5_MiB); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a1"))._type, ItemTypeFile); + QCOMPARE(isPlaceholderWithOnDiskSize(localA2), 5_MiB); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("A/a2"))._type, ItemTypeFile); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a1"), CSYNC_INSTRUCTION_SYNC)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a2"), CSYNC_INSTRUCTION_SYNC)); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a1")).get(), PinState::Unspecified); // since no attribute got propagated + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a2")).get(), PinState::AlwaysLocal); + cleanup(); + + // + // If remote changes get propagated, the pin state stays unchanged + // + + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a2")); + QVERIFY(fakeFolder.syncOnce()); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a1")).get(), PinState::Unspecified); // since no attribute got propagated + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("A/a2")).get(), PinState::AlwaysLocal); + cleanup(); + } + + // Placeholder moving and deleting gets propagated + void testMoveDelete() + { + FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; + + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + auto cleanup = [&]() { + completeSpy.clear(); + }; + cleanup(); + + auto l = [&](const QString &p) { return QString(fakeFolder.localPath() + p); }; + + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a1"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/b1"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("f1"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("q1"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/d1"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/d2"), 5_MiB, 'A'); + QVERIFY(fakeFolder.syncOnce()); + cleanup(); + + fakeFolder.localModifier().rename(QStringLiteral("f1"), QStringLiteral("f2")); + fakeFolder.localModifier().rename(QStringLiteral("A/a1"), QStringLiteral("a1")); + fakeFolder.localModifier().remove(QStringLiteral("A/d1")); + fakeFolder.remoteModifier().rename(QStringLiteral("q1"), QStringLiteral("q2")); + fakeFolder.remoteModifier().rename(QStringLiteral("A/b1"), QStringLiteral("b1")); + fakeFolder.remoteModifier().remove(QStringLiteral("A/d2")); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QVERIFY(itemInstruction(completeSpy, QStringLiteral("f2"), CSYNC_INSTRUCTION_RENAME)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("a1"), CSYNC_INSTRUCTION_RENAME)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("q2"), CSYNC_INSTRUCTION_RENAME)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("b1"), CSYNC_INSTRUCTION_RENAME)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/d1"), CSYNC_INSTRUCTION_REMOVE)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/d2"), CSYNC_INSTRUCTION_REMOVE)); + + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("f2")))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("a1")))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("q2")))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("b1")))); + QVERIFY(!fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/d1")))); + QVERIFY(!fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/d2")))); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("f2"))._type, ItemTypeVirtualFile); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("a1"))._type, ItemTypeVirtualFile); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("q2"))._type, ItemTypeVirtualFile); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("b1"))._type, ItemTypeVirtualFile); + QVERIFY(!dbRecord(fakeFolder, QStringLiteral("A/d1")).isValid()); + QVERIFY(!dbRecord(fakeFolder, QStringLiteral("A/d2")).isValid()); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("f2"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("a1"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("A/d1"))); + cleanup(); + } + + // Placeholders update size/mtime if remote metadata changes + void testMetadataUpdate() + { + FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; + + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + auto cleanup = [&]() { + completeSpy.clear(); + }; + cleanup(); + + auto l = [&](const QString &p) { return QString(fakeFolder.localPath() + p); }; + + auto someDate = QDateTime(QDate(1984, 07, 30), QTime(1,3,2)); + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a1"), 5_MiB, 'A'); + fakeFolder.remoteModifier().setModTime(QStringLiteral("A/a1"), someDate); + QVERIFY(fakeFolder.syncOnce()); + + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a1"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/a1")))); + QCOMPARE(QFileInfo(l(QStringLiteral("A/a1"))).size(), 5_MiB); + QCOMPARE(QFileInfo(l(QStringLiteral("A/a1"))).lastModified(), someDate); + cleanup(); + + // Change the remote metadata and check whether it propagates + + auto otherDate = QDateTime(QDate(2000, 03, 03), QTime(2,5,4)); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a1")); + fakeFolder.remoteModifier().setModTime(QStringLiteral("A/a1"), otherDate); + + QVERIFY(fakeFolder.syncOnce()); + + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a1"), CSYNC_INSTRUCTION_UPDATE_METADATA)); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/a1")))); + QCOMPARE(QFileInfo(l(QStringLiteral("A/a1"))).size(), 5_MiB + 1_B); + QCOMPARE(QFileInfo(l(QStringLiteral("A/a1"))).lastModified(), otherDate); + cleanup(); + + // No spurious sync + QVERIFY(fakeFolder.syncOnce()); + QVERIFY(completeSpy.isEmpty()); + cleanup(); + } + + // Various kinds of conflicts + void testConflicts() + { + FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; + + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + auto cleanup = [&]() { + completeSpy.clear(); + }; + cleanup(); + + auto l = [&](const QString &p) { return QString(fakeFolder.localPath() + p); }; + + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/localdelete"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/localreplace1"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/localreplace2"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/localreplace3"), 5_MiB, 'A'); + fakeFolder.remoteModifier().insert(QStringLiteral("A/localreplace4"), 5_MiB, 'A'); + QVERIFY(fakeFolder.syncOnce()); + + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/localdelete")))); + cleanup(); + + // Now try remote/local conflicts + + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/localdelete")); + // localreplace1/2 aren't actual conflicts, but switch out the placeholder in an + // interesting way + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/localreplace3")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/localreplace4")); + fakeFolder.localModifier().remove(QStringLiteral("A/localdelete")); + fakeFolder.localModifier().remove(QStringLiteral("A/localreplace1")); + fakeFolder.localModifier().remove(QStringLiteral("A/localreplace2")); + fakeFolder.localModifier().remove(QStringLiteral("A/localreplace3")); + fakeFolder.localModifier().remove(QStringLiteral("A/localreplace4")); + fakeFolder.localModifier().insert(QStringLiteral("A/localreplace1"), 1_MiB, 'B'); + fakeFolder.localModifier().insert(QStringLiteral("A/localreplace2"), 5_MiB, 'A'); + fakeFolder.localModifier().insert(QStringLiteral("A/localreplace3"), 1_MiB, 'B'); + fakeFolder.localModifier().insert(QStringLiteral("A/localreplace4"), 5_MiB, 'A'); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/localdelete"), CSYNC_INSTRUCTION_NEW)); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/localdelete")))); + QCOMPARE(QFileInfo(l(QStringLiteral("A/localdelete"))).size(), 5_MiB + 1_B); + + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/localreplace1"), CSYNC_INSTRUCTION_SYNC)); + // isn't a placeholder at all + + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/localreplace2"), CSYNC_INSTRUCTION_SYNC)); // no sync? + // isn't a placeholder at all + + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/localreplace3"), CSYNC_INSTRUCTION_CONFLICT)); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/localreplace4"), CSYNC_INSTRUCTION_CONFLICT)); + + cleanup(); + } + + // Check what happens if there's a rename and a pin/hydration change + // Check SyncVirtualFiles::testRenameVirtual2 which does the same for suffix vfs + void testRenameVirtual2() + { + FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; + auto l = [&](const QString &p) { return QString(fakeFolder.localPath() + p); }; + + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + auto cleanup = [&]() { + completeSpy.clear(); + }; + cleanup(); + + fakeFolder.remoteModifier().insert(QStringLiteral("case3"), 128_B, 'C'); + fakeFolder.remoteModifier().insert(QStringLiteral("case4"), 256_B, 'C'); + fakeFolder.remoteModifier().insert(QStringLiteral("case5"), 256_B, 'C'); + fakeFolder.remoteModifier().insert(QStringLiteral("case6"), 256_B, 'C'); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("case4"), PinState::AlwaysLocal)); + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("case6"), PinState::AlwaysLocal)); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("case3")))); + QCOMPARE(isPlaceholderWithOnDiskSize(l(QStringLiteral("case4"))), 256); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("case5")))); + cleanup(); + + // Case 1: foo -> bar (tested elsewhere) + // Case 2: foo.oc -> bar.oc (tested elsewhere) + + // Case 3: foo -> bar (foo starts dehydrated, pin state changed to alwayslocal) + fakeFolder.localModifier().rename(QStringLiteral("case3"), QStringLiteral("case3-rename")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("case3-rename"), PinState::AlwaysLocal)); + + // Case 4: foo -> bar (foo starts hydrated, pin state changed to onlineonly) + fakeFolder.localModifier().rename(QStringLiteral("case4"), QStringLiteral("case4-rename")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("case4-rename"), PinState::OnlineOnly)); + + // Case 5: foo -> bar (foo starts dehydrated, db changed to ITVFDownload) + fakeFolder.localModifier().rename(QStringLiteral("case5"), QStringLiteral("case5-rename")); + markForDownload(fakeFolder, "case5"); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + // Case 3: the rename went though, pin state change still on file + QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("case3"))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("case3-rename")))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("case3"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("case3-rename"))); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("case3-rename"), CSYNC_INSTRUCTION_RENAME)); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("case3-rename"))._type, ItemTypeVirtualFile); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("case3-rename")).get(), PinState::AlwaysLocal); + + // Case 4: the rename went though, pin state change still on file + QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("case4"))); + QVERIFY(fakeFolder.currentLocalState().find(QStringLiteral("case4-rename"))); + QCOMPARE(isPlaceholderWithOnDiskSize(l(QStringLiteral("case4-rename"))), 256); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("case4"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("case4-rename"))); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("case4-rename"), CSYNC_INSTRUCTION_RENAME)); + QCOMPARE(dbRecord(fakeFolder, QStringLiteral("case4-rename"))._type, ItemTypeFile); + QCOMPARE(fakeFolder.vfs()->pinState(QStringLiteral("case4-rename")).get(), PinState::OnlineOnly); + + // at this point it'd be nice if the client had anotherSyncNeeded set, to + // make sure the pin state change gets applied! + + // Case 5: the rename went though, hydration is forgotten + QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("case5"))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("case5-rename")))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("case5"))); + QVERIFY(fakeFolder.currentRemoteState().find(QStringLiteral("case5-rename"))); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("case5-rename"), CSYNC_INSTRUCTION_RENAME)); + QVERIFY(dbRecord(fakeFolder, QStringLiteral("case5-rename"))._type == ItemTypeVirtualFile); + } + + // Dehydration via sync works, when going through file attributes + void testSyncDehydrationViaUnpin() + { + FakeFolder fakeFolder { FileInfo::A12_B12_C12_S12(), Vfs::WindowsCfApi }; + auto l = [&](const QString &p) { return QString(fakeFolder.localPath() + p); }; + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + auto cleanup = [&]() { + completeSpy.clear(); + }; + cleanup(); + + // + // Mark for dehydration and check + // + + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/a1"), PinState::OnlineOnly)); + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/a2"), PinState::OnlineOnly)); + fakeFolder.remoteModifier().appendByte(QStringLiteral("A/a2")); + // expect: normal dehydration + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("B/b1"), PinState::OnlineOnly)); + fakeFolder.remoteModifier().remove(QStringLiteral("B/b1")); + // expect: local removal + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("B/b2"), PinState::OnlineOnly)); + fakeFolder.remoteModifier().rename(QStringLiteral("B/b2"), QStringLiteral("B/b3")); + // expect: B/b2 is renamed to B/b3, later maybe dehydration? + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("C/c1"), PinState::OnlineOnly)); + fakeFolder.localModifier().appendByte(QStringLiteral("C/c1")); + // expect: no dehydration, upload of c1 + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("C/c2"), PinState::OnlineOnly)); + fakeFolder.localModifier().appendByte(QStringLiteral("C/c2")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c2")); + fakeFolder.remoteModifier().appendByte(QStringLiteral("C/c2")); + // expect: no dehydration, conflict + + // A new not-in-db file that's nevertheless unpinned + fakeFolder.localModifier().insert(QStringLiteral("C/n")); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("C/n"), PinState::OnlineOnly)); + // expect: uploaded, later maybe dehydration? + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/a1")))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/a2")))); + + QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("B/b1"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("B/b1"))); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("B/b1"), CSYNC_INSTRUCTION_REMOVE)); + + QVERIFY(!fakeFolder.currentLocalState().find(QStringLiteral("B/b2"))); + QVERIFY(!fakeFolder.currentRemoteState().find(QStringLiteral("B/b2"))); + QVERIFY(!fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("B/b3")))); // because the non-virtual was renamed + QVERIFY(itemInstruction(completeSpy, QStringLiteral("B/b3"), CSYNC_INSTRUCTION_RENAME)); + // TODO: b3 still has the UNPINNED state? + + QCOMPARE(isPlaceholderWithOnDiskSize(l(QStringLiteral("C/c1"))), 25); + QCOMPARE(fakeFolder.currentRemoteState().find(QStringLiteral("C/c1"))->contentSize, 25); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("C/c1"), CSYNC_INSTRUCTION_SYNC)); + + QCOMPARE(isPlaceholderWithOnDiskSize(l(QStringLiteral("C/c2"))), 26); + QCOMPARE(fakeFolder.currentRemoteState().find(QStringLiteral("C/c2"))->contentSize, 26); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("C/c2"), CSYNC_INSTRUCTION_CONFLICT)); + + QCOMPARE(isPlaceholderWithOnDiskSize(l(QStringLiteral("C/n"))), 64); + QCOMPARE(fakeFolder.currentRemoteState().find(QStringLiteral("C/n"))->contentSize, 64); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("C/n"), CSYNC_INSTRUCTION_NEW)); + // TODO: n still has UNPINNED state? + cleanup(); + } + + void testWipePlaceholders() + { + FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; + auto l = [&](const QString &p) { return QString(fakeFolder.localPath() + p); }; + + // Create a baseline + + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().mkdir(QStringLiteral("A/B")); + fakeFolder.remoteModifier().insert(QStringLiteral("f1")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a1")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a3")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/B/b1")); + fakeFolder.localModifier().mkdir(QStringLiteral("A")); + fakeFolder.localModifier().mkdir(QStringLiteral("A/B")); + fakeFolder.localModifier().insert(QStringLiteral("f2")); + fakeFolder.localModifier().insert(QStringLiteral("A/a2")); + fakeFolder.localModifier().insert(QStringLiteral("A/B/b2")); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("f1")))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/a1")))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/a3")))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(l(QStringLiteral("A/B/b1")))); + + // Make local changes to a3 + fakeFolder.localModifier().remove(QStringLiteral("A/a3")); + fakeFolder.localModifier().insert(QStringLiteral("A/a3"), 100_B); + QVERIFY(fakeFolder.applyLocalModificationsWithoutSync()); + + // todo: review this. AFAIK it's already covered in FakeFolder::swtichToVfs or *should* be + fakeFolder.vfs()->wipeDehydratedVirtualFiles(); + fakeFolder.vfs()->stop(); + fakeFolder.vfs()->unregisterFolder(); + + QVERIFY(!QFile::exists(l(QStringLiteral("f1")))); + QVERIFY(!QFile::exists(l(QStringLiteral("A/a1")))); + QVERIFY(QFile::exists(l(QStringLiteral("A/a3")))); + QVERIFY(!QFile::exists(l(QStringLiteral("A/B/b1")))); + + // Check that syncing with vfs disabled is fine + auto vfsOff = VfsPluginManager::instance().createVfsFromPlugin(Vfs::Off, &fakeFolder); + QVERIFY(vfsOff); + fakeFolder.switchToVfs(vfsOff); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + auto conflicts = fakeFolder.syncJournal()->conflictRecordPaths(); + QCOMPARE(conflicts.size(), 1); + QFile::remove(l(QString::fromUtf8(conflicts[0]))); + QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState()); + } + + void testAvailability() + { + FakeFolder fakeFolder { FileInfo(), Vfs::WindowsCfApi, true }; + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a1")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a2")); + fakeFolder.syncOnce(); + QCOMPARE(fakeFolder.vfs()->availability(QString()).get(), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")).get(), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")).get(), VfsItemAvailability::AllDehydrated); + + // The Vfs property is only accessible when it's not "sync pending" + auto updateSyncState = [&] { + fakeFolder.vfs()->onFileStatusChanged(fakeFolder.localPath() + QStringLiteral("A/a1"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + fakeFolder.vfs()->onFileStatusChanged(fakeFolder.localPath() + QStringLiteral("A/a2"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + fakeFolder.vfs()->onFileStatusChanged(fakeFolder.localPath() + QStringLiteral("A"), SyncFileStatus(SyncFileStatus::StatusUpToDate)); + }; + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A"), PinState::AlwaysLocal)); + fakeFolder.syncOnce(); + updateSyncState(); + QCOMPARE(fakeFolder.vfs()->availability(QString()).get(), VfsItemAvailability::AllHydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")).get(), VfsItemAvailability::AlwaysLocal); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")).get(), VfsItemAvailability::AlwaysLocal); + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/a1"), PinState::Unspecified)); + fakeFolder.syncOnce(); + updateSyncState(); + QCOMPARE(fakeFolder.vfs()->availability(QString()).get(), VfsItemAvailability::AllHydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")).get(), VfsItemAvailability::AllHydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")).get(), VfsItemAvailability::AllHydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a2")).get(), VfsItemAvailability::AlwaysLocal); + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A"), PinState::OnlineOnly)); + fakeFolder.syncOnce(); + updateSyncState(); + QCOMPARE(fakeFolder.vfs()->availability(QString()).get(), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")).get(), VfsItemAvailability::AllDehydrated); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")).get(), VfsItemAvailability::AllDehydrated); + + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/a1"), PinState::AlwaysLocal)); + fakeFolder.syncOnce(); + updateSyncState(); + QCOMPARE(fakeFolder.vfs()->availability(QString()).get(), VfsItemAvailability::Mixed); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A")).get(), VfsItemAvailability::Mixed); + QCOMPARE(fakeFolder.vfs()->availability(QStringLiteral("A/a1")).get(), VfsItemAvailability::AlwaysLocal); + } + + // Check that previously hydrated files become placeholders on sync + void testConvertToPlaceholders() + { + FakeFolder fakeFolder { FileInfo {}, Vfs::Off }; + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/a1"), 64_B); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + + fakeFolder.switchToVfs(new VfsWin(&fakeFolder)); + QSignalSpy completeSpy(fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &))); + + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a1"), CSYNC_INSTRUCTION_UPDATE_METADATA)); + QCOMPARE(isPlaceholderWithOnDiskSize(QString(fakeFolder.localPath() + QStringLiteral("A/a1"))), 64_B); + + completeSpy.clear(); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(itemInstruction(completeSpy, QStringLiteral("A/a1"), CSYNC_INSTRUCTION_NONE)); + QCOMPARE(isPlaceholderWithOnDiskSize(QString(fakeFolder.localPath() + QStringLiteral("A/a1"))), 64_B); + } + + void testIsPlaceholder() + { + FakeFolder fakeFolder = { FileInfo {}, Vfs::WindowsCfApi }; + fakeFolder.remoteModifier().mkdir(QStringLiteral("A")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/zeroBytesHydrated"), 0_B); + fakeFolder.remoteModifier().insert(QStringLiteral("A/zeroBytesDeHydrated"), 0_B); + fakeFolder.remoteModifier().insert(QStringLiteral("A/Hydrated")); + fakeFolder.remoteModifier().insert(QStringLiteral("A/DeHydrated")); + QVERIFY(fakeFolder.applyLocalModificationsAndSync()); + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/zeroBytesHydrated"), PinState::AlwaysLocal)); + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/zeroBytesDeHydrated"), PinState::OnlineOnly)); + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/Hydrated"), PinState::AlwaysLocal)); + QVERIFY(fakeFolder.vfs()->setPinState(QStringLiteral("A/DeHydrated"), PinState::OnlineOnly)); + QVERIFY(fakeFolder.syncOnce()); + + QVERIFY(!fakeFolder.vfs()->isDehydratedPlaceholder(fakeFolder.localPath() + QStringLiteral("A/zeroBytesHydrated"))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(fakeFolder.localPath() + QStringLiteral("A/zeroBytesDeHydrated"))); + + QVERIFY(!fakeFolder.vfs()->isDehydratedPlaceholder(fakeFolder.localPath() + QStringLiteral("A/Hydrated"))); + QVERIFY(fakeFolder.vfs()->isDehydratedPlaceholder(fakeFolder.localPath() + QStringLiteral("A/DeHydrated"))); + } +}; + +QTEST_GUILESS_MAIN(TestWinVfs) +#include "testwinvfs.moc" diff --git a/test/testxmlparse.cpp b/test/testxmlparse.cpp index e244bdc9fe0..228ea492c43 100644 --- a/test/testxmlparse.cpp +++ b/test/testxmlparse.cpp @@ -15,466 +15,446 @@ class TestXmlParse : public QObject Q_OBJECT private: - bool _success; - QStringList _subdirs; - QStringList _items; - -public slots: - void slotDirectoryListingSubFolders(const QStringList& list) - { - qDebug() << "subfolders: " << list; - _subdirs.append(list); - } - - void slotDirectoryListingIterated(const QString& item, const QMap& ) - { - qDebug() << " item: " << item; - _items.append(item); - } - - void slotFinishedSuccessfully() - { - _success = true; - } - -private slots: - void init() { - qDebug() << Q_FUNC_INFO; - _success = false; - _subdirs.clear(); - _items.clear(); + bool _success; + QStringList _subdirs; + QStringList _items; + +public Q_SLOTS: + void slotDirectoryListingSubFolders(const QStringList &list) + { + qDebug() << "subfolders: " << list; + _subdirs.append(list); } - void cleanup() { + void slotDirectoryListingIterated(const QString &item, const QMap &) + { + qDebug() << " item: " << item; + _items.append(item); } + void slotFinishedSuccessfully() { _success = true; } + +private Q_SLOTS: + void init() + { + qDebug() << Q_FUNC_INFO; + _success = false; + _subdirs.clear(); + _items.clear(); + } - void testParser1() { + void cleanup() { } + + + void testParser1() + { const QByteArray testXml = "" - "" - "" - "/oc/remote.php/webdav/sharefolder/" - "" - "" - "00004213ocobzus5kn6s" - "RDNVCK" - "121780" - "\"5527beb0400b0\"" - "" - "" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - "" - "/oc/remote.php/webdav/sharefolder/quitte.pdf" - "" - "" - "00004215ocobzus5kn6s" - "RDNVW" - "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "121780" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - ""; + "" + "" + "/oc/remote.php/webdav/sharefolder/" + "" + "" + "00004213ocobzus5kn6s" + "RDNVCK" + "121780" + "\"5527beb0400b0\"" + "" + "" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + "" + "/oc/remote.php/webdav/sharefolder/quitte.pdf" + "" + "" + "00004215ocobzus5kn6s" + "RDNVW" + "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "121780" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + ""; LsColXMLParser parser; - connect(&parser, &LsColXMLParser::directoryListingSubfolders, - this, &TestXmlParse::slotDirectoryListingSubFolders); - connect(&parser, &LsColXMLParser::directoryListingIterated, - this, &TestXmlParse::slotDirectoryListingIterated); - connect(&parser, &LsColXMLParser::finishedWithoutError, - this, &TestXmlParse::slotFinishedSuccessfully); + connect(&parser, &LsColXMLParser::directoryListingSubfolders, this, &TestXmlParse::slotDirectoryListingSubFolders); + connect(&parser, &LsColXMLParser::directoryListingIterated, this, &TestXmlParse::slotDirectoryListingIterated); + connect(&parser, &LsColXMLParser::finishedWithoutError, this, &TestXmlParse::slotFinishedSuccessfully); - QHash sizes; - QVERIFY(parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); + QHash sizes; + QVERIFY(parser.parse(testXml, &sizes, QStringLiteral("/oc/remote.php/webdav/sharefolder"))); QVERIFY(_success); - QCOMPARE(sizes.size(), 1 ); // Quota info in the XML + QCOMPARE(sizes.size(), 1); // Quota info in the XML - QVERIFY(_items.contains("/oc/remote.php/webdav/sharefolder/quitte.pdf")); - QVERIFY(_items.contains("/oc/remote.php/webdav/sharefolder")); - QVERIFY(_items.size() == 2 ); + QVERIFY(_items.contains(QStringLiteral("/oc/remote.php/webdav/sharefolder/quitte.pdf"))); + QVERIFY(_items.contains(QStringLiteral("/oc/remote.php/webdav/sharefolder"))); + QVERIFY(_items.size() == 2); - QVERIFY(_subdirs.contains("/oc/remote.php/webdav/sharefolder/")); + QVERIFY(_subdirs.contains(QStringLiteral("/oc/remote.php/webdav/sharefolder/"))); QVERIFY(_subdirs.size() == 1); } - void testParserBrokenXml() { + void testParserBrokenXml() + { const QByteArray testXml = "X" - "" - "" - "/oc/remote.php/webdav/sharefolder/" - "" - "" - "00004213ocobzus5kn6s" - "RDNVCK" - "121780" - "\"5527beb0400b0\"" - "" - "" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - "" - "/oc/remote.php/webdav/sharefolder/quitte.pdf" - "" - "" - "00004215ocobzus5kn6s" - "RDNVW" - "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "121780" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - ""; + "" + "" + "/oc/remote.php/webdav/sharefolder/" + "" + "" + "00004213ocobzus5kn6s" + "RDNVCK" + "121780" + "\"5527beb0400b0\"" + "" + "" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + "" + "/oc/remote.php/webdav/sharefolder/quitte.pdf" + "" + "" + "00004215ocobzus5kn6s" + "RDNVW" + "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "121780" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + ""; LsColXMLParser parser; - connect(&parser, &LsColXMLParser::directoryListingSubfolders, - this, &TestXmlParse::slotDirectoryListingSubFolders); - connect(&parser, &LsColXMLParser::directoryListingIterated, - this, &TestXmlParse::slotDirectoryListingIterated); - connect(&parser, &LsColXMLParser::finishedWithoutError, - this, &TestXmlParse::slotFinishedSuccessfully); + connect(&parser, &LsColXMLParser::directoryListingSubfolders, this, &TestXmlParse::slotDirectoryListingSubFolders); + connect(&parser, &LsColXMLParser::directoryListingIterated, this, &TestXmlParse::slotDirectoryListingIterated); + connect(&parser, &LsColXMLParser::finishedWithoutError, this, &TestXmlParse::slotFinishedSuccessfully); - QHash sizes; - QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); // verify false + QHash sizes; + QVERIFY(false == parser.parse(testXml, &sizes, QStringLiteral("/oc/remote.php/webdav/sharefolder"))); // verify false QVERIFY(!_success); - QVERIFY(sizes.size() == 0 ); // No quota info in the XML + QVERIFY(sizes.size() == 0); // No quota info in the XML - QVERIFY(_items.size() == 0 ); // FIXME: We should change the parser to not emit during parsing but at the end + QVERIFY(_items.size() == 0); // FIXME: We should change the parser to not emit during parsing but at the end QVERIFY(_subdirs.size() == 0); } - void testParserEmptyXmlNoDav() { + void testParserEmptyXmlNoDav() + { const QByteArray testXml = "I am under construction"; LsColXMLParser parser; - connect(&parser, &LsColXMLParser::directoryListingSubfolders, - this, &TestXmlParse::slotDirectoryListingSubFolders); - connect(&parser, &LsColXMLParser::directoryListingIterated, - this, &TestXmlParse::slotDirectoryListingIterated); - connect(&parser, &LsColXMLParser::finishedWithoutError, - this, &TestXmlParse::slotFinishedSuccessfully); + connect(&parser, &LsColXMLParser::directoryListingSubfolders, this, &TestXmlParse::slotDirectoryListingSubFolders); + connect(&parser, &LsColXMLParser::directoryListingIterated, this, &TestXmlParse::slotDirectoryListingIterated); + connect(&parser, &LsColXMLParser::finishedWithoutError, this, &TestXmlParse::slotFinishedSuccessfully); - QHash sizes; - QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); // verify false + QHash sizes; + QVERIFY(false == parser.parse(testXml, &sizes, QStringLiteral("/oc/remote.php/webdav/sharefolder"))); // verify false QVERIFY(!_success); - QVERIFY(sizes.size() == 0 ); // No quota info in the XML + QVERIFY(sizes.size() == 0); // No quota info in the XML - QVERIFY(_items.size() == 0 ); // FIXME: We should change the parser to not emit during parsing but at the end + QVERIFY(_items.size() == 0); // FIXME: We should change the parser to not emit during parsing but at the end QVERIFY(_subdirs.size() == 0); } - void testParserEmptyXml() { + void testParserEmptyXml() + { const QByteArray testXml = ""; LsColXMLParser parser; - connect(&parser, &LsColXMLParser::directoryListingSubfolders, - this, &TestXmlParse::slotDirectoryListingSubFolders); - connect(&parser, &LsColXMLParser::directoryListingIterated, - this, &TestXmlParse::slotDirectoryListingIterated); - connect(&parser, &LsColXMLParser::finishedWithoutError, - this, &TestXmlParse::slotFinishedSuccessfully); + connect(&parser, &LsColXMLParser::directoryListingSubfolders, this, &TestXmlParse::slotDirectoryListingSubFolders); + connect(&parser, &LsColXMLParser::directoryListingIterated, this, &TestXmlParse::slotDirectoryListingIterated); + connect(&parser, &LsColXMLParser::finishedWithoutError, this, &TestXmlParse::slotFinishedSuccessfully); - QHash sizes; - QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); // verify false + QHash sizes; + QVERIFY(false == parser.parse(testXml, &sizes, QStringLiteral("/oc/remote.php/webdav/sharefolder"))); // verify false QVERIFY(!_success); - QVERIFY(sizes.size() == 0 ); // No quota info in the XML + QVERIFY(sizes.size() == 0); // No quota info in the XML - QVERIFY(_items.size() == 0 ); // FIXME: We should change the parser to not emit during parsing but at the end + QVERIFY(_items.size() == 0); // FIXME: We should change the parser to not emit during parsing but at the end QVERIFY(_subdirs.size() == 0); } - void testParserTruncatedXml() { + void testParserTruncatedXml() + { const QByteArray testXml = "" - "" - "" - "/oc/remote.php/webdav/sharefolder/" - "" - "" - "00004213ocobzus5kn6s" - "RDNVCK" - "121780" - "\"5527beb0400b0\"" - "" - "" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "" - "HTTP/1.1 200 OK" - ""; // no proper end here + "" + "" + "/oc/remote.php/webdav/sharefolder/" + "" + "" + "00004213ocobzus5kn6s" + "RDNVCK" + "121780" + "\"5527beb0400b0\"" + "" + "" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "" + "HTTP/1.1 200 OK" + ""; // no proper end here LsColXMLParser parser; - connect(&parser, &LsColXMLParser::directoryListingSubfolders, - this, &TestXmlParse::slotDirectoryListingSubFolders); - connect(&parser, &LsColXMLParser::directoryListingIterated, - this, &TestXmlParse::slotDirectoryListingIterated); - connect(&parser, &LsColXMLParser::finishedWithoutError, - this, &TestXmlParse::slotFinishedSuccessfully); + connect(&parser, &LsColXMLParser::directoryListingSubfolders, this, &TestXmlParse::slotDirectoryListingSubFolders); + connect(&parser, &LsColXMLParser::directoryListingIterated, this, &TestXmlParse::slotDirectoryListingIterated); + connect(&parser, &LsColXMLParser::finishedWithoutError, this, &TestXmlParse::slotFinishedSuccessfully); - QHash sizes; - QVERIFY(!parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); + QHash sizes; + QVERIFY(!parser.parse(testXml, &sizes, QStringLiteral("/oc/remote.php/webdav/sharefolder"))); QVERIFY(!_success); } - void testParserBogfusHref1() { + void testParserBogfusHref1() + { const QByteArray testXml = "" - "" - "" - "http://127.0.0.1:81/oc/remote.php/webdav/sharefolder/" - "" - "" - "00004213ocobzus5kn6s" - "RDNVCK" - "121780" - "\"5527beb0400b0\"" - "" - "" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - "" - "http://127.0.0.1:81/oc/remote.php/webdav/sharefolder/quitte.pdf" - "" - "" - "00004215ocobzus5kn6s" - "RDNVW" - "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "121780" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - ""; + "" + "" + "http://127.0.0.1:81/oc/remote.php/webdav/sharefolder/" + "" + "" + "00004213ocobzus5kn6s" + "RDNVCK" + "121780" + "\"5527beb0400b0\"" + "" + "" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + "" + "http://127.0.0.1:81/oc/remote.php/webdav/sharefolder/quitte.pdf" + "" + "" + "00004215ocobzus5kn6s" + "RDNVW" + "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "121780" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + ""; LsColXMLParser parser; - connect(&parser, &LsColXMLParser::directoryListingSubfolders, - this, &TestXmlParse::slotDirectoryListingSubFolders); - connect(&parser, &LsColXMLParser::directoryListingIterated, - this, &TestXmlParse::slotDirectoryListingIterated); - connect(&parser, &LsColXMLParser::finishedWithoutError, - this, &TestXmlParse::slotFinishedSuccessfully); + connect(&parser, &LsColXMLParser::directoryListingSubfolders, this, &TestXmlParse::slotDirectoryListingSubFolders); + connect(&parser, &LsColXMLParser::directoryListingIterated, this, &TestXmlParse::slotDirectoryListingIterated); + connect(&parser, &LsColXMLParser::finishedWithoutError, this, &TestXmlParse::slotFinishedSuccessfully); - QHash sizes; - QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); + QHash sizes; + QVERIFY(false == parser.parse(testXml, &sizes, QStringLiteral("/oc/remote.php/webdav/sharefolder"))); QVERIFY(!_success); } - void testParserBogfusHref2() { + void testParserBogfusHref2() + { const QByteArray testXml = "" - "" - "" - "/sharefolder" - "" - "" - "00004213ocobzus5kn6s" - "RDNVCK" - "121780" - "\"5527beb0400b0\"" - "" - "" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - "" - "/sharefolder/quitte.pdf" - "" - "" - "00004215ocobzus5kn6s" - "RDNVW" - "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "121780" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - ""; + "" + "" + "/sharefolder" + "" + "" + "00004213ocobzus5kn6s" + "RDNVCK" + "121780" + "\"5527beb0400b0\"" + "" + "" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + "" + "/sharefolder/quitte.pdf" + "" + "" + "00004215ocobzus5kn6s" + "RDNVW" + "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "121780" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + ""; LsColXMLParser parser; - connect(&parser, &LsColXMLParser::directoryListingSubfolders, - this, &TestXmlParse::slotDirectoryListingSubFolders); - connect(&parser, &LsColXMLParser::directoryListingIterated, - this, &TestXmlParse::slotDirectoryListingIterated); - connect(&parser, &LsColXMLParser::finishedWithoutError, - this, &TestXmlParse::slotFinishedSuccessfully); + connect(&parser, &LsColXMLParser::directoryListingSubfolders, this, &TestXmlParse::slotDirectoryListingSubFolders); + connect(&parser, &LsColXMLParser::directoryListingIterated, this, &TestXmlParse::slotDirectoryListingIterated); + connect(&parser, &LsColXMLParser::finishedWithoutError, this, &TestXmlParse::slotFinishedSuccessfully); - QHash sizes; - QVERIFY(false == parser.parse( testXml, &sizes, "/oc/remote.php/webdav/sharefolder" )); + QHash sizes; + QVERIFY(false == parser.parse(testXml, &sizes, QStringLiteral("/oc/remote.php/webdav/sharefolder"))); QVERIFY(!_success); } - void testHrefUrlEncoding() { + void testHrefUrlEncoding() + { const QByteArray testXml = "" - "" - "" - "/%C3%A4" // a-umlaut utf8 - "" - "" - "00004213ocobzus5kn6s" - "RDNVCK" - "121780" - "\"5527beb0400b0\"" - "" - "" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - "" - "/%C3%A4/%C3%A4.pdf" - "" - "" - "00004215ocobzus5kn6s" - "RDNVW" - "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" - "" - "Fri, 06 Feb 2015 13:49:55 GMT" - "121780" - "" - "HTTP/1.1 200 OK" - "" - "" - "" - "" - "" - "" - "HTTP/1.1 404 Not Found" - "" - "" - ""; + "" + "" + "/%C3%A4" // a-umlaut utf8 + "" + "" + "00004213ocobzus5kn6s" + "RDNVCK" + "121780" + "\"5527beb0400b0\"" + "" + "" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + "" + "/%C3%A4/%C3%A4.pdf" + "" + "" + "00004215ocobzus5kn6s" + "RDNVW" + "\"2fa2f0d9ed49ea0c3e409d49e652dea0\"" + "" + "Fri, 06 Feb 2015 13:49:55 GMT" + "121780" + "" + "HTTP/1.1 200 OK" + "" + "" + "" + "" + "" + "" + "HTTP/1.1 404 Not Found" + "" + "" + ""; LsColXMLParser parser; - connect(&parser, &LsColXMLParser::directoryListingSubfolders, - this, &TestXmlParse::slotDirectoryListingSubFolders); - connect(&parser, &LsColXMLParser::directoryListingIterated, - this, &TestXmlParse::slotDirectoryListingIterated); - connect(&parser, &LsColXMLParser::finishedWithoutError, - this, &TestXmlParse::slotFinishedSuccessfully); + connect(&parser, &LsColXMLParser::directoryListingSubfolders, this, &TestXmlParse::slotDirectoryListingSubFolders); + connect(&parser, &LsColXMLParser::directoryListingIterated, this, &TestXmlParse::slotDirectoryListingIterated); + connect(&parser, &LsColXMLParser::finishedWithoutError, this, &TestXmlParse::slotFinishedSuccessfully); - QHash sizes; - QVERIFY(parser.parse( testXml, &sizes, QString::fromUtf8("/ä") )); + QHash sizes; + QVERIFY(parser.parse(testXml, &sizes, QString::fromUtf8("/ä"))); QVERIFY(_success); QVERIFY(_items.contains(QString::fromUtf8("/ä/ä.pdf"))); QVERIFY(_items.contains(QString::fromUtf8("/ä"))); - QVERIFY(_items.size() == 2 ); + QVERIFY(_items.size() == 2); QVERIFY(_subdirs.contains(QString::fromUtf8("/ä"))); QVERIFY(_subdirs.size() == 1); } - }; - QTEST_GUILESS_MAIN(TestXmlParse) +QTEST_GUILESS_MAIN(TestXmlParse) #include "testxmlparse.moc" diff --git a/theme/colored/ui-light-plain-color-state-downloaded.svg b/theme/colored/ui-light-plain-color-state-downloaded.svg deleted file mode 100644 index b38aef98332..00000000000 --- a/theme/colored/ui-light-plain-color-state-downloaded.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/theme/colored/ui-light-plain-color-state-warning.svg b/theme/colored/ui-light-plain-color-state-warning.svg deleted file mode 100644 index d8c10126f83..00000000000 --- a/theme/colored/ui-light-plain-color-state-warning.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/theme/dark/ui-dark-plain-color-state-downloaded.svg b/theme/dark/ui-dark-plain-color-state-downloaded.svg deleted file mode 100644 index 4497b2d9fbc..00000000000 --- a/theme/dark/ui-dark-plain-color-state-downloaded.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/theme/dark/ui-dark-plain-color-state-warning.svg b/theme/dark/ui-dark-plain-color-state-warning.svg deleted file mode 100644 index 76b01889402..00000000000 --- a/theme/dark/ui-dark-plain-color-state-warning.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/theme/white/ui-dark-plain-monochrom-state-downloaded.svg b/theme/white/ui-dark-plain-monochrom-state-downloaded.svg deleted file mode 100644 index 9eb0046775e..00000000000 --- a/theme/white/ui-dark-plain-monochrom-state-downloaded.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/theme/white/ui-dark-plain-monochrom-state-warning.svg b/theme/white/ui-dark-plain-monochrom-state-warning.svg deleted file mode 100644 index 1a768eae2b3..00000000000 --- a/theme/white/ui-dark-plain-monochrom-state-warning.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/translations/.tx/config b/translations/.tx/config deleted file mode 100644 index 042e199681a..00000000000 --- a/translations/.tx/config +++ /dev/null @@ -1,16 +0,0 @@ -[main] -host = https://www.transifex.com - -[owncloud.client] -source_file = client_en.ts -source_lang = en -file_filter = client_.ts -type = TS - -# de_DE holds the formal translation which we want as default -lang_map = de_DE: de, pt_PT: pt, cs_CZ: cs, et_EE: et, fi_FI: fi, ja_JP: ja, hu_HU: hu, sk_SK: client_sk, th_TH: th - -#[owncloud.client-desktop] -#source_file = ../mirall.desktop.in -#source_lang = en -#type = DESKTOP diff --git a/translations/Makefile b/translations/Makefile deleted file mode 100644 index 47fd930bd83..00000000000 --- a/translations/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# -# Translation -#-------------------------------------- - -.PHONY: l10n-push -l10n-push: - tx -d push -s --skip --no-interactive - -.PHONY: l10n-pull -l10n-pull: - tx -d pull --force --skip --all --minimum-perc=75 - -.PHONY: l10n-clean -l10n-clean: - rm -rf l10n/ - git checkout .tx/config - -.PHONY: l10n-read -l10n-read: - cd .. && lupdate src -no-obsolete -ts translations/client_en.ts - -.PHONY: l10n-write -l10n-write: ; diff --git a/translations/client_TW.ts b/translations/client_TW.ts deleted file mode 100644 index abd29881ab7..00000000000 --- a/translations/client_TW.ts +++ /dev/null @@ -1,1885 +0,0 @@ - - - FolderWizardSourcePage - - - Form - 格式 - - - - Sync Directory - 同步目錄 - - - - Pick a local folder on your computer to sync: - 選擇一個進行同步的本地資料夾 - - - - &Choose... - 選擇(&C)... - - - - &Directory alias name: - 目錄別名(&D): - - - - FolderWizardTargetPage - - - Form - 表格 - - - - Synchronization Target - 同步目標 - - - - Select a destination folder - 選擇目標資料夾 - - - - Add Folder - 新增資料夾 - - - - Refresh - 重新整理 - - - - TextLabel - 文字標籤 - - - - Mirall::AccountSettings - - - Form - 格式 - - - - Account Maintenance - - - - - Edit Ignored Files - - - - - Remove Account - - - - - Modify Account - - - - - Sync Status - - - - - Connected with <server> as <user> - - - - - Add Sync... - 增加同步... - - - - - Pause - 暫停 - - - - Remove - 移除 - - - - Reset - - - - - Info... - 資訊... - - - - Storage Usage - - - - - Retrieving usage information... - - - - - <b>Note:</b> Some folders, including network mounted or shared folders, might have different limits. - - - - - Resume - 繼續 - - - - Confirm Folder Remove - 確認移除資料夾 - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will not remove the files from your client.</p> - - - - - Confirm Folder Reset - - - - - <p>Do you really want to reset folder <i>%1</i> and rebuild your client database?</p><p><b>Note:</b> While no files will be removed, this can cause significant data traffic and take several minutes to hours, depending on the size of the folder.</p> - - - - - Checking %1 connection... - 檢查%1連線... - - - - No %1 connection configured. - 無%1連線設定 - - - - Sync Running - 同步中 - - - - The syncing operation is running.<br/>Do you want to terminate it? - 正在同步中<br/>你真的想要中斷? - - - - Connected to <a href="%1">%1</a> as <i>%2</i>. - 連線到 <a href="%1">%1</a> 當 <i>%2</i>. - - - - Version: %1 (%2) - 版本: : %1 (%2) - - - - unknown problem. - 未知的問題 - - - - <p>Failed to connect to %1: <tt>%2</tt></p> - <p>連線到 %1: <tt>%2</tt>失敗</p> - - - - Start - - - - - Currently - - - - - Completely - - - - - %1 %2 %3 (%4 of %5) - - - - - Completely finished. - - - - - %1 of %2, file %3 of %4 - - - - - You are using %1 of your available %2 storage. - - - - - Mirall::Application - - - Sync started for %n configured sync folder(s). - - - - - Open %1 in browser - - - - - Calculating quota... - - - - - Unknown status - - - - - Settings... - - - - - more... - - - - - Help - 說明 - - - - Quit - 離開 - - - - None. - - - - - Recent Changes... - - - - - Open local folder '%1' - - - - - Open folder '%1' - - - - - Quota n/a - - - - - %1% of %2 used - - - - - No items synced recently - - - - - Upload - 上傳 - - - - Download - 下載 - - - - %1 (%2, %3) - - - - - Syncing %1 of %2 (%3 of %4) - - - - - %1 Sync Started - %1同步已經開始 - - - - Managed Folders: - 管理的資料夾: - - - - In Sync - - - - - Folder %1: %2 - - - - - No sync folders configured. - 無已配置的同步資料夾 - - - - Mirall::CSyncThread - - - CSync failed to create a lock file. - CSync 無法建立一個文件鎖 - - - - Success. - 成功。 - - - - CSync Logging setup failed. - CSync Logging 設定失敗。 - - - - CSync failed to load the state db. - CSync 無法讀取狀態資料庫 - - - - The system time on this client is different than the system time on the server. Please use a time synchronization service (NTP) on the server and client machines so that the times remain the same. - 本客戶端的系統時間和伺服器系統時間不一致,請在伺服器與客戶端上使用時間同步服務(NTP)讓時間保持一致 - - - - CSync could not detect the filesystem type. - CSync 無法偵測檔案系統的類型 - - - - CSync got an error while processing internal trees. - CSync 處理內部資料樹時發生錯誤 - - - - The local filesystem can not be written. Please check permissions. - 本地檔案系統無法寫入, 請確認權限。 - - - - A remote file can not be written. Please check the remote access. - 遠端檔案無法寫入, 請確認遠端存取權限。 - - - - CSync failed to reserve memory. - CSync 無法取得記憶體空間。 - - - - CSync fatal parameter error. - CSync 參數錯誤。 - - - - CSync processing step update failed. - CSync 處理步驟 "update" 失敗。 - - - - CSync processing step reconcile failed. - CSync 處理步驟 "reconcile" 失敗。 - - - - CSync processing step propagate failed. - CSync 處理步驟 "propagate" 失敗。 - - - - <p>The target directory does not exist.</p><p>Please check the sync setup.</p> - <p>目標資料夾不存在</p><p>請檢查同步設定</p> - - - - CSync failed to connect through a proxy. - CSync 透過代理伺服器連線失敗。 - - - - CSync failed to lookup proxy or server. - CSync 查詢代理伺服器或伺服器失敗。 - - - - CSync failed to authenticate at the %1 server. - CSync 於伺服器 %1 認證失敗。 - - - - CSync failed to authenticate at the proxy. - CSync 於代理伺服器認證失敗。 - - - - CSync failed to connect to the network. - CSync 無法連接到網路。 - - - - A network connection timeout happend. - 網路連線逾時。 - - - - A HTTP transmission error happened. - HTTP 傳輸錯誤。 - - - - CSync failed due to not handled permission deniend. - CSync 失敗,由於未處理的存取被拒。 - - - - CSync failed to find a specific file. - CSync 沒辦法找到某一個檔案。 - - - - CSync tried to create a directory that already exists. - CSync 試圖建立一個已經存在的目錄。 - - - - CSync: No space on %1 server available. - CSync:伺服器 %1 沒有可用空間。 - - - - CSync unspecified error. - CSync 未知的錯誤。 - - - - An internal error number %1 happend. - 發生內部錯誤, 錯誤編號 %1 - - - - <br/>Backend Message: - <br/>後端訊息: - - - - <p>The %1 plugin for csync could not be loaded.<br/>Please verify the installation!</p> - <p>用於csync的套件%1</p> - - - - Mirall::ConnectionValidator - - - <p>The configured server for this client is too old.</p><p>Please update to the latest server and restart the client.</p> - <p>此客戶端配置的伺服器太舊了。</p><p>請更新伺服器到最新的版本,並重新啟動客戶端。</p> - - - - Error: Could not retrieve the password! - 錯誤. 無法取得密碼 - - - - Mirall::CredentialStore - - - No password entry found in keychain. Please reconfigure. - 在鑰匙圈當中找不到密碼資訊,請重新設定。 - - - - Mirall::FileItemDialog - - - Files - 檔案 - - - - File Count - 檔案數目 - - - - Copy - 複製 - - - - Sync Protocol - 同步協定 - - - - Undefined Folder State - 未定義的資料夾狀態 - - - - The folder waits to start syncing. - 資料夾等待同步開始。 - - - - Determining which files to sync. - 正在決定要同步哪些檔案。 - - - - Server is currently not available. - 伺服器目前無法使用。 - - - - Sync is running. - 同步中。 - - - - Last Sync was successful. - 上次的同步成功。 - - - - Syncing Error. - 同步錯誤。 - - - - Setup Error. - 設定錯誤。 - - - - Undefined Error State. - 未定義的錯誤狀態。 - - - - Copied to clipboard - 複製至剪貼簿中 - - - - The sync protocol has been copied to the clipboard. - 同步協定已複製至剪貼簿 - - - - Uploaded - 已上傳 - - - - Downloaded - 已下載 - - - - Synced Files - 已同步的檔案 - - - - %1 (last finished %n sec. ago) - - - - - %1 (last finished %n min. ago) - - - - - New Files - 新檔案 - - - - Deleted Files - 已刪除的檔案 - - - - Renamed Files - 已更名的檔案 - - - - Ignored Files - 忽略的檔案 - - - - Errors - 錯誤 - - - - Conflicts - 衝突 - - - - Form - 格式 - - - - Detailed Sync Protocol - - - - - - TextLabel - 文字標籤 - - - - Mirall::Folder - - - Unable to create csync-context - - - - - Local folder %1 does not exist. - 本地資料夾 %1 不存在 - - - - %1 should be a directory but is not. - 資料夾不存在, %1 必須是資料夾 - - - - %1 is not readable. - %1 是不可讀的 - - - - The CSync thread terminated. - CSync 執行緒終止 - - - - This sync would remove all the files in the local sync folder '%1'. -If you or your administrator have reset your account on the server, choose "Keep files". If you want your data to be removed, choose "Remove all files". - - - - - This sync would remove all the files in the sync folder '%1'. -This might be because the folder was silently reconfigured, or that all the file were manually removed. -Are you sure you want to perform this operation? - - - - - Remove All Files? - - - - - Remove all files - - - - - Keep files - - - - - Mirall::FolderMan - - - Could not reset folder state - - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - - - - - Undefined State. - 未知狀態 - - - - Waits to start syncing. - 等待啟動同步 - - - - Preparing for sync. - - - - - Sync is running. - 同步執行中 - - - - Server is currently not available. - - - - - Last Sync was successful. - 最後一次同步成功 - - - - Setup Error. - 安裝失敗 - - - - Mirall::FolderStatusDelegate - - - - File - - - - - Syncing all files in your account with - - - - - Remote path: %1 - 遠端路徑: %1 - - - - Mirall::FolderWizard - - - %1 Folder Wizard - %1 資料夾精靈 - - - - Mirall::FolderWizardSourcePage - - - No local folder selected! - - - - - You have no permission to write to the selected folder! - - - - - The local path %1 is already an upload folder.<br/>Please pick another one! - 本地路徑 %1 已是上傳資料夾.<br/>請選擇其他資料夾! - - - - An already configured folder is contained in the current entry. - 目前紀錄中已經有一個已配置的資料夾 - - - - An already configured folder contains the currently entered directory. - 已配置的資料夾包含了目前輸入的資料夾 - - - - The alias can not be empty. Please provide a descriptive alias word. - 別名不能空白, 請提供一個描述性的別名 - - - - <br/>The alias <i>%1</i> is already in use. Please pick another alias. - <br/>別名 <i>%1</i> 重複. 請取別的別名. - - - - Select the source folder - 選擇來源資料夾 - - - - Mirall::FolderWizardTargetPage - - - Folder was successfully created on %1. - 資料夾成功建立在%1 - - - - Failed to create the folder on %1.<br/>Please check manually. - 在%1建立資料夾失敗<br/>請手動檢查 - - - - If you sync the root folder, you can <b>not</b> configure another sync directory. - 如果您選擇同步根目錄(最上層資料夾),就<b>不能</b>再設定另一個同步資料夾。 - - - - Mirall::GeneralSettings - - - Form - 格式 - - - - General - 一般 - - - - Launch on System Startup - - - - - Show Desktop Notifications - - - - - Use Monochrome Icons - - - - - - About - 關於 - - - - Mirall::IgnoreListEditor - - - Ignored Files Editor - - - - - Add - 增加 - - - - Remove - 移除 - - - - Files matching the following patterns will not be synchronized: - - - - - Could not open file - - - - - Cannot write changes to '%1'. - - - - - - Add Ignore Pattern - - - - - - Add a new ignore pattern: - - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - - - - - Mirall::LogBrowser - - - Log Output - 記錄輸出 - - - - &Search: - &搜尋: - - - - &Find - &尋找: - - - - Clear - 清除 - - - - Clear the log display. - 清除所顯示的記錄 - - - - S&ave - 儲存 - - - - Save the log file to a file on disk for debugging. - 將記錄檔儲存到硬碟用於除錯 - - - - - Error - 錯誤 - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>檔案 '%1'<br/>無法開啟與寫入<br/><br/>記錄 <b>無法</b> 被儲存</nobr> - - - - Save log file - 儲存記錄檔 - - - - Could not write to log file - 無法寫入記錄檔 - - - - Mirall::NetworkSettings - - - Form - 格式 - - - - Proxy Settings - 代理伺服器設定 - - - - No Proxy - 不使用代理伺服器 - - - - Use system proxy - 使用系統預設代理伺服器 - - - - Specify proxy manually as - - - - - Host - 主機 - - - - : - - - - - Proxy server requires authentication - - - - - Download Bandwidth - - - - - - Limit to - - - - - - KBytes/s - - - - - - No Limit - - - - - Upload Bandwidth - - - - - Limit automatically - - - - - Hostname of proxy server - 代理伺服器主機名稱 - - - - Username for proxy server - 代理伺服器使用者名稱 - - - - Password for proxy server - 代理伺服器密碼 - - - - HTTP(S) proxy - - - - - SOCKS5 proxy - - - - - Mirall::OwncloudSetupPage - - - <font color="%1" size="5">Connect to %2</font> - <font color="%1" size="5">連線至 %2</font> - - - - <font color="%1">Enter user credentials</font> - <font color="%1">輸入帳號密碼</font> - - - - This url is secure. You can use it. - 這個 URL 是安全的,您可以使用它。 - - - - This url is NOT secure. You should not use it. - 這個 URL 不安全,您不應該使用它。 - - - - Change the Password for your configured account. - 更改帳戶密碼。 - - - - Your entire account will be synced to the local folder '%1'. - 您整個帳號的資料會與本地資料夾 %1 同步。 - - - - %1 folder '%2' is synced to local folder '%3' - %1 資料夾 '%2' 與本地資料夾 '%3' 同步 - - - - <p><small><strong>Warning:</strong> You currently have multiple folders configured. If you continue with the current settings, the folder configurations will be discarded and a single root folder sync will be created!</small></p> - - - - - <p><small><strong>Warning:</strong> The local directory is not empty. Pick a resolution in the advanced settings!</small></p> - - - - - Local Sync Folder - 本地同步資料夾 - - - - <font color="%1">Change your user credentials</font> - - - - - Mirall::OwncloudSetupWizard - - - %1 Connection Wizard - %1連線精靈 - - - - Trying to connect to %1 at %2... - 嘗試連線到%1從%2 - - - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">成功連線到 %1: %2 版本 %3 (%4)</font><br/><br/> - - - - Folder rename failed - 重新命名資料夾失敗 - - - - Can't remove and back up the folder because the folder or a file in it is open in another program.Please close the folder or file and hit retry or cancel the setup. - - - - - Failed to connect to %1:<br/>%2 - 無法連線到 %1 :<br/>%2 - - - - Could not create local folder %1 - 無法建立本地資料夾 %1 - - - - The given credentials do not authenticate. - 提供的登入資料無法通過認證。 - - - - Username or password is wrong! - 帳號名稱或密碼不正確! - - - - The remote folder could not be accessed! - 無法存取遠端資料夾! - - - - Error: %1 - 錯誤: %1 - - - - creating folder on ownCloud: %1 - 在 ownCloud 建立資料夾: %1 - - - - Remote folder %1 created successfully. - 遠端資料夾%1建立成功! - - - - The remote folder %1 already exists. Connecting it for syncing. - 遠端資料夾%1已存在,連線同步中 - - - - - The folder creation resulted in HTTP error code %1 - 在HTTP建立資料夾失敗, error code %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - 由於帳號或密碼錯誤,遠端資料夾建立失敗<br/>請檢查您的帳號密碼。</p> - - - - A sync connection from %1 to remote directory %2 was set up. - 從%1到遠端資料夾%2的連線已建立 - - - - Successfully connected to %1! - 成功連接到 %1 ! - - - - Connection to %1 could not be established. Please check again. - 無法建立連線%1, 請重新檢查 - - - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - 本地同步資料夾%1已存在, 將其設置為同步<br/><br/> - - - - Creating local sync folder %1... - 建立本地同步資料夾 %1 - - - - ok - ok - - - - failed. - 失敗 - - - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>本地同步資料夾 %1 建立成功!</b></font> - - - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">遠端資料夾建立失敗,也許是因為所提供的帳號密碼錯誤</font><br/>請重新檢查您的帳號密碼</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - 建立遠端資料夾%1發生錯誤<tt>%2</tt>失敗 - - - - Mirall::OwncloudWizard - - - Connect... - 連線中... - - - - Mirall::OwncloudWizardResultPage - - - <font color="%1" size="5">Everything set up!</font> - <font color="%1" size="5">一切設定完成!</font> - - - - - Open %1 - 打開 %1 - - - - Open Local Folder - 打開本地資料夾 - - - - Your entire account is synced to the local folder <i>%1</i> - 您整個帳號的資料會與本地資料夾 <i>%1</i> 同步 - - - - ownCloud folder <i>%1</i> is synced to local folder <i>%2</i> - ownCloud 資料夾 <i>%1</i> 已經與本地資料夾 <i>%2</i> 同步 - - - - Mirall::ServerActionNotifier - - - New file available - 新的可用檔案 - - - - '%1' has been synced to this machine. - %1 已經被同步到這台機器。 - - - - New files available - 新的可用檔案 - - - - '%1' and %n other file(s) have been synced to this machine. - %1 和 %n 個其他檔案已經被同步到這台機器。 - - - - File removed - 文件已被移除 - - - - '%1' has been removed. - '%1'已被移除。 - - - - '%1' and %n other file(s) have been removed. - '%1'和其他%n個文件已被移除。 - - - - Files removed - 檔案已移除 - - - - '%1' has been updated. - '%1'已被更新。 - - - - File updated - 檔案已上傳 - - - - '%1' and %n other file(s) have been updated. - '%1'和其他%n個文件已被更新。 - - - - Files updated - 檔案已上傳 - - - - Mirall::SettingsDialog - - - Settings - 設定 - - - - %1 Settings - - - - - General - 一般 - - - - Network - - - - - Account - 帳號 - - - - Mirall::SslErrorDialog - - - SSL Connection - SSL連線 - - - - Warnings about current SSL Connection: - 目前的SSL連線的警告 - - - - with Certificate %1 - 使用認證%1 - - - - - - &lt;not specified&gt; - &lt;未指定&gt; - - - - - Organization: %1 - 組織:%1 - - - - - Unit: %1 - 單位:%1 - - - - - Country: %1 - 國家:%1 - - - - Fingerprint (MD5): <tt>%1</tt> - 指紋 (MD5): &lt;tt&gt;%1&lt;/tt&gt; - - - - Fingerprint (SHA1): <tt>%1</tt> - 指紋 (SHA1): &lt;tt&gt;%1&lt;/tt&gt; - - - - Effective Date: %1 - 有效日期:%1 - - - - Expiry Date: %1 - 失效日期:%1 - - - - Issuer: %1 - 發行者:%1 - - - - Mirall::UpdateDetector - - - New Version Available - 有新版本 - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.<p> - <p>%1 客戶端有新版本了。</p><p><b>%2</b> 可供下載,目前安裝的版本是 %3 。<p> - - - - Skip update - 略過更新 - - - - Skip this time - 跳過這次更新 - - - - Get update - 取得更新 - - - - Mirall::ownCloudInfo - - - Proxy Refused Connection - 代理伺服器拒絕連線 - - - - The configured proxy has refused the connection. Please check the proxy settings. - 所設定的代理伺服器拒絕連線,請檢查代理伺服器設定。 - - - - Proxy Closed Connection - 代理伺服器關閉了連線 - - - - The configured proxy has closed the connection. Please check the proxy settings. - 所設定的代理伺服器關閉了連線,請檢查代理伺服器設定。 - - - - Proxy Not Found - 找不到代理伺服器 - - - - The configured proxy could not be found. Please check the proxy settings. - 找不到所設定的代理伺服器,請檢查代理伺服器設定。 - - - - Proxy Authentication Error - 代理伺服器認證錯誤 - - - - The configured proxy requires login but the proxy credentials are invalid. Please check the proxy settings. - 所設定的代理伺服器帳密無法登入,請檢查代理伺服器設定。 - - - - Proxy Connection Timed Out - 代理伺服器連線逾時 - - - - The connection to the configured proxy has timed out. - 代理伺服器的連線已經逾時。 - - - - OwncloudSetupPage - - - - Form - 格式 - - - - Server &address: - 伺服器&位置: - - - - &Do not store password on local machine - &不儲存密碼在本地機器上 - - - - https:// - https:// - - - - - Enter the url of the ownCloud you want to connect to (without http or https). - 輸入欲連線的ownCloud的url(不需要輸入http或http) - - - - Use &secure connection - 使用&安全連線 - - - - CheckBox - 核取方塊 - - - - &Username: - &使用者名稱 - - - - - Enter the ownCloud username. - 輸入ownCloud使用者名稱 - - - - &Password: - &密碼: - - - - - Enter the ownCloud password. - 輸入ownCloud使用者密碼 - - - - Do not allow the local storage of the password. - 不儲存密碼在本地機器上 - - - - - - - - TextLabel - 文字標籤 - - - - &Local Folder - 本地資料夾 - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - 保留本地資料 - - - - <small>Syncs your existing data to new location.</small> - <small>將現有的資料同步至新地點。</small> - - - - <html><head/><body><p>If this box is checked, existing content in the local directory will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers directory.</p></body></html> - <html><head/><body><p>如果勾選這個選項,本地資料夾當中現存的內容會被刪除,然後從伺服器端重新進行一次全新的同步。</p><p>如果您要將本地資料同步至伺服器上,請勿勾選這個選項。</p></body></html> - - - - &Start a clean sync - 開始一個全新的同步 - - - - <small>Erases the contents of the local folder before syncing using the new settings.</small> - <small>在使用新設定來同步之前,請清空本地資料夾。</small> - - - - Server &Address - 伺服器位址 - - - - https://... - https://... - - - - &Username - 使用者名稱 - - - - &Password - 密碼 - - - - Error Label - 錯誤標籤 - - - - Advanced &Settings - 進階選項 - - - - Status message - 狀態訊息 - - - - OwncloudWizardResultPage - - - Form - 格式 - - - - Your entire account is synced to the local folder - 您整個帳號的資料會與本地資料夾同步 - - - - - PushButton - 按鈕 - - - - TextLabel - 文字標籤 - - - - QObject - - - - Status undefined - 未定義的狀態 - - - - Waiting to start sync - 等待開始同步 - - - - Sync is running - 同步中 - - - - Sync Success - 同步完成 - - - - Sync Error - Click info button for details. - 同步錯誤- 請點擊 資訊按鈕 以獲取詳細資訊 - - - - Setup Error - 安裝錯誤 - - - - - - downloading - - - - - - - uploading - - - - - Context - - - - - inactive - - - - - starting - - - - - finished - - - - - SslErrorDialog - - - Form - 格式 - - - - Trust this certificate anyway - 信任此憑證 - - - - SSL Connection - SSL連線 - - - - main.cpp - - - System Tray not available - - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - - - - - ownCloudTheme - - - If you don't have an ownCloud server yet, see <a href="https://owncloud.com">owncloud.com</a> for more info. - Top text in setup wizard. Keep short! - - - - - ownCloudTheme::about() - - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using OCsync %5 and Qt %6.</small><p> - - - - - <p>Version %2. For more information visit <a href="%3">%4</a></p><p><small>written by Klaas Freitag, Daniel Molkentin, ownCloud Inc., based on Mirall by Duncan Mac-Vicar P.</small></p>%7 - - - - \ No newline at end of file diff --git a/translations/client_ar.ts b/translations/client_ar.ts deleted file mode 100644 index d7082c32eec..00000000000 --- a/translations/client_ar.ts +++ /dev/null @@ -1,4912 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - إلغاء. - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - إغلاق - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - لا يمكن إنشاء أدلة داخل سلة المحذوفات - - - - - Could not move '%1' to '%2' - لا يمكن نقل '%1' إلى '%2' - - - - Moving to the trash is not implemented on this platform - لا يمكن إجراء النقل إلى سلة المحذوفات على هذه المنصة - - - - Error removing '%1': %2 - خطأ في نقل '%1': %2 - - - - Could not remove folder '%1' - لا يمكن نقل المجلد '%1' - - - - FolderWizardSourcePage - - - Form - نموذج - - - - Pick a local folder on your computer to sync - اختر مجلدًا محليًا على جهاز الكمبيوتر لبدء المزامنة - - - - &Choose... - واختر... - - - - FolderWizardTargetPage - - - Form - نموذجًا - - - - Select a remote destination folder - اختر مجلد وجهة بعيدًا - - - - Create Folder - أنشئ مجلدًا - - - - Refresh - تحديث - - - - Folders - مجلدات - - - - TextLabel - تسمية نصية - - - - NotificationWidget - - - Form - نموذج - - - - Lorem ipsum dolor sit amet - ترتيب أبجد هوز - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - ترتيب أبجد هوز حطي كلمن سعفص قرشت ثخذ ضظغ - - - - TextLabel - تسمية نصية - - - - OCC::AboutDialog - - - About - عن - - - - TextLabel - تسمية نصية - - - - Versions - إصدارات - - - - About %1 - عن %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - انتهت مهلة الاتصال - - - - Unknown error: network reply was deleted - خطأ غير معروف: تم حذف رد الشبكة - - - - Server replied "%1 %2" to "%3 %4" - رد الخادم "%1 %2" على "%3 %4" - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - نموذج - - - - Re-open Browser - إعادة فتح المتصفح - - - - ... - ... - - - - Storage space: ... - مساحة التخزين: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - <b>ستحذف</b> المجلدات غير المحددة من نظام الملف المحلي ولن تتم مزامنتها مع هذا الكمبيوتر بعد الآن - - - - Synchronize all - مزامنة الكل - - - - Synchronize none - عدم المزامنة - - - - Apply manual changes - نفِّذ التغييرات اليدوية - - - - Apply - تنفيذ - - - - - - - Cancel - إلغاء - - - - Connected with <server> as <user> - متصل بـ <server> باعتباره <user> - - - - No account configured. - لا يوجد حساب مضبوط. - - - - Remove - إزالة - - - - Account - حساب - - - - Choose what to sync - اختر المحتوى المطلوب مزامنته - - - - Force sync now - إجراء المزامنة إجباريًا الآن - - - - Restart sync - أعد تشغيل المزامنة - - - - Remove folder sync connection - أزل اتصال مزامنة المجلد - - - - Folder creation failed - فشل إنشاء المجلد - - - - <p>Could not create local folder <i>%1</i>. - <p>تعذّر إنشاء مجلد محلي <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - أكِّد إزالة اتصال مزامنة المجلد - - - - Remove Folder Sync Connection - أزل اتصال مزامنة المجلد - - - - Disable virtual file support? - هل ترغب في تعطيل دعم الملف الظاهري؟ - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - سيعطِّل هذا الإجراء دعم الملف الظاهري. ونتيجة لذلك، ستُنزل محتويات المجلدات التي تم تمييزها بعلامة "متوفر عبر الإنترنت فقط". - -الميزة الوحيدة لتعطيل دعم الملف الافتراضي هي أن ميزة المزامنة الاختيارية ستصبح متوفرة مرة أخرى. - -سيؤدي هذا الإجراء إلى إلغاء أي مزامنة قيد التشغيل في الوقت الحالي. - - - - Disable support - تعطيل الدعم - - - - Sync Running - المزامنة قيد التشغيل - - - - The syncing operation is running.<br/>Do you want to terminate it? - عملية المزامنة قيد التشغيل. <br/>هل ترغب في إلغائها؟ - - - - %1 in use - %1 قيد الاستخدام - - - - %1 as <i>%2</i> - %1 باعتباره <i>%2</i> - - - - Connected to %1. - متصل بـ %1. - - - - Server %1 is temporarily unavailable. - الخادم %1 غير متوفر مؤقتًا. - - - - Server %1 is currently in maintenance mode. - الخادم %1 في وضع الصيانة حاليًا. - - - - Signed out from %1. - تم تسجيل الخروج من %1. - - - - Connecting to %1... - جارٍ الاتصال بـ %1... - - - - No connection to %1 at %2. - لا يوجد اتصال بـ %1 في %2. - - - - Log in - تسجيل الدخول - - - - There are folders that were not synchronized because they are too big: - توجد مجلدات لم تتم مزامنتها لأن حجمها كبير جدًا: - - - - There are folders that were not synchronized because they are external storages: - توجد مجلدات لم تتم مزامنتها لأنها وحدات تخزين خارجية: - - - - There are folders that were not synchronized because they are too big or external storages: - توجد مجلدات لم تتم مزامنتها لأن حجمها كبير جدًا أو أنها وحدات تخزين خارجية: - - - - Confirm Account Removal - أكِّد إزالة الحساب - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>هل ترغب حقًا في إزالة الاتصال بالحساب <i>%1</i>؟</p><p><b>ملاحظة:</b> <b>لن</b> يؤدي ذلك إلى حذف أي ملفات.</p> - - - - Remove connection - أزِل الاتصال - - - - Open folder - افتح مجلدًا - - - - - Log out - تسجيل الخروج - - - - Open local folder - افتح مجلدًا محليًا - - - - Open folder in browser - افتح مجلدًا في المتصفح - - - - Resume sync - استأنف المزامنة - - - - Pause sync - أوقف المزامنة مؤقتًا - - - - Availability - التوفر - - - - Disable virtual file support... - عطِّل دعم الملف الظاهري ... - - - - Enable virtual file support%1... - تمكين دعم الملف الظاهري٪ 1 ... - - - - (experimental) - (تجريبي) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>هل ترغب حقًا في إيقاف مزامنة المجلد <i>%1</i>؟</p><p><b>ملاحظة:</b> <b>لن</b> يؤدي ذلك إلى حذف أي ملفات.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) من %2 قيد الاستخدام. قد تحتوي بعض المجلدات بما في ذلك المجلدات الموجودة أو المُشاركة على الشبكة على حدود مختلفة. - - - - %1 of %2 in use - %1 من %2 قيد الاستخدام - - - - Currently there is no storage usage information available. - لا تتوفر معلومات عن استخدام مساحة التخزين في الوقت الحالي. - - - - The server version %1 is unsupported! Proceed at your own risk. - إصدار الخادم %1 غير مدعوم! المتابعة على مسؤوليتك الخاصة. - - - - Obtaining authorization from the browser. - الحصول على تخويل من المتصفح. - - - - Server configuration error: %1 at %2. - خطأ في تكوين الخادم: ٪ 1 عند ٪ 2. - - - - No %1 connection configured. - لم تتم تهيئة اتصال %1. - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - - - - - Signed out - تم تسجيل الخروج - - - - Disconnected - غير متصل - - - - Connected - متصل - - - - Service unavailable - الخدمة غير متوفرة - - - - Maintenance mode - وضع الصيانة - - - - Network error - خطأ في الشبكة - - - - Configuration error - خطا في التهيئة - - - - Asking Credentials - طلب بيانات الاعتماد - - - - Unknown account state - حالة حساب غير معروفة - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 في %3 - - - - Activity - نشاط - - - - Account - حساب - - - - Time - وقت - - - - Local path - - - - - OCC::ActivitySettings - - - - Server Activity - نشاط الخادم - - - - Sync Protocol - بروتوكول المزامنة - - - - Not Synced - لم تتم المزامنة - - - - Not Synced (%1) - %1 is the number of not synced files. - لم تتم المزامنة (%1) - - - - OCC::ActivityWidget - - - Form - نموذج - - - - - - TextLabel - تسمية نصية - - - - Filter - عامل تصفية - - - - Server Activities - أنشطة الخادم - - - - Notifications - الإشعارات - - - - %n notifications(s) for %1. - %n إشعار لـ %1. %n إشعارات لـ %1. %n إشعارين لـ %1.%n إشعارات لـ %1.%n إشعارات لـ %1.%n إشعارات لـ %1. - - - - %n notifications(s) for %1 and %2. - %n إشعار لـ %1 و%2.%n إشعار لـ %1 و%2.%n إشعارين لـ %1 و%2%n إشعارات لـ %1 و%2.%n إشعارات لـ %1 و%2.%n إشعارات لـ %1 و%2. - - - - New notifications for %1, %2 and other accounts. - إشعارات جديدة لـ %1 و%2 والحسابات الأخرى. - - - - Open the activity view for details. - افتح عرض النشاط للحصول على التفاصيل. - - - - Copy to clipboard - نسخ إلى الحافظة - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>لا يحتوي الحساب %1 على أنشطة ممكنة. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - تعني المتابعة <b>حذف هذه الإعدادات</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - تعني المتابعة <b> تجاهل هذه الإعدادات</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - ستتم تهيئة بعض الإعدادات في إصدارات أحدث من هذا العميل واستخدام الميزات غير المتوفرة في هذا الإصدار.<br><br>%1<br><br>تم نسخ ملف التكوين الحالي بالفعل احتياطيًا إلى <i>%2</i>. - - - - Quit - إنهاء - - - - Continue - متابعة - - - - Error accessing the configuration file - خطأ في الوصول إلى ملف التكوين - - - - There was an error while accessing the configuration file at %1. - حدث خطأ في أثناء الوصول إلى ملف التكوين في %1. - - - - Quit %1 - إنهاء %1 - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - المصادقة مطلوبة - - - - Enter username and password for '%1' at %2. - أدخل اسم المستخدم وكلمة مرور '%1' في %2. - - - - &User: - المستخدم: - - - - &Password: - كلمة المرور: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - لا يوجد حساب مضبوط على ownCloud - - - - The configured server for this client is too old - الخادم المكوَّن لهذا العميل قديم جدًا - - - - Please update to the latest server and restart the client. - يُرجى التحديث إلى أحدث خادم وإعادة تشغيل العميل. - - - - Authentication error: Either username or password are wrong. - خطأ في المصادقة: إمّا اسم المستخدم وإمّا كلمة المرور غير صحيح. - - - - timeout - المهلة - - - - The provided credentials are not correct - بيانات الاعتماد المتوفرة غير صحيحة - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - خطأ في الخادم: رد PROPFIND ليس بتنسيق XML! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - حدث خطأ أثناء فتح الدليل %1 - - - - Directory not accessible on client, permission denied - لا يمكن الوصول إلى الدليل عن العميل، تم رفض الإذن - - - - Directory not found: %1 - لم يتم العثور على الدليل: %1 - - - - Filename encoding is not valid - ترميز اسم الملف غير صالح - - - - Error while reading directory %1 - حدث خطأ في أثناء قراءة الدليل %1 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - - - - - OCC::Folder - - - Local folder %1 does not exist. - المجلد المحلي %1 غير موجود. - - - - %1 should be a folder but is not. - %1 يجب أن يكون مجلدًا لكنه ليس كذلك. - - - - %1 is not readable. - %1 غير قابل للقراءة. - - - - %1 has been removed. - %1 names a file. - %1 تمت إزالته. - - - - %1 has been updated. - %1 names a file. - %1 تم تحديثه. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 تمت إعادة تسميته باسم %2. - - - - %1 has been moved to %2. - %1 تم نقله إلى %2. - - - - %1 and %n other file(s) have been removed. - تمت إزالة %1 و%n ملف آخر.تمت إزالة %1 و%n ملف آخر.تمت إزالة %1 و%n ملفين آخرين.تمت إزالة %1 و%n ملفات أخرى.تمت إزالة %1 و%n ملفات أخرى.تمت إزالة %1 و%n ملفات أخرى. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - تمت إضافة %1 و%n ملف آخر.تمت إضافة %1 و%n ملف آخر.تمت إضافة %1 و%n ملفين آخرين.تمت إضافة %1 و%n ملفات أخرى.تمت إضافة %1 و%n ملفات أخرى.تمت إضافة %1 و%n ملفات أخرى. - - - - %1 has been added. - %1 names a file. - %1 تمت إضافته. - - - - %1 and %n other file(s) have been updated. - تم تحديث %1 و%n ملف آخر.تم تحديث %1 و%n ملف آخر.تم تحديث %1 و%n ملفين آخرين.تم تحديث %1 و%n ملفات أخرى.تم تحديث %1 و%n ملفات أخرى.تم تحديث %1 و%n ملفات أخرى. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - تمت إعادة تسمية %1 باسم %2 وتمت إعادة تسمية %n ملف آخر.تمت إعادة تسمية %1 باسم %2 وتمت إعادة تسمية %n ملف آخر.تمت إعادة تسمية %1 باسم %2 وتمت إعادة تسمية %n ملفين آخرين.تمت إعادة تسمية %1 باسم %2 وتمت إعادة تسمية %n ملفات أخرى.تمت إعادة تسمية %1 باسم %2 وتمت إعادة تسمية %n ملفات أخرى.تمت إعادة تسمية %1 باسم %2 وتمت إعادة تسمية %n ملفات أخرى. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - تم نقل %1 إلى %2 وتم نقل %n ملف آخر.تم نقل %1 إلى %2 وتم نقل %n ملف آخر.تم نقل %1 إلى %2 وتم نقل %n ملفين آخرين.تم نقل %1 إلى %2 وتم نقل %n ملفات أخرى.تم نقل %1 إلى %2 وتم نقل %n ملفات أخرى.تم نقل %1 إلى %2 وتم نقل %n ملفات أخرى. - - - - %1 has and %n other file(s) have sync conflicts. - حدث تعارض في مزامنة %1 ومزامنة %n ملف آخر.حدث تعارض في مزامنة %1 ومزامنة %n ملف آخر.حدث تعارض في مزامنة %1 ومزامنة %n ملفين آخرين.حدث تعارض في مزامنة %1 ومزامنة %n ملفات أخرى.حدث تعارض في مزامنة %1 ومزامنة %n ملفات أخرى.حدث تعارض في مزامنة %1 ومزامنة %n ملفات أخرى. - - - - %1 has a sync conflict. Please check the conflict file! - %1 حدث تعارض في مزامنته. يُرجى فحص ملف التعارض! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - تعذّرت مزامنة %1 و%n ملف آخر بسبب الأخطاء. اطلع على السجل للحصول على التفاصيل.تعذّرت مزامنة %1 و%n ملف آخر بسبب الأخطاء. اطلع على السجل للحصول على التفاصيل.تعذّرت مزامنة %1 و%n ملفين آخرين بسبب الأخطاء. اطلع على السجل للحصول على التفاصيل.تعذّرت مزامنة %1 و%n ملفات أخرى بسبب الأخطاء. اطلع على السجل للحصول على التفاصيل.تعذّرت مزامنة %1 و%n ملفات أخرى بسبب الأخطاء. اطلع على السجل للحصول على التفاصيل.تعذّرت مزامنة %1 و%n ملفات أخرى بسبب الأخطاء. اطلع على السجل للحصول على التفاصيل. - - - - %1 could not be synced due to an error. See the log for details. - %1 تعذّرت المزامنة بسبب حدوث خطأ. اطلع على السجل للحصول على التفاصيل. - - - - Sync Activity - نشاط المزامنة - - - - Could not read system exclude file - تعذّرت قراءة ملف استبعاد النظام - - - - A new folder larger than %1 MB has been added: %2. - - تمت إضافة مجلد جديد أكبر من %1 ميجا بايت: %2. - - - - - A folder from an external storage has been added. - - تمت إضافة مجلد من وحدات التخزين الخارجية. - - - - - Please go in the settings to select it if you wish to download it. - يُرجى الانتقال إلى الإعدادات لتحديده إذا كنت ترغب في تنزيله. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - تم إنشاء المجلد %1 لكن تم استبعاده من المزامنة مسبقًا. لن تتم مزامنة البيانات الموجودة بداخله. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - تم إنشاء ملف %1 لكن تم استبعاده من المزامنة مسبقًا. لن تتم مزامنته. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - لا يمكن تتبع التغييرات في المجلدات المتزامنة بشكل موثوق. - -يعني ذلك أن عميل المزامنة قد لا يحمِّل التغييرات المحلية فورًا، وبدلًا من ذلك سيبحث عن التغييرات المحلية ويحملها من حين لآخر فقط (كل ساعتين بشكل افتراضي). - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - ستُحذَّف جميع الملفات الموجودة في مجلد المزامنة '%1' على الخادم. -ستتم مزامنة عمليات الحذف المذكورة مع مجلد المزامنة المحلي، مما يجعل هذه الملفات غير متوفرة ما لم يكن لديك الحق في استعادتها. -إذا قررت الاحتفاظ بالملفات، ستتم إعادة مزامنتها مع الخادم إذا كان لديك الحق في القيام بذلك. -إذا قررت حذف الملفات، ستكون غير متوفرة لك، ما لم تكن أنت المالك. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - ستُحذَف جميع الملفات الموجودة في مجلد المزامنة المحلي '%1'. ستتم مزامنة عمليات الحذف المذكورة مع خادمك، مما يجعل هذه الملفات غير متوفرة ما لم تتم استعادتها. -هل أنت متأكد من رغبتك في مزامنة هذه الإجراءات مع الخادم؟ -إذا حدث ذلك صدفة، وقررت الاحتفاظ بملفاتك، ستتم إعادة مزامنتها من الخادم. - - - - Remove All Files? - هل تريد إزالة كل الملفات؟ - - - - Remove all files - إزالة كل الملفات - - - - Keep files - حفظ الملفات - - - - OCC::FolderMan - - - Could not reset folder state - تعذّرت إعادة تعيين حالة المجلد - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - تم العثور على دفتر يومية مزامنة قديم '%1'، لكن تعذّرت إزالته. يُرجى التأكد من عدم وجود أي تطبيق يستخدمها حاليًا. - - - - (backup) - (النسخ الاحتياطي) - - - - (backup %1) - (النسخ الاحتياطي %1) - - - - Undefined State. - حالة غير معرفة. - - - - Waiting to start syncing. - الانتظار لبدء المزامنة. - - - - Preparing for sync. - التحضير للمزامنة. - - - - Sync is running. - المزامنة قيد التشغيل. - - - - Sync was successful, unresolved conflicts. - المزامنة ناجحة، ولم يتم حل التعارضات. - - - - Last Sync was successful. - المزامنة الأخيرة تمت بنجاح. - - - - Setup Error. - خطأ في الإعداد. - - - - User Abort. - إلغاء المستخدم. - - - - Sync is paused. - توقفت المزامنة مؤقتًا. - - - - %1 (Sync is paused) - %1 (المزامنة متوقفة مؤقتًا) - - - - No valid folder selected! - لم يتم تحديد مجلد صالح! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - المسار المحدد غير موجود! - - - - The selected path is not a folder! - المسار المحدد ليس مجلدًا! - - - - You have no permission to write to the selected folder! - ليس لديك الصلاحية للكتابة في المجلد المحدد! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - تم إجراء مزامنة من الخادم إلى المجلد المحلي هذا بالفعل. يُرجى اختيار مجلد محلي آخر! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - يحتوي المجلد المحلي %1 بالفعل على مجلد مستخدم في اتصال مزامنة المجلد. يُرجى اختيار مجلد آخر! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - المجلد المحلي %1 موجود في مجلد مستخدم في اتصال مزامنة المجلد بالفعل. يُرجى اختيار مجلد آخر! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - أضف اتصال مزامنة المجلد - - - - File - ملف - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - يجب أن تكون متصلًا لإضافة مجلد - - - - Click this button to add a folder to synchronize. - انقر فوق هذا الزر لإضافة مجلد للمزامنة. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - حدث خطأ في أثناء تحميل قائمة المجلدات من الخادم. - - - - Virtual file support is enabled. - تم تمكين دعم الملف الظاهري. - - - - Signed out - تم تسجيل الخروج - - - - Fetching folder list from server... - جارٍ إحضار قائمة المجلد من الخادم... - - - - There are unresolved conflicts. Click for details. - لم يتم حل التعارضات. انقر للحصول على التفاصيل. - - - - Checking for changes in remote '%1' - البحث عن تغييرات في '%1' البعيد - - - - Checking for changes in local '%1' - البحث عن تغييرات في '%1' المحلي - - - - Reconciling changes - توافق التغييرات - - - - , '%1' - Build a list of file names - ، '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - مزامنة %1 - - - - - , - ، - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - تنزيل %1/ثانية - - - - ↓ %1/s - ↓ %1/ثانية - - - - ↑ %1/s - ↑ %1/ثانية - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - تحميل %1/ثانية - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 من %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 متبقٍّ %1 من %2، ملف %3 من %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 من %2، ملف %3 من %4 - - - - file %1 of %2 - ملف %1 من %2 - - - - Waiting... - جارٍ الانتظار... - - - - Waiting for %n other folder(s)... - جارٍ انتظار %n مجلد آخر ...جارٍ انتظار %n مجلد آخر ...جارٍ انتظار %n مجلدين آخر ...جارٍ انتظار %n مجلدات أخرى ...جارٍ انتظار %n مجلدات أخرى ...جارٍ انتظار %n مجلدات أخرى ... - - - - Preparing to sync... - جارٍ تحضير المزامنة... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - لم يستلم المراقب إشعار اختبار. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - عادة ما تحدث هذه المشكلة عند استنفاد عمليات مراقبة inotify. تحقق من الأسئلة المتداولة للحصول على التفاصيل. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - أضف اتصال مزامنة المجلد - - - - Add Sync Connection - أضف اتصال المزامنة - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - انقر لتحديد مجلد محلي لمزامنته. - - - - Enter the path to the local folder. - أدخل المسار في المجلد المحلي. - - - - Select the source folder - حدد مجلد المصدر - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - أنشئ المجلد البعيد - - - - Enter the name of the new folder to be created below '%1': - أدخل اسم المجلد الجديد المراد إنشاؤه أدناه '%1': - - - - Folder was successfully created on %1. - تم إنشاء المجلد بنجاح في %1. - - - - Authentication failed accessing %1 - المصادقة فشلت في الوصول إلى %1 - - - - Failed to create the folder on %1. Please check manually. - تعذّر إنشاء المجلد في %1. يُرجى التحقق يدويًا. - - - - Failed to list a folder. Error: %1 - فشل إدراج مجلد. خطأ: %1 - - - - Choose this to sync the entire account - اختر هذا لمزامنة الحساب بالكامل - - - - This folder is already being synced. - تتم مزامنة هذا المجلد بالفعل. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - تقوم الآن بمزامنة <i>%1</i> بالفعل، وهو المجلد الأصلي لـ <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - تقوم الآن بمزامنة <i>%1</i> بالفعل، وهو المجلد الفرعي لـ <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - استخدم الملفات الظاهرية بدلاً من تنزيل المحتوى فورًا٪ 1 - - - - (experimental) - (تجريبي) - - - - Virtual files are not available for the selected folder - الملفات الظاهرية غير متوفرة للمجلد المحدد - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>تحذير:</b> %1 - - - - <b>Warning:</b> - <b>تحذير:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - لم يتم استلام أي E-Tag من الخادم، تحقق من الوكيل/البوابة - - - - We received a different E-Tag for resuming. Retrying next time. - استلمنا E-Tag مختلفة للمتابعة. إعادة المحاولة المرة القادمة. - - - - We received an unexpected download Content-Length. - استلمنا تنزيلًا بطول محتوى غير متوقع. - - - - Server returned wrong content-range - أعاد الخادم خطأ في نطاق المحتوى - - - - OCC::GETJob - - - Connection Timeout - انتهاء مهلة الاتصال - - - - OCC::GeneralSettings - - - Form - نموذج - - - - General Settings - الإعدادات العامة - - - - Language - اللغة - - - - Sync hidden files - مزامنة الملفات المخفية - - - - Log Settings - إعدادات السجل - - - - Advanced - Approval of folder sync (Non virtual file mode only) - متقدم - الموافقة على مزامنة المجلد (غير وضع الملف الظاهري فقط) - - - - Network - الشبكة - - - - Updates - التحديثات - - - - stable - ثابت - - - - beta - بيتا - - - - About - عن - - - - Advanced - الإعدادات المتقدمة - - - - Use &Monochrome Icons in the system tray - استخدم & الرموز أحادية اللون في لوحة النظام - - - - Ask for confirmation before synchronizing folders larger than - طلب التأكيد قبل مزامنة المجلدات ذات الحجم الأكبر من - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - ميجا بايت - - - - Ask for confirmation before synchronizing external storages - طلب التأكيد قبل مزامنة وحدة التخزين الخارجية - - - - &Launch on System Startup - والتشغيل على بدء تشغيل النظام - - - - Show &Desktop Notifications - عرض إشعارات سطح المكتب - - - - &Update Channel - قناة التحديث - - - - Edit &Ignored Files - التحرير والملفات المتجاهلة - - - - Show sync folders in &Explorer's Navigation Pane - عرض مجلدات المزامنة في شريط التنقل في المستكشف - - - - S&how crash reporter - عرض أداة crash reporter - - - - &Restart && Update - إعادة التشغيل والتحديث - - - - You cannot disable autostart because system-wide autostart is enabled. - لا يمكن تعطيل التشغيل التلقائي بسبب تمكين التشغيل التلقائي على مستوى النظام. - - - - Change update channel? - هل ترغب في تغيير قناة التحديث؟ - - - - Warning - التحذير - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - تحدد قناة التحديث تحديثات العميل التي ستُقدَّم للتثبيت. لا تحتوي القناة "ثابت" إلا على الترقيات التي تعتبر موثوقة فقط، في حين أن الإصدارات الموجودة في قناة "بيتا" قد تحتوي على ميزات وإصلاحات أجدد، لكن لم يتم اختبارها بشكل كامل حتى الآن. - -لاحظ أن ذلك لا يحدد سوى ترقيات المجموعة التي يتم الحصول عليها منه فقط، وأنه لا توجد أي إمكانية لاستخدام إصدارات أقدم: لذلك لا يمكن الرجوع من قناة بيتا إلى قناة ثابتة على الفور، ويجب انتظار ظهور إصدار ثابت أحدث من الإصدار التجريبي المثبت حاليًا. - - - - Change update channel - تغيير قناة التحديث - - - - Cancel - إلغاء - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - يُرجى إدخال %1 كلمة المرور:<br><br>المستخدم: %2<br>الحساب: %3<br> - - - - Reading from keychain failed with error: '%1' - فشلت القراءة من سلسلة المفاتيح مع خطأ: '%1' - - - - Enter Password - إدخال كلمة مرور - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">انقر هنا</a> لطلب كلمة تطبيق من واجهة الويب. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - محرر الملفات التي تم تجاهلها - - - - Files Ignored by Patterns - تجاهلت الأنماط الملفات - - - - Add - إضافة - - - - Pattern - نمط - - - - Allow Deletion - السماح بالحذف - - - - Remove - إزالة - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - لن تتم مزامنة الملفات أو المجلدات المطابقة لنمط معين. تسري التغييرات عند مزامنة المجلدات في المرة القادمة. - -سيتم حذف العناصر التي يُسمح فيها بالحذف في حالة منعها إزالة الدليل. هذا مفيد لبيانات التعريف. - - - - Could not open file - لا يمكن فتح الملف - - - - Cannot write changes to '%1'. - تعذّرت كتابة التغييرات في '%1'. - - - - Add Ignore Pattern - إضافة نمط تجاهل - - - - Add a new ignore pattern: - أضف نمط تجاهل جديدًا: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - يوفر النظام هذا الإدخال في '%1' ولا يمكن تعديله في طريقة العرض هذه. - - - - OCC::IssuesWidget - - - Form - نموذج - - - - List of issues - قائمة المشكلات - - - - Filter - عامل تصفية - - - - There were too many issues. Not all will be visible here. - يوجد عدد كبير للغاية من المشكلات. ولن يتم عرضها جميعًا هنا. - - - - There were conflicts. Check the documentation on how to resolve them. - توجد تعارضات. افحص المستندات لمعرفة كيفية إصلاحها. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - توجد تعارضات. <a href="%1">افحص المستندات لمعرفة كيفية إصلاحها.</a> - - - - Account - حساب - - - - OCC::LibCloudProvidersPrivate - - - Settings - الإعدادات - - - - OCC::LogBrowser - - - Log Output - إخراج السجل - - - - Enable logging to temporary folder - تمكين كتابة السجلات إلى مجلد مؤقت - - - - Delete logs older than %1 hours - حذف السجلات الأقدم من %1 ساعة - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">تحتوي السجلات على معلومات حساسة لا ينبغي لك إتاحتها للجمهور</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - يمكن للعميل كتابة سجلات التصحيح إلى مجلد مؤقت. هذه السجلات مفيدة جدًا في تشخيص المشكلات. -نظرًا لأن ملفات السجل يمكن أن تكبر ، سيبدأ العميل ملفًا جديدًا لكل عملية مزامنة وضغط الملفات القديمة. يوصى أيضًا بتمكين حذف ملفات السجل بعد بضع ساعات لتجنب استهلاك مساحة كبيرة على القرص. - - - - If enabled, logs will be written to: - في حالة التمكين ، ستتم كتابة السجلات على: - - - - C:/log - C:/log - - - - Log Http traffic - تسجيل حركة بروتوكول HTTP - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - تستمر هذه الإعدادات خلال عمليات إعادة تشغيل العميل. -لاحظ أن استخدام أي خيارات لسطر أوامر التسجيل سيتجاوز الإعدادات. - - - - Open folder - افتح مجلدًا - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - يتوفر إصدار جديد - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>يتوفر إصدار جديد من عميل %1.</p><p><b>%2</b> متوفر للتنزيل. الإصدار المثبت هو %3.</p> - - - - - Skip this version - تجاوز هذا الإصدار - - - - Skip this time - التجاهل هذه المرة - - - - Get update - الحصول على التحديث - - - - Update Failed - فشل التحديث - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>يتوفر إصدار جديد من عميل %1، لكن فشلت عملية التحديث.</p><p><b>%2</b> متوفر للتنزيل. الإصدار المثبت هو %3.</p> - - - - Ask again later - الطلب مرة أخرى لاحقًا - - - - Restart and update - إعادة التشغيل والتحديث - - - - Update manually - التحديث يدويًا - - - - OCC::NetworkSettings - - - Form - نموذج - - - - Proxy Settings - إعدادات الوكيل - - - - No Proxy - لا يوجد وكيل - - - - Use system proxy - استخدام وكيل النظام - - - - Specify proxy manually as - تحديد الوكيل يدويًا باعتباره - - - - Host - المضيف - - - - : - : - - - - Proxy server requires authentication - خادم الوكيل يتطلب مصادقة - - - - Note: proxy settings have no effects for accounts on localhost - ملاحظة: إعدادات الوكيل لا تؤثر على الحسابات الموجودة في localhost - - - - Download Bandwidth - النطاق الترددي للتنزيل - - - - - Limit to - الحد الأقصى - - - - - KBytes/s - كيلو بايت/الثانية - - - - - No limit - لا توجد حدود - - - - - Limit to 3/4 of estimated bandwidth - الحد الأقصى 3/4 من النطاق الترددي المقدر - - - - Upload Bandwidth - النطاق الترددي للتحميل - - - - - Limit automatically - وضع الحد تلقائيًا - - - - Hostname of proxy server - اسم مضيف خادم الوكيل - - - - Username for proxy server - اسم مستخدم خادم الوكيل - - - - Password for proxy server - كلمة مرور خادم الوكيل - - - - HTTP(S) proxy - وكيل HTTP(S) - - - - SOCKS5 proxy - وكيل SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - تم الإنشاء في %1 - - - - Closing in a few seconds... - جارٍ الإغلاق في غضون بضع ثوانٍ... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 فشل الطلب في %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' تم تحديده في %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - خطأ في البيانات الراجعة من الخادم: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - حدث خطأ أثناء الوصول إلى نقطة النهاية "الرمز المميز": <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - لا يمكن توزيع JSON الراجع من الخادم: <br><em>%1</em> - - - - Unsupported token type: %1 - نوع الرمز المميز غير معتمد: ٪ 1 - - - - The reply from the server did not contain all expected fields -:%1 - لم يتضمن الرد من الخادم جميع الحقول المتوقعة -:٪ 1 - - - - Unknown Error - خطأ غير معروف - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>خطأ تسجيل الدخول</h1><p> فشل في استرداد معلومات المستخدم</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>خطأ في تسجيل الدخول</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - خطأ أثناء تحديث الرمز المميز: ٪ 1 : ٪ 2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - لم يتضمن الرد من الخادم جميع الخانات المتوقعة -:٪ 1 -البيانات الواردة : ٪ 2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>مستخدم خطأ</h1><p>لقد سجّلت دخولك باستخدام المستخدم <em>%1</em>، لكن يجب تسجيل الدخول باستخدام المستخدم <em>%2</em>.<br>يُرجى تسجيل الخروج من %3 في علامة تبويب أخرى، ثم <a href='%4'>انقر هنا</a> وسجِّل الدخول باستخدام مستخدم %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - تحديث %1 جديد جاهز - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - يوجد تحديث جديد لـ %1 على وشك التثبيت. قد تطلب أداة التثبيت -امتيازات إضافية في أثناء العملية. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - تعذّر تنزيل التحديث. يُرجى النقر <a href='%1'>هنا</a> لتنزيل التحديث يدويًا. - - - - Could not check for new updates. - تعذّر البحث عن تحديثات جديدة. - - - - Downloading %1. Please wait... - تنزيل %1. يُرجى الانتظار… - - - - %1 available. Restart application to start the update. - %1 متوفر. أعد تشغيل التطبيق لبدء التحديث. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - %1 جديد متوفر. يُرجى استخدام أداة تحديث النظام لتثبيته. - - - - Checking update server... - جارٍ فحص خادم التحديث... - - - - Update status is unknown: Did not check for new updates. - حالة التحديث غير معروفة: لم يتم البحث عن وجود تحديثات جديدة. - - - - No updates available. Your installation is at the latest version. - لا توجد تحديثات متوفرة. لديك أحدث إصدار. - - - - Update Check - التحقق من وجود تحديث - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - اتصال بـ %1 - - - - Setup local folder options - إعداد خيارات المجلد المحلي - - - - Connect... - جارٍ الاتصال… - - - - (experimental) - (تجريبي) - - - - Use &virtual files instead of downloading content immediately%1 - استخدم & الملفات الظاهرية بدلاً من تنزيل المحتوى فورًا٪ 1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - مزامنة المجلد '%1' - - - - Virtual files are not available for the selected folder - الملفات الإفتراضية غير متوفرة للمجلد المحدد - - - - Error - خطأ - - - - Local Sync Folder - مجلد المزامنة المحلي - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - البريد الإلكتروني - - - - Connect to %1 - اتصال بـ %1 - - - - Enter user credentials - أدخل بيانات اعتماد المستخدم - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - اتصال بـ %1 - - - - Login in your browser - سجِّل الدخول في متصفحك - - - - Copy link to clipboard - انسخ الرابط إلى الحافظة - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - اتصال بـ %1 - - - - Setup %1 server - إعداد خادم %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - عنوان url هذا غير آمن لأنه ليس مشفرًا. -لا يُنصح باستخدامه. - - - - This url is secure. You can use it. - عنوان url هذا آمن. يمكنك استخدامه. - - - - &Next > - التالي > - - - - Invalid URL - عنوان URL غير صالح - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - فشل الاتصال بـ %1 في %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - انتهاء مهلة محاولة الاتصال بـ %1 في %2. - - - - Invalid URL - عنوان URL غير صالح - - - - Could not create local folder %1 - تعذّر إنشاء مجلد محلي %1 - - - - Error: %1 - خطأ: %1 - - - - The folder creation resulted in HTTP error code %1 - أدت عملية إنشاء المجلد إلى رمز خطأ HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - فشل إنشاء المجلد البعيد؛ لأن بيانات الاعتماد المقدمة غير صحيحة!<br/>يُرجى الانتقال إلى الخلف وفحص بيانات الاعتماد الخاصة بك.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - فشل إنشاء المجلد البعيد %1 مع خطأ <tt>%2</tt>. - - - - Folder rename failed - فشلت إعادة تسمية المجلد - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - تعذّرت إزالة المجلد ونسخه احتياطيًا، لأن المجلد أو الملف الموجود به مفتوح في برنامج آخر. يُرجى إغلاق المجلد أو الملف وإعادة المحاولة أو إلغاء الإعداد. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 معالج الاتصال - - - - Enable experimental feature? - هل ترغب في تمكين ميزة التجريبي؟ - - - - Enable experimental placeholder mode - تمكين وضع العنصر النائب التجريبي - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - عند تمكين وضع "الملفات الظاهرية"، لن يتم تنزيل أي ملفات في البداية. وبدلًا من ذلك، سيتم إنشاء ملف "%1" صغير لكل ملف موجود على الخادم. ويمكن تنزيل المحتويات بتشغيل هذه الملفات أو باستخدام قائمة السياق الخاصة بها. - -لا يمكن الجمع بين وضع الملفات الظاهرية والمزامنة الانتقائية. سيتم تحويل المجلدات غير المحددة حاليًا إلى مجلدات عبر الإنترنت فقط، وستتم إعادة تعيين إعدادات المزامنة الانتقائية. - -سيؤدي التبديل إلى هذا الوضع إلى إلغاء أي مزامنة قيد التشغيل حاليًا. - -هذا الوضع جديد وتجريبي. فإذا قررت استخدامه، فيُرجى الإبلاغ عن أي مشكلات تطرأ. - - - - Stay safe - تمتع بالأمان الدائم - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - الروابط الرمزية غير مدعومة في المزامنة. - - - - File is listed on the ignore list. - الملف مدرج على قائمة التجاهل. - - - - File names ending with a period are not supported on this file system. - أسماء الملفات التي تنتهي بمسافة غير مدعومة على نظام الملفات هذا. - - - - File names containing the character '%1' are not supported on this file system. - أسماء الملفات التي تحتوي على حرف '%1' غير مدعومة على نظام الملفات هذا. - - - - File name contains at least one invalid character - يحتوي اسم الملف على حرف واحد غير صالح على الأقل - - - - The file name is a reserved name on this file system. - اسم الملف هو اسم محجوز على نظام الملفات هذا. - - - - Filename contains trailing spaces. - اسم الملف يحتوي على مسافات زائدة. - - - - Filename is too long. - اسم الملف طويل للغاية. - - - - File/Folder is ignored because it's hidden. - تم تجاهل الملف/المجلد بسبب أنه مخفي. - - - - Stat failed. - فشلت الإحصاءات. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - تعارض: تم تنزيل إصدار الخادم وإعادة تسمية النسخة المحلية ولم يتم تحميلها. - - - - The filename cannot be encoded on your file system. - لا يمكن ترميز اسم الملف على نظام الملفات هذا. - - - - The filename is blacklisted on the server. - تم إدراج اسم الملف بالقائمة السوداء على الخادم. - - - - size - الحجم - - - - permissions - أذونات - - - - etag - etag - - - - file id - معرف ملف - - - - server reported no %1 - لم يبلغ الخادم عن شيء %1 - - - - Moved to invalid target, restoring - تم النقل إلى منطقة مستهدفة غير صالحة، استعادة - - - - Not allowed to remove, restoring - غير مسموح بالإزالة، استعادة - - - - Ignored because of the "choose what to sync" blacklist - تم التجاهل بسبب القائمة السوداء "اختيار المحتوى المطلوب مزامنته" - - - - Not allowed because you don't have permission to add subfolders to that folder - غير مسموح بسبب عدم حصولك على إذن لإضافة مجلدات فرعية إلى هذا المجلد - - - - Not allowed because you don't have permission to add files in that folder - غير مسموح بسبب عدم حصولك على إذن لإضافة ملفات إلى هذا المجلد - - - - Not allowed to upload this file because it is read-only on the server, restoring - غير مسموح بتحميل هذا الملف لأنه مخصص لقراءة فقط على الخادم، استعادة - - - - Error while reading the database - حدث خطأ أثناء قراءة قاعدة البيانات - - - - Server replied with an error while reading directory '%1' : %2 - رد الخادم بخطأ في أثناء قراءة الدليل '%1': %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - سيقلل التنزيل المساحة الفارغة على القرص المحلي أقل من الحد - - - - Free space on disk is less than %1 - المساحة الفارغة على القرص أقل من %1 - - - - File was deleted from server - تم حذف الملف من الخادم - - - - The file could not be downloaded completely. - تعذّر تنزيل الملف بالكامل. - - - - The downloaded file is empty despite the server announced it should have been %1. - الملف الذي تم تنزيله فارغ على الرغم من أن الخادم وضح أنه سيكون %1. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - تم تغيير الملف منذ الاكتشاف - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ؛ فشلت الاستعادة: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - لا يمكن حذف الملف %1، خطأ: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - لا يمكن إنشاء المجلد %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - لا يمكن إعادة تسمية الملف %1 إلى %2 بسبب تعارض اسم الملف المحلي - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - خطأ في إعداد حالة التثبيت - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - يُرجِع الخادم رمز HTTP خاطئًا. الرمز المتوقع 204، لكن تم استلام "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - يُرجِع الخادم رمز HTTP خاطئًا. الرمز المتوقع 201، لكن تم استلام "%1 %2". - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - لا يمكن إعادة تسمية %1 إلى %2، خطأ: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - يُرجِع الخادم رمز HTTP خاطئًا. الرمز المتوقع 201، لكن تم استلام "%1 %2". - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - خطأ في كتابة بيانات التعريف على قاعدة البيانات - - - - Error setting pin state - خطأ في إعداد حالة التثبيت - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - تعذر تحديث الملف : ٪ 1 - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - لا يمكن تحميل الملف %1 بسبب وجود ملف آخر بنفس الاسم لكنه يختلف عنه في الحالة فقط - - - - File Removed - تمت إزالة الملف - - - - Local file changed during syncing. It will be resumed. - تم تغيير الملف المحلي أثناء المزامنة. ستتم المتابعة. - - - - Local file changed during sync. - تم تغيير الملف المحلي أثناء المزامنة. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - تحميل %1 يتجاوز الحصة النسبية المخصصة للمجلد - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - تمت إزالة الملف المحلي أثناء المزامنة. - - - - Local file changed during sync. - تم تغيير الملف المحلي أثناء المزامنة. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - رمز غير متوقع راجع من الخادم (%1) - - - - Missing File ID from server - معرف ملف مفقود من الخادم - - - - Missing ETag from server - ETag مفقود من الخادم - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - تمت إزالة الملف المحلي أثناء المزامنة. - - - - Local file changed during sync. - تم تغيير الملف المحلي أثناء المزامنة. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - تمت إزالة الملف المحلي أثناء المزامنة. - - - - Local file changed during sync. - تم تغيير الملف المحلي أثناء المزامنة. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - لم يتعرف الخادم على المجموعة الأخيرة. (لا يوجد e-tag) - - - - OCC::ProtocolItemModel - - - Time - وقت - - - - File - ملف - - - - Folder - مجلد - - - - Issues - مسائل - - - - Action - الإجراء - - - - Size - الحجم - - - - Account - حساب - - - - Status - الحالة - - - - OCC::ProtocolWidget - - - Form - نموذج - - - - Local sync protocol - بروتوكول المزامنة المحلي - - - - Filter - عامل تصفية - - - - Account - حساب - - - - Copy to clipboard - نسخ إلى الحافظة - - - - Show in file browser - - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - مصادقة الوكيل مطلوبة - - - - Username: - اسم المستخدم: - - - - Proxy: - الوكيل: - - - - The proxy server needs a username and password. - يحتاج خادم الوكيل اسم مستخدم وكلمة مرور. - - - - Password: - كلمة المرور: - - - - TextLabel - تسمية نصية - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - اختر المحتوى المطلوب مزامنته - - - - OCC::SelectiveSyncWidget - - - Loading ... - جارٍ التحميل... - - - - Deselect remote folders you do not wish to synchronize. - ألغِ تحديد المجلدات البعيدة التي لا ترغب في مزامنتها. - - - - Name - الاسم - - - - Size - الحجم - - - - - No subfolders currently on the server. - لم يتم العثور أي مجلدات فرعية على الخادم حاليًا. - - - - An error occurred while loading the list of sub folders. - حدث خطأ أثناء تحميل قائمة المجلدات الفرعية. - - - - OCC::ServerNotificationHandler - - - Dismiss - تجاهل - - - - OCC::SettingsDialog - - - Settings - الإعدادات - - - - Activity - نشاط - - - - Add account - إضافة حساب - - - - - Quit %1 - إنهاء %1 - - - - Are you sure you want to quit %1? - هل أنت متأكد من رغبتك في إنهاء %1؟ - - - - Account - حساب - - - - MainWindow - النافذة الرئيسية - - - - Hide - إخفاء - - - - OCC::ShareDialog - - - TextLabel - تسمية نصية - - - - share label - تصنيف المشاركة - - - - Dialog - الحوار - - - - ownCloud Path: - مسار ownCloud: - - - - %1 Sharing - %1 مشاركة - - - - %1 - %1 - - - - Folder: %2 - مجلد: %2 - - - - The server does not allow sharing - لا يسمح الخادم بالمشاركة - - - - Retrieving maximum possible sharing permissions from server... - جارٍ استرداد أقصى عدد ممكن من أذونات المشاركة من الخادم... - - - - The file can not be shared because it was shared without sharing permission. - لا يمكن مشاركة الملف بسبب مشاركته بدون إذن مشاركة. - - - - Users and Groups - المستخدمون والمجموعات - - - - Public Links - روابط عامة - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - مشاركة NewDocument.odt - - - - Upload only (File Drop) - تحميل فقط (إفلات الملف) - - - - Receive files from others without revealing the contents of the folder. - استلم ملفات من آخرين دون إظهار محتويات المجلد. - - - - Download / View / Edit - تنزيل/عرض/تحرير - - - - Name: - الاسم: - - - - Enter name of new share... - جارٍ إدخال اسم مشاركة جديدة... - - - - &Create - إنشاء - - - - TextLabel - تسمية نصية - - - - Set &password - تعيين كلمة المرور - - - - Set &expiration date - تعيين تاريخ انتهاء الصلاحية - - - - Set password - تعيين كلمة المرور - - - - Recipients can view or download contents. - يستطيع المستلمون عرض المحتويات أو تنزيلها. - - - - Download / View - تنزيل/عرض - - - - Recipients can view, download, edit, delete and upload contents. - يستطيع المستلمون عرض المحتويات، أو تنزيلها، أو تعديلها، أو حذفها، أو تحميلها. - - - - Anyone with the link has access to the file/folder - يمكن لأي شخص لديه الرابط الوصول إلى الملف/المجلد - - - - - P&assword protect - حماية كلمة المرور - - - - The file can not be shared because it was shared without sharing permission. - لا يمكن مشاركة الملف بسبب مشاركته بدون إذن مشاركة. - - - - Link shares have been disabled - تم تعطيل مشاركة الرابط - - - - Create public link share - أنشئ مشاركة رابط عام - - - - - Delete - حذف - - - - Open link in browser - افتح الرابط في متصفح - - - - Copy link to clipboard - انسخ الرابط إلى الحافظة - - - - Copy link to clipboard (direct download) - انسخ الرابط إلى الحافظة (تنزيل مباشر) - - - - Send link by email - أرسل الرابط بالبريد الإلكتروني - - - - Send link by email (direct download) - أرسل الرابط بالبريد الإلكتروني (تنزيل مباشر) - - - - Create new... - جارٍ إنشاء جديد... - - - - I shared %1 with you - شاركت %1 معك - - - - Confirm Link Share Deletion - تأكيد حذف مشاركة الرابط - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>هل ترغب حقًا في حذف مشاركة الرابط العام <i>%1</i>؟</p><p>ملاحظة: لا يمكن التراجع عن هذا الإجراء.</p> - - - - Cancel - إلغاء - - - - - Public link - رابط عام - - - - Delete link share - مشاركة رابط عام - - - - - Please Set Password - يُرجى تعيين كلمة المرور - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - مشاركة NewDocument.odt - - - - Share with users or groups ... - جارٍ المشاركة مع المستخدمين أو المجموعات ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>يمكنك توجيه الأشخاص إلى هذا الملف أو المجلد المُشارك <a href="private link menu"><span style=" text-decoration: underline">عن طريق تقديم رابط خاص لهم</span></a>.</p></body></html> - - - - The item is not shared with any users or groups - لم تتم مشاركة العنصر مع أي مستخدمين أو مجموعات - - - - Open link in browser - افتح الرابط في متصفح - - - - Copy link to clipboard - انسخ الرابط إلى الحافظة - - - - Send link by email - أرسل الرابط بالبريد الإلكتروني - - - - No results for '%1' - لم يتم العثور على نتائج لـ '%1' - - - - I shared something with you - شاركت شيئًا ما معك - - - - OCC::ShareUserLine - - - Form - نموذج - - - - - TextLabel - تسمية نصية - - - - can edit - يمكن تحرير - - - - can share - يمكن مشاركة - - - - ... - ... - - - - create - إنشاء - - - - change - تغيير - - - - delete - حذف - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - مشاركة مع %1 - - - - Context menu share - مشاركة قائمة السياق - - - - Context menu share %1 - مشاركة قائمة السياق %1 - - - - Confirm deletion - تأكيد الحذف - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - هل ترغب في حذف الدليل <i>%1</i> ومحتوياته بشكل دائم؟ - - - - Do you want to delete the file <i>%1</i> permanently? - هل ترغب في حذف الملف <i>%1</i> بشكل دائم؟ - - - - Select new location... - جارٍ تحديد مكان جديد... - - - - Error - خطأ - - - - Moving file failed: - -%1 - فشل نقل الملف: - -%1 - - - - I shared something with you - شاركت شيئًا ما معك - - - - - Share... - جارٍ المشاركة... - - - - - Copy private link to clipboard - نسخ رابط خاص إلى الحافظة - - - - Send private link by email... - جارٍ إرسال رابط خاص بالبريد الإلكتروني... - - - - Resharing this file is not allowed - غير مسموح بإعادة مشاركة هذا الملف - - - - Resharing this folder is not allowed - غير مسموح بإعادة مشاركة هذا المجلد - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - انسخ رابطًا عامًا إلى الحافظة - - - - Open in browser - فتح في المتصفح - - - - Show file versions in browser - اعرض إصدارات الملف في المتصفح - - - - Rename... - جارٍ إعادة التسمية... - - - - Rename and upload... - جارٍ إعادة التسمية والتحميل... - - - - Move and rename... - جارٍ النقل وإعادة التسمية... - - - - Move, rename and upload... - جارٍ النقل وإعادة التسمية والتحميل... - - - - Delete local changes - حذف التغييرات المحلية - - - - Move and upload... - جارٍ النقل والتحميل… - - - - Delete - حذف - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - بدأ النسخ الاحتياطي لـ ٪ 1 - - - - Failed to find %1 - فشل في العثور على ٪ 1 - - - - Local path must be a an absolute path - يجب أن يكون المسار المحلي مسارًا مطلقًا - - - - Failed to create temporary database - فشل إنشاء قاعدة بيانات مؤقتة - - - - Backup of %1 succeeded - نجح النسخ الاحتياطي لـ ٪ 1 - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - فشل النسخ الاحتياطي لـ ٪ 1 مع: ٪ 2 - - - - %1 backup - ٪ 1 نسخة احتياطية - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>تفاصيل الشهادة</h3> - - - - Common Name (CN): - الاسم الشائع (CN): - - - - Subject Alternative Names: - الأسماء البديلة للموضوع: - - - - Organization (O): - المؤسسة (O): - - - - Organizational Unit (OU): - الوحدة التنظيمية (OU): - - - - State/Province: - الولاية /المقاطعة: - - - - Country: - البلد: - - - - Serial: - الرقم التسلسلي: - - - - <h3>Issuer</h3> - <h3>المُصدِّر</h3> - - - - Issuer: - المُصدِّر: - - - - Issued on: - صدر في: - - - - Expires on: - انتهاء الصلاحية في: - - - - <h3>Fingerprints</h3> - <h3>بصمات الأصابع</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>ملاحظة:</b> تمت الموافقة على هذه الشهادة يدويًا</p> - - - - %1 (self-signed) - %1 (موقَّع ذاتيًا) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - تم تشفير هذا الاتصال باستخدام %1 بت %2. - - - - - Server version: %1 - إصدار الخادم: %1 - - - - No support for SSL session tickets/identifiers - لا يوجد دعم لتذاكر/معرفات جلسة عمل SSL - - - - Certificate information: - معلومات الشهادة: - - - - The connection is not secure - الاتصال غير آمن - - - - This connection is NOT secure as it is not encrypted. - - هذا الاتصال غير آمن لأنه ليس مشفرًا. - - - - - OCC::SslErrorDialog - - - Form - نموذج - - - - Trust this certificate anyway - الوثوق في هذه الشهادة على أي حال - - - - Untrusted Certificate - شهادة غير موثوقة - - - - Cannot connect securely to <i>%1</i>: - تعذر الاتصال بـ <i>%1</i> بأمان: - - - - with Certificate %1 - مع شهادة %1 - - - - - - &lt;not specified&gt; - غير محدد؛ - - - - - Organization: %1 - المؤسسة: %1 - - - - - Unit: %1 - الوحدة: %1 - - - - - Country: %1 - البلد: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - بصمة الإصبع (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - بصمة الإصبع (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - تاريخ السريان: %1 - - - - Expiration Date: %1 - تاريخ انتهاء الصلاحية: %1 - - - - Issuer: %1 - المُصدِّر: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (تم التجاوز بسبب خطأ سابق، أعد المحاولة مرة أخرى في %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - يتوفر %1 فقط، مطلوب %2 على الأقل للبدء - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - تعذّر فتح قاعد بيانات المزامنة المحلية أو إنشاؤها. تأكد أن لديك حق وصول للكتابة في مجلد المزامنة. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - مساحة القرص منخفضة: تم تجاوز التنزيلات التي تقلل المساحة الفارغة عن %1. - - - - There is insufficient space available on the server for some uploads. - لا تتوفر مساحة كافية على الخادم لبعض التحميلات. - - - - Unresolved conflict. - تعارض معلق. - - - - Using virtual files with suffix, but suffix is not set - استخدام ملفات ظاهرية ذات لاحقة، ولكن بدون تعيين اللاحقة - - - - Unable to read the blacklist from the local database - تعذّرت قراءة القائمة السوداء من قاعدة البيانات المحلية - - - - Unable to read from the sync journal. - تعذّرت القراءة من دفتر يومية المزامنة. - - - - Cannot open the sync journal - لا يمكن فتح دفتر يومية المزامنة - - - - Aborted - تم الإلغاء - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - تم تكوين عنوان المجموع الاختباري بشكل غير صحيح. - - - - The checksum header contained an unknown checksum type '%1' - احتوى عنوان المجموع الاختباري على نوع مجموع اختباري غير معروف '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - الملف الذي تم تنزيله لا يتطابق مع المجموع الاختباري ، وسيتم استئنافه. '٪ 1'! = '٪ 2' - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - تتطلب ميزة نظام الملفات الإفتراضية نظام ملفات NTFS ، يستخدم٪ 1٪ 2 - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - يُرجى تسجيل الدخول - - - - Folder %1: %2 - مجلد %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - لا توجد مجلدات مزامنة مكونة. - - - - Open in browser - فتح في المتصفح - - - - - - Log in... - جارٍ تسجيل الدخول... - - - - - - Log out - تسجيل الخروج - - - - Recent Changes - التغييرات الأخيرة - - - - Managed Folders: - المجلدات المُدارة: - - - - Open folder '%1' - فتح مجلد '%1' - - - - Open %1 in browser - فتح %1 في متصفح - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - يشغِّل الخادم الموجود على الحساب %1 إصدارًا غير مدعوم%2. لم يتم اختبار استخدام هذا العميل مع إصدارات الخادم غير المدعومة وقد يشكل خطورة. تابع على مسؤوليتك. - - - - Unknown status - حالة غير معروفة - - - - Settings... - الإعدادات... - - - - Details... - التفاصيل... - - - - Help - تعليمات - - - - Quit %1 - إنهاء %1 - - - - Disconnected from %1 - انقطع الاتصال عن %1 - - - - Unsupported Server Version - إصدار خادم غير مدعوم - - - - Disconnected - غير متصل - - - - Disconnected from some accounts - غير متصل ببعض الحسابات - - - - Disconnected from accounts: - غير متصل بالحسابات: - - - - Account %1: %2 - حساب %1: %2 - - - - Signed out - تم تسجيل الخروج - - - - Account synchronization is disabled - تم تعطيل مزامنة الحساب - - - - Synchronization is paused - تم إيقاف المزامنة مؤقتًا - - - - Up to date (%1) - - - - - No sync folders configured - لم يتم تكوين أي مجلدات مزامنة - - - - Unpause all folders - إلغاء الإيقاف المؤقت لجميع المجلدات - - - - Pause all folders - إيقاف جميع المجلدات مؤقتًا - - - - Unpause all synchronization - إلغاء الإيقاف المؤقت لجميع عمليات المزامنة - - - - Unpause synchronization - إلغاء الإيقاف المؤقت لعملية المزامنة - - - - Pause all synchronization - إيقاف جميع عمليات المزامنة مؤقتًا - - - - Pause synchronization - إيقاف عملية المزامنة مؤقتًا - - - - Log out of all accounts - تسجيل الخروج من جميع الحسابات - - - - Log in to all accounts... - جارٍ تسجيل الدخول إلى جميع الحسابات... - - - - New account... - حساب جديد... - - - - About %1 - عن %1 - - - - No items synced recently - لم تتم مزامنة أي عناصر مؤخرًا - - - - Checking for changes in remote '%1' - البحث عن تغييرات في '%1' البعيد - - - - Checking for changes in local '%1' - البحث عن تغييرات في '%1' المحلي - - - - Syncing %1 of %2 (%3 left) - مزامنة %1 من %2 (%3 متبقٍّ) - - - - Syncing %1 of %2 - مزامنة %1 من %2 - - - - Syncing %1 (%2 left) - مزامنة %1 (%2 متبقٍّ) - - - - Syncing %1 - مزامنة %1 - - - - %1 (%2, %3) - %1 (%2، %3) - - - - OwncloudAdvancedSetupPage - - - Form - نموذج - - - - - TextLabel - تسمية نصية - - - - Server - خادم - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>إذا تم تحديد هذا المربع، فسيتم مسح المحتوى الموجود في المجلد المحلي لبدء مزامنة نظيفة من الخادم.</p><p>لا تحدِّد ذلك في حالة ضرورة تحميل المحتوى المحلي إلى مجلد الخوادم.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - طلب التأكيد قبل مزامنة المجلدات ذات الحجم الأكبر من - - - - S&ynchronize everything from server - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - ميجا بايت - - - - Ask for confirmation before synchronizing e&xternal storages - طلب التأكيد قبل مزامنة وحدة التخزين الخارجية - - - - Choose what to sync - اختر المحتوى المطلوب مزامنته - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>عند تحديد هذا الخيار، سيغلق المعالج دون مزامنة أي شيء. يمكنك استخدام زر &quot؛ إضافة اتصال مزامنة المجلد&quot من إعدادات الحساب لاختيار أي زوج من المجلدات المحلية والبعيدة التي تريد مزامنتها</p></body></html> - - - - Manually create folder sync connections - إنشاء اتصالات مزامنة المجلد يدويًا - - - - &Local Folder - &المجلد المحلي - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &الاحتفاظ بالبيانات المحلية - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - نموذج - - - - &Username - اسم المستخدم - - - - &Password - كلمة المرور - - - - OwncloudOAuthCredsPage - - - Form - نموذج - - - - Please switch to your browser to proceed. - يُرجى التبديل إلى متصفحك للمتابعة. - - - - An error occurred while connecting. Please try again. - حدث خطأ أثناء الاتصال. يُرجى المحاولة مرة أخرى. - - - - Re-open Browser - إعادة فتح المتصفح - - - - OwncloudSetupPage - - - Form - نموذج - - - - Ser&ver Address - عنوان الخادم - - - - https://... - https://... - - - - Error Label - خطأ في التصنيف - - - - QObject - - - in the future - في المستقبل - - - - %n day(s) ago - يوم n% منذيوم n% منذيومين n% منذأيام n% منذأيام n% منذأيام n% منذ - - - - %n hour(s) ago - ساعة n% منذساعة n% منذساعتين n% منذساعات n% منذساعات n% منذساعات n% منذ - - - - now - الآن - - - - Less than a minute ago - منذ أقل من دقيقة واحدة - - - - %n minute(s) ago - دقيقة n% منذدقيقة n% منذدقيقتين n% منذدقائق n% منذدقائق n% منذدقائق n% منذ - - - - Some time ago - منذ فترة من الوقت - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - - - - - - Error - خطأ - - - - Info - معلومات - - - - Success - نجاح - - - - Conflict - تعارض - - - - File Ignored - - - - - Restored - - - - - Blacklisted - - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 جيجا بايت - - - - %L1 MB - %L1 ميجا بايت - - - - %L1 KB - %L1 كيلو بايت - - - - %L1 B - %L1 بايت - - - - %n year(s) - عام n%عام n%عامين n%عامين n%عامين n%عامين n% - - - - %n month(s) - شهر n%شهر n%شهرين n%أشهر n%أشهر n%أشهر n% - - - - %n day(s) - يوم n%يوم n%يومين n%أيام n%أيام n%أيام n% - - - - %n hour(s) - ساعة n%ساعة n%ساعتين n%ساعات n%ساعات n%ساعات n% - - - - %n minute(s) - دقيقة n%دقيقة n%دقيقتين n%دقائق n%دقائق n%دقائق n% - - - - %n second(s) - ثانية n%ثانية n%ثانيتين n%ثوانٍ n%ثوانٍ n%ثوانٍ n% - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - منطقة أدوات النظام غير متوفرة - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 يتطلب في منطقة أدوات نظام العمل. إذا كنت تشغِّل XFCE، فيُرجى اتباع <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">هذه التعليمات</a>. وإلا فيُرجى تثبيت تطبيق منطقة أدوات النظام مثل "trayer" والمحاولة مرة أخرى. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - ٪ 1 (تم البناء مقابل Qt٪ 1) - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - تم إنشاء الملف الظاهري - - - - Replaced by virtual file - تم الاستبدال بملف ظاهري - - - - Downloaded - تم التنزيل - - - - Uploaded - تم التحميل - - - - Server version downloaded, copied changed local file into conflict file - تم تنزيل إصدار الخادم، الملف المحلي المُغيَّر المنسوخ في ملف التعارض - - - - Deleted - تم الحذف - - - - Moved to %1 - تم النقل إلى %1 - - - - Ignored - تم التجاهل - - - - Filesystem access error - خطأ في الوصول إلى نظام الملف - - - - Error - خطأ - - - - Updated local metadata - بيانات التعريف المحلية المُحدثة - - - - - Unknown - غير معروف - - - - downloading - تنزيل - - - - uploading - تحميل - - - - deleting - حذف - - - - moving - نقل - - - - ignoring - تجاهل - - - - - error - خطأ - - - - updating local metadata - تحديث بيانات التعريف المحلية - - - - theme - - - Status undefined - الحالة غير معرفة - - - - Waiting to start sync - الانتظار لبدء المزامنة - - - - Sync is running - المزامنة قيد التشغيل - - - - Sync Success - تمت المزامنة بنجاح - - - - Sync Success, some files were ignored. - نجحت المزامنة، سيتم تجاهل بعض الملفات. - - - - Sync Error - خطأ في المزامنة - - - - Setup Error - خطأ في الإعداد - - - - Preparing to sync - تحضير المزامنة - - - - Aborting... - جارٍ الإلغاء... - - - - Sync is paused - تم إيقاف المزامنة مؤقتًا - - - - utility - - - Could not open browser - لا يمكن فتح المتصفح - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - حدث خطأ عند تشغيل المتصفح للانتقال إلى عنوان URL %1. هل من المحتمل أنه لم يتم تكوين المتصفح الافتراضي؟ - - - - Could not open email client - لا يمكن فتح عميل البريد الإلكتروني - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - حدث خطأ عند تشغيل عميل البريد الإلكتروني لإنشاء رسالة جديدة. هل من المحتمل أنه لم يتم تكوين عميل بريد إلكتروني افتراضي؟ - - - - Always available locally - يتوفر دائمًا محليًا - - - - Some available online only - يتوفر أحيانًا عبر الإنترنت فقط - - - - - Available online only - يتوفر عبر الإنترنت فقط - - - - Currently available locally - يتوفر حاليًا محليًا - - - - Make always available locally - وفِّره دائمًا محليًا - - - - Free up local space - تحرير المساحة المحلية - - - \ No newline at end of file diff --git a/translations/client_bg_BG.ts b/translations/client_bg_BG.ts deleted file mode 100644 index a1395ed8f5b..00000000000 --- a/translations/client_bg_BG.ts +++ /dev/null @@ -1,4908 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - Прекрати - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - Затвори - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - Директориите в кошчето не бяха създадени - - - - - Could not move '%1' to '%2' - Не може да се премести '%1' в '%2' - - - - Moving to the trash is not implemented on this platform - Преместването в кошчето не се реализира на тази платформа - - - - Error removing '%1': %2 - Грешка при изтриване на '%1': %2 - - - - Could not remove folder '%1' - Неуспешно изтриване на папка '%1' - - - - FolderWizardSourcePage - - - Form - Формуляр - - - - Pick a local folder on your computer to sync - Изберете папка на Вашия компютър за синхронизация - - - - &Choose... - &Избиране... - - - - FolderWizardTargetPage - - - Form - Формуляр - - - - Select a remote destination folder - Избиране на отдалечена папка - - - - Create Folder - Създаване на папка - - - - Refresh - Опресняване - - - - Folders - Папки - - - - TextLabel - Текстов етикет - - - - NotificationWidget - - - Form - Формуляр - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Текстов етикет - - - - OCC::AboutDialog - - - About - Относно - - - - TextLabel - Текстов етикет - - - - Versions - Версии - - - - About %1 - Около %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Връзката се провали - - - - Unknown error: network reply was deleted - Неизвестна грешка: мрежовият отговор беше изтрит - - - - Server replied "%1 %2" to "%3 %4" - Сървърът отговори „%1%2“ в „%3%4“ - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - Формуляр - - - - Re-open Browser - Отворете отново браузъра - - - - ... - ... - - - - Storage space: ... - Място за съхранение: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Неотбелязаните папки ще бъдат <b>премахнати</b> от локалната файлова система и повече няма да бъдат синхронизирани с този компютър - - - - Synchronize all - Синхронизиране на всичко - - - - Synchronize none - Синхронизиране на нищо - - - - Apply manual changes - Прилагане на ръчните промени - - - - Apply - Приложи - - - - - - - Cancel - Отказ - - - - Connected with <server> as <user> - Свръзан със <server> като <user> - - - - No account configured. - Не е конфигуриран профил - - - - Remove - Премахване - - - - Account - Профил - - - - Choose what to sync - Избери какво да се синхронизира - - - - Force sync now - Синхронизирай сега - - - - Restart sync - Рестартирай синхронизирането - - - - Remove folder sync connection - Пемахване на връзка за синхронизиране на папка - - - - Folder creation failed - Неуспешно създаване на папка - - - - <p>Could not create local folder <i>%1</i>. - <p>Не може да създаде локална папка <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Потвърждаване на премахването на връзката за синхронизиране на папката - - - - Remove Folder Sync Connection - Премахване на синхронизираща връзка на папка - - - - Disable virtual file support? - Деактивиране на поддръжката на виртуални файлове? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - Това действие ще деактивира поддръжката на виртуални файлове. В резултат на това ще бъде изтеглено съдържание на папки, които в момента са маркирани като „достъпни само онлайн“. - -Единственото предимство на деактивирането на поддръжката на виртуални файлове е, че функцията за селективно синхронизиране отново ще стане достъпна. - -Това действие ще прекрати всяка текуща синхронизация. - - - - Disable support - Деактивиране на поддръжката - - - - Sync Running - Синхронизирането е включено - - - - The syncing operation is running.<br/>Do you want to terminate it? - Активна е синхронизираща операция.<br/>Искате ли да я преустановите? - - - - %1 in use - %1 се използва - - - - %1 as <i>%2</i> - %1 като <i>%2</i> - - - - Connected to %1. - Свързан към %1. - - - - Server %1 is temporarily unavailable. - Сървъра %1 е временно недостъпен. - - - - Server %1 is currently in maintenance mode. - В момента сървър %1 е в режим на поддръжка. - - - - Signed out from %1. - Излизане от профила на %1. - - - - Connecting to %1... - Свързване с %1... - - - - No connection to %1 at %2. - Няма връзка с %1 от %2. - - - - Log in - Влизане - - - - There are folders that were not synchronized because they are too big: - Има папки, които не са синхронизирани защото са твърде големи: - - - - There are folders that were not synchronized because they are external storages: - Има папки, които не са синхронизирани, защото са на външни дискови устройства: - - - - There are folders that were not synchronized because they are too big or external storages: - Има папки, които не са синхронизирани защото са твърде големи или на външни дискови устройства: - - - - Confirm Account Removal - Потвърждаване за премахване на профил - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Сигурни ли сте, че желаете да премахнете връзката с профил <i>%1</i>?</p><p><b>Блежка:</b> Това <b>няма</b> да изтрие никакви файлове.</p> - - - - Remove connection - Премахване на връзка - - - - Open folder - Отваряне на папка - - - - - Log out - Излизане - - - - Open local folder - Отваряне на локална папка - - - - Open folder in browser - Отваряне на папка в браузър - - - - Resume sync - Продължаване на синхронизацията - - - - Pause sync - Пауза на синхронизацията - - - - Availability - Налично - - - - Disable virtual file support... - Деактивиране на поддръжката на виртуални файлове ... - - - - Enable virtual file support%1... - - - - - (experimental) - - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Сигурни ли сте, че желаете да спрете синхронизацията на папка<i>%1</i>?</p><p><b>Бележка:</b> Това <b>няма</b> да изтрие никакви файлове.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) от %2 се използва. Някои папки, включително мрежови или споделени папки, могат да имат различни ограничения. - - - - %1 of %2 in use - %1 от %2 се използва - - - - Currently there is no storage usage information available. - В момента липсва информация за използваното пространство. - - - - The server version %1 is unsupported! Proceed at your own risk. - Версията на сървъра %1 не се поддържа! Продължете на свой риск. - - - - Obtaining authorization from the browser. - - - - - Server configuration error: %1 at %2. - - - - - No %1 connection configured. - Липсва %1 конфигурирана връзка. - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - - - - - Signed out - Излизане - - - - Disconnected - Несвързан - - - - Connected - Свързан - - - - Service unavailable - Неналична услуга - - - - Maintenance mode - Режим на поддръжка - - - - Network error - Мрежова грешка - - - - Configuration error - Конфигурационна грешка - - - - Asking Credentials - Запитване на удостоверения - - - - Unknown account state - Неизвестно състояние на профила - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 към %3 - - - - Activity - Активност - - - - Account - Профил - - - - Time - Време - - - - Local path - - - - - OCC::ActivitySettings - - - - Server Activity - Сървърна активност - - - - Sync Protocol - Протокол за синхронизация - - - - Not Synced - Не синхронизиран - - - - Not Synced (%1) - %1 is the number of not synced files. - Не синхронизиран (%1) - - - - OCC::ActivityWidget - - - Form - Формуляр - - - - - - TextLabel - Текстов етикет - - - - Filter - Филтър - - - - Server Activities - Сървърна активност - - - - Notifications - Известия - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - Нови известия за%1, %2 и други профили. - - - - Open the activity view for details. - Отворете изгледа за активност за повече подробности. - - - - Copy to clipboard - Копирай - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>Профилът %1 няма включена функцията за активност. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Продължаването ще означава <b>изтриване на тези настройки</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Продължаването ще означава <b>игнориране на тези настройки</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Някои настройки бяха конфигурирани от по-нови версии на този клиент и използват функции, които не са налични в тази версия.<br><br>%1<br><br>Текущият конфигурационен файл беше архивиран до<i>%2</i>. - - - - Quit - Изход - - - - Continue - Продължаване - - - - Error accessing the configuration file - Грешка при достъп до конфигурационния файл - - - - There was an error while accessing the configuration file at %1. - Възникна грешка при достъп до конфигурационния файл в %1. - - - - Quit %1 - Затвори %1 - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - Изисква се .оторизация - - - - Enter username and password for '%1' at %2. - Въведете потребителско име и парола за '%1' на '%2'. - - - - &User: - &Потребител: - - - - &Password: - &Парола: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Няма конфигуриран ownCloud профил - - - - The configured server for this client is too old - Конфигурираният сървър за този клиент е с твърде стара версия - - - - Please update to the latest server and restart the client. - Моля, подновете сървъра до последна версия и рестартирайте клиента - - - - Authentication error: Either username or password are wrong. - Грешка в удостоверяването: Потребителското име или парола са грешни. - - - - timeout - изчакване - - - - The provided credentials are not correct - Предоставените идентификационни данни са неправилни - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - Грешка в сървъра: Отговорът на PROPFIND не е във формат XML! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - Грешка при отваряне на директория % 1 - - - - Directory not accessible on client, permission denied - Директория не е достъпна за клиента, отказано е разрешение - - - - Directory not found: %1 - Директория не е намерена: %1 - - - - Filename encoding is not valid - Кодировката на файловото име не е валидна - - - - Error while reading directory %1 - Грешка при четене на директория %1 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - - - - - OCC::Folder - - - Local folder %1 does not exist. - Локалната папка %1 не съществува. - - - - %1 should be a folder but is not. - %1 би трябвало да е папка, но не е. - - - - %1 is not readable. - %1 не може да бъде прочетена. - - - - %1 has been removed. - %1 names a file. - %1 бе премахнат. - - - - %1 has been updated. - %1 names a file. - %1 бе обновен. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 бе преименуван на %2. - - - - %1 has been moved to %2. - %1 бе преместен в %2. - - - - %1 and %n other file(s) have been removed. - %1 и %n други файл(а) са премахнати.%1 и %n други файл(а) са премахнати. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - - - - - %1 has been added. - %1 names a file. - %1 е добавен. - - - - %1 and %n other file(s) have been updated. - %1 и %n други файл(а) са обновени.%1 и %n други файл(а) са обновени. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 е преименуван на %2 и %n други файл(а) са също преименувани.%1 е преименуван на %2 и %n други файл(а) са също преименувани. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 е преместен до %2 и %n други файл(а) са също преместени.%1 е преместен до %2 и %n други файл(а) са също преместени. - - - - %1 has and %n other file(s) have sync conflicts. - %1 и още %n други файл(а) имат конфликти при синхронизацията.%1 и още %n други файл(а) имат конфликти при синхронизацията. - - - - %1 has a sync conflict. Please check the conflict file! - %1 има конфликт при синхронизацията. Моля, проверете конфликта! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 и %n други файл(а) не са синхронизирани, защото имат грешки. Вижте доклада за допълнителна информация.%1 и %n други файл(а) не са синхронизирани защото имат грешки. Вижте доклада за допълнителна информация. - - - - %1 could not be synced due to an error. See the log for details. - %1 не успя да се синхронизира поради грешка. Разгледайте доклада за повече информация. - - - - Sync Activity - Активност на Синхронизацията - - - - Could not read system exclude file - Неуспешно прочитане на системния файл за пренебрегване на файлове. - - - - A new folder larger than %1 MB has been added: %2. - - Нова папка по-голяма от %1 МБ е добавена: %2. - - - - - A folder from an external storage has been added. - - Добавена е папка от външно дисково устройство. - - - - - Please go in the settings to select it if you wish to download it. - Моля, отидете в настройки за да изберете дали искате да го изтеглите. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - Папката %1 беше създадена, но преди това беше изключена от синхронизацията. Данните в нея няма да бъдат синхронизирани. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - Файлът %1 беше създаден, но преди това беше изключен от синхронизация. Той няма да бъде синхронизиран. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - Промените в синхронизираните папки не могат да бъдат проследени надеждно. - -Това означава, че клиентът за синхронизация може да не качва локалните промени веднага, а вместо това само ще сканира за локални промени и ще ги качва от време на време (на всеки два часа по подразбиране). - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Всички файлове в папката за синхронизиране '%1' бяха изтрити от сървъра. -Това действие е свързано и с вашата локална папка за синхронизиране, което прави тези файлове недостъпни, освен ако нямате право на възстановяване. -Ако решите да запазите файловете те ще бъдат синхронизирани отново със сървъра, ако имате права за това. -Но ако решите да изтриете файловете, то те няма да са достъпни за вас, освен ако не сте собственик. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Всички файлове в локалната папка за синхронизиране „%1“ бяха изтрити. Това е синхронизирано с вашия сървър, което прави тези файлове недостъпни, освен ако не бъдат възстановени. -Сигурни ли сте, че искате да синхронизирате тези действия със сървъра? -Ако това е инцидент и решите да запазите вашите файлове те ще бъдат синхронизирани отново от сървъра. - - - - Remove All Files? - Премахни Всички Файлове? - - - - Remove all files - Премахни всички файлове - - - - Keep files - Запази файловете - - - - OCC::FolderMan - - - Could not reset folder state - Неуспешно изчистване на състоянието на папката - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Открит е стар файл за синхронизирне %1, но не може да бъде премахнат. Моля уверете се, че никоя програма не го използва в момента. - - - - (backup) - (архив) - - - - (backup %1) - (архив %1) - - - - Undefined State. - Неопределено Състояние - - - - Waiting to start syncing. - Изчакване за започване на синхронизацията. - - - - Preparing for sync. - Подготовка за синхронизация - - - - Sync is running. - Синхронизирането работи. - - - - Sync was successful, unresolved conflicts. - Синхронизирането беше успешно, няма нерешени конфликти. - - - - Last Sync was successful. - Последната синхронизация е беше успешна. - - - - Setup Error. - Грешка в Настройките. - - - - User Abort. - Прекрати - - - - Sync is paused. - Синхронизирането е на пауза. - - - - %1 (Sync is paused) - %1 (Синхронизирането е на пауза) - - - - No valid folder selected! - Няма маркирана валидна папка! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - Избраният път не съществува! - - - - The selected path is not a folder! - Избраният път не е папка! - - - - You have no permission to write to the selected folder! - Вие нямате права да записвате в избраната папка! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Вече съществува синхронизиране между сървъра и тази локална папка. Моля, изберете друга локална папка! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Локалната папка %1 вече съдържа папка, която се синхронизирва. Моля изберете друга! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Локалната папка %1 вече се съдържа в папка, която участва в синхронизация. Моля изберете друга! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Добавяне на синхронизиция за папка - - - - File - Файл - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Трябва да сте свързан за да добавите папка - - - - Click this button to add a folder to synchronize. - Щракнете върху този бутон за да добавите папка за синхронизиране. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Грешка при зареждането на списъка с папки от сървъра. - - - - Virtual file support is enabled. - Поддръжката на виртуални файлове е активирана. - - - - Signed out - Излизане - - - - Fetching folder list from server... - Зареждане на списък с папки от сървъра... - - - - There are unresolved conflicts. Click for details. - Има нерешени конфликти. Кликнете за повече подробности. - - - - Checking for changes in remote '%1' - Проверка за промени в отдалечената „%1“ - - - - Checking for changes in local '%1' - Проверка за промени в локалната „%1“ - - - - Reconciling changes - Запис на промените - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Синхронизиране на %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - теглене с %1/s - - - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - качване с %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 от %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - още %5, %1 от %2, файл %3 от %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 от %2, файл %3 от %4 - - - - file %1 of %2 - файл %1 от %2 - - - - Waiting... - Изчакване... - - - - Waiting for %n other folder(s)... - Изчакване на %n други папка(и)...Изчакване на %n други папка(и)... - - - - Preparing to sync... - Подготовка за синхронизация... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - Наблюдението не получи тестово известие. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Този проблем обикновено се случва, когато наблюдението е прекъснато. Проверете често задавани въпроси за подробности. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Добавяне на синхронизираща връзка за папка - - - - Add Sync Connection - Добавяне на връзка за синхронизиране - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Натисни за избор на локална папка за синхронизиране. - - - - Enter the path to the local folder. - Въведи път до локалната папка. - - - - Select the source folder - Изберете начална папка - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Създаване на отдалечена папка - - - - Enter the name of the new folder to be created below '%1': - Напишете името на папката, която трябва да бъде създадена '%1': - - - - Folder was successfully created on %1. - Папката е успешно създадена на %1. - - - - Authentication failed accessing %1 - Неуспешна идентификация за достъп на %1 - - - - Failed to create the folder on %1. Please check manually. - Неуспех за създаване на папка на %1. Моля проверете ръчно. - - - - Failed to list a folder. Error: %1 - Неуспешно добавяне на папка в списъка. Грешка: %1 - - - - Choose this to sync the entire account - Изберете за да синхронизирате целия профил - - - - This folder is already being synced. - Тази папка вече е сред тези, които се синхронизират. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Вече синхронизирате <i>%1</i>, която е родителска папка на <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Внимание:</b> %1 - - - - <b>Warning:</b> - <b>Внимание:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Не е получен E-Tag от сървъра, провери Proxy/Gateway - - - - We received a different E-Tag for resuming. Retrying next time. - Получи се различен E-Tag от очаквания. Опитайте по-късно. - - - - We received an unexpected download Content-Length. - Получи се неочаквано изтегляне Content-Length. - - - - Server returned wrong content-range - Сървърът върна грешен списък със съдържание - - - - OCC::GETJob - - - Connection Timeout - Прескочен е времевият лимит за връзка - - - - OCC::GeneralSettings - - - Form - Формуляр - - - - General Settings - Общи Настройки - - - - Language - Език - - - - Sync hidden files - Синхронизирай скрити файлове - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Мрежа - - - - Updates - Обновления - - - - stable - стабилна - - - - beta - бета - - - - About - Относно - - - - Advanced - Разширени - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Изискване на потвърждение преди синхронизиране на папки по-големи от - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - МБ - - - - Ask for confirmation before synchronizing external storages - Изискване на потвърждение преди синхронизиране на външни дискови устройства - - - - &Launch on System Startup - &Стартирай при Стартиране на Системата - - - - Show &Desktop Notifications - Показване на &десктоп известия - - - - &Update Channel - & Актуализиране на канал - - - - Edit &Ignored Files - Редактиране на &игнорирани файлове - - - - Show sync folders in &Explorer's Navigation Pane - Показване на папки за синхронизация в &Explorer Навигационен Прозорец - - - - S&how crash reporter - П&оказване на доклад за грешки - - - - &Restart && Update - &Рестартирай && Обнови - - - - You cannot disable autostart because system-wide autostart is enabled. - Не можете да деактивирате автоматичното стартиране, тъй като автоматичният старт за цялата система е активиран. - - - - Change update channel? - Промяна на канала за актуализация? - - - - Warning - Предупреждение - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Каналът за актуализиране определя кои клиентски актуализации ще бъдат предложени за инсталиране. "Стабилният" канал съдържа само ъпгрейди, които се считат за надеждни, докато версиите в "бета" канала могат да съдържат по-нови функции и грешки, но все още не са тествани старателно. - -Обърнете внимание, че се избира само от това, от което са взети ъпгрейдите на пула, и че няма понижаване на скоростта: Така че връщането от бета канала към стабилния канал обикновено не може да се извърши веднага и означава да изчакате стабилна версия, която е по-нова от инсталираната понастоящем бета версия. - - - - Change update channel - Промяна на канала за актуализация - - - - Cancel - Отказ - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Моля, въведете %1 парола:<br><br>Потребител: %2<br>Акаунт: %3<br> - - - - Reading from keychain failed with error: '%1' - Четенето от ключовата верига се провали с грешка: '%1' - - - - Enter Password - Въведи Парола - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Натиснете тук</a>, за да изискате парола за приложението посредтвом уеб интерфейса. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Редактор за Игнорираните Файлове - - - - Files Ignored by Patterns - Игнорирани Файлове по Еталон - - - - Add - Добави - - - - Pattern - Еталон - - - - Allow Deletion - Позволяване на Изтриване - - - - Remove - Премахване - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Файлове или папки, съответстващи на даден шаблон, няма да бъдат синхронизирани. Промените влизат в сила следващия път, когато папките се синхронизират. - -Елементите, при които е разрешено изтриването, ще бъдат изтрити даже ако не е позволено премахването на директория. Това е полезно за мета данните. - - - - Could not open file - Неуспешно отваряне на файл - - - - Cannot write changes to '%1'. - Неуспешно записване на промените в '%1'. - - - - Add Ignore Pattern - Добави Шаблон за Игнориране - - - - Add a new ignore pattern: - Добави нов шаблон за игнориране: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Тази настройка е направена от системата в '%1' и не може да бъде промена на този екран. - - - - OCC::IssuesWidget - - - Form - Формуляр - - - - List of issues - Списък на изданията - - - - Filter - Филтър - - - - There were too many issues. Not all will be visible here. - Имаше твърде много проблеми. Не всички ще бъдат показани. - - - - There were conflicts. Check the documentation on how to resolve them. - Налични са конфликти. Проверете документацията как да ги разрешите. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Има конфликти. <a href="%1">Проверете документацията как да ги разрешите.</a> - - - - Account - Профил - - - - OCC::LibCloudProvidersPrivate - - - Settings - Настройки - - - - OCC::LogBrowser - - - Log Output - Доклада - - - - Enable logging to temporary folder - Активиране на записването във временна папка - - - - Delete logs older than %1 hours - Изтриване на дневници по-стари от %1 час - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Тези настройки продължават при рестартиране на клиента. -Обърнете внимание, че използването на опции на командния ред за регистриране ще отмени настройките. - - - - Open folder - Отваряне на папка - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Има Нова Версия - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Нова версия на %1 е на разположение .</p><p><b>%2</b> е готова за изтегляне. Инсталираната версия е %3.</p> - - - - - Skip this version - Прескочи тази версия - - - - Skip this time - Прескочи този път - - - - Get update - Обнови - - - - Update Failed - Актуализацията не е успешна - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Налична е нова версия на %1 Клиент, но процесът на актуализиране не бе успешен.</p><p><b>%2</b> беше изтеглена. Инсталираната версия е %3.</p> - - - - Ask again later - Попитай отново по-късно - - - - Restart and update - Рестарт и обновяване - - - - Update manually - Обновете ръчно - - - - OCC::NetworkSettings - - - Form - Формуляр - - - - Proxy Settings - Прокси Настройки - - - - No Proxy - Няма Прокси - - - - Use system proxy - Използвай системното прокси - - - - Specify proxy manually as - Уточни пркси саморъчно като - - - - Host - Host - - - - : - : - - - - Proxy server requires authentication - Прокси сървърът изисква оторизация - - - - Note: proxy settings have no effects for accounts on localhost - Забележка: Настройките на прокси сървъра нямат ефекти за акаунти в localhost - - - - Download Bandwidth - Скорост на Сваляне - - - - - Limit to - Ограничи до - - - - - KBytes/s - КБайта/с - - - - - No limit - Без Ограничение - - - - - Limit to 3/4 of estimated bandwidth - Ограничение до 3/4 от възможната скорост - - - - Upload Bandwidth - Скорост на Сваляне - - - - - Limit automatically - Ограничи автоматично - - - - Hostname of proxy server - Име на прокси сървъра - - - - Username for proxy server - Потребителско име за прокси сървъра - - - - Password for proxy server - Парола за прокси сървъра - - - - HTTP(S) proxy - HTTP(S) прокси - - - - SOCKS5 proxy - SOCKS5 прокси - - - - OCC::NotificationWidget - - - Created at %1 - Създаден на %1 - - - - Closing in a few seconds... - Затваряне след няколко секунди... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 заявка не бе успешна за %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' избра в %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Грешка върната от сървъра: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Възникна грешка при достъп с „маркера“ в края на процеса: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Не можа да се анализира въраният JSON от сървъра: <br><em>%1</em> - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Грешка при влизане</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Грешен потребител</h1><p>Влезли сте с потребител<em>%1</em>, но трябва да влезете с потребител<em>%2</em>.<br> Моля, излезте от %3 в друг раздел и след това <a href='%4'>щракнете тук</a> и влезте като потребител %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Новото %1 Обновление е готово - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Новото обновление за %1 е на път да бъде инсталирано. При обновяването -могат да бъдат поискани допълнителни превилегии по време на процеса. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Неуспешно изтегляне на обновлението. Моля, щракнете <a href='%1'>тук</a> за да го изтеглите ръчно. - - - - Could not check for new updates. - Неуспешна проверка за обновления. - - - - Downloading %1. Please wait... - Изтегля се %1. Изчакайте... - - - - %1 available. Restart application to start the update. - %1 е налично. Рестартирайте приложението за да стартирате актуализацията. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Ново %1 е налично. Моля, използвайте инструмента за актуализиране на системата за да го инсталирате. - - - - Checking update server... - Проверява се сървърът за обновления... - - - - Update status is unknown: Did not check for new updates. - Статус на обновяването неизвестен: Проверката за обновление не е извършена. - - - - No updates available. Your installation is at the latest version. - Няма нови обновления. Инсталацията е с най-новата версия. - - - - Update Check - Провери за Обновления - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Свържи се с %1 - - - - Setup local folder options - Задаване на опции за локална папка - - - - Connect... - Свързване... - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Синхронизиране на папка '%1' - - - - Virtual files are not available for the selected folder - - - - - Error - Грешка - - - - Local Sync Folder - Локална Папка за Синхронизиране - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Имейл - - - - Connect to %1 - Свързване с %1 - - - - Enter user credentials - Въведете потребителско име и парола - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Свързване с %1 - - - - Login in your browser - Влизане със вашия браузър - - - - Copy link to clipboard - Копирайте връзката в клипборда - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Свързване с %1 - - - - Setup %1 server - Настройка на сървър %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Този интернет адрес НЕ е сигурен защото не е криптиран. -Не се препоръчва да го използвате. - - - - This url is secure. You can use it. - Интернет адресът е сигурен. Можете да го използвате. - - - - &Next > - &Напред > - - - - Invalid URL - Невалиден URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Неуспешно свързване с %1 на %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Изтече времето за свързване към to %1 на %2. - - - - Invalid URL - Невалиден URL - - - - Could not create local folder %1 - Неуспешно създаване на локална папка %1 - - - - Error: %1 - Грешка: %1 - - - - The folder creation resulted in HTTP error code %1 - Създаването на папката доведе до HTTP грешка с код %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Създаването на отдалечена папка не бе успешно, защото предоставените идентификационни данни са грешни! <br/>Моля, върнете се и проверете идентификационните си данни.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Създаването на отдалечена папка % ю1 не бе успешно с грешка <tt>%2</tt>. - - - - Folder rename failed - Неуспешно преименуване на папка - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Неуспешно премахване и създаване на резервно копие на папката защото тя или файл в нея са отворени в друга програма. Моля затворете папката или файла и изберете опцията за повторен опит, или го откажете. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Съветник за връзка - - - - Enable experimental feature? - Да се активира експерименталната функция? - - - - Enable experimental placeholder mode - Активиране на експериментален режим на заместване - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Когато режимът "виртуални файлове" е активиран първоначално няма да бъдат изтегляни файлове. Вместо това ще бъде създаден малък "%1" файл за всеки файл, който съществува на сървъра. Съдържанието може да бъде изтеглено чрез стартиране на тези файлове или чрез тяхното контекстно меню. - -Режимът на виртуалните файлове е взаимно се изключва със селективното синхронизиране. Понастоящем неизбраните папки ще бъдат преведени в онлайн папки и вашите настройки за селективно синхронизиране ще бъдат нулирани. - -Преминаването към този режим ще прекрати всяка текуща синхронизация. - -Това е нов експериментален режим. Ако решите да го използвате, моля докладвайте ако има възникнали проблеми. - - - - Stay safe - Само безопасно - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Symbolic връзките не се подържат при синхронизация. - - - - File is listed on the ignore list. - Файлът е посочен в списъка за игнориране. - - - - File names ending with a period are not supported on this file system. - Имена на файлове завършващи с точка не се поддържат от тази файлова система. - - - - File names containing the character '%1' are not supported on this file system. - Имена на файлове съдържащи символа '%1' не се поддържат от тази файлова система. - - - - File name contains at least one invalid character - Името на файла съдържа поне един невалиден символ - - - - The file name is a reserved name on this file system. - Името на файла е резервирано от тази файлова система. - - - - Filename contains trailing spaces. - Името на файла завършва с празни интервали. - - - - Filename is too long. - Името на файла е прекалено дълго. - - - - File/Folder is ignored because it's hidden. - Файл/Папка са игнорирани, защото са скрити. - - - - Stat failed. - Неуспешно извличане на статистика. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Конфликт: Версията на сървъра изтегли локалното копие и е преименувано, но не е качено. - - - - The filename cannot be encoded on your file system. - Името на файла не може да бъде декодирано от вашата файлова система. - - - - The filename is blacklisted on the server. - Името на файла е в черния списък на сървъра. - - - - size - размер - - - - permissions - разрешения - - - - etag - etag - - - - file id - файл id - - - - server reported no %1 - сървърът отчете, че няма %1 - - - - Moved to invalid target, restoring - Преместено в невалидна цел, възстановяване - - - - Not allowed to remove, restoring - Няма разрешение за премахване, възстановяване - - - - Ignored because of the "choose what to sync" blacklist - Пренебрегнато е заради черния списък „изберете какво да синхронизирате“ - - - - Not allowed because you don't have permission to add subfolders to that folder - Не е позволено защото нямате право да добавяте подпапки в тази папка. - - - - Not allowed because you don't have permission to add files in that folder - Не е позволено защото нямате право да добавяте файлове в тази папка. - - - - Not allowed to upload this file because it is read-only on the server, restoring - Нямате разрешение да качвате този файл защото е само с разрешения за четене на този сървър, възстановяване - - - - Error while reading the database - Грешка при четене на базата данни - - - - Server replied with an error while reading directory '%1' : %2 - Сървърът отговори с грешка докато чете директорията "%1" : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - Изтеглянето ще намали свободното дисково пространство под ограничението - - - - Free space on disk is less than %1 - Свободното място на диска е по-малко от %1 - - - - File was deleted from server - Файлът е изтрит от сървъра - - - - The file could not be downloaded completely. - Файлът не може да бъде свален напълно. - - - - The downloaded file is empty despite the server announced it should have been %1. - Изтегленият файл е празен, въпреки че сървърът заяви, че той би трябвало да е %1. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - Файлът е променен от последната проверка - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Неуспешно Възстановяване: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - неуспешно изтриване на файл %1, грешка: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - неуспешно създаване на папка %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Файл %1 не може да бъде преименуван на %2 поради сблъсък с името на локален файл - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - Грешка при настройка на пин определението - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Върнат е грешен HTTP код от сървъра. Очакваше се 204, но е получен "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Върнат е грешен HTTP код от сървъра. Очакваше се 201, но е получен "%1 %2". - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - Не можа да се преименува %1 на %2, грешка: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Върнат е грешен HTTP код от сървъра. Очакваше се 201, но е получен "%1 %2". - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - Грешка при запазване на мета информация в базата данни - - - - Error setting pin state - Грешка при настройка на пин определението - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Файл %1 не може да бъде качен, тъй като съществува друг файл със същото име, разликата е само, че съществува - - - - File Removed - Файлът е премахнат - - - - Local file changed during syncing. It will be resumed. - Локалният файл бе променен по време на синхронизация. Той ще бъде обновен. - - - - Local file changed during sync. - Локалният файл се промени по време на синхронизацията. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - Качването на %1 надвишава квотата за папката - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Локалния файл бе премахнат по време на синхронизацията. - - - - Local file changed during sync. - Локалният файл се промени по време на синхронизацията. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - Неочакван отговор от сървъра (%1) - - - - Missing File ID from server - Липсващ File ID от сървъра - - - - Missing ETag from server - Липсващ ETag от сървъра - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Локалния файл бе премахнат по време на синхронизацията. - - - - Local file changed during sync. - Локалният файл се промени по време на синхронизацията. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - Локалния файл бе премахнат по време на синхронизацията. - - - - Local file changed during sync. - Локалният файл се промени по време на синхронизацията. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Сървърът не потвърди последното парче. (Липсва e-tag) - - - - OCC::ProtocolItemModel - - - Time - Време - - - - File - Файл - - - - Folder - Папка - - - - Issues - - - - - Action - Действие - - - - Size - Размер - - - - Account - Профил - - - - Status - Статус - - - - OCC::ProtocolWidget - - - Form - Формуляр - - - - Local sync protocol - Протокол за локална синхронизация - - - - Filter - Филтър - - - - Account - Профил - - - - Copy to clipboard - Копирай - - - - Show in file browser - - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Изисква се прокси идентификация - - - - Username: - Потребител: - - - - Proxy: - Прокси: - - - - The proxy server needs a username and password. - Прокси сървъра се нуждае от потребителско име и парола. - - - - Password: - Парола: - - - - TextLabel - Текстов етикет - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Изберете какво да синхронизирате - - - - OCC::SelectiveSyncWidget - - - Loading ... - Зареждане ... - - - - Deselect remote folders you do not wish to synchronize. - Премахнете избора на папки, които не желаете да синхронизирате. - - - - Name - Име - - - - Size - Размер - - - - - No subfolders currently on the server. - В момента липсват подпапки на сървъра. - - - - An error occurred while loading the list of sub folders. - Настъпи грешка при зареждането на списъка с подпапки. - - - - OCC::ServerNotificationHandler - - - Dismiss - Затвори - - - - OCC::SettingsDialog - - - Settings - Настройки - - - - Activity - Активност - - - - Add account - Добавяне на профил - - - - - Quit %1 - Затвори %1 - - - - Are you sure you want to quit %1? - Наистина ли искате да излезете от %1? - - - - Account - Профил - - - - MainWindow - - - - - Hide - Скриване - - - - OCC::ShareDialog - - - TextLabel - Текстов етикет - - - - share label - етикет на споделяне - - - - Dialog - Диалог - - - - ownCloud Path: - OwnCloud път: - - - - %1 Sharing - %1 Споделяне - - - - %1 - %1 - - - - Folder: %2 - Папка: %2 - - - - The server does not allow sharing - Сървърът не разрешава споделяне - - - - Retrieving maximum possible sharing permissions from server... - Изтегляне на максималното разрешение за споделяне от сървъра... - - - - The file can not be shared because it was shared without sharing permission. - Файлът не може да споделен защото се иска споделяне без това право. - - - - Users and Groups - Потребители и Групи - - - - Public Links - Публични връзки - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Споделяне на NewDocument.odt - - - - Upload only (File Drop) - Само качване (пускане на файл) - - - - Receive files from others without revealing the contents of the folder. - Получавайте файлове от други, без да разкривате съдържанието на папката. - - - - Download / View / Edit - Изтегляне / Преглед / Промяна - - - - Name: - Име: - - - - Enter name of new share... - Въведете име за новото споделяне... - - - - &Create - &Създаване - - - - TextLabel - Текстов етикет - - - - Set &password - Задаване на &парола - - - - Set &expiration date - Посочи &дата на изтичане - - - - Set password - Задаване на парола - - - - Recipients can view or download contents. - Получателите могат да преглеждат или изтеглят съдържанието. - - - - Download / View - Изтегляне / Преглед - - - - Recipients can view, download, edit, delete and upload contents. - Получателите могат да преглеждат, изтеглят, редактират, изтриват и качват съдържание. - - - - Anyone with the link has access to the file/folder - Всеки знаещ тази връзка ще има достъп до файл/папка - - - - - P&assword protect - З&ащитено с парола - - - - The file can not be shared because it was shared without sharing permission. - Файлът не може да споделен защото се иска споделяне без да има това право. - - - - Link shares have been disabled - Споделянето с връзки е деактивирано - - - - Create public link share - Създаване на публична връзка за споделяне - - - - - Delete - Изтриване - - - - Open link in browser - Отвори линка в браузър - - - - Copy link to clipboard - Копирайте връзката в клипборда - - - - Copy link to clipboard (direct download) - Копиране на връзката в клипборда (директно изтегляне) - - - - Send link by email - Изпрати връзката по имейл - - - - Send link by email (direct download) - Изпрати връзката по имейл (директно изтегляне) - - - - Create new... - Създаване на нов... - - - - I shared %1 with you - Споделиха %1 с вас - - - - Confirm Link Share Deletion - Потвърдете изтриването на връзката за споделяне - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Наистина ли искате да изтриете обществената връзка за споделяне <i>%1</i>?</p><p> Забележка: Това действие не може да бъде отменено.</p> - - - - Cancel - Отказ - - - - - Public link - Публичнa връзкa - - - - Delete link share - Изтриване на връзка за споделяне - - - - - Please Set Password - Моля, задайте парола - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Споделяне на NewDocument.odt - - - - Share with users or groups ... - Споделяне с потребители и групи... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - Елементът не се споделя с никакви потребители или групи - - - - Open link in browser - Отвори линка в браузър - - - - Copy link to clipboard - Копирайте връзката в клипборда - - - - Send link by email - Изпрати връзката по имейл - - - - No results for '%1' - Няма резултати за '%1' - - - - I shared something with you - Споделиха нещо с теб - - - - OCC::ShareUserLine - - - Form - Формуляр - - - - - TextLabel - TextLabel - - - - can edit - може да променя - - - - can share - може да споделя - - - - ... - ... - - - - create - създаване - - - - change - промяна - - - - delete - изтриване - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Споделяне с %1 - - - - Context menu share - Контекстно меню за споделяне - - - - Context menu share %1 - Контекстно меню за споделяне %1 - - - - Confirm deletion - Потвърдете изтриването - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Искате ли да изтриете директорията <i>%1</i> и цялото й съдържание за постоянно? - - - - Do you want to delete the file <i>%1</i> permanently? - Искате ли да изтриете файла <i>%1</i> за постоянно? - - - - Select new location... - Изберете ново местоположение ... - - - - Error - Грешка - - - - Moving file failed: - -%1 - Преместването на файла не бе успешно: - -%1 - - - - I shared something with you - Споделиха нещо с теб - - - - - Share... - Споделяне... - - - - - Copy private link to clipboard - Копиране на поверителната връзка в клипборда - - - - Send private link by email... - Изпращане на поверителната връзка чрез имейл... - - - - Resharing this file is not allowed - Повторното споделяне на този файл не е позволено - - - - Resharing this folder is not allowed - Повторното споделяне на тази папка не е позволено - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - Копиране на публичната връзка в клипборда - - - - Open in browser - Отваряне в браузър - - - - Show file versions in browser - Показване на версиите на файла в браузъра - - - - Rename... - Преименуване - - - - Rename and upload... - Преименуване и качване... - - - - Move and rename... - Преместване и преименуване... - - - - Move, rename and upload... - Преместване, преименуване и качване... - - - - Delete local changes - Изтриване на локалните промени - - - - Move and upload... - Преместване и качване... - - - - Delete - Изтриване - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Информация за Сертификата</h3> - - - - Common Name (CN): - Common Name (CN): - - - - Subject Alternative Names: - Subject Alternative Names: - - - - Organization (O): - Организация (O): - - - - Organizational Unit (OU): - Отдел (OU): - - - - State/Province: - Област/Община: - - - - Country: - Държава: - - - - Serial: - Сериен Номер: - - - - <h3>Issuer</h3> - <h3>Издател</h3> - - - - Issuer: - Издател: - - - - Issued on: - Издаден на: - - - - Expires on: - Изтича на: - - - - <h3>Fingerprints</h3> - <h3>Отпечатъци</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Бележка:</b> Този сертификат е бил ръчно одобрен</p> - - - - %1 (self-signed) - %1 (самоподписан) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Тази връзка е криптирана използвайки %1 битово %2. - - - - - Server version: %1 - Версия на сървъра: %1 - - - - No support for SSL session tickets/identifiers - Не се подържат SSL сесийни билети/идентификатори - - - - Certificate information: - Информация за сертификата: - - - - The connection is not secure - Връзката не е защитена - - - - This connection is NOT secure as it is not encrypted. - - Тази връзка НЕ Е сигурна, тъй като не е криптирана. - - - - - OCC::SslErrorDialog - - - Form - Формуляр - - - - Trust this certificate anyway - Довери се на сертификата въпреки това - - - - Untrusted Certificate - Ненадежден сертификат - - - - Cannot connect securely to <i>%1</i>: - Неуспешно установяване на сигурна връзка с <i>%1</i>: - - - - with Certificate %1 - със Сертификат %1 - - - - - - &lt;not specified&gt; - &lt;непосочен&gt; - - - - - Organization: %1 - Организация: %1 - - - - - Unit: %1 - Отдел: %1 - - - - - Country: %1 - Държава: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Fingerprint (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Fingerprint (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Валиден от: %1 - - - - Expiration Date: %1 - Дата на изтичане: %1 - - - - Issuer: %1 - Издател: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (пропусна поради по-ранна грешка, опита се отоново в %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - На разположение са само %1, минимум %2 са нужни - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Не може да се отвори или създаде локална база данни за синхронизация. Уверете се, че имате достъп за запис в папката за синхронизиране. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Дисковото пространство е малко: Ограничени са изтеглянията, които биха намалили свободното пространство под %1. - - - - There is insufficient space available on the server for some uploads. - На сървъра няма достатъчно място за някои качвания. - - - - Unresolved conflict. - Нерешен конфликт. - - - - Using virtual files with suffix, but suffix is not set - Използване на виртуални файлове със суфикс, но суфиксът не е зададен - - - - Unable to read the blacklist from the local database - Не може да се прочете черния списък от локалната база данни - - - - Unable to read from the sync journal. - Не може да се прочете дневника за синхронизиране. - - - - Cannot open the sync journal - Не може да се отвори дневника за синхронизиране - - - - Aborted - Преустановено - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Заглавката на контролната сума е неправилно оформена. - - - - The checksum header contained an unknown checksum type '%1' - Заглавката на контролната сума съдържа неизвестен тип контролна сума '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Моля, влезте в профила си - - - - Folder %1: %2 - Папка %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - Няма конфигурирани папки за синхронизиране. - - - - Open in browser - Отваряне в браузър - - - - - - Log in... - Вписване... - - - - - - Log out - Отписване - - - - Recent Changes - Последни Промени - - - - Managed Folders: - Управлявани Папки: - - - - Open folder '%1' - Отвори папка '%1' - - - - Open %1 in browser - Отвори %1 в браузър - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - Сървърът в акаунт %1 изпълнява неподдържаната версия %2. Използването на този клиент с неподдържани версии на сървъра е непроверено и потенциално опасно. Продължете на свой риск. - - - - Unknown status - Неизвестно състояние - - - - Settings... - Настройки... - - - - Details... - Детайли... - - - - Help - Помощ - - - - Quit %1 - Затвори %1 - - - - Disconnected from %1 - Преустановена връзка с %1 - - - - Unsupported Server Version - Не поддържана сървърна версия - - - - Disconnected - Несвързан - - - - Disconnected from some accounts - Преустановена връзка от някои профили - - - - Disconnected from accounts: - Преустановена връзка от профилите: - - - - Account %1: %2 - Профил %1: %2 - - - - Signed out - Излизане - - - - Account synchronization is disabled - Синхронизирането на профила е деактивирано - - - - Synchronization is paused - Синхронизирането е на пауза - - - - Up to date (%1) - - - - - No sync folders configured - Няма конфигурирани папки за синхронизация - - - - Unpause all folders - Отмяна на паузата за всички папки - - - - Pause all folders - Пауза на всички папки - - - - Unpause all synchronization - Отмяна на паузата на синхронизация - - - - Unpause synchronization - Отмяна на паузата за синхронизация - - - - Pause all synchronization - Пауза на цялата синхронизация - - - - Pause synchronization - Пауза на синхронизацията - - - - Log out of all accounts - Отписване от всички профили - - - - Log in to all accounts... - Вписване във всички профили... - - - - New account... - Нов профил... - - - - About %1 - Около %1 - - - - No items synced recently - Няма скоро синхронизирани файлове - - - - Checking for changes in remote '%1' - Проверка за промени в отдалечената „%1“ - - - - Checking for changes in local '%1' - Проверка за промени в локалната „%1“ - - - - Syncing %1 of %2 (%3 left) - Синхронизиране на %1 от %2 (остават %3) - - - - Syncing %1 of %2 - Синхронизиране на %1 от %2 - - - - Syncing %1 (%2 left) - Синхронизиране на %1 (остават %2) - - - - Syncing %1 - Синхронизиране на %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - Формуляр - - - - - TextLabel - Текстов етикет - - - - Server - Сървър - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Ако тази опция е избрана текущото съдържание на локалната папка ще бъде изтрито за да започне от начало синхронизация със сървъра.</p><p>Не избирайте тази опция ако искате локалното съдържание да бъде качено в папката в сървъра.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - Изискване на потвърждение преди сигнхронизиране на папки по-големи от - - - - S&ynchronize everything from server - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - МБ - - - - Ask for confirmation before synchronizing e&xternal storages - Изискване на потвърждение преди синхронизиране на външни дискови устройства - - - - Choose what to sync - Избери какво да се синхронизира - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Когато бъде избрана тази опция, съветникът ще се затвори, без да се синхронизира нищо. Можете да използвате &quot;Добавете връзка за синхронизиране на папки&quot; от настройките на акаунта за да изберете коя локална и отдалечена папка искате да синхронизирате</p></body></html> - - - - Manually create folder sync connections - Ръчно създаване връзки за синхронизиране на папки - - - - &Local Folder - &Локална Папка - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Запази локалните данни - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - Формуляр - - - - &Username - &Потребителско Име - - - - &Password - &Парола - - - - OwncloudOAuthCredsPage - - - Form - Формуляр - - - - Please switch to your browser to proceed. - Моля отидете в браузъра за да продължите. - - - - An error occurred while connecting. Please try again. - При свързването възникна грешка. Моля, опитайте отново. - - - - Re-open Browser - Отворете отново браузъра - - - - OwncloudSetupPage - - - Form - Формуляр - - - - Ser&ver Address - Сървърен адрес - - - - https://... - https://... - - - - Error Label - Грешен етикет - - - - QObject - - - in the future - в бъдеще - - - - %n day(s) ago - преди %n ден(а) преди %n ден(а) - - - - %n hour(s) ago - преди %n час(а) преди %n час(а) - - - - now - сега - - - - Less than a minute ago - Преди по-малко от минута - - - - %n minute(s) ago - преди %n минута(и) преди %n минута(и) - - - - Some time ago - Преди време - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - - - - - - Error - Грешка - - - - Info - Информация - - - - Success - Успех - - - - Conflict - Конфликт - - - - File Ignored - - - - - Restored - - - - - Blacklisted - - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 ГБ - - - - %L1 MB - %L1 МБ - - - - %L1 KB - %L1 КБ - - - - %L1 B - %L1 Б - - - - %n year(s) - преди %n година(и) преди %n година(и) - - - - %n month(s) - преди %n месеца(и) преди %n месеца(и) - - - - %n day(s) - %n ден(а) %n ден(а) - - - - %n hour(s) - %n час(а) %n час(а) - - - - %n minute(s) - %n минута(и) %n минута(и) - - - - %n second(s) - %n секунда(и) %n секунда(и) - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Липсва Системна Лента - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 изисква работещ системна лента. Ако използвате XFCE, моля следвайте <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">тези инструкции</a>. В противен случай инсталирайте приложение за системна лента, като 'trayer' и опитайте отново. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - Виртуалният файл е създаден - - - - Replaced by virtual file - Заменен с виртуален файл - - - - Downloaded - Изтеглени - - - - Uploaded - Качени - - - - Server version downloaded, copied changed local file into conflict file - Изтеглена е сървърната версия, копиран и променен е локалният файл към конфликтният файл - - - - Deleted - Изтрити - - - - Moved to %1 - Преместен в %1 - - - - Ignored - Игнориран - - - - Filesystem access error - Грешка при достъп на файловата система - - - - Error - Грешка - - - - Updated local metadata - Обновена локална мета информация - - - - - Unknown - Неизвестно - - - - downloading - теглене - - - - uploading - качване - - - - deleting - изтриване - - - - moving - преместване - - - - ignoring - игнориране - - - - - error - грешка - - - - updating local metadata - обновяване на локалната мета информация - - - - theme - - - Status undefined - Състояние неопределено - - - - Waiting to start sync - Изчкване за начало на синхронизацията... - - - - Sync is running - Синхронизаране - - - - Sync Success - Синхронизацията Успешна - - - - Sync Success, some files were ignored. - Успешна синхронизация, някои файлове бяха игнорирани. - - - - Sync Error - Грешка при Синхронизацията - - - - Setup Error - Грешка в Настройките - - - - Preparing to sync - Подготовка за синхронизация - - - - Aborting... - Прекратяване... - - - - Sync is paused - Синхронизирането е на пауза. - - - - utility - - - Could not open browser - Браузърът не можа да се отвори - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - При стартирането на браузъра възникна грешка, за да отидете на URL %1. Може би не е конфигуриран браузър по подразбиране? - - - - Could not open email client - Неуспешно отваряне на имейл приложение - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Възникна грешка при стартиране на имейл приложение за създаване на съобщение. Може би липсва зададено имейл приложение по подразбиране? - - - - Always available locally - - - - - Some available online only - Винаги достъпно локално - - - - - Available online only - Достъпно само онлайн - - - - Currently available locally - В момента е достъпно локално - - - - Make always available locally - Изберете винаги да е достъпно локално - - - - Free up local space - Освободете локалното пространство - - - \ No newline at end of file diff --git a/translations/client_ca.ts b/translations/client_ca.ts deleted file mode 100644 index 348001f3e6b..00000000000 --- a/translations/client_ca.ts +++ /dev/null @@ -1,4426 +0,0 @@ - - - FileSystem - - - Could not make directories in trash - - - - - - Could not move '%1' to '%2' - - - - - Moving to the trash is not implemented on this platform - - - - - FolderWizardSourcePage - - - Form - Formulari - - - - Pick a local folder on your computer to sync - Escolliu una carpeta local en el vostre equip per sincronitzar-la - - - - &Choose... - &Escull... - - - - FolderWizardTargetPage - - - Form - Formulari - - - - Select a remote destination folder - Seleccioneu una carpeta de destinació remota - - - - Create Folder - Crea una carpeta - - - - Refresh - Refresca - - - - Folders - Carpetes: - - - - TextLabel - TextLabel - - - - NotificationWidget - - - Form - Formulari - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - TextLabel - - - - OCC::AbstractNetworkJob - - - Connection timed out - Temps d'espera de la connexió esgotat. - - - - Unknown error: network reply was deleted - - - - - Server replied "%1 %2" to "%3 %4" - - - - - OCC::AccountSettings - - - Form - Formulari - - - - ... - ... - - - - Storage space: ... - Espai d'emmagatzematge: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Les carpetes desmarcades <b>s'eliminaran</b> del vostre sistema de fitxers local i ja no es sincronitzaran en aquest ordinador - - - - Synchronize all - Sincronitza-ho tot - - - - Synchronize none - No sincronitzis res - - - - Apply manual changes - Aplica els canvis manuals - - - - Apply - Aplica - - - - - - Cancel - Cancel·la - - - - Connected with <server> as <user> - Connectat amb <server> com a <user> - - - - No account configured. - No hi ha cap compte configurat - - - - Add new - Afegeix-ne un de nou - - - - Remove - Elimina - - - - Account - Compte - - - - Choose what to sync - Trieu què sincronitzar - - - - Force sync now - Força la sincronització ara - - - - Restart sync - Reinicia la sincronització - - - - Remove folder sync connection - Elimina la connexió de la carpeta sincronitzada - - - - Create virtual files for new files (Experimental) - - - - - Folder creation failed - Ha fallat la creació de la carpeta - - - - <p>Could not create local folder <i>%1</i>. - <p>No s'ha pogut crear la carpeta local <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Confirma l'eliminació de la connexió de la carpeta sincronitzada - - - - Remove Folder Sync Connection - Elimina la connexió de la carpeta sincronitzada - - - - Sync Running - S'està sincronitzant - - - - The syncing operation is running.<br/>Do you want to terminate it? - S'està sincronitzant.<br/>Voleu parar-la? - - - - %1 in use - %1 en ús - - - - %1 as <i>%2</i> - %1 com a <i>%2</i> - - - - Connected to %1. - Connectat a %1 - - - - Server %1 is temporarily unavailable. - El servidor %1 no està disponible temporalment - - - - Server %1 is currently in maintenance mode. - - - - - Signed out from %1. - S'ha desconnectat de %1. - - - - Obtaining authorization from the browser. <a href='%1'>Click here</a> to re-open the browser. - - - - - Connecting to %1... - - - - - No connection to %1 at %2. - No hi ha connexió amb %1 a %2. - - - - Log in - Inici de sessió - - - - There are folders that were not synchronized because they are too big: - Hi ha carpetes que no s'han sincronitzat perquè són massa grans: - - - - There are folders that were not synchronized because they are external storages: - Hi ha carpetes que no s'han sincronitzat perquè són fonts d'emmagatzematge externes: - - - - There are folders that were not synchronized because they are too big or external storages: - Hi ha carpetes que no s'han sincronitzat perquè són massa grans o són fonts d'emmagatzematge externes: - - - - Confirm Account Removal - Confirmeu l'eliminació del compte - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Segur que voleu eliminar la connexió al compte <i>%1</i>?</p><p><b>Nota:</b> això <b>no</b> esborrarà cap fitxer.</p> - - - - Remove connection - Elimina la connexió - - - - - Open folder - Obre la carpeta - - - - - Log out - Surt - - - - Resume sync - Continua la sincronització - - - - Pause sync - Pausa la sincronització - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Segur que voleu aturar la sincronització de la carpeta <i>%1</i>?</p><p><b>Nota:</b> això <b>no</b> esborrarà cap fitxer.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) de %2 en ús. Algunes carpetes, incloent les carpetes muntades a través de xarxa o les compartides, poden tenir límits diferents. - - - - %1 of %2 in use - %1 de %2 en ús - - - - Currently there is no storage usage information available. - Actualment no hi ha informació disponible de l'ús d'emmagatzemament. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - No %1 connection configured. - La connexió %1 no està configurada. - - - - OCC::AccountState - - - Signed out - S'ha desconnectat - - - - Disconnected - Desconnectat - - - - Connected - Connectat - - - - Service unavailable - Servei no disponible - - - - Maintenance mode - Mode de manteniment - - - - Network error - Error de xarxa - - - - Configuration error - Error de configuració - - - - Asking Credentials - - - - - Unknown account state - Estat del compte desconegut - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 a %2 - - - - %1 on %2 (disconnected) - %1 a %2 (desconnectat) - - - - OCC::ActivitySettings - - - - Server Activity - Activitat del servidor - - - - Sync Protocol - Protocol de sincronització - - - - Not Synced - No sincronitzat - - - - Not Synced (%1) - %1 is the number of not synced files. - No sincronitzat (%1) - - - - The server activity list has been copied to the clipboard. - La llista de l'activitat del servidor s'ha copiat al porta-retalls. - - - - The sync activity list has been copied to the clipboard. - La llista d'activitat de sincronització s'ha copiat al porta-retalls - - - - The list of unsynced items has been copied to the clipboard. - S'ha copiat una llista d'elements no sincronitzats al porta-retalls. - - - - Copied to clipboard - S'ha copiat al porta-retalls - - - - OCC::ActivityWidget - - - Form - Formulari - - - - - - TextLabel - TextLabel - - - - Server Activities - Activitats del servidor - - - - Copy - Còpia - - - - Copy the activity list to the clipboard. - Copia la llista d'activitats al porta-retalls. - - - - Action Required: Notifications - Es requereix una acció: Notificacions - - - - <br/>Account %1 does not have activities enabled. - <br/>El compte %1 no té les activitats habilitades. - - - - You received %n new notification(s) from %2. - Heu rebut %n nova notificació de %2.Heu rebut %n noves notificacions de %2. - - - - You received %n new notification(s) from %1 and %2. - Heu rebut %n nova notificació de %1 i %2.Heu rebut %n noves notificacions de %1 i %2. - - - - You received new notifications from %1, %2 and other accounts. - Heu rebut noves notificacions de %1, %2 i altres comptes. - - - - %1 Notifications - Action Required - %1 notificacions - Es requereix una acció - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Autenticació del certificat de client SSL - - - - This server probably requires a SSL client certificate. - Aquest servidor probablement requereix un certificat de client SSL. - - - - Certificate & Key (pkcs12) : - Certificat i clau (pkcs12) - - - - Browse... - Navega... - - - - Certificate password : - Contrasenya del certificat : - - - - Select a certificate - Selecciona un certificat - - - - Certificate files (*.p12 *.pfx) - Fitxers de certificats (*.p12 *pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Surt - - - - Continue - Continua - - - - Error accessing the configuration file - S'ha produït un error en accedir al fitxer de configuració - - - - There was an error while accessing the configuration file at %1. - Hi ha hagut un error en accedir al fitxer de configuració a %1. - - - - Quit ownCloud - Surt de l'ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - Es requereix autenticació - - - - Enter username and password for '%1' at %2. - Introduir nom d'usuari i paraula de pas per '%1' a %2 - - - - &User: - &Usuari: - - - - &Password: - &Contrasenya: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Error en escriure les metadades a la base de dades - - - - OCC::ConnectionValidator - - - No ownCloud account configured - No hi ha cap compte d'OwnCloud configurat - - - - The configured server for this client is too old - El servidor configurat per aquest client és massa antic - - - - Please update to the latest server and restart the client. - Actualitzeu el servidor a l'última versió i reestabliu el client. - - - - Authentication error: Either username or password are wrong. - Error d'autentificació: Aquest nom d'usuari o contrasenya son incorrectes. - - - - timeout - temps excedit - - - - The provided credentials are not correct - Les credencials proporcionades no són correctes - - - - OCC::DiscoveryMainThread - - - Aborted by the user - Aturat per l'usuari - - - - OCC::DiscoverySingleDirectoryJob - - - The server file discovery reply is missing data. - - - - - OCC::Folder - - - Local folder %1 does not exist. - El fitxer local %1 no existeix. - - - - %1 should be a folder but is not. - %1 hauria de ser una carpeta, però no ho és. - - - - %1 is not readable. - No es pot llegir %1. - - - - %1 has been removed. - %1 names a file. - S'ha esborrat '%1' - - - - %1 has been downloaded. - %1 names a file. - S'ha descarregat %1 - - - - %1 has been updated. - %1 names a file. - S'ha actualitzat %1 - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 s'ha reanomenat a %2. - - - - %1 has been moved to %2. - %1 s'ha mogut a %2. - - - - %1 and %n other file(s) have been removed. - %1 i %n altre fitxer s'ha esborrat.%1 i %n altres fitxers s'han esborrat. - - - - %1 and %n other file(s) have been downloaded. - %1 i %n altre fitxer s'han descarregat.%1 i %n altres fitxers s'han descarregat. - - - - %1 and %n other file(s) have been updated. - %1 i %n altre fitxer s'han actualitzat.%1 i %n altres fitxers s'han actualitzat. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 s'ha reanomenat a %2 i %n altre fitxer s'ha reanomenat.%1 s'ha reanomenat a %2 i %n altres fitxers s'han reanomenat. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 s'ha mogut a %2 i %n altre fitxer s'ha mogut.%1 s'ha mogut a %2 i %n altres fitxers s'han mogut. - - - - %1 has and %n other file(s) have sync conflicts. - %1 i %n altre fitxer tenen conflictes de sincronització%1 i %n altres fitxers tenen conflictes de sincronització - - - - %1 has a sync conflict. Please check the conflict file! - %1 té conflictes de sincronització. Comproveu el fitxer conflictiu! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 i %n altre fitxer no s'han sincronitzat per errors. -Consulteu el registre per obtenir més informació.%1 i %n altres fitxers no s'han sincronitzat per errors. -Consulteu el registre per obtenir més informació. - - - - %1 could not be synced due to an error. See the log for details. - %1 no s'ha pogut sincronitzar degut a un error. Mireu el registre per més detalls. - - - - Sync Activity - Activitat de sincronització - - - - Could not read system exclude file - No s'ha pogut llegir el fitxer d'exclusió del sistema - - - - A new folder larger than %1 MB has been added: %2. - - S'ha afegit una carpeta de més de %1 MB: %2. - - - - - A folder from an external storage has been added. - - S'ha afegit una carpeta d'una font d'emmagatzematge extern. - - - - - Please go in the settings to select it if you wish to download it. - - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - Esborra tots els fitxers? - - - - Remove all files - Esborra tots els fitxers - - - - Keep files - Mantén els fitxers - - - - This sync would reset the files to an earlier time in the sync folder '%1'. -This might be because a backup was restored on the server. -Continuing the sync as normal will cause all your files to be overwritten by an older file in an earlier state. Do you want to keep your local most recent files as conflict files? - - - - - Backup detected - Copia de seguretat detectada - - - - Normal Synchronisation - Sincronització normal - - - - Keep Local Files as Conflict - Manté els fitxers locals com a conflicte - - - - OCC::FolderMan - - - Could not reset folder state - No es pot restablir l'estat de la carpeta - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - S'ha trobat un diari de sincronització antic '%1', però no s'ha pogut eliminar. Assegureu-vos que no hi ha cap aplicació que actualment en faci ús. - - - - (backup) - (copia de seguretat) - - - - (backup %1) - (copia de seguretat %1) - - - - Undefined State. - Estat indefinit. - - - - Waiting to start syncing. - S'està esperant per començar a sincronitzar. - - - - Preparing for sync. - S'està preparant per la sincronització. - - - - Sync is running. - S'està sincronitzant. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - La darrera sincronització va ser correcta. - - - - Setup Error. - Error de configuració. - - - - User Abort. - Cancel·la usuari. - - - - Sync is paused. - La sincronització està en pausa. - - - - %1 (Sync is paused) - %1 (Sync està pausat) - - - - No valid folder selected! - No s'ha seleccionat cap directori vàlid! - - - - The selected path is not a folder! - La ruta seleccionada no és un directori! - - - - You have no permission to write to the selected folder! - No teniu permisos per escriure en la carpeta seleccionada! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Afegeix connexions de carpetes sincronitzades - - - - Synchronizing with local folder - S'està sincronitzant amb les carpetes locals - - - - File - Fitxer - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Cal que tingueu connexió per afegir una carpeta - - - - Click this button to add a folder to synchronize. - Cliqueu aquest botó per afegir una carpeta per sincronitzar. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Error en carregar la llista de carpetes del servidor. - - - - Signed out - S'ha desconnectat - - - - Fetching folder list from server... - Obtenint la llista de carpetes del servidor... - - - - There are unresolved conflicts. Click for details. - - - - - New files are being created as virtual files. - - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - S'està sincronitzant %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - descarrega %1/s - - - - u2193 %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - pujada %1/s - - - - u2191 %1/s - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 de %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 pendent, %1 de %2, fitxer %3 de %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 de %2, fitxer %3 de %4 - - - - file %1 of %2 - fitxer %1 de %2 - - - - Waiting... - S'està esperant... - - - - Waiting for %n other folder(s)... - S'està esperant %n altra carpeta...S'està esperant %n altres carpetes - - - - Preparing to sync... - S'està preparant per sincronitzar... - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Afegeix connexions de carpetes sincronitzades - - - - Add Sync Connection - Afegir una connexió de sincronització - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Feu clic per seleccionar un directori local per sincronitzar. - - - - Enter the path to the local folder. - Introduïu la ruta del directori local. - - - - Select the source folder - Seleccioneu la carpeta font - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Crea una carpeta remota - - - - Enter the name of the new folder to be created below '%1': - Introduïu el nom de la carpeta nova que es crearà a sota '%1': - - - - Folder was successfully created on %1. - La carpeta s'ha creat correctament a %1. - - - - Authentication failed accessing %1 - Autentificació fallida en accedir %1 - - - - Failed to create the folder on %1. Please check manually. - No s'ha pogut crear el directori en %1. Si us plau, comproveu-lo manualment. - - - - Failed to list a folder. Error: %1 - Ha fallat en llistar una carpeta. Error: %1 - - - - Choose this to sync the entire account - Escolliu-ho per sincronitzar el compte sencer - - - - This folder is already being synced. - Ja s'està sincronitzant aquest directori. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Ja esteu sincronitzant <i>%1</i>, que és una carpeta dins de <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately (experimental) - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Atenció:</b> %1 - - - - <b>Warning:</b> - <b>Atenció:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - No s'ha rebut cap E-Tag del servidor, comproveu el Proxy/Gateway - - - - We received a different E-Tag for resuming. Retrying next time. - Hem rebut un E-Tag diferent en la represa. Es comprovarà la pròxima vegada. - - - - Server returned wrong content-range - El servidor retorna un error de contingut o rang - - - - Connection Timeout - Temps de connexió excedit - - - - OCC::GeneralSettings - - - Form - Formulari - - - - General Settings - Arranjament general - - - - For System Tray - Per la safata del sistema - - - - stable - - - - - beta - - - - - Advanced - Avançat - - - - Ask for confirmation before synchronizing folders larger than - Demana la confirmació abans de sincronitzar carpetes més grans de - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Demana la confirmació abans de sincronitzar fonts d'emmagatzematge externes - - - - &Launch on System Startup - &Executa en iniciar el sistema - - - - Show &Desktop Notifications - Mostra les notificacions &d'escriptori - - - - Use &Monochrome Icons - Usa icones en &monocrom - - - - Version - Versió - - - - &Update Channel - - - - - • - - - - - Edit &Ignored Files - Editeu els fitxers &ignorats - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - Mostra l'informe de &fallades - - - - &Restart && Update - &Reiniciar && Actualitzar - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Cancel·lar - - - - OCC::GetOrCreatePublicLinkShare - - - Sharing error - - - - - Could not retrieve or create the public link share. Error: - -%1 - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Introduïu la contrassenya de %1:<br><br>Usuari: %2<br>Compte: %3<br> - - - - Reading from keychain failed with error: '%1' - La lectura de la cadena de claus ha fallat amb l'error: '%1' - - - - Enter Password - Escriviu contrasenya - - - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor de fitxers ignorats - - - - Global Ignore Settings - Arranjament global de fitxers ignorats - - - - Sync hidden files - Sincronitza els fitxers ocults - - - - Files Ignored by Patterns - Fitxers ignorats per patrons - - - - Add - Afegeix - - - - Pattern - Patró - - - - Allow Deletion - Permet l'eliminació - - - - Remove - Elimina - - - - Files or folders matching a pattern will not be synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Els fitxers o directoris que concordin amb algun patró no es sincronitzaran. - -Els elements que poden ser eliminats s'eliminaran si impedeixen que una carpeta sigui eliminada. Això és útil per les metadades. - - - - Could not open file - No s'ha pogut obrir el fitxer - - - - Cannot write changes to '%1'. - No es poden desar els canvis a '%1'. - - - - Add Ignore Pattern - Afegeix una plantilla per ignorar - - - - Add a new ignore pattern: - Afegeix una nova plantilla d'ignorats: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Això es proporciona completament pel sistema a '%1' i no es pot modificar en aquesta vista. - - - - OCC::IssuesWidget - - - Form - Formulari - - - - List of issues - - - - - Account - Compte - - - - - <no filter> - - - - - - Folder - Carpeta - - - - Show warnings - Mostra avisos - - - - Show ignored files - Mostra arxius descartats - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - - - - - Copy - Copiar - - - - Time - Hora - - - - File - Fitxer - - - - Issue - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LogBrowser - - - Log Output - Sortida de registre - - - - &Search: - &Cerca: - - - - &Find - &Troba - - - - &Capture debug messages - - - - - Permanently save logs - - - - - When this option is enabled and no other logging is configured, logs will be written to a temporary folder and expire after a few hours. This setting persists across client restarts. - -Logs will be written to %1 - - - - - Clear - Neteja - - - - Clear the log display. - Neteja l'inici de sessió. - - - - S&ave - Des&a - - - - Save the log file to a file on disk for debugging. - Desa el fitxer de registre al disc per depuració - - - - Save log file - Desa el fitxer de registre - - - - Error - Error - - - - Could not write to log file %1 - No es pot escriure al fitxer de registre %1 - - - - OCC::Logger - - - Error - Error - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>El fitxer '%1'<br/>no es pot obrir en mode escriptura.<br/><br/>La sortida del registre <b>no</b> es pot desar!</nobr> - - - - OCC::NSISUpdater - - - New Version Available - Versió nova disponible - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Hi ha una nova versió del client %1 disponible.</p><p><b>%2</b> està disponible per a la baixada. La versió instal·lada és la %3.</p> - - - - Skip this version - Omet aquesta versió - - - - Skip this time - Omet aquesta vegada - - - - Get update - Obtingueu l'actualització - - - - OCC::NetworkSettings - - - Form - Formulari - - - - Proxy Settings - Configuració del proxy - - - - No Proxy - Sense proxy - - - - Use system proxy - Usa el proxy del sistema - - - - Specify proxy manually as - Especifiqueu manualment el proxi com a - - - - Host - Equip remot - - - - : - : - - - - Proxy server requires authentication - El servidor Proxy requereix autenticació - - - - Download Bandwidth - Ample de banda de baixada - - - - - Limit to - Limita a - - - - - KBytes/s - KBytes/s - - - - - No limit - Sense límit - - - - - Limit to 3/4 of estimated bandwidth - Limita a 3/4 de l'ample de banda estimat - - - - Upload Bandwidth - Ample de banda de pujada - - - - - Limit automatically - Limita automàticament - - - - Hostname of proxy server - Equip remot del servidor proxy - - - - Username for proxy server - Nom d'usuari del servidor proxy - - - - Password for proxy server - Contrasenya del servidor proxy - - - - HTTP(S) proxy - proxy HTTP(S) - - - - SOCKS5 proxy - proxy SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - Creat el %1 - - - - Closing in a few seconds... - Es tancarà en pocs segons... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - La petició %1 ha fallat a %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - S'ha seleccionat '%1' a %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - The reply from the server did not contain all expected fields - - - - - <h1>Login Error</h1><p>%1</p> - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - Nova actualització de %1 preparada - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - S'instal·lara un nova actualització per %1. L'actualitzador pot demanar -privilegis addicionals durant el procés. - - - - Downloading version %1. Please wait... - Descarregat la versió %1. Espeu si us play... - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - No s'ha pogut decarregar l'actualització. Si us plau, cliqueu <a href='%1'>aqui</a>per descarregar l'actualització manualment. - - - - Could not check for new updates. - No s'ha pogut comprovar si hi ha actualitzacions. - - - - %1 version %2 available. Restart application to start the update. - Versió %2 de %1 disponible. Reinicieu l'aplicació per començar l'actualització. - - - - New %1 version %2 available. Please use the system's update tool to install it. - Nova versió %1 de %2 disponible. Utilitzeu l'eina d'actualització del sistema per instal·lar-la. - - - - Checking update server... - Comprovació d'actualitzacions al servidor - - - - Update status is unknown: Did not check for new updates. - Es desconeix l'estat de les actualitzacions: no s'ha comprovat si hi ha actualitzacions noves. - - - - No updates available. Your installation is at the latest version. - No hi ha actualitzacions disponibles. La instal·lació ja es troba en la versió més recent. - - - - Update Check - Comprova les actualitzacions - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Connecta a %1 - - - - Setup local folder options - Estableix les opcions de carpeta local - - - - Connect... - Connecta... - - - - %1 folder '%2' is synced to local folder '%3' - %1 carpeta '%2' està sincronitzat amb la carpeta local '%3' - - - - Sync the folder '%1' - Sincronitzar el directori '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>Atenció:</strong> La carpeta local no està buida. Trieu una resolució!</small></p> - - - - Local Sync Folder - Fitxer local de sincronització - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - La connexió ha fallat - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Ha fallat en connectar a l'adreça segura del servidor especificada. Com voleu procedir?</p></body></html> - - - - Select a different URL - Seleccioneu un URL diferent - - - - Retry unencrypted over HTTP (insecure) - Torna-ho a provar sense encriptar via HTTP (insegur) - - - - Configure client-side TLS certificate - Configureu el certificat TLS de la banda del client - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Ha fallat en connectar a l'adreça segura del servidor <em>%1</em>. Com voleu procedir?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - Correu &electrònic - - - - Connect to %1 - Connecta a %1 - - - - Enter user credentials - Escriviu les credencials d'usuari - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Connecta a %1 - - - - Login in your browser - - - - - Copy link to clipboard - Copiar al portadocuments - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Connecta a %1 - - - - Setup %1 server - Configura el sevidor %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Aquesta adreça NO és segura i no és encriptada. -No és aconsellada usar-la. - - - - This url is secure. You can use it. - Aquesta url és segura. Podeu usar-la. - - - - &Next > - &Següent > - - - - Invalid URL - URL incorrecte - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">S'ha connectat correctament amb %1: %2 versió %3 (%4)</font><br/><br/> - - - - Failed to connect to %1 at %2:<br/>%3 - Ha fallat la connexió amb %1 a %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - S'ha esgotat el temps d'espera mentres es conectava a %1 a les %2. - - - - Trying to connect to %1 at %2... - Intentant connectar amb %1 a %2... - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - - - - - There was an invalid response to an authenticated webdav request - - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - El servidor ha prohibit l'accés. Per verificar que teniu permisos, <a href="%1">cliqueu aquí</a> per accedir al servei amb el vostre navegador. - - - - Invalid URL - URL incorrecte - - - - The server reported the following error: - - - - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - La carpeta local %1 ja existeix, s'està configurant per sincronitzar.<br/><br/> - - - - Creating local sync folder %1... - S'està creant la carpeta de sincronització local %1... - - - - ok - correcte - - - - failed. - ha fallat. - - - - Could not create local folder %1 - No s'ha pogut crear la carpeta local %1 - - - - No remote folder specified! - No heu especificat cap carpeta remota! - - - - Error: %1 - Error: %1 - - - - creating folder on ownCloud: %1 - creant la carpeta a ownCloud: %1 - - - - Remote folder %1 created successfully. - La carpeta remota %1 s'ha creat correctament. - - - - The remote folder %1 already exists. Connecting it for syncing. - La carpeta remota %1 ja existeix. S'hi està connectant per sincronitzar-les. - - - - - The folder creation resulted in HTTP error code %1 - La creació de la carpeta ha resultat en el codi d'error HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Ha fallat la creació de la carpeta perquè les credencials proporcionades són incorrectes!<br/>Aneu enrera i comproveu les credencials.</p> - - - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">La creació de la carpeta remota ha fallat, probablement perquè les credencials facilitades són incorrectes.</font><br/>Comproveu les vostres credencials.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - La creació de la carpeta remota %1 ha fallat amb l'error <tt>%2</tt>. - - - - A sync connection from %1 to remote directory %2 was set up. - S'ha establert una connexió de sincronització des de %1 a la carpeta remota %2. - - - - Successfully connected to %1! - Connectat amb èxit a %1! - - - - Connection to %1 could not be established. Please check again. - No s'ha pogut establir la connexió amb %1. Comproveu-ho de nou. - - - - Folder rename failed - Ha fallat en canviar el nom de la carpeta - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - No es pot esborrar i restaurar la carpeta perquè una carpeta o un fitxer de dins està obert en un altre programa. Tanqueu la carpeta o el fitxer i intenteu-ho de nou o cancel·leu la configuració. - - - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>la carpeta de sincronització %1 s'ha creat correctament!</b></font> - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Assistent de connexió %1 - - - - Enable experimental feature? - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Enable experimental mode - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Tot està configurat! - - - - Open Local Folder - Obre carpeta local - - - - Open %1 in Browser - Obre %1 al navegador - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Error en escriure les metadades a la base de dades - - - - OCC::PropagateDownloadFile - - - File %1 can not be downloaded because of a local file name clash! - El fitxer %1 no es pot baixar perquè hi ha un xoc amb el nom d'un fitxer local! - - - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - L'espai lliure del disc dur es inferior a %1 - - - - File was deleted from server - El fitxer s'ha esborrat del servidor - - - - The file could not be downloaded completely. - No es pot descarregar el fitxer completament. - - - - The downloaded file is empty despite the server announced it should have been %1. - - - - - File %1 cannot be saved because of a local file name clash! - - - - - File has changed since discovery - El fitxer ha canviat des de que es va descobrir - - - - Error writing metadata to the database - Error en escriure les metadades a la base de dades - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Ha fallat la restauració: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - no s'ha pogut esborrar el fitxer %1, error: %2 - - - - Attention, possible case sensitivity clash with %1 - Atenció, possible xoc entre majúscules i minúscules amb %1 - - - - could not create folder %1 - no s'ha pogut crear la carpeta %1 - - - - Error writing metadata to the database - Error en escriure les metadades a la base de dades - - - - OCC::PropagateLocalRemove - - - Error removing '%1': %2; - Error en eliminar '%1': %2; - - - - Could not remove folder '%1' - No s'ha pogut eliminar la carpeta '%1' - - - - Could not remove %1 because of a local file name clash - No es pot eliminar %1 perquè hi ha un xoc amb el nom d'un fitxer local - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - El fitxer %1 no es pot reanomenar a %2 perquè hi ha un xoc amb el nom d'un fitxer local - - - - - Error writing metadata to the database - Error en escriure les metadades a la base de dades - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - Error writing metadata to the database - Error en escriure les metadades a la base de dades - - - - OCC::PropagateRemoteMove - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - - Error writing metadata to the database - Error en escriure les metadades a la base de dades - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - Fitxer eliminat - - - - Local file changed during syncing. It will be resumed. - - - - - Local file changed during sync. - El fitxer local ha canviat durant la sincronització. - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - Error en escriure les metadades a la base de dades - - - - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - El fitxer local s'ha eliminat durant la sincronització. - - - - Local file changed during sync. - El fitxer local ha canviat durant la sincronització. - - - - Unexpected return code from server (%1) - - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileV1 - - - Poll URL missing - - - - - The local file was removed during sync. - El fitxer local s'ha eliminat durant la sincronització. - - - - Local file changed during sync. - El fitxer local ha canviat durant la sincronització. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolWidget - - - Form - Formulari - - - - TextLabel - TextLabel - - - - Open in browser - Obre al navegador - - - - Time - Hora - - - - File - Fitxer - - - - Folder - Carpeta - - - - Action - Acció - - - - Size - Mida - - - - Local sync protocol - Protocol de sincronització local - - - - Copy - Copia - - - - Copy the activity list to the clipboard. - Copia la llista d'activitats al porta-retalls. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - - - - - Username: - Usuari: - - - - Proxy: - - - - - The proxy server needs a username and password. - - - - - Password: - Contrasenya: - - - - TextLabel - TextLabel - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Trieu què sincronitzar - - - - OCC::SelectiveSyncWidget - - - Loading ... - Carregant... - - - - Deselect remote folders you do not wish to synchronize. - - - - - Name - Nom - - - - Size - Mida - - - - - No subfolders currently on the server. - Actualment no hi ha subcarpetes al servidor. - - - - An error occurred while loading the list of sub folders. - S'ha produit un error en carregar la llista de subcarpetes. - - - - OCC::ServerNotificationHandler - - - Dismiss - Ignora - - - - OCC::SettingsDialog - - - Settings - Arranjament - - - - Activity - Activitat - - - - General - General - - - - Network - Xarxa - - - - Account - Compte - - - - OCC::SettingsDialogMac - - - %1 - %1 - - - - Activity - Activitat - - - - General - General - - - - Network - Xarxa - - - - - Account - Compte - - - - OCC::ShareDialog - - - TextLabel - TextLabel - - - - share label - comparteix l'etiqueta - - - - Dialog - Diàleg - - - - ownCloud Path: - camí de l'OwnCloud: - - - - %1 Sharing - Compartint amb %1 - - - - %1 - %1 - - - - Folder: %2 - Carpeta: %2 - - - - The server does not allow sharing - El servidor no permet la compartició de fitxers - - - - Retrieving maximum possible sharing permissions from server... - - - - - The file can not be shared because it was shared without sharing permission. - El fitxer no es pot compartir perquè va ser compartit sense permisos de compartició. - - - - Users and Groups - Usuaris i Grups - - - - Public Links - Enllaços públics - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Comparteix DocumentNou.odt - - - - Upload only (File Drop) - - - - - Receive files from others without revealing the contents of the folder. - - - - - TextLabel - TextLabel - - - - Set &password - Estableix la &contrasenya - - - - Enter a name to create a new public link... - Escriu un nom per crear un enllaç públic... - - - - &Create new - - - - - Set &expiration date - Estableix la data de &venciment - - - - Set password - Estableix la contrasenya - - - - Recipients can view or download contents. - - - - - Download / View - Descarregar / Veure - - - - Download / View / Upload - Descarregar / Veure / Pujar - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - Qualsevol podrà accedir a l'arxiu o carpeta amb aquest link - - - - - P&assword protect - Protegit amb contr&asenya - - - - The file can not be shared because it was shared without sharing permission. - El fitxer no es pot compartir perquè va ser compartit sense permisos de compartició. - - - - Link shares have been disabled - - - - - Create public link share - - - - - - Delete - Esborra - - - - Open link in browser - - - - - Copy link to clipboard - Copiar al portadocuments - - - - Copy link to clipboard (direct download) - - - - - Send link by email - Envia l'enllaç per correu electrònic - - - - Send link by email (direct download) - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - - - - - Cancel - Cancel·lar - - - - - Public link - Enllaç públic - - - - Delete link share - - - - - Public sh&aring requires a password - La comp&artició pública requereix una contrasenya - - - - Please Set Password - Establiu la contrasenya - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Comparteix DocumentNou.odt - - - - Share with users or groups ... - Comparteix amb usuaris o grups ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - - - - - Open link in browser - - - - - Copy link to clipboard - Copiar al portadocuments - - - - Send link by email - Envia l'enllaç per correu electrònic - - - - No results for '%1' - No hi ha resultats per '%1' - - - - I shared something with you - He compartit amb tu - - - - OCC::ShareUserLine - - - Form - Formulari - - - - - TextLabel - TextLabel - - - - can edit - pot editar - - - - can share - pot compartir - - - - ... - ... - - - - create - crea - - - - change - canvi - - - - delete - elimina - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::ShibbolethCredentials - - - Login Error - Error d'accés - - - - You must sign in as user %1 - Cal identificar-se com a usuari %1 - - - - OCC::ShibbolethWebView - - - %1 - Authenticate - %1 - Autenticat - - - - SSL Chipher Debug View - - - - - Reauthentication required - Es requereix nova acreditació - - - - Your session has expired. You need to re-login to continue to use the client. - La vostra sessió ha vençut. Heu d'acreditar-vos de nou per continuar usant el client. - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Comparteix amb %1 - - - - Context menu share - - - - - I shared something with you - He compartit amb tu - - - - - Share... - - - - - - Copy private link to clipboard - - - - - Send private link by email... - - - - - Resharing this file is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - Obre al navegador - - - - Download file(s) - - - - - Replace file(s) by virtual file - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Detalls del certificat</h3> - - - - Common Name (CN): - Nom comú (NC): - - - - Subject Alternative Names: - Noms alternatius de concepte: - - - - Organization (O): - Organització (O): - - - - Organizational Unit (OU): - Unitat organitzativa (UO): - - - - State/Province: - Estat o província - - - - Country: - País: - - - - Serial: - Serial: - - - - <h3>Issuer</h3> - <h3>Emissor</h3> - - - - Issuer: - Emissor: - - - - Issued on: - Enviat el: - - - - Expires on: - Expira el: - - - - <h3>Fingerprints</h3> - <h3>Empremtes</h3> - - - - SHA-256: - SHA-256 - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Nota:</b> Aquest certificat es va aprovar manualment</p> - - - - %1 (self-signed) - %1 (auto-signat) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Aquesta connexió és encriptada usant %1 bit %2. - - - - - No support for SSL session tickets/identifiers - Els tiquets/identificadors de sessió SSL no estan implementats - - - - Certificate information: - Informació del certificat: - - - - This connection is NOT secure as it is not encrypted. - - Aquesta connexió NO és segura , ja que no està xifrada. - - - - - OCC::SslErrorDialog - - - Form - Formulari - - - - Trust this certificate anyway - Confia en aquest certificat igualment - - - - Untrusted Certificate - Certificat no fiable - - - - Cannot connect securely to <i>%1</i>: - No es pot connectar de forma segura a <i>%1</i>: - - - - with Certificate %1 - amb certificat %1 - - - - - - &lt;not specified&gt; - &lt;no especificat&gt; - - - - - Organization: %1 - Organització %1 - - - - - Unit: %1 - Unitat: %1 - - - - - Country: %1 - País: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Empremta digital (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Empremta digital (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Data d'efecte: %1 - - - - Expiration Date: %1 - Data de venciment: %1 - - - - Issuer: %1 - Emissor: %1 - - - - OCC::SyncEngine - - - Success. - Èxit. - - - - A network connection timeout happened. - Temps excedit en la connexió. - - - - A HTTP transmission error happened. - S'ha produït un error en la transmissió HTTP. - - - - The mounted folder is temporarily not available on the server - - - - - An error occurred while opening a folder - S'ha produït un error en obrir una carpeta - - - - Error while reading folder. - Error en llegir la carpeta. - - - - %1 (skipped due to earlier error, trying again in %2) - - - - - File/Folder is ignored because it's hidden. - El fitxer/carpeta s'ha ignorat perquè és ocult. - - - - Folder hierarchy is too deep - - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Not allowed because you don't have permission to add parent folder - - - - - Not allowed because you don't have permission to add files in that folder - - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - - - - - CSync unspecified error. - Error inespecífic de CSync. - - - - Aborted by the user - Aturat per l'usuari - - - - Failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder. - - - - - Discovery step failed. - - - - - Permission denied. - - - - - File or directory not found: - - - - - Tried to create a folder that already exists. - - - - - No space on %1 server available. - - - - - The service is temporarily unavailable - El servei no està disponible temporalment - - - - Access is forbidden - Accés prohibit - - - - An internal error number %1 occurred. - S'ha produït l'error intern número %1. - - - - Symbolic links are not supported in syncing. - La sincronització d'enllaços simbòlics no està implementada. - - - - File is listed on the ignore list. - El fitxer està a la llista d'ignorats. - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - - - - - The file name is a reserved name on this file system. - - - - - Filename contains trailing spaces. - - - - - Filename is too long. - El nom de fitxer és massa llarg. - - - - The filename cannot be encoded on your file system. - - - - - Unresolved conflict. - - - - - Stat failed. - - - - - Filename encoding is not valid - La codificació del nom de fitxer no és vàlida - - - - Invalid characters, please rename "%1" - Caràcters no vàlids. Reanomeneu "%1" - - - - Using virtual files but suffix is not set - - - - - Unable to read the blacklist from the local database - - - - - Unable to read from the sync journal. - - - - - Cannot open the sync journal - No es pot obrir el diari de sincronització - - - - File name contains at least one invalid character - El nom del fitxer conté al menys un caràcter invàlid - - - - - Ignored because of the "choose what to sync" blacklist - S'ignora degut al filtre a «Trieu què sincronitzar» - - - - Not allowed because you don't have permission to add subfolders to that folder - - - - - Not allowed to upload this file because it is read-only on the server, restoring - No es permet pujar aquest fitxer perquè només és de lectura en el servidor, es restaura - - - - - Not allowed to remove, restoring - No es permet l'eliminació, es restaura - - - - Local files and share folder removed. - Fitxers locals i carpeta compartida esborrats. - - - - Move not allowed, item restored - No es permet moure'l, l'element es restaura - - - - Move not allowed because %1 is read-only - No es permet moure perquè %1 només és de lectura - - - - the destination - el destí - - - - the source - l'origen - - - - OCC::SyncLogDialog - - - Synchronisation Log - Registre de sincronització - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p> - - - - - <p>Copyright ownCloud GmbH</p> - <p>Copyright ownCloud GmbH</p> - - - - <p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%2 and the %2 logo are registered trademarks of %1 in the United States, other countries, or both.</p> - <p>Distribuït per %1 i amb llicència GNU General Public License (GPL) Versió 2.0.<br/>%2 i el %2 logo són marques registrades de %1 als Estats Units, altres països, o ambdós.</p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - - - - - The checksum header contained an unknown checksum type '%1' - - - - - The downloaded file does not match the checksum, it will be resumed. - El fitxer descarregat no concorda amb la suma de verificació. Es reintentarà. - - - - OCC::ownCloudGui - - - Please sign in - Acrediteu-vos - - - - Folder %1: %2 - Carpeta %1: %2 - - - - There are no sync folders configured. - No hi han carpetes sincronitzades configurades. - - - - Open in browser - Obre al navegador - - - - - - Log in... - Inicia la sessió - - - - - - Log out - Surt - - - - Recent Changes - Canvis recents - - - - Managed Folders: - Fitxers gestionats: - - - - Open folder '%1' - Obre carpeta '%1' - - - - Open %1 in browser - Obre %1 en el navegador - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Estat desconegut - - - - Settings... - Arranjament... - - - - Details... - Detalls... - - - - Help - Ajuda - - - - Quit %1 - Surt %1 - - - - Disconnected from %1 - S'ha desconnectat de %1 - - - - Unsupported Server Version - La versió del servidor no és compatible - - - - Disconnected - Desconnectat - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - Desconnectat dels comptes: - - - - Account %1: %2 - Compte %1: %2 - - - - Signed out - S'ha desconnectat - - - - Account synchronization is disabled - La sincronització del compte està deshabilitada - - - - - Synchronization is paused - - - - - Unresolved conflicts - - - - - Error during synchronization - - - - - No sync folders configured - - - - - Unpause all folders - Reprén totes les carpetes - - - - Pause all folders - Pausa totes les carpetes - - - - Unpause all synchronization - Reprèn totes les sincronitzacions - - - - Unpause synchronization - Reprèn la sincronització - - - - Pause all synchronization - Pausa totes les sincronitzacions - - - - Pause synchronization - Pausa la sincronització - - - - Log out of all accounts - Surt de tots els comptes - - - - Log in to all accounts... - Connecta a tots els comptes... - - - - New account... - Compte nou... - - - - - About %1 - Sobre %1 - - - - No items synced recently - No hi ha elements sincronitzats recentment - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - Sincronitzant %1 de %2 (%3 pendents) - - - - Syncing %1 of %2 - S'està sincronitzant %1 de %2 - - - - Syncing %1 (%2 left) - Sincronitzant %1 (%2 pendents) - - - - Syncing %1 - S'està sincronitzant %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Actualitzat - - - - OwncloudAdvancedSetupPage - - - Form - Formulari - - - - - - - - - - TextLabel - TextLabel - - - - Server - Servidor - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Si aquesta caixa està marcada, el contingut existent en la carpeta local s'eliminarà per començar una nova sincronització des del servidor.</p><p>No la marqueu si el contingut local s'ha de pujar a la carpeta del servidor.</p></body></html> - - - - Start a &clean sync (Erases the local folder!) - Comença una sin&cronització des de zero (esborra la carpeta local!) - - - - Ask for confirmation before synchroni&zing folders larger than - Demana la confirmació abans de sincronit&zar carpetes més grans de - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Demana la confirmació abans de sincronitzar fonts d'emmagatzematge e&xternes - - - - Choose what to sync - Trieu què sincronitzar - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - Carpeta &local - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Mantén les dades locals - - - - S&ynchronize everything from server (recommended) - - - - - Use virtual files instead of downloading content immediately (e&xperimental) - - - - - Status message - Missatge d'estat - - - - OwncloudHttpCredsPage - - - Form - Formulari - - - - &Username - Nom d'&usuari - - - - &Password - &Contrasenya - - - - OwncloudOAuthCredsPage - - - Form - Formulari - - - - Please switch to your browser to proceed. - - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - Reobrir navegador - - - - OwncloudSetupPage - - - Form - Formulari - - - - - TextLabel - TextLabel - - - - Ser&ver Address - Adreça del ser&vidor - - - - https://... - https://... - - - - Error Label - Etiqueta d'error - - - - OwncloudWizardResultPage - - - Form - Formulari - - - - TextLabel - TextLabel - - - - Your entire account is synced to the local folder - El vostre compte està totalment sincronitzat amb la carpeta local - - - - - PushButton - PushButton - - - - QObject - - - in the future - al futur - - - - %n day(s) ago - fa %n diafa %n dies - - - - %n hour(s) ago - fa %n horafa %n hores - - - - now - ara - - - - Less than a minute ago - Fa menys d'un minut - - - - %n minute(s) ago - fa %n minutfa %n minuts - - - - Some time ago - Fa una estona - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n any%n anys - - - - %n month(s) - %n mes%n mesos - - - - %n day(s) - %n dia%n dies - - - - %n hour(s) - %n hora%n hores - - - - %n minute(s) - %n minut%n minuts - - - - %n second(s) - %n segon%n segons - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - La safata del sistema no està disponible - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 requereix una safata del sistema que funcioni. Si esteu executant XFCE seguiu <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">aquestes instruccions</a>. Altrament, instal·leu una aplicació de safata com 'trayer' i intenteu-ho de nou. - - - - ownCloudTheme::about() - - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - <p><small>Construït de la revisió de Git <a href="%1">%2</a> el %4 de %3 usant Qt %5, %6</small></p> - - - - progress - - - Downloaded - Descarregat - - - - Uploaded - Carregat - - - - Server version downloaded, copied changed local file into conflict file - S'ha baixat la versió del servidor i s'ha renomenat el fitxer local com a fitxer de conflicte - - - - Deleted - Eliminat - - - - Moved to %1 - S'ha mogut a %1 - - - - Ignored - Ignorat - - - - Filesystem access error - Error d'accés al sistema de fitxers - - - - Error - Error - - - - Updated local metadata - S'han actualitzat les metadades locals - - - - - Unknown - Desconegut - - - - downloading - baixant - - - - uploading - pujant - - - - deleting - esborrant - - - - moving - movent - - - - ignoring - ignorant - - - - - error - error - - - - updating local metadata - s'estan actualitzant les metadades locals - - - - theme - - - Status undefined - Estat indefinit - - - - Waiting to start sync - Pendent d'iniciar la sincronització - - - - Sync is running - La sincronització s'està executant - - - - Sync Success - Sincronització amb èxit - - - - Sync Success, some files were ignored. - Sincronització realitzada amb èxit, alguns fitxers s'han ignorat. - - - - Sync Error - Error de sincronització - - - - Setup Error - Error de configuració - - - - Preparing to sync - Preparant per sincronitzar - - - - Aborting... - Cancel·lant... - - - - Sync is paused - La sincronització està pausada. - - - - utility - - - Could not open browser - No es pot obrir el navegador - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - - - \ No newline at end of file diff --git a/translations/client_cs.ts b/translations/client_cs.ts deleted file mode 100644 index 136b4557a5b..00000000000 --- a/translations/client_cs.ts +++ /dev/null @@ -1,4892 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - Přerušit - - - - Send this report - Odeslat tento report - - - - Don't send - Neposílat - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - Zavřít - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - Získávání dodatečných informací... - - - - Uploaded %L1 of %L2 KB. - Odesláno %L1 z %L2 kB - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - Nelze přejmenovat %1, soubor se právě používá - - - - Could not make directories in trash - Adresáře v koši se nepodařilo vytvořit - - - - - Could not move '%1' to '%2' - Nelze přesunout "%1" do "%2" - - - - Moving to the trash is not implemented on this platform - Přesunutí do koše zde není implementováno - - - - Error removing '%1': %2 - Chyba při odstraňování '%1': %2 - - - - Could not remove folder '%1' - Nezdařilo se odstranění adresáře '%1' - - - - FolderWizardSourcePage - - - Form - Formulář - - - - Pick a local folder on your computer to sync - Zvolte místní adresář na svém počítači k synchronizaci - - - - &Choose... - Vy&brat... - - - - FolderWizardTargetPage - - - Form - Formulář - - - - Select a remote destination folder - Zvolte vzdálený cílový adresář - - - - Create Folder - Vytvořit adresář - - - - Refresh - Obnovit - - - - Folders - Adresáře - - - - TextLabel - TextLabel - - - - NotificationWidget - - - Form - Formulář - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Textový popisek - - - - OCC::AboutDialog - - - About - O aplikaci - - - - TextLabel - Textový popisek - - - - Versions - Verze - - - - About %1 - O %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Připojení vypršelo - - - - Unknown error: network reply was deleted - Neznámá chyba: odpověď sítě byla smazána - - - - Server replied "%1 %2" to "%3 %4" - Odpověď serveru "%1 %2" do "%3 %4" - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - Formulář - - - - Re-open Browser - Znovu otevřít prohlížeč - - - - ... - ... - - - - Storage space: ... - Prostor úložiště: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Neoznačené adresáře budou <b>odstraněny</b> z místního souborového systému a nebudou již synchronizovány na tento počítač - - - - Synchronize all - Synchronizovat vše - - - - Synchronize none - Nic nesynchronizovat - - - - Apply manual changes - Použít manuální úpravy - - - - Apply - Použít - - - - - - - Cancel - Zrušit - - - - Connected with <server> as <user> - Připojen k <server> jako <user> - - - - No account configured. - Žádný účet nenastaven. - - - - Remove - Odebrat - - - - Account - Účet - - - - Choose what to sync - Vybrat co sesynchronizovat - - - - Force sync now - Vynutit synchronizaci nyní - - - - Restart sync - Restartovat synchronizaci - - - - Remove folder sync connection - Odstranit připojení synchronizace adresáře - - - - Folder creation failed - Vytvoření adresáře selhalo - - - - <p>Could not create local folder <i>%1</i>. - <p>Nelze vytvořit místní adresář <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Potvrdit odstranění připojení synchronizace adresáře - - - - Remove Folder Sync Connection - Odstranit připojení synchronizace adresáře - - - - Disable virtual file support? - Zakázat virtuální soubory? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - Zakázat podporu - - - - Sync Running - Synchronizace probíhá - - - - The syncing operation is running.<br/>Do you want to terminate it? - Operace synchronizace právě probíhá.<br/>Přejete si ji ukončit? - - - - %1 in use - %1 používaný - - - - %1 as <i>%2</i> - %1 jako <i>%2</i> - - - - Connected to %1. - Připojeno k %1. - - - - Server %1 is temporarily unavailable. - Server %1 je dočasně nedostupný. - - - - Server %1 is currently in maintenance mode. - server %1 je v režimu údržby. - - - - Signed out from %1. - Odhlášeno z %1. - - - - Connecting to %1... - Připojeno k %1... - - - - No connection to %1 at %2. - Bez připojení k %1 na %2. - - - - Log in - Přihlásit - - - - There are folders that were not synchronized because they are too big: - Tyto složky nebyly synchronizovány, protože jsou příšliš velké: - - - - There are folders that were not synchronized because they are external storages: - Tyto složky nebyly synchronizovány, protože se nachází na externím úložišti: - - - - There are folders that were not synchronized because they are too big or external storages: - Tyto složky nebyly synchronizovány, protože jsou příliš velké, nebo se nachází na externím úložišti: - - - - Confirm Account Removal - Potvrdit odstranění účtu - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Opravdu chcete odstranit připojení k účtu <i>%1</i>?</p><p><b>Poznámka:</b> Toto <b>neodstraní</b> žádné soubory.</p> - - - - Remove connection - Odstranit připojení - - - - Open folder - Otevřít adresář - - - - - Log out - Odhlásit se - - - - Open local folder - Otevřít místní složku - - - - Open folder in browser - Otevřít složku v prohlížeči - - - - Resume sync - Pokračovat v synchronizaci - - - - Pause sync - Pozastavit synchronizaci - - - - Availability - Dostupnost - - - - Disable virtual file support... - Zakázat virtuální soubory... - - - - Enable virtual file support%1... - Povolit virtuální soubory %1... - - - - (experimental) - (experimentální) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Opravdu chcete zastavit synchronizaci adresáře <i>%1</i>?</p><p><b>Poznámka:</b> Toto <b>neodstraní</b> žádné soubory.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - Používaných %1 (%3%) z %2. Některé adresáře včetně připojených síťových nebo sdílených adresářů mohou mít odlišné limity. - - - - %1 of %2 in use - Používaných %1 z %2 - - - - Currently there is no storage usage information available. - Momentálně nejsou k dispozici žádné informace o využití úložiště. - - - - The server version %1 is unsupported! Proceed at your own risk. - Verze serveru %1 není podporována! Pokračujete na vlastní riziko. - - - - Obtaining authorization from the browser. - - - - - Server configuration error: %1 at %2. - Chyba konfigurace serveru: %1 v %2. - - - - No %1 connection configured. - Žádné spojení s %1 nenastaveno. - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - - - - - Signed out - Odhlášeno - - - - Disconnected - Odpojeno - - - - Connected - Připojeno - - - - Service unavailable - Služba nedostupná - - - - Maintenance mode - režim údržby - - - - Network error - Chyba sítě - - - - Configuration error - Chyba konfigurace - - - - Asking Credentials - Požádat o pověření - - - - Unknown account state - Neznámý stav účtu - - - - OCC::ActivityListModel - - - %1 %2 on %3 - - - - - Activity - Aktivita - - - - Account - Účet - - - - Time - Čas - - - - Local path - - - - - OCC::ActivitySettings - - - - Server Activity - Aktivita serveru - - - - Sync Protocol - Protokol synchronizace - - - - Not Synced - Nesesynchronizováno - - - - Not Synced (%1) - %1 is the number of not synced files. - Nesesynchronizováno (%1) - - - - OCC::ActivityWidget - - - Form - Formulář - - - - - - TextLabel - Textový popisek - - - - Filter - Filtr - - - - Server Activities - Aktivity serveru - - - - Notifications - Upozornění - - - - %n notifications(s) for %1. - %n notifikace pro %1.%n notifikací pro %1.%n notifikací pro %1.%n notifikací pro %1. - - - - %n notifications(s) for %1 and %2. - %n notifikace pro %1 a %2.%n notifikací pro %1 a %2.%n notifikací pro %1 a %2.%n notifikací pro %1 a %2. - - - - New notifications for %1, %2 and other accounts. - Dostali jste nové upozornění od %1, %2 a dalších uživatelů. - - - - Open the activity view for details. - - - - - Copy to clipboard - Zkopírovat do schránky - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>Účet %1 nemá zapnuté aktivity. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Pokračování bude mít za následek <b>smazání těchto nastavení</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Pokračování bude mít za následek <b>ignorování těchto nastavení</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Ukončit - - - - Continue - Pokračovat - - - - Error accessing the configuration file - Chyba přístupu ke konfiguračnímu souboru - - - - There was an error while accessing the configuration file at %1. - Došlo k chybě při přístupu ke konfigurační soubor %1. - - - - Quit %1 - Ukončit %1 - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - Ověření vyžadováno - - - - Enter username and password for '%1' at %2. - Zadejte uživatelské jméno a heslo pro '%1' na %2. - - - - &User: - &Uživatel: - - - - &Password: - &Heslo: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Žádný účet ownCloud nenastaven - - - - The configured server for this client is too old - Server nastavený pro tohoto klienta je příliš starý - - - - Please update to the latest server and restart the client. - Aktualizujte prosím na poslední verzi serveru a restartujte klienta. - - - - Authentication error: Either username or password are wrong. - Chyba ověření: Uživatelské jméno nebo heslo není správné. - - - - timeout - vypršel časový interval - - - - The provided credentials are not correct - Poskytnuté přihlašovací údaje nejsou správné - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - Chyba serveru: PROPFIND není správně formátována jako XML! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - Chyba při otevírání složky %1 - - - - Directory not accessible on client, permission denied - Složka není dostupná, přístup odepřen - - - - Directory not found: %1 - Adresář nenalezen: %1 - - - - Filename encoding is not valid - Kódování znaků jména soubor je neplatné - - - - Error while reading directory %1 - Chyba při čtení adresáře %1 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - - - - - OCC::Folder - - - Local folder %1 does not exist. - Místní adresář %1 neexistuje. - - - - %1 should be a folder but is not. - %1 by měl být adresář, ale není. - - - - %1 is not readable. - %1 není čitelný. - - - - %1 has been removed. - %1 names a file. - %1 byl odebrán. - - - - %1 has been updated. - %1 names a file. - %1 byl aktualizován. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 byl přejmenován na %2. - - - - %1 has been moved to %2. - %1 byl přemístěn do %2. - - - - %1 and %n other file(s) have been removed. - %1 soubor bude smazán.%1 a %n další soubory budou smazány.%1 a %n další soubory budou smazány.%1 a %n další soubory budou smazány. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - %1 a %n další soubor byl přidán.%1 a %n dalších souborů bylo přidáno.%1 a %n dalších souborů bylo přidáno.%1 a %n další soubory(ů) byly přidány. - - - - %1 has been added. - %1 names a file. - %1 přidán. - - - - %1 and %n other file(s) have been updated. - %1 soubor byl aktualizován.%1 a %n další soubory byly aktualizovány.%1 a %n další soubory byly aktualizovány.%1 a %n další soubory byly aktualizovány. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 byl přejmenován na %2.%1 byl přejmenován na %2 a %n další soubory byly přejmenovány.%1 byl přejmenován na %2 a %n další soubory byly přejmenovány.%1 byl přejmenován na %2 a %n další soubory byly přejmenovány. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 byl přesunut do %2.%1 byl přesunut do %2 a %n dalších souborů bylo přesunuto.%1 byl přesunut do %2 a %n dalších souborů bylo přesunuto.%1 byl přesunut do %2 a %n dalších souborů bylo přesunuto. - - - - %1 has and %n other file(s) have sync conflicts. - %1 má problém se synchronizací.%1 a %n dalších souborů má problém se synchronizací.%1 a %n dalších souborů má problém se synchronizací.%1 a %n dalších souborů má problém se synchronizací. - - - - %1 has a sync conflict. Please check the conflict file! - %1 má problém se synchronizací. Prosím zkontrolujte chybový soubor. - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 soubor nemůže být synchronizován kvůli chybám. Shlédněte log pro detaily.%1 a %n dalších souborů nemohou být synchronizovány kvůli chybám. Shlédněte log pro detaily.%1 a %n dalších souborů nemohou být synchronizovány kvůli chybám. Shlédněte log pro detaily.%1 a %n dalších souborů nemohou být synchronizovány kvůli chybám. Shlédněte log pro detaily. - - - - %1 could not be synced due to an error. See the log for details. - %1 nebyl kvůli chybě synchronizován. Detaily jsou k nalezení v logu. - - - - Sync Activity - Průběh synchronizace - - - - Could not read system exclude file - Nezdařilo se přečtení systémového exclude souboru - - - - A new folder larger than %1 MB has been added: %2. - - Nová složka větší než %1 MB byla přidána: %2. - - - - - A folder from an external storage has been added. - - Byla přidána složka z externího úložiště. - - - - Please go in the settings to select it if you wish to download it. - Pokud to chcete stáhnout, běžte do nastavení a vyberte to. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Všechny soubory v synchronizované složce '%1' byly na serveru smazány. -Tyto soubory budou smazány i ve vaší místní synchronizované složce a nebudou tedy dostupné, pokud nemáte právo obnovovat. Rozhodnete-li se soubory ponechat, budou opět synchronizovány se serverem, pokud k tomu máte práva. -Rozhodnete-li se soubory smazat, budou vám nedostupné, nejste-li jejich vlastníkem. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Všechny soubory ve vaší místní synchronizované složce '%1' byly smazány. Tyto soubory budou smazány i na serveru a nebudou tedy dostupné, pokud následně neprovedete jejich obnovení. -Jste si jisti, že chcete tyto akce synchronizovat se serverem? -Pokud to byl omyl a chcete si soubory ponechat, budou opět synchronizovány ze serveru. - - - - Remove All Files? - Odstranit všechny soubory? - - - - Remove all files - Odstranit všechny soubory - - - - Keep files - Ponechat soubory - - - - OCC::FolderMan - - - Could not reset folder state - Nelze obnovit stav adresáře - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Byl nalezen starý záznam synchronizace '%1', ale nebylo možné jej odebrat. Ujistěte se, že není aktuálně používán jinou aplikací. - - - - (backup) - (záloha) - - - - (backup %1) - (záloha %1) - - - - Undefined State. - Nedefinovaný stav. - - - - Waiting to start syncing. - Čeká na spuštění synchronizace. - - - - Preparing for sync. - Příprava na synchronizaci. - - - - Sync is running. - Synchronizace probíhá. - - - - Sync was successful, unresolved conflicts. - Synchronizace byla úspěšně dokončena, nevyřešené konflikty - - - - Last Sync was successful. - Poslední synchronizace byla úspěšná. - - - - Setup Error. - Chyba nastavení. - - - - User Abort. - Zrušení uživatelem. - - - - Sync is paused. - Synchronizace pozastavena. - - - - %1 (Sync is paused) - %1 (Synchronizace je pozastavena) - - - - No valid folder selected! - Nebyl vybrán platný adresář! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - Vybraná cesta ke složce neexistuje! - - - - The selected path is not a folder! - Vybraná cesta nevede do adresáře! - - - - You have no permission to write to the selected folder! - Nemáte oprávnění pro zápis do zvoleného adresáře! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Ze serveru se do tohoto umístění již synchronizuje. Prosím zvolte jinou místní složku! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Místní adresář %1 již obsahuje podadresář použitý pro synchronizaci odesílání. Zvolte prosím jiný! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Místní adresář %1 je již obsažen ve adresáři použitém pro synchronizaci. Vyberte prosím jiný! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Přidat synchronizaci adresáře - - - - File - Soubor - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Pro přidání adresáře musíte být připojeni - - - - Click this button to add a folder to synchronize. - Stisknutím tlačítka přidáte adresář k synchronizaci. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Chyba při načítání seznamu adresářů ze serveru. - - - - Virtual file support is enabled. - Virtuální soubory povoleny - - - - Signed out - Odhlášeno - - - - Fetching folder list from server... - Načítání seznamu adresářů ze serveru... - - - - There are unresolved conflicts. Click for details. - Jsou zde nevyřešené konflikty. Klikněte pro detaily. - - - - Checking for changes in remote '%1' - Zjišťuji změny ve vzdáleném '%1' - - - - Checking for changes in local '%1' - Zjišťuji změny v místním '%1' - - - - Reconciling changes - Srovnávám změny - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synchronizuji %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - stahování %1/s - - - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - nahrávání %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 ze %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 zbývá, %1 ze %2, soubor %3 z %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 z %2, soubor %3 z %4 - - - - file %1 of %2 - soubor %1 z %2 - - - - Waiting... - Chvíli strpení... - - - - Waiting for %n other folder(s)... - Čeká se na %n další adresář...Čeká se na %n další adresáře...Čeká se na %n dalších adresářů...Čeká se na %n dalších adresářů... - - - - Preparing to sync... - Synchronizace se připravuje... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Přidat synchronizaci adresáře - - - - Add Sync Connection - Přidat synchronizační připojení - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Kliknutím zvolíte místní adresář k synchronizaci. - - - - Enter the path to the local folder. - Zadejte cestu k místnímu adresáři. - - - - Select the source folder - Zvolte zdrojový adresář - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Vytvořit vzdálený adresář - - - - Enter the name of the new folder to be created below '%1': - Zadejte název nově vytvářeného adresáře níže '%1': - - - - Folder was successfully created on %1. - Adresář byl úspěšně vytvořen na %1. - - - - Authentication failed accessing %1 - Ověření selhalo při připojení %1 - - - - Failed to create the folder on %1. Please check manually. - Na %1 selhalo vytvoření adresáře. Zkontrolujte to prosím ručně. - - - - Failed to list a folder. Error: %1 - Chyba při vypsání adresáře. Chyba: %1 - - - - Choose this to sync the entire account - Zvolte toto k provedení synchronizace celého účtu - - - - This folder is already being synced. - Tento adresář je již synchronizován. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Již synchronizujete adresář <i>%1</i>, který je adresáři <i>%2</i> nadřazený. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Již synchronizujete adresář <i>%1</i>, který je podsložkou <i>%2</i> . - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Použijte virtuální soubory namísto přímého stahování obsahu %1 - - - - (experimental) - (experimentální) - - - - Virtual files are not available for the selected folder - Virtuální soubory nejsou dostupné pro zvolenou složku - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Varování:</b> %1 - - - - <b>Warning:</b> - <b>Varování:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Ze serveru nebyl obdržen E-Tag, zkontrolujte proxy/bránu - - - - We received a different E-Tag for resuming. Retrying next time. - Obdrželi jsme jiný E-Tag pro pokračování. Zkusím znovu příště. - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - Server odpověděl chybným rozsahem obsahu - - - - OCC::GETJob - - - Connection Timeout - Čas spojení vypršel - - - - OCC::GeneralSettings - - - Form - Formulář - - - - General Settings - Obecná nastavení - - - - Language - Jazyk - - - - Sync hidden files - Synchronizovat skryté soubory - - - - Log Settings - Nastavení logu - - - - Advanced - Approval of folder sync (Non virtual file mode only) - Pokročilé - Souhlas se synchronizací složek (neplatí pro virtuální soubory) - - - - Network - Síť - - - - Updates - Aktualizace - - - - stable - stable - - - - beta - beta - - - - About - O aplikaci - - - - Advanced - Pokročilé - - - - Use &Monochrome Icons in the system tray - Používat černobílé ikony na hlavním panelu - - - - Ask for confirmation before synchronizing folders larger than - Zeptat se před synchronizací složek větších než - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Zeptat se na potvrzení před synchronizací externích úlošišť - - - - &Launch on System Startup - &Spustit při startu systému - - - - Show &Desktop Notifications - Zobrazovat &upozornění na ploše - - - - &Update Channel - &Aktualizační kanál - - - - Edit &Ignored Files - Upravit &ignorované soubory - - - - Show sync folders in &Explorer's Navigation Pane - Zobrazit synchronizované složky v postranní liště &Průzkumníka souborů - - - - S&how crash reporter - Z&obrazit hlášení o pádech - - - - &Restart && Update - &Restart && aktualizace - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - Změnit aktualizační kanál? - - - - Warning - Varování - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - Změnit aktualizační kanál - - - - Cancel - Zrušit - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Vložte prosím %1 heslo:<br><br>Uživatel: %2<br>Účet: %3<br> - - - - Reading from keychain failed with error: '%1' - Čtení z klíčenky selhalo s chybou: '%1' - - - - Enter Password - Zadejte heslo - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Klikněte sem</a> pro vyžádání hesla aplikace z webového rozhraní. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor ignorovaných souborů - - - - Files Ignored by Patterns - Ignorovat soubory podle masky - - - - Add - Přidat - - - - Pattern - Maska - - - - Allow Deletion - Povolit mazání - - - - Remove - Odebrat - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Nepodařilo se otevřít soubor - - - - Cannot write changes to '%1'. - Nelze zapsat změny do '%1'. - - - - Add Ignore Pattern - Přidat masku ignorovaných - - - - Add a new ignore pattern: - Přidat novou masku ignorovaných souborů: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Tato položka je poskytnuta systémem na '%1' a nemůže být v tomto pohledu změněna. - - - - OCC::IssuesWidget - - - Form - Formulář - - - - List of issues - Seznam problémů - - - - Filter - Filtr - - - - There were too many issues. Not all will be visible here. - Přiliš mnoho chyb...... Ne všechny jsou zde k vidění. - - - - There were conflicts. Check the documentation on how to resolve them. - Byly nalezeny konflikty. Přečtěte si dokumentaci, jak je vyřešit. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Byly nalezeny konflikty. <a href="%1">Přečtěte si dokumentaci k jejich vyřešení.</a> - - - - Account - Účet - - - - OCC::LibCloudProvidersPrivate - - - Settings - Nastavení - - - - OCC::LogBrowser - - - Log Output - Zaznamenat výstup - - - - Enable logging to temporary folder - Povolit logování do dočasné složky - - - - Delete logs older than %1 hours - Smazat logy starší než %1 hodin - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">Log obsahuje citlivé údaje, které by neměly být zveřejňovány</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - Aplikace může ukládat ukecané logy do dočasné složky. Tyto logy jsou velmi užitečné k hledání problému. -Jelikož mohou být tyto soubory velmi velké, aplikace vytvoří nový pro každou synchronizaci a komprimuje všechny předchozí. Je doporučeno povolit mazání logů po několika hodinách z důvodu konzumace velkého místa na disku. - - - - If enabled, logs will be written to: - Pokud povoleno, logy budou uloženy do: - - - - C:/log - C:/log - - - - Log Http traffic - Logovat Http provoz - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Tato nastavení budou platná i po restartu aplikace. -Poznámka: Spouštění aplikace s parametrem přepíše toto nastavení. - - - - Open folder - Otevřít adresář - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Je dostupná nová verze - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Je k dispozici nová verze klienta %1.</p><p><b>%2</b> je k dispozici ke stažení. Momentálně je nainstalována verze %3.</p> - - - - - Skip this version - Přeskoč tuto verzi - - - - Skip this time - Tentokrát přeskočit - - - - Get update - Získat aktualizaci - - - - Update Failed - Aktualizace selhala - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Je k dispozici nová verze aplikace (%1), ale její aktualizace selhala.</p><p><b>%2</b> byla stažena. Nainstalovaná verze je %3.</p> - - - - Ask again later - Zeptej se příště - - - - Restart and update - Restartovat a aktualizovat - - - - Update manually - Ruční aktualizace - - - - OCC::NetworkSettings - - - Form - Formulář - - - - Proxy Settings - Nastavení proxy - - - - No Proxy - Bez proxy - - - - Use system proxy - Použít systémové proxy - - - - Specify proxy manually as - Zadat proxy server ručně jako - - - - Host - Počítač - - - - : - : - - - - Proxy server requires authentication - Proxy server vyžaduje přihlášení - - - - Note: proxy settings have no effects for accounts on localhost - Poznámka: nastavení proxy nemá vliv na účty z localhostu - - - - Download Bandwidth - Rychlost stahování - - - - - Limit to - Omezit na - - - - - KBytes/s - KBytů/s - - - - - No limit - Bez limitu - - - - - Limit to 3/4 of estimated bandwidth - Omezit na 3/4 odhadované rychlosti - - - - Upload Bandwidth - Rychlost odesílání - - - - - Limit automatically - Omezovat automaticky - - - - Hostname of proxy server - Adresa proxy serveru - - - - Username for proxy server - Uživatelské jméno pro proxy server - - - - Password for proxy server - Heslo pro proxy server - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - Vytvořen v %1 - - - - Closing in a few seconds... - Uzavření za několik sekund... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 požadavek selhal při %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' vybrán na %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Chybná odpověď serveru: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Není možné ověřit formát JSON souboru přijatého ze serveru: <br><em>%1</em> - - - - Unsupported token type: %1 - Nepodporovaný typ tokenu: %1 - - - - The reply from the server did not contain all expected fields -:%1 - Odpověď serveru neobsahovala všechna očekávaná pole: -%1 - - - - Unknown Error - Neznámá chyba - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>Chyba přihlášení</h1><p>Nepovedlo se stáhnout informace o uživateli</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Chyba přihlášení</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Chyba při obnově tokenu: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - Odpověď serveru neobsahovala všechna očekávaná pole: -%1 -Přijatá data: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Chybný uživatel</h1><p>Přihlásil jste se jako uživatel <em>%1</em>, ale musíte se přihlásit jako <em>%2</em>.<br>Prosím odhlaste se z %3 v jiném okně, a poté <a href='%4'>klikněte sem</a> a přihlaste se jako %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Je připravena nová aktualizace %1 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Bude instalována nová aktualizace pro %1. V průběhu aktualizace -můžete být požádáni o dodatečná oprávnění. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Nemohu stáhnout aktualizaci. Klikněte prosím na <a href='%1'>tento odkaz</a> pro ruční stažení aktualizace. - - - - Could not check for new updates. - Nemohu zkontrolovat aktualizace. - - - - Downloading %1. Please wait... - Stahování %1. Prosím čekejte... - - - - %1 available. Restart application to start the update. - %1 dostupná. Restartujte aplikaci pro zahájení aktualizace. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Nová %1 dostupná. Prosím, použijte aktualizační nástroje systému k její instalaci. - - - - Checking update server... - Kontroluji aktualizační server... - - - - Update status is unknown: Did not check for new updates. - Stav aktualizace neznámý: Nové aktualizace nebyly zkontrolovány. - - - - No updates available. Your installation is at the latest version. - Žádne aktualizace nejsou k dispozici. Používáte aktuální verzi. - - - - Update Check - Ověřit aktualizace - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Připojit k %1 - - - - Setup local folder options - Možnosti nastavení místního adresáře - - - - Connect... - Připojit... - - - - (experimental) - (experimentální) - - - - Use &virtual files instead of downloading content immediately%1 - Použijte &virtuální soubory namísto přímého stahování obsahu %1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Synchronizovat adresář '%1' - - - - Virtual files are not available for the selected folder - Virtuální soubory nejsou dostupné pro zvolenou složku - - - - Error - Chyba - - - - Local Sync Folder - Místní synchronizovaný adresář - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Email - - - - Connect to %1 - Připojit k %1 - - - - Enter user credentials - Zadejte přihlašovací údaje - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Připojit k %1 - - - - Login in your browser - Přihlášení v prohlížeči - - - - Copy link to clipboard - Zkopírovat URL do schránky - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - Soubor %1 je právě využíván - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Připojit k %1 - - - - Setup %1 server - Nastavit server %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Tato adresa NENÍ bezpečná, protože není šifrovaná. -Nedoporučuje se jí používat. - - - - This url is secure. You can use it. - URL je bezpečná. Můžete ji použít. - - - - &Next > - &Následující > - - - - Invalid URL - Neplatná URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Selhalo spojení s %1 v %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Vypršení časového limitu při pokusu o připojení k %1 na %2. - - - - Invalid URL - Neplatná URL - - - - Could not create local folder %1 - Nelze vytvořit místní adresář %1 - - - - Error: %1 - Chyba: %1 - - - - The folder creation resulted in HTTP error code %1 - Vytvoření adresáře selhalo s HTTP chybou %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Vytvoření vzdáleného adresáře selhalo, pravděpodobně z důvodu neplatných přihlašovacích údajů.<br/>Vraťte se prosím zpět a zkontrolujte je.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Vytváření vzdáleného adresáře %1 selhalo s chybou <tt>%2</tt>. - - - - Folder rename failed - Přejmenování adresáře selhalo - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Nelze odstranit a zazálohovat adresář, protože adresář nebo soubor v něm je otevřen v jiném programu. Prosím zavřete adresář nebo soubor a zkuste znovu nebo zrušte akci. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Průvodce spojením - - - - Enable experimental feature? - Povolit experimentální fukce? - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - Buď zdráv! - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Symbolické odkazy nejsou při synchronizaci podporovány. - - - - File is listed on the ignore list. - Soubor se nachází na seznamu ignorovaných. - - - - File names ending with a period are not supported on this file system. - Jména souborů končících tečkou nejsou na tomto systému souborů podporována. - - - - File names containing the character '%1' are not supported on this file system. - Názvy souborů obsahující znak '%1' nejsou na tomto souborovém systému podporovány. - - - - File name contains at least one invalid character - Jméno souboru obsahuje alespoň jeden neplatný znak - - - - The file name is a reserved name on this file system. - Jméno souboru je na tomto systému souborů rezervovaným jménem. - - - - Filename contains trailing spaces. - Jméno souboru obsahuje mezery na konci řádky. - - - - Filename is too long. - Jméno souboru je příliš dlouhé. - - - - File/Folder is ignored because it's hidden. - Soubor/adresář je ignorován, protože je skrytý. - - - - Stat failed. - Stat selhal. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Conflict: Stažena verze ze serveru, lokální verze přejmenována a nenahrána na server. - - - - The filename cannot be encoded on your file system. - Název souboru nelze zakódovat ve vašem souborovém systému. - - - - The filename is blacklisted on the server. - Soubor je na černé listině serveru. - - - - size - velikost - - - - permissions - práva - - - - etag - etag - - - - file id - id souboru - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - Odstranění není povoleno, obnovuji - - - - Ignored because of the "choose what to sync" blacklist - Ignorováno podle nastavení "vybrat co synchronizovat" - - - - Not allowed because you don't have permission to add subfolders to that folder - Není povoleno, protože nemáte oprávnění přidávat podadresáře do tohoto adresáře - - - - Not allowed because you don't have permission to add files in that folder - Není povoleno, protože nemáte oprávnění přidávat soubory do tohoto adresáře - - - - Not allowed to upload this file because it is read-only on the server, restoring - Není povoleno nahrát tento soubor, protože je na serveru uložen pouze pro čtení, obnovuji - - - - Error while reading the database - Chyba při čtení databáze - - - - Server replied with an error while reading directory '%1' : %2 - Server odpověděl chybou při čtení složky '%1' : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - Stažení by snížilo velikost volného místa na disku pod limit - - - - Free space on disk is less than %1 - Volné místo na disku je méně než %1 - - - - File was deleted from server - Soubor byl smazán ze serveru - - - - The file could not be downloaded completely. - Soubor nemohl být kompletně stažen. - - - - The downloaded file is empty despite the server announced it should have been %1. - Stažený soubor je prázdný, přestože server oznámil, že měl být %1. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - Soubor se mezitím změnil - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Obnovení selhalo: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - smazání souboru %1 se nezdařilo, chyba: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - nepodařilo se vytvořit adresář %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Soubor %1 nemohl být přejmenován na %2 z důvodu kolize názvu se souborem v místním systému - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Server vrátil neplatný HTTP kód. Očekáván 204, ale obdržen "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Server vrátil neplatný HTTP kód. Očekáván 201, ale obdržen "%1 %2". - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - Nelze přejmenovat %1 na %2, chyba: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Server vrátil neplatný HTTP kód. Očekáván 201, ale obdržen "%1 %2". - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - Chyba zápisu metadat do databáze - - - - Error setting pin state - - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - Nedaří se aktualizovat soubor: %1 - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Soubor %1 nelze nahrát, protože existuje jiný soubor se stejným názvem, lišící se pouze velikostí písmen - - - - File Removed - Soubor odebrán - - - - Local file changed during syncing. It will be resumed. - Místní soubor se během synchronizace změnil. - - - - Local file changed during sync. - Místní soubor byl změněn během synchronizace. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - Nahrávání %1 překročilo velikostní limit složky - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Místní soubor byl odstraněn během synchronizace. - - - - Local file changed during sync. - Místní soubor byl změněn během synchronizace. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - Neočekávaný návratový kód ze serveru (%1) - - - - Missing File ID from server - Chybějící souborové ID ze serveru - - - - Missing ETag from server - Chybějící ETag ze serveru - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Místní soubor byl odstraněn během synchronizace. - - - - Local file changed during sync. - Místní soubor byl změněn během synchronizace. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - Místní soubor byl odstraněn během synchronizace. - - - - Local file changed during sync. - Místní soubor byl změněn během synchronizace. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Server nepotvrdil poslední část dat. (Nebyl nalezen e-tag) - - - - OCC::ProtocolItemModel - - - Time - Čas - - - - File - Soubor - - - - Folder - Adresář - - - - Issues - - - - - Action - Akce - - - - Size - Velikost - - - - Account - Účet - - - - Status - Stav - - - - OCC::ProtocolWidget - - - Form - Formulář - - - - Local sync protocol - Místní protokol synchronizace - - - - Filter - Filtr - - - - Account - Účet - - - - Copy to clipboard - Zkopírovat do schránky - - - - Show in file browser - - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Vyžadováno ověření pro proxy - - - - Username: - Uživatelské jméno: - - - - Proxy: - Proxy: - - - - The proxy server needs a username and password. - Proxy server vyžaduje uživatelské jméno a heslo. - - - - Password: - Heslo: - - - - TextLabel - Textový popisek - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Vybrat co synchronizovat - - - - OCC::SelectiveSyncWidget - - - Loading ... - Načítám ... - - - - Deselect remote folders you do not wish to synchronize. - Zrušte výběr vzdálených složek, které nechcete synchronizovat. - - - - Name - Název - - - - Size - Velikost - - - - - No subfolders currently on the server. - Na serveru nejsou momentálně žádné podadresáře. - - - - An error occurred while loading the list of sub folders. - Došlo k chybě v průběhu načítání seznamu podadresářů. - - - - OCC::ServerNotificationHandler - - - Dismiss - Zamítnout - - - - OCC::SettingsDialog - - - Settings - Nastavení - - - - Activity - Aktivita - - - - Add account - Přidat účet - - - - - Quit %1 - Ukončit %1 - - - - Are you sure you want to quit %1? - Jste si jistí, že chcete skončit %1? - - - - Account - Účet - - - - MainWindow - - - - - Hide - Skrýt - - - - OCC::ShareDialog - - - TextLabel - Textový popisek - - - - share label - sdílet popisek - - - - Dialog - Dialog - - - - ownCloud Path: - ownCloud cesta: - - - - %1 Sharing - %1 sdílení - - - - %1 - %1 - - - - Folder: %2 - Adresář: %2 - - - - The server does not allow sharing - Sdílení je na serveru zakázáno - - - - Retrieving maximum possible sharing permissions from server... - Přijímání nejvyšších možných oprávnění pro sdílení ze serveru... - - - - The file can not be shared because it was shared without sharing permission. - Tento soubor nelze sdílet, protože byl nasdílen bez možnosti dalšího sdílení. - - - - Users and Groups - Uživatelé a skupiny - - - - Public Links - Veřejné odkazy - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Sdílet NewDocument.odt - - - - Upload only (File Drop) - Pouze nahrávání - - - - Receive files from others without revealing the contents of the folder. - Příjemce může nahrát soubor do složky, ale neuvidí její obsah. - - - - Download / View / Edit - Stáhnout / zobrazit / upravit - - - - Name: - Jméno: - - - - Enter name of new share... - Zadejte název nového sdílení... - - - - &Create - &Vytvořit - - - - TextLabel - Textový popisek - - - - Set &password - Nastavit &heslo - - - - Set &expiration date - Nastavit datum &vypršení - - - - Set password - Nastavit heslo - - - - Recipients can view or download contents. - Příjemce může zobrazit a stáhnout obsah - - - - Download / View - Stáhnout / zobrazit - - - - Recipients can view, download, edit, delete and upload contents. - Příjemce může zobrazit, stáhnout, upravovat, mazat a nahrávat obsah. - - - - Anyone with the link has access to the file/folder - Kdokoliv, kdo má odkaz, může přistupovat k tomuto souboru/složce - - - - - P&assword protect - Ch&ránit heslem - - - - The file can not be shared because it was shared without sharing permission. - Tento soubor nelze sdílet, protože byl nasdílen bez možnosti dalšího sdílení. - - - - Link shares have been disabled - Sdílení odkazu bylo zakázáno - - - - Create public link share - Vytvoření veřejného odkazu - - - - - Delete - Smazat - - - - Open link in browser - Otevřít odkaz v prohlížeči - - - - Copy link to clipboard - Zkopírovat odkaz do schránky - - - - Copy link to clipboard (direct download) - Zkopírovat odkaz do schránky (přímé stažení) - - - - Send link by email - Poslat odkaz emailem - - - - Send link by email (direct download) - Poslat odkaz emailem (přímé stažení) - - - - Create new... - Vytvořit nový... - - - - I shared %1 with you - Sdílím s Vámi %1 - - - - Confirm Link Share Deletion - Potvrdit smazání sdíleného odkazu - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Opravdu chcete smazat veřejný odkaz ke sdílení <i>%1</i>?</p><p>Poznámka: Toto je nevratná akce.</p> - - - - Cancel - Zrušit - - - - - Public link - Veřejný odkaz - - - - Delete link share - Smazat odkaz ke sdílení - - - - - Please Set Password - Nastavte prosím heslo - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Sdílet NewDocument.odt - - - - Share with users or groups ... - Sdílet s uživateli nebo skupinami - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Můžete nasměrovat lidi na tento soubor nebo složku tím, že jim dáte<a href="private link menu"><span style=" text-decoration: underline">soukromý odkaz</span></a>.</p></body></html> - - - - The item is not shared with any users or groups - Položka není sdílena s žádným uživatelem ani skupinou - - - - Open link in browser - Otevřít odkaz v prohlížeči - - - - Copy link to clipboard - Zkopírovat URL do schránky - - - - Send link by email - Poslat odkaz emailem - - - - No results for '%1' - Žádné výsledky pro '%1' - - - - I shared something with you - Něco s Vámi sdílím - - - - OCC::ShareUserLine - - - Form - Formulář - - - - - TextLabel - Textový popisek - - - - can edit - lze upravovat - - - - can share - může sdílet - - - - ... - ... - - - - create - vytvořit - - - - change - změnit - - - - delete - smazat - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Sdílet s %1 - - - - Context menu share - - - - - Context menu share %1 - - - - - Confirm deletion - Potvrdit smazání - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Opravdu chcete trvale smazat složku <i>%1</i> včetně jejího obsahu? - - - - Do you want to delete the file <i>%1</i> permanently? - Opravdu si přejete trvale odstranit soubor <i>%1</i>? - - - - Select new location... - Vybrat nové umístění... - - - - Error - Chyba - - - - Moving file failed: - -%1 - Přesun souboru se nepovedl: - -%1 - - - - I shared something with you - Něco s Vámi sdílím - - - - - Share... - Sdílet... - - - - - Copy private link to clipboard - Zkopírovat soukromý odkaz do schránky - - - - Send private link by email... - Odeslat soukromý odkaz emailem... - - - - Resharing this file is not allowed - Sdílení tohoto odkazu není dovoleno - - - - Resharing this folder is not allowed - Sdílení této složky není dovoleno - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - Zkopírovat veřejný odkaz do schránky - - - - Open in browser - Otevřít v prohlížeči - - - - Show file versions in browser - Zobrazit verze souboru v prohlížeči - - - - Rename... - Přejmenovat... - - - - Rename and upload... - Přejmenovat a nahrát... - - - - Move and rename... - Přesunout a přejmenovat... - - - - Move, rename and upload... - Přesunout, přejmenovat a nahrát... - - - - Delete local changes - Smazat místní změny - - - - Move and upload... - Přesunout a nahrát... - - - - Delete - Smazat - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Detaily certifikátu</h3> - - - - Common Name (CN): - Běžný název (CN): - - - - Subject Alternative Names: - Alternativní jména subjektu: - - - - Organization (O): - Organizace (O): - - - - Organizational Unit (OU): - Organizační jednotka (OU): - - - - State/Province: - Stát/provincie: - - - - Country: - Země: - - - - Serial: - Sériové číslo: - - - - <h3>Issuer</h3> - <h3>Vydavatel</h3> - - - - Issuer: - Vydavatel: - - - - Issued on: - Vydáno: - - - - Expires on: - Platný do: - - - - <h3>Fingerprints</h3> - <h3>Otisky</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Poznámka:</b> Tento certifikát byl schválen ručně</p> - - - - %1 (self-signed) - %1 (podepsaný sám sebou) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Toto spojení je šifrováno pomocí %1 bitové šifry %2 - - - - - Server version: %1 - Verze serveru: %1 - - - - No support for SSL session tickets/identifiers - Podpora tiketů/identifikátorů SSL sezení není dostupná - - - - Certificate information: - Informace o certifikátu: - - - - The connection is not secure - Připojení není bezpečné - - - - This connection is NOT secure as it is not encrypted. - - Toto spojení NENÍ bezpečné, protože není šifrované. - - - - - OCC::SslErrorDialog - - - Form - Formulář - - - - Trust this certificate anyway - Přesto tomuto certifikátu důvěřovat - - - - Untrusted Certificate - Nedůvěryhodný certifikát - - - - Cannot connect securely to <i>%1</i>: - Nelze se bezpečně připojit k <i>%1</i>: - - - - with Certificate %1 - s certifikátem %1 - - - - - - &lt;not specified&gt; - &lt;nespecifikováno&gt; - - - - - Organization: %1 - Organizace: %1 - - - - - Unit: %1 - Jednotka: %1 - - - - - Country: %1 - Země: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Otisk (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Otisk (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Datum účinnosti: %1 - - - - Expiration Date: %1 - Datum vypršení: %1 - - - - Issuer: %1 - Vydavatel: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (Přeskočeno kvůli předchozí chybě. Zkusím to znovu za %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Je dostupných pouze %1, pro spuštění je potřeba alespoň %2 - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - Na serveru není dostatek místa. - - - - Unresolved conflict. - Nevyřešený konflikt. - - - - Using virtual files with suffix, but suffix is not set - Používání virtuálních souborů s koncovkou, která ovšem není nastavena - - - - Unable to read the blacklist from the local database - Nelze načíst blacklist z místní databáze - - - - Unable to read from the sync journal. - Nelze číst ze žurnálu synchronizace. - - - - Cannot open the sync journal - Nelze otevřít synchronizační žurnál - - - - Aborted - Zrušeno - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>Verze %1. Pro více informací navštivte <a href="%2">https://%3</a></p><p>Pro nahlášení chyb a podporu navštivte prosím: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Autoři: Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, a ostatní.</small></p><p>Copyright ownCloud GmbH</p><p>Distribuováno %4 pod licencí GNU General Public License (GPL) Version 2.0.<br/>%5 a %5 logo jsou registrované obchodní známky %4 ve Spojených státech amerických, jiných zemích, nebo obojí.</p><p><small>%6</small></p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Hlavička kontrolního součtu je poškozena. - - - - The checksum header contained an unknown checksum type '%1' - Hlavička kontrolního součtu obsahovala neznámý typ součtu '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - Stažený soubor neodpovídá kontrolnímu součtu, bude pokračováno. '%1' != '%2' - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - Virtuální souborový systém vyžaduje NTFS, ale %1 používá %2 - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Přihlašte se prosím - - - - Folder %1: %2 - Adresář %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - Nejsou nastaveny žádné adresáře pro synchronizaci. - - - - Open in browser - Otevřít v prohlížeči - - - - - - Log in... - Přihlásit... - - - - - - Log out - Odhlásit se - - - - Recent Changes - Poslední změny - - - - Managed Folders: - Spravované adresáře: - - - - Open folder '%1' - Otevřít adresář '%1' - - - - Open %1 in browser - Otevřít %1 v prohlížeči - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Neznámý stav - - - - Settings... - Nastavení... - - - - Details... - Podrobnosti... - - - - Help - Nápověda - - - - Quit %1 - Ukončit %1 - - - - Disconnected from %1 - Odpojeno od %1 - - - - Unsupported Server Version - Nepodporovaná verze serveru - - - - Disconnected - Odpojeno - - - - Disconnected from some accounts - Odpojeno z některých účtů - - - - Disconnected from accounts: - Odpojeno od účtů: - - - - Account %1: %2 - Účet %1: %2 - - - - Signed out - Odhlášeno - - - - Account synchronization is disabled - Synchronizace účtu je vypnuta - - - - Synchronization is paused - Synchronizace je pozastavena - - - - Up to date (%1) - - - - - No sync folders configured - - - - - Unpause all folders - Spustit všechny adresáře - - - - Pause all folders - Pozastavit všechny adresáře - - - - Unpause all synchronization - Spustit veškerou synchronizaci - - - - Unpause synchronization - Spustit synchronizaci - - - - Pause all synchronization - Pozastavit veškerou synchronizaci - - - - Pause synchronization - Pozastavit synchronizaci - - - - Log out of all accounts - Odhlásit ze všech účtů - - - - Log in to all accounts... - Přihlásit ke všem účtům... - - - - New account... - Nový účet... - - - - About %1 - O %1 - - - - No items synced recently - Žádné položky nebyly nedávno synchronizovány - - - - Checking for changes in remote '%1' - Zjišťuji změny ve vzdáleném '%1' - - - - Checking for changes in local '%1' - Zjišťuji změny v místním '%1' - - - - Syncing %1 of %2 (%3 left) - Synchronizuji %1 ze %2 (zbývá %3) - - - - Syncing %1 of %2 - Synchronizace %1 z %2 - - - - Syncing %1 (%2 left) - Synchronizuji %1 (zbývá %2) - - - - Syncing %1 - Synchronizuji %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - Formulář - - - - - TextLabel - Textový štítek - - - - Server - Server - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Pokud je tato volba zaškrtnuta, aktuální obsah v místním adresáři bude smazán a bude zahájena nová synchronizace ze serveru.</p><p>Nezaškrtávejte pokud má být místní obsah nahrán do adresářů na serveru.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - Zeptat se na synchroni&zaci složek větších než - - - - S&ynchronize everything from server - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Zeptat se na potvrzení před synchronizací e&xterních úložišť - - - - Choose what to sync - Vybrat co sesynchronizovat - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - Místní s&ložka - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Ponechat místní data - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - Formulář - - - - &Username - &Uživatelské jméno - - - - &Password - &Heslo - - - - OwncloudOAuthCredsPage - - - Form - Formulář - - - - Please switch to your browser to proceed. - Chcete-li pokračovat, přepněte prosím do svého prohlížeče. - - - - An error occurred while connecting. Please try again. - Při připojování došlo k chybě. Prosím zkuste to znovu. - - - - Re-open Browser - Znovu otevřít prohlížeč - - - - OwncloudSetupPage - - - Form - Formulář - - - - Ser&ver Address - Adresa serveru - - - - https://... - https://... - - - - Error Label - Popis chyby - - - - QObject - - - in the future - V budoucnosti - - - - %n day(s) ago - před %n dnempřed %n dnypřed %n dnypřed %n dny - - - - %n hour(s) ago - před %n hodinoupřed %n hodinamipřed %n hodinamipřed %n hodinami - - - - now - nyní - - - - Less than a minute ago - Méně než před minutou - - - - %n minute(s) ago - před %n minutoupřed %n minutamipřed %n minutamipřed %n minutami - - - - Some time ago - Před nějakým časem - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - Kritická chyba - - - - - Error - Chyba - - - - Info - Info - - - - Success - Úspěch - - - - Conflict - Konflikt - - - - File Ignored - - - - - Restored - - - - - Blacklisted - Na černé listině - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 kB - - - - %L1 B - %L1 B - - - - %n year(s) - %n rok%n roky%n let%n let - - - - %n month(s) - %n měsíc%n měsíce%n měsíců%n měsíců - - - - %n day(s) - %n den%n dny%n dní%n dní - - - - %n hour(s) - %n hodina%n hodiny%n hodin%n hodin - - - - %n minute(s) - %n minuta%n minuty%n minut%n minut - - - - %n second(s) - %n sekunda%n sekundy%n sekund%n sekund - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Systémová lišta není k dispozici - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 vyžaduje fungující systémovou lištu. Pokud používáte XFCE, řiďte se <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">těmito instrukcemi</a>. V ostatních případech prosím nainstalujte do svého systému aplikaci pro systémovou lištu, např. 'trayer', a zkuste to znovu. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - Virtuální soubor vytvořen - - - - Replaced by virtual file - Nahrazeno virtuálním souborem - - - - Downloaded - Staženo - - - - Uploaded - Odesláno - - - - Server version downloaded, copied changed local file into conflict file - Stažena verze ze serveru, změněný místní soubor zkopírován do konfliktního souboru - - - - Deleted - Smazáno - - - - Moved to %1 - Přesunuto do %1 - - - - Ignored - Ignorováno - - - - Filesystem access error - Chyba přístupu k souborovému systému - - - - Error - Chyba - - - - Updated local metadata - Místní metadata aktualizována - - - - - Unknown - Neznámý - - - - downloading - stahování - - - - uploading - odesílání - - - - deleting - mazání - - - - moving - přesouvání - - - - ignoring - ignoruji - - - - - error - chyba - - - - updating local metadata - aktualizace místních metadat - - - - theme - - - Status undefined - Nedefinovaný stav - - - - Waiting to start sync - Čekám na zahájení synchronizace - - - - Sync is running - Synchronizace běží - - - - Sync Success - Synchronizace úspěšná - - - - Sync Success, some files were ignored. - Synchronizace úspěšná, některé soubory byly ignorovány. - - - - Sync Error - Chyba synchronizace - - - - Setup Error - Chyba nastavení - - - - Preparing to sync - Připravuji na synchronizaci - - - - Aborting... - Ruším... - - - - Sync is paused - Synchronizace pozastavena - - - - utility - - - Could not open browser - Nemohu otevřít prohlížeč - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - Vznikla chyba při otevírání prohlížeče s adresou %1. Možná není žádný nastavený výchozí prohlížeč? - - - - Could not open email client - Nelze otevřít poštovního klienta - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Došlo k chybě při otevírání nové zprávy v emailovém klientu. Možná nebyl nastaven výchozí emailový klient? - - - - Always available locally - Vždy dostupné lokálně - - - - Some available online only - Některé dostupné pouze online - - - - - Available online only - Dostupné pouze online - - - - Currently available locally - Aktuálně dostupné lokálně - - - - Make always available locally - Chci vždy dostupné lokálně - - - - Free up local space - Uvolnit místo na místním disku - - - \ No newline at end of file diff --git a/translations/client_de-informal.ts b/translations/client_de-informal.ts new file mode 100644 index 00000000000..17745672c13 --- /dev/null +++ b/translations/client_de-informal.ts @@ -0,0 +1,3680 @@ + + + AccountBar + + + Navigation bar + Navigationsleiste + + + + Add Account + Konto hinzufügen + + + + Activity + Aktivität + + + + Settings + Einstellungen + + + + Quit + Beenden + + + + AppImageUpdateAvailableWidgetUi + + + Skip this version + Diese Version überspringen + + + + AppProvider + + + Failed to open %1 in web. Error: %2. + %1 konnte in Web nicht geöffnet werden. Fehler: %2. + + + + ChecksumHeader + + + The checksum header is malformed: %1 + Der Prüfsummen-Header hat ein fehlerhaftes Format: %1 + + + + The checksum header contained an unknown checksum type '%1' + Der Prüfsummenkopf beinhaltet eine unbekannte Prüfsumme vom Typ '%1' + + + + CommandLine + + + %1 version %2 +File synchronization desktop utility. + %1 Version %2<byte value="xd"/> +Datei Synchronisations-Applikation. + + + + For more information, see %1 + link to homepage + Siehe %1 für mehr Informationen. + + + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Starte den Client mit dem Hauptfenster sichtbar. Falls er bereits läuft, hol das Fenster in den Vordergrund. Standardmäßig wird es im Hintergrund geöffnet. + + + + Quit the running instance. + Die laufende Instanz beenden. + + + + Write log to file (use - to write to stdout). + Protokolldatei schreiben (Ausgabe nach stdout). + + + + Write each sync log output in a new file in folder. + Jedes Synchronisationsprotokoll in eine neue Datei im Verzeichnis schreiben. + + + + Flush the log file after every write. + Protokolldatei nach jedem Schreibvorgang leeren. + + + + Output debug-level messages in the log. + Debug-Meldungen in die Protokolldatei schreiben. + + + + Enable debug mode. + Aktivieren des Debug-Modus + + + + --logfile and --logdir are mutually exclusive + --logfile und --logdir schließen sich gegenseitig aus. + + + + CommonStrings + + + file manager + Dateimanager + + + + Show in %1 + In %1 anzeigen + + + + Show in web browser + Im Webbrowser anzeigen + + + + Copy + Kopieren + + + + %n Filter(s) + %1 Filter%n Filter + + + + folder + Ordner + + + + space + Space + + + + folders + Ordner + + + + spaces + Spaces + + + + Folder + Ordner + + + + Space + Space + + + + FetchUserInfoJobFactory + + + Failed to retrieve user info + Die Benutzerinformationen konnten nicht abgerufen werden + + + + FileSystem + + + + + + Can't rename %1, the file is currently in use + Kann %1 nicht umbenennen, die Datei wird gerade benutzt + + + + Could not remove folder + Der Ordner konnte nicht gelöscht weden + + + + FolderItem + + + + Offline + Offline + + + + + Unavailable: the space is no longer available on the server + Nicht verfügbar: Der Speicherplatz ist auf dem Server nicht mehr verfügbar + + + + + Sync paused + Synchronisation wurde angehalten + + + + Synced + Synchronisiert + + + + Sync failed + Synchronisation fehlgeschlagen + + + + Sync pending + Synchronisierung steht noch aus + + + + Syncing %1 of %2 (%3 % + %1 von %2 werden synchronisiert (%3 % + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, Synchronisierungsstatus: %2 + first argument is the displayed name of the folder. + + + + FolderManagementUtils + + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + Der Pfad '%1' ist zu lang. Entweder die Unterstützung für lange Pfadnamen in den Windows Einstellungen einschalten oder ein anderes Verzeichnis auswählen. + + + + Local folder %1 does not exist. + Das Lokale Verzeichnis %1 existiert nicht + + + + %1 is not a folder. + %1 ist kein Ordner + + + + %1 is not readable. + %1 ist nicht lesbar. + + + + %1 is not writable. + %1 ist nicht beschreibbar. + + + + FolderWizard + + + <b>Error:</b> %1 + <b>Fehler:</b> %1 + + + + <b>Warning:</b> %1 + <b>Warnung:</b> %1 + + + + <b>Error:</b><ul>%1</ul> + <b>Fehler:</b><ul>%1</ul> + + + + <b>Warning:</b><ul>%1</ul> + <b>Warnung:</b><ul>%1</ul> + + + + FolderWizardSourcePage + + + Form + Formular + + + + Pick a local folder on your computer to sync + Wähle einen lokalen Ordner zum Synchronisieren aus + + + + Browse… + Durchsuchen… + + + + FolderWizardTargetPage + + + Form + Formular + + + + Select a remote destination folder + Entfernten Zielordner auswählen + + + + Create Folder + Ordner erstellen + + + + Refresh + Aktualisieren + + + + Folders + Ordner + + + + OCC::AboutView + + + About + Über + + + + Versions + Versionen + + + + OCC::AbstractNetworkJob + + + Connection timed out + Zeitüberschreitung beim Verbindungsaufbau + + + + Unknown error: network reply was deleted + Unbekannter Fehler: Die Netzwerk-Antwort wurde gelöscht + + + + Server replied "%1 %2" to "%3 %4" + Der Server hat "%1 %2" auf "%3 %4" geantwortet + + + + OCC::Account + + + %1@%2 + %1@%2 + + + + OCC::AccountFoldersController + + + Choose what to sync + Zu synchronisierende Elemente auswählen + + + + + Sync now + Jetzt synchronisieren + + + + Manage subfolder sync + Synchronisierung von Unterordnern verwalten + + + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + %1-Synchronisierung entfernen + + + + Disable virtual file support? + Deaktiviere Unterstützung für virtuelle Dateien? + + + + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. + +The only advantage of disabling virtual file support is that the selective sync feature will become available again. + +This action will abort any currently running synchronization. + Diese Aktion wird die Unterstützung für virtuelle Dateien abschalten. Als Konsequenz werden Inhalte aus Ordnern heruntergeladen, die aktuell als 'nur online verfügbar' markiert sind. + +Das Feature "selective sync" wird wieder verfügbar. Diese Aktion bricht die aktuell laufende Synchronisation ab. + + + + Disable support + Unterstützung abschalten + + + + Restart sync + Synchronisation neu starten + + + + Resume sync + Synchronisation fortsetzen + + + + Pause sync + Synchronisation pausieren + + + + Deactivate virtual files + Virtuelle Dateien deaktivieren + + + + Activate virtual files + Virtuelle Dateien aktivieren + + + + Internet connection is metered + Die Internetverbindung ist getaktet. + + + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + Synchronisierung wird pausiert, weil die Internetverbindung getaktet ist<p>Soll die sofortige Fortsetzung der Synchronisierung wirklich erzwungen werden? + + + + Sync Running + Synchronisation läuft + + + + The sync operation is running.<br/>Do you want to stop it? + Die Synchronisation läuft gerade.<br/>Trotzdem beenden? + + + + Sync paused by user + Synchronisierung vom Benutzer pausiert + + + + Confirm Folder Sync Connection Removal + Löschen der Ordnersychronisation bestätigen + + + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Möchten Sie das Verzeichnis <i>%1</i> wirklich nicht mehr synchronisieren?</p><p><b>Hinweis:</b> Dies wird <b>keine</b> Daten löschen.</p> + + + + Remove Folder Sync Connection + Ordner-Synchronisation entfernen + + + + + Cancel + Abbrechen + + + + OCC::AccountFoldersView + + + Sync options menu + Menü „Synchronisierungsoptionen“ + + + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 synchronisieren + + + + Manage your synced %1. + Verwalten Sie Ihre synchronisierten %1. + + + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Neue %1-Synchronisierung hinzufügen… + + + + %1 list view + %1 Listenansicht + + + + Navigate the %1 list using the up and down arrows + Navigieren Sie mit den Auf- und Ab-Pfeilen durch die Liste %1 + + + + %1 out of %2 %3 are synchronized + %1 von %2 %3 sind synchronisiert + + + + OCC::AccountModalWidget + + + + Form + Formular + + + + OCC::AccountPlaceholderWidget + + + %1 logo + %1 Logo + + + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + + + + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo + + + + OCC::AccountView + + + Form + Formular + + + + Manage Account + Konto verwalten + + + + Preparing the account + Konto vorbereiten + + + + Sync connections + Ordnersynchronisierung + + + + Account options menu + Menü „Kontooptionen“ + + + + OCC::AccountViewController + + + + Log in + Anmelden + + + + Reconnect + Erneut verbinden + + + + Remove + Entfernen + + + + Log out + Abmelden + + + + Confirm Account Removal + Löschen des Benutzerkontos bestätigen + + + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Möchtest Du den Zugang zum Benutzerkonto <i>%1</i> wirklich entfernen?</p><p><b>Hinweis:</b>Es werden <b>keine</b> lokalen Daten gelöscht.</p> + + + + Remove connection + Zugang löschen + + + + Cancel + Abbrechen + + + + The server version %1 is unsupported! Proceed at your own risk. + Die Server Version %1 wird nicht unterstützt. Weitere Schritte erfolgen auf eigene Gefahr. + + + + Connected + Verbunden + + + + Server is temporarily unavailable. + + + + + Server is currently in maintenance mode. + + + + + Signed out + Abgemeldet + + + + Updating credentials… + Zugangsdaten werden aktualisiert… + + + + Captive portal prevents connections to the server. + Ein "Captive Portal" verhindert die Verbindung zum Server. + + + + Sync is paused due to metered internet connection. + + + + + Connecting… + Verbinde… + + + + Server configuration error + Serverkonfigurationsfehler + + + + Disconnected + Getrennt + + + + Invalid connection status + + + + + OCC::AccountsGuiController + + + + New account failure + Fehler bei neuem Konto + + + + The information required to create a new account is incomplete. Please run the wizard again. + Die für die Erstellung eines neuen Kontos erforderlichen Informationen sind unvollständig. Bitte führen Sie den Assistenten erneut aus. + + + + Accounts + Konten + + + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + Das Konto konnte aufgrund eines Fehlers nicht erstellt werden: +%1 +Bitte prüfen Sie die Serververfügbarkeit und führen Sie den Assistenten erneut aus. + + + + OCC::ActivitySettings + + + Local Activity + Lokale Aktivität + + + + Not Synced + Nicht synchronisiert + + + + Not Synced (%1) + %1 is the number of not synced files. + Nicht synchronisiert (%1) + + + + OCC::AdvancedSettingsPageController + + + Advanced settings + Erweiterte Einstellungen + + + + Sync and download options + Synchronisierungs- und Downloadoptionen + + + + Download location + Download-Speicherort + + + + Sync and download files as you use them (saves hard drive space) + Dateien werden synchronisiert und heruntergeladen, während sie verwendet werden (spart Festplattenspeicher). + + + + Automatically sync and download folders and files + Ordner und Dateien automatisch synchronisieren und herunterladen + + + + Sync and download folders manually + Ordner manuell synchronisieren und herunterladen + + + + Download location on the local machine + Download-Speicherort auf dem lokalen Rechner + + + + Browse… + Durchsuchen… + + + + Browse for a download location + Suchen Sie nach einem Download-Speicherort + + + + Invalid local download directory %1: %2 + Falsches lokales Download Verzeichnis %1: %2 + + + + your user directory may not be chosen as the sync root. + Ihr Benutzerverzeichnis kann nicht als Synchronisierungsstammverzeichnis ausgewählt werden. + + + + path is not located on a supported Apple File System. + Der Pfad befindet sich nicht auf einem unterstützten Apple-Dateisystem. + + + + path must be absolute. + Der Pfad muss absolut sein. + + + + selected path does not support using virtual file system. %1 + Der ausgewählte Pfad unterstützt die Verwendung eines virtuellen Dateisystems nicht. %1 + + + + Select sync root + Wählen Sie das Synchronisierungsstammverzeichnis aus + + + + OCC::AppImageUpdateAvailableWidget + + + Installed version: %1 + Installierte Version: %1 + + + + Available update: %1 + Update verfügbar: %1 + + + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Eine Aktualisierung ist für dieses AppImage verfügbar von %1. Möchtest du die Aktualisierung installieren? + +Die Aktualisierung wird im Hintergrund durchgeführt und überschreibt die aktuelle AppImage-Datei. Du musst die App neu starten, um die Aktualisierung abzuschließen. + + + + OCC::Application + + + Quit + Beenden + + + + OCC::AuthSuccessPageController + + + Application Name Logo %1 + Anwendungsname Logo %1 + + + + You're all set! Open %1 and get started. + Sie sind fertig! Öffnen Sie %1 und legen Sie los. + + + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo + + + + OCC::ButtonDelegate + + + %1 options button + %1-Optionen-Schaltfläche + + + + Menu button with %1 options. Use the space key to show the menu + Menü-Schaltfläche mit %1 Optionen. Drücke die Leertaste, um das Menü anzuzeigen + + + + OCC::ConnectionValidator + + + No ownCloud account configured + Kein ownCloud-Konto konfiguriert + + + + Please update to the latest server and restart the client. + Bitte auf die neueste Server-Version aktualisieren und den Client neu starten. + + + + Authentication error: Either username or password are wrong. + Authentifizierungsfehler: Entweder der Benutzername oder das Passwort sind falsch. + + + + timeout + Zeitüberschreitung + + + + No account configured + Kein Konto konfiguriert + + + + The provided credentials are not correct + Die zur Verfügung gestellten Anmeldeinformationen sind nicht korrekt + + + + The configured server for this client is too old. + Der konfigurierte Server ist für diesen Client veraltet. + + + + OCC::CredentialJob + + + Failed to parse credentials %1 + Fehler beim Verarbeiten der Zugangsdaten %1 + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + Der Server unterstützt OAuth nicht + + + + Server did not ask for authorization + Der Server hat nicht nach einer Autorisierung gefragt + + + + Failed to determine authentication type: %1 + Der Authentifizierungstyp konnte nicht bestimmt werden: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + Ungültige Antwort vom Server erhalten. + + + + OCC::DiscoverySingleDirectoryJob + + + The account was deleted before we could start the propfind job + Das Konto wurde gelöscht, bevor wir den Propfind-Job starten konnten. + + + + + Server error: PROPFIND reply is not XML formatted! + Server Fehler: Die PROPFIND Antwort ist nicht XML formattiert! + + + + OCC::DiscoverySingleLocalDirectoryJob + + + Error while opening directory %1 + Fehler beim Öffnen des Ordners %1 + + + + Directory not accessible on client, permission denied + Der Ordner ist auf dem Client nicht erreichbar, Zugriff verweigert + + + + Directory not found: %1 + Ordner nicht gefunden: %1 + + + + Error while reading directory %1 + Fehler beim Lesen des Ordners %1 + + + + OCC::ExpandingHeaderView + + + Reset column sizes + Spaltenbreiten zurücksetzen + + + + OCC::FetchCapabilitiesAdapter + + + server sent invalid content type: %1. + Der Server hat einen ungültigen Inhaltstyp gesendet: %1. + + + + could not parse Capabilities from server. + Die Capabilities vom Server konnten nicht analysiert werden. + + + + reply sent for different subject (server): %1 + Antwort wurde für ein anderes Subjekt (Server) gesendet: %1 + + + + OCC::Folder + + + %1 has been removed. + %1 names a file. + %1 wurde gelöscht. + + + + %1 has been updated. + %1 names a file. + %1 wurde aktualisiert. + + + + %1 has been renamed to %2. + %1 and %2 name files. + %1 wurde in %2 umbenannt. + + + + %1 has been moved to %2. + %1 wurde in %2 verschoben. + + + + %1 and %n other file(s) have been removed. + %1 und %n andere Datei wurden gelöscht.%1 und %n andere Dateien wurden gelöscht. + + + + The folder has been disabled or removed from the server + Der Ordner wurde deaktiviert oder vom Server entfernt + + + + %1 and %n other file(s) have been added. + %1 und %n andere Datei wurden hinzugefügt.%1 und %n andere Dateien wurden hinzugefügt. + + + + %1 has been added. + %1 names a file. + %1 wurde hinzugefügt. + + + + %1 and %n other file(s) have been updated. + %1 und %n andere Datei wurden aktualisiert.%1 und %n andere Dateien wurden aktualisiert. + + + + %1 has been renamed to %2 and %n other file(s) have been renamed. + %1 wurde in %2 umbenannt und %n andere Datei wurden umbenannt.%1 wurde in %2 umbenannt und %n andere Dateien wurden umbenannt. + + + + %1 has been moved to %2 and %n other file(s) have been moved. + %1 wurde in %2 verschoben und %n andere Datei wurden verschoben.%1 wurde in %2 verschoben und %n andere Dateien wurden verschoben. + + + + %1 and %n other file(s) have sync conflicts. + %1 und %n andere Datei haben Konflikte beim Synchronisieren.%1 und %n andere Dateien haben Konflikte beim Synchronisieren. + + + + %1 has a sync conflict. Please check the conflict file! + Es gab einen Konflikt bei der Synchronisierung von %1. Bitte prüfe die Konfliktdatei! + + + + %1 and %n other file(s) could not be synced due to errors. See the log for details. + %1 und %n andere Datei konnten aufgrund von Fehlern nicht synchronisiert werden. Schaue in das Protokoll für Details.%1 und %n andere Dateien konnten aufgrund von Fehlern nicht synchronisiert werden. Schaue in das Protokoll für Details. + + + + %1 could not be synced due to an error. See the log for details. + %1 konnte aufgrund eines Fehlers nicht synchronisiert werden. Lesen Sie das Protokoll für weitere Details. + + + + Sync Activity + Synchronisierungsaktivität + + + + Switching VFS mode on folder '%1' + Wechseln des VFS-Modus im Ordner '%1' + + + + Could not read system exclude file + Die systemeigene Ausschlussdatei kann nicht gelesen werden + + + + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. + Ordner %1 wurde angelegt, aber früher von der Synchronisation ausgeschlossen. Daten im Ordner werden nicht synchronisiert. + + + + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. + Datei %1 wurde angelegt, aber früher von der Synchronisation ausgeschlossen, sie wird nicht synchronisiert. + + + + Changes in synchronized folders could not be tracked reliably. + +This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). + +%1 + Änderungen in synchronisierten Ordnern konnten nicht zuverlässig verfolgt werden. + +Dies bedeutet, dass der Synchronisationsclient lokale Änderungen möglicherweise nicht sofort hochlädt, stattdessen nur nach lokalen Änderungen sucht und diese gelegentlich hochlädt (standardmäßig alle zwei Stunden). + +%1 + + + + OCC::FolderItemUpdater + + + There are unresolved conflicts. + Es existieren ungelöste Konflikte. + + + + Sync error: %1 + Synchronisierungsfehler: %1 + + + + OCC::FolderMan + + + Could not reset folder state + Der Ordnerzustand konnte nicht zurückgesetzt werden + + + + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. + Ein altes Synchronisations-Journal '%1' wurde gefunden, konnte jedoch nicht entfernt werden. Bitte sicherstellen, dass es derzeit von keiner Anwendung verwendet wird. + + + + Unsupported filesystem + Nicht unterstütztes Dateisystem + + + + Account disconnected or paused + Konto getrennt oder pausiert + + + + User triggered force sync + Benutzer hat die Synchronisierung erzwungen + + + + Folder is about to be removed + Ordner wird entfernt. + + + + (backup) + (Backup) + + + + (backup %1) + (Backup %1) + + + + The selected path is not a folder. + Der ausgewählte Pfad ist kein Ordner. + + + + The folder %1 is used in a folder sync connection. + Der Ordner %1 wird in einer Ordnersynchronisierungsverbindung verwendet. + + + + Folder '%1' is already in use by application %2. + Der Ordner „%1“ wird bereits von der Anwendung %2 verwendet. + + + + There is already a sync from the server to this local folder. + Es besteht bereits eine Synchronisierung vom Server zu diesem lokalen Ordner. + + + + The local folder %1 already contains a folder used in a folder sync connection. + Der lokale Ordner %1 enthält bereits einen Ordner, der in einer Ordnersynchronisierungsverbindung verwendet wird. + + + + The local folder %1 is already contained in a folder used in a folder sync connection. + Der lokale Ordner %1 ist bereits in einem Ordner enthalten, der in einer Ordnersynchronisierungsverbindung verwendet wird. + + + + No valid folder selected. + Es wurde kein gültiger Ordner ausgewählt. + + + + The selected path does not exist. + Der angegebene Pfad existiert nicht. + + + + You have no permission to write to the selected folder. + Sie haben keine Schreibberechtigung für den ausgewählten Ordner. + + + + Folder '%1' is already in use by another account. + Der Ordner '%1' wird bereits von einem anderen Konto verwendet. + + + + On macOS, only the Apple File System is supported. + Unter macOS wird nur das Apple File System unterstützt. + + + + OCC::FolderModelController + + + %1 options + %1 Optionen + + + + Menu button with %1 options. Use the space key to pop the %2 options menu + Menüschaltfläche mit %1 Optionen. Drücke die Leertaste, um das %2-Optionsmenü aufzurufen. + + + + OCC::FolderWatcher + + + The watcher did not receive a test notification. + Der Beobachterservice hat keine Test-Benachrichtigung erhalten. + + + + OCC::FolderWatcherPrivate + + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Dieses Problem tritt normalerweise auf, wenn die Inotify-Beobachter mechanismen erschöpft sind. Überprüfe die FAQ für Details. + + + + OCC::FolderWizard + + + Virtual files are not available for the selected folder + Virtuelle Dateien sind für den gewählten Ordner nicht verügbar + + + + Add Folder Sync Connection + Synchronisiertes Verzeichnis hinzufügen + + + + Add Sync Connection + Füge eine Verzeichnis Sychronisation hinzu + + + + OCC::FolderWizardLocalPath + + + Click to select a local folder to sync. + Wählen durch einen Klick ein lokales Verzeichnis für die Synchronisation aus. + + + + Enter the path to the local folder. + Einen Pfad zum lokalen Verzeichnis eingeben + + + + Select the local folder + Einen Lokalen Ordner auswählen + + + + OCC::FolderWizardSelectiveSync + + + Use virtual files instead of downloading content immediately + Virtuelle Dateien verwenden statt Inhalte gleich herunterzuladen + + + + OCC::GETFileJob + + + Connection Timeout + Zeitüberschreitung der Verbindung + + + + No E-Tag received from server, check Proxy/Gateway + Kein E-Tag vom Server empfangen, bitte Proxy / Gateway überprüfen + + + + We received a different E-Tag for resuming. Retrying next time. + Beim Fortfahren wurde ein abweichender E-Tag empfangen. Bitte erneut versuchen. + + + + We received an unexpected download Content-Length. + Unerwartete Daten-Länge beim Herunterladen empfangen. + + + + Server returned wrong content-range + Der Server hat einen falschen Bereich für den Inhalt zurück gegeben + + + + OCC::GraphApi::Space + + + Personal + Persönlich + + + + Shares + Freigaben + + + + OCC::IgnoreListEditor + + + Ignored Files Editor + Editor für ignorierte Dateien + + + + Files Ignored by Patterns + Dateien, welche durch Mustererkennung ignoriert werden + + + + Add + Hinzufügen + + + + Pattern + Muster + + + + Allow Deletion + Erlaube Löschung + + + + Remove + Entfernen + + + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Dateien oder Ordner, die einem definierten Muster entsprechen, werden nicht synchronisiert. Änderungen werden bei der nächsten Synchronisation übernommen. + +Objekte bei denen das Löschen erlaubt ist werden gelöscht, wenn sie die Löschung eines Ordners verhindern würden. Das ist hilfreich für Metadaten. + + + + Could not open file + Die Datei konnte nicht geöffnet werden + + + + Cannot write changes to '%1'. + Änderungen konnten nicht nach '%1' geschrieben werden. + + + + Add Ignore Pattern + Ignoriermuster hinzufügen + + + + Add a new ignore pattern: + Neues Ignoriermuster hinzufügen: + + + + This entry is provided by the system at '%1' and cannot be modified in this view. + Dieser Eintrag wird vom System auf '%1' bereitgestellt und kann in dieser Ansicht nicht geändert werden. + + + + OCC::LocalActivityWidget + + + Filter + Filter + + + + Local activity table + Tabelle mit lokaler Aktivität + + + + + Account + Konto + + + + Filter menu + Filtermenü + + + + Actions menu + Aktionsmenü + + + + Sort ascending by %1 + Aufsteigend nach %1 sortieren + + + + Sort descending by %1 + Absteigend nach %1 sortieren + + + + Retry sync + Synchronisation wiederholen + + + + OCC::LogBrowser + + + Log Output + Log-Ausgabe + + + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>Die Protokolle enthalten sensible Informationen, die nicht öffentlich verfügbar gemacht werden sollten.</b></span></p></body></html> + + + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + Der Client kann Protokolldateien in einen temporären Ordner schreiben. Die darin enthaltenen Informationen sind hilfreich zur Diagnose von Problemen. +Da diese Protokolldateien sehr groß werden können, erstellt der Client für jede Synchronisierung ein neues Protokoll und komprimiert ältere. + + + + Enable logging to temporary folder + Einschalten von Logging in einen temporären Ordner + + + + If enabled, logs will be written to: + Wenn eingeschaltet, werden Logs hier erstellt: + + + + C:/log + C:/log + + + + Log Http traffic + HTTP-Kommunikation aufzeichnen + + + + Log files to keep: + Zu behaltende Logfiles: + + + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Diese Einstellungen bleiben nach einem Neustart des Clients erhalten. +Beachten Sie, dass Kommandozeilenoptionen zum Logging diese Einstellungen überschreiben. + + + + Open folder + Ordner öffnen + + + + OCC::MainWindow + + + Hide + Ausblenden + + + + Main toolbar for the application + + + + + Main toolbar + + + + + + More + Mehr + + + + OCC::MainWindowController + + + Add account... + + + + + Settings... + Einstellungen … + + + + About... + Über... + + + + Quit + Beenden + + + + + Errors: %1 + + + + + Activity + Aktivität + + + + Quit %1 + %1 beenden + + + + Are you sure you want to quit %1? + Sind Sie sicher, dass sie %1 beenden möchten? + + + + OCC::ModalWrapperWidget + + + Close + Schließen + + + + OCC::Models + + + %1 Filter: + %1 Filter: + + + + All + Alle + + + + Reset Filters + Filter zurücksetzen + + + + Status Filter: + Status Filter: + + + + OCC::NetworkSettings + + + Form + Formular + + + + Pause synchronization when the Internet connection is metered + Pausiere Synchronisierung, wenn die Internetverbindung getaktet wird. + + + + Proxy Settings + Proxy-Einstellungen + + + + No Proxy + Kein Proxy + + + + Use system proxy + Benutze System-Proxy + + + + Host + Host + + + + Proxy Hostname + Proxy-Hostname + + + + : + : + + + + Proxy Port Number + Proxy-Portnummer + + + + Proxy server requires authentication + Proxyserver erfordert Authentifizierung + + + + Note: proxy settings have no effects for accounts on localhost + Beachte: Proxy Einstellungen haben keinen Effekt bei Konten auf localhost + + + + Specify proxy manually + Proxy manuell angeben + + + + Proxy type + Proxy-Typ + + + + + Hostname of proxy server + Hostname des Proxy-Servers + + + + + Username for proxy server + Benutzername für den Proxy-Server + + + + + Password for proxy server + Passwort für den Proxy-Server + + + + HTTP(S) proxy + HTTP(S)-Proxy + + + + SOCKS5 proxy + SOCKS5-Proxy + + + + OCC::NewAccountBuilder + + + no error details are available + Es liegen keine Fehlerdetails vor. + + + + Unable to connect to server during account creation: %1. + Verbindung zum Server konnte während der Kontoerstellung nicht hergestellt werden: %1. + + + + OCC::NewAccountWizard + + + Cancel Setup + Setup abbrechen + + + + Do you really want to cancel the account setup? + Die Benutzereinrichtung wirklich abbrechen? + + + + OCC::NewAccountWizardController + + + Add account… + Konto hinzufügen… + + + + Back + Zurück + + + + Advanced + Erweitert + + + + Sign in + Anmelden + + + + Open sign in again + Öffnen Sie die Anmeldung erneut + + + + OCC::NewVersionAvailableWidget + + + Skip this version + Diese Version überspringen + + + + Get update + Update durchführen + + + + Skip this time + Dieses Mal überspringen + + + + OCC::OAuth + + + Successfully signed in + Erfolgreich angemeldet + + + + Now, explore %1 on desktop. + Erkunden Sie nun %1 auf Ihrem Desktop. + + + + Error returned from the server: <em>%1</em> + Fehler vom Server zurückgegeben: <em>%1</em> + + + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Beim Zugriff auf den 'token' Endpunkt trat ein Fehler auf: <br><em>%1</em> + + + + Could not parse the JSON returned from the server: <br><em>%1</em> + Die JSON Antwort des Servers konnte nicht geparst werden: <br><em>%1</em> + + + + Unsupported token type: %1 + Nicht unterstützter Token-Typ: %1 + + + + The reply from the server did not contain all expected fields +:%1 + Die Server-Antwort hat nicht alle erwarteten Felder enthalten: %1 + + + + Unknown Error + Ein unbekannter Fehler ist aufgetreten. + + + + + Login Error + Log-In Fehler + + + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + Sie haben sich mit Benutzer <em>%1</em> angemeldet, müssen sich aber mit Benutzer <em>%2</em>.<br>Bitte kehren Sie zum %3-Client zurück und starten Sie die Authentifizierung neu. + + + + Wrong user + Falscher Benutzer + + + + OCC::OAuthPageController + + + Application Name Logo %1 + Anwendungsname Logo %1 + + + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Lassen Sie dieses Fenster geöffnet. In Ihrem Webbrowser erscheint eine Anmeldeaufforderung, die Sie mit der folgenden Adresse verbindet. + + + + Sign in URL + Anmelde-URL + + + + To copy the sign in URL to the clipboard, use the copy button + Um die Anmelde-URL in die Zwischenablage zu kopieren, verwenden Sie die Schaltfläche „Kopieren“. + + + + Copy URL to sign in + URL zum Anmelden kopieren + + + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo + + + + URL copied + URL kopiert + + + + Copy URL + URL kopieren + + + + Unable to connect to the login service. If the problem continues, please contact support. + + + + + Please copy the authentication URL using the button above, and provide this information to support. + Bitte kopieren Sie die Authentifizierungs-URL mithilfe der Schaltfläche oben und geben Sie diese Informationen an den Support weiter. + + + + Failed to look up WebFinger instances: %1 + Fehler beim Auffinden von WebFinger-Instanzen: %1 + + + + You are already connected to an account with these credentials. + Sie sind bereits mit einem Konto mit diesen Anmeldedaten verbunden. + + + + Unable to retrieve capabilities from server. + Die Capabilities vom Server konnten nicht abgerufen werden. + + + + The server is not supported by this client. + Dieser Client unterstützt diesen Server nicht. + + + + Error while trying to log in to OAuth2-enabled server. %1 + Fehler beim Versuch sich bei einem OAuth2-fähigen Server anzumelden. %1 + + + + Unable to open browser with provided URL. %1 + Der Browser konnte die angegebene URL nicht öffnen. %1 + + + + OAuth2 authentication requires a secured connection. %1 + OAuth2 Anmeldung benötigt eine gesicherte Verbindung. %1 + + + + Authorization server unreachable. %1 + Autorisierungsserver nicht erreichbar. %1 + + + + OCC::OCUpdater + + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Aktualisierung kann nicht herunter geladen werden. Bitte klicke <a href='%1'>hier</a> um die Aktualisierung manuell herunter zu laden. + + + + Could not check for new updates. + Auf neue Aktualisierungen kann nicht geprüft werden. + + + + Downloading %1. Please wait… + Herunterladen von %1. Bitte warten… + + + + %1 installed successfully. Restart the application to finish installing the update. + %1 erfolgreich installiert. Anwendung bitte neu starten, um das Update zu beenden. + + + + %1 available. Restart application to start the update. + %1 ist verfügbar. Anwendung zum Start der Aktualisierung neu starten. + + + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Neue %1 verfügbar. Bitte clicken Sie <a href='%2'>hier</a> um das neue AppImage manuell downzuloaden. + + + + New %1 available. Please use the system's update tool to install it. + Aktualisierung von %1 verfügbar. Bitte nutzen Sie zur Installation das Systemaktualisierungstool. + + + + Checking update server… + Überprüfung des Update-Servers… + + + + Update status is unknown: Did not check for new updates. + Aktualisierungsstatus unbekannt: Auf neue Aktualisierungen konnte nicht geprüft werden. + + + + No updates available. Your installation is at the latest version. + Keine Aktualisierungen verfügbar. Ihre Installation ist die aktuelle Version. + + + + OCC::OwncloudPropagator + + + File %1 is currently in use + Datei %1 wird gerade benutzt + + + + vfs instance is not available. + Die VFS-Instanz ist nicht verfügbar. + + + + OCC::ProcessDirectoryJob + + + Symbolic links are not supported in syncing. + Symbolische Verknüpfungen werden bei der Synchronisation nicht unterstützt. + + + + File is listed on the ignore list. + Die Datei ist in der Ignorierliste geführt. + + + + File names ending with a period are not supported on this file system. + Dateinamen enden mit einem Punkt. Dies wird in diesem Dateisystem nicht unterstützt. + + + + File names containing the character '%1' are not supported on this file system. + Dateinamen beinhalten das Zeichen '%1'. Diese werden in diesem Dateisystems nicht unterstützt. + + + + File name contains at least one invalid character + Der Dateiname enthält mindestens ein ungültiges Zeichen + + + + The file name is a reserved name on this file system. + Der Dateiname ist ein reservierter Name in diesem Dateisystem. + + + + Filename contains trailing spaces. + Dateiname endet mit Leerzeichen. + + + + Filename is too long. + Der Dateiname ist zu lang. + + + + File/Folder is ignored because it's hidden. + Datei/Ordner wird ignoriert, da sie versteckt sind. + + + + Stat failed. + "Stat" fehlgeschlagen. + + + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Konflikt: Die Serverversion wurde heruntergeladen, die lokale Kopie wurde umbenannt und nicht hochgeladen. + + + + The filename cannot be encoded on your file system. + Der Dateiname kann auf deinem Dateisystem nicht kodiert werden. + + + + The filename is blacklisted on the server. + Der Dateiname ist auf der schwarzen Liste des Servers. + + + + + + + the VFS instance is missing. Unable to continue. + Die VFS-Instanz fehlt. Der Vorgang kann nicht fortgesetzt werden. + + + + server reported no %1 + Der Server meldet kein %1 + + + + + account was deleted. Unable to continue + Konto wurde gelöscht. Vorgang kann nicht fortgesetzt werden. + + + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: Selektive Synchronisierung wurde ignoriert, da der Pfad dafür abgewählt ist. + + + + Moved to invalid target, restoring + An einen ungültigen Zielort verschoben, Wiederherstellung + + + + Not allowed to remove, restoring + Das Löschen ist nicht erlaubt, Wiederherstellung + + + + Error while reading the database. + Beim Lesen der Datenbank ist ein Fehler aufgetreten. + + + + account was deleted. Unable to continue. + Das Konto wurde gelöscht. Es kann nicht fortgefahren werden. + + + + Not allowed because you don't have permission to add subfolders to that folder + Nicht erlaubt, weil keine Rechte bestehen, diesem Verzeichnis Unterverzeichnisse hinzuzufügen. + + + + Error while doing a rename, unhandled status code: %1 + Fehler während des Umbenennes, nicht behandelter Status Code: %1 + + + + Not allowed because you don't have permission to add files in that folder + Nicht erlaubt, weil die nötigen Rechte fehlen, Dateien zu dem Verzeichnis hinzuzufügen + + + + Not allowed to upload this file because it is read-only on the server, restoring + Das Hochladen dieser Datei ist nicht erlaubt, da die Datei auf dem Server schreibgeschützt ist, Wiederherstellung + + + + This Space is currently unavailable + Dieser Space ist aktuell nicht erreichbar + + + + Server replied with an error while reading directory '%1' : %2 + Der Server meldet einen Fehler beim Lesen des Ordners '%1' : %2 + + + + OCC::PropagateDirectory + + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 + + + + %1 the folder is currently in use + Ordner %1 wird gerade benutzt + + + + OCC::PropagateDownloadFile + + + Failed to free up space, the file %1 is currently in use + Speicherplatz konnte nicht freigegeben werden, die Datei %1 wird derzeit verwendet + + + + + File %1 can not be downloaded because of a local file name clash with %2! + Datei %1 kann nicht heruntergeladen werden, da der lokale Dateiname mit %2 kollidiert! + + + + The download would reduce free local disk space below the limit + Das Herunterladen würde den lokalen freien Speicherplatz unter das Limit reduzieren + + + + Free space on disk is less than %1 + Der freie Speicherplatz beträgt weniger als %1 + + + + File was deleted from server + Datei wurde vom Server gelöscht + + + + Broken webserver returned empty content length for non-empty file on resume + Der Webserver hat eine leere Länge bezüglich des Inhalts für eine nicht leere Datei in der Wiederaufnahme zurückgegeben (Defekt) + + + + The file could not be downloaded completely. + Die Datei konnte nicht vollständig herunter geladen werden. + + + + The downloaded file is empty despite the server announced it should have been %1. + Die heruntergeladene Datei ist leer, obwohl der Server %1 als Größe übermittelt hat. + + + + File %1 cannot be saved because of a local file name clash with %2! + Datei %1 kann nicht gespeichert werden, da der lokale Dateiname mit %2 kollidiert! + + + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 + + + + + + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt + + + + + File has changed since discovery + Datei ist seit der Entdeckung geändert worden + + + + OCC::PropagateItemJob + + + ; Restoration Failed: %1 + ; Wiederherstellung fehlgeschlagen: %1 + + + + OCC::PropagateLocalMkdir + + + could not delete file %1, error: %2 + Die Datei %1 konnte nicht gelöscht werden, Fehler: %2 + + + + Can not create local folder %1 because of a local file name clash with %2 + Der lokale Ordner %1 konnte aufgrund eines Namenkonfliktes mit %2 nicht erstellt werden + + + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 + + + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt + + + + could not create folder %1 + Ordner %1 konnte nicht erstellt werden + + + + OCC::PropagateLocalRemove + + + %1 failed with: %2 + %1 fehlgeschlagen mit: %2 + + + + Could not remove %1 because of a local file name clash with %2! + %1 kann aufgrund eines Konfliktes mit dem lokalen Dateinamen von %2 nicht gelöscht werden! + + + + Could not move '%1' to the trash bin + Konnte '%1' nicht in den Papierkorb verschieben + + + + + %1 the file is currently in use + Datei %1 wird gerade benutzt + + + + OCC::PropagateLocalRename + + + File %1 can not be renamed to %2 because of a local file name clash + %1 kann aufgrund eines Konfliktes mit dem lokalen Dateinamen nicht zu %2 umbenannt werden + + + + Could not rename %1 to %2, the file is currently in use + Konnte %1 nicht in %2 umbenennen, die Datei wird gerade benutzt + + + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 + + + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt + + + + Failed to rename file + Umbenennen der Datei fehlgeschlagen + + + + Error setting pin state + Fehler beim Setzen des PIN-Zustandes + + + + OCC::PropagateRemoteDelete + + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 204, gesendet wurde "%1 %2". + + + + OCC::PropagateRemoteMkdir + + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 201, gesendet wurde "%1 %2". + + + + Error writing metadata to the database: %1 + Fehler beim Schreiben der Metadaten in die Datenbank: %1 + + + + Setting file status failed due to file lock + Das Festlegen des Dateistatus ist aufgrund einer Dateisperre fehlgeschlagen + + + + OCC::PropagateRemoteMove + + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 201, gesendet wurde "%1 %2". + + + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 + + + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt + + + + Error writing metadata to the database + Fehler beim Schreiben der Metadaten in die Datenbank + + + + Error setting pin state + Fehler beim Setzen des PIN-Zustandes + + + + OCC::PropagateUpdateMetaDataJob + + + Could not update file : %1 + Die Datei konnte nicht aktualisiert werden: %1 + + + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt + + + + OCC::PropagateUploadCommon + + + Local file changed during sync. It will be resumed. + Lokale Datei während der Synchronisierung geändert. Der Synchronisationsvorgang wird wiederholt. + + + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + Die Datei %1 kann nicht hochgeladen werden, da eine andere Datei mit dem selben Namen, nur unterschiedlicher Großschreibung, existiert + + + + + Upload of %1 exceeds the quota for the folder + Das Hochladen von %1 überschreitet das Speicherkontingent des Ordners + + + + + %1 the file is currently in use + Datei %1 wird gerade benutzt + + + + File Removed + Datei entfernt + + + + The server did not provide the file permissions + Der Server hat die Dateiberechtigungen nicht bereitgestellt + + + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 + + + + OCC::PropagateUploadFile + + + %1 the file is currently in use + Datei %1 wird gerade benutzt + + + + The server did ask for a removed legacy feature(polling) + Der Server hat nach einer nicht mehr benutzen Funktion angefragt (Polling) + + + + The local file was removed during sync. + Die lokale Datei wurde während der Synchronisation gelöscht. + + + + The server did not acknowledge the file upload. (No e-tag was present) + Der Server hat den Datei-Upload nicht bestätigt. (Es war kein E-Tag vorhanden.) + + + + OCC::PropagateUploadFileTUS + + + %1 the file is currently in use + Datei %1 wird gerade benutzt + + + + Checksum computation failed + Die Generierung der Checksumme ist fehlgeschlagen + + + + The local file was removed during sync. + Die lokale Datei wurde während der Synchronisation gelöscht. + + + + OCC::ProtocolItemModel + + + Time + Zeit + + + + File + Datei + + + + Folder + Ordner + + + + Issues + Probleme + + + + Action + Aktion + + + + Size + Größe + + + + Account + Konto + + + + Status + Status + + + + OCC::RequestAuthenticationController + + + IdP is unreachable. Contact your system administrator or try again later. + Der Identitätsanbieter (IdP) ist nicht erreichbar. Wenden Sie sich an Ihren Systemadministrator oder versuchen Sie es später erneut. + + + + Authentication failed. + Authentifizierung fehlgeschlagen. + + + + OCC::RequestAuthenticationWidget + + + Application Name Logo %1 + Anwendungsname Logo %1 + + + + Sign in required + Anmeldung erforderlich + + + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + Möglicherweise wurden Sie aufgrund eines Serverproblems oder einer Zeitüberschreitung automatisch getrennt. Bitte melden Sie sich erneut an. + + + + Sign in URL + Anmelde-URL + + + + To copy the sign in URL to the clipboard, use the copy button + Um die Anmelde-URL in die Zwischenablage zu kopieren, verwenden Sie die Schaltfläche „Kopieren“. + + + + Copy URL to sign in + URL zum Anmelden kopieren + + + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo + + + + Stay logged out + Abgemeldet bleiben + + + + Sign in + Anmelden + + + + URL copied + URL kopiert + + + + Copy URL + URL kopieren + + + + OCC::ResolveUrlAdapter + + + SSL failure when connecting to server at %1 + SSL-Fehler beim Verbindungsaufbau zum Server unter %1 + + + + Could not detect compatible server at %1 + Ein kompatibler Server konnte unter %1 nicht gefunden werden + + + + Resolved url is invalid %1 + Die aufgelöste URL ist ungültig %1 + + + + Rejected redirect from %1 to %2 + Die Umleitung von %1 nach %2 wurde abgelehnt + + + + OCC::SelectiveSyncWidget + + + Loading … + Laden… + + + + Deselect remote folders you do not wish to synchronize. + Entfernte Ordner abwählen die nicht synchronisiert werden sollen. + + + + Name + Name + + + + Size + Größe + + + + + Currently there are no subfolders on the server. + Derzeit befinden sich keine Unterordner auf dem Server. + + + + An error occurred while loading the list of subfolders. + Beim Laden der Liste der Unterordner ist ein Fehler aufgetreten. + + + + OCC::SettingsView + + + Form + Formular + + + + General Settings + Allgemeine Einstellungen + + + + Use Monochrome Icons in the system tray + Verwenden Sie monochrome Symbole in der Taskleiste + + + + Show Desktop Notifications + Zeige Desktop-Benachrichtigungen + + + + Start on Login + Beim Anmeldung starten + + + + Language + Sprache + + + + Language selector + Sprachauswahl + + + + Advanced + Erweitert + + + + Sync hidden files + Versteckte Dateien synchronisieren + + + + Show crash reporter + Crash-Reporter anzeigen + + + + Move remotely deleted files to the local trash bin instead of deleting them + Auf dem Server gelöschte Dateien in den lokalen Papierkorb verschieben statt sie zu löschen + + + + Edit Ignored Files + Ignorierte Dateien bearbeiten + + + + Log Settings + Log-Einstellungen + + + + Network + Netzwerk + + + + Warning + Warnung + + + + Language changes require a restart of this application to take effect. + Änderungen der Sprachänderungen werden erst nach Neustart des Clients wirksam. + + + + You cannot disable autostart because system-wide autostart is enabled. + Autostart kann nicht abgeschaltet werden, da Autostart als Systemeinstellung aktiviert ist. + + + + (use default) + (benutze den Standard) + + + + unknown (%1) + Unbekannt (%1) + + + + OCC::SocketApi + + + Share with %1 + parameter is the application name + Mit %1 teilen + + + + Confirm deletion + Löschung bestätigen + + + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Soll der Ordner <i>%1</i> und alle seine Inhalte dauerhaft gelöscht werden? + + + + Do you want to delete the file <i>%1</i> permanently? + Soll die Datei <i>%1</i> dauerhaft gelöscht werden? + + + + Error + Fehler + + + + Moving file failed: + +%1 + Verschieben der Datei fehlgeschlagen: + +%1 + + + + I shared something with you + Ich habe etwas mit dir geteilt + + + + Select new location… + Neuen Ort wählen… + + + + + Share… + Teilen… + + + + + Copy private link to clipboard + Privaten Link in die Zwischenablage kopieren + + + + Resharing this file is not allowed + Weiteres Teilen ist nicht erlaubt. + + + + Resharing this folder is not allowed + Weiterteilen des Ordners ist nicht erlaubt + + + + Open in %1 + In %1 öffnen + + + + Show file versions in web browser + Zeige Dateiversionen im Browser + + + + Rename… + Umbenennen… + + + + Rename and upload… + Umbenennen und hochladen… + + + + + Move and rename… + Verschieben und umbenennen… + + + + Move, rename and upload… + Verschieben, umbenennen und hochladen… + + + + Move and upload… + Verschieben und hochladen… + + + + Delete local changes + Lokale Änderungen löschen + + + + Delete + Löschen + + + + OCC::SpacesPage + + + Select a folder to sync it to your computer. + Wähle einen Ordner aus, um diesen mit Deinem Computer zu synchronisieren. + + + + Select a Space to sync it to your computer. + Wähle einen Space aus, um diesen mit Deinem Computer zu synchronisieren. + + + + OCC::SyncEngine + + + %1 (skipped due to earlier error, trying again in %2) + %1 (Übersprungen aufgrund des früheren Fehlers, erneuter Versuch in %2) + + + + Unable to sync: the local folder is missing. + Synchronisierung nicht möglich: Der lokale Ordner fehlt. + + + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Es stehen nur %1 zur Verfügung, es müssen mindestens %2 sein um zu starten + + + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Das Öffnen oder erstellen der Synchronisations-Datenbank ist nicht möglich. Bitte stelle sicher, dass Schreibrechte für den zu synchronisierenden Ordner existieren. + + + + + + + Cannot open the sync journal. + Das Synchronisierungsprotokoll kann nicht geöffnet werden. + + + + Aborted due to %1 + Abgebrochen aufgrund von %1 + + + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Der freie Speicherplatz wird knapp: Downloads, die den freien Speicher unter %1 senken, wurden ausgelassen. + + + + There is insufficient space available on the server for some uploads. + Auf dem Server ist zum Hochladen für einige Dateien nicht genug Platz. + + + + application exit + abort reason + Anwendung beendet + + + + Unable to read the blacklist from the local database + Fehler beim Einlesen der Blacklist aus der lokalen Datenbank + + + + Unable to read from the sync journal. + Fehler beim Einlesen des Synchronisierungsprotokolls. + + + + OCC::SyncErrorWidget + + + Issues table + Problemtabelle + + + + There were too many issues. Not all will be visible here. + Es gab zu viele Fehler. Nicht alle werden hier angezeigt. + + + + There were conflicts. Check the documentation on how to resolve them. + Es bestehen Konflikte. Prüfe die Dokumentation, wie diese zu lösen sind. + + + + The file %1 was ignored as its name is reserved by %2 + Die Datei %1 wurde ignoriert, da ihr Name von %2 reserviert ist + + + + Reset column sizes + Zurücksetzen der Spaltenbreiten + + + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Es bestehen Konflikte. <a href="%1">Prüfe die Dokumentation, wie diese zu lösen sind.</a> + + + + Filter menu + Filtermenü + + + + Account + Konto + + + + Filter + Filter + + + + OCC::SyncJournalDb + + + Failed to connect database. + Die Verbindung zur Datenbank konnte nicht hergestellt werden. + + + + OCC::Theme + + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Version %1. Für mehr Informationen gehen Sie auf <a href="%2">https://%3</a></p><p>Für bekannte Probleme und Hilfe, gehen Sie auf: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Verteilt durch %4 und lizensiert unter der GNU General Public License (GPL) Version 2.0.<br/>%5 und das %5 Logo sind registierte Handelsmarken von %4 in den Vereinigten Staaten, anderen Ländern, oder beiden.</p><p><small>%6</small></p> + + + + OCC::TlsErrorDialog + + + TLS Certificate Error + TLS Zertifikat Fehler + + + + Do you want to trust this certificate anyway? + Möchtest Du diesem Zertifikat trotzdem vertrauen? + + + + Cannot connect securely to %1 + Keine sichere Verbindung zu %1 möglich + + + + &lt;not specified&gt; + &lt;Nicht angegeben&gt; + + + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>mit Zertifikat %1</h3><div id="ccert"><p>Organisation: %2<br/>Einheit: %3<br/>Land: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Ausstellungsdatum: %8Ablaufdatum: %9</div><h3>Aussteller: %10</h3><div id="issuer"><p>Organisation: %11<br/>Einheit: %12<br/>Land: %13</p></div></div> + + + + OCC::TrayMenuController + + + Unsupported Server Version + Nicht unterstützte Server Version + + + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + Der Server für Konto %1 benutzt eine nicht unterstützte Version %2. Die Client Software funktioniert möglicherweise mit nicht unterstützten Versionen, dies ist jedoch nicht getestet. Weiter auf eigenes Risiko. + + + + Show %1 + Zeige %1 + + + + Help + Hilfe + + + + About %1 + Über %1 + + + + Quit %1 + %1 beenden + + + + OCC::UpdateDownloadedWidget + + + Restart later + Später neu starten + + + + Restart now + Jetzt neu starten + + + + OCC::UpdaterScheduler + + + Update available + Aktualisierung verfügbar + + + + OCC::UrlPageController + + + Your web browser will be opened to complete sign in. + Ihr Webbrowser wird geöffnet, um die Anmeldung abzuschließen. + + + + %1 logo + %1 Logo + + + + Welcome to %1 + Willkommen bei %1 + + + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Geben Sie Ihre Serveradresse ein, um zu beginnen. Ihr Webbrowser wird geöffnet, um die Anmeldung abzuschließen. + + + + Server address field + Serveradressfeld + + + + Enter your server address here + Geben Sie hier Ihre Serveradresse ein. + + + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo + + + + Invalid server URL: %1 + Ungültige Server-URL: %1 + + + + Invalid URL scheme. Only https is accepted. + Ungültiges URL-Schema. Nur https wird akzeptiert. + + + + The requested URL failed with query value: %1 + Die angefragte URL hat mit folgender Abfrage einen Fehler zurückgemeldet: %1 + + + + OCC::UserInfoAdapter + + + Network request to collect user info failed + Netzwerkanfrage zum Abrufen von Benutzerinformationen fehlgeschlagen + + + + Unable to retrieve user info: invalid credentials. + Benutzerinformationen konnten nicht abgerufen werden: Ungültige Anmeldeinformationen. + + + + Unexpected network response when retrieving user info. + Unerwartete Netzwerkantwort beim Abrufen von Benutzerinformationen. + + + + Retrieving user info failed with JSON error. + Der Abruf der Benutzerinformationen ist mit einem JSON-Fehler fehlgeschlagen. + + + + OCC::ValidateChecksumHeader + + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + Die heruntergeladene Datei entspricht nicht der Prüfsumme, das Herunterladen wird wiederaufgenommen. '%1' != '%2' + + + + OCC::Vfs + + + The Virtual filesystem feature does not support a drive as sync root + Das virtuelle Dateisystem Feature unterstützt kein Laufwerk als Synchronisationsordner + + + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + Das virtuelle Dateisystem Feature benötigt ein NTFS Dateisystem, %1 nutzt %2 + + + + The Virtual filesystem feature is not supported on network drives + Die Funktion für das virtuelle Dateisystem wird auf Netzwerklaufwerken nicht unterstützt + + + + OCC::VfsWinPrivate + + + + %1 - %2 + %1 - %2 + + + + Could not find StorageProviderState property %1 + Die StorageProviderState-Eigenschaft %1 konnte nicht gefunden werden. + + + + Could not find StorageFolder for %1 error: %2 + Speicherordner für %1 konnte nicht gefunden werden: %2 + + + + Could not retrieve StorageFolder for %1 %2 (0x%3) + Der Speicherordner für %1 %2 konnte nicht abgerufen werden (0x%3) + + + + The folder is used by a different client: %1 + Der Ordner wird von einem anderen Client verwendet: %1 + + + + Error registering StorageProvider for %1: %2 (0x%3) + Fehler beim Registrieren des StorageProviders für %1: %2 (0x%3) + + + + Unable to connect sync root: %1 error: %2 + Verbindung zum Sync-Root konnte nicht hergestellt werden: %1 Fehler: %2 + + + + %1 is managed by another sync client + %1 wird von einem anderen Synchronisierungsclient verwaltet. + + + + Downgrading the folder from %1 to %2 is not supported + Das Herabstufen des Ordners von %1 auf %2 wird nicht unterstützt. + + + + OCC::WebFingerLookupAdapter + + + Failed to retrieve user info + Die Benutzerinformationen konnten nicht abgerufen werden + + + + WebFinger lookup returned no links + Die WebFinger-Suche ergab keine Ergebnisse. + + + + OCC::WindowsUpdater + + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Eine neue Version des %1 Clients ist verfügbar.</p><p><b>%2</b> steht zum Herunterladen bereit. Die installierte Version ist %3.</p> + + + + Skip this version + Diese Version überspringen + + + + Update Failed + Update fehlgeschlagen + + + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Eine neue Version des %1 Clients ist verfügbar, der Aktualisierungs-Prozess ist aber fehlgeschlagen.</p><p><b>%2</b> wurde heruntergeladen. Die installierte Version ist %3.</p> + + + + Ask again later + Später noch einmal fragen + + + + Restart and update + Neu starten und aktualisieren + + + + Update manually + Manuell aktualisieren + + + + QObject + + + in the future + in der Zukunft + + + + %n day(s) ago + Vor %n TagVor %n Tagen + + + + %n hour(s) ago + Vor %n StundeVor %n Stunden + + + + now + jetzt + + + + less than a minute ago + vor weniger als einer Minute + + + + %n minute(s) ago + Vor %n MinuteVor %n Minuten + + + + %1: %2 + this displays an error string (%2) for a file %1 + %1: %2 + + + + SpacesBrowser + + + Form + Formular + + + + SpacesPage + + + Spaces list + Liste der Spaces + + + + SpacesView + + + Spaces + Spaces + + + + SyncFileItem::Status + + + Undefined + Nicht definiert + + + + Fatal Error + Schwerer Fehler + + + + + Error + Fehler + + + + Info + Info + + + + Success + Erfolgreich + + + + Conflict + Konflikt + + + + File Ignored + Datei ignoriert + + + + Restored + Wiederhergestellt + + + + Blacklisted + Zur schwarzen Liste hinzugefügt + + + + Excluded + Ausgeschlossen + + + + Message + Mitteilung + + + + Filename Reserved + Dateiname reserviert + + + + UpdateDownloadedWidget + + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Neustart erforderlich</span></p></body></html> + + + + Utility + + + %n year(s) + %n Jahr%n Jahre + + + + %n month(s) + %n Monat%n Monate + + + + %n day(s) + %n Tag%n Tage + + + + %n hour(s) + %n Stunde%n Stunden + + + + %n minute(s) + %n Minute%n Minuten + + + + %n second(s) + %n Sekunde%n Sekunden + + + + %1 %2 + %1 %2 + + + + Shutting down %1 + Herunterfahren von %1 + + + + account loading + + + Error accessing the configuration file + Fehler beim Zugriff auf die Konfigurationsdatei + + + + There was an error while accessing the configuration file at %1. + Es ist ein Fehler beim Zugriff auf die Konfigurationsdatei unter %1 aufgetreten. + + + + ownCloudTheme::aboutVersions() + + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Qt Bibliotheken %3, %4%7Verwendete Erweiterung für virtuelle Dateien: %5%7%6 + + + + ownCloudTheme::qtVer + + + %1 (Built against Qt %2) + %1 (Gebaut mit Qt %2) + + + + ownCloudTheme::versionWithSha + + + %1 %2 + %1 %2 + + + + pinstate + + + Always available locally + Immer lokal verfügbar + + + + Currently available locally + Aktuell lokal verfügbar + + + + Some available online only + Einige nur online verfügbar + + + + Available online only + Nur Online verfügbar + + + + progress + + + Virtual file created + Virtuelle Datei wurde erstellt + + + + Replaced by virtual file + Ersetzt durch virtuelle Datei + + + + Downloaded + Heruntergeladen + + + + Uploaded + Hochgeladen + + + + Deleted + gelöscht + + + + Ignored + Ignoriert + + + + Server version downloaded, local copy was backed up as conflict file + Serverversion heruntergeladen, lokale Kopie wurde als Konfliktdatei gesichert. + + + + Renamed %1 to %2 + %1 wurde in %2 umbenannt + + + + Moved %1 to %2 + %1 wurde nach %2 verschoben + + + + Error + Fehler + + + + Updated local metadata + Lokale Metadaten wurden aktualisiert + + + + + Unknown + Unbekannt + + + + downloading + Lade herunter + + + + uploading + Lade hoch + + + + deleting + Lösche + + + + moving + Verschiebe + + + + ignoring + ignoriere + + + + error + Fehler + + + + updating local metadata + aktualisiere lokale Metadaten + + + + utility + + + Could not open browser + Browser konnte nicht geöffnet werden + + + + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? + Die URL %1 konnte aufgrund eines Fehlers beim Start des Browsers nicht aufgerufen werden. Ist vielleicht kein Standardbrowser konfiguriert? + + + + Could not open email client + Die E-Mail Anwendung konnte nicht geöffnet werden + + + + There was an error when launching the email client to create a new message. Maybe no default email client is configured? + Es ist ein Fehler beim Öffnen der E-Mail Anwendung, zum Erstellen einer neuen Nachricht, aufgetreten. Vielleicht ist keine standardmäßige E-Mail Anwendung konfiguriert? + + + + Make always available locally + Immer lokal verfügbar machen + + + + Free up local space + Lokalen Speicherplatz freigeben + + + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Manche Einstellungen wurden in neueren Versionen dieses Clients konfiguriert und benutzen Features, die in dieser Version nicht verfügbar sind. + + + \ No newline at end of file diff --git a/translations/client_de.ts b/translations/client_de.ts index 32f5959884b..114d664522a 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -1,360 +1,426 @@ - + - CrashReporter + AccountBar - - Tomahawk Crash Reporter - Tomahawk Crash-Reporter + + Navigation bar + Navigationsleiste - - <html><head/><body><p><br/></p></body></html> - <html><head/><body><p><br/></p></body></html> + + Add Account + Konto hinzufügen - - Abort - Abbrechen + + Activity + Aktivität - - Send this report - Diesen Bericht senden + + Settings + Einstellungen - - Don't send - Nicht senden + + Quit + Beenden + + + AppImageUpdateAvailableWidgetUi - - You can disable sending crash reports in the configuration dialog. - Sie können das Versenden von Crash-Reports im Konfigurations-Dialog abschalten. + + Skip this version + Diese Version überspringen + + + AppProvider - - - - We cannot gather useful debug information on your system. - Es konnte keine brauchbare Debug-Information vom System eingesammelt werden. + + Failed to open %1 in web. Error: %2. + %1 konnte in Web nicht geöffnet werden. Fehler: %2. + + + ChecksumHeader - - - - - - Close - Schließen + + The checksum header is malformed: %1 + Der Prüfsummen-Kopfzeile hat ein fehlerhaftes Format. - - Ready to send debug information (<a href="%1">view backtrace</a>). - Bereit zum Senden von Debug Informationen (<a href="%1"> backtrace ansehen </a>). + + The checksum header contained an unknown checksum type '%1' + Die Prüfsummen-Kopfzeile enthielt den unbekannten Prüfsummentypen '%1'. + + + CommandLine - - Gathering debug information... - Einsammeln von Debug-Informationen... + + %1 version %2 +File synchronization desktop utility. + %1 Version %2<byte value="xd"/> +Anwendung zur Datei-Synchronisation. - - Uploaded %L1 of %L2 KB. - %L1 of %L2 KB hochgeladen. + + For more information, see %1 + link to homepage + Mehr Informationen unter %1 - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - Gesendet! <b>Vielen Dank</b>. Bitte referenzieren Sie Crash <a href="clipboard://%1"><b>%1</b></a> (anklicken zum Kopieren) in Ihren Bug-Reports. + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Starten Sie den Client mit dem Hauptfenster sichtbar. Falls er bereits läuft, holen Sie das Fenster in den Vordergrund. Standardmäßig wird es im Hintergrund geöffnet. - - Failed to send crash info. - Fehler beim Senden von Crash Informationen. + + Quit the running instance. + Die laufende Instanz beenden. - - - FileSystem - - - - - Can't rename %1, the file is currently in use - Kann %1 nicht umbenennen, die Datei wird gerade benutzt + + Write log to file (use - to write to stdout). + Protokolldatei schreiben (Ausgabe nach stdout). - - Could not make directories in trash - Ordner können nicht im Papierkorb erstellt werden + + Write each sync log output in a new file in folder. + Jedes Synchronisationsprotokoll in eine neue Datei im Verzeichnis schreiben. - - - Could not move '%1' to '%2' - Konnte '%1' nicht nach '%2' verschieben + + Flush the log file after every write. + Protokolldatei nach jedem Schreibvorgang leeren. - - Moving to the trash is not implemented on this platform - Das Verschieben in den Papierkorb wird auf dieser Plattform nicht unterstützt + + Output debug-level messages in the log. + Debug-Meldungen in die Protokolldatei schreiben. - - Error removing '%1': %2 - Fehler beim Entfernen von '%1': %2 + + Enable debug mode. + Aktivieren des Debug-Modus - - Could not remove folder '%1' - Der Ordner konnte nicht gelöscht werden '%1' + + --logfile and --logdir are mutually exclusive + --logfile und --logdir schließen sich gegenseitig aus. - FolderWizardSourcePage + CommonStrings - - Form - Formular + + file manager + Dateimanager - - Pick a local folder on your computer to sync - Wählen Sie einen lokalen Ordner zum Synchronisieren aus + + Show in %1 + In %1 anzeigen - - &Choose... - Aus&wählen… + + Show in web browser + Im Webbrowser anzeigen - - - FolderWizardTargetPage - - Form - Formular + + Copy + Kopieren + + + + %n Filter(s) + %1 Filter%n Filter - - Select a remote destination folder - Einen entfernten Zielordner auswählen + + folder + Ordner - - Create Folder - Ordner erstellen + + space + Space - - Refresh - Aktualisieren + + folders + Ordner - - Folders + + spaces + Spaces + + + + Folder Ordner - - TextLabel - TextLabel + + Space + Space - NotificationWidget - - - Form - Formular - + FetchUserInfoJobFactory - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet + + Failed to retrieve user info + Die Benutzerinformationen konnten nicht abgerufen werden. + + + FileSystem - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + + + + Can't rename %1, the file is currently in use + Kann %1 nicht umbenennen, die Datei wird gerade benutzt. - - TextLabel - Text Label + + Could not remove folder + Der Ordner konnte nicht entfernt werden. - OCC::AboutDialog + FolderItem - - About - Über + + + Offline + Offline - - TextLabel - Text Label + + + Unavailable: the space is no longer available on the server + Nicht verfügbar: Der Speicherplatz ist auf dem Server nicht mehr verfügbar + - - Versions - Versionen + + + Sync paused + Synchronisation wurde angehalten - - About %1 - Über %1 + + Synced + Synchronisiert - - - OCC::AbstractNetworkJob - - Connection timed out - Zeitüberschreitung bei der Verbindung + + Sync failed + Synchronisation fehlgeschlagen - - Unknown error: network reply was deleted - Unbekannter Fehler: Netzwerk-Antwort wurde gelöscht + + Sync pending + Synchronisierung steht noch aus - - Server replied "%1 %2" to "%3 %4" - Server hat "%1 %2" auf "%3 %4" geantwortet + + Syncing %1 of %2 (%3 % + %1 von %2 werden synchronisiert (%3 % + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, Synchronisierungsstatus: %2 + first argument is the displayed name of the folder. - OCC::Account + FolderManagementUtils - - %1@%2 - %1@%2 + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + Der Pfad '%1' ist zu lang. Entweder die Unterstützung für lange Pfadnamen in den Windows Einstellungen einschalten oder ein anderes Verzeichnis auswählen. + + + + Local folder %1 does not exist. + Das Lokale Verzeichnis %1 existiert nicht. + + + + %1 is not a folder. + %1 ist kein Ordner + + + + %1 is not readable. + %1 ist nicht lesbar. + + + + %1 is not writable. + %1 ist nicht beschreibbar. - OCC::AccountSettings + FolderWizard - - Form - Formular + + <b>Error:</b> %1 + <b>Fehler:</b> %1 - - Re-open Browser - Browser erneut öffnen + + <b>Warning:</b> %1 + <b>Warnung:</b> %1 - - ... - ... + + <b>Error:</b><ul>%1</ul> + <b>Fehler:</b><ul>%1</ul> - - Storage space: ... - Speicherplatz: ... + + <b>Warning:</b><ul>%1</ul> + <b>Warnung:</b><ul>%1</ul> + + + FolderWizardSourcePage - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Nicht markierte Ordner werden von Ihrem lokalen Dateisystem <b>entfernt</b> und werden auch nicht mehr auf diesem Rechner synchronisiert + + Form + Formular - - Synchronize all - Alles synchronisieren + + Pick a local folder on your computer to sync + Wählen Sie einen lokalen Ordner zum Synchronisieren aus. - - Synchronize none - Nichts davon synchronisieren + + Browse… + Durchsuchen… + + + FolderWizardTargetPage - - Apply manual changes - Manuelle Änderungen übernehmen + + Form + Formular - - Apply - Anwenden + + Select a remote destination folder + Einen entfernten Zielordner auswählen - - - - - Cancel - Abbrechen + + Create Folder + Ordner erstellen - - Connected with <server> as <user> - Verbunden mit <server> als <user> + + Refresh + Aktualisieren - - No account configured. - Kein Konto konfiguriert. + + Folders + Ordner + + + OCC::AboutView - - Remove - Entfernen + + About + Über - - Account - Benutzerkonto + + Versions + Versionen + + + OCC::AbstractNetworkJob - - Choose what to sync - Zu synchronisierende Elemente auswählen + + Connection timed out + Zeitüberschreitung bei der Verbindung - - Force sync now - Synchronisation jetzt erzwingen + + Unknown error: network reply was deleted + Unbekannter Fehler: Die Netzwerk-Antwort wurde gelöscht - - Restart sync - Synchronisation neustarten + + Server replied "%1 %2" to "%3 %4" + Der Server hat "%1 %2" auf "%3 %4" geantwortet + + + OCC::Account - - Remove folder sync connection - Ordner-Synchronisation entfernen + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - Folder creation failed - Anlegen des Ordners fehlgeschlagen + + Choose what to sync + Zu synchronisierende Elemente auswählen - - <p>Could not create local folder <i>%1</i>. - <p>Konnte lokalen Ordner <i>%1</i> nicht anlegen. + + + Sync now + Jetzt synchronisieren - - Confirm Folder Sync Connection Removal - Bestätigen Sie die Löschung der Ordner-Synchronisation + + Manage subfolder sync + Synchronisierung von Unterordnern verwalten + - - Remove Folder Sync Connection - Ordner-Synchronisation entfernen + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + %1-Synchronisierung entfernen - + Disable virtual file support? Deaktiviere Unterstützung für virtuelle Dateien? - + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. The only advantage of disabling virtual file support is that the selective sync feature will become available again. @@ -362,577 +428,554 @@ The only advantage of disabling virtual file support is that the selective sync This action will abort any currently running synchronization. Diese Aktion wird die Unterstützung für virtuelle Dateien abschalten. Als Konsequenz werden Inhalte aus Ordnern heruntergeladen, die aktuell als 'nur online verfügbar' markiert sind. -Der einzige Vorteil durch das Abschalten der Unterstützung für virtuelle Dateien ist, dass das Feature selective sync wieder verfügbar wird. - -Diese Aktion wird gerade laufende Synchronisationen abbrechen. +Das Feature "selective sync" wird wieder verfügbar. Diese Aktion bricht die aktuell laufende Synchronisation ab. - + Disable support Unterstützung abschalten - - Sync Running - Synchronisation läuft + + Restart sync + Synchronisation neu starten - - The syncing operation is running.<br/>Do you want to terminate it? - Die Synchronisation läuft gerade.<br/>Wollen Sie sie beenden? + + Resume sync + Synchronisation fortsetzen - - %1 in use - %1 wird verwendet + + Pause sync + Synchronisation pausieren - - %1 as <i>%2</i> - %1 als <i>%2</i> + + Deactivate virtual files + Virtuelle Dateien deaktivieren - - Connected to %1. - Verbunden mit %1. + + Activate virtual files + Virtuelle Dateien aktivieren - - Server %1 is temporarily unavailable. - Server %1 ist derzeit nicht verfügbar. + + Internet connection is metered + Die Internetverbindung ist getaktet. - - Server %1 is currently in maintenance mode. - Server %1 befindet sich im Wartungsmodus. + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + Synchronisierung wird pausiert, weil die Internetverbindung getaktet ist<p>Soll die sofortige Fortsetzung der Synchronisierung wirklich erzwungen werden? - - Signed out from %1. - Abgemeldet von %1. + + Sync Running + Synchronisation läuft - - Connecting to %1... - Verbinde mit %1... + + The sync operation is running.<br/>Do you want to stop it? + Die Synchronisation läuft gerade.<br/>Trotzdem beenden? - - No connection to %1 at %2. - Keine Verbindung zu %1 auf %2 + + Sync paused by user + Synchronisierung vom Benutzer pausiert - - Log in - Einloggen + + Confirm Folder Sync Connection Removal + Löschen der Ordnersychronisation bestätigen - - There are folders that were not synchronized because they are too big: - Einige Verzeichnisse konnten nicht synchronisiert werden, da sie zu groß sind: + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Möchten Sie das Verzeichnis <i>%1</i> wirklich nicht mehr synchronisieren?</p><p><b>Hinweis:</b> Dies wird <b>keine</b> Daten löschen.</p> - - There are folders that were not synchronized because they are external storages: - Es gibt Verzeichnisse, die nicht synchronisiert werden konnten, da diese externe Speicher sind: + + Remove Folder Sync Connection + Ordner-Synchronisation entfernen - - There are folders that were not synchronized because they are too big or external storages: - Es gibt Verzeichnisse, die nicht synchronisiert werden konnten, da diese zu groß oder externe Speicher sind: + + + Cancel + Abbrechen + + + OCC::AccountFoldersView - - Confirm Account Removal - Konto wirklich entfernen? + + Sync options menu + Menü „Synchronisierungsoptionen“ - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Wollen Sie wirklich die Verbindung zum Konto <i>%1</i> lösen?</p><p><b>Anmerkung:</b> Dieser Vorgang wird <b>keine</b> Dateien löschen.</p> + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 synchronisieren - - Remove connection - Verbindung entfernen + + Manage your synced %1. + Verwalten Sie Ihre synchronisierten %1. - - Open folder - Ordner öffnen + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Neue %1-Synchronisierung hinzufügen… - - - Log out - Abmelden + + %1 list view + %1 Listenansicht - - Open local folder - Öffne lokalen Ordner + + Navigate the %1 list using the up and down arrows + Navigieren Sie mit den Auf- und Ab-Pfeilen durch die Liste %1 - - Open folder in browser - Ordner im Browser öffnen + + %1 out of %2 %3 are synchronized + %1 von %2 %3 sind synchronisiert + + + OCC::AccountModalWidget - - Resume sync - Synchronisation fortsetzen + + + Form + Formular + + + + OCC::AccountPlaceholderWidget + + + %1 logo + %1 Logo - - Pause sync - Synchronisation pausieren + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + - - Availability - Verfügbarkeit + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo + + + OCC::AccountView - - Disable virtual file support... - Deaktiviere Unterstützung für virtuelle Dateien ... + + Form + Formular - - Enable virtual file support%1... - Unterstützung für virtuelle Dateien einschalten %1... + + Manage Account + Konto verwalten - - (experimental) - (experimentell) + + Preparing the account + Konto vorbereiten - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Möchten Sie den Ordner <i>%1</i> wirklich nicht mehr synchronisieren?</p><p><b>Anmerkung:</b> Dies wird <b>keine</b> Dateien löschen.</p> + + Sync connections + Ordnersynchronisierung - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) von %2 Serverkapazität verwendet. Einige Ordner, einschließlich über das Netzwerk verbundene oder freigegebene Ordner oder geteilte Ordner können unterschiedliche Beschränkungen aufweisen. + + Account options menu + Menü „Kontooptionen“ + + + OCC::AccountViewController - - %1 of %2 in use - %1 von %2 Serverkapazität verwendet + + + Log in + Anmelden - - Currently there is no storage usage information available. - Derzeit sind keine Speichernutzungsinformationen verfügbar. + + Reconnect + Erneut verbinden - - The server version %1 is unsupported! Proceed at your own risk. - Die Serverversion %1 ist nicht unterstützt! Fortfahren auf eigenes Risiko. + + Remove + Entfernen - - Obtaining authorization from the browser. - Hole Berechtigung vom Browser ein. + + Log out + Abmelden - - Server configuration error: %1 at %2. - Konfigurationsfehler im Server: %1 auf %2. + + Confirm Account Removal + Löschen des Benutzerkontos bestätigen - - No %1 connection configured. - Keine %1-Verbindung konfiguriert. + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Möchtest Du den Zugang zum Benutzerkonto <i>%1</i> wirklich entfernen?</p><p><b>Hinweis:</b>Es werden <b>keine</b> lokalen Daten gelöscht.</p> - - - OCC::AccountState - - Url update requested for %1 - + + Remove connection + Zugang löschen - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - + + Cancel + Abbrechen + + + + The server version %1 is unsupported! Proceed at your own risk. + Die Server Version %1 wird nicht unterstützt. Weitere Schritte erfolgen auf eigene Gefahr. - - Change url permanently to %1 + + Connected + Verbunden + + + + Server is temporarily unavailable. - - Reject + + Server is currently in maintenance mode. - + Signed out Abgemeldet - - Disconnected - Getrennt - - - - Connected - Verbunden + + Updating credentials… + Zugangsdaten werden aktualisiert… - - Service unavailable - Dienst nicht verfügbar + + Captive portal prevents connections to the server. + Ein "Captive Portal" verhindert die Verbindung zum Server. - - Maintenance mode - Wartungsmodus + + Sync is paused due to metered internet connection. + - - Network error - Netzwerkfehler + + Connecting… + Verbinde… - - Configuration error - Konfigurationsfehler + + Server configuration error + Serverkonfigurationsfehler - - Asking Credentials - Zugangsdaten werden abgefragt + + Disconnected + Getrennt - - Unknown account state - Unbekannten Konto-Zustand + + Invalid connection status + - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 für %3 - + OCC::AccountsGuiController - - Activity - Aktivität + + + New account failure + Fehler bei neuem Konto - - Account - Benutzerkonto + + The information required to create a new account is incomplete. Please run the wizard again. + Die für die Erstellung eines neuen Kontos erforderlichen Informationen sind unvollständig. Bitte führen Sie den Assistenten erneut aus. - - Time - Zeit + + Accounts + Konten - - Local path - + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + Das Konto konnte aufgrund eines Fehlers nicht erstellt werden: +%1 +Bitte prüfen Sie die Serververfügbarkeit und führen Sie den Assistenten erneut aus. OCC::ActivitySettings - - - Server Activity - Serveraktivität - - - - Sync Protocol - Synchronisationsprotokoll + + Local Activity + Lokale Aktivität - + Not Synced nicht synchronisiert - + Not Synced (%1) %1 is the number of not synced files. - nicht synchronisiert (%1) + Nicht synchronisiert (%1) - OCC::ActivityWidget + OCC::AdvancedSettingsPageController - - Form - Formular + + Advanced settings + Erweiterte Einstellungen - - - - TextLabel - TextLabel + + Sync and download options + Synchronisierungs- und Downloadoptionen - - Filter - Filter + + Download location + Download-Speicherort - - Server Activities - Server Aktivitäten + + Sync and download files as you use them (saves hard drive space) + Dateien werden synchronisiert und heruntergeladen, während sie verwendet werden (spart Festplattenspeicher). - - Notifications - Benachrichtigungen + + Automatically sync and download folders and files + Ordner und Dateien automatisch synchronisieren und herunterladen - - - %n notifications(s) for %1. - %n Benachrichtigung für %1.%n Benachrichtigungen für %1. + + + Sync and download folders manually + Ordner manuell synchronisieren und herunterladen - - - %n notifications(s) for %1 and %2. - %n Benachrichtigung für %1 und %2.%n Benachrichtigungen für %1 und %2. - - - - New notifications for %1, %2 and other accounts. - Neue Benachrichtigungen für %1, %2 und andere Konten. - - - - Open the activity view for details. - Öffne die Aktivitäts-Ansicht für Genaueres. - - - - Copy to clipboard - In die Zwischenablage kopieren - - - - Show in file browser - Im Datei Browser anzeigen - - - - <br/>Account %1 does not have activities enabled. - <br/>Für Konto %1 sind serverseitig keine Aktivitäten aktiviert. - - - - OCC::Application - - Continuing will mean <b>deleting these settings</b>. - Fortsetzen bedeutet <b>dass diese Einstellungen gelöscht werden</b>. + + Download location on the local machine + Download-Speicherort auf dem lokalen Rechner - - Continuing will mean <b>ignoring these settings</b>. - Fortsetzen bedeutet <b>dass diese Einstellungen ignoriert werden</b>. + + Browse… + Durchsuchen… - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Einige Einstellungen wurden in neueren Versionen dieses Programms eingestellt und nutzen Funktionen die in dieser Version nicht verfügbar sind. <br><br>%1<br><br> Die aktuelle Konfigurations-Datei wurde bereits an folgenden Ort gesichert: <i>%2</i> + + Browse for a download location + Suchen Sie nach einem Download-Speicherort - - Quit - Beenden + + Invalid local download directory %1: %2 + Falsches lokales Download Verzeichnis %1: %2 - - Continue - Fortsetzen + + your user directory may not be chosen as the sync root. + Ihr Benutzerverzeichnis kann nicht als Synchronisierungsstammverzeichnis ausgewählt werden. - - Error accessing the configuration file - Fehler beim Zugriff auf die Konfigurationsdatei + + path is not located on a supported Apple File System. + Der Pfad befindet sich nicht auf einem unterstützten Apple-Dateisystem. - - There was an error while accessing the configuration file at %1. - Es ist ein Fehler beim Zugriff auf die Konfigurationsdatei unter %1 aufgetreten. + + path must be absolute. + Der Pfad muss absolut sein. - - Quit %1 - %1 beenden + + selected path does not support using virtual file system. %1 + Der ausgewählte Pfad unterstützt die Verwendung eines virtuellen Dateisystems nicht. %1 - - %1 version %2 -File synchronization desktop utility. - + + Select sync root + Wählen Sie das Synchronisierungsstammverzeichnis aus + + + OCC::AppImageUpdateAvailableWidget - - For more information, see http://www.owncloud.org - Für weitere Informationen, siehe http://www.owncloud.org + + Installed version: %1 + Installierte Version: %1 - - Show the settings dialog while starting. - Den Einstellungs-Dialog beim Start anzeigen. + + Available update: %1 + Update verfügbar: %1 - - Quit the running instance. - + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Eine Aktualisierung ist für dieses AppImage von %1 verfügbar. Möchten Sie die Aktualisierung installieren? + +Die Aktualisierung wird im Hintergrund durchgeführt und überschreibt die aktuelle AppImage-Datei. Sie müssen die App neu starten, um die Aktualisierung abzuschließen. + + + OCC::Application - - Write log to file (use - to write to stdout). - + + Quit + Beenden + + + OCC::AuthSuccessPageController - - Write each sync log output in a new file in folder. - + + Application Name Logo %1 + Anwendungsname Logo %1 - - Remove logs older than <hours> hours (to be used with --logdir). - + + You're all set! Open %1 and get started. + Sie sind fertig! Öffnen Sie %1 und legen Sie los. - - Flush the log file after every write. - + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo + + + OCC::ButtonDelegate - - Output debug-level messages in the log. - + + %1 options button + %1-Optionen-Schaltfläche - - - Override UI language. - + + Menu button with %1 options. Use the space key to show the menu + Menü-Schaltfläche mit %1 Optionen. Drücke die Leertaste, um das Menü anzuzeigen + + + OCC::ConnectionValidator - - Use the given configuration folder. - + + No ownCloud account configured + Kein ownCloud-Konto konfiguriert - - Enable debug mode. - + + Please update to the latest server and restart the client. + Bitte auf die neueste Server-Version aktualisieren und den Client neu starten. - - Virtual file system file to be opened (optional). - + + Authentication error: Either username or password are wrong. + Authentifizierungsfehler: Entweder der Benutzername oder das Passwort sind falsch. - - [<vfs file>] - + + timeout + Zeitüberschreitung - - Invalid path passed to --confdir - + + No account configured + Kein Konto konfiguriert - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - + + The provided credentials are not correct + Die zur Verfügung gestellten Anmeldeinformationen sind nicht korrekt. - - Available translations: %1 - + + The configured server for this client is too old. + Der konfigurierte Server ist für diesen Client veraltet. - OCC::AuthenticationDialog - - - Authentication Required - Authentifizierung erforderlich - - - - Enter username and password for '%1' at %2. - Benutzername und Passwort für '%1' auf %2 eingeben. - - - - &User: - &Benutzer: - + OCC::CredentialJob - - &Password: - &Passwort: + + Failed to parse credentials %1 + Fehler beim Verarbeiten der Zugangsdaten %1 - OCC::ConnectionValidator - - - No ownCloud account configured - Kein ownCloud-Konto konfiguriert - - - - The configured server for this client is too old - Der konfigurierte Server ist für diesen Client zu alt - - - - Please update to the latest server and restart the client. - Aktualisieren Sie auf die letzte Server-Version und starten Sie den Client neu. - + OCC::DetermineAuthTypeAdapter - - Authentication error: Either username or password are wrong. - Authentifizierungsfehler: Entweder der Benutzername oder das Passwort sind falsch. + + Server does not support OAuth + Der Server unterstützt OAuth nicht - - timeout - Zeitüberschreitung + + Server did not ask for authorization + Der Server hat nicht nach Authentifizierung gefragt. - - The provided credentials are not correct - Die zur Verfügung gestellten Anmeldeinformationen sind nicht korrekt + + Failed to determine authentication type: %1 + Der Authentifizierungstyp konnte nicht bestimmt werden: %1 - OCC::CredentialJob + OCC::DiscoverWebFingerServiceAdapter - - Failed to parse credentials %1 - + + Invalid reply received from server. + Ungültige Antwort vom Server erhalten. OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + Das Konto wurde gelöscht, bevor wir den Propfind-Job starten konnten. + + + + Server error: PROPFIND reply is not XML formatted! Server Fehler: Die PROPFIND Antwort ist nicht XML formattiert! @@ -940,27 +983,22 @@ File synchronization desktop utility. OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 Fehler beim Öffnen des Ordners %1 - + Directory not accessible on client, permission denied - Der Ordner ist auf dem Client nicht erreichbar, Zugriff verweigert + Der Ordner ist auf dem Client nicht erreichbar, Zugriff verweigert. - + Directory not found: %1 Ordner nicht gefunden: %1 - - Filename encoding is not valid - Dateikodierung ist ungültig - - - + Error while reading directory %1 Fehler beim Lesen des Ordners %1 @@ -968,148 +1006,137 @@ File synchronization desktop utility. OCC::ExpandingHeaderView - + Reset column sizes Spaltenbreiten zurücksetzen - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - Lokales Verzeichnis %1 existiert nicht. + + server sent invalid content type: %1. + Der Server hat einen ungültigen Inhaltstyp gesendet: %1. - - %1 should be a folder but is not. - %1 sollte ein Ordner sein, ist es aber nicht. + + could not parse Capabilities from server. + Die Capabilities konnten nicht vom Server analysiert werden. - - %1 is not readable. - %1 ist nicht lesbar. + + reply sent for different subject (server): %1 + Antwort wurde für ein anderes Subjekt (Server) gesendet: %1 + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 wurde gelöscht. - + %1 has been updated. %1 names a file. %1 wurde aktualisiert. - + %1 has been renamed to %2. %1 and %2 name files. %1 wurde in %2 umbenannt. - + %1 has been moved to %2. %1 wurde in %2 verschoben. - + %1 and %n other file(s) have been removed. - %1 und %n andere Datei wurde gelöscht.%1 und %n andere Dateien wurden gelöscht. + %1 und %n andere Datei wurden gelöscht.%1 und %n andere Dateien wurden gelöscht. - - %1 is not writable. - + + The folder has been disabled or removed from the server + Der Ordner wurde deaktiviert oder vom Server entfernt - + %1 and %n other file(s) have been added. - %1 und %n andere Datei(en) wurden hinzugefügt.%1 und %n andere Datei(en) wurden hinzugefügt. + %1 und %n andere Datei wurden hinzugefügt.%1 und %n andere Dateien wurden hinzugefügt. - + %1 has been added. %1 names a file. %1 wurde hinzugefügt. - + %1 and %n other file(s) have been updated. - %1 und %n andere Datei wurde aktualisiert.%1 und %n andere Dateien wurden aktualisiert. + %1 und %n andere Datei wurden aktualisiert.%1 und %n andere Dateien wurden aktualisiert. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 wurde in %2 umbenannt und %n andere Datei wurde umbenannt.%1 wurde in %2 umbenannt und %n andere Dateien wurden umbenannt. + %1 wurde in %2 umbenannt und %n andere Datei wurden umbenannt.%1 wurde in %2 umbenannt und %n andere Dateien wurden umbenannt. - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 wurde in %2 verschoben und %n andere Datei wurde verschoben.%1 wurde in %2 verschoben und %n andere Dateien wurden verschoben. + %1 wurde in %2 verschoben und %n andere Datei wurden verschoben.%1 wurde in %2 verschoben und %n andere Dateien wurden verschoben. - - %1 has and %n other file(s) have sync conflicts. - %1 und %n andere Datei haben Konflikte beim Abgleichen.%1 und %n andere Dateien haben Konflikte beim Abgleichen. + + %1 and %n other file(s) have sync conflicts. + %1 und %n andere Datei haben Konflikte beim Synchronisieren.%1 und %n andere Dateien haben Konflikte beim Synchronisieren. - + %1 has a sync conflict. Please check the conflict file! Es gab einen Konflikt bei der Synchronisierung von %1. Bitte prüfen Sie die Konfliktdatei! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 und %n weitere Datei konnten aufgrund von Fehlern nicht synchronisiert werden. Schauen Sie in das Protokoll für Details.%1 und %n weitere Dateien konnten aufgrund von Fehlern nicht synchronisiert werden. Schauen Sie in das Protokoll für Details. + %1 und %n andere Datei konnten aufgrund von Fehlern nicht synchronisiert werden. Schaue in das Protokoll für Details.%1 und %n andere Dateien konnten aufgrund von Fehlern nicht synchronisiert werden. Schaue in das Protokoll für Details. - + %1 could not be synced due to an error. See the log for details. - %1 konnte aufgrund eines Fehlers nicht synchronisiert werden. Schauen Sie in das Protokoll für Details. + %1 konnte aufgrund eines Fehlers nicht synchronisiert werden. Lesen Sie das Protokoll für weitere Details. - + Sync Activity Synchronisierungsaktivität - - Could not read system exclude file - Systemeigene Ausschlussdatei kann nicht gelesen werden - - - - A new folder larger than %1 MB has been added: %2. - - Ein neues Verzeichnis größer als %1 MB wurde hinzugefügt: %2. - - - - - A folder from an external storage has been added. - - Ein Verzeichnis, von einem externen Speicher wurde hinzugefügt. - + + Switching VFS mode on folder '%1' + Wechseln des VFS-Modus im Ordner '%1' - - Please go in the settings to select it if you wish to download it. - Bitte wechseln Sie zu den Einstellungen, falls Sie das Verzeichnis herunterladen möchten. + + Could not read system exclude file + Die systemeigene Ausschlussdatei kann nicht gelesen werden. - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - Der Ordner %1 wurde erzeugt aber früher von der Synchronisation ausgeschlossen. Daten im Ordner werden nicht synchronisiert. + Der Ordner %1 wurde erzeugt, aber von der Synchronisation ausgeschlossen. Daten im Ordner werden nicht synchronisiert. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - Die Datei %1 wurde erzeugt aber früher von der Synchronisation ausgeschlossen. Sie wird nicht synchronisiert. + Die Datei %1 wurde erzeugt, aber von der Synchronisation ausgeschlossen. Sie wird nicht synchronisiert. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1117,3587 +1144,2403 @@ This means that the synchronization client might not upload local changes immedi %1 Änderungen in synchronisierten Ordnern konnten nicht zuverlässig verfolgt werden. -Dies bedeutet, dass der Synchronisationsclient lokale Änderungen möglicherweise nicht sofort hochlädt und stattdessen nur nach lokalen Änderungen sucht und sie gelegentlich hochlädt (standardmäßig alle zwei Stunden). +Dies bedeutet, dass der Synchronisationsclient lokale Änderungen möglicherweise nicht sofort hochlädt, stattdessen nur nach lokalen Änderungen sucht und diese gelegentlich hochlädt (standardmäßig alle zwei Stunden). %1 + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Alle Dateien im Synchronisationsordner '%1' wurden auf dem Server gelöscht. -Diese Löschung wird in Ihren lokalen Synchronisationsordner übernommen. Dadurch sind die Dateien nicht mehr verfügbar, falls Sie keine Möglichkeit zur Wiederherstellung haben. -Wenn Sie sich dazu entscheiden, diese Dateien zu behalten, werden diese wieder zum Server synchronisiert. Dies geschieht jedoch nur, wenn Sie die Rechte dazu haben. -Wenn Sie sich zum Löschen der Dateien entscheiden, sind diese nicht mehr verfügbar, außer Sie sind der Eigentümer. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Alle Dateien im lokalen Synchronisationsordner '%1' wurden gelöscht. Diese Löschung wird mit Ihrem Server synchronisiert, wodurch die Dateien nicht mehr verfügbar sind, es sei denn, sie werden wiederhergestellt. -Sind Sie sich sicher, dass Sie diese Aktion mit Ihrem Server synchronisieren möchten? -Falls dies ein Missgeschick war und Sie sich zum Behalten der Dateien entscheiden, werden diese wieder vom Server synchronisiert. - - - - Remove All Files? - Alle Dateien löschen? - - - - Remove all files - Lösche alle Dateien + + There are unresolved conflicts. + Es existieren ungelöste Konflikte. - - Keep files - Dateien behalten + + Sync error: %1 + Synchronisierungsfehler: %1 OCC::FolderMan - + Could not reset folder state - Konnte Ordner-Zustand nicht zurücksetzen + Der Ordnerzustand konnte nicht zurückgesetzt werden. - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Ein altes Synchronisations-Journal '%1' wurde gefunden, konnte jedoch nicht entfernt werden. Bitte stellen Sie sicher, dass keine Anwendung es verwendet. - - - - (backup) - (Sicherung) + Ein altes Synchronisations-Journal '%1' wurde gefunden, konnte jedoch nicht entfernt werden. Bitte sicherstellen, dass es derzeit von keiner Anwendung verwendet wird. - - (backup %1) - (Sicherung %1) + + Unsupported filesystem + Nicht unterstütztes Dateisystem - - Undefined State. - Undefinierter Zustand. + + Account disconnected or paused + Konto getrennt oder pausiert - - Waiting to start syncing. - Wartet auf Beginn der Synchronisation + + User triggered force sync + Benutzer hat die Synchronisierung erzwungen - - Preparing for sync. - Synchronisation wird vorbereitet. + + Folder is about to be removed + Ordner wird entfernt. - - Sync is running. - Synchronisation läuft. + + (backup) + (Sicherung) - - Sync was successful, unresolved conflicts. - Synchronisation war erfolgreich, ungelöste Konflikte. + + (backup %1) + (Sicherung %1) - - Last Sync was successful. - Die letzte Synchronisation war erfolgreich. + + The selected path is not a folder. + Der ausgewählte Pfad ist kein Ordner. - - Setup Error. - Installationsfehler. + + The folder %1 is used in a folder sync connection. + Der Ordner %1 wird in einer Ordnersynchronisierungsverbindung verwendet. - - User Abort. - Benutzer-Abbruch + + Folder '%1' is already in use by application %2. + Der Ordner „%1“ wird bereits von der Anwendung %2 verwendet. - - Sync is paused. - Synchronisation wurde angehalten. + + There is already a sync from the server to this local folder. + Es besteht bereits eine Synchronisierung vom Server zu diesem lokalen Ordner. - - %1 (Sync is paused) - %1 (Synchronisation ist pausiert) + + The local folder %1 already contains a folder used in a folder sync connection. + Der lokale Ordner %1 enthält bereits einen Ordner, der in einer Ordnersynchronisierungsverbindung verwendet wird. - - No valid folder selected! - Kein gültiger Ordner gewählt! + + The local folder %1 is already contained in a folder used in a folder sync connection. + Der lokale Ordner %1 ist bereits in einem Ordner enthalten, der in einer Ordnersynchronisierungsverbindung verwendet wird. - - The folder %1 is used in a folder sync connection! - + + No valid folder selected. + Es wurde kein gültiger Ordner ausgewählt. - - The selected path does not exist! - Der gewählte Pfad existiert nicht! + + The selected path does not exist. + Der angegebene Pfad existiert nicht. - - The selected path is not a folder! - Der gewählte Pfad ist kein Ordner! + + You have no permission to write to the selected folder. + Sie haben keine Schreibberechtigung für den ausgewählten Ordner. + - - You have no permission to write to the selected folder! - Sie haben keine Schreibberechtigung für den ausgewählten Ordner! + + Folder '%1' is already in use by another account. + Der Ordner '%1' wird bereits von einem anderen Konto verwendet. - - There is already a sync from the server to this local folder. Please pick another local folder! - Es existiert bereits eine Synchronisation vom Server zu diesem lokalen Ordner. Bitte wählen Sie ein anderes lokales Verzeichnis! + + On macOS, only the Apple File System is supported. + Unter macOS wird nur das Apple File System unterstützt. + + + OCC::FolderModelController - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Der lokale Ordner %1 liegt innerhalb eines synchronisierten Ordners. Bitte wählen Sie einen anderen aus! + + %1 options + %1 Optionen - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Der lokale Ordner %1 liegt in einem Ordner, der bereits synchronisiert wird. Bitte wählen Sie einen anderen aus! + + Menu button with %1 options. Use the space key to pop the %2 options menu + Menüschaltfläche mit %1 Optionen. Drücke die Leertaste, um das %2-Optionsmenü aufzurufen. + + + OCC::FolderWatcher - - %1 Please pick another one! - + + The watcher did not receive a test notification. + Der Beobachterservice hat keine Test-Benachrichtigung erhalten. + + + OCC::FolderWatcherPrivate - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Dieses Problem tritt normalerweise auf, wenn die Inotify-Beobachter mechanismen erschöpft sind. Überprüfen Sie die FAQ für Details. - OCC::FolderStatusDelegate + OCC::FolderWizard + + + Virtual files are not available for the selected folder + Virtuelle Dateien sind für den ausgewählten Ordner nicht verfügbar. + - + Add Folder Sync Connection Ordner-Synchronisation hinzufügen - - File - Datei + + Add Sync Connection + Synchronisation hinzufügen - OCC::FolderStatusModel + OCC::FolderWizardLocalPath - - You need to be connected to add a folder - Sie müssen verbunden sein, um einen Ordner hinzuzufügen + + Click to select a local folder to sync. + Wählen Sie durch einen Klick ein lokales Verzeichnis für die Synchronisation aus. - - Click this button to add a folder to synchronize. - Wählen Sie diese Schaltfläche, um einen zu synchronisierenden Ordner hinzuzufügen. + + Enter the path to the local folder. + Einen Pfad zum lokalen Verzeichnis eingeben - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Select the local folder + Einen lokalen Ordner auswählen + + + OCC::FolderWizardSelectiveSync - - Error while loading the list of folders from the server. - Fehler beim Empfang der Ordnerliste vom Server. + + Use virtual files instead of downloading content immediately + Virtuelle Dateien verwenden statt die Dateien gleich herunterzuladen + + + OCC::GETFileJob - - Virtual file support is enabled. - Unterstützung für virtuelle Dateien ist aktiviert. + + Connection Timeout + Zeitüberschreitung der Verbindung - - Signed out - Abgemeldet + + No E-Tag received from server, check Proxy/Gateway + Kein E-Tag vom Server empfangen, bitte Proxy / Gateway überprüfen. - - Fetching folder list from server... - Empfange Orderliste vom Server... + + We received a different E-Tag for resuming. Retrying next time. + Es wurde ein unterschiedlicher E-Tag zum Fortfahren empfangen. Bitte beim nächsten Mal erneut versuchen. - - There are unresolved conflicts. Click for details. - Es existieren ungelöste Konflikte. Für Details klicken. + + We received an unexpected download Content-Length. + Unerwartete Datenlänge beim Herunterladen empfangen. - - Checking for changes in remote '%1' - Änderungen der entfernten Ressource '%1' werden geprüft + + Server returned wrong content-range + Der Server hat einen falschen Bereich für den Inhalt zurück gegeben. + + + OCC::GraphApi::Space - - Checking for changes in local '%1' - Änderungen der lokalen Ressource '%1' werden geprüft + + Personal + Persönlich - - Reconciling changes - Änderungen zusammenführen + + Shares + Freigaben + + + OCC::IgnoreListEditor - - , '%1' - Build a list of file names - , '%1' + + Ignored Files Editor + Editor für ignorierte Dateien - - '%1' - Argument is a file name - '%1' + + Files Ignored by Patterns + Nach Muster ignorierte Dateien - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synchronisiere %1 + + Add + Hinzufügen - - - , - , + + Pattern + Muster - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - Download %1/s + + Allow Deletion + Löschen erlauben - - ↓ %1/s - ↓ %1/s + + Remove + Entfernen - - ↑ %1/s - ↑ %1/s + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Dateien oder Ordner, die einem definierten Muster entsprechen, werden nicht synchronisiert. Änderungen werden bei der nächsten Synchronisation übernommen. + +Objekte, bei denen das Löschen erlaubt ist, werden gelöscht, falls sie die Löschung eines Ordners verhindern würden. Das trifft besonders auf Metadaten zu. - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - Upload %1/s + + Could not open file + Die Datei konnte nicht geöffnet werden. - - Local folder: %1 - Lokaler Ordner: %1 + + Cannot write changes to '%1'. + Änderungen konnten nicht nach '%1' geschrieben werden. - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 von %4) + + Add Ignore Pattern + Ignoriermuster hinzufügen - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 + + Add a new ignore pattern: + Neues Ignoriermuster hinzufügen: - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 übrig, %1 von %2, Datei %3 von %4 + + This entry is provided by the system at '%1' and cannot be modified in this view. + Dieser Eintrag wird vom System auf '%1' bereitgestellt und kann in dieser Ansicht nicht geändert werden. + + + OCC::LocalActivityWidget - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 of %2, Datei %3 von %4 + + Filter + Filter - - file %1 of %2 - Datei %1 von %2 + + Local activity table + Tabelle mit lokaler Aktivität - - Waiting... - Warte... - - - - Waiting for %n other folder(s)... - Warte auf einen anderen OrdnerWarte auf %n andere Ordner + + + Account + Konto - - Preparing to sync... - Bereite Synchronisation vor... + + Filter menu + Filtermenü - - - OCC::FolderWatcher - - The watcher did not receive a test notification. - Der Beobachter hat keine Test-Benachrichtigung erhalten. + + Actions menu + Aktionsmenü - - - OCC::FolderWatcherPrivate - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Dieses Problem tritt normalerweise auf, wenn die Inotify-watcher erschöpft sind. Überprüfen Sie die FAQ für Details. + + Sort ascending by %1 + Aufsteigend nach %1 sortieren - - - OCC::FolderWizard - - Add Folder Sync Connection - Ordner-Synchronisation hinzufügen + + Sort descending by %1 + Absteigend nach %1 sortieren - - Add Sync Connection - Synchronisation hinzufügen + + Retry sync + Synchronisation wiederholen - OCC::FolderWizardLocalPath + OCC::LogBrowser - - Click to select a local folder to sync. - Zur Auswahl eines lokalen Verzeichnisses für die Synchronisation klicken. + + Log Output + Log-Ausgabe - - Enter the path to the local folder. - Pfad zum lokalen Verzeichnis eingeben + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>Die Protokolle enthalten sensible Informationen, die nicht öffentlich verfügbar gemacht werden sollten.</b></span></p></body></html> - - Select the source folder - Den Quellordner wählen + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + Der Client kann Protokolldateien in einen temporären Ordner schreiben. Die darin enthaltenen Informationen sind hilfreich zur Diagnose von Problemen. +Da diese Protokolldateien sehr groß werden können, erstellt der Client für jede Synchronisierung ein neues Protokoll und komprimiert ältere. - - - OCC::FolderWizardRemotePath - - Create Remote Folder - Entfernten Ordner erstellen + + Enable logging to temporary folder + Einschalten von Logging in einen temporären Ordner - - Enter the name of the new folder to be created below '%1': - Geben Sie den Namen des neuen, unter '%1' zu erstellenden Ordners ein: + + If enabled, logs will be written to: + Logs werden - wenn eingeschaltet - geschrieben nach: - - Folder was successfully created on %1. - Order erfolgreich auf %1 erstellt. - - - - Authentication failed accessing %1 - Die Authentifizierung ist beim Zugriff auf %1 fehlgeschlagen - - - - Failed to create the folder on %1. Please check manually. - Die Erstellung des Ordners auf %1 ist fehlgeschlagen. Bitte prüfen Sie dies manuell. - - - - Failed to list a folder. Error: %1 - Konnte Ordner nicht listen. Fehler: %1 - - - - Choose this to sync the entire account - Wählen Sie dies, um das gesamte Konto zu synchronisieren - - - - This folder is already being synced. - Dieser Ordner wird bereits synchronisiert. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Sie synchronisieren bereits <i>%1</i>, das ein übergeordneten Ordner von <i>%2</i> ist. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Sie synchronisieren bereits <i>%1</i>, das ein untergeordneter Ordner von <i>%2</i> ist. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Virtuelle Dateien verwenden, anstatt die Dateien herunter zu laden%1 - - - - (experimental) - (Experimentell) - - - - Virtual files are not available for the selected folder - Virtuelle Dateien sind für den ausgewählten Ordner nicht verfügbar - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Warnung:</b> %1 - - - - <b>Warning:</b> - <b>Warnung:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Kein E-Tag vom Server empfangen, bitte Proxy / Gateway überprüfen - - - - We received a different E-Tag for resuming. Retrying next time. - Es wurde ein unterschiedlicher E-Tag zum Fortfahren empfangen. Bitte beim nächsten mal nochmal versuchen. - - - - We received an unexpected download Content-Length. - Unerwartete Content-Length beim Herunterladen empfangen. - - - - Server returned wrong content-range - Server hat falschen Bereich für den Inhalt zurück gegeben - - - - OCC::GETJob - - - Connection Timeout - Zeitüberschreitung der Verbindung - - - - OCC::GeneralSettings - - - Form - Formular - - - - General Settings - Allgemeine Einstellungen - - - - Language - Sprache - - - - Sync hidden files - Synchronisiere versteckte Dateien - - - - Log Settings - Einstellungen für Logging - - - - Advanced - Approval of folder sync (Non virtual file mode only) - Erweitert - Synchronisierung von Ordner genehmigen (Ausschliesslich im nicht-virtuellen Dateien Modus) - - - - Network - Netzwerk - - - - Updates - Aktualisierungen - - - - stable - Stabil - - - - beta - Beta - - - - About - Über - - - - Advanced - Erweitert - - - - Use &Monochrome Icons in the system tray - &Einfarbige Icons in der Taskleiste verwenden - - - - Ask for confirmation before synchronizing folders larger than - Nachfragen, bei Synchronisation von Ordnern grösser als - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Bestätigung erfragen, bevor externe Speicher synchronisiert werden - - - - &Launch on System Startup - &Starten beim Systemstart - - - - Show &Desktop Notifications - Zeige &Desktop-Benachrichtigungen - - - - &Update Channel - &Update Kanal - - - - Edit &Ignored Files - I&gnorierte Dateien bearbeiten - - - - Show sync folders in &Explorer's Navigation Pane - Synchronisations-Ordner und Explorer-Navigation zeigen - - - - S&how crash reporter - &Crash-Reporter anzeigen - - - - &Restart && Update - &Neustarten && aktualisieren - - - - You cannot disable autostart because system-wide autostart is enabled. - Autostart kann nicht abgeschaltet werden, da Autostart als Systemeinstellung aktiviert ist. - - - - Change update channel? - Update-Kanal verändern? - - - - Warning - Warnung - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Der Update-Kanal legt fest welche Programm-Updates zur Installation angeboten werden. Der "Stabil"-Kanal enthält nur Upgrades die als verlässlich betrachtet werden, während Versionen im "Beta"-Kanal neuer Funktionen und Fehlerbehebungen enthalten können, die bisher noch nicht komplett bzw. ausführlich genug getestet werden konnten. -Beachten Sie, das dies nur bestimmt von welchem Bereich Sie Upgrades erhalten und dass es keine Downgrade-Möglichkeiten gibt: Ein "Schritt zurück" vom Beta-Kanal zum Stabilen-Kanal kann normalerweise nicht unverzüglich erfolgen und bedeutet Wartezeiten auf eine Stabile-Version, die dann neuer ist als die jeweils aktuell installierte Beta-Version. - - - - Change update channel - Update-Kanal ändern - - - - Cancel - Abbrechen - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Bitte Kennwort für %1 eingeben:<br><br>Benutzer: %2<br>Konto: %3<br> - - - - Reading from keychain failed with error: '%1' - Schlüsselbund fehlgeschlagen mit Fehler: '%1' - - - - Enter Password - Passwort eingeben - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Klicken Sie hier</a> um ein App-Passwort von dem Web-Interface zu erhalten. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor für ignorierte Dateien - - - - Files Ignored by Patterns - Nach Muster ignorierte Dateien - - - - Add - Hinzufügen - - - - Pattern - Muster - - - - Allow Deletion - Löschen erlauben - - - - Remove - Entfernen - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Dateien oder Ordner, die diesem Muster entsprechen, werden nicht synchronisiert. Änderungen werden bei der nächsten Synchronisation übernommen. - -Objekte, bei denen Löschen erlaubt ist, werden gelöscht, wenn sie die Löschung eines Ordners verhindern würden. Das ist für Metadaten nützlich. - - - - Could not open file - Datei konnte nicht geöffnet werden - - - - Cannot write changes to '%1'. - Konnte Änderungen nicht in '%1' schreiben. - - - - Add Ignore Pattern - Ignoriermuster hinzufügen - - - - Add a new ignore pattern: - Neues Ignoriermuster hinzufügen: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Dieser Eintrag wird vom System auf '%1' bereitgestellt und kann in dieser Ansicht nicht geändert werden. - - - - OCC::IssuesWidget - - - Form - Formular - - - - List of issues - Liste der Fehler - - - - Filter - Filter - - - - There were too many issues. Not all will be visible here. - Es gab zu viele Probleme. Nicht alle können hier dargestellt werden. - - - - There were conflicts. Check the documentation on how to resolve them. - Es bestehen Konflikte. Prüfen Sie die Dokumentation, wie diese zu lösen sind. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - Spaltenbreiten zurücksetzen - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Es bestehen Konflikte. <a href="%1">Prüfen Sie die Dokumentation, wie diese zu lösen sind.</a> - - - - Account - Benutzerkonto - - - - OCC::LibCloudProvidersPrivate - - - Settings - Einstellungen - - - - OCC::LogBrowser - - - Log Output - Log-Ausgabe - - - - Enable logging to temporary folder - Einschalten von Logging in einen temporären Ordner - - - - Delete logs older than %1 hours - Logs löschen, die älter sind als %1 Stunden - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">Die Logs enthalten sensible Informationen und sollten nicht veröffentlicht werden</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - Der Client kann Debug-Logs in einen temporären Ordner schreiben. Diese Logs sind sehr hilfreich bei der Fehlersuche. -Da die Log Dateien groß werden können, wird der Client eine neue Datei für jeden Synchronisationslauf anlegen und alte Dateien komprimieren. Es wird außerdem empfohlen das Löschen von Log Dateien nach einigen Stunden einzuschalten. Dies verhindert unnötigen Verbrauch von Plattenplatz. - - - - If enabled, logs will be written to: - Logs werden - wenn eingeschaltet - geschrieben nach: - - - - C:/log - C:/log - - - - Log Http traffic - HTTP Datenverkehr loggen - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Diese Einstellungen bleiben nach einem Neustart des Clients erhalten. -Beachten Sie, dass Kommandozeilenoptionen zum Logging diese Einstellungen überschreiben. - - - - Open folder - Ordner öffnen - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Eine neue Version ist verfügbar. - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Eine neue Version des %1 Clients ist verfügbar.</p><p><b>%2</b> steht zum Herunterladen bereit. Die installierte Version ist %3.</p> - - - - - Skip this version - Diese Version auslassen - - - - Skip this time - Dieses Mal überspringen - - - - Get update - Update durchführen - - - - Update Failed - Update fehlgeschlagen - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Eine neue Version des %1 Clients ist verfügbar, aber der Aktualisierungs-Prozess ist fehlgeschlagen.</p><p><b>%2</b> wurde heruntergeladen. Die installierte Version ist %3.</p> - - - - Ask again later - Später noch einmal fragen - - - - Restart and update - Neustarten und aktualisieren - - - - Update manually - Manuell aktualisieren - - - - OCC::NetworkSettings - - - Form - Formular - - - - Proxy Settings - Proxy-Einstellungen - - - - No Proxy - Kein Proxy - - - - Use system proxy - Benutze System-Proxy - - - - Specify proxy manually as - Proxy manuell definieren als - - - - Host - Host - - - - : - : - - - - Proxy server requires authentication - Proxyserver erfordert eine Authentifizierung - - - - Note: proxy settings have no effects for accounts on localhost - Beachte: Proxy Einstellungen haben keinen Effekt bei Konten auf localhost - - - - Download Bandwidth - Download-Bandbreite - - - - - Limit to - Begrenzen auf - - - - - KBytes/s - KBytes/s - - - - - No limit - Keine Begrenzung - - - - - Limit to 3/4 of estimated bandwidth - Auf 3/4 der geschätzten Bandbreite begrenzen - - - - Upload Bandwidth - Upload-Bandbreite - - - - - Limit automatically - Automatisch begrenzen - - - - Hostname of proxy server - Hostname des Proxy-Servers - - - - Username for proxy server - Benutzername für den Proxy-Server - - - - Password for proxy server - Passwort für den Proxy-Server - - - - HTTP(S) proxy - HTTP(S)-Proxy - - - - SOCKS5 proxy - SOCKS5-Proxy - - - - OCC::NotificationWidget - - - Created at %1 - Erstellt von %1 - - - - Closing in a few seconds... - Schließe in wenigen Sekunden... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 Anfrage an %2 fehlgeschlagen - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' ausgewählt von %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Fehler vom Server zurückgegeben: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Beim Zugriff auf den 'token' Endpunkt trat ein Fehler auf: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Die JSON Antwort des Servers konnte nicht geparst werden: <br><em>%1</em> - - - - Unsupported token type: %1 - Nicht unterstützter Tokentyp: %1 - - - - The reply from the server did not contain all expected fields -:%1 - Die Antwort des Servers hat nicht alle erwarteten Felder beinhaltet -:%1 - - - - Unknown Error - Unbekannter Fehler - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>Anmeldefehler </h1><p>Konnte Benutzerdaten nicht auslesen</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Anmeldefehler </h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Fehler beim Erneuern des Tokens: %1: %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - Die Antwort des Servers enthält nicht alle erwarteten Felder -:%1 -Empfangene Daten: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Falscher Benutzer</h1><p>Sie haben sich mit dem Benutzer <em>%1</em> angemeldet, sollten sich jedoch mit dem Benutzer <em>%2</em> anmelden. <br>Bitte melden Sie sich in einem anderen Tab von %3 ab und <a href='%4'>klicken Sie hier</a> um sich als %2</p> anzumelden - - - - OCC::OCUpdater - - - New %1 Update Ready - Neues %1 Update verfügbar - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Ein neues Update für %1 wird installiert. Während des Updatevorganges werden Sie eventuell gefragt zusätzliche Rechte im Rahmen des Updatevorganges zu gewähren. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Aktualisierung kann nicht herunter geladen werden. Bitte klicken Sie <a href='%1'>hier</a> um die Aktualisierung manuell herunter zu laden. - - - - Could not check for new updates. - Auf neue Aktualisierungen kann nicht geprüft werden. - - - - Downloading %1. Please wait... - Herunterladen von %1. Bitte warten… - - - - %1 available. Restart application to start the update. - %1 ist verfügbar. Anwendung zum Start der Aktualisierung neu starten. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Aktualisierung von %1 verfügbar. Bitte nutzen Sie zur Installation das Systemaktualisierungstool. - - - - Checking update server... - Aktualisierungsserver wird überprüft … - - - - Update status is unknown: Did not check for new updates. - Updatestatus unbekannt: Auf neue Updates wurde nicht geprüft. - - - - No updates available. Your installation is at the latest version. - Keine Aktualisierungen verfügbar. Ihre Installation ist die aktuelle Version. - - - - Update Check - Aktualitätsprüfung - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Verbinden mit %1 - - - - Setup local folder options - Einstellungen der Optionen für lokale Verzeichnisse - - - - Connect... - Verbinden… - - - - (experimental) - (Experimentell) - - - - Use &virtual files instead of downloading content immediately%1 - &Virtuelle Dateien verwenden, anstatt die Dateien herunter zu laden%1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - <p><strong>Achtung:</strong> Der lokale Ordner ist nicht leer. Bitte wählen Sie eine entsprechende Lösung!</p> - - - - Sync the folder '%1' - Ordner '%1' synchronisieren - - - - Virtual files are not available for the selected folder - Virtuelle Dateien sind für den ausgewählten Ordner nicht verfügbar - - - - Error - Fehler - - - - Local Sync Folder - Lokaler Ordner für die Synchronisation - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &E-Mail - - - - Connect to %1 - Verbinden mit %1 - - - - Enter user credentials - Geben Sie die Benutzer-Anmeldeinformationen ein - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Verbinden mit %1 - - - - Login in your browser - Melden Sie sich in Ihrem Browser an - - - - Copy link to clipboard - Link in Zwischenablage kopieren - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - Datei %1 wird gerade benutzt - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Verbinden mit %1 - - - - Setup %1 server - %1 Server einrichten - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Diese URL ist NICHT sicher, das sie nicht verschlüsselt ist. -Es ist nicht ratsam, diese zu benutzen. - - - - This url is secure. You can use it. - Diese URL ist sicher. Sie können Sie benutzen. - - - - &Next > - &Weiter > - - - - Invalid URL - Ungültige URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Die Verbindung zu %1 auf %2:<br/>%3 konnte nicht hergestellt werden - - - - Timeout while trying to connect to %1 at %2. - Zeitüberschreitung beim Verbindungsversuch mit %1 unter %2. - - - - Invalid URL - Ungültige URL - - - - Could not create local folder %1 - Der lokale Ordner %1 konnte nicht angelegt werden - - - - Error: %1 - Fehler: %1 - - - - The folder creation resulted in HTTP error code %1 - Das Erstellen des Verzeichnisses erzeugte den HTTP-Fehler-Code %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Die Remote-Ordner-Erstellung ist fehlgeschlagen, weil die angegebenen Zugangsdaten falsch sind. Bitte gehen Sie zurück und überprüfen Sie die Zugangsdaten. - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Remote-Ordner %1 konnte mit folgendem Fehler nicht erstellt werden: <tt>%2</tt>. - - - - Folder rename failed - Ordner umbenennen fehlgeschlagen. - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Der Ordner kann nicht entfernt und gesichert werden, da der Ordner oder einer seiner Dateien in einem anderen Programm geöffnet ist. Bitte schließen Sie den Ordner oder die Datei oder beenden Sie die Installation. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Verbindungsassistent + + C:/log + C:/log - - Enable experimental feature? - Experimentelle Funktion aktivieren? + + Log Http traffic + HTTP Datenverkehr loggen - - Enable experimental placeholder mode - Experimentellen Platzhalter Modus freischalten + + Log files to keep: + Zu behaltende Logfiles: - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Wenn der Modus "Virtuelle Dateien" aktiviert ist, werden die Dateien nicht sofort heruntergeladen. Stattdessen wird für jede Datei des Servers eine winzige "%1" Datei erstellt. Der Inhalt der Dateien kann durch Ausführen der Datei oder über das Kontext-Menü heruntergeladen werden. - -Der Modus virtuelle Dateien schließt sich mit dem Modus "Selective Sync" gegenseitig aus. Aktuell abgewählte Ordner werden zu nur online verfügbaren Ordnern umgesetzt und die Einstellungen im "Selective Sync" werden verworfen. - -Dies ist ein neuer, experimenteller Modus. Wenn sie sich dafür entscheiden, berichten sie bitte alle Probleme, die auftreten. + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Diese Einstellungen bleiben nach einem Neustart des Clients erhalten. +Beachten Sie, dass Kommandozeilenoptionen zum Logging diese Einstellungen überschreiben. - - Stay safe - Sicher bleiben + + Open folder + Ordner öffnen - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Symbolische Verknüpfungen werden bei der Synchronisation nicht unterstützt. - - - - File is listed on the ignore list. - Die Datei ist in der Ignorierliste geführt. - - - - File names ending with a period are not supported on this file system. - Dateinamen die mit einem Punkt enden sind in diesem Dateisystem nicht unterstützt. - - - - File names containing the character '%1' are not supported on this file system. - Dateinamen beinhalten das Zeichen '%1' diese werden in diesem Dateisystem nicht unterstützt. - - - - File name contains at least one invalid character - Der Dateiname enthält mindestens ein ungültiges Zeichen - - - - The file name is a reserved name on this file system. - Der Dateiname ist ein reservierter Name in diesem Dateisystem. - - - - Filename contains trailing spaces. - Der Dateiname endet mit Leerzeichen. - - - - Filename is too long. - Der Dateiname ist zu lang. - - - - File/Folder is ignored because it's hidden. - Datei wird ignoriert, weil sie versteckt ist. - - - - Stat failed. - Stat fehlgeschlagen. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Konflikt: Serverversion heruntergeladen, lokale Kopie umbenannt und nicht hochgeladen. - - - - The filename cannot be encoded on your file system. - Der Dateiname kann auf Ihrem Dateisystem nicht kodiert werden. - - - - The filename is blacklisted on the server. - Der Dateiname ist auf der schwarzen Liste des Servers. - - - - size - Größe - - - - permissions - Berechtigungen - - - - etag - E-Tag - - - - file id - file id - - - - server reported no %1 - Der Server meldet kein %1 - - - - Moved to invalid target, restoring - An ungültigen Zielort verschoben, Wiederherstellung - - - - Not allowed to remove, restoring - Löschen nicht erlaubt, Wiederherstellung - - - - Ignored because of the "choose what to sync" blacklist - Aufgrund der »Zu synchronisierende Elemente auswählen«-Sperrliste ignoriert - - - - Not allowed because you don't have permission to add subfolders to that folder - Nicht erlaubt, da Sie keine Rechte zur Erstellung von Unterordnern haben - - - - Not allowed because you don't have permission to add files in that folder - Nicht erlaubt, da Sie keine Rechte zum Hinzufügen von Dateien in diesen Ordner haben - - - - Not allowed to upload this file because it is read-only on the server, restoring - Das Hochladen dieser Datei ist nicht erlaubt, da die Datei auf dem Server schreibgeschützt ist, Wiederherstellung - + OCC::MainWindow - - Error while reading the database - Fehler beim Lesen der Datenbank + + Hide + Ausblenden - - Server replied with an error while reading directory '%1' : %2 - Der Server meldet einen Fehler beim Lesen des Ordners '%1' : %2 + + Main toolbar for the application + - - - OCC::PropagateDirectory - - Error updating metadata: %1 + + Main toolbar - - %1 the folder is currently in use - + + + More + Mehr - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - + OCC::MainWindowController - - - File %1 can not be downloaded because of a local file name clash with %2! + + Add account... - - The download would reduce free local disk space below the limit - Das Herunterladen würde den lokalen freien Speicherplatz unter das Limit reduzieren - - - - Free space on disk is less than %1 - Der freie Speicher auf der Festplatte ist weniger als %1 - - - - File was deleted from server - Die Datei wurde vom Server gelöscht - - - - The file could not be downloaded completely. - Die Datei konnte nicht vollständig herunter geladen werden. - - - - The downloaded file is empty despite the server announced it should have been %1. - Die heruntergeladene Datei ist leer, obwohl der Server %1 als Größe übermittelt hat. + + Settings... + Einstellungen … - - File %1 cannot be saved because of a local file name clash with %2! - + + About... + Über... - - Error updating metadata: %1 - + + Quit + Beenden - - - - - The file %1 is currently in use + + + Errors: %1 - - - File has changed since discovery - Datei ist seit der Entdeckung geändert worden - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Wiederherstellung fehlgeschlagen: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - Konnte Datei %1 nicht löschen. Fehler: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - Der lokale Ordner %1 konnte aufgrund eines Namenkonfliktes mit %2 nicht erstellt werden - - - - Error updating metadata: %1 - + + Activity + Aktivität - - The file %1 is currently in use - + + Quit %1 + %1 beenden - - could not create folder %1 - Ordner %1 konnte nicht angelegt werden + + Are you sure you want to quit %1? + Sind Sie sicher, dass Sie %1 beenden möchten? - OCC::PropagateLocalRemove + OCC::ModalWrapperWidget - - Could not remove %1 because of a local file name clash with %2! - + + Close + Schließen - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - %1 kann aufgrund eines Konfliktes mit dem lokalen Dateinamen nicht zu %2 umbenannt werden - - - - Could not rename %1 to %2, the file is currently in use - Konnte %1 nicht in %2 umbenennen, die Datei wird gerade benutzt - + OCC::Models - - Error updating metadata: %1 - + + %1 Filter: + %1 Filter: - - The file %1 is currently in use - + + All + Alle - - - Failed to rename file - + + + Reset Filters + Filter zurücksetzen - - Error setting pin state - Fehler beim Setzen des PIN-Zustandes + + Status Filter: + Status Filter: - OCC::PropagateRemoteDelete + OCC::NetworkSettings - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 204, aber gesendet wurde "%1 %2". + + Form + Formular - - - OCC::PropagateRemoteMkdir - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 201, aber gesendet wurde "%1 %2". + + Pause synchronization when the Internet connection is metered + Pausiere Synchronisierung, wenn die Internetverbindung getaktet wird. - - Error writing metadata to the database: %1 - + + Proxy Settings + Proxy-Einstellungen - - Setting file status failed due to file lock - + + No Proxy + Kein Proxy - - - OCC::PropagateRemoteMove - - Could not rename %1 to %2, error: %3 - Konnte %1 nicht nach %2 umbenennen, Fehler: %3 + + Use system proxy + Benutze System-Proxy - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 201, aber gesendet wurde "%1 %2". + + Host + Host - - Error updating metadata: %1 - + + Proxy Hostname + Proxy-Hostname - - The file %1 is currently in use - + + : + : - - Error writing metadata to the database - Fehler beim Schreiben der Metadaten in die Datenbank + + Proxy Port Number + Proxy-Portnummer - - Error setting pin state - Fehler beim Setzen des PIN-Zustandes + + Proxy server requires authentication + Proxyserver erfordert eine Authentifizierung - - - OCC::PropagateUpdateMetaDataJob - - Could not update file : %1 - Die Datei konnte nicht aktualisiert werden: %1 + + Note: proxy settings have no effects for accounts on localhost + Beachte: Proxy Einstellungen haben keinen Effekt bei Konten auf localhost - - The file %1 is currently in use - + + Specify proxy manually + Geben Sie den Proxy manuell an - - - OCC::PropagateUploadFileCommon - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Die Datei %1 kann nicht hochgeladen werden, da eine andere Datei mit dem selben Namen, nur unterschiedlicher Großschreibung, existiert + + Proxy type + Proxy-Typ - - File Removed - Datei gelöscht + + + Hostname of proxy server + Hostname des Proxy-Servers + + + + + Username for proxy server + Benutzername für den Proxy-Server - - Local file changed during syncing. It will be resumed. - Lokale Datei hat sich während der Synchronisation geändert. Die Synchronisation wird wiederaufgenommen. + + + Password for proxy server + Passwort für den Proxy-Server - - Local file changed during sync. - Eine lokale Datei wurde während der Synchronisation geändert. + + HTTP(S) proxy + HTTP(S)-Proxy - - Error updating metadata: %1 - + + SOCKS5 proxy + SOCKS5-Proxy + + + OCC::NewAccountBuilder - - The file %1 is currently in use - + + no error details are available + Es liegen keine Fehlerdetails vor. - - - Upload of %1 exceeds the quota for the folder - Das Hochladen von %1 überschreitet das Speicherkontingent des Ordners + + Unable to connect to server during account creation: %1. + Unable to connect to server during account creation: %1.Verbindung zum Server konnte während der Kontoerstellung nicht hergestellt werden: %1. - OCC::PropagateUploadFileNG + OCC::NewAccountWizard - - %1 the file is currently in use - Datei %1 wird gerade benutzt + + Cancel Setup + Setup abbrechen - - The local file was removed during sync. - Die lokale Datei wurde während der Synchronisation gelöscht. + + Do you really want to cancel the account setup? + Die Benutzereinrichtung wirklich abbrechen? + + + OCC::NewAccountWizardController - - Local file changed during sync. - Eine lokale Datei wurde während der Synchronisation geändert. + + Add account… + Konto hinzufügen… - - The server did ask for a removed legacy feature(polling) - + + Back + Zurück - - Unexpected return code from server (%1) - Unerwarteter Rückgabe-Code Antwort vom Server (%1) + + Advanced + Erweitert - - Missing File ID from server - Fehlende Datei-ID vom Server + + Sign in + Anmelden - - Missing ETag from server - Fehlender ETag vom Server + + Open sign in again + Öffnen Sie die Anmeldung erneut - OCC::PropagateUploadFileTUS + OCC::NewVersionAvailableWidget - - %1 the file is currently in use - Datei %1 wird gerade benutzt + + Skip this version + Diese Version überspringen - - The local file was removed during sync. - Die lokale Datei wurde während der Synchronisation gelöscht. + + Get update + Update durchführen - - Local file changed during sync. - Eine lokale Datei wurde während der Synchronisation geändert. + + Skip this time + Dieses Mal überspringen - OCC::PropagateUploadFileV1 + OCC::OAuth - - %1 the file is currently in use - Datei %1 wird gerade benutzt + + Successfully signed in + Erfolgreich angemeldet - - The server did ask for a removed legacy feature(polling) - + + Now, explore %1 on desktop. + Erkunden Sie nun %1 auf Ihrem Desktop. - - The local file was removed during sync. - Die lokale Datei wurde während der Synchronisation gelöscht. + + Error returned from the server: <em>%1</em> + Fehler vom Server zurückgegeben: <em>%1</em> - - Local file changed during sync. - Eine lokale Datei wurde während der Synchronisation geändert. + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Beim Zugriff auf den 'token' Endpunkt trat ein Fehler auf: <br><em>%1</em> - - The server did not acknowledge the last chunk. (No e-tag was present) - Der Server hat den letzten Block nicht bestätigt. (Der E-Tag war nicht vorhanden) + + Could not parse the JSON returned from the server: <br><em>%1</em> + Die JSON Antwort des Servers konnte nicht geparst werden: <br><em>%1</em> - - - OCC::ProtocolItemModel - - Time - Zeit + + Unsupported token type: %1 + Nicht unterstützter Tokentyp: %1 - - File - Datei + + The reply from the server did not contain all expected fields +:%1 + Die Antwort des Servers hat nicht alle erwarteten Felder beinhaltet +:%1 - - Folder - Ordner + + Unknown Error + Unbekannter Fehler - - Issues - + + + Login Error + Anmeldefehler - - Action - Aktion + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + Sie haben sich mit Benutzer <em>%1</em>angemeldet, müssen sich aber mit Benutzer<em>%2</em>.<br>Bitte kehren Sie zum %3-Client zurück und starten Sie die Authentifizierung neu. - - Size - Größe + + Wrong user + Falscher Benutzer + + + OCC::OAuthPageController - - Account - Benutzerkonto + + Application Name Logo %1 + Anwendungsname Logo %1 - - Status - Status + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Lassen Sie dieses Fenster geöffnet. In Ihrem Webbrowser erscheint eine Anmeldeaufforderung, die Sie mit der folgenden Adresse verbindet. - - - OCC::ProtocolWidget - - Form - Formular + + Sign in URL + Anmelde-URL - - Local sync protocol - Lokales Sychronisationsprotokoll + + To copy the sign in URL to the clipboard, use the copy button + Um die Anmelde-URL in die Zwischenablage zu kopieren, verwenden Sie die Schaltfläche „Kopieren“. - - Filter - Filter + + Copy URL to sign in + URL zum Anmelden kopieren - - Account - Benutzerkonto + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo - - Copy to clipboard - In die Zwischenablage kopieren + + URL copied + URL kopiert - - Show in file browser - Im Datei Browser anzeigen + + Copy URL + URL kopieren - - Show in web browser + + Unable to connect to the login service. If the problem continues, please contact support. - - Retry sync - + + Please copy the authentication URL using the button above, and provide this information to support. + Bitte kopieren Sie die Authentifizierungs-URL mithilfe der Schaltfläche oben und geben Sie diese Informationen an den Support weiter. - - - OCC::ProxyAuthDialog - - Proxy authentication required - Proxy-Authentifzierung erforderlich + + Failed to look up WebFinger instances: %1 + Fehler beim Auffinden von WebFinger-Instanzen: %1 - - Username: - Benutzername: + + You are already connected to an account with these credentials. + Sie sind bereits mit einem Konto mit diesen Anmeldedaten verbunden. - - Proxy: - Proxy: + + Unable to retrieve capabilities from server. + Die Capabilities konnten nicht vom Server abgerufen werden. - - The proxy server needs a username and password. - Der Proxyserver benötigt Benutzername und Passwort + + The server is not supported by this client. + Dieser Client unterstützt diesen Server nicht. - - Password: - Passwort: + + Error while trying to log in to OAuth2-enabled server. %1 + Fehler beim Versuch sich bei einem OAuth2-fähigen Server anzumelden. %1 - - TextLabel - TextLabel + + Unable to open browser with provided URL. %1 + Der Browser konnte die angegebene URL nicht öffnen. %1 - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Zu synchronisierende Elemente auswählen + + OAuth2 authentication requires a secured connection. %1 + OAuth2 Anmeldung benötigt eine gesicherte Verbindung. %1 - - - OCC::SelectiveSyncWidget - - Loading ... - Laden… + + Authorization server unreachable. %1 + Autorisierungsserver nicht erreichbar. %1 + + + OCC::OCUpdater - - Deselect remote folders you do not wish to synchronize. - Entfernte Ordner abwählen, die nicht synchronisiert werden sollen. + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Aktualisierung kann nicht heruntergeladen werden. Bitte klicken Sie <a href='%1'>hier</a>, um die Aktualisierung manuell herunterzuladen. - - Name - Name + + Could not check for new updates. + Auf neue Aktualisierungen kann nicht geprüft werden. - - Size - Größe + + Downloading %1. Please wait… + Herunterladen von %1. Bitte warten… - - - No subfolders currently on the server. - Aktuell befinden sich keine Unterordner auf dem Server. + + %1 installed successfully. Restart the application to finish installing the update. + %1 erfolgreich installiert. Anwendung bitte neu starten, um das Update zu beenden. - - An error occurred while loading the list of sub folders. - Ein Fehler ist aufgetreten, während die Liste der Unterordner geladen wurde. + + %1 available. Restart application to start the update. + %1 ist verfügbar. Anwendung zum Start der Aktualisierung neu starten. - - - OCC::ServerNotificationHandler - - Dismiss - Ausblenden + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Neue %1 verfügbar. Bitte clicken Sie <a href='%2'>hier</a>, um das neue AppImage manuell herunterzuladen. - - - OCC::SettingsDialog - - Settings - Einstellungen + + New %1 available. Please use the system's update tool to install it. + Aktualisierung von %1 verfügbar. Bitte nutzen Sie zur Installation das Systemaktualisierungstool. - - Activity - Aktivität + + Checking update server… + Überprüfung des Update-Servers… - - Add account - Konto hinzufügen + + Update status is unknown: Did not check for new updates. + Aktualisierungsstatus unbekannt: Auf neue Aktualisierungen konnte nicht geprüft werden. - - - Quit %1 - %1 beenden + + No updates available. Your installation is at the latest version. + Keine Aktualisierungen verfügbar. Ihre Installation ist die aktuelle Version. + + + OCC::OwncloudPropagator - - Are you sure you want to quit %1? - Sind Sie sicher, dass sie %1 beenden möchten? + + File %1 is currently in use + Datei %1 wird gerade benutzt - - Account - Benutzerkonto + + vfs instance is not available. + Die VFS-Instanz ist nicht verfügbar. + + + OCC::ProcessDirectoryJob - - MainWindow - Hauptfenster + + Symbolic links are not supported in syncing. + Symbolische Verknüpfungen werden bei der Synchronisation nicht unterstützt. - - Hide - Ausblenden + + File is listed on the ignore list. + Die Datei ist in der Ignorierliste aufgeführt. - - - OCC::ShareDialog - - TextLabel - TextLabel + + File names ending with a period are not supported on this file system. + Dateinamen enden mit einem Punkt. Das wird in diesem Dateisystem nicht unterstützt. - - share label - Freigabe-Label + + File names containing the character '%1' are not supported on this file system. + Dateinamen beinhalten das Zeichen '%1'. Das wird in diesem Dateisystems nicht unterstützt. - - Dialog - Dialog + + File name contains at least one invalid character + Der Dateiname enthält mindestens ein ungültiges Zeichen. - - ownCloud Path: - ownCloud-Pfad: + + The file name is a reserved name on this file system. + Der Dateiname ist ein reservierter Name in diesem Dateisystem. - - %1 Sharing - %1-Freigabe + + Filename contains trailing spaces. + Der Dateiname endet mit Leerzeichen. - - %1 - %1 + + Filename is too long. + Der Dateiname ist zu lang. - - Folder: %2 - Ordner: %2 + + File/Folder is ignored because it's hidden. + Datei/Ordner wird ignoriert, da sie versteckt sind. - - The server does not allow sharing - Der Server erlaubt das teilen nicht + + Stat failed. + "Stat" fehlgeschlagen. - - Retrieving maximum possible sharing permissions from server... - Abrufen der maximal möglichen Freigabeberechtigungen vom Server... + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Konflikt: Die Serverversion wurde heruntergeladen, die lokale Kopie wurde umbenannt und nicht hochgeladen. - - The file can not be shared because it was shared without sharing permission. - Die Datei kann nicht geteilt werden, weil sie ohne erneute Teilungs-Berechtigung für Sie geteilt wurde. + + The filename cannot be encoded on your file system. + Der Dateiname kann auf Ihrem Dateisystem nicht kodiert werden. - - Users and Groups - Nutzer und Gruppen + + The filename is blacklisted on the server. + Der Dateiname ist auf der schwarzen Liste des Servers. - - Public Links - Öffentliche Links + + + + + the VFS instance is missing. Unable to continue. + Die VFS-Instanz fehlt. Der Vorgang kann nicht fortgesetzt werden. - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Teile NewDocument.odt + + server reported no %1 + Der Server meldet kein %1 - - Upload only (File Drop) - Nur hochladen (Datei hineinziehen) + + + account was deleted. Unable to continue + Konto wurde gelöscht. Vorgang kann nicht fortgesetzt werden. - - Receive files from others without revealing the contents of the folder. - Von anderen Dateien empfangen, ohne den Inhalt des Ordners preiszugeben. + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: Selektive Synchronisierung wurde ignoriert, da der Pfad dafür abgewählt ist. - - Download / View / Edit - Herunterladen / Ansehen / Bearbeiten + + Moved to invalid target, restoring + An einen ungültigen Zielort verschoben, Wiederherstellung - - Name: - Name: + + Not allowed to remove, restoring + Das Löschen ist nicht erlaubt, Wiederherstellung - - Enter name of new share... - Geben Sie den Namen der neuen Freigabe ein ... + + Error while reading the database. + Beim Lesen der Datenbank ist ein Fehler aufgetreten. - - &Create - Erstellen + + account was deleted. Unable to continue. + Das Konto wurde gelöscht. Es kann nicht fortgefahren werden. - - TextLabel - TextLabel + + Not allowed because you don't have permission to add subfolders to that folder + Nicht erlaubt, da Sie keine Rechte zur Erstellung von Unterordnern haben - - Set &password - &password festlegen + + Error while doing a rename, unhandled status code: %1 + Fehler während des Umbenennes, nicht behandelter Status Code: %1 - - Set &expiration date - Ablaufdatum setzen + + Not allowed because you don't have permission to add files in that folder + Nicht erlaubt, da Sie keine Rechte zum Hinzufügen von Dateien in diesen Ordner haben - - Set password - Passwort setzen + + Not allowed to upload this file because it is read-only on the server, restoring + Das Hochladen dieser Datei ist nicht erlaubt, da die Datei auf dem Server schreibgeschützt ist, Wiederherstellung - - Recipients can view or download contents. - Empfänger können Inhalte sehen und herunterladen. + + This Space is currently unavailable + Dieser Space ist aktuell nicht erreichbar. - - Download / View - Herunterladen/Ansehen + + Server replied with an error while reading directory '%1' : %2 + Der Server meldet einen Fehler beim Lesen des Ordners '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - Empfänger können Inhalte sehen, herunterladen, ändern, löschen und hochladen. + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 - - Anyone with the link has access to the file/folder - Jeder mit dem Link hat Zugriff auf die Datei/Ordner + + %1 the folder is currently in use + Ordner %1 wird gerade benutzt + + + OCC::PropagateDownloadFile - - - P&assword protect - Passwort geschützt + + Failed to free up space, the file %1 is currently in use + Speicherplatz konnte nicht freigegeben werden, die Datei %1 wird derzeit verwendet. - - The file can not be shared because it was shared without sharing permission. - Die Datei kann nicht geteilt werden, weil sie ohne erneute Teilungs-Berechtigung für Sie geteilt wurde. + + + File %1 can not be downloaded because of a local file name clash with %2! + Datei %1 kann nicht heruntergeladen werden, da der lokale Dateiname mit %2 kollidiert. - - Link shares have been disabled - Das Teilen von Links wurde deaktiviert + + The download would reduce free local disk space below the limit + Das Herunterladen würde den lokalen freien Speicherplatz unter das Limit reduzieren. - - Create public link share - Öffentlichen Link zum Teilen erstellen + + Free space on disk is less than %1 + Der freie Speicher auf der Festplatte ist weniger als %1 - - - Delete - Löschen + + File was deleted from server + Die Datei wurde vom Server gelöscht. - - Open link in browser - Link im Browser öffnen + + Broken webserver returned empty content length for non-empty file on resume + Der Webserver hat eine leere Länge bezüglich des Inhalts für eine nicht leere Datei in der Wiederaufnahme zurückgegeben (defekt). - - Copy link to clipboard - Link in Zwischenablage kopieren + + The file could not be downloaded completely. + Die Datei konnte nicht vollständig herunter geladen werden. - - Copy link to clipboard (direct download) - Link in die Zwischenablage kopieren (direkter download) + + The downloaded file is empty despite the server announced it should have been %1. + Die heruntergeladene Datei ist leer, obwohl der Server %1 als Größe übermittelt hat. - - Send link by email - Link als E-Mail verschicken + + File %1 cannot be saved because of a local file name clash with %2! + Datei %1 kann nicht gespeichert werden, da der lokale Dateiname mit %2 kollidiert. - - Send link by email (direct download) - Link per Email senden (direkter download) + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 - - Create new... - Neu erstellen... + + + + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt. - - I shared %1 with you - Ich habe %1 mit Ihnen geteilt + + + File has changed since discovery + Datei ist seit der Entdeckung geändert worden. + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - Löschung des öffentlichen Links bestätigen + + ; Restoration Failed: %1 + ; Wiederherstellung fehlgeschlagen: %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Möchte Sie wirklich den öffentlichen Link <i>%1 </i>löschen?<p>Hinweis: Dies kann nicht rückgängig gemacht werden.</p> + + could not delete file %1, error: %2 + Konnte Datei %1 nicht löschen. Fehler: %2 - - Cancel - Abbrechen + + Can not create local folder %1 because of a local file name clash with %2 + Der lokale Ordner %1 konnte aufgrund eines Namenskonflikts mit %2 nicht erstellt werden. - - - Public link - Öffentlicher Link + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 - - Delete link share - Öffentlichen Link löschen + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt. - - - Please Set Password - Bitte wählen Sie ein Passwort: + + could not create folder %1 + Ordner %1 konnte nicht angelegt werden. - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove + + + %1 failed with: %2 + %1 fehlgeschlagen mit: %2 + - - Share NewDocument.odt - Teile NewDocument.odt + + Could not remove %1 because of a local file name clash with %2! + %1 kann aufgrund eines Konflikts mit dem lokalen Dateinamen von %2 nicht gelöscht werden. - - Share with users or groups ... - Mit Benutzern oder Gruppen teilen… + + Could not move '%1' to the trash bin + Konnte '%1' nicht in den Papierkorb verschieben - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Sie können Personen direkt zu dieser Freigabe leiten, wenn Sie Ihnen diesen <a href="private link menu"><span style=" text-decoration: underline">privaten Link</span></a> geben.</p></body></html> + + + %1 the file is currently in use + Datei %1 wird gerade benutzt. + + + OCC::PropagateLocalRename - - The item is not shared with any users or groups - Das Element wird mit keinem Benutzer oder Gruppe geteilt + + File %1 can not be renamed to %2 because of a local file name clash + %1 kann aufgrund eines Konflikts mit dem lokalen Dateinamen nicht zu %2 umbenannt werden. - - Open link in browser - Link im Browser öffnen + + Could not rename %1 to %2, the file is currently in use + Konnte %1 nicht in %2 umbenennen, die Datei wird gerade benutzt. - - Copy link to clipboard - Link in Zwischenablage kopieren + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 - - Send link by email - Link als E-Mail verschicken + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt. - - No results for '%1' - Keine Ergebnisse für '%1' + + Failed to rename file + Umbenennen der Datei fehlgeschlagen - - I shared something with you - Ich habe etwas mit Ihnen geteilt + + Error setting pin state + Fehler beim Setzen des PIN-Zustandes - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Formular + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 204, gesendet wurde "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - TextLabel + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 201, gesendet wurde "%1 %2". - - can edit - kann bearbeiten + + Error writing metadata to the database: %1 + Fehler beim Schreiben der Metadaten in die Datenbank: %1 - - can share - kann teilen + + Setting file status failed due to file lock + Das Festlegen des Dateistatus ist aufgrund einer Dateisperre fehlgeschlagen. + + + OCC::PropagateRemoteMove - - ... - ... + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 201, gesendet wurde "%1 %2". - - create - erstellen + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 - - change - Ändern + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt. - - delete - löschen + + Error writing metadata to the database + Fehler beim Schreiben der Metadaten in die Datenbank - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Error setting pin state + Fehler beim Setzen des PIN-Zustandes - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Via %1 teilen + + Could not update file : %1 + Die Datei konnte nicht aktualisiert werden: %1 - - Context menu share - Teilen über das Kontextmenü + + The file %1 is currently in use + Die Datei %1 wird gerade benutzt. + + + OCC::PropagateUploadCommon - - Context menu share %1 - Freigabe %1 über das Kontextmenü + + Local file changed during sync. It will be resumed. + Lokale Datei während der Synchronisierung geändert. Der Synchronisationsvorgang wird wiederholt. - - Confirm deletion - Löschung bestätigen + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + Die Datei %1 kann nicht hochgeladen werden, da eine andere Datei mit dem selben Namen, nur unterschiedlicher Großschreibung, existiert - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Möchten Sie den Ordner <i>%1</i> und alle seine Inhalte dauerhaft löschen? + + + Upload of %1 exceeds the quota for the folder + Das Hochladen von %1 überschreitet das Speicherkontingent des Ordners - - Do you want to delete the file <i>%1</i> permanently? - Möchten Sie die Datei <i>%1</i> dauerhaft löschen? + + + %1 the file is currently in use + Datei %1 wird gerade benutzt - - Select new location... - Neuen Ort wählen... + + File Removed + Datei entfernt - - Error - Fehler + + The server did not provide the file permissions + Der Server hat die Dateiberechtigungen nicht bereitgestellt - - Moving file failed: - -%1 - Verschieben der Datei fehlgeschlagen: - -%1 + + Error updating metadata: %1 + Fehler beim Aktualisieren von Metadaten: %1 + + + OCC::PropagateUploadFile - - I shared something with you - Ich habe etwas mit Ihnen geteilt + + %1 the file is currently in use + Datei %1 wird gerade benutzt - - - Share... - Teilen… + + The server did ask for a removed legacy feature(polling) + Der Server hat nach einer nicht mehr benutzen Funktion gefragt (Polling). - - - Copy private link to clipboard - Privater Link in die Zwischenablage kopiert + + The local file was removed during sync. + Die lokale Datei wurde während der Synchronisation gelöscht. - - Send private link by email... - Sende privaten Link per Email... + + The server did not acknowledge the file upload. (No e-tag was present) + Der Server hat den Datei-Upload nicht bestätigt. (Es war kein E-Tag vorhanden.) + + + OCC::PropagateUploadFileTUS - - Resharing this file is not allowed - Weiter-Teilen dieser Datei ist nicht erlaubt. + + %1 the file is currently in use + Datei %1 wird gerade benutzt. - - Resharing this folder is not allowed - Weiterteilen des Ordners ist nicht erlaubt + + Checksum computation failed + Die Generierung der Checksumme ist fehlgeschlagen - - Create and copy public link to clipboard - Neuen öffentlichen Link erstellen und in die Zwischenablage kopieren + + The local file was removed during sync. + Die lokale Datei wurde während der Synchronisation gelöscht. + + + OCC::ProtocolItemModel - - Copy public link to clipboard - Öffentlichen Link in die Zwischenablage kopieren + + Time + Zeit - - Open in browser - Im Browser öffnen + + File + Datei - - Show file versions in browser - Zeige Dateiversionen im Browser + + Folder + Ordner - - Rename... - Umbenennen... + + Issues + Probleme - - Rename and upload... - Umbenennen und hochladen... + + Action + Aktion - - Move and rename... - Verschieben und umbenennen... + + Size + Größe - - Move, rename and upload... - Verschieben, umbenennen und hochladen... + + Account + Benutzerkonto - - Delete local changes - Lokale Änderungen löschen + + Status + Status + + + OCC::RequestAuthenticationController - - Move and upload... - Verschieben und hochladen... + + IdP is unreachable. Contact your system administrator or try again later. + IdP is unreachable. Contact your system administrator or try again later.Der Identitätsanbieter (IdP) ist nicht erreichbar. Wenden Sie sich an Ihren Systemadministrator oder versuchen Sie es später erneut. - - Delete - Löschen + + Authentication failed. + Authentifizierung fehlgeschlagen. - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags - Fehler bim auslesen von Tags + + Application Name Logo %1 + Anwendungsname Logo %1 - - Backup of %1 started - Backup von %1 gestartet + + Sign in required + Anmeldung erforderlich - - Failed to find %1 - Konnte %1 nicht finden + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + Möglicherweise wurden Sie aufgrund eines Serverproblems oder einer Zeitüberschreitung automatisch getrennt. Bitte melden Sie sich erneut an. - - Local path must be a an absolute path - Der lokale Pfad muss ein absoluter Pfad sein + + Sign in URL + Anmelde-URL - - Failed to create temporary database - Fehler beim Erstellen der temporären Datenbank + + To copy the sign in URL to the clipboard, use the copy button + Um die Anmelde-URL in die Zwischenablage zu kopieren, verwenden Sie die Schaltfläche „Kopieren“. - - Backup of %1 succeeded - Backup von %1 war erfolgreich + + Copy URL to sign in + URL zum Anmelden kopieren - - Failed to set success tag - Fehler beim Setzen des Success-Tags + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo - - Failed to create backup: %1 - Fehler beim Erstellen des Backups: %1 + + Stay logged out + Abgemeldet bleiben - - Failed to file id tags - Fehler beim Setzen des ID-Tags + + Sign in + Anmelden - - Backup of %1 failed with: %2 - Backup von %1 fehlgeschlagen mit: %2 + + URL copied + URL kopiert - - %1 backup - %1 Backup + + Copy URL + URL kopieren - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Zertifikatdetails</h3> - - - - Common Name (CN): - Gemeinsamer Name (CN): - - - - Subject Alternative Names: - Subject Alternative Names: - - - - Organization (O): - Organisation (O): - - - - Organizational Unit (OU): - Organisationseinheit (OU): - + OCC::ResolveUrlAdapter - - State/Province: - Staat/Provinz: + + SSL failure when connecting to server at %1 + SSL-Fehler beim Verbindungsaufbau zum Server unter %1 - - Country: - Land: + + Could not detect compatible server at %1 + Ein kompatibler Server konnte unter %1 nicht gefunden werden. - - Serial: - Seriennummer: + + Resolved url is invalid %1 + Die aufgelöste URL ist ungültig %1 - - <h3>Issuer</h3> - <h3>Aussteller</h3> + + Rejected redirect from %1 to %2 + Die Umleitung von %1 nach %2 wurde abgelehnt + + + OCC::SelectiveSyncWidget - - Issuer: - Aussteller: + + Loading … + Laden… - - Issued on: - Ausgestellt am: + + Deselect remote folders you do not wish to synchronize. + Entfernte Ordner abwählen, die nicht synchronisiert werden sollen. - - Expires on: - Ablaufdatum: + + Name + Name - - <h3>Fingerprints</h3> - <h3>Fingerabdrücke</h3> + + Size + Größe - - SHA-256: - SHA-256: + + + Currently there are no subfolders on the server. + Derzeit befinden sich keine Unterordner auf dem Server. - - SHA-1: - SHA-1: + + An error occurred while loading the list of subfolders. + Beim Laden der Liste der Unterordner ist ein Fehler aufgetreten. + + + OCC::SettingsView - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Hinweis:</b> Dieses Zertifikat wurde manuell bestätigt</p> + + Form + Formular - - %1 (self-signed) - %1 (selbst signiert) + + General Settings + Allgemeine Einstellungen - - %1 - %1 + + Use Monochrome Icons in the system tray + Verwenden Sie monochrome Symbole in der Taskleiste - - This connection is encrypted using %1 bit %2. - - Diese Verbindung ist verschlüsselt mit %1 Bit %2. - + + Show Desktop Notifications + Zeige Desktop-Benachrichtigungen - - Server version: %1 - Serverversion: %1 + + Start on Login + Beim Anmeldung starten - - No support for SSL session tickets/identifiers - Keine Unterstützung für SSL session tickets + + Language + Sprache - - Certificate information: - Zertifikatsinformation: + + Language selector + Sprachauswahl - - The connection is not secure - Die Verbindung ist nicht sicher + + Advanced + Erweitert - - This connection is NOT secure as it is not encrypted. - - Diese Verbindung ist NICHT sicher, da diese nicht verschlüsselt ist. - + + Sync hidden files + Synchronisiere versteckte Dateien - - - OCC::SslErrorDialog - - Form - Formular + + Show crash reporter + Crash-Reporter anzeigen - - Trust this certificate anyway - Diesem Zertifikat trotzdem vertrauen + + Move remotely deleted files to the local trash bin instead of deleting them + Auf dem Server gelöschte Dateien in den lokalen Papierkorb verschieben statt sie zu löschen - - Untrusted Certificate - Nicht vertrauenswürdiges Zertifikat + + Edit Ignored Files + Ignorierte Dateien bearbeiten - - Cannot connect securely to <i>%1</i>: - Kann keine sichere Verbindung zu <i>%1</i> herstellen: + + Log Settings + Einstellungen für Logging - - with Certificate %1 - mit Zertifikat %1 + + Network + Netzwerk - - - - &lt;not specified&gt; - &lt;nicht angegeben&gt; + + Warning + Warnung - - - Organization: %1 - Organisation: %1 + + Language changes require a restart of this application to take effect. + Änderungen der Sprache werden erst nach Neustart des Clients wirksam. - - - Unit: %1 - Einheit: %1 + + You cannot disable autostart because system-wide autostart is enabled. + Autostart kann nicht abgeschaltet werden, da Autostart als Systemeinstellung aktiviert ist. - - - Country: %1 - Land: %1 + + (use default) + (benutze den Standard) - - Fingerprint (MD5): <tt>%1</tt> - Fingerabdruck (MD5): <tt>%1</tt> + + unknown (%1) + Unbekannt (%1) + + + OCC::SocketApi - - Fingerprint (SHA1): <tt>%1</tt> - Fingerabdruck (SHA1): <tt>%1</tt> + + Share with %1 + parameter is the application name + Via %1 teilen - - Effective Date: %1 - Aktuelles Datum: %1 + + Confirm deletion + Löschung bestätigen - - Expiration Date: %1 - Ablaufdatum: %1 + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Soll der Ordner <i>%1</i> mit gesamtem Inhalt dauerhaft gelöscht werden? - - Issuer: %1 - Aussteller: %1 + + Do you want to delete the file <i>%1</i> permanently? + Soll die Datei <i>%1</i> dauerhaft gelöscht werden? - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - %1 (Übersprungen aufgrund des früheren Fehlers, erneuter Versuch in %2) + + Error + Fehler - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Nur %1 sind verfügbar. Zum Beginnen werden mindestens %2 benötigt. + + Moving file failed: + +%1 + Verschieben der Datei fehlgeschlagen: + +%1 - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Öffnen oder Erstellen der Sync-Datenbank nicht möglich. Bitte sicherstellen, dass Schreibrechte für den zu synchronisierenden Ordner existieren. + + I shared something with you + Ich habe etwas mit Ihnen geteilt. - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Der freie Speicherplatz wird knapp: Downloads, die den freien Speicher unter %1 senken, wurden ausgelassen. + + Select new location… + Neuen Ort wählen… - - There is insufficient space available on the server for some uploads. - Auf dem Server ist für einige Dateien zum Hochladen nicht genug Platz. + + + Share… + Teilen… - - Unresolved conflict. - Ungelöster Konflikt. + + + Copy private link to clipboard + Privaten Link in die Zwischenablage kopieren - - Using virtual files with suffix, but suffix is not set - Virtuelle Dateien mit Dateiendung werden genutzt, aber die Dateiendung ist nicht definiert + + Resharing this file is not allowed + Weiteres Teilen dieser Datei ist nicht erlaubt. - - Unable to read the blacklist from the local database - Fehler beim Einlesen der Blacklist aus der lokalen Datenbank + + Resharing this folder is not allowed + Weiteres Teilen des Ordners ist nicht erlaubt. - - Unable to read from the sync journal. - Fehler beim Einlesen des Synchronisierungsprotokolls. + + Open in %1 + In %1 öffnen - - Cannot open the sync journal - Synchronisationsbericht kann nicht geöffnet werden + + Show file versions in web browser + Dateiversion im Webbrowser anzeigen. - - Aborted - Abgebrochen + + Rename… + Umbenennen… - - - OCC::SyncJournalDb - - Failed to connect database. - + + Rename and upload… + Umbenennen und hochladen… - - - OCC::Systray - - %1: %2 - %1: %2 + + + Move and rename… + Verschieben und umbenennen… - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>Version %1. Weitere Informationen unter <a href="%2">https://%3</a></p><p>Für bekannte Fehler und die Hilfe, besuchen Sie bitte: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Von Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth und anderen.</small></p><p>Copyright ownCloud GmbH</p><p>Lizenziert unter den Bedingungen der GNU General Public License (GPL) Version 2.0.<br/>%5 und das %5 Logo sind eingetragene Warenzeichen von %4 in den USA, anderen Ländern, oder beidem.</p> + + Move, rename and upload… + Verschieben, umbenennen und hochladen… - - - OCC::ValidateChecksumHeader - - The checksum header is malformed. - Der Prüfsummen-Header hat ein fehlerhaftes Format. + + Move and upload… + Verschieben und hochladen… - - The checksum header contained an unknown checksum type '%1' - Die Prüfsummen-Kopfzeile enthielt den unbekannten Prüfsummentypen '%1'. + + Delete local changes + Lokale Änderungen löschen - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - Die heruntergeladene Datei entspricht nicht der Prüfsumme, das Herunterladen wird wiederaufgenommen. '%1' != '%2' + + Delete + Löschen - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - Das Feature Virtuelle Dateien unterstützt keine Laufwerke als Synchronisationspunkt - + + OCC::SpacesPage - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - Das Feature Virtuelle Dateien benötigt ein NTFS Dateisystem, %1 benutzt %2 + + Select a folder to sync it to your computer. + Wählen Sie einen Ordner, um diesen mit Ihren Computer zu synchronisieren. - - The Virtual filesystem feature is not supported on network drives - Das Feature Virtuelle Dateien unterstützt keine Netzwerk-Laufwerke + + Select a Space to sync it to your computer. + Wählen Sie einen Space, um diesen mit Ihren Computer zu synchronisieren. - OCC::ownCloudGui - - - Please sign in - Bitte melden Sie sich an - + OCC::SyncEngine - - Folder %1: %2 - Ordner %1: %2 + + %1 (skipped due to earlier error, trying again in %2) + %1 (Übersprungen aufgrund des früheren Fehlers, erneuter Versuch in %2) - - Unresolved %1 conflicts - + + Unable to sync: the local folder is missing. + Synchronisierung nicht möglich: Der lokale Ordner fehlt. - - Ignored errors %1 - + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Es stehen nur %1 zur Verfügung; es müssen mindestens %2 sein, um zu starten. - - There are no sync folders configured. - Es wurden keine Synchonisationsordner konfiguriert. + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Das Öffnen oder Erstellen der Synchronisations-Datenbank ist nicht möglich. Bitte stellen Sie sicher, dass Schreibrechte für den zu synchronisierenden Ordner existieren. - - Open in browser - Im Browser öffnen + + + + + Cannot open the sync journal. + Das Synchronisierungsprotokoll kann nicht geöffnet werden. - - - - Log in... - Einloggen... + + Aborted due to %1 + Abgebrochen aufgrund von %1 - - - - Log out - Abmelden + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Der freie Speicherplatz wird knapp: Downloads, die den freien Speicher unter %1 senken würden, wurden ausgelassen. - - Recent Changes - Letzte Änderungen + + There is insufficient space available on the server for some uploads. + Auf dem Server ist nicht genug Platz, um alle Dateien hochzuladen. - - Managed Folders: - Verwaltete Ordner: + + application exit + abort reason + Anwendung beendet - - Open folder '%1' - Ordner »%1« öffnen + + Unable to read the blacklist from the local database + Fehler beim Einlesen der schwarzen Liste aus der lokalen Datenbank - - Open %1 in browser - %1 im Browser öffnen + + Unable to read from the sync journal. + Fehler beim Einlesen des Synchronisierungsprotokolls. + + + OCC::SyncErrorWidget - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - Der Server mit Konto %1 läuft mit einer nicht unterstützten Version %2. Die Verwendung dieses Clienten mit nicht unterstützten Serverversionen ist ungetestet und möglicherweise gefährlich. Fortfahren auf eigenes Risiko. + + Issues table + Problemtabelle - - Unknown status - Unbekannter Status + + There were too many issues. Not all will be visible here. + Es gab zu viele Probleme. Nicht alle können hier dargestellt werden. - - Settings... - Einstellungen … + + There were conflicts. Check the documentation on how to resolve them. + Es bestehen Konflikte. Prüfen Sie die Dokumentation, wie diese zu lösen sind. - - Details... - Details … + + The file %1 was ignored as its name is reserved by %2 + Die Datei %1 wurde ignoriert, da ihr Name von %2 reserviert ist. - - Help - Hilfe + + Reset column sizes + Zurücksetzen der Spaltenbreiten - - Quit %1 - %1 beenden + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Es bestehen Konflikte. <a href="%1">Prüfen Sie die Dokumentation, wie diese zu lösen sind.</a> - - Disconnected from %1 - Von %1 getrennt + + Filter menu + Filtermenü - - Unsupported Server Version - nicht unterstützte Server Version + + Account + Konto - - Disconnected - Getrennt + + Filter + Filter + + + OCC::SyncJournalDb - - Disconnected from some accounts - Die Verbindungen zu einigen Konten wurde getrennt + + Failed to connect database. + Die Verbindung zur Datenbank konnte nicht hergestellt werden. + + + OCC::Theme - - Disconnected from accounts: - Verbindungen zu Konten wurde getrennt: + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Version %1. Für mehr Informationen gehen Sie auf <a href="%2">https://%3</a></p><p>Für bekannte Probleme und Hilfe, gehen Sie auf: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Verteilt durch %4 und lizensiert unter der GNU General Public License (GPL) Version 2.0.<br/>%5 und das %5 Logo sind registierte Handelsmarken von %4 in den Vereinigten Staaten, anderen Ländern, oder beiden.</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Account %1: %2 - Konto %1: %2 + + TLS Certificate Error + TLS-Zertifikatfehler - - Signed out - Abgemeldet + + Do you want to trust this certificate anyway? + Möchten Sie diesem Zertifikat trotzdem vertrauen? - - Account synchronization is disabled - Account Synchronisation ist deaktiviert + + Cannot connect securely to %1 + Keine sichere Verbindung zu %1 möglich - - Synchronization is paused - Synchronisation pausiert + + &lt;not specified&gt; + &lt;nicht angegeben&gt; - - Up to date (%1) - + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>mit Zertifikat %1</h3><div id="ccert"><p>Organisation: %2<br/>Einheit: %3<br/>Land: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Ausstellungsdatum: %8Ablaufdatum: %9</div><h3>Aussteller: %10</h3><div id="issuer"><p>Organisation: %11<br/>Einheit: %12<br/>Land: %13</p></div></div> + + + OCC::TrayMenuController - - No sync folders configured - Keine Ordner zur Synchronisation konfiguriert + + Unsupported Server Version + nicht unterstützte Server Version - - Unpause all folders - Setze alle Ordner fort + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + Der Server für Konto %1 benutzt eine nicht unterstützte Version %2. Die Client-Software funktioniert möglicherweise mit nicht unterstützten Versionen, dies ist jedoch nicht getestet. Sie fahren auf eigenes Risiko fort. - - Pause all folders - Stoppe alle Ordner + + Show %1 + Zeige %1 - - Unpause all synchronization - Setze gesamte Synchronisation fort + + Help + Hilfe - - Unpause synchronization - Setze Synchronisation fort + + About %1 + Über %1 - - Pause all synchronization - Stoppe gesamte Synchronisation + + Quit %1 + %1 beenden + + + OCC::UpdateDownloadedWidget - - Pause synchronization - Stoppe Synchronisation + + Restart later + Später neu starten - - Log out of all accounts - Bei allen Konten abmelden + + Restart now + Jetzt neu starten + + + OCC::UpdaterScheduler - - Log in to all accounts... - Bei allen Konten anmelden... + + Update available + Update verfügbar + + + OCC::UrlPageController - - New account... - Neues Konto... + + Your web browser will be opened to complete sign in. + Ihr Webbrowser wird geöffnet, um die Anmeldung abzuschließen. - - About %1 - Über %1 + + %1 logo + %1 Logo - - No items synced recently - Keine kürzlich synchronisierten Elemente + + Welcome to %1 + Willkommen bei %1 - - Checking for changes in remote '%1' - Änderungen der entfernten Ressource '%1' werden geprüft + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Geben Sie Ihre Serveradresse ein, um zu beginnen. Ihr Webbrowser wird geöffnet, um die Anmeldung abzuschließen. - - Checking for changes in local '%1' - Änderungen der lokalen Ressource '%1' werden geprüft + + Server address field + Serveradressfeld - - Syncing %1 of %2 (%3 left) - Synchronisiere %1 von %2 (%3 übrig) + + Enter your server address here + Geben Sie hier Ihre Serveradresse ein. - - Syncing %1 of %2 - Synchronisiere %1 von %2 + + Additional logo defined by the organization + Zusätzliches, von der Organisation definiertes Logo - - Syncing %1 (%2 left) - Synchronisiere %1 (%2 übrig) + + Invalid server URL: %1 + Ungültige Server-URL: %1 - - Syncing %1 - Synchronisiere %1 + + Invalid URL scheme. Only https is accepted. + Ungültiges URL-Schema. Nur https wird akzeptiert. - - %1 (%2, %3) - %1 (%2, %3) + + The requested URL failed with query value: %1 + Die angeforderte URL ist mit folgendem Abfragewert fehlgeschlagen: %1 - OwncloudAdvancedSetupPage - - - Form - Formular - + OCC::UserInfoAdapter - - - TextLabel - TextLabel + + Network request to collect user info failed + Netzwerkanfrage zum Abrufen von Benutzerinformationen fehlgeschlagen - - Server - Server + + Unable to retrieve user info: invalid credentials. + Benutzerinformationen konnten nicht abgerufen werden: Ungültige Anmeldeinformationen. - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Wenn diese Option gesetzt ist, werden bestehende Inhalte im lokalen Ordner gelöscht, um eine saubere Synchronisation nur der Serverdaten zu ermöglichen.</p><p>Wählen Sie diese Option nicht, wenn die lokale Inhalte auf den Server übertragen werden sollen.</p></body></html> + + Unexpected network response when retrieving user info. + Unerwartete Netzwerkantwort beim Abrufen von Benutzerinformationen. - - Ask for confirmation before synchroni&zing folders larger than - Nachfragen, bei Synchronisation von Ordnern grösser als + + Retrieving user info failed with JSON error. + Der Abruf der Benutzerinformationen ist mit einem JSON-Fehler fehlgeschlagen. + + + OCC::ValidateChecksumHeader - - S&ynchronize everything from server - Alle Daten vom Server s&ynchronisieren + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + Die heruntergeladene Datei entspricht nicht der Prüfsumme. Das Herunterladen wird wiederaufgenommen. '%1' != '%2' + + + OCC::Vfs - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + The Virtual filesystem feature does not support a drive as sync root + Virtuelle Dateien funktionieren nicht mit einem Laufwerk als Synchronisationspunkt. - - Ask for confirmation before synchronizing e&xternal storages - Nachfragen, bei Synchronisation von externen Speichern + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + Virtuelle Dateien benötigt ein NTFS Dateisystem. %1 benutzt %2. - - Choose what to sync - Zu synchronisierende Elemente auswählen + + The Virtual filesystem feature is not supported on network drives + Virtuelle Dateien funktionieren nicht mit Netzwerk-Laufwerken. + + + OCC::VfsWinPrivate - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Wenn diese Option angewählt ist, wird der Installationsassistent, ohne irgendetwas zu synchronisieren, beendet. Sie können den &quot;Synchronisationsverzeichnis&quot; Button in den Kontoeinstellungen nutzen, um das Ordnerpaar zwischen lokal und entfernt festzulegen, die Sie synchronisieren möchten</p></body></html> + + + %1 - %2 + %1 - %2 - - Manually create folder sync connections - Manuell Synchronisationsverzeichnis erstellen + + Could not find StorageProviderState property %1 + Die StorageProviderState-Eigenschaft %1 konnte nicht gefunden werden. - - &Local Folder - &Lokaler Ordner + + Could not find StorageFolder for %1 error: %2 + Speicherordner für %1 konnte nicht gefunden werden: %2 - - pbSelectLocalFolder - pbSelectLocalFolder + + Could not retrieve StorageFolder for %1 %2 (0x%3) + Der Speicherordner für %1 %2 konnte nicht abgerufen werden (0x%3) - - &Keep local data - &Lokale Daten behalten + + The folder is used by a different client: %1 + Der Ordner wird von einem anderen Client verwendet: %1 - - Start a &clean sync (Crreate a backup and erases the local folder!) - + + Error registering StorageProvider for %1: %2 (0x%3) + Fehler beim Registrieren des StorageProviders für %1: %2 (0x%3) - - - OwncloudHttpCredsPage - - Form - Formular + + Unable to connect sync root: %1 error: %2 + Verbindung zum Sync-Root konnte nicht hergestellt werden: %1 Fehler: %2 - - &Username - &Benutzername + + %1 is managed by another sync client + %1 wird von einem anderen Synchronisierungsclient verwaltet. - - &Password - &Passwort + + Downgrading the folder from %1 to %2 is not supported + Das Herabstufen des Ordners von %1 auf %2 wird nicht unterstützt. - OwncloudOAuthCredsPage + OCC::WebFingerLookupAdapter - - Form - Formular + + Failed to retrieve user info + Die Benutzerinformationen konnten nicht abgerufen werden. + + + + WebFinger lookup returned no links + Die WebFinger-Suche ergab keine Ergebnisse. + + + OCC::WindowsUpdater - - Please switch to your browser to proceed. - Bitte wechseln Sie zum Fortfahren in den Browser. + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Eine neue Version des %1 Clients ist verfügbar.</p><p><b>%2</b> steht zum Herunterladen bereit. Die installierte Version ist %3.</p> - - An error occurred while connecting. Please try again. - Fehler beim Aufbau der Verbindung. Bitte nochmal versuchen. + + Skip this version + Diese Version überspringen - - Re-open Browser - Browser erneut öffnen + + Update Failed + Update fehlgeschlagen - - - OwncloudSetupPage - - Form - Formular + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Eine neue Version des %1 Clients ist verfügbar, der Aktualisierungs-Prozess ist aber fehlgeschlagen.</p><p><b>%2</b> wurde heruntergeladen. Die installierte Version ist %3.</p> - - Ser&ver Address - Ser&veradresse + + Ask again later + Später noch einmal fragen - - https://... - https://… + + Restart and update + Neu starten und aktualisieren - - Error Label - Fehler-Label + + Update manually + Manuell aktualisieren QObject - + in the future in der Zukunft - + %n day(s) ago - vor %n Tage(n)vor %n Tage(n) + Vor %n TagVor %n Tagen - + %n hour(s) ago - vor %n Stunde(n)vor %n Stunde(n) + Vor %n StundeVor %n Stunden - + now jetzt - - Less than a minute ago + + less than a minute ago vor weniger als einer Minute - + %n minute(s) ago - vor %n Minute(n)vor %n Minute(n) + Vor %n MinuteVor %n Minuten - - Some time ago - vor einiger Zeit - - - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 + + SpacesBrowser + + + Form + Formular + + + + SpacesPage + + + Spaces list + Liste der Spaces + + + + SpacesView + + + Spaces + Spaces + + SyncFileItem::Status - + Undefined - + Nicht definiert - + Fatal Error Schwerer Fehler - - + + Error Fehler - + Info Info - + Success Erfolg - + Conflict Konflikt - + File Ignored - + Datei ignoriert - + Restored - + Wiederhergestellt - + Blacklisted Zur schwarzen Liste hinzugefügt - + Excluded - - - - - Utility - - - %L1 GB - %L1 GB + Ausgeschlossen - - %L1 MB - %L1 MB + + Message + Mitteilung - - %L1 KB - %L1 KB + + Filename Reserved + Dateiname reserviert + + + UpdateDownloadedWidget - - %L1 B - %L1 B + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Neustart erforderlich</span></p></body></html> + + + Utility - + %n year(s) %n Jahr%n Jahre - + %n month(s) %n Monat%n Monate - + %n day(s) %n Tag%n Tage - + %n hour(s) %n Stunde%n Stunden - + %n minute(s) %n Minute%n Minuten - + %n second(s) %n Sekunde%n Sekunden - + %1 %2 %1 %2 + + + Shutting down %1 + Herunterfahren von %1 + - main.cpp + account loading - - System Tray not available - Die Taskleiste ist nicht verfügbar. + + Error accessing the configuration file + Fehler beim Zugriff auf die Konfigurationsdatei - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 benötigt ein funktionierendes Benachrichtigungsfeld. Falls Sie XFCE einsetzen, dann folgen Sie bitte <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">diesen Anweisungen</a>. Andernfalls installieren Sie bitte ein Benachrichtigungsfeld wie zum Beispiel „Trayer“ und versuchen es nochmal. + + There was an error while accessing the configuration file at %1. + Es ist ein Fehler beim Zugriff auf die Konfigurationsdatei unter %1 aufgetreten. ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Qt Bibliotheken %3, %4%7Verwendete Erweiterung für virtuelle Dateien: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - %1 (Mit Qt %1 gebaut) + + %1 (Built against Qt %2) + %1 (Gebaut mit Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Immer lokal verfügbar + + + + Currently available locally + Aktuell lokal verfügbar + + + + Some available online only + Einige nur online verfügbar + + + + Available online only + Nur Online verfügbar + + progress @@ -4720,30 +3563,30 @@ Dies ist ein neuer, experimenteller Modus. Wenn sie sich dafür entscheiden, ber Uploaded Hochgeladen - - - Server version downloaded, copied changed local file into conflict file - Serverversion heruntergeladen. Die bearbeitete lokale Datei wurde in eine Konfliktdatei kopiert - Deleted Gelöscht - - Moved to %1 - Verschoben nach %1 - - - + Ignored Ignoriert - - Filesystem access error - Zugriffsfehler im Dateisystem + + Server version downloaded, local copy was backed up as conflict file + Serverversion heruntergeladen, lokale Kopie wurde als Konfliktdatei gesichert. + + + + Renamed %1 to %2 + %1 wurde in %2 umbenannt + + + + Moved %1 to %2 + %1 wurde nach %2 verschoben @@ -4756,153 +3599,86 @@ Dies ist ein neuer, experimenteller Modus. Wenn sie sich dafür entscheiden, ber Lokale Metadaten aktualisiert - - + + Unknown Unbekannt - + downloading Herunterladen - + uploading Lade hoch - + deleting Lösche - + moving Verschiebe - + ignoring ignoriere - - + error Fehler - + updating local metadata aktualisiere lokale Metadaten - - theme - - - Status undefined - Status nicht definiert - - - - Waiting to start sync - Warte, um mit der Synchronistation zu beginnen - - - - Sync is running - Synchronisation läuft - - - - Sync Success - Synchronisation erfolgreich - - - - Sync Success, some files were ignored. - Synchronisation abgeschlossen, einige Dateien wurden ignoriert. - - - - Sync Error - Synchronisationsfehler - - - - Setup Error - Installationsfehler - - - - Preparing to sync - Synchronisation wird vorbereitet - - - - Aborting... - Abbruch … - - - - Sync is paused - Synchronisation wird angehalten - - utility - + Could not open browser Konnte Browser nicht öffnen - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? Die URL %1 konnte aufgrund eines Fehlers nicht aufgerufen werden. Ist vielleicht kein Standard-Browser konfiguriert? - + Could not open email client - Die E-Mail Anwendung konnte nicht geöffnet werden + Die E-Mail-Anwendung konnte nicht geöffnet werden. - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Es ist ein Fehler beim Öffnen einer E-Mail Anwendung zum Erstellen einer neuen Nachricht aufgetreten. Vielleicht ist keine E-Mail Anwendung konfiguriert? - - - - Always available locally - Immer lokal verfügbar - - - - Some available online only - Nur einige Online verfügbar - - - - - Available online only - Nur Online verfügbar + Beim Öffnen einer E-Mail-Anwendung zum Erstellen einer neuen Nachricht ist ein Fehler aufgetreten. Vielleicht ist keine E-Mail-Anwendung konfiguriert? - - Currently available locally - Aktuell lokal verfügbar - - - + Make always available locally - Auf immer lokal verfügbar umschalten + Immer lokal verfügbar machen - + Free up local space Lokalen Speicherplatz freigeben + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Manche Einstellungen wurden in neueren Versionen dieses Clients konfiguriert und benutzen Features, die in dieser Version nicht verfügbar sind. + + \ No newline at end of file diff --git a/translations/client_de_CH.ts b/translations/client_de_CH.ts deleted file mode 100644 index 37338057617..00000000000 --- a/translations/client_de_CH.ts +++ /dev/null @@ -1,4904 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - Abbrechen - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - Schliessen - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - Im Papierkorb können keine Ordner erstellt werden - - - - - Could not move '%1' to '%2' - Konnte '%1' nicht nach '%2' verschieben - - - - Moving to the trash is not implemented on this platform - Das Verschieben in den Papierkorb wird auf dieser Plattform nicht unterstützt - - - - Error removing '%1': %2 - Fehler beim Entfernen von '%1': %2 - - - - Could not remove folder '%1' - Der Order '%1' konnte nicht gelöscht werden - - - - FolderWizardSourcePage - - - Form - Formular - - - - Pick a local folder on your computer to sync - Lokales Verzeichnis zum Synchronisieren auswählen - - - - &Choose... - Auswählen… - - - - FolderWizardTargetPage - - - Form - Formular - - - - Select a remote destination folder - Entfernten Zielordner auswählen - - - - Create Folder - Ordner erstellen - - - - Refresh - Aktualisieren - - - - Folders - Ordner - - - - TextLabel - TextLabel - - - - NotificationWidget - - - Form - Formular - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Text-Label - - - - OCC::AboutDialog - - - About - Über - - - - TextLabel - Text-Label - - - - Versions - Versionen - - - - About %1 - Über %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Zeitüberschreitung beim Verbindungsaufbau - - - - Unknown error: network reply was deleted - Unbekannter Fehler: Netzwerk-Antwort wurde gelöscht - - - - Server replied "%1 %2" to "%3 %4" - Server hat "%1 %2" auf "%3 %4" geantwortet - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - Formular - - - - Re-open Browser - Browser erneut öffnen - - - - ... - ... - - - - Storage space: ... - Speicherplatz: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Nicht markierte Ordner werden von Deinem lokalen Dateisystem <b>entfernt</b> und künftig nicht mehr synchronisiert - - - - Synchronize all - Alles synchronisieren - - - - Synchronize none - Nichts davon synchronisieren - - - - Apply manual changes - Manuelle Änderungen übernehmen - - - - Apply - Anwenden - - - - - - - Cancel - Abbrechen - - - - Connected with <server> as <user> - Verbunden mit <server> als <user> - - - - No account configured. - Kein Konto konfiguriert. - - - - Remove - Entfernen - - - - Account - Konto - - - - Choose what to sync - Zu synchronisierende Elemente auswählen - - - - Force sync now - Synchronisation jetzt erzwingen - - - - Restart sync - Synchronisation neu starten - - - - Remove folder sync connection - Synchronisations Verzeichnis löschen - - - - Folder creation failed - Erstellung des Ordners fehlgeschlagen - - - - <p>Could not create local folder <i>%1</i>. - <p>Der lokale Ordner <i>%1</i> konnte nicht angelegt werden. - - - - Confirm Folder Sync Connection Removal - Löschen der Ordner Sychronisation bestätigen - - - - Remove Folder Sync Connection - Synchronisations Verzeichnis löschen - - - - Disable virtual file support? - Deaktiviere Unterstützung für virtuelle Dateien? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - Diese Aktion wird die Unterstützung für virtuelle Dateien abschalten. Als Konsequenz werden Inhalte aus Ordnern heruntergeladen, die aktuell als 'nur online verfügbar' markiert sind. - -Das Feature "selective sync" wird wieder verfügbar. Diese Aktion bricht die aktuell laufende Synchronisation ab. - - - - Disable support - Unterstützung abschalten - - - - Sync Running - Synchronisation läuft - - - - The syncing operation is running.<br/>Do you want to terminate it? - Die Synchronisation läuft gerade.<br/>Willst Du sie beenden? - - - - %1 in use - %1 benutzt - - - - %1 as <i>%2</i> - %1 als <i>%2</i> - - - - Connected to %1. - Verbunden mit %1. - - - - Server %1 is temporarily unavailable. - Der Server %1 ist temporär nicht erreichbar. - - - - Server %1 is currently in maintenance mode. - Server %1 befindet sich im Wartungsmodus. - - - - Signed out from %1. - Von %1 ausgeloggt. - - - - Connecting to %1... - Verbinde mit %1... - - - - No connection to %1 at %2. - Keine Verbindung zu %1 auf %2. - - - - Log in - Anmelden - - - - There are folders that were not synchronized because they are too big: - Einige Verzeichnisse konnten nicht synchronisiert werden, da sie zu gross sind: - - - - There are folders that were not synchronized because they are external storages: - Es gibt Verzeichnisse, die nicht synchronisiert werden konnten, da sie externer Speicher sind: - - - - There are folders that were not synchronized because they are too big or external storages: - Es gibt Verzeichnisse, die nicht synchronisiert werden konnten, da sie zu gross oder externer Speicher sind: - - - - Confirm Account Removal - Nutzerkonto Löschen bestätigen - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Möchtest Du den Zugang zum Nutzerkonto <i>%1</i> wirklich löschen?</p><p><b>Hinweis:</b>Es werden <b>keine</b> lokalen Daten gelöscht.</p> - - - - Remove connection - Zugang löschen - - - - Open folder - Ordner öffnen - - - - - Log out - Abmelden - - - - Open local folder - Öffne lokalen Ordner - - - - Open folder in browser - Ordner im Browser öffnen - - - - Resume sync - Sync fortsetzen - - - - Pause sync - Sync pausieren - - - - Availability - Verfügbarkeit - - - - Disable virtual file support... - Deaktiviere Unterstützung für virtuelle Dateien ... - - - - Enable virtual file support%1... - Unterstützung für virtuelle Dateien aktivieren%1... - - - - (experimental) - (experimentell) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <P>Willst Du die Synchronisation des Verzeichnisses <i>%1</i> wirklich aufgeben?</p><p><b>Hinweis:</b> Diese Aktion wird <b>keine</b> Dateien löschen.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) von %2 verwendet. Einige Verzeichnisse, z. B. Netzwerklaufwerke oder geteilte Verzeichnisse, können abweichende Speicher-Kontingente haben. - - - - %1 of %2 in use - %1 von %2 belegt - - - - Currently there is no storage usage information available. - Derzeit sind keine Speichernutzungsinformationen verfügbar. - - - - The server version %1 is unsupported! Proceed at your own risk. - Die Server Version %1 wird nicht unterstützt. Auf eigenes Risiko weitermachen? - - - - Obtaining authorization from the browser. - Berechtigung vom Browser einholen. - - - - Server configuration error: %1 at %2. - Fehler in der Serverkonfiguration: %1 at %2. - - - - No %1 connection configured. - Keine %1-Verbindung konfiguriert. - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - - - - - Signed out - Abgemeldet - - - - Disconnected - Getrennt - - - - Connected - Verbunden - - - - Service unavailable - Service nicht erreichbar - - - - Maintenance mode - Wartungsmodus - - - - Network error - Netzwerkfehler - - - - Configuration error - Konfigurationsfehler - - - - Asking Credentials - Zugangsdaten werden abgefragt - - - - Unknown account state - Status des Kontos unbekannt - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 für %3 - - - - Activity - Aktivität - - - - Account - Nutzerkonto - - - - Time - Zeit - - - - Local path - - - - - OCC::ActivitySettings - - - - Server Activity - Server aktivität - - - - Sync Protocol - Synchronisationsprotokoll - - - - Not Synced - Nicht synchronisiert - - - - Not Synced (%1) - %1 is the number of not synced files. - nicht synchronisiert (%1) - - - - OCC::ActivityWidget - - - Form - Formular - - - - - - TextLabel - TextLabel - - - - Filter - Filter - - - - Server Activities - Server Aktivitäten - - - - Notifications - Benachrichtigungen - - - - %n notifications(s) for %1. - %n Benachrichtigung für %1.%n Benachrichtigungen für %1. - - - - %n notifications(s) for %1 and %2. - %1 Benachrichtigung für %1 und %2.%n Benachrichtigungen für %1 und %2. - - - - New notifications for %1, %2 and other accounts. - Neue Benachrichtigungen für %1, %2 und andere Konten. - - - - Open the activity view for details. - Öffne die Aktivitäts-Ansicht für Genaueres. - - - - Copy to clipboard - In Zwischenablage kopieren - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>Benutzerkonto %1 hat keine Aktivitäten aktiviert. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Fortsetzen bedeutet <b>das diese Einstellungen gelöscht werden</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Fortsetzen bedeutet <b>das diese Einstellungen ignoriert werden</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Einige Einstellungen wurden in neueren Versionen dieses Programms eingestellt und nutzen Funktionen, die in dieser Version nicht verfügbar sind. <br><br>%1<br><br> Die aktuelle Konfigurations-Datei wurde bereits an folgenden Ort gesichert: <i>%2</i>. - - - - Quit - Beenden - - - - Continue - Fortsetzen - - - - Error accessing the configuration file - Fehler beim Zugriff auf die Konfigurationsdatei - - - - There was an error while accessing the configuration file at %1. - Es ist ein Fehler beim Zugriff auf die Konfigurationsdatei unter %1 aufgetreten. - - - - Quit %1 - %1 beenden - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - Authentifizierung erforderlich - - - - Enter username and password for '%1' at %2. - Benutzernamen und Passwort für '%1' auf %2 eingeben. - - - - &User: - &Benutzer: - - - - &Password: - &Passwort: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Kein ownCloud-Konto konfiguriert - - - - The configured server for this client is too old - Der konfigurierte Server ist zu alt für diesen Client - - - - Please update to the latest server and restart the client. - Bitte aktualisiere auf die neueste Server-Version und starte den Client neu. - - - - Authentication error: Either username or password are wrong. - Authentifizierungsfehler: Entweder der Benutzername oder das Passwort sind falsch. - - - - timeout - Zeitüberschreitung - - - - The provided credentials are not correct - Die zur Verfügung gestellten Anmeldeinformationen sind nicht korrekt - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - Server Fehler: Die PROPFIND Antwort ist nicht XML formattiert! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - Fehler beim Öffnen des Ordners %1 - - - - Directory not accessible on client, permission denied - Der Ordner ist auf dem Client nicht erreichbar, Zugriff verweigert - - - - Directory not found: %1 - Ordner nicht gefunden: %1 - - - - Filename encoding is not valid - Dateikodierung ist ungültig - - - - Error while reading directory %1 - Fehler beim Lesen des Ordners %1 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - - - - - OCC::Folder - - - Local folder %1 does not exist. - Lokales Verzeichnis %1 existiert nicht - - - - %1 should be a folder but is not. - %1 sollte ein Verzeichnis sein, ist es aber nicht. - - - - %1 is not readable. - %1 ist nicht lesbar. - - - - %1 has been removed. - %1 names a file. - %1 wurde gelöscht. - - - - %1 has been updated. - %1 names a file. - %1 wurde aktualisiert. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 wurde in %2 umbenannt. - - - - %1 has been moved to %2. - %1 wurde in %2 verschoben. - - - - %1 and %n other file(s) have been removed. - %1 und %n andere Datei wurde gelöscht.%1 und %n andere Dateien wurden gelöscht. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - %1 und %n andere Dateien wurden hinzugefügt.%1 und %n andere Datei(en) wurden hinzugefügt. - - - - %1 has been added. - %1 names a file. - %1 wurde hinzugefügt. - - - - %1 and %n other file(s) have been updated. - %1 und %n andere Datei wurde aktualisiert.%1 und %n andere Dateien wurden aktualisiert. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 wurde in %2 umbenannt und %n andere Datei wurde umbenannt.%1 wurde in %2 umbenannt und %n andere Dateien wurden umbenannt. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 wurde in %2 verschoben und %n andere Datei wurde verschoben.%1 wurde in %2 verschoben und %n andere Dateien wurden verschoben. - - - - %1 has and %n other file(s) have sync conflicts. - %1 und %n andere Datei haben Konflikte beim Abgleichen.%1 und %n andere Dateien haben Konflikte beim Abgleichen. - - - - %1 has a sync conflict. Please check the conflict file! - Es gab einen Konflikt bei der Synchronisierung von %1. Bitte prüfe die Konfliktdatei! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 und %n weitere Datei konnten aufgrund von Fehlern nicht synchronisiert werden. Schaue in das Protokoll für Details.%1 und %n weitere Dateien konnten aufgrund von Fehlern nicht synchronisiert werden. Schaue in das Protokoll für Details. - - - - %1 could not be synced due to an error. See the log for details. - %1 konnte aufgrund eines Fehlers nicht synchronisiert werden. Schaue in das Protokoll für Details. - - - - Sync Activity - Synchronisierungsaktivität - - - - Could not read system exclude file - Systemeigene Ausschlussdatei kann nicht gelesen werden - - - - A new folder larger than %1 MB has been added: %2. - - Ein neues Verzeichnis, welcher grösser als %1 MB ist, wurde hinzugefügt: %2. - - - - - A folder from an external storage has been added. - - Ein Verzeichnis, von einem externen Speicher wurde hinzugefügt. - - - - - Please go in the settings to select it if you wish to download it. - Bitte wechsele zu den Einstellungen, falls du das Verzeichnis herunterladen möchtest. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - Ordner %1 wurde angelegt, aber früher von der Synchronisation ausgeschlossen. Daten im Ordner werden nicht synchronisiert. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - Datei %1 wurde angelegt, aber früher von der Synchronisation ausgeschlossen, sie wird nicht synchronisiert. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - Änderungen in synchronisierten Ordnern konnten nicht zuverlässig verfolgt werden. - -Dies bedeutet, dass der Synchronisationsclient lokale Änderungen möglicherweise nicht sofort hochlädt und stattdessen nur nach lokalen Änderungen sucht und sie gelegentlich hochlädt (standardmässig alle zwei Stunden). - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Alle Dateien im Synchronisationsordner '%1' wurden auf dem Server gelöscht. -Diese Löschung wird in dein lokales Synchronisationsverzeichnis synchronisiert. Dadurch sind die Dateien nicht mehr verfügbar, falls du keine Möglichkeit zur Wiederherstellung hast. -Wenn du dich entscheidest, diese Dateien zu behalten, werden diese wieder zum Server synchronisiert. Dies geschieht nur, wenn du die Rechte dazu hast. -Wenn du dich zum Löschen der Dateien entscheidest, sind diese nicht mehr verfügbar, ausser du bist der Eigentümer. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Alle Dateien im Synchronisationsordner '%1' werden auf dem Server gelöscht. Diese Löschung wird mit deinem Server synchronisiert, wodurch die Dateien nicht mehr verfügbar sind, ausser diese werden wiederhergestellt. -Bist du dir sicher, dass du diese Aktion mit Ihrem Server synchronisieren möchtest? -Falls dies ein Missgeschick war und du dich zum Behalten der Datei entscheidest, werden diese wieder vom Server synchronisiert. - - - - Remove All Files? - Alle Dateien löschen? - - - - Remove all files - Alle Dateien löschen - - - - Keep files - Dateien behalten - - - - OCC::FolderMan - - - Could not reset folder state - Ordnerzustand konnte nicht zurückgesetzt werden - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Ein altes Synchronisations-Journal '%1' wurde gefunden, konnte jedoch nicht entfernt werden. Bitte stelle sicher, dass keine Anwendung es verwendet. - - - - (backup) - (Backup) - - - - (backup %1) - (Backup %1) - - - - Undefined State. - Undefinierter Zustand. - - - - Waiting to start syncing. - Warten auf Beginn der Synchronisation - - - - Preparing for sync. - Synchronisation wird vorbereitet. - - - - Sync is running. - Synchronisation läuft. - - - - Sync was successful, unresolved conflicts. - Synchronisation war erfolgreich, ungelöste Konflikte. - - - - Last Sync was successful. - Die letzte Synchronisation war erfolgreich. - - - - Setup Error. - Installationsfehler. - - - - User Abort. - Benutzer-Abbruch - - - - Sync is paused. - Synchronisation wurde angehalten. - - - - %1 (Sync is paused) - %1 (Synchronisation ist pausiert) - - - - No valid folder selected! - Keinen gültigen Ordner ausgewählt! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - Der gewählte Pfad existiert nicht! - - - - The selected path is not a folder! - Der gewählte Pfad ist kein Verzeichnis! - - - - You have no permission to write to the selected folder! - Sie sind nicht berechtigt, in diesen Ordner zu schreiben! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Es exisitiert bereits eine Synchronisation vom Server zu diesem lokalen Ordner. Bitte wähle ein anderes lokales Verzeichnis! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Das lokale Verzeichnis %1 enthält schon ein Verzeichnis, das schon mit einer anderen Verbindung synchronisiert wird. Bitte wähle ein anderes Verzeichnis! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Das lokale Verzeichnis %1 liegt innerhalb eines Verzeichnisses, das schon synchronisiert wird. Bitte wähle ein anderes Verzeichnis! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Synchronisiertes Verzeichnis hinzufügen - - - - File - Datei - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Du musst verbunden sein, um einen Ordner hinzuzufügen - - - - Click this button to add a folder to synchronize. - Zum Hinzufügen eines Ordner zur Synchronisation diesen Knopf drücken. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Fehler beim Herunterladen der Verzeichnisliste vom Server. - - - - Virtual file support is enabled. - Unterstützung für virtuelle Dateien ist aktiviert. - - - - Signed out - austreten - - - - Fetching folder list from server... - Abrufen der Ordnerlisten vom Server... - - - - There are unresolved conflicts. Click for details. - Es existieren ungelöste Konflikte. Für Details klicken. - - - - Checking for changes in remote '%1' - Prüfe Änderungen der entfernten '%1' - - - - Checking for changes in local '%1' - Prüfe Änderungen der lokalen '%1' - - - - Reconciling changes - Änderungen zusammenführen - - - - , '%1' - Build a list of file names - ,'%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synchronisiere %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - Download %1/s - - - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - Upload %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 von %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 übrig, %1 von %2, Datei %3 von %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 von %2, Datei %3 von %4 - - - - file %1 of %2 - Datei %1 von %2 - - - - Waiting... - warten... - - - - Waiting for %n other folder(s)... - Warte auf ein Verzeichnis...Warte auf %n andere Verzeichnisse... - - - - Preparing to sync... - Synchronisation vorbereiten... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - Der Beobachter hat keine Test-Benachrichtigung erhalten. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Dieses Problem tritt normalerweise auf, wenn die Inotify-watcher erschöpft sind. Überprüfe die FAQ für Details. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Synchronisiertes Verzeichnis hinzufügen - - - - Add Sync Connection - Füge eine Verzeichnis Sychronisation hinzu - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Zur Auswahl eines lokalen Verzeichnisses für die Synchronisation klicken. - - - - Enter the path to the local folder. - Pfad zum lokalen Verzeichnis eingeben - - - - Select the source folder - Den Quellordner wählen - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Entfernten Ordner erstellen - - - - Enter the name of the new folder to be created below '%1': - Gib den Namen des neuen, unter '%1' zu erstellenden Ordners ein: - - - - Folder was successfully created on %1. - Order erfolgreich auf %1 erstellt. - - - - Authentication failed accessing %1 - Beim Zugriff auf %1 ist die Authentifizierung fehlgeschlagen - - - - Failed to create the folder on %1. Please check manually. - Die Erstellung des Ordners auf %1 ist fehlgeschlagen. Bitte prüfe dies manuell. - - - - Failed to list a folder. Error: %1 - Fehler beim Auflisten des Verzeichnisses. Fehler: %1 - - - - Choose this to sync the entire account - Wähle dies, um das gesamte Konto zu synchronisieren - - - - This folder is already being synced. - Dieser Ordner wird bereits synchronisiert. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Du synchronisierst bereits <i>%1</i>, das ein übergeordneten Ordner von <i>%2</i> ist. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Der sync von <i>%1</i> läuft schon, als Unterverzeichnis von <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Virtuelle Dateien verwenden, anstatt die Dateien gleich herunter zu laden%1 - - - - (experimental) - (experimentell) - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Warnung:</b> %1 - - - - <b>Warning:</b> - <b>Warnung:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Kein E-Tag vom Server empfangen, bitte Proxy / Gateway überprüfen - - - - We received a different E-Tag for resuming. Retrying next time. - Beim Fortfahren wurde ein abweichender E-Tag empfangen. Bitte noch mal versuchen. - - - - We received an unexpected download Content-Length. - Unerwartete Daten-Länge beim Herunterladen empfangen. - - - - Server returned wrong content-range - Server hat falschen Bereich für den Inhalt zurück gegeben - - - - OCC::GETJob - - - Connection Timeout - Zeitüberschreitung der Verbindung - - - - OCC::GeneralSettings - - - Form - Formular - - - - General Settings - Allgemeine Einstellungen - - - - Language - Sprache - - - - Sync hidden files - Synchronisiere versteckte Dateien - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Netzwerk - - - - Updates - Aktualisierungen - - - - stable - Stabil - - - - beta - Beta - - - - About - Über - - - - Advanced - Fortgeschritten - - - - Use &Monochrome Icons in the system tray - &Monochrome-Icons in der Taskleiste verwenden - - - - Ask for confirmation before synchronizing folders larger than - Nachfragen, bei Synchronisation von Ordnern grösser als - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Bestätigung erfragen, bevor externe Speicher synchronisiert werden. - - - - &Launch on System Startup - &Starten beim Systemstart - - - - Show &Desktop Notifications - zeige Desktop Benachrichtigungen - - - - &Update Channel - &Update Kanal - - - - Edit &Ignored Files - &Ignorierte Dateien bearbeiten - - - - Show sync folders in &Explorer's Navigation Pane - Zeige synchronisierte Ordner in der Explorer Navigation. - - - - S&how crash reporter - Logfile anzeigen - - - - &Restart && Update - &Neustarten && Aktualisieren - - - - You cannot disable autostart because system-wide autostart is enabled. - Autostart kann nicht abgeschaltet werden, da Autostart als Systemeinstellung aktiviert ist. - - - - Change update channel? - Update-Kanal verändern? - - - - Warning - Warnung - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Der Update-Kanal legt fest welche Programm-Updates zur Installation angeboten werden. Der "Stabil"-Kanal enthält nur Upgrades die als verlässlich betrachtet werden, während Versionen im "Beta"-Kanal neuer Funktionen und Fehlerbehebungen enthalten können, die bisher noch nicht komplett bzw. ausführlich genug getestet werden konnten. -Beachten Sie, das dies nur bestimmt von welchem Bereich Sie Upgrades erhalten und das es keine Downgrade-Möglichkeiten gibt: Einen "Schritt zurück" vom Beta-Kanal zum Stabilen-Kanal kann normalerweise nicht unverzüglich erfolgen und bedeutet Wartezeiten auf eine Stabile-Version, die dann neuer ist als die jeweils aktuell installierte Beta-Version. - - - - Change update channel - Update-Kanal ändern - - - - Cancel - Abbrechen - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Bitte Kennwort für %1 eingeben:<br><br>Benutzer: %2<br>Konto: %3<br> - - - - Reading from keychain failed with error: '%1' - Das Lesen aus dem Schlüsselbund schlug mit folgendem Fehler schief: '%1' - - - - Enter Password - Passwort eingeben - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Klicke hier</a> um ein App-Passwort von dem Web-Interface zu erhalten. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor für ignorierte Dateien - - - - Files Ignored by Patterns - Dateien, welche durch Mustererkennung ignoriert werden - - - - Add - Hinzufügen - - - - Pattern - Muster - - - - Allow Deletion - Erlaube Löschung - - - - Remove - Entfernen - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Dateien oder Verzeichnisse deren Namen mit dem regulären Ausdruck erfasst werden, werden von der Synchronisation ausgeschlossen. Änderungen werden bei der nächsten Synchronisation übernommen. - -Elemente, für die Löschen erlaubt ist, werden gelöscht, wenn sie andernfalls das Löschen eines ansonsten leeren Ordners verhindern würden. - - - - Could not open file - Datei konnte nicht geöffnet werden - - - - Cannot write changes to '%1'. - Konnte Änderungen nicht in '%1' schreiben. - - - - Add Ignore Pattern - Ignoriermuster hinzufügen - - - - Add a new ignore pattern: - Neues Ignoriermuster hinzufügen: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Dieser Eintrag wird vom System auf '%1' bereitgestellt und kann in dieser Ansicht nicht geändert werden. - - - - OCC::IssuesWidget - - - Form - Formular - - - - List of issues - Liste der Fehler - - - - Filter - Filter - - - - There were too many issues. Not all will be visible here. - Es gab zu viele Fehler. Nicht alle werden hier angezeigt. - - - - There were conflicts. Check the documentation on how to resolve them. - Es bestehen Konflikte. Prüfe die Dokumentation, wie diese zu lösen sind. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Es bestehen Konflikte. <a href="%1">Prüfe die Dokumentation, wie diese zu lösen sind.</a> - - - - Account - Nutzerkonto - - - - OCC::LibCloudProvidersPrivate - - - Settings - Einstellungen - - - - OCC::LogBrowser - - - Log Output - Log-Ausgabe - - - - Enable logging to temporary folder - Einschalten von Logging in einen temporären Ordner - - - - Delete logs older than %1 hours - Logs löschen, die älter sind als %1 Stunden - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Diese Einstellungen bleiben nach einem Neustart des Clients erhalten. -Beachten Sie, dass Kommandozeilenoptionen zum Logging diese Einstellungen überschreiben. - - - - Open folder - Ordner öffnen - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Eine neue Version ist verfügbar. - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Eine neue Version des %1 Clients ist verfügbar.</p><p><b>%2</b> steht zum Herunterladen bereit. Die installierte Version ist %3.</p> - - - - - Skip this version - Diese Version auslassen - - - - Skip this time - Dieses Mal überspringen - - - - Get update - Update durchführen - - - - Update Failed - Update fehlgeschlagen - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Eine neue Version des %1 Clients ist verfügbar, aber der Aktualisierungs-Prozess ist fehlgeschlagen.</p><p><b>%2</b> wurde heruntergeladen. Die installierte Version ist %3.</p> - - - - Ask again later - Später noch einmal fragen - - - - Restart and update - Neustarten und aktualisieren - - - - Update manually - Manuell aktualisieren - - - - OCC::NetworkSettings - - - Form - Formular - - - - Proxy Settings - Proxy-Einstellungen - - - - No Proxy - Kein Proxy - - - - Use system proxy - Benutze System-Proxy - - - - Specify proxy manually as - Proxy manuell definieren als - - - - Host - Host - - - - : - : - - - - Proxy server requires authentication - Proxyserver erfordert Authentifizierung - - - - Note: proxy settings have no effects for accounts on localhost - Beachte: Proxy Einstellungen haben keinen Effekt bei Konten auf localhost - - - - Download Bandwidth - Download-Bandbreite - - - - - Limit to - Begrenzen auf - - - - - KBytes/s - KBytes/s - - - - - No limit - Keine Begrenzung - - - - - Limit to 3/4 of estimated bandwidth - Limitierung auf 3/4 der geschätzten Bandbreite - - - - Upload Bandwidth - Upload-Bandbreite - - - - - Limit automatically - Automatisch begrenzen - - - - Hostname of proxy server - Hostname des Proxy-Servers - - - - Username for proxy server - Benutzername für den Proxy-Server - - - - Password for proxy server - Passwort für den Proxy-Server - - - - HTTP(S) proxy - HTTP(S)-Proxy - - - - SOCKS5 proxy - SOCKS5-Proxy - - - - OCC::NotificationWidget - - - Created at %1 - Erstellt von %1 - - - - Closing in a few seconds... - Schliesse in wenigen Sekunden... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 Anfrage an %2 fehlgeschlagen - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' ausgewählt von %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Fehler vom Server zurückgegeben: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Beim Zugriff auf den 'token' Endpunkt trat ein Fehler auf: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Die JSON Antwort des Servers konnte nicht reparst werden: <br><em>%1</em> - - - - Unsupported token type: %1 - Nicht unterstützter Token-Typ: %1 - - - - The reply from the server did not contain all expected fields -:%1 - Die Server-Antwort hat nicht alle erwarteten Felder enthalten: %1 - - - - Unknown Error - Ein unbekannter Fehler ist aufgetreten. - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Anmeldefehler</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Beim Aktualisieren des Tokens ist ein Fehler aufgetreten: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - Die Server-Antwort hat nicht alle erwarteten Felder enthalten -:%1 -Empfangene Daten: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Falscher Benutzer</h1><p>Du hast dich mit dem Benutzer <em>%1</em> angemeldet, solltest dich jedoch mit dem Benutzer <em>%2</em> anmelden. Bitte melde dich in einem anderen Tab von %3 ab und <a href='%4'>klicke hier</a> um dich als %2 anzumelden</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Neues %1 Update verfügbar - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Ein neues Update für %1 wird installiert. Während des Updatevorganges werden Sie eventuell gefragt zusätzliche Rechte im Rahmen des Updatevorganges zu gewähren. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Aktualisierung kann nicht herunter geladen werden. Bitte klicke <a href='%1'>hier</a> um die Aktualisierung manuell herunter zu laden. - - - - Could not check for new updates. - Auf neue Aktualisierungen kann nicht geprüft werden. - - - - Downloading %1. Please wait... - Herunterladen von %1. Bitte warten… - - - - %1 available. Restart application to start the update. - %1 ist verfügbar. Anwendung zum Start der Aktualisierung neu starten. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Aktualisierung von %1 verfügbar. Bitte nutzen Sie zur Installation das Systemaktualisierungstool. - - - - Checking update server... - Update-Server wird überprüft… - - - - Update status is unknown: Did not check for new updates. - Updatestatus unbekannt: Auf neue Updates wurde nicht geprüft. - - - - No updates available. Your installation is at the latest version. - Keine Updates verfügbar. Ihre Installation ist die aktuelle Version. - - - - Update Check - Aktualisierungsüberprüfung - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Verbinden mit %1 - - - - Setup local folder options - Einstellungen der Optionen für lokale Verzeichnisse - - - - Connect... - Verbinden… - - - - (experimental) - (experimentell) - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Synchronisiere Verzeichnis '%1' - - - - Virtual files are not available for the selected folder - - - - - Error - Fehler - - - - Local Sync Folder - Lokaler Ordner für die Synchronisation - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &E-Mail - - - - Connect to %1 - Verbinden mit %1 - - - - Enter user credentials - Gib die Benutzer-Anmeldeinformationen ein - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Verbinden mit %1 - - - - Login in your browser - Melde dich in deinem Browser an - - - - Copy link to clipboard - Link in die Zwischenablage kopieren - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Verbinden mit %1 - - - - Setup %1 server - %1 Server einrichten - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Diese URL ist NICHT sicher, das sie nicht verschlüsselt ist. -Es ist nicht ratsam, diese zu benutzen. - - - - This url is secure. You can use it. - Diese URL ist sicher. Du kannst Sie benutzen. - - - - &Next > - &Weiter > - - - - Invalid URL - Ungültige URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Die Verbindung zu %1 auf %2:<br/>%3 konnte nicht hergestellt werden - - - - Timeout while trying to connect to %1 at %2. - Zeitüberschreitung beim Verbindungsversuch mit %1 unter %2. - - - - Invalid URL - Ungültige URL - - - - Could not create local folder %1 - Der lokale Ordner %1 konnte nicht angelegt werden - - - - Error: %1 - Fehler: %1 - - - - The folder creation resulted in HTTP error code %1 - Das Erstellen des Verzeichnisses erzeugte den HTTP-Fehler-Code %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Die Remote-Ordner-Erstellung ist fehlgeschlagen, weil die angegebenen Zugangsdaten falsch sind. Bitte gehe zurück und überprüfe die Zugangsdaten. - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Remote-Ordner %1 konnte mit folgendem Fehler nicht erstellt werden: <tt>%2</tt>. - - - - Folder rename failed - Das Umbenennen des Ordners ist fehlgeschlagen - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Der Ordner kann nicht entfernt und gesichert werden, da der Ordner oder einer seiner Dateien in einem anderen Programm geöffnet ist. Bitte schliessen Sie den Ordner oder die Datei oder beenden Sie die Installation. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Verbindungsassistent - - - - Enable experimental feature? - Experimentelle Funktion aktivieren? - - - - Enable experimental placeholder mode - Experimentellen Platzhalter Modus freischalten - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Wenn der Modus "Virtuelle Dateien" aktiviert ist, werden die Dateien nicht sofort heruntergeladen. Stattdessen wird für jede Datei des Servers eine winzige "%1" Datei erstellt. Der Inhalt der Dateien kann durch Ausführen der Datei oder über das Kontext-Menü heruntergeladen werden. - -Der Modus virtuelle Dateien schliesst sich mit dem Modus "Selective Sync" gegenseitig aus. Aktuell abgewählte Ordner werden zu nur online verfügbaren Ordnern umgesetzt und die Einstellungen im "Selective Sync" werden verworfen. - -Dies ist ein neuer, experimenteller Modus. Wenn sie sich dafür entscheiden, berichten sie bitte alle Probleme, die auftreten. - - - - Stay safe - Sicher bleiben - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Symbolische Verknüpfungen werden bei der Synchronisation nicht unterstützt. - - - - File is listed on the ignore list. - Die Datei ist in der Ignorierliste geführt. - - - - File names ending with a period are not supported on this file system. - Dateinamen enden mit einem Punkt, die in diesem Dateisystem nicht unterstützt wird. - - - - File names containing the character '%1' are not supported on this file system. - Dateinamen beinhalten das Zeichen '%1' und diese werden in diesem Dateisystems nicht unterstützt. - - - - File name contains at least one invalid character - Der Dateiname enthält mindestens ein ungültiges Zeichen - - - - The file name is a reserved name on this file system. - Der Dateiname ist ein reservierter Name in diesem Dateisystem. - - - - Filename contains trailing spaces. - Dateiname endet mit Leerzeichen. - - - - Filename is too long. - Der Dateiname ist zu lang. - - - - File/Folder is ignored because it's hidden. - Datei/Ordner wird ignoriert, da sie versteck sind. - - - - Stat failed. - Stat fehlgeschlagen. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Konflikt: Version des Servers heruntergeladen, lokale Kopie umbenannt und nicht hochgeladen. - - - - The filename cannot be encoded on your file system. - Der Dateiname kann auf deinem Dateisystem nicht kodiert werden. - - - - The filename is blacklisted on the server. - Der Dateiname ist auf der schwarzen Liste des Servers. - - - - size - Grösse - - - - permissions - Berechtigungen - - - - etag - E-Tag - - - - file id - file id - - - - server reported no %1 - Der Server meldet kein %1 - - - - Moved to invalid target, restoring - An ungültigen Zielort verschoben, Wiederherstellung - - - - Not allowed to remove, restoring - Löschen nicht erlaubt, Wiederherstellung - - - - Ignored because of the "choose what to sync" blacklist - Aufgrund der »Zu synchronisierende Elemente auswählen«-Sperrliste ignoriert - - - - Not allowed because you don't have permission to add subfolders to that folder - Nicht erlaubt, weil keine Rechte bestehen, diesem Verzeichnis Unterverzeichnisse hinzuzufügen. - - - - Not allowed because you don't have permission to add files in that folder - Nicht erlaubt, weil die nötigen Rechte fehlen, Dateien zu dem Verzeichnis hinzuzufügen - - - - Not allowed to upload this file because it is read-only on the server, restoring - Das Hochladen dieser Datei ist nicht erlaubt, da die Datei auf dem Server schreibgeschützt ist, Wiederherstellung - - - - Error while reading the database - Fehler beim Lesen der Datenbank - - - - Server replied with an error while reading directory '%1' : %2 - Der Server meldet einen Fehler beim Lesen des Ordners '%1' : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - Das Herunterladen würde den lokalen freien Speicherplatz unter das Limit reduzieren - - - - Free space on disk is less than %1 - Der freie Speicherplatz beträgt weniger als %1 - - - - File was deleted from server - Datei wurde vom Server gelöscht - - - - The file could not be downloaded completely. - Die Datei konnte nicht vollständig herunter geladen werden. - - - - The downloaded file is empty despite the server announced it should have been %1. - Die heruntergeladene Datei ist leer, obwohl der Server %1 als Grösse übermittelt hat. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - Datei ist seit der Entdeckung geändert worden - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Wiederherstellung fehlgeschlagen: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - Die Datei %1 konnte nicht gelöscht werden, Fehler: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - konnte Verzeichnis %1 nicht anlegen - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - %1 kann aufgrund eines Konfliktes mit dem lokalen Dateinamen nicht zu %2 umbenannt werden - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - Fehler beim Setzen des PIN-Zustandes - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 204, aber gesendet wurde "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 201, aber gesendet wurde "%1 %2". - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - Konnte %1 nicht nach %2 umbenennen, Fehler: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Es wurde ein falscher HTTP-Status-Code vom Server gesendet. Erwartet wurde 201, aber gesendet wurde "%1 %2". - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - Fehler beim Schreiben der Metadaten in die Datenbank - - - - Error setting pin state - Fehler beim Setzen des PIN-Zustandes - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Die Datei %1 kann nicht hochgeladen werden, da eine andere Datei mit dem selben Namen, nur unterschiedlicher Grossschreibung, existiert - - - - File Removed - Datei entfernt - - - - Local file changed during syncing. It will be resumed. - Heruntergeladene Datei entspricht nicht der Prüfsumme, das Herunterladen wird wiederaufgenommen. - - - - Local file changed during sync. - Eine lokale Datei wurde während der Synchronisation geändert. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - Das Hochladen von %1 überschreitet das Speicherkontingent des Ordners - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Die lokale Datei wurde während der Synchronisation gelöscht. - - - - Local file changed during sync. - Eine lokale Datei wurde während der Synchronisation geändert. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - Unerwarteter Rückgabe-Code Antwort vom Server (%1) - - - - Missing File ID from server - Fehlende Datei-ID vom Server - - - - Missing ETag from server - Fehlender ETag vom Server - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Die lokale Datei wurde während der Synchronisation gelöscht. - - - - Local file changed during sync. - Eine lokale Datei wurde während der Synchronisation geändert. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - Die lokale Datei wurde während der Synchronisation gelöscht. - - - - Local file changed during sync. - Eine lokale Datei wurde während der Synchronisation geändert. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Der Server konnte das letzte Stück nicht bestätigen. (Keine Entitätmarke war vorhanden) - - - - OCC::ProtocolItemModel - - - Time - Zeit - - - - File - Datei - - - - Folder - Ordner - - - - Issues - - - - - Action - Aktion - - - - Size - Grösse - - - - Account - Nutzerkonto - - - - Status - Status - - - - OCC::ProtocolWidget - - - Form - Formular - - - - Local sync protocol - Lokales synchronisations Protokoll - - - - Filter - Filter - - - - Account - Nutzerkonto - - - - Copy to clipboard - In Zwischenablage kopieren - - - - Show in file browser - - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Authentifizierung am Proxy notwendig - - - - Username: - Benutzername: - - - - Proxy: - Proxy: - - - - The proxy server needs a username and password. - Das Proxy-Server braucht einen Benutzername und ein Password. - - - - Password: - Passwort: - - - - TextLabel - TextLabel - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Zu synchronisierende Elemente auswählen - - - - OCC::SelectiveSyncWidget - - - Loading ... - Ladevorgang … - - - - Deselect remote folders you do not wish to synchronize. - Entfernte Ordner abwählen, die nicht synchronisiert werden sollen. - - - - Name - Name - - - - Size - Grösse - - - - - No subfolders currently on the server. - Aktuell befinden sich keine Unterordner auf dem Server. - - - - An error occurred while loading the list of sub folders. - Ein Fehler ist aufgetreten, während die Liste der Unterordner geladen wurde. - - - - OCC::ServerNotificationHandler - - - Dismiss - Ausblenden - - - - OCC::SettingsDialog - - - Settings - Einstellungen - - - - Activity - Aktivität - - - - Add account - Konto hinzufügen - - - - - Quit %1 - %1 beenden - - - - Are you sure you want to quit %1? - Sind Sie sicher, dass sie %1 beenden möchten? - - - - Account - Konto - - - - MainWindow - - - - - Hide - Ausblenden - - - - OCC::ShareDialog - - - TextLabel - TextLabel - - - - share label - Freigabe-Label - - - - Dialog - Dialog - - - - ownCloud Path: - ownCloud-Pfad: - - - - %1 Sharing - %1-Freigabe - - - - %1 - %1 - - - - Folder: %2 - Ordner: %2 - - - - The server does not allow sharing - Dieser Server erlaubt das Teilen nicht - - - - Retrieving maximum possible sharing permissions from server... - Abrufen der maximal möglichen Freigabeberechtigungen vom Server... - - - - The file can not be shared because it was shared without sharing permission. - Die Datei kann nicht geteilt werden, weil sie ohne erneute Teilungs-Berechtigung für Sie geteilt wurde. - - - - Users and Groups - Nutzer und Gruppen - - - - Public Links - Öffentliche Links - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - NeuesDokument.odt teilen - - - - Upload only (File Drop) - Nur hochladen (Datei hineinziehen) - - - - Receive files from others without revealing the contents of the folder. - Von anderen Dateien empfangen, ohne den Inhalt des Ordners preiszugeben. - - - - Download / View / Edit - Herunterladen / Ansehen / Bearbeiten - - - - Name: - Name: - - - - Enter name of new share... - Geben Sie den Namen der neuen Freigabe ein ... - - - - &Create - Erstellen - - - - TextLabel - TextLabel - - - - Set &password - &password festlegen - - - - Set &expiration date - Ablaufdatum s&etzen - - - - Set password - Passwort setzen - - - - Recipients can view or download contents. - Empfänger können Inhalte sehen und herunterladen. - - - - Download / View - Herunterladen / Ansehen - - - - Recipients can view, download, edit, delete and upload contents. - Empfänger können Inhalte sehen, herunterladen, ändern, löschen und hochladen. - - - - Anyone with the link has access to the file/folder - Jeder mit dem Link hat Zugriff auf die Datei/Ordner - - - - - P&assword protect - passwortgeschützt - - - - The file can not be shared because it was shared without sharing permission. - Die Datei kann nicht geteilt werden, weil sie ohne erneute Teilungs-Berechtigung für Sie geteilt wurde. - - - - Link shares have been disabled - Das Teilen von Links wurde deaktiviert - - - - Create public link share - Öffentlichen Link zum Teilen erstellen - - - - - Delete - Löschen - - - - Open link in browser - Link im Browser öffnen - - - - Copy link to clipboard - Link in die Zwischenablage kopieren - - - - Copy link to clipboard (direct download) - Link in die Zwischenablage kopieren (Sofortiges herunterladen) - - - - Send link by email - Link als E-Mail verschicken - - - - Send link by email (direct download) - Link über E-Mail senden (Sofortiges herunterladen) - - - - Create new... - Neu erstellen... - - - - I shared %1 with you - Ich habe %1 mit dir geteilt - - - - Confirm Link Share Deletion - Löschung des öffentlichen Links bestätigen - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Möchtest du wirklich den öffentlichen Link <i>%1</i> löschen?</p><p>Hinweis: Dies kann nicht rückgängig gemacht werden.</p> - - - - Cancel - Abbrechen - - - - - Public link - Öffentlicher Link - - - - Delete link share - Öffentlichen Link löschen - - - - - Please Set Password - Bitte setze ein Passwort - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - NeuesDokument.odt teilen - - - - Share with users or groups ... - Mit Benutzern oder Gruppen teilen ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Du kannst Leute direkt zu dieser Freigabe leiten, wenn du ihnen einen <a href="private link menu"><span style=" text-decoration: underline">privaten Link</span></a> gibst.</p></body></html> - - - - The item is not shared with any users or groups - Das Element wird mit keinem Benutzer oder Gruppe geteilt - - - - Open link in browser - Link im Browser öffnen - - - - Copy link to clipboard - Link in die Zwischenablage kopieren - - - - Send link by email - Link als E-Mail verschicken - - - - No results for '%1' - keine Ergebnisse für '%1' - - - - I shared something with you - Ich habe etwas mit dir geteilt - - - - OCC::ShareUserLine - - - Form - Formular - - - - - TextLabel - TextLabel - - - - can edit - kann bearbeiten - - - - can share - kann teilen - - - - ... - ... - - - - create - erstellen - - - - change - Ändern - - - - delete - löschen - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Mit %1 teilen - - - - Context menu share - Teilen über das Kontextmenü - - - - Context menu share %1 - Freigabe %1 über das Kontextmenü - - - - Confirm deletion - Löschung bestätigen - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Möchten Sie den Ordner <i>%1</i> und alle seine Inhalte dauerhaft löschen? - - - - Do you want to delete the file <i>%1</i> permanently? - Möchten Sie die Datei <i>%1</i> dauerhaft löschen? - - - - Select new location... - Neuen Ort wählen... - - - - Error - Fehler - - - - Moving file failed: - -%1 - Verschieben der Datei fehlgeschlagen: - -%1 - - - - I shared something with you - Ich habe etwas mit dir geteilt - - - - - Share... - Teilen… - - - - - Copy private link to clipboard - Privaten Link in die Zwischenablage kopieren - - - - Send private link by email... - Privaten Link über E-Mail senden... - - - - Resharing this file is not allowed - Weiteres Teilen ist nicht erlaubt. - - - - Resharing this folder is not allowed - Weiterteilen des Ordners ist nicht erlaubt - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - Öffentlichen Link in die Zwischenablage kopieren - - - - Open in browser - Öffnen im Browser - - - - Show file versions in browser - Zeige Dateiversionen im Browser - - - - Rename... - Umbenennen... - - - - Rename and upload... - Umbenennen und hochladen... - - - - Move and rename... - Verschieben und umbenennen... - - - - Move, rename and upload... - Verschieben, umbenennen und hochladen... - - - - Delete local changes - Lokale Änderungen löschen - - - - Move and upload... - Verschieben und hochladen... - - - - Delete - Löschen - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Zertifikatdetails</h3> - - - - Common Name (CN): - Gemeinsamer Name (CN): - - - - Subject Alternative Names: - Betreffende alternative Namen (SAN): - - - - Organization (O): - Organisation (O): - - - - Organizational Unit (OU): - Organisationseinheit (OU): - - - - State/Province: - Staat/Provinz: - - - - Country: - Land: - - - - Serial: - Schlüssel: - - - - <h3>Issuer</h3> - <h3>Aussteller</h3> - - - - Issuer: - Aussteller: - - - - Issued on: - Ausgestellt auf: - - - - Expires on: - Ablaufdatum: - - - - <h3>Fingerprints</h3> - <h3>Fingerabdrücke</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Hinweis:</b> Dieses Zertifikat wurde manuell bestätigt</p> - - - - %1 (self-signed) - %1 (selbst signiert) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Diese Verbindung ist verschlüsselt mit %1 Bit %2. - - - - - Server version: %1 - Serverversion: %1 - - - - No support for SSL session tickets/identifiers - Keine Unterstützung für sogenannte SSL Session Tickets/Identifiers. - - - - Certificate information: - Zertifikatsinformation: - - - - The connection is not secure - Die Verbindung ist nicht sicher - - - - This connection is NOT secure as it is not encrypted. - - Diese Verbindung ist NICHT sicher, da sie nicht verschlüsselt ist. - - - - - OCC::SslErrorDialog - - - Form - Formular - - - - Trust this certificate anyway - Diesem Zertifikat trotzdem vertrauen. - - - - Untrusted Certificate - Unvertrautes Zertifikat - - - - Cannot connect securely to <i>%1</i>: - Kann nicht verschlüsselt mit <i>%1</i>: verbinden: - - - - with Certificate %1 - mit Zertifikat %1 - - - - - - &lt;not specified&gt; - &lt;Nicht angegeben&gt; - - - - - Organization: %1 - Organisation: %1 - - - - - Unit: %1 - Einheit: %1 - - - - - Country: %1 - Land: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Prüfsumme (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Prüfsumme (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Aktuelles Datum: %1 - - - - Expiration Date: %1 - Ablaufdatum: %1 - - - - Issuer: %1 - Aussteller: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1(Übersprungen aufgrund des früheren Fehler,s erneuter Versuch in %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Nur %1 zur Verfügung stehen, müssen mindestens %2 zu starten - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Öffnen oder erstellen der Sync-Datenbank nicht möglich. Bitte sicherstellen, dass Schreibrechte für den zu synchronisierenden Ordner existieren. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Der freie Speicherplatz wird knapp: Downloads, die den freien Speicher unter %1 senken, wurden ausgelassen. - - - - There is insufficient space available on the server for some uploads. - Auf dem Server ist für einige Dateien zum Hochladen nicht genug Platz. - - - - Unresolved conflict. - Ungelöster Konflikt. - - - - Using virtual files with suffix, but suffix is not set - Virtuelle Dateien mit Dateiendung werden genutzt, aber die Dateiendung ist nicht definiert - - - - Unable to read the blacklist from the local database - Fehler beim Einlesen der Blacklist aus der lokalen Datenbank - - - - Unable to read from the sync journal. - Fehler beim Einlesen des Synchronisierungsprotokolls. - - - - Cannot open the sync journal - Synchronisationsbericht kann nicht geöffnet werden - - - - Aborted - Abgebrochen - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Der Prüfsummen-Header hat ein fehlerhaftes Format. - - - - The checksum header contained an unknown checksum type '%1' - Der Prüfsummenkopf beinhaltet eine unbekannte Prüfsumme vom Typ '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - Die heruntergeladene Datei entspricht nicht der Prüfsumme, das Herunterladen wird wiederaufgenommen. '%1' != '%2' - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Bitte melde Dich an - - - - Folder %1: %2 - Ordner %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - Es wurden keine Synchonisationsordner konfiguriert. - - - - Open in browser - Öffnen im Browser - - - - - - Log in... - Anmelden... - - - - - - Log out - Abmelden - - - - Recent Changes - Letzte Änderungen - - - - Managed Folders: - Verwaltete Ordner: - - - - Open folder '%1' - Ordner »%1« öffnen - - - - Open %1 in browser - %1 im Browser öffnen - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - Der Server für Konto %1 benutzt eine nicht unterstützte Version %2. Die Client Software kann vielleicht mit nicht unterstützten Versionen funktionieren, doch dies ist nicht getestet. Weiter auf eigenes Risiko. - - - - Unknown status - Unbekannter Status - - - - Settings... - Einstellungen … - - - - Details... - Details … - - - - Help - Hilfe - - - - Quit %1 - %1 beenden - - - - Disconnected from %1 - Getrennt von %1 - - - - Unsupported Server Version - Nicht unterstützte Server Version - - - - Disconnected - Getrennt - - - - Disconnected from some accounts - Verbindungen zu einigen Konnten getrennt - - - - Disconnected from accounts: - Verbindungen zu Konnten getrennt: - - - - Account %1: %2 - Konto %1: %2 - - - - Signed out - Ausgeloggt - - - - Account synchronization is disabled - Synchronisieren der Konten ist deaktiviert - - - - Synchronization is paused - Synchronisation pausiert - - - - Up to date (%1) - - - - - No sync folders configured - Keine Ordner zur Synchronisation konfiguriert - - - - Unpause all folders - Mit allen Ordnern fortfahren - - - - Pause all folders - Alle Ordner pausieren - - - - Unpause all synchronization - Alle Synchronisationen fortsetzen - - - - Unpause synchronization - Synchronisation fortsetzen - - - - Pause all synchronization - Alle Synchronisationen anhalten - - - - Pause synchronization - Synchronisation anhalten - - - - Log out of all accounts - Auss allen Konten abmelden - - - - Log in to all accounts... - In allen Konten anmelden... - - - - New account... - Neues Konto... - - - - About %1 - Über %1 - - - - No items synced recently - Keine kürzlich synchronisierten Elemente - - - - Checking for changes in remote '%1' - Prüfe Änderungen der entfernten '%1' - - - - Checking for changes in local '%1' - Prüfe Änderungen der lokalen '%1' - - - - Syncing %1 of %2 (%3 left) - Synchronisiere %1 von %2 (%3 übrig) - - - - Syncing %1 of %2 - Synchronisiere %1 von %2 - - - - Syncing %1 (%2 left) - Synchronisiere %1 (%2 übrig) - - - - Syncing %1 - Synchronisiere %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - Formular - - - - - TextLabel - TextLabel - - - - Server - Server - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Wenn diese Auswahlbox angeklickt wird, wird vorhandener Inhalt des lokalen Verzeichnisses gelöscht, um eine saubere Synchronisation des Servers zu ermöglichen.</p><p>Sie darf nicht angewählt werden, wenn die vorhandenen Dateien in dem Verzeichnis auf den Server hochgeladen werden sollen.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - Nachfragen, bei Synchronisation von Ordnern grösser als - - - - S&ynchronize everything from server - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Nachfragen, bei Synchronisation von externen Speichern - - - - Choose what to sync - Zu synchronisierende Elemente auswählen - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Wenn diese Option angewählt ist, wird der Installationsassistent, ohne irgendetwas zu synchronisieren, beendet. Du kannst den &quot;Synchronisationsverzeichnis&quot; Button in den Kontoeinstellungen nutzen, um das Ordnerpaar zwischen lokal und entfernt festzulegen, die du synchronisieren möchtest</p></body></html> - - - - Manually create folder sync connections - Manuell Synchronisationsverzeichnis erstellen - - - - &Local Folder - &Lokaler Ordner - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - Lokale Daten &behalten - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - Formular - - - - &Username - &Benutzername - - - - &Password - &Passwort - - - - OwncloudOAuthCredsPage - - - Form - Formular - - - - Please switch to your browser to proceed. - Bitte wechsle zum Fortfahren in deinen Browser. - - - - An error occurred while connecting. Please try again. - Verbindungsproblem. Bitte nochmal versuchen. - - - - Re-open Browser - Browser erneut öffnen - - - - OwncloudSetupPage - - - Form - Formular - - - - Ser&ver Address - Ser&veradresse - - - - https://... - https://… - - - - Error Label - Fehler-Label - - - - QObject - - - in the future - in der Zukunft - - - - %n day(s) ago - vor %n Tag(en)Vor %n Tage(n) - - - - %n hour(s) ago - Vor %n Stunde(n)Vor %n Stunde(n) - - - - now - jetzt - - - - Less than a minute ago - vor weniger als einer Minute - - - - %n minute(s) ago - vor %n Minute(n)vor %n Minute(n) - - - - Some time ago - vor einiger Zeit - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - - - - - - Error - Fehler - - - - Info - Info - - - - Success - Erfolg - - - - Conflict - Konflikt - - - - File Ignored - - - - - Restored - - - - - Blacklisted - - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n Jahr%n Jahre - - - - %n month(s) - %n Monat%n Monate - - - - %n day(s) - %n Tag%n Tage - - - - %n hour(s) - %n Stunde%n Stunden - - - - %n minute(s) - %n Minute%n Minuten - - - - %n second(s) - %n Sekunde%n Sekunden - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Benachrichtigungsfeld nicht verfügbar - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 benötigt ein funktionierendes Benachrichtigungsfeld. Wenn Sie XFCE benutzen, folden Sie bitte <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">diesen Anweisungen</a>. Andernfalls installieren Sie bitte ein Benachrichtigungsfeld wie zum Beispiel „Trayer“ und versuchen es noch einmal. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - Virtuelle Datei wurde erstellt - - - - Replaced by virtual file - Ersetzt durch virtuelle Datei - - - - Downloaded - Heruntergeladen - - - - Uploaded - Hochgeladen - - - - Server version downloaded, copied changed local file into conflict file - Serverversion heruntergeladen. Die bearbeitete lokale Datei wurde in eine Konfliktdatei verschoben. - - - - Deleted - gelöscht - - - - Moved to %1 - Verschoben nach %1 - - - - Ignored - Ignoriert - - - - Filesystem access error - Zugriffsfehler im Dateisystem - - - - Error - Fehler - - - - Updated local metadata - Lokale Metadaten wurden aktualisiert - - - - - Unknown - Unbekannt - - - - downloading - Lade herunter - - - - uploading - Lade hoch - - - - deleting - Lösche - - - - moving - Verschiebe - - - - ignoring - ignoriere - - - - - error - Fehler - - - - updating local metadata - aktualisiere lokale Metadaten - - - - theme - - - Status undefined - Status undefiniert - - - - Waiting to start sync - Warte, um mit der Synchronistation zu beginnen - - - - Sync is running - Sync läuft - - - - Sync Success - Sync erfolgreich - - - - Sync Success, some files were ignored. - Synchronisation abgeschlossen, einige Dateien wurden ignoriert. - - - - Sync Error - Synchronisationsfehler - - - - Setup Error - Installationsfehler - - - - Preparing to sync - Synchronisation wird vorbereitet - - - - Aborting... - Es wird abgebrochen … - - - - Sync is paused - Synchronisation wurde angehalten - - - - utility - - - Could not open browser - Browser konnte nicht geöffnet werden - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - Die URL %1 konnte aufgrund eines Fehlers beim Start des Browsers nicht aufgerufen werden. Ist vielleicht kein Standardbrowser konfiguriert? - - - - Could not open email client - Die E-Mail Anwendung konnte nicht geöffnet werden - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Es ist ein Fehler beim Öffnen der E-Mail Anwendung, zum Erstellen einer neuen Nachricht, aufgetreten. Vielleicht ist keine standardmässige E-Mail Anwendung konfiguriert? - - - - Always available locally - Immer lokal verfügbar - - - - Some available online only - Nur einige Online verfügbar - - - - - Available online only - Nur Online verfügbar - - - - Currently available locally - Aktuell lokal verfügbar - - - - Make always available locally - Auf immer lokal verfügbar umschalten - - - - Free up local space - Lokalen Speicherplatz freigeben - - - \ No newline at end of file diff --git a/translations/client_el.ts b/translations/client_el.ts deleted file mode 100644 index 13b0e0ad31e..00000000000 --- a/translations/client_el.ts +++ /dev/null @@ -1,4908 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - <html><head/><body><p><br/></p></body></html> - - - - Abort - Ματαίωση - - - - Send this report - Αποστολή αυτής της αναφοράς - - - - Don't send - Να μην αποσταλεί - - - - You can disable sending crash reports in the configuration dialog. - Μπορείτε να απενεργοποιήσετε την αποστολή αναφορών σφαλμάτων στο παράθυρο διαλόγου ρυθμίσεων. - - - - - - We cannot gather useful debug information on your system. - Δεν μπορούμε να συλλέξουμε χρήσιμες πληροφορίες εντοπισμού σφαλμάτων στο σύστημά σας. - - - - - - - - Close - Κλείσιμο - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - Συλλογή πληροφοριών εντοπισμού σφαλμάτων... - - - - Uploaded %L1 of %L2 KB. - Μεταφορτώθηκαν %L1 από %L2 KB. - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - Αποτυχία αποστολής πληροφοριών σφάλματος. - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - Δεν ήταν δυνατή η δημιουργία καταλόγων σε απορρίμματα - - - - - Could not move '%1' to '%2' - Αδυναμία μετακίνησης του '%1' σε '%2' - - - - Moving to the trash is not implemented on this platform - Η μετακίνηση στα απορρίμματα δεν υποστηρίζεται σε αυτήν την πλατφόρμα - - - - Error removing '%1': %2 - Σφάλμα μετακίνησης '%1': %2 - - - - Could not remove folder '%1' - Αδυναμία αφαίρεσης του φακέλου '%1' - - - - FolderWizardSourcePage - - - Form - Φόρμα - - - - Pick a local folder on your computer to sync - Επιλέξτε έναν τοπικό φάκελο στον υπολογιστή σας προς συγχρονισμό - - - - &Choose... - &Επιλογή... - - - - FolderWizardTargetPage - - - Form - Φόρμα - - - - Select a remote destination folder - Επιλέξτε έναν απομακρυσμένο φάκελο προορισμού - - - - Create Folder - Δημιουργία Φακέλου - - - - Refresh - Ανανέωση - - - - Folders - Φάκελοι - - - - TextLabel - TextLabel - - - - NotificationWidget - - - Form - Φόρμα - - - - Lorem ipsum dolor sit amet - Ξεσκεπάζω την ψυχοφθόρα βδελυγμία - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Ξεσκεπάζω την ψυχοφθόρα βδελυγμία - - - - TextLabel - TextLabel - - - - OCC::AboutDialog - - - About - Περί - - - - TextLabel - TextLabel - - - - Versions - Εκδόσεις - - - - About %1 - Σχετικά %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Η σύνδεση έληξε. - - - - Unknown error: network reply was deleted - Άγνωστο σφάλμα: η απάντηση δικτύου διαγράφηκε - - - - Server replied "%1 %2" to "%3 %4" - Ο διακομιστής απάντησε "%1 %2" σε "%3 %4" - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - Φόρμα - - - - Re-open Browser - Άνοιγμα ξανά του προγράμματος πλοήγησης - - - - ... - ... - - - - Storage space: ... - Χώρος αποθήκευσης: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Οι μη επιλεγμένοι φάκελοι θα <b>αφαιρεθούν</ b> από το τοπικό σύστημα αρχείων σας και δεν θα συγχρονιστούν πια με αυτόν τον υπολογιστή - - - - Synchronize all - Συγχρονισμός όλων - - - - Synchronize none - Κανένας συγχρονισμός - - - - Apply manual changes - Χειροκίνητη αλλαγή εφαρμογών - - - - Apply - Εφαρμογή - - - - - - - Cancel - Άκυρο - - - - Connected with <server> as <user> - Συνδεδεμένοι με το <server> ως <user> - - - - No account configured. - Δεν ρυθμίστηκε λογαριασμός. - - - - Remove - Αφαίρεση - - - - Account - Λογαριασμός - - - - Choose what to sync - Επιλέξτε τι θα συγχρονιστεί - - - - Force sync now - Εξαναγκασμός συγχρονισμού τώρα - - - - Restart sync - Επανεκκίνηση συγχρονισμού - - - - Remove folder sync connection - Αφαίρεση σύνδεσης συγχρονισμού φακέλου - - - - Folder creation failed - Αποτυχία αφαίρεσης φακέλου - - - - <p>Could not create local folder <i>%1</i>. - <p>Αδυναμία δημιουργίας τοπικού φακέλου <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Επιβεβαίωση αφαίρεσης σύνδεσης συγχρονισμού φακέλου - - - - Remove Folder Sync Connection - Αφαίρεση σύνδεσης συγχρονισμού φακέλου - - - - Disable virtual file support? - Απενεργοποίηση υποστήριξης εικονικού αρχείου; - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - Αυτή η ενέργεια θα απενεργοποιήσει την υποστήριξη εικονικού αρχείου. Κατά συνέπεια, τα περιεχόμενα φακέλων που επισημαίνονται επί του παρόντος ως «διαθέσιμα μόνο σε σύνδεση» θα μεταφορτωθούν. - -Το μόνο πλεονέκτημα της απενεργοποίησης της υποστήριξης εικονικού αρχείου είναι ότι η λειτουργία επιλεκτικού συγχρονισμού θα είναι και πάλι διαθέσιμη. - -Αυτή η ενέργεια θα ακυρώσει οποιονδήποτε τρέχοντα συγχρονισμό. - - - - Disable support - Απενεργοποίηση υποστήριξης - - - - Sync Running - Εκτελείται Συγχρονισμός - - - - The syncing operation is running.<br/>Do you want to terminate it? - Η λειτουργία συγχρονισμού εκτελείται.<br/> Θέλετε να την τερματίσετε; - - - - %1 in use - %1 σε χρήση - - - - %1 as <i>%2</i> - %1 ως <i>%2</i> - - - - Connected to %1. - Συνδεδεμένο με %1. - - - - Server %1 is temporarily unavailable. - Ο διακομιστής %1 δεν είναι διαθέσιμος προσωρινά. - - - - Server %1 is currently in maintenance mode. - Ο διακομιστής %1 είναι προς το παρόν σε κατάσταση συντήρησης. - - - - Signed out from %1. - Αποσυνδέθηκε από %1. - - - - Connecting to %1... - Σύνδεση σε %1... - - - - No connection to %1 at %2. - Δεν υπάρχει σύνδεση με το %1 στο %2. - - - - Log in - Είσοδος - - - - There are folders that were not synchronized because they are too big: - Υπάρχουν φάκελοι που δεν συγχρονίστηκαν επειδή είναι πολύ μεγάλοι: - - - - There are folders that were not synchronized because they are external storages: - Υπάρχουν φάκελοι που δεν συγχρονίστηκαν επειδή είναι εξωτερικοί αποθηκευτικοί χώροι: - - - - There are folders that were not synchronized because they are too big or external storages: - Υπάρχουν φάκελοι που δεν συγχρονίστηκαν επειδή είναι πολύ μεγάλοι ή αποθηκευτικοί χώροι: - - - - Confirm Account Removal - Επιβεβαίωση Αφαίρεσης Λογαριασμού - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Θέλετε πραγματικά να αφαιρέσετε τη σύνδεση με το λογαριασμό <i>%1</i>;</p><p><b>Σημείωση:</b> Αυτό <b>δεν</b> θα διαγράψει κανένα αρχείο.</p> - - - - Remove connection - Αφαίρεση σύνδεσης - - - - Open folder - Άνοιγμα φακέλου - - - - - Log out - Αποσύνδεση - - - - Open local folder - Άνοιγμα τοπικού φακέλου - - - - Open folder in browser - Άνοιγμα φακέλου στον περιηγητή - - - - Resume sync - Συνέχιση συγχρονισμού - - - - Pause sync - Παύση συγχρονισμού - - - - Availability - Διαθεσιμότητα - - - - Disable virtual file support... - Απενεργοποίηση υποστήριξης εικονικού αρχείου... - - - - Enable virtual file support%1... - - - - - (experimental) - (πειραματικό) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Θέλετε πραγματικά να σταματήσετε το συγχρονισμό του φακέλου <i>%1</i>;</p><p><b>Σημείωση:</b> Αυτό <b>δεν</b> θα διαγράψει κανένα αρχείο.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) από %2 σε χρήση. Μερικοί φάκελοι, συμπεριλαμβανομένων των δικτυακών ή των κοινόχρηστων μπορεί να έχουν διαφορετικά όρια. - - - - %1 of %2 in use - %1 από %2 σε χρήση - - - - Currently there is no storage usage information available. - Προς το παρόν δεν υπάρχουν πληροφορίες χρήσης χώρου αποθήκευσης διαθέσιμες. - - - - The server version %1 is unsupported! Proceed at your own risk. - Η έκδοση διακομιστή %1 δεν υποστηρίζεται! Προχωρήστε με δική σας ευθύνη. - - - - Obtaining authorization from the browser. - - - - - Server configuration error: %1 at %2. - - - - - No %1 connection configured. - Δεν έχει ρυθμιστεί σύνδεση με το %1. - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - Απόρριψη - - - - Signed out - Αποσύνδεση - - - - Disconnected - Αποσυνδεδεμένο - - - - Connected - Συνδεδεμένο - - - - Service unavailable - Η υπηρεσία δεν είναι διαθέσιμη - - - - Maintenance mode - Λειτουργία συντήρησης - - - - Network error - Σφάλμα δικτύου - - - - Configuration error - Σφάλμα ρυθμίσεων - - - - Asking Credentials - Ερώτημα διαπιστευτηρίων - - - - Unknown account state - Άγνωστη κατάσταση λογαριασμού - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 στο %3 - - - - Activity - Δραστηριότητα - - - - Account - Λογαριασμός - - - - Time - Ώρα - - - - Local path - Τοπική διαδρομή - - - - OCC::ActivitySettings - - - - Server Activity - Δραστηριότητα διακομιστή - - - - Sync Protocol - Προτοκολο συγχρονισμου - - - - Not Synced - Δεν είναι συγχρονισμένα - - - - Not Synced (%1) - %1 is the number of not synced files. - Δεν είναι συγχρονισμένα (%1) - - - - OCC::ActivityWidget - - - Form - Φόρμα - - - - - - TextLabel - TextLabel - - - - Filter - Φίλτρο - - - - Server Activities - Δραστηριότητες διακομιστή - - - - Notifications - Ειδοποιήσεις - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - - - - - Open the activity view for details. - - - - - Copy to clipboard - Αντιγραφή στο πρόχειρο - - - - Show in file browser - Εμφάνιση στο πρόγραμμα περιήγησης αρχείων - - - - <br/>Account %1 does not have activities enabled. - <br/>Ο λογαριασμός %1 δεν έχει ενεργοποιήσει τις δραστηριότητες. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Η συνέχιση θα σημαίνει τη <b>διαγραφή αυτών των ρυθμίσεων</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Η συνέχιση θα σημαίνει την <b>παράβλεψη αυτών των ρυθμίσεων</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Ορισμένες ρυθμίσεις έχουν ρυθμιστεί σε νεότερες εκδόσεις αυτού του πελάτη και χρησιμοποιούν λειτουργίες που δεν είναι διαθέσιμες σε αυτή την έκδοση. <br><br>%1<br><br> Έχει ήδη δημιουργηθεί ήδη αντίγραφο ασφαλείας του τρέχοντος αρχείου ρυθμίσεων στο <i>%2</i>. - - - - Quit - Έξοδος - - - - Continue - Συνέχεια - - - - Error accessing the configuration file - Σφάλμα πρόσβασης στο αρχείο ρυθμίσεων - - - - There was an error while accessing the configuration file at %1. - Υπήρξε σφάλμα κατά την πρόσβαση του αρχείου ρυθμίσεων στο %1 - - - - Quit %1 - Κλείσιμο %1 - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - Ενεργοποίηση λειτουργίας εντοπισμού σφαλμάτων. - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - [<vfs file>] - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - Απαιτείται Πιστοποίηση - - - - Enter username and password for '%1' at %2. - Εισάγετε όνομα χρήστη και κωδικό πρόσβασης για το '%1' στο %2. - - - - &User: - &Χρήστης: - - - - &Password: - &Κωδικός Πρόσβασης: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Δεν έχει ρυθμιστεί λογαριασμός ownCloud - - - - The configured server for this client is too old - Ο ρυθμισμένος διακομιστής για αυτό το δέκτη είναι πολύ παλιός - - - - Please update to the latest server and restart the client. - Παρακαλώ ενημερώστε το διακομιστή στη νεώτερη έκδοση και επανεκκινήστε το δέκτη. - - - - Authentication error: Either username or password are wrong. - Σφάλμα Πιστοποίησης: Το όνομα χρήστη ή ο κωδικός πρόσβασης είναι λανθασμένα. - - - - timeout - παρέλευση χρονικού ορίου - - - - The provided credentials are not correct - Τα παρεχόμενα διαπιστευτήρια δεν είναι σωστά - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - Σφάλμα διακομιστή: Η απάντηση PROPFIND δεν έχει μορφοποιηθεί με XML! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - Σφάλμα κατά το άνοιγμα του καταλόγου %1 - - - - Directory not accessible on client, permission denied - Ο κατάλογος δεν είναι προσβάσιμος από τον πελάτη, άρνηση πρόσβασης - - - - Directory not found: %1 - Δεν βρέθηκε ο κατάλογος: %1 - - - - Filename encoding is not valid - Η κωδικοποίηση του ονόματος αρχείου δεν είναι έγκυρη - - - - Error while reading directory %1 - Σφάλμα κατά την ανάγνωση του καταλόγου %1 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - Επαναφορά μεγεθών στήλης - - - - OCC::Folder - - - Local folder %1 does not exist. - Δεν υπάρχει ο τοπικός φάκελος %1. - - - - %1 should be a folder but is not. - Το %1 θα έπρεπε να είναι φάκελος αλλά δεν είναι. - - - - %1 is not readable. - Το %1 δεν είναι αναγνώσιμο. - - - - %1 has been removed. - %1 names a file. - Το %1 αφαιρέθηκε. - - - - %1 has been updated. - %1 names a file. - Το %1 έχει ενημερωθεί. - - - - %1 has been renamed to %2. - %1 and %2 name files. - Το %1 έχει μετονομαστεί σε %2. - - - - %1 has been moved to %2. - Το %1 έχει μετακινηθεί στο %2. - - - - %1 and %n other file(s) have been removed. - %1 και%n άλλo αρχείo(α) έχουν καταργηθεί.%1 και%n άλλo αρχείo(α) έχουν καταργηθεί. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - %1 και %n άλλο(-α) αρχείο(-α) έχουν προστεθεί.%1 και %n άλλο(-α) αρχείο(-α) έχουν προστεθεί. - - - - %1 has been added. - %1 names a file. - %1 προστέθηκαν. - - - - %1 and %n other file(s) have been updated. - %1 και%n άλλο αρχείο(α) έχουν ενημερωθεί.%1 και%n άλλο αρχείο(α) έχουν ενημερωθεί. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 μετονομάστηκε σε %2 και %n άλλο αρχείο(α) έχουν μετονομαστεί.%1 μετονομάστηκε σε %2 και %n άλλο αρχείο(α) έχουν μετονομαστεί. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 έχει μετακινηθεί σε %2 και %n άλλo αρχείο(α) έχουν μετακινηθεί.%1 έχει μετακινηθεί σε %2 και %n άλλo αρχείο(α) έχουν μετακινηθεί. - - - - %1 has and %n other file(s) have sync conflicts. - %1 έχει και %n άλλο αρχείο(α) έχουν διένεξη συγχρονισμού.%1 έχει και %n άλλο αρχείο(α) έχουν διένεξη συγχρονισμού. - - - - %1 has a sync conflict. Please check the conflict file! - %1 έχει μια διένεξη συγχρονισμού. Παρακαλώ ελέγξτε τη διένεξη του αρχείου! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 και %n άλλο(α) αρχείο(α) δεν μπορούν να συγχρονιστούν λόγω σφαλμάτων. Δείτε το ιστορικό για λεπτομέρειες%1 και %n άλλο αρχείο(α) δεν μπορούν να συγχρονιστούν λόγω σφαλμάτων. Δείτε το ημερολόγιο για λεπτομέρειες. - - - - %1 could not be synced due to an error. See the log for details. - %1 δεν ήταν δυνατό να συγχρονιστεί εξαιτίας ενός σφάλματος. Δείτε το αρχείο καταγραφής για λεπτομέρειες. - - - - Sync Activity - Δραστηριότητα Συγχρονισμού - - - - Could not read system exclude file - Αδυναμία ανάγνωσης αρχείου αποκλεισμού συστήματος - - - - A new folder larger than %1 MB has been added: %2. - - Προστέθηκε ένας νέος φάκελος μεγαλύτερος από %1 MB: %2 - - - - - A folder from an external storage has been added. - - Προστέθηκε ένας φάκελος από εξωτερικό αποθηκευτικό χώρο. - - - - - Please go in the settings to select it if you wish to download it. - Μεταβείτε στις ρυθμίσεις για να το επιλέξετε εάν επιθυμείτε να το κατεβάσετε. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - Ο φάκελος %1 δημιουργήθηκε αλλά εξαιρέθηκε προηγουμένως από συγχρονισμό. Τα δεδομένα στο εσωτερικό του δεν θα συγχρονιστούν. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - Το αρχείο %1 δημιουργήθηκε αλλά εξαιρέθηκε προηγουμένως από συγχρονισμό. Δεν θα συγχρονιστεί. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - Δεν ήταν δυνατή η αξιόπιστη παρακολούθηση των αλλαγών στους συγχρονισμένους φακέλους. - -Αυτό σημαίνει ότι ο πελάτης συγχρονισμού ενδέχεται να μην μεταφορτώνει άμεσα τις τοπικές αλλαγές και αντίθετα, θα ανιχνεύει μόνο τοπικές αλλαγές και θα τις ανεβάζει περιστασιακά (κάθε δύο ώρες από προεπιλογή). - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Όλα τα αρχεία στο φάκελο συγχρονισμού '%1' έχουν διαγραφεί στον διακομιστή. -Αυτές οι διαγραφές θα συγχρονιστούν στον τοπικό σας φάκελο συγχρονισμού, κάνοντας τέτοιου είδους αρχεία μη διαθέσιμα, εκτός εάν έχετε δικαίωμα επαναφοράς. -Εάν αποφασίσετε να διατηρήσετε αυτά τα αρχεία, θα συγχρονιστούν εκ νέου με το διακομιστή, εφόσον έχετε δικαιώματα να το κάνετε. -Εφόσον επιλέξετε να διαγράψετε τα αρχεία, δε θα είναι διαθέσιμα σε εσάς, εκτός εάν είστε ο ιδιοκτήτης τους. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Όλα τα αρχεία στον τοπικό φάκελο συγχρονισμού σας '% 1' διαγράφηκαν. Αυτές οι διαγραφές θα συγχρονιστούν με το διακομιστή σας, καθιστώντας αυτά τα αρχεία μη διαθέσιμα εκτός εάν τα έχετε επαναφέρει. -Είστε βέβαιοι ότι θέλετε να συγχρονίσετε αυτές τις ενέργειες με το διακομιστή; -Αν αυτό ήταν ένα ατύχημα και αποφασίσατε να διατηρήσετε τα αρχεία σας, θα συγχρονιστούν εκ νέου από το διακομιστή. - - - - Remove All Files? - Αφαίρεση Όλων των Αρχείων; - - - - Remove all files - Αφαίρεση όλων των αρχείων - - - - Keep files - Διατήρηση αρχείων - - - - OCC::FolderMan - - - Could not reset folder state - Δεν ήταν δυνατό να επαναφερθεί η κατάσταση του φακέλου - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Βρέθηκε ένα παλαιότερο αρχείο συγχρονισμού '%1', αλλά δεν μπόρεσε να αφαιρεθεί. Παρακαλώ βεβαιωθείτε ότι καμμία εφαρμογή δεν το χρησιμοποιεί αυτή τη στιγμή. - - - - (backup) - (αντίγραφο ασφαλείας) - - - - (backup %1) - (αντίγραοφ ασφαλέιας %1) - - - - Undefined State. - Απροσδιόριστη Κατάσταση. - - - - Waiting to start syncing. - Αναμονή έναρξης συγχρονισμού. - - - - Preparing for sync. - Προετοιμασία για συγχρονισμό. - - - - Sync is running. - Ο συγχρονισμός εκτελείται. - - - - Sync was successful, unresolved conflicts. - Επιτυχημένος συγχρονισμός, ανεπίλυτες συγκρούσεις. - - - - Last Sync was successful. - Ο τελευταίος συγχρονισμός ήταν επιτυχής. - - - - Setup Error. - Σφάλμα Ρύθμισης. - - - - User Abort. - Ματαίωση από Χρήστη. - - - - Sync is paused. - Παύση συγχρονισμού. - - - - %1 (Sync is paused) - %1 (Παύση συγχρονισμού) - - - - No valid folder selected! - Δεν επιλέχθηκε έγκυρος φάκελος! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - Η επιλεγμένη διαδρομή δεν υπάρχει! - - - - The selected path is not a folder! - Η επιλεγμένη διαδρομή δεν είναι φάκελος! - - - - You have no permission to write to the selected folder! - Δεν έχετε δικαιώματα εγγραφής στον επιλεγμένο φάκελο! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Υπάρχει ήδη συγχρονισμός από το διακομιστή σε αυτόν τον τοπικό φάκελο. Επιλέξτε έναν άλλο τοπικό φάκελο! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Ο τοπικός φάκελος %1 περιέχει ήδη ένα φάκελο που χρησιμοποιείται σε μια σύνδεση συγχρονισμού φακέλου. Παρακαλώ επιλέξτε άλλον! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Ο τοπικός φάκελος %1 περιέχεται ήδη σε φάκελο που χρησιμοποιείται σε μια σύνδεση συγχρονισμού. Παρακαλώ επιλέξτε άλλον! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Προσθήκη σύνδεσης συγχρονισμού φακέλου - - - - File - Αρχείο - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Πρέπει να έχετε συνδεθεί για να προσθέσετε φάκελο - - - - Click this button to add a folder to synchronize. - Κάντε κλικ σε αυτό το κουμπί για να προσθέσετε ένα φάκελο προς συγχρονισμό. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Σφάλμα κατά τη φόρτωση της λίστας φακέλων από το διακομιστή. - - - - Virtual file support is enabled. - Η υποστήριξη για το εικονικό αρχείο είναι ενεργοποιημένη. - - - - Signed out - Αποσύνδεση - - - - Fetching folder list from server... - Λήψη λίστας φακέλων από το διακομιστή... - - - - There are unresolved conflicts. Click for details. - Αυτές είναι οι ανεπίλυτες διενέξεις. Κάντε κλικ για λεπτομέρειες. - - - - Checking for changes in remote '%1' - Έλεγχος για αλλαγές στο απομακρυσμένο '%1' - - - - Checking for changes in local '%1' - Έλεγχος για αλλαγές στο τοπικό '%1' - - - - Reconciling changes - Συνδυασμός αλλαγών - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Συγχρονισμός %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - λήψη %1/s - - - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - μεταφόρτωση %1/s - - - - Local folder: %1 - Τοπικός φάκελος: %1 - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 από %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - Απομένει %5, %1 από %2, αρχείο %3 από %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 από %2, αρχείο %3 από %4 - - - - file %1 of %2 - αρχείο %1 από %2 - - - - Waiting... - Αναμονή... - - - - Waiting for %n other folder(s)... - Αναμονή για %n άλλο φάκελο...Αναμονή για %n άλλους φακέλους... - - - - Preparing to sync... - Προετοιμασία για συγχρονισμό... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - Ο παρατηρητής δεν έλαβε μια ειδοποίηση δοκιμής. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Αυτό το πρόβλημα συμβαίνει συνήθως όταν εξαντληθούν τα ρολόγια inotify. Ελέγξτε τις Συχνές Ερωτήσεις (FAQ) για λεπτομέρειες. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Προσθήκη Σύνδεσης Συγχρονισμού Φακέλου - - - - Add Sync Connection - Προσθήκη Σύνδεσης Συγχρονισμού - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Κλικάρετε για να επιλέξετε έναν τοπικό φάκελο προς συγχρονισμό. - - - - Enter the path to the local folder. - Εισάγετε τη διαδρομή προς τον τοπικό φάκελο. - - - - Select the source folder - Επιλογή του φακέλου προέλευσης - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Δημιουργία Απομακρυσμένου Φακέλου - - - - Enter the name of the new folder to be created below '%1': - Εισάγετε το όνομα του νέου φακέλου που θα δημιουργηθεί παρακάτω '%1': - - - - Folder was successfully created on %1. - Επιτυχής δημιουργία φακέλου στο %1. - - - - Authentication failed accessing %1 - Η πιστοποίηση απέτυχε κατά την πρόσβαση %1 - - - - Failed to create the folder on %1. Please check manually. - Αποτυχία δημιουργίας φακέλου στο %1. Παρακαλώ ελέγξτε χειροκίνητα. - - - - Failed to list a folder. Error: %1 - Αδυναμία εμφάνισης ενός φακέλου. Σφάλμα: %1 - - - - Choose this to sync the entire account - Επιλέξτε να συγχρονίσετε ολόκληρο το λογαριασμό - - - - This folder is already being synced. - Αυτός ο φάκελος συγχρονίζεται ήδη. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Ο φάκελος <i>%1</i>, ο οποίος είναι γονεϊκός φάκελος του <i>%2</i>, συγχρονίζεται ήδη. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - (πειραματικό) - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Προειδοποίηση:</b> %1 - - - - <b>Warning:</b> - <b>Προειδοποίηση:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Δεν ελήφθη ηλεκτρονική ετικέτα από το διακομιστή, ελέγξτε το διακομιστή μεσολάβησης/πύλη - - - - We received a different E-Tag for resuming. Retrying next time. - Ελήφθη διαφορετική ηλεκτρονική ετικέτα για συνέχιση. Επανάληψη την επόμενη φορά. - - - - We received an unexpected download Content-Length. - Λάβαμε μια μη αναμενόμενη λήψη περιεχομένου-μήκους. - - - - Server returned wrong content-range - Ο διακομιστής επέστρεψε εσφαλμένο πεδίο τιμών - - - - OCC::GETJob - - - Connection Timeout - Λήξη Χρόνου Αναμονής Σύνδεσης - - - - OCC::GeneralSettings - - - Form - Φόρμα - - - - General Settings - Γενικές Ρυθμίσεις - - - - Language - Γλώσσα - - - - Sync hidden files - Συγχρονισμός κρυφών αρχείων - - - - Log Settings - Ρυθμίσεις ιστορικού - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Δίκτυο - - - - Updates - Ενημερώσεις - - - - stable - σταθερή - - - - beta - beta - - - - About - Περί - - - - Advanced - Για προχωρημένους - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Ζητήστε επιβεβαίωση πριν τον συγχρονισμό φακέλων μεγαλύτερων από - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - ΜΒ - - - - Ask for confirmation before synchronizing external storages - Ζητήστε επιβεβαίωση πριν τον συγχρονισμό εξωτερικών αποθηκευτικών χώρων - - - - &Launch on System Startup - &Προώθηση κατά την Εκκίνηση Συστήματος - - - - Show &Desktop Notifications - Εμφάνιση &Ειδοποιήσεις επιφάνειας εργασίας - - - - &Update Channel - &Ενημέρωση καναλιού - - - - Edit &Ignored Files - Εισαγωγή & Αγνοούμενα Αρχεία - - - - Show sync folders in &Explorer's Navigation Pane - Εμφάνιση φακέλων συγχρονισμού στο &Πίνακα περιήγησης του διαχειριστή αρχείων - - - - S&how crash reporter - S&πως κατάρρευση αναφοράς - - - - &Restart && Update - &Επανεκκίνηση && Ενημέρωση - - - - You cannot disable autostart because system-wide autostart is enabled. - Δεν μπορείτε να απενεργοποιήσετε την αυτόματη εκκίνηση επειδή είναι ενεργοποιημένη η αυτόματη εκκίνηση σε επίπεδο συστήματος. - - - - Change update channel? - Αλλαγή καναλιού ενημέρωσης; - - - - Warning - Προειδοποίηση - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Το κανάλι ενημέρωσης καθορίζει ποιες ενημερώσεις πελατών θα προσφέρονται για εγκατάσταση. Το "σταθερό" κανάλι περιέχει μόνο αναβαθμίσεις που θεωρούνται αξιόπιστες, ενώ οι εκδόσεις στο κανάλι "beta" ενδέχεται να περιέχουν νεότερες λειτουργίες και διορθώσεις σφαλμάτων, αλλά δεν έχουν ακόμη δοκιμαστεί πλήρως. - -Σημειώστε ότι αυτό επιλέγει μόνο από ποιες αναβαθμίσεις έχουν ληφθεί και ότι δεν υπάρχουν υποβαθμίσεις: Έτσι, η επιστροφή από το κανάλι beta στο σταθερό κανάλι συνήθως δεν μπορεί να γίνει αμέσως και σημαίνει αναμονή για μια σταθερή έκδοση που είναι νεότερη από την τρέχουσα beta έκδοση. - - - - Change update channel - Αλλαγή καναλιού ενημέρωσης - - - - Cancel - Ακύρωση - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Παρακαλώ εισάγετε %1 κωδικού πρόσβασης:<br><br>Χρήστης: %2<br>Λογαριασμός: %3<br> - - - - Reading from keychain failed with error: '%1' - Η ανάγνωση από την κλειδοθήκη απέτυχε με σφάλμα: '%1' - - - - Enter Password - Εισάγετε Κωδικό Πρόσβασης - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">πατήστε εδώ</a>για να ζητήσετε έναν κωδικό πρόσβασης εφαρμογής από τη διεπαφή ιστού. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Επεξεργαστής Αγνοημένων Αρχείων - - - - Files Ignored by Patterns - Αρχεία που αγνοούνται από τα πρότυπα - - - - Add - Προσθήκη - - - - Pattern - Πρότυπα - - - - Allow Deletion - Να πειτρέπεται η διαγραφή - - - - Remove - Αφαίρεση - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Τα αρχεία ή οι φάκελοι που ταιριάζουν με ένα μοτίβο δεν θα συγχρονιστούν. Οι αλλαγές αρχίζουν να ισχύουν την επόμενη φορά που θα συγχρονίζονται οι φάκελοι. - -Τα στοιχεία όπου επιτρέπεται η διαγραφή θα διαγραφούν αν αποτρέψουν την αφαίρεση ενός καταλόγου. Αυτό είναι χρήσιμο για μεταδεδομένα. - - - - Could not open file - Αδυναμία ανοίγματος αρχείου - - - - Cannot write changes to '%1'. - Αδυναμία εγγραφής αλλαγών στο '%1'. - - - - Add Ignore Pattern - Προσθήκη Προτύπου Αγνόησης - - - - Add a new ignore pattern: - Προσθήκη νέου προτύπου αγνόησης: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Αυτή η είσοδος παρέχεται από το σύστημα στο '%1' και δεν μπορεί να τροποποιηθεί σε αυτή την προβολή. - - - - OCC::IssuesWidget - - - Form - Φόρμα - - - - List of issues - Λίστα των θεμάτων - - - - Filter - Φίλτρο - - - - There were too many issues. Not all will be visible here. - Υπήρχαν πάρα πολλά ζητήματα. Δεν θα είναι ορατά όλες εδώ. - - - - There were conflicts. Check the documentation on how to resolve them. - Υπήρχαν συγκρούσεις. Ελέγξτε την τεκμηρίωση σχετικά με τον τρόπο επίλυσής τους. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - Επαναφορά μεγεθών στήλης - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Υπήρχαν συγκρούσεις. <a href="%1">Ελέγξτε την τεκμηρίωση σχετικά με τον τρόπο επίλυσής τους.</a> - - - - Account - Λογαριασμός - - - - OCC::LibCloudProvidersPrivate - - - Settings - Ρυθμίσεις - - - - OCC::LogBrowser - - - Log Output - Καταγραφή Εξόδου - - - - Enable logging to temporary folder - Ενεργοποίηση της καταγραφής σε προσωρινό φάκελο - - - - Delete logs older than %1 hours - Αρχεία καταγραφής παλαιότερα των %1 ωρών, να διαγράφονται - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - C:/log - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Αυτές οι ρυθμίσεις παραμένουν σε όλες τις επανεκκινήσεις πελάτη. -Σημειώστε ότι η χρήση οποιασδήποτε επιλογής γραμμής εντολών καταγραφής θα παρακάμψει τις ρυθμίσεις. - - - - Open folder - Άνοιγμα φακέλου - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Νέα Έκδοση Διαθέσιμη - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Μια νέα έκδοση του %1 Δέκτη είναι διαθέσιμη.</p><p><b>%2</b> είναι διαθέσιμη για λήψη. Η εγκατεστημένη έκδοση είναι %3.</p> - - - - - Skip this version - Παράλειψη αυτής της έκδοσης - - - - Skip this time - Παράλειψη αυτή τη φορά - - - - Get update - Λήψη ενημέρωσης - - - - Update Failed - Απέτυχε η ενημέρωση - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Μια νέα έκδοση του πελάτη %1 είναι διαθέσιμη αλλά η διαδικασία ενημέρωσης απέτυχε.</p><p>Το <b>%2</b> έχει ληφθεί. Η εγκατεστημένη έκδοση είναι %3.</p> - - - - Ask again later - Να γίνει ερώτηση ξανά αργότερα - - - - Restart and update - Επανεκκίνηση και ενημέρωση - - - - Update manually - Χειροκίνητη ενημέρωση - - - - OCC::NetworkSettings - - - Form - Φόρμα - - - - Proxy Settings - Ρυθμίσεις Ενδιάμεσου Διακομιστή - - - - No Proxy - Χωρίς Ενδιάμεσο Διακομιστή - - - - Use system proxy - Χρήση ενδιάμεσου διακομιστή συστήματος - - - - Specify proxy manually as - Προσδιορίστε ενδιάμεσο διακομιστή χειροκίνητα ως - - - - Host - Διακομιστής - - - - : - : - - - - Proxy server requires authentication - Ο ενδιάμεσος διακομιστής απαιτεί πιστοποίηση - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Ταχύτητα λήψεως - - - - - Limit to - Περιορισμός σε - - - - - KBytes/s - KBytes/s - - - - - No limit - Χωρίς όριο - - - - - Limit to 3/4 of estimated bandwidth - Περιορισμός στα 3/4 της εκτιμώμενης ταχύτητας σύνδεσης - - - - Upload Bandwidth - Ταχύτητα Μεταφόρτωσης - - - - - Limit automatically - Αυτόματος περιορισμός - - - - Hostname of proxy server - Όνομα συστήματος του ενδιάμεσου διακομιστή - - - - Username for proxy server - Όνομα χρήστη για τον ενδιάμεσο διακομιστή - - - - Password for proxy server - Κωδικός πρόσβασης για τον ενδιάμεσο διακομιστή - - - - HTTP(S) proxy - Ενδιάμεσος HTTP(S) - - - - SOCKS5 proxy - Ενδιάμεσος SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - Ολοκληρώθηκε στο %1 - - - - Closing in a few seconds... - Κλείσιμο σε λίγα δευτερόλεπτα... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - Το αίτημα% 1 απέτυχε στο% 2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '% 1' επιλέχθηκε στο% 2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Σφάλμα που επέστρεψε από το διακομιστή: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Παρουσιάστηκε ένα σφάλμα κατά την πρόσβαση στο τελικό σημείο του 'διακριτικού': <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Αδυναμία ανάλυσης του JSON που επέστρεψε από το διακομιστή: <br><em>%1</em> - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - Άγνωστο σφάλμα - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Oauth2 Error - Σφάλμα Oauth2 - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Σφάλμα εισόδου</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Λάθος χρήστης</h1><p> Συνδεθήκατε με το χρήστη <em>%1</em>, αλλά πρέπει να συνδεθείτε με το χρήστη <em>%2</em>. <br>Παρακαλούμε αποσυνδεθείτε από τον %3 σε άλλη καρτέλα, στη συνέχεια <a href='%4'>κάντε κλικ εδώ</a> και συνδεθείτε ως χρήστης %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Νέα %1 Ενημέρωση Έτοιμη - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Μια νέα ενημέρωση για %1 πρόκειται να εγκατασταθεί. Ο βοηθός ενημέρωσης μπορεί να ζητήσει -επιπλέον παραχωρήσεις δικαιωμάτων κατά τη διαδικασία. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Αδυναμία λήψης της ενημέρωσης. Παρακαλώ κλικάρετε <a href='%1'>εδώ</a> για να κατεβάσετε την ενημέρωση χειροκίνητα. - - - - Could not check for new updates. - Αδυναμία ελέγχου για νέες ενημερώσεις. - - - - Downloading %1. Please wait... - Γίνεται λήψη %1. Παρακαλούμε περιμένετε... - - - - %1 available. Restart application to start the update. - %1 διαθέσιμη. Κάντε επανεκκίνηση της εφαρμογής για να ξεκινήσετε την ενημέρωση. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Υπάρχει διαθέσιμο νέο %1. Χρησιμοποιήστε το εργαλείο ενημέρωσης του συστήματος για να το εγκαταστήσετε. - - - - Checking update server... - Έλεγχος διακομιστή ενημερώσεων... - - - - Update status is unknown: Did not check for new updates. - Η κατάσταση ενημέρωσης είναι άγνωστη: Δεν έγινε έλεγχος για για νέες ενημερώσεις. - - - - No updates available. Your installation is at the latest version. - Δεν υπάρχουν ενημερώσεις διαθέσιμες. Η εγκατάστασή σας βρίσκεται στην τελευταία έκδοση. - - - - Update Check - Έλεγχος για ενημερώσεις - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Σύνδεση με %1 - - - - Setup local folder options - Ρύθμιση επιλογών τοπικών φακέλων - - - - Connect... - Σύνδεση... - - - - (experimental) - (πειραματικό) - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Συγχρονισμός φακέλου '%1' - - - - Virtual files are not available for the selected folder - - - - - Error - Σφάλμα - - - - Local Sync Folder - Τοπικός Φάκελος Συγχρονισμού - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Ηλεκτρονικό Ταχυδρομείο - - - - Connect to %1 - Σύνδεση με %1 - - - - Enter user credentials - Εισάγετε διαπιστευτήρια χρήστη - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Σύνδεση με %1 - - - - Login in your browser - Είσοδος στο πρόγραμμα περιήγησης - - - - Copy link to clipboard - Αντιγραφή συνδέσμου στο πρόχειρο - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Σύνδεση με %1 - - - - Setup %1 server - Ρύθμιση %1 διακομιστή - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Αυτή η ιστοσελίδα ΔΕΝ είναι ασφαλής καθώς δεν είναι κρυπτογραφημένη. -Δεν προτείνεται να τη χρησιμοποιήσετε. - - - - This url is secure. You can use it. - Αυτή η url είναι ασφαλής. Μπορείτε να τη χρησιμοποιήσετε. - - - - &Next > - &Επόμενο > - - - - Invalid URL - Μη έγκυρη URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Αποτυχία σύνδεσης με το %1 στο %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Λήξη χρονικού ορίου κατά τη σύνδεση σε %1 σε %2. - - - - Invalid URL - Μη έγκυρη URL - - - - Could not create local folder %1 - Αδυναμία δημιουργίας τοπικού φακέλου %1 - - - - Error: %1 - Σφάλμα: %1 - - - - The folder creation resulted in HTTP error code %1 - Η δημιουργία φακέλου είχε ως αποτέλεσμα τον κωδικό σφάλματος HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Η δημιουργία απομακρυσμένου φακέλλου απέτυχε επειδή τα διαπιστευτήρια είναι λάθος!<br/>Παρακαλώ επιστρέψετε και ελέγξετε τα διαπιστευτήριά σας.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Η δημιουργία απομακρυσμένου φακέλου %1 απέτυχε με σφάλμα <tt>%2</tt>. - - - - Folder rename failed - Αποτυχία μετονομασίας φακέλου - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Αδυναμία αφαίρεσης και δημιουργίας αντιγράφου ασφαλείας του φακέλου διότι ο φάκελος ή ένα αρχείο του είναι ανοικτό από άλλο πρόγραμμα. Παρακαλώ κλείστε τον φάκελο ή το αρχείο και πατήστε επανάληψη ή ακυρώστε την ρύθμιση. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Οδηγός Σύνδεσης - - - - Enable experimental feature? - Ενεργοποίηση πειραματικής λειτουργίας; - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Όταν είναι ενεργοποιημένη η λειτουργία "εικονικά αρχεία" δεν θα ληφθούν αρχικά αρχεία. Αντ 'αυτού, θα δημιουργηθεί ένα μικροσκοπικό αρχείο "%1" για κάθε αρχείο που υπάρχει στο διακομιστή. Τα περιεχόμενα μπορεί να ληφθούν εκτελώντας αυτά τα αρχεία ή χρησιμοποιώντας το μενού περιβάλλοντος. - -Η λειτουργία εικονικών αρχείων είναι αμοιβαία αποκλειστική με επιλεκτικό συγχρονισμό. Οι φάκελοι που δεν έχουν επιλεγεί προς το παρόν θα μεταφραστούν σε φακέλους στο διαδίκτυο και οι επιλεγμένες ρυθμίσεις συγχρονισμού θα επαναρυθμιστούν. - -Εάν μεταβείτε σε αυτήν τη λειτουργία, θα ακυρωθεί οποιοσδήποτε τρέχων συγχρονισμός. - -Πρόκειται για μια νέα, πειραματική λειτουργία. Αν αποφασίσετε να την χρησιμοποιήσετε, αναφέρετε τυχόν ζητήματα που προκύπτουν. - - - - Stay safe - Μείνετε ασφαλείς - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Οι συμβολικού σύνδεσμοι δεν υποστηρίζονται για το συγχρονισμό. - - - - File is listed on the ignore list. - Το αρχείο περιέχεται στη λίστα αρχείων προς αγνόηση. - - - - File names ending with a period are not supported on this file system. - Τα ονόματα αρχείων που διαρκούν μια ορισμένη χρονική περίοδο δεν υποστηρίζονται σε αυτό το σύστημα αρχείων. - - - - File names containing the character '%1' are not supported on this file system. - Τα ονόματα αρχείων που περιέχουν τον χαρακτήρα '% 1' δεν υποστηρίζονται σε αυτό το σύστημα αρχείων. - - - - File name contains at least one invalid character - Το όνομα αρχείου περιέχει τουλάχιστο ένα μη έγκυρο χαρακτήρα - - - - The file name is a reserved name on this file system. - Το όνομα αρχείου είναι ένα κατοχυρωμένο όνομα σε αυτό το σύστημα αρχείων. - - - - Filename contains trailing spaces. - Το όνομα του αρχείου περιέχει συνεχόμενα κενά. - - - - Filename is too long. - Το όνομα αρχείου είνια πολύ μεγάλο. - - - - File/Folder is ignored because it's hidden. - Το Αρχείο/ο Φάκελος αγνοήθηκε επειδή είναι κρυφό. - - - - Stat failed. - Απέτυχε. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Σύγκρουση: Έγινε λήψη της έκδοσης του διακομιστή, το τοπικό αντίγραφο μετονομάστηκε και δεν μεταφορτώθηκε. - - - - The filename cannot be encoded on your file system. - Το όνομα αρχείου δεν μπορεί να κωδικοποιηθεί στο σύστημα αρχείων σας. - - - - The filename is blacklisted on the server. - Το όνομα αρχείου είναι στην μαύρη λίστα του διακομιστή. - - - - size - μέγεθος - - - - permissions - δικαιώματα - - - - etag - ηλεκτρονική ετικέτα - - - - file id - id αρχείου - - - - server reported no %1 - ο διακομιστής ανέφερε όχι %1 - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - Δεν επιτρέπεται η αφαίρεση, αποκατάσταση σε εξέλιξη - - - - Ignored because of the "choose what to sync" blacklist - Αγνοήθηκε εξαιτίας της μαύρης λίστας "διάλεξε τι να συγχρονιστεί" - - - - Not allowed because you don't have permission to add subfolders to that folder - Δεν επιτρέπεται επειδή δεν έχετε δικαιώματα να προσθέσετε υποφακέλους σε αυτό τον φάκελο - - - - Not allowed because you don't have permission to add files in that folder - Δεν επιτρέπεται επειδή δεν έχετε δικαιώματα να προσθέσετε αρχεία σε αυτόν τον φάκελο - - - - Not allowed to upload this file because it is read-only on the server, restoring - Δεν επιτρέπεται να μεταφορτώσετε αυτό το αρχείο επειδή είναι μόνο για ανάγνωση στο διακομιστή, αποκατάσταση σε εξέλιξη - - - - Error while reading the database - Σφάλμα κατά την ανάγνωση της βάσης δεδομένων - - - - Server replied with an error while reading directory '%1' : %2 - Ο διακομιστής απάντησε με σφάλμα κατά την ανάγνωση του καταλόγου '%1' : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - Η λήψη θα μειώσει τον ελεύθερο χώρο τοπικού δίσκου κάτω από το όριο - - - - Free space on disk is less than %1 - Ο διαθέσιμος χώρος στο δίσκο είναι λιγότερος από %1 - - - - File was deleted from server - Το αρχείο διαγράφηκε από τον διακομιστή - - - - The file could not be downloaded completely. - Η λήψη του αρχείου δεν ολοκληρώθηκε. - - - - The downloaded file is empty despite the server announced it should have been %1. - Το ληφθέν αρχείο είναι άδειο, παρόλο που ο διακομιστής ανακοίνωσε ότι θα έπρεπε να ήταν% 1. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - Το αρχείο έχει αλλάξει από όταν ανακαλύφθηκε - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Η Αποκατάσταση Απέτυχε: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - αδυναμία διαγραφής αρχείου %1, σφάλμα: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - αδυναμία δημιουργίας φακέλου %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Το αρχείο %1 δεν είναι δυνατό να μετονομαστεί σε %2 λόγω μιας διένεξης με το όνομα ενός τοπικού αρχείου - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - Αποτυχία μετονομασίας αρχείου - - - - Error setting pin state - Σφάλμα στη ρύθμιση της κατάστασης pin - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Ο διακομιστής επέστρεψε εσφαλμένο κωδικό HTTP. Αναμενόταν 204, αλλά ελήφθη "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Ο διακομιστής επέστρεψε εσφαλμένο κωδικό HTTP. Αναμενόταν 201, αλλά ελήφθη "%1 %2". - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - Αδυναμία μετονομασίας %1 σε %2, σφάλμα: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Ο διακομιστής επέστρεψε εσφαλμένο κωδικό HTTP. Αναμενόταν 201, αλλά ελήφθη "%1 %2". - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - Σφάλμα εγγραφής μεταδεδομένων στην βάση δεδομένων - - - - Error setting pin state - Σφάλμα στη ρύθμιση της κατάστασης pin - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Το αρχείο % 1 δεν μπορεί να ανέβει επειδή υπάρχει ένα άλλο αρχείο με το ίδιο όνομα, που διαφέρει μόνο στη περίπτωση, - - - - File Removed - Το αρχείο αφαιρέθηκε - - - - Local file changed during syncing. It will be resumed. - Το τοπικό αρχείο τροποποιήθηκε κατά τη διάρκεια του συγχρονισμού. Θα συγχρονιστεί πάλι. - - - - Local file changed during sync. - Το τοπικό αρχείο τροποποιήθηκε κατά τον συγχρονισμό. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - Η μεταφόρτωση του %1 υπερβαίνει το μέγεθος του φακέλου - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Το τοπικό αρχείο αφαιρέθηκε κατά το συγχρονισμό. - - - - Local file changed during sync. - Το τοπικό αρχείο τροποποιήθηκε κατά τον συγχρονισμό. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - Ο διακομιστής επέστρεψε απροσδόκητο κωδικό (%1) - - - - Missing File ID from server - Απουσία ID αρχείου από τον διακομιστή - - - - Missing ETag from server - Απουσία ETag από τον διακομιστή - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Το τοπικό αρχείο αφαιρέθηκε κατά το συγχρονισμό. - - - - Local file changed during sync. - Το τοπικό αρχείο τροποποιήθηκε κατά τον συγχρονισμό. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - Το τοπικό αρχείο αφαιρέθηκε κατά το συγχρονισμό. - - - - Local file changed during sync. - Το τοπικό αρχείο τροποποιήθηκε κατά τον συγχρονισμό. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Ο διακομιστής δεν αναγνώρισε το τελευταίο τμήμα. (Δεν υπήρχε e-tag) - - - - OCC::ProtocolItemModel - - - Time - Ώρα - - - - File - Αρχείο - - - - Folder - Φάκελος - - - - Issues - - - - - Action - Ενέργεια - - - - Size - Μέγεθος - - - - Account - Λογαριασμός - - - - Status - Κατάσταση - - - - OCC::ProtocolWidget - - - Form - Φόρμα - - - - Local sync protocol - Πρωτόκολλο τοπικού συγχρονισμού - - - - Filter - Φίλτρο - - - - Account - Λογαριασμός - - - - Copy to clipboard - Αντιγραφή στο πρόχειρο - - - - Show in file browser - Εμφάνιση στο πρόγραμμα περιήγησης αρχείων - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Απαιτείται πιστοποίηση στον διαμεσολαβητή - - - - Username: - Όνομα χρήστη: - - - - Proxy: - Διαμεσολαβητής: - - - - The proxy server needs a username and password. - Ο διαμεσολαβητής απαιτεί όνομα χρήστη και κωδικό. - - - - Password: - Κωδικός Πρόσβασης: - - - - TextLabel - TextLabel - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Επιλέξτε Τι θα Συγχρονιστεί - - - - OCC::SelectiveSyncWidget - - - Loading ... - Φόρτωση ... - - - - Deselect remote folders you do not wish to synchronize. - Απορρίψτε τους απομακρυσμένους φακέλους που δεν θέλετε να συγχρονιστούν. - - - - Name - Όνομα - - - - Size - Μέγεθος - - - - - No subfolders currently on the server. - Δεν υπάρχουν υποφάκελοι αυτή τη στιγμή στον διακομιστή. - - - - An error occurred while loading the list of sub folders. - Παρουσιάστηκε σφάλμα κατά την φόρτωση της λίστας των υπο-φακέλων - - - - OCC::ServerNotificationHandler - - - Dismiss - Απόρριψη - - - - OCC::SettingsDialog - - - Settings - Ρυθμίσεις - - - - Activity - Δραστηριότητα - - - - Add account - Προσθήκη λογαριασμού - - - - - Quit %1 - Κλείσιμο %1 - - - - Are you sure you want to quit %1? - - - - - Account - Λογαριασμός - - - - MainWindow - - - - - Hide - Απόκρυψη - - - - OCC::ShareDialog - - - TextLabel - TextLabel - - - - share label - Διαμοιρασμός ετικέτας - - - - Dialog - Παράθυρο διαλόγου - - - - ownCloud Path: - Διαδρομή ownCloud: - - - - %1 Sharing - %1 Διαμοιράστηκε - - - - %1 - %1 - - - - Folder: %2 - Φάκελος: %2 - - - - The server does not allow sharing - Ο διακομιστής δεν επιτρέπει τον διαμοιρασμό - - - - Retrieving maximum possible sharing permissions from server... - Ανάκτηση των μέγιστων δυνατών δικαιωμάτων κοινής χρήσης από το διακομιστή ... - - - - The file can not be shared because it was shared without sharing permission. - Το αρχείο δεν μπορεί να διαμοιραστεί γιατί διαμοιράστηκε χωρίς δικαιώματα διαμοιρασμού. - - - - Users and Groups - Χρήστες και Ομάδες - - - - Public Links - Δημόσιοι σύνδεσμοι - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Διαμοιρασμός NewDocument.odt - - - - Upload only (File Drop) - Μόνο μεταφόρτωση (File Drop) - - - - Receive files from others without revealing the contents of the folder. - Λήψη αρχείων από άλλους χωρίς να αποκαλύπτεται το περιεχόμενο του φακέλου. - - - - Download / View / Edit - Λήψη / Προβολή / Επεξεργασία - - - - Name: - Όνομα: - - - - Enter name of new share... - Εισάγετε όνομα του νέου κοινόχρηστου... - - - - &Create - &Δημιουργία - - - - TextLabel - TextLabel - - - - Set &password - Ορισμός &κωδικού πρόσβασης - - - - Set &expiration date - Ορισμός &ημερομηνίας λήξης - - - - Set password - Ορισμός κωδικού πρόσβασης - - - - Recipients can view or download contents. - Οι παραλήπτες μπορούν να προβάλλουν ή να κατεβάζουν περιεχόμενο. - - - - Download / View - Λήψη / Προβολή - - - - Recipients can view, download, edit, delete and upload contents. - Οι παραλήπτες μπορούν να προβάλλουν, να κατεβάζουν, να επεξεργάζονται, να διαγράφουν και να μεταφορτώνουν περιεχόμενα. - - - - Anyone with the link has access to the file/folder - Οποιοσδήποτε με τη σύνδεση έχει πρόσβαση στο αρχείο / φάκελο - - - - - P&assword protect - Π&ροστασία με κωδικό - - - - The file can not be shared because it was shared without sharing permission. - Το αρχείο δεν μπορεί να διαμοιραστεί γιατί διαμοιράστηκε χωρίς δικαιώματα διαμοιρασμού. - - - - Link shares have been disabled - Οι σύνδεσμοι των κοινόχρηστων έχουν απενεργοποιηθεί - - - - Create public link share - Δημιουργία δημόσιου συνδέσμου κοινόχρηστου - - - - - Delete - Διαγραφή - - - - Open link in browser - Άνοιγμα συνδέσμου στον περιηγητή - - - - Copy link to clipboard - Αντιγραφή συνδέσμου στο πρόχειρο - - - - Copy link to clipboard (direct download) - Αντιγραφή συνδέσμου στο πρόχειρο (άμεση λήψη) - - - - Send link by email - Αποστολή συνδέσμου με αλληλογραφία - - - - Send link by email (direct download) - Αποστολή συνδέσμου με ηλεκτρονικό ταχυδρομείο (απευθείας λήψη) - - - - Create new... - Δημιουργία νέου... - - - - I shared %1 with you - Διαμοιράστηκα %1 με εσένα - - - - Confirm Link Share Deletion - Επιβεβαίωση διαγραφής συνδέσμου κοινόχρηστου - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Θέλετε πραγματικά να διαγράψετε τον σύνδεσμο κοινόχρηστου <i>%1</i>; </p><p>Σημείωση: Αυτή η ενέργεια δεν μπορεί να ανακληθεί.</p> - - - - Cancel - Ακύρωση - - - - - Public link - Δημόσιος σύνδεσμος - - - - Delete link share - Διαγραφή συνδέσμου κοινόχρηστου - - - - - Please Set Password - Παρακαλούμε ορίστε Κωδικό - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Διαμοιρασμός NewDocument.odt - - - - Share with users or groups ... - Διαμοιρασμός με χρήστες ή ομάδες ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - Το στοιχείο δεν διαμοιράζεται με κανέναν χρήστη ή ομάδα - - - - Open link in browser - Άνοιγμα συνδέσμου στον περιηγητή - - - - Copy link to clipboard - Αντιγραφή συνδέσμου στο πρόχειρο - - - - Send link by email - Αποστολή συνδέσμου με αλληλογραφία - - - - No results for '%1' - Κανένα αποτέλεσμα για '%1' - - - - I shared something with you - Διαμοιράστηκα κάτι μαζί σου - - - - OCC::ShareUserLine - - - Form - Φόρμα - - - - - TextLabel - TextLabel - - - - can edit - δυνατότητα επεξεργασίας - - - - can share - δυνατότητα διαμοιρασμού - - - - ... - ... - - - - create - δημιουργία - - - - change - αλλαγή - - - - delete - διαγραφή - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Διαμοιρασμός με %1 - - - - Context menu share - Μενού περιβάλλοντος κοινόχρηστου - - - - Context menu share %1 - Μενού περιβάλλοντος κοινόχρηστου %1 - - - - Confirm deletion - Επιβεβαίωση διαγραφής - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Θέλετε να διαγράψετε μονίμως τον κατάλογο <i>%1</i> και όλα τα περιεχόμενά του; - - - - Do you want to delete the file <i>%1</i> permanently? - Θέλετε να διαγράψετε οριστικά το αρχείο <i>%1</i>; - - - - Select new location... - Επιλογή νέας τοποθεσίας... - - - - Error - Σφάλμα - - - - Moving file failed: - -%1 - Αποτυχία μετακίνησης αρχείου: - -%1 - - - - I shared something with you - Διαμοιράστηκα κάτι μαζί σου - - - - - Share... - Διαμοιρασμός - - - - - Copy private link to clipboard - Αντιγραφή ιδιωτικού συνδέσμου στο πρόχειρο - - - - Send private link by email... - Αποστολή ιδιωτικού συνδέσμου με αλληλογραφία... - - - - Resharing this file is not allowed - Δεν επιτρέπεται να διαμοιράσετε ξανά αυτό το αρχείο - - - - Resharing this folder is not allowed - Δεν επιτρέπεται να διαμοιράσετε ξανά αυτόν τον φάκελο - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - Αντιγραφή δημόσιου συνδέσμου στο πρόχειρο - - - - Open in browser - Άνοιγμα στον περιηγητή ιστού - - - - Show file versions in browser - Εμφάνιση εκδόσεων αρχείου στον περιηγητή - - - - Rename... - Μετονομασία... - - - - Rename and upload... - Μετονομασία και μεταφόρτωση... - - - - Move and rename... - Μετακίνηση και μετονομασία... - - - - Move, rename and upload... - Μετακίνηση, μετονομασία και μεταφόρτωση... - - - - Delete local changes - Διαγραφή τοπικών αλλαγών - - - - Move and upload... - Μετακίνηση και μεταφόρτωση... - - - - Delete - Διαγραφή - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Λεπτομέρειες Πιστοποιητικού</h3> - - - - Common Name (CN): - Κοινό Όνομα (ΚΟ): - - - - Subject Alternative Names: - Εναλλακτικά Ονόματα Υποκειμένου: - - - - Organization (O): - Οργανισμός (Ο): - - - - Organizational Unit (OU): - Μονάδα Οργανισμού (ΜΟ): - - - - State/Province: - Νομός/Περιφέρεια: - - - - Country: - Χώρα: - - - - Serial: - Σειριακός αριθμός: - - - - <h3>Issuer</h3> - <h3>Εκδότης</h3> - - - - Issuer: - Εκδότης: - - - - Issued on: - Εκδόθηκε στις: - - - - Expires on: - Λήγει στις: - - - - <h3>Fingerprints</h3> - <h3>Αποτυπώματα</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - p><b>Σημείωση:</b> Αυτό το πιστοποιητικό εγκρίθηκε χειροκίνητα</p> - - - - %1 (self-signed) - %1 (αυτό-πιστοποιημένο) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Η σύνδεση είναι κρυπτογραφημένη με %1 bit %2 - - - - - Server version: %1 - Έκδοση διακομιστή: %1 - - - - No support for SSL session tickets/identifiers - Χωρίς υποστήριξη για ταυτοποιητές συνεδρίας SSL - - - - Certificate information: - Πληροφορίες πιστοποιητικού: - - - - The connection is not secure - Η σύνδεση δεν είναι ασφαλής - - - - This connection is NOT secure as it is not encrypted. - - Αυτή η σύνδεση δεν είναι ασφαλής καθώς δεν είναι κρυπτογραφημένη. - - - - - OCC::SslErrorDialog - - - Form - Φόρμα - - - - Trust this certificate anyway - Προσθήκη αυτού του πιστοποιητικού στα έμπιστα παρ'όλα αυτά - - - - Untrusted Certificate - Μη έμπιστο πιστοποιητικό - - - - Cannot connect securely to <i>%1</i>: - Αδυναμία ασφαλούς σύνδεσης σε <i>%1</i>: - - - - with Certificate %1 - με Πιστοποιητικό: %1 - - - - - - &lt;not specified&gt; - &lt;δεν κατονομάζονται&gt; - - - - - Organization: %1 - Οργανισμός: %1 - - - - - Unit: %1 - Μονάδα: %1 - - - - - Country: %1 - Χώρα: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Αποτύπωμα (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Αποτύπωμα (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Ημερομηνία Έναρξης: 1% - - - - Expiration Date: %1 - Ημερομηνία Λήξης: %1 - - - - Issuer: %1 - Εκδότης: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (έχει παραλειφθεί λόγω προηγούμενου σφάλματος, θα γίνει προσπάθεια ξανά σε %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Μόνο %1 είναι διαθέσιμα, απαιτούνται τουλάχιστον %2 για την εκκίνηση - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Δεν είναι δυνατό το άνοιγμα ή η δημιουργία της τοπικής βάσης δεδομένων συγχρονισμού. Βεβαιωθείτε ότι έχετε πρόσβαση εγγραφής στο φάκελο συγχρονισμού. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Ο χώρος στο δίσκο είναι χαμηλός: Λήψεις που θα μειώσουν τον ελεύθερο χώρο κάτω από το %1 έχουν παραβλεφθεί. - - - - There is insufficient space available on the server for some uploads. - Ανεπαρκής διαθέσιμος χώρος στο διακομιστή για κάποιες μεταφορτώσεις. - - - - Unresolved conflict. - Ανεπίλυτη σύγκρουση. - - - - Using virtual files with suffix, but suffix is not set - Γίνεται χρήση εικονικών αρχεία με κατάληξη, αλλά η κατάληξη δεν έχει οριστεί - - - - Unable to read the blacklist from the local database - Αδυναμία ανάγνωσης της μαύρης λίστας από την τοπική βάση δεδομένων - - - - Unable to read from the sync journal. - Αδυναμία ανάγνωσης από το ημερολόγιο συγχρονισμού. - - - - Cannot open the sync journal - Αδυναμία ανοίγματος του αρχείου συγχρονισμού - - - - Aborted - Ματαιώθηκε - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Η κεφαλίδα του αθροίσματος ελέγχου δεν είναι σωστά διαμορφωμένη. - - - - The checksum header contained an unknown checksum type '%1' - Το checksum header περιέχει άγνωστο τύπο checksum '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Παρκαλώ συνδεθείτε - - - - Folder %1: %2 - Φάκελος %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - Δεν έχουν ρυθμιστεί φάκελοι συγχρονισμού. - - - - Open in browser - Άνοιγμα στον περιηγητή ιστού - - - - - - Log in... - Σύνδεση... - - - - - - Log out - Αποσύνδεση - - - - Recent Changes - Πρόσφατες Αλλαγές - - - - Managed Folders: - Φάκελοι υπό Διαχείριση: - - - - Open folder '%1' - Άνοιγμα φακέλου '%1' - - - - Open %1 in browser - Άνοιγμα %1 στον περιηγητή - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - Ο διακομιστής στο λογαριασμό %1 εκτελεί μια μη υποστηριζόμενη έκδοση %2. Η χρήση αυτού του πελάτη με μη υποστηριζόμενες εκδόσεις διακομιστών δεν είναι δοκιμασμένη και δυνητικά επικίνδυνη. Προχωρήστε με δική σας ευθύνη. - - - - Unknown status - Άγνωστη κατάσταση - - - - Settings... - Ρυθμίσεις... - - - - Details... - Λεπτομέρειες... - - - - Help - Βοήθεια - - - - Quit %1 - Κλείσιμο %1 - - - - Disconnected from %1 - Αποσυνδέθηκε από %1 - - - - Unsupported Server Version - Μη υποστηριζόμενη έκδοση διακομιστή - - - - Disconnected - Αποσυνδεδεμένο - - - - Disconnected from some accounts - Έγινε αποσύνδεση από μερικούς λογαριασμούς - - - - Disconnected from accounts: - Αποσυνδέθηκε από τους λογαριασμούς: - - - - Account %1: %2 - Λογαριασμός %1: %2 - - - - Signed out - Αποσύνδεση - - - - Account synchronization is disabled - Ο λογαριασμός συγχρονισμού έχει απενεργοποιηθεί - - - - Synchronization is paused - Παύθηκε ο συγχρονισμός - - - - Up to date (%1) - - - - - No sync folders configured - Δεν ρυθμίστηκαν φάκελοι συγχρονισμού - - - - Unpause all folders - Αναίρεση παύσης όλων των φακέλων - - - - Pause all folders - Παύση όλων των φακέλων - - - - Unpause all synchronization - Αναίρεση παύσης όλων των συγχρονισμών - - - - Unpause synchronization - Αναίρεση παύσης συγχρονσμού - - - - Pause all synchronization - Παύση όλων των συγχρονισμών - - - - Pause synchronization - Παύση συγχρονισμού - - - - Log out of all accounts - Έξοδος από όλους τους λογαριασμούς - - - - Log in to all accounts... - Είσοδος σε όλους τους λογαριασμούς - - - - New account... - Νέος λογαριασμός... - - - - About %1 - Σχετικά %1 - - - - No items synced recently - Κανένα στοιχείο δεν συγχρονίστηκε πρόσφατα - - - - Checking for changes in remote '%1' - Έλεγχος για αλλαγές στο απομακρυσμένο '%1' - - - - Checking for changes in local '%1' - Έλεγχος για αλλαγές στο τοπικό '%1' - - - - Syncing %1 of %2 (%3 left) - Συγχρονισμός %1 από %2 (%3 απομένουν) - - - - Syncing %1 of %2 - Συγχρονισμός %1 από %2 - - - - Syncing %1 (%2 left) - Συγχρονισμός %1 (%2 απομένουν) - - - - Syncing %1 - Συγχρονισμός %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - Φόρμα - - - - - TextLabel - TextLabel - - - - Server - Διακομιστής - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Εάν αυτό το κουτί είναι επιλεγμένο, το υπάρχον περιεχόμενο του τοπικού καταλόγου θα διαγραφεί ώστε να αρχίσει ένας νέος συγχρονισμός από το διακομιστή.</p><p>Μην το επιλέξετε εάν το τοπικό περιεχόμενο πρέπει να μεταφορτωθεί στον κατάλογο του διακομιστή.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - Επιβεβαίωση πριν τον &συγχρονισμό φακέλων μεγαλύτερων από - - - - S&ynchronize everything from server - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - ΜΒ - - - - Ask for confirmation before synchronizing e&xternal storages - Επιβεβαίωση πριν τον συγχρονισμό &εξωτερικών αποθηκευτικών χώρων - - - - Choose what to sync - Επιλέξτε τι θα συγχρονιστεί - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Όταν αυτή η επιλογή είναι επιλεγμένη, ο οδηγός θα κλείσει χωρίς συγχρονισμό οτιδήποτε. Μπορείτε να χρησιμοποιήσετε το κουμπί &quot;Προσθήκη σύνδεσης συγχρονισμού φακέλων&quot; από τις ρυθμίσεις λογαριασμού για να επιλέξετε το ζεύγος τοπικών και απομακρυσμένων φακέλων που θέλετε να συγχρονίσετε</p></body></html> - - - - Manually create folder sync connections - Χειροκίνητη δημιουργία συνδέσεων συγχρονισμού φακέλων - - - - &Local Folder - &Τοπικός Φάκελος - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Διατήρηση τοπικών δεδομένων - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - Φόρμα - - - - &Username - &Όνομα Χρήστη - - - - &Password - &Κωδικός Πρόσβασης - - - - OwncloudOAuthCredsPage - - - Form - Φόρμα - - - - Please switch to your browser to proceed. - Παρακαλούμε μεταβείτε στο πρόγραμμα περιήγησής σας για να συνεχίσετε. - - - - An error occurred while connecting. Please try again. - Παρουσιάστηκε σφάλμα κατά τη σύνδεση. Παρακαλούμε δοκιμάστε αργότερα. - - - - Re-open Browser - Άνοιγμα ξανά του προγράμματος πλοήγησης - - - - OwncloudSetupPage - - - Form - Φόρμα - - - - Ser&ver Address - Διεύθυνση &διακομιστή - - - - https://... - https://... - - - - Error Label - Ετικέτα Σφάλματος - - - - QObject - - - in the future - στο μέλλον - - - - %n day(s) ago - %n μέρα(ες) πριν%n μέρα(ες) πριν - - - - %n hour(s) ago - %n ώρα(ες) πριν%n ώρα(ες) πριν - - - - now - τώρα - - - - Less than a minute ago - Λιγότερο από ένα λεπτό πριν - - - - %n minute(s) ago - %n λεπτό(ά) πριν%n λεπτό(ά) πριν - - - - Some time ago - Λίγη ώρα πριν - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - Μοιραίο Σφάλμα - - - - - Error - Σφάλμα - - - - Info - Πληροφορίες - - - - Success - Επιτυχία - - - - Conflict - Διένεξη - - - - File Ignored - - - - - Restored - - - - - Blacklisted - - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n χρόνος%n χρόνια - - - - %n month(s) - %n μήνας(ες)%n μήνας(ες) - - - - %n day(s) - %n ημέρα(ες)%n ημέρα(ες) - - - - %n hour(s) - %n ώρα(ες)%n ώρα(ες) - - - - %n minute(s) - %n λεπτό(ά)%n λεπτό(ά) - - - - %n second(s) - %n δευτερόλεπτο(α)%n δευτερόλεπτο(α) - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Μπάρα Συστήματος μη-διαθέσιμη - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - Το %1 απαιτεί μια μπάρα συστήματος σε λειτουργία. Εάν χρησιμοποιείτε το XFCE, παρακαλώ ακολουθείστε <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">αυτές τις οδηγίες</a>. Διαφορετικά, παρακαλώ εγκαταστείστε μια εφαρμογή μπάρας συστήματος όπως η 'trayer' και δοκιμάστε ξανά. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - Το εικονικό αρχείο δημιουργήθηκε - - - - Replaced by virtual file - Αντικαταστάθηκε από εικονικό αρχείο - - - - Downloaded - Ελήφθη - - - - Uploaded - Μεταφορτώθηκε - - - - Server version downloaded, copied changed local file into conflict file - Η έκδοση του διακομιστή κατέβηκε, η αντιγραφή άλλαξε το τοπικό αρχείο σε συγκρουόμενο αρχείο - - - - Deleted - Διεγράφη - - - - Moved to %1 - Μετακινήθηκαν στο %1 - - - - Ignored - Αγνοήθηκε - - - - Filesystem access error - Σφάλμα πρόσβασης στο σύστημα αρχείων - - - - Error - Σφάλμα - - - - Updated local metadata - Ενημερωμένα τοπικά μεταδεδομένα - - - - - Unknown - Άγνωστο - - - - downloading - λήψη - - - - uploading - μεταφόρτωση - - - - deleting - διαγραφή - - - - moving - μετακίνηση - - - - ignoring - αγνοείται - - - - - error - σφάλμα - - - - updating local metadata - γίνεται ενημέρωση μεταδεδομένων - - - - theme - - - Status undefined - Απροσδιόριστη κατάσταση - - - - Waiting to start sync - Αναμονή έναρξης συγχρονισμού - - - - Sync is running - Ο συγχρονισμός εκτελείται - - - - Sync Success - Επιτυχημένος Συγχρονισμός - - - - Sync Success, some files were ignored. - Επιτυχία Συγχρονισμού, κάποια αρχεία αγνοήθηκαν. - - - - Sync Error - Σφάλμα Συγχρονισμού - - - - Setup Error - Σφάλμα Ρυθμίσεων - - - - Preparing to sync - Προετοιμασία για συγχρονισμό - - - - Aborting... - Ματαίωση σε εξέλιξη... - - - - Sync is paused - Παύση συγχρονισμού - - - - utility - - - Could not open browser - Αδυναμία ανοίγματος περιηγητή - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - Παρουσιάστηκε ένα σφάλμα κατά την εκκίνηση του περιηγητή για την μετάβαση στη διεύθυνση URL %1. Ίσως δεν έχει ρυθμιστεί προεπιλεγμένο πρόγραμμα περιήγησης; - - - - Could not open email client - Αδυναμία ανοίγματος πελάτη ηλεκτρονικής αλληλογραφίας - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Παρουσιάστηκε σφάλμα κατά την εκκίνηση του προγράμματος-πελάτη ηλεκτρονικού ταχυδρομείου για τη δημιουργία νέου μηνύματος. Ίσως δεν έχει ρυθμιστεί προεπιλεγμένο πρόγραμμα-πελάτη ηλεκτρονικού ταχυδρομείου; - - - - Always available locally - Πάντα διαθέσιμο τοπικά - - - - Some available online only - Μερικά διαθέσιμα μόνο σε σύνδεση - - - - - Available online only - Διαθέσιμο μόνο σε σύνδεση - - - - Currently available locally - Διατίθεται επί του παρόντος τοπικά - - - - Make always available locally - Να είναι πάντα διαθέσιμα τοπικά - - - - Free up local space - Ελευθερώστε τοπικό αποθηκευτικό χώρο - - - \ No newline at end of file diff --git a/translations/client_en.ts b/translations/client_en.ts index 64e64d89325..49a4d43992e 100644 --- a/translations/client_en.ts +++ b/translations/client_en.ts @@ -2,361 +2,427 @@ - CrashReporter + AccountBar - - Tomahawk Crash Reporter + + Navigation bar - - <html><head/><body><p><br/></p></body></html> + + Add Account - - Abort + + Activity - - Send this report + + Settings - - Don't send + + Quit + + + AppImageUpdateAvailableWidgetUi - - You can disable sending crash reports in the configuration dialog. + + Skip this version + + + AppProvider - - - - We cannot gather useful debug information on your system. + + Failed to open %1 in web. Error: %2. + + + ChecksumHeader - - - - - - Close + + The checksum header is malformed: %1 - - Ready to send debug information (<a href="%1">view backtrace</a>). + + The checksum header contained an unknown checksum type '%1' + + + CommandLine - - Gathering debug information... + + %1 version %2 +File synchronization desktop utility. - - Uploaded %L1 of %L2 KB. + + For more information, see %1 + link to homepage - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. - - Failed to send crash info. + + Quit the running instance. - - - FileSystem - - - - - Can't rename %1, the file is currently in use + + Write log to file (use - to write to stdout). - - Could not make directories in trash + + Write each sync log output in a new file in folder. - - - Could not move '%1' to '%2' + + Flush the log file after every write. - - Moving to the trash is not implemented on this platform + + Output debug-level messages in the log. - - Error removing '%1': %2 + + Enable debug mode. - - Could not remove folder '%1' + + --logfile and --logdir are mutually exclusive - FolderWizardSourcePage + CommonStrings - - Form + + file manager - - Pick a local folder on your computer to sync + + Show in %1 - - &Choose... + + Show in web browser - - - FolderWizardTargetPage - - Form + + Copy + + + %n Filter(s) + + %n Filter + %n Filters + + - - Select a remote destination folder + + folder - - Create Folder + + space - - Refresh + + folders - - Folders + + spaces - - TextLabel + + Folder - - - NotificationWidget - - Form + + Space + + + FetchUserInfoJobFactory - - Lorem ipsum dolor sit amet + + Failed to retrieve user info + + + FileSystem - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + + + + Can't rename %1, the file is currently in use - - TextLabel + + Could not remove folder - OCC::AboutDialog + FolderItem - - About + + + Offline - - TextLabel + + + Unavailable: the space is no longer available on the server - - Versions + + + Sync paused - - About %1 + + Synced - - - OCC::AbstractNetworkJob - - Connection timed out + + Sync failed - - Unknown error: network reply was deleted + + Sync pending - - Server replied "%1 %2" to "%3 %4" + + Syncing %1 of %2 (%3 % + + + + + , ⬇️ %1/s + + + + + , ⬆️ %1/s + + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + first argument is the displayed name of the folder. - OCC::Account + FolderManagementUtils - - %1@%2 + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + + + + + Local folder %1 does not exist. + + + + + %1 is not a folder. + + + + + %1 is not readable. + + + + + %1 is not writable. - OCC::AccountSettings + FolderWizard - - Form + + <b>Error:</b> %1 - - Re-open Browser + + <b>Warning:</b> %1 - - ... + + <b>Error:</b><ul>%1</ul> - - Storage space: ... + + <b>Warning:</b><ul>%1</ul> + + + FolderWizardSourcePage - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore + + Form - - Synchronize all + + Pick a local folder on your computer to sync - - Synchronize none + + Browse… + + + FolderWizardTargetPage - - Apply manual changes + + Form - - Apply + + Select a remote destination folder - - - - - Cancel + + Create Folder - - Connected with <server> as <user> + + Refresh - - No account configured. + + Folders + + + OCC::AboutView - - Remove + + About - - Account + + Versions + + + OCC::AbstractNetworkJob - - Choose what to sync + + Connection timed out - - Force sync now + + Unknown error: network reply was deleted - - Restart sync + + Server replied "%1 %2" to "%3 %4" + + + OCC::Account - - Remove folder sync connection + + %1@%2 + + + OCC::AccountFoldersController - - Folder creation failed + + Choose what to sync - - <p>Could not create local folder <i>%1</i>. + + + Sync now - - Confirm Folder Sync Connection Removal + + Manage subfolder sync - - Remove Folder Sync Connection + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' - + Disable virtual file support? - + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. The only advantage of disabling virtual file support is that the selective sync feature will become available again. @@ -365,774 +431,729 @@ This action will abort any currently running synchronization. - + Disable support - - Sync Running + + Restart sync + + + + + Resume sync - - The syncing operation is running.<br/>Do you want to terminate it? + + Pause sync - - %1 in use + + Deactivate virtual files - - %1 as <i>%2</i> + + Activate virtual files - - Connected to %1. + + Internet connection is metered - - Server %1 is temporarily unavailable. + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? - - Server %1 is currently in maintenance mode. + + Sync Running - - Signed out from %1. + + The sync operation is running.<br/>Do you want to stop it? - - Connecting to %1... + + Sync paused by user - - No connection to %1 at %2. + + Confirm Folder Sync Connection Removal - - Log in + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - There are folders that were not synchronized because they are too big: + + Remove Folder Sync Connection - - There are folders that were not synchronized because they are external storages: + + + Cancel + + + OCC::AccountFoldersView - - There are folders that were not synchronized because they are too big or external storages: + + Sync options menu - - Confirm Account Removal + + %1 sync + This shows as 'Folder sync' or 'Space sync' - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + + Manage your synced %1. - - Remove connection + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' - - Open folder + + %1 list view - - - Log out + + Navigate the %1 list using the up and down arrows - - Open local folder + + %1 out of %2 %3 are synchronized + + + OCC::AccountModalWidget - - Open folder in browser + + + Form + + + OCC::AccountPlaceholderWidget - - Resume sync + + %1 logo - - Pause sync + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. - - Availability + + Additional logo defined by the organization + + + OCC::AccountView - - Disable virtual file support... + + Form - - Enable virtual file support%1... + + Manage Account - - (experimental) + + Preparing the account - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + + Sync connections - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. + + Account options menu + + + OCC::AccountViewController - - %1 of %2 in use + + + Log in - - Currently there is no storage usage information available. + + Reconnect - - The server version %1 is unsupported! Proceed at your own risk. + + Remove - - Obtaining authorization from the browser. + + Log out - - Server configuration error: %1 at %2. + + Confirm Account Removal - - No %1 connection configured. + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - - OCC::AccountState - - Url update requested for %1 + + Remove connection - - The url for %1 changed from %2 to %3, do you want to accept the changed url? + + Cancel - - Change url permanently to %1 + + The server version %1 is unsupported! Proceed at your own risk. - - Reject + + Connected - - Signed out + + Server is temporarily unavailable. - - Disconnected + + Server is currently in maintenance mode. - - Connected + + Signed out - - Service unavailable + + Updating credentials… - - Maintenance mode + + Captive portal prevents connections to the server. - - Network error + + Sync is paused due to metered internet connection. - - Configuration error + + Connecting… - - Asking Credentials + + Server configuration error - - Unknown account state + + Disconnected - - - OCC::ActivityListModel - - %1 %2 on %3 + + Invalid connection status + + + OCC::AccountsGuiController - - Activity + + + New account failure - - Account + + The information required to create a new account is incomplete. Please run the wizard again. - - Time + + Accounts - - Local path + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. OCC::ActivitySettings - - - Server Activity - - - - - Sync Protocol + + Local Activity - + Not Synced - + Not Synced (%1) %1 is the number of not synced files. - OCC::ActivityWidget + OCC::AdvancedSettingsPageController - - Form + + Advanced settings - - - - TextLabel + + Sync and download options - - Filter + + Download location - - Server Activities + + Sync and download files as you use them (saves hard drive space) - - Notifications + + Automatically sync and download folders and files - - - %n notifications(s) for %1. - - - - - - - - %n notifications(s) for %1 and %2. - - - - - - - New notifications for %1, %2 and other accounts. + + Sync and download folders manually - - Open the activity view for details. + + Download location on the local machine - - Copy to clipboard + + Browse… - - Show in file browser + + Browse for a download location - - <br/>Account %1 does not have activities enabled. + + Invalid local download directory %1: %2 - - - OCC::Application - - Continuing will mean <b>deleting these settings</b>. + + your user directory may not be chosen as the sync root. - - Continuing will mean <b>ignoring these settings</b>. + + path is not located on a supported Apple File System. - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. + + path must be absolute. - - Quit + + selected path does not support using virtual file system. %1 - - Continue + + Select sync root + + + OCC::AppImageUpdateAvailableWidget - - Error accessing the configuration file + + Installed version: %1 - - There was an error while accessing the configuration file at %1. + + Available update: %1 - - Quit %1 + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + + + OCC::Application - - %1 version %2 -File synchronization desktop utility. + + Quit + + + OCC::AuthSuccessPageController - - For more information, see http://www.owncloud.org + + Application Name Logo %1 - - Show the settings dialog while starting. + + You're all set! Open %1 and get started. - - Quit the running instance. + + Additional logo defined by the organization + + + OCC::ButtonDelegate - - Write log to file (use - to write to stdout). + + %1 options button - - Write each sync log output in a new file in folder. + + Menu button with %1 options. Use the space key to show the menu + + + OCC::ConnectionValidator - - Remove logs older than <hours> hours (to be used with --logdir). + + No ownCloud account configured - - Flush the log file after every write. + + Please update to the latest server and restart the client. - - Output debug-level messages in the log. + + Authentication error: Either username or password are wrong. - - - Override UI language. + + timeout - - Use the given configuration folder. + + No account configured - - Enable debug mode. + + The provided credentials are not correct - - Virtual file system file to be opened (optional). + + The configured server for this client is too old. + + + OCC::CredentialJob - - [<vfs file>] + + Failed to parse credentials %1 + + + OCC::DetermineAuthTypeAdapter - - Invalid path passed to --confdir + + Server does not support OAuth - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) + + Server did not ask for authorization - - Available translations: %1 + + Failed to determine authentication type: %1 - OCC::AuthenticationDialog - - - Authentication Required - - + OCC::DiscoverWebFingerServiceAdapter - - Enter username and password for '%1' at %2. + + Invalid reply received from server. + + + OCC::DiscoverySingleDirectoryJob - - &User: + + The account was deleted before we could start the propfind job - - &Password: + + + Server error: PROPFIND reply is not XML formatted! - OCC::ConnectionValidator + OCC::DiscoverySingleLocalDirectoryJob - - No ownCloud account configured + + Error while opening directory %1 - - The configured server for this client is too old + + Directory not accessible on client, permission denied - - Please update to the latest server and restart the client. + + Directory not found: %1 - - Authentication error: Either username or password are wrong. + + Error while reading directory %1 + + + OCC::ExpandingHeaderView - - timeout - - - - - The provided credentials are not correct - - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! + + Reset column sizes - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - - - - - Directory not accessible on client, permission denied - - - - - Directory not found: %1 - - + OCC::FetchCapabilitiesAdapter - - Filename encoding is not valid + + server sent invalid content type: %1. - - Error while reading directory %1 + + could not parse Capabilities from server. - - - OCC::ExpandingHeaderView - - Reset column sizes + + reply sent for different subject (server): %1 OCC::Folder - - Local folder %1 does not exist. - - - - - %1 should be a folder but is not. - - - - - %1 is not readable. - - - - + %1 has been removed. %1 names a file. - + %1 has been updated. %1 names a file. - + %1 has been renamed to %2. %1 and %2 name files. - + %1 has been moved to %2. - + %1 and %n other file(s) have been removed. - - - + + %1 and %n other file have been removed. + %1 and %n other files have been removed. - - %1 is not writable. + + The folder has been disabled or removed from the server - + %1 and %n other file(s) have been added. - - - + + %1 and %n other file have been added. + %1 and %n other files have been added. - + %1 has been added. %1 names a file. - + %1 and %n other file(s) have been updated. - - - + + %1 and %n other file have been updated. + %1 and %n other files have been updated. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - - - + + %1 has been renamed to %2 and %n other file have been renamed. + %1 has been renamed to %2 and %n other files have been renamed. - + %1 has been moved to %2 and %n other file(s) have been moved. - - - + + %1 has been moved to %2 and %n other file have been moved. + %1 has been moved to %2 and %n other files have been moved. - - %1 has and %n other file(s) have sync conflicts. - - - + + %1 and %n other file(s) have sync conflicts. + + %1 and %n other file have sync conflicts. + %1 and %n other files have sync conflicts. - + %1 has a sync conflict. Please check the conflict file! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - - - + + %1 and %n other file could not be synced due to errors. See the log for details. + %1 and %n other files could not be synced due to errors. See the log for details. - + %1 could not be synced due to an error. See the log for details. - + Sync Activity - - Could not read system exclude file - - - - - A new folder larger than %1 MB has been added: %2. - - - - - - A folder from an external storage has been added. - + + Switching VFS mode on folder '%1' - - Please go in the settings to select it if you wish to download it. + + Could not read system exclude file - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1140,3591 +1161,2418 @@ This means that the synchronization client might not upload local changes immedi %1 + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - - - - - Remove all files + + There are unresolved conflicts. - - Keep files + + Sync error: %1 OCC::FolderMan - + Could not reset folder state - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - - (backup) + + Unsupported filesystem - - (backup %1) + + Account disconnected or paused - - Undefined State. + + User triggered force sync - - Waiting to start syncing. + + Folder is about to be removed - - Preparing for sync. + + (backup) - - Sync is running. + + (backup %1) - - Sync was successful, unresolved conflicts. + + The selected path is not a folder. - - Last Sync was successful. + + The folder %1 is used in a folder sync connection. - - Setup Error. + + Folder '%1' is already in use by application %2. - - User Abort. + + There is already a sync from the server to this local folder. - - Sync is paused. + + The local folder %1 already contains a folder used in a folder sync connection. - - %1 (Sync is paused) + + The local folder %1 is already contained in a folder used in a folder sync connection. - - No valid folder selected! + + No valid folder selected. - - The folder %1 is used in a folder sync connection! + + The selected path does not exist. - - The selected path does not exist! + + You have no permission to write to the selected folder. - - The selected path is not a folder! + + Folder '%1' is already in use by another account. - - You have no permission to write to the selected folder! + + On macOS, only the Apple File System is supported. + + + OCC::FolderModelController - - There is already a sync from the server to this local folder. Please pick another local folder! + + %1 options - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! + + Menu button with %1 options. Use the space key to pop the %2 options menu + + + OCC::FolderWatcher - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! + + The watcher did not receive a test notification. + + + OCC::FolderWatcherPrivate - - %1 Please pick another one! + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + + + OCC::FolderWizard - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. + + Virtual files are not available for the selected folder - - - OCC::FolderStatusDelegate - + Add Folder Sync Connection - - File + + Add Sync Connection - OCC::FolderStatusModel + OCC::FolderWizardLocalPath - - You need to be connected to add a folder + + Click to select a local folder to sync. - - Click this button to add a folder to synchronize. + + Enter the path to the local folder. - - - %1 (%2) - Example text: "File.txt (23KB)" + + Select the local folder + + + OCC::FolderWizardSelectiveSync - - Error while loading the list of folders from the server. + + Use virtual files instead of downloading content immediately + + + OCC::GETFileJob - - Virtual file support is enabled. + + Connection Timeout - - Signed out + + No E-Tag received from server, check Proxy/Gateway - - Fetching folder list from server... + + We received a different E-Tag for resuming. Retrying next time. - - There are unresolved conflicts. Click for details. + + We received an unexpected download Content-Length. - - Checking for changes in remote '%1' + + Server returned wrong content-range + + + OCC::GraphApi::Space - - Checking for changes in local '%1' + + Personal - - Reconciling changes + + Shares + + + OCC::IgnoreListEditor - - , '%1' - Build a list of file names + + Ignored Files Editor - - '%1' - Argument is a file name + + Files Ignored by Patterns - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" + + Add - - - , + + Pattern - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) + + Allow Deletion - - ↓ %1/s + + Remove - - ↑ %1/s + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) + + Could not open file - - Local folder: %1 + + Cannot write changes to '%1'. - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" + + Add Ignore Pattern - - %1 %2 - Example text: "uploading foobar.png" + + Add a new ignore pattern: - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" + + This entry is provided by the system at '%1' and cannot be modified in this view. + + + OCC::LocalActivityWidget - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" + + Filter - - file %1 of %2 + + Local activity table - - Waiting... + + + Account - - - Waiting for %n other folder(s)... - - - - - - - Preparing to sync... + + Filter menu - - - OCC::FolderWatcher - - The watcher did not receive a test notification. + + Actions menu - - - OCC::FolderWatcherPrivate - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + + Sort ascending by %1 - - - OCC::FolderWizard - - Add Folder Sync Connection + + Sort descending by %1 - - Add Sync Connection + + Retry sync - OCC::FolderWizardLocalPath + OCC::LogBrowser - - Click to select a local folder to sync. + + Log Output - - Enter the path to the local folder. + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> - - Select the source folder + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. - - - OCC::FolderWizardRemotePath - - Create Remote Folder + + Enable logging to temporary folder - - Enter the name of the new folder to be created below '%1': + + If enabled, logs will be written to: - - Folder was successfully created on %1. + + C:/log - - Authentication failed accessing %1 + + Log Http traffic - - Failed to create the folder on %1. Please check manually. + + Log files to keep: - - Failed to list a folder. Error: %1 + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. - - Choose this to sync the entire account + + Open folder + + + OCC::MainWindow - - This folder is already being synced. + + Hide - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. + + Main toolbar for the application - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. + + Main toolbar - - - OCC::FolderWizardSelectiveSync - - Use virtual files instead of downloading content immediately%1 + + + More + + + OCC::MainWindowController - - (experimental) + + Add account... - - Virtual files are not available for the selected folder + + Settings... - - - OCC::FormatWarningsWizardPage - - <b>Warning:</b> %1 + + About... - - <b>Warning:</b> + + Quit - - - OCC::GETFileJob - - No E-Tag received from server, check Proxy/Gateway + + + Errors: %1 - - We received a different E-Tag for resuming. Retrying next time. + + Activity - - We received an unexpected download Content-Length. + + Quit %1 - - Server returned wrong content-range + + Are you sure you want to quit %1? - OCC::GETJob + OCC::ModalWrapperWidget - - Connection Timeout + + Close - OCC::GeneralSettings - - - Form - - + OCC::Models - - General Settings + + %1 Filter: - - Language + + All - - Sync hidden files + + Reset Filters - - Log Settings + + Status Filter: + + + OCC::NetworkSettings - - Advanced - Approval of folder sync (Non virtual file mode only) + + Form - - Network + + Pause synchronization when the Internet connection is metered - - Updates + + Proxy Settings - - stable + + No Proxy - - beta + + Use system proxy - - About + + Host - - Advanced + + Proxy Hostname - - Use &Monochrome Icons in the system tray + + : - - Ask for confirmation before synchronizing folders larger than + + Proxy Port Number - - MB - Trailing part of "Ask confirmation before syncing folder larger than" + + Proxy server requires authentication - - Ask for confirmation before synchronizing external storages + + Note: proxy settings have no effects for accounts on localhost - - &Launch on System Startup + + Specify proxy manually - - Show &Desktop Notifications + + Proxy type - - &Update Channel + + + Hostname of proxy server - - Edit &Ignored Files + + + Username for proxy server - - Show sync folders in &Explorer's Navigation Pane + + + Password for proxy server - - S&how crash reporter + + HTTP(S) proxy - - &Restart && Update + + SOCKS5 proxy + + + OCC::NewAccountBuilder - - You cannot disable autostart because system-wide autostart is enabled. + + no error details are available - - Change update channel? + + Unable to connect to server during account creation: %1. + + + OCC::NewAccountWizard - - Warning + + Cancel Setup - - Language changes require a restart of this application to take effect. + + Do you really want to cancel the account setup? + + + OCC::NewAccountWizardController - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. + + Add account… - - Change update channel + + Back - - Cancel + + Advanced - - unknown (%1) + + Sign in - - (use default) + + Open sign in again - OCC::HttpCredentialsGui + OCC::NewVersionAvailableWidget - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> + + Skip this version - - Reading from keychain failed with error: '%1' + + Get update - - Enter Password + + Skip this time + + + OCC::OAuth - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - - - - - Files Ignored by Patterns - - - - - Add - - - - - Pattern - - - - - Allow Deletion - - - - - Remove - - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - - - - - Cannot write changes to '%1'. - - - - - Add Ignore Pattern - - - - - Add a new ignore pattern: - - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - - - - - OCC::IssuesWidget - - - Form - - - - - List of issues - - - - - Filter - - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - Account - - - - - OCC::LibCloudProvidersPrivate - - - Settings - - - - - OCC::LogBrowser - - - Log Output - - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - - - - - - Skip this version - - - - - Skip this time - - - - - Get update - - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - - - - - Proxy Settings - - - - - No Proxy - - - - - Use system proxy - - - - - Specify proxy manually as - - - - - Host - - - - - : - - - - - Proxy server requires authentication - - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - - - - - - Limit to - - - - - - KBytes/s - - - - - - No limit - - - - - - Limit to 3/4 of estimated bandwidth - - - - - Upload Bandwidth - - - - - - Limit automatically - - - - - Hostname of proxy server - - - - - Username for proxy server - - - - - Password for proxy server - - - - - HTTP(S) proxy - - - - - SOCKS5 proxy - - - - - OCC::NotificationWidget - - - Created at %1 - - - - - Closing in a few seconds... - - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - - - - - Could not check for new updates. - - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - - - - - Update status is unknown: Did not check for new updates. - - - - - No updates available. Your installation is at the latest version. - - - - - Update Check - - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - - - - - Setup local folder options - - - - - Connect... - - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - - - - - Virtual files are not available for the selected folder - - - - - Error - - - - - Local Sync Folder - - - - - - (%1) - - - - - OCC::OwncloudHttpCredsPage - - - &Email - - - - - Connect to %1 - - - - - Enter user credentials - - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - - - - - Login in your browser - - - - - Copy link to clipboard - - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - - - - - Setup %1 server - - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - - - - - This url is secure. You can use it. - - - - - &Next > - - - - - Invalid URL - - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - - - - - Timeout while trying to connect to %1 at %2. - - - - - Invalid URL - - - - - Could not create local folder %1 - - - - - Error: %1 - - - - - The folder creation resulted in HTTP error code %1 - - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - - - - - Folder rename failed - - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - - - - - Enable experimental feature? - - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - - - - - File is listed on the ignore list. - - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - - - - - File name contains at least one invalid character - - - - - The file name is a reserved name on this file system. - - - - - Filename contains trailing spaces. - - - - - Filename is too long. - - - - - File/Folder is ignored because it's hidden. - - - - - Stat failed. - - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - The filename cannot be encoded on your file system. - - - - - The filename is blacklisted on the server. - - - - - size - - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - - - - - Ignored because of the "choose what to sync" blacklist - - - - - Not allowed because you don't have permission to add subfolders to that folder - - - - - Not allowed because you don't have permission to add files in that folder - - - - - Not allowed to upload this file because it is read-only on the server, restoring - - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! + + Successfully signed in - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - - - - - File was deleted from server - - - - - The file could not be downloaded completely. - - - - - The downloaded file is empty despite the server announced it should have been %1. - - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - - - - - Error setting pin state - - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - - - - - Local file changed during syncing. It will be resumed. - - - - - Local file changed during sync. - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - - - - - Local file changed during sync. - - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - - - - - Local file changed during sync. - - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - - - - - Local file changed during sync. - - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolItemModel - - - Time - - - - - File + + Now, explore %1 on desktop. - - Folder + + Error returned from the server: <em>%1</em> - - Issues + + There was an error accessing the 'token' endpoint: <br><em>%1</em> - - Action + + Could not parse the JSON returned from the server: <br><em>%1</em> - - Size + + Unsupported token type: %1 - - Account + + The reply from the server did not contain all expected fields +:%1 - - Status + + Unknown Error - - - OCC::ProtocolWidget - - Form + + + Login Error - - Local sync protocol + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. - - Filter + + Wrong user + + + OCC::OAuthPageController - - Account + + Application Name Logo %1 - - Copy to clipboard + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. - - Show in file browser + + Sign in URL - - Show in web browser + + To copy the sign in URL to the clipboard, use the copy button - - Retry sync + + Copy URL to sign in - - - OCC::ProxyAuthDialog - - Proxy authentication required + + Additional logo defined by the organization - - Username: + + URL copied - - Proxy: + + Copy URL - - The proxy server needs a username and password. + + Unable to connect to the login service. If the problem continues, please contact support. - - Password: + + Please copy the authentication URL using the button above, and provide this information to support. - - TextLabel + + Failed to look up WebFinger instances: %1 - - - OCC::SelectiveSyncDialog - - Choose What to Sync + + You are already connected to an account with these credentials. - - - OCC::SelectiveSyncWidget - - Loading ... + + Unable to retrieve capabilities from server. - - Deselect remote folders you do not wish to synchronize. + + The server is not supported by this client. - - Name + + Error while trying to log in to OAuth2-enabled server. %1 - - Size + + Unable to open browser with provided URL. %1 - - - No subfolders currently on the server. + + OAuth2 authentication requires a secured connection. %1 - - An error occurred while loading the list of sub folders. + + Authorization server unreachable. %1 - OCC::ServerNotificationHandler + OCC::OCUpdater - - Dismiss + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. - - - OCC::SettingsDialog - - Settings + + Could not check for new updates. - - Activity + + Downloading %1. Please wait… - - Add account + + %1 installed successfully. Restart the application to finish installing the update. - - - Quit %1 + + %1 available. Restart application to start the update. - - Are you sure you want to quit %1? + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - Account + + New %1 available. Please use the system's update tool to install it. - - MainWindow + + Checking update server… - - Hide + + Update status is unknown: Did not check for new updates. - - - OCC::ShareDialog - - TextLabel + + No updates available. Your installation is at the latest version. + + + OCC::OwncloudPropagator - - share label + + File %1 is currently in use - - Dialog + + vfs instance is not available. + + + OCC::ProcessDirectoryJob - - ownCloud Path: + + Symbolic links are not supported in syncing. - - %1 Sharing + + File is listed on the ignore list. - - %1 + + File names ending with a period are not supported on this file system. - - Folder: %2 + + File names containing the character '%1' are not supported on this file system. - - The server does not allow sharing + + File name contains at least one invalid character - - Retrieving maximum possible sharing permissions from server... + + The file name is a reserved name on this file system. - - The file can not be shared because it was shared without sharing permission. + + Filename contains trailing spaces. - - Users and Groups + + Filename is too long. - - Public Links + + File/Folder is ignored because it's hidden. - - - OCC::ShareLinkWidget - - Share NewDocument.odt + + Stat failed. - - Upload only (File Drop) + + Conflict: Server version downloaded, local copy renamed and not uploaded. - - Receive files from others without revealing the contents of the folder. + + The filename cannot be encoded on your file system. - - Download / View / Edit + + The filename is blacklisted on the server. - - Name: + + + + + the VFS instance is missing. Unable to continue. - - Enter name of new share... + + server reported no %1 - - &Create + + + account was deleted. Unable to continue - - TextLabel + + SelectiveSync: Ignored because its path is deselected - - Set &password + + Moved to invalid target, restoring - - Set &expiration date + + Not allowed to remove, restoring - - Set password + + Error while reading the database. - - Recipients can view or download contents. + + account was deleted. Unable to continue. - - Download / View + + Not allowed because you don't have permission to add subfolders to that folder - - Recipients can view, download, edit, delete and upload contents. + + Error while doing a rename, unhandled status code: %1 - - Anyone with the link has access to the file/folder + + Not allowed because you don't have permission to add files in that folder - - - P&assword protect + + Not allowed to upload this file because it is read-only on the server, restoring - - The file can not be shared because it was shared without sharing permission. + + This Space is currently unavailable - - Link shares have been disabled + + Server replied with an error while reading directory '%1' : %2 + + + OCC::PropagateDirectory - - Create public link share + + Error updating metadata: %1 - - - Delete + + %1 the folder is currently in use + + + OCC::PropagateDownloadFile - - Open link in browser + + Failed to free up space, the file %1 is currently in use - - Copy link to clipboard + + + File %1 can not be downloaded because of a local file name clash with %2! - - Copy link to clipboard (direct download) + + The download would reduce free local disk space below the limit - - Send link by email + + Free space on disk is less than %1 - - Send link by email (direct download) + + File was deleted from server - - Create new... + + Broken webserver returned empty content length for non-empty file on resume - - I shared %1 with you + + The file could not be downloaded completely. - - Confirm Link Share Deletion + + The downloaded file is empty despite the server announced it should have been %1. - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> + + File %1 cannot be saved because of a local file name clash with %2! - - Cancel + + Error updating metadata: %1 - - - Public link + + + + + The file %1 is currently in use - - Delete link share + + + File has changed since discovery + + + OCC::PropagateItemJob - - - Please Set Password + + ; Restoration Failed: %1 - OCC::ShareUserGroupWidget + OCC::PropagateLocalMkdir - - Share NewDocument.odt + + could not delete file %1, error: %2 - - Share with users or groups ... + + Can not create local folder %1 because of a local file name clash with %2 - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> + + Error updating metadata: %1 - - The item is not shared with any users or groups + + The file %1 is currently in use - - Open link in browser + + could not create folder %1 + + + OCC::PropagateLocalRemove - - Copy link to clipboard + + %1 failed with: %2 - - Send link by email + + Could not remove %1 because of a local file name clash with %2! - - No results for '%1' + + Could not move '%1' to the trash bin - - I shared something with you + + + %1 the file is currently in use - OCC::ShareUserLine + OCC::PropagateLocalRename - - Form + + File %1 can not be renamed to %2 because of a local file name clash + + + + + Could not rename %1 to %2, the file is currently in use - - - TextLabel + + Error updating metadata: %1 - - can edit + + The file %1 is currently in use - - can share + + Failed to rename file - - ... + + Error setting pin state + + + OCC::PropagateRemoteDelete - - create + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + + + OCC::PropagateRemoteMkdir - - change + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - delete + + Error writing metadata to the database: %1 - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) + + Setting file status failed due to file lock - OCC::SocketApi + OCC::PropagateRemoteMove - - Share with %1 - parameter is ownCloud + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - Context menu share + + Error updating metadata: %1 - - Context menu share %1 + + The file %1 is currently in use - - Confirm deletion + + Error writing metadata to the database - - Do you want to delete the directory <i>%1</i> and all its contents permanently? + + Error setting pin state + + + OCC::PropagateUpdateMetaDataJob - - Do you want to delete the file <i>%1</i> permanently? + + Could not update file : %1 - - Select new location... + + The file %1 is currently in use + + + OCC::PropagateUploadCommon - - Error + + Local file changed during sync. It will be resumed. - - Moving file failed: - -%1 + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - I shared something with you + + + Upload of %1 exceeds the quota for the folder - - - Share... + + + %1 the file is currently in use - - - Copy private link to clipboard + + File Removed - - Send private link by email... + + The server did not provide the file permissions - - Resharing this file is not allowed + + Error updating metadata: %1 + + + OCC::PropagateUploadFile - - Resharing this folder is not allowed + + %1 the file is currently in use - - Create and copy public link to clipboard + + The server did ask for a removed legacy feature(polling) - - Copy public link to clipboard + + The local file was removed during sync. - - Open in browser + + The server did not acknowledge the file upload. (No e-tag was present) + + + OCC::PropagateUploadFileTUS - - Show file versions in browser + + %1 the file is currently in use - - Rename... + + Checksum computation failed - - Rename and upload... + + The local file was removed during sync. + + + OCC::ProtocolItemModel - - Move and rename... + + Time - - Move, rename and upload... + + File - - Delete local changes + + Folder - - Move and upload... + + Issues - - Delete + + Action - - - OCC::SocketUploadJob - - Failed to rerieve tags + + Size - - Backup of %1 started + + Account - - Failed to find %1 + + Status + + + OCC::RequestAuthenticationController - - Local path must be a an absolute path + + IdP is unreachable. Contact your system administrator or try again later. - - Failed to create temporary database + + Authentication failed. + + + OCC::RequestAuthenticationWidget - - Backup of %1 succeeded + + Application Name Logo %1 - - Failed to set success tag + + Sign in required - - Failed to create backup: %1 + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. - - Failed to file id tags + + Sign in URL - - Backup of %1 failed with: %2 + + To copy the sign in URL to the clipboard, use the copy button - - %1 backup + + Copy URL to sign in - - - OCC::SslButton - - <h3>Certificate Details</h3> + + Additional logo defined by the organization - - Common Name (CN): + + Stay logged out - - Subject Alternative Names: + + Sign in - - Organization (O): + + URL copied - - Organizational Unit (OU): + + Copy URL + + + OCC::ResolveUrlAdapter - - State/Province: + + SSL failure when connecting to server at %1 - - Country: + + Could not detect compatible server at %1 - - Serial: + + Resolved url is invalid %1 - - <h3>Issuer</h3> + + Rejected redirect from %1 to %2 + + + OCC::SelectiveSyncWidget - - Issuer: + + Loading … - - Issued on: + + Deselect remote folders you do not wish to synchronize. - - Expires on: + + Name - - <h3>Fingerprints</h3> + + Size - - SHA-256: + + + Currently there are no subfolders on the server. - - SHA-1: + + An error occurred while loading the list of subfolders. + + + OCC::SettingsView - - <p><b>Note:</b> This certificate was manually approved</p> + + Form - - %1 (self-signed) + + General Settings - - %1 + + Use Monochrome Icons in the system tray - - This connection is encrypted using %1 bit %2. - + + Show Desktop Notifications - - Server version: %1 + + Start on Login - - No support for SSL session tickets/identifiers + + Language - - Certificate information: + + Language selector - - The connection is not secure + + Advanced - - This connection is NOT secure as it is not encrypted. - + + Sync hidden files - - - OCC::SslErrorDialog - - Form + + Show crash reporter - - Trust this certificate anyway + + Move remotely deleted files to the local trash bin instead of deleting them - - Untrusted Certificate + + Edit Ignored Files - - Cannot connect securely to <i>%1</i>: + + Log Settings - - with Certificate %1 + + Network - - - - &lt;not specified&gt; + + Warning - - - Organization: %1 + + Language changes require a restart of this application to take effect. - - - Unit: %1 + + You cannot disable autostart because system-wide autostart is enabled. - - - Country: %1 + + (use default) - - Fingerprint (MD5): <tt>%1</tt> + + unknown (%1) + + + OCC::SocketApi - - Fingerprint (SHA1): <tt>%1</tt> + + Share with %1 + parameter is the application name - - Effective Date: %1 + + Confirm deletion - - Expiration Date: %1 + + Do you want to delete the directory <i>%1</i> and all its contents permanently? - - Issuer: %1 + + Do you want to delete the file <i>%1</i> permanently? - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) + + Error - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() + + Moving file failed: + +%1 - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. + + I shared something with you - - Disk space is low: Downloads that would reduce free space below %1 were skipped. + + Select new location… - - There is insufficient space available on the server for some uploads. + + + Share… - - Unresolved conflict. + + + Copy private link to clipboard - - Using virtual files with suffix, but suffix is not set + + Resharing this file is not allowed - - Unable to read the blacklist from the local database + + Resharing this folder is not allowed - - Unable to read from the sync journal. + + Open in %1 - - Cannot open the sync journal + + Show file versions in web browser - - Aborted + + Rename… - - - OCC::SyncJournalDb - - Failed to connect database. + + Rename and upload… - - - OCC::Systray - - %1: %2 + + + Move and rename… - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + + Move, rename and upload… - - - OCC::ValidateChecksumHeader - - The checksum header is malformed. + + Move and upload… - - The checksum header contained an unknown checksum type '%1' + + Delete local changes - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + + Delete - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - + OCC::SpacesPage - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + + Select a folder to sync it to your computer. - - The Virtual filesystem feature is not supported on network drives + + Select a Space to sync it to your computer. - OCC::ownCloudGui - - - Please sign in - - + OCC::SyncEngine - - Folder %1: %2 + + %1 (skipped due to earlier error, trying again in %2) - - Unresolved %1 conflicts + + Unable to sync: the local folder is missing. - - Ignored errors %1 + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() - - There are no sync folders configured. + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - Open in browser + + + + + Cannot open the sync journal. - - - - Log in... + + Aborted due to %1 - - - - Log out + + Disk space is low: Downloads that would reduce free space below %1 were skipped. - - Recent Changes + + There is insufficient space available on the server for some uploads. - - Managed Folders: + + application exit + abort reason - - Open folder '%1' + + Unable to read the blacklist from the local database - - Open %1 in browser + + Unable to read from the sync journal. + + + OCC::SyncErrorWidget - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + + Issues table - - Unknown status + + There were too many issues. Not all will be visible here. - - Settings... + + There were conflicts. Check the documentation on how to resolve them. - - Details... + + The file %1 was ignored as its name is reserved by %2 - - Help + + Reset column sizes - - Quit %1 + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - Disconnected from %1 + + Filter menu - - Unsupported Server Version + + Account - - Disconnected + + Filter + + + OCC::SyncJournalDb - - Disconnected from some accounts + + Failed to connect database. + + + OCC::Theme - - Disconnected from accounts: + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Account %1: %2 + + TLS Certificate Error - - Signed out + + Do you want to trust this certificate anyway? - - Account synchronization is disabled + + Cannot connect securely to %1 - - Synchronization is paused + + &lt;not specified&gt; - - Up to date (%1) + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + + + OCC::TrayMenuController - - No sync folders configured + + Unsupported Server Version - - Unpause all folders + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - Pause all folders + + Show %1 - - Unpause all synchronization + + Help - - Unpause synchronization + + About %1 - - Pause all synchronization + + Quit %1 + + + OCC::UpdateDownloadedWidget - - Pause synchronization + + Restart later - - Log out of all accounts + + Restart now + + + OCC::UpdaterScheduler - - Log in to all accounts... + + Update available + + + OCC::UrlPageController - - New account... + + Your web browser will be opened to complete sign in. - - About %1 + + %1 logo - - No items synced recently + + Welcome to %1 - - Checking for changes in remote '%1' + + Enter your server address to get started. Your web browser will be opened to complete sign in. - - Checking for changes in local '%1' + + Server address field - - Syncing %1 of %2 (%3 left) + + Enter your server address here - - Syncing %1 of %2 + + Additional logo defined by the organization - - Syncing %1 (%2 left) + + Invalid server URL: %1 - - Syncing %1 + + Invalid URL scheme. Only https is accepted. - - %1 (%2, %3) + + The requested URL failed with query value: %1 - OwncloudAdvancedSetupPage + OCC::UserInfoAdapter - - Form + + Network request to collect user info failed - - - TextLabel + + Unable to retrieve user info: invalid credentials. - - Server + + Unexpected network response when retrieving user info. - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> + + Retrieving user info failed with JSON error. + + + OCC::ValidateChecksumHeader - - Ask for confirmation before synchroni&zing folders larger than + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + + + OCC::Vfs - - S&ynchronize everything from server + + The Virtual filesystem feature does not support a drive as sync root - - MB - Trailing part of "Ask confirmation before syncing folder larger than" + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - Ask for confirmation before synchronizing e&xternal storages + + The Virtual filesystem feature is not supported on network drives + + + OCC::VfsWinPrivate - - Choose what to sync + + + %1 - %2 - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> + + Could not find StorageProviderState property %1 - - Manually create folder sync connections + + Could not find StorageFolder for %1 error: %2 - - &Local Folder + + Could not retrieve StorageFolder for %1 %2 (0x%3) - - pbSelectLocalFolder + + The folder is used by a different client: %1 - - &Keep local data + + Error registering StorageProvider for %1: %2 (0x%3) - - Start a &clean sync (Crreate a backup and erases the local folder!) + + Unable to connect sync root: %1 error: %2 - - - OwncloudHttpCredsPage - - Form + + %1 is managed by another sync client - - &Username + + Downgrading the folder from %1 to %2 is not supported + + + OCC::WebFingerLookupAdapter - - &Password + + Failed to retrieve user info - - - OwncloudOAuthCredsPage - - Form + + WebFinger lookup returned no links + + + OCC::WindowsUpdater - - Please switch to your browser to proceed. + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - - An error occurred while connecting. Please try again. + + Skip this version - - Re-open Browser + + Update Failed - - - OwncloudSetupPage - - Form + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - Ser&ver Address + + Ask again later - - https://... + + Restart and update - - Error Label + + Update manually QObject - + in the future - + %n day(s) ago - - - + + %n day ago + %n days ago - + %n hour(s) ago - - - + + %n hour ago + %n hours ago - + now - - Less than a minute ago + + less than a minute ago - + %n minute(s) ago - - - + + %n minute ago + %n minutes ago - - Some time ago + + %1: %2 + this displays an error string (%2) for a file %1 + + + + + SpacesBrowser + + + Form + + + SpacesPage - - %1: %2 - this displays an error string (%2) for a file %1 + + Spaces list + + + + + SpacesView + + + Spaces SyncFileItem::Status - + Undefined - + Fatal Error - - + + Error - + Info - + Success - + Conflict - + File Ignored - + Restored - + Blacklisted - + Excluded - - - Utility - - - %L1 GB - - - - %L1 MB + + Message - - %L1 KB + + Filename Reserved + + + UpdateDownloadedWidget - - %L1 B + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + + + Utility - + %n year(s) - - - + + %n year + %n years - + %n month(s) - - - + + %n month + %n months - + %n day(s) - - - + + %n day + %n days - + %n hour(s) - - - + + %n hour + %n hours - + %n minute(s) - - - + + %n minute + %n minutes - + %n second(s) - - - + + %n second + %n seconds - + %1 %2 + + + Shutting down %1 + + - main.cpp + account loading - - System Tray not available + + Error accessing the configuration file - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. + + There was an error while accessing the configuration file at %1. ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) + + %1 (Built against Qt %2) ownCloudTheme::versionWithSha - + %1 %2 + + pinstate + + + Always available locally + + + + + Currently available locally + + + + + Some available online only + + + + + Available online only + + + progress @@ -4748,28 +3596,28 @@ This is a new, experimental mode. If you decide to use it, please report any iss - - Server version downloaded, copied changed local file into conflict file + + Deleted - - Deleted + + Ignored - - Moved to %1 + + Server version downloaded, local copy was backed up as conflict file - - Ignored + + Renamed %1 to %2 - - Filesystem access error + + Moved %1 to %2 @@ -4783,152 +3631,85 @@ This is a new, experimental mode. If you decide to use it, please report any iss - - + + Unknown - + downloading - + uploading - + deleting - + moving - + ignoring - - + error - + updating local metadata - - theme - - - Status undefined - - - - - Waiting to start sync - - - - - Sync is running - - - - - Sync Success - - - - - Sync Success, some files were ignored. - - - - - Sync Error - - - - - Setup Error - - - - - Preparing to sync - - - - - Aborting... - - - - - Sync is paused - - - utility - + Could not open browser - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - + Could not open email client - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally + + Make always available locally - - Make always available locally + + Free up local space + + + version check - - Free up local space + + Some settings were configured in newer versions of this client and use features that are not available in this version diff --git a/translations/client_en_GB.ts b/translations/client_en_GB.ts index f1ba1c9fedc..a9320cef3f2 100644 --- a/translations/client_en_GB.ts +++ b/translations/client_en_GB.ts @@ -1,360 +1,424 @@ - + - CrashReporter + AccountBar - - Tomahawk Crash Reporter - + + Navigation bar + Navigation bar - - <html><head/><body><p><br/></p></body></html> - + + Add Account + Add Account - - Abort - Abort + + Activity + Activity - - Send this report - + + Settings + Settings - - Don't send - + + Quit + Quit + + + AppImageUpdateAvailableWidgetUi - - You can disable sending crash reports in the configuration dialog. - + + Skip this version + Skip this version + + + AppProvider - - - - We cannot gather useful debug information on your system. - + + Failed to open %1 in web. Error: %2. + Failed to open %1 in web. Error: %2. + + + ChecksumHeader - - - - - - Close - Close + + The checksum header is malformed: %1 + The checksum header is malformed: %1 - - Ready to send debug information (<a href="%1">view backtrace</a>). - + + The checksum header contained an unknown checksum type '%1' + The checksum header contained an unknown checksum type '%1' + + + CommandLine - - Gathering debug information... - + + %1 version %2 +File synchronization desktop utility. + %1 version %2<byte value="xd"/> +File synchronisation desktop utility. - - Uploaded %L1 of %L2 KB. - + + For more information, see %1 + link to homepage + For more information, see %1 - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. - - Failed to send crash info. - + + Quit the running instance. + Quit the running instance. - - - FileSystem - - - - - Can't rename %1, the file is currently in use - + + Write log to file (use - to write to stdout). + Write log to file (use - to write to stdout). - - Could not make directories in trash - Could not make directories in the rubbish bin + + Write each sync log output in a new file in folder. + Write each sync log output in a new file in folder. - - - Could not move '%1' to '%2' - Could not move '%1' to '%2' + + Flush the log file after every write. + Flush the log file after every write. - - Moving to the trash is not implemented on this platform - Moving to the rubbish bin is not implemented on this platform + + Output debug-level messages in the log. + Output debug-level messages in the log. - - Error removing '%1': %2 - Error removing '%1': %2 + + Enable debug mode. + Enable debug mode. - - Could not remove folder '%1' - Could not remove folder '%1' + + --logfile and --logdir are mutually exclusive + --logfile and --logdir are mutually exclusive - FolderWizardSourcePage + CommonStrings - - Form - Form + + file manager + file manager - - Pick a local folder on your computer to sync - Pick a local folder on your computer to sync + + Show in %1 + Show in %1 - - &Choose... - &Choose... + + Show in web browser + Show in web browser - - - FolderWizardTargetPage - - Form - Form + + Copy + Copy + + + + %n Filter(s) + %n Filter%n Filters - - Select a remote destination folder - Select a remote destination folder + + folder + folder - - Create Folder - Create Folder + + space + space - - Refresh - Refresh + + folders + folders - - Folders - Folders + + spaces + spaces - - TextLabel - TextLabel + + Folder + Folder - - - NotificationWidget - - Form - Form + + Space + Space + + + FetchUserInfoJobFactory - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet + + Failed to retrieve user info + Failed to retrieve user info + + + FileSystem - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + + + + Can't rename %1, the file is currently in use + Can't rename %1, the file is currently in use - - TextLabel - TextLabel + + Could not remove folder + Could not remove folder - OCC::AboutDialog + FolderItem - - About - About + + + Offline + Offline - - TextLabel - TextLabel + + + Unavailable: the space is no longer available on the server + Unavailable: the space is no longer available on the server - - Versions - Versions + + + Sync paused + Sync paused - - About %1 - About %1 + + Synced + Synced - - - OCC::AbstractNetworkJob - - Connection timed out - Connection timed out + + Sync failed + Sync failed - - Unknown error: network reply was deleted - Unknown error: network reply was deleted + + Sync pending + Sync pending - - Server replied "%1 %2" to "%3 %4" - Server replied "%1 %2" to "%3 %4" + + Syncing %1 of %2 (%3 % + Syncing %1 of %2 (%3 % + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, sync status: %2 + first argument is the displayed name of the folder. - OCC::Account + FolderManagementUtils - - %1@%2 - + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + + + + Local folder %1 does not exist. + Local folder %1 does not exist. + + + + %1 is not a folder. + %1 is not a folder. + + + + %1 is not readable. + %1 is not readable. + + + + %1 is not writable. + %1 is not writable. - OCC::AccountSettings + FolderWizard - - Form - Form + + <b>Error:</b> %1 + <b>Error:</b> %1 - - Re-open Browser - Re-open Browser + + <b>Warning:</b> %1 + <b>Warning:</b> %1 - - ... - ... + + <b>Error:</b><ul>%1</ul> + <b>Error:</b><ul>%1</ul> - - Storage space: ... - Storage space: ... + + <b>Warning:</b><ul>%1</ul> + <b>Warning:</b><ul>%1</ul> + + + FolderWizardSourcePage - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronised to this computer anymore + + Form + Form - - Synchronize all - Synchronise all + + Pick a local folder on your computer to sync + Pick a local folder on your computer to sync - - Synchronize none - Synchronise none + + Browse… + Browse… + + + FolderWizardTargetPage - - Apply manual changes - Apply manual changes + + Form + Form - - Apply - Apply + + Select a remote destination folder + Select a remote destination folder - - - - - Cancel - Cancel + + Create Folder + Create Folder - - Connected with <server> as <user> - Connected with <server> as <user> + + Refresh + Refresh - - No account configured. - No account configured. + + Folders + Folders + + + OCC::AboutView - - Remove - Remove + + About + About - - Account - Account + + Versions + Versions + + + OCC::AbstractNetworkJob - - Choose what to sync - Choose what to sync + + Connection timed out + Connection timed out - - Force sync now - Force sync now + + Unknown error: network reply was deleted + Unknown error: network reply was deleted - - Restart sync - Restart sync + + Server replied "%1 %2" to "%3 %4" + Server replied "%1 %2" to "%3 %4" + + + OCC::Account - - Remove folder sync connection - Remove folder sync connection + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - Folder creation failed - Folder creation failed + + Choose what to sync + Choose what to sync - - <p>Could not create local folder <i>%1</i>. - <p>Could not create local folder <i>%1</i>. + + + Sync now + Sync now - - Confirm Folder Sync Connection Removal - Confirm Folder Sync Connection Removal + + Manage subfolder sync + Manage subfolder sync - - Remove Folder Sync Connection - Remove Folder Sync Connection + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + Remove %1 sync - + Disable virtual file support? Disable virtual file support? - + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. The only advantage of disabling virtual file support is that the selective sync feature will become available again. @@ -367,572 +431,553 @@ The only advantage of disabling virtual file support is that the selective sync This action will abort any currently running synchronisation. - + Disable support Disable support - - Sync Running - Sync Running + + Restart sync + Restart sync - - The syncing operation is running.<br/>Do you want to terminate it? - The syncing operation is running.<br/>Do you want to terminate it? + + Resume sync + Resume sync - - %1 in use - %1 in use + + Pause sync + Pause sync - - %1 as <i>%2</i> - %1 as <i>%2</i> + + Deactivate virtual files + Deactivate virtual files - - Connected to %1. - Connected to %1. + + Activate virtual files + Activate virtual files - - Server %1 is temporarily unavailable. - Server %1 is temporarily unavailable. + + Internet connection is metered + Internet connection is metered - - Server %1 is currently in maintenance mode. - Server %1 is currently in maintenance mode. + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + Synchronisation is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronisation now? - - Signed out from %1. - Signed out from %1. + + Sync Running + Sync Running - - Connecting to %1... - Connecting to %1... + + The sync operation is running.<br/>Do you want to stop it? + The sync operation is running.<br/>Do you want to stop it? - - No connection to %1 at %2. - No connection to %1 at %2. + + Sync paused by user + Sync paused by user - - Log in - Log in + + Confirm Folder Sync Connection Removal + Confirm Folder Sync Connection Removal - - There are folders that were not synchronized because they are too big: - There are folders that were not synchronised because they are too big: + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - There are folders that were not synchronized because they are external storages: - There are folders that were not synchronised because they are external storages: + + Remove Folder Sync Connection + Remove Folder Sync Connection - - There are folders that were not synchronized because they are too big or external storages: - There are folders that were not synchronised because they are too big or external storages: + + + Cancel + Cancel + + + OCC::AccountFoldersView - - Confirm Account Removal - Confirm Account Removal + + Sync options menu + Sync options menu - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 sync - - Remove connection - Remove connection + + Manage your synced %1. + Manage your synced %1. - - Open folder - Open folder + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Add new %1 sync… - - - Log out - Log out + + %1 list view + %1 list view - - Open local folder - Open local folder + + Navigate the %1 list using the up and down arrows + Navigate the %1 list using the up and down arrows - - Open folder in browser - Open folder in browser + + %1 out of %2 %3 are synchronized + %1 out of %2 %3 are synchronised + + + OCC::AccountModalWidget - - Resume sync - Resume sync + + + Form + Form + + + OCC::AccountPlaceholderWidget - - Pause sync - Pause sync + + %1 logo + %1 logo - - Availability - Availability + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. - - Disable virtual file support... - Disable virtual file support... + + Additional logo defined by the organization + Additional logo defined by the organization + + + OCC::AccountView - - Enable virtual file support%1... - + + Form + Form - - (experimental) - + + Manage Account + Manage Account - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + + Preparing the account + Preparing the account - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. + + Sync connections + Sync connections - - %1 of %2 in use - %1 of %2 in use + + Account options menu + Account options menu + + + OCC::AccountViewController - - Currently there is no storage usage information available. - There is currently no storage usage information available. + + + Log in + Log in - - The server version %1 is unsupported! Proceed at your own risk. - The server version %1 is unsupported! Proceed at your own risk. + + Reconnect + Reconnect - - Obtaining authorization from the browser. - + + Remove + Remove - - Server configuration error: %1 at %2. - + + Log out + Log out - - No %1 connection configured. - No %1 connection configured. + + Confirm Account Removal + Confirm Account Removal - - - OCC::AccountState - - Url update requested for %1 - + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - + + Remove connection + Remove connection - - Change url permanently to %1 - + + Cancel + Cancel - - Reject - + + The server version %1 is unsupported! Proceed at your own risk. + The server version %1 is unsupported! Proceed at your own risk. - - Signed out - Signed out + + Connected + Connected - - Disconnected - Disconnected + + Server is temporarily unavailable. + Server is temporarily unavailable. - - Connected - Connected + + Server is currently in maintenance mode. + Server is currently in maintenance mode. - - Service unavailable - Service unavailable + + Signed out + Signed out - - Maintenance mode - Maintenance mode + + Updating credentials… + Updating credentials… - - Network error - Network error + + Captive portal prevents connections to the server. + Captive portal prevents connections to the server. - - Configuration error - Configuration error + + Sync is paused due to metered internet connection. + Sync is paused due to metered Internet connection. - - Asking Credentials - Asking Credentials + + Connecting… + Connecting… - - Unknown account state - Unknown account state + + Server configuration error + Server configuration error - - - OCC::ActivityListModel - - %1 %2 on %3 - + + Disconnected + Disconnected - - Activity - Activity + + Invalid connection status + Invalid connection status + + + OCC::AccountsGuiController - - Account - Account + + + New account failure + New account failure - - Time - Time + + The information required to create a new account is incomplete. Please run the wizard again. + The information required to create a new account is incomplete. Please run the wizard again. - - Local path - + + Accounts + Accounts + + + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. OCC::ActivitySettings - - - Server Activity - Server Activity - - - - Sync Protocol - Sync Protocol + + Local Activity + Local Activity - + Not Synced Not Synced - + Not Synced (%1) %1 is the number of not synced files. Not Synced (%1) - OCC::ActivityWidget - - - Form - Form - - - - - - TextLabel - TextLabel - - - - Filter - Filter - - - - Server Activities - Server Activities - - - - Notifications - Notifications - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - - - - - Open the activity view for details. - - - - - Copy to clipboard - Copy to clipboard - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>Account %1 does not have activities enabled. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Continuing will mean <b>deleting these settings</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Continuing will mean <b>ignoring these settings</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - + OCC::AdvancedSettingsPageController - - Quit - Quit + + Advanced settings + Advanced settings - - Continue - Continue + + Sync and download options + Sync and download options - - Error accessing the configuration file - Error accessing the configuration file + + Download location + Download location - - There was an error while accessing the configuration file at %1. - There was an error while accessing the configuration file at %1. + + Sync and download files as you use them (saves hard drive space) + Sync and download files as you use them (saves hard drive space) - - Quit %1 - Quit %1 + + Automatically sync and download folders and files + Automatically sync and download folders and files - - %1 version %2 -File synchronization desktop utility. - + + Sync and download folders manually + Sync and download folders manually - - For more information, see http://www.owncloud.org - + + Download location on the local machine + Download location on the local machine - - Show the settings dialog while starting. - + + Browse… + Browse… - - Quit the running instance. - + + Browse for a download location + Browse for a download location - - Write log to file (use - to write to stdout). - + + Invalid local download directory %1: %2 + Invalid local download directory %1: %2 - - Write each sync log output in a new file in folder. - + + your user directory may not be chosen as the sync root. + your user directory may not be chosen as the sync root. - - Remove logs older than <hours> hours (to be used with --logdir). - + + path is not located on a supported Apple File System. + path is not located on a supported Apple File System. - - Flush the log file after every write. - + + path must be absolute. + path must be absolute. - - Output debug-level messages in the log. - + + selected path does not support using virtual file system. %1 + selected path does not support using virtual file system. %1 - - - Override UI language. - + + Select sync root + Select sync root + + + OCC::AppImageUpdateAvailableWidget - - Use the given configuration folder. - + + Installed version: %1 + Installed version: %1 - - Enable debug mode. - + + Available update: %1 + Available update: %1 - - Virtual file system file to be opened (optional). - + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + + + OCC::Application - - [<vfs file>] - + + Quit + Quit + + + OCC::AuthSuccessPageController - - Invalid path passed to --confdir - + + Application Name Logo %1 + Application Name Logo %1 - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - + + You're all set! Open %1 and get started. + You're all set! Open %1 and get started. - - Available translations: %1 - + + Additional logo defined by the organization + Additional logo defined by the organization - OCC::AuthenticationDialog - - - Authentication Required - Authentication Required - - - - Enter username and password for '%1' at %2. - Enter username and password for '%1' at %2. - + OCC::ButtonDelegate - - &User: - &User: + + %1 options button + %1 options button - - &Password: - &Password: + + Menu button with %1 options. Use the space key to show the menu + Menu button with %1 options. Use the space key to show the menu OCC::ConnectionValidator - + No ownCloud account configured No ownCloud account configured - - The configured server for this client is too old - The configured server for this client is too old - - - + Please update to the latest server and restart the client. Please update to the latest server and restart the client. - + Authentication error: Either username or password are wrong. Authentication error: Either username or password is wrong. - + timeout timeout - + + No account configured + No account configured + + + The provided credentials are not correct The provided credentials are not correct + + + The configured server for this client is too old. + The configured server for this client is too old. + OCC::CredentialJob - + Failed to parse credentials %1 - + Failed to parse credentials %1 + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + Server does not support OAuth + + + + Server did not ask for authorization + Server did not ask for authorisation + + + + Failed to determine authentication type: %1 + Failed to determine authentication type: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + Invalid reply received from server. OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + The account was deleted before we could start the propfind job + + + + Server error: PROPFIND reply is not XML formatted! Server error: PROPFIND reply is not XML formatted! @@ -940,27 +985,22 @@ File synchronization desktop utility. OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 Error while opening directory %1 - + Directory not accessible on client, permission denied Directory not accessible on client, permission denied - + Directory not found: %1 Directory not found: %1 - - Filename encoding is not valid - Filename encoding is not valid - - - + Error while reading directory %1 Error while reading directory %1 @@ -968,148 +1008,137 @@ File synchronization desktop utility. OCC::ExpandingHeaderView - + Reset column sizes - + Reset column sizes - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - Local folder %1 does not exist. + + server sent invalid content type: %1. + server sent invalid content type: %1. - - %1 should be a folder but is not. - %1 should be a folder but is not. + + could not parse Capabilities from server. + could not parse Capabilities from server. - - %1 is not readable. - %1 is not readable. + + reply sent for different subject (server): %1 + reply sent for different subject (server): %1 + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 has been removed. - + %1 has been updated. %1 names a file. %1 has been updated. - + %1 has been renamed to %2. %1 and %2 name files. %1 has been renamed to %2. - + %1 has been moved to %2. %1 has been moved to %2. - + %1 and %n other file(s) have been removed. - %1 and %n other file(s) have been removed.%1 and %n other file(s) have been removed. + %1 and %n other file have been removed.%1 and %n other files have been removed. - - %1 is not writable. - + + The folder has been disabled or removed from the server + The folder has been disabled or removed from the server - + %1 and %n other file(s) have been added. - %1 and %n other file(s) have been added.%1 and %n other file(s) have been added. + %1 and %n other file have been added.%1 and %n other files have been added. - + %1 has been added. %1 names a file. %1 has been added. - + %1 and %n other file(s) have been updated. - %1 and %n other file(s) have been updated.%1 and %n other file(s) have been updated. + %1 and %n other file have been updated.%1 and %n other files have been updated. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 has been renamed to %2 and %n other file(s) have been renamed.%1 has been renamed to %2 and %n other file(s) have been renamed. + %1 has been renamed to %2 and %n other file have been renamed.%1 has been renamed to %2 and %n other files have been renamed. - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 has been moved to %2 and %n other file(s) have been moved.%1 has been moved to %2 and %n other file(s) have been moved. + %1 has been moved to %2 and %n other file have been moved.%1 has been moved to %2 and %n other files have been moved. - - %1 has and %n other file(s) have sync conflicts. - %1 has and %n other file(s) have sync conflicts.%1 has and %n other file(s) have sync conflicts. + + %1 and %n other file(s) have sync conflicts. + %1 and %n other file have sync conflicts.%1 and %n other files have sync conflicts. - + %1 has a sync conflict. Please check the conflict file! %1 has a sync conflict. Please check the conflict file! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 and %n other file(s) could not be synced due to errors. See the log for details.%1 and %n other file(s) could not be synced due to errors. See the log for details. + %1 and %n other file could not be synced due to errors. See the log for details.%1 and %n other files could not be synced due to errors. See the log for details. - + %1 could not be synced due to an error. See the log for details. %1 could not be synced due to an error. See the log for details. - + Sync Activity Sync Activity - - Could not read system exclude file - Could not read system exclude file - - - - A new folder larger than %1 MB has been added: %2. - - A new folder larger than %1 MB has been added: %2. - - - - - A folder from an external storage has been added. - - A folder from an external storage has been added. - + + Switching VFS mode on folder '%1' + Switching VFS mode on folder '%1' - - Please go in the settings to select it if you wish to download it. - Please go in the settings to select it if you wish to download it. + + Could not read system exclude file + Could not read system exclude file - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. The folder %1 was created but was excluded from synchronisation previously. Data inside it will not be synchronised. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. The file %1 was created but was excluded from synchronisation previously. It will not be synchronised. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1121,3583 +1150,2409 @@ This means that the synchronisation client might not upload local changes immedi %1 + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronised to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - All the files in your local sync folder '%1' were deleted. These deletes will be synchronised with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - Remove All Files? - Remove All Files? - - - - Remove all files - Remove all files + + There are unresolved conflicts. + There are unresolved conflicts. - - Keep files - Keep files + + Sync error: %1 + Sync error: %1 OCC::FolderMan - + Could not reset folder state Could not reset folder state - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - - (backup) - (backup) - - - - (backup %1) - (backup %1) + + Unsupported filesystem + Unsupported filesystem - - Undefined State. - Undefined State. + + Account disconnected or paused + Account disconnected or paused - - Waiting to start syncing. - Waiting to start syncing. + + User triggered force sync + User triggered force sync - - Preparing for sync. - Preparing for sync. + + Folder is about to be removed + Folder is about to be removed - - Sync is running. - Sync is running. + + (backup) + (backup) - - Sync was successful, unresolved conflicts. - Sync was successful, unresolved conflicts. + + (backup %1) + (backup %1) - - Last Sync was successful. - Last sync was successful. + + The selected path is not a folder. + The selected path is not a folder. - - Setup Error. - Setup Error. + + The folder %1 is used in a folder sync connection. + The folder %1 is used in a folder sync connection. - - User Abort. - User Abort. + + Folder '%1' is already in use by application %2. + Folder '%1' is already in use by application %2. - - Sync is paused. - Sync is paused. + + There is already a sync from the server to this local folder. + There is already a sync from the server to this local folder. - - %1 (Sync is paused) - %1 (Sync is paused) + + The local folder %1 already contains a folder used in a folder sync connection. + The local folder %1 already contains a folder used in a folder sync connection. - - No valid folder selected! - No valid folder selected! + + The local folder %1 is already contained in a folder used in a folder sync connection. + The local folder %1 is already contained in a folder used in a folder sync connection. - - The folder %1 is used in a folder sync connection! - + + No valid folder selected. + No valid folder selected. - - The selected path does not exist! - The selected path does not exist! + + The selected path does not exist. + The selected path does not exist. - - The selected path is not a folder! - The selected path is not a folder! + + You have no permission to write to the selected folder. + You have no permission to write to the selected folder. - - You have no permission to write to the selected folder! - You don't have permission to write to the selected folder! + + Folder '%1' is already in use by another account. + Folder '%1' is already in use by another account. - - There is already a sync from the server to this local folder. Please pick another local folder! - There is already a sync from the server to this local folder. Please pick another local folder! + + On macOS, only the Apple File System is supported. + On macOS, only the Apple File System is supported. + + + OCC::FolderModelController - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! + + %1 options + %1 options - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! + + Menu button with %1 options. Use the space key to pop the %2 options menu + Menu button with %1 options. Use the space key to pop the %2 options menu + + + OCC::FolderWatcher - - %1 Please pick another one! - + + The watcher did not receive a test notification. + The watcher did not receive a test notification. + + + OCC::FolderWatcherPrivate - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - OCC::FolderStatusDelegate + OCC::FolderWizard - + + Virtual files are not available for the selected folder + Virtual files are not available for the selected folder + + + Add Folder Sync Connection Add Folder Sync Connection - - File - File + + Add Sync Connection + Add Sync Connection - OCC::FolderStatusModel + OCC::FolderWizardLocalPath - - You need to be connected to add a folder - You need to be connected to add a folder + + Click to select a local folder to sync. + Click to select a local folder to sync. - - Click this button to add a folder to synchronize. - Click this button to add a folder to synchronise. + + Enter the path to the local folder. + Enter the path to the local folder. - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Select the local folder + Select the local folder + + + OCC::FolderWizardSelectiveSync - - Error while loading the list of folders from the server. - Error whilst loading the list of folders from the server. + + Use virtual files instead of downloading content immediately + Use virtual files instead of downloading content immediately + + + OCC::GETFileJob - - Virtual file support is enabled. - Virtual file support is enabled. + + Connection Timeout + Connection Timeout - - Signed out - Signed out + + No E-Tag received from server, check Proxy/Gateway + No E-Tag received from server, check Proxy/Gateway - - Fetching folder list from server... - Fetching folder list from server... + + We received a different E-Tag for resuming. Retrying next time. + We received a different E-Tag for resuming. Retrying next time. - - There are unresolved conflicts. Click for details. - There are unresolved conflicts. Click for details. + + We received an unexpected download Content-Length. + We received an unexpected download Content-Length. - - Checking for changes in remote '%1' - Checking for changes in remote '%1' + + Server returned wrong content-range + Server returned wrong content-range + + + OCC::GraphApi::Space - - Checking for changes in local '%1' - Checking for changes in local '%1' + + Personal + Personal - - Reconciling changes - Reconciling changes + + Shares + Shares + + + OCC::IgnoreListEditor - - , '%1' - Build a list of file names - , '%1' + + Ignored Files Editor + Ignored Files Editor - - '%1' - Argument is a file name - '%1' + + Files Ignored by Patterns + Files Ignored by Patterns - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Syncing %1 + + Add + Add - - - , - , + + Pattern + Pattern - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - download %1/s + + Allow Deletion + Allow Deletion - - ↓ %1/s - ↓ %1/s + + Remove + Remove - - ↑ %1/s - ↑ %1/s + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Files or folders matching a pattern will not be synchronised. Changes take effect the next time folders are synchronised. + +Items, where deletion is allowed, will be deleted if they prevent a directory from being removed. This is useful for metadata. - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - upload %1/s + + Could not open file + Could not open file - - Local folder: %1 - + + Cannot write changes to '%1'. + Cannot write changes to '%1'. - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 of %4) + + Add Ignore Pattern + Add Ignore Pattern - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 + + Add a new ignore pattern: + Add a new ignore pattern: - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 left, %1 of %2, file %3 of %4 + + This entry is provided by the system at '%1' and cannot be modified in this view. + This entry is provided by the system at '%1' and cannot be modified in this view. + + + OCC::LocalActivityWidget - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 of %2, file %3 of %4 + + Filter + Filter - - file %1 of %2 - file %1 of %2 + + Local activity table + Local activity table - - Waiting... - Waiting... - - - - Waiting for %n other folder(s)... - Waiting for %n other folder(s)...Waiting for %n other folder(s)... + + + Account + Account - - Preparing to sync... - Preparing to sync... + + Filter menu + Filter menu - - - OCC::FolderWatcher - - The watcher did not receive a test notification. - The watcher did not receive a test notification. + + Actions menu + Actions menu - - - OCC::FolderWatcherPrivate - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + + Sort ascending by %1 + Sort ascending by %1 - - - OCC::FolderWizard - - Add Folder Sync Connection - Add Folder Sync Connection + + Sort descending by %1 + Sort descending by %1 - - Add Sync Connection - Add Sync Connection + + Retry sync + Retry sync - OCC::FolderWizardLocalPath + OCC::LogBrowser - - Click to select a local folder to sync. - Click to select a local folder to sync. + + Log Output + Log Output - - Enter the path to the local folder. - Enter the path to the local folder. + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> - - Select the source folder - Select the source folder + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. - - - OCC::FolderWizardRemotePath - - Create Remote Folder - Create Remote Folder - - - - Enter the name of the new folder to be created below '%1': - Enter the name of the new folder to be created below '%1': - - - - Folder was successfully created on %1. - Folder was successfully created on %1. - - - - Authentication failed accessing %1 - Authentication failed accessing %1 - - - - Failed to create the folder on %1. Please check manually. - Failed to create the folder on %1. Please check manually. - - - - Failed to list a folder. Error: %1 - Failed to list a folder. Error: %1 - - - - Choose this to sync the entire account - Choose this to sync the entire account - - - - This folder is already being synced. - This folder is already being synced. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Warning:</b> %1 - - - - <b>Warning:</b> - <b>Warning:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - No E-Tag received from server, check Proxy/Gateway - - - - We received a different E-Tag for resuming. Retrying next time. - We received a different E-Tag for resuming. Retrying next time. - - - - We received an unexpected download Content-Length. - We received an unexpected download Content-Length. - - - - Server returned wrong content-range - Server returned wrong content-range - - - - OCC::GETJob - - - Connection Timeout - Connection Timeout - - - - OCC::GeneralSettings - - - Form - Form - - - - General Settings - General Settings - - - - Language - Language - - - - Sync hidden files - Sync hidden files - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Network - - - - Updates - Updates - - - - stable - stable - - - - beta - beta - - - - About - About - - - - Advanced - Advanced - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Ask for confirmation before synchronising folders larger than - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Ask for confirmation before synchronising external storages - - - - &Launch on System Startup - &Launch on System Startup - - - - Show &Desktop Notifications - Show &Desktop Notifications - - - - &Update Channel - &Update Channel - - - - Edit &Ignored Files - Edit &Ignored Files - - - - Show sync folders in &Explorer's Navigation Pane - Show sync folders in &Explorer's Navigation Pane - - - - S&how crash reporter - S&how crash reporter - - - - &Restart && Update - &Restart && Update - - - - You cannot disable autostart because system-wide autostart is enabled. - You cannot disable autostart because system-wide autostart is enabled. - - - - Change update channel? - Change update channel? - - - - Warning - Warning - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - Change update channel - Change update channel - - - - Cancel - Cancel - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - - - - Reading from keychain failed with error: '%1' - Reading from keychain failed with error: '%1' - - - - Enter Password - Enter Password - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Click here</a> to request an app password from the web interface. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Ignored Files Editor - - - - Files Ignored by Patterns - Files Ignored by Patterns - - - - Add - Add - - - - Pattern - Pattern - - - - Allow Deletion - Allow Deletion - - - - Remove - Remove - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Could not open file - - - - Cannot write changes to '%1'. - Cannot write changes to '%1'. - - - - Add Ignore Pattern - Add Ignore Pattern - - - - Add a new ignore pattern: - Add a new ignore pattern: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - This entry is provided by the system at '%1' and cannot be modified in this view. - - - - OCC::IssuesWidget - - - Form - Form - - - - List of issues - List of issues - - - - Filter - Filter - - - - There were too many issues. Not all will be visible here. - There were too many issues. Not all will be visible here. - - - - There were conflicts. Check the documentation on how to resolve them. - There were conflicts. Check the documentation on how to resolve them. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - Account - Account - - - - OCC::LibCloudProvidersPrivate - - - Settings - Settings - - - - OCC::LogBrowser - - - Log Output - Log Output - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Open folder - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - New Version Available - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - - - - - Skip this version - Skip this version - - - - Skip this time - Skip this time - - - - Get update - Get update - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - Form - - - - Proxy Settings - Proxy Settings - - - - No Proxy - No Proxy - - - - Use system proxy - Use system proxy - - - - Specify proxy manually as - Specify proxy manually as - - - - Host - Host - - - - : - : - - - - Proxy server requires authentication - Proxy server requires authentication - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Download Bandwidth - - - - - Limit to - Limit to - - - - - KBytes/s - KBytes/s - - - - - No limit - No limit - - - - - Limit to 3/4 of estimated bandwidth - Limit to 3/4 of estimated bandwidth - - - - Upload Bandwidth - Upload Bandwidth - - - - - Limit automatically - Limit automatically - - - - Hostname of proxy server - Hostname of proxy server - - - - Username for proxy server - Username for proxy server - - - - Password for proxy server - Password for proxy server - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - Created at %1 - - - - Closing in a few seconds... - Closing in a few seconds... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 request failed at %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' selected at %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Error returned from the server: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Login Error</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - New %1 Update Ready - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - - - - Could not check for new updates. - Could not check for new updates. - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - Checking update server... - - - - Update status is unknown: Did not check for new updates. - Update status is unknown: Did not check for new updates. - - - - No updates available. Your installation is at the latest version. - No updates available. Your installation is at the latest version. - - - - Update Check - Update Check - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Connect to %1 - - - - Setup local folder options - Setup local folder options - - - - Connect... - Connect... - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Sync the folder '%1' - - - - Virtual files are not available for the selected folder - - - - - Error - Error - - - - Local Sync Folder - Local Sync Folder - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Email - - - - Connect to %1 - Connect to %1 - - - - Enter user credentials - Enter user credentials - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Connect to %1 - - - - Login in your browser - Login in your browser - - - - Copy link to clipboard - Copy link to clipboard - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Connect to %1 - - - - Setup %1 server - Setup %1 server - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - This URL is NOT secure as it is not encrypted. -It is not advisable to use it. - - - - This url is secure. You can use it. - This URL is secure. You can use it. - - - - &Next > - &Next > - - - - Invalid URL - Invalid URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Failed to connect to %1 at %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Timeout whilst trying to connect to %1 at %2. - - - - Invalid URL - Invalid URL - - - - Could not create local folder %1 - Could not create local folder %1 - - - - Error: %1 - Error: %1 - - - - The folder creation resulted in HTTP error code %1 - The folder creation resulted in HTTP error code %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Remote folder %1 creation failed with error <tt>%2</tt>. - - - - Folder rename failed - Folder rename failed - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Connection Wizard - - - - Enable experimental feature? - Enable experimental feature? - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - Stay safe - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Symbolic links are not supported in syncing. - - - - File is listed on the ignore list. - File is listed on the ignore list. - - - - File names ending with a period are not supported on this file system. - File names ending with a period are not supported on this file system. - - - - File names containing the character '%1' are not supported on this file system. - File names containing the character '%1' are not supported on this file system. - - - - File name contains at least one invalid character - File name contains at least one invalid character - - - - The file name is a reserved name on this file system. - The file name is a reserved name on this file system. - - - - Filename contains trailing spaces. - Filename contains trailing spaces. - - - - Filename is too long. - Filename is too long. - - - - File/Folder is ignored because it's hidden. - File/Folder is ignored because it's hidden. - - - - Stat failed. - Stat failed. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - The filename cannot be encoded on your file system. - The filename cannot be encoded on your file system. - - - - The filename is blacklisted on the server. - - - - - size - size - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - Not allowed to remove, restoring - - - - Ignored because of the "choose what to sync" blacklist - Ignored because of the "choose what to sync" blacklist - - - - Not allowed because you don't have permission to add subfolders to that folder - Not allowed because you don't have permission to add subfolders to that folder - - - - Not allowed because you don't have permission to add files in that folder - Not allowed because you don't have permission to add files in that folder - - - - Not allowed to upload this file because it is read-only on the server, restoring - Not allowed to upload this file because it is read-only on the server, restoring - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - The download would reduce free local disk space below the limit - - - - Free space on disk is less than %1 - Free space on disk is less than %1 - - - - File was deleted from server - File was deleted from server - - - - The file could not be downloaded completely. - The file could not be downloaded completely. + + Enable logging to temporary folder + Enable logging to temporary folder - - The downloaded file is empty despite the server announced it should have been %1. - The downloaded file is empty despite the server announced it should have been %1. + + If enabled, logs will be written to: + If enabled, logs will be written to: - - File %1 cannot be saved because of a local file name clash with %2! - + + C:/log + C:/log - - Error updating metadata: %1 - + + Log Http traffic + Log HTTP traffic - - - - - The file %1 is currently in use - + + Log files to keep: + Log files to keep: - - - File has changed since discovery - File has changed since discovery + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + These settings persist across client restarts. +Note that using any logging command line options will override the settings. - - - OCC::PropagateItemJob - - ; Restoration Failed: %1 - ; Restoration Failed: %1 + + Open folder + Open folder - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - could not delete file %1, error: %2 - + OCC::MainWindow - - Can not create local folder %1 because of a local file name clash with %2 - + + Hide + Hide - - Error updating metadata: %1 - + + Main toolbar for the application + Main toolbar for the application - - The file %1 is currently in use - + + Main toolbar + Main toolbar - - could not create folder %1 - could not create folder %1 + + + More + More - OCC::PropagateLocalRemove + OCC::MainWindowController - - Could not remove %1 because of a local file name clash with %2! - + + Add account... + Add account... - - - OCC::PropagateLocalRename - - File %1 can not be renamed to %2 because of a local file name clash - File %1 cannot be renamed to %2 because of a local file name clash + + Settings... + Settings... - - Could not rename %1 to %2, the file is currently in use - + + About... + About... - - Error updating metadata: %1 - + + Quit + Quit - - The file %1 is currently in use - + + + Errors: %1 + Errors: %1 - - Failed to rename file - + + Activity + Activity - - Error setting pin state - + + Quit %1 + Quit %1 + + + + Are you sure you want to quit %1? + Are you sure you want to quit %1? - OCC::PropagateRemoteDelete + OCC::ModalWrapperWidget - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + + Close + Close - OCC::PropagateRemoteMkdir + OCC::Models - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + + %1 Filter: + %1 Filter: - - Error writing metadata to the database: %1 - + + All + All - - Setting file status failed due to file lock - + + Reset Filters + Reset Filters + + + + Status Filter: + Status Filter: - OCC::PropagateRemoteMove + OCC::NetworkSettings - - Could not rename %1 to %2, error: %3 - + + Form + Form - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + + Pause synchronization when the Internet connection is metered + Pause synchronization when the Internet connection is metered - - Error updating metadata: %1 - + + Proxy Settings + Proxy Settings - - The file %1 is currently in use - + + No Proxy + No Proxy - - Error writing metadata to the database - Error writing metadata to the database + + Use system proxy + Use system proxy - - Error setting pin state - + + Host + Host - - - OCC::PropagateUpdateMetaDataJob - - Could not update file : %1 - + + Proxy Hostname + Proxy Hostname - - The file %1 is currently in use - + + : + : - - - OCC::PropagateUploadFileCommon - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - File %1 cannot be uploaded because another file with the same name, differing only in case, exists + + Proxy Port Number + Proxy Port Number - - File Removed - File Removed + + Proxy server requires authentication + Proxy server requires authentication - - Local file changed during syncing. It will be resumed. - Local file changed during syncing. It will be resumed. + + Note: proxy settings have no effects for accounts on localhost + Note: proxy settings have no effects for accounts on localhost - - Local file changed during sync. - Local file changed during sync. + + Specify proxy manually + Specify proxy manually - - Error updating metadata: %1 - + + Proxy type + Proxy type - - The file %1 is currently in use - + + + Hostname of proxy server + Hostname of proxy server - - - Upload of %1 exceeds the quota for the folder - Upload of %1 exceeds the quota for the folder + + + Username for proxy server + Username for proxy server - - - OCC::PropagateUploadFileNG - - %1 the file is currently in use - + + + Password for proxy server + Password for proxy server - - The local file was removed during sync. - The local file was removed during sync. + + HTTP(S) proxy + HTTP(S) proxy - - Local file changed during sync. - Local file changed during sync. + + SOCKS5 proxy + SOCKS5 proxy + + + OCC::NewAccountBuilder - - The server did ask for a removed legacy feature(polling) - + + no error details are available + no error details are available - - Unexpected return code from server (%1) - Unexpected return code from server (%1) + + Unable to connect to server during account creation: %1. + Unable to connect to server during account creation: %1. + + + OCC::NewAccountWizard - - Missing File ID from server - Missing File ID from server + + Cancel Setup + Cancel Setup - - Missing ETag from server - Missing ETag from server + + Do you really want to cancel the account setup? + Do you really want to cancel the account setup? - OCC::PropagateUploadFileTUS + OCC::NewAccountWizardController - - %1 the file is currently in use - + + Add account… + Add account… - - The local file was removed during sync. - The local file was removed during sync. + + Back + Back - - Local file changed during sync. - Local file changed during sync. + + Advanced + Advanced - - - OCC::PropagateUploadFileV1 - - %1 the file is currently in use - + + Sign in + Sign in - - The server did ask for a removed legacy feature(polling) - + + Open sign in again + Open sign in again + + + OCC::NewVersionAvailableWidget - - The local file was removed during sync. - The local file was removed during sync. + + Skip this version + Skip this version - - Local file changed during sync. - Local file changed during sync. + + Get update + Get update - - The server did not acknowledge the last chunk. (No e-tag was present) - The server did not acknowledge the last chunk. (No e-tag was present) + + Skip this time + Skip this time - OCC::ProtocolItemModel + OCC::OAuth - - Time - Time + + Successfully signed in + Successfully signed in - - File - File + + Now, explore %1 on desktop. + Now, explore %1 on desktop. - - Folder - Folder + + Error returned from the server: <em>%1</em> + Error returned from the server: <em>%1</em> - - Issues - + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + There was an error accessing the 'token' endpoint: <br><em>%1</em> - - Action - Action + + Could not parse the JSON returned from the server: <br><em>%1</em> + Could not parse the JSON returned from the server: <br><em>%1</em> - - Size - Size + + Unsupported token type: %1 + Unsupported token type: %1 - - Account - Account + + The reply from the server did not contain all expected fields +:%1 + The reply from the server did not contain all expected fields +:%1 - - Status - Status + + Unknown Error + Unknown Error - - - OCC::ProtocolWidget - - Form - Form + + + Login Error + Login Error - - Local sync protocol - Local sync protocol + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. - - Filter - Filter + + Wrong user + Wrong user + + + OCC::OAuthPageController - - Account - Account + + Application Name Logo %1 + Application Name Logo %1 - - Copy to clipboard - Copy to clipboard + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. - - Show in file browser - + + Sign in URL + Sign in URL - - Show in web browser - + + To copy the sign in URL to the clipboard, use the copy button + To copy the sign in URL to the clipboard, use the copy button - - Retry sync - + + Copy URL to sign in + Copy URL to sign in - - - OCC::ProxyAuthDialog - - Proxy authentication required - Proxy authentication required + + Additional logo defined by the organization + Additional logo defined by the organization - - Username: - Username: + + URL copied + URL copied - - Proxy: - Proxy: + + Copy URL + Copy URL - - The proxy server needs a username and password. - The proxy server needs a username and password. + + Unable to connect to the login service. If the problem continues, please contact support. + Unable to connect to the login service. If the problem continues, please contact support. - - Password: - Password: + + Please copy the authentication URL using the button above, and provide this information to support. + Please copy the authentication URL using the button above, and provide this information to support. - - TextLabel - TextLabel + + Failed to look up WebFinger instances: %1 + Failed to look up WebFinger instances: %1 - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Choose What to Sync + + You are already connected to an account with these credentials. + You are already connected to an account with these credentials. - - - OCC::SelectiveSyncWidget - - Loading ... - Loading... + + Unable to retrieve capabilities from server. + Unable to retrieve capabilities from server. - - Deselect remote folders you do not wish to synchronize. - Deselect remote folders you do not wish to synchronise. + + The server is not supported by this client. + The server is not supported by this client. - - Name - Name + + Error while trying to log in to OAuth2-enabled server. %1 + Error while trying to log in to OAuth2-enabled server. %1 - - Size - Size + + Unable to open browser with provided URL. %1 + Unable to open browser with provided URL. %1 - - - No subfolders currently on the server. - No subfolders currently on the server. + + OAuth2 authentication requires a secured connection. %1 + OAuth2 authentication requires a secured connection. %1 - - An error occurred while loading the list of sub folders. - An error occurred whilst loading the list of sub folders. + + Authorization server unreachable. %1 + Authorization server unreachable. %1 - OCC::ServerNotificationHandler + OCC::OCUpdater - - Dismiss - Dismiss + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Could not download update. Please click <a href='%1'>here</a> to download the update manually. - - - OCC::SettingsDialog - - Settings - Settings + + Could not check for new updates. + Could not check for new updates. - - Activity - Activity + + Downloading %1. Please wait… + Downloading %1. Please wait… - - Add account - Add account + + %1 installed successfully. Restart the application to finish installing the update. + %1 installed successfully. Restart the application to finish installing the update. - - - Quit %1 - Quit %1 + + %1 available. Restart application to start the update. + %1 available. Restart application to start the update. - - Are you sure you want to quit %1? - + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - Account - Account + + New %1 available. Please use the system's update tool to install it. + New %1 available. Please use the system's update tool to install it. - - MainWindow - + + Checking update server… + Checking update server… - - Hide - Hide + + Update status is unknown: Did not check for new updates. + Update status is unknown: Did not check for new updates. + + + + No updates available. Your installation is at the latest version. + No updates available. Your installation is at the latest version. + + + + OCC::OwncloudPropagator + + + File %1 is currently in use + File %1 is currently in use + + + + vfs instance is not available. + vfs instance is not available. - OCC::ShareDialog + OCC::ProcessDirectoryJob + + + Symbolic links are not supported in syncing. + Symbolic links are not supported in syncing. + + + + File is listed on the ignore list. + File is listed on the ignore list. + - - TextLabel - TextLabel + + File names ending with a period are not supported on this file system. + File names ending with a period are not supported on this file system. - - share label - share label + + File names containing the character '%1' are not supported on this file system. + File names containing the character '%1' are not supported on this file system. - - Dialog - Dialog + + File name contains at least one invalid character + File name contains at least one invalid character - - ownCloud Path: - ownCloud Path: + + The file name is a reserved name on this file system. + The file name is a reserved name on this file system. - - %1 Sharing - %1 Sharing + + Filename contains trailing spaces. + Filename contains trailing spaces. - - %1 - %1 + + Filename is too long. + Filename is too long. - - Folder: %2 - Folder: %2 + + File/Folder is ignored because it's hidden. + File/Folder is ignored because it's hidden. - - The server does not allow sharing - The server does not allow sharing + + Stat failed. + Stat failed. - - Retrieving maximum possible sharing permissions from server... - Retrieving maximum possible sharing permissions from server... + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Conflict: Server version downloaded, local copy renamed and not uploaded. - - The file can not be shared because it was shared without sharing permission. - The file can not be shared because it was shared without sharing permission. + + The filename cannot be encoded on your file system. + The filename cannot be encoded on your file system. - - Users and Groups - Users and Groups + + The filename is blacklisted on the server. + The filename is blacklisted on the server. - - Public Links - Public Links + + + + + the VFS instance is missing. Unable to continue. + the VFS instance is missing. Unable to continue. - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Share NewDocument.odt + + server reported no %1 + server reported no %1 - - Upload only (File Drop) - Upload only (File Drop) + + + account was deleted. Unable to continue + account was deleted. Unable to continue - - Receive files from others without revealing the contents of the folder. - Receive files from others without revealing the contents of the folder. + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: Ignored because its path is deselected - - Download / View / Edit - Download / View / Edit + + Moved to invalid target, restoring + Moved to invalid target, restoring - - Name: - + + Not allowed to remove, restoring + Not allowed to remove, restoring - - Enter name of new share... - + + Error while reading the database. + Error while reading the database. - - &Create - + + account was deleted. Unable to continue. + account was deleted. Unable to continue. - - TextLabel - TextLabel + + Not allowed because you don't have permission to add subfolders to that folder + Not allowed because you don't have permission to add subfolders to that folder - - Set &password - Set &password + + Error while doing a rename, unhandled status code: %1 + Error while doing a rename, unhandled status code: %1 - - Set &expiration date - Set &expiration date + + Not allowed because you don't have permission to add files in that folder + Not allowed because you don't have permission to add files in that folder - - Set password - Set password + + Not allowed to upload this file because it is read-only on the server, restoring + Not allowed to upload this file because it is read-only on the server, restoring - - Recipients can view or download contents. - Recipients can view or download contents. + + This Space is currently unavailable + This Space is currently unavailable - - Download / View - Download / View + + Server replied with an error while reading directory '%1' : %2 + Server replied with an error while reading directory '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - Recipients can view, download, edit, delete and upload contents. + + Error updating metadata: %1 + Error updating metadata: %1 - - Anyone with the link has access to the file/folder - Anyone with the link has access to the file/folder + + %1 the folder is currently in use + %1 the folder is currently in use + + + OCC::PropagateDownloadFile - - - P&assword protect - P&assword protect + + Failed to free up space, the file %1 is currently in use + Failed to free up space, the file %1 is currently in use - - The file can not be shared because it was shared without sharing permission. - The file can not be shared because it was shared without sharing permission. + + + File %1 can not be downloaded because of a local file name clash with %2! + File %1 can not be downloaded because of a local file name clash with %2! - - Link shares have been disabled - Link shares have been disabled + + The download would reduce free local disk space below the limit + The download would reduce free local disk space below the limit - - Create public link share - Create public link share + + Free space on disk is less than %1 + Free space on disk is less than %1 - - - Delete - Delete + + File was deleted from server + File was deleted from server - - Open link in browser - Open link in browser + + Broken webserver returned empty content length for non-empty file on resume + Broken webserver returned empty content length for non-empty file on resume - - Copy link to clipboard - Copy link to clipboard + + The file could not be downloaded completely. + The file could not be downloaded completely. - - Copy link to clipboard (direct download) - Copy link to clipboard (direct download) + + The downloaded file is empty despite the server announced it should have been %1. + The downloaded file is empty despite the server announced it should have been %1. - - Send link by email - Send link by email + + File %1 cannot be saved because of a local file name clash with %2! + File %1 cannot be saved because of a local file name clash with %2! - - Send link by email (direct download) - Send link by email (direct download) + + Error updating metadata: %1 + Error updating metadata: %1 - - Create new... - + + + + + The file %1 is currently in use + The file %1 is currently in use - - I shared %1 with you - I shared %1 with you + + + File has changed since discovery + File has changed since discovery + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - Confirm Link Share Deletion + + ; Restoration Failed: %1 + ; Restoration Failed: %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> + + could not delete file %1, error: %2 + could not delete file %1, error: %2 - - Cancel - Cancel + + Can not create local folder %1 because of a local file name clash with %2 + Can not create local folder %1 because of a local file name clash with %2 - - - Public link - Public link + + Error updating metadata: %1 + Error updating metadata: %1 - - Delete link share - Delete link share + + The file %1 is currently in use + The file %1 is currently in use - - - Please Set Password - Please Set Password + + could not create folder %1 + could not create folder %1 - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove + + + %1 failed with: %2 + %1 failed with: %2 + - - Share NewDocument.odt - Share NewDocument.odt + + Could not remove %1 because of a local file name clash with %2! + Could not remove %1 because of a local file name clash with %2! - - Share with users or groups ... - Share with users or groups ... + + Could not move '%1' to the trash bin + Could not move '%1' to the Rubbish Bin - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - + + + %1 the file is currently in use + %1 the file is currently in use + + + OCC::PropagateLocalRename - - The item is not shared with any users or groups - The item is not shared with any users or groups + + File %1 can not be renamed to %2 because of a local file name clash + File %1 cannot be renamed to %2 because of a local file name clash - - Open link in browser - Open link in browser + + Could not rename %1 to %2, the file is currently in use + Could not rename %1 to %2, the file is currently in use - - Copy link to clipboard - Copy link to clipboard + + Error updating metadata: %1 + Error updating metadata: %1 - - Send link by email - Send link by email + + The file %1 is currently in use + The file %1 is currently in use - - No results for '%1' - No results for '%1' + + Failed to rename file + Failed to rename file - - I shared something with you - I shared something with you + + Error setting pin state + Error setting pin state - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Form + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - TextLabel + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - can edit - can edit + + Error writing metadata to the database: %1 + Error writing metadata to the database: %1 - - can share - can share + + Setting file status failed due to file lock + Setting file status failed due to file lock + + + OCC::PropagateRemoteMove - - ... - ... + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - create - create + + Error updating metadata: %1 + Error updating metadata: %1 - - change - change + + The file %1 is currently in use + The file %1 is currently in use - - delete - delete + + Error writing metadata to the database + Error writing metadata to the database - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Error setting pin state + Error setting pin state - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Share with %1 + + Could not update file : %1 + Could not update file : %1 - - Context menu share - Context menu share + + The file %1 is currently in use + The file %1 is currently in use + + + OCC::PropagateUploadCommon - - Context menu share %1 - + + Local file changed during sync. It will be resumed. + Local file changed during sync. It will be resumed. - - Confirm deletion - + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - + + + Upload of %1 exceeds the quota for the folder + Upload of %1 exceeds the quota for the folder - - Do you want to delete the file <i>%1</i> permanently? - + + + %1 the file is currently in use + %1 the file is currently in use - - Select new location... - + + File Removed + File Removed - - Error - Error + + The server did not provide the file permissions + The server did not provide the file permissions - - Moving file failed: - -%1 - + + Error updating metadata: %1 + Error updating metadata: %1 + + + OCC::PropagateUploadFile - - I shared something with you - I shared something with you + + %1 the file is currently in use + %1 the file is currently in use - - - Share... - Share... + + The server did ask for a removed legacy feature(polling) + The server did ask for a removed legacy feature(polling) - - - Copy private link to clipboard - Copy private link to clipboard + + The local file was removed during sync. + The local file was removed during sync. - - Send private link by email... - Send private link by email... + + The server did not acknowledge the file upload. (No e-tag was present) + The server did not acknowledge the file upload. (No e-tag was present) + + + OCC::PropagateUploadFileTUS - - Resharing this file is not allowed - Resharing this file is not allowed + + %1 the file is currently in use + %1 the file is currently in use - - Resharing this folder is not allowed - + + Checksum computation failed + Checksum computation failed - - Create and copy public link to clipboard - + + The local file was removed during sync. + The local file was removed during sync. + + + OCC::ProtocolItemModel - - Copy public link to clipboard - Copy public link to clipboard + + Time + Time - - Open in browser - Open in browser + + File + File - - Show file versions in browser - + + Folder + Folder - - Rename... - + + Issues + Issues - - Rename and upload... - + + Action + Action - - Move and rename... - + + Size + Size - - Move, rename and upload... - + + Account + Account - - Delete local changes - + + Status + Status + + + OCC::RequestAuthenticationController - - Move and upload... - + + IdP is unreachable. Contact your system administrator or try again later. + IdP is unreachable. Contact your system administrator or try again later. - - Delete - Delete + + Authentication failed. + Authentication failed. - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags - + + Application Name Logo %1 + Application Name Logo %1 - - Backup of %1 started - + + Sign in required + Sign in required - - Failed to find %1 - + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + You may have been automatically disconnected due to a server issue or time out. Please sign in again. - - Local path must be a an absolute path - + + Sign in URL + Sign in URL - - Failed to create temporary database - + + To copy the sign in URL to the clipboard, use the copy button + To copy the sign in URL to the clipboard, use the copy button - - Backup of %1 succeeded - + + Copy URL to sign in + Copy URL to sign in - - Failed to set success tag - + + Additional logo defined by the organization + Additional logo defined by the organization - - Failed to create backup: %1 - + + Stay logged out + Stay logged out - - Failed to file id tags - + + Sign in + Sign in - - Backup of %1 failed with: %2 - + + URL copied + URL copied - - %1 backup - + + Copy URL + Copy URL - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Certificate Details</h3> - - - - Common Name (CN): - Common Name (CN): - - - - Subject Alternative Names: - Subject Alternative Names: - + OCC::ResolveUrlAdapter - - Organization (O): - Organisation (O): + + SSL failure when connecting to server at %1 + SSL failure when connecting to server at %1 - - Organizational Unit (OU): - Organisational Unit (OU): + + Could not detect compatible server at %1 + Could not detect compatible server at %1 - - State/Province: - State/Province: + + Resolved url is invalid %1 + Resolved url is invalid %1 - - Country: - Country: - - - - Serial: - Serial: - - - - <h3>Issuer</h3> - <h3>Issuer</h3> + + Rejected redirect from %1 to %2 + Rejected redirect from %1 to %2 + + + OCC::SelectiveSyncWidget - - Issuer: - Issuer: + + Loading … + Loading… - - Issued on: - Issued on: + + Deselect remote folders you do not wish to synchronize. + Deselect remote folders you do not wish to synchronise. - - Expires on: - Expires on: + + Name + Name - - <h3>Fingerprints</h3> - <h3>Fingerprints</h3> + + Size + Size - - SHA-256: - SHA-256: + + + Currently there are no subfolders on the server. + Currently there are no subfolders on the server. - - SHA-1: - SHA-1: + + An error occurred while loading the list of subfolders. + An error occurred while loading the list of subfolders. + + + OCC::SettingsView - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Note:</b> This certificate was manually approved</p> + + Form + Form - - %1 (self-signed) - %1 (self-signed) + + General Settings + General Settings - - %1 - %1 + + Use Monochrome Icons in the system tray + Use Monochrome Icons in the system tray - - This connection is encrypted using %1 bit %2. - - This connection is encrypted using %1 bit %2. - + + Show Desktop Notifications + Show Desktop Notifications - - Server version: %1 - + + Start on Login + Start on Login - - No support for SSL session tickets/identifiers - No support for SSL session tickets/identifiers + + Language + Language - - Certificate information: - Certificate information: + + Language selector + Language selector - - The connection is not secure - + + Advanced + Advanced - - This connection is NOT secure as it is not encrypted. - - This connection is NOT secure as it is not encrypted. - + + Sync hidden files + Sync hidden files - - - OCC::SslErrorDialog - - Form - Form + + Show crash reporter + Show crash reporter - - Trust this certificate anyway - Trust this certificate anyway + + Move remotely deleted files to the local trash bin instead of deleting them + Move remotely deleted files to the local trash bin instead of deleting them - - Untrusted Certificate - Untrusted Certificate + + Edit Ignored Files + Edit Ignored Files - - Cannot connect securely to <i>%1</i>: - Cannot connect securely to <i>%1</i>: + + Log Settings + Log Settings - - with Certificate %1 - with certificate %1 + + Network + Network - - - - &lt;not specified&gt; - &lt;not specified&gt; + + Warning + Warning - - - Organization: %1 - Organisation: %1 + + Language changes require a restart of this application to take effect. + Language changes require a restart of this application to take effect. - - - Unit: %1 - Unit: %1 + + You cannot disable autostart because system-wide autostart is enabled. + You cannot disable autostart because system-wide autostart is enabled. - - - Country: %1 - Country: %1 + + (use default) + (use default) - - Fingerprint (MD5): <tt>%1</tt> - Fingerprint (MD5): <tt>%1</tt> + + unknown (%1) + unknown (%1) + + + OCC::SocketApi - - Fingerprint (SHA1): <tt>%1</tt> - Fingerprint (SHA1): <tt>%1</tt> + + Share with %1 + parameter is the application name + Share with %1 - - Effective Date: %1 - Effective Date: %1 + + Confirm deletion + Confirm deletion - - Expiration Date: %1 - Expiry Date: %1 + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Do you want to delete the directory <i>%1</i> and all its contents permanently? - - Issuer: %1 - Issuer: %1 + + Do you want to delete the file <i>%1</i> permanently? + Do you want to delete the file <i>%1</i> permanently? - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - %1 (skipped due to earlier error, trying again in %2) + + Error + Error - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Only %1 are available, need at least %2 to start + + Moving file failed: + +%1 + Moving file failed: + +%1 - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Unable to open or create the local sync database. Make sure you have write access in the sync folder. + + I shared something with you + I shared something with you - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Disk space is low: Downloads that would reduce free space below %1 were skipped. + + Select new location… + Select new location… - - There is insufficient space available on the server for some uploads. - There is insufficient space available on the server for some uploads. + + + Share… + Share… - - Unresolved conflict. - Unresolved conflict. + + + Copy private link to clipboard + Copy private link to clipboard - - Using virtual files with suffix, but suffix is not set - + + Resharing this file is not allowed + Resharing this file is not allowed - - Unable to read the blacklist from the local database - Unable to read the blacklist from the local database + + Resharing this folder is not allowed + Resharing this folder is not allowed - - Unable to read from the sync journal. - Unable to read from the sync journal. + + Open in %1 + Open in %1 - - Cannot open the sync journal - Cannot open the sync journal + + Show file versions in web browser + Show file versions in web browser - - Aborted - Aborted + + Rename… + Rename… - - - OCC::SyncJournalDb - - Failed to connect database. - + + Rename and upload… + Rename and upload… - - - OCC::Systray - - %1: %2 - %1: %2 + + + Move and rename… + Move and rename… - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - + + Move, rename and upload… + Move, rename and upload… - - - OCC::ValidateChecksumHeader - - The checksum header is malformed. - The checksum header is malformed. + + Move and upload… + Move and upload… - - The checksum header contained an unknown checksum type '%1' - The checksum header contained an unknown checksum type '%1' + + Delete local changes + Delete local changes - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - + + Delete + Delete - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - + OCC::SpacesPage - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - + + Select a folder to sync it to your computer. + Select a folder to sync it to your computer. - - The Virtual filesystem feature is not supported on network drives - + + Select a Space to sync it to your computer. + Select a Space to sync it to your computer. - OCC::ownCloudGui - - - Please sign in - Please sign in - + OCC::SyncEngine - - Folder %1: %2 - Folder %1: %2 + + %1 (skipped due to earlier error, trying again in %2) + %1 (skipped due to earlier error, trying again in %2) - - Unresolved %1 conflicts - + + Unable to sync: the local folder is missing. + Unable to sync: the local folder is missing. - - Ignored errors %1 - + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Only %1 are available, need at least %2 to start - - There are no sync folders configured. - There are no sync folders configured. + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - Open in browser - Open in browser + + + + + Cannot open the sync journal. + Cannot open the sync journal. - - - - Log in... - Log in... + + Aborted due to %1 + Aborted due to %1 - - - - Log out - Log out + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Disk space is low: Downloads that would reduce free space below %1 were skipped. - - Recent Changes - Recent Changes + + There is insufficient space available on the server for some uploads. + There is insufficient space available on the server for some uploads. - - Managed Folders: - Managed Folders: + + application exit + abort reason + application exit - - Open folder '%1' - Open folder '%1' + + Unable to read the blacklist from the local database + Unable to read the blacklist from the local database - - Open %1 in browser - Open %1 in browser + + Unable to read from the sync journal. + Unable to read from the sync journal. + + + OCC::SyncErrorWidget - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + + Issues table + Issues table - - Unknown status - Unknown status + + There were too many issues. Not all will be visible here. + There were too many issues. Not all will be visible here. - - Settings... - Settings... + + There were conflicts. Check the documentation on how to resolve them. + There were conflicts. Check the documentation on how to resolve them. - - Details... - Details... + + The file %1 was ignored as its name is reserved by %2 + The file %1 was ignored as its name is reserved by %2 - - Help - Help + + Reset column sizes + Reset column sizes - - Quit %1 - Quit %1 + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - Disconnected from %1 - Disconnected from %1 + + Filter menu + Filter menu - - Unsupported Server Version - Unsupported Server Version + + Account + Account - - Disconnected - Disconnected + + Filter + Filter + + + OCC::SyncJournalDb - - Disconnected from some accounts - Disconnected from some accounts + + Failed to connect database. + Failed to connect database. + + + OCC::Theme - - Disconnected from accounts: - Disconnected from accounts: + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public Licence (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Account %1: %2 - Account %1: %2 + + TLS Certificate Error + TLS Certificate Error - - Signed out - Signed out + + Do you want to trust this certificate anyway? + Do you want to trust this certificate anyway? - - Account synchronization is disabled - Account synchronisation is disabled + + Cannot connect securely to %1 + Cannot connect securely to %1 - - Synchronization is paused - Synchronisation is paused + + &lt;not specified&gt; + &lt;not specified&gt; - - Up to date (%1) - + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organisation: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organisation: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + + + OCC::TrayMenuController - - No sync folders configured - No sync folders configured + + Unsupported Server Version + Unsupported Server Version - - Unpause all folders - Unpause all folders + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - Pause all folders - Pause all folders + + Show %1 + Show %1 - - Unpause all synchronization - Unpause all synchronisation + + Help + Help - - Unpause synchronization - Unpause synchronisation + + About %1 + About %1 - - Pause all synchronization - Pause all synchronisation + + Quit %1 + Quit %1 + + + OCC::UpdateDownloadedWidget - - Pause synchronization - Pause synchronisation + + Restart later + Restart later - - Log out of all accounts - Log out of all accounts + + Restart now + Restart now + + + OCC::UpdaterScheduler - - Log in to all accounts... - Log in to all accounts... + + Update available + Update available + + + OCC::UrlPageController - - New account... - New account... + + Your web browser will be opened to complete sign in. + Your web browser will be opened to complete sign in. - - About %1 - About %1 + + %1 logo + %1 logo - - No items synced recently - No items synced recently + + Welcome to %1 + Welcome to %1 - - Checking for changes in remote '%1' - Checking for changes in remote '%1' + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Enter your server address to get started. Your web browser will be opened to complete sign in. - - Checking for changes in local '%1' - Checking for changes in local '%1' + + Server address field + Server address field - - Syncing %1 of %2 (%3 left) - Syncing %1 of %2 (%3 left) + + Enter your server address here + Enter your server address here - - Syncing %1 of %2 - Syncing %1 of %2 + + Additional logo defined by the organization + Additional logo defined by the organization - - Syncing %1 (%2 left) - Syncing %1 (%2 left) + + Invalid server URL: %1 + Invalid server URL: %1 - - Syncing %1 - Syncing %1 + + Invalid URL scheme. Only https is accepted. + Invalid URL scheme. Only https is accepted. - - %1 (%2, %3) - %1 (%2, %3) + + The requested URL failed with query value: %1 + The requested URL failed with query value: %1 - OwncloudAdvancedSetupPage - - - Form - Form - + OCC::UserInfoAdapter - - - TextLabel - TextLabel + + Network request to collect user info failed + Network request to collect user info failed - - Server - Server + + Unable to retrieve user info: invalid credentials. + Unable to retrieve user info: invalid credentials. - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> + + Unexpected network response when retrieving user info. + Unexpected network response when retrieving user info. - - Ask for confirmation before synchroni&zing folders larger than - Ask for confirmation before synchroni&sing folders larger than + + Retrieving user info failed with JSON error. + Retrieving user info failed with JSON error. + + + OCC::ValidateChecksumHeader - - S&ynchronize everything from server - + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + + + OCC::Vfs - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + The Virtual filesystem feature does not support a drive as sync root + The Virtual filesystem feature does not support a drive as sync root - - Ask for confirmation before synchronizing e&xternal storages - Ask for confirmation before synchronising e&xternal storages + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - Choose what to sync - Choose what to sync + + The Virtual filesystem feature is not supported on network drives + The Virtual filesystem feature is not supported on network drives + + + OCC::VfsWinPrivate - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>When this option is selected, the wizard will close without synchronising anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronise</p></body></html> + + + %1 - %2 + %1 - %2 - - Manually create folder sync connections - Manually create folder sync connections + + Could not find StorageProviderState property %1 + Could not find StorageProviderState property %1 - - &Local Folder - &Local Folder + + Could not find StorageFolder for %1 error: %2 + Could not find StorageFolder for %1 error: %2 - - pbSelectLocalFolder - pbSelectLocalFolder + + Could not retrieve StorageFolder for %1 %2 (0x%3) + Could not retrieve StorageFolder for %1 %2 (0x%3) - - &Keep local data - &Keep local data + + The folder is used by a different client: %1 + The folder is used by a different client: %1 - - Start a &clean sync (Crreate a backup and erases the local folder!) - + + Error registering StorageProvider for %1: %2 (0x%3) + Error registering StorageProvider for %1: %2 (0x%3) - - - OwncloudHttpCredsPage - - Form - Form + + Unable to connect sync root: %1 error: %2 + Unable to connect sync root: %1 error: %2 - - &Username - &Username + + %1 is managed by another sync client + %1 is managed by another sync client - - &Password - &Password + + Downgrading the folder from %1 to %2 is not supported + Downgrading the folder from %1 to %2 is not supported - OwncloudOAuthCredsPage + OCC::WebFingerLookupAdapter - - Form - Form + + Failed to retrieve user info + Failed to retrieve user info - - Please switch to your browser to proceed. - Please switch to your browser to proceed. + + WebFinger lookup returned no links + WebFinger lookup returned no links + + + OCC::WindowsUpdater - - An error occurred while connecting. Please try again. - An error occurred while connecting. Please try again. + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - - Re-open Browser - Re-open Browser + + Skip this version + Skip this version - - - OwncloudSetupPage - - Form - Form + + Update Failed + Update Failed - - Ser&ver Address - Ser&ver Address + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + + + + Ask again later + Ask again later - - https://... - https://... + + Restart and update + Restart and update - - Error Label - Error Label + + Update manually + Update manually QObject - + in the future in the future - + %n day(s) ago - %n day(s) ago%n day(s) ago + %n day ago%n days ago - + %n hour(s) ago - %n hour(s) ago%n hour(s) ago + %n hour ago%n hours ago - + now now - - Less than a minute ago - Less than a minute ago + + less than a minute ago + less than a minute ago - + %n minute(s) ago - %n minute(s) ago%n minute(s) ago + %n minute ago%n minutes ago - - Some time ago - Some time ago - - - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 + + SpacesBrowser + + + Form + Form + + + + SpacesPage + + + Spaces list + Spaces list + + + + SpacesView + + + Spaces + Spaces + + SyncFileItem::Status - + Undefined - + Undefined - + Fatal Error - + Fatal Error - - + + Error Error - + Info Info - + Success Success - + Conflict Conflict - + File Ignored - + File Ignored - + Restored - + Restored - + Blacklisted - + Blacklisted - + Excluded - - - - - Utility - - - %L1 GB - %L1 GB + Excluded - - %L1 MB - %L1 MB + + Message + Message - - %L1 KB - %L1 KB + + Filename Reserved + Filename Reserved + + + UpdateDownloadedWidget - - %L1 B - %L1 B + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + + + Utility - + %n year(s) - %n year(s)%n year(s) + %n year%n years - + %n month(s) - %n month(s)%n month(s) + %n month%n months - + %n day(s) - %n day(s)%n day(s) + %n day%n days - + %n hour(s) - %n hour(s)%n hour(s) + %n hour%n hours - + %n minute(s) - %n minute(s)%n minute(s) + %n minute%n minutes - + %n second(s) - %n second(s)%n second(s) + %n second%n seconds - + %1 %2 %1 %2 + + + Shutting down %1 + Shutting down %1 + - main.cpp + account loading - - System Tray not available - System Tray not available + + Error accessing the configuration file + Error accessing the configuration file - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 requires a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. + + There was an error while accessing the configuration file at %1. + There was an error while accessing the configuration file at %1. ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - + + %1 (Built against Qt %2) + %1 (Built against Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Always available locally + + + + Currently available locally + Currently available locally + + + + Some available online only + Some available online only + + + + Available online only + Available online only + + progress Virtual file created - + Virtual file created Replaced by virtual file - + Replaced by virtual file @@ -4709,30 +3564,30 @@ This is a new, experimental mode. If you decide to use it, please report any iss Uploaded Uploaded - - - Server version downloaded, copied changed local file into conflict file - Server version downloaded, copied changed local file into conflict file - Deleted Deleted - - Moved to %1 - Moved to %1 - - - + Ignored Ignored - - Filesystem access error - Filesystem access error + + Server version downloaded, local copy was backed up as conflict file + Server version downloaded, local copy was backed up as conflict file + + + + Renamed %1 to %2 + Renamed %1 to %2 + + + + Moved %1 to %2 + Moved %1 to %2 @@ -4745,153 +3600,86 @@ This is a new, experimental mode. If you decide to use it, please report any iss Updated local metadata - - + + Unknown Unknown - + downloading downloading - + uploading uploading - + deleting deleting - + moving moving - + ignoring ignoring - - + error error - + updating local metadata updating local metadata - - theme - - - Status undefined - Status undefined - - - - Waiting to start sync - Waiting to start sync - - - - Sync is running - Sync is running - - - - Sync Success - Sync Success - - - - Sync Success, some files were ignored. - Sync Success, some files were ignored. - - - - Sync Error - Sync Error - - - - Setup Error - Setup Error - - - - Preparing to sync - Preparing to sync - - - - Aborting... - Aborting... - - - - Sync is paused - Sync is paused - - utility - + Could not open browser Could not open browser - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - + Could not open email client Could not open email client - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - - - - + Make always available locally - + Make always available locally - + Free up local space - + Free up local space + + + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Some settings were configured in newer versions of this client and use features that are not available in this version \ No newline at end of file diff --git a/translations/client_en_US.ts b/translations/client_en_US.ts new file mode 100644 index 00000000000..31393be54e7 --- /dev/null +++ b/translations/client_en_US.ts @@ -0,0 +1,3685 @@ + + + AccountBar + + + Navigation bar + Navigation bar + + + + Add Account + Add Account + + + + Activity + Activity + + + + Settings + Settings + + + + Quit + Quit + + + + AppImageUpdateAvailableWidgetUi + + + Skip this version + Skip this version + + + + AppProvider + + + Failed to open %1 in web. Error: %2. + Failed to open %1 in web. Error: %2. + + + + ChecksumHeader + + + The checksum header is malformed: %1 + The checksum header is malformed: %1 + + + + The checksum header contained an unknown checksum type '%1' + The checksum header contained an unknown checksum type '%1' + + + + CommandLine + + + %1 version %2 +File synchronization desktop utility. + %1 version %2<byte value="xd"/> +File synchronisation desktop utility. + + + + For more information, see %1 + link to homepage + For more information, see %1 + + + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + + + + Quit the running instance. + Quit the running instance. + + + + Write log to file (use - to write to stdout). + Write log to file (use - to write to stdout). + + + + Write each sync log output in a new file in folder. + Write each sync log output in a new file in folder. + + + + Flush the log file after every write. + Flush the log file after every write. + + + + Output debug-level messages in the log. + Output debug-level messages in the log. + + + + Enable debug mode. + Enable debug mode. + + + + --logfile and --logdir are mutually exclusive + --logfile and --logdir are mutually exclusive + + + + CommonStrings + + + file manager + file manager + + + + Show in %1 + Show in %1 + + + + Show in web browser + Show in web browser + + + + Copy + Copy + + + + %n Filter(s) + %n Filter%n Filters + + + + folder + folder + + + + space + space + + + + folders + folders + + + + spaces + spaces + + + + Folder + Folder + + + + Space + Space + + + + FetchUserInfoJobFactory + + + Failed to retrieve user info + Failed to retrieve user info + + + + FileSystem + + + + + + Can't rename %1, the file is currently in use + Can't rename %1, the file is currently in use + + + + Could not remove folder + Could not remove folder + + + + FolderItem + + + + Offline + Offline + + + + + Unavailable: the space is no longer available on the server + Unavailable: the space is no longer available on the server + + + + + Sync paused + Sync paused + + + + Synced + Synced + + + + Sync failed + Sync failed + + + + Sync pending + Sync pending + + + + Syncing %1 of %2 (%3 % + Syncing %1 of %2 (%3 % + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, sync status: %2 + first argument is the displayed name of the folder. + + + + FolderManagementUtils + + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + + + + Local folder %1 does not exist. + Local folder %1 does not exist. + + + + %1 is not a folder. + %1 is not a folder. + + + + %1 is not readable. + %1 is not readable. + + + + %1 is not writable. + %1 is not writable. + + + + FolderWizard + + + <b>Error:</b> %1 + <b>Error:</b> %1 + + + + <b>Warning:</b> %1 + <b>Warning:</b> %1 + + + + <b>Error:</b><ul>%1</ul> + <b>Error:</b><ul>%1</ul> + + + + <b>Warning:</b><ul>%1</ul> + <b>Warning:</b><ul>%1</ul> + + + + FolderWizardSourcePage + + + Form + Form + + + + Pick a local folder on your computer to sync + Pick a local folder on your computer to sync + + + + Browse… + Browse… + + + + FolderWizardTargetPage + + + Form + Form + + + + Select a remote destination folder + Select a remote destination folder + + + + Create Folder + Create Folder + + + + Refresh + Refresh + + + + Folders + Folders + + + + OCC::AboutView + + + About + About + + + + Versions + Versions + + + + OCC::AbstractNetworkJob + + + Connection timed out + Connection timed out + + + + Unknown error: network reply was deleted + Unknown error: network reply was deleted + + + + Server replied "%1 %2" to "%3 %4" + Server replied "%1 %2" to "%3 %4" + + + + OCC::Account + + + %1@%2 + %1@%2 + + + + OCC::AccountFoldersController + + + Choose what to sync + Choose what to sync + + + + + Sync now + Sync now + + + + Manage subfolder sync + Manage subfolder sync + + + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + Remove %1 sync + + + + Disable virtual file support? + Disable virtual file support? + + + + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. + +The only advantage of disabling virtual file support is that the selective sync feature will become available again. + +This action will abort any currently running synchronization. + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. + +The only advantage of disabling virtual file support is that the selective sync feature will become available again. + +This action will abort any currently running synchronisation. + + + + Disable support + Disable support + + + + Restart sync + Restart sync + + + + Resume sync + Resume sync + + + + Pause sync + Pause sync + + + + Deactivate virtual files + Deactivate virtual files + + + + Activate virtual files + Activate virtual files + + + + Internet connection is metered + Internet connection is metered + + + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + Synchronisation is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronisation now? + + + + Sync Running + Sync Running + + + + The sync operation is running.<br/>Do you want to stop it? + The sync operation is running.<br/>Do you want to stop it? + + + + Sync paused by user + Sync paused by user + + + + Confirm Folder Sync Connection Removal + Confirm Folder Sync Connection Removal + + + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + + + + Remove Folder Sync Connection + Remove Folder Sync Connection + + + + + Cancel + Cancel + + + + OCC::AccountFoldersView + + + Sync options menu + Sync options menu + + + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 sync + + + + Manage your synced %1. + Manage your synced %1. + + + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Add new %1 sync… + + + + %1 list view + %1 list view + + + + Navigate the %1 list using the up and down arrows + Navigate the %1 list using the up and down arrows + + + + %1 out of %2 %3 are synchronized + %1 out of %2 %3 are synchronised + + + + OCC::AccountModalWidget + + + + Form + Form + + + + OCC::AccountPlaceholderWidget + + + %1 logo + %1 logo + + + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + + + + Additional logo defined by the organization + Additional logo defined by the organization + + + + OCC::AccountView + + + Form + Form + + + + Manage Account + Manage Account + + + + Preparing the account + Preparing the account + + + + Sync connections + Sync connections + + + + Account options menu + Account options menu + + + + OCC::AccountViewController + + + + Log in + Log in + + + + Reconnect + Reconnect + + + + Remove + Remove + + + + Log out + Log out + + + + Confirm Account Removal + Confirm Account Removal + + + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + + + + Remove connection + Remove connection + + + + Cancel + Cancel + + + + The server version %1 is unsupported! Proceed at your own risk. + The server version %1 is unsupported! Proceed at your own risk. + + + + Connected + Connected + + + + Server is temporarily unavailable. + Server is temporarily unavailable. + + + + Server is currently in maintenance mode. + Server is currently in maintenance mode. + + + + Signed out + Signed out + + + + Updating credentials… + Updating credentials… + + + + Captive portal prevents connections to the server. + Captive portal prevents connections to the server. + + + + Sync is paused due to metered internet connection. + Sync is paused due to metered Internet connection. + + + + Connecting… + Connecting… + + + + Server configuration error + Server configuration error + + + + Disconnected + Disconnected + + + + Invalid connection status + Invalid connection status + + + + OCC::AccountsGuiController + + + + New account failure + New account failure + + + + The information required to create a new account is incomplete. Please run the wizard again. + The information required to create a new account is incomplete. Please run the wizard again. + + + + Accounts + Accounts + + + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + + + + OCC::ActivitySettings + + + Local Activity + Local Activity + + + + Not Synced + Not Synced + + + + Not Synced (%1) + %1 is the number of not synced files. + Not Synced (%1) + + + + OCC::AdvancedSettingsPageController + + + Advanced settings + Advanced settings + + + + Sync and download options + Sync and download options + + + + Download location + Download location + + + + Sync and download files as you use them (saves hard drive space) + Sync and download files as you use them (saves hard drive space) + + + + Automatically sync and download folders and files + Automatically sync and download folders and files + + + + Sync and download folders manually + Sync and download folders manually + + + + Download location on the local machine + Download location on the local machine + + + + Browse… + Browse… + + + + Browse for a download location + Browse for a download location + + + + Invalid local download directory %1: %2 + Invalid local download directory %1: %2 + + + + your user directory may not be chosen as the sync root. + your user directory may not be chosen as the sync root. + + + + path is not located on a supported Apple File System. + path is not located on a supported Apple File System. + + + + path must be absolute. + path must be absolute. + + + + selected path does not support using virtual file system. %1 + selected path does not support using virtual file system. %1 + + + + Select sync root + Select sync root + + + + OCC::AppImageUpdateAvailableWidget + + + Installed version: %1 + Installed version: %1 + + + + Available update: %1 + Available update: %1 + + + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + + + + OCC::Application + + + Quit + Quit + + + + OCC::AuthSuccessPageController + + + Application Name Logo %1 + Application Name Logo %1 + + + + You're all set! Open %1 and get started. + You're all set! Open %1 and get started. + + + + Additional logo defined by the organization + Additional logo defined by the organization + + + + OCC::ButtonDelegate + + + %1 options button + %1 options button + + + + Menu button with %1 options. Use the space key to show the menu + Menu button with %1 options. Use the space key to show the menu + + + + OCC::ConnectionValidator + + + No ownCloud account configured + No ownCloud account configured + + + + Please update to the latest server and restart the client. + Please update to the latest server and restart the client. + + + + Authentication error: Either username or password are wrong. + Authentication error: Either username or password is wrong. + + + + timeout + timeout + + + + No account configured + No account configured + + + + The provided credentials are not correct + The provided credentials are not correct + + + + The configured server for this client is too old. + The configured server for this client is too old. + + + + OCC::CredentialJob + + + Failed to parse credentials %1 + Failed to parse credentials %1 + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + Server does not support OAuth + + + + Server did not ask for authorization + Server did not ask for authorisation + + + + Failed to determine authentication type: %1 + Failed to determine authentication type: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + Invalid reply received from server. + + + + OCC::DiscoverySingleDirectoryJob + + + The account was deleted before we could start the propfind job + The account was deleted before we could start the propfind job + + + + + Server error: PROPFIND reply is not XML formatted! + Server error: PROPFIND reply is not XML formatted! + + + + OCC::DiscoverySingleLocalDirectoryJob + + + Error while opening directory %1 + Error while opening directory %1 + + + + Directory not accessible on client, permission denied + Directory not accessible on client, permission denied + + + + Directory not found: %1 + Directory not found: %1 + + + + Error while reading directory %1 + Error while reading directory %1 + + + + OCC::ExpandingHeaderView + + + Reset column sizes + Reset column sizes + + + + OCC::FetchCapabilitiesAdapter + + + server sent invalid content type: %1. + server sent invalid content type: %1. + + + + could not parse Capabilities from server. + could not parse Capabilities from server. + + + + reply sent for different subject (server): %1 + reply sent for different subject (server): %1 + + + + OCC::Folder + + + %1 has been removed. + %1 names a file. + %1 has been removed. + + + + %1 has been updated. + %1 names a file. + %1 has been updated. + + + + %1 has been renamed to %2. + %1 and %2 name files. + %1 has been renamed to %2. + + + + %1 has been moved to %2. + %1 has been moved to %2. + + + + %1 and %n other file(s) have been removed. + %1 and %n other file have been removed.%1 and %n other files have been removed. + + + + The folder has been disabled or removed from the server + The folder has been disabled or removed from the server + + + + %1 and %n other file(s) have been added. + %1 and %n other file have been added.%1 and %n other files have been added. + + + + %1 has been added. + %1 names a file. + %1 has been added. + + + + %1 and %n other file(s) have been updated. + %1 and %n other file have been updated.%1 and %n other files have been updated. + + + + %1 has been renamed to %2 and %n other file(s) have been renamed. + %1 has been renamed to %2 and %n other file have been renamed.%1 has been renamed to %2 and %n other files have been renamed. + + + + %1 has been moved to %2 and %n other file(s) have been moved. + %1 has been moved to %2 and %n other file have been moved.%1 has been moved to %2 and %n other files have been moved. + + + + %1 and %n other file(s) have sync conflicts. + %1 and %n other file have sync conflicts.%1 and %n other files have sync conflicts. + + + + %1 has a sync conflict. Please check the conflict file! + %1 has a sync conflict. Please check the conflict file! + + + + %1 and %n other file(s) could not be synced due to errors. See the log for details. + %1 and %n other file could not be synced due to errors. See the log for details.%1 and %n other files could not be synced due to errors. See the log for details. + + + + %1 could not be synced due to an error. See the log for details. + %1 could not be synced due to an error. See the log for details. + + + + Sync Activity + Sync Activity + + + + Switching VFS mode on folder '%1' + Switching VFS mode on folder '%1' + + + + Could not read system exclude file + Could not read system exclude file + + + + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. + The folder %1 was created but was excluded from synchronisation previously. Data inside it will not be synchronised. + + + + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. + The file %1 was created but was excluded from synchronisation previously. It will not be synchronised. + + + + Changes in synchronized folders could not be tracked reliably. + +This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). + +%1 + Changes in synchronised folders could not be tracked reliably. + +This means that the synchronisation client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). + +%1 + + + + OCC::FolderItemUpdater + + + There are unresolved conflicts. + There are unresolved conflicts. + + + + Sync error: %1 + Sync error: %1 + + + + OCC::FolderMan + + + Could not reset folder state + Could not reset folder state + + + + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. + + + + Unsupported filesystem + Unsupported filesystem + + + + Account disconnected or paused + Account disconnected or paused + + + + User triggered force sync + User triggered force sync + + + + Folder is about to be removed + Folder is about to be removed + + + + (backup) + (backup) + + + + (backup %1) + (backup %1) + + + + The selected path is not a folder. + The selected path is not a folder. + + + + The folder %1 is used in a folder sync connection. + The folder %1 is used in a folder sync connection. + + + + Folder '%1' is already in use by application %2. + Folder '%1' is already in use by application %2. + + + + There is already a sync from the server to this local folder. + There is already a sync from the server to this local folder. + + + + The local folder %1 already contains a folder used in a folder sync connection. + The local folder %1 already contains a folder used in a folder sync connection. + + + + The local folder %1 is already contained in a folder used in a folder sync connection. + The local folder %1 is already contained in a folder used in a folder sync connection. + + + + No valid folder selected. + No valid folder selected. + + + + The selected path does not exist. + The selected path does not exist. + + + + You have no permission to write to the selected folder. + You have no permission to write to the selected folder. + + + + Folder '%1' is already in use by another account. + Folder '%1' is already in use by another account. + + + + On macOS, only the Apple File System is supported. + On macOS, only the Apple File System is supported. + + + + OCC::FolderModelController + + + %1 options + %1 options + + + + Menu button with %1 options. Use the space key to pop the %2 options menu + Menu button with %1 options. Use the space key to pop the %2 options menu + + + + OCC::FolderWatcher + + + The watcher did not receive a test notification. + The watcher did not receive a test notification. + + + + OCC::FolderWatcherPrivate + + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + + + + OCC::FolderWizard + + + Virtual files are not available for the selected folder + Virtual files are not available for the selected folder + + + + Add Folder Sync Connection + Add Folder Sync Connection + + + + Add Sync Connection + Add Sync Connection + + + + OCC::FolderWizardLocalPath + + + Click to select a local folder to sync. + Click to select a local folder to sync. + + + + Enter the path to the local folder. + Enter the path to the local folder. + + + + Select the local folder + Select the local folder + + + + OCC::FolderWizardSelectiveSync + + + Use virtual files instead of downloading content immediately + Use virtual files instead of downloading content immediately + + + + OCC::GETFileJob + + + Connection Timeout + Connection Timeout + + + + No E-Tag received from server, check Proxy/Gateway + No E-Tag received from server, check Proxy/Gateway + + + + We received a different E-Tag for resuming. Retrying next time. + We received a different E-Tag for resuming. Retrying next time. + + + + We received an unexpected download Content-Length. + We received an unexpected download Content-Length. + + + + Server returned wrong content-range + Server returned wrong content-range + + + + OCC::GraphApi::Space + + + Personal + Personal + + + + Shares + Shares + + + + OCC::IgnoreListEditor + + + Ignored Files Editor + Ignored Files Editor + + + + Files Ignored by Patterns + Files Ignored by Patterns + + + + Add + Add + + + + Pattern + Pattern + + + + Allow Deletion + Allow Deletion + + + + Remove + Remove + + + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Files or folders matching a pattern will not be synchronised. Changes take effect the next time folders are synchronised. + +Items, where deletion is allowed, will be deleted if they prevent a directory from being removed. This is useful for metadata. + + + + Could not open file + Could not open file + + + + Cannot write changes to '%1'. + Cannot write changes to '%1'. + + + + Add Ignore Pattern + Add Ignore Pattern + + + + Add a new ignore pattern: + Add a new ignore pattern: + + + + This entry is provided by the system at '%1' and cannot be modified in this view. + This entry is provided by the system at '%1' and cannot be modified in this view. + + + + OCC::LocalActivityWidget + + + Filter + Filter + + + + Local activity table + Local activity table + + + + + Account + Account + + + + Filter menu + Filter menu + + + + Actions menu + Actions menu + + + + Sort ascending by %1 + Sort ascending by %1 + + + + Sort descending by %1 + Sort descending by %1 + + + + Retry sync + Retry sync + + + + OCC::LogBrowser + + + Log Output + Log Output + + + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + + + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + + + + Enable logging to temporary folder + Enable logging to temporary folder + + + + If enabled, logs will be written to: + If enabled, logs will be written to: + + + + C:/log + C:/log + + + + Log Http traffic + Log HTTP traffic + + + + Log files to keep: + Log files to keep: + + + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + + + + Open folder + Open folder + + + + OCC::MainWindow + + + Hide + Hide + + + + Main toolbar for the application + Main toolbar for the application + + + + Main toolbar + Main toolbar + + + + + More + More + + + + OCC::MainWindowController + + + Add account... + Add account... + + + + Settings... + Settings... + + + + About... + About... + + + + Quit + Quit + + + + + Errors: %1 + Errors: %1 + + + + Activity + Activity + + + + Quit %1 + Quit %1 + + + + Are you sure you want to quit %1? + Are you sure you want to quit %1? + + + + OCC::ModalWrapperWidget + + + Close + Close + + + + OCC::Models + + + %1 Filter: + %1 Filter: + + + + All + All + + + + Reset Filters + Reset Filters + + + + Status Filter: + Status Filter: + + + + OCC::NetworkSettings + + + Form + Form + + + + Pause synchronization when the Internet connection is metered + Pause synchronization when the Internet connection is metered + + + + Proxy Settings + Proxy Settings + + + + No Proxy + No Proxy + + + + Use system proxy + Use system proxy + + + + Host + Host + + + + Proxy Hostname + Proxy Hostname + + + + : + : + + + + Proxy Port Number + Proxy Port Number + + + + Proxy server requires authentication + Proxy server requires authentication + + + + Note: proxy settings have no effects for accounts on localhost + Note: proxy settings have no effects for accounts on localhost + + + + Specify proxy manually + Specify proxy manually + + + + Proxy type + Proxy type + + + + + Hostname of proxy server + Hostname of proxy server + + + + + Username for proxy server + Username for proxy server + + + + + Password for proxy server + Password for proxy server + + + + HTTP(S) proxy + HTTP(S) proxy + + + + SOCKS5 proxy + SOCKS5 proxy + + + + OCC::NewAccountBuilder + + + no error details are available + no error details are available + + + + Unable to connect to server during account creation: %1. + Unable to connect to server during account creation: %1. + + + + OCC::NewAccountWizard + + + Cancel Setup + Cancel Setup + + + + Do you really want to cancel the account setup? + Do you really want to cancel the account setup? + + + + OCC::NewAccountWizardController + + + Add account… + Add account… + + + + Back + Back + + + + Advanced + Advanced + + + + Sign in + Sign in + + + + Open sign in again + Open sign in again + + + + OCC::NewVersionAvailableWidget + + + Skip this version + Skip this version + + + + Get update + Get update + + + + Skip this time + Skip this time + + + + OCC::OAuth + + + Successfully signed in + Successfully signed in + + + + Now, explore %1 on desktop. + Now, explore %1 on desktop. + + + + Error returned from the server: <em>%1</em> + Error returned from the server: <em>%1</em> + + + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + There was an error accessing the 'token' endpoint: <br><em>%1</em> + + + + Could not parse the JSON returned from the server: <br><em>%1</em> + Could not parse the JSON returned from the server: <br><em>%1</em> + + + + Unsupported token type: %1 + Unsupported token type: %1 + + + + The reply from the server did not contain all expected fields +:%1 + The reply from the server did not contain all expected fields +:%1 + + + + Unknown Error + Unknown Error + + + + + Login Error + Login Error + + + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + + + + Wrong user + Wrong user + + + + OCC::OAuthPageController + + + Application Name Logo %1 + Application Name Logo %1 + + + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + + + + Sign in URL + Sign in URL + + + + To copy the sign in URL to the clipboard, use the copy button + To copy the sign in URL to the clipboard, use the copy button + + + + Copy URL to sign in + Copy URL to sign in + + + + Additional logo defined by the organization + Additional logo defined by the organization + + + + URL copied + URL copied + + + + Copy URL + Copy URL + + + + Unable to connect to the login service. If the problem continues, please contact support. + Unable to connect to the login service. If the problem continues, please contact support. + + + + Please copy the authentication URL using the button above, and provide this information to support. + Please copy the authentication URL using the button above, and provide this information to support. + + + + Failed to look up WebFinger instances: %1 + Failed to look up WebFinger instances: %1 + + + + You are already connected to an account with these credentials. + You are already connected to an account with these credentials. + + + + Unable to retrieve capabilities from server. + Unable to retrieve capabilities from server. + + + + The server is not supported by this client. + The server is not supported by this client. + + + + Error while trying to log in to OAuth2-enabled server. %1 + Error while trying to log in to OAuth2-enabled server. %1 + + + + Unable to open browser with provided URL. %1 + Unable to open browser with provided URL. %1 + + + + OAuth2 authentication requires a secured connection. %1 + OAuth2 authentication requires a secured connection. %1 + + + + Authorization server unreachable. %1 + Authorization server unreachable. %1 + + + + OCC::OCUpdater + + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + + + + Could not check for new updates. + Could not check for new updates. + + + + Downloading %1. Please wait… + Downloading %1. Please wait… + + + + %1 installed successfully. Restart the application to finish installing the update. + %1 installed successfully. Restart the application to finish installing the update. + + + + %1 available. Restart application to start the update. + %1 available. Restart application to start the update. + + + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + + + + New %1 available. Please use the system's update tool to install it. + New %1 available. Please use the system's update tool to install it. + + + + Checking update server… + Checking update server… + + + + Update status is unknown: Did not check for new updates. + Update status is unknown: Did not check for new updates. + + + + No updates available. Your installation is at the latest version. + No updates available. Your installation is at the latest version. + + + + OCC::OwncloudPropagator + + + File %1 is currently in use + File %1 is currently in use + + + + vfs instance is not available. + vfs instance is not available. + + + + OCC::ProcessDirectoryJob + + + Symbolic links are not supported in syncing. + Symbolic links are not supported in syncing. + + + + File is listed on the ignore list. + File is listed on the ignore list. + + + + File names ending with a period are not supported on this file system. + File names ending with a period are not supported on this file system. + + + + File names containing the character '%1' are not supported on this file system. + File names containing the character '%1' are not supported on this file system. + + + + File name contains at least one invalid character + File name contains at least one invalid character + + + + The file name is a reserved name on this file system. + The file name is a reserved name on this file system. + + + + Filename contains trailing spaces. + Filename contains trailing spaces. + + + + Filename is too long. + Filename is too long. + + + + File/Folder is ignored because it's hidden. + File/Folder is ignored because it's hidden. + + + + Stat failed. + Stat failed. + + + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Conflict: Server version downloaded, local copy renamed and not uploaded. + + + + The filename cannot be encoded on your file system. + The filename cannot be encoded on your file system. + + + + The filename is blacklisted on the server. + The filename is blacklisted on the server. + + + + + + + the VFS instance is missing. Unable to continue. + the VFS instance is missing. Unable to continue. + + + + server reported no %1 + server reported no %1 + + + + + account was deleted. Unable to continue + account was deleted. Unable to continue + + + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: Ignored because its path is deselected + + + + Moved to invalid target, restoring + Moved to invalid target, restoring + + + + Not allowed to remove, restoring + Not allowed to remove, restoring + + + + Error while reading the database. + Error while reading the database. + + + + account was deleted. Unable to continue. + account was deleted. Unable to continue. + + + + Not allowed because you don't have permission to add subfolders to that folder + Not allowed because you don't have permission to add subfolders to that folder + + + + Error while doing a rename, unhandled status code: %1 + Error while doing a rename, unhandled status code: %1 + + + + Not allowed because you don't have permission to add files in that folder + Not allowed because you don't have permission to add files in that folder + + + + Not allowed to upload this file because it is read-only on the server, restoring + Not allowed to upload this file because it is read-only on the server, restoring + + + + This Space is currently unavailable + This Space is currently unavailable + + + + Server replied with an error while reading directory '%1' : %2 + Server replied with an error while reading directory '%1' : %2 + + + + OCC::PropagateDirectory + + + Error updating metadata: %1 + Error updating metadata: %1 + + + + %1 the folder is currently in use + %1 the folder is currently in use + + + + OCC::PropagateDownloadFile + + + Failed to free up space, the file %1 is currently in use + Failed to free up space, the file %1 is currently in use + + + + + File %1 can not be downloaded because of a local file name clash with %2! + File %1 can not be downloaded because of a local file name clash with %2! + + + + The download would reduce free local disk space below the limit + The download would reduce free local disk space below the limit + + + + Free space on disk is less than %1 + Free space on disk is less than %1 + + + + File was deleted from server + File was deleted from server + + + + Broken webserver returned empty content length for non-empty file on resume + Broken webserver returned empty content length for non-empty file on resume + + + + The file could not be downloaded completely. + The file could not be downloaded completely. + + + + The downloaded file is empty despite the server announced it should have been %1. + The downloaded file is empty despite the server announced it should have been %1. + + + + File %1 cannot be saved because of a local file name clash with %2! + File %1 cannot be saved because of a local file name clash with %2! + + + + Error updating metadata: %1 + Error updating metadata: %1 + + + + + + + The file %1 is currently in use + The file %1 is currently in use + + + + + File has changed since discovery + File has changed since discovery + + + + OCC::PropagateItemJob + + + ; Restoration Failed: %1 + ; Restoration Failed: %1 + + + + OCC::PropagateLocalMkdir + + + could not delete file %1, error: %2 + could not delete file %1, error: %2 + + + + Can not create local folder %1 because of a local file name clash with %2 + Can not create local folder %1 because of a local file name clash with %2 + + + + Error updating metadata: %1 + Error updating metadata: %1 + + + + The file %1 is currently in use + The file %1 is currently in use + + + + could not create folder %1 + could not create folder %1 + + + + OCC::PropagateLocalRemove + + + %1 failed with: %2 + %1 failed with: %2 + + + + Could not remove %1 because of a local file name clash with %2! + Could not remove %1 because of a local file name clash with %2! + + + + Could not move '%1' to the trash bin + Could not move '%1' to the Rubbish Bin + + + + + %1 the file is currently in use + %1 the file is currently in use + + + + OCC::PropagateLocalRename + + + File %1 can not be renamed to %2 because of a local file name clash + File %1 cannot be renamed to %2 because of a local file name clash + + + + Could not rename %1 to %2, the file is currently in use + Could not rename %1 to %2, the file is currently in use + + + + Error updating metadata: %1 + Error updating metadata: %1 + + + + The file %1 is currently in use + The file %1 is currently in use + + + + Failed to rename file + Failed to rename file + + + + Error setting pin state + Error setting pin state + + + + OCC::PropagateRemoteDelete + + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + + + + OCC::PropagateRemoteMkdir + + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + + + + Error writing metadata to the database: %1 + Error writing metadata to the database: %1 + + + + Setting file status failed due to file lock + Setting file status failed due to file lock + + + + OCC::PropagateRemoteMove + + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + + + + Error updating metadata: %1 + Error updating metadata: %1 + + + + The file %1 is currently in use + The file %1 is currently in use + + + + Error writing metadata to the database + Error writing metadata to the database + + + + Error setting pin state + Error setting pin state + + + + OCC::PropagateUpdateMetaDataJob + + + Could not update file : %1 + Could not update file : %1 + + + + The file %1 is currently in use + The file %1 is currently in use + + + + OCC::PropagateUploadCommon + + + Local file changed during sync. It will be resumed. + Local file changed during sync. It will be resumed. + + + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + + + + + Upload of %1 exceeds the quota for the folder + Upload of %1 exceeds the quota for the folder + + + + + %1 the file is currently in use + %1 the file is currently in use + + + + File Removed + File Removed + + + + The server did not provide the file permissions + The server did not provide the file permissions + + + + Error updating metadata: %1 + Error updating metadata: %1 + + + + OCC::PropagateUploadFile + + + %1 the file is currently in use + %1 the file is currently in use + + + + The server did ask for a removed legacy feature(polling) + The server did ask for a removed legacy feature(polling) + + + + The local file was removed during sync. + The local file was removed during sync. + + + + The server did not acknowledge the file upload. (No e-tag was present) + The server did not acknowledge the file upload. (No e-tag was present) + + + + OCC::PropagateUploadFileTUS + + + %1 the file is currently in use + %1 the file is currently in use + + + + Checksum computation failed + Checksum computation failed + + + + The local file was removed during sync. + The local file was removed during sync. + + + + OCC::ProtocolItemModel + + + Time + Time + + + + File + File + + + + Folder + Folder + + + + Issues + Issues + + + + Action + Action + + + + Size + Size + + + + Account + Account + + + + Status + Status + + + + OCC::RequestAuthenticationController + + + IdP is unreachable. Contact your system administrator or try again later. + IdP is unreachable. Contact your system administrator or try again later. + + + + Authentication failed. + Authentication failed. + + + + OCC::RequestAuthenticationWidget + + + Application Name Logo %1 + Application Name Logo %1 + + + + Sign in required + Sign in required + + + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + + + + Sign in URL + Sign in URL + + + + To copy the sign in URL to the clipboard, use the copy button + To copy the sign in URL to the clipboard, use the copy button + + + + Copy URL to sign in + Copy URL to sign in + + + + Additional logo defined by the organization + Additional logo defined by the organization + + + + Stay logged out + Stay logged out + + + + Sign in + Sign in + + + + URL copied + URL copied + + + + Copy URL + Copy URL + + + + OCC::ResolveUrlAdapter + + + SSL failure when connecting to server at %1 + SSL failure when connecting to server at %1 + + + + Could not detect compatible server at %1 + Could not detect compatible server at %1 + + + + Resolved url is invalid %1 + Resolved url is invalid %1 + + + + Rejected redirect from %1 to %2 + Rejected redirect from %1 to %2 + + + + OCC::SelectiveSyncWidget + + + Loading … + Loading… + + + + Deselect remote folders you do not wish to synchronize. + Deselect remote folders you do not wish to synchronise. + + + + Name + Name + + + + Size + Size + + + + + Currently there are no subfolders on the server. + Currently there are no subfolders on the server. + + + + An error occurred while loading the list of subfolders. + An error occurred while loading the list of subfolders. + + + + OCC::SettingsView + + + Form + Form + + + + General Settings + General Settings + + + + Use Monochrome Icons in the system tray + Use Monochrome Icons in the system tray + + + + Show Desktop Notifications + Show Desktop Notifications + + + + Start on Login + Start on Login + + + + Language + Language + + + + Language selector + Language selector + + + + Advanced + Advanced + + + + Sync hidden files + Sync hidden files + + + + Show crash reporter + Show crash reporter + + + + Move remotely deleted files to the local trash bin instead of deleting them + Move remotely deleted files to the local trash bin instead of deleting them + + + + Edit Ignored Files + Edit Ignored Files + + + + Log Settings + Log Settings + + + + Network + Network + + + + Warning + Warning + + + + Language changes require a restart of this application to take effect. + Language changes require a restart of this application to take effect. + + + + You cannot disable autostart because system-wide autostart is enabled. + You cannot disable autostart because system-wide autostart is enabled. + + + + (use default) + (use default) + + + + unknown (%1) + unknown (%1) + + + + OCC::SocketApi + + + Share with %1 + parameter is the application name + Share with %1 + + + + Confirm deletion + Confirm deletion + + + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Do you want to delete the directory <i>%1</i> and all its contents permanently? + + + + Do you want to delete the file <i>%1</i> permanently? + Do you want to delete the file <i>%1</i> permanently? + + + + Error + Error + + + + Moving file failed: + +%1 + Moving file failed: + +%1 + + + + I shared something with you + I shared something with you + + + + Select new location… + Select new location… + + + + + Share… + Share… + + + + + Copy private link to clipboard + Copy private link to clipboard + + + + Resharing this file is not allowed + Resharing this file is not allowed + + + + Resharing this folder is not allowed + Resharing this folder is not allowed + + + + Open in %1 + Open in %1 + + + + Show file versions in web browser + Show file versions in web browser + + + + Rename… + Rename… + + + + Rename and upload… + Rename and upload… + + + + + Move and rename… + Move and rename… + + + + Move, rename and upload… + Move, rename and upload… + + + + Move and upload… + Move and upload… + + + + Delete local changes + Delete local changes + + + + Delete + Delete + + + + OCC::SpacesPage + + + Select a folder to sync it to your computer. + Select a folder to sync it to your computer. + + + + Select a Space to sync it to your computer. + Select a Space to sync it to your computer. + + + + OCC::SyncEngine + + + %1 (skipped due to earlier error, trying again in %2) + %1 (skipped due to earlier error, trying again in %2) + + + + Unable to sync: the local folder is missing. + Unable to sync: the local folder is missing. + + + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Only %1 are available, need at least %2 to start + + + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + + + + + + + Cannot open the sync journal. + Cannot open the sync journal. + + + + Aborted due to %1 + Aborted due to %1 + + + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Disk space is low: Downloads that would reduce free space below %1 were skipped. + + + + There is insufficient space available on the server for some uploads. + There is insufficient space available on the server for some uploads. + + + + application exit + abort reason + application exit + + + + Unable to read the blacklist from the local database + Unable to read the blacklist from the local database + + + + Unable to read from the sync journal. + Unable to read from the sync journal. + + + + OCC::SyncErrorWidget + + + Issues table + Issues table + + + + There were too many issues. Not all will be visible here. + There were too many issues. Not all will be visible here. + + + + There were conflicts. Check the documentation on how to resolve them. + There were conflicts. Check the documentation on how to resolve them. + + + + The file %1 was ignored as its name is reserved by %2 + The file %1 was ignored as its name is reserved by %2 + + + + Reset column sizes + Reset column sizes + + + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + + + + Filter menu + Filter menu + + + + Account + Account + + + + Filter + Filter + + + + OCC::SyncJournalDb + + + Failed to connect database. + Failed to connect database. + + + + OCC::Theme + + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public Licence (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + + + + OCC::TlsErrorDialog + + + TLS Certificate Error + TLS Certificate Error + + + + Do you want to trust this certificate anyway? + Do you want to trust this certificate anyway? + + + + Cannot connect securely to %1 + Cannot connect securely to %1 + + + + &lt;not specified&gt; + &lt;not specified&gt; + + + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organisation: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organisation: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + + + + OCC::TrayMenuController + + + Unsupported Server Version + Unsupported Server Version + + + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + + + + Show %1 + Show %1 + + + + Help + Help + + + + About %1 + About %1 + + + + Quit %1 + Quit %1 + + + + OCC::UpdateDownloadedWidget + + + Restart later + Restart later + + + + Restart now + Restart now + + + + OCC::UpdaterScheduler + + + Update available + Update available + + + + OCC::UrlPageController + + + Your web browser will be opened to complete sign in. + Your web browser will be opened to complete sign in. + + + + %1 logo + %1 logo + + + + Welcome to %1 + Welcome to %1 + + + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Enter your server address to get started. Your web browser will be opened to complete sign in. + + + + Server address field + Server address field + + + + Enter your server address here + Enter your server address here + + + + Additional logo defined by the organization + Additional logo defined by the organization + + + + Invalid server URL: %1 + Invalid server URL: %1 + + + + Invalid URL scheme. Only https is accepted. + Invalid URL scheme. Only https is accepted. + + + + The requested URL failed with query value: %1 + The requested URL failed with query value: %1 + + + + OCC::UserInfoAdapter + + + Network request to collect user info failed + Network request to collect user info failed + + + + Unable to retrieve user info: invalid credentials. + Unable to retrieve user info: invalid credentials. + + + + Unexpected network response when retrieving user info. + Unexpected network response when retrieving user info. + + + + Retrieving user info failed with JSON error. + Retrieving user info failed with JSON error. + + + + OCC::ValidateChecksumHeader + + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + + + + OCC::Vfs + + + The Virtual filesystem feature does not support a drive as sync root + The Virtual filesystem feature does not support a drive as sync root + + + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + + + + The Virtual filesystem feature is not supported on network drives + The Virtual filesystem feature is not supported on network drives + + + + OCC::VfsWinPrivate + + + + %1 - %2 + %1 - %2 + + + + Could not find StorageProviderState property %1 + Could not find StorageProviderState property %1 + + + + Could not find StorageFolder for %1 error: %2 + Could not find StorageFolder for %1 error: %2 + + + + Could not retrieve StorageFolder for %1 %2 (0x%3) + Could not retrieve StorageFolder for %1 %2 (0x%3) + + + + The folder is used by a different client: %1 + The folder is used by a different client: %1 + + + + Error registering StorageProvider for %1: %2 (0x%3) + Error registering StorageProvider for %1: %2 (0x%3) + + + + Unable to connect sync root: %1 error: %2 + Unable to connect sync root: %1 error: %2 + + + + %1 is managed by another sync client + %1 is managed by another sync client + + + + Downgrading the folder from %1 to %2 is not supported + Downgrading the folder from %1 to %2 is not supported + + + + OCC::WebFingerLookupAdapter + + + Failed to retrieve user info + Failed to retrieve user info + + + + WebFinger lookup returned no links + WebFinger lookup returned no links + + + + OCC::WindowsUpdater + + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + + + + Skip this version + Skip this version + + + + Update Failed + Update Failed + + + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + + + + Ask again later + Ask again later + + + + Restart and update + Restart and update + + + + Update manually + Update manually + + + + QObject + + + in the future + in the future + + + + %n day(s) ago + %n day ago%n days ago + + + + %n hour(s) ago + %n hour ago%n hours ago + + + + now + now + + + + less than a minute ago + less than a minute ago + + + + %n minute(s) ago + %n minute ago%n minutes ago + + + + %1: %2 + this displays an error string (%2) for a file %1 + %1: %2 + + + + SpacesBrowser + + + Form + Form + + + + SpacesPage + + + Spaces list + Spaces list + + + + SpacesView + + + Spaces + Spaces + + + + SyncFileItem::Status + + + Undefined + Undefined + + + + Fatal Error + Fatal Error + + + + + Error + Error + + + + Info + Info + + + + Success + Success + + + + Conflict + Conflict + + + + File Ignored + File Ignored + + + + Restored + Restored + + + + Blacklisted + Blacklisted + + + + Excluded + Excluded + + + + Message + Message + + + + Filename Reserved + Filename Reserved + + + + UpdateDownloadedWidget + + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + + + + Utility + + + %n year(s) + %n year%n years + + + + %n month(s) + %n month%n months + + + + %n day(s) + %n day%n days + + + + %n hour(s) + %n hour%n hours + + + + %n minute(s) + %n minute%n minutes + + + + %n second(s) + %n second%n seconds + + + + %1 %2 + %1 %2 + + + + Shutting down %1 + Shutting down %1 + + + + account loading + + + Error accessing the configuration file + Error accessing the configuration file + + + + There was an error while accessing the configuration file at %1. + There was an error while accessing the configuration file at %1. + + + + ownCloudTheme::aboutVersions() + + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + + + + ownCloudTheme::qtVer + + + %1 (Built against Qt %2) + %1 (Built against Qt %2) + + + + ownCloudTheme::versionWithSha + + + %1 %2 + %1 %2 + + + + pinstate + + + Always available locally + Always available locally + + + + Currently available locally + Currently available locally + + + + Some available online only + Some available online only + + + + Available online only + Available online only + + + + progress + + + Virtual file created + Virtual file created + + + + Replaced by virtual file + Replaced by virtual file + + + + Downloaded + Downloaded + + + + Uploaded + Uploaded + + + + Deleted + Deleted + + + + Ignored + Ignored + + + + Server version downloaded, local copy was backed up as conflict file + Server version downloaded, local copy was backed up as conflict file + + + + Renamed %1 to %2 + Renamed %1 to %2 + + + + Moved %1 to %2 + Moved %1 to %2 + + + + Error + Error + + + + Updated local metadata + Updated local metadata + + + + + Unknown + Unknown + + + + downloading + downloading + + + + uploading + uploading + + + + deleting + deleting + + + + moving + moving + + + + ignoring + ignoring + + + + error + error + + + + updating local metadata + updating local metadata + + + + utility + + + Could not open browser + Could not open browser + + + + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? + + + + Could not open email client + Could not open email client + + + + There was an error when launching the email client to create a new message. Maybe no default email client is configured? + There was an error when launching the email client to create a new message. Maybe no default email client is configured? + + + + Make always available locally + Make always available locally + + + + Free up local space + Free up local space + + + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Some settings were configured in newer versions of this client and use features that are not available in this version + + + \ No newline at end of file diff --git a/translations/client_es.ts b/translations/client_es.ts index fee9f72d15d..76ba1a180eb 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -1,360 +1,424 @@ - + - CrashReporter + AccountBar - - Tomahawk Crash Reporter - + + Navigation bar + Barra de navegación - - <html><head/><body><p><br/></p></body></html> - + + Add Account + Añadir Cuenta - - Abort - Cancelar + + Activity + Actividad - - Send this report - + + Settings + Configuración - - Don't send - + + Quit + Salir + + + AppImageUpdateAvailableWidgetUi - - You can disable sending crash reports in the configuration dialog. - + + Skip this version + Saltarse esta versión + + + AppProvider - - - - We cannot gather useful debug information on your system. - + + Failed to open %1 in web. Error: %2. + Error al abrir %1 en web. Error: %2. + + + ChecksumHeader - - - - - - Close - Cerrar + + The checksum header is malformed: %1 + La cabecera de checksum no es correcta: %1 - - Ready to send debug information (<a href="%1">view backtrace</a>). - + + The checksum header contained an unknown checksum type '%1' + La suma de comprobación de cabeceras contenía una suma de comprobación desconocida de tipo '%1' + + + CommandLine - - Gathering debug information... - + + %1 version %2 +File synchronization desktop utility. + %1 version %2<byte value="xd"/> +Cliente de escritorio para la sincronización de archivos. - - Uploaded %L1 of %L2 KB. - + + For more information, see %1 + link to homepage + Para más información, mira %1 - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Starten Sie mit dem sichtbaren Hauptfenster, oder wenn es bereits läuft, bringen Sie es in den Vordergrund. Standardmäßig wird die App im Hintergrund gestartet. - - Failed to send crash info. - + + Quit the running instance. + Salir de la actual instancia. - - - FileSystem - - - - - Can't rename %1, the file is currently in use - + + Write log to file (use - to write to stdout). + Salvar el log en un archivo (usar - para escribir en formato estandar) - - Could not make directories in trash - No se pueden crear directorios en la papelera. + + Write each sync log output in a new file in folder. + Escribir cada log de sincronización en un archivo nuevo en la carpeta. - - - Could not move '%1' to '%2' - No se puede mover '%1' a '%2' + + Flush the log file after every write. + Vacíe el fichero de log después de cada escritura. - - Moving to the trash is not implemented on this platform - Mover a la papelera no está implementado en esta plataforma. + + Output debug-level messages in the log. + Mostrar mensajes de depuración en el log. - - Error removing '%1': %2 - Error eliminando '%1': %2 + + Enable debug mode. + Habilitar modo de depuración. - - Could not remove folder '%1' - No se pudo eliminar la carpeta '%1' + + --logfile and --logdir are mutually exclusive + --logfile y --logdir son mutuamente exclusivos - FolderWizardSourcePage + CommonStrings - - Form - Formulario + + file manager + Gestor de archivos - - Pick a local folder on your computer to sync - Seleccione una carpeta local en su equipo para sincronizar + + Show in %1 + Mostrar en %1 - - &Choose... - &Seleccionar... + + Show in web browser + Mostrar en el Navegador Web - - - FolderWizardTargetPage - - Form - Formulario + + Copy + Copiar + + + + %n Filter(s) + %n Filtro%n Filtros%n Filtros - - Select a remote destination folder - Seleccionar una carpeta remota de destino + + folder + carpeta - - Create Folder - Crear carpeta + + space + espacio - - Refresh - Actualizar + + folders + carpetas - - Folders - Carpetas + + spaces + espacios - - TextLabel - EtiquetaDeTexto + + Folder + Carpeta - - - NotificationWidget - - Form - Formulario + + Space + Espacio + + + FetchUserInfoJobFactory - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet + + Failed to retrieve user info + Error al obtener información del usuario + + + FileSystem - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + + + + Can't rename %1, the file is currently in use + No se puede renombrar %1, el archivo está actualmente en uso - - TextLabel - Etiqueta de texto + + Could not remove folder + No se ha podido eliminar la carpeta - OCC::AboutDialog + FolderItem - - About - Acerca de + + + Offline + Offline - - TextLabel - EtiquetaDeTexto + + + Unavailable: the space is no longer available on the server + No disponible: ya no hay espacio disponible en el servidor - - Versions - Versiones + + + Sync paused + Sincronización en pausa - - About %1 - Acerca de %1 + + Synced + Sincronizado - - - OCC::AbstractNetworkJob - - Connection timed out - Tiempo de conexión agotado + + Sync failed + Sincronización fallida - - Unknown error: network reply was deleted - Error desconocido: la respuesta de red fue eliminada + + Sync pending + Sincronización pendiente - - Server replied "%1 %2" to "%3 %4" - El servidor respondió "%1 %2" a "%3 %4" + + Syncing %1 of %2 (%3 % + Sincronizando %1 de %2 (%3 % + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, estado de la sincronización: %2 + first argument is the displayed name of the folder. - OCC::Account + FolderManagementUtils - - %1@%2 - %1@%2 + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + Der Pfad '%1' ist zu lange. Bitte aktivieren Sie lange Pfade in den Windows-Einstellungen oder wählen Sie einen anderen Ordner. + + + + Local folder %1 does not exist. + La carpeta local %1 no existe. + + + + %1 is not a folder. + %1 no es una carpeta. + + + + %1 is not readable. + %1 es ilegible. + + + + %1 is not writable. + %1 no es editable. - OCC::AccountSettings + FolderWizard - - Form - Formulario + + <b>Error:</b> %1 + <b>Error:</b>%1 - - Re-open Browser - Re-abrir navegador + + <b>Warning:</b> %1 + <b>Advertencia:</b> %1 - - ... - ... + + <b>Error:</b><ul>%1</ul> + <b>Error:</b><ul>%1</ul> - - Storage space: ... - Espacio de almacenamiento: ... + + <b>Warning:</b><ul>%1</ul> + <b>Advertencia:</b><ul>%1</ul> + + + FolderWizardSourcePage - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Las carpetas no seleccionadas serán <b>eliminadas</b> de su sistema de archivos local y ya no serán sincronizadas con este ordenador + + Form + Formulario - - Synchronize all - Sincronizar todo + + Pick a local folder on your computer to sync + Seleccione una carpeta local en su equipo para sincronizar - - Synchronize none - No sincronizar nada + + Browse… + Examinar… + + + FolderWizardTargetPage - - Apply manual changes - Aplicar cambios manuales + + Form + Formulario - - Apply - Aplicar + + Select a remote destination folder + Seleccionar una carpeta remota de destino - - - - - Cancel - Cancelar + + Create Folder + Crear carpeta - - Connected with <server> as <user> - Conectado a <server> como <user> + + Refresh + Actualizar - - No account configured. - No se ha configurado ninguna cuenta. + + Folders + Carpetas + + + OCC::AboutView - - Remove - Eliminar + + About + Acerca de - - Account - Cuenta + + Versions + Versiones + + + OCC::AbstractNetworkJob - - Choose what to sync - Elija qué sincronizar + + Connection timed out + Tiempo de conexión agotado - - Force sync now - Forzar la sincronización ahora + + Unknown error: network reply was deleted + Error desconocido: la respuesta de red fue eliminada - - Restart sync - Reiniciar sincronización + + Server replied "%1 %2" to "%3 %4" + El servidor respondió "%1 %2" a "%3 %4" + + + OCC::Account - - Remove folder sync connection - Eliminar la sincronización de carpetas conectadas + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - Folder creation failed - Hubo un fallo al crear la carpeta + + Choose what to sync + Elija qué sincronizar - - <p>Could not create local folder <i>%1</i>. - <p>No se pudo crear la carpeta local <i>%1</i>. + + + Sync now + Sincronizar ahora - - Confirm Folder Sync Connection Removal - Confirme eliminación de la Conexión a la carpeta sincronizada + + Manage subfolder sync + Gestionar la sincronización de subcarpetas - - Remove Folder Sync Connection - Eliminar carpeta de sincronización conectada + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + Quitar sincronización de %1  - + Disable virtual file support? - Desactivar soporte a archivo virtual? + ¿Desactivar soporte a archivo virtual? - + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. The only advantage of disabling virtual file support is that the selective sync feature will become available again. @@ -367,572 +431,553 @@ La única ventaja de deshabilitar el soporte de archivos virtuales es que la fun Esta acción abortará cualquier sincronización actualmente en ejecución. - + Disable support Desactivar ayuda - - Sync Running - Sincronización en curso + + Restart sync + Reiniciar sincronización + + + + Resume sync + Continuar sincronización - - The syncing operation is running.<br/>Do you want to terminate it? - La sincronización está en curso.<br/>¿Desea interrumpirla? + + Pause sync + Pausar sincronización - - %1 in use - %1 en uso + + Deactivate virtual files + Desactivar archivos virtuales - - %1 as <i>%2</i> - %1 como <i>%2</i> + + Activate virtual files + Activar archivos virtuales - - Connected to %1. - Conectado a %1. + + Internet connection is metered + Die Internetverbindung wird für den Durchsatz überwacht - - Server %1 is temporarily unavailable. - Servidor %1 no está disponible temporalmente. + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + Die Synchronisation wurde pausiert, da die Internetverbindung für den Durchsatz überwacht wird<p>Wollen Sie die Synchronisation trotzdem durchführen? - - Server %1 is currently in maintenance mode. - El servidor %1 está actualmente en modo mantenimiento. + + Sync Running + Sincronización en curso - - Signed out from %1. - Cerró sesión desde %1. + + The sync operation is running.<br/>Do you want to stop it? + Die sync Operation läuft gerade.<br/>Wollen Sie sie stoppen? - - Connecting to %1... - Conectando a %1... + + Sync paused by user + Synchronisation durch den Benutzer pausiert - - No connection to %1 at %2. - Sin conexión a %1 en %2. + + Confirm Folder Sync Connection Removal + Confirme eliminación de la Conexión a la carpeta sincronizada - - Log in - Iniciar sesión + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>¿De verdad quiere dejar de sincronizar la carpeta <i>%1</i>?</p><p><b>Nota:</b> Esto <b>no</b> eliminará los archivos.</p> - - There are folders that were not synchronized because they are too big: - Hay carpetas que no fueron sincronizadas porque son demasiado grandes: + + Remove Folder Sync Connection + Eliminar carpeta de sincronización conectada - - There are folders that were not synchronized because they are external storages: - Hay carpetas que no fueron sincronizadas porque residen en almacenamiento externo: + + + Cancel + Cancelar + + + OCC::AccountFoldersView - - There are folders that were not synchronized because they are too big or external storages: - Hay carpetas que no fueron sincronizadas porque son demasiado grandes o residen en almacenamiento externo: + + Sync options menu + Menú de opciones de sincronización - - Confirm Account Removal - Confirmar eliminación de cuenta + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 sincronización - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>¿De verdad quiere eliminar la conexión a la cuenta <i>%1</i>?</p><p><b>Nota:</b> Esto <b>no</b> eliminará los archivos.</p> + + Manage your synced %1. + Gestione su %1 sincronizado. - - Remove connection - Eliminar conexión + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Añada nueva sincronización %1… - - Open folder - Abrir carpeta + + %1 list view + Vista de la lista %1 - - - Log out - Cerrar sesión + + Navigate the %1 list using the up and down arrows + Navegue por la lista %1 con las flechas arriba y abajo - - Open local folder - Abrir carpeta local + + %1 out of %2 %3 are synchronized + %1 de %2 %3 están sincronizados + + + OCC::AccountModalWidget - - Open folder in browser - Abrir carpeta en el navegador de internet + + + Form + Formulario + + + OCC::AccountPlaceholderWidget - - Resume sync - Continuar sincronización + + %1 logo + %1 logotipo - - Pause sync - Pausar sincronización + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + Aún no tiene ninguna cuenta vinculada. + +Cree una cuenta seleccionando la opción «Añadir cuenta...» en el menú del botón «Más». + + + + Additional logo defined by the organization + Logotipo adicional definido por la organización. + + + + OCC::AccountView + + + Form + Formulario - - Availability - Disponibilidad + + Manage Account + Gestionar cuenta - - Disable virtual file support... - Deshabilitar el soporte de archivos virtuales... + + Preparing the account + Benutzeraccount vorbereiten - - Enable virtual file support%1... - Activar soporte para archivos virtuales %1... + + Sync connections + Conexiones de sincronización - - (experimental) - (experimental) + + Account options menu + Menú de opciones de la cuenta + + + OCC::AccountViewController - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>¿De verdad quiere dejar de sincronizar la carpeta <i>%1</i>?</p><p><b>Nota:</b> Esto <b>no</b> elminará los archivo.</p> + + + Log in + Iniciar sesión - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) de %2 en uso. Algunas carpetas, como carpetas de red o compartidas, podrían tener límites diferentes. + + Reconnect + Reconectar - - %1 of %2 in use - %1 de %2 en uso + + Remove + Eliminar - - Currently there is no storage usage information available. - Actualmente no hay información disponible sobre el uso de almacenamiento. + + Log out + Salir - - The server version %1 is unsupported! Proceed at your own risk. - ¡La versión del servidor %1 no es compatible! Continúa bajo tu responsabilidad. + + Confirm Account Removal + Confirmar eliminación de cuenta - - Obtaining authorization from the browser. - Obteniendo autorización del navegador. + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>¿De verdad quiere eliminar la conexión a la cuenta <i>%1</i>?</p><p><b>Nota:</b> Esto <b>no</b> eliminará los archivos.</p> - - Server configuration error: %1 at %2. - Error de configuración del servidor: %1 en %2. + + Remove connection + Eliminar conexión - - No %1 connection configured. - No hay ninguna conexión de %1 configurada. + + Cancel + Cancelar - - - OCC::AccountState - - Url update requested for %1 - + + The server version %1 is unsupported! Proceed at your own risk. + ¡La versión del servidor %1 no es compatible! Continúa bajo tu responsabilidad. - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - + + Connected + Conectado - - Change url permanently to %1 + + Server is temporarily unavailable. - - Reject + + Server is currently in maintenance mode. - + Signed out Cerrar sesión - - Disconnected - Desconectado - - - - Connected - Conectado + + Updating credentials… + Actualizando credenciales... - - Service unavailable - Servicio no disponible + + Captive portal prevents connections to the server. + Portal Captivo previene conexiones al servidor. - - Maintenance mode - Modo mantenimiento + + Sync is paused due to metered internet connection. + - - Network error - Error en la red + + Connecting… + Conectando... - - Configuration error - Error en la configuración + + Server configuration error + Error de configuración en el servidor - - Asking Credentials - Solicitando Credenciales + + Disconnected + Desconectado - - Unknown account state - Estado de cuenta desconocido + + Invalid connection status + - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 en %3 - + OCC::AccountsGuiController - - Activity - Actividad + + + New account failure + Error al crear una nueva cuenta - - Account - Cuenta + + The information required to create a new account is incomplete. Please run the wizard again. + La información necesaria para crear una nueva cuenta está incompleta. Vuelva a ejecutar el asistente. - - Time - Hora + + Accounts + Cuentas - - Local path - + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + No se pudo crear la cuenta debido a un error: +%1 +Compruebe la disponibilidad del servidor y vuelva a ejecutar el asistente. OCC::ActivitySettings - - - Server Activity - Actividad del servidor - - - - Sync Protocol - Protocolo de Sincronización + + Local Activity + Actividad local - + Not Synced No sincronizado - + Not Synced (%1) %1 is the number of not synced files. - No Sincronizado (%1) - - - - OCC::ActivityWidget - - - Form - Formulario - - - - - - TextLabel - Etiqueta de texto - - - - Filter - Filtro - - - - Server Activities - Actividades del servidor - - - - Notifications - Notificaciones - - - - %n notifications(s) for %1. - %n notificación para %1.%n notificaciones para %1. - - - - %n notifications(s) for %1 and %2. - %n notificación para %1 y %2.%n notificaciones para %1 y %2. - - - - New notifications for %1, %2 and other accounts. - Nuevas notificaciones para %1, %2 y otras cuentas. - - - - Open the activity view for details. - Abrir la vista de actividad para ver los detalles. - - - - Copy to clipboard - Copiar al portapapeles - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>La cuenta %1 no tiene actividades habilitadas. + No sincronizado (%1) - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Continuar significa que <b>borrará estas opciones:</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Continuar significa que <b>ignorará estas opciones:</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Algunas opciones han sido configuradas con nuevas versiones del cliente y por ello algunas opciones no están disponibles en esta versión.<br><br>%1<br><br>El archivo de configuración debe ser respaldado en<i>%2</i>. - + OCC::AdvancedSettingsPageController - - Quit - Salir + + Advanced settings + Configuración avanzada - - Continue - Continuar + + Sync and download options + Opciones de sincronización y descarga - - Error accessing the configuration file - Error al acceder al archivo de configuración + + Download location + Ubicación de descarga - - There was an error while accessing the configuration file at %1. - Ha ocurrido un error al acceder al archivo de configuración %1. + + Sync and download files as you use them (saves hard drive space) + Sincroniza y descarga archivos a medida que los utiliza (ahorra espacio en el disco duro). - - Quit %1 - Salir de %1 + + Automatically sync and download folders and files + Sincronizar y descargar automáticamente carpetas y archivos - - %1 version %2 -File synchronization desktop utility. - + + Sync and download folders manually + Sincronizar y descargar carpetas manualmente - - For more information, see http://www.owncloud.org - + + Download location on the local machine + Ubicación de descarga en el equipo local - - Show the settings dialog while starting. - + + Browse… + Examinar… - - Quit the running instance. - + + Browse for a download location + Buscar una ubicación para la descarga - - Write log to file (use - to write to stdout). - + + Invalid local download directory %1: %2 + Directorio de descarga local no válido %1: %2 - - Write each sync log output in a new file in folder. - + + your user directory may not be chosen as the sync root. + Es posible que su directorio de usuario no pueda seleccionarse como raíz de sincronización. - - Remove logs older than <hours> hours (to be used with --logdir). - + + path is not located on a supported Apple File System. + La ruta no se encuentra en un sistema de archivos Apple compatible. - - Flush the log file after every write. - + + path must be absolute. + La ruta debe ser absoluta. - - Output debug-level messages in the log. - + + selected path does not support using virtual file system. %1 + la ruta seleccionada no admite el uso del sistema de archivos virtual. %1 - - - Override UI language. - + + Select sync root + Seleccionar raíz de sincronización + + + OCC::AppImageUpdateAvailableWidget - - Use the given configuration folder. - + + Installed version: %1 + Versión instalada: %1 - - Enable debug mode. - + + Available update: %1 + Actualización disponible: %1 - - Virtual file system file to be opened (optional). - + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Una actualización esta disponible para este AppImage de %1. Quiere instalarla? + +La actualización se realizará en segundo plano, y sobreescribirá el actual fichero AppImage. Necesitará reiniciar la aplicación para completar la actualización. + + + OCC::Application - - [<vfs file>] - + + Quit + Salir + + + OCC::AuthSuccessPageController - - Invalid path passed to --confdir - + + Application Name Logo %1 + Logotipo de nombre de la aplicación %1 - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - + + You're all set! Open %1 and get started. + ¡Ya está todo listo! Abra %1 y comience. - - Available translations: %1 - + + Additional logo defined by the organization + Logotipo adicional definido por la organización. - OCC::AuthenticationDialog - - - Authentication Required - Se requiere autenticación - - - - Enter username and password for '%1' at %2. - Introducir usuario y contraseña para '%1' en %2. - + OCC::ButtonDelegate - - &User: - &Usuario: + + %1 options button + Botón de opciones %1 - - &Password: - Contraseña: + + Menu button with %1 options. Use the space key to show the menu + Botón de menú con %1 opciones. Utilice la tecla de espacio para mostrar el menú OCC::ConnectionValidator - + No ownCloud account configured No se ha configurado ninguna cuenta de ownCloud - - The configured server for this client is too old - La configuración del servidor para este cliente está obsoleta - - - + Please update to the latest server and restart the client. Por favor, actualice a la última versión del servidor y reinicie el cliente - + Authentication error: Either username or password are wrong. Error de autenticación: El usuario o la contraseña son incorrectos - + timeout tiempo de espera - + + No account configured + No hay ninguna cuenta configurada. + + + The provided credentials are not correct Las credenciales proporcionadas no son correctas + + + The configured server for this client is too old. + El servidor configurado para este cliente es demasiado antiguo. + OCC::CredentialJob - + Failed to parse credentials %1 - + Ha fallado la comprobación de credenciales %1 + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + El servidor no admite OAuth + + + + Server did not ask for authorization + El servidor no solicitó autorización + + + + Failed to determine authentication type: %1 + No se pudo determinar el tipo de autenticación: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + Se ha recibido una respuesta no válida del servidor. OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + La cuenta fue eliminada antes de que pudiéramos iniciar el trabajo de propfind. + + + + Server error: PROPFIND reply is not XML formatted! Error en el servidor: la réplica PROPFIND no está en un formato XML! @@ -940,27 +985,22 @@ File synchronization desktop utility. OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 Error durante la apertura de la carpeta %1 - + Directory not accessible on client, permission denied Directorio no accesible en el cliente, permiso denegado - + Directory not found: %1 No se encuentra carpeta: %1 - - Filename encoding is not valid - Los caracteres del nombre de fichero no son válidos - - - + Error while reading directory %1 Error durante la lectura del directorio %1 @@ -968,148 +1008,137 @@ File synchronization desktop utility. OCC::ExpandingHeaderView - + Reset column sizes - + Restablecer el tamaño de las columnas - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - La carpeta local %1 no existe. + + server sent invalid content type: %1. + El servidor envió un tipo de contenido no válido: %1. - - %1 should be a folder but is not. - %1 debería ser un directorio, pero no lo es. + + could not parse Capabilities from server. + No se pudieron analizar las capacidades del servidor. - - %1 is not readable. - %1 es ilegible. + + reply sent for different subject (server): %1 + Respuesta enviada para otro asunto (servidor): %1 + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 ha sido eliminado. - + %1 has been updated. %1 names a file. %1 ha sido actualizado. - + %1 has been renamed to %2. %1 and %2 name files. %1 ha sido renombrado a %2. - + %1 has been moved to %2. %1 ha sido movido a %2. - + %1 and %n other file(s) have been removed. - %1 y otro archivo han sido borrados.%1 y otros %n archivos han sido borrados. + %1 y %n otro fichero se borraron.%1 y %n otros ficheros se borraron.%1 y %n otros ficheros se borraron. - - %1 is not writable. - + + The folder has been disabled or removed from the server + La carpeta se ha desactivado o se ha eliminado del servidor - + %1 and %n other file(s) have been added. - %1 y % otros archivo(s) han sido añadidos.%1 y % otros archivo(s) han sido añadidos. + %1 y %n otro fichero se añadieron.%1 y %n otros ficheros se añadieron.%1 y %n otros ficheros se añadieron. - + %1 has been added. %1 names a file. %1 ha sido añadido. - + %1 and %n other file(s) have been updated. - %1 y otro archivo han sido actualizados.%1 y otros %n archivos han sido actualizados. + %1 y %n otro fichero se actualizaron.%1 y %n otros ficheros se actualizaron.%1 y %n otros ficheros se actualizaron. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 ha sido renombrado a %2 y otro archivo ha sido renombrado.%1 ha sido renombrado a %2 y otros %n archivos han sido renombrado. + %1 ha sido renombrado a %2 y %n otro fichero se renombraron.%1 ha sido renombrado a %2 y %n otros ficheros se renombraron.%1 ha sido renombrado a %2 y %n otros ficheros se renombraron. - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 ha sido movido a %2 y otro archivo ha sido movido.%1 ha sido movido a %2 y otros %n archivos han sido movidos. + %1 ha sido movido a %2 y %n otro fichero se movieron.%1 ha sido movido a %2 y %n otros ficheros se movieron.%1 ha sido movido a %2 y %n otros ficheros se movieron. - - %1 has and %n other file(s) have sync conflicts. - %1 y otro archivo han tenido conflictos al sincronizar.%1 y otros %n archivos han tenido conflictos al sincronizar. + + %1 and %n other file(s) have sync conflicts. + %1 y %n otro fichero tienen conflictos.%1 y %n otros ficheros tienen conflictos.%1 y %n otros ficheros tienen conflictos. - + %1 has a sync conflict. Please check the conflict file! Conflicto al sincronizar %1. ¡Por favor, compruebe el archivo! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 y otro archivo no pudieron ser sincronizados debido a errores. Para más detalles vea el registro.%1 y otros %n archivos no pudieron ser sincronizados debido a errores. Para más detalles vea el registro. + %1 y %n otro fichero no se pudieron sincronizar debido a errores. Detalles en el log.%1 y %n otros ficheros no se pudieron sincronizar debido a errores. Detalles en el log.%1 y %n otros ficheros no se pudieron sincronizar debido a errores. Detalles en el log. - + %1 could not be synced due to an error. See the log for details. %1 no pudo ser sincronizado debido a un error. Para más detalles, vea el registro. - + Sync Activity Actividad de la sincronización - - Could not read system exclude file - No se ha podido leer el archivo de exclusión del sistema - - - - A new folder larger than %1 MB has been added: %2. - - Una carpeta mayor de %1 MB ha sido añadida: %2. - - - - - A folder from an external storage has been added. - - Una carpeta de almacenamiento externo ha sido añadida. - + + Switching VFS mode on folder '%1' + Ändere zu VFS Modus im Ordner '%1' - - Please go in the settings to select it if you wish to download it. - Por favor vaya a opciones a seleccionarlo si desea descargar esto. + + Could not read system exclude file + No se ha podido leer el archivo de exclusión del sistema - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. La carpeta %1 ha sido creada, pero tambien ha sido excluida de la sincronización. Los datos que contenga, no serán sincronizados. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. El archivo %1 ha sido creado, pero tambien ha sido excluido de la sincronización. Por tanto, no será sincronizado. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1121,3585 +1150,2397 @@ Esto significa que el cliente de sincronización podría no subir los cambios lo %1 + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Todos los archivos en la carpeta %1 serán borrados en el servidor. -Estas eliminaciones se sincronizarán con su carpeta local sync, haciendo imposible su recuperación a menos que disponga de permisos para restaurar. -Si decide mantener estos archivos, serán re-sincronizados con el servidor si dispone usted de permisos para ello. -Si decide borrarlos, no estarán disponibles para usted a menos que sea el propietario. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Todos los archivos en la carpeta %1 serán borrados en el servidor. -¿Está Vd. seguro de que desea sincronizarse con el servidor? -Si ha sido un accidente, y decide mantener los archivos, serán re-sincronizados con el servidor. - - - - Remove All Files? - ¿Eliminar todos los archivos? - - - - Remove all files - Eliminar todos los archivos + + There are unresolved conflicts. + Hay conflictos sin resolver - - Keep files - Conservar archivos + + Sync error: %1 + Error de sincronización: %1 OCC::FolderMan - + Could not reset folder state No se ha podido restablecer el estado de la carpeta - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Se ha encontrado un antiguo registro de sincronización '%1'; pero no se ha podido eliminar. Por favor, asegúrese de que ninguna aplicación la esté utilizando. - - (backup) - (copia de seguridad) - - - - (backup %1) - (copia de seguridad %1) + + Unsupported filesystem + Sistema de ficheros no soportado - - Undefined State. - Estado no definido. + + Account disconnected or paused + Benutzerkonto unterbrochen oder pausiert - - Waiting to start syncing. - Esperando para comenzar la sincronización. + + User triggered force sync + Benutzergesteuerte Zwangssynchronisation - - Preparing for sync. - Preparándose para sincronizar. + + Folder is about to be removed + Der Ordner wird demnächst entfernt - - Sync is running. - Sincronización en funcionamiento. + + (backup) + (copia de seguridad) - - Sync was successful, unresolved conflicts. - La sincronización se ha realizado. pero hay conflictos sin resolver. + + (backup %1) + (copia de seguridad %1) - - Last Sync was successful. - La última sincronización se ha realizado con éxito. + + The selected path is not a folder. + La ruta seleccionada no es una carpeta. - - Setup Error. - Error de configuración. + + The folder %1 is used in a folder sync connection. + La carpeta %1 se utiliza en una conexión de sincronización de carpetas. - - User Abort. - Interrumpido por el usuario. + + Folder '%1' is already in use by application %2. + La carpeta «%1» ya está en uso por la aplicación %2. - - Sync is paused. - La sincronización está en pausa. + + There is already a sync from the server to this local folder. + Ya hay una sincronización desde el servidor a esta carpeta local. - - %1 (Sync is paused) - %1 (Sincronización en pausa) + + The local folder %1 already contains a folder used in a folder sync connection. + La carpeta local %1 ya contiene una carpeta que se utiliza en una conexión de sincronización de carpetas. - - No valid folder selected! - ¡La carpeta seleccionada no es válida! + + The local folder %1 is already contained in a folder used in a folder sync connection. + La carpeta local %1 ya se encuentra dentro de una carpeta utilizada en una conexión de sincronización de carpetas. - - The folder %1 is used in a folder sync connection! - + + No valid folder selected. + No se ha seleccionado ninguna carpeta válida. - - The selected path does not exist! - La ruta seleccionada no existe! + + The selected path does not exist. + La ruta seleccionada no existe. - - The selected path is not a folder! - ¡La ruta seleccionada no es un directorio! + + You have no permission to write to the selected folder. + No tienes permiso para escribir en la carpeta seleccionada. - - You have no permission to write to the selected folder! - ¡Usted no tiene permiso para escribir en la carpeta seleccionada! + + Folder '%1' is already in use by another account. + La carpeta '%1' está siendo usada por otra cuenta. - - There is already a sync from the server to this local folder. Please pick another local folder! - Ya existe una tarea de sincronización entre el servidor y esta carpeta. Por favor elija otra carpeta local. + + On macOS, only the Apple File System is supported. + En macOS, solo se soporta el sistema de ficheros de Apple. + + + OCC::FolderModelController - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - El directorio local %1 ya contiene un directorio usado en una conexión de sincronización de directorios. Por favor, elija otro. + + %1 options + %1 opciones - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - El directorio local %1 está dentro de un directorio usado en una conexión de sincronización de directorios. Por favor, elija otro. + + Menu button with %1 options. Use the space key to pop the %2 options menu + Botón de menú con %1 opciones. Utilice la tecla de espacio para abrir el menú de opciones %2. + + + OCC::FolderWatcher - - %1 Please pick another one! - + + The watcher did not receive a test notification. + El observador no recibió una notificación de prueba + + + OCC::FolderWatcherPrivate - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Este problema sucede habitualmente cuando las búsquedas de inotify están agotadas. Comprueba las FAQ para más detalles. - OCC::FolderStatusDelegate + OCC::FolderWizard + + + Virtual files are not available for the selected folder + Archivos Virtuales no está disponible para la carpeta seleccionada + - + Add Folder Sync Connection - Añadir Conexión para el Directorio de Sincronización + Añadir conexión de sincronización de carpetas - - File - Archivo + + Add Sync Connection + Añadir Sincronización de Conexión - OCC::FolderStatusModel + OCC::FolderWizardLocalPath - - You need to be connected to add a folder - Necesita estar conectado para añadir una carpeta + + Click to select a local folder to sync. + Haga clic para seleccionar una carpeta local para sincronizar. - - Click this button to add a folder to synchronize. - Haga clic en este botón para añadir una carpeta a sincronizar + + Enter the path to the local folder. + Ingrese la ruta de la carpeta local. - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Select the local folder + Selecciones la carpeta local + + + OCC::FolderWizardSelectiveSync - - Error while loading the list of folders from the server. - Error mientras se cargaba la lista de carpetas desde el servidor. + + Use virtual files instead of downloading content immediately + Verwenden Sie virtuelle Dateien, anstatt die Inhalte sofort herunterzuladen + + + OCC::GETFileJob - - Virtual file support is enabled. - El soporte al archivo virtual está activado. + + Connection Timeout + Tiempo de espera de conexión agotado - - Signed out - Cerrar sesión + + No E-Tag received from server, check Proxy/Gateway + No se ha recibido ninguna e-tag del servidor, revise el proxy/puerta de enlace - - Fetching folder list from server... - Obtención de lista de carpetas del servidor... + + We received a different E-Tag for resuming. Retrying next time. + Se ha recibido una e-tag distinta para reanudar. Se volverá a intentar. - - There are unresolved conflicts. Click for details. - Hay conflictos sin resolver. Haz click para mas detalles. + + We received an unexpected download Content-Length. + Recibimos una descarga inesperada dado su volumen. - - Checking for changes in remote '%1' - Buscando cambios en '%1' + + Server returned wrong content-range + El servidor ha devuelto un content-range erróneo + + + OCC::GraphApi::Space - - Checking for changes in local '%1' - Buscando cambios en local '%1' + + Personal + Personal - - Reconciling changes - Reconciliando cambios + + Shares + Compartidos + + + OCC::IgnoreListEditor - - , '%1' - Build a list of file names - , '%1' + + Ignored Files Editor + Editor de archivos ignorados - - '%1' - Argument is a file name - '%1' + + Files Ignored by Patterns + Archivos ignorados por patrones - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Sincronizando %1 + + Add + Añadir - - - , - , + + Pattern + Patrón - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - descargando: %1/s + + Allow Deletion + Permitir eliminación - - ↓ %1/s - ↓ %1/s + + Remove + Eliminar - - ↑ %1/s - ↑ %1/s + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Los archivos o carpetas que coincidan con un patrón no se sincronizarán. Los cambios tienen efecto la siguiente vez que se sincronizan las carpetas. + +Los objetos donde se permite el borrado serán borrados si impiden que un directorio sea eliminado. Esto es útil para los metadatos. - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - cargar %1/s + + Could not open file + No se ha podido abrir el archivo - - Local folder: %1 - + + Cannot write changes to '%1'. + No se pueden guardar cambios en '%1'. - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 de %4) + + Add Ignore Pattern + Añadir patrón para ignorar - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 restantes, %1 de %2, archivo %3 de %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 de %2, archivo %3 de %4 - - - - file %1 of %2 - archivo %1 de %2 - - - - Waiting... - Esperando... - - - - Waiting for %n other folder(s)... - Esperando por %n carpeta...Esperando por %n otras carpetas... - - - - Preparing to sync... - Preparando para sincronizar... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - El observador no recibió una notificación de prueba - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Este problema sucede habitualmente cuando las búsquedas de inotify están agotadas. Comprueba las FAQ para más detalles. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Añadir Conexión para el Directorio de Sincronización - - - - Add Sync Connection - Añadir Sincronización de Conexión - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Haga clic para seleccionar una carpeta local para sincronizar. - - - - Enter the path to the local folder. - Ingrese la ruta de la carpeta local. - - - - Select the source folder - Seleccione la carpeta de origen - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Crear carpeta remota - - - - Enter the name of the new folder to be created below '%1': - Introduzca más abajo el nombre de la nueva carpeta que crear '%1': - - - - Folder was successfully created on %1. - La carpeta fue creada con éxito en %1. - - - - Authentication failed accessing %1 - Falló la autenticación al acceder a %1 - - - - Failed to create the folder on %1. Please check manually. - Fallo al crear la carpeta %1. Por favor, revíselo manualmente. - - - - Failed to list a folder. Error: %1 - Falló al listar una carpeta. Error: %1 - - - - Choose this to sync the entire account - Elija esto para sincronizar la cuenta entera - - - - This folder is already being synced. - Este directorio ya se ha sincronizado. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Ya ha sincronizado <i>%1</i>, el cual es la carpeta de <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Ya estás sincronizando <i>%1</i>, el cual es un subdirectorio de <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Usar archivos virtuales en lugar de descargar inmediatamente el contenido %1 - - - - (experimental) - (experimental) - - - - Virtual files are not available for the selected folder - Archivos Virtuales no está disponible para la carpeta seleccionada - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Advertencia:</b> %1 - - - - <b>Warning:</b> - <b>Advertencia:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - No se ha recibido ninguna e-tag del servidor, revise el proxy/puerta de enlace - - - - We received a different E-Tag for resuming. Retrying next time. - Se ha recibido una e-tag distinta para reanudar. Se volverá a intentar. - - - - We received an unexpected download Content-Length. - Recibimos una descarga inesperada dado su volumen. - - - - Server returned wrong content-range - El servidor ha devuelto un content-range erróneo - - - - OCC::GETJob - - - Connection Timeout - Tiempo de espera de conexión agotado - - - - OCC::GeneralSettings - - - Form - Formulario - - - - General Settings - Ajustes generales - - - - Language - Idioma - - - - Sync hidden files - Sincronizar archivos ocultos - - - - Log Settings - Configuraciones del LOG - - - - Advanced - Approval of folder sync (Non virtual file mode only) - Avanzado - aprobación de la sincronización de carpetas (solo en modo de archivo no virtual) - - - - Network - Red - - - - Updates - Actualizaciones - - - - stable - estable - - - - beta - beta - - - - About - Acerca de - - - - Advanced - Avanzado - - - - Use &Monochrome Icons in the system tray - Usar Iconos &Monocromáticos en la bandeja del sistema - - - - Ask for confirmation before synchronizing folders larger than - Preguntar si se desea sincronizar carpetas mayores de - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Preguntar si se desea sincronizar carpetas de almacenamiento externo - - - - &Launch on System Startup - &Ejecutar al iniciar el sistema - - - - Show &Desktop Notifications - Mostrar &Notificaciones de Escritorio - - - - &Update Channel - &Actualizar canal - - - - Edit &Ignored Files - Editar archivos &ignorados - - - - Show sync folders in &Explorer's Navigation Pane - Mostrar carpetas a sincronizar en &Explorer's Navigation Panel - - - - S&how crash reporter - M&ostrar el informe de fallos - - - - &Restart && Update - &Reiniciar && Actualizar - - - - You cannot disable autostart because system-wide autostart is enabled. - No puede deshabilitar autoinicio porque el autoinicio del sistema principal está habilitado. - - - - Change update channel? - ¿Cambiar canal de actualizaciones? - - - - Warning - Alerta - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - El canal de actualizaciones determina que actualizaciones son necesarias y ofrecidas para su instalación en el cliente. El canal "Estable" contiene actualizaciones consideradas estables, mientras que el canal "beta" contiene parches y funcionalidad nueva que podria ser inestable. - -Nota: esto solo selecciona el canal deseado y no permite vueltas atras. Por lo que volver atras desde el canal beta al canal estable, puede ser que tarde un cierto tiempo dado que los parches y nueva funcionalidades, deben acabar siendo trasladadas al canal "estable" - - - - Change update channel - Cambiar canal de actualizaciones - - - - Cancel - Cancelar - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Por favor introduzca la contraseña de %1:<br><br>Usuario: %2<br>Cuenta: %3<br> - - - - Reading from keychain failed with error: '%1' - La lectura del llavero ha fallado y ha generado este error: '%1' - - - - Enter Password - Introduzca la contraseña - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Haga clic aquí</a> para solicitar una contraseña de aplicación desde la interfaz web. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor de archivos ignorados - - - - Files Ignored by Patterns - Archivos ignorados por patrones - - - - Add - Añadir - - - - Pattern - Patrón - - - - Allow Deletion - Permitir eliminación - - - - Remove - Eliminar - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Los archivos o carpetas que coincidan con un patrón no se sincronizarán. Los cambios tienen efecto la siguiente vez que se sincronizan las carpetas. - -Los objetos donde se permite el borrado serán borrados si impiden que un directorio sea eliminado. Esto es útil para los metadatos. - - - - Could not open file - No se ha podido abrir el archivo - - - - Cannot write changes to '%1'. - No se pueden guardar cambios en '%1'. - - - - Add Ignore Pattern - Añadir patrón para ignorar - - - - Add a new ignore pattern: - Añadir nuevo patrón para ignorar: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Esta entrada la proporciona el sistema en '%1' y no se puede modificar en esta vista. - - - - OCC::IssuesWidget - - - Form - Formulario - - - - List of issues - Lista de problemas - - - - Filter - Filtro - - - - There were too many issues. Not all will be visible here. - Hay demasiados problemas, No todos se visualizan aquí. - - - - There were conflicts. Check the documentation on how to resolve them. - Ha habido conflictos. <a href="%1">Comprueba la documentación sobre cómo resolverlos.</a> - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Ha habido conflictos. <a href="%1">Comprueba la documentación sobre cómo resolverlos.</a> - - - - Account - Cuenta - - - - OCC::LibCloudProvidersPrivate - - - Settings - Ajustes - - - - OCC::LogBrowser - - - Log Output - Salida del sistema - - - - Enable logging to temporary folder - Habilitar el registro en la carpeta temporal - - - - Delete logs older than %1 hours - Eliminar registros anteriores a %1 horas - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">El log contiene información sensible que no deberías hacer pública</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - El cliente puede escribir registros de depuración en una carpeta temporal. Estos registros son muy útiles para diagnosticar problemas. -Dado que los archivos de registro pueden aumentar de tamaño, el cliente iniciará uno nuevo para cada ejecución de sincronización y comprimirá los más antiguos. También se recomienda habilitar la eliminación de archivos de registro después de un par de horas para evitar consumir demasiado espacio en el disco. - - - - If enabled, logs will be written to: - Si se habilita, los logs serán escritos en: - - - - C:/log - C:/log - - - - Log Http traffic - Registro de tráfico HTTP - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Estas configuraciones persisten en los reinicios del cliente. -Tenga en cuenta que el uso de cualquier opción de línea de comando de registro anulará la configuración. - - - - Open folder - Abrir carpeta - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Nueva versión disponible - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Una nueva versión del programa cliente de %1 está disponible.</p><p><b>%2</b> está disponible para descargar. La versión instalada es la %3.</p> - - - - - Skip this version - Saltarse esta versión - - - - Skip this time - Omitir esta vez - - - - Get update - Actualizar - - - - Update Failed - Actualización falló - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Una nueva versión de %1 Client está disponible pero el proceso de actualización falló.</p><p><b>%2</b> ha sido descargado. La versión instalada es %3.</p> - - - - Ask again later - Preguntar de nuevo más tarde - - - - Restart and update - Reiniciar y actualizar - - - - Update manually - Actualizar manualmente - - - - OCC::NetworkSettings - - - Form - Formulario - - - - Proxy Settings - Configuración del proxy - - - - No Proxy - Sin proxy - - - - Use system proxy - Usar proxy del sistema - - - - Specify proxy manually as - Especificar manualmente un proxy como - - - - Host - Servidor - - - - : - : - - - - Proxy server requires authentication - El servidor proxy requiere autenticación - - - - Note: proxy settings have no effects for accounts on localhost - Nota: la configuración de proxy no tiene efecto para las cuentas en el servidor local - - - - Download Bandwidth - Ancho de banda de descarga - - - - - Limit to - Limitar a - - - - - KBytes/s - KBytes/s - - - - - No limit - Ilimitado - - - - - Limit to 3/4 of estimated bandwidth - Limitar a 3/4 de la banda ancha estimada - - - - Upload Bandwidth - Ancho de banda para subidas - - - - - Limit automatically - Limitar automáticamente - - - - Hostname of proxy server - Nombre del host para servidor proxy - - - - Username for proxy server - Nombre de usuario para el servidor proxy - - - - Password for proxy server - Contraseña para el servidor proxy - - - - HTTP(S) proxy - Proxy HTTP(S) - - - - SOCKS5 proxy - Proxy SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - Creado en %1 - - - - Closing in a few seconds... - Cerrando en pocos segundos... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 petición fallida en %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' seleccionado en %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Error devuelto desde el servidor: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Ha ocurrido un error accediendo al Token endpoint:: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - No puedo procesar el código JSON recibido del servidor: <br><em>%1</em> - - - - Unsupported token type: %1 - Tipo de token no soportado - - - - The reply from the server did not contain all expected fields -:%1 - La respuesta del servidor no contiene todos los campos esperados - - - - Unknown Error - Error desconocido - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>Error de acceso</h1><p>Fallo al tratar de obtener información del usuario</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Error de Login</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Error al actualizar el token: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - La respuesta del servidor no contiene todos los campos esperados -:%1 -Datos recibidos: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Usuario incorrecto</h1><p>Se ha intentado logar con el usuario: <em>%1</em>, pero debe logarse con el usuario: <em>%2</em>.<br>Por favor des-loguese de %3 en otra pestaña, entonces<a href='%4'>click aqui</a> y loguese como el usuario %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Nueva %1 actualización está lista - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Se instalará una nueva actualización de %1. Durante esta actualización puede que se soliciten privilegios adicionales. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - No se puede descargar la actualización. Por favor, haga clic <a href='%1'>aquí</a> para iniciar una descarga manual. - - - - Could not check for new updates. - No se puede comprobar si hay actualizaciones. - - - - Downloading %1. Please wait... - Descargando %1. Por favor espere... - - - - %1 available. Restart application to start the update. - %1 disponible. Reinicie la aplicación para iniciar la actualización. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Nueva %1 disponible. Por favor utilice la herramienta de actualización de su sistema para instalarlo. - - - - Checking update server... - Consultando al servidor de actualizaciones... - - - - Update status is unknown: Did not check for new updates. - Estado de actualización desconocido: no se buscaron nuevas actualizaciones. - - - - No updates available. Your installation is at the latest version. - No hay actualizaciones disponibles. Tiene la última versión. - - - - Update Check - Comprobar si hay actualizaciones - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Conectar a %1 - - - - Setup local folder options - Configurar opciones de carpeta local - - - - Connect... - Conectar... - - - - (experimental) - (experimental) - - - - Use &virtual files instead of downloading content immediately%1 - Utiliza &virtual files en lugar de descargar inmediatamente el contenido %1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Sincronizar el directorio '%1' - - - - Virtual files are not available for the selected folder - Archivos Virtuales no está disponible para la carpeta seleccionada - - - - Error - Error - - - - Local Sync Folder - Carpeta local de sincronización - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Email - - - - Connect to %1 - Conectar a %1 - - - - Enter user credentials - Introduzca las credenciales de usuario - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Conectar a %1 - - - - Login in your browser - Inicia sesión en tu navegador de internet - - - - Copy link to clipboard - Copiar enlace al portapapeles - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Conectar a %1 - - - - Setup %1 server - Configurar servidor %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Esta URL NO ES SEGURA; pues no está cifrada. -No se recomienda usarla. - - - - This url is secure. You can use it. - Esta url es segura. Puede utilizarla. - - - - &Next > - &Siguiente > - - - - Invalid URL - URL inválida. - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Fallo al conectar %1 a %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Tiempo de espera agotado mientras se intentaba conectar a %1 en %2 - - - - Invalid URL - URL inválida. - - - - Could not create local folder %1 - No se ha podido crear la carpeta local %1 - - - - Error: %1 - Error: %1 - - - - The folder creation resulted in HTTP error code %1 - La creación de la carpeta ha producido el código de error HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - ¡La creación de la carpeta remota ha fallado debido a que las credenciales proporcionadas son incorrectas!<br/>Por favor, vuelva atrás y compruebe sus credenciales</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Creación %1 de carpeta remota ha fallado con el error <tt>%2</tt>. - - - - Folder rename failed - Error al renombrar la carpeta - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - No se puede eliminar y respaldar la carpeta porque la misma o un fichero en ella está abierto por otro programa. Por favor, cierre la carpeta o el fichero y reintente, o cancele la instalación. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Asistente de conexión %1 - - - - Enable experimental feature? - ¿Activar característica experimental? - - - - Enable experimental placeholder mode - Activar modalidad experimental de marcadores de posición - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Cuando el modo "archivos virtuales" está habilitado, inicialmente no se descargarán archivos. En su lugar, se creará un pequeño archivo "%1" para cada archivo que exista en el servidor. Los contenidos se pueden descargar ejecutando estos archivos o utilizando su menú contextual. - -El modo de archivos virtuales se excluyen mutuamente bajo una sincronización selectiva. Las carpetas actualmente no seleccionadas se traducirán a carpetas solo en línea y se restablecerán sus configuraciones de sincronización selectiva. - -Cambiar a este modo abortará cualquier sincronización actualmente en ejecución. - -Este es un nuevo modo experimental. Si decide utilizarlo, reporte cualquier problema que surja. + + Add a new ignore pattern: + Añadir nuevo patrón para ignorar: - - Stay safe - Permanecer seguro + + This entry is provided by the system at '%1' and cannot be modified in this view. + Esta entrada la proporciona el sistema en '%1' y no se puede modificar en esta vista. - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - No se admiten enlaces simbólicos en la sincronización. - - - - File is listed on the ignore list. - El fichero está en la lista de ignorados - - - - File names ending with a period are not supported on this file system. - Los nombres de archivo que terminan con un punto no son compatibles con este sistema de archivos. - - - - File names containing the character '%1' are not supported on this file system. - Los nombres de archivo que contengan el caracter '%1' no son compatibles con este sistema de archivos. - - - - File name contains at least one invalid character - El nombre de archivo contiene al menos un carácter no válido - - - - The file name is a reserved name on this file system. - El nombre del archivo es una palabra reservada del sistema de archivos. - - - - Filename contains trailing spaces. - El nombre del archivo contiene espacios finales. - - - - Filename is too long. - El nombre del archivo es demasiado largo. - + OCC::LocalActivityWidget - - File/Folder is ignored because it's hidden. - Se ignoran los Archivos/Carpetas ocultos. + + Filter + Filtro - - Stat failed. - Stat ha fallado. + + Local activity table + Tabla de actividades locales - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Conflicto: versión del servidor descargada, la copia local ha sido renombrada pero no se ha podido subir. + + + Account + Cuenta - - The filename cannot be encoded on your file system. - El nombre de archivo no se puede codificar en su sistema de archivos. + + Filter menu + Menú de filtro - - The filename is blacklisted on the server. - El nombre del archivo está en la lista negra del servidor. + + Actions menu + Menú Acciones - - size - tamaño + + Sort ascending by %1 + Ordenar ascendente por %1 - - permissions - permisos + + Sort descending by %1 + Ordenar descendente por %1 - - etag - etag + + Retry sync + Reintentar sincronización + + + OCC::LogBrowser - - file id - id de archivo + + Log Output + Salida del sistema - - server reported no %1 - servidor informó no %1 + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>Die Protokolle enthalten sensible Informationen die Sie nicht öffentlich zugänglich machen sollten</b></span></p></body></html> - - Moved to invalid target, restoring - Movido a objetivo no válido, restaurando + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + Der Benutzer kann Debug-Protokolle in einen temporären Ordner schreiben. Diese Protokolle sind für die Diagnose von Problemen sehr hilfreich. +Da die Protokolldateien sehr groß werden können, legt der Benutzer für jeden Synchronisierungslauf eine neue Datei an und komprimiert ältere Dateien. - - Not allowed to remove, restoring - No está permitido borrar, restaurando. + + Enable logging to temporary folder + Habilitar el registro en la carpeta temporal - - Ignored because of the "choose what to sync" blacklist - Ignorado porque se encuentra en la lista negra de "elija qué va a sincronizar" + + If enabled, logs will be written to: + Si se habilita, los logs serán escritos en: - - Not allowed because you don't have permission to add subfolders to that folder - No permitido porque no tiene permiso para añadir subdirectorios a ese directorio + + C:/log + C:/log - - Not allowed because you don't have permission to add files in that folder - No permitido porque no tiene permiso para añadir archivos a ese directorio + + Log Http traffic + Registro de tráfico HTTP - - Not allowed to upload this file because it is read-only on the server, restoring - No está permitido subir este archivo porque es de solo lectura en el servidor, restaurando. + + Log files to keep: + Ficheros de log a mantener: - - Error while reading the database - Error durante la lectura de la base de datos + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Estas configuraciones persisten en los reinicios del cliente. +Tenga en cuenta que el uso de cualquier opción de línea de comando de registro anulará la configuración. - - Server replied with an error while reading directory '%1' : %2 - El servidor respondió con un error al leer el directorio '%1' : %2 + + Open folder + Abrir carpeta - OCC::PropagateDirectory - - - Error updating metadata: %1 - - + OCC::MainWindow - - %1 the folder is currently in use - + + Hide + Ocultar - - - OCC::PropagateDownloadFile - - Failed to free up space, the file %1 is currently in use - + + Main toolbar for the application + Barra de herramientas principal de la aplicación - - - File %1 can not be downloaded because of a local file name clash with %2! - + + Main toolbar + Barra de herramientas principal - - The download would reduce free local disk space below the limit - La descarga reducirá el espacio libre local por debajo del límite. + + + More + Más + + + OCC::MainWindowController - - Free space on disk is less than %1 - El espacio libre en el disco es inferior a %1 + + Add account... + Añadir cuenta... - - File was deleted from server - Se ha eliminado el archivo del servidor + + Settings... + Configuraciones... - - The file could not be downloaded completely. - No se ha podido descargar el archivo completamente. + + About... + Acerca de... - - The downloaded file is empty despite the server announced it should have been %1. - El archivo descargado está vacio aunque el servidor dice que deberia haber sido %1. + + Quit + Salir - - File %1 cannot be saved because of a local file name clash with %2! - + + + Errors: %1 + Errores: %1 - - Error updating metadata: %1 - + + Activity + Actividad - - - - - The file %1 is currently in use - + + Quit %1 + Salir de %1 - - - File has changed since discovery - El archivo ha cambiado desde que fue descubierto + + Are you sure you want to quit %1? + ¿Estás seguro de querer salir de %1? - OCC::PropagateItemJob + OCC::ModalWrapperWidget - - ; Restoration Failed: %1 - ; Falló la restauración: %1 + + Close + Cerrar - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - no pudo borrar el archivo %1, error: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - + OCC::Models - - Error updating metadata: %1 - + + %1 Filter: + %1 Filtro: - - The file %1 is currently in use - + + All + Todos - - could not create folder %1 - No se ha podido crear el directorio %1 + + Reset Filters + Restablecer filtros - - - OCC::PropagateLocalRemove - - Could not remove %1 because of a local file name clash with %2! - + + Status Filter: + Filtro de estado: - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - El archivo %1 no se puede renombrar a %2 por causa de un conflicto con el nombre de un archivo local - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - + OCC::NetworkSettings - - Error setting pin state - Error al establecer el estado del pin + + Form + Formulario - - - OCC::PropagateRemoteDelete - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - El código HTTP devuelto por el servidor es erróneo. Esperado 204, pero recibido "%1 %2". + + Pause synchronization when the Internet connection is metered + Pausiere die Synchronisation wenn die Internet Verbindung für den Durchsatz überwacht wird. - - - OCC::PropagateRemoteMkdir - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - El código HTTP devuelto por el servidor es erróneo. Esperado 201, pero recibido "%1 %2". + + Proxy Settings + Configuración del proxy - - Error writing metadata to the database: %1 - + + No Proxy + Sin proxy - - Setting file status failed due to file lock - + + Use system proxy + Usar proxy del sistema - - - OCC::PropagateRemoteMove - - Could not rename %1 to %2, error: %3 - No se pudo renombrar %1 de %2, error: %3 + + Host + Servidor - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - El código HTTP devuelto por el servidor es erróneo. Esperado 201, pero recibido "%1 %2". + + Proxy Hostname + Nombre de servidor del proxy - - Error updating metadata: %1 - + + : + : - - The file %1 is currently in use - + + Proxy Port Number + Número de puerto proxy - - Error writing metadata to the database - Error al escribir los metadatos en la base de datos + + Proxy server requires authentication + El servidor proxy requiere autenticación - - Error setting pin state - Error al establecer el estado del pin + + Note: proxy settings have no effects for accounts on localhost + Nota: la configuración de proxy no tiene efecto para las cuentas en el servidor local - - - OCC::PropagateUpdateMetaDataJob - - Could not update file : %1 - No se pudo actualizar el archivo: %1 + + Specify proxy manually + Especificar el proxy manualmente - - - The file %1 is currently in use - + + + Proxy type + Tipo de proxy - - - OCC::PropagateUploadFileCommon - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - El archivo %1 no puede ser subido porque ya existe otro con el mismo nombre. + + + Hostname of proxy server + Nombre del host para servidor proxy - - File Removed - Archivo eliminado + + + Username for proxy server + Nombre de usuario para el servidor proxy - - Local file changed during syncing. It will be resumed. - Archivo local cambió durante la sincronización. Será actualizado. + + + Password for proxy server + Contraseña para el servidor proxy - - Local file changed during sync. - Un archivo local fue modificado durante la sincronización. + + HTTP(S) proxy + Proxy HTTP(S) - - Error updating metadata: %1 - + + SOCKS5 proxy + Proxy SOCKS5 + + + OCC::NewAccountBuilder - - The file %1 is currently in use - + + no error details are available + No hay detalles del error disponibles - - - Upload of %1 exceeds the quota for the folder - La subida %1 excede la quota de la carpeta + + Unable to connect to server during account creation: %1. + No se puede conectar al servidor durante la creación de la cuenta: %1. - OCC::PropagateUploadFileNG + OCC::NewAccountWizard - - %1 the file is currently in use - + + Cancel Setup + Cancelar configuración - - The local file was removed during sync. - El archivo local ha sido eliminado durante la sincronización. + + Do you really want to cancel the account setup? + Desea realmente cancelar la configuración de la cuenta? + + + OCC::NewAccountWizardController - - Local file changed during sync. - Un archivo local fue modificado durante la sincronización. + + Add account… + Añadir cuenta... - - The server did ask for a removed legacy feature(polling) - + + Back + Volver - - Unexpected return code from server (%1) - Respuesta inesperada del servidor (%1) + + Advanced + Avanzado - - Missing File ID from server - Perdido ID del archivo en el servidor + + Sign in + Entrar - - Missing ETag from server - Perdido ETag del servidor + + Open sign in again + Iniciar sesión de nuevo - OCC::PropagateUploadFileTUS + OCC::NewVersionAvailableWidget - - %1 the file is currently in use - + + Skip this version + Saltarse esta versión - - The local file was removed during sync. - El archivo local ha sido eliminado durante la sincronización. + + Get update + Actualizar - - Local file changed during sync. - Un archivo local fue modificado durante la sincronización. + + Skip this time + Omitir esta vez - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - + OCC::OAuth - - The local file was removed during sync. - El archivo local ha sido eliminado durante la sincronización. + + Successfully signed in + Inicio de sesión correcto - - Local file changed during sync. - Un archivo local fue modificado durante la sincronización. + + Now, explore %1 on desktop. + Ahora, explore %1 en el escritorio. - - The server did not acknowledge the last chunk. (No e-tag was present) - El servidor no reconoció la última parte. (No había una e-tag presente) + + Error returned from the server: <em>%1</em> + Error devuelto desde el servidor: <em>%1</em> - - - OCC::ProtocolItemModel - - Time - Hora + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Ha ocurrido un error accediendo al Token endpoint:: <br><em>%1</em> - - File - Archivo + + Could not parse the JSON returned from the server: <br><em>%1</em> + No puedo procesar el código JSON recibido del servidor: <br><em>%1</em> - - Folder - Carpeta + + Unsupported token type: %1 + Tipo de token no soportado - - Issues - Problemas + + The reply from the server did not contain all expected fields +:%1 + La respuesta del servidor no contiene todos los campos esperados - - Action - Acción + + Unknown Error + Error desconocido - - Size - Tamaño + + + Login Error + Error al iniciar sesión - - Account - Cuenta + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + Ha iniciado sesión con el usuario 1%11, pero debe iniciar sesión con el usuario 2%22.3. Vuelva al cliente %3 y reinicie la autenticación. - - Status - Estado + + Wrong user + Usuario incorrecto - OCC::ProtocolWidget + OCC::OAuthPageController - - Form - Formulario + + Application Name Logo %1 + Logotipo de nombre de la aplicación %1 - - Local sync protocol - Protocolo de sincronización local + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Deje esta pantalla abierta. Aparecerá un mensaje de inicio de sesión en su navegador web para conectarle a la siguiente dirección. - - Filter - Filtro + + Sign in URL + URL de inicio de sesión - - Account - Cuenta + + To copy the sign in URL to the clipboard, use the copy button + Para copiar la URL de inicio de sesión al portapapeles, utilice el botón Copiar. - - Copy to clipboard - Copiar al portapapeles + + Copy URL to sign in + Copiar URL para iniciar sesión - - Show in file browser - + + Additional logo defined by the organization + Logotipo adicional definido por la organización. - - Show in web browser - + + URL copied + URL copiada - - Retry sync - + + Copy URL + Copiar URL - - - OCC::ProxyAuthDialog - - Proxy authentication required - Autenticación de proxy necesaria + + Unable to connect to the login service. If the problem continues, please contact support. + No se ha podido conectar al servicio de inicio de sesión. Si el problema persiste, póngase en contacto con el servicio de asistencia. - - Username: - Nombre de usuario: + + Please copy the authentication URL using the button above, and provide this information to support. + Copie la URL de autenticación utilizando el botón de arriba y facilite esta información al servicio de asistencia. - - Proxy: - Proxy: + + Failed to look up WebFinger instances: %1 + No se pudieron buscar instancias de WebFinger: %1 - - The proxy server needs a username and password. - El servidor de proxy necesita de un usuario y contraseña. + + You are already connected to an account with these credentials. + Ya está conectado a una cuenta con estas credenciales. - - Password: - Contraseña: + + Unable to retrieve capabilities from server. + No se pueden recuperar las capacidades del servidor. - - TextLabel - EtiquetaDeTexto + + The server is not supported by this client. + El servidor no es compatible con este cliente. - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Escoja qué sincronizar + + Error while trying to log in to OAuth2-enabled server. %1 + Error al intentar iniciar sesión en el servidor habilitado para OAuth2. %1 - - - OCC::SelectiveSyncWidget - - Loading ... - Cargando... + + Unable to open browser with provided URL. %1 + No se puede abrir el navegador con la URL proporcionada. %1 - - Deselect remote folders you do not wish to synchronize. - Deseleccione las carpetas remotas que no desea sincronizar. + + OAuth2 authentication requires a secured connection. %1 + La autenticación OAuth2 requiere una conexión segura. %1 - - Name - Nombre + + Authorization server unreachable. %1 + No se puede acceder al servidor de autorización. %1 + + + OCC::OCUpdater - - Size - Tamaño + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + No se puede descargar la actualización. Por favor, haga clic <a href='%1'>aquí</a> para iniciar una descarga manual. - - - No subfolders currently on the server. - No hay subcarpetas actualmente en el servidor. + + Could not check for new updates. + No se puede comprobar si hay actualizaciones. - - An error occurred while loading the list of sub folders. - Ha ocurrido un error mientras cargaba la lista de carpetas. + + Downloading %1. Please wait… + Descargando %1. Por favor, espere... - - - OCC::ServerNotificationHandler - - Dismiss - Descartar + + %1 installed successfully. Restart the application to finish installing the update. + %1 erfolgreich installiert. Starten Sie die Anwendung neu um die Installation des Updates abzuschließen. - - - OCC::SettingsDialog - - Settings - Ajustes + + %1 available. Restart application to start the update. + %1 disponible. Reinicie la aplicación para iniciar la actualización. - - Activity - Actividad + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Nueva versión %1 disponible. Por favor, haga clic <a href='%2'>aquí</a> para descargar la nueva AppImage manualmente. - - Add account - Agregar cuenta + + New %1 available. Please use the system's update tool to install it. + Nueva %1 disponible. Por favor utilice la herramienta de actualización de su sistema para instalarlo. - - - Quit %1 - Salir de %1 + + Checking update server… + Comprobando el servidor de actualizaciones... - - Are you sure you want to quit %1? - ¿Estás seguro de querer salir de %1? + + Update status is unknown: Did not check for new updates. + Estado de actualización desconocido: no se buscaron nuevas actualizaciones. - - Account - Cuenta + + No updates available. Your installation is at the latest version. + No hay actualizaciones disponibles. Tiene la última versión. + + + OCC::OwncloudPropagator - - MainWindow - MainWindow + + File %1 is currently in use + El archivo %1 está actualmente en uso - - Hide - Ocultar + + vfs instance is not available. + La instancia de VFS no está disponible. - OCC::ShareDialog + OCC::ProcessDirectoryJob + + + Symbolic links are not supported in syncing. + No se admiten enlaces simbólicos en la sincronización. + + + + File is listed on the ignore list. + El fichero está en la lista de ignorados + - - TextLabel - EtiquetaDeTexto + + File names ending with a period are not supported on this file system. + Los nombres de archivo que terminan con un punto no son compatibles con este sistema de archivos. - - share label - Compartido por + + File names containing the character '%1' are not supported on this file system. + Los nombres de archivo que contengan el caracter '%1' no son compatibles con este sistema de archivos. - - Dialog - Diálogo + + File name contains at least one invalid character + El nombre de archivo contiene al menos un carácter no válido - - ownCloud Path: - Ruta de OwnCloud: + + The file name is a reserved name on this file system. + El nombre del archivo es una palabra reservada del sistema de archivos. - - %1 Sharing - %1 Compartiendo + + Filename contains trailing spaces. + El nombre del archivo contiene espacios finales. - - %1 - %1 + + Filename is too long. + El nombre del archivo es demasiado largo. - - Folder: %2 - Carpeta: %2 + + File/Folder is ignored because it's hidden. + Se ignoran los Archivos/Carpetas ocultos. - - The server does not allow sharing - El servidor no permite compartir + + Stat failed. + Stat ha fallado. - - Retrieving maximum possible sharing permissions from server... - Descargando el maximo número de permisos compartidos del servidor... + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Conflicto: versión del servidor descargada, la copia local ha sido renombrada pero no se ha podido subir. - - The file can not be shared because it was shared without sharing permission. - El archivo no puede compartirse; ya que fue compartido sin permisos correspondientes. + + The filename cannot be encoded on your file system. + El nombre de archivo no se puede codificar en su sistema de archivos. - - Users and Groups - Usuarios y Grupos + + The filename is blacklisted on the server. + El nombre del archivo está en la lista negra del servidor. - - Public Links - Enlaces Publicos + + + + + the VFS instance is missing. Unable to continue. + falta la instancia de VFS. No se puede continuar. - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Compartir nuevo documento.odt + + server reported no %1 + servidor informó no %1 - - Upload only (File Drop) - Sólo subir (sin mostrar archivos) + + + account was deleted. Unable to continue + La cuenta ha sido eliminada. No se puede continuar. - - Receive files from others without revealing the contents of the folder. - Reciba archivos de otros sin revelar el contenido de la carpeta. + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: Ignoriert, da der Pfad deaktiviert wurde - - Download / View / Edit - Descargar / Ver / Editar + + Moved to invalid target, restoring + Movido a objetivo no válido, restaurando - - Name: - Nombre: + + Not allowed to remove, restoring + No está permitido borrar, restaurando. - - Enter name of new share... - Ingrese el nombre del nuevo recurso compartido... + + Error while reading the database. + Se ha producido un error al leer la base de datos. - - &Create - &Crear + + account was deleted. Unable to continue. + la cuenta se ha eliminado. No es posible continuar. - - TextLabel - Etiqueta de texto + + Not allowed because you don't have permission to add subfolders to that folder + No permitido porque no tiene permiso para añadir subdirectorios a ese directorio - - Set &password - Establecer &contraseña + + Error while doing a rename, unhandled status code: %1 + Fehler beim Umbenennen, unbehandelter Statuscode: %1 - - Set &expiration date - Establecer fecha de v&encimiento + + Not allowed because you don't have permission to add files in that folder + No permitido porque no tiene permiso para añadir archivos a ese directorio - - Set password - Establecer contraseña + + Not allowed to upload this file because it is read-only on the server, restoring + No está permitido subir este archivo porque es de solo lectura en el servidor, restaurando. - - Recipients can view or download contents. - Se puede ver o descargar el contenido + + This Space is currently unavailable + Este espacio no se actualmente disponible - - Download / View - Descargar / Ver + + Server replied with an error while reading directory '%1' : %2 + El servidor respondió con un error al leer el directorio '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - Se puede ver, descargar, editar, borrar y subir contenido. + + Error updating metadata: %1 + Error al actualizar los metadatos de: %1 - - Anyone with the link has access to the file/folder - Quienquiera que posea el vínculo tendrá acceso al archivo/carpeta + + %1 the folder is currently in use + La carpeta %1 está actualmente en uso + + + OCC::PropagateDownloadFile - - - P&assword protect - Protegido por contr&aseña + + Failed to free up space, the file %1 is currently in use + Ha habido un fallo al liberar espacio, el archivo %1 está actualmente en uso - - The file can not be shared because it was shared without sharing permission. - El archivo no puede compartirse; ya que fue compartido sin permisos correspondientes. + + + File %1 can not be downloaded because of a local file name clash with %2! + El archivo %1 no ha podido ser descargado porque su nombre coincide con el archivo local %2 - - Link shares have been disabled - Se ha deshabilitado la compartición de enlaces + + The download would reduce free local disk space below the limit + La descarga reducirá el espacio libre local por debajo del límite. - - Create public link share - Crear un enlace público compartido + + Free space on disk is less than %1 + El espacio libre en el disco es inferior a %1 - - - Delete - Eliminar + + File was deleted from server + Se ha eliminado el archivo del servidor - - Open link in browser - Abrir enlace en el explorador + + Broken webserver returned empty content length for non-empty file on resume + El servidor web roto devolvió la longitud del contenido vacío para un archivo no vacío en el resumen - - Copy link to clipboard - Copiar enlace al portapapeles + + The file could not be downloaded completely. + No se ha podido descargar el archivo completamente. - - Copy link to clipboard (direct download) - Copiar enlace al portapapeles (descarga directa) + + The downloaded file is empty despite the server announced it should have been %1. + El archivo descargado está vacio aunque el servidor dice que deberia haber sido %1. - - Send link by email - Enviar enlace por e-mail + + File %1 cannot be saved because of a local file name clash with %2! + El archivo %1 no ha podido ser guardado porque su nombre coincide con el archivo local %2 - - Send link by email (direct download) - Enviar enlace por e-mail (descarga directa) + + Error updating metadata: %1 + Error al actualizar los metadatos de: %1 - - Create new... - Crear nuevo... + + + + + The file %1 is currently in use + El archivo %1 está actualmente en uso - - I shared %1 with you - He compartido %1 contigo + + + File has changed since discovery + El archivo ha cambiado desde que fue descubierto + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - Confirmar eliminación de enlace de recurso compartido + + ; Restoration Failed: %1 + ; Falló la restauración: %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>¿Realmente desea borrar el enlace público compartido <i>%1</i>?</p><p>Nota: Esta acción no se puede deshacer</p> + + could not delete file %1, error: %2 + no se ha podido borrar el archivo %1, error: %2 - - Cancel - Cancelar + + Can not create local folder %1 because of a local file name clash with %2 + No se ha podido crear la carpeta local %1 porque su nombre coincide con el archivo local %2 - - - Public link - Enlace Publico + + Error updating metadata: %1 + Error al actualizar los metadatos de: %1 - - Delete link share - Borrar el enlace compartido + + The file %1 is currently in use + El archivo %1 está actualmente en uso - - - Please Set Password - Por favor establece una contraseña + + could not create folder %1 + No se ha podido crear el directorio %1 - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove + + + %1 failed with: %2 + %1 ha fallado con: %2 + - - Share NewDocument.odt - Compartir nuevo documento.odt + + Could not remove %1 because of a local file name clash with %2! + No se ha podido eliminar %1 porque su nombre coincide con el archivo local %2 - - Share with users or groups ... - Compartir con usuarios o grupos ... + + Could not move '%1' to the trash bin + Konnte '%1' nicht in den Papierkorb berschieben - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Puedes dirigir a los usuarios a este archivo o carpeta compartida <a href="private link menu"><span style=" text-decoration: underline"> enviándoles un enlace privado</span></a>.</p></body></html> + + + %1 the file is currently in use + El archivo %1 está actualmente en uso + + + OCC::PropagateLocalRename - - The item is not shared with any users or groups - El elemento no ha sido compartido con ningun grupo o persona. + + File %1 can not be renamed to %2 because of a local file name clash + El archivo %1 no se puede renombrar a %2 por causa de un conflicto con el nombre de un archivo local - - Open link in browser - Abrir enlace en el explorador + + Could not rename %1 to %2, the file is currently in use + No se ha podido renombrar %1 a %2, el archivo está actualmente en uso - - Copy link to clipboard - Copiar enlace al portapapeles + + Error updating metadata: %1 + Error al actualizar los metadatos de: %1 - - Send link by email - Enviar enlace por e-mail + + The file %1 is currently in use + El archivo %1 está actualmente en uso - - No results for '%1' - No hay resultados para '%1' + + Failed to rename file + Ha habido un fallo al renombrar el archivo - - I shared something with you - He compartido algo contigo + + Error setting pin state + Error al establecer el estado del pin - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Formulario + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + El código HTTP devuelto por el servidor es erróneo. Esperado 204, pero recibido "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - Etiqueta de texto + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + El código HTTP devuelto por el servidor es erróneo. Esperado 201, pero recibido "%1 %2". - - can edit - puede editar + + Error writing metadata to the database: %1 + Error al escribir los metadatos en la base de datos: %1 - - can share - puede compartir + + Setting file status failed due to file lock + Ha habido un fallo al establecer las propiedades del archivo debido a que está bloqueado + + + OCC::PropagateRemoteMove - - ... - ... + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + El código HTTP devuelto por el servidor es erróneo. Esperado 201, pero recibido "%1 %2". - - create - crear + + Error updating metadata: %1 + Error al actualizar los metadatos de: %1 - - change - Cambiar + + The file %1 is currently in use + El archivo %1 está actualmente en uso - - delete - eliminar + + Error writing metadata to the database + Error al escribir los metadatos en la base de datos - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Error setting pin state + Error al establecer el estado del pin - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Compartir con %1 + + Could not update file : %1 + No se pudo actualizar el archivo: %1 - - Context menu share - Compartido de menu contextual + + The file %1 is currently in use + El archivo %1 está actualmente en uso + + + OCC::PropagateUploadCommon - - Context menu share %1 - Menú contextual compartir %1 + + Local file changed during sync. It will be resumed. + El archivo local cambió durante la sincronización. Se reanudará. - - Confirm deletion - Confirmar eliminación + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + El archivo %1 no puede ser subido porque ya existe otro con el mismo nombre. - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - ¿Desea eliminar el directorio <i>%1</i> y todo su contenido permanentemente? + + + Upload of %1 exceeds the quota for the folder + La subida %1 excede la quota de la carpeta - - Do you want to delete the file <i>%1</i> permanently? - ¿Desea eliminar el archivo <i>%1</i> de manera permanente? + + + %1 the file is currently in use + El archivo %1 está actualmente en uso - - Select new location... - Seleccionar nueva ubicación... + + File Removed + Archivo eliminado - - Error - Error + + The server did not provide the file permissions + El servidor no proporcionó los permisos del archivo. - - Moving file failed: - -%1 - Error al mover el archivo: - -%1 + + Error updating metadata: %1 + Error al actualizar los metadatos de: %1 + + + OCC::PropagateUploadFile - - I shared something with you - He compartido algo contigo + + %1 the file is currently in use + El archivo %1 está actualmente en uso - - - Share... - Compartir... + + The server did ask for a removed legacy feature(polling) + El servidor solicitó una función heredada eliminada (encuesta) - - - Copy private link to clipboard - Copiar enlace privado al portapapeles + + The local file was removed during sync. + El archivo local ha sido eliminado durante la sincronización. - - Send private link by email... - Enviar enlace privado por e-mail... + + The server did not acknowledge the file upload. (No e-tag was present) + El servidor no reconoció la carga del archivo. (No había ninguna etiqueta electrónica presente). + + + OCC::PropagateUploadFileTUS - - Resharing this file is not allowed - No está permitido volver a compartir este archivo + + %1 the file is currently in use + El archivo %1 está actualmente en uso - - Resharing this folder is not allowed - Recompartir esta carpeta no está permitido + + Checksum computation failed + La suma de comprobación ha fallado - - Create and copy public link to clipboard - + + The local file was removed during sync. + El archivo local ha sido eliminado durante la sincronización. + + + OCC::ProtocolItemModel - - Copy public link to clipboard - Copiar enlace público al portapapeles + + Time + Hora - - Open in browser - Abrir en navegador + + File + Archivo - - Show file versions in browser - Mostrar las versiones del archivo en el navegador de internet + + Folder + Carpeta - - Rename... - Renombrar... + + Issues + Problemas - - Rename and upload... - Renombrar y subir... + + Action + Acción - - Move and rename... - Mover y renombrar... + + Size + Tamaño - - Move, rename and upload... - Mover, renombrar y subir... + + Account + Cuenta - - Delete local changes - Eliminar cambios locales + + Status + Estado + + + OCC::RequestAuthenticationController - - Move and upload... - Mover y subir... + + IdP is unreachable. Contact your system administrator or try again later. + No se puede acceder a IdP. Póngase en contacto con el administrador del sistema o vuelva a intentarlo más tarde. - - Delete - Eliminar + + Authentication failed. + La autenticación ha fallado. - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags - Error al recuperar las etiquetas + + Application Name Logo %1 + Logotipo de nombre de la aplicación %1 - - Backup of %1 started - Copia de seguridad de %1 iniciada + + Sign in required + Es necesario iniciar sesión - - Failed to find %1 - No se pudo encontrar %1 + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + Es posible que se haya desconectado automáticamente debido a un problema con el servidor o a un tiempo de espera agotado. Vuelva a iniciar sesión. - - Local path must be a an absolute path - La ruta local debe ser una ruta absoluta + + Sign in URL + URL de inicio de sesión - - Failed to create temporary database - Error al crear la base de datos temporal + + To copy the sign in URL to the clipboard, use the copy button + Para copiar la URL de inicio de sesión al portapapeles, utilice el botón Copiar. - - Backup of %1 succeeded - Copia de seguridad de %1 se completó exitosamente + + Copy URL to sign in + Copiar URL para iniciar sesión - - Failed to set success tag - Error al establecer la etiqueta de éxito + + Additional logo defined by the organization + Logotipo adicional definido por la organización. - - Failed to create backup: %1 - Error al crear la copia de seguridad: %1 + + Stay logged out + Permanezca deslogueado - - Failed to file id tags - No se pudieron presentar las etiquetas de identificación + + Sign in + Entrar - - Backup of %1 failed with: %2 - Copia de seguridad de %1 fallo con: %2 + + URL copied + URL copiada - - %1 backup - %1 copia de seguridad + + Copy URL + Copiar URL - OCC::SslButton + OCC::ResolveUrlAdapter - - <h3>Certificate Details</h3> - <h3>Detalles del certificado</h3> + + SSL failure when connecting to server at %1 + Error SSL al conectarse al servidor en %1 - - Common Name (CN): - Nombre común (NC): + + Could not detect compatible server at %1 + No se pudo detectar un servidor compatible en %1 - - Subject Alternative Names: - Nombres alternativos del sujeto: + + Resolved url is invalid %1 + La URL resuelta no es válida %1 - - Organization (O): - Organización (O): - - - - Organizational Unit (OU): - Unidad organizacional (UO): - - - - State/Province: - Estado/provincia: - - - - Country: - País: - - - - Serial: - Nº de serie: - - - - <h3>Issuer</h3> - <h3>Emisor</h3> + + Rejected redirect from %1 to %2 + Redirección rechazada de %1 a %2 + + + OCC::SelectiveSyncWidget - - Issuer: - Emisor: + + Loading … + Cargando... - - Issued on: - Emitido en: + + Deselect remote folders you do not wish to synchronize. + Deseleccione las carpetas remotas que no desea sincronizar. - - Expires on: - Expira en: + + Name + Nombre - - <h3>Fingerprints</h3> - <h3>Firma</h3> + + Size + Tamaño - - SHA-256: - SHA-256: + + + Currently there are no subfolders on the server. + Actualmente no hay subcarpetas en el servidor. - - SHA-1: - SHA-1: + + An error occurred while loading the list of subfolders. + Se ha producido un error al cargar la lista de subcarpetas. + + + OCC::SettingsView - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Notificación:</b> Este certificado fue aprobado manualmente</p> + + Form + Formulario - - %1 (self-signed) - %1 (autofirmado) + + General Settings + Ajustes generales - - %1 - %1 + + Use Monochrome Icons in the system tray + Use iconos monocromáticos en la bandeja de sistema - - This connection is encrypted using %1 bit %2. - - Esta conexión está cifrada con %1 bit %2. - + + Show Desktop Notifications + Mostrar notificaciones de escritorio - - Server version: %1 - Versión del servidor: %1 + + Start on Login + Comienza login - - No support for SSL session tickets/identifiers - No admite tickets de sesión/dentificadores SSL + + Language + Idioma - - Certificate information: - Información del certificado: + + Language selector + Selector de idioma - - The connection is not secure - La conexión no es segura + + Advanced + Avanzado - - This connection is NOT secure as it is not encrypted. - - Esta conexión NO es segura, pues no está cifrada. - + + Sync hidden files + Sincronizar archivos ocultos - - - OCC::SslErrorDialog - - Form - Formulario + + Show crash reporter + Mostrar el informe de fallos - - Trust this certificate anyway - Confiar en este certificado de todas maneras + + Move remotely deleted files to the local trash bin instead of deleting them + Entfernt gelöschte Dateien in den lokalen Papierkorb verschieben, anstatt sie zu löschen - - Untrusted Certificate - Certificado sin verificar + + Edit Ignored Files + Editar archivos ignorados - - Cannot connect securely to <i>%1</i>: - No se puede conectar de forma segura a <i>%1</i>: + + Log Settings + Configuraciones del LOG - - with Certificate %1 - con certificado %1 + + Network + Red - - - - &lt;not specified&gt; - &lt;no especificado&gt; + + Warning + Alerta - - - Organization: %1 - Organización: %1 + + Language changes require a restart of this application to take effect. + Cambiar el idioma requiere reiniciar esta aplicación para que tenga efecto. - - - Unit: %1 - Unidad: %1 + + You cannot disable autostart because system-wide autostart is enabled. + No puede deshabilitar autoinicio porque el autoinicio del sistema principal está habilitado. - - - Country: %1 - País: %1 + + (use default) + (usar por defecto) - - Fingerprint (MD5): <tt>%1</tt> - Huella (MD5): <tt>%1</tt> + + unknown (%1) + desconocido (%1) + + + OCC::SocketApi - - Fingerprint (SHA1): <tt>%1</tt> - Huella digital (SHA1): <tt>%1</tt> + + Share with %1 + parameter is the application name + Compartir con %1 - - Effective Date: %1 - Fecha de vigencia: %1 + + Confirm deletion + Confirmar eliminación - - Expiration Date: %1 - Fecha de caducidad: %1 + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + ¿Desea eliminar el directorio <i>%1</i> y todo su contenido permanentemente? - - Issuer: %1 - Emisor: %1 + + Do you want to delete the file <i>%1</i> permanently? + ¿Desea eliminar el archivo <i>%1</i> de manera permanente? - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - %1 (no realizado por el error anterior, intente de nuevo %2) + + Error + Error - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Solo %1 disponible, se necesita por lo menos %2 para comenzar + + Moving file failed: + +%1 + Error al mover el archivo: + +%1 - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Imposible abrir o crear la base de datos local de sincronización. Asegúrese de que tiene permisos de escritura en la carpeta de sincronización. + + I shared something with you + He compartido algo contigo - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Poco espacio libre en disco: La descarga lo reducirá por debajo del %1, debería abortar. + + Select new location… + Seleccione nueva ubicación... - - There is insufficient space available on the server for some uploads. - No hay suficiente espacio libre en el servidor para algunas subidas. + + + Share… + Compartir… - - Unresolved conflict. - Conflicto sin resolver. + + + Copy private link to clipboard + Copiar enlace privado al portapapeles - - Using virtual files with suffix, but suffix is not set - Usando archivos virtuales con sufijo, pero el mismo no está establecido + + Resharing this file is not allowed + No está permitido volver a compartir este archivo - - Unable to read the blacklist from the local database - No se pudo leer la lista de bloqueo de la base de datos local + + Resharing this folder is not allowed + Recompartir esta carpeta no está permitido - - Unable to read from the sync journal. - No se ha podido leer desde el registro de sincronización + + Open in %1 + Abierto en %1 - - Cannot open the sync journal - No es posible abrir el diario de sincronización + + Show file versions in web browser + Dateiversionen im Webbrowser anzeigen - - Aborted - Interrumpido + + Rename… + Cambiar nombre… - - - OCC::SyncJournalDb - - Failed to connect database. - + + Rename and upload… + Cambiar el nombre y subir... - - - OCC::Systray - - %1: %2 - %1: %2 + + + Move and rename… + Mover y cambiar de nombre... - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>Versión %1. Para más información visite<a href="%2">https://%3</a></p><p>Para saber más acerca de problemas y ayuda, por favor visite: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Por Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth y otros. </small></p><p>Derechos de autor ownCloud GmbH </p><p>Distribuido por %4 y licencia por GNU General Public License (GPL) Versión 2.0. <br/>%5 y el %5 logo son marcas registradas de %4 en los Estados Unidos, otras países, o ambos. </p><p><small>%6</small></p> + + Move, rename and upload… + Mover, renombrar y subir... - - - OCC::ValidateChecksumHeader - - The checksum header is malformed. - La cabecera del archivo de comprobación es incorrecto. + + Move and upload… + Mover y subir... - - The checksum header contained an unknown checksum type '%1' - La suma de comprobación de cabeceras contenía una suma de comprobación desconocida de tipo '%1' + + Delete local changes + Eliminar cambios locales - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - El archivo descargado no coincide con el archivo de comprobación, será reanudado. + + Delete + Eliminar - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - La función del sistema de archivos virtual no apoya una unidad como raíz de sincronización - + OCC::SpacesPage - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - La función del sistema de archivos virtual requiere un sistema de archivos NTFS, %1 usa %2 + + Select a folder to sync it to your computer. + Seleccione una carpeta para sincronizarla con su ordenador. - - The Virtual filesystem feature is not supported on network drives - La función de sistema de archivos virtual no es compatible con la unidad de red + + Select a Space to sync it to your computer. + Seleccione un espacio para sincronizarlo con su ordenador. - OCC::ownCloudGui - - - Please sign in - Por favor, regístrese - + OCC::SyncEngine - - Folder %1: %2 - Archivo %1: %2 + + %1 (skipped due to earlier error, trying again in %2) + %1 (no realizado por el error anterior, intente de nuevo %2) - - Unresolved %1 conflicts - + + Unable to sync: the local folder is missing. + No se puede sincronizar: falta la carpeta local. - - Ignored errors %1 - + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Solo %1 disponible, se necesita por lo menos %2 para comenzar - - There are no sync folders configured. - No hay carpetas configuradas para sincronizar. + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Imposible abrir o crear la base de datos local de sincronización. Asegúrese de que tiene permisos de escritura en la carpeta de sincronización. - - Open in browser - Abrir en navegador + + + + + Cannot open the sync journal. + No se puede abrir el diario de sincronización. - - - - Log in... - Inicio de sesión... + + Aborted due to %1 + Abgebrochen wegen %1 - - - - Log out - Cerrar sesión + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Poco espacio libre en disco: La descarga lo reducirá por debajo del %1, debería abortar. - - Recent Changes - Cambios recientes + + There is insufficient space available on the server for some uploads. + No hay suficiente espacio libre en el servidor para algunas subidas. - - Managed Folders: - Carpetas administradas: + + application exit + abort reason + Die Anwendung wurde beendet - - Open folder '%1' - Abrir carpeta '%1' + + Unable to read the blacklist from the local database + No se pudo leer la lista de bloqueo de la base de datos local - - Open %1 in browser - Abrir %1 en el navegador + + Unable to read from the sync journal. + No se ha podido leer desde el registro de sincronización + + + OCC::SyncErrorWidget - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - El servidor de la cuenta %1 ejecuta una versión %2 no compatible. Este cliente con versiones del servidor no compatibles es potencialmente peligroso. Continúa bajo tu responsabilidad. + + Issues table + Tabla de problemas - - Unknown status - Estado desconocido + + There were too many issues. Not all will be visible here. + Hay demasiados problemas, No todos se visualizan aquí. - - Settings... - Configuraciones... + + There were conflicts. Check the documentation on how to resolve them. + Ha habido conflictos. <a href="%1">Comprueba la documentación sobre cómo resolverlos.</a> - - Details... - Detalles... + + The file %1 was ignored as its name is reserved by %2 + El archivo %1 se ha ignorado, ya que su nombre está reservado por %2 - - Help - Ayuda + + Reset column sizes + Restablecer el tamaño de las columnas - - Quit %1 - Salir de %1 + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Ha habido conflictos. <a href="%1">Comprueba la documentación sobre cómo resolverlos.</a> - - Disconnected from %1 - Desconectado de %1 + + Filter menu + Menú de filtro - - Unsupported Server Version - Versión del servidor no soportada + + Account + Cuenta - - Disconnected - Desconectado + + Filter + Filtro + + + OCC::SyncJournalDb - - Disconnected from some accounts - Desconectado desde varias cuentas + + Failed to connect database. + Ha habido un fallo al conectar con la base de datos. + + + OCC::Theme - - Disconnected from accounts: - Desconectado de las cuentas: + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + 1Versión %1. Para obtener más información, visite 2https://%3213For. Para conocer los problemas conocidos y obtener ayuda, visite: 4https://central.owncloud.com4356By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,7Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller y otros.658Copyright ownCloud GmbH (Una empresa de Kiteworks)89Distribuido por %4 y con licencia GNU General Public License (GPL) Versión 2.0.10%5 y el logotipo %5 son marcas registradas de %4 en Estados Unidos, otros países o ambos.9<p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Account %1: %2 - Cuenta %1: %2 + + TLS Certificate Error + Error en certificado TLS - - Signed out - Cerrar sesión + + Do you want to trust this certificate anyway? + ¿Confía de todas formas en este certificado? - - Account synchronization is disabled - La sincronización está deshabilitada + + Cannot connect securely to %1 + No se puede conectar de manera segura a %1 - - Synchronization is paused - La sincronización se ha detenido + + &lt;not specified&gt; + &lt;no especificado&gt; - - Up to date (%1) - + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>con certificador %1</h3><div id="ccert"><p>Organización: %2<br/>Unidad: %3<br/>País: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/>Fecha efectiva %8Fecha de expiración: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organización: %11<br/>Unidad: %12<br/>Pais: %13</p></div></div> + + + OCC::TrayMenuController - - No sync folders configured - No hay carpetas sincronizado configuradas + + Unsupported Server Version + Versión del servidor no soportada - - Unpause all folders - Reanudar todas las carpetas + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + El servidor de la cuenta %1 ejecuta una versión %2 no compatible. Este cliente con versiones del servidor no compatibles es potencialmente peligroso. Continúa bajo tu responsabilidad. - - Pause all folders - Pausar todas las carpetas + + Show %1 + Mostrar %1 - - Unpause all synchronization - Reanudar toda la sincronización + + Help + Ayuda - - Unpause synchronization - Reanudar la sincronización + + About %1 + Acerca de %1 - - Pause all synchronization - Pausar todas las sincronizaciones + + Quit %1 + Salir de %1 + + + OCC::UpdateDownloadedWidget - - Pause synchronization - Pausar la sincronización + + Restart later + Späterer Neustart - - Log out of all accounts - Terminar sessión en todas las cuentas + + Restart now + Neustart sofort durchführen + + + OCC::UpdaterScheduler - - Log in to all accounts... - Iniciar sesión en todas las cuentas... + + Update available + Actualización disponible + + + OCC::UrlPageController - - New account... - Nueva cuenta ... + + Your web browser will be opened to complete sign in. + Se abrirá su navegador web para completar el inicio de sesión. - - About %1 - Acerca de %1 + + %1 logo + %1 logotipo - - No items synced recently - No se han sincronizado elementos recientemente + + Welcome to %1 + Bienvenido a %1 - - Checking for changes in remote '%1' - Buscando cambios en remoto: '%1' + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Introduce la dirección de su servidor para empezar. Se abrirá su navegador web para completar el inicio de sesión. - - Checking for changes in local '%1' - Buscando cambios en local '%1' + + Server address field + Campo de dirección del servidor - - Syncing %1 of %2 (%3 left) - Sincronizando %1 de %2 (quedan %3) + + Enter your server address here + Introduzca aquí la dirección de su servidor. - - Syncing %1 of %2 - Sincronizando %1 de %2 + + Additional logo defined by the organization + Logotipo adicional definido por la organización. - - Syncing %1 (%2 left) - Sincronizando %1 (quedan %2) + + Invalid server URL: %1 + URL del servidor no válida: %1 - - Syncing %1 - Sincronizando %1 + + Invalid URL scheme. Only https is accepted. + Esquema de URL no válido. Solo se acepta https. - - %1 (%2, %3) - %1 (%2, %3) + + The requested URL failed with query value: %1 + La URL solicitada ha fallado con el valor de consulta: %1 - OwncloudAdvancedSetupPage - - - Form - Formulario - + OCC::UserInfoAdapter - - - TextLabel - EtiquetaDeTexto + + Network request to collect user info failed + La solicitud de red para recopilar información del usuario ha fallado. - - Server - Servidor + + Unable to retrieve user info: invalid credentials. + No se puede recuperar la información del usuario: credenciales no válidas. - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Si esta casilla está marcada, el contenido existente en el directorio local será eliminado para comenzar una sincronización limpia desde el servidor.</p><p>No marque esta casilla si el contenido local debería subirse al directorio del servidor.</p></body></html> + + Unexpected network response when retrieving user info. + Respuesta inesperada de la red al recuperar la información del usuario. - - Ask for confirmation before synchroni&zing folders larger than - Preguntar si se desea sincroni&zar carpetas mayores de + + Retrieving user info failed with JSON error. + Error al recuperar la información del usuario con error JSON. + + + OCC::ValidateChecksumHeader - - S&ynchronize everything from server - Sincronizar todo desde el servidor + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + El archivo descargado no coincide con el archivo de comprobación, será reanudado. + + + OCC::Vfs - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + The Virtual filesystem feature does not support a drive as sync root + La función del sistema de archivos virtual no apoya una unidad como raíz de sincronización - - Ask for confirmation before synchronizing e&xternal storages - Preguntar si se desea sincronizar carpetas de almacenamiento e&xterno + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + La función del sistema de archivos virtual requiere un sistema de archivos NTFS, %1 usa %2 - - Choose what to sync - Elija qué sincronizar + + The Virtual filesystem feature is not supported on network drives + La función de sistema de archivos virtual no es compatible con la unidad de red + + + OCC::VfsWinPrivate - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Cuando se selecciona esta opción, el asistente se cerrará sin sincronizar nada. Puedes usar el botón &quot;Añadir conexión de sincronización de carpeta&quot; en las configuración de la cuenta para escoger qué pareja de carpetas local y remota quieres sincronizar</p></body></html> + + + %1 - %2 + %1 - %2 - - Manually create folder sync connections - Crear manualmente las conexiones de sincronización de carpeta + + Could not find StorageProviderState property %1 + No se pudo encontrar la propiedad StorageProviderState %1. - - &Local Folder - Carpeta &local + + Could not find StorageFolder for %1 error: %2 + No se pudo encontrar StorageFolder para %1 error: %2 - - pbSelectLocalFolder - pbSelectLocalFolder + + Could not retrieve StorageFolder for %1 %2 (0x%3) + No se ha podido recuperar StorageFolder para %1 %2 (0x%3) - - &Keep local data - &Mantener datos locales + + The folder is used by a different client: %1 + La carpeta la está utilizando otro cliente: %1 - - Start a &clean sync (Crreate a backup and erases the local folder!) - + + Error registering StorageProvider for %1: %2 (0x%3) + Error al registrar StorageProvider para %1: %2 (0x%3) - - - OwncloudHttpCredsPage - - Form - Formulario + + Unable to connect sync root: %1 error: %2 + No se ha podido conectar a la raíz de sincronización: %1 error: %2 - - &Username - &Nombre de usuario + + %1 is managed by another sync client + %1 es gestionado por otro cliente de sincronización. - - &Password - &Contraseña + + Downgrading the folder from %1 to %2 is not supported + No se admite la degradación de la carpeta de %1 a %2. - OwncloudOAuthCredsPage + OCC::WebFingerLookupAdapter - - Form - Formulario + + Failed to retrieve user info + Error al obtener información del usuario - - Please switch to your browser to proceed. - Por favor vaya a su navegador para proceder. + + WebFinger lookup returned no links + La búsqueda de WebFinger no ha devuelto ningún enlace. + + + OCC::WindowsUpdater - - An error occurred while connecting. Please try again. - Ha ocurrido un error al conectar. Por favor intentelo de nuevo. + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Una nueva versión del programa cliente de %1 está disponible.</p><p><b>%2</b> está disponible para descargar. La versión instalada es la %3.</p> - - Re-open Browser - Re-abrir navegador + + Skip this version + Saltarse esta versión - - - OwncloudSetupPage - - Form - Formulario + + Update Failed + La actualización ha fallado + + + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Una nueva versión de %1 Client está disponible pero el proceso de actualización falló.</p><p><b>%2</b> ha sido descargado. La versión instalada es %3.</p> - - Ser&ver Address - Dirección del ser&vidor + + Ask again later + Preguntar de nuevo más tarde - - https://... - https://... + + Restart and update + Reiniciar y actualizar - - Error Label - Etiqueta de error + + Update manually + Actualizar manualmente QObject - + in the future en el futuro - + %n day(s) ago - Hace %n día(s)Hace %n día(s) + Hace %n díahace %n díashace %n días - + %n hour(s) ago - Hace %n hora(s)Hace %n hora(s) + Hace %n horaHace %n horasHace %n horas - + now ahora - - Less than a minute ago - Hace menos de un minuto + + less than a minute ago + weniger als eine Minute - + %n minute(s) ago - Hace %n minutos(s)Hace %n minutos(s) - - - - Some time ago - Hace algún tiempo + Hace %n minutoHace %n minutosHace %n minutos - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 + + SpacesBrowser + + + Form + Formulario + + + + SpacesPage + + + Spaces list + Lista de espacios + + + + SpacesView + + + Spaces + Espacios + + SyncFileItem::Status - + Undefined - + No definido - + Fatal Error Error fatal. - - + + Error Error - + Info Información - + Success Éxito - + Conflict Conflicto - + File Ignored - + Archivo ignorado - + Restored - + Restaurado - + Blacklisted - + En la lista negra - + Excluded - - - - - Utility - - - %L1 GB - %L1 GB + Excluido - - %L1 MB - %L1 MB + + Message + Mensaje - - %L1 KB - %L1 KB + + Filename Reserved + Reservierter Dateiname + + + UpdateDownloadedWidget - - %L1 B - %L1 B + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Neustart erforderlich</span></p></body></html> + + + Utility - + %n year(s) - %n año%n año(s) + %n año%n years%n años - + %n month(s) - %n Mes%n mes(es) + %n month%n months%n months - + %n day(s) - %n dia%n día(s) + %n día%n días%n días - + %n hour(s) - %n hora%n hora(s) + %n hora%n horas%n horas - + %n minute(s) - %n minuto%n minuto(s) + %n minuto%n minutos%n minutos - + %n second(s) - %n segundo%n segundo(s) + %n second%n seconds%n seconds - + %1 %2 %1 %2 + + + Shutting down %1 + Apagando %1 + - main.cpp + account loading - - System Tray not available - La bandeja del sistema no está disponible + + Error accessing the configuration file + Error al acceder al archivo de configuración - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - % 1 requiere una bandeja del sistema de trabajo. Si está ejecutando XFCE, por favor siga <a href="http://docs.xfce.org/xfce/xfce4-panel/systray"> estas instrucciones</a>. De lo contrario, instale una aplicación de la bandeja del sistema, como 'trayer', y vuelva a intentarlo. + + There was an error while accessing the configuration file at %1. + Ha ocurrido un error al acceder al archivo de configuración %1. ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Librerias Qt %3, %4%7Usando el conector de ficheros virtuales: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - %1 (construido contra Qt %1) + + %1 (Built against Qt %2) + %1 (Creado a partir de Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Siempre disponible en local + + + + Currently available locally + Actualmente disponible localmente + + + + Some available online only + Actualmente algunos solo están disponibles en línea + + + + Available online only + Solo disponible en línea + + progress @@ -4722,30 +3563,30 @@ Este es un nuevo modo experimental. Si decide utilizarlo, reporte cualquier prob Uploaded Subido - - - Server version downloaded, copied changed local file into conflict file - Versión del servidor descargada, se ha copiado el fichero local cambiado al fichero en conflicto - Deleted Eliminado - - Moved to %1 - Movido a %1 - - - + Ignored Ignorado - - Filesystem access error - Error de acceso al sistema de archivos + + Server version downloaded, local copy was backed up as conflict file + Serverversion heruntergeladen, die lokale Kopie wurde als Konfliktdatei gesichert + + + + Renamed %1 to %2 + Cambio de nombre de %1 a %2 + + + + Moved %1 to %2 + Movido de %1 a %2 @@ -4758,153 +3599,86 @@ Este es un nuevo modo experimental. Si decide utilizarlo, reporte cualquier prob Actualizados metadatos locales - - + + Unknown Desconocido - + downloading descargando - + uploading subiendo - + deleting eliminando - + moving moviendo - + ignoring ignorando - - + error error - + updating local metadata actualizando metadatos locales - - theme - - - Status undefined - Estado indefinido - - - - Waiting to start sync - Esperando para comenzar la sincronización - - - - Sync is running - Sincronizado en proceso - - - - Sync Success - Sincronizado con éxito - - - - Sync Success, some files were ignored. - Sincronizado con éxito, algunos archivos han sido ignorados. - - - - Sync Error - Error de sincronización. - - - - Setup Error - Error en la instalación - - - - Preparing to sync - Preparando para sincronizar - - - - Aborting... - Cancelando... - - - - Sync is paused - La sincronización está en pausa. - - utility - + Could not open browser - No puedo abrir el explorador + No se ha podido abrir el navegador - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? Ocurrió un error al lanzar el navegador para ir a la URL: %1 , ¿podría ser que no haya ningún navegador configurado como predeterminado? - + Could not open email client - No se pudo abrir el cliente de correo + No se ha podido abrir el cliente de correo - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Ocurrió un error al lanzar el cliente de correo electrónico para crear un nuevo mensaje. ¿Podría ser que no haya ningún cliente de correo electrónico configurado como predeterminado? - - - - Always available locally - Siempre disponible en local + Ha ocurrido un error al lanzar el cliente de correo electrónico para crear un nuevo mensaje. ¿Podría ser que no haya ningún cliente de correo electrónico configurado como predeterminado? - - Some available online only - Actualmente algunos solo están disponibles en línea - - - - - Available online only - Solo disponible en línea - - - - Currently available locally - Actualmente disponible localmente - - - + Make always available locally Hacerlo siempre disponible localmente - + Free up local space Liberar espacio local + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Einige Einstellungen wurden in neueren Versionen dieses Clients konfiguriert und verwenden Funktionen, die in dieser Version nicht verfügbar sind + + \ No newline at end of file diff --git a/translations/client_es_AR.ts b/translations/client_es_AR.ts deleted file mode 100644 index f58c0fbc78c..00000000000 --- a/translations/client_es_AR.ts +++ /dev/null @@ -1,4417 +0,0 @@ - - - FileSystem - - - Could not make directories in trash - Imposible colocar directorios en la papelera - - - - - Could not move '%1' to '%2' - Imposible mover '%1' a '%2' - - - - Moving to the trash is not implemented on this platform - Mover hacia la papelera no está implementado en esta plataforma - - - - FolderWizardSourcePage - - - Form - Formulario - - - - Pick a local folder on your computer to sync - Seleccioná un directorio local en tu equipo para sincronizar - - - - &Choose... - &Choose... - - - - FolderWizardTargetPage - - - Form - Formulario - - - - Select a remote destination folder - Selecciona una carpeta de destino remota - - - - Create Folder - Crear Carpeta - - - - Refresh - Actualizar - - - - Folders - Carpetas - - - - TextLabel - Texto de la etiqueta - - - - NotificationWidget - - - Form - Formulario - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - EtiquetaDeTexto - - - - OCC::AbstractNetworkJob - - - Connection timed out - Tiempo de conexión agotado - - - - Unknown error: network reply was deleted - Error desconocido: la respuesta de la red fue eliminada - - - - Server replied "%1 %2" to "%3 %4" - - - - - OCC::AccountSettings - - - Form - Formulario - - - - ... - ... - - - - Storage space: ... - Espacio de almacenamiento: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Deseleccionar Carpetas que serán <b>eliminadas</b> de su sistema de archivos local y no será sincronizado más en este equipo - - - - Synchronize all - Sincronizar todos - - - - Synchronize none - Sincronizar ninguno - - - - Apply manual changes - Aplicar cambios manuales - - - - Apply - Aplicar - - - - - - Cancel - Cancelar - - - - Connected with <server> as <user> - Conectado con <server> como <user> - - - - No account configured. - No hay cuenta configurada. - - - - Add new - Agregar nuevo - - - - Remove - Borrar - - - - Account - Cuenta - - - - Choose what to sync - Elegir que sincronizar - - - - Force sync now - Forzar sincronización ahora - - - - Restart sync - Reinicia sincronización - - - - Remove folder sync connection - Eliminar conexión de sincronización de carpeta - - - - Create virtual files for new files (Experimental) - - - - - Folder creation failed - Falló la creacion de la carpeta - - - - <p>Could not create local folder <i>%1</i>. - <p>No pudó ser creada la carpeta local <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Confirmar eliminación de conexión de sincronización de carpeta - - - - Remove Folder Sync Connection - Eliminar Conexión de Sincronización de Carpeta - - - - Sync Running - Sincronización en curso - - - - The syncing operation is running.<br/>Do you want to terminate it? - La sincronización está en curso.<br/>¿Querés interrumpirla? - - - - %1 in use - %1 en uso - - - - %1 as <i>%2</i> - %1 de <i>%2</i> - - - - Connected to %1. - Conectado a %1 - - - - Server %1 is temporarily unavailable. - El servidor %1 esta temporalmente sin conexión - - - - Server %1 is currently in maintenance mode. - Servidor %1 actualmente en modo mantenimiento. - - - - Signed out from %1. - Registrado desde %1. - - - - Obtaining authorization from the browser. <a href='%1'>Click here</a> to re-open the browser. - Obteniendo autorización del navegador. <a href='%1'>Click aquí</a> para volver a abrir el navegador. - - - - Connecting to %1... - Conectando a %1... - - - - No connection to %1 at %2. - Sin conexión a %1 desde %2. - - - - Log in - Iniciar sesión - - - - There are folders that were not synchronized because they are too big: - Hay carpetas que no fueron sincronizadas debido a que son demasiado grandes: - - - - There are folders that were not synchronized because they are external storages: - - - - - There are folders that were not synchronized because they are too big or external storages: - - - - - Confirm Account Removal - Confirmar la eliminación de la cuenta - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - - - - Remove connection - Eliminar conexión - - - - - Open folder - Abrir carpeta - - - - - Log out - Cerrar la sesión - - - - Resume sync - Reanudar sincronización - - - - Pause sync - Pausar sincronización - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Realmente quieres detener la sincronización de la carpeta <i>%1</i>?</p><p><b>Nota:</b> Esto <b>no</b> eliminara ningun archivo.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - - - - - %1 of %2 in use - %1 de %2 en uso - - - - Currently there is no storage usage information available. - Actualmente no hay información disponible acerca del uso del almacenamiento. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - No %1 connection configured. - No hay ninguna conexión de %1 configurada. - - - - OCC::AccountState - - - Signed out - Desautentificado - - - - Disconnected - Desconectado - - - - Connected - Conectado - - - - Service unavailable - Servicio no disponible - - - - Maintenance mode - Modo mantenimiento - - - - Network error - Error de red - - - - Configuration error - Error de configuración - - - - Asking Credentials - Pidiendo Credenciales - - - - Unknown account state - Estado de la cuenta desconocido - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 en %2 - - - - %1 on %2 (disconnected) - %1 en %2 (desconectado) - - - - OCC::ActivitySettings - - - - Server Activity - Actividad de Servidor - - - - Sync Protocol - Protocolo de Sincronización - - - - Not Synced - No Sincronizado - - - - Not Synced (%1) - %1 is the number of not synced files. - No Sincronizado (%1) - - - - The server activity list has been copied to the clipboard. - - - - - The sync activity list has been copied to the clipboard. - - - - - The list of unsynced items has been copied to the clipboard. - - - - - Copied to clipboard - Copiado al portapapeles - - - - OCC::ActivityWidget - - - Form - Formulario - - - - - - TextLabel - EtiquetaDeTexto - - - - Server Activities - Actividades de Servidor - - - - Copy - Copiar - - - - Copy the activity list to the clipboard. - Copiar la lista de actividades al portapapeles. - - - - Action Required: Notifications - Acción Requerida: Notificaciones. - - - - <br/>Account %1 does not have activities enabled. - - - - - You received %n new notification(s) from %2. - - - - - You received %n new notification(s) from %1 and %2. - - - - - You received new notifications from %1, %2 and other accounts. - - - - - %1 Notifications - Action Required - - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Autentificación de certificado de cliente SSL - - - - This server probably requires a SSL client certificate. - Este servidor probablemente requiere un certificado de cliente SSL. - - - - Certificate & Key (pkcs12) : - - - - - Browse... - Examinar... - - - - Certificate password : - Contraseña del certificado: - - - - Select a certificate - Seleccionar certificado - - - - Certificate files (*.p12 *.pfx) - Archivo de certificado (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Salir - - - - Continue - Continuar - - - - Error accessing the configuration file - - - - - There was an error while accessing the configuration file at %1. - - - - - Quit ownCloud - Salir de ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - Autentificación requerida - - - - Enter username and password for '%1' at %2. - Ingrese usuario y contraseña para '%1' at %2. - - - - &User: - &User: - - - - &Password: - &Contraseña - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Error escribiendo metadatos a la base de datos - - - - OCC::ConnectionValidator - - - No ownCloud account configured - No hay una cuenta ownCloud configurada. - - - - The configured server for this client is too old - La configuración del servidor al cliente es obsoleta - - - - Please update to the latest server and restart the client. - Por favor actualice a la última versión del servidor y reinicie el cliente. - - - - Authentication error: Either username or password are wrong. - Error de autentificación: el usuario o contraseña es incorrecta. - - - - timeout - agotado - - - - The provided credentials are not correct - Las credenciales otorgadas no son correctas - - - - OCC::DiscoveryMainThread - - - Aborted by the user - Interrumpido por el usuario - - - - OCC::DiscoverySingleDirectoryJob - - - The server file discovery reply is missing data. - - - - - OCC::Folder - - - Local folder %1 does not exist. - El directorio local %1 no existe. - - - - %1 should be a folder but is not. - %1 debé ser una carpeta pero no lo es. - - - - %1 is not readable. - No se puede leer %1. - - - - %1 has been removed. - %1 names a file. - %1 ha sido eliminado. - - - - %1 has been downloaded. - %1 names a file. - %1 ha sido descargado. - - - - %1 has been updated. - %1 names a file. - %1 ha sido actualizado - - - - %1 has been renamed to %2. - %1 and %2 name files. - - - - - %1 has been moved to %2. - - - - - %1 and %n other file(s) have been removed. - - - - - %1 and %n other file(s) have been downloaded. - - - - - %1 and %n other file(s) have been updated. - - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - - - - - %1 has been moved to %2 and %n other file(s) have been moved. - - - - - %1 has and %n other file(s) have sync conflicts. - - - - - %1 has a sync conflict. Please check the conflict file! - - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - - - - - %1 could not be synced due to an error. See the log for details. - - - - - Sync Activity - Actividad de Sync - - - - Could not read system exclude file - - - - - A new folder larger than %1 MB has been added: %2. - - - - - - A folder from an external storage has been added. - - - - - - Please go in the settings to select it if you wish to download it. - - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - ¿Borrar todos los archivos? - - - - Remove all files - Borrar todos los archivos - - - - Keep files - Conservar archivos - - - - This sync would reset the files to an earlier time in the sync folder '%1'. -This might be because a backup was restored on the server. -Continuing the sync as normal will cause all your files to be overwritten by an older file in an earlier state. Do you want to keep your local most recent files as conflict files? - - - - - Backup detected - - - - - Normal Synchronisation - Sincronizacón Normal. - - - - Keep Local Files as Conflict - Mantener Archivos Locales como Conflicto - - - - OCC::FolderMan - - - Could not reset folder state - No se pudo - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Una antigua sincronización con journaling '%1' fue encontrada, pero no se pudo eliminar. Por favor, asegurate que ninguna aplicación la está utilizando. - - - - (backup) - (Copia de seguridad) - - - - (backup %1) - (Copia de seguridad %1) - - - - Undefined State. - Estado no definido. - - - - Waiting to start syncing. - Esperando para comenzar sincronización. - - - - Preparing for sync. - Preparando la sincronización. - - - - Sync is running. - Sincronización en funcionamiento. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - La última sincronización fue exitosa. - - - - Setup Error. - Error de configuración. - - - - User Abort. - Interrumpir. - - - - Sync is paused. - La sincronización está en pausa. - - - - %1 (Sync is paused) - %1 (Sincronización en pausa) - - - - No valid folder selected! - Carpeta válida no seleccionada! - - - - The selected path is not a folder! - - - - - You have no permission to write to the selected folder! - ¡No tenés permisos para escribir el directorio seleccionado! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - - - - - Synchronizing with local folder - - - - - File - Archivo - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - - - - - Click this button to add a folder to synchronize. - - - - - - %1 (%2) - Example text: "File.txt (23KB)" - - - - - Error while loading the list of folders from the server. - - - - - Signed out - Desautentificado - - - - Fetching folder list from server... - - - - - There are unresolved conflicts. Click for details. - - - - - New files are being created as virtual files. - - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Sincronizando %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - descargado %1/s - - - - u2193 %1/s - u2193 %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - Cargado %1/s - - - - u2191 %1/s - u2191 %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 of %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 izquierda, %1 de %2, archivo %3 de %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 de %2, archivo %3 de %4 - - - - file %1 of %2 - Archivo %1 de %2 - - - - Waiting... - Esperando... - - - - Waiting for %n other folder(s)... - - - - - Preparing to sync... - Preparando para sincronizar... - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - - - - - Add Sync Connection - - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Clic para seleccionar un directorio local a sincornizar - - - - Enter the path to the local folder. - Ingrese el path al directorio local. - - - - Select the source folder - Seleccioná el directorio origen - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Crear carpeta remota - - - - Enter the name of the new folder to be created below '%1': - - - - - Folder was successfully created on %1. - El directorio fue creado con éxito en %1. - - - - Authentication failed accessing %1 - Autenticación fallida accediendo a %1. - - - - Failed to create the folder on %1. Please check manually. - Fallo al crear el directorio en %1. Por favor chequee manualmente. - - - - Failed to list a folder. Error: %1 - Fallo al listar carpetas. Error: %1 - - - - Choose this to sync the entire account - Seleccioná acá para sincronizar la cuenta completa - - - - This folder is already being synced. - Este folder ya está siendo sincronizado. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Ya estás sincronizando <i>%1</i>, el cual es el directorio de <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately (experimental) - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - - - - - <b>Warning:</b> - - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - - - - - We received a different E-Tag for resuming. Retrying next time. - - - - - Server returned wrong content-range - - - - - Connection Timeout - - - - - OCC::GeneralSettings - - - Form - Formulario - - - - General Settings - - - - - For System Tray - - - - - stable - - - - - beta - - - - - Advanced - Avanzado - - - - Ask for confirmation before synchronizing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - - - - - Ask for confirmation before synchronizing external storages - - - - - &Launch on System Startup - - - - - Show &Desktop Notifications - - - - - Use &Monochrome Icons - - - - - Version - Versión - - - - &Update Channel - - - - - • - - - - - Edit &Ignored Files - - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - - - - - &Restart && Update - - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Cancelar - - - - OCC::GetOrCreatePublicLinkShare - - - Sharing error - - - - - Could not retrieve or create the public link share. Error: - -%1 - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - Ingresar contraseña - - - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor de archivos ignorados - - - - Global Ignore Settings - - - - - Sync hidden files - - - - - Files Ignored by Patterns - - - - - Add - Agregar - - - - Pattern - - - - - Allow Deletion - - - - - Remove - Borrar - - - - Files or folders matching a pattern will not be synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - No se pudo abrir el archivo - - - - Cannot write changes to '%1'. - No se pueden guardar cambios en '%1'. - - - - Add Ignore Pattern - Agregar patrón a ignorar - - - - Add a new ignore pattern: - Añadir nuevo patrón a ignorar: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Esta entrada es provista por el sistema en '%1' y no puede ser modificada en esta vista. - - - - OCC::IssuesWidget - - - Form - Formulario - - - - List of issues - - - - - Account - Cuenta - - - - - <no filter> - - - - - - Folder - Carpeta - - - - Show warnings - - - - - Show ignored files - - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - - - - - Copy - Copiar - - - - Time - Hora - - - - File - Archivo - - - - Issue - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LogBrowser - - - Log Output - Contenido del log - - - - &Search: - - - - - &Find - &Encontrar - - - - &Capture debug messages - - - - - Permanently save logs - - - - - When this option is enabled and no other logging is configured, logs will be written to a temporary folder and expire after a few hours. This setting persists across client restarts. - -Logs will be written to %1 - - - - - Clear - Borrar - - - - Clear the log display. - Limpiar la pantalla de registros. - - - - S&ave - G&uardar - - - - Save the log file to a file on disk for debugging. - Guardar el log en un archivo en disco para depuración. - - - - Save log file - Guardar archivo de log - - - - Error - Error - - - - Could not write to log file %1 - - - - - OCC::Logger - - - Error - Error - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>El archivo '%1'<br/>no puede ser abierto para escritura.<br/><br/>¡El archivo de log <b>no</b> puede ser guardado!</nobr> - - - - OCC::NSISUpdater - - - New Version Available - Nueva versión disponible - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - - - - - Skip this version - - - - - Skip this time - Saltear esta vez - - - - Get update - Obtener actualización - - - - OCC::NetworkSettings - - - Form - Formulario - - - - Proxy Settings - Configuración del proxy - - - - No Proxy - Sin Proxy - - - - Use system proxy - Usar proxy del sistema - - - - Specify proxy manually as - Especificar manualmente un proxy como - - - - Host - Servidor - - - - : - : - - - - Proxy server requires authentication - El servidor proxy requiere autenticación - - - - Download Bandwidth - Ancho de banda de descarga - - - - - Limit to - Limitar a - - - - - KBytes/s - KBytes/s - - - - - No limit - Ilimitado - - - - - Limit to 3/4 of estimated bandwidth - - - - - Upload Bandwidth - Acho de banda de subida - - - - - Limit automatically - Limitar automáticamente - - - - Hostname of proxy server - Nombre del host para servidor proxy - - - - Username for proxy server - Nombre de usuario para el servidor proxy - - - - Password for proxy server - Contraseña para el servidor proxy - - - - HTTP(S) proxy - Proxy HTTP(S) - - - - SOCKS5 proxy - Proxy SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - - - - - Closing in a few seconds... - - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - The reply from the server did not contain all expected fields - - - - - <h1>Login Error</h1><p>%1</p> - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - - - - - Downloading version %1. Please wait... - - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - - - - - Could not check for new updates. - - - - - %1 version %2 available. Restart application to start the update. - - - - - New %1 version %2 available. Please use the system's update tool to install it. - - - - - Checking update server... - - - - - Update status is unknown: Did not check for new updates. - - - - - No updates available. Your installation is at the latest version. - - - - - Update Check - - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Conectar a %1 - - - - Setup local folder options - Configurar opciones de directorio local - - - - Connect... - Conectar... - - - - %1 folder '%2' is synced to local folder '%3' - El directorio %1 '%2' está sincronizado con el directorio local '%3' - - - - Sync the folder '%1' - - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - - - - - Local Sync Folder - Directorio local de sincronización - - - - - (%1) - - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Fallo de Conexión - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - - - - - Select a different URL - - - - - Retry unencrypted over HTTP (insecure) - - - - - Configure client-side TLS certificate - - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - - - - - OCC::OwncloudHttpCredsPage - - - &Email - - - - - Connect to %1 - Conectar a %1 - - - - Enter user credentials - Ingresá las credenciales de usuario - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Conectar a %1 - - - - Login in your browser - - - - - Copy link to clipboard - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Conectar a %1 - - - - Setup %1 server - Configurando servidor %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - - - - - This url is secure. You can use it. - Esta URL es segura. La podés usar. - - - - &Next > - - - - - Invalid URL - - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">Conectado a %1: versión de %2 %3 (%4)</font><br/><br/> - - - - Failed to connect to %1 at %2:<br/>%3 - Falló al conectarse a %1 en %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Tiempo excedido mientras se intentaba conectar a %1 desde %2. - - - - Trying to connect to %1 at %2... - Intentando conectar a %1 en %2... - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - - - - - There was an invalid response to an authenticated webdav request - - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - - - - - Invalid URL - - - - - The server reported the following error: - - - - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - El directorio de sincronización local %1 ya existe, configurándolo para la sincronización.<br/><br/> - - - - Creating local sync folder %1... - - - - - ok - aceptar - - - - failed. - Error. - - - - Could not create local folder %1 - No fue posible crear el directorio local %1 - - - - No remote folder specified! - ¡No se ha especificado un directorio remoto! - - - - Error: %1 - Error: %1 - - - - creating folder on ownCloud: %1 - Creando carpeta en ownCloud: %1 - - - - Remote folder %1 created successfully. - El directorio remoto %1 fue creado con éxito. - - - - The remote folder %1 already exists. Connecting it for syncing. - El directorio remoto %1 ya existe. Estableciendo conexión para sincronizar. - - - - - The folder creation resulted in HTTP error code %1 - La creación del directorio resultó en un error HTTP con código de error %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - <p><font color="red">Error al crear el directorio remoto porque las credenciales provistas son incorrectas.</font><br/>Por favor, volvé atrás y verificá tus credenciales.</p> - - - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">Error al crear el directorio remoto, probablemente porque las credenciales provistas son incorrectas.</font><br/>Por favor, volvé atrás y verificá tus credenciales.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Se prtodujo un error <tt>%2</tt> al crear el directorio remoto %1. - - - - A sync connection from %1 to remote directory %2 was set up. - Fue creada una conexión de sincronización desde %1 al directorio remoto %2. - - - - Successfully connected to %1! - Conectado con éxito a %1! - - - - Connection to %1 could not be established. Please check again. - No fue posible establecer la conexión a %1. Por favor, intentalo nuevamente. - - - - Folder rename failed - Error Al Renombrar Directorio - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - - - - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>Directorio local %1 creado</b></font> - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Asistente de Conexión - - - - Enable experimental feature? - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Enable experimental mode - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - ¡Todo configurado! - - - - Open Local Folder - Abrir directorio local - - - - Open %1 in Browser - - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Error escribiendo metadatos a la base de datos - - - - OCC::PropagateDownloadFile - - - File %1 can not be downloaded because of a local file name clash! - - - - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - - - - - File was deleted from server - - - - - The file could not be downloaded completely. - - - - - The downloaded file is empty despite the server announced it should have been %1. - - - - - File %1 cannot be saved because of a local file name clash! - - - - - File has changed since discovery - - - - - Error writing metadata to the database - Error escribiendo metadatos a la base de datos - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - - - - - A file or folder was removed from a read only share, but restoring failed: %1 - - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - - - - - Attention, possible case sensitivity clash with %1 - - - - - could not create folder %1 - - - - - Error writing metadata to the database - Error escribiendo metadatos a la base de datos - - - - OCC::PropagateLocalRemove - - - Error removing '%1': %2; - - - - - Could not remove folder '%1' - - - - - Could not remove %1 because of a local file name clash - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - - - - - - Error writing metadata to the database - Error escribiendo metadatos a la base de datos - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - Error writing metadata to the database - Error escribiendo metadatos a la base de datos - - - - OCC::PropagateRemoteMove - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - - Error writing metadata to the database - Error escribiendo metadatos a la base de datos - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - - - - - Local file changed during syncing. It will be resumed. - - - - - Local file changed during sync. - - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - Error escribiendo metadatos a la base de datos - - - - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - - - - - Local file changed during sync. - - - - - Unexpected return code from server (%1) - - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileV1 - - - Poll URL missing - - - - - The local file was removed during sync. - - - - - Local file changed during sync. - - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolWidget - - - Form - Formulario - - - - TextLabel - EtiquetaDeTexto - - - - Open in browser - - - - - Time - Hora - - - - File - Archivo - - - - Folder - Carpeta - - - - Action - Acción - - - - Size - Tamaño - - - - Local sync protocol - - - - - Copy - Copiar - - - - Copy the activity list to the clipboard. - Copiar la lista de actividades al portapapeles. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - - - - - Username: - Nombre de usuario: - - - - Proxy: - - - - - The proxy server needs a username and password. - - - - - Password: - Contraseña: - - - - TextLabel - EtiquetaDeTexto - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Elige - - - - OCC::SelectiveSyncWidget - - - Loading ... - Cargando... - - - - Deselect remote folders you do not wish to synchronize. - - - - - Name - Nombre - - - - Size - Tamaño - - - - - No subfolders currently on the server. - - - - - An error occurred while loading the list of sub folders. - - - - - OCC::ServerNotificationHandler - - - Dismiss - - - - - OCC::SettingsDialog - - - Settings - Configuración - - - - Activity - Actividad - - - - General - General - - - - Network - Red - - - - Account - Cuenta - - - - OCC::SettingsDialogMac - - - %1 - %1 - - - - Activity - Actividad - - - - General - General - - - - Network - Red - - - - - Account - Cuenta - - - - OCC::ShareDialog - - - TextLabel - EtiquetaDeTexto - - - - share label - - - - - Dialog - - - - - ownCloud Path: - - - - - %1 Sharing - - - - - %1 - %1 - - - - Folder: %2 - - - - - The server does not allow sharing - - - - - Retrieving maximum possible sharing permissions from server... - - - - - The file can not be shared because it was shared without sharing permission. - - - - - Users and Groups - - - - - Public Links - - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - - - - - Upload only (File Drop) - - - - - Receive files from others without revealing the contents of the folder. - - - - - TextLabel - EtiquetaDeTexto - - - - Set &password - - - - - Enter a name to create a new public link... - - - - - &Create new - - - - - Set &expiration date - - - - - Set password - - - - - Recipients can view or download contents. - - - - - Download / View - - - - - Download / View / Upload - - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - - - - - - P&assword protect - - - - - The file can not be shared because it was shared without sharing permission. - - - - - Link shares have been disabled - - - - - Create public link share - - - - - - Delete - Borrar - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Copy link to clipboard (direct download) - - - - - Send link by email - Mandar enlace por e-mail - - - - Send link by email (direct download) - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - - - - - Cancel - Cancelar - - - - - Public link - - - - - Delete link share - - - - - Public sh&aring requires a password - - - - - Please Set Password - - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - - - - - Share with users or groups ... - Compartir con usuario o grupo ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Send link by email - Mandar enlace por e-mail - - - - No results for '%1' - - - - - I shared something with you - - - - - OCC::ShareUserLine - - - Form - Formulario - - - - - TextLabel - EtiquetaDeTexto - - - - can edit - podés editar - - - - can share - puede compartir - - - - ... - ... - - - - create - crear - - - - change - - - - - delete - borrar - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - - - - - OCC::ShibbolethCredentials - - - Login Error - - - - - You must sign in as user %1 - - - - - OCC::ShibbolethWebView - - - %1 - Authenticate - %1 - Autenticarse - - - - SSL Chipher Debug View - - - - - Reauthentication required - - - - - Your session has expired. You need to re-login to continue to use the client. - - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - - - - - Context menu share - - - - - I shared something with you - - - - - - Share... - - - - - - Copy private link to clipboard - - - - - Send private link by email... - - - - - Resharing this file is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - - - - - Download file(s) - - - - - Replace file(s) by virtual file - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - - - - - Common Name (CN): - - - - - Subject Alternative Names: - - - - - Organization (O): - - - - - Organizational Unit (OU): - - - - - State/Province: - - - - - Country: - Pais: - - - - Serial: - - - - - <h3>Issuer</h3> - - - - - Issuer: - - - - - Issued on: - - - - - Expires on: - - - - - <h3>Fingerprints</h3> - - - - - SHA-256: - - - - - SHA-1: - - - - - <p><b>Note:</b> This certificate was manually approved</p> - - - - - %1 (self-signed) - - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - - - - - No support for SSL session tickets/identifiers - - - - - Certificate information: - - - - - This connection is NOT secure as it is not encrypted. - - - - - - OCC::SslErrorDialog - - - Form - Formulario - - - - Trust this certificate anyway - Confiar en este certificado de todas maneras - - - - Untrusted Certificate - - - - - Cannot connect securely to <i>%1</i>: - - - - - with Certificate %1 - con certificado %1 - - - - - - &lt;not specified&gt; - &lt;no especificado&gt; - - - - - Organization: %1 - Empresa: %1 - - - - - Unit: %1 - Unidad: %1 - - - - - Country: %1 - País: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Huella (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Huella (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Desde: %1 - - - - Expiration Date: %1 - - - - - Issuer: %1 - Generado por: %1 - - - - OCC::SyncEngine - - - Success. - Éxito. - - - - A network connection timeout happened. - - - - - A HTTP transmission error happened. - Ha ocurrido un error de transmisión HTTP. - - - - The mounted folder is temporarily not available on the server - - - - - An error occurred while opening a folder - - - - - Error while reading folder. - - - - - %1 (skipped due to earlier error, trying again in %2) - - - - - File/Folder is ignored because it's hidden. - - - - - Folder hierarchy is too deep - - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Not allowed because you don't have permission to add parent folder - - - - - Not allowed because you don't have permission to add files in that folder - - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - - - - - CSync unspecified error. - Error no especificado de CSync - - - - Aborted by the user - Interrumpido por el usuario - - - - Failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder. - - - - - Discovery step failed. - - - - - Permission denied. - - - - - File or directory not found: - - - - - Tried to create a folder that already exists. - - - - - No space on %1 server available. - - - - - The service is temporarily unavailable - - - - - Access is forbidden - - - - - An internal error number %1 occurred. - - - - - Symbolic links are not supported in syncing. - Los vínculos simbólicos no está soportados al sincronizar. - - - - File is listed on the ignore list. - El archivo está en la lista de ignorados. - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - - - - - The file name is a reserved name on this file system. - - - - - Filename contains trailing spaces. - - - - - Filename is too long. - - - - - The filename cannot be encoded on your file system. - - - - - Unresolved conflict. - - - - - Stat failed. - - - - - Filename encoding is not valid - - - - - Invalid characters, please rename "%1" - - - - - Using virtual files but suffix is not set - - - - - Unable to read the blacklist from the local database - - - - - Unable to read from the sync journal. - - - - - Cannot open the sync journal - - - - - File name contains at least one invalid character - - - - - - Ignored because of the "choose what to sync" blacklist - - - - - Not allowed because you don't have permission to add subfolders to that folder - - - - - Not allowed to upload this file because it is read-only on the server, restoring - - - - - - Not allowed to remove, restoring - - - - - Local files and share folder removed. - - - - - Move not allowed, item restored - - - - - Move not allowed because %1 is read-only - - - - - the destination - - - - - the source - - - - - OCC::SyncLogDialog - - - Synchronisation Log - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p> - - - - - <p>Copyright ownCloud GmbH</p> - - - - - <p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%2 and the %2 logo are registered trademarks of %1 in the United States, other countries, or both.</p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - - - - - The checksum header contained an unknown checksum type '%1' - - - - - The downloaded file does not match the checksum, it will be resumed. - - - - - OCC::ownCloudGui - - - Please sign in - Por favor, inicie sesión - - - - Folder %1: %2 - Directorio %1: %2 - - - - There are no sync folders configured. - - - - - Open in browser - - - - - - - Log in... - - - - - - - Log out - Cerrar la sesión - - - - Recent Changes - Cambios recientes - - - - Managed Folders: - Directorios administrados: - - - - Open folder '%1' - Abrir carpeta '%1' - - - - Open %1 in browser - Abrir %1 en el navegador... - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Estado desconocido - - - - Settings... - Configuraciones... - - - - Details... - Detalles... - - - - Help - Ayuda - - - - Quit %1 - Cancelar %1 - - - - Disconnected from %1 - - - - - Unsupported Server Version - - - - - Disconnected - Desconectado - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - - - - - Account %1: %2 - - - - - Signed out - Desautentificado - - - - Account synchronization is disabled - - - - - - Synchronization is paused - - - - - Unresolved conflicts - - - - - Error during synchronization - - - - - No sync folders configured - - - - - Unpause all folders - - - - - Pause all folders - - - - - Unpause all synchronization - - - - - Unpause synchronization - - - - - Pause all synchronization - - - - - Pause synchronization - - - - - Log out of all accounts - - - - - Log in to all accounts... - - - - - New account... - - - - - - About %1 - Sobre %1 - - - - No items synced recently - No se sincronizaron elementos recientemente - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - - - - - Syncing %1 of %2 - - - - - Syncing %1 (%2 left) - - - - - Syncing %1 - Sincronizando %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - actualizado - - - - OwncloudAdvancedSetupPage - - - Form - Formulario - - - - - - - - - - TextLabel - EtiquetaDeTexto - - - - Server - - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - - - - - Start a &clean sync (Erases the local folder!) - - - - - Ask for confirmation before synchroni&zing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - - - - - Ask for confirmation before synchronizing e&xternal storages - - - - - Choose what to sync - Elegir que sincronizar - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - Directorio &local - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Mantener datos locales - - - - S&ynchronize everything from server (recommended) - - - - - Use virtual files instead of downloading content immediately (e&xperimental) - - - - - Status message - Mensaje de estado - - - - OwncloudHttpCredsPage - - - Form - Formulario - - - - &Username - &Nombre de usuario - - - - &Password - &Contraseña - - - - OwncloudOAuthCredsPage - - - Form - Formulario - - - - Please switch to your browser to proceed. - - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - - - - - OwncloudSetupPage - - - Form - Formulario - - - - - TextLabel - EtiquetaDeTexto - - - - Ser&ver Address - - - - - https://... - https://... - - - - - Error Label - Etiqueta de Error - - - - OwncloudWizardResultPage - - - Form - Formulario - - - - TextLabel - Etiqueta - - - - Your entire account is synced to the local folder - Tu cuenta completa está sincronizada al directorio local - - - - - PushButton - Botón - - - - QObject - - - in the future - - - - - %n day(s) ago - - - - - %n hour(s) ago - - - - - now - - - - - Less than a minute ago - - - - - %n minute(s) ago - - - - - Some time ago - - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - - - - - %L1 MB - - - - - %L1 KB - - - - - %L1 B - %L1 B - - - - %n year(s) - - - - - %n month(s) - - - - - %n day(s) - - - - - %n hour(s) - - - - - %n minute(s) - - - - - %n second(s) - - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - La bandeja del sistema no está disponible - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - % 1 requiere una bandeja del sistema funcionando. Si estás usando XFCE, por favor leé <a href="http://docs.xfce.org/xfce/xfce4-panel/systray"> estas instrucciones</ a>. De lo contrario, instalá una aplicación de la bandeja del sistema, como 'trayer' e intentalo de nuevo. - - - - ownCloudTheme::about() - - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - - - - - progress - - - Downloaded - Descargado - - - - Uploaded - Subido - - - - Server version downloaded, copied changed local file into conflict file - - - - - Deleted - Borrado - - - - Moved to %1 - Movido a %1 - - - - Ignored - Ignorado - - - - Filesystem access error - Error de acceso al sistema de archivos - - - - Error - Error - - - - Updated local metadata - - - - - - Unknown - Desconocido - - - - downloading - descargando - - - - uploading - subiendo - - - - deleting - Eliminado - - - - moving - Movido - - - - ignoring - ignorado - - - - - error - error - - - - updating local metadata - - - - - theme - - - Status undefined - Estado indefinido - - - - Waiting to start sync - Esperando para comenzar sincronización - - - - Sync is running - Sincronización en ejecución - - - - Sync Success - Sincronización Exitosa - - - - Sync Success, some files were ignored. - Syncronización exitosa, algunos archivos fueron ignorados. - - - - Sync Error - Error de Sync - - - - Setup Error - Error de instalación - - - - Preparing to sync - Preparando para la sincronizacipon - - - - Aborting... - Abortando... - - - - Sync is paused - Sincronización Pausada - - - - utility - - - Could not open browser - - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - - - \ No newline at end of file diff --git a/translations/client_et.ts b/translations/client_et.ts index 25d275919f8..fbe9ee39952 100644 --- a/translations/client_et.ts +++ b/translations/client_et.ts @@ -1,4419 +1,3680 @@ - + - FileSystem + AccountBar - - Could not make directories in trash - + + Navigation bar + Navigatsiooniriba - - - Could not move '%1' to '%2' - + + Add Account + Lisa konto - - Moving to the trash is not implemented on this platform - + + Activity + Tegevus - - - FolderWizardSourcePage - - Form - Vorm + + Settings + Seaded - - Pick a local folder on your computer to sync - Vali oma arvutist sünkroniseeritav kataloog + + Quit + Lõpeta + + + AppImageUpdateAvailableWidgetUi - - &Choose... - &Vali... + + Skip this version + Jäta see versioon vahele - FolderWizardTargetPage + AppProvider - - Form - Vorm + + Failed to open %1 in web. Error: %2. + %1 avamine veebis nurjus. Viga: %2. + + + ChecksumHeader - - Select a remote destination folder - Vali eemalolev sihtkaust + + The checksum header is malformed: %1 + Kontrollsumma päis on valesti vormistatud: %1 - - Create Folder - Loo kaust + + The checksum header contained an unknown checksum type '%1' + Kontrollsumma päis sisaldas tundmatut kontrollsumma tüüpi '%1' + + + CommandLine - - Refresh - Värskenda + + %1 version %2 +File synchronization desktop utility. + %1 versioon %2<byte value="xd"/> +Failide sünkroniseerimise töölaua rakendus. - - Folders - Kaustad + + For more information, see %1 + link to homepage + Lisateabe saamiseks vt %1 - - TextLabel - TekstiSilt + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Alusta nähtava peamise aknaga või kui see juba töötab, too see esiplaanile. Vaikimisi käivitub klient taustal. - - - NotificationWidget - - Form - Vorm + + Quit the running instance. + Lõpeta käimasolev instants. - - Lorem ipsum dolor sit amet - + + Write log to file (use - to write to stdout). + Logi kirjutamine faili (stdouti kirjutamiseks kasuta -). - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - + + Write each sync log output in a new file in folder. + Kirjuta iga sünkroniseerimise logi väljund kataloogis uude faili. - - TextLabel - Tekstisilt + + Flush the log file after every write. + Tühjenda logifail pärast iga kirjutamist. - - - OCC::AbstractNetworkJob - - Connection timed out - Ühendus aegus + + Output debug-level messages in the log. + Väljasta silumisteated logisse. - - Unknown error: network reply was deleted - + + Enable debug mode. + Luba silumisrežiim. - - Server replied "%1 %2" to "%3 %4" - + + --logfile and --logdir are mutually exclusive + --logfile ja --logdir välistavad üksteist. - OCC::AccountSettings + CommonStrings - - Form - Vorm + + file manager + failihaldur - - ... - ... + + Show in %1 + Näita %1 - - Storage space: ... - Salvestusruum: ... + + Show in web browser + Näita veebibrauseris - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Märkimata kataloogid <b>eemaldatakse</b> kohalikust failisüsteemist ning neid ei sünkroniseerita enam sellesse arvutisse + + Copy + Kopeeri + + + + %n Filter(s) + %n filter%n filtrit - - Synchronize all - Sünkrooni kõik + + folder + kaust - - Synchronize none - Ära sünkrooni midagi + + space + - - Apply manual changes - Rakenda käsitsi tehtud muudatused + + folders + kaustad - - Apply - Rakenda + + spaces + - - - - Cancel - Loobu + + Folder + Kaust - - Connected with <server> as <user> - Ühendatud <server> kasutajana <user> + + Space + + + + FetchUserInfoJobFactory - - No account configured. - Ühtegi kontot pole seadistatud + + Failed to retrieve user info + Kasutaja info kättesaamine ebaõnnestus + + + FileSystem - - Add new - Lisa uus + + + + + Can't rename %1, the file is currently in use + Ei saa ümber nimetada %1, fail on kasutuses - - Remove - Eemalda + + Could not remove folder + Kausta ei saanud eemaldada + + + FolderItem - - Account - Konto + + + Offline + Võrguühenduseta - - Choose what to sync - Vali, mida sünkroonida + + + Unavailable: the space is no longer available on the server + - - Force sync now - Sünkrooni jõuga kohe + + + Sync paused + - - Restart sync - Taaskäivita sünkroonimine + + Synced + + + + + Sync failed + Sünkroniseerimine ebaõnnestus - - Remove folder sync connection + + Sync pending - - Create virtual files for new files (Experimental) + + Syncing %1 of %2 (%3 % - - Folder creation failed - Kausta loomine ebaõnnestus + + , ⬇️ %1/s + , ⬇️ %1/s - - <p>Could not create local folder <i>%1</i>. - + + , ⬆️ %1/s + , ⬆️ %1/s - - Confirm Folder Sync Connection Removal + + %1, sync status: %2 + Accessible text, read out by a screen reader. + first argument is the displayed name of the folder. + + + FolderManagementUtils - - Remove Folder Sync Connection - + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + Rada '%1' on liiga pikk. Luba pikad rajanimed Windows'i seadetes või vali teine kaust. - - Sync Running - Sünkroniseerimine on käimas + + Local folder %1 does not exist. + Kohalikku kausta %1 pole olemas. - - The syncing operation is running.<br/>Do you want to terminate it? - Sünkroniseerimine on käimas.<br/>Kas sa soovid seda lõpetada? + + %1 is not a folder. + - - %1 in use - %1 kasutusel + + %1 is not readable. + %1 pole loetav. - - %1 as <i>%2</i> - %1 as <i>%2</i> + + %1 is not writable. + %1 ei ole kirjutatav. + + + FolderWizard - - Connected to %1. - Ühendatud %1 + + <b>Error:</b> %1 + <b>Veateade:</b> %1 - - Server %1 is temporarily unavailable. - Server %1 pole ajutiselt saadaval. + + <b>Warning:</b> %1 + <b>Hoiatus:</b> %1 - - Server %1 is currently in maintenance mode. - + + <b>Error:</b><ul>%1</ul> + <b>Veateade:</b> <ul>%1</ul> - - Signed out from %1. - + + <b>Warning:</b><ul>%1</ul> + <b>Hoiatus:</b> <ul>%1</ul> + + + FolderWizardSourcePage - - Obtaining authorization from the browser. <a href='%1'>Click here</a> to re-open the browser. - + + Form + Vorm - - Connecting to %1... - + + Pick a local folder on your computer to sync + Vali oma arvutist sünkroniseeritav kataloog - - No connection to %1 at %2. + + Browse… + + + FolderWizardTargetPage - - Log in - Logi sisse + + Form + Vorm - - There are folders that were not synchronized because they are too big: - + + Select a remote destination folder + Vali eemalolev sihtkaust - - There are folders that were not synchronized because they are external storages: - + + Create Folder + Loo kaust - - There are folders that were not synchronized because they are too big or external storages: - + + Refresh + Värskenda - - Confirm Account Removal - Kinnita konto eemaldamine + + Folders + Kaustad + + + OCC::AboutView - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - + + About + Info - - Remove connection - Eemalda ühendus + + Versions + Versioonid + + + OCC::AbstractNetworkJob - - - Open folder - Ava kaust + + Connection timed out + Ühendus aegus - - - Log out - Logi välja + + Unknown error: network reply was deleted + Tundmatu viga: võrguvastus kustutati - - Resume sync - Taasta sünroonimist + + Server replied "%1 %2" to "%3 %4" + Server vastas "%3 %4" päringule "%1 %2" + + + OCC::Account - - Pause sync - Peata sünkroonimine + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + + Choose what to sync + Vali, mida sünkroniseerida + + + + + Sync now - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. + + Manage subfolder sync - - %1 of %2 in use + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' - - Currently there is no storage usage information available. - Hetkel pole mahu kasutuse info saadaval. + + Disable virtual file support? + Lülita virtuaalse faili tugi välja? - - The server version %1 is unsupported! Proceed at your own risk. - + + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. + +The only advantage of disabling virtual file support is that the selective sync feature will become available again. + +This action will abort any currently running synchronization. + See tegevus lülitab välja virtuaalse faili toe. Selle tagajärjel laetakse alla kataloogid märkega 'saadaval vaid võrguühendusega'. + +Ainus eelis virtuaalse faili toe väljalülitamisel on valikulise sünkroniseerimise võimaluse taastumine. + +See tegevus katkestab kõik käimasolevad sünkroniseerimised. - - No %1 connection configured. - Ühtegi %1 ühendust pole seadistatud. + + Disable support + Eemalda tugi - - - OCC::AccountState - - Signed out - Välja logitud + + Restart sync + Taaskäivita sünkroniseerimine - - Disconnected - Lahti ühendatud + + Resume sync + Jätka sünkroniseerimist - - Connected - Ühendatud + + Pause sync + Peata sünkroniseerimine - - Service unavailable - Teenus pole saadaval + + Deactivate virtual files + - - Maintenance mode - Hooldusrežiim + + Activate virtual files + - - Network error - Võrgu viga + + Internet connection is metered + Internetiühendus on mahupõhine - - Configuration error - Seadistamise tõrge + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + Sünkroniseerimine on peatatud, kuna interneti-ühendus on mahupõhine<p>Kas soovite tõesti sünkroniseerida? - - Asking Credentials - Kasutajatunnuste küsimine + + Sync Running + Sünkroniseerimine on käimas - - Unknown account state - Tundmatu konto olek + + The sync operation is running.<br/>Do you want to stop it? + Sünkroniseerimine käib. <br/> Kas soovid katkestada? - - - OCC::ActivityItemDelegate - - %1 on %2 - + + Sync paused by user + Sünkroniseerimine peatati kasutaja poolt - - %1 on %2 (disconnected) - + + Confirm Folder Sync Connection Removal + Kinnita kaustade sünkroniseerimise ühenduse kustutamine - - - OCC::ActivitySettings - - - Server Activity - Serveri aktiivsus + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Kas soovid tõesti katkestada kataloogi <i>%1</i> sünkroniseerimist?</p><p><b>Märkus:</b> See <b>ei</b> kustuta ühtegi faili.</p> - - Sync Protocol - Sünkroniseerimisprotokoll + + Remove Folder Sync Connection + Eemalda kaustade sünkroniseerimise ühendus - - Not Synced - Pole sünkroonitud + + + Cancel + Loobu + + + OCC::AccountFoldersView - - Not Synced (%1) - %1 is the number of not synced files. + + Sync options menu + Sünkroniseerimise valikute menüü + + + + %1 sync + This shows as 'Folder sync' or 'Space sync' + + + + + Manage your synced %1. - - The server activity list has been copied to the clipboard. + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' - - The sync activity list has been copied to the clipboard. + + %1 list view - - The list of unsynced items has been copied to the clipboard. + + Navigate the %1 list using the up and down arrows - - Copied to clipboard - Kopeeritud lõikepuhvrisse + + %1 out of %2 %3 are synchronized + - OCC::ActivityWidget + OCC::AccountModalWidget - + + Form Vorm + + + OCC::AccountPlaceholderWidget - - - - TextLabel - Tekstisilt + + %1 logo + %1 logo - - Server Activities - Serveri tegevused + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + - - Copy - Kopeeri + + Additional logo defined by the organization + Organisatsiooni poolt defineeritud täiendav logo + + + OCC::AccountView - - Copy the activity list to the clipboard. - Kopeeri tegevuste nimistu puhvrisse. + + Form + Vorm - - Action Required: Notifications - + + Manage Account + Halda kontot - - <br/>Account %1 does not have activities enabled. - - - - - You received %n new notification(s) from %2. - - - - - You received %n new notification(s) from %1 and %2. - + + Preparing the account + Konto ettevalmistamine - - You received new notifications from %1, %2 and other accounts. - + + Sync connections + Sünkroniseeri ühendused - - %1 Notifications - Action Required - + + Account options menu + Konto valikute menüü - OCC::AddCertificateDialog + OCC::AccountViewController - - SSL client certificate authentication - SSL kliendisertifikaadiga autentimine + + + Log in + Logi sisse - - This server probably requires a SSL client certificate. - See server nõuab tõenäoliselt SSL kliendisertifikaati + + Reconnect + Ühendu uuesti - - Certificate & Key (pkcs12) : - + + Remove + Eemalda - - Browse... - Lehitse... + + Log out + Logi välja - - Certificate password : - Sertifikaadi parool: + + Confirm Account Removal + Kinnita konto eemaldamine - - Select a certificate - Vali sertifikaat + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Kas soovid tõesti ühenduse kontoga <i>%1</i> eemaldada?</p><p><b>Märkus:</b> See <b>ei</b> kustuta ühtegi faili.</p> - - Certificate files (*.p12 *.pfx) - Sertifikaadifailid (*.p12 *.pfx) + + Remove connection + Eemalda ühendus - - - OCC::Application - - Continuing will mean <b>deleting these settings</b>. - + + Cancel + Loobu - - Continuing will mean <b>ignoring these settings</b>. - + + The server version %1 is unsupported! Proceed at your own risk. + Serveri versioon %1 ei ole toetatud! Jätkake omal vastutusel. - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - + + Connected + Ühendatud - - Quit - Lõpeta + + Server is temporarily unavailable. + - - Continue - Jätka + + Server is currently in maintenance mode. + - - Error accessing the configuration file - + + Signed out + Välja logitud - - There was an error while accessing the configuration file at %1. + + Updating credentials… - - Quit ownCloud - Sulge ownCloud + + Captive portal prevents connections to the server. + Piiratud portaal takistab ühendusi serverisse. - - - OCC::AuthenticationDialog - - Authentication Required - Vajalik on autentimine. + + Sync is paused due to metered internet connection. + - - Enter username and password for '%1' at %2. - Sisesta kasutajanimi ja parool '%1' %2 + + Connecting… + Ühendamine... - - &User: - Kas&utaja: + + Server configuration error + Serveri seadete viga - - &Password: - &Parool: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Ühtegi ownCloud kontot pole seadistatud - - - - The configured server for this client is too old - Seadistatud server on selle kliendi jaoks liiga vana - - - - Please update to the latest server and restart the client. - Palun uuenda server viimasele versioonile ning taaskäivita klient. - - - - Authentication error: Either username or password are wrong. - Autentimise tõrge: Kasutajanimi või parool on vale - - - - timeout - aegumine - - - - The provided credentials are not correct - Sisestatud kasutajatunnused pole õiged - - - - OCC::DiscoveryMainThread - - - Aborted by the user - Kasutaja poolt tühistatud - - - - OCC::DiscoverySingleDirectoryJob - - - The server file discovery reply is missing data. - - - - - OCC::Folder - - - Local folder %1 does not exist. - Kohalikku kausta %1 pole olemas. - - - - %1 should be a folder but is not. - - - - - %1 is not readable. - %1 pole loetav. - - - - %1 has been removed. - %1 names a file. - %1 on eemaldatud. - - - - %1 has been downloaded. - %1 names a file. - %1 on alla laaditud. - - - - %1 has been updated. - %1 names a file. - %1 on uuendatud. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 on ümber nimetatud %2. - - - - %1 has been moved to %2. - %1 on tõstetud %2. - - - - %1 and %n other file(s) have been removed. - - - - - %1 and %n other file(s) have been downloaded. - - - - - %1 and %n other file(s) have been updated. - - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - - - - - %1 has been moved to %2 and %n other file(s) have been moved. - - - - - %1 has and %n other file(s) have sync conflicts. - - - - - %1 has a sync conflict. Please check the conflict file! - - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - - - - - %1 could not be synced due to an error. See the log for details. - %1 sünkroniseerimine ebaõnnestus tõrke tõttu. Lisainfot vaata logist. - - - - Sync Activity - Sünkroniseerimise tegevus - - - - Could not read system exclude file - Süsteemi väljajätmiste faili lugemine ebaõnnestus - - - - A new folder larger than %1 MB has been added: %2. - - - - - - A folder from an external storage has been added. - - - - - - Please go in the settings to select it if you wish to download it. - - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - Kustutada kõik failid? - - - - Remove all files - Kustutada kõik failid - - - - Keep files - Säilita failid - - - - This sync would reset the files to an earlier time in the sync folder '%1'. -This might be because a backup was restored on the server. -Continuing the sync as normal will cause all your files to be overwritten by an older file in an earlier state. Do you want to keep your local most recent files as conflict files? - - - - - Backup detected - Leiti varukoopia - - - - Normal Synchronisation - Tavaline sünkroonimine - - - - Keep Local Files as Conflict - - - - - OCC::FolderMan - - - Could not reset folder state - Ei suutnud tühistada kataloogi staatust - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Leiti vana sünkroniseeringu zurnaal '%1', kuid selle eemaldamine ebaõnnenstus. Palun veendu, et seda kasutaks ükski programm. - - - - (backup) - (varukoopia) - - - - (backup %1) - (varukoopia %1) - - - - Undefined State. - Määramata staatus. - - - - Waiting to start syncing. - Oodatakse sünkroonimise alustamist. - - - - Preparing for sync. - Valmistun sünkroniseerima. - - - - Sync is running. - Sünkroniseerimine on käimas. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Viimane sünkroniseerimine oli edukas. - - - - Setup Error. - Seadistamise viga. - - - - User Abort. - Kasutaja tühistamine. - - - - Sync is paused. - Sünkroniseerimine on peatatud. - - - - %1 (Sync is paused) - %1 (Sünkroniseerimine on peatatud) - - - - No valid folder selected! - Sobilikku kausta pole valitud! - - - - The selected path is not a folder! - Valitud asukoht pole kaust! - - - - You have no permission to write to the selected folder! - Sul puuduvad õigused valitud kataloogi kirjutamiseks! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - - - - - Synchronizing with local folder - - - - - File - Fail - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Kausta lisamiseks pead sa olema ühendatud - - - - Click this button to add a folder to synchronize. - Sünkroniseeritava kausta lisamiseks kliki sellele nupule. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - - - - - Signed out - Välja logitud - - - - Fetching folder list from server... - - - - - There are unresolved conflicts. Click for details. - - - - - New files are being created as virtual files. - - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Sünkroniseerimine %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - allalaadimine %1/s - - - - u2193 %1/s - u2193 %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - üleslaadimine %1/s - - - - u2191 %1/s - u2191 %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 / %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 / %2, fail %3 / %4 - - - - file %1 of %2 - fail %1 / %2-st - - - - Waiting... - Ootamine... - - - - Waiting for %n other folder(s)... - - - - - Preparing to sync... - Sünkroniseerimiseks valmistumine... - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - - - - - Add Sync Connection - - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Klõpsa valimaks kohalikku sünkroniseeritavat kataloogi. - - - - Enter the path to the local folder. - Sisesta otsingutee kohaliku kataloogini. - - - - Select the source folder - Vali algne kaust - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Loo kaugkaust - - - - Enter the name of the new folder to be created below '%1': - - - - - Folder was successfully created on %1. - %1 - kaust on loodud. - - - - Authentication failed accessing %1 - - - - - Failed to create the folder on %1. Please check manually. - Kausta loomine ebaõnnestus - %1. Palun kontrolli käsitsi. - - - - Failed to list a folder. Error: %1 - - - - - Choose this to sync the entire account - Vali see sünkroniseering tervele kontole - - - - This folder is already being synced. - Seda kataloogi juba sünkroniseeritakse. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Sa juba sünkroniseerid <i>%1</i>, mis on <i>%2</i> ülemkataloog. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately (experimental) - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Hoiatus:</b> %1 - - - - <b>Warning:</b> - <b>Hoiatus:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Ühtegi E-Silti ei saabunud serverist, kontrolli puhverserverit/lüüsi. - - - - We received a different E-Tag for resuming. Retrying next time. - Saime jätkamiseks erineva E-Sildi. Proovin järgmine kord uuesti. - - - - Server returned wrong content-range - Server tagastas vale vahemiku - - - - Connection Timeout - Ühenduse aegumine - - - - OCC::GeneralSettings - - - Form - Vorm - - - - General Settings - Üldised seaded - - - - For System Tray - Süsteemisalve jaoks - - - - stable - - - - - beta - - - - - Advanced - Täpsem - - - - Ask for confirmation before synchronizing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - - - - - &Launch on System Startup - Käivita süsteemi startimise&l - - - - Show &Desktop Notifications - Näita &töölauateavitusi - - - - Use &Monochrome Icons - Kasuta ühevärvilisi ikoone - - - - Version - Versioon - - - - &Update Channel - - - - - • - - - - - Edit &Ignored Files - Muuda &ignoreeritud faile - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - &Näita kokkujooksmise teavitajat - - - - &Restart && Update - &Taaskäivita && Uuenda - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Loobu - - - - OCC::GetOrCreatePublicLinkShare - - - Sharing error - - - - - Could not retrieve or create the public link share. Error: - -%1 - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - Sisesta parool - - - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Ignoreeritud failide redaktor - - - - Global Ignore Settings - Üldised ignoreerimise seaded - - - - Sync hidden files - Sünkroniseeri peidetud failid - - - - Files Ignored by Patterns - Mustri poolt ignoreeritud failid - - - - Add - Lisa - - - - Pattern - Muster - - - - Allow Deletion - Luba kustutamine - - - - Remove - Eemalda + + Disconnected + Lahti ühendatud - - Files or folders matching a pattern will not be synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + + Invalid connection status - - - Could not open file - Ei suutunud avada faili - - - - Cannot write changes to '%1'. - Ei saa kirjutada muudatusi '%1'. - - - - Add Ignore Pattern - Lisa ignoreerimise muster - - - - Add a new ignore pattern: - Lisa uus ignoreerimise muster: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Selle kirje on pakkunud süsteem '%1' ning seda ei saa antud vaates muuta. - - OCC::IssuesWidget - - - Form - Vorm - - - - List of issues - - - - - Account - Konto - - - - - <no filter> - - - - - - Folder - Kaust - - - - Show warnings - - - - - Show ignored files - - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - - - - - Copy - Kopeeri - - - - Time - Aeg - - - - File - Fail - - - - Issue - - + OCC::AccountsGuiController - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + + + New account failure - - - OCC::LogBrowser - - - Log Output - Logi väljund - - - - &Search: - &Otsi; - - - - &Find - Leia: - - - &Capture debug messages + + The information required to create a new account is incomplete. Please run the wizard again. - - Permanently save logs - + + Accounts + Kontod - - When this option is enabled and no other logging is configured, logs will be written to a temporary folder and expire after a few hours. This setting persists across client restarts. - -Logs will be written to %1 + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. - - - Clear - Tühjenda - - - - Clear the log display. - Tühjenda näidatav logi. - - - - S&ave - S&alvesta - - - - Save the log file to a file on disk for debugging. - Salvesta logi fail kettale täpsemaks uurimiseks. - - - - Save log file - Salvesta logifail - - - - Error - Viga - - - - Could not write to log file %1 - Logifaili %1 kirjutamine ebaõnnestus - - - - OCC::Logger - - - Error - Viga - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>Faili '%1'<br/>ei saa kirjutamiseks avada.<br/><br/>Logi väljundit <b>ei saa</b> salvestada!</nobr> - - OCC::NSISUpdater - - - New Version Available - Saadaval on uuem versioon - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Uus versioon %1 kliendist on saadaval.</p><p><b>%2</b> on saadaval alla laadimiseks. Paigaldatud on versioon %3.<p> - + OCC::ActivitySettings - - Skip this version - Jäta see versioon vahele + + Local Activity + Kohalik tegevus - - Skip this time - Jäta seekord vahele + + Not Synced + Pole sünkroniseeritud - - Get update - Hangi uuendus + + Not Synced (%1) + %1 is the number of not synced files. + Pole sünkroniseeritud (%1) - OCC::NetworkSettings - - - Form - Vorm - + OCC::AdvancedSettingsPageController - - Proxy Settings - Proksi seaded + + Advanced settings + Lisavalikud - - No Proxy - Proksit pole + + Sync and download options + - - Use system proxy - Kasuta süsteemi proksit + + Download location + - - Specify proxy manually as - Määra proksi käsitsi kui + + Sync and download files as you use them (saves hard drive space) + - - Host - Host + + Automatically sync and download folders and files + - - : - : + + Sync and download folders manually + - - Proxy server requires authentication - Proksi server nõuab autentimist + + Download location on the local machine + - - Download Bandwidth - Allalaadimise ülekandekiirus + + Browse… + - - - Limit to - Piira + + Browse for a download location + - - - KBytes/s - KBaiti/s + + Invalid local download directory %1: %2 + - - - No limit - Piiramatu + + your user directory may not be chosen as the sync root. + - - - Limit to 3/4 of estimated bandwidth + + path is not located on a supported Apple File System. - - Upload Bandwidth - Üleslaadimise ülekandekiirus + + path must be absolute. + - - - Limit automatically - Piira automaatselt + + selected path does not support using virtual file system. %1 + - - Hostname of proxy server - Proksiserveri hostinimi + + Select sync root + + + + OCC::AppImageUpdateAvailableWidget - - Username for proxy server - Proksiserveri kasutajanimi + + Installed version: %1 + Paigaldatud versioon: %1 - - Password for proxy server - Proksiserveri parool + + Available update: %1 + Uuendus saadaval: %1 - - HTTP(S) proxy - HTTP(S) proksi + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Selle AppImage %1 jaoks on saadaval uuendus. Kas soovite selle uuenduse paigaldada? + +Uuendus toimub taustal ja kirjutab praeguse AppImage'i faili üle. Uuendamise lõpuleviimiseks peate rakenduse taaskäivitama. + + + OCC::Application - - SOCKS5 proxy - SOCKS5 proksi + + Quit + Lõpeta - OCC::NotificationWidget + OCC::AuthSuccessPageController - - Created at %1 - + + Application Name Logo %1 + Rakendus Nimi Logo %1 - - Closing in a few seconds... + + You're all set! Open %1 and get started. - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' + + Additional logo defined by the organization + Organisatsiooni poolt defineeritud täiendav logo + + + + OCC::ButtonDelegate + + + %1 options button - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' + + Menu button with %1 options. Use the space key to show the menu - OCC::OAuth + OCC::ConnectionValidator - - Error returned from the server: <em>%1</em> - + + No ownCloud account configured + Ühtegi ownCloud kontot pole seadistatud - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - + + Please update to the latest server and restart the client. + Palun uuenda server viimasele versioonile ning taaskäivita klient. - - Could not parse the JSON returned from the server: <br><em>%1</em> - + + Authentication error: Either username or password are wrong. + Autentimise viga: kasutajanimi või parool on vale - - The reply from the server did not contain all expected fields - + + timeout + aegumine - - <h1>Login Error</h1><p>%1</p> + + No account configured - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - + + The provided credentials are not correct + Sisestatud kasutajatunnused pole õiged + + + + The configured server for this client is too old. + Selle kliendi jaoks seadistatud server on liiga vana. - OCC::OCUpdater + OCC::CredentialJob - - New %1 Update Ready - Uus %1 uuendus on valmis + + Failed to parse credentials %1 + Kasutajaandmete parsimine ebaõnnestus %1 + + + OCC::DetermineAuthTypeAdapter - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - + + Server does not support OAuth + Server ei toeta OAuth protokolli - - Downloading version %1. Please wait... - Laadin alla versiooni %1. Palun oota... + + Server did not ask for authorization + Server ei küsinud autoriseerimist - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Ei suutnud uuendust alla laadida. Palun klõpsa käsitsi uuenduse alla laadimiseks <a href='%1'>siia</a> %2 + + Failed to determine authentication type: %1 + Autentimistüübi määramine ebaõnnestus: %1 + + + OCC::DiscoverWebFingerServiceAdapter - - Could not check for new updates. - Uuenduste kontrollimine ebaõnnestus. + + Invalid reply received from server. + Serverilt saabus vigane vastus. + + + OCC::DiscoverySingleDirectoryJob - - %1 version %2 available. Restart application to start the update. + + The account was deleted before we could start the propfind job - - New %1 version %2 available. Please use the system's update tool to install it. - + + + Server error: PROPFIND reply is not XML formatted! + Serveri viga: PROPFIND vastus ei ole XML-formaadis! + + + OCC::DiscoverySingleLocalDirectoryJob - - Checking update server... - Kontrollin uuendusserverit... + + Error while opening directory %1 + Viga kataloogi %1 avamisel - - Update status is unknown: Did not check for new updates. - Uuenduse staatus on teadmata: Uuendusi ei kontrollitud. + + Directory not accessible on client, permission denied + Kataloog ei ole kliendile kättesaadav, juurdepääs on keelatud - - No updates available. Your installation is at the latest version. - Uuendusi pole saadaval. Kasutad kõige viimast versiooni. + + Directory not found: %1 + Kataloogi ei leitud: %1 - - Update Check - Uuenduste kontrollimine + + Error while reading directory %1 + Viga kataloogi %1 lugemisel - OCC::OwncloudAdvancedSetupPage + OCC::ExpandingHeaderView - - Connect to %1 - Ühendu %1 + + Reset column sizes + Lähtesta veergude suurused + + + OCC::FetchCapabilitiesAdapter - - Setup local folder options - Seadista kohaliku kataloogi valikud + + server sent invalid content type: %1. + server saatis vigase sisu tüübi: %1. - - Connect... - Ühenda... + + could not parse Capabilities from server. + serveri saadetud võimekuste lugemine ebaõnnestus. - - %1 folder '%2' is synced to local folder '%3' - %1 kataloog '%2' on sünkroniseeritud kohalikku kataloogi '%3' + + reply sent for different subject (server): %1 + vastus saadeti teisele subjektile (server): %1 + + + OCC::Folder - - Sync the folder '%1' - Sünkrooni kaust '%1' + + %1 has been removed. + %1 names a file. + %1 on eemaldatud. - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - + + %1 has been updated. + %1 names a file. + %1 on uuendatud. - - Local Sync Folder - Kohalik Sync Kataloog + + %1 has been renamed to %2. + %1 and %2 name files. + %1 on ümber nimetatud %2-ks. - - - (%1) - (%1) + + %1 has been moved to %2. + %1 on tõstetud %2. - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Ühendus ebaõnnestus + + + %1 and %n other file(s) have been removed. + %1 ja veel %n fail eemaldati%1 ja veel %n faili eemaldati. - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> + + The folder has been disabled or removed from the server - - - Select a different URL - Vali teine URL + + + %1 and %n other file(s) have been added. + %1 ja veel %n fail lisati.%1 ja veel %n faili lisati. - - Retry unencrypted over HTTP (insecure) - + + %1 has been added. + %1 names a file. + %1 on lisatud. + + + + %1 and %n other file(s) have been updated. + %1 ja veel %n fail uuendati.%1 ja veel %n faili uuendati. + + + + %1 has been renamed to %2 and %n other file(s) have been renamed. + %1 nimetati ümber %2-ks ja veel %n fail nimetati ümber.%1 nimetati ümber %2-ks ja veel %n faili nimetati ümber. + + + + %1 has been moved to %2 and %n other file(s) have been moved. + %1 liigutati %2 ja veel %n fail liigutati.%1 liigutati %2 ja veel %n faili liigutati. + + + + %1 and %n other file(s) have sync conflicts. + %1-l ja veel %n-l failil on sünkroniseerimise konfliktid.%1-l ja veel %n-l failil on sünkroniseerimise konfliktid. - - Configure client-side TLS certificate - Seadista kliendipoolne TLS sertifikaat + + %1 has a sync conflict. Please check the conflict file! + %1 on sünkroniseerimiskonflikt. Palun kontrollige konfliktifaili! + + + + %1 and %n other file(s) could not be synced due to errors. See the log for details. + %1 ja veel %n faili ei sünkroniseeritud vigade tõttu. Täpsemaks infoks vaata logisid.%1 ja veel %n faili ei sünkroniseeritud vigade tõttu. Täpsemaks infoks vaata logisid. - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - + + %1 could not be synced due to an error. See the log for details. + %1 sünkroniseerimine ebaõnnestus vea tõttu. Lisainfot vaata logist. - - - OCC::OwncloudHttpCredsPage - - &Email - + + Sync Activity + Sünkroniseerimise tegevus - - Connect to %1 - Ühendu %1 + + Switching VFS mode on folder '%1' + VFS režiimi vahetamine kaustas '%1' - - Enter user credentials - Sisesta kasutajaandmed + + Could not read system exclude file + Süsteemi väljajätmiste faili lugemine ebaõnnestus - - - OCC::OwncloudOAuthCredsPage - - Connect to %1 - Ühendu %1 + + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. + Kaust %1 loodi, kuid oli varem sünkroniseerimisest välja jäetud. Selles olevaid andmeid ei sünkroniseerita. - - Login in your browser - + + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. + Fail %1 loodi, kuid jäeti varem sünkroniseerimisest välja. Seda ei sünkroniseerita. - - Copy link to clipboard - + + Changes in synchronized folders could not be tracked reliably. + +This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). + +%1 + Sünkroniseeritud kaustade muudatusi ei olnud võimalik usaldusväärselt jälgida. +See tähendab, et sünkroniseerimisklient ei pruugi kohalikke muudatusi kohe üles laadida, vaid otsib ainult kohalikke muudatusi ja laadib need üles aeg-ajalt (vaikimisi iga kahe tunni järel). + +% 1 - OCC::OwncloudSetupPage + OCC::FolderItemUpdater - - Connect to %1 - Ühendu %1 + + There are unresolved conflicts. + Mõned konfliktid on lahendamata. - - Setup %1 server - Seadista %1 server + + Sync error: %1 + + + + OCC::FolderMan - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - See url EI OLE turvaline, kuna see pole krüpteeritud. -Selle kasutamine pole soovitatav. + + Could not reset folder state + Ei suutnud tühistada kataloogi staatust - - This url is secure. You can use it. - See aadress on turvaline. Võid seda kasutada. + + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. + Leiti vana sünkroniseeringu žurnaal '%1', kuid selle eemaldamine ebaõnnenstus. Palun veendu, et seda ei kasutaks ükski programm. - - &Next > - &Edasi > + + Unsupported filesystem + Mittetoetatud failisüsteem - - Invalid URL - + + Account disconnected or paused + Ühendus kontoga on katkenud või peatatud - - Could not load certificate. Maybe wrong password? - + + User triggered force sync + Kasutaja käivitas sunnitud sünkroniseerimise + + + + Folder is about to be removed + Kaust on eemaldamisel - - - OCC::OwncloudSetupWizard - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">Edukalt ühendatud %1: %2 versioon %3 (4)</font><br/><br/> + + (backup) + (varukoopia) - - Failed to connect to %1 at %2:<br/>%3 - Ühendumine ebaõnnestus %1 %2-st:<br/>%3 + + (backup %1) + (varukoopia %1) - - Timeout while trying to connect to %1 at %2. + + The selected path is not a folder. - - Trying to connect to %1 at %2... - Püüan ühenduda %1 kohast %2 + + The folder %1 is used in a folder sync connection. + - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. + + Folder '%1' is already in use by application %2. - - There was an invalid response to an authenticated webdav request + + There is already a sync from the server to this local folder. - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. + + The local folder %1 already contains a folder used in a folder sync connection. - - Invalid URL + + The local folder %1 is already contained in a folder used in a folder sync connection. - - The server reported the following error: + + No valid folder selected. - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - Kohalik kataloog %1 on juba olemas. Valmistan selle ette sünkroniseerimiseks. + + The selected path does not exist. + - - Creating local sync folder %1... - Kohaliku kausta %1 sünkroonimise loomine ... + + You have no permission to write to the selected folder. + - - ok - ok + + Folder '%1' is already in use by another account. + Kaust '%1' on juba teise konto poolt kasutuses. - - failed. - ebaõnnestus. + + On macOS, only the Apple File System is supported. + MacOS puhul on toetatud ainult Apple File System + + + OCC::FolderModelController - - Could not create local folder %1 - Ei suuda tekitada kohalikku kataloogi %1 + + %1 options + - - No remote folder specified! - Ühtegi võrgukataloogi pole määratletud! + + Menu button with %1 options. Use the space key to pop the %2 options menu + + + + OCC::FolderWatcher - - Error: %1 - Viga: %1 + + The watcher did not receive a test notification. + Vaatleja ei saanud testiteadet. + + + OCC::FolderWatcherPrivate - - creating folder on ownCloud: %1 - loon uue kataloogi ownCloudi: %1 + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Antud probleem esineb tavaliselt, kui inotify-kellad on ammendunud. Vaadake KKK-st üksikasju. + + + OCC::FolderWizard - - Remote folder %1 created successfully. - Eemalolev kaust %1 on loodud. + + Virtual files are not available for the selected folder + Virtuaalsed failid ei ole valitud kausta jaoks saadaval - - The remote folder %1 already exists. Connecting it for syncing. - Serveris on kataloog %1 juba olemas. Ühendan selle sünkroniseerimiseks. + + Add Folder Sync Connection + Lisa kaustade sünkroniseerimise ühendus - - - The folder creation resulted in HTTP error code %1 - Kausta tekitamine lõppes HTTP veakoodiga %1 + + Add Sync Connection + Lisa sünkroniseerimisühendus + + + OCC::FolderWizardLocalPath - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Kataloogi loomine serverisse ebaõnnestus, kuna kasutajatõendid on valed!<br/>Palun kontrolli oma kasutajatunnust ja parooli.</p> + + Click to select a local folder to sync. + Klõpsa valimaks kohalikku sünkroniseeritavat kataloogi. - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">Serveris oleva kataloogi tekitamine ebaõnnestus tõenäoliselt valede kasutajatunnuste tõttu.</font><br/>Palun mine tagasi ning kontrolli kasutajatunnust ning parooli.</p> + + Enter the path to the local folder. + Sisesta kohaliku kataloogi asukoht. - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Kataloogi %1 tekitamine serverisse ebaõnnestus veaga <tt>%2</tt> + + Select the local folder + Valige kohalik kaust + + + OCC::FolderWizardSelectiveSync - - A sync connection from %1 to remote directory %2 was set up. - Loodi sünkroniseerimisühendus kataloogist %1 serveri kataloogi %2 + + Use virtual files instead of downloading content immediately + Kasuta virtuaalseid faile sisu kohese allalaadimise asemel + + + OCC::GETFileJob - - Successfully connected to %1! - Edukalt ühendatud %1! + + Connection Timeout + Ühenduse aegumine - - Connection to %1 could not be established. Please check again. - Ühenduse loomine %1 ebaõnnestus. Palun kontrolli uuesti. + + No E-Tag received from server, check Proxy/Gateway + Ühtegi E-Silti ei saabunud serverist, kontrolli proksi serverit/lüüsi. - - Folder rename failed - Kataloogi ümbernimetamine ebaõnnestus + + We received a different E-Tag for resuming. Retrying next time. + Saime jätkamiseks erineva E-Sildi. Proovin järgmine kord uuesti. - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Ei suuda eemaldada ning varundada kataloogi kuna kataloog või selles asuv fail on avatud mõne teise programmi poolt. Palun sulge kataloog või fail ning proovi uuesti või katkesta paigaldus. + + We received an unexpected download Content-Length. + Saime ootamatu allalaadimise sisupikkuse. - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>Kohalik kataloog %1 edukalt loodud!</b></font> + + Server returned wrong content-range + Server tagastas vale sisuvahemiku - OCC::OwncloudWizard + OCC::GraphApi::Space - - %1 Connection Wizard - %1 seadistamise juhendaja + + Personal + Isiklik - - Enable experimental feature? - + + Shares + Jagamised + + + OCC::IgnoreListEditor - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - + + Ignored Files Editor + Ignoreeritud failide redaktor - - Enable experimental mode - + + Files Ignored by Patterns + Mustri poolt ignoreeritud failid - - Stay safe - + + Add + Lisa - - - OCC::OwncloudWizardResultPage - - Everything set up! - Kõik on seadistatud! + + Pattern + Muster - - Open Local Folder - Ava Kohalik Kataloog + + Allow Deletion + Luba kustutamine - - Open %1 in Browser - Ava %1 veebilehitsejas + + Remove + Eemalda - - - OCC::PollJob - - Invalid JSON reply from the poll URL - + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Mustrile vastavaid faile või kaustu ei sünkroniseerita. Muudatused jõustuvad järgmisel sünkroniseerimisel. + +Üksused, mille kustutamine on lubatud, kustutatakse, kui need takistavad kataloogi eemaldamist. See on kasulik metaandmete jaoks. - - - OCC::PropagateDirectory - - Error writing metadata to the database - + + Could not open file + Ei suutunud avada faili + + + + Cannot write changes to '%1'. + Ei saa kirjutada muudatusi '%1'-le. - - - OCC::PropagateDownloadFile - - File %1 can not be downloaded because of a local file name clash! - Faili %1 ei saa alla laadida kuna on konflikt kohaliku faili nimega. + + Add Ignore Pattern + Lisa ignoreerimise muster - - The download would reduce free local disk space below the limit - + + Add a new ignore pattern: + Lisa uus ignoreerimise muster: - - Free space on disk is less than %1 - + + This entry is provided by the system at '%1' and cannot be modified in this view. + Selle kirje on pakkunud süsteem '%1' ning seda ei saa antud vaates muuta. + + + OCC::LocalActivityWidget - - File was deleted from server - Fail on serverist kustutatud + + Filter + Filter - - The file could not be downloaded completely. - Faili täielik allalaadimine ebaõnnestus. + + Local activity table + Kohalike tegevuste tabel - - The downloaded file is empty despite the server announced it should have been %1. - + + + Account + Konto - - File %1 cannot be saved because of a local file name clash! - Faili %1 ei saa salvestada kuna on nime konflikt kohaliku failiga! + + Filter menu + Filtri menüü - - File has changed since discovery - Faili on pärast avastamist muudetud + + Actions menu + Tegevuste menüü - - Error writing metadata to the database - + + Sort ascending by %1 + Sort järgi %1 kasvavalt - - - OCC::PropagateItemJob - - ; Restoration Failed: %1 - ; Taastamine ebaõnnestus: %1 + + Sort descending by %1 + Sorteeri %1 järgi kahanevalt - - A file or folder was removed from a read only share, but restoring failed: %1 - + + Retry sync + Korda sünkroniseerimist - OCC::PropagateLocalMkdir + OCC::LogBrowser - - could not delete file %1, error: %2 - ei saa kustutada faili %1, viga: %2 + + Log Output + Logi väljund - - Attention, possible case sensitivity clash with %1 - Tähelepanu, võimalik tõusutundliku nime konflikt %1-ga + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>Logid sisaldavad tundlikku teavet, mida te ei tohiks avalikult kättesaadavaks teha</b></span></p></body></html> - - could not create folder %1 - kausta %1 loomine ebaõnnestus + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + Klient saab silumislogisid kirjutada ajutisse kausta. Need logid on väga kasulikud probleemide tuvastamisel. +Kuna logifailid võivad kasvada suureks, siis iga sünkroniseerimise käivtamisel alustab klient uut ja pakib kokku vanemad. - - Error writing metadata to the database - + + Enable logging to temporary folder + Lubada logimine ajutisse kausta - - - OCC::PropagateLocalRemove - - Error removing '%1': %2; - Viga eemaldamisel '%1': %2; + + If enabled, logs will be written to: + Kui sisse lülitatud, siis kirjutatakse logid: - - Could not remove folder '%1' - Kausta '%1' eemaldamine ebaõnnestus + + C:/log + C:/log - - Could not remove %1 because of a local file name clash - Ei saa eemaldada %1 kuna on konflikt kohaliku faili nimega + + Log Http traffic + Logi Http liiklust - - - OCC::PropagateLocalRename - - File %1 can not be renamed to %2 because of a local file name clash - Faili %1 ei saa ümber nimetada %2-ks, kuna on konflikt kohaliku faili nimega + + Log files to keep: + Logifailid, mida säilitada: - - - Error writing metadata to the database - + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Need sätted jäävad püsima ka kliendi taaskäivitamisel. +Pange tähele, et mistahes logimise käsurea suvandite kasutamine tühistab seaded. - - - OCC::PropagateRemoteDelete - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Server saatis vale HTTP koodi. Ootuspärane kood oli 204, aga saadeti kood "%1 %2". + + Open folder + Ava kaust - OCC::PropagateRemoteMkdir + OCC::MainWindow - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Server saatis vale HTTP koodi. Ootuspärane kood oli 201, aga saadeti kood "%1 %2". + + Hide + Peida - - Error writing metadata to the database + + Main toolbar for the application - - - OCC::PropagateRemoteMove - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Server saatis vale HTTP koodi. Ootuspärane kood oli 201, aga saadeti kood "%1 %2". + + Main toolbar + - - - Error writing metadata to the database - + + + More + Rohkem - OCC::PropagateUploadFileCommon + OCC::MainWindowController - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists + + Add account... - - File Removed - Fail eemaldatud - - - - Local file changed during syncing. It will be resumed. - Kohalik fail muutus sünkroniseeringu käigus. See taastakse. + + Settings... + Seaded... - - Local file changed during sync. - Kohalik fail muutus sünkroniseeringu käigus. + + About... + Rakendusest... - - - Upload of %1 exceeds the quota for the folder - + + Quit + Lõpeta - - Error writing metadata to the database + + + Errors: %1 - - - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - Kohalik fail on eemaldatud sünkroniseeringu käigus. - - - Local file changed during sync. - Kohalik fail muutus sünkroniseeringu käigus. + + Activity + Tegevus - - Unexpected return code from server (%1) - + + Quit %1 + Lõpeta %1 - - Missing File ID from server - + + Are you sure you want to quit %1? + Kas olete kindel, et soovite %1-st väljuda? + + + OCC::ModalWrapperWidget - - Missing ETag from server - + + Close + Sulge - OCC::PropagateUploadFileV1 + OCC::Models - - Poll URL missing - Küsitluse URL puudub + + %1 Filter: + %1 Filter: - - The local file was removed during sync. - Kohalik fail on eemaldatud sünkroniseeringu käigus. + + All + Kõik - - Local file changed during sync. - Kohalik fail muutus sünkroniseeringu käigus. + + Reset Filters + Filtrite lähtestamine - - The server did not acknowledge the last chunk. (No e-tag was present) - + + Status Filter: + Staatusfilter: - OCC::ProtocolWidget + OCC::NetworkSettings - + Form Vorm - - TextLabel - Tekstisilt + + Pause synchronization when the Internet connection is metered + Peata sünkroniseerimine, kui internetiühendus on mahupõhine - - Open in browser - Ava veebilehitsejas + + Proxy Settings + Proksi seaded - - Time - Aeg + + No Proxy + Proksit pole - - File - Fail + + Use system proxy + Kasuta süsteemi proksit - - Folder - Kaust + + Host + Host - - Action - Tegevus + + Proxy Hostname + Proksi hosti nimi - - Size - Suurus + + : + : - - Local sync protocol - + + Proxy Port Number + Proksi pordi number - - Copy - Kopeeri + + Proxy server requires authentication + Proksi server nõuab autentimist - - Copy the activity list to the clipboard. - Kopeeri tegevuste nimistu puhvrisse. + + Note: proxy settings have no effects for accounts on localhost + Märkus: proxy seaded ei mõjuta localhosti kontosid. + + + + Specify proxy manually + Määra proksi käsitsi - - - OCC::ProxyAuthDialog - - Proxy authentication required - Vajalik on proksi autentimine + + Proxy type + Proksi tüüp - - Username: - Kasutajanimi: + + + Hostname of proxy server + Proksiserveri hostinimi - - Proxy: - Proksi: + + + Username for proxy server + Proksiserveri kasutajanimi - - The proxy server needs a username and password. - Proksi serveri jaoks on vajalik kasutajanimi ja parool. + + + Password for proxy server + Proksiserveri parool - - Password: - Parool: + + HTTP(S) proxy + HTTP(S) proksi - - TextLabel - Tekstisilt + + SOCKS5 proxy + SOCKS5 proksi - OCC::SelectiveSyncDialog + OCC::NewAccountBuilder - - Choose What to Sync - Vali, mida sünkroniseerida + + no error details are available + + + + + Unable to connect to server during account creation: %1. + - OCC::SelectiveSyncWidget + OCC::NewAccountWizard - - Loading ... - Laadimine ... + + Cancel Setup + Tühista seadistamine - - Deselect remote folders you do not wish to synchronize. + + Do you really want to cancel the account setup? + Kas te tõesti soovite konto seadistamise tühistada? + + + + OCC::NewAccountWizardController + + + Add account… - - Name - Nimi + + Back + Tagasi - - Size - Suurus + + Advanced + Täpsemad seaded - - - No subfolders currently on the server. - Serveris pole praegu alamkaustasid. + + Sign in + Logi sisse - - An error occurred while loading the list of sub folders. - + + Open sign in again + Ava sisselogimine uuesti - OCC::ServerNotificationHandler + OCC::NewVersionAvailableWidget + + + Skip this version + Jäta see versioon vahele + + + + Get update + Hangi uuendus + - - Dismiss - Jäta vahele + + Skip this time + Jäta seekord vahele - OCC::SettingsDialog + OCC::OAuth - - Settings - Seaded + + Successfully signed in + - - Activity - Toimingud + + Now, explore %1 on desktop. + - - General - Üldine + + Error returned from the server: <em>%1</em> + Serverist tagastatud viga:<em>%1</em> - - Network - Võrk + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + „Token” lõpp-punktile juurdepääsul ilmnes viga:<br><em>%1</em> - - Account - Konto + + Could not parse the JSON returned from the server: <br><em>%1</em> + Serverist tagastatud JSON-i parsimine ebaõnnestus: <br><em>%1</em> - - - OCC::SettingsDialogMac - - %1 - %1 + + Unsupported token type: %1 + Mittetoetatud tokeni tüüp: %1 - - Activity - Toimingud + + The reply from the server did not contain all expected fields +:%1 + Serveri vastus ei sisaldanud kõiki oodatud välju +:%1 - - General - Üldine + + Unknown Error + Tundmatu Viga - - Network - Võrk + + + Login Error + Sisselogimise viga - - - Account - Konto + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + + + + + Wrong user + Vale kasutaja - OCC::ShareDialog + OCC::OAuthPageController - - TextLabel - Tekstisilt + + Application Name Logo %1 + Rakendus Nimi Logo %1 - - share label - jagamise silt + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Jätke see kuva avatuks. Sisselogimine avaneb brauseris, et järgmise aadressiga ühenduda. - - Dialog - Dialoog + + Sign in URL + - - ownCloud Path: - ownCloudi kaust: + + To copy the sign in URL to the clipboard, use the copy button + - - %1 Sharing - %1 jagamine + + Copy URL to sign in + - - %1 - %1 + + Additional logo defined by the organization + Organisatsiooni poolt defineeritud täiendav logo - - Folder: %2 - Kaust: %2 + + URL copied + URL kopeeritud - - The server does not allow sharing - See server ei luba jagamist + + Copy URL + Kopeeri URL - - Retrieving maximum possible sharing permissions from server... + + Unable to connect to the login service. If the problem continues, please contact support. - - The file can not be shared because it was shared without sharing permission. + + Please copy the authentication URL using the button above, and provide this information to support. - - Users and Groups + + Failed to look up WebFinger instances: %1 - - Public Links - Avalikud lingid + + You are already connected to an account with these credentials. + Oled juba käesolevate kasutajatunnustega kontoga ühendatud. - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Jaga faili NewDocument.odt + + Unable to retrieve capabilities from server. + - - Upload only (File Drop) + + The server is not supported by this client. - - Receive files from others without revealing the contents of the folder. + + Error while trying to log in to OAuth2-enabled server. %1 - - TextLabel - Tekstisilt + + Unable to open browser with provided URL. %1 + - - Set &password - Määra &parool + + OAuth2 authentication requires a secured connection. %1 + - - Enter a name to create a new public link... + + Authorization server unreachable. %1 + + + OCC::OCUpdater - - &Create new - &Loo uus + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Ei suutnud uuendust alla laadida. Uuenduse käsitsi allalaadimiseks klõpsa palun <a href='%1'>siia</a> %2 - - Set &expiration date - Määra &aegumise kuupäev + + Could not check for new updates. + Uuenduste kontrollimine ebaõnnestus. - - Set password - Määra parool + + Downloading %1. Please wait… + - - Recipients can view or download contents. - + + %1 installed successfully. Restart the application to finish installing the update. + %1 paigaldati edukalt. Taaskäivitage rakendus, et lõpetada uuenduste paigaldamine. - - Download / View - + + %1 available. Restart application to start the update. + %1 saadaval. Uuendamise alustamiseks taaskäivitage rakendus. - - Download / View / Upload - + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Uus %1 saadaval. Uue AppImage'i käsitsi allalaadimiseks klõpsake <a href='%2'>siin</a>. - - Recipients can view, download, edit, delete and upload contents. - + + New %1 available. Please use the system's update tool to install it. + Uus %1 saadaval. Palun kasutage selle paigaldamiseks süsteemi uuendustööriista. - - Anyone with the link has access to the file/folder + + Checking update server… - - - P&assword protect - P&arooliga kaitstud + + Update status is unknown: Did not check for new updates. + Uuenduse staatus on teadmata: uuendusi ei kontrollitud. - - The file can not be shared because it was shared without sharing permission. - + + No updates available. Your installation is at the latest version. + Uuendusi pole saadaval. Kasutad kõige viimast versiooni. + + + OCC::OwncloudPropagator - - Link shares have been disabled - + + File %1 is currently in use + Fail %1 on hetkel kasutuses - - Create public link share + + vfs instance is not available. + + + OCC::ProcessDirectoryJob - - - Delete - Kustuta + + Symbolic links are not supported in syncing. + Sümboolsed lingid ei ole sünkroniseerimisel toetatud. - - Open link in browser - + + File is listed on the ignore list. + Fail on märgitud ignoreeritavate nimistus. - - Copy link to clipboard - + + File names ending with a period are not supported on this file system. + See failisüsteem ei toeta punktiga lõppevaid failinimesid. - - Copy link to clipboard (direct download) - + + File names containing the character '%1' are not supported on this file system. + See failisüsteem ei toeta märki '%1' sisaldavaid failinimesid. - - Send link by email - Saada link e-postiga + + File name contains at least one invalid character + Faili nimes on vähemalt üks keelatud märk - - Send link by email (direct download) - + + The file name is a reserved name on this file system. + Faili nimi on selles failisüsteemis reserveeritud nimi. + + + + Filename contains trailing spaces. + Faili nimi sisaldab lõpus tühikuid. + + + + Filename is too long. + Faili nimi on liiga pikk. + + + + File/Folder is ignored because it's hidden. + Faili/kausta ignoreeritakse, kuna see on peidetud. + + + + Stat failed. + Statistika ebaõnnestus. - - I shared %1 with you - + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Konflikt: serveri versioon on alla laaditud, kohalik koopia on ümber nimetatud ja üles laadimata. - - Confirm Link Share Deletion - + + The filename cannot be encoded on your file system. + Failinime ei saa teie failisüsteemis kodeerida. - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - + + The filename is blacklisted on the server. + Failinimi on serveris mustas nimekirjas. - - Cancel - Loobu + + + + + the VFS instance is missing. Unable to continue. + - - - Public link - Avalik link + + server reported no %1 + server vastas, et %1 puudub - - Delete link share + + + account was deleted. Unable to continue - - Public sh&aring requires a password - Avalik j&agamine nõuab parooli + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: Ignoreeritud, sest selle rada ei ole valitud - - Please Set Password - Palun määra parool + + Moved to invalid target, restoring + Teisaldatud kehtetule sihtmärgile, taastamine - - - OCC::ShareUserGroupWidget - - Share NewDocument.odt - Jaga faili NewDocument.odt + + Not allowed to remove, restoring + Eemaldamine pole lubatud, taastan - - Share with users or groups ... - Jaga kasutajate või gruppidega ... + + Error while reading the database. + - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> + + account was deleted. Unable to continue. - - The item is not shared with any users or groups - + + Not allowed because you don't have permission to add subfolders to that folder + Ei ole lubatud, kuna teil puuduvad õigused sellesse kausta alamkaustu lisada - - Open link in browser - + + Error while doing a rename, unhandled status code: %1 + Viga ümbernimetamisel, staatuse kood: %1 - - Copy link to clipboard - + + Not allowed because you don't have permission to add files in that folder + Ei ole lubatud, kuna teil puuduvad õigused sellesse kausta faile lisada - - Send link by email - Saada link e-postiga + + Not allowed to upload this file because it is read-only on the server, restoring + Pole lubatud üles laadida, kuna tegemist on ainult loetava serveriga, taastan - - No results for '%1' - + + This Space is currently unavailable + See ruum ei ole hetkel kättesaadav - - I shared something with you - + + Server replied with an error while reading directory '%1' : %2 + Server vastas veaga kataloogi '%1' lugemisel: %2 - OCC::ShareUserLine + OCC::PropagateDirectory - - Form - Vorm + + Error updating metadata: %1 + Viga metaandmete uuendamisel: %1 - - - TextLabel - Tekstisilt + + %1 the folder is currently in use + %1 kaust on hetkel kasutuses + + + OCC::PropagateDownloadFile - - can edit - saab muuta + + Failed to free up space, the file %1 is currently in use + Ruumi vabastamine ebaõnnestus, fail %1 on hetkel kasutuses - - can share - saab jagada + + + File %1 can not be downloaded because of a local file name clash with %2! + Faili %1 ei saa alla laadida, sest kohalik failinimi on vastuolus failinimega %2! - - ... - ... + + The download would reduce free local disk space below the limit + Allalaadimine vähendaks vaba kohalikku kettaruumi alla piirmäära - - create - loo + + Free space on disk is less than %1 + Vaba ruumi kettal on vähem kui %1 - - change - muuda + + File was deleted from server + Fail on serverist kustutatud - - delete - kustuta + + Broken webserver returned empty content length for non-empty file on resume + Rikkis veebiserver tagastas jätkamisel tühja sisu pikkuse mittetühja faili jaoks - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + The file could not be downloaded completely. + Faili täielik allalaadimine ebaõnnestus. - - - OCC::ShibbolethCredentials - - Login Error - Sisselogimise viga + + The downloaded file is empty despite the server announced it should have been %1. + Allalaaditud fail on tühi, kuigi server teatas, et see oleks pidanud olema %1. - - You must sign in as user %1 - Pead sisse logima kui kasutaja %1 + + File %1 cannot be saved because of a local file name clash with %2! + Faili %1 ei saa alla laadida, sest kohalik failinimi on vastuolus failinimega %2! - - - OCC::ShibbolethWebView - - %1 - Authenticate - %1 - autentimine + + Error updating metadata: %1 + Viga metaandmete uuendamisel: %1 - - SSL Chipher Debug View - + + + + + The file %1 is currently in use + Fail %1 on hetkel kasutuses - - Reauthentication required - Vajalik on uuesti autentimine + + + File has changed since discovery + Faili on pärast avastamist muudetud + + + OCC::PropagateItemJob - - Your session has expired. You need to re-login to continue to use the client. - Sinu sessioon on aegunud. Sa pead kliendi kasutamiseks uuesti sisse logima. + + ; Restoration Failed: %1 + ; Taastamine ebaõnnestus: %1 - OCC::SocketApi + OCC::PropagateLocalMkdir - - Share with %1 - parameter is ownCloud - Jagatud kasutajaga %1 + + could not delete file %1, error: %2 + ei saa kustutada faili %1, viga: %2 - - Context menu share - + + Can not create local folder %1 because of a local file name clash with %2 + Kohalikku kausta %1 ei saa luua, sest kohalik failinimi on vastuolus %2-ga - - I shared something with you - + + Error updating metadata: %1 + Viga metaandmete uuendamisel: %1 - - - Share... - + + The file %1 is currently in use + Fail %1 on hetkel kasutuses - - - Copy private link to clipboard - + + could not create folder %1 + kausta %1 loomine ebaõnnestus + + + OCC::PropagateLocalRemove - - Send private link by email... - + + %1 failed with: %2 + %1 ebaõnnestus: %2 - - Resharing this file is not allowed - + + Could not remove %1 because of a local file name clash with %2! + Ei saanud eemaldada %1, sest kohalik failinimi on vastuolus %2-ga! - - - Copy public link to clipboard - + + Could not move '%1' to the trash bin + Ei suutnud liigutada '%1' prügikasti - - Open in browser - Ava veebilehitsejas - - - - Download file(s) - - - - - Replace file(s) by virtual file - + + + %1 the file is currently in use + %1 fail on hetkel kasutuses - OCC::SslButton + OCC::PropagateLocalRename + + + File %1 can not be renamed to %2 because of a local file name clash + Faili %1 ei saa ümber nimetada %2-ks, kuna on konflikt kohaliku faili nimega + + + + Could not rename %1 to %2, the file is currently in use + %1 ei saanud ümber nimetada %2, kuna fail on hetkel kasutuses + - - <h3>Certificate Details</h3> - <h3>Sertifikaadi detailid</h3> + + Error updating metadata: %1 + Viga metaandmete uuendamisel: %1 - - Common Name (CN): - Üldine nimi (CN): + + The file %1 is currently in use + Fail %1 on hetkel kasutuses - - Subject Alternative Names: - Subjekti Alternatiivsed Nimed: + + Failed to rename file + Faili ümbernimetamine ebaõnnestus - - Organization (O): - Organisatsioon (O): + + Error setting pin state + Viga pin-koodi seisu seadistamisel + + + OCC::PropagateRemoteDelete - - Organizational Unit (OU): - Organisatsiooni üksus (OU): + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Server saatis vale HTTP koodi. Ootuspärane kood oli 204, aga saadeti kood "%1 %2". + + + OCC::PropagateRemoteMkdir - - State/Province: - Riik/Maakond: + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Server saatis vale HTTP koodi. Ootuspärane kood oli 201, aga saadeti kood "%1 %2". - - Country: - Riik: + + Error writing metadata to the database: %1 + Viga metaandmete kirjutamisel andmebaasi: %1 - - Serial: - Järjenumber: + + Setting file status failed due to file lock + Faili oleku seadistamine ebaõnnestus faililuku tõttu + + + OCC::PropagateRemoteMove - - <h3>Issuer</h3> - <h3>Väljastaja</h3> + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Server saatis vale HTTP koodi. Ootuspärane kood oli 201, aga saadeti kood "%1 %2". - - Issuer: - Väljastaja: + + Error updating metadata: %1 + Viga metaandmete uuendamisel: %1 - - Issued on: - Väljastatud: + + The file %1 is currently in use + Fail %1 on hetkel kasutuses - - Expires on: - Aegub: + + Error writing metadata to the database + Viga metaandmete kirjutamisel andmebaasi - - <h3>Fingerprints</h3> - <h3>Sõrmejäljendid</h3> + + Error setting pin state + Viga pin-koodi seisu seadistamisel + + + OCC::PropagateUpdateMetaDataJob - - SHA-256: - SHA-256: + + Could not update file : %1 + Faili ei saanud uuendada: %1 - - SHA-1: - SHA-1: + + The file %1 is currently in use + Fail %1 on hetkel kasutuses + + + OCC::PropagateUploadCommon - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Märkus</b>See sertifikaat on käsitsi heakskiidetud</p> + + Local file changed during sync. It will be resumed. + Kohalik fail muutus sünkroniseerimise ajal. Seda jätkatakse. - - %1 (self-signed) - %1 (oma-signeering) + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + Faili %1 ei saa üles laadida, sest on olemas teine samanimeline fail, mis erineb ainult tõstutundlikkuse poolest. - - %1 - %1 + + + Upload of %1 exceeds the quota for the folder + %1 üleslaadimine ületab kausta piirmäära - - This connection is encrypted using %1 bit %2. - - Ühendus on krüpteeritud kasutades %1 bitt %2 - + + + %1 the file is currently in use + %1 fail on hetkel kasutuses - - No support for SSL session tickets/identifiers - + + File Removed + Fail eemaldatud - - Certificate information: - Seritifikaadi informatsioon: + + The server did not provide the file permissions + Server ei väljastanud faili õiguseid - - This connection is NOT secure as it is not encrypted. - - See url EI OLE turvaline, kuna see pole krüpteeritud. - + + Error updating metadata: %1 + Viga metaandmete uuendamisel: %1 - OCC::SslErrorDialog + OCC::PropagateUploadFile - - Form - Vorm + + %1 the file is currently in use + %1 fail on hetkel kasutuses - - Trust this certificate anyway - Usalda siiski seda sertifikaati + + The server did ask for a removed legacy feature(polling) + Server päris eemaldatud pärandfunktsiooni (polling) - - Untrusted Certificate - Tundmatu sertifikaat + + The local file was removed during sync. + Kohalik fail on eemaldatud sünkroniseeringu käigus. - - Cannot connect securely to <i>%1</i>: + + The server did not acknowledge the file upload. (No e-tag was present) + + + OCC::PropagateUploadFileTUS + + + %1 the file is currently in use + %1 fail on hetkel kasutuses + - - with Certificate %1 - sertifikaadiga %1 + + Checksum computation failed + Kontrollsumma arvutus ebaõnnestus - - - - &lt;not specified&gt; - &lt;pole määratud&gt; + + The local file was removed during sync. + Kohalik fail on eemaldatud sünkroniseerimise käigus. + + + OCC::ProtocolItemModel - - - Organization: %1 - Organisatsioon: %1 + + Time + Aeg - - - Unit: %1 - Ühik: %1 + + File + Fail - - - Country: %1 - Riik: %1 + + Folder + Kaust - - Fingerprint (MD5): <tt>%1</tt> - Sõrmejälg (MD5): <tt>%1</tt> + + Issues + Vead - - Fingerprint (SHA1): <tt>%1</tt> - Sõrmejälg (SHA1): <tt>%1</tt> + + Action + Tegevus - - Effective Date: %1 - Efektiivne kuupäev: %1 + + Size + Suurus - - Expiration Date: %1 - Aegumise kuupäev: %1 + + Account + Konto - - Issuer: %1 - Esitaja: %1 + + Status + Staatus - OCC::SyncEngine + OCC::RequestAuthenticationController - - Success. - Korras. + + IdP is unreachable. Contact your system administrator or try again later. + - - A network connection timeout happened. - Toimus võrgukatkestus. + + Authentication failed. + Autentimine ebaõnnestus. + + + OCC::RequestAuthenticationWidget - - A HTTP transmission error happened. - HTTP ülekande viga. + + Application Name Logo %1 + Rakendus Nimi Logo %1 - - The mounted folder is temporarily not available on the server + + Sign in required - - An error occurred while opening a folder + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. - - Error while reading folder. + + Sign in URL - - %1 (skipped due to earlier error, trying again in %2) + + To copy the sign in URL to the clipboard, use the copy button - - File/Folder is ignored because it's hidden. + + Copy URL to sign in - - Folder hierarchy is too deep - + + Additional logo defined by the organization + Organisatsiooni poolt defineeritud täiendav logo - - Conflict: Server version downloaded, local copy renamed and not uploaded. - + + Stay logged out + Jää väljalogituks - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - + + Sign in + Logi sisse - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - + + URL copied + URL kopeeritud - - Not allowed because you don't have permission to add parent folder - + + Copy URL + Kopeeri URL + + + OCC::ResolveUrlAdapter - - Not allowed because you don't have permission to add files in that folder - + + SSL failure when connecting to server at %1 + SSL viga kui ühenduti serveriga aadressil %1 - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - + + Could not detect compatible server at %1 + Ühilduvat serverit asukohas %1 ei õnnestunud tuvastada - - There is insufficient space available on the server for some uploads. - + + Resolved url is invalid %1 + Lahendatud url on vigane %1 - - CSync unspecified error. - CSync tuvastamatu viga. + + Rejected redirect from %1 to %2 + Ümbersuunamine asukohast %1 asukohta %2 lükati tagasi + + + OCC::SelectiveSyncWidget - - Aborted by the user - Kasutaja poolt tühistatud + + Loading … + Laadimine ... - - Failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder. - + + Deselect remote folders you do not wish to synchronize. + Tühjendage kaugkaustade valik, mida te ei soovi sünkroniseerida. - - Discovery step failed. - + + Name + Nimi - - Permission denied. - + + Size + Suurus - - File or directory not found: - + + + Currently there are no subfolders on the server. + Hetkel ei ole serveris alamkaustasid. - - Tried to create a folder that already exists. - + + An error occurred while loading the list of subfolders. + Alamkaustade nimekirja laadimisel tekkis viga. + + + OCC::SettingsView - - No space on %1 server available. - + + Form + Vorm - - The service is temporarily unavailable - Teenus pole ajutiselt saadaval + + General Settings + Üldised seaded - - Access is forbidden - Ligipääs on keelatud + + Use Monochrome Icons in the system tray + Kasuta menüüribal mustvalgeid ikoone - - An internal error number %1 occurred. - + + Show Desktop Notifications + Näita töölaua teavitusi - - Symbolic links are not supported in syncing. - Sümboolsed lingid ei ole sünkroniseerimisel toetatud. + + Start on Login + Käivita sisselogimisel - - File is listed on the ignore list. - Fail on märgitud ignoreeritavate nimistus. + + Language + Keel - - File names ending with a period are not supported on this file system. - + + Language selector + Keelte valik - - File names containing the character '%1' are not supported on this file system. - + + Advanced + Täpsemad seaded - - The file name is a reserved name on this file system. - + + Sync hidden files + Sünkroniseeri peidetud failid - - Filename contains trailing spaces. - + + Show crash reporter + Näita kokkujooksmise teavitajat - - Filename is too long. - Faili nimi on liiga pikk. + + Move remotely deleted files to the local trash bin instead of deleting them + Liiguta kaugelt kustutatud failid kohalikku prügikasti, selle asemel et neid kustutada. - - The filename cannot be encoded on your file system. - + + Edit Ignored Files + Redigeeri ignoreeritud faile - - Unresolved conflict. - + + Log Settings + Logi seaded - - Stat failed. - + + Network + Võrk - - Filename encoding is not valid - Failinime kodeering pole kehtiv + + Warning + Hoiatus - - Invalid characters, please rename "%1" - + + Language changes require a restart of this application to take effect. + Keelemuudatuste jõustamiseks tuleb see rakendus taaskäivitada. - - Using virtual files but suffix is not set - + + You cannot disable autostart because system-wide autostart is enabled. + Automaatkäivitust ei saa keelata, sest kogu süsteemi hõlmav automaatkäivitus on lubatud. - - Unable to read the blacklist from the local database - + + (use default) + (kasuta vaikimisi) - - Unable to read from the sync journal. - + + unknown (%1) + tundmatu (%1) + + + OCC::SocketApi - - Cannot open the sync journal - Ei suuda avada sünkroniseeringu zurnaali + + Share with %1 + parameter is the application name + Jaga kasutajaga %1 - - File name contains at least one invalid character - Faili nimesonvähemalt üks keelatud märk + + Confirm deletion + Kinnita kustutamine - - - Ignored because of the "choose what to sync" blacklist - "Vali, mida sünkroniseerida" musta nimekirja tõttu vahele jäetud + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Kas soovite kataloogi <i>%1</i> ja kogu selle sisu jäädavalt kustutada? - - Not allowed because you don't have permission to add subfolders to that folder - + + Do you want to delete the file <i>%1</i> permanently? + Kas soovite faili <i>%1</i> jäädavalt kustutada? - - Not allowed to upload this file because it is read-only on the server, restoring - Pole lubatud üles laadida, kuna tegemist on ainult-loetava serveriga, taastan + + Error + Viga - - - Not allowed to remove, restoring - Eemaldamine pole lubatud, taastan + + Moving file failed: + +%1 + Faili teisaldamine ebaõnnestus: + +%1 - - Local files and share folder removed. - Kohalikud failid ja jagatud kaustad eemaldatud. + + I shared something with you + Jagasin teiega midagi - - Move not allowed, item restored - Liigutamine pole lubatud, üksus taastatud + + Select new location… + - - Move not allowed because %1 is read-only - Liigutamien pole võimalik kuna %1 on ainult lugemiseks + + + Share… + - - the destination - sihtkoht + + + Copy private link to clipboard + Kopeeri privaatne link lõikelauale - - the source - allikas + + Resharing this file is not allowed + Selle faili edasijagamine ei ole lubatud - - - OCC::SyncLogDialog - - Synchronisation Log - Sünkroonimise logi + + Resharing this folder is not allowed + Selle kausta edasijagamine ei ole lubatud - - - OCC::Systray - - %1: %2 - %1: %2 + + Open in %1 + Ava asukohas %1 - - - OCC::Theme - - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p> - + + Show file versions in web browser + Näita faili versioone veebisirvijas - - <p>Copyright ownCloud GmbH</p> + + Rename… - - <p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%2 and the %2 logo are registered trademarks of %1 in the United States, other countries, or both.</p> + + Rename and upload… - - - OCC::ValidateChecksumHeader - - The checksum header is malformed. + + + Move and rename… - - The checksum header contained an unknown checksum type '%1' + + Move, rename and upload… - - The downloaded file does not match the checksum, it will be resumed. + + Move and upload… - - - OCC::ownCloudGui - - - Please sign in - Palun logi sisse - - - - Folder %1: %2 - Kaust %1: %2 - - - There are no sync folders configured. - Sünkroniseeritavaid kaustasid pole määratud. + + Delete local changes + Kustuta kohalikud muudatused - - Open in browser - Ava veebilehitsejas + + Delete + Kustuta + + + OCC::SpacesPage - - - - Log in... - Logi sisse... + + Select a folder to sync it to your computer. + Vali kaust oma arvutisse sünkroniseerimiseks. - - - - Log out - Logi välja + + Select a Space to sync it to your computer. + Vali ruum oma arvutisse sünkroniseerimiseks. + + + OCC::SyncEngine - - Recent Changes - Hiljutised muudatused + + %1 (skipped due to earlier error, trying again in %2) + %1 (jäeti vahele varasema vea tõttu, uuestiproovimine %2 pärast) - - Managed Folders: - Hallatavad kaustad: + + Unable to sync: the local folder is missing. + - - Open folder '%1' - Ava kaust '%1' + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Ainult %1 on saadaval, alustamiseks on vaja vähemalt %2 - - Open %1 in browser - Ava %1 veebilehitsejas + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Kohaliku sünkroniseerimisandmebaasi avamine või loomine ei ole võimalik. Veenduge, et teil oleks sünkroniseerimiskaustas kirjutamisõigus. - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + + + + + Cannot open the sync journal. - - Unknown status - Tundmatu staatus + + Aborted due to %1 + Katkestati %1 tõttu - - Settings... - Seaded... + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Kettaruumi on vähe: Allalaadimised, mis vähendaksid vaba ruumi alla %1, jäeti vahele. - - Details... - Üksikasjad... + + There is insufficient space available on the server for some uploads. + Serveris piisavalt ruumi mõne üleslaadimise jaoks. - - Help - Abiinfo + + application exit + abort reason + rakenduse lõpetamine - - Quit %1 - Lõpeta %1 + + Unable to read the blacklist from the local database + Musta nimekirja ei saa kohalikust andmebaasist lugeda - - Disconnected from %1 - + + Unable to read from the sync journal. + Sünkroniseerimispäevikust ei saa lugeda. + + + OCC::SyncErrorWidget - - Unsupported Server Version - + + Issues table + Vigade tabel - - Disconnected - Lahti ühendatud + + There were too many issues. Not all will be visible here. + Esines liiga palju vigu. Kõiki ei ole võimalik kuvada. - - Disconnected from some accounts - + + There were conflicts. Check the documentation on how to resolve them. + Tekkisid konfliktid. Lahendamiseks vaadake dokumentatsiooni. - - Disconnected from accounts: - Kontodest lahtiühendatud + + The file %1 was ignored as its name is reserved by %2 + Faili %1 ignoreeriti, kuna selle nimi on reserveeritud %2 poolt. - - Account %1: %2 - Konto %1: %2 + + Reset column sizes + Lähtesta veergude suurused - - Signed out - Välja logitud + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Tekkisid konfliktid. <a href="%1">Lahendamiseks vaadake dokumentatsiooni.</a> - - Account synchronization is disabled - + + Filter menu + Filtri menüü - - - Synchronization is paused - + + Account + Konto - - Unresolved conflicts - + + Filter + Filter + + + OCC::SyncJournalDb - - Error during synchronization - + + Failed to connect database. + Andmebaasi ühendamine ebaõnnestus. + + + OCC::Theme - - No sync folders configured - + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Versioon %1. Lisainformatsiooni saamiseks külastage <a href="%2">https://%3</a></p><p> 3Teadaolevate vigade ja abi lugemiseks külastage: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>Autorid: Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller jt.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Turustatud %4 poolt ja litsenseeritud vastavalt GNU General Public License (GPL) versioonile 2.0.<br/>%5 ja %5 logo on %4 registreeritud kaubamärgid USA-s, teistes riikides või mõlemas.</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Unpause all folders - + + TLS Certificate Error + TLS sertifikaadi viga - - Pause all folders - + + Do you want to trust this certificate anyway? + Kas sa soovid siiski seda sertifikaati usaldada? - - Unpause all synchronization - + + Cannot connect securely to %1 + Ei saa turvaliselt ühendada %1 - - Unpause synchronization - + + &lt;not specified&gt; + &lt;pole määratud&gt; - - Pause all synchronization - + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>sertifikaadiga %1</h3><div id="ccert"><p>Organisatsioon: %2<br/>Üksus: %3<br/>Maa: %4</p><p>Sõrmejälg (MD5): <tt>%5</tt><br/>Sõrmejälg (SHA1): <tt>%6</tt><br/>Sõrmejälg (SHA256): <tt>%7</tt><br/><br/>Kehtiv kuupäev: %8Aegumiskuupäev: %9</div><h3>Väljaandja: %10</h3><div id="issuer"><p>Organisatsioon: %11<br/>Üksus: %12<br/>Maa: %13</p></div></div> + + + OCC::TrayMenuController - - Pause synchronization - + + Unsupported Server Version + Toetamata serveri versioon - - Log out of all accounts - Logi välja kõikidelt kontodelt + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + Konto %1 server on kasutab mittetoetatud versiooni %2. Selle kliendi kasutamine koos toetamata serveriversioonidega on testimata ja potentsiaalselt ohtlik. Tegutsege omal vastutusel. - - Log in to all accounts... - Logi sisse kõikidesse kontodesse... + + Show %1 + Näita %1 - - New account... - + + Help + Abiinfo - - + About %1 Rakendusest %1 - - No items synced recently - Ühtegi üksust pole hiljuti sünkroniseeritud + + Quit %1 + Lõpeta %1 + + + OCC::UpdateDownloadedWidget - - Checking for changes in remote '%1' - + + Restart later + Taaskäivita hiljem - - Checking for changes in local '%1' - + + Restart now + Taaskäivita kohe + + + OCC::UpdaterScheduler - - Syncing %1 of %2 (%3 left) - Sünkroniseerin %1 %2-st (%3 veel) + + Update available + Uuendus saadaval + + + OCC::UrlPageController - - Syncing %1 of %2 + + Your web browser will be opened to complete sign in. - - Syncing %1 (%2 left) - Sünkroniseerin %1 (%2 veel) + + %1 logo + %1 logo - - Syncing %1 - Sünkroniseerimine %1 + + Welcome to %1 + Tere tulemast %1'i - - %1 (%2, %3) - %1 (%2, %3) + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Alustamiseks sisesta oma serveri aadress. Sisselogimise lõpuleviimiseks avatakse veebibrauser. - - Up to date - Ajakohane + + Server address field + - - - OwncloudAdvancedSetupPage - - Form - Vorm + + Enter your server address here + - - - - - - - - TextLabel - Tekstisilt + + Additional logo defined by the organization + Organisatsiooni poolt defineeritud täiendav logo - - Server - Server + + Invalid server URL: %1 + Vigane serveri URL: %1 - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - + + Invalid URL scheme. Only https is accepted. + Vigane URL skeem. Ainult https on lubatud. - - Start a &clean sync (Erases the local folder!) - + + The requested URL failed with query value: %1 + Päring küsitud URL-ile ebaõnnestus päringu väärtusega: %1 + + + OCC::UserInfoAdapter - - Ask for confirmation before synchroni&zing folders larger than - + + Network request to collect user info failed + Võrgupäring kasutajainfo kogumiseks ebaõnnestus - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + Unable to retrieve user info: invalid credentials. + Kasutaja info pärimine ebaõnnestus: vigased kasutajatunnused. - - Ask for confirmation before synchronizing e&xternal storages - + + Unexpected network response when retrieving user info. + Ootamatu võrguvastus kasutajainfo pärimisel. - - Choose what to sync - Vali, mida sünkroniseerida + + Retrieving user info failed with JSON error. + Kasutajainfo pärimine ebaõnnestus – vigane JSON. + + + OCC::ValidateChecksumHeader - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + Allalaaditud fail ei vasta kontrollsummale, seda jätkatakse. '%1' != '%2' + + + OCC::Vfs - - Manually create folder sync connections - + + The Virtual filesystem feature does not support a drive as sync root + Virtuaalse failisüsteemi funktsioon ei toeta salvestusseadet sünkroniseerimise juur-rajana - - &Local Folder - &Kohalik kataloog + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + Virtuaalse failisüsteemi funktsioon nõuab NTFS-failisüsteemi, %1 kasutab %2. - - pbSelectLocalFolder - pbSelectLocalFolder + + The Virtual filesystem feature is not supported on network drives + Virtuaalse failisüsteemi funktsioon ei ole võrguketastel toetatud + + + OCC::VfsWinPrivate - - &Keep local data - &Säilita kohalikud andmed + + + %1 - %2 + %1 - %2 - - S&ynchronize everything from server (recommended) + + Could not find StorageProviderState property %1 - - Use virtual files instead of downloading content immediately (e&xperimental) + + Could not find StorageFolder for %1 error: %2 - - Status message - Staatuse teade - - - - OwncloudHttpCredsPage - - - Form - Vorm - - - - &Username - &Kasutajatunnus + + Could not retrieve StorageFolder for %1 %2 (0x%3) + - - &Password - &Parool + + The folder is used by a different client: %1 + - - - OwncloudOAuthCredsPage - - Form - Vorm + + Error registering StorageProvider for %1: %2 (0x%3) + - - Please switch to your browser to proceed. + + Unable to connect sync root: %1 error: %2 - - An error occurred while connecting. Please try again. + + %1 is managed by another sync client - - Re-open Browser + + Downgrading the folder from %1 to %2 is not supported - OwncloudSetupPage + OCC::WebFingerLookupAdapter - - Form - Vorm + + Failed to retrieve user info + Kasutaja info kättesaamine ebaõnnestus - - - TextLabel - Tekstisilt + + WebFinger lookup returned no links + WebFinger otsing ei tagastanud ühtegi linki + + + OCC::WindowsUpdater - - Ser&ver Address - + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Uus versioon %1 kliendist on saadaval.</p><p><b>%2</b> on saadaval allalaadimiseks. Paigaldatud on versioon %3.<p> - - https://... - https://... + + Skip this version + Jäta see versioon vahele - - Error Label - Vea silt + + Update Failed + Uuendamine ebaõnnestus - - - OwncloudWizardResultPage - - Form - Vorm + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Kliendi %1 uus versioon on saadaval, kuid värskendamine nurjus.</p><p><b>%2</b> on alla laaditud. Installitud versioon on %3.</p> - - TextLabel - TekstiSilt + + Ask again later + Küsi hiljem uuesti - - Your entire account is synced to the local folder - Kogu Su konto sünkroniseeriti kohalikku kataloogi + + Restart and update + Taaskäivita ja uuenda - - - PushButton - Nupp + + Update manually + Uuenda käsitsi QObject - + in the future tulevikus - + %n day(s) ago - + %n päev tagasi%n päeva tagasi - + %n hour(s) ago - + %n tundi tagasi - + now kohe - - Less than a minute ago - Vähem kui minut tagasi + + less than a minute ago + vähem kui minut tagasi - + %n minute(s) ago - - - - - Some time ago - Mõni aeg tagasi + %n minutit tagasi - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 - Utility + SpacesBrowser + + + Form + Vorm + + + + SpacesPage + + + Spaces list + Ruumide nimekiri + + + + SpacesView + + + Spaces + Ruumid + + + + SyncFileItem::Status + + + Undefined + Määratlemata + + + + Fatal Error + Saatuslik viga + + + + + Error + Viga + + + + Info + Info + + + + Success + Korras + + + + Conflict + Konflikt + + + + File Ignored + Faili ignoreeriti + - - %L1 GB - %L1 GB + + Restored + Taastatud - - %L1 MB - %L1 MB + + Blacklisted + Mustas nimekirjas - - %L1 KB - %L1 KB + + Excluded + Välistatud - - %L1 B - %L1 B + + Message + Sõnum + + + Filename Reserved + Failinimi on reserveeritud + + + + UpdateDownloadedWidget + + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Taaskäivitamine on nõutud</span></p></body></html> + + + + Utility - + %n year(s) - + %n aasta%n aastat - + %n month(s) - + %n kuu%n kuud - + %n day(s) - + %n päeva%n päeva - + %n hour(s) - + %n tund%n tundi - + %n minute(s) - + %n minut%n nimutit - + %n second(s) - + %n sekund%n sekundit - + %1 %2 %1 %2 + + + Shutting down %1 + Väljalülitamine %1 + - main.cpp + account loading - - System Tray not available - Süsteemisalv pole saadaval + + Error accessing the configuration file + Viga seadete faili pääsemisel - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 vajab tööks süsteemisalve. Kui Sa kasutad XFCE-d, palun järgi <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">neid juhiseid</a>. Muudel juhtudel palun paigalda süsteemisalve rakendus nagu näiteks 'trayer' ning proovi uuesti. + + There was an error while accessing the configuration file at %1. + Tekkis viga pääsuga seadete faili aadressil %1. - ownCloudTheme::about() + ownCloudTheme::aboutVersions() - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Qt teegid %3, %4%7Kasutatakse virtuaalsete failide pluginat: %5%7%6 + + + + ownCloudTheme::qtVer + + + %1 (Built against Qt %2) + %1 (Ehitatud vastavalt Qt %2) + + + + ownCloudTheme::versionWithSha + + + %1 %2 + %1 %2 + + + + pinstate + + + Always available locally + Alati saadaval kohalikus seadmes + + + + Currently available locally + Hetkel saadaval kohalikus seadmes + + + + Some available online only + Mõned on saadaval vaid võrguühendusega + + + + Available online only + Saadaval vaid võrguühendusega progress - + + Virtual file created + Virtuaalne fail loodud + + + + Replaced by virtual file + Asendatud virtuaalse failiga + + + Downloaded Allalaetud - + Uploaded Üles laetud - - Server version downloaded, copied changed local file into conflict file - - - - + Deleted Kustutatud - - Moved to %1 - Tõstetud %1 - - - + Ignored Eiratud - - Filesystem access error - Failisüsteemi ligipääsu viga + + Server version downloaded, local copy was backed up as conflict file + Serveri versioon laeti alla, kohalik koopia varundati konfliktse failina + Renamed %1 to %2 + + + + + Moved %1 to %2 + + + + Error Viga - + Updated local metadata - + Uuendatud kohalikud metaandmed - - + + Unknown Tundmatu - + downloading allalaadimine - + uploading üleslaadimine - + deleting kustutan - + moving liigutan - + ignoring eiran - - + error viga - + updating local metadata - + kohalike metaandmete uuendamine - theme - - - Status undefined - Staatus on määramata - - - - Waiting to start sync - Oodatakse sünkroniseerimise alustamist - - - - Sync is running - Sünkroniseerimine on käimas - - - - Sync Success - Sünkroniseerimine on tehtud - + utility - - Sync Success, some files were ignored. - Sünkroniseering õnnestus, mõnda faili ignoreeriti. + + Could not open browser + Brauserit ei õnnestunud avada - - Sync Error - Sünkroniseerimise viga + + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? + Brauseri käivitamisel URL-le %1 minekuks tekkis viga. Võib-olla ei ole vaikimisi brauserit konfigureeritud? - - Setup Error - Viga sünkroniseerimisel + + Could not open email client + Meiliklienti ei õnnestunud avada - - Preparing to sync - Sünkroniseerimiseks valmistumine + + There was an error when launching the email client to create a new message. Maybe no default email client is configured? + Meilikliendi käivitamisel uue kirja loomiseks ilmnes viga. Võib-olla ei ole vaikimisi e-posti klienti seadistatud? - - Aborting... - Tühistamine ... + + Make always available locally + Teha alati kohalikus seadmes kättesaadavaks - - Sync is paused - Sünkroniseerimine on peatatud + + Free up local space + Vabasta ruum kohalikus seadmes - utility - - - Could not open browser - - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - - + version check - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - + + Some settings were configured in newer versions of this client and use features that are not available in this version + Mõned sätted olid seadistatud selle kliendi uuemas versioonis ja kasutavad võimalusi, misa käesolevas versioonis ei ole saadaval \ No newline at end of file diff --git a/translations/client_eu.ts b/translations/client_eu.ts deleted file mode 100644 index f669c5d3d67..00000000000 --- a/translations/client_eu.ts +++ /dev/null @@ -1,4419 +0,0 @@ - - - FileSystem - - - Could not make directories in trash - Ezin da zakarrontzian direktoriorik sortu - - - - - Could not move '%1' to '%2' - Ezin da '%1' '%2'-ra mugitu - - - - Moving to the trash is not implemented on this platform - Errore ezezaguna: sare erantzuna ezabatua izan da - - - - FolderWizardSourcePage - - - Form - Formularioa - - - - Pick a local folder on your computer to sync - Hautatu zure ordenagailuko karpeta lokala sinkronizatzeko - - - - &Choose... - &Aukeratu... - - - - FolderWizardTargetPage - - - Form - Formularioa - - - - Select a remote destination folder - Hautatu hurruneko helburu karpeta - - - - Create Folder - Sortu Karpeta - - - - Refresh - Birkargatu - - - - Folders - Karpetak - - - - TextLabel - TestuEtiketa - - - - NotificationWidget - - - Form - Formularioa - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - TestuEtiketa - - - - OCC::AbstractNetworkJob - - - Connection timed out - Konexioa denboraz kanpo - - - - Unknown error: network reply was deleted - - - - - Server replied "%1 %2" to "%3 %4" - - - - - OCC::AccountSettings - - - Form - Formularioa - - - - ... - ... - - - - Storage space: ... - Biltegiratze ahalmena: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Desmarkatutako karpetak zure bertako fitxategi sistematik <b>ezabatuko</b> dira eta ez dira gehiago ordenagailu honekin sinkronizatuko - - - - Synchronize all - Sinkronizatu dena - - - - Synchronize none - Ez sinkronizatu ezer - - - - Apply manual changes - Aplikatu eskuzko aldaketak - - - - Apply - Aplikatu - - - - - - Cancel - Ezeztatu - - - - Connected with <server> as <user> - <user> bezala <server>-n konektatuta - - - - No account configured. - Ez da konturik konfiguratu. - - - - Add new - Gehitu berria - - - - Remove - Ezabatu - - - - Account - Kontua - - - - Choose what to sync - Hautatu zer sinkronizatu - - - - Force sync now - Behartu orain sinkronizatzen - - - - Restart sync - Berrabiarazi sinkronizazioa - - - - Remove folder sync connection - Ezabatu karpeta honen konexioa - - - - Create virtual files for new files (Experimental) - - - - - Folder creation failed - Karpeta sortzeak huts egin du - - - - <p>Could not create local folder <i>%1</i>. - <p>Ezin izan da <i>%1</i> bertako karpeta sortu. - - - - Confirm Folder Sync Connection Removal - Baieztatu Karpetaren Konexioaren Ezabatzea - - - - Remove Folder Sync Connection - Ezabatu Karpeta Honen Konexioa - - - - Sync Running - Sinkronizazioa martxan da - - - - The syncing operation is running.<br/>Do you want to terminate it? - Sinkronizazio martxan da.<br/>Bukatu nahi al duzu? - - - - %1 in use - %1 erabiltzen - - - - %1 as <i>%2</i> - - - - - Connected to %1. - %1ra konektatuta. - - - - Server %1 is temporarily unavailable. - %1 zerbitzaria ez dago orain eskuragarri - - - - Server %1 is currently in maintenance mode. - %1 zerbitzaria mantenu moduan dago. - - - - Signed out from %1. - %1etik saioa itxita. - - - - Obtaining authorization from the browser. <a href='%1'>Click here</a> to re-open the browser. - - - - - Connecting to %1... - %1-era konektatzen... - - - - No connection to %1 at %2. - Konexiorik ez %1-ekin %2-etan - - - - Log in - Hasi saioa - - - - There are folders that were not synchronized because they are too big: - - - - - There are folders that were not synchronized because they are external storages: - - - - - There are folders that were not synchronized because they are too big or external storages: - - - - - Confirm Account Removal - Baieztatu Kontuaren Ezabatzea - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - - - - Remove connection - Ezabatu konexioa - - - - - Open folder - Ireki karpeta - - - - - Log out - Saioa bukatu - - - - Resume sync - Berrekin sinkronizazioa - - - - Pause sync - Gelditu sinkronizazioa - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - - - - - %1 of %2 in use - %2tik %1 erabilita - - - - Currently there is no storage usage information available. - Orain ez dago eskuragarri biltegiratze erabileraren informazioa. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - No %1 connection configured. - Ez dago %1 konexiorik konfiguratuta. - - - - OCC::AccountState - - - Signed out - Saioa bukatuta - - - - Disconnected - Deskonektatuta - - - - Connected - Konektatuta - - - - Service unavailable - Zerbitzua ez dago eskuragarri - - - - Maintenance mode - Mantenu modua - - - - Network error - Sare errorea - - - - Configuration error - Konfigurazio errorea - - - - Asking Credentials - - - - - Unknown account state - Kontuaren egoera ezezaguna - - - - OCC::ActivityItemDelegate - - - %1 on %2 - - - - - %1 on %2 (disconnected) - - - - - OCC::ActivitySettings - - - - Server Activity - Zerbitzariaren Jarduera - - - - Sync Protocol - Sinkronizazio protokoloa - - - - Not Synced - Sinkronizatu gabekoak - - - - Not Synced (%1) - %1 is the number of not synced files. - - - - - The server activity list has been copied to the clipboard. - - - - - The sync activity list has been copied to the clipboard. - - - - - The list of unsynced items has been copied to the clipboard. - - - - - Copied to clipboard - Arbelera kopiatua - - - - OCC::ActivityWidget - - - Form - Formularioa - - - - - - TextLabel - TestuEtiketa - - - - Server Activities - Zerbitzariaren Jarduerak - - - - Copy - Kopiatu - - - - Copy the activity list to the clipboard. - Kopiatu jarduera zerrenda arbelara. - - - - Action Required: Notifications - - - - - <br/>Account %1 does not have activities enabled. - <br/>%1 kontuak ez ditu jarduerak gaituta. - - - - You received %n new notification(s) from %2. - - - - - You received %n new notification(s) from %1 and %2. - - - - - You received new notifications from %1, %2 and other accounts. - - - - - %1 Notifications - Action Required - - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - SSL bezeroaren ziurtagiriaren autentikazioa - - - - This server probably requires a SSL client certificate. - Zerbitzari honek ziurasko SSL bezero ziurtagiri bat behar du. - - - - Certificate & Key (pkcs12) : - - - - - Browse... - Arakatu... - - - - Certificate password : - Ziurtagiriaren pasahitza: - - - - Select a certificate - Hautatu ziurtagiri bat - - - - Certificate files (*.p12 *.pfx) - Ziurtagiri fitxategiak (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Irten - - - - Continue - Jarraitu - - - - Error accessing the configuration file - Errorea ezarpen fitxategia atzitzean - - - - There was an error while accessing the configuration file at %1. - Errore bat gertatu da ezarpen fitxategia atzitzean %1-etan - - - - Quit ownCloud - Irten ownCloud-etik - - - - OCC::AuthenticationDialog - - - Authentication Required - Autentikazioa beharrezkoa - - - - Enter username and password for '%1' at %2. - Sartu erabiltzaile izena eta pasahitza '%1'-rentzat hemen: %2. - - - - &User: - &Erabiltzailea: - - - - &Password: - &Pasahitza: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Errorea metadatuak datu-basean idaztean - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Ez dago ownCloud konturik konfiguratuta - - - - The configured server for this client is too old - Bezero honentzako konfiguratutako zerbitzaria oso zaharra da - - - - Please update to the latest server and restart the client. - Mesedez eguneratu zerbitzarira eta berrabiarazi bezeroa. - - - - Authentication error: Either username or password are wrong. - Autentikazio errorea: Erabiltzaile izena edota pasahitza gaizki daude. - - - - timeout - denbora iraungi da - - - - The provided credentials are not correct - Emandako kredentzialak ez dira zuzenak - - - - OCC::DiscoveryMainThread - - - Aborted by the user - Erabiltzaileak bertan behera utzita - - - - OCC::DiscoverySingleDirectoryJob - - - The server file discovery reply is missing data. - - - - - OCC::Folder - - - Local folder %1 does not exist. - Bertako %1 karpeta ez da existitzen. - - - - %1 should be a folder but is not. - %1 karpeta bat izan behar zen baina ez da. - - - - %1 is not readable. - %1 ezin da irakurri. - - - - %1 has been removed. - %1 names a file. - %1 ezabatua izan da. - - - - %1 has been downloaded. - %1 names a file. - %1 deskargatu da. - - - - %1 has been updated. - %1 names a file. - %1 kargatu da. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 %2-(e)ra berrizendatu da. - - - - %1 has been moved to %2. - %1 %2-(e)ra mugitu da. - - - - %1 and %n other file(s) have been removed. - - - - - %1 and %n other file(s) have been downloaded. - - - - - %1 and %n other file(s) have been updated. - - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - - - - - %1 has been moved to %2 and %n other file(s) have been moved. - - - - - %1 has and %n other file(s) have sync conflicts. - - - - - %1 has a sync conflict. Please check the conflict file! - - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - - - - - %1 could not be synced due to an error. See the log for details. - %1 ezin izan da sinkronizatu akats bat dela eta. Ikusi egunerkoa zehaztapen gehiago izateko. - - - - Sync Activity - Sinkronizazio Jarduerak - - - - Could not read system exclude file - Ezin izan da sistemako baztertutakoen fitxategia irakurri - - - - A new folder larger than %1 MB has been added: %2. - - - - - - A folder from an external storage has been added. - - - - - - Please go in the settings to select it if you wish to download it. - - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - Ezabatu Fitxategi Guztiak? - - - - Remove all files - Ezabatu fitxategi guztiak - - - - Keep files - Mantendu fitxategiak - - - - This sync would reset the files to an earlier time in the sync folder '%1'. -This might be because a backup was restored on the server. -Continuing the sync as normal will cause all your files to be overwritten by an older file in an earlier state. Do you want to keep your local most recent files as conflict files? - - - - - Backup detected - - - - - Normal Synchronisation - - - - - Keep Local Files as Conflict - - - - - OCC::FolderMan - - - Could not reset folder state - Ezin izan da karpetaren egoera berrezarri - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Aurkitu da '%1' sinkronizazio erregistro zaharra, baina ezin da ezabatu. Ziurtatu aplikaziorik ez dela erabiltzen ari. - - - - (backup) - - - - - (backup %1) - - - - - Undefined State. - Definitu gabeko egoera. - - - - Waiting to start syncing. - Itxoiten sinkronizazioa hasteko. - - - - Preparing for sync. - Sinkronizazioa prestatzen. - - - - Sync is running. - Sinkronizazioa martxan da. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Azkeneko sinkronizazioa ongi burutu zen. - - - - Setup Error. - Konfigurazio errorea. - - - - User Abort. - Erabiltzaileak bertan behera utzi. - - - - Sync is paused. - Sinkronizazioa pausatuta dago. - - - - %1 (Sync is paused) - %1 (Sinkronizazioa pausatuta dago) - - - - No valid folder selected! - Ez da karpeta egokirik hautatu! - - - - The selected path is not a folder! - Hautatutako bidea ez da karpeta bat! - - - - You have no permission to write to the selected folder! - Ez daukazu hautatutako karpetan idazteko baimenik! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Gehitu Karpeta Sinkronizatzeko Konexioa - - - - Synchronizing with local folder - - - - - File - Fitxategia - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - - - - - Click this button to add a folder to synchronize. - - - - - - %1 (%2) - Example text: "File.txt (23KB)" - - - - - Error while loading the list of folders from the server. - Errorea zerbitzaritik karpeten zerrenda eskuratzean. - - - - Signed out - Saioa bukatuta - - - - Fetching folder list from server... - Zerbitzaritik karpeta zerrenda eskuratzen... - - - - There are unresolved conflicts. Click for details. - - - - - New files are being created as virtual files. - - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - - - - - '%1' - Argument is a file name - - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - %1 Sinkronizatzen - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - Deskargatu %1/s - - - - u2193 %1/s - u2193 %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - igo %1/s - - - - u2191 %1/s - u2191 %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%4 - %3tik) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - - - - - file %1 of %2 - %1. fitxategia %2tik - - - - Waiting... - Itxoiten... - - - - Waiting for %n other folder(s)... - Itxoiten beste karpeta %n...Itxoiten beste %n karpeta... - - - - Preparing to sync... - Sinkronizatzeko prestatzen... - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Gehitu Karpeta Sinkronizatzeko Konexioa - - - - Add Sync Connection - Gehitu Sinkronizazio Konexioa - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Klikatu sinkronizatzeko bertako karpeta bat sinkronizatzeko. - - - - Enter the path to the local folder. - Sartu bertako karpeta berriaren bidea: - - - - Select the source folder - Hautatu jatorrizko karpeta - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Sortu Urruneko Karpeta - - - - Enter the name of the new folder to be created below '%1': - Sartu behean sortuko den karpeta berriaren izena '%1': - - - - Folder was successfully created on %1. - %1-en karpeta ongi sortu da. - - - - Authentication failed accessing %1 - Autentikazioak huts egin du %1en sartzean - - - - Failed to create the folder on %1. Please check manually. - Huts egin du %1-(e)an karpeta sortzen. Egiaztatu eskuz. - - - - Failed to list a folder. Error: %1 - Karpetaren zerrenda lortzeak huts egin du: Error: %1 - - - - Choose this to sync the entire account - Hautatu hau kontu osoa sinkronizatzeko - - - - This folder is already being synced. - Karpeta hau dagoeneko sinkronizatzen ari da. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Dagoeneko <i>%1</i> sinkronizatzen ari zara, <i>%2</i>-ren guraso karpeta dena. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately (experimental) - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Abisua:</b> %1 - - - - <b>Warning:</b> - <b>Abisua:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Ez da E-Tagik jaso zerbitzaritik, egiaztatu Proxy/Gateway - - - - We received a different E-Tag for resuming. Retrying next time. - Jarraitzeko E-Tag ezberdina jaso dugu. Hurrengoan saiatuko gara berriz. - - - - Server returned wrong content-range - Zerbitzariak eduki-hein desegokia itzuli du - - - - Connection Timeout - Konexioa denboraz kanpo - - - - OCC::GeneralSettings - - - Form - Formularioa - - - - General Settings - Ezarpen orokorrak - - - - For System Tray - - - - - stable - - - - - beta - - - - - Advanced - Aurreratua - - - - Ask for confirmation before synchronizing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - - - - - &Launch on System Startup - &Abiarazi Sistema Hasterakoan - - - - Show &Desktop Notifications - &Erakutsi Mahagaineko Jakinarazpenak - - - - Use &Monochrome Icons - Erabili &Kolore Bakarreko Ikonoak - - - - Version - Bertsioa - - - - &Update Channel - - - - - • - - - - - Edit &Ignored Files - - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - - - - - &Restart && Update - Be&rrabiarazi eta Eguneratu - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Ezeztatu - - - - OCC::GetOrCreatePublicLinkShare - - - Sharing error - - - - - Could not retrieve or create the public link share. Error: - -%1 - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - Sartu Pasahitza - - - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Baztertutako Fitxategien Editorea - - - - Global Ignore Settings - - - - - Sync hidden files - Sinkronizatu ezkutuko fitxategiak - - - - Files Ignored by Patterns - - - - - Add - Gehitu - - - - Pattern - - - - - Allow Deletion - Baimendu Ezabatzea - - - - Remove - Ezabatu - - - - Files or folders matching a pattern will not be synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Eredu bat jarraitzen duten fitxategiak ez dira sinkronizatuko. - -Ezabatzeko baimena duten itemak ezabatuko dira hauek karpeta bat ezabatzea uzten ez badute. Hau meta datuentzat interesgarria da. - - - - Could not open file - Ezin izan da fitxategia ireki - - - - Cannot write changes to '%1'. - Ezin izan dira aldaketa idatzi hemen '%1'. - - - - Add Ignore Pattern - Gehitu Baztertzeko Eredua - - - - Add a new ignore pattern: - Gehitu baztertzeko eredu berria: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - - - - - OCC::IssuesWidget - - - Form - Formularioa - - - - List of issues - - - - - Account - Kontua - - - - - <no filter> - - - - - - Folder - Karpeta - - - - Show warnings - - - - - Show ignored files - - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - - - - - Copy - Kopiatu - - - - Time - Noiz - - - - File - Fitxategia - - - - Issue - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LogBrowser - - - Log Output - Egunkari Irteera - - - - &Search: - &Bilaketa: - - - - &Find - &Aurkitu: - - - - &Capture debug messages - - - - - Permanently save logs - - - - - When this option is enabled and no other logging is configured, logs will be written to a temporary folder and expire after a few hours. This setting persists across client restarts. - -Logs will be written to %1 - - - - - Clear - Garbitu - - - - Clear the log display. - Garbitu egunkari bistaratzea. - - - - S&ave - &Gorde - - - - Save the log file to a file on disk for debugging. - Gorde egunkari fitxategia fitxategi batean arazteko. - - - - Save log file - Gorde egunkari fitxategia - - - - Error - Errorea - - - - Could not write to log file %1 - - - - - OCC::Logger - - - Error - Errorea - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>'%1' Fitxategia<br/> ezin da idazteko ireki.<br/><br/>Egunkariaren irteera <b>ezin</b> da gorde!</nobr> - - - - OCC::NSISUpdater - - - New Version Available - Bertsio berria eskuragarri - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p> %1 bezeroaren bertsio berri bat eskuragarri dago.</p><p><b>%2</b>deskargatzeko prest dago. Instalatuta dagoen bersioa %3 da.</p> - - - - Skip this version - Ez eguneratu bertsio hau - - - - Skip this time - Utzi aldi honetan - - - - Get update - Eskuratu eguneraketa - - - - OCC::NetworkSettings - - - Form - Formularioa - - - - Proxy Settings - Proxy Ezarpenak - - - - No Proxy - Proxyrik ez - - - - Use system proxy - Erabili sistemaren proxya - - - - Specify proxy manually as - Zehaztu eskuz honako proxy mota - - - - Host - Ostalaria - - - - : - : - - - - Proxy server requires authentication - Proxy zerbitzariak autentikazioa behar du - - - - Download Bandwidth - Deskargatzeko Banda Zabalera - - - - - Limit to - Honetara mugatu - - - - - KBytes/s - KByte/s - - - - - No limit - Mugarik ez - - - - - Limit to 3/4 of estimated bandwidth - Mugatu estimatutako banda zabaleraren 3/4etara - - - - Upload Bandwidth - Igotzeko Banda Zabalera - - - - - Limit automatically - Automatikoki mugatu - - - - Hostname of proxy server - Proxy zerbitzariaren hostalari izena - - - - Username for proxy server - Proxy zerbitzariaren erabiltzaile izena - - - - Password for proxy server - Proxy zerbitzarirako pasahitza - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - - - - - Closing in a few seconds... - Ixten segundo gutxi barru... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - The reply from the server did not contain all expected fields - - - - - <h1>Login Error</h1><p>%1</p> - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - - - - - Downloading version %1. Please wait... - %1 Bertsioa deskargatzen. Mesedez itxoin... - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - - - - - Could not check for new updates. - Ezin da eguneraketarik bilatu. - - - - %1 version %2 available. Restart application to start the update. - - - - - New %1 version %2 available. Please use the system's update tool to install it. - - - - - Checking update server... - Eguneraketa zerbitzaria galdetzen... - - - - Update status is unknown: Did not check for new updates. - - - - - No updates available. Your installation is at the latest version. - Ez dago eguneraketarik eskuragarri. Zure instalazioa azkenengo bertsioa da. - - - - Update Check - Begiratu bertsio berririk ba ote den - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - %1ra konektatu - - - - Setup local folder options - Konfiguratu bertako karpeten aukerak - - - - Connect... - Konektatu... - - - - %1 folder '%2' is synced to local folder '%3' - - - - - Sync the folder '%1' - '%1' karpeta sinkronizatu - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - - - - - Local Sync Folder - Sinkronizazio karpeta lokala - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Konexioak huts egin du - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - - - - - Select a different URL - Hautatu beste URL ezberdin bat - - - - Retry unencrypted over HTTP (insecure) - - - - - Configure client-side TLS certificate - - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - - - - - OCC::OwncloudHttpCredsPage - - - &Email - - - - - Connect to %1 - %1ra konektatu - - - - Enter user credentials - Sartu erabiltzailearen kredentzialak - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - %1ra konektatu - - - - Login in your browser - - - - - Copy link to clipboard - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - %1ra konektatu - - - - Setup %1 server - Konfiguratu %1 zerbitzaria - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Url hori EZ da segurua ez baitago kodetuta. -Ez da gomendagarria erabltzea. - - - - This url is secure. You can use it. - Url hau segurua da. Berau erabili dezakezu. - - - - &Next > - &Hurrengoa > - - - - Invalid URL - Baliogabeko URLa - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">Konexioa ongi burutu da %1 zerbitzarian: %2 bertsioa %3 (%4)</font><br/><br/> - - - - Failed to connect to %1 at %2:<br/>%3 - - - - - Timeout while trying to connect to %1 at %2. - Denbora iraungi da %1era %2n konektatzen saiatzean. - - - - Trying to connect to %1 at %2... - %2 zerbitzarian dagoen %1 konektatzen... - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - - - - - There was an invalid response to an authenticated webdav request - - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - - - - - Invalid URL - Baliogabeko URLa - - - - The server reported the following error: - - - - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - Bertako %1 karpeta dagoeneko existitzen da, sinkronizaziorako prestatzen.<br/><br/> - - - - Creating local sync folder %1... - Bertako %1 sinkronizazio karpeta sortzen... - - - - ok - ados - - - - failed. - huts egin du. - - - - Could not create local folder %1 - Ezin da %1 karpeta lokala sortu - - - - No remote folder specified! - Ez da urruneko karpeta zehaztu! - - - - Error: %1 - Errorea: %1 - - - - creating folder on ownCloud: %1 - ownClouden karpeta sortzen: %1 - - - - Remote folder %1 created successfully. - Urruneko %1 karpeta ongi sortu da. - - - - The remote folder %1 already exists. Connecting it for syncing. - Urruneko %1 karpeta dagoeneko existintzen da. Bertara konetatuko da sinkronizatzeko. - - - - - The folder creation resulted in HTTP error code %1 - Karpeta sortzeak HTTP %1 errore kodea igorri du - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Huts egin du urrutiko karpeta sortzen emandako kredintzialak ez direlako zuzenak!<br/> Egin atzera eta egiaztatu zure kredentzialak.</p> - - - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">Urruneko karpeten sortzeak huts egin du ziuraski emandako kredentzialak gaizki daudelako.</font><br/>Mesedez atzera joan eta egiaztatu zure kredentzialak.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Urruneko %1 karpetaren sortzeak huts egin du <tt>%2</tt> errorearekin. - - - - A sync connection from %1 to remote directory %2 was set up. - Sinkronizazio konexio bat konfiguratu da %1 karpetatik urruneko %2 karpetara. - - - - Successfully connected to %1! - %1-era ongi konektatu da! - - - - Connection to %1 could not be established. Please check again. - %1 konexioa ezin da ezarri. Mesedez egiaztatu berriz. - - - - Folder rename failed - Karpetaren berrizendatzeak huts egin du - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Ezin da karpeta ezabatu eta kopia egin, karpeta edo barruko fitxategiren bat beste programa batean irekita dagoelako. Mesedez itxi karpeta edo fitxategia eta sakatu berrekin edo ezeztatu konfigurazioa. - - - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>Bertako sinkronizazio %1 karpeta ongi sortu da!</b></font> - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Konexio Morroia - - - - Enable experimental feature? - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Enable experimental mode - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Dena konfiguratu da! - - - - Open Local Folder - Ireki karpeta lokala - - - - Open %1 in Browser - Ireki %1 Arakatzailean - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Errorea metadatuak datu-basean idaztean - - - - OCC::PropagateDownloadFile - - - File %1 can not be downloaded because of a local file name clash! - - - - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - - - - - File was deleted from server - Fitxategia zerbitzaritik ezabatua izan da - - - - The file could not be downloaded completely. - - - - - The downloaded file is empty despite the server announced it should have been %1. - - - - - File %1 cannot be saved because of a local file name clash! - - - - - File has changed since discovery - - - - - Error writing metadata to the database - Errorea metadatuak datu-basean idaztean - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - - - - - A file or folder was removed from a read only share, but restoring failed: %1 - - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - - - - - Attention, possible case sensitivity clash with %1 - - - - - could not create folder %1 - Ezin da %1 karpeta sortu - - - - Error writing metadata to the database - Errorea metadatuak datu-basean idaztean - - - - OCC::PropagateLocalRemove - - - Error removing '%1': %2; - - - - - Could not remove folder '%1' - - - - - Could not remove %1 because of a local file name clash - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - - - - - - Error writing metadata to the database - Errorea metadatuak datu-basean idaztean - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - Error writing metadata to the database - Errorea metadatuak datu-basean idaztean - - - - OCC::PropagateRemoteMove - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - - Error writing metadata to the database - Errorea metadatuak datu-basean idaztean - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - Fitxategia Ezabatua - - - - Local file changed during syncing. It will be resumed. - - - - - Local file changed during sync. - Fitxategi lokala aldatu da sinkronizazioan. - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - Errorea metadatuak datu-basean idaztean - - - - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - Fitxategi lokala ezabatu da sinkronizazioan. - - - - Local file changed during sync. - Fitxategi lokala aldatu da sinkronizazioan. - - - - Unexpected return code from server (%1) - - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileV1 - - - Poll URL missing - - - - - The local file was removed during sync. - Fitxategi lokala ezabatu da sinkronizazioan. - - - - Local file changed during sync. - Fitxategi lokala aldatu da sinkronizazioan. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolWidget - - - Form - Formularioa - - - - TextLabel - TestuEtiketa - - - - Open in browser - Ireki nabigatzailean - - - - Time - Noiz - - - - File - Fitxategia - - - - Folder - Karpeta - - - - Action - Ekintza - - - - Size - Tamaina - - - - Local sync protocol - Bertako sinkronizazio protokolo - - - - Copy - Kopiatu - - - - Copy the activity list to the clipboard. - Kopiatu jarduera zerrenda arbelara. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - - - - - Username: - Erabiltzaile izena: - - - - Proxy: - Proxya: - - - - The proxy server needs a username and password. - - - - - Password: - Pasahitza: - - - - TextLabel - TestuEtiketa - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Hautatu zer sinkronizatu - - - - OCC::SelectiveSyncWidget - - - Loading ... - Kargatzen... - - - - Deselect remote folders you do not wish to synchronize. - - - - - Name - Izena - - - - Size - Tamaina - - - - - No subfolders currently on the server. - Ez dago azpikarpetarik zerbitzarian. - - - - An error occurred while loading the list of sub folders. - - - - - OCC::ServerNotificationHandler - - - Dismiss - Baztertu - - - - OCC::SettingsDialog - - - Settings - Ezarpenak - - - - Activity - Jarduera - - - - General - Orokorra - - - - Network - Sarea - - - - Account - Kontua - - - - OCC::SettingsDialogMac - - - %1 - %1 - - - - Activity - Jarduera - - - - General - Orokorra - - - - Network - Sarea - - - - - Account - Kontua - - - - OCC::ShareDialog - - - TextLabel - TestuEtiketa - - - - share label - - - - - Dialog - - - - - ownCloud Path: - ownCloud-en bidea: - - - - %1 Sharing - - - - - %1 - %1 - - - - Folder: %2 - Karpeta: %2 - - - - The server does not allow sharing - - - - - Retrieving maximum possible sharing permissions from server... - - - - - The file can not be shared because it was shared without sharing permission. - - - - - Users and Groups - - - - - Public Links - - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - - - - - Upload only (File Drop) - - - - - Receive files from others without revealing the contents of the folder. - - - - - TextLabel - TestuEtiketa - - - - Set &password - Ezarri &pasahitza - - - - Enter a name to create a new public link... - - - - - &Create new - - - - - Set &expiration date - Ezarri &muga data - - - - Set password - Ezarri pasahitza - - - - Recipients can view or download contents. - - - - - Download / View - Jaitsi / Ikusi - - - - Download / View / Upload - Deskargatu / Ikusi / Kargatu - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - Esteka duen edonork atzitu dezake fitxategi/karpeta - - - - - P&assword protect - - - - - The file can not be shared because it was shared without sharing permission. - - - - - Link shares have been disabled - - - - - Create public link share - - - - - - Delete - Ezabatu - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Copy link to clipboard (direct download) - - - - - Send link by email - Bidali lotura posta bidez - - - - Send link by email (direct download) - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - - - - - Cancel - Ezeztatu - - - - - Public link - - - - - Delete link share - - - - - Public sh&aring requires a password - - - - - Please Set Password - Mesedez Ezarri Pasahitza - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - - - - - Share with users or groups ... - Elkarbanatu erabiltzaile edo taldearekin... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Send link by email - Bidali lotura posta bidez - - - - No results for '%1' - - - - - I shared something with you - - - - - OCC::ShareUserLine - - - Form - Formularioa - - - - - TextLabel - TestuEtiketa - - - - can edit - editatu dezake - - - - can share - partekatu dezake - - - - ... - ... - - - - create - sortu - - - - change - aldatu - - - - delete - ezabatu - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - - - - - OCC::ShibbolethCredentials - - - Login Error - Errorea sartzean - - - - You must sign in as user %1 - - - - - OCC::ShibbolethWebView - - - %1 - Authenticate - - - - - SSL Chipher Debug View - - - - - Reauthentication required - Berautentikatzea beharrezkoa - - - - Your session has expired. You need to re-login to continue to use the client. - - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - - - - - Context menu share - - - - - I shared something with you - - - - - - Share... - - - - - - Copy private link to clipboard - - - - - Send private link by email... - - - - - Resharing this file is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - Ireki nabigatzailean - - - - Download file(s) - - - - - Replace file(s) by virtual file - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>>Ziurtagiriaren Zehaztapenak</h3> - - - - Common Name (CN): - Izen Arrunta (IA): - - - - Subject Alternative Names: - - - - - Organization (O): - Erakundea (O): - - - - Organizational Unit (OU): - Erakunde Atala (OU): - - - - State/Province: - Estatua/Erkidegoa: - - - - Country: - Herrialdea: - - - - Serial: - - - - - <h3>Issuer</h3> - - - - - Issuer: - Jaulkitzailea: - - - - Issued on: - Jaulkitze-data: - - - - Expires on: - Iraungitze-data: - - - - <h3>Fingerprints</h3> - <h3>Hatz-markak</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - - - - - %1 (self-signed) - - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - - - - - No support for SSL session tickets/identifiers - - - - - Certificate information: - Ziurtagiriaren informazioa: - - - - This connection is NOT secure as it is not encrypted. - - - - - - OCC::SslErrorDialog - - - Form - Formularioa - - - - Trust this certificate anyway - Fidatu ziurtagiri honetaz hala ere - - - - Untrusted Certificate - - - - - Cannot connect securely to <i>%1</i>: - - - - - with Certificate %1 - %1 ziurtagiriarekin - - - - - - &lt;not specified&gt; - &lt;zehaztu gabe&gt; - - - - - Organization: %1 - Erakundea: %1 - - - - - Unit: %1 - Unitatea: %1 - - - - - Country: %1 - Herrialdea: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Hatz-marka (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Hatz-marka (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Balio-data: %1 - - - - Expiration Date: %1 - - - - - Issuer: %1 - Jaulkitzailea: %1 - - - - OCC::SyncEngine - - - Success. - Arrakasta. - - - - A network connection timeout happened. - - - - - A HTTP transmission error happened. - HTTP transmisio errore bat gertatu da. - - - - The mounted folder is temporarily not available on the server - - - - - An error occurred while opening a folder - Errore bat egon da karpeta bat irekitzearkoan - - - - Error while reading folder. - - - - - %1 (skipped due to earlier error, trying again in %2) - - - - - File/Folder is ignored because it's hidden. - - - - - Folder hierarchy is too deep - - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Not allowed because you don't have permission to add parent folder - - - - - Not allowed because you don't have permission to add files in that folder - - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - - - - - CSync unspecified error. - CSyncen zehaztugabeko errorea. - - - - Aborted by the user - Erabiltzaileak bertan behera utzita - - - - Failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder. - - - - - Discovery step failed. - - - - - Permission denied. - - - - - File or directory not found: - - - - - Tried to create a folder that already exists. - - - - - No space on %1 server available. - - - - - The service is temporarily unavailable - - - - - Access is forbidden - - - - - An internal error number %1 occurred. - - - - - Symbolic links are not supported in syncing. - Esteka sinbolikoak ezin dira sinkronizatu. - - - - File is listed on the ignore list. - Fitxategia baztertutakoen zerrendan dago. - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - - - - - The file name is a reserved name on this file system. - - - - - Filename contains trailing spaces. - - - - - Filename is too long. - - - - - The filename cannot be encoded on your file system. - - - - - Unresolved conflict. - - - - - Stat failed. - - - - - Filename encoding is not valid - - - - - Invalid characters, please rename "%1" - - - - - Using virtual files but suffix is not set - - - - - Unable to read the blacklist from the local database - - - - - Unable to read from the sync journal. - - - - - Cannot open the sync journal - Ezin da sinkronizazio egunerokoa ireki - - - - File name contains at least one invalid character - Fitxategi izenak behintzat baliogabeko karaktere bat du - - - - - Ignored because of the "choose what to sync" blacklist - - - - - Not allowed because you don't have permission to add subfolders to that folder - - - - - Not allowed to upload this file because it is read-only on the server, restoring - - - - - - Not allowed to remove, restoring - Ezabatzeko baimenik gabe, berrezartzen - - - - Local files and share folder removed. - - - - - Move not allowed, item restored - Mugitzea ez dago baimenduta, elementua berrezarri da - - - - Move not allowed because %1 is read-only - Mugitzea ez dago baimenduta %1 irakurtzeko bakarrik delako - - - - the destination - helburua - - - - the source - jatorria - - - - OCC::SyncLogDialog - - - Synchronisation Log - Sinkronizazio egunerkoa - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p> - - - - - <p>Copyright ownCloud GmbH</p> - - - - - <p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%2 and the %2 logo are registered trademarks of %1 in the United States, other countries, or both.</p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - - - - - The checksum header contained an unknown checksum type '%1' - - - - - The downloaded file does not match the checksum, it will be resumed. - - - - - OCC::ownCloudGui - - - Please sign in - Mesedez saioa hasi - - - - Folder %1: %2 - - - - - There are no sync folders configured. - Ez dago sinkronizazio karpetarik definituta. - - - - Open in browser - Ireki nabigatzailean - - - - - - Log in... - Saioa hasi... - - - - - - Log out - Saioa bukatu - - - - Recent Changes - Azkenengo Aldaketak - - - - Managed Folders: - Kudeatutako karpetak: - - - - Open folder '%1' - Ireki '%1' karpeta - - - - Open %1 in browser - Ireki %1 arakatzailean - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Egoera ezezaguna - - - - Settings... - Ezarpenak... - - - - Details... - Xehetasunak... - - - - Help - Laguntza - - - - Quit %1 - %1etik Irten - - - - Disconnected from %1 - %1etik deskonektatuta - - - - Unsupported Server Version - - - - - Disconnected - Deskonektatuta - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - Kontuetatik deskonektatuta: - - - - Account %1: %2 - %1 Kontua: %2 - - - - Signed out - Saioa bukatuta - - - - Account synchronization is disabled - - - - - - Synchronization is paused - - - - - Unresolved conflicts - - - - - Error during synchronization - - - - - No sync folders configured - - - - - Unpause all folders - - - - - Pause all folders - - - - - Unpause all synchronization - - - - - Unpause synchronization - - - - - Pause all synchronization - - - - - Pause synchronization - - - - - Log out of all accounts - Saioa bukatu kontu guztietan - - - - Log in to all accounts... - Saioa hasi kontu guztietan... - - - - New account... - - - - - - About %1 - %1 buruz - - - - No items synced recently - Ez da azken aldian ezer sinkronizatu - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - Sinkronizatzen %1 %2tik (%3 faltan) - - - - Syncing %1 of %2 - - - - - Syncing %1 (%2 left) - Sinkronizatzen %1 (%2faltan) - - - - Syncing %1 - %1 Sinkronizatzen - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Eguneratua - - - - OwncloudAdvancedSetupPage - - - Form - Formularioa - - - - - - - - - - TextLabel - TestuEtiketa - - - - Server - Zerbitzaria - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - - - - - Start a &clean sync (Erases the local folder!) - Hasi sinkronizazio &garbia (Bertako karpeta ezabatzen du!) - - - - Ask for confirmation before synchroni&zing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - - - - - Choose what to sync - Hautatu zer sinkronizatu - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - Karpeta &lokala - - - - pbSelectLocalFolder - - - - - &Keep local data - Mantendu datu lo&kalak - - - - S&ynchronize everything from server (recommended) - - - - - Use virtual files instead of downloading content immediately (e&xperimental) - - - - - Status message - Egoera mezua - - - - OwncloudHttpCredsPage - - - Form - Formularioa - - - - &Username - &Erabiltzaile-izena - - - - &Password - &Pasahitza - - - - OwncloudOAuthCredsPage - - - Form - Formularioa - - - - Please switch to your browser to proceed. - - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - - - - - OwncloudSetupPage - - - Form - Formularioa - - - - - TextLabel - TestuEtiketa - - - - Ser&ver Address - - - - - https://... - https://... - - - - Error Label - - - - - OwncloudWizardResultPage - - - Form - Formularioa - - - - TextLabel - TestuEtiketa - - - - Your entire account is synced to the local folder - Zure kontu osoa karpeta lokalera sinkronizaturik dago - - - - - PushButton - - - - - QObject - - - in the future - - - - - %n day(s) ago - - - - - %n hour(s) ago - - - - - now - - - - - Less than a minute ago - orain dela minutu bat baino gutxiago - - - - %n minute(s) ago - - - - - Some time ago - - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - - - - - %n month(s) - - - - - %n day(s) - - - - - %n hour(s) - - - - - %n minute(s) - - - - - %n second(s) - - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Sistemaren erretilua ez dago erabilgarri - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - - - - - ownCloudTheme::about() - - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - <p><small>Giteko <a href="%1">%2</a>. errebisiotik konpilatuta %3-an, %4etan Qt %5, %6 erabiliz</small></p> - - - - progress - - - Downloaded - Deskargatua - - - - Uploaded - Igoa - - - - Server version downloaded, copied changed local file into conflict file - - - - - Deleted - Ezabatuta - - - - Moved to %1 - %1era mugituta - - - - Ignored - - - - - Filesystem access error - - - - - Error - Errorea - - - - Updated local metadata - - - - - - Unknown - Ezezaguna - - - - downloading - deskargatzen - - - - uploading - igotzen - - - - deleting - ezabatzen - - - - moving - mugitzen - - - - ignoring - - - - - - error - errorea - - - - updating local metadata - - - - - theme - - - Status undefined - Definitu gabeko egoera - - - - Waiting to start sync - Itxoiten sinkronizazioaren hasiera - - - - Sync is running - Sinkronizazioa martxan da - - - - Sync Success - Sinkronizazioa ongi burutu da - - - - Sync Success, some files were ignored. - Sinkronizazioa ongi burutu da, fitxategi batzuk baztertu dira. - - - - Sync Error - Sinkronizazio Errorea - - - - Setup Error - Konfigurazio Errorea - - - - Preparing to sync - Sinkronizazioa prestatzen - - - - Aborting... - Bertan-behera uzten - - - - Sync is paused - Sinkronizazioa pausatuta dago - - - - utility - - - Could not open browser - - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - - - \ No newline at end of file diff --git a/translations/client_fa.ts b/translations/client_fa.ts deleted file mode 100644 index f103e2978bf..00000000000 --- a/translations/client_fa.ts +++ /dev/null @@ -1,4780 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - قطع عملیات - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - بستن - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - - - - - - Could not move '%1' to '%2' - - - - - Moving to the trash is not implemented on this platform - - - - - Error removing '%1': %2 - - - - - Could not remove folder '%1' - پوشه '1%' حذف نمی شود - - - - FolderWizardSourcePage - - - Form - فرم - - - - Pick a local folder on your computer to sync - یک پوشه محلی را برای اجرای بهنگام‌سازی انتخاب کنید - - - - &Choose... - &انتخاب... - - - - FolderWizardTargetPage - - - Form - فرم - - - - Select a remote destination folder - انتخاب یک پوشه مقصد راه‌دور - - - - Create Folder - ایجاد پوشه - - - - Refresh - بازنمایی - - - - Folders - پوشه ها - - - - TextLabel - برچسب متنی - - - - NotificationWidget - - - Form - فرم - - - - Lorem ipsum dolor sit amet - - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - - TextLabel - برچسب متنی - - - - OCC::AboutDialog - - - About - درباره - - - - TextLabel - برچسب متنی - - - - Versions - نسخه‌ها - - - - About %1 - درباره %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - زمان ارتباط تمام شد - - - - Unknown error: network reply was deleted - خطای ناشناخته: پاسخ شبکه پاک شد - - - - Server replied "%1 %2" to "%3 %4" - سرور "1% 2%" به "3% 4%" پاسخ داد - - - - OCC::Account - - - %1@%2 - - - - - OCC::AccountSettings - - - Form - فرم - - - - Re-open Browser - - - - - ... - ... - - - - Storage space: ... - حجم فضای ذخیره‌سازی: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - پوشه های بررسی نشده از سیستم فایل محلی شما <b>حذف</b> خواهد شد و دیگر در این کامپیوتر همگام سازی نخواهد شد. - - - - Synchronize all - همگام‌سازی همه - - - - Synchronize none - همگام سازی هیچ - - - - Apply manual changes - تایید تغییرات دستی - - - - Apply - اعمال - - - - - - - Cancel - منصرف شدن - - - - Connected with <server> as <user> - متصل به <server> به عنوان <user> - - - - No account configured. - هیچ حساب‌کاربری‌ای تنظیم نشده‌ است. - - - - Remove - حذف - - - - Account - حساب کاربری - - - - Choose what to sync - انتخاب موارد همگام‌سازی - - - - Force sync now - - - - - Restart sync - راه اندازی مجدد همگام سازی - - - - Remove folder sync connection - حذف اتصال همگام سازی پوشه - - - - Folder creation failed - ساخت پوشه ناموفق - - - - <p>Could not create local folder <i>%1</i>. - <p>ناتوانی در ساخت پوشه محلی <i>1%</i> - - - - Confirm Folder Sync Connection Removal - تأیید حذف اتصال همگام سازی پوشه - - - - Remove Folder Sync Connection - حذف اتصال همگام سازی پوشه - - - - Disable virtual file support? - - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - - - - - Sync Running - همگام سازی در حال اجراست - - - - The syncing operation is running.<br/>Do you want to terminate it? - عملیات همگام سازی در حال اجراست.<br/>آیا دوست دارید آن را متوقف کنید؟ - - - - %1 in use - 1% در استفاده - - - - %1 as <i>%2</i> - 1% به عنوان <i>2%</i> - - - - Connected to %1. - متصل به %1. - - - - Server %1 is temporarily unavailable. - سرور %1 بصورت موقت خارج از دسترس است. - - - - Server %1 is currently in maintenance mode. - سرور 1% اکنون در حالت تعمیر است. - - - - Signed out from %1. - از 1% خارج شد. - - - - Connecting to %1... - اتصال به %1... - - - - No connection to %1 at %2. - اتصال به 1% در 2% وجود ندارد. - - - - Log in - ورود - - - - There are folders that were not synchronized because they are too big: - پوشه‌هایی وجود دارند که همگام سازی نشده اند زیرا آن ها بسیار بزرگ هستند: - - - - There are folders that were not synchronized because they are external storages: - پوشه‌هایی وجود دارند که همگام سازی نشده اند زیرا آن ها مخازن خارجی هستند: - - - - There are folders that were not synchronized because they are too big or external storages: - پوشه‌هایی وجود دارند که همگام سازی نشده اند زیرا آن ها بسیار بزرگ یا مخازن خارجی هستند: - - - - Confirm Account Removal - تائید حذف حساب‌کاربری - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>آیا شما واقعا می خواهید اتصال به حساب <i>1%</i>را حذف کنید؟</p> <p><b>توجه:</b> این هیچ فایلی را حذف نخواهد کرد.</p> - - - - Remove connection - حذف ارتباط - - - - Open folder - بازکردن پوشه - - - - - Log out - خروج - - - - Open local folder - - - - - Open folder in browser - - - - - Resume sync - از سر‎گیری همگام‌سازی - - - - Pause sync - توقف به‌هنگام‌سازی - - - - Availability - - - - - Disable virtual file support... - - - - - Enable virtual file support%1... - - - - - (experimental) - - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>آیا شما واقعا می خواهید همگام سازی پوشه <i>1%</i> را متوقف نمایید؟</p><p><b>توجه:</b>این هیچ فایلی را حذف <b>نخواهد</b> کرد. </p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - 1% (%3%) از 2% در استفاده. برخی پوشه‌ها، شامل شبکه نصب شده یا پوشه های مشترک، ممکن است محدودیت های متفاوت داشته باشند. - - - - %1 of %2 in use - 1% از 2% در استفاده - - - - Currently there is no storage usage information available. - در حال حاضر هیچ اطلاعات کاربرد ذخیره سازی در دسترس نیست. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - Obtaining authorization from the browser. - - - - - Server configuration error: %1 at %2. - - - - - No %1 connection configured. - بدون %1 اتصال پیکربندی شده. - - - - OCC::AccountState - - - Signed out - خارج شد - - - - Disconnected - قطع شده - - - - Connected - متصل - - - - Service unavailable - سرویس خارج از دسترس - - - - Maintenance mode - حالت تعمیر - - - - Network error - خطای شبکه - - - - Configuration error - خطای پیکربندی - - - - Asking Credentials - درخواست مجوزها - - - - Unknown account state - وضعیت حساب ناشناخته - - - - OCC::ActivityItemDelegate - - - %1 on %2 - 1% روی 2% - - - - %1 on %2 (disconnected) - 1% روی 2% (قطع شده) - - - - OCC::ActivityListModel - - - %1 %2 on %3 - - - - - OCC::ActivitySettings - - - - Server Activity - فعالیت سرور - - - - Sync Protocol - پروتکل همگام سازی - - - - Not Synced - همگام سازی نشده - - - - Not Synced (%1) - %1 is the number of not synced files. - همگام سازی نشده (1%) - - - - The server activity list has been copied to the clipboard. - لیست فعالیت سرور در کلیپ بورد کپی شده است. - - - - The sync activity list has been copied to the clipboard. - لیست فعالیت همگام سازی در کلیپ بورد کپی شده است. - - - - The list of unsynced items has been copied to the clipboard. - لیست موارد همگام سازی نشده در کلیپ بورد کپی شده است. - - - - Copied to clipboard - کپی به کلیپ بورد - - - - OCC::ActivityWidget - - - Form - فرم - - - - - - TextLabel - برچسب متنی - - - - Server Activities - فعالیت‌های سرور - - - - Copy - کپی کردن - - - - Copy the activity list to the clipboard. - لیست فعالیت را در کلیپ بورد کپی کنید. - - - - Notifications - آگاهی‌ها - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - - - - - Open the activity view for details. - - - - - <br/>Account %1 does not have activities enabled. - <br/>فعالیت های حساب 1% فعال نیست. - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - تأیید اعتبار گواهی‌نامه مشتری SSL - - - - This server probably requires a SSL client certificate. - سرور احتمالا نیاز به گواهی‌نامه مشتری SSL دارد. - - - - Certificate & Key (pkcs12) : - گواهی‌نامه و کلید (pkcs12) : - - - - Browse... - مرور... - - - - Certificate password : - گواهی‌نامه رمز عبور : - - - - An encrypted pkcs12 bundle is strongly recommended as a copy will be stored in the configuration file. - - - - - Select a certificate - انتخاب یک گواهی‌نامه - - - - Certificate files (*.p12 *.pfx) - گواهی‌نامه فایل های (p12 *.pfx.*) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - خروج - - - - Continue - ادامه - - - - Error accessing the configuration file - خطای دسترسی به پرونده پیکربندی - - - - There was an error while accessing the configuration file at %1. - خطایی هنگام دسترسی به پرونده پیکربندی در 1% وحود دارد. - - - - Quit ownCloud - خروج از ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - نیازمند تاییدیه - - - - Enter username and password for '%1' at %2. - نام کاربری و رمز عبور را برای '1%' در 2% وارد کنید. - - - - &User: - &کاربر: - - - - &Password: - &رمزعبور: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - خطا در نوشتن متادیتا در پایگاه داده - - - - OCC::ConnectionValidator - - - No ownCloud account configured - هیچ حساب ownCloud پیکربندی نشده است - - - - The configured server for this client is too old - پیکربندی سرور برای این مشتری بسیار قدیمی است. - - - - Please update to the latest server and restart the client. - لطفا به آخرین سرور به روز رسانی کنید و مشتری را مجددا راه اندازی نمایید. - - - - Authentication error: Either username or password are wrong. - خطای تأیید: نام کاربری یا رمز عبور اشتباه است. - - - - timeout - وقفه - - - - The provided credentials are not correct - مدارک ارائه شده صحیح نیستند - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - - - - - Directory not accessible on client, permission denied - - - - - Directory not found: %1 - - - - - Filename encoding is not valid - رمزگذاری نام فایل معتبر نیست - - - - Error while reading directory %1 - - - - - OCC::Folder - - - Local folder %1 does not exist. - پوشه محلی %1 موجود نیست. - - - - %1 should be a folder but is not. - 1% باید یک پوشه باشد اما نیست. - - - - %1 is not readable. - %1 قابل خواندن نیست. - - - - %1 has been removed. - %1 names a file. - %1 حذف شده است. - - - - %1 has been updated. - %1 names a file. - %1 بروز رسانی شده است. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 به %2 تغییر نام داده شده است. - - - - %1 has been moved to %2. - %1 به %2 انتقال داده شده است. - - - - %1 and %n other file(s) have been removed. - 1% و n% پرونده های دیگر حذف شده اند.1% و n% پرونده های دیگر حذف شده اند. - - - - %1 and %n other file(s) have been added. - - - - - %1 has been added. - %1 names a file. - - - - - %1 and %n other file(s) have been updated. - 1% و n% پرونده های دیگر به روز رسانی شده اند. 1% و n% پرونده های دیگر به روز رسانی شده اند. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - 1% به 2% تغییر نام داده شده و n% پرونده های دیگر تغییر نام داده شده اند.1% به 2% تغییر نام داده شده و n% پرونده های دیگر تغییر نام داده شده اند. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - 1% به 2% منتقل شده و n% پرونده های دیگر منتقل شده اند.1% به 2% منتقل شده و n% پرونده های دیگر منتقل شده اند. - - - - %1 has and %n other file(s) have sync conflicts. - 1% و n% سایر پرونده ها ناسازگاری همگام سازی دارند.1% و n% سایر پرونده ها ناسازگاری همگام سازی دارند. - - - - %1 has a sync conflict. Please check the conflict file! - 1% داراری ناسازگاری همگام سازی است. لطفا پرونده ناسازگار را بررسی نمایید. - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - 1% و n% سایر پرونده ها به دلیل خطاها نمی توانند همگام سازی شوند. برای جزییات log را مشاهده کنید.1% و n% سایر پرونده ها به دلیل خطاها نمی توانند همگام سازی شوند. برای جزییات log را مشاهده کنید. - - - - %1 could not be synced due to an error. See the log for details. - 1% به دلیل خطاها نمی تواند همگام سازی شود. برای جزییات log را مشاهده کنید. - - - - Sync Activity - فعالیت همگام سازی - - - - Could not read system exclude file - نمی توان پرونده خارجی سیستم را خواند. - - - - A new folder larger than %1 MB has been added: %2. - - یک پوشه جدید بزرگتر از 1% MB اضافه شده است: 2%. - - - - - A folder from an external storage has been added. - - یک پوشه از یک مخزن خارجی اضافه شده است. - - - - - Please go in the settings to select it if you wish to download it. - اگر می خواهید این را دانلود کنید لطفا به تنظیمات بروید تا آن را انتخاب کنید. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - تمامی پرونده های پوشه همگام ساز پوشه '1%' در سرور حذف شد. -این حذف ها با پوشه همگام ساز محلی شما هماهنگ خواهند شد، ساخت چنین پرونده هایی در دسترس نیستند مگر اینکه حق بازگرداندن داشته باشید. -اگر شما تصمیم بگیرید پرونده ها را نگهداری کنید، آن ها با سرور مجددا همگام سازی می شوند در صورتی که شما حق انجام آن را داشته باشید. -اگر شما تصمیم بگیرید پرونده ها را حذف کنید، آن ها در دسترس شما نخواهند بود، مگر اینکه مالک باشید. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - تمامی پرونده های پوشه همگام ساز '1%' در سرور حذف شد. این حذف ها با پوشه همگام ساز محلی شما هماهنگ خواهند شد، ساخت چنین پرونده هایی در دسترس نیستند مگر اینکه حق بازگرداندن داشته باشید. -آیا شما اطمینان دارید که می خواهید آن اعمال را با سرور همگام نمایید؟ -اگر این یک اتفاق بوده و شما تصمیم دارید پرونده هایتان را نگه دارید، آن ها از سرور مجددا همگام سازی خواهند شد. - - - - Remove All Files? - حذف تمام فایل ها؟ - - - - Remove all files - حذف تمام فایل ها - - - - Keep files - نگه داشتن فایل ها - - - - OCC::FolderMan - - - Could not reset folder state - نمی تواند حالت پوشه را تنظیم مجدد کند - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - یک مجله همگام قدیمی '1%' پیدا شد، اما حذف نمی شود. لطفا مطمئن شوید که هیچ برنامه ای در حال حاضر از آن استفاده نمی کند. - - - - (backup) - (backup) - - - - (backup %1) - (پشتیبان %1) - - - - Undefined State. - موقعیت تعریف نشده - - - - Waiting to start syncing. - انتظار برای شروع همگام‌سازی - - - - Preparing for sync. - آماده سازی برای همگام سازی کردن. - - - - Sync is running. - همگام سازی در حال اجراست - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - آخرین همگام سازی موفقیت آمیز بود - - - - Setup Error. - خطا در پیکر بندی. - - - - User Abort. - خارج کردن کاربر. - - - - Sync is paused. - همگام سازی فعلا متوقف شده است - - - - %1 (Sync is paused) - %1 (همگام‌سازی موقتا متوقف شده است) - - - - No valid folder selected! - هیچ پوشه‌ی معتبری انتخاب نشده است! - - - - The selected path does not exist! - - - - - The selected path is not a folder! - مسیر انتخاب شده یک پوشه نیست! - - - - You have no permission to write to the selected folder! - شما اجازه نوشتن در پوشه های انتخاب شده را ندارید! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - در حال حاضر یک همگام سازی از سرور به این پوشه محلی وجود دارد. لطفا یک پوشه محلی دیگر را انتخاب کنید! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - پوشه محلی 1% از قبل شامل یک پوشه استفاده شده در یک اتصال همگام سازی پوشه است. لطفا یکی دیگر را انتخاب کنید! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - پوشه محلی 1% از قبل یک پوشه استفاده شده در یک اتصال همگام سازی پوشه دارد. لطفا یکی دیگر را انتخاب کنید! - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - افزودن اتصال همگام سازی پوشه - - - - File - فایل - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - شما باید متصل باشید تا پوشه ای را اضافه کنید - - - - Click this button to add a folder to synchronize. - برای افزودن پوشه به همگام‌سازی روی این دکمه کلیک کنید. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - خطای هنگام بارگذاری لیست پوشه‌ها از سرور. - - - - Virtual file support is enabled. - - - - - Signed out - خارج شد - - - - Fetching folder list from server... - آوردن لیست پوشه از سرور... - - - - There are unresolved conflicts. Click for details. - ناسازگاری های حل نشده ای وجود دارد. برای جزییات کلیک نمایید. - - - - Synchronizing VirtualFiles with local folder - - - - - Synchronizing with local folder - همگام‌سازی با پوشه محلی - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - تطبیق تغییرات - - - - , '%1' - Build a list of file names - ، '1%' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - همگام‌سازی %1 - - - - - , - رشته های ترجمه نشده - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - دانلود %1/s - - - - ↓ %1/s - - - - - ↑ %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - آپلود %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 از %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - 5% باقی ماند، 1% از 2%، پرونده 3% از 4% - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - 1% از 2%، پرونده 3% از 4% - - - - file %1 of %2 - فایل %1 از %2 - - - - Waiting... - درحال انتظار... - - - - Waiting for %n other folder(s)... - در انتظار برای %n پوشه‌‎ی دیگر ...در انتظار برای %n پوشه‌‎ی دیگر ... - - - - Preparing to sync... - آماده‌سازی همگام‌سازی ... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - اضافه کردن اتصال همگام سازی پوشه - - - - Add Sync Connection - افزودن اتصال همگام سازی - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - کلیک کنید تا یک پوشه را برای همگام سازی انتخاب نمایید. - - - - Enter the path to the local folder. - مسیر را به پوشه محلی وارد کنید. - - - - Select the source folder - پوشه ی اصلی را انتخاب کنید - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - ایجاد پوشه از راه دور - - - - Enter the name of the new folder to be created below '%1': - نام پوشه جدید را برای ایجاد شدن در زیر '1%' وارد نمایید: - - - - Folder was successfully created on %1. - پوشه با موفقیت ایجاد شده است %1. - - - - Authentication failed accessing %1 - تأیید اعتبار دسترسی به 1% شکست خورد - - - - Failed to create the folder on %1. Please check manually. - شکست در ساخت پوشه در 1%. لطفا به صورت دستی بررسی کنید. - - - - Failed to list a folder. Error: %1 - شکست در فهرست کردن یک پوشه. خطا: 1% - - - - Choose this to sync the entire account - این را برای همگام سازی کل حساب انتخاب کنید - - - - This folder is already being synced. - این پوشه همگام‌سازی شده است. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - شما از قبل <i>1%</i> را همگام سازی کرده اید، که یک پوشه والد از <i>2%</i> است. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>اخطار:</b> %1 - - - - <b>Warning:</b> - <b>اخطار:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - E-Tag از سرور دریافت نشده است، لطفا پروکسی/دروازه را بررسی نمایید - - - - We received a different E-Tag for resuming. Retrying next time. - ما برای بازخوانی E-Tag متفاوتی دریافت کردیم. بار بعدی دوباره امتحان کنید. - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - سرور محدوده محتوای اشتباهی برگرداند. - - - - OCC::GETJob - - - Connection Timeout - تایم اوت اتصال - - - - OCC::GeneralSettings - - - Form - فرم - - - - General Settings - تنظیمات عمومی - - - - Sync hidden files - همگام سازی پرونده های مخفی - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - شبکه - - - - Updates - به روز رسانی ها - - - - stable - - - - - beta - - - - - About - درباره - - - - Advanced - پیشرفته - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - درخواست تایید قبل از همگام سازی پوشه های بزرگتر - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - درخواست تایید قبل از همگام سازی مخازن خارجی - - - - &Launch on System Startup - راه انداختن در سیستم راه اندازی - - - - Show &Desktop Notifications - نمایش هشدارهای دسکتاپ - - - - &Update Channel - - - - - Edit &Ignored Files - ویرایش پرونده های رد شده - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - نمایش گزارشگر برخورد - - - - &Restart && Update - راه اندازی مجدد و به روز رسانی - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - لغو - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - لطفا رمز عبور 1% را وارد کنید: <br><br> کاربر: 2% <br> حساب: 3% <br> - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - رمز را وارد کنید - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">اینجا کلیک کنید</a> تا رمز عبور یک برنامه را از رابط وب درخواست کنید. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - ویرایشگر پرونده های رد شده - - - - Files Ignored by Patterns - پرونده ها توسط الگوها رد شده اند - - - - Add - افزودن - - - - Pattern - الگو - - - - Allow Deletion - اجازه حذف - - - - Remove - حذف - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - امکان باز کردن فایل وجود ندارد - - - - Cannot write changes to '%1'. - نمی‌توان تغییرات را در '%1' نوشت. - - - - Add Ignore Pattern - افزودن الگوی رد کردن - - - - Add a new ignore pattern: - افزودن یک الگوی رد کردن جدید: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - این ورودی توسط سیستم در '1%' ارائه شده است و در این دیدگاه قابل تغییر نیست. - - - - OCC::IssuesWidget - - - Form - فرم - - - - List of issues - فهرست موضوعات - - - - Account - حساب کاربری - - - - - <no filter> - <no filter> - - - - - Folder - پوشه - - - - Show warnings - نمایش هشدارها - - - - Show ignored files - نمایش پرونده های رد شده - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - کپی کردن فهرست موضوعات در کلیپ بورد. - - - - Copy - کپی کردن - - - - Time - زمان - - - - File - فایل - - - - Issue - موضوع - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LibCloudProvidersPrivate - - - Settings - تنظیمات - - - - OCC::LogBrowser - - - Log Output - خروجی Log - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - بازکردن پوشه - - - - OCC::NSISUpdater - - - New Version Available - نسخه جدید در دسترس است - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>یک نسخه جدید از مشتری 1% در دسترس است.</p> <p><b>2% </b> برای دانلود در دسترس است. نسخه نصب شده 3% است. </p> - - - - - Skip this version - نادیده گرفتن این نسخه - - - - Skip this time - نادیده گرفتن این زمان - - - - Get update - به دست آوردن به روز رسانی - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - فرم - - - - Proxy Settings - تنظیمات پروکسی - - - - No Proxy - بدون پروکسی - - - - Use system proxy - استفاده از پروکسی سیستم - - - - Specify proxy manually as - تعیین پروکسی دستی - - - - Host - میزبانی - - - - : - : - - - - Proxy server requires authentication - سرور پروکسی نیازمند تایید است - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - پهنای‌باند دانلود - - - - - Limit to - محدود به - - - - - KBytes/s - کیلوبایت - - - - - No limit - بدون محدودیت - - - - - Limit to 3/4 of estimated bandwidth - محدود به 3/4 پهنای باند تخمین زده شده - - - - Upload Bandwidth - پهنای باند آپلود - - - - - Limit automatically - محدودسازی خودکار - - - - Hostname of proxy server - نام میزبان سرور پروکسی - - - - Username for proxy server - نام کاربری سرور پروکسی - - - - Password for proxy server - رمزعبور برای سرور پروکسی - - - - HTTP(S) proxy - پروکسی HTTP(S) - - - - SOCKS5 proxy - پروکسی SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - ایجاد شده در %1 - - - - Closing in a few seconds... - بستن در چند ثانیه... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - درخواست 1% در 2% شکست خورد - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '1%' در 2% انتخاب شد - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - خطای بازگشت از سرور: <em>1%</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - خطایی در دسترسی به نقطه پایانی 'token' وجود داشت: <br><em>1%</em> - - - - Empty JSON from OAuth2 redirect - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - JSON بازگشتی از سرور قابل تجزیه نیست: <br><em>1%</em> - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Error: Missing field %1 - - - - - - <h1>Login Error</h1><p>%1</p> - <h1>خطای ورود</h1><p>1%</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>کاربر نادرست</h1><p> شما با کاربر<em>1%</em> وارد شدید، اما باید با کاربر <em>2%</em> وارد می شدید. <br> لطفا در برگه دیگری از 3% خارج شده، سپس <a href='%4'>اینجا را کلیک کنید</a> و به عنوان کاربر 2% وارد شوید</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - به روز رسانی جدید 1% آماده است - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - یک به روز رسانی جدید برای 1% در حال نصب است. به روز رسان ممکن است امتیازات اضافی در طول پردازش درخواست کند. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - نمی توان به روز رسانی را دانلود کرد. لطفا <a href='%1'>اینجا</a> را بررس کنید تا به روز رسانی را به صورت دستی دانلود نمایید. - - - - Could not check for new updates. - نمی توان به روز رسانی های جدید را بررسی کرد. - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - بررسی سرور به روز رسانی... - - - - Update status is unknown: Did not check for new updates. - وضعیت به روز رسانی نامشخص است: به روز رسانی های جدید را بررسی نکردید. - - - - No updates available. Your installation is at the latest version. - به روز رسانی موجود نیست. نصب شما آخرین نسخه است. - - - - Update Check - بررسی بروز‌ رسانی - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - متصل به %1 - - - - Setup local folder options - راه اندازی گزینه های پوشه محلی - - - - Connect... - اتصال... - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - %1 folder '%2' is synced to local folder '%3' - %1 پوشه '%2' با پوشه محلی همگام شده است '%3' - - - - Sync the folder '%1' - همگام‌سازی پوشه‌ی '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>هشدار:</strong> پوشه محلی خالی نیست. یک دقت انتخاب کنید!</small></p> - - - - Virtual files are not available for the selected folder - - - - - Local Sync Folder - پوشه همگام سازی محلی - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - ارتباط ناموفق بود - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>اتصال به نشانی سرور امن مشخص شکست خورد. چگونه می خواهید ادامه دهید؟</p></body></html> - - - - Select a different URL - انتخاب URL متفاوت - - - - Retry unencrypted over HTTP (insecure) - - - - - Configure client-side TLS certificate - پیکربندی سمت مشتری گواهی‌نامه TLS - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>شکست در اتصال به نشانی سرور <em>1%</em>. چگونه می خواهید ادامه دهید؟</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - پست الکترونیکی - - - - Connect to %1 - متصل به %1 - - - - Enter user credentials - وارد کردن گواهی نامه کاربر - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - متصل به %1 - - - - Login in your browser - در مرورگر خود وارد شوید - - - - Copy link to clipboard - لینک را در کلیپ بورد کپی کنید - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - متصل به %1 - - - - Setup %1 server - راه‌اندازی %1 سرور - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - این آدرس امن نیست زیرا رمزگذاری شده نیست. -استفاده از آن توصیه نمی شود. - - - - This url is secure. You can use it. - این ادرس امن است. شما می توانید از آن استفاده کنید. - - - - &Next > - &بعدی> - - - - Invalid URL - آدرس نامعتبر - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - ارتباط ناموفق با %1 در %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - هنگام تلاش برای اتصال به 1% در 2% زمان به پایان رسید. - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - درخواست تایید شده به سرور '1%' هدایت شد. آدرس بد است، سرور اشتباه پیکربندی شده است. - - - - There was an invalid response to an authenticated webdav request - یک پاسخ نادرست به درخواست webdov تایید شده وجود داشت - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - دسترسی توسط سرور ممنوع شد. برای تأیید اینکه شما دسترسی مناسب دارید، <a href="%1">اینجا را کلیک کنید </a> تا با مرورگر خود به سرویس دسترسی پیدا کنید. - - - - Invalid URL - آدرس نامعتبر - - - - Could not create local folder %1 - نمی تواند پوشه محلی ایجاد کند %1 - - - - No remote folder specified! - هیچ پوشه از راه دوری مشخص نشده است! - - - - Error: %1 - خطا: %1 - - - - The folder creation resulted in HTTP error code %1 - ایجاد پوشه به خطای HTTP کد 1% منجر شد - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - ایجاد پوشه از راه دور ناموفق بود به علت اینکه اعتبارهای ارائه شده اشتباه هستند!<br/>لطفا اعتبارهای خودتان را بررسی کنید.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - ایجاد پوشه از راه دور %1 ناموفق بود با خطا <tt>%2</tt>. - - - - Folder rename failed - تغییر نام پوشه ناموفق بود - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - نمی توانید پوشه را حذف کنید یا پشتیبان بگیرید زیرا پوشه یا یک پرونده در آن در برنامه دیگری باز است. لطفا پوشه یا پرونده را ببندید و مجددا تلاش کنید یا تنظیم را لغو کنید. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - - - - - Enable experimental feature? - - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - همه چیز تنظیم شده است! - - - - Open Local Folder - باز کردن پوشه محلی - - - - Open %1 in Browser - بازکردن %1 در مرورگر - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - پاسخ JSON نامعتبر از آدرس نظرسنجی - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - پیوندهای نمادین در همگام سازی پشتیبانی نمی شوند. - - - - File is listed on the ignore list. - پرونده در فهرست رد شده ها موجود است. - - - - File names ending with a period are not supported on this file system. - نام پرونده هایی که با دوره ای پایان می یابند در این سیستم پرونده پشتیبانی نشده اند. - - - - File names containing the character '%1' are not supported on this file system. - نام پرونده هایی که شامل کاراکتر '1%' هستند در این سیستم پرونده پشتیبانی نشده اند. - - - - File name contains at least one invalid character - نام فایل دارای حداقل یک کاراکتر نامعتبر است - - - - The file name is a reserved name on this file system. - نام پرونده یک نام رزرو شده در این سیستم پرونده است. - - - - Filename contains trailing spaces. - نام پرونده شامل دنباله فضای خالی است. - - - - Filename is too long. - نام فایل خیلی طولانی است. - - - - File/Folder is ignored because it's hidden. - پرونده/پوشه رد شد زیرا مخفی است. - - - - Stat failed. - وضعیت ناموفق - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - ناسازگاری: نسخه سرور دانلود شد، کپی محلی تغییر نام داده شده و بارگذاری نشده است. - - - - The filename cannot be encoded on your file system. - نام پرونده در سیستم پرونده شما رمزگذاری نمی شود. - - - - The filename is blacklisted on the server. - - - - - File has extension reserved for virtual files. - - - - - size - - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - حذف مجاز نیست، در حال بازگردادن - - - - Ignored because of the "choose what to sync" blacklist - به علت لیست سیاه "انتخاب کنید چه چیزی همگام سازی شود" رد شد - - - - Not allowed because you don't have permission to add subfolders to that folder - با توجه به عدم اجازه‌ی شما به ایجاد زیرپوشه به پوشه مجاز نیست - - - - Not allowed because you don't have permission to add files in that folder - مجاز نیستید زیرا شما اجازه افزودن پرونده به آن پوشه را ندارید - - - - Not allowed to upload this file because it is read-only on the server, restoring - آپلود این فایل با توجه به فقط-خواندنی بودن آن در سرور مجاز نیست، در حال بازگرداندن - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - خطا در نوشتن متادیتا در پایگاه داده - - - - OCC::PropagateDownloadFile - - - - File %1 can not be downloaded because of a local file name clash! - پرونده 1% بخاطر یک پرونده محلی به نام برخورد دانلود نمی شود! - - - - - %1 is currently in use - - - - - The download would reduce free local disk space below the limit - دانلود فضای دیسک محلی آزاد تحت محدودیت را کاهش می دهد - - - - Free space on disk is less than %1 - فضای خالی دیسک کمتر از %1 است - - - - File was deleted from server - فایل از روی سرور حذف شد - - - - The file could not be downloaded completely. - فایل به طور کامل قابل دانلود نیست. - - - - The downloaded file is empty despite the server announced it should have been %1. - پرونده دانلود شده خالی است با وجود اینکه سرور اعلام کرده است این باید 1% باشد. - - - - File %1 cannot be saved because of a local file name clash! - پرونده 1% بخاطر یک پرونده محلی به نام برخورد ذخیره نمی شود! - - - - - File has changed since discovery - پرونده از زمان کشف تغییر کرده است. - - - - Failed to dehydrate %1, the file is currently in use - - - - - Error writing metadata to the database - خطا در نوشتن متادیتا در پایگاه داده - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ؛ بازگردانی شکست خورد: 1% - - - - A file or folder was removed from a read only share, but restoring failed: %1 - یک پرونده یا پوشه از یک اشتراک فقط خواندنی حذف شد، اما بازگردانی شکست خورد: 1% - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - نمی توان پرونده 1% را حذف کرد: خطای 2% - - - - Attention, possible case sensitivity clash with %1 - - - - - could not create folder %1 - امکان ایجاد پوشه %1 وجود ندارد - - - - Error writing metadata to the database - خطا در نوشتن متادیتا در پایگاه داده - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash - 1% بخاطر یک پرونده محلی به نام برخورد حذف نمی شود - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - پرونده 1% بخاطر یک پرونده محلی به نام برخورد نتغییر نام داده نمی شود - - - - Could not rename %1 to %2, the file is currently in use - - - - - - Error writing metadata to the database - خطا در نوشتن متادیتا در پایگاه داده - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - کد HTTP اشتباه توسط سرور برگردانده شد. 204 انتظار می رفت، اما "1% 2%" دریافت شد. - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - کد HTTP اشتباه توسط سرور برگردانده شد. 201 انتظار می رفت، اما "1% 2%" دریافت شد. - - - - Error writing metadata to the database - خطا در نوشتن متادیتا در پایگاه داده - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - کد HTTP اشتباه توسط سرور برگردانده شد. 201 انتظار می رفت، اما "1% 2%" دریافت شد. - - - - - Error writing metadata to the database - خطا در نوشتن متادیتا در پایگاه داده - - - - Error setting pin state - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - پرونده 1% بارگذاری نمی شود زیرا پرونده دیگری با نام مشابه، که تنها در وضعیت متفاوت است، وجود دارد - - - - File Removed - فایل حذف شد - - - - Local file changed during syncing. It will be resumed. - پرونده محلی در طول همگام سازی تغییر کرد. این ادامه خواهد یافت. - - - - Local file changed during sync. - فایل محلی در حین همگام‌سازی تغییر کرده است. - - - - - Upload of %1 exceeds the quota for the folder - بارگذاری از 1% بیش از سهمیه برای پوشه است - - - - Error writing metadata to the database - خطا در نوشتن متادیتا در پایگاه داده - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - فایل محلی در حین همگام‌سازی حذف شده است. - - - - Local file changed during sync. - فایل محلی در حین همگام‌سازی تغییر کرده است. - - - - Poll URL missing - فاقد آدرس نظرسنجی - - - - Unexpected return code from server (%1) - کد بازگشت غیر منتظره از سرور (1%) - - - - Missing File ID from server - فاقد شناسه پرونده از سرور - - - - Missing ETag from server - فاقد ETag از سرور - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - فایل محلی در حین همگام‌سازی حذف شده است. - - - - Local file changed during sync. - فایل محلی در حین همگام‌سازی تغییر کرده است. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - Poll URL missing - فاقد آدرس نظرسنجی - - - - The local file was removed during sync. - فایل محلی در حین همگام‌سازی حذف شده است. - - - - Local file changed during sync. - فایل محلی در حین همگام‌سازی تغییر کرده است. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - سرور آخرین تکه را تایید نکرد. (برچسب الکترونیکی وجود نداشت) - - - - OCC::ProtocolWidget - - - Form - فرم - - - - TextLabel - برچسب متنی - - - - Open in browser - باز کردن در مرورگر - - - - Time - زمان - - - - File - فایل - - - - Folder - پوشه - - - - Action - فعالیت - - - - Size - اندازه - - - - Local sync protocol - پروتکل همگام سازی محلی - - - - Copy - کپی کردن - - - - Copy the activity list to the clipboard. - فهرست فعالیت را در کلیپ بورد کپی کنید. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - نیازمند احراز هویت پروکسی - - - - Username: - نام کاربری: - - - - Proxy: - پروکسی: - - - - The proxy server needs a username and password. - سرور پروکسی نیازمند نام‌کاربری و رمزعبور است. - - - - Password: - رمز عبور: - - - - TextLabel - برچسب متنی - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - انتخاب موارد همگام‌سازی - - - - OCC::SelectiveSyncWidget - - - Loading ... - درحال بارگذاری... - - - - Deselect remote folders you do not wish to synchronize. - پوشه‌های از راه دور را که نمی خواهید همگام سازی کنید، انتخاب نکنید. - - - - Name - نام - - - - Size - اندازه - - - - - No subfolders currently on the server. - هیچ زیر پوشه ای در حال حاضر در سرور وجود ندارد. - - - - An error occurred while loading the list of sub folders. - هنگام بارگذاری فهرست زیر پوشه ها خطایی روی داد. - - - - OCC::ServerNotificationHandler - - - Dismiss - پنهان کن - - - - OCC::SettingsDialog - - - Settings - تنظیمات - - - - Activity - فعالیت - - - - Add account - ایحاد حساب کاربری - - - - - Quit %1 - خروج %1 - - - - Are you sure you want to quit %1? - - - - - Account - حساب کاربری - - - - MainWindow - - - - - Hide - پنهان کردن - - - - OCC::ShareDialog - - - TextLabel - برچسب متنی - - - - share label - اشتراک‌گذاری برچسب - - - - Dialog - گفتگو - - - - ownCloud Path: - مسیر ownCloud: - - - - %1 Sharing - %1 اشتراک‌گذاری - - - - %1 - %1 - - - - Folder: %2 - پوشه: %2 - - - - The server does not allow sharing - سرور اجازه به اشتراک گذاشتن نمی دهد - - - - Retrieving maximum possible sharing permissions from server... - گرفتن مجوز بیشترین اشتراک ممکن از سرور... - - - - The file can not be shared because it was shared without sharing permission. - پرونده نمی تواند به اشتراک گذاشته شود زیرا بدون مجوز به اشتراک گذاشته شده است. - - - - Users and Groups - کاربران و گروه ها - - - - Public Links - پیوندهای عمومی - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - اشتراک NewDocument.odt - - - - Upload only (File Drop) - فقط بارگذاری (حذف پرونده) - - - - Receive files from others without revealing the contents of the folder. - پرونده ها را بدون آشکار شدن محتوای پوشه از سایرین دریافت کنید. - - - - Download / View / Edit - - - - - Name: - - - - - Enter name of new share... - - - - - &Create - - - - - TextLabel - برچسب متنی - - - - Set &password - تعیین &رمزعبور - - - - Set &expiration date - تعیین &تاریخ انقضا - - - - Set password - تعیین رمزعبور - - - - Recipients can view or download contents. - - - - - Download / View - - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - هر کسی با پیوند به پرونده/پوشه دسترسی دارد - - - - - P&assword protect - رمز عبور محافظت می شود - - - - The file can not be shared because it was shared without sharing permission. - پرونده نمی تواند به اشتراک گذاشته شود زیرا بدون مجوز به اشتراک گذاشته شده است. - - - - Link shares have been disabled - اشتراک های پیوند غیر فعال شده اند - - - - Create public link share - اشتراک لینک عمومی بسازید - - - - - Delete - حذف - - - - Open link in browser - لینک را در مرورگر باز کنید - - - - Copy link to clipboard - لینک را در کلیپ بورد کپی کنید - - - - Copy link to clipboard (direct download) - لینک را در کلیپ بورد کپی کنید (دانلود مستقیم) - - - - Send link by email - لینک را توسط ایمیل بفرست. - - - - Send link by email (direct download) - لینک را با پست الکترونیکی ارسال کنید (دانلود مستقیم) - - - - Create new... - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - حذف اشتراک گذاری لینک را تایید کنید - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>آیا شما واقعا می خواهید اشتراک لینک عمومی را حذف کنید<i>1%</i>؟</p><p> توجه: این عمل نمی تواند انجام نشود.</p> - - - - Cancel - لغو - - - - - Public link - پیوند عمومی - - - - Delete link share - حذف پیوند اشتراک - - - - - Please Set Password - لطفا رمزعبور را تعیین کنید - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - اشتراک NewDocument.odt - - - - Share with users or groups ... - اشتراک گذاری با کاربران یا گروه ها ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - این مورد با هیچ کاربر یا گروهی به اشتراک گذاشته نشده است - - - - Open link in browser - لینک را در مرورگر باز کنید - - - - Copy link to clipboard - لینک را در کلیپ بورد کپی کنید - - - - Send link by email - لینک را توسط ایمیل بفرست. - - - - No results for '%1' - هیچ نتیجه ای برای '1%' وجود ندارد - - - - I shared something with you - من چیزی را با شما به اشتراک گذاشتم - - - - OCC::ShareUserLine - - - Form - فرم - - - - - TextLabel - برچسب متنی - - - - can edit - می توان ویرایش کرد - - - - can share - قابل به اشتراک گذاری - - - - ... - ... - - - - create - ایجاد - - - - change - تغییر - - - - delete - حذف - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - اشتراک‌گذاری با %1 - - - - Context menu share - - - - - Context menu share %1 - - - - - Confirm deletion - - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - - - - - Do you want to delete the file <i>%1</i> permanently? - - - - - Select new location... - - - - - Error - خطا - - - - Moving file failed: - -%1 - - - - - I shared something with you - من چیزی را با شما به اشتراک گذاشتم - - - - - Share... - اشتراک گذاری... - - - - - Copy private link to clipboard - لینک خصوصی را در کلیپ بورد کپی کنید - - - - Send private link by email... - لینک خصوصی را پست الکترونیکی ارسال کنید... - - - - Resharing this file is not allowed - - - - - Resharing this folder is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - باز کردن در مرورگر - - - - Show file versions in browser - - - - - Rename... - - - - - Rename and upload... - - - - - Move and rename... - - - - - Move, rename and upload... - - - - - Delete local changes - - - - - Move and upload... - - - - - Delete - حذف - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>جزئیات گواهینامه s</h3> - - - - Common Name (CN): - نام مشترک (CN): - - - - Subject Alternative Names: - نام های جایگزین موضوع: - - - - Organization (O): - سازمان (O): - - - - Organizational Unit (OU): - واحد سازمان (OU): - - - - State/Province: - استان: - - - - Country: - کشور: - - - - Serial: - سریال: - - - - <h3>Issuer</h3> - <h3>صادر کننده</h3> - - - - Issuer: - صادرکننده: - - - - Issued on: - صدور در: - - - - Expires on: - منقضی شده در: - - - - <h3>Fingerprints</h3> - <h3>اثر انگشت</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>توجه: </b>این گواهی‌نامه به طور دستی تایید شده است</p> - - - - %1 (self-signed) - %1 (self-signed) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - این اتصال با استفاده از 1% بیت 2% رمزگذاری شده است. - - - - Server version: %1 - - - - - No support for SSL session tickets/identifiers - پشتیبانی برای جلسه SSL برچسب ها/شناسه ها وجود ندارد - - - - Certificate information: - اطلاعات گواهینامه: - - - - The connection is not secure - - - - - This connection is NOT secure as it is not encrypted. - - این اتصال امن نیست زیرا رمزگذاری نشده است. - - - - - OCC::SslErrorDialog - - - Form - فرم - - - - Trust this certificate anyway - د رهر صورت به این گواهی نامه اطمینان کن. - - - - Untrusted Certificate - گواهینامه‎ی غیر معتبر - - - - Cannot connect securely to <i>%1</i>: - عدم امکان اتصال امن به <i>%1</i>: - - - - with Certificate %1 - با گواهی %1 - - - - - - &lt;not specified&gt; - &lt؛ مشخص نشده است &gt؛ - - - - - Organization: %1 - سازماندهی : %1 - - - - - Unit: %1 - واحد: %1 - - - - - Country: %1 - کشور: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - اثر انگشت (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - اثرانگشت (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - تاریخ موثر: %1 - - - - Expiration Date: %1 - تاریخ انقضا: %1 - - - - Issuer: %1 - صادرکننده: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - 1% (به علت خطای قبلی از بین رفته است، دوباره در 2% امتحان کنید) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - تنها 1% موجود است، حداقل 2% برای شروع مورد نیاز است - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - پایگاه داده محلی باز یا ساخته نمی شود. اطمینان حاصل کنید که دسترسی به نوشتن در پوشه همگام سازی دارید. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - فضای دیسک کم است: دانلودهایی که فضای آزاد را به کمتر از 1% کاهش می دهند رد می شوند. - - - - There is insufficient space available on the server for some uploads. - برای بعضی از بارگذاری ها در سرور فضای کافی موجود نیست. - - - - Unresolved conflict. - ناسازگاری حل نشده. - - - - Could not update file : %1 - - - - - Could not update virtual file metadata: %1 - - - - - Using virtual files with suffix, but suffix is not set - - - - - Unable to read the blacklist from the local database - نمی توان لیست سیاه را از پایگاه داده محلی خواند - - - - Unable to read from the sync journal. - نمی توان از مجله همگام ساز خواند. - - - - Cannot open the sync journal - نمی توان مجله همگام ساز را باز کرد - - - - Aborted - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - سرتیتر checksum نادرست است. - - - - The checksum header contained an unknown checksum type '%1' - سرتیتر checksum شامل یک checksum نوع '1%' ناشناخته است - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - لطفا وارد شوید - - - - Folder %1: %2 - پوشه %1: %2 - - - - There are no sync folders configured. - هیچ پوشه‌ای برای همگام‌سازی تنظیم نشده است. - - - - Open in browser - باز کردن در مرورگر - - - - - - Log in... - ورود... - - - - - - Log out - خروج - - - - Recent Changes - تغییرات اخیر - - - - Managed Folders: - پوشه های مدیریت شده: - - - - Open folder '%1' - بازکردن پوشه‌ی '%1' - - - - Open %1 in browser - 1% را در مرورگر باز کنید - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - وضعیت نامعلوم - - - - Settings... - تنظیمات... - - - - Details... - جزئیات... - - - - Help - راه‌نما - - - - Quit %1 - خروج %1 - - - - Disconnected from %1 - قطع‌شده از %1 - - - - Unsupported Server Version - نسخه سرور پشتیبانی نشده - - - - Disconnected - قطع شده - - - - Disconnected from some accounts - قطع شده از برخی حساب ها - - - - Disconnected from accounts: - قطع شده از حساب ها: - - - - Account %1: %2 - حساب‌کاربری %1: %2 - - - - Signed out - خارج شد - - - - Account synchronization is disabled - همگام سازی حساب غیر فعال است - - - - - Synchronization is paused - همگام سازی متوقف شده است - - - - Unresolved conflicts - - - - - Error during synchronization - خطا حین همگام سازی - - - - No sync folders configured - هیچ پوشه‌ همگام سازی پیکربندی شده - - - - Unpause all folders - شروع مجدد تمام پوشه‌ها - - - - Pause all folders - توقف تمام پوشه‌ها - - - - Unpause all synchronization - شروع مجدد تمام همگام سازی ها - - - - Unpause synchronization - شروع مجدد همگام سازی - - - - Pause all synchronization - توقف تمام همگام سازی ها - - - - Pause synchronization - توقف همگام سازی - - - - Log out of all accounts - خروج از تمام حساب ها - - - - Log in to all accounts... - ورود به تمام حساب ها... - - - - New account... - حساب جدید... - - - - About %1 - درباره %1 - - - - No items synced recently - هیچ موردی به تازگی همگام‌سازی نشده است - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - همگام‌سازی %1 از%2 (%3 باقیمانده) - - - - Syncing %1 of %2 - همگام سازی 1% از 2% - - - - Syncing %1 (%2 left) - همگام‌سازی %1 (%2 باقیمانده) - - - - Syncing %1 - همگام‌سازی %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - تا تاریخ - - - - OwncloudAdvancedSetupPage - - - Form - فرم - - - - - - - - TextLabel - برچسب متنی - - - - Server - سرور - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>اگر جعبه بررسی شده است، وجود محتوا در پوشه محلی پاک خواهد شد تا یک همگام سازی جدید از سرور آغاز شود. </p><p> اگر محتوای محلی باید در پوشه های سرور بارگذاری شود، این را بررسی نکنید. </p></body></html> - - - - Start a &clean sync (Erases the local folder!) - شروع یک همگام سازی &clean (پوشه محلی را پاک می کند!) - - - - Ask for confirmation before synchroni&zing folders larger than - درخواست تایید پیش از همگام سازی پوشه‌های بزرگتر از - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - درخواست تایید پیش از همگام سازی مخازن خارجی - - - - Choose what to sync - انتخاب موارد همگام‌سازی - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - &پوشه محلی - - - - pbSelectLocalFolder - - - - - &Keep local data - & نگهداری داده‌ محلی - - - - S&ynchronize everything from server (recommended) - - - - - Status message - پیغام وضعیت - - - - OwncloudHttpCredsPage - - - Form - فرم - - - - &Username - &نام‌کاربری - - - - &Password - &رمزعبور - - - - OwncloudOAuthCredsPage - - - Form - فرم - - - - Please switch to your browser to proceed. - لطفا برای ادامه به مرورگر خود سوئیچ کنید. - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - مرورگر را دوباره باز کنید - - - - OwncloudSetupPage - - - Form - فرم - - - - - TextLabel - برچسب متنی - - - - Ser&ver Address - آدرس سرور - - - - https://... - https://... - - - - Error Label - برچسب خطا - - - - OwncloudWizardResultPage - - - Form - فرم - - - - TextLabel - برچسب متنی - - - - Your entire account is synced to the local folder - کل حساب شما با پوشه محلی همگام سازی شده است - - - - - PushButton - دکمه - - - - QObject - - - in the future - در آینده - - - - %n day(s) ago - n% روز پیشn% روز پیش - - - - %n hour(s) ago - n% ساعت پیشn% ساعت پیش - - - - now - اکنون - - - - Less than a minute ago - کمتر از یک دقیقه پیش - - - - %n minute(s) ago - n% دقیقه پیشn% دقیقه پیش - - - - Some time ago - چند وقت پیش - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - n% سالn% سال - - - - %n month(s) - n% ماهn% ماه - - - - %n day(s) - n% روزn% روز - - - - %n hour(s) - n% ساعتn% ساعت - - - - %n minute(s) - n% دقیقهn% دقیقه - - - - %n second(s) - n% ثانیهn% ثانیه - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3 %4%8%9Libraries Qt %5, %6%8Using virtual files plugin: %7%8 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - - - - - Replaced by virtual file - - - - - Downloaded - دریافت شده اند - - - - Uploaded - بارگذاری شده است. - - - - Server version downloaded, copied changed local file into conflict file - نسخه سرور دانلود شد، پرونده محلی تغییر یافته به پرونده ناسازگار کپی شد - - - - Deleted - حذف شده - - - - Moved to %1 - به %1 انتقال یافت - - - - Ignored - نادیده گرفته شد - - - - Filesystem access error - خطای دسترسی به فایل‌های سیستمی - - - - Error - خطا - - - - Updated local metadata - فرا داده محلی به روز رسانی شده - - - - - Unknown - نامشخص - - - - downloading - در حال دانلود - - - - uploading - در حال آپلود - - - - deleting - در حال حذف - - - - moving - در حال انتقال - - - - ignoring - نادیده گرفتن - - - - - error - خطا - - - - updating local metadata - فرا داده محلی به روز رسانی شده - - - - theme - - - Status undefined - وضعیت نامشخص است - - - - Waiting to start sync - در حال انتظار برای شروع همگام‌سازی - - - - Sync is running - همگام سازی در حال اجراست - - - - Sync Success - همگام‌سازی با موفقیت انجام شد - - - - Sync Success, some files were ignored. - همگام‌سازی موفق، تعداد از فایل‌ها نادیده گرفته شده‌اند. - - - - Sync Error - خطای همگام‌سازی - - - - Setup Error - خطای نصب - - - - Preparing to sync - آماده‌سازی همگام‌سازی - - - - Aborting... - - - - - Sync is paused - همگام‌سازیی فعلا متوقف شده است - - - - utility - - - Could not open browser - مرورگر باز نمی شود - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - در راه اندازی مرورگر برای رفتن به آدرس 1% خطایی وجود دارد. شاید مرورگر پیش فرض پیکربندی نشده است؟ - - - - Could not open email client - پست الکترونیکی مشتری باز نمی شود - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - در راه اندازی پست الکترونیکی مشتری برای ساخت یک پیام جدید خطایی وجود دارد. شاید پست الکترونیکی مشتری پیش فرض پیکربندی نشده است؟ - - - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - - - - - Make always available locally - - - - - Free up local space - - - - \ No newline at end of file diff --git a/translations/client_fi.ts b/translations/client_fi.ts deleted file mode 100644 index b444bc7d29f..00000000000 --- a/translations/client_fi.ts +++ /dev/null @@ -1,4423 +0,0 @@ - - - FileSystem - - - Could not make directories in trash - Ei voitu luoda kansioita roskakoriin - - - - - Could not move '%1' to '%2' - Ei voitu siirtää '%1' kohteeseen '%2' - - - - Moving to the trash is not implemented on this platform - - - - - FolderWizardSourcePage - - - Form - Lomake - - - - Pick a local folder on your computer to sync - Valitse synkronoitava paikalliskansio tietokoneelta - - - - &Choose... - &Valitse... - - - - FolderWizardTargetPage - - - Form - Lomake - - - - Select a remote destination folder - Valitse etäkohdekansio - - - - Create Folder - Luo kansio - - - - Refresh - Päivitä - - - - Folders - Kansiot - - - - TextLabel - TextLabel - - - - NotificationWidget - - - Form - Lomake - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - TekstiLeima - - - - OCC::AbstractNetworkJob - - - Connection timed out - Yhteys aikakatkaistiin - - - - Unknown error: network reply was deleted - Tuntematon virhe: verkkovastaus poistettiin - - - - Server replied "%1 %2" to "%3 %4" - - - - - OCC::AccountSettings - - - Form - Lomake - - - - ... - ... - - - - Storage space: ... - Tallennustila: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Ilman valintaa olevat kansiot <b>poistetaan</b> paikallisesta tiedostojärjestelmästä, eikä niitä synkronoida enää jatkossa tämän tietokoneen kanssa - - - - Synchronize all - Synkronoi kaikki - - - - Synchronize none - Älä synkronoi mitään - - - - Apply manual changes - Toteuta manuaalimuutokset - - - - Apply - Toteuta - - - - - - Cancel - Peruuta - - - - Connected with <server> as <user> - Yhdistetty palvelimeen <server> käyttäen tunnusta <user> - - - - No account configured. - Tiliä ei ole määritelty. - - - - Add new - Lisää uusi - - - - Remove - Poista - - - - Account - Tili - - - - Choose what to sync - Valitse synkronoitavat tiedot - - - - Force sync now - Pakota synkronointi nyt - - - - Restart sync - Käynnistä synkronointi uudelleen - - - - Remove folder sync connection - Poista kansion synkronointiyhteys - - - - Create virtual files for new files (Experimental) - - - - - Folder creation failed - Kansion luominen epäonnistui - - - - <p>Could not create local folder <i>%1</i>. - <p>Paikallisen kansion <i>%1</i> luominen epäonnistui. - - - - Confirm Folder Sync Connection Removal - Vahvista kansion synkronointiyhteyden poisto - - - - Remove Folder Sync Connection - Poista kansion synkronointiyhteys - - - - Sync Running - Synkronointi meneillään - - - - The syncing operation is running.<br/>Do you want to terminate it? - Synkronointioperaatio on meneillään.<br/>Haluatko keskeyttää sen? - - - - %1 in use - %1 käytössä - - - - %1 as <i>%2</i> - %1 käyttäjänä <i>%2</i> - - - - Connected to %1. - Yhteys muodostettu kohteeseen %1. - - - - Server %1 is temporarily unavailable. - Palvelin %1 ei ole juuri nyt saatavilla. - - - - Server %1 is currently in maintenance mode. - Palvelin %1 on juuri nyt huoltotilassa. - - - - Signed out from %1. - Kirjauduttu ulos kohteesta %1. - - - - Obtaining authorization from the browser. <a href='%1'>Click here</a> to re-open the browser. - - - - - Connecting to %1... - - - - - No connection to %1 at %2. - Ei yhteyttä kohteeseen %1 osoitteessa %2. - - - - Log in - Kirjaudu sisään - - - - There are folders that were not synchronized because they are too big: - - - - - There are folders that were not synchronized because they are external storages: - - - - - There are folders that were not synchronized because they are too big or external storages: - - - - - Confirm Account Removal - Vahvista tilin poistaminen - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Haluatko varmasti poistaa tilin <i>%1</i>?</p><p><b>Huomio:</b> Tämä toimenpide <b>ei</b> poista mitään tiedostoja.</p> - - - - Remove connection - Poista yhteys - - - - - Open folder - Avaa kansio - - - - - Log out - Kirjaudu ulos - - - - Resume sync - Palauta synkronointi - - - - Pause sync - Keskeytä synkronointi - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Haluatko varmasti lopettaa kansion <i>%1</i> synkronoinnin?</p><p><b>Huomio:</b> Tämä toimenpide <b>ei</b> poista mitään tiedostoja.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1/%2 (%3 %) käytössä. Jotkin kansiot, mukaan lukien verkkojaot ja jaetut kansiot, voivat sisältää eri rajoitukset. - - - - %1 of %2 in use - %1/%2 käytössä - - - - Currently there is no storage usage information available. - Tallennustilan käyttötietoja ei ole juuri nyt saatavilla. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - No %1 connection configured. - %1-yhteyttä ei ole määritelty. - - - - OCC::AccountState - - - Signed out - Kirjauduttu ulos - - - - Disconnected - Yhteys katkaistu - - - - Connected - Yhdistetty - - - - Service unavailable - Palvelu ei ole käytettävissä - - - - Maintenance mode - Huoltotila - - - - Network error - Verkkovirhe - - - - Configuration error - Asetusvirhe - - - - Asking Credentials - - - - - Unknown account state - Tuntematon tilin tila - - - - OCC::ActivityItemDelegate - - - %1 on %2 - - - - - %1 on %2 (disconnected) - - - - - OCC::ActivitySettings - - - - Server Activity - Palvelimen toimet - - - - Sync Protocol - Synkronointiprotokolla - - - - Not Synced - Ei synkronoitu - - - - Not Synced (%1) - %1 is the number of not synced files. - Ei synkronoitu (%1) - - - - The server activity list has been copied to the clipboard. - - - - - The sync activity list has been copied to the clipboard. - - - - - The list of unsynced items has been copied to the clipboard. - - - - - Copied to clipboard - Kopioitu leikepöydälle - - - - OCC::ActivityWidget - - - Form - Lomake - - - - - - TextLabel - TekstiLeima - - - - Server Activities - Palvelimen toimet - - - - Copy - Kopioi - - - - Copy the activity list to the clipboard. - Kopioi toimilista leikepöydälle. - - - - Action Required: Notifications - Toimenpiteitä vaaditaan: ilmoitukset - - - - <br/>Account %1 does not have activities enabled. - <br/>Tilillä %1 ei ole toimia käytössä. - - - - You received %n new notification(s) from %2. - - - - - You received %n new notification(s) from %1 and %2. - - - - - You received new notifications from %1, %2 and other accounts. - - - - - %1 Notifications - Action Required - - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - SSL-asiakkaan varmenteen tunnistautuminen - - - - This server probably requires a SSL client certificate. - Tämä palvelin vaatii luultavasti SSL-asiakasvarmenteen. - - - - Certificate & Key (pkcs12) : - Varmenne ja avain (pkcs12) : - - - - Browse... - Selaa... - - - - Certificate password : - Varmenteen salasana: - - - - Select a certificate - Valitse varmenne - - - - Certificate files (*.p12 *.pfx) - Varmennetiedostot (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Lopeta - - - - Continue - Jatka - - - - Error accessing the configuration file - - - - - There was an error while accessing the configuration file at %1. - - - - - Quit ownCloud - Lopeta ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - Tunnistautuminen vaaditaan - - - - Enter username and password for '%1' at %2. - Anna käyttäjätunnus ja salasana kohteeseen '%1' osoitteessa %2. - - - - &User: - K&äyttäjä: - - - - &Password: - &Salasana: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Virhe kirjoittaessa metadataa tietokantaan - - - - OCC::ConnectionValidator - - - No ownCloud account configured - ownCloud-tiliä ei ole määritelty - - - - The configured server for this client is too old - Määritelty palvelin on ohjelmistoversioltaan liian vanha tälle asiakasohjelmistolle - - - - Please update to the latest server and restart the client. - Päivitä uusimpaan palvelinversioon ja käynnistä asiakasohjelmisto uudelleen. - - - - Authentication error: Either username or password are wrong. - Tunnistautumisvirhe: käyttäjätunnus tai salasana on väärin. - - - - timeout - aikakatkaisu - - - - The provided credentials are not correct - Annetut tilitiedot eivät ole oikein - - - - OCC::DiscoveryMainThread - - - Aborted by the user - Keskeytetty käyttäjän toimesta - - - - OCC::DiscoverySingleDirectoryJob - - - The server file discovery reply is missing data. - - - - - OCC::Folder - - - Local folder %1 does not exist. - Paikallista kansiota %1 ei ole olemassa. - - - - %1 should be a folder but is not. - Kohteen %1 pitäisi olla kansio, mutta se ei kuitenkaan ole kansio. - - - - %1 is not readable. - %1 ei ole luettavissa. - - - - %1 has been removed. - %1 names a file. - %1 on poistettu. - - - - %1 has been downloaded. - %1 names a file. - %1 on ladattu. - - - - %1 has been updated. - %1 names a file. - %1 on päivitetty. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 on nimetty uudeelleen muotoon %2. - - - - %1 has been moved to %2. - %1 on siirretty kohteeseen %2. - - - - %1 and %n other file(s) have been removed. - - - - - %1 and %n other file(s) have been downloaded. - - - - - %1 and %n other file(s) have been updated. - - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - - - - - %1 has been moved to %2 and %n other file(s) have been moved. - - - - - %1 has and %n other file(s) have sync conflicts. - - - - - %1 has a sync conflict. Please check the conflict file! - - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - - - - - %1 could not be synced due to an error. See the log for details. - Kohdetta %1 ei voi synkronoida virheen vuoksi. Katso tarkemmat tiedot lokista. - - - - Sync Activity - Synkronointiaktiviteetti - - - - Could not read system exclude file - - - - - A new folder larger than %1 MB has been added: %2. - - Uusi kansio, joka on suurempi kuin %1 Mt, on lisätty: %2. - - - - - A folder from an external storage has been added. - - Kansio erillisestä tallennustilasta on lisätty. - - - - - Please go in the settings to select it if you wish to download it. - - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - Poistetaanko kaikki tiedostot? - - - - Remove all files - Poista kaikki tiedostot - - - - Keep files - Säilytä tiedostot - - - - This sync would reset the files to an earlier time in the sync folder '%1'. -This might be because a backup was restored on the server. -Continuing the sync as normal will cause all your files to be overwritten by an older file in an earlier state. Do you want to keep your local most recent files as conflict files? - - - - - Backup detected - Varmuuskopio poistettu - - - - Normal Synchronisation - Normaali synkronointi - - - - Keep Local Files as Conflict - - - - - OCC::FolderMan - - - Could not reset folder state - Kansion tilaa ei voitu alustaa - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - - - - - (backup) - (varmuuskopio) - - - - (backup %1) - (varmuuskopio %1) - - - - Undefined State. - Määrittelemätön tila. - - - - Waiting to start syncing. - Odotetaan synkronoinnin aloitusta. - - - - Preparing for sync. - Valmistellaan synkronointia. - - - - Sync is running. - Synkronointi on meneillään. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Viimeisin synkronointi suoritettiin onnistuneesti. - - - - Setup Error. - Asetusvirhe. - - - - User Abort. - - - - - Sync is paused. - Synkronointi on keskeytetty. - - - - %1 (Sync is paused) - %1 (Synkronointi on keskeytetty) - - - - No valid folder selected! - Kelvollista kansiota ei ole valittu! - - - - The selected path is not a folder! - Valittu polku ei ole kansio! - - - - You have no permission to write to the selected folder! - Sinulla ei ole kirjoitusoikeutta valittuun kansioon! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Paikallinen kansio %1 sisältää kansion, jota käytetään kansion synkronointiyhteydessä. Valitse toinen kansio! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Lisää kansion synkronointiyhteys - - - - Synchronizing with local folder - Synkronoidaan paikallisen kansion kanssa - - - - File - Tiedosto - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Yhteyden tulee olla muodostettu, jotta voit lisätä kansion - - - - Click this button to add a folder to synchronize. - Napsauta valitaksesi synkronoitavan kansion. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Virhe ladatessa kansiolistausta palvelimelta. - - - - Signed out - Kirjauduttu ulos - - - - Fetching folder list from server... - Haetaan kansioluetteloa palvelimelta... - - - - There are unresolved conflicts. Click for details. - Selvittämättömiä ristiriitoja. Napsauta saadaksesi lisätietoja. - - - - New files are being created as virtual files. - - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synkronoidaan %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - - - - - u2193 %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - - - - - u2191 %1/s - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3/%4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 jäljellä, %1/%2, tiedosto %3/%4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1/%2, tiedosto %3/%4 - - - - file %1 of %2 - tiedosto %1/%2 - - - - Waiting... - Odotetaan... - - - - Waiting for %n other folder(s)... - Odotetaan %n muuta kansiota...Odotetaan %n muuta kansiota... - - - - Preparing to sync... - Valmistaudutaan synkronointiin... - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Lisää kansion synkronointiyhteys - - - - Add Sync Connection - Lisää synkronointiyhteys - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Napsauta valitaksesi synkronoitavan paikalliskansion. - - - - Enter the path to the local folder. - Anna paikallisen kansion polku - - - - Select the source folder - Valitse lähdekansio - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Luo etäkansio - - - - Enter the name of the new folder to be created below '%1': - - - - - Folder was successfully created on %1. - - - - - Authentication failed accessing %1 - - - - - Failed to create the folder on %1. Please check manually. - - - - - Failed to list a folder. Error: %1 - Kansion listaaminen epäonnistui. Virhe: %1 - - - - Choose this to sync the entire account - Valitse tämä synkronoidaksesi koko tilin - - - - This folder is already being synced. - Tätä kansiota synkronoidaan jo. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Synkronoit jo kansiota <i>%1</i>, ja se on kansion <i>%2</i> yläkansio. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately (experimental) - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Varoitus:</b> %1 - - - - <b>Warning:</b> - <b>Varoitus:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - - - - - We received a different E-Tag for resuming. Retrying next time. - - - - - Server returned wrong content-range - - - - - Connection Timeout - Yhteys aikakatkaistiin - - - - OCC::GeneralSettings - - - Form - Lomake - - - - General Settings - Yleisasetukset - - - - For System Tray - Ilmoitusalueelle - - - - stable - - - - - beta - - - - - Advanced - Lisäasetukset - - - - Ask for confirmation before synchronizing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - Mt - - - - Ask for confirmation before synchronizing external storages - - - - - &Launch on System Startup - &Käynnistä järjestelmän käynnistyessä - - - - Show &Desktop Notifications - &Näytä työpöytäilmoitukset - - - - Use &Monochrome Icons - Käytä &mustavalkoisia kuvakkeita - - - - Version - Versio - - - - &Update Channel - - - - - • - - - - - Edit &Ignored Files - Muokkaa &ohitettavia tiedostoja - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - N&äytä kaatumisraportoija - - - - &Restart && Update - &Käynnistä uudelleen && päivitä - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Peruuta - - - - OCC::GetOrCreatePublicLinkShare - - - Sharing error - - - - - Could not retrieve or create the public link share. Error: - -%1 - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Anna %1-salasana:<br><br>Käyttäjä: %2<br>Tili: %3<br> - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - Anna salasana - - - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Ohitettavien tiedostojen muokkain - - - - Global Ignore Settings - Yleiset ohitusasetukset - - - - Sync hidden files - Synkronoi piilotiedostot - - - - Files Ignored by Patterns - Kaavojen perusteella ohitettavat tiedostot - - - - Add - Lisää - - - - Pattern - Kaava - - - - Allow Deletion - Salli poistaminen - - - - Remove - Poista - - - - Files or folders matching a pattern will not be synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Kaavaa vastaavat tiedostot ja kansiot jätetään synkronoimatta. - -Kohteet, joiden poisto on sallittu, poistetaan, jos ne estävät kansion poistamisen. Tämä on hyödyllistä metatietojen osalta. - - - - Could not open file - Tiedoston avaaminen ei onnistunut - - - - Cannot write changes to '%1'. - Muutoksien kirjoittaminen kohteeseen '%1' epäonnistui. - - - - Add Ignore Pattern - Lisää ohituskaava - - - - Add a new ignore pattern: - Lisää uusi ohituskaava: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - - - - - OCC::IssuesWidget - - - Form - Lomake - - - - List of issues - Lista ongelmista - - - - Account - Tili - - - - - <no filter> - <no filter> - - - - - Folder - Kansio - - - - Show warnings - Näytä varoitukset - - - - Show ignored files - Näytä ohitetut tiedostot - - - - There were too many issues. Not all will be visible here. - Liian monta virhettä. Kaikki eivät ole näkyvissä tässä. - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - Kopioi ongelmalista leikepöydälle. - - - - Copy - Kopioi - - - - Time - Aika - - - - File - Tiedosto - - - - Issue - Ongelma - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LogBrowser - - - Log Output - Loki - - - - &Search: - &Etsi: - - - - &Find - &Etsi - - - - &Capture debug messages - &Kaappaa vianjäljitysviestit - - - - Permanently save logs - - - - - When this option is enabled and no other logging is configured, logs will be written to a temporary folder and expire after a few hours. This setting persists across client restarts. - -Logs will be written to %1 - - - - - Clear - Tyhjennä - - - - Clear the log display. - Tyhjennä lokinäyttö. - - - - S&ave - &Tallenna - - - - Save the log file to a file on disk for debugging. - Tallenna loki tiedostoon virheenetsintää varten. - - - - Save log file - Tallenna lokitiedosto - - - - Error - Virhe - - - - Could not write to log file %1 - Lokitiedostoon %1 kirjoittaminen epäonnistui - - - - OCC::Logger - - - Error - Virhe - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>Tiedostoa '%1'<br/>ei voida avata kirjoittamista varten.<br/><br/>Lokitulostusta<b>ei</b>pystytä tallentamaan!</nobr> - - - - OCC::NSISUpdater - - - New Version Available - Uusi versio saatavilla - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Uusi versio %1-asiakasohjelmistosta on saatavilla.</p><p><b>%2</b> on ladattavissa. Asennettu versio on %3.</p> - - - - Skip this version - Ohita tämä versio - - - - Skip this time - Ohita tämän kerran - - - - Get update - Päivitä - - - - OCC::NetworkSettings - - - Form - Lomake - - - - Proxy Settings - Välityspalvelimen asetukset - - - - No Proxy - Ei välityspalvelinta - - - - Use system proxy - Käytä järjestelmän välityspalvelinta - - - - Specify proxy manually as - Määritä välityspalvelin käsin - - - - Host - Isäntä - - - - : - : - - - - Proxy server requires authentication - Välityspalvelin vaatii tunnistautumisen - - - - Download Bandwidth - Latauskaista - - - - - Limit to - Rajoita - - - - - KBytes/s - kilotavua/s - - - - - No limit - Ei rajoitusta - - - - - Limit to 3/4 of estimated bandwidth - Rajoita 3/4:aan arvioidusta kaistanleveydestä - - - - Upload Bandwidth - Lähetyskaista - - - - - Limit automatically - Rajoita automaattisesti - - - - Hostname of proxy server - Välityspalvelinkoneen nimi - - - - Username for proxy server - Välityspalvelimen käyttäjätunnus - - - - Password for proxy server - Välityspalvelimen salasana - - - - HTTP(S) proxy - HTTP(S)-välityspalvelin - - - - SOCKS5 proxy - SOCKS5-välityspalvelin - - - - OCC::NotificationWidget - - - Created at %1 - Luotu %1 - - - - Closing in a few seconds... - Suljetaan muutamassa sekunnissa... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' valittu %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Palvelimelta palautettu virhe: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - The reply from the server did not contain all expected fields - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Kirjautumisvirhe</h1><p>%1</p> - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - Uusi %1-päivitys valmiina - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Uusi %1-päivitys on valmiina asennettavaksi. Päivitysohjelma saattaa pyytää lisäoikeuksia päivityksen aikana. - - - - Downloading version %1. Please wait... - Ladataan versiota %1. Odota hetki... - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Päivityksen lataaminen epäonnistui. Napsauta <a href='%1'>tästä</a> ladataksesi päivityksen manuaalisesti. - - - - Could not check for new updates. - Päivitysten tarkistus epäonnistui. - - - - %1 version %2 available. Restart application to start the update. - %1-versio %2 saatavilla. Käynnistä sovellus uudelleen aloittaaksesi päivityksen. - - - - New %1 version %2 available. Please use the system's update tool to install it. - Uusi %1-versio %2 on saatavilla. Käytä järjestelmän päivitystyökalua asentaaksesi sen. - - - - Checking update server... - Tarkistetaan päivityspalvelinta... - - - - Update status is unknown: Did not check for new updates. - Päivityksen tila on tuntematon: uusia päivityksiä ei tarkistettu. - - - - No updates available. Your installation is at the latest version. - Päivityksiä ei ole saatavilla. Käytössäsi on uusin versio. - - - - Update Check - Päivitystarkistus - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Muodosta yhteys - %1 - - - - Setup local folder options - Aseta paikallisen kansion valinnat - - - - Connect... - Yhdistä... - - - - %1 folder '%2' is synced to local folder '%3' - %1-kansio '%2' on synkronoitu paikalliseen kansioon '%3' - - - - Sync the folder '%1' - Synkronoi kansio '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>Varoitus:</strong> Paikallinen kansio ei ole tyhjä. Valitse jatkotoimenpide!</small></p> - - - - Local Sync Folder - Paikallinen synkronointikansio - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Yhteys epäonnistui - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Yhteys määritettyyn palvelimen salattuun osoitteeseen epäonnistui. Miten haluat edetä?</p></body></html> - - - - Select a different URL - Valitse eri verkko-osoite - - - - Retry unencrypted over HTTP (insecure) - Yritä uudelleen salaamattomana HTTP:n yli (turvaton!) - - - - Configure client-side TLS certificate - Määritä asiakaspuolen TLS-varmenteen asetukset - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Yhteys palvelimen salattuun osoitteeseen <em>%1</em> epäonnistui. Miten haluat edetä?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Sähköpostiosoite - - - - Connect to %1 - Muodosta yhteys - %1 - - - - Enter user credentials - Anna käyttäjätiedot - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Muodosta yhteys - %1 - - - - Login in your browser - Kirjaudu selaimeesi - - - - Copy link to clipboard - Kopioi linkki leikepöydälle - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Muodosta yhteys - %1 - - - - Setup %1 server - %1-palvelimen asetuksien määritys - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Tämä osoite ei ole turvallinen, koska sitä ei ole salattu. -Osoitteen käyttäminen ei ole suositeltavaa. - - - - This url is secure. You can use it. - Tämä osoite on turvallinen. Voit käyttää sitä. - - - - &Next > - &Seuraava > - - - - Invalid URL - Virheellinen verkko-osoite - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">Muodostettu yhteys onnistuneesti kohteeseen %1: %2 versio %3 (%4)</font><br/><br/> - - - - Failed to connect to %1 at %2:<br/>%3 - Yhteys %1iin osoitteessa %2 epäonnistui:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Aikakatkaisu yrittäessä yhteyttä kohteeseen %1 osoitteessa %2. - - - - Trying to connect to %1 at %2... - Yritetään yhdistetää palvelimeen %1 portissa %2... - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - - - - - There was an invalid response to an authenticated webdav request - Todennettuun webdav-pyyntöön saatiin virheellinen vastaus - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - Palvelin esti käyttämisen. Vahvista käyttöoikeutesi palvelimeen <a href="%1">napsauttamalla tästä</a> ja kirjaudu palveluun selaimella. - - - - Invalid URL - Virheellinen verkko-osoite - - - - The server reported the following error: - Palvelin ilmoitti seuraavan virheen: - - - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - Paikallinen kansio %1 on jo olemassa, asetetaan se synkronoitavaksi.<br/><br/> - - - - Creating local sync folder %1... - Luodaan paikallista synkronointikansiota %1... - - - - ok - ok - - - - failed. - epäonnistui. - - - - Could not create local folder %1 - Paikalliskansion %1 luonti epäonnistui - - - - No remote folder specified! - Etäkansiota ei määritelty! - - - - Error: %1 - Virhe: %1 - - - - creating folder on ownCloud: %1 - luodaan kansio ownCloudiin: %1 - - - - Remote folder %1 created successfully. - Etäkansio %1 luotiin onnistuneesti. - - - - The remote folder %1 already exists. Connecting it for syncing. - Etäkansio %1 on jo olemassa. Otetaan siihen yhteyttä tiedostojen täsmäystä varten. - - - - - The folder creation resulted in HTTP error code %1 - Kansion luonti aiheutti HTTP-virhekoodin %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Etäkansion luominen epäonnistui koska antamasi tunnus/salasana ei täsmää!<br/>Ole hyvä ja palaa tarkistamaan tunnus/salasana</p> - - - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">Pilvipalvelun etäkansion luominen ei onnistunut , koska tunnistautumistietosi ovat todennäköisesti väärin.</font><br/>Palaa takaisin ja tarkista käyttäjätunnus ja salasana.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Etäkansion %1 luonti epäonnistui, virhe <tt>%2</tt>. - - - - A sync connection from %1 to remote directory %2 was set up. - Täsmäysyhteys kansiosta %1 etäkansioon %2 on asetettu. - - - - Successfully connected to %1! - Yhteys kohteeseen %1 muodostettiin onnistuneesti! - - - - Connection to %1 could not be established. Please check again. - Yhteyttä osoitteeseen %1 ei voitu muodostaa. Ole hyvä ja tarkista uudelleen. - - - - Folder rename failed - Kansion nimen muuttaminen epäonnistui - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - - - - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>Paikallinen synkronointikansio %1 luotu onnistuneesti!</b></font> - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1-yhteysavustaja - - - - Enable experimental feature? - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Enable experimental mode - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Kaikki valmiina! - - - - Open Local Folder - Avaa paikallinen kansio - - - - Open %1 in Browser - Avaa %1 selaimessa - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Virhe kirjoittaessa metadataa tietokantaan - - - - OCC::PropagateDownloadFile - - - File %1 can not be downloaded because of a local file name clash! - - - - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - Levyllä on vapaata tilaa vähemmän kuin %1 - - - - File was deleted from server - Tiedosto poistettiin palvelimelta - - - - The file could not be downloaded completely. - Tiedostoa ei voitu ladata täysin. - - - - The downloaded file is empty despite the server announced it should have been %1. - - - - - File %1 cannot be saved because of a local file name clash! - - - - - File has changed since discovery - Tiedosto on muuttunut löytymisen jälkeen - - - - Error writing metadata to the database - Virhe kirjoittaessa metadataa tietokantaan - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - - - - - A file or folder was removed from a read only share, but restoring failed: %1 - - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - - - - - Attention, possible case sensitivity clash with %1 - - - - - could not create folder %1 - kansiota %1 ei voitu luoda - - - - Error writing metadata to the database - Virhe kirjoittaessa metadataa tietokantaan - - - - OCC::PropagateLocalRemove - - - Error removing '%1': %2; - Virhe poistaessa '%1': %2; - - - - Could not remove folder '%1' - Ei voitu poistaa kansiota '%1' - - - - Could not remove %1 because of a local file name clash - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - - - - - - Error writing metadata to the database - Virhe kirjoittaessa metadataa tietokantaan - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - HTTP-palvelin palautti väärän koodin. Odotettiin koodia 204, vastaanotettiin "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - HTTP-palvelin palautti väärän koodin. Odotettiin koodia 201, vastaanotettiin "%1 %2". - - - - Error writing metadata to the database - Virhe kirjoittaessa metadataa tietokantaan - - - - OCC::PropagateRemoteMove - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - HTTP-palvelin palautti väärän koodin. Odotettiin koodia 201, vastaanotettiin "%1 %2". - - - - - Error writing metadata to the database - Virhe kirjoittaessa metadataa tietokantaan - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - Tiedosto poistettu - - - - Local file changed during syncing. It will be resumed. - - - - - Local file changed during sync. - Paikallinen tiedosto muuttui synkronoinnin aikana. - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - Virhe kirjoittaessa metadataa tietokantaan - - - - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - Paikallinen tiedosto poistettiin synkronoinnin aikana. - - - - Local file changed during sync. - Paikallinen tiedosto muuttui synkronoinnin aikana. - - - - Unexpected return code from server (%1) - Odottamaton paluukoodi palvelimelta (%1) - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileV1 - - - Poll URL missing - - - - - The local file was removed during sync. - Paikallinen tiedosto poistettiin synkronoinnin aikana. - - - - Local file changed during sync. - Paikallinen tiedosto muuttui synkronoinnin aikana. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolWidget - - - Form - Lomake - - - - TextLabel - TekstiLeima - - - - Open in browser - Avaa selaimessa - - - - Time - Aika - - - - File - Tiedosto - - - - Folder - Kansio - - - - Action - Toiminto - - - - Size - Koko - - - - Local sync protocol - Paikallinen synkronointiprotokolla - - - - Copy - Kopioi - - - - Copy the activity list to the clipboard. - Kopioi toimilista leikepöydälle. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Välityspalvelin vaatii tunnistautumisen - - - - Username: - Käyttäjätunnus: - - - - Proxy: - Välityspalvelin: - - - - The proxy server needs a username and password. - Välityspalvelin vaatii käyttäjätunnuksen ja salasanan. - - - - Password: - Salasana: - - - - TextLabel - TekstiLeima - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Valitse synkronoitavat tiedot - - - - OCC::SelectiveSyncWidget - - - Loading ... - Ladataan... - - - - Deselect remote folders you do not wish to synchronize. - - - - - Name - Nimi - - - - Size - Koko - - - - - No subfolders currently on the server. - Palvelimella ei ole alihakemistoja juuri nyt. - - - - An error occurred while loading the list of sub folders. - Alikansioluetteloa ladatessa tapahtui virhe. - - - - OCC::ServerNotificationHandler - - - Dismiss - Hylkää - - - - OCC::SettingsDialog - - - Settings - Asetukset - - - - Activity - Toimet - - - - General - Yleiset - - - - Network - Verkko - - - - Account - Tili - - - - OCC::SettingsDialogMac - - - %1 - %1 - - - - Activity - Toimet - - - - General - Yleiset - - - - Network - Verkko - - - - - Account - Tili - - - - OCC::ShareDialog - - - TextLabel - TekstiLeima - - - - share label - - - - - Dialog - - - - - ownCloud Path: - ownCloud-polku: - - - - %1 Sharing - %1 - Jakaminen - - - - %1 - %1 - - - - Folder: %2 - Kansio: %2 - - - - The server does not allow sharing - Palvelin ei salli jakamista - - - - Retrieving maximum possible sharing permissions from server... - - - - - The file can not be shared because it was shared without sharing permission. - - - - - Users and Groups - Käyttäjät ja ryhmät - - - - Public Links - Julkiset linkit - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Jaa UusiAsiakirja.odt - - - - Upload only (File Drop) - - - - - Receive files from others without revealing the contents of the folder. - - - - - TextLabel - TekstiLeima - - - - Set &password - Aseta &salasana - - - - Enter a name to create a new public link... - Anna nimi luodaksesi uuden julkisen linkin... - - - - &Create new - &Luo uusi - - - - Set &expiration date - Aseta &vanhenemispäivä - - - - Set password - Aseta salasana - - - - Recipients can view or download contents. - Vastaanottaja voi katsoa tai ladata sisältöä. - - - - Download / View - Lataa / Näytä - - - - Download / View / Upload - Lataa / Näytä / Lähetä - - - - Recipients can view, download, edit, delete and upload contents. - Vastaanottaja voi katsoa, ladata, muokata, poistaa ja lähettää sisältöä. - - - - Anyone with the link has access to the file/folder - Kenellä tahansa linkin omaavalla on pääsy tiedostoon/kansioon - - - - - P&assword protect - &Suojaa salasanalla - - - - The file can not be shared because it was shared without sharing permission. - - - - - Link shares have been disabled - Linkkijaot on poistettu käytöstä - - - - Create public link share - Luo julkinen linkkijako - - - - - Delete - Poista - - - - Open link in browser - Avaa linkki selaimessa - - - - Copy link to clipboard - Kopioi linkki leikepöydälle - - - - Copy link to clipboard (direct download) - - - - - Send link by email - Lähetä linkki sähköpostitse - - - - Send link by email (direct download) - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - - - - - Cancel - Peruuta - - - - - Public link - Julkinen linkki - - - - Delete link share - Poista linkkijako - - - - Public sh&aring requires a password - &Julkinen jakaminen vaatii salasanan - - - - Please Set Password - Aseta salasana - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Jaa UusiAsiakirja.odt - - - - Share with users or groups ... - Jaa käyttäjien tai ryhmien kanssa… - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - - - - - Open link in browser - Avaa linkki selaimessa - - - - Copy link to clipboard - Kopioi linkki leikepöydälle - - - - Send link by email - Lähetä linkki sähköpostitse - - - - No results for '%1' - Ei tuloksia haulla '%1' - - - - I shared something with you - Jaoin jotain kanssasi - - - - OCC::ShareUserLine - - - Form - Lomake - - - - - TextLabel - TekstiLeima - - - - can edit - voi muokata - - - - can share - jaa - - - - ... - ... - - - - create - luo - - - - change - muuta - - - - delete - poista - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::ShibbolethCredentials - - - Login Error - Kirjautumisvirhe - - - - You must sign in as user %1 - Sinun tulee kirjautua käyttäjänä %1 - - - - OCC::ShibbolethWebView - - - %1 - Authenticate - %1 - Tunnistaudu - - - - SSL Chipher Debug View - - - - - Reauthentication required - Tunnistaudu uudelleen - - - - Your session has expired. You need to re-login to continue to use the client. - Istunto on vanhentunut. Kirjaudu uudelleen jatkaaksesi sovelluksen käyttämistä. - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - - - - - Context menu share - - - - - I shared something with you - Jaoin jotain kanssasi - - - - - Share... - Jaa... - - - - - Copy private link to clipboard - Kopioi yksityinen linkki leikepöydälle - - - - Send private link by email... - Lähetä yksityinen linkki sähköpostitse... - - - - Resharing this file is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - Avaa selaimessa - - - - Download file(s) - - - - - Replace file(s) by virtual file - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Varmenteen tiedot</h3> - - - - Common Name (CN): - Yleinen nimi (CN): - - - - Subject Alternative Names: - - - - - Organization (O): - Organisaatio (O): - - - - Organizational Unit (OU): - Organisaatioyksikkö (OU): - - - - State/Province: - Lääni/maakunta/provinssi: - - - - Country: - Maa: - - - - Serial: - Sarjanumero: - - - - <h3>Issuer</h3> - <h3>Myöntäjä</h3> - - - - Issuer: - Myöntäjä: - - - - Issued on: - Myönnetty: - - - - Expires on: - Vanhenee: - - - - <h3>Fingerprints</h3> - <h3>Sormenjäljet</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Huomio:</b> Tämä varmenne hyväksyttiin käsin</p> - - - - %1 (self-signed) - %1 (allekirjoitettu itse) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Yhteys on salattu, käytössä %1-bittinen %2. - - - - - No support for SSL session tickets/identifiers - - - - - Certificate information: - Varmenteen tiedot: - - - - This connection is NOT secure as it is not encrypted. - - Yhteys EI OLE turvallinen, koska sitä ei ole salattu. - - - - - OCC::SslErrorDialog - - - Form - Lomake - - - - Trust this certificate anyway - Luota tähän varmisteeseen silti - - - - Untrusted Certificate - Varmenne ei ole luotettu - - - - Cannot connect securely to <i>%1</i>: - Yhteyttä kohteeseen <i>%1</i> ei voi muodostaa turvallisesti: - - - - with Certificate %1 - varmenteella %1 - - - - - - &lt;not specified&gt; - &lt;ei määritelty&gt; - - - - - Organization: %1 - Organisaatio: %1 - - - - - Unit: %1 - Yksikkö: %1 - - - - - Country: %1 - Maa: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Sormenjälki (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Sormenjälki (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Voimassa oleva päivämäärä: %1 - - - - Expiration Date: %1 - Vanhenemispäivä: %1 - - - - Issuer: %1 - Myöntäjä: %1 - - - - OCC::SyncEngine - - - Success. - Onnistui. - - - - A network connection timeout happened. - Tapahtui verkon aikakatkaisu. - - - - A HTTP transmission error happened. - Tapahtui HTTP-välitysvirhe. - - - - The mounted folder is temporarily not available on the server - Liitetty kansio on väliaikaisesti pois käytöstä tällä palvelimella - - - - An error occurred while opening a folder - Kansiota avatessa tapahtui virhe - - - - Error while reading folder. - Kansiota lukiessa tapahtui virhe - - - - %1 (skipped due to earlier error, trying again in %2) - - - - - File/Folder is ignored because it's hidden. - Tiedosto/kansi ohitetaan, koska se on piilotettu. - - - - Folder hierarchy is too deep - - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Vain %1 on käytettävissä, käynnistymiseen tarvitaan %2 - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Not allowed because you don't have permission to add parent folder - Ei sallittu, koska käyttöoikeutesi eivät riitä ylätason kansion lisäämiseen - - - - Not allowed because you don't have permission to add files in that folder - Ei sallittu, koska käyttöoikeutesi eivät riitä tiedostojen lisäämiseen kyseiseen kansioon - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - Palvelimella ei ole riittävästi tallennustilaa joidenkin lähetysten osalta. - - - - CSync unspecified error. - CSync - määrittämätön virhe. - - - - Aborted by the user - Keskeytetty käyttäjän toimesta - - - - Failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder. - - - - - Discovery step failed. - - - - - Permission denied. - - - - - File or directory not found: - - - - - Tried to create a folder that already exists. - - - - - No space on %1 server available. - - - - - The service is temporarily unavailable - Palvelu ei ole juuri nyt käytettävissä - - - - Access is forbidden - Pääsy estetty - - - - An internal error number %1 occurred. - Sisäinen virhe, numero %1. - - - - Symbolic links are not supported in syncing. - Symboliset linkit eivät ole tuettuja synkronoinnissa. - - - - File is listed on the ignore list. - Tiedosto on ohituslistalla. - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - - - - - The file name is a reserved name on this file system. - - - - - Filename contains trailing spaces. - - - - - Filename is too long. - Tiedoston nimi on liian pitkä. - - - - The filename cannot be encoded on your file system. - - - - - Unresolved conflict. - Selvittämätön ristiriita. - - - - Stat failed. - Stat epäonnistui. - - - - Filename encoding is not valid - Tiedostonimen merkistökoodaus ei ole kelvollista - - - - Invalid characters, please rename "%1" - Virheellisiä merkkejä, anna uusi nimi kohteelle "%1" - - - - Using virtual files but suffix is not set - - - - - Unable to read the blacklist from the local database - - - - - Unable to read from the sync journal. - - - - - Cannot open the sync journal - - - - - File name contains at least one invalid character - Tiedoston nimi sisältää ainakin yhden virheellisen merkin - - - - - Ignored because of the "choose what to sync" blacklist - - - - - Not allowed because you don't have permission to add subfolders to that folder - Ei sallittu, koska oikeutesi eivät riitä alikansioiden lisäämiseen kyseiseen kansioon - - - - Not allowed to upload this file because it is read-only on the server, restoring - - - - - - Not allowed to remove, restoring - Poistaminen ei ole sallittua, palautetaan - - - - Local files and share folder removed. - Paikalliset tiedostot ja jakokansio poistettu. - - - - Move not allowed, item restored - Siirtäminen ei ole sallittua, kohde palautettu - - - - Move not allowed because %1 is read-only - Siirto ei ole sallittu, koska %1 on "vain luku"-tilassa - - - - the destination - kohde - - - - the source - lähde - - - - OCC::SyncLogDialog - - - Synchronisation Log - Synkronointiloki - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p> - - - - - <p>Copyright ownCloud GmbH</p> - <p>Tekijänoikeus ownCloud GmbH</p> - - - - <p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%2 and the %2 logo are registered trademarks of %1 in the United States, other countries, or both.</p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Tarkistesumman otsake on muodostettu väärin. - - - - The checksum header contained an unknown checksum type '%1' - - - - - The downloaded file does not match the checksum, it will be resumed. - - - - - OCC::ownCloudGui - - - Please sign in - Kirjaudu sisään - - - - Folder %1: %2 - Kansio %1: %2 - - - - There are no sync folders configured. - Synkronointikansioita ei ole määritelty. - - - - Open in browser - Avaa selaimessa - - - - - - Log in... - Kirjaudu sisään... - - - - - - Log out - Kirjaudu ulos - - - - Recent Changes - Viimeisimmät muutokset - - - - Managed Folders: - Hallitut kansiot: - - - - Open folder '%1' - Avaa kansio '%1' - - - - Open %1 in browser - Avaa %1 selaimeen - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Tuntematon tila - - - - Settings... - Asetukset... - - - - Details... - Tiedot... - - - - Help - Ohje - - - - Quit %1 - Lopeta %1 - - - - Disconnected from %1 - Katkaise yhteys kohteeseen %1 - - - - Unsupported Server Version - Palvelimen versio ei ole tuettu - - - - Disconnected - Yhteys katkaistu - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - Katkaistu yhteys tileihin: - - - - Account %1: %2 - Tili %1: %2 - - - - Signed out - Kirjauduttu ulos - - - - Account synchronization is disabled - Tilin synkronointi on poistettu käytöstä - - - - - Synchronization is paused - Synkronointi on keskeytetty - - - - Unresolved conflicts - - - - - Error during synchronization - Virhe synkronoinnin aikana - - - - No sync folders configured - Synkronointikansioita ei ole määritetty - - - - Unpause all folders - - - - - Pause all folders - Keskeytä kaikki kansiot - - - - Unpause all synchronization - Palauta kaikki synkronointi - - - - Unpause synchronization - Palauta synkronointi - - - - Pause all synchronization - Keskeytä kaikki synkronointi - - - - Pause synchronization - Keskeytä synkronointi - - - - Log out of all accounts - Kirjaudu ulos kaikista tileistä - - - - Log in to all accounts... - Kirjaudu kaikille tileille... - - - - New account... - Uusi tili... - - - - - About %1 - Tietoja - %1 - - - - No items synced recently - Kohteita ei ole synkronoitu äskettäin - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - Synkronoidaan %1/%2 (%3 jäljellä) - - - - Syncing %1 of %2 - Synkronoidaan %1/%2 - - - - Syncing %1 (%2 left) - Synkronoidaan %1 (%2 jäljellä) - - - - Syncing %1 - Synkronoidaan %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Ajan tasalla - - - - OwncloudAdvancedSetupPage - - - Form - Lomake - - - - - - - - - - TextLabel - TekstiLeima - - - - Server - Palvelin - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Jos tämä kohta on valittu, paikallisen kansion olemassa oleva sisältö poistetaan ja sisältö synkronoidaan palvelimelta.</p><p>Älä valitse tätä, jos tarkoituksesi on lähettää paikallisen kansion sisältö palvelimelle.</p></body></html> - - - - Start a &clean sync (Erases the local folder!) - Aloita &puhdas synkronointi (poistaa paikallisen kansion!) - - - - Ask for confirmation before synchroni&zing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - Mt - - - - Ask for confirmation before synchronizing e&xternal storages - - - - - Choose what to sync - Valitse synkronoitavat tiedot - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - &Paikallinen kansio - - - - pbSelectLocalFolder - - - - - &Keep local data - &Säilytä paikallinen data - - - - S&ynchronize everything from server (recommended) - - - - - Use virtual files instead of downloading content immediately (e&xperimental) - - - - - Status message - Tilaviesti - - - - OwncloudHttpCredsPage - - - Form - Lomake - - - - &Username - &Käyttäjätunnus - - - - &Password - &Salasana - - - - OwncloudOAuthCredsPage - - - Form - Lomake - - - - Please switch to your browser to proceed. - Siirry selaimeen jatkaaksesi. - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - Avaa selain uudelleen - - - - OwncloudSetupPage - - - Form - Lomake - - - - - TextLabel - TekstiLeima - - - - Ser&ver Address - &Palvelinosoite - - - - https://... - https://... - - - - Error Label - - - - - OwncloudWizardResultPage - - - Form - Lomake - - - - TextLabel - TekstiLeima - - - - Your entire account is synced to the local folder - Koko tilisi on synkronoitu paikalliseen kansioon - - - - - PushButton - - - - - QObject - - - in the future - tulevaisuudessa - - - - %n day(s) ago - %n päivä sitten%n päivää sitten - - - - %n hour(s) ago - %n tunti sitten%n tuntia sitten - - - - now - nyt - - - - Less than a minute ago - Alle minuutti sitten - - - - %n minute(s) ago - %n minuutti sitten%n minuuttia sitten - - - - Some time ago - Jokin aika sitten - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 Gt - - - - %L1 MB - %L1 Mt - - - - %L1 KB - %L1 kt - - - - %L1 B - %L1 t - - - - %n year(s) - %n vuosi%n vuotta - - - - %n month(s) - %n kuukausi%n kuukautta - - - - %n day(s) - %n päivä%n päivää - - - - %n hour(s) - %n tunti%n tuntia - - - - %n minute(s) - %n minuutti%n minuuttia - - - - %n second(s) - %n sekunti%n sekuntia - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Ilmoitusaluetta ei ole saatavilla - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 vaatii toimivan ilmoitusalueen. Jos käytät XFCE:tä, seuraa <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">näitä ohjeita</a>. Muussa tapauksessa asenna jokin ilmoitusalueen tarjoava sovellus, kuten "trayer" ja yritä uudelleen. - - - - ownCloudTheme::about() - - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - <p><small>Koostettu Git-revisiosta <a href="%1">%2</a> %3, %4 käyttäen Qt:n versiota %5, %6</small></p> - - - - progress - - - Downloaded - Ladattu - - - - Uploaded - Lähetetty - - - - Server version downloaded, copied changed local file into conflict file - - - - - Deleted - Poistettu - - - - Moved to %1 - - - - - Ignored - - - - - Filesystem access error - Tiedostojärjestelmän käyttövirhe - - - - Error - Virhe - - - - Updated local metadata - - - - - - Unknown - Tuntematon - - - - downloading - ladataan - - - - uploading - lähetetään - - - - deleting - poistetaan - - - - moving - siirretään - - - - ignoring - - - - - - error - virhe - - - - updating local metadata - - - - - theme - - - Status undefined - Tila on määrittelemätön. - - - - Waiting to start sync - Odotetaan synkronoinnin käynnistymistä - - - - Sync is running - Synkronointi meneillään - - - - Sync Success - Synkronointi valmistui - - - - Sync Success, some files were ignored. - Synkronointi onnistui, jotkin tiedostot ohitettiin. - - - - Sync Error - Synkronointivirhe - - - - Setup Error - Asetusvirhe. - - - - Preparing to sync - Valmistaudutaan synkronointiin - - - - Aborting... - Keskeytetään... - - - - Sync is paused - Synkronointi on keskeytetty - - - - utility - - - Could not open browser - Selainta ei voitu avata - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - Sähköpostisovelluksen avaaminen epäonnistui - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - - - \ No newline at end of file diff --git a/translations/client_fr.ts b/translations/client_fr.ts index 87e5d6f2cdd..c9d16b9f564 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -1,360 +1,424 @@ - + - CrashReporter + AccountBar - - Tomahawk Crash Reporter - + + Navigation bar + Barre de navigation - - <html><head/><body><p><br/></p></body></html> - + + Add Account + Ajouter un compte - - Abort - Annuler + + Activity + Activité - - Send this report - + + Settings + Paramètres - - Don't send - + + Quit + Quitter + + + AppImageUpdateAvailableWidgetUi - - You can disable sending crash reports in the configuration dialog. - + + Skip this version + Ignorer cette version + + + AppProvider - - - - We cannot gather useful debug information on your system. - + + Failed to open %1 in web. Error: %2. + Échec de l'ouverture de %1 dans le web. Erreur: %2. + + + ChecksumHeader - - - - - - Close - Fermer + + The checksum header is malformed: %1 + L'en-tête de la checksum n'est pas conforme: %1 - - Ready to send debug information (<a href="%1">view backtrace</a>). - + + The checksum header contained an unknown checksum type '%1' + L'en-tête de la somme de contrôle contenait un type de somme de contrôle inconnu '%1' + + + CommandLine - - Gathering debug information... - + + %1 version %2 +File synchronization desktop utility. + %1 version %2<byte value="xd"/> +Utilitaire de synchronisation de fichiers pour ordinateur de bureau. - - Uploaded %L1 of %L2 KB. - + + For more information, see %1 + link to homepage + Pour plus d'informations, voir %1 - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Démarrez avec la fenêtre principale visible ou, si elle est déjà en cours d'exécution, placez-la au premier plan. Par défaut, le client démarre en arrière-plan. - - Failed to send crash info. - + + Quit the running instance. + Quitter l'instance en cours d'exécution. - - - FileSystem - - - - - Can't rename %1, the file is currently in use - + + Write log to file (use - to write to stdout). + Écrire le journal dans un fichier (utilisez - pour écrire sur stdout). - - Could not make directories in trash - Impossible de créer des dossiers dans la corbeille + + Write each sync log output in a new file in folder. + Inscrivez chaque sortie du journal de synchronisation dans un nouveau fichier du dossier. - - - Could not move '%1' to '%2' - Impossible de déplacer '%1' vers '%2' + + Flush the log file after every write. + Videz le fichier journal après chaque écriture. - - Moving to the trash is not implemented on this platform - Le déplacement dans la corbeille n'est pas implémenté sur cette plateforme + + Output debug-level messages in the log. + Afficher les messages de debugage dans les logs - - Error removing '%1': %2 - Erreur lors de la suppression de '%1' : %2 + + Enable debug mode. + Activer le mode débogage. - - Could not remove folder '%1' - Impossible de supprimer le dossier '%1' + + --logfile and --logdir are mutually exclusive + --logfile et --logdir sont mutuellement exclusifs - FolderWizardSourcePage + CommonStrings - - Form - Formulaire + + file manager + gestionnaire de fichiers - - Pick a local folder on your computer to sync - Sélectionner un dossier local de votre ordinateur à synchroniser + + Show in %1 + Afficher dans %1 - - &Choose... - &Choisir... + + Show in web browser + Afficher dans le navigateur - - - FolderWizardTargetPage - - Form - Formulaire + + Copy + Copier + + + + %n Filter(s) + %n Filtre%n Filtres%n Filtres - - Select a remote destination folder - Veuillez sélectionner un dossier distant + + folder + dossier - - Create Folder - Créer un dossier + + space + - - Refresh - Rafraîchir + + folders + dossiers - - Folders - Dossiers + + spaces + - - TextLabel - TextLabel + + Folder + Dossier - - - NotificationWidget - - Form - Formulaire + + Space + Espace + + + FetchUserInfoJobFactory - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet + + Failed to retrieve user info + Echec de la récupération des informations de l'utilisateur + + + FileSystem - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + + + + Can't rename %1, the file is currently in use + Impossible de renommer le fichier %1 car il est en cours d'utilisation - - TextLabel - TextLabel + + Could not remove folder + Impossible de supprimer le dossier - OCC::AboutDialog + FolderItem - - About - A propos + + + Offline + Hors-ligne - - TextLabel - TextLabel + + + Unavailable: the space is no longer available on the server + - - Versions - Versions + + + Sync paused + Synchronisation en pause - - About %1 - À propos de %1 + + Synced + - - - OCC::AbstractNetworkJob - - Connection timed out - Délai de connexion dépassé + + Sync failed + La synchronisation a échoué - - Unknown error: network reply was deleted - Erreur inconnue : La réponse du réseau a été supprimée + + Sync pending + - - Server replied "%1 %2" to "%3 %4" - Le serveur a répondu "%1 %2" à "%3 %4" + + Syncing %1 of %2 (%3 % + + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + + first argument is the displayed name of the folder. - OCC::Account + FolderManagementUtils - - %1@%2 - %1@%2 + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + Le chemin d'accès '%1' est trop long. Veuillez activer les chemins longs dans les paramètres Windows ou choisir un dossier différent. + + + + Local folder %1 does not exist. + Le dossier local %1 n'existe pas. + + + + %1 is not a folder. + + + + + %1 is not readable. + %1 ne peut pas être lu. + + + + %1 is not writable. + %1 n'est pas ouvert en écriture. - OCC::AccountSettings + FolderWizard - - Form - Formulaire + + <b>Error:</b> %1 + <b>Erreur : </b> %1 - - Re-open Browser - Redémarrez votre navigateur + + <b>Warning:</b> %1 + <b>Avertissement :</b> %1 - - ... - ... + + <b>Error:</b><ul>%1</ul> + <b>Erreur : </b><ul>%1</ul> - - Storage space: ... - Espace de stockage : ... + + <b>Warning:</b><ul>%1</ul> + <b>Avertissement : </b><ul>%1</ul> + + + FolderWizardSourcePage - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Décochez les dossiers qui doivent être <b>supprimés</b> de votre disque local et qui ne doivent pas être synchronisés avec cet ordinateur. + + Form + Formulaire - - Synchronize all - Tout synchroniser + + Pick a local folder on your computer to sync + Sélectionner un dossier local de votre ordinateur à synchroniser - - Synchronize none - Ne rien synchroniser + + Browse… + Parcourir… + + + FolderWizardTargetPage - - Apply manual changes - Appliquer les changements manuels + + Form + Formulaire - - Apply - Appliquer + + Select a remote destination folder + Veuillez sélectionner un dossier distant - - - - - Cancel - Annuler + + Create Folder + Créer un dossier - - Connected with <server> as <user> - Connecté au serveur <server> avec le compte <user> + + Refresh + Rafraîchir - - No account configured. - Aucun compte configuré. + + Folders + Dossiers + + + OCC::AboutView - - Remove - Supprimer + + About + A propos - - Account - Compte + + Versions + Versions + + + OCC::AbstractNetworkJob - - Choose what to sync - Sélectionner le contenu à synchroniser + + Connection timed out + Délai de connexion dépassé - - Force sync now - Forcer la synchronisation maintenant + + Unknown error: network reply was deleted + Erreur inconnue : La réponse du réseau a été supprimée - - Restart sync - Redémarrer la synchronisation + + Server replied "%1 %2" to "%3 %4" + Le serveur a répondu "%1 %2" à "%3 %4" + + + OCC::Account - - Remove folder sync connection - Supprimer la synchronisation du dossier + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - Folder creation failed - Échec de création du dossier + + Choose what to sync + Sélectionner le contenu à synchroniser - - <p>Could not create local folder <i>%1</i>. - <p>Impossible de créer le dossier local <i>%1</i>. + + + Sync now + - - Confirm Folder Sync Connection Removal - Confirmer une suppression de synchronisation de dossier + + Manage subfolder sync + - - Remove Folder Sync Connection - Supprimer la synchronisation de ce dossier + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + - + Disable virtual file support? Voulez-vous désactiver le support des fichiers virtuels ? - + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. The only advantage of disabling virtual file support is that the selective sync feature will become available again. @@ -367,572 +431,551 @@ Le seul avantage à désactiver le support des fichiers virtuels est que la fonc Cette action stoppera toute synchronisation en cours. - + Disable support Désactiver le support - - Sync Running - Synchronisation en cours + + Restart sync + Redémarrer la synchronisation - - The syncing operation is running.<br/>Do you want to terminate it? - La synchronisation est en cours.<br/>Voulez-vous l'arrêter ? + + Resume sync + Reprendre la synchronisation - - %1 in use - %1 utilisés + + Pause sync + Mettre en pause la synchronisation - - %1 as <i>%2</i> - %1 avec le compte <i>%2</i> + + Deactivate virtual files + - - Connected to %1. - Connecté au serveur %1. + + Activate virtual files + - - Server %1 is temporarily unavailable. - Le serveur %1 est temporairement indisponible. + + Internet connection is metered + La connexion Internet est limitée - - Server %1 is currently in maintenance mode. - Le serveur %1 est en cours de maintenance. + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + La synchronisation est mise en pause parce que la connexion internet est limitée<p>Voulez-vous vraiment forcer la synchronisation maintenant ? - - Signed out from %1. - Session sur %1 fermée. + + Sync Running + Synchronisation en cours - - Connecting to %1... - Connexion à %1 + + The sync operation is running.<br/>Do you want to stop it? + L'opération de synchronisation est en cours. <br/> Voulez-vous l'arrêter ? - - No connection to %1 at %2. - Aucune connexion au serveur %1 à l'adresse %2. + + Sync paused by user + Synchronisation mise en pause pas l'utilisateur - - Log in - Se connecter + + Confirm Folder Sync Connection Removal + Confirmer une suppression de synchronisation de dossier - - There are folders that were not synchronized because they are too big: - Certains dossiers n'ont pas été synchronisés parce qu'ils sont de taille trop importante : + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Voulez-vous vraiment arrêter de synchroniser le dossier <i>%1</i> ?</p><p><b>Note :</b> <b>Aucun</b> fichier ne sera supprimé.</p> - - There are folders that were not synchronized because they are external storages: - Certains dossiers n'ont pas été synchronisés parce qu'ils sont localisés sur un stockage externe : + + Remove Folder Sync Connection + Supprimer la synchronisation de ce dossier - - There are folders that were not synchronized because they are too big or external storages: - Certains dossiers n'ont pas été synchronisés parce qu'ils sont localisés sur un stockage externe ou qu'ils sont de taille trop importante : + + + Cancel + Annuler + + + OCC::AccountFoldersView - - Confirm Account Removal - Confirmation de retrait du compte + + Sync options menu + Menu des options de synchronisation - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Êtes-vous certain de vouloir retirer <i>%1</i> des comptes synchronisés avec le serveur ?</p><p><b>Remarque :</b> cela ne supprimera pas votre compte sur le serveur, et aucun fichier ne sera supprimé ni localement ni en ligne.</p> + + %1 sync + This shows as 'Folder sync' or 'Space sync' + - - Remove connection - Retirer le compte + + Manage your synced %1. + - - Open folder - Ouvrir le dossier local + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + - - - Log out - Se déconnecter + + %1 list view + - - Open local folder - Ouvrir le dossier local + + Navigate the %1 list using the up and down arrows + - - Open folder in browser - Ouvrir le dossier dans le navigateur + + %1 out of %2 %3 are synchronized + + + + OCC::AccountModalWidget - - Resume sync - Reprendre la synchronisation + + + Form + Formulaire + + + OCC::AccountPlaceholderWidget - - Pause sync - Mettre en pause + + %1 logo + logo de %1 - - Availability - Disponibilité + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + - - Disable virtual file support... - Désactivez le support des fichiers virtuels… + + Additional logo defined by the organization + Logo supplémentaire défini par l'organisation + + + OCC::AccountView - - Enable virtual file support%1... - Activer la prise en charge des fichiers virtuels%1... + + Form + Formulaire - - (experimental) - (expérimental) + + Manage Account + Gérer le compte - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Voulez-vous vraiment arrêter de synchroniser le dossier <i>%1</i> ?</p><p><b>Note :</b> Aucun fichier ne sera supprimé.</p> + + Preparing the account + Préparation du compte - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) utilisés sur %2. Certains dossiers, montés depuis le réseau ou partagés, peuvent avoir des limites différentes. + + Sync connections + Synchronisation des connexions - - %1 of %2 in use - %1 utilisés sur %2 + + Account options menu + Menu des options de compte + + + OCC::AccountViewController - - Currently there is no storage usage information available. - Actuellement aucune information d'utilisation de stockage n'est disponible. + + + Log in + Se connecter - - The server version %1 is unsupported! Proceed at your own risk. - La version %1 du serveur n’est pas supportée. Poursuivez à vos risques et périls. + + Reconnect + Se reconnecter - - Obtaining authorization from the browser. - Obtention de l'autorisation du navigateur. + + Remove + Supprimer - - Server configuration error: %1 at %2. - Erreur de configuration du serveur: %1 à %2. + + Log out + Se déconnecter - - No %1 connection configured. - Aucune connexion à %1 configurée + + Confirm Account Removal + Confirmation de retrait du compte - - - OCC::AccountState - - Url update requested for %1 - + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Êtes-vous certain de vouloir retirer <i>%1</i> des comptes synchronisés avec le serveur ?</p><p><b>Remarque :</b> cela ne supprimera pas votre compte sur le serveur, et aucun fichier ne sera supprimé ni localement ni en ligne.</p> - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - + + Remove connection + Retirer la connexion + + + + Cancel + Annuler + + + + The server version %1 is unsupported! Proceed at your own risk. + La version %1 du serveur n’est pas supportée. Poursuivez à vos risques et périls. + + + + Connected + Connecté - - Change url permanently to %1 + + Server is temporarily unavailable. - - Reject + + Server is currently in maintenance mode. - + Signed out Session fermée - - Disconnected - Déconnecté - - - - Connected - Connecté + + Updating credentials… + Mise à jour des informations d'identification… - - Service unavailable - Service indisponible + + Captive portal prevents connections to the server. + Le portail captif empêche la connexion au serveur. - - Maintenance mode - Mode maintenance + + Sync is paused due to metered internet connection. + - - Network error - Erreur réseau + + Connecting… + Connexion en cours… - - Configuration error - Erreur de configuration + + Server configuration error + Erreur de configuration du serveur - - Asking Credentials - informations d'identification demandées + + Disconnected + Déconnecté - - Unknown account state - Etat du compte inconnu + + Invalid connection status + - OCC::ActivityListModel + OCC::AccountsGuiController - - %1 %2 on %3 - %1 %2 sur %3 - - - - Activity - Activité + + + New account failure + Erreur de nouveau compte - - Account - Compte + + The information required to create a new account is incomplete. Please run the wizard again. + Les informations requises pour créer un nouveau compte sont incomplètes. Veuillez relancer l'assistant. - - Time - Heure + + Accounts + Comptes - - Local path - + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + Le compte n'a pas pu être créé en raison d'une erreur : +%1 +Veuillez vérifier la disponibilité du serveur, puis relancez l'assistant. OCC::ActivitySettings - - - Server Activity - Activité serveur - - - - Sync Protocol - Activité de synchronisation + + Local Activity + Activité locale - + Not Synced Fichiers non synchronisés - + Not Synced (%1) %1 is the number of not synced files. Non Synchronisé (%1) - OCC::ActivityWidget - - - Form - Form - - - - - - TextLabel - TextLabel - - - - Filter - Filtre - - - - Server Activities - Historique des opérations sur le serveur - - - - Notifications - Notifications - - - - %n notifications(s) for %1. - %n notification(s) pour %1.%n notification(s) pour %1. - - - - %n notifications(s) for %1 and %2. - %n notification(s) pour %1 and %2.%n notification(s) pour %1 and %2. - - - - New notifications for %1, %2 and other accounts. - Nouvelles notifications pour %1, %2 et d'autres comptes. - - - - Open the activity view for details. - Ouvrez la vue des activités pour plus de détails. - - - - Copy to clipboard - Copier dans le presse papiers - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>Le compte %1 n'a aucune activité activée. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - En continuant vous allez <b>effacer ces paramètres</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - En continuant vous allez <b>ignorer ces paramètres</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Certains paramètres ont été configurés dans des versions plus récentes de ce logiciel client et utilisent des fonctionnalités non disponibles dans cette version.<br><br>%1<br><br>Le fichier de configuration actuel a été d'ores et déjà sauvegardé en tant que <i>%2</i>. - + OCC::AdvancedSettingsPageController - - Quit - Quitter + + Advanced settings + Paramètres avancés - - Continue - Continuer + + Sync and download options + Options de synchronisation et de téléchargement - - Error accessing the configuration file - Erreur lors de l'accès au fichier de configuration + + Download location + Emplacement de téléchargement - - There was an error while accessing the configuration file at %1. - Une erreur s'est produite lors de l'accès au fichier de configuration situé dans %1. + + Sync and download files as you use them (saves hard drive space) + Synchronisez et téléchargez les fichiers au fur et à mesure que vous les utilisez (pour économiser de l'espace sur votre disque dur) - - Quit %1 - Quitter %1 + + Automatically sync and download folders and files + Synchronisez et téléchargez automatiquement des dossiers et des fichiers - - %1 version %2 -File synchronization desktop utility. - + + Sync and download folders manually + Synchroniser et télécharger des dossiers manuellement - - For more information, see http://www.owncloud.org - + + Download location on the local machine + Emplacement de téléchargement sur l'ordinateur local - - Show the settings dialog while starting. - + + Browse… + Parcourir… - - Quit the running instance. - + + Browse for a download location + Rechercher un emplacement de téléchargement - - Write log to file (use - to write to stdout). - + + Invalid local download directory %1: %2 + Répertoire de téléchargement local invalide %1 : %2 - - Write each sync log output in a new file in folder. - + + your user directory may not be chosen as the sync root. + Votre répertoire utilisateur ne peut pas être choisi comme racine de synchronisation. - - Remove logs older than <hours> hours (to be used with --logdir). - + + path is not located on a supported Apple File System. + Le chemin d'accès n'est pas situé sur un système de fichiers Apple pris en charge. - - Flush the log file after every write. - + + path must be absolute. + Le chemin d'accès doit être absolu. - - Output debug-level messages in the log. + + selected path does not support using virtual file system. %1 - - - Override UI language. - + + Select sync root + Sélectionner la racine de synchronisation + + + OCC::AppImageUpdateAvailableWidget - - Use the given configuration folder. - + + Installed version: %1 + Version installée : %1 - - Enable debug mode. - + + Available update: %1 + Mise à jour disponible : %1 - - Virtual file system file to be opened (optional). - + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Une mise à jour est disponible pour cette Appimage de %1. Voulez-vous installer cette mise à jour ? + +La mise à jour sera effectuée en arrière-plan et va écraser le fichier Appimage actuel. Vous devrez redémarrer l'application pour compléter la mise à jour. + + + OCC::Application - - [<vfs file>] - + + Quit + Quitter + + + OCC::AuthSuccessPageController - - Invalid path passed to --confdir - + + Application Name Logo %1 + Nom de l'application Logo %1 - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - + + You're all set! Open %1 and get started. + Tout est prêt ! Ouvrez %1 et commencez. - - Available translations: %1 - + + Additional logo defined by the organization + Logo supplémentaire défini par l'organisation - OCC::AuthenticationDialog + OCC::ButtonDelegate - - Authentication Required - Authentification requise - - - - Enter username and password for '%1' at %2. - Saisir le nom d'utilisateur et le mot de passe pour '%1' sur %2. - - - - &User: - &Utilisateur : + + %1 options button + - - &Password: - &Mot de passe : + + Menu button with %1 options. Use the space key to show the menu + OCC::ConnectionValidator - + No ownCloud account configured Aucun compte ownCloud configuré - - The configured server for this client is too old - Le serveur configuré pour ce client est trop vieux - - - + Please update to the latest server and restart the client. Veuillez mettre à jour le serveur vers la dernière version et redémarrer le client. - + Authentication error: Either username or password are wrong. Erreur d'authentification: nom d'utilisateur et/ou mot de passe incorrect(s). - + timeout délai d'attente - + + No account configured + Aucun compte configuré + + + The provided credentials are not correct Les informations d'identification fournies ne sont pas correctes + + + The configured server for this client is too old. + Le serveur configuré pour ce client est trop vieux. + OCC::CredentialJob - + Failed to parse credentials %1 - + Echec d'analyse des informations d'identification %1 + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + Le serveur ne prend pas en charge OAuth. + + + + Server did not ask for authorization + Le serveur n'a pas demandé d'autorisation + + + + Failed to determine authentication type: %1 + Échec de la détermination du type d'authentification : %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + Réponse non valide reçue du serveur. OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + Le compte a été supprimé avant que nous puissions commencer le travail propfind + + + + Server error: PROPFIND reply is not XML formatted! Erreur du serveur : la réponse PROPFIND n’est pas formatée en XML ! @@ -940,27 +983,22 @@ File synchronization desktop utility. OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 Erreur lors de l’ouverture du répertoire %1 - + Directory not accessible on client, permission denied Répertoire non accessible depuis le client. Permission refusée - + Directory not found: %1 Répertoire non trouvé : %1 - - Filename encoding is not valid - L'encodage du nom de fichier n'est pas valide - - - + Error while reading directory %1 Erreur lors de la lecture du répertoire %1 @@ -968,149 +1006,137 @@ File synchronization desktop utility. OCC::ExpandingHeaderView - + Reset column sizes - + Réinitialiser la taille des colonnes - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - Le dossier local %1 n'existe pas. + + server sent invalid content type: %1. + Le serveur a envoyé un type de contenu non valide : %1. - - %1 should be a folder but is not. - %1 devrait être un dossier mais ne l'est pas. + + could not parse Capabilities from server. + Impossible d'analyser les capacités du serveur. - - %1 is not readable. - %1 ne peut pas être lu. + + reply sent for different subject (server): %1 + Réponse envoyée pour un autre sujet (serveur) : %1 + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 a été supprimé. - + %1 has been updated. %1 names a file. %1 a été mis à jour. - + %1 has been renamed to %2. %1 and %2 name files. %1 a été renommé en %2. - + %1 has been moved to %2. %1 a été déplacé vers %2. - + %1 and %n other file(s) have been removed. - %1 a été supprimé.%1 et %n autres fichiers ont été supprimés. + %1 et %2 autre fichier ont été supprimés.%1 et %2 autres fichiers ont été supprimés.%1 et %2 autres fichiers ont été supprimés. - - %1 is not writable. - + + The folder has been disabled or removed from the server + Le dossier a été désactivé ou supprimé du serveur - + %1 and %n other file(s) have been added. - %1 et %n autre(s) fichier(s) ont été ajouté(s).%1 et %n autre(s) fichier(s) ont été ajouté(s). + %1 et %2 autre fichier ont été ajoutés.%1 et %2 autres fichiers ont été ajoutés.%1 et %2 autres fichiers ont été ajoutés. - + %1 has been added. %1 names a file. %1 a été ajouté. - + %1 and %n other file(s) have been updated. - %1 a été mis à jour.%1 et %n autres fichiers ont été mis à jour. + %1 et %2 autre fichier ont été mis à jour.%1 et %2 autres fichiers ont été mis à jour.%1 et %2 autres fichiers ont été mis à jour. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 a été renommé en %2.%1 a été renommé en %2 et %n autres fichiers ont été renommés. + %1 a été renommé en %2 et %n autre fichier a été renommé%1 a été renommé en %2 et %n autres fichiers ont été renommés%1 a été renommé en %2 et %n autres fichiers ont été renommés - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 a été déplacé vers %2.%1 a été déplacé vers %2 et %n autres fichiers ont été déplacés. + %1 a été déplacé vers %2 et %n autre fichier a été déplacé%1 a été déplacé vers %2 et %n autres fichiers ont été déplacés%1 a été déplacé vers %2 et %n autres fichiers ont été déplacés - - %1 has and %n other file(s) have sync conflicts. - %1 a un conflit de synchronisation.%1 et %n autres fichiers ont des problèmes de synchronisation. + + %1 and %n other file(s) have sync conflicts. + %1 et %n autre fichier ont des conflits de synchronisation%1 et %n autres fichiers ont des conflits de synchronisation%1 et %n autres fichiers ont des conflits de synchronisation - + %1 has a sync conflict. Please check the conflict file! %1 a un problème de synchronisation. Merci de vérifier le fichier conflit ! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 ne peut pas être synchronisé en raison d'erreurs. Consultez les logs pour les détails.%1 et %n autres fichiers n'ont pas pu être synchronisés en raison d'erreurs. Consultez les logs pour les détails. + %1 et %n autre fichier n'ont pas pu être synchronisés à causes d'erreurs. Voir les logs pour plus de détails.%1 et %n autres fichiers n'ont pas pu être synchronisés à causes d'erreurs. Voir les logs pour plus de détails.%1 et %n autres fichiers n'ont pas pu être synchronisés à causes d'erreurs. Voir les logs pour plus de détails. - + %1 could not be synced due to an error. See the log for details. %1 n'a pu être synchronisé pour cause d'erreur. Consultez les logs pour les détails. - + Sync Activity Activité de synchronisation - - Could not read system exclude file - Impossible de lire le fichier d'exclusion du système - - - - A new folder larger than %1 MB has been added: %2. - - Un nouveau dossier de taille supérieure à %1 Mo a été ajouté : %2. - - - - - A folder from an external storage has been added. - - Un nouveau dossier localisé sur un stockage externe a été ajouté. - - + + Switching VFS mode on folder '%1' + Commutation du mode VFS sur le dossier '%1' - - Please go in the settings to select it if you wish to download it. - Merci d'aller dans les Paramètres pour indiquer si vous souhaitez le télécharger. + + Could not read system exclude file + Impossible de lire le fichier d'exclusion du système - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. Le dossier %1 a été créé mais avait été exclu de la synchronisation auparavant. Les données à l'intérieur ne seront pas synchronisées. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. Le fichier %1 a été créé mais avait été exclu de la synchronisation auparavant. Il ne sera pas synchronisé. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1122,3588 +1148,2398 @@ Cela veut dire que le client de synchronisation ne peut pas téléverser les cha %1 + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Tous les fichiers contenus dans le dossier synchronisé '%1' ont été supprimés du serveur. -Ces suppressions seront synchronisées sur vos dossiers locaux synchronisés, rendant ces fichiers inaccessibles, à moins que vous n'ayez le droit de les restaurer. -Si vous décidez de conserver ces fichiers, ils seront synchronisés à nouveau avec le serveur si vous en avez le droit. -Si vous décidez de supprimer ces fichiers, ils vous seront inaccessibles, sauf si vous en êtes le/la propriétaire. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Tous les fichiers contenus dans le dossier synchronisé '%1' ont été supprimés. Ces suppressions seront synchronisées avec votre serveur, rendant ces fichiers inaccessibles sauf s'ils sont restaurés. -Êtes-vous sûr·e de vouloir synchroniser ces actions avec le serveur ? -S'il s'agissait d'un accident et que vous choisissiez de conserver vos fichiers, ils seront synchronisés à nouveau depuis le serveur. - - - - Remove All Files? - Supprimer tous les fichiers ? - - - - Remove all files - Supprimer tous les fichiers + + There are unresolved conflicts. + Il y a des conflits non résolus - - Keep files - Conserver les fichiers + + Sync error: %1 + OCC::FolderMan - + Could not reset folder state Impossible de réinitialiser l'état du dossier - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Un ancien fichier journal '%1' a été trouvé, mais ne peut être supprimé. Veuillez vous assurer qu’aucune application ne l'utilise en ce moment. - - (backup) - (sauvegarde) - - - - (backup %1) - (sauvegarde %1) - - - - Undefined State. - Statut indéfini. + + Unsupported filesystem + Système de fichiers non pris en charge - - Waiting to start syncing. - En attente de synchronisation. + + Account disconnected or paused + Compte déconnecté ou mis en pause - - Preparing for sync. - Préparation de la synchronisation. + + User triggered force sync + L'utilisateur a déclenché la synchronisation forcée - - Sync is running. - Synchronisation en cours - - - - Sync was successful, unresolved conflicts. - Synchronisation réussie, conflits non résolus. + + Folder is about to be removed + Le dossier est sur le point d'être retiré - - Last Sync was successful. - Synchronisation terminée avec succès + + (backup) + (sauvegarde) - - Setup Error. - Erreur d'installation. + + (backup %1) + (sauvegarde %1) - - User Abort. - Abandon par l'utilisateur. + + The selected path is not a folder. + Le chemin sélectionné n'est pas un dossier. - - Sync is paused. - La synchronisation est en pause. + + The folder %1 is used in a folder sync connection. + Le dossier %1 est utilisé dans une connexion de dossier synchronisé. - - %1 (Sync is paused) - %1 (Synchronisation en pause) + + Folder '%1' is already in use by application %2. + Le dossier '%1' est déjà utilisé par l'application %2 - - No valid folder selected! - Aucun dossier valable sélectionné ! + + There is already a sync from the server to this local folder. + Il y a déjà une synchronisation du serveur vers ce dossier local. - - The folder %1 is used in a folder sync connection! - + + The local folder %1 already contains a folder used in a folder sync connection. + Le dossier local %1 contient déjà un dossier utilisé dans une connexion de dossier synchronisé. - - The selected path does not exist! - Le chemin sélectionné n’existe pas ! + + The local folder %1 is already contained in a folder used in a folder sync connection. + Le dossier local %1 est déjà contenu dans un dossier utilisé dans une connexion de dossier synchronisé. - - The selected path is not a folder! - Le chemin sélectionné n'est pas un dossier ! + + No valid folder selected. + Pas de dossier valide sélectionné. - - You have no permission to write to the selected folder! - Vous n'avez pas la permission d'écrire dans le dossier sélectionné ! + + The selected path does not exist. + Le chemin sélectionné n'existe pas. - - There is already a sync from the server to this local folder. Please pick another local folder! - Il y a déjà une synchronisation depuis le serveur vers ce dossier local. Merci de choisir un autre dossier local ! + + You have no permission to write to the selected folder. + Vous n'avez pas l'autorisation d'écrire dans le dossier sélectionné. - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Le dossier local %1 contient un dossier déjà utilisé pour une synchronisation de dossiers. Veuillez en choisir un autre ! + + Folder '%1' is already in use by another account. + Le dossier '%1' est déjà utilisé par un autre compte. - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Le dossier local %1 se trouve dans un dossier déjà configuré pour une synchronisation de dossier. Veuillez en choisir un autre ! + + On macOS, only the Apple File System is supported. + Sur macOS, seul le système de fichier de Apple est supporté. + + + OCC::FolderModelController - - %1 Please pick another one! + + %1 options - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. + + Menu button with %1 options. Use the space key to pop the %2 options menu - OCC::FolderStatusDelegate + OCC::FolderWatcher - - Add Folder Sync Connection - Ajouter une synchronisation de dossier + + The watcher did not receive a test notification. + L'observateur n'a pas reçu de notification de test. + + + OCC::FolderWatcherPrivate - - File - Fichier + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Ce problème survient généralement lorsque les contrôleurs inotify sont saturés. Consulter la FAQ pour plus de détails. - OCC::FolderStatusModel + OCC::FolderWizard - - You need to be connected to add a folder - Vous devez être connecté pour ajouter un dossier + + Virtual files are not available for the selected folder + Les fichiers virtuels ne sont pas disponibles pour le dossier sélectionné - - Click this button to add a folder to synchronize. - Cliquez ce bouton pour ajouter un dossier à synchroniser. + + Add Folder Sync Connection + Ajouter une synchronisation de dossier - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Add Sync Connection + Ajouter une Synchronisation + + + OCC::FolderWizardLocalPath - - Error while loading the list of folders from the server. - Une erreur est survenue lors du chargement de la liste des dossiers depuis le serveur. + + Click to select a local folder to sync. + Cliquez pour choisir un dossier local à synchroniser. - - Virtual file support is enabled. - Le support des fichiers virtuels est activé. + + Enter the path to the local folder. + Entrez le chemin du dossier local. - - Signed out - Session fermée + + Select the local folder + Sélectionner le dossier local + + + OCC::FolderWizardSelectiveSync - - Fetching folder list from server... - Récupération de la liste des dossiers depuis le serveur... + + Use virtual files instead of downloading content immediately + Utiliser des fichiers virtuels plutôt que de télécharger le contenu immédiatement + + + OCC::GETFileJob - - There are unresolved conflicts. Click for details. - Il y a des conflits non résolus. Cliquez pour plus de détails. + + Connection Timeout + Délai d'attente de connexion dépassé - - Checking for changes in remote '%1' - Vérification des changements distants '%1' + + No E-Tag received from server, check Proxy/Gateway + Aucun E-Tag reçu du serveur, vérifiez le proxy / la passerelle - - Checking for changes in local '%1' - Vérification des changements locaux '%1' + + We received a different E-Tag for resuming. Retrying next time. + Nous avons reçu un E-Tag différent pour reprendre le téléchargement. Nouvel essai la prochaine fois. - - Reconciling changes - Rapprochement des modifications + + We received an unexpected download Content-Length. + Nous avons reçu un téléchargement inattendu Content-Length. - - , '%1' - Build a list of file names - , '%1' + + Server returned wrong content-range + Le serveur a retourné une gamme de contenu erronée + + + OCC::GraphApi::Space - - '%1' - Argument is a file name - '%1' + + Personal + Personnel - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synchronisation de %1 + + Shares + Partages + + + OCC::IgnoreListEditor - - - , - , + + Ignored Files Editor + Éditeur d'exclusion de fichiers - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - réception %1/s + + Files Ignored by Patterns + Fichiers exclus sur motif de nom - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - envoi %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 sur %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 restantes, %1 sur %2, fichier %3 sur %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 sur %2, fichier %3 sur %4 - - - - file %1 of %2 - fichier %1 sur %2 - - - - Waiting... - En attente ... - - - - Waiting for %n other folder(s)... - En attente de %n autre(s) dossier(s)En attente de %n autre(s) dossier(s) - - - - Preparing to sync... - Préparation à la synchronisation - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - L'observateur n'a pas reçu de notification de test. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Ce problème survient généralement lorsque les contrôleurs inotify sont saturés. Consulter la FAQ pour plus de détails. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Ajouter une synchronisation de dossier - - - - Add Sync Connection - Ajouter une Synchronisation - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Cliquez pour choisir un dossier local à synchroniser. - - - - Enter the path to the local folder. - Entrez le chemin du dossier local. - - - - Select the source folder - Sélectionnez le dossier source - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Créer un dossier distant - - - - Enter the name of the new folder to be created below '%1': - Entrez le nom du nouveau dossier à créer dans '%1' : - - - - Folder was successfully created on %1. - Le dossier a été créé sur %1 - - - - Authentication failed accessing %1 - Erreur d'authentification lors de l'accès à %1 - - - - Failed to create the folder on %1. Please check manually. - Échec à la création du dossier sur %1. Veuillez vérifier manuellement. - - - - Failed to list a folder. Error: %1 - Impossible de lister un dossier. Erreur : %1 - - - - Choose this to sync the entire account - Sélectionnez ceci pour synchroniser l'ensemble du compte - - - - This folder is already being synced. - Ce dossier est déjà en cours de synchronisation. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Vous synchronisez déja <i>%1</i>, qui est un dossier parent de <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Vous synchronisez déjà <i>%1</i>, qui est un sous-dossier de <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Utilisez des fichiers virtuels au lieu de télécharger du contenu immédiatement%1 - - - - (experimental) - (expérimental) - - - - Virtual files are not available for the selected folder - Les fichiers virtuels ne sont pas disponibles pour le dossier sélectionné - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Avertissement :</b> %1 - - - - <b>Warning:</b> - <b>Avertissement :</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Aucun E-Tag reçu du serveur, vérifiez le proxy / la passerelle - - - - We received a different E-Tag for resuming. Retrying next time. - Nous avons reçu un E-Tag différent pour reprendre le téléchargement. Nouvel essai la prochaine fois. - - - - We received an unexpected download Content-Length. - Nous avons reçu un téléchargement inattendu Content-Length. - - - - Server returned wrong content-range - Le serveur a retourné une gamme de contenu erronée - - - - OCC::GETJob - - - Connection Timeout - Délai d'attente de connexion dépassé - - - - OCC::GeneralSettings - - - Form - Formulaire - - - - General Settings - Paramètres généraux - - - - Language - Langue - - - - Sync hidden files - Synchroniser les fichiers masqués - - - - Log Settings - Paramètres du journal - - - - Advanced - Approval of folder sync (Non virtual file mode only) - Avancé - Approbation de la synchronisation des dossiers (mode fichier non virtuel uniquement) - - - - Network - Réseau - - - - Updates - Mises à jour - - - - stable - stable - - - - beta - bêta - - - - About - A propos - - - - Advanced - Avancé - - - - Use &Monochrome Icons in the system tray - Utiliser les icônes &Monochrome dans la barre d'état système - - - - Ask for confirmation before synchronizing folders larger than - Demander confirmation avant de synchroniser les dossiers de taille supérieure à - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - Mo - - - - Ask for confirmation before synchronizing external storages - Demander confirmation avant de synchroniser des stockages externes - - - - &Launch on System Startup - &Lancer au démarrage du système - - - - Show &Desktop Notifications - Afficher les notifications sur le &bureau - - - - &Update Channel - &Mettre à jour la chaîne - - - - Edit &Ignored Files - Modifier les fichiers exclus - - - - Show sync folders in &Explorer's Navigation Pane - Montrer les répertoires synchronisés dans le panneau de navigation de l'&Explorateur de fichiers - - - - S&how crash reporter - Affic&her le rapport d'incident - - - - &Restart && Update - &Redémarrer && Mettre à jour - - - - You cannot disable autostart because system-wide autostart is enabled. - Vous ne pouvez pas désactiver le démarrage automatique parce que le démarrage automatique à l'échelle du système est activé. - - - - Change update channel? - Changer le canal de mise à jour ? - - - - Warning - Attention - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Le canal de mise à jour détermine quelles mises à jour du client seront proposées pour être installées. Le canal « stable » contient uniquement des mises à niveau considérées comme étant fiables, tandis que les versions dans le canal « bêta » peuvent contenir de nouvelles fonctionnalités et des corrections de bugs, mais qui n'ont pas encore été testées suffisamment. - -Veuillez noter que cela sélectionne seulement la zone de mises à jour, et qu'il n'y a pas de retour à des versions antérieures : par conséquent retourner du canal bêta au canal stable ne peut s'effectuer de façon instantanée et nécessite d'attendre une version stable plus récente que la version bêta installée. - - - - Change update channel - Changer le canal de mise à jour - - - - Cancel - Annuler - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Merci de saisir le mot de passe de %1 :<br><br>Utilisateur : %2<br>Compte : %3<br> - - - - Reading from keychain failed with error: '%1' - Erreur lors de l'accès au trousseau : '%1' - - - - Enter Password - Saisissez le mot de passe - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Cliquez ici</a> pour demander un mot de passe d'application depuis l'interface web. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Éditeur d'exclusion de fichiers - - - - Files Ignored by Patterns - Fichiers exclus sur motif de nom - - - - Add - Ajouter + + Add + Ajouter - Pattern - Motif de nom - - - - Allow Deletion - Autoriser suppression - - - - Remove - Supprimer - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Les fichiers ou dossiers correspondant au motif ne seront pas synchronisés. Les changements prendront effet à la prochaine synchronisation des dossiers. - -Les éléments dont la suppression est autorisée seront supprimés s'ils empêchent un dossier d'être supprimé. Cela est utile pour les métadonnées. - - - - Could not open file - Impossible d'ouvrir le fichier - - - - Cannot write changes to '%1'. - Impossible d'écrire les modifications sur '%1'. - - - - Add Ignore Pattern - Ajouter un motif d'exclusion - - - - Add a new ignore pattern: - Ajoutez un nouveau motif d'exclusion : - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Cette entrée est fournie par le système dans '%1' et ne peut être modifiée dans cette vue. - - - - OCC::IssuesWidget - - - Form - Form - - - - List of issues - Liste des anomalies - - - - Filter - Filtre - - - - There were too many issues. Not all will be visible here. - Il y a eu trop de problèmes. Tous n'apparaissent pas ici. - - - - There were conflicts. Check the documentation on how to resolve them. - Des conflits ont été détectés. Consultez la documentation pour savoir comment les résoudre. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Il y a eu des conflits. <a href="%1">Consultez la documentation pour pouvoir les résoudre.</a> - - - - Account - Compte - - - - OCC::LibCloudProvidersPrivate - - - Settings - Paramètres - - - - OCC::LogBrowser - - - Log Output - Consigner la sortie dans des fichiers de log - - - - Enable logging to temporary folder - Activer la journalisation dans le dossier temporaire - - - - Delete logs older than %1 hours - Supprimer les journaux plus anciens que %1 heures - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">Les journaux contiennent des informations sensibles que vous ne devez pas rendre publiques</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - Le client peut écrire des journaux de déboguage dans un dossier temporaire. Ces journaux sont utiles pour diagnostiquer des problèmes. -Depuis que le journal peut être plus grand, le client démarrera un nouveau journal pour chaque synchronisation lancée et compresse les anciens journaux. Il est recommandé d'activer la suppression des fichiers journaux après plusieurs heures afin d'éviter qu'ils n'occupent trop d'espace mémoire. - - - - If enabled, logs will be written to: - Si activé, les journaux seront écrits dans: - - - - C:/log - C:/log - - - - Log Http traffic - Journaliser le trafic HTTP - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Ces paramètres sont conservés malgré les redémarrages du client. -Veuillez noter qu’utiliser les options de journalisation en ligne de commande est prioritaire sur ces paramètres. - - - - Open folder - Ouvrir le dossier local - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Nouvelle version disponible - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Une nouvelle version du client %1 est disponible.</p><p><b>%2</b> est disponible au téléchargement. La version actuellement installée est %3.<p> - - - - - Skip this version - Ignorer cette version - - - - Skip this time - Ignorer pour cette fois - - - - Get update - Télécharger la version - - - - Update Failed - La mise à jour a échoué - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p> Une nouvelle version du Client %1 est disponible mais la mise à jour a échoué. </p><p><b>%2</b> a été téléchargé. La version installée est %3.</p> - - - - Ask again later - Redemander plus tard - - - - Restart and update - Redémarrer et mettre à jour - - - - Update manually - Mettre à jour manuellement - - - - OCC::NetworkSettings - - - Form - Formulaire - - - - Proxy Settings - Paramètres de serveur proxy - - - - No Proxy - Pas de serveur proxy - - - - Use system proxy - Utiliser les paramètres du système - - - - Specify proxy manually as - Indiquer un proxy manuellement - - - - Host - Hôte - - - - : - : - - - - Proxy server requires authentication - Le serveur proxy requiert une authentification - - - - Note: proxy settings have no effects for accounts on localhost - Remarque: les paramètres de proxy n'ont aucun effet sur les comptes localhost - - - - Download Bandwidth - Bande passante de téléchargement - - - - - Limit to - Limiter à - - - - - KBytes/s - Ko/s - - - - - No limit - Aucune limite - - - - - Limit to 3/4 of estimated bandwidth - Limiter aux 3/4 de la bande passante estimée - - - - Upload Bandwidth - Bande passante de téléversement - - - - - Limit automatically - Limiter automatiquement - - - - Hostname of proxy server - Nom d'hôte du serveur proxy - - - - Username for proxy server - Nom d'utilisateur pour le serveur proxy - - - - Password for proxy server - Mot de passe pour le serveur proxy - - - - HTTP(S) proxy - Proxy HTTP(S) - - - - SOCKS5 proxy - Proxy SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - Créé à %1 - - - - Closing in a few seconds... - Fermeture dans quelques secondes... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - La requête %1 a échoué à %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' sélectionné à %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Erreur retournée par le serveur: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Il y a eu une erreur durant l'accès au jeton de destination: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Impossible d'analyser le fichier JSON récupéré sur le serveur : <br><em>%1</em> - - - - Unsupported token type: %1 - Type de jeton non pris en charge: %1 - - - - The reply from the server did not contain all expected fields -:%1 - La réponse du serveur ne contenait pas tous les champs attendus -:%1 - - - - Unknown Error - Erreur inconnue - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>Erreur d'identification</h1><p>Échec de la récupération des informations sur l'utilisateur</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Erreur de connexion</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Erreur lors de l'actualisation du jeton: %1 : %1 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - La réponse du serveur ne contenait pas tous les champs attendus -:%1 -Données reçues: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Mauvais utilisateur</h1><p>Vous êtes connecté avec l'utilisateur <em>%1</em>, mais vous devez vous connecter avec l'utilisateur<em>%2</em>. Veuillez vous déconnecter de %3 l'autre onglet, puis <a href='%4'> cliquez ici</a> et connectez-vous en tant que l'utilisateur </p>%2 - - - - OCC::OCUpdater - - - New %1 Update Ready - Une nouvelle mise à jour de %1 est disponible - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Une mise à jour de %1 est sur le point d'être installée. -L'assistant de mise à jour peut vous demandez des autorisations supplémentaires afin de procéder à l'installation. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Impossible de télécharger la mise à jour. Veuillez la <a href='%1'>télécharger manuellement</a>. - - - - Could not check for new updates. - Impossible de vérifier la présence de nouvelles mises à jour. - - - - Downloading %1. Please wait... - Téléchargement %1 en cours. Veuillez patienter… - - - - %1 available. Restart application to start the update. - %1 disponible. Veuillez redémarrer l’application afin de démarrer la mise à jour. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Nouvel(le) %1 disponible. Veuillez utiliser l’outils de mise à jour système afin de l’installer. - - - - Checking update server... - Interrogation du serveur de mises à jour... - - - - Update status is unknown: Did not check for new updates. - L'état du processus de mise à jour est inconnu. Impossible de vérifier la présence de mises à jour. - - - - No updates available. Your installation is at the latest version. - Aucune mise à jour disponible. Votre installation est à jour. - - - - Update Check - Recherche de mise à jour - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Connexion à %1 - - - - Setup local folder options - Configurez les options de dossier local - - - - Connect... - Connexion… - - - - (experimental) - (expérimental) - - - - Use &virtual files instead of downloading content immediately%1 - Utiliser des &fichiers virtuels au lieu de télécharger le contenu immédiatement%1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Synchroniser le dossier '%1' - - - - Virtual files are not available for the selected folder - Les fichiers virtuels ne sont pas disponibles pour le dossier sélectionné - - - - Error - Erreur - - - - Local Sync Folder - Dossier de synchronisation local - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Adresse mail - - - - Connect to %1 - Connexion à %1 - - - - Enter user credentials - Saisissez les identifiants de connexion de l'utilisateur - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Connexion à %1 - - - - Login in your browser - Connectez vous sur votre navigateur - - - - Copy link to clipboard - Copier le lien vers le presse-papier - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Connexion à %1 - - - - Setup %1 server - Configurez le serveur %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Cette adresse n'est PAS sécurisée car non chiffrée. -Il est déconseillé de l'utiliser. - - - - This url is secure. You can use it. - Cette URL est sécurisée. Vous pouvez l'utiliser. - - - - &Next > - &Suivant > - - - - Invalid URL - URL invalide - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Échec de la connexion à %1 sur %2 :<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Délai d'attente dépassé lors de la connexion à %1 sur %2. - - - - Invalid URL - URL invalide - - - - Could not create local folder %1 - Impossible de créer le dossier local %1 - - - - Error: %1 - Erreur : %1 - - - - The folder creation resulted in HTTP error code %1 - La création du dossier a généré le code d'erreur HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - La création du dossier distant a échoué car les identifiants de connexion sont erronés !<br/>Veuillez revenir en arrière et vérifier ces derniers.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - La création du dossier distant "%1" a échouée avec l'erreur <tt>%2</tt>. - - - - Folder rename failed - Echec du renommage du dossier - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Impossible de supprimer et de sauvegarder le dossier parce que ce dossier ou un de ses fichiers est ouvert dans un autre programme. Veuillez fermer le dossier ou le fichier et ré-essayer, ou annuler l'installation. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Assistant de Connexion %1 - - - - Enable experimental feature? - Activer les fonctionnalités expérimentales ? - - - - Enable experimental placeholder mode - Activer le mode placeholder expérimental - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Lorsque le mode "fichiers virtuels" est activé, aucun fichier ne sera initialement téléchargé. À la place, un minuscule fichier "%1" sera créé pour chaque fichier qui existe sur le serveur. Les contenus pourront être téléchargés en lançant ces fichiers ou en ouvrant leur menu contextuel. - -Le mode fichiers virtuels est mutuellement exclusif avec une synchronisation sélective. Les dossiers qui ne sont actuellement pas sélectionnés seront traduits en dossiers en ligne et vos paramètres de synchronisation seront réinitialisés. - -Passer à ce mode annulera toute les synchronisations actuellement en cours d'exécution. - -Il s'agit d'un nouveau mode expérimental. Si vous décidez de l'utiliser, merci de signaler toute anomalie qui pourrait se produire. - - - - Stay safe - Rester sauf - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Les liens symboliques ne sont pas pris en charge par la synchronisation. - - - - File is listed on the ignore list. - Le fichier est présent dans la liste des fichiers à exclure. + Pattern + Motif de nom - - File names ending with a period are not supported on this file system. - Les noms de fichier se terminant par un point ne sont pas pris en charge sur votre système. + + Allow Deletion + Autoriser suppression - - File names containing the character '%1' are not supported on this file system. - Les noms de fichier contenant le caractère '%1' ne sont pas pris en charge sur votre système. + + Remove + Supprimer - - File name contains at least one invalid character - Le nom de fichier contient au moins un caractère non valable + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Les fichiers ou dossiers correspondant au motif ne seront pas synchronisés. Les changements prendront effet à la prochaine synchronisation des dossiers. + +Les éléments dont la suppression est autorisée seront supprimés s'ils empêchent un dossier d'être supprimé. Cela est utile pour les métadonnées. - - The file name is a reserved name on this file system. - Le nom du fichier est réservé sur votre système. + + Could not open file + Impossible d'ouvrir le fichier - - Filename contains trailing spaces. - Le nom du fichier se fini par des espaces. + + Cannot write changes to '%1'. + Impossible d'écrire les modifications sur '%1'. - - Filename is too long. - Le nom de fichier est trop long. + + Add Ignore Pattern + Ajouter un motif d'exclusion - - File/Folder is ignored because it's hidden. - Le fichier ou dossier a été ignoré car il est masqué. + + Add a new ignore pattern: + Ajoutez un nouveau motif d'exclusion : - - Stat failed. - Stat échoué. + + This entry is provided by the system at '%1' and cannot be modified in this view. + Cette entrée est fournie par le système dans '%1' et ne peut être modifiée dans cette vue. + + + OCC::LocalActivityWidget - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Conflit : La version du serveur a été téléchargée, la version locale renommée et non téléversée. + + Filter + Filtre - - The filename cannot be encoded on your file system. - Le nom de fichier ne peut pas être encodé sur votre système. + + Local activity table + Tableau des activités locales - - The filename is blacklisted on the server. - Le nom de fichier est sur la liste noire sur le serveur. + + + Account + Compte - - size - taille + + Filter menu + Menu des filtres - - permissions - permissions, autorisations + + Actions menu + Menu des actions - - etag - etag + + Sort ascending by %1 + Trier par ordre croissant selon %1 - - file id - identifiant de fichier + + Sort descending by %1 + Trier par ordre décroissant selon %1 - - server reported no %1 - Le serveur n'a pas reporté de %1 + + Retry sync + Relancer la synchronisation + + + OCC::LogBrowser - - Moved to invalid target, restoring - Déplacé vers une cible invalide, restauration + + Log Output + Consigner la sortie dans des fichiers de log - - Not allowed to remove, restoring - Non autorisé à supprimer. Restauration + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>Les logs contiennent des informations sensibles que vous ne devriez pas rendre publiques</b></span></p></body></html> - - Ignored because of the "choose what to sync" blacklist - Ignoré en raison de la liste noire "Sélectionner le contenu à synchroniser". + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + Le client peut écrire des fichiers journaux de débogage dans un dossier temporaire. Ces fichiers journaux sont très utiles pour diagnostiquer les problèmes. +Étant donné que les fichiers journaux peuvent devenir larges, le client en commencera un nouveau pour chaque exécution de synchronisation et compressera les anciens. - - Not allowed because you don't have permission to add subfolders to that folder - Non autorisé car vous n'avez pas la permission d'ajouter des sous-dossiers dans ce dossier + + Enable logging to temporary folder + Activer la journalisation dans le dossier temporaire - - Not allowed because you don't have permission to add files in that folder - Non autorisé car vous n'avez pas la permission d'ajouter des fichiers dans ce dossier + + If enabled, logs will be written to: + Si activé, les journaux seront écrits dans: - - Not allowed to upload this file because it is read-only on the server, restoring - Non autorisé à envoyer ce fichier car il est en lecture seule sur le serveur. Restauration + + C:/log + C:/log - - Error while reading the database - Erreur lors de la lecture de la base de données + + Log Http traffic + Journaliser le trafic HTTP - - Server replied with an error while reading directory '%1' : %2 - Le serveur a répondu avec une erreur durant la lecture du répertoire '%1' : %2 + + Log files to keep: + Fichiers journaux à garder : - - - OCC::PropagateDirectory - - Error updating metadata: %1 - + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Ces paramètres sont conservés malgré les redémarrages du client. +Veuillez noter qu’utiliser les options de journalisation en ligne de commande est prioritaire sur ces paramètres. - - %1 the folder is currently in use - + + Open folder + Ouvrir le dossier local - OCC::PropagateDownloadFile + OCC::MainWindow - - Failed to free up space, the file %1 is currently in use - + + Hide + Masquer - - - File %1 can not be downloaded because of a local file name clash with %2! + + Main toolbar for the application - - The download would reduce free local disk space below the limit - Le téléchargement réduira l'espace disque libre en dessous de la limite - - - - Free space on disk is less than %1 - Il y a moins de %1 d'espace libre sur le disque - - - - File was deleted from server - Le fichier a été supprimé du serveur - - - - The file could not be downloaded completely. - Le fichier n'a pas pu être téléchargé intégralement. - - - - The downloaded file is empty despite the server announced it should have been %1. - Le fichier téléchargé est vide bien que le serveur indique que sa taille devrait être de %1. - - - - File %1 cannot be saved because of a local file name clash with %2! + + Main toolbar - - Error updating metadata: %1 - + + + More + Plus + + + OCC::MainWindowController - - - - - The file %1 is currently in use - + + Add account... + Ajouter un compte... - - - File has changed since discovery - Le fichier a changé depuis sa découverte + + Settings... + Paramètres... - - - OCC::PropagateItemJob - - ; Restoration Failed: %1 - ; Échec de la restauration : %1 + + About... + À propos de… - - - OCC::PropagateLocalMkdir - - could not delete file %1, error: %2 - impossible de supprimer le fichier %1. Erreur : %2 + + Quit + Quitter - - Can not create local folder %1 because of a local file name clash with %2 + + + Errors: %1 - - Error updating metadata: %1 - + + Activity + Activité - - The file %1 is currently in use - + + Quit %1 + Quitter %1 - - could not create folder %1 - Impossible de créer le dossier %1 + + Are you sure you want to quit %1? + Êtes-vous sûr de vouloir quitter %1 ? - OCC::PropagateLocalRemove + OCC::ModalWrapperWidget - - Could not remove %1 because of a local file name clash with %2! - + + Close + Fermer - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Le fichier %1 ne peut pas être renommé en %2 à cause d'un conflit local de nom de fichier - - - - Could not rename %1 to %2, the file is currently in use - - + OCC::Models - - Error updating metadata: %1 - + + %1 Filter: + %1 Filtre : - - The file %1 is currently in use - + + All + Tout - - Failed to rename file - + + Reset Filters + Réinitialiser les filtres - - Error setting pin state - Erreur lors de la modification de l'état de l'épingle + + Status Filter: + Filtre d'état : - OCC::PropagateRemoteDelete + OCC::NetworkSettings - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Le code HTTP retourné par le serveur n'est pas valide. La valeur attendue est 204 mais la valeur reçue est "%1 %2". + + Form + Formulaire - - - OCC::PropagateRemoteMkdir - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Le code HTTP retourné par le serveur n'est pas valide. La valeur attendue est 201 mais la valeur reçue est "%1 %2". + + Pause synchronization when the Internet connection is metered + Pauser la synchronisation quand la connexion internet est limitée - - Error writing metadata to the database: %1 - + + Proxy Settings + Paramètres de serveur proxy - - Setting file status failed due to file lock - + + No Proxy + Pas de serveur proxy - - - OCC::PropagateRemoteMove - - Could not rename %1 to %2, error: %3 - Impossible de renommer %1 vers %2, erreur : %3 + + Use system proxy + Utiliser les paramètres du système - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Le code HTTP retourné par le serveur n'est pas valide. La valeur attendue est 201 mais la valeur reçue est "%1 %2". + + Host + Hôte - - Error updating metadata: %1 - + + Proxy Hostname + Nom d'hôte du Proxy - - The file %1 is currently in use - + + : + : - - Error writing metadata to the database - Erreur à l'écriture des métadonnées dans la base de données + + Proxy Port Number + Numéro de port du Proxy - - Error setting pin state - Erreur lors de la modification de l'état de l'épingle + + Proxy server requires authentication + Le serveur proxy requiert une authentification - - - OCC::PropagateUpdateMetaDataJob - - Could not update file : %1 - + + Note: proxy settings have no effects for accounts on localhost + Remarque: les paramètres de proxy n'ont aucun effet sur les comptes localhost - - The file %1 is currently in use - + + Specify proxy manually + Spécifier le proxy manuellement - - - OCC::PropagateUploadFileCommon - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Impossible d'envoyer le fichier %1 car un autre fichier de même nom existe, différant seulement par la casse. + + Proxy type + Type de proxy - - File Removed - Fichier supprimé + + + Hostname of proxy server + Nom d'hôte du serveur proxy + + + + + Username for proxy server + Nom d'utilisateur pour le serveur proxy - - Local file changed during syncing. It will be resumed. - Fichier local modifié pendant la synchronisation. Elle va reprendre. + + + Password for proxy server + Mot de passe pour le serveur proxy - - Local file changed during sync. - Fichier local modifié pendant la synchronisation. + + HTTP(S) proxy + Proxy HTTP(S) - - Error updating metadata: %1 - + + SOCKS5 proxy + Proxy SOCKS5 + + + OCC::NewAccountBuilder - - The file %1 is currently in use - + + no error details are available + aucune information sur l'erreur n'est disponible - - - Upload of %1 exceeds the quota for the folder - Le téléversement de %1 amène un dépassement de quota pour le dossier + + Unable to connect to server during account creation: %1. + Impossible de se connecter au serveur lors de la création du compte : %1. - OCC::PropagateUploadFileNG + OCC::NewAccountWizard - - %1 the file is currently in use - + + Cancel Setup + Annuler la configuration - - The local file was removed during sync. - Fichier local supprimé pendant la synchronisation. + + Do you really want to cancel the account setup? + Voulez-vous vraiment annuler la configuration du compte ? + + + OCC::NewAccountWizardController - - Local file changed during sync. - Fichier local modifié pendant la synchronisation. + + Add account… + Ajouter un compte… - - The server did ask for a removed legacy feature(polling) - + + Back + Retour - - Unexpected return code from server (%1) - Le serveur a retourné un code inattendu (%1) + + Advanced + Avancé - - Missing File ID from server - L'identifiant de fichier est manquant sur le serveur + + Sign in + S'identifier - - Missing ETag from server - L'information Etag de modification de fichier est manquante sur le serveur + + Open sign in again + Se reconnecter - OCC::PropagateUploadFileTUS + OCC::NewVersionAvailableWidget - - %1 the file is currently in use - + + Skip this version + Ignorer cette version - - The local file was removed during sync. - Fichier local supprimé pendant la synchronisation. + + Get update + Télécharger la version - - Local file changed during sync. - Fichier local modifié pendant la synchronisation. + + Skip this time + Ignorer pour cette fois - OCC::PropagateUploadFileV1 + OCC::OAuth - - %1 the file is currently in use - + + Successfully signed in + Identification réussie - - The server did ask for a removed legacy feature(polling) - + + Now, explore %1 on desktop. + Maintenant, explorez %1 sur votre ordinateur. - - The local file was removed during sync. - Fichier local supprimé pendant la synchronisation. + + Error returned from the server: <em>%1</em> + Erreur retournée par le serveur: <em>%1</em> - - Local file changed during sync. - Fichier local modifié pendant la synchronisation. + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Il y a eu une erreur durant l'accès au jeton de destination: <br><em>%1</em> - - The server did not acknowledge the last chunk. (No e-tag was present) - Le serveur n'a pas confirmé la réception du dernier morceau. (Aucun e-tag n'était présent). + + Could not parse the JSON returned from the server: <br><em>%1</em> + Impossible d'analyser le fichier JSON récupéré sur le serveur : <br><em>%1</em> - - - OCC::ProtocolItemModel - - Time - Heure + + Unsupported token type: %1 + Type de jeton non pris en charge: %1 - - File - Fichier + + The reply from the server did not contain all expected fields +:%1 + La réponse du serveur ne contenait pas tous les champs attendus +:%1 - - Folder - Dossier + + Unknown Error + Erreur inconnue - - Issues - problèmes + + + Login Error + Erreur de connexion - - Action - Action + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + Vous vous êtes connecté avec l'utilisateur <em>%1</em>, mais vous devez vous connecter avec l'utilisateur <em>%2</em>.<br>Veuillez retourner au client %3 et redémarrer l'authentification. - - Size - Taille + + Wrong user + Mauvais utilisateur + + + OCC::OAuthPageController - - Account - Compte + + Application Name Logo %1 + Nom de l'application Logo %1 - - Status - Status + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Laissez cet écran ouvert. Une invite de connexion apparaîtra dans votre navigateur Web pour vous connecter à l'adresse suivante. - - - OCC::ProtocolWidget - - Form - Formulaire + + Sign in URL + URL d'identification - - Local sync protocol - Historique des opérations de synchronisation locale + + To copy the sign in URL to the clipboard, use the copy button + Pour copier l'URL d'identification dans le presse-papiers, utilisez le bouton Copier. - - Filter - Filtre + + Copy URL to sign in + Copier l'URL pour vous identifier - - Account - Compte + + Additional logo defined by the organization + Logo supplémentaire défini par l'organisation - - Copy to clipboard - Copier dans le presse papiers + + URL copied + URL copiée - - Show in file browser - + + Copy URL + Copier l'URL - - Show in web browser + + Unable to connect to the login service. If the problem continues, please contact support. - - Retry sync - + + Please copy the authentication URL using the button above, and provide this information to support. + Veuillez copier l'URL d'authentification à l'aide du bouton ci-dessus et fournir ces informations au service d'assistance. - - - OCC::ProxyAuthDialog - - Proxy authentication required - Authentification requise sur le proxy + + Failed to look up WebFinger instances: %1 + Échec de la recherche des instances WebFinger : %1 - - Username: - Nom d'utilisateur : + + You are already connected to an account with these credentials. + Vous êtes déjà connecté à un compte avec ces identifiants. - - Proxy: - Proxy : + + Unable to retrieve capabilities from server. + Impossible de récupérer les capacités du serveur. - - The proxy server needs a username and password. - Le serveur proxy requiert un identifiant et un mot de passe. + + The server is not supported by this client. + Le serveur n'est pas pris en charge par ce client. - - Password: - Mot de passe : + + Error while trying to log in to OAuth2-enabled server. %1 + Erreur lors de la tentative de connexion au serveur compatible OAuth2. %1 - - TextLabel - TextLabel + + Unable to open browser with provided URL. %1 + Impossible d'ouvrir le navigateur avec l'URL fournie. %1 - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Sélectionner le contenu à synchroniser + + OAuth2 authentication requires a secured connection. %1 + L'authentification OAuth2 nécessite une connexion sécurisée. %1 - - - OCC::SelectiveSyncWidget - - Loading ... - Chargement… + + Authorization server unreachable. %1 + Serveur d'autorisation inaccessible. %1 + + + OCC::OCUpdater - - Deselect remote folders you do not wish to synchronize. - Désélectionnez les sous-dossiers distants que vous ne souhaitez pas synchroniser. + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Impossible de télécharger la mise à jour. Veuillez la <a href='%1'>télécharger manuellement</a>. - - Name - Nom + + Could not check for new updates. + Impossible de vérifier la présence de nouvelles mises à jour. - - Size - Taille + + Downloading %1. Please wait… + Téléchargement %1 en cours. Veuillez patienter… - - - No subfolders currently on the server. - Aucun sous-dossier sur le serveur. + + %1 installed successfully. Restart the application to finish installing the update. + %1 installé avec succès. Redémarrez l'application pour terminer l'installation de la mise à jour. - - An error occurred while loading the list of sub folders. - Une erreur est survenue lors du chargement de la liste des sous-dossiers. + + %1 available. Restart application to start the update. + %1 disponible. Veuillez redémarrer l’application afin de démarrer la mise à jour. - - - OCC::ServerNotificationHandler - - Dismiss - Ignorer + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Nouvelle %1 disponible. Veuillez cliquer <a href='%2'>ici</a>pour télécharger la nouvelle AppImage manuellement. - - - OCC::SettingsDialog - - Settings - Paramètres + + New %1 available. Please use the system's update tool to install it. + Nouvel(le) %1 disponible. Veuillez utiliser l’outils de mise à jour système afin de l’installer. - - Activity - Activité + + Checking update server… + Interrogation du serveur de mises à jour… - - Add account - Ajouter un compte + + Update status is unknown: Did not check for new updates. + L'état du processus de mise à jour est inconnu. Impossible de vérifier la présence de mises à jour. - - - Quit %1 - Quitter %1 + + No updates available. Your installation is at the latest version. + Aucune mise à jour disponible. Votre installation est à jour. + + + OCC::OwncloudPropagator - - Are you sure you want to quit %1? - Êtes-vous sûr de vouloir quitter %1 ? + + File %1 is currently in use + Le fichier %1 est en cours d'utilisation - - Account - Compte + + vfs instance is not available. + + + + OCC::ProcessDirectoryJob - - MainWindow - FenêtrePrincipale + + Symbolic links are not supported in syncing. + Les liens symboliques ne sont pas pris en charge par la synchronisation. - - Hide - Masquer + + File is listed on the ignore list. + Le fichier est présent dans la liste des fichiers à exclure. - - - OCC::ShareDialog - - TextLabel - TextLabel + + File names ending with a period are not supported on this file system. + Les noms de fichier se terminant par un point ne sont pas pris en charge sur votre système. - - share label - label de partage + + File names containing the character '%1' are not supported on this file system. + Les noms de fichier contenant le caractère '%1' ne sont pas pris en charge sur votre système. - - Dialog - Fenêtre + + File name contains at least one invalid character + Le nom de fichier contient au moins un caractère non valable - - ownCloud Path: - Chemin ownCloud : + + The file name is a reserved name on this file system. + Le nom du fichier est réservé sur votre système. - - %1 Sharing - Partage %1 + + Filename contains trailing spaces. + Le nom du fichier se fini par des espaces. - - %1 - %1 + + Filename is too long. + Le nom de fichier est trop long. - - Folder: %2 - Dossier: %2 + + File/Folder is ignored because it's hidden. + Le fichier ou dossier a été ignoré car il est masqué. - - The server does not allow sharing - Le serveur ne prend pas en charge le partage + + Stat failed. + Stat échoué. - - Retrieving maximum possible sharing permissions from server... - Récupération des permissions de partage maximales possibles depuis le serveur... + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Conflit : La version du serveur a été téléchargée, la version locale renommée et non téléversée. - - The file can not be shared because it was shared without sharing permission. - Le fichier ne peut pas être partagé car il a été partagé sans permission de repartage. + + The filename cannot be encoded on your file system. + Le nom de fichier ne peut pas être encodé sur votre système. - - Users and Groups - Utilisateurs et Groupes + + The filename is blacklisted on the server. + Le nom de fichier est sur la liste noire sur le serveur. - - Public Links - Liens publics + + + + + the VFS instance is missing. Unable to continue. + - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Partager NewDocument.odt + + server reported no %1 + Le serveur n'a pas reporté de %1 - - Upload only (File Drop) - Téléversement seulement (Glisser le Fichier) + + + account was deleted. Unable to continue + Le compte a été supprimé. Impossible de continuer. - - Receive files from others without revealing the contents of the folder. - Recevoir les fichiers des autres sans révéler le contenu du dossier. + + SelectiveSync: Ignored because its path is deselected + SelectiveSync : ignoré car son chemin d'accès est désélectionné - - Download / View / Edit - Télécharger / Voir / Modifier + + Moved to invalid target, restoring + Déplacé vers une cible invalide, restauration - - Name: - Nom : + + Not allowed to remove, restoring + Non autorisé à supprimer. Restauration - - Enter name of new share... - Donnez un nom au nouveau partage… + + Error while reading the database. + - - &Create - &Créer + + account was deleted. Unable to continue. + - - TextLabel - TextLabel + + Not allowed because you don't have permission to add subfolders to that folder + Non autorisé car vous n'avez pas la permission d'ajouter des sous-dossiers dans ce dossier - - Set &password - Enregistrer le mot de &passe + + Error while doing a rename, unhandled status code: %1 + Erreur lors du renommage, code d'état non géré : %1 - - Set &expiration date - Spécifier une date d'&expiration + + Not allowed because you don't have permission to add files in that folder + Non autorisé car vous n'avez pas la permission d'ajouter des fichiers dans ce dossier - - Set password - Définir le mot de passe + + Not allowed to upload this file because it is read-only on the server, restoring + Non autorisé à envoyer ce fichier car il est en lecture seule sur le serveur. Restauration - - Recipients can view or download contents. - Les bénéficiaires peuvent voir ou télécharger le contenu. + + This Space is currently unavailable + Cet espace n'est actuellement pas disponible - - Download / View - Télécharger / Voir + + Server replied with an error while reading directory '%1' : %2 + Le serveur a répondu avec une erreur durant la lecture du répertoire '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - Les bénéficiaires peuvent voir, télécharger, modifier, supprimer et téléverser le contenu. + + Error updating metadata: %1 + Erreur lors de la mise à jour des métadonnées : %1 - - Anyone with the link has access to the file/folder - Quiconque dispose du lien a accès aux fichiers/dossiers + + %1 the folder is currently in use + %1 le dossier est actuellement utilisé + + + OCC::PropagateDownloadFile - - - P&assword protect - Protéger par mot de p&asse + + Failed to free up space, the file %1 is currently in use + Impossible de libérer de l'espace, le fichier %1 est actuellement utilisé. - - The file can not be shared because it was shared without sharing permission. - Le fichier ne peut pas être partagé car il a été partagé sans permission de repartage. + + + File %1 can not be downloaded because of a local file name clash with %2! + Le fichier %1 ne peut pas être téléchargé en raison d'un conflit de nom de fichier local avec %2 ! - - Link shares have been disabled - Les partages par lien ont été désactivés + + The download would reduce free local disk space below the limit + Le téléchargement réduira l'espace disque libre en dessous de la limite - - Create public link share - Créer une lien de partage public + + Free space on disk is less than %1 + Il y a moins de %1 d'espace libre sur le disque - - - Delete - Supprimer + + File was deleted from server + Le fichier a été supprimé du serveur - - Open link in browser - Ouvrir le lien dans le navigateur + + Broken webserver returned empty content length for non-empty file on resume + Le serveur web défectueux a renvoyé une longueur de contenu vide pour un fichier non vide lors de la reprise. - - Copy link to clipboard - Copier le lien vers le presse-papier + + The file could not be downloaded completely. + Le fichier n'a pas pu être téléchargé intégralement. - - Copy link to clipboard (direct download) - Copier le lien vers le presse-papier (téléchargement direct) + + The downloaded file is empty despite the server announced it should have been %1. + Le fichier téléchargé est vide bien que le serveur indique que sa taille devrait être de %1. - - Send link by email - Envoyer le lien par email + + File %1 cannot be saved because of a local file name clash with %2! + Le fichier %1 ne peut pas être sauvegardé en raison d'un conflit de nom de fichier local avec %2 ! - - Send link by email (direct download) - Envoyer le lien par courriel (téléchargement direct) + + Error updating metadata: %1 + Erreur lors de la mise à jour des métadonnées : %1 - - Create new... - Créer nouveau... + + + + + The file %1 is currently in use + Le fichier %1 est en cours d'utilisation - - I shared %1 with you - J'ai partagé %1 avec vous + + + File has changed since discovery + Le fichier a changé depuis sa découverte + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - Confirmer la suppression du partage par lien + + ; Restoration Failed: %1 + ; Échec de la restauration : %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Voulez-vous vraiment supprimer le partage par lien public<i>%1</i>? -<p>Note: Cette action ne peut être annulée.</p> + + could not delete file %1, error: %2 + impossible de supprimer le fichier %1. Erreur : %2 - - Cancel - Annuler + + Can not create local folder %1 because of a local file name clash with %2 + Impossible de créer le dossier local %1 en raison d'un conflit de nom de fichier local avec %2 - - - Public link - Lien public + + Error updating metadata: %1 + Erreur lors de la mise à jour des métadonnées : %1 - - Delete link share - Supprimer le partage par lien + + The file %1 is currently in use + Le fichier %1 est en cours d'utilisation - - - Please Set Password - Veuillez choisir un mot de passe + + could not create folder %1 + Impossible de créer le dossier %1 - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove - - Share NewDocument.odt - Partager NewDocument.odt + + %1 failed with: %2 + %1 a échoué avec : %2 - - Share with users or groups ... - Partager avec des utilisateurs ou groupes... + + Could not remove %1 because of a local file name clash with %2! + Impossible de supprimer %1 à cause d'un conflit local de nom de fichier avec %2 ! + + + + Could not move '%1' to the trash bin + Impossible de déplacer '%1' vers la corbeille. - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Vous pouvez rediriger des personnes vers ce fichier ou dossier partagé <a href="private link menu"><span style=" text-decoration: underline">en leur donnant un lien privé</span></a>.</p></body></html> + + + %1 the file is currently in use + Le fichier %1 est en cours d'utilisation + + + OCC::PropagateLocalRename - - The item is not shared with any users or groups - Cet élément n'est partagé avec aucun utilisateur ou groupe + + File %1 can not be renamed to %2 because of a local file name clash + Le fichier %1 ne peut pas être renommé en %2 à cause d'un conflit local de nom de fichier - - Open link in browser - Ouvrir le lien dans le navigateur + + Could not rename %1 to %2, the file is currently in use + Impossible de renommer le fichier %1 en %2 car il est en cours d'utilisation - - Copy link to clipboard - Copier le lien vers le presse-papier + + Error updating metadata: %1 + Erreur lors de la mise à jour des métadonnées : %1 - - Send link by email - Envoyer le lien par email + + The file %1 is currently in use + Le fichier %1 est en cours d'utilisation - - No results for '%1' - Aucun résultat pour '%1' + + Failed to rename file + Impossible de renommer le fichier - - I shared something with you - J'ai partagé quelque chose avec vous + + Error setting pin state + Erreur lors de la modification de l'état de l'épingle - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Form + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Le code HTTP retourné par le serveur n'est pas valide. La valeur attendue est 204 mais la valeur reçue est "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - TextLabel + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Le code HTTP retourné par le serveur n'est pas valide. La valeur attendue est 201 mais la valeur reçue est "%1 %2". - - can edit - peut modifier + + Error writing metadata to the database: %1 + Erreur lors de l'écriture des métadonnées dans la base de données : %1 - - can share - peut partager + + Setting file status failed due to file lock + Échec de la définition du statut du fichier en raison du verrouillage du fichier + + + OCC::PropagateRemoteMove - - ... - ... + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Le code HTTP retourné par le serveur n'est pas valide. La valeur attendue est 201 mais la valeur reçue est "%1 %2". - - create - création + + Error updating metadata: %1 + Erreur lors de la mise à jour des métadonnées : %1 - - change - modification + + The file %1 is currently in use + Le fichier %1 est en cours d'utilisation - - delete - suppression + + Error writing metadata to the database + Erreur à l'écriture des métadonnées dans la base de données - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Error setting pin state + Erreur lors de la modification de l'état de l'épingle - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Partager avec %1 + + Could not update file : %1 + Impossible de mettre à jour le fichier : %1 - - Context menu share - Partage du menu contextuel + + The file %1 is currently in use + Le fichier %1 est en cours d'utilisation + + + OCC::PropagateUploadCommon - - Context menu share %1 - Partage du menu contextuel %1 + + Local file changed during sync. It will be resumed. + Fichier local modifié pendant la synchronisation. Elle va reprendre. - - Confirm deletion - Confirmez la suppression + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + Impossible de téléverser le fichier %1 car un autre fichier de même nom existe, différant seulement par la casse. - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Êtes-vous sûr de vouloir supprimer le répertoire <i>%1</i> et tout son contenu de manière permanente ? + + + Upload of %1 exceeds the quota for the folder + Le téléversement de %1 dépasse le quota alloué au dossier. - - Do you want to delete the file <i>%1</i> permanently? - Voulez-vous vraiment supprimer définitivement le fichier <i>%1</i> ? + + + %1 the file is currently in use + Le fichier %1 est en cours d'utilisation - - Select new location... - Sélectionner nouvel emplacement.. + + File Removed + Fichier supprimé - - Error - Erreur + + The server did not provide the file permissions + Le serveur n'a pas fourni les autorisations d'accès au fichier. - - Moving file failed: - -%1 - Le déplacement du fichier a échoué : - -%1 + + Error updating metadata: %1 + Erreur lors de la mise à jour des métadonnées : %1 + + + OCC::PropagateUploadFile - - I shared something with you - J'ai partagé quelque chose avec vous + + %1 the file is currently in use + Le fichier %1 est en cours d'utilisation - - - Share... - partager + + The server did ask for a removed legacy feature(polling) + Le serveur a demandé une fonctionnalité héritée supprimée (polling). - - - Copy private link to clipboard - Copier le lien privé vers le presse-papier + + The local file was removed during sync. + Fichier local supprimé pendant la synchronisation. - - Send private link by email... - Envoyer le lien privé par courriel... + + The server did not acknowledge the file upload. (No e-tag was present) + Le serveur n'a pas confirmé le téléchargement du fichier. (Aucun e-tag n'était présent) + + + OCC::PropagateUploadFileTUS - - Resharing this file is not allowed - Le repartage de ce fichier n'est pas permis + + %1 the file is currently in use + Le fichier %1 est en cours d'utilisation - - Resharing this folder is not allowed - Le repartage de ce dossier n'est pas autorisé + + Checksum computation failed + Échec de calcul de la somme de contrôle - - Create and copy public link to clipboard - + + The local file was removed during sync. + Fichier local supprimé pendant la synchronisation. + + + OCC::ProtocolItemModel - - Copy public link to clipboard - Copier le lien public dans le presse-papier + + Time + Heure - - Open in browser - Ouvrir dans le navigateur web + + File + Fichier - - Show file versions in browser - Montrer les versions du fichier dans le navigateur + + Folder + Dossier - - Rename... - Renommer... + + Issues + problèmes - - Rename and upload... - Renommer et téléverser... + + Action + Action - - Move and rename... - Déplacer et renommer... + + Size + Taille - - Move, rename and upload... - Déplacer, renommer et téléverser... + + Account + Compte - - Delete local changes - Supprimer les changements locaux + + Status + Status + + + OCC::RequestAuthenticationController - - Move and upload... - Déplacer et téléverser... + + IdP is unreachable. Contact your system administrator or try again later. + IdP est inaccessible. Contactez votre administrateur système ou réessayez plus tard. - - Delete - Supprimer + + Authentication failed. + L'authentification a échoué. - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags - + + Application Name Logo %1 + Nom de l'application Logo %1 - - Backup of %1 started - + + Sign in required + Identification requise - - Failed to find %1 - + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + Vous avez peut-être été déconnecté automatiquement en raison d'un problème de serveur ou d'un délai d'expiration. Veuillez vous identifier à nouveau. - - Local path must be a an absolute path - + + Sign in URL + URL d'identification - - Failed to create temporary database - + + To copy the sign in URL to the clipboard, use the copy button + Pour copier l'URL d'identification dans le presse-papiers, utilisez le bouton Copier. - - Backup of %1 succeeded - + + Copy URL to sign in + Copier l'URL pour vous identifier - - Failed to set success tag - + + Additional logo defined by the organization + Logo supplémentaire défini par l'organisation - - Failed to create backup: %1 - + + Stay logged out + Rester déconnecté - - Failed to file id tags - + + Sign in + S'identifier - - Backup of %1 failed with: %2 - + + URL copied + URL copiée - - %1 backup - + + Copy URL + Copier l'URL - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Détails du certificat</h3> - - - - Common Name (CN): - Nom commun (CN) : - + OCC::ResolveUrlAdapter - - Subject Alternative Names: - Noms alternatifs du sujet : + + SSL failure when connecting to server at %1 + Échec SSL lors de la connexion au serveur à %1 - - Organization (O): - Organisation (O) : + + Could not detect compatible server at %1 + Impossible de détecter un serveur compatible à %1 - - Organizational Unit (OU): - Unité d'organisation (OU) : + + Resolved url is invalid %1 + L'URL résolue n'est pas valide %1 - - State/Province: - État/Région : + + Rejected redirect from %1 to %2 + Redirection rejetée de %1 vers %2 + + + OCC::SelectiveSyncWidget - - Country: - Pays : + + Loading … + Chargement … - - Serial: - Numéro de série : + + Deselect remote folders you do not wish to synchronize. + Désélectionnez les sous-dossiers distants que vous ne souhaitez pas synchroniser. - - <h3>Issuer</h3> - <h3>Émetteur</h3> + + Name + Nom - - Issuer: - Émetteur : + + Size + Taille - - Issued on: - Émis le : + + + Currently there are no subfolders on the server. + Il n'y a actuellement aucun sous-dossier sur le serveur. - - Expires on: - Expire le : + + An error occurred while loading the list of subfolders. + Une erreur s'est produite lors du chargement de la liste des sous-dossiers. + + + OCC::SettingsView - - <h3>Fingerprints</h3> - <h3>Empreintes numériques</h3> + + Form + Formulaire - - SHA-256: - SHA-256 : + + General Settings + Paramètres généraux - - SHA-1: - SHA-1 : + + Use Monochrome Icons in the system tray + Utiliser des icônes monochromes dans la barre d'état système - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Note :</b> Ce certificat a été approuvé manuellement</p> + + Show Desktop Notifications + Afficher les notifications sur le bureau - - %1 (self-signed) - %1 (auto-signé) + + Start on Login + Démarrer à la connexion - - %1 - %1 + + Language + Langue - - This connection is encrypted using %1 bit %2. - - Cette connexion est chiffrée en utilisant %1 bit %2. - + + Language selector + Sélecteur de langue - - Server version: %1 - Version du serveur: %1 + + Advanced + Avancé - - No support for SSL session tickets/identifiers - Identifiants/tickets de sessions SSL non pris en charge + + Sync hidden files + Synchroniser les fichiers masqués - - Certificate information: - Informations du certificat : + + Show crash reporter + Afficher le rapport d'incident - - The connection is not secure - La connexion n'est pas sécurisée + + Move remotely deleted files to the local trash bin instead of deleting them + Déplacer les fichiers supprimés à distance dans la corbeille locale plutôt que de les supprimer - - This connection is NOT secure as it is not encrypted. - - Cette connexion n'est PAS sécurisée car elle n'est pas chiffrée. - + + Edit Ignored Files + Modifier les fichiers ignorés - - - OCC::SslErrorDialog - - Form - Formulaire + + Log Settings + Paramètres du journal - - Trust this certificate anyway - Faire confiance à ce certificat malgré tout + + Network + Réseau - - Untrusted Certificate - Certificat non fiable + + Warning + Attention - - Cannot connect securely to <i>%1</i>: - Impossible de se connecter de manière sécurisée à <i>%1</i> : + + Language changes require a restart of this application to take effect. + Le changement de langue nécessite un redémarrage de l'application pour prendre effet. - - with Certificate %1 - avec certificat %1 + + You cannot disable autostart because system-wide autostart is enabled. + Vous ne pouvez pas désactiver le démarrage automatique parce que le démarrage automatique à l'échelle du système est activé. - - - - &lt;not specified&gt; - &lt;non spécifié&gt; + + (use default) + (Utiliser la valeur par défaut) - - - Organization: %1 - Organisation : %1 + + unknown (%1) + Inconnu (%1) + + + OCC::SocketApi - - - Unit: %1 - Unité : %1 + + Share with %1 + parameter is the application name + Partager avec %1 - - - Country: %1 - Pays : %1 + + Confirm deletion + Confirmez la suppression - - Fingerprint (MD5): <tt>%1</tt> - Empreinte (MD5) : <tt>%1</tt> + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Êtes-vous sûr de vouloir supprimer le répertoire <i>%1</i> et tout son contenu de manière permanente ? - - Fingerprint (SHA1): <tt>%1</tt> - Empreinte (SHA1) : <tt>%1</tt> + + Do you want to delete the file <i>%1</i> permanently? + Voulez-vous vraiment supprimer définitivement le fichier <i>%1</i> ? - - Effective Date: %1 - Date de début de validité : %1 + + Error + Erreur - - Expiration Date: %1 - Date d'expiration : %1 + + Moving file failed: + +%1 + Le déplacement du fichier a échoué : + +%1 - - Issuer: %1 - Émetteur : %1 + + I shared something with you + J'ai partagé quelque chose avec vous - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - %1 (ignoré à cause d'une précédente erreur, nouvel essai dans %2) + + Select new location… + Sélectionner un nouvel emplacement… - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Seulement %1 disponibles, il faut au moins %2 pour démarrer + + + Share… + Partage… - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Impossible d'accéder ou de créer une base de données locale de synchronisation. Assurez vous de disposer des droits d'écriture dans le dossier de synchronisation. + + + Copy private link to clipboard + Copier le lien privé vers le presse-papier - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - L'espace disque est faible : les téléchargements qui amèneraient à réduire l'espace libre en dessous de %1 ont été ignorés. + + Resharing this file is not allowed + Le repartage de ce fichier n'est pas permis - - There is insufficient space available on the server for some uploads. - Il n'y a pas suffisamment d’espace disponible sur le serveur pour certains téléversements. + + Resharing this folder is not allowed + Le repartage de ce dossier n'est pas autorisé - - Unresolved conflict. - conflit non résolu. + + Open in %1 + Ouvrir dans %1 - - Using virtual files with suffix, but suffix is not set - Utilisation d'un fichier virtuel avec un suffixe qui n'a pas été défini + + Show file versions in web browser + Afficher les versions des fichiers dans le navigateur Web - - Unable to read the blacklist from the local database - Impossible de lire la liste noire de la base de données locale + + Rename… + Renommer… - - Unable to read from the sync journal. - Impossible de lire le journal de synchronisation. + + Rename and upload… + Renommer et uploader… - - Cannot open the sync journal - Impossible d'ouvrir le journal de synchronisation + + + Move and rename… + Déplacer et renommer… - - Aborted - Abandonné + + Move, rename and upload… + Déplacer, renommer et téléverser… - - - OCC::SyncJournalDb - - Failed to connect database. - + + Move and upload… + Déplacer et téléverser… - - - OCC::Systray - - %1: %2 - %1 : %2 + + Delete local changes + Supprimer les changements locaux - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>Version %1. Pour plus d'informations, visitez <a href="%2">https://%3</a></p><p>Pour voir les problèmes connus et obtenir de l'aide, merci de visiter : <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Par Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, et d'autres.</small></p><p>Copyright ownCloud GmbH</p><p>Distribué par %4 et sous licence GNU General Public License (GPL) Version 2.0.<br/>%5 et le logo %5 sont une une marqué déposée de %4 aux États-Unis, d'autres pays, ou les deux.</p><p><small>%6</small></p> + + Delete + Supprimer - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - L'entête de somme de contrôle est mal formée. - + OCC::SpacesPage - - The checksum header contained an unknown checksum type '%1' - L'en-tête de la somme de contrôle contenait un type de somme de contrôle inconnu '%1' + + Select a folder to sync it to your computer. + Sélectionnez un dossier pour le synchroniser avec votre ordinateur. - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - Le fichier téléchargé ne correspond pas au checksum, il va être retéléchargé. '%1' != '%2' + + Select a Space to sync it to your computer. + Sélectionnez un espace pour le synchroniser avec votre ordinateur. - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - + OCC::SyncEngine - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - La fonctionnalité de système de fichiers virtuels nécessite un système de fichier NTFS, %1 utilise un système %2 + + %1 (skipped due to earlier error, trying again in %2) + %1 (ignoré à cause d'une précédente erreur, nouvel essai dans %2) - - The Virtual filesystem feature is not supported on network drives + + Unable to sync: the local folder is missing. - - - OCC::ownCloudGui - - - Please sign in - Veuillez vous connecter - - - Folder %1: %2 - Dossier %1 : %2 + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Seulement %1 disponibles, il faut au moins %2 pour démarrer - - Unresolved %1 conflicts - + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Impossible d'accéder ou de créer une base de données locale de synchronisation. Assurez vous de disposer des droits d'écriture dans le dossier de synchronisation. - - Ignored errors %1 + + + + + Cannot open the sync journal. - - There are no sync folders configured. - Aucun dossier à synchroniser n'est configuré + + Aborted due to %1 + Interrompu en raison de %1 - - Open in browser - Ouvrir dans le navigateur web - - - - - - Log in... - Connexion... - - - - - - Log out - Se déconnecter + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + L'espace disque est faible : les téléchargements qui amèneraient à réduire l'espace libre en dessous de %1 ont été ignorés. - - Recent Changes - Modifications récentes + + There is insufficient space available on the server for some uploads. + Il n'y a pas suffisamment d’espace disponible sur le serveur pour certains téléversements. - - Managed Folders: - Dossiers configurés + + application exit + abort reason + sortie de l'applicatio - - Open folder '%1' - Ouvrir le dossier local '%1' + + Unable to read the blacklist from the local database + Impossible de lire la liste noire de la base de données locale - - Open %1 in browser - Ouvrir %1 dans le navigateur + + Unable to read from the sync journal. + Impossible de lire le journal de synchronisation. + + + OCC::SyncErrorWidget - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - Le serveur du compte %1 utilise une version non-supportée %2. Utiliser ce client avec cette version non supportée du serveur n'a pas été testée et est potentiellement dangereux. Continuez à vos risques et périls. + + Issues table + Tableau des problèmes - - Unknown status - Statut inconnu + + There were too many issues. Not all will be visible here. + Il y a eu trop de problèmes. Tous n'apparaissent pas ici. - - Settings... - Paramètres... + + There were conflicts. Check the documentation on how to resolve them. + Des conflits ont été détectés. Consultez la documentation pour savoir comment les résoudre. - - Details... - Toutes les modifications... + + The file %1 was ignored as its name is reserved by %2 + Le fichier %1 a été ignoré car son nom est réservé par %2 - - Help - Aide... + + Reset column sizes + Réinitialiser la taille des colonnes - - Quit %1 - Quitter %1 + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Il y a eu des conflits. <a href="%1">Consultez la documentation pour pouvoir les résoudre.</a> - - Disconnected from %1 - Déconnecté de %1 + + Filter menu + Menu des filtres - - Unsupported Server Version - Version du Serveur non prise en charge + + Account + Compte - - Disconnected - Déconnecté + + Filter + Filtre + + + OCC::SyncJournalDb - - Disconnected from some accounts - Déconnecté de certains comptes + + Failed to connect database. + Échec de connexion de la base de données. + + + OCC::Theme - - Disconnected from accounts: - Déconnecté des comptes : + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Version %1. Pour plus d'informations rendez-vous sur <a href="%2">https://%3</a></p><p>Pour connaître les problèmes connus et obtenir de l'aide, veuillez consulter : <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>Par Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller et d'autres.</small></p><p>Copyright ownCloud GmbH (Une société Kiteworks)</p><p>Distribué par %4 et sous license GNU General Public License (GPL) Version 2.0.<br/>%5 et le logo %5 sont des marques déposées de %4 aux États-Unis, dans d'autres pays, ou dans les deux.</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Account %1: %2 - Compte %1 : %2 + + TLS Certificate Error + Erreur de certificat TLS - - Signed out - Session fermée + + Do you want to trust this certificate anyway? + Voulez-vous tout de même faire confiance à ce certificat ? - - Account synchronization is disabled - La synchronisation est en pause + + Cannot connect securely to %1 + Impossible de se connecter de manière sécurisée à %1 - - Synchronization is paused - La synchronisation est en pause + + &lt;not specified&gt; + &lt;non spécifié&gt; - - Up to date (%1) - + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>avec le certificat %1</h3><div id="ccert"><p>Organisation : %2 <br/> Unité : %3<br/>Pays : %4</p><p>Empreinte numérique (MD5) : <tt>%5</tt><br/> Empreinte numérique (SHA1) : <tt>%6</tt><br/>Empreinte numérique (SHA256) : <tt>%7</tt><br/>Date d'entrée en vigueur : %8Date d'expiration : %9</div><h3>Émetteur : %10 </h3><div id="issuer"><p>Organisation : %11<br/>Unité : %12<br/>Pays : %13</p></div></div> + + + OCC::TrayMenuController - - No sync folders configured - Aucun dossier de synchronisation configuré + + Unsupported Server Version + Version du Serveur non prise en charge - - Unpause all folders - Relancer tous les dossiers + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + Le serveur du compte %1 utilise une version non-supportée %2. Utiliser ce client avec cette version non supportée du serveur n'a pas été testée et est potentiellement dangereux. Continuez à vos risques et périls. - - Pause all folders - Mettre en pause tous les dossiers + + Show %1 + Afficher %1 - - Unpause all synchronization - Relancer toutes les synchronisations + + Help + Aide... - - Unpause synchronization - Relancer la synchronisation + + About %1 + À propos de %1 - - Pause all synchronization - Mettre en pause toutes les synchronisations + + Quit %1 + Quitter %1 + + + OCC::UpdateDownloadedWidget - - Pause synchronization - Mettre en pause la synchronisation + + Restart later + Redémarrer plus tard - - Log out of all accounts - Se déconnecter de tous les comptes + + Restart now + Redémarrer maintenant + + + OCC::UpdaterScheduler - - Log in to all accounts... - Se connecter à tous les comptes... + + Update available + Des mises à jour sont disponibles + + + OCC::UrlPageController - - New account... - Nouveau compte… + + Your web browser will be opened to complete sign in. + Votre navigateur Web s'ouvrira pour terminer la connexion. - - About %1 - À propos de %1 + + %1 logo + logo de %1 - - No items synced recently - Aucun élément synchronisé récemment + + Welcome to %1 + Bienvenue sur %1 - - Checking for changes in remote '%1' - Vérification des changements distants '%1' + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Entrez l'adresse de votre serveur pour commencer. Votre navigateur Web s'ouvrira pour vous permettre de vous connecter. - - Checking for changes in local '%1' - Vérification des changements locaux '%1' + + Server address field + Champ d'adresse du serveur - - Syncing %1 of %2 (%3 left) - Synchronisation de %1 sur %2 (%3 restantes) + + Enter your server address here + Entrez l'adresse de votre serveur ici - - Syncing %1 of %2 - Synchronisation %1 sur %2 + + Additional logo defined by the organization + Logo supplémentaire défini par l'organisation - - Syncing %1 (%2 left) - Synchronisation de %1 (%2 restantes) + + Invalid server URL: %1 + URL du serveur non valide : %1 - - Syncing %1 - Synchronisation de %1 + + Invalid URL scheme. Only https is accepted. + Schéma d'URL non valide. Seul le protocole https est accepté. - - %1 (%2, %3) - %1 (%2, %3) + + The requested URL failed with query value: %1 + L'URL demandée a échoué avec la valeur de requête : %1 - OwncloudAdvancedSetupPage - - - Form - Formulaire - + OCC::UserInfoAdapter - - - TextLabel - TextLabel + + Network request to collect user info failed + Échec de la requête réseau visant à collecter les informations utilisateur - - Server - Serveur + + Unable to retrieve user info: invalid credentials. + Impossible de récupérer les informations utilisateur : identifiants non valides. - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Si cette case est cochée, le contenu existant du dossier local sera supprimé pour démarrer une synchronisation propre depuis le serveur.</p><p>Ne pas cocher si le contenu local doit être téléversé vers le serveur.</p></body></html> + + Unexpected network response when retrieving user info. + Réponse réseau inattendue lors de la récupération des informations utilisateur. - - Ask for confirmation before synchroni&zing folders larger than - Demander confirmation avant de synchroniser les dossiers de taille supérieure à + + Retrieving user info failed with JSON error. + Échec de la récupération des informations utilisateur avec une erreur JSON. + + + OCC::ValidateChecksumHeader - - S&ynchronize everything from server - + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + Le fichier téléchargé ne correspond pas au checksum, il va être retéléchargé. '%1' != '%2' + + + OCC::Vfs - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - Mo + + The Virtual filesystem feature does not support a drive as sync root + Le système de fichiers virtuels ne prends pas en charge un disque en tant que racine de synchronisation - - Ask for confirmation before synchronizing e&xternal storages - Demander confirmation avant de synchroniser des stockages externes + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + La fonctionnalité de système de fichiers virtuels nécessite un système de fichier NTFS, %1 utilise un système %2 - - Choose what to sync - Sélectionner le contenu à synchroniser + + The Virtual filesystem feature is not supported on network drives + Le système de fichiers virtuels ne prends pas en charge les disques virtuels. + + + OCC::VfsWinPrivate - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Lorsque cette option est sélectionnée, l'assistant se fermera sans rien synchroniser. Vous pouvez utiliser l'option &quot;Ajouter un dossier connecté de synchronisation&quot; depuis les paramètres du compte pour choisir quels dossiers parmi les locaux et les distants vous souhaitez synchroniser</p></body></html> + + + %1 - %2 + %1 - %2 - - Manually create folder sync connections - Créer manuellement un dossier de synchronisation + + Could not find StorageProviderState property %1 + Impossible de trouver la propriété StorageProviderState %1 - - &Local Folder - &Dossier local + + Could not find StorageFolder for %1 error: %2 + Impossible de trouver StorageFolder pour %1. Erreur : %2 - - pbSelectLocalFolder - pbSelectLocalFolder + + Could not retrieve StorageFolder for %1 %2 (0x%3) + - - &Keep local data - &Conserver les données locales + + The folder is used by a different client: %1 + - - Start a &clean sync (Crreate a backup and erases the local folder!) + + Error registering StorageProvider for %1: %2 (0x%3) - - - OwncloudHttpCredsPage - - Form - Formulaire + + Unable to connect sync root: %1 error: %2 + - - &Username - &Nom d'utilisateur + + %1 is managed by another sync client + %1 est géré par un autre client de synchronisation. - - &Password - &Mot de passe + + Downgrading the folder from %1 to %2 is not supported + La rétrogradation du dossier de %1 à %2 n'est pas prise en charge. - OwncloudOAuthCredsPage + OCC::WebFingerLookupAdapter - - Form - Form + + Failed to retrieve user info + Echec de la récupération des informations de l'utilisateur - - Please switch to your browser to proceed. - Merci de basculer vers votre navigateur pour poursuivre. + + WebFinger lookup returned no links + La recherche WebFinger n'a renvoyé aucun lien. + + + OCC::WindowsUpdater - - An error occurred while connecting. Please try again. - Une erreur s'est produite pendant la connexion. Veuillez réessayer. + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Une nouvelle version du client %1 est disponible.</p><p><b>%2</b> est disponible au téléchargement. La version actuellement installée est %3.<p> - - Re-open Browser - Redémarrez votre navigateur + + Skip this version + Ignorer cette version - - - OwncloudSetupPage - - Form - Formulaire + + Update Failed + La mise à jour a échoué + + + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p> Une nouvelle version du Client %1 est disponible mais la mise à jour a échoué. </p><p><b>%2</b> a été téléchargé. La version installée est %3.</p> - - Ser&ver Address - Adresse du serveur + + Ask again later + Redemander plus tard - - https://... - https://... + + Restart and update + Redémarrer et mettre à jour - - Error Label - Message d'erreur + + Update manually + Mettre à jour manuellement QObject - + in the future Dans le futur - + %n day(s) ago - HierIl y a %n jours + il y a %n jouril y a %n joursil y a %n jours - + %n hour(s) ago - Il y a %n heureIl y a %n heures + Il y a %n heureIl y a %n heuresIl y a %n heures - + now maintenant - - Less than a minute ago - Il y a moins d'une minute + + less than a minute ago + il y a moins d'une minute - + %n minute(s) ago - Il y a %n minuteIl y a %n minutes - - - - Some time ago - Il y a quelque temps + il y a %n minuteil y a %n minutesil y a %n minutes - + %1: %2 this displays an error string (%2) for a file %1 %1 : %2 + + SpacesBrowser + + + Form + Formulaire + + + + SpacesPage + + + Spaces list + Liste des espaces + + + + SpacesView + + + Spaces + Espaces + + SyncFileItem::Status - + Undefined - + Non défini - + Fatal Error Erreur fatale - - + + Error Erreur - + Info Info - + Success Succès - + Conflict Conflit - + File Ignored - + Fichier Ignoré - + Restored - + Restauré - + Blacklisted - + Blacklisté - + Excluded - - - - - Utility - - - %L1 GB - %L1 Go + Exclus - - %L1 MB - %L1 Mo + + Message + Message - - %L1 KB - %L1 Ko + + Filename Reserved + Nom de fichier reservé + + + UpdateDownloadedWidget - - %L1 B - %L1 octets + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Redemarrage requis</span></p></body></html> + + + Utility - + %n year(s) - %n an%n ans + %n an%n ans%n ans - + %n month(s) - %n mois%n mois + %n mois%n mois%n mois - + %n day(s) - %n jour%n jours + %n jour%n jours%n jours - + %n hour(s) - %n heure%n heures + %n heure%n heures%n heures - + %n minute(s) - %n minute%n minutes + %n minute%n minutes%n minutes - + %n second(s) - %n seconde%n secondes + %n seconde%n secondes%n secondes - + %1 %2 %1 %2 + + + Shutting down %1 + Extinction %1 + - main.cpp + account loading - - System Tray not available - Zone de notification non disponible + + Error accessing the configuration file + Erreur lors de l'accès au fichier de configuration - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 nécessite la présence d'une zone de notification système. Si vous utilisez XFCE, veuillez suivre <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">ces instructions</a>. Sinon, installez une application fournissant une zone de notification, telle que 'trayer', et essayez à nouveau. + + There was an error while accessing the configuration file at %1. + Une erreur s'est produite lors de l'accès au fichier de configuration situé dans %1. ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Bibliothèques Qt %3, %4%7Utilisation du plugin de fichiers virtuels : %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - %1 (Construit pour Qt %1) + + %1 (Built against Qt %2) + %1 (Compilé avec Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Toujours disponible localement + + + + Currently available locally + Actuellement disponible localement + + + + Some available online only + Certains sont disponibles uniquement en ligne  + + + + Available online only + Disponible uniquement en ligne + + progress @@ -4726,30 +3562,30 @@ Il s'agit d'un nouveau mode expérimental. Si vous décidez de l' Uploaded Envoyé - - - Server version downloaded, copied changed local file into conflict file - La version du serveur est téléchargée, les changements locaux ont été copiés dans un fichier conflit. - Deleted Supprimé - - Moved to %1 - Déplacé vers %1 - - - + Ignored Ignoré - - Filesystem access error - Erreur d'accès au système de fichiers + + Server version downloaded, local copy was backed up as conflict file + Version du serveur téléchargée, copie locale sauvegardée en tant que fichier de conflit + + + + Renamed %1 to %2 + + + + + Moved %1 to %2 + @@ -4762,153 +3598,86 @@ Il s'agit d'un nouveau mode expérimental. Si vous décidez de l' Métadonnées locales mises à jour - - + + Unknown Inconnu - + downloading téléchargement de - + uploading téléversement - + deleting suppression - + moving déplacement - + ignoring Ignore - - + error erreur - + updating local metadata Mise à jour des métadonnées locales - - theme - - - Status undefined - Statut indéfini - - - - Waiting to start sync - Synchronisation en attente - - - - Sync is running - Synchronisation en cours - - - - Sync Success - Synchronisation réussie - - - - Sync Success, some files were ignored. - Synchronisation terminée avec succès, certains fichiers ont été ignorés. - - - - Sync Error - Erreur de synchronisation - - - - Setup Error - Erreur de configuration - - - - Preparing to sync - Préparation à la synchronisation - - - - Aborting... - Annulation... - - - - Sync is paused - La synchronisation est en pause - - utility - + Could not open browser Impossible de démarrer le navigateur - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? Une erreur est survenue au lancement du navigateur pour visiter l'adresse %1. Il est possible qu'aucun navigateur par défaut ne soit configuré. - + Could not open email client Impossible d'ouvrir le client de messagerie - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? Il y a eu une erreur lors du lancement du client de messagerie pour créer un nouveau message. Peut-être qu'aucun client de messagerie n'est configuré ? - - Always available locally - Toujours disponible localement - - - - Some available online only - Certains sont disponibles uniquement en ligne  - - - - - Available online only - Disponible uniquement en ligne - - - - Currently available locally - Actuellement disponible localement - - - + Make always available locally Rendre toujours disponible localement - + Free up local space Libérer de l'espace local + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Certains paramètres ont été configurés dans une version ultérieure de ce client et utilisent des fonctionnalités qui ne sont pas disponibles dans cette version. + + \ No newline at end of file diff --git a/translations/client_gl.ts b/translations/client_gl.ts deleted file mode 100644 index 6716e2a3b08..00000000000 --- a/translations/client_gl.ts +++ /dev/null @@ -1,4913 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - Interromper - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - Pechar - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - Non foi posíbel crear directorios no lixo - - - - - Could not move '%1' to '%2' - Non foi posíbel mover «%1» a «%2» - - - - Moving to the trash is not implemented on this platform - Moving to the trash is not implemented on this platform - - - - Error removing '%1': %2 - Produciuse un erro ao retirar «%1»: %2 - - - - Could not remove folder '%1' - Non foi posíbel retirar o cartafol «%1» - - - - FolderWizardSourcePage - - - Form - Formulario - - - - Pick a local folder on your computer to sync - Escolla un cartafol local no seu ordenador para sincronizar - - - - &Choose... - &Escoller... - - - - FolderWizardTargetPage - - - Form - Formulario - - - - Select a remote destination folder - Seleccione o cartafol remoto de destino - - - - Create Folder - Crear un cartafol - - - - Refresh - Actualizar - - - - Folders - Cartafoles - - - - TextLabel - Etiqueta de texto - - - - NotificationWidget - - - Form - Formulario - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Etiqueta de texto - - - - OCC::AboutDialog - - - About - Sobre - - - - TextLabel - Etiqueta de texto - - - - Versions - Versións - - - - About %1 - Sobre %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Esgotouse o tempo de conexión - - - - Unknown error: network reply was deleted - Produciuse un error descoñecido: a resposta de rede foi eliminada - - - - Server replied "%1 %2" to "%3 %4" - O servidor repondeu «%1 %2» a «%3 %4» - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - Formulario - - - - Re-open Browser - Volver abrir o navegador - - - - ... - ... - - - - Storage space: ... - Espazo de almacenamento: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Os cartafoles non seleccionados van seren <b>eliminados</b> do seu sistema de ficheiros local e non volverán sincronizarse con este computador - - - - Synchronize all - Sincronizar todo - - - - Synchronize none - Non sincronizar - - - - Apply manual changes - Aplicar cambios manuais - - - - Apply - Aplicar - - - - - - - Cancel - Cancelar - - - - Connected with <server> as <user> - Conectado con <server> como <user> - - - - No account configured. - Non hai contas configuradas. - - - - Remove - Retirar - - - - Account - Conta - - - - Choose what to sync - Escolla que sincronizar - - - - Force sync now - Forzar a sincronización - - - - Restart sync - Reiniciar a sincronización - - - - Remove folder sync connection - Retirar a conexión da sincronización do cartafol - - - - Folder creation failed - Non foi posíbel crear o cartafol - - - - <p>Could not create local folder <i>%1</i>. - <p>Non foi posíbel crear o cartafol local <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Confirmar a retirada da conexión da sincronización do cartafol - - - - Remove Folder Sync Connection - Retirar a conexión da sincronización do cartafol - - - - Disable virtual file support? - Desactivar a compatibilidade con ficheiro virtual? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - Esta acción desactivará a compatibilidade de ficheiros virtuais. Como consecuencia, descargaranse os contidos dos cartafoles que agora están marcadas como «dispoñíbeis só en liña». - -A única vantaxe de desactivar a compatibilidade de ficheiros virtuais é que a función de sincronización selectiva volverá estar dispoñíbel. - -Esta acción interromperá calquera sincronización que estea agora en proceso. - - - - Disable support - Desactivar a compatibilidade - - - - Sync Running - Sincronización en proceso - - - - The syncing operation is running.<br/>Do you want to terminate it? - Estase a realizar a sincronización.<br/>Quere interrompela e rematala? - - - - %1 in use - %1 en uso - - - - %1 as <i>%2</i> - %1 como <i>%2</i> - - - - Connected to %1. - Conectado a %1. - - - - Server %1 is temporarily unavailable. - O servidor %1 non está dispoñíbel temporalmente. - - - - Server %1 is currently in maintenance mode. - O servidor %1 neste momento está en modo de mantemento. - - - - Signed out from %1. - Desconectado de %1. - - - - Connecting to %1... - Conectando con %1... - - - - No connection to %1 at %2. - Non hai conexión con %1 en %2. - - - - Log in - Acceder - - - - There are folders that were not synchronized because they are too big: - Hai cartafoles que non se sincronizaron por ser demasiado grandes: - - - - There are folders that were not synchronized because they are external storages: - Hai cartafoles que non se sincronizaron porque son almacenamentos externos: - - - - There are folders that were not synchronized because they are too big or external storages: - Hai cartafoles que non se sincronizaron porque son demasiado grandes ou almacenamentos externos: - - - - Confirm Account Removal - Confirme a retirada da conta - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>De verdade quere retirar a conexión a conta <i>%1</i>?</p><p><b>Aviso:</b> Isto <b>non</b> eliminará ningún ficheiro.</p> - - - - Remove connection - Retirar conexión - - - - Open folder - Abrir o cartafol - - - - - Log out - Desconectar - - - - Open local folder - Abrir o cartafol local - - - - Open folder in browser - Abrir o cartafol no navegador - - - - Resume sync - Continuar coa sincronización - - - - Pause sync - Por en pausa a sincronización - - - - Availability - Dispoñíbilidade - - - - Disable virtual file support... - Desactivar a compatibilidade con ficheiro virtual… - - - - Enable virtual file support%1... - Activar a compatibilidade co ficheiro virtual%1... - - - - (experimental) - (experimental) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Quere realmente deixar de sincronizar o cartafol <i>%1</i>?</p><p><b>Aviso:</b> Isto <b>non</b> eliminará ningún ficheiro.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) de %2 en uso. Algúns cartafoles, incluíndo os compartidos e os montados en rede, poderían ter diferentes límites. - - - - %1 of %2 in use - %1 de %2 en uso - - - - Currently there is no storage usage information available. - Actualmente non hai dispoñíbel ningunha información sobre o uso do almacenamento. - - - - The server version %1 is unsupported! Proceed at your own risk. - Este servidor da versión %1 non é compatíbel! Continúe baixo a súa responsabilidade. - - - - Obtaining authorization from the browser. - Obtendo autorización dende o navegador. - - - - Server configuration error: %1 at %2. - Produciuse un erro de configuración do servidor: %1 en %2. - - - - No %1 connection configured. - Non se configurou a conexión %1. - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - - - - - Signed out - Desconectado - - - - Disconnected - Desconectado - - - - Connected - Conectado - - - - Service unavailable - Servizo non dispoñíbel - - - - Maintenance mode - Modo de mantemento - - - - Network error - Produciuse un erro de rede - - - - Configuration error - Produciuse un erro de configuración - - - - Asking Credentials - Solicitando credenciais - - - - Unknown account state - Estado da conta descoñecido - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 en %3 - - - - Activity - Actividade - - - - Account - Conta - - - - Time - Hora - - - - Local path - - - - - OCC::ActivitySettings - - - - Server Activity - Actividade do servidor - - - - Sync Protocol - Protocolo de sincronización - - - - Not Synced - Non sincronizado - - - - Not Synced (%1) - %1 is the number of not synced files. - Non sincronizado (%1) - - - - OCC::ActivityWidget - - - Form - Formulario - - - - - - TextLabel - Etiqueta de texto - - - - Filter - Filtro - - - - Server Activities - Actividades do servidor - - - - Notifications - Notificacións - - - - %n notifications(s) for %1. - %n notificación para %1.%n notificacións para %1. - - - - %n notifications(s) for %1 and %2. - %n notificación para %1 e %2.%n notificacións para %1 e %2. - - - - New notifications for %1, %2 and other accounts. - Notificacións novas para %1, %2 e outras contas. - - - - Open the activity view for details. - Abrir a vista da actividade para máis detalles. - - - - Copy to clipboard - Copiar ao portapapeis - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>A conta %1 non ten as actividades activadas. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Continuar significa que <b>eliminará estes axustes</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Continuar significa que <b>ignorará estes axustes</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Algúns axustes foron configurados nas versións máis recentes deste cliente e usan características que non están dispoñíbis nesta versión.<br><br>%1<br><br>O ficheiro de configuración actual xa tiña unha copia de seguranxa en <i>%2</i>. - - - - Quit - Saír - - - - Continue - Continuar - - - - Error accessing the configuration file - Produciuse un erro ao acceder ao ficheiro de configuración - - - - There was an error while accessing the configuration file at %1. - Produciuse un erro ao acceder ao ficheiro de configuración en %1. - - - - Quit %1 - Saír de %1 - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - É necesario autenticarse - - - - Enter username and password for '%1' at %2. - Escriba o nome de usuario e o contrasinal para «%1» en %2. - - - - &User: - &Usuario: - - - - &Password: - &Contrasinal: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Non hai configurada ningunha conta ownCloud - - - - The configured server for this client is too old - O servidor configurado para este cliente é moi antigo - - - - Please update to the latest server and restart the client. - Actualice ao último servidor e reinicie o cliente. - - - - Authentication error: Either username or password are wrong. - Produciuse un erro de autenticación: Ou o nome de usuario ou o contrasinal poderían ser erróneos. - - - - timeout - caducidade - - - - The provided credentials are not correct - As credenciais fornecidas non son correctas - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - Produciuse un erro no servidor: a réplica PROPFIND non está en formato XML! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - Produciuse un erro ao abrir o directorio %1 - - - - Directory not accessible on client, permission denied - Directorio non accesíbel no cliente, permiso denegado - - - - Directory not found: %1 - Non se atopou o directorio: %1 - - - - Filename encoding is not valid - O nome de ficheiro codificado non é correcto - - - - Error while reading directory %1 - Produciuse un erro ao ler o directorio %1 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - - - - - OCC::Folder - - - Local folder %1 does not exist. - O cartafol local %1 non existe. - - - - %1 should be a folder but is not. - %1 debería ser un cartafol, mais non o é. - - - - %1 is not readable. - %1 non é lexíbel. - - - - %1 has been removed. - %1 names a file. - %1 foi retirado. - - - - %1 has been updated. - %1 names a file. - %1 foi enviado. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 foi renomeado a %2. - - - - %1 has been moved to %2. - %1 foi movido a %2. - - - - %1 and %n other file(s) have been removed. - %1 e outro ficheiro foi retirado.%1 e outros %n ficheiros foron retirados. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - %1 e outro %n ficheiro foron engadidos.%1 e outros %n ficheiro(s) foron engadidos. - - - - %1 has been added. - %1 names a file. - Foi engadido %1. - - - - %1 and %n other file(s) have been updated. - %1 e outro ficheiro foi actualizado.%1 e outros %n ficheiro(s) foron actualizados. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 foi renomeado a %2 e outro ficheiro tamén foi renomeado.%1 foi renomeado a %2 e outros %n ficheiros tamén foron renomeados. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 foi movido a %2 e outro ficheiro tamén foi movido.%1 foi movido a %2 e outros %n ficheiros tamén foron movidos. - - - - %1 has and %n other file(s) have sync conflicts. - %1 e outro ficheiro tiveron conflitos ao sincronizar.%1 e outros %n ficheiros tiveron conflitos ao sincronizar. - - - - %1 has a sync conflict. Please check the conflict file! - Produciuse un conflito ao sincronizar %1. Comprobe o ficheiro! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - Non foi posíbel sincronizar %1 e outro ficheiro por mor de erros. Vexa os detalles no rexistro.Non foi posíbel sincronizar %1 e outros %n ficheiros por mor de erros. Vexa os detalles no rexistro. - - - - %1 could not be synced due to an error. See the log for details. - Non foi posíbel sincronizar %1 por mor dun erro. Vexa os detalles no rexistro. - - - - Sync Activity - Actividade de sincronización - - - - Could not read system exclude file - Non foi posíbel ler o ficheiro de exclusión do sistema - - - - A new folder larger than %1 MB has been added: %2. - - Foi engadido un cartafol maior de %1 MB: %2. - - - - - A folder from an external storage has been added. - - Foi engadido un cartafol de almacenamento externo. - - - - - Please go in the settings to select it if you wish to download it. - Vaia a axustes a seleccionalo se quere descargar isto. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - Creouse o cartafol %1 mais foi excluído da sincronización con anterioridade. Os datos no seu interior non se sincronizarán. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - Creouse o ficheiro %1 mais foi excluído da sincronización con anterioridade. Non se sincronizará. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - Non foi posíbel rastrexar de xeito fiábel os cambios nos cartafoles sincronizados. - -Isto significa que o cliente de sincronización podería non enviar os cambios inmediatamente, e no canto diso, só examinará buscando cambios locais e enviaraos ocasionalmente (por omisión cada dúas horas) - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Todos os ficheiros no cartafol de sincronización «%1» foron eliminados no servidor. -Estas eliminacións sincronizarse co seu cartafol de sincronización local, facendo que estes ficheiros non estean dispoñíbeis a menos que teña dereitos para restauralos. -Se decide manter os ficheiros, resincronizaranse co servidor se ten dereitos para facelo. -Se decide eliminar os ficheiros, non poderá dispor deles a non ser que sexa o propietario. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Todos os ficheiros do cartafol de sincronización local «%1» foron eliminados. -Estas eliminacións sincronizaranse co servidor, facendo que estes ficheiros non estean dispoñíbeis a non ser que se restauren. -Confirma que quere sincronizar estas accións co servidor? -Se fose un accidente e decide manter os seus ficheiros, volverán ser sincronizados dende o servidor. - - - - Remove All Files? - Retirar todos os ficheiros? - - - - Remove all files - Retirar todos os ficheiros - - - - Keep files - Manter os ficheiros - - - - OCC::FolderMan - - - Could not reset folder state - Non foi posíbel restabelecer o estado do cartafol - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Atopouse un diario de sincronización antigo en «%1» máis non pode ser retirado. Asegúrese de que non o está a usar ningunha aplicación. - - - - (backup) - (copia de seguridade) - - - - (backup %1) - (copia de seguridade %1) - - - - Undefined State. - Estado sen definir. - - - - Waiting to start syncing. - Agardando para iniciar a sincronización. - - - - Preparing for sync. - Preparando para sincronizar. - - - - Sync is running. - Estase sincronizando. - - - - Sync was successful, unresolved conflicts. - A sincronización foi stifactoria, hai conflitos sen resolver. - - - - Last Sync was successful. - A última sincronización fíxose correctamente. - - - - Setup Error. - Erro de configuración. - - - - User Abort. - Interrompido polo usuario. - - - - Sync is paused. - Sincronización en pausa. - - - - %1 (Sync is paused) - %1 (sincronización en pausa) - - - - No valid folder selected! - Non seleccionou ningún cartafol correcto! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - A ruta seleccionada non existe! - - - - The selected path is not a folder! - A ruta seleccionada non é un cartafol! - - - - You have no permission to write to the selected folder! - Vostede non ten permiso para escribir neste cartafol! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Xa existe unha tarefa de sincronización entre o servidor e este cartafol. Escolla outro cartafol local! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - O cartafol local %1 xa contén un cartafol usado nunha conexión de sincronización de cartafoles. Escolla outro! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - O cartafol local %1 está dentro dun cartafol usado nunha conexión de sincronización de cartafoles. Escolla outro! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Engadir a conexión da sincronización do cartafol - - - - File - Ficheiro - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Ten que estar conectado para engadir un cartafol - - - - Click this button to add a folder to synchronize. - Prema nesta botón para engadir un cartafol para sincronizar - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Produciuse un erro ao cargar a lista de cartafoles dende o servidor. - - - - Virtual file support is enabled. - Está activada a compatibilidade con ficheiro virtual. - - - - Signed out - Desconectado - - - - Fetching folder list from server... - Obtendo a lista de cartafoles do servidor... - - - - There are unresolved conflicts. Click for details. - Hai conflitos sen resolver. Prema para obter máis detalles. - - - - Checking for changes in remote '%1' - Comprobando os cambios no cartafol remoto «%1» - - - - Checking for changes in local '%1' - Comprobando os cambios no cartafol local «%1» - - - - Reconciling changes - Reconciliando os cambios - - - - , '%1' - Build a list of file names - , «%1» - - - - '%1' - Argument is a file name - «%1» - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Sincronizando %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - descargar %1/s - - - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - enviar %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 de %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - Restan %5, %1 de %2, ficheiro %3 de %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 de %2, ficheiro %3 de %4 - - - - file %1 of %2 - ficheiro %1 de %2 - - - - Waiting... - Agardando... - - - - Waiting for %n other folder(s)... - Agardando por outro cartafol...Agardando por outros %n cartafoles... - - - - Preparing to sync... - Preparando para sincronizar... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - O observador non recibiu unha notificación de proba. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Este problema xeralmente acontece cando os reloxos inotify están esgotados. Comprobe as FAQ para obter máis detalles. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Engadir a conexión da sincronización do cartafol - - - - Add Sync Connection - Engadir a conexión da sincronización - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Prema para escoller un cartafol local para sincronizar. - - - - Enter the path to the local folder. - Escriba a ruta ao cartafol local. - - - - Select the source folder - Escolla o cartafol de orixe - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Crear un cartafol remoto - - - - Enter the name of the new folder to be created below '%1': - Introduza o nome do novo cartafol que se creará a seguir «%1»: - - - - Folder was successfully created on %1. - Creouse correctamente o cartafol en %1. - - - - Authentication failed accessing %1 - Non foi posíbel facer a autenticación accedendo a %1 - - - - Failed to create the folder on %1. Please check manually. - Non foi posíbel crear o cartafol en %1. Compróbeo manualmente. - - - - Failed to list a folder. Error: %1 - Produciuse un fallo ao listar un cartafol. Erro: %1 - - - - Choose this to sync the entire account - Escolla isto para sincronizar toda a conta - - - - This folder is already being synced. - Este cartafol xa está sincronizado. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Xa está a sincronizar <i>%1</i>, que é o cartafol pai de <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Xa está a sincronizar <i>%1</i>, que é un subcartafol de <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Usar ficheiros virtuais, no canto de descargar o contido inmediatamente%1 - - - - (experimental) - (experimental) - - - - Virtual files are not available for the selected folder - Os ficheiros virtuais non están dispoñíbeis para o cartafol seleccionado - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Aviso:</b> %1 - - - - <b>Warning:</b> - <b>Aviso:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Non se recibiu a «E-Tag» do servidor, comprobe o proxy e/ou a pasarela - - - - We received a different E-Tag for resuming. Retrying next time. - Recibiuse unha «E-Tag» diferente para continuar. Tentándoo outra vez. - - - - We received an unexpected download Content-Length. - Recibimos unha descarga inesperada polo seu tamaño. - - - - Server returned wrong content-range - O servidor devolveu un intervalo de contidos estragado - - - - OCC::GETJob - - - Connection Timeout - Esgotouse o tempo de conexión - - - - OCC::GeneralSettings - - - Form - Formulario - - - - General Settings - Axustes xerais - - - - Language - Idioma - - - - Sync hidden files - Sincronizar os ficheiros agochados - - - - Log Settings - Axustes do rexistro - - - - Advanced - Approval of folder sync (Non virtual file mode only) - Avanzado: aprobación da sincronización de cartafoles (só no modo de ficheiro non virtual) - - - - Network - Rede - - - - Updates - Actualizacións - - - - stable - estábel - - - - beta - beta - - - - About - Sobre - - - - Advanced - Avanzado - - - - Use &Monochrome Icons in the system tray - Usar iconas &monocromáticas na área de notificación - - - - Ask for confirmation before synchronizing folders larger than - Preguntar se se queren sincronizar cartafoles maiores de - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Preguntar se se queren sincronizar os cartafoles de almacenamento externo - - - - &Launch on System Startup - &Executar ao iniciar o sistema - - - - Show &Desktop Notifications - Amosar as notificacións do &escritorio - - - - &Update Channel - Act&ualizar a canle - - - - Edit &Ignored Files - Editar ficheiros &ignorados - - - - Show sync folders in &Explorer's Navigation Pane - Amosar os cartafoles sincronizados no &panel de navegación do navegador - - - - S&how crash reporter - Amosar o informe de &fallos - - - - &Restart && Update - &Reiniciar e actualizar - - - - You cannot disable autostart because system-wide autostart is enabled. - Non pode desactivar o inicio automático porque está activado o inicio automático do sistema principal. - - - - Change update channel? - Cambiar a canle de actualizacións? - - - - Warning - Aviso - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - A canle de actualización determina que actualizacións do cliente que se ofrecerán para a instalación. A canle «estábel» só contén actualizacións que se consideran fiábeis, mentres que as versións na canle «beta» poden conter características e correccións de erros novas, pero que aínda non se probaron abondo. - -Teña en conta que isto só selecciona a canle desexada e que non hai reversións: por iso, volver do canal de beta á canle estábel xeralmente non se pode facer de inmediato e significa agardar por unha versión estábel que sexa máis recente que a beta instalada actualmente. - - - - Change update channel - Cambiar a canle de actualizacións - - - - Cancel - Cancelar - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Introduza o contrasinal de %1:<br><br>Usuario: %2<br>Conta: %3<br> - - - - Reading from keychain failed with error: '%1' - A lectura do chaveiro fallou co erro: «%1» - - - - Enter Password - Escriba o contrasinal - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Prema aquí</a> para solicitar un contrasinal de aplicación dende a interface web. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor de ficheiros ignorados - - - - Files Ignored by Patterns - Ficheiros ignorados por patróns - - - - Add - Engadir - - - - Pattern - Patrón - - - - Allow Deletion - Permitir a eliminación - - - - Remove - Retirar - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Os ficheiros ou cartafoles que coincidan cun patrón non se sincronizarán.Os cambios terán efecto en vigor a próxima vez que se sincronicenn os cartafoles. - -Os elementos onde se permite a eliminación eliminaranse se impiden que se elimine un directorio. Isto é útil para os metadatos. - - - - Could not open file - Non foi posíbel abrir o ficheiro - - - - Cannot write changes to '%1'. - Non é posíbel escribir os cambios en «%1». - - - - Add Ignore Pattern - Engadir o patrón a ignorar - - - - Add a new ignore pattern: - Engadir un novo patrón a ignorar: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Esta entrada é fornecida polo sistema en «%1» e non pode ser modificado nesta vista. - - - - OCC::IssuesWidget - - - Form - Formulario - - - - List of issues - Lista de incidentes - - - - Filter - Filtro - - - - There were too many issues. Not all will be visible here. - Hai demasiados incidentes, Non todos son visíbeis aquí. - - - - There were conflicts. Check the documentation on how to resolve them. - Houbo conflitos. Vexa na documentación como resolvelos. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Houbo conflitos. <a href="%1">Vexa na documentación como resolvelos.</a> - - - - Account - Conta - - - - OCC::LibCloudProvidersPrivate - - - Settings - Axustes - - - - OCC::LogBrowser - - - Log Output - Rexistro da saída - - - - Enable logging to temporary folder - Activar o rexistro no cartafol temporal - - - - Delete logs older than %1 hours - Eliminar registros anteriores a %1 horas - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">Os rexistros conteñen información confidencial que non debería facer pública</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - O cliente pode escribir rexistros de depuración nun cartafol temporal. Estes rexistros son moi útiles para diagnosticar problemas. -Como os ficheiros de rexistro poden aumentar de tamaño, o cliente iniciará un novo para cada execución de sincronización e comprimirá os máis antigos. Recomendase tamén activar a eliminación de ficheiros de rexistro após un par de horas para evitar ocupar espazo de máis no disco. - - - - If enabled, logs will be written to: - Se está activado, os rexistros escribiranse en: - - - - C:/log - C:/log - - - - Log Http traffic - Rexistrar o tráfico HTTP - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Estes axustes persisten nos reinicios do cliente. -Teña en conta que o uso de calquera opción de rexistro por liña de ordes anulará os axustes. - - - - Open folder - Abrir o cartafol - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Hai dispoñíbel unha nova versión - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Hai dispoñíbel unha nova versión do cliente %1.</p><p>Pode descargar a versión <b>%2</b>. A versión instalada é a %3</p> - - - - - Skip this version - Omitir esta versión - - - - Skip this time - Omitir polo de agora - - - - Get update - Obter a actualización - - - - Update Failed - Produciuse un fallo na actualización - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Está dispoñible unha nova versión do cliente %1 pero fallou o proceso de actualización.</p><p>Descargouse <b>%2</b>. A versión instalada é %3.</p> - - - - Ask again later - Preguntar máis tarde - - - - Restart and update - Reiniciar e actualizar - - - - Update manually - Actualizar manualmente - - - - OCC::NetworkSettings - - - Form - Formulario - - - - Proxy Settings - Configuración do proxy - - - - No Proxy - Sen proxy - - - - Use system proxy - Usar o proxy do sistema - - - - Specify proxy manually as - Especifique o proxy manualmente como - - - - Host - Servidor - - - - : - : - - - - Proxy server requires authentication - O servidor proxy precisa autenticación - - - - Note: proxy settings have no effects for accounts on localhost - Nota: a configuración do proxy non ten efectos sobre as contas en localhost - - - - Download Bandwidth - Largo de banda de descarga - - - - - Limit to - Limitar a - - - - - KBytes/s - KBytes/s - - - - - No limit - Sen límite - - - - - Limit to 3/4 of estimated bandwidth - Limitar a 3/4 do largo de banda estimado - - - - Upload Bandwidth - Largo de banda de envíos - - - - - Limit automatically - Limitar automáticamente - - - - Hostname of proxy server - Nome de máquina para o servidor proxy - - - - Username for proxy server - Nome de usuario para o servidor proxy - - - - Password for proxy server - Contrasinal para o servidor proxy - - - - HTTP(S) proxy - Proxy HTTP(S) - - - - SOCKS5 proxy - Proxy SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - Creado en %1 - - - - Closing in a few seconds... - Pechando en poucos segundos... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 solicitude fallada en %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - «%1» seleccionado en %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Erro devolto dende o servidor: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Produciuse un erro ao acceder ao punto final do «testemuño»: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Non foi posíbel analizar o JSON devolto dende o servidor: <br><em>%1</em> - - - - Unsupported token type: %1 - Tipo de testemuño non admitido: %1 - - - - The reply from the server did not contain all expected fields -:%1 - A resposta do servidor non contiña todos os campos agardados -:%1 - - - - Unknown Error - Erro descoñecido - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>Produciuse un erro de acceso</h1><p>Produciuse un fallo ao recuperar a información do usuario</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Produciuse un erro de acceso</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Produciuse un erro ao eliminar o testemuño: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - A resposta do servidor non contiña todos os campos agardados -:%1 -Datos recibidos: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Usuario errado</h1><p>Accedeu co usuario <em>%1</em>, mais debe acceder co usuario <em>%2</em>.<br>Peche a sesión de %3 noutra lapela, entón <a href='%4'>prema aquí</a> e acceda como o usuario %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Preparada a nova actualización %1 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Está a piques de instalarse unha nova actualización para %1. O proceso de -actualización pode pedir privilexios adicionais durante o procedemento. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Non foi posíbel descargar a actualización. Prema <a href='%1'>aquí</a> para descargar manualmente a actualización. - - - - Could not check for new updates. - Non foi posíbel comprobar se hai novas actualizacións. - - - - Downloading %1. Please wait... - Descargando %1. Agarde... - - - - %1 available. Restart application to start the update. - %1 está dispoñíbel. Reinicie a aplicación para iniciar a actualización. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Un novo %1 está dispoñibel. Empregue a ferramenta de actualización do seu sistema para instalalo. - - - - Checking update server... - Comprobando o servidor de actualizacións... - - - - Update status is unknown: Did not check for new updates. - O estado da actualización é descoñecido: non se comprobaron as actualizacións. - - - - No updates available. Your installation is at the latest version. - Non hai actualizacións dispoñíbeis. A instalación xa está na versión máis recente. - - - - Update Check - Comprobar actualizacións - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Conectar con %1 - - - - Setup local folder options - Estabelecer as opcións do cartafol local - - - - Connect... - Conectar... - - - - (experimental) - (experimental) - - - - Use &virtual files instead of downloading content immediately%1 - Usar ficheiros &virtuais, no canto de descargar o contido inmediatamente%1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Sincronizar o cartafol «%1» - - - - Virtual files are not available for the selected folder - Os ficheiros virtuais non están dispoñíbeis para o cartafol seleccionado - - - - Error - Erro - - - - Local Sync Folder - Sincronización do cartafol local - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Correo-e - - - - Connect to %1 - Conectar con %1 - - - - Enter user credentials - Escriba as credenciais do usuario - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Conectar con %1 - - - - Login in your browser - Acceda no seu navegador - - - - Copy link to clipboard - Copiar a ligazón no portapapeis - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Conectar con %1 - - - - Setup %1 server - Estabelecer o servidor %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Este sitio de Internet non é seguro, xa que non está cifrado. -Recomendámoslle que non o use. - - - - This url is secure. You can use it. - Este URL é seguro. Pode utilizalo. - - - - &Next > - &Seguinte > - - - - Invalid URL - URL incorrecto - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Non foi posíbel conectar con %1 en %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Esgotouse o tempo tentando conectarse a %1 en %2. - - - - Invalid URL - URL incorrecto - - - - Could not create local folder %1 - Non foi posíbel crear o cartafol local %1 - - - - Error: %1 - Erro: %1 - - - - The folder creation resulted in HTTP error code %1 - A creación do cartafol resultou nun código de erro HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - A creación do cartafol remoto fracasou por mor de seren incorrectas as credenciais!<br/>Volva atrás e comprobe as súas credenciais.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Produciuse un fallo ao crear o cartafol remoto %1 e dou o erro <tt>%2</tt>. - - - - Folder rename failed - Non foi posíbel renomear o cartafol - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Non é posíbel retirar e facer unha copia de seguridade do cartafol, xa que o cartafol ou un ficheiro está aberto noutro programa Peche o cartafol ou o ficheiro e ténteo de novo, ou cancele a acción. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Asistente de conexión %1 - - - - Enable experimental feature? - Activar a característica experimental? - - - - Enable experimental placeholder mode - Activa o modo de marcador de posición experimental - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Cando estea activado o modo «ficheiros virtuais», non se descargará ningún ficheiro inicialmente. No canto diso, crearase un pequeno ficheiro «%1» para cada ficheiro existente no servidor. Os contidos poden descargarse executando estes ficheiros ou usando o seu menú contextual. - -O modo de ficheiros virtuais é mutuamente exclusivo coa sincronización selectiva. Os cartafoles non seleccionados actualmente serán traducidos aos cartafoles só en liña e axustarase a configuración de sincronización selectiva. - -Cambiar a este modo interromperá calquera sincronización en proceso. - -Este é un modo novo e experimental. Se decide usalo, informe de calquera problema que apareza. - - - - Stay safe - Permanecer seguro - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - As ligazóns simbolicas non son admitidas nas sincronizacións - - - - File is listed on the ignore list. - O ficheiro está na lista de ignorados. - - - - File names ending with a period are not supported on this file system. - Os nomes de ficheiros que rematan cun punto non son compatíbeis con este sistema de ficheiros. - - - - File names containing the character '%1' are not supported on this file system. - Os nomes de ficheiros que conteñen o carácter «%1» non son compatíbeis con este sistema de ficheiros. - - - - File name contains at least one invalid character - O nome de ficheiro contén algún carácter incorrecto - - - - The file name is a reserved name on this file system. - O nome do ficheiro é un nome reservado neste sistema de ficheiros. - - - - Filename contains trailing spaces. - O nome do ficheiro contén espazos finais. - - - - Filename is too long. - O nome de ficheiro é longo de máis. - - - - File/Folder is ignored because it's hidden. - O ficheiro/cartafol ignórase por estar agochado. - - - - Stat failed. - Produciuse un fallo na obtención de estatísticas. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Conflito: Versión do servidor descargada, a copia local cambiou o nome e non se cargou. - - - - The filename cannot be encoded on your file system. - O nome do ficheiro non pode ser codificado no seu sistema de ficheiros. - - - - The filename is blacklisted on the server. - O nome do ficheiro está na lista de bloqueo do servidor. - - - - size - tamaño - - - - permissions - permisos - - - - etag - etag - - - - file id - ID de ficheiro - - - - server reported no %1 - o servidor non informou de %1 - - - - Moved to invalid target, restoring - Movido a un destino incorrecto, restaurando - - - - Not allowed to remove, restoring - Non está permitido retiralo, restaurando - - - - Ignored because of the "choose what to sync" blacklist - Ignorado por mor da lista de bloqueo de «escolla que sincronizar» - - - - Not allowed because you don't have permission to add subfolders to that folder - Non se lle permite porque vostede non ten permiso para engadir subcartafoles neste cartafol - - - - Not allowed because you don't have permission to add files in that folder - Non se lle permite porque vostede non ten permiso para engadir ficheiros neste cartafol - - - - Not allowed to upload this file because it is read-only on the server, restoring - Non está permitido o envío xa que o ficheiro é só de lectura no servidor, restaurando - - - - Error while reading the database - Produciuse un erro ao ler a base de datos - - - - Server replied with an error while reading directory '%1' : %2 - O servidor respondeu cun erro ao ler o directorio «%1» : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - A descarga reducirá o espazo libre local por baixo do límite - - - - Free space on disk is less than %1 - O espazo libre no disco é inferior a %1 - - - - File was deleted from server - O ficheiro vai ser eliminado do servidor - - - - The file could not be downloaded completely. - Non foi posíbel descargar completamente o ficheiro. - - - - The downloaded file is empty despite the server announced it should have been %1. - O ficheiro descargado está baleiro, aínda que o servidor dí que o seu tamaño debe ser de %1. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - O ficheiro cambiou após ser atopado - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Fallou a restauración: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - non foi posíbel eliminar o ficheiro %1, erro: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - Non foi posíbel crear o cartafol %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Non é posíbel renomear o ficheiro %1 como %2 por mor dunha colisión co nome dun ficheiro local - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - Produciuse un erro ao axustar o estado do PIN - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - O servidor devolveu código HTTP incorrecto. Agardábase 204, mais recibiuse «%1 %2». - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - O servidor devolveu código HTTP incorrecto. Agardábase 201, mais recibiuse «%1 %2». - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - Non foi posíbel renomear %1 a %2, erro: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - O servidor devolveu código HTTP incorrecto. Agardábase 201, mais recibiuse «%1 %2». - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - Produciuse un erro ao escribir os metadatos na base de datos - - - - Error setting pin state - Produciuse un erro ao axustar o estado do PIN - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - Non foi posíbel actualizar o ficheiro: %1 - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Non foi posíbel enviar o ficheiro %1 xa que existe outro co mesmo nome. Difire só nas maiusculas/minúsculas - - - - File Removed - Ficheiro retirado - - - - Local file changed during syncing. It will be resumed. - O ficheiro local cambiou durante a sincronización. Retomase. - - - - Local file changed during sync. - O ficheiro local cambiou durante a sincronización. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - O envío de %1 excede o límite de tamaño do cartafol - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - O ficheiro local retirarase durante a sincronización. - - - - Local file changed during sync. - O ficheiro local cambiou durante a sincronización. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - O servidor devolveu un código non agardado (%1) - - - - Missing File ID from server - Falta o ID do ficheiro do servidor - - - - Missing ETag from server - Falta ETag do servidor - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - O ficheiro local retirarase durante a sincronización. - - - - Local file changed during sync. - O ficheiro local cambiou durante a sincronización. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - O ficheiro local retirarase durante a sincronización. - - - - Local file changed during sync. - O ficheiro local cambiou durante a sincronización. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - O servidor non recoñeceu o último bloque. (Non había unha e-tag presente) - - - - OCC::ProtocolItemModel - - - Time - Hora - - - - File - Ficheiro - - - - Folder - Cartafol - - - - Issues - Incidencias - - - - Action - Acción - - - - Size - Tamaño - - - - Account - Conta - - - - Status - Estado - - - - OCC::ProtocolWidget - - - Form - Formulario - - - - Local sync protocol - Protocolo de sincronización local - - - - Filter - Filtro - - - - Account - Conta - - - - Copy to clipboard - Copiar ao portapapeis - - - - Show in file browser - - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - O proxy require autenticación - - - - Username: - Nome de usuario: - - - - Proxy: - Proxy: - - - - The proxy server needs a username and password. - O servidor proxy precisa dun nome de usuario de dun contrasinal. - - - - Password: - Contrasinal: - - - - TextLabel - Etiqueta de texto - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Escolla que sincronizar - - - - OCC::SelectiveSyncWidget - - - Loading ... - Cargando ... - - - - Deselect remote folders you do not wish to synchronize. - Desmarque os cartafoles remotos que non quere sincronizar. - - - - Name - Nome - - - - Size - Tamaño - - - - - No subfolders currently on the server. - Actualmente non hai subcartafoles no servidor. - - - - An error occurred while loading the list of sub folders. - Produciuse un erro ao cargar a lista de subcartafoles. - - - - OCC::ServerNotificationHandler - - - Dismiss - Rexeitar - - - - OCC::SettingsDialog - - - Settings - Axustes - - - - Activity - Actividade - - - - Add account - Engadir unha conta - - - - - Quit %1 - Saír de %1 - - - - Are you sure you want to quit %1? - Confirma que quere saír de %1? - - - - Account - Conta - - - - MainWindow - XanelaPrincipal - - - - Hide - Agochar - - - - OCC::ShareDialog - - - TextLabel - Etiqueta de texto - - - - share label - compartir etiqueta - - - - Dialog - Dialogo - - - - ownCloud Path: - Ruta ao ownCloud: - - - - %1 Sharing - Compartindo %1 - - - - %1 - %1 - - - - Folder: %2 - Cartafol: %2 - - - - The server does not allow sharing - O servidor non permite compartir - - - - Retrieving maximum possible sharing permissions from server... - Recuperando o máximo número posíbel de permisos de uso compartido do servidor... - - - - The file can not be shared because it was shared without sharing permission. - Non é posíbel compartir o ficheiro, xa que foi compartido sen permisis para compartir. - - - - Users and Groups - Usuarios e grupos - - - - Public Links - Ligazóns públicas - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Compartir NovoDocumento.odt - - - - Upload only (File Drop) - Só para enviar (soltar ficheiro) - - - - Receive files from others without revealing the contents of the folder. - Recibir ficheiros doutros sen revelar os contidos do cartafol. - - - - Download / View / Edit - Descargar / Ver / Editar - - - - Name: - Nome: - - - - Enter name of new share... - Introduza o nome do novo recurso compartido… - - - - &Create - &Crear - - - - TextLabel - Etiqueta de texto - - - - Set &password - Estabelecer o &contrasinal - - - - Set &expiration date - Estabelecer a &data de caducidade - - - - Set password - Estabelecer o contrasinal - - - - Recipients can view or download contents. - Os destinatarios poden ver ou descargar contidos. - - - - Download / View - Descargar / Ver - - - - Recipients can view, download, edit, delete and upload contents. - Os destinatarios poden ver, descargar, editar, eliminar e enviar contido. - - - - Anyone with the link has access to the file/folder - Calquera que teña a ligazón terá acceso ao ficheiro/cartafol - - - - - P&assword protect - Contr&asinal de protección - - - - The file can not be shared because it was shared without sharing permission. - Non é posíbel compartir o ficheiro, xa que foi compartido sen permisis para compartir. - - - - Link shares have been disabled - Desactivouse a compartición de ligazóns - - - - Create public link share - Crear unha ligazón pública compartida - - - - - Delete - Eliminar - - - - Open link in browser - Abrir a ligazón no navegador - - - - Copy link to clipboard - Copiar a ligazón no portapapeis - - - - Copy link to clipboard (direct download) - Copiar a ligazón no portapapeis (descarga directa) - - - - Send link by email - Enviar a ligazón por correo - - - - Send link by email (direct download) - Enviar a ligazón por correo (descarga directa) - - - - Create new... - Crear novo… - - - - I shared %1 with you - Compartin %1 con vostede - - - - Confirm Link Share Deletion - Confirmar a eliminación da ligazón compartida - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Confirma que quere eliminar a ligazón pública compartida <i>%1</i>?</p><p>Nota: Esta acción non se pode desfacer.</p> - - - - Cancel - Cancelar - - - - - Public link - Ligazón pública - - - - Delete link share - Eliminar a ligazón compartida - - - - - Please Set Password - Estabeleza o contrasinal - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Compartir NovoDocumento.odt - - - - Share with users or groups ... - Compartir con usuarios ou grupos ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Pode dirixir a xente a este ficheiro ou cartafol compartido <a href="private link menu"><span style=" text-decoration: underline">dándolles unha ligazón privada</span></a>.</p></body></html> - - - - The item is not shared with any users or groups - O elemento non foi compartido con ningún grupo ou persoa - - - - Open link in browser - Abrir a ligazón no navegador - - - - Copy link to clipboard - Copiar a ligazón no portapapeis - - - - Send link by email - Enviar a ligazón por correo - - - - No results for '%1' - Non hai resultados para «%1» - - - - I shared something with you - Compartín algo con vostede - - - - OCC::ShareUserLine - - - Form - Formulario - - - - - TextLabel - Etiqueta de texto - - - - can edit - pode editar - - - - can share - pode compartir - - - - ... - ... - - - - create - crear - - - - change - cambio - - - - delete - eliminar - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Compartir con %1 - - - - Context menu share - Compartir o menú contextual - - - - Context menu share %1 - Menú contextual compartir %1 - - - - Confirm deletion - Confirmar a eliminación - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Confirma que quere eliminar o directorio <i>%1</i> e todo o seu contido de xeito permanente? - - - - Do you want to delete the file <i>%1</i> permanently? - Confirma que quere eliminar o ficheiro <i>%1</i> de xeito permanente? - - - - Select new location... - Seleccionar nova localización… - - - - Error - Erro - - - - Moving file failed: - -%1 - Produciuse un fallo ao mover o ficheiro: - -%1 - - - - I shared something with you - Compartín algo con vostede - - - - - Share... - Compartir... - - - - - Copy private link to clipboard - Copiar a ligazón privada no portapapeis - - - - Send private link by email... - Enviar a ligazón privada por correo... - - - - Resharing this file is not allowed - Non está permitido volver compartir este ficheiro - - - - Resharing this folder is not allowed - Non está permitido volver compartir este cartafol - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - Copiar a ligazón pública no portapapeis - - - - Open in browser - Abrir no navegador - - - - Show file versions in browser - Amosar as versións do ficheiro no navegador - - - - Rename... - Renomear… - - - - Rename and upload... - Renomear e enviar… - - - - Move and rename... - Mover e renomear… - - - - Move, rename and upload... - Mover, renomear e enviar… - - - - Delete local changes - Eliminar os cambios locais - - - - Move and upload... - Mover e enviar… - - - - Delete - Eliminar - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Detalles do certificado</h3> - - - - Common Name (CN): - Nome común (CN): - - - - Subject Alternative Names: - Nomes alternativos do titular: - - - - Organization (O): - Organización (O): - - - - Organizational Unit (OU): - Unidade organizativa (OU): - - - - State/Province: - Estado/provincia: - - - - Country: - País: - - - - Serial: - Serie: - - - - <h3>Issuer</h3> - <h3>Emisor</h3> - - - - Issuer: - Emisor: - - - - Issued on: - Emitido o: - - - - Expires on: - Caduca o: - - - - <h3>Fingerprints</h3> - <h3>Pegadas dixitais</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Nota:</b> Este certificado foi aprobado manualmente</p> - - - - %1 (self-signed) - %1 (autoasinada) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Esta conexión está cifrada empregando %1 a %2 bits. - - - - - Server version: %1 - Versión do servidor: %1 - - - - No support for SSL session tickets/identifiers - Non hai compatiblidade cos billetes/identificadores da sesión SSL - - - - Certificate information: - Información do certificado: - - - - The connection is not secure - A conexión non é segura - - - - This connection is NOT secure as it is not encrypted. - - Esta conexión non é segura e non está cifrada. - - - - - OCC::SslErrorDialog - - - Form - Formulario - - - - Trust this certificate anyway - Confiar igualmente neste certificado - - - - Untrusted Certificate - Certificado non fiábel - - - - Cannot connect securely to <i>%1</i>: - Non se pode conectar de xeito seguro con <i>%1</i>: - - - - with Certificate %1 - co certificado %1 - - - - - - &lt;not specified&gt; - &lt;sen especificar&gt; - - - - - Organization: %1 - Organización: %1 - - - - - Unit: %1 - Unidade: %1 - - - - - Country: %1 - País: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Pegada dixital (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Pegada dixital (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Data de aplicación: %1 - - - - Expiration Date: %1 - Data de caducidade: %1 - - - - Issuer: %1 - Emisor: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (omitido por mor do erro anterior, tentándoo de novo en %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Só %1 está dispoñíbel, necesita polo menos %2 para comezar - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Non foi posíbel abrir ou crear a base de datos de sincronización local. Asegúrese de ter acceso de escritura no cartafol de sincronización. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Pouco espazo dispoñíbel no disco: As descargas que reduzan o tamaño por baixo de %1 van ser omitidas. - - - - There is insufficient space available on the server for some uploads. - Non hai espazo libre abondo no servisor para algúns envíos. - - - - Unresolved conflict. - Conflito sen resolver. - - - - Using virtual files with suffix, but suffix is not set - Empregando ficheiros virtuais con sufixo, mais o sufixo non está estabelecido - - - - Unable to read the blacklist from the local database - Non foi posíbel ler a lista de bloqueo da base de datos local - - - - Unable to read from the sync journal. - Non foi posíbel ler dende o diario de sincronización. - - - - Cannot open the sync journal - Non foi posíbel abrir o rexistro de sincronización - - - - Aborted - Interrompido - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>Version %1. Para obter máis información visite<a href="%2">https://%3</a></p><p>Para incidentes coñecidos e axuda, visite: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, e outros.</small></p><p>Copyright ownCloud GmbH</p><p>Distribuído por %4 e licenciado baixo a Licenza Pública Xeral (GPL) de GNU Versión 2.0.<br/>%5 e o logotipo de %5 son marcas rexistradas de %4 nos Estados Unidos, noutros países ou en ambos.</p><p><small>%6</small></p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - A cabeceira da suma de comprobación é incorrecta. - - - - The checksum header contained an unknown checksum type '%1' - A cabeceira da suma de comprobación contén un tipo de suma de comprobación descoñecido «%1» - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - O ficheiro descargado non coincide coa suma de comprobación. Retomase. «%1» != «%2» - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - A característica do sistema de ficheiros virtual require un sistema de ficheiros NTFS, %1 está a usar %2 - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Ten que rexistrarse - - - - Folder %1: %2 - Cartafol %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - Non existen cartafoles de sincronización configurados. - - - - Open in browser - Abrir no navegador - - - - - - Log in... - Acceder... - - - - - - Log out - Desconectar - - - - Recent Changes - Cambios recentes - - - - Managed Folders: - Cartafoles xestionados: - - - - Open folder '%1' - Abrir o cartafol «%1» - - - - Open %1 in browser - Abrir %1 nun navegador - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - O servidor na conta %1 executa unha versión %2 que xa non é compatíbel. O uso deste cliente con versións de servidor non admitidas non está probado e é potencialmente perigoso. Proceda baixo a súa propia responsabilidade. - - - - Unknown status - Estado descoñecido - - - - Settings... - Axustes... - - - - Details... - Detalles... - - - - Help - Axuda - - - - Quit %1 - Saír de %1 - - - - Disconnected from %1 - Desconectado de %1 - - - - Unsupported Server Version - Versión del servidor non compatíbel - - - - Disconnected - Desconectado - - - - Disconnected from some accounts - Desconectado dalgunhas contas - - - - Disconnected from accounts: - Desconectado das contas: - - - - Account %1: %2 - Conta %1: %2 - - - - Signed out - Desconectado - - - - Account synchronization is disabled - A sincronización está desactivada - - - - Synchronization is paused - A sincronización está en pausa - - - - Up to date (%1) - - - - - No sync folders configured - Non hai cartafoles de sincronización configurados - - - - Unpause all folders - Continuar todos os cartafoles - - - - Pause all folders - Pausar todos os cartafoles - - - - Unpause all synchronization - Continuar con todas as sincronizacións - - - - Unpause synchronization - Continuar coa sincronización - - - - Pause all synchronization - Pausar todas as sincronizacións - - - - Pause synchronization - Pausar a sincronización - - - - Log out of all accounts - Saír de todas as contas - - - - Log in to all accounts... - Acceder a todas as contas... - - - - New account... - Nova conta... - - - - About %1 - Sobre %1 - - - - No items synced recently - Non hai elementos sincronizados recentemente - - - - Checking for changes in remote '%1' - Comprobando os cambios no cartafol remoto «%1» - - - - Checking for changes in local '%1' - Comprobando os cambios no cartafol local «%1» - - - - Syncing %1 of %2 (%3 left) - Sincronizando %1 of %2 (restan %3) - - - - Syncing %1 of %2 - Sincronizando %1 de %2 - - - - Syncing %1 (%2 left) - Sincronizando %1 (restan %2) - - - - Syncing %1 - Sincronizando %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - Formulario - - - - - TextLabel - Etiqueta de texto - - - - Server - Servidor - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Se marca esta caixa, o contido existente no cartafol local borrarase para iniciar unha sincronización limpa do servidor.</p><p>Non marque isto se o contido local debe ser enviado ao cartafol do servidore.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - Preguntar se se queren sincroni&zar cartafoles maiores de - - - - S&ynchronize everything from server - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Preguntar se se queren sincronizar os cartafoles de almacenamento e&xterno - - - - Choose what to sync - Escolla que sincronizar - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Cando se selecciona esta opción, o asistente pecharase sen sincronizar nada. Podes usar o botón &quot;Engadir a conexión da sincronización do cartafol&quot; dende os axustes da conta para escoller o par de cartafoles local e remoto que quere sincronizar</p></body></html> - - - - Manually create folder sync connections - Crear manualmente as conexións da sincronización do cartafol - - - - &Local Folder - Cartafol &local - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Conservar os datos locais - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - Formulario - - - - &Username - Nome do &usuario - - - - &Password - &Contrasinal - - - - OwncloudOAuthCredsPage - - - Form - Formulario - - - - Please switch to your browser to proceed. - Cambie para o seu navegador para proseguir. - - - - An error occurred while connecting. Please try again. - Produciuse un erro ao conectarse. Ténteo de novo. - - - - Re-open Browser - Volver abrir o navegador - - - - OwncloudSetupPage - - - Form - Formulario - - - - Ser&ver Address - Enderezo do ser&vidor - - - - https://... - https://... - - - - Error Label - Etiqueta de erro - - - - QObject - - - in the future - no futuro - - - - %n day(s) ago - vai %n díavai %n días - - - - %n hour(s) ago - vai %n horavai %n horas - - - - now - agora - - - - Less than a minute ago - Vai menos dun minuto - - - - %n minute(s) ago - vai %n minutovai %n minutos - - - - Some time ago - Vai algún tempo - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - Produciuse un erro fatal - - - - - Error - Erro - - - - Info - Información - - - - Success - Correcto - - - - Conflict - Conflito - - - - File Ignored - - - - - Restored - - - - - Blacklisted - Na lista negra - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - vai %n anovai %n anos - - - - %n month(s) - vai %n mesvai %n meses - - - - %n day(s) - vai %n díavai %n días - - - - %n hour(s) - vai %n horavai %n horas - - - - %n minute(s) - vai %n minutovai %n minutos - - - - %n second(s) - vai %n segundovai %n segundos - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Área de notificación non dispoñíbel - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 require dunha área de notificación. Se está executando XFCE, siga <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">estas instrucións</a>. Senón, instale unha aplicación de área de notificación como «trayer» e ténteo de novo. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - %1 (Construído sobre Qt %1) - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - Creouse o ficheiro virtual - - - - Replaced by virtual file - Substituír por un ficheiro virtual - - - - Downloaded - Descargado - - - - Uploaded - Enviado - - - - Server version downloaded, copied changed local file into conflict file - Versión do servidor descargada, copiouse o cambio local do ficheiro dentro do ficheiro en conflito - - - - Deleted - Eliminado - - - - Moved to %1 - Movido a %1 - - - - Ignored - Ignorado - - - - Filesystem access error - Produciuse un erro de acceso ao sistema de ficheiros - - - - Error - Erro - - - - Updated local metadata - Actualizados os metadatos locais - - - - - Unknown - Descoñecido - - - - downloading - descargando - - - - uploading - enviando - - - - deleting - eliminando - - - - moving - movendo - - - - ignoring - ignorando - - - - - error - erro - - - - updating local metadata - actualizando os metadatos locais - - - - theme - - - Status undefined - Estado sen definir - - - - Waiting to start sync - Agardando o comezo da sincronización - - - - Sync is running - Sincronización en proceso - - - - Sync Success - Sincronización realizada - - - - Sync Success, some files were ignored. - A sincronización foi correcta, algúns ficheiros foron ignorados. - - - - Sync Error - Produciuse un erro de sincronización - - - - Setup Error - Erro de configuración - - - - Preparing to sync - Preparando para sincronizar - - - - Aborting... - Cancelando... - - - - Sync is paused - Sincronización en pausa - - - - utility - - - Could not open browser - Non foi posíbel abrir o navegador - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - Produciuse un erro ao iniciar o navegador para ir ao URL %1. Quizais non estea configurado ningún navegador predeterminado? - - - - Could not open email client - Non foi posíbel abrir o cliente de correo - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Produciuse un erro ao iniciar o cliente de correo electrónico para crear unha nova mensaxe. Quizais non estea configurado ningún cliente de correo electrónico? - - - - Always available locally - Sempre dispoñíbel localmente - - - - Some available online only - Algúns dispoñíbeis só en liña - - - - - Available online only - Só dispoñíbel en liña - - - - Currently available locally - Actualmente dispoñíbel localmente - - - - Make always available locally - Facelo sempre dispoñíbel localmente - - - - Free up local space - Liberar espazo local - - - \ No newline at end of file diff --git a/translations/client_he.ts b/translations/client_he.ts deleted file mode 100644 index 49560b6b6e7..00000000000 --- a/translations/client_he.ts +++ /dev/null @@ -1,4912 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - מדווח הקריסות טומהוק - - - - <html><head/><body><p><br/></p></body></html> - <html><head/><body><p><br/></p></body></html> - - - - Abort - ביטול פעולה - - - - Send this report - לשלוח את הדוח הזה - - - - Don't send - לא לשלוח - - - - You can disable sending crash reports in the configuration dialog. - אפשר להשבית שליחת דוחות קריסה בחלונית ההגדרות. - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - סגירה - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - לא ניתן ליצור תיקיות בפח האשפה - - - - - Could not move '%1' to '%2' - לא ניתן להעביר '%1' ל- '%2' - - - - Moving to the trash is not implemented on this platform - העברה לפח האשמפה אינה נתמכת בפלטפורמה זו - - - - Error removing '%1': %2 - שגיאה בהסרת '%1': %2 - - - - Could not remove folder '%1' - לא ניתן להסיר תיקייה '%1' - - - - FolderWizardSourcePage - - - Form - טופס - - - - Pick a local folder on your computer to sync - יש לבחור תיקייה מקומית על המחשב לסנכרון - - - - &Choose... - &בחירה… - - - - FolderWizardTargetPage - - - Form - טופס - - - - Select a remote destination folder - יש לבחור תיקיית יעד - - - - Create Folder - יצירת תיקייה - - - - Refresh - רענון - - - - Folders - תיקייה - - - - TextLabel - טקסט תווית - - - - NotificationWidget - - - Form - טופס - - - - Lorem ipsum dolor sit amet - לורם איפסום עכשיו היה זה כאן - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - לורם איפסום עכשיו היה זה כאן, הלך לשם בכל יום כמו גם בסוציולוגיה, גם אני שומע מתוך היעד לכדי מצווה - - - - TextLabel - טקסט תווית - - - - OCC::AboutDialog - - - About - על אודות - - - - TextLabel - טקסט תווית - - - - Versions - גרסאות - - - - About %1 - על אודות %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - פסק זמן חיבור - - - - Unknown error: network reply was deleted - שגיאה לא ידועה: תשובת רשת נמחקה - - - - Server replied "%1 %2" to "%3 %4" - השרת ענה "%1 %2" ל- "%3 %4" - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - טופס - - - - Re-open Browser - פתיחה מחדש של הדפדפן - - - - ... - ... - - - - Storage space: ... - שטח אחסון: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - קבצים שלא מסומנים <b>יוסרו</b> ממערכת הקבצים המקומיים ולא עוד יסונכרנו למחשב זה - - - - Synchronize all - סנכרון מלא - - - - Synchronize none - לא לסנכרן - - - - Apply manual changes - הפעלת שינויים ידנית - - - - Apply - הפעלה - - - - - - - Cancel - ביטול - - - - Connected with <server> as <user> - חיבור של ה- <server> כ- <user> - - - - No account configured. - לא הוגדר חשבון. - - - - Remove - הסרה - - - - Account - חשבון - - - - Choose what to sync - יש לבחור מה לסנכרן - - - - Force sync now - חייב סנכרון מיידי - - - - Restart sync - חידוש סנכרון - - - - Remove folder sync connection - הסרת חיבור של סנכרון תיקייה - - - - Folder creation failed - יצירת תיקייה נכשלה - - - - <p>Could not create local folder <i>%1</i>. - <p>לא ניתן היה ליצור תיקייה מקומית <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - אישור הסרת חיבור סנכרון תיקייה - - - - Remove Folder Sync Connection - הסרת חיבור סנכרון תיקייה - - - - Disable virtual file support? - להשבית תמיכה בקובץ וירטואלי? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - פעולה זו תשבית תמיכה בקובץ וירטואלי. כתוצאה מכך תכנים של תיקיות המסומנות כ- 'זמינות במצב מקוון בלבד' יורדו. - -היתרון היחיד של השבתת תמיכה בקובץ וירטואלי הוא שתכונת הסנכרון הסלקטיבי תהיה זמינה שוב. - -פעולה זו תבטל כל פעילות סנכרון המתבצעת ברגע זה. - - - - Disable support - השבתת תמיכה - - - - Sync Running - סנכרון בפעולה - - - - The syncing operation is running.<br/>Do you want to terminate it? - פעולת הסנכרון בריצה.<br/>האם להפסיק אותה? - - - - %1 in use - %1 בשימוש - - - - %1 as <i>%2</i> - %1 בתור <i>%2</i> - - - - Connected to %1. - מחובר אל %1. - - - - Server %1 is temporarily unavailable. - שרת %1 אינו זמין באופן זמני.. - - - - Server %1 is currently in maintenance mode. - שרת %1 כרגע במצב תחזוקה. - - - - Signed out from %1. - יציאה מ- %1. - - - - Connecting to %1... - מתחבר ל- %1... - - - - No connection to %1 at %2. - אין חיבור ל- %1 ב- %2. - - - - Log in - כניסה - - - - There are folders that were not synchronized because they are too big: - קיימות תיקיות שלא סונכרנו כיוון שהן גדולות מדי: - - - - There are folders that were not synchronized because they are external storages: - קיימות תיקיות שלא סונכרנו כיוון שהן על אחסון חיצוני: - - - - There are folders that were not synchronized because they are too big or external storages: - קיימות תיקיות שלא סונכרנו כיוון שהן גדולות מדי או שהן על אחסון חיצוני: - - - - Confirm Account Removal - יש לאשר הסרת חשבון - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>האם באמת להסיר את החיבור לחשבון <i>%1</i>?</p><p><b>הערה:</b> הפעולה <b>לא</b> תמחק אף קובץ.</p> - - - - Remove connection - הסרת חיבור - - - - Open folder - פתיחת תיקייה - - - - - Log out - התנתקות - - - - Open local folder - פתיחת תיקייה מקומית - - - - Open folder in browser - פתיחת תיקייה בדפדפן - - - - Resume sync - חידוש סנכרון - - - - Pause sync - השהיית סנכרון - - - - Availability - זמינות - - - - Disable virtual file support... - השבתת תמיכה בקובץ וירטואלי... - - - - Enable virtual file support%1... - הפעלת תמיכה בקובץ וירטואלי %1... - - - - (experimental) - (ניסיוני) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>האם באמת להפסיק את סנכרון התיקייה <i>%1</i>?</p><p><b>הערה:</b> פעולה זו <b>לא</b> תמחק אף קובץ.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) מתוך %2 בשימוש. מספר תיקיות, כולל נקודות עגינה ברשת או תיקיות משותפות, ייתכן ולהן יש הגבלות אחרות. - - - - %1 of %2 in use - %1 מתוך %2 בשימוש - - - - Currently there is no storage usage information available. - כרגע לא קיים מידע שימוש באחסון. - - - - The server version %1 is unsupported! Proceed at your own risk. - גרסת השרת %1 אינה נתמכת! ניתן להמשיך על אחריותך בלבד. - - - - Obtaining authorization from the browser. - קבלת הרשאות מהדפדפן. - - - - Server configuration error: %1 at %2. - שגיאת תצורת שרת: %1 ב- %2. - - - - No %1 connection configured. - לא מוגדר חיבור %1. - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - - - - - Signed out - יציאה - - - - Disconnected - מנותק - - - - Connected - מחובר - - - - Service unavailable - שירות לא זמין - - - - Maintenance mode - מצב תחזוקה - - - - Network error - שגיאת תקשורת - - - - Configuration error - שגיאת הגדרה - - - - Asking Credentials - מבקש אישורי אימות - - - - Unknown account state - מצב חשבון לא ידוע - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 ב- %3 - - - - Activity - פעילות - - - - Account - חשבון - - - - Time - זמן - - - - Local path - - - - - OCC::ActivitySettings - - - - Server Activity - פעילות שרת - - - - Sync Protocol - פרוטוקול סנכרון - - - - Not Synced - לא מסונכרן - - - - Not Synced (%1) - %1 is the number of not synced files. - לא מסונכרן (%1) - - - - OCC::ActivityWidget - - - Form - טופס - - - - - - TextLabel - טקסט תווית - - - - Filter - סינון - - - - Server Activities - פעילויות שרת - - - - Notifications - התראות - - - - %n notifications(s) for %1. - %n התראה עבור %1.%n התראות עבור %1.%n התראות עבור %1.%n התראות עבור %1. - - - - %n notifications(s) for %1 and %2. - %n התראה עבור %1 ו- %2.%n התראות עבור %1 ו- %2.%n התראות עבור %1 ו- %2.%n התראות עבור %1 ו- %2. - - - - New notifications for %1, %2 and other accounts. - התראות חדשות עבור %1, %2 וחשבונות אחרים. - - - - Open the activity view for details. - יש לפתוח את תצוגת הפעילות לפרטים. - - - - Copy to clipboard - העתקה ללוח הגזירים - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>בחשבון %1 אין פעילויות מאופשרות. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - המשך פירושו <b>מחיקת ההגדרות הבאות</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - המך פירושו <b>התעלמות מההגדרות הבאות</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - .חלק מההגדרות הוגדרו בגרסה חדשה יותר של לקוח זה ומשתמשות במאפיינים שאינם נתמכים בגרסה זו<br><br>%1<br>קובץ ההגדרות הנוכחי כבר נאפה עבור <i>%2</i>. - - - - Quit - יציאה - - - - Continue - המשך - - - - Error accessing the configuration file - שגיאה בכניסה לקובץ הגדרות - - - - There was an error while accessing the configuration file at %1. - אירעה שגיאה בכניסה לקובץ ההגדרות ב- %1. - - - - Quit %1 - יציאה %1 - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - נדרש אימות - - - - Enter username and password for '%1' at %2. - יש להכניס שם משתמש וסיסמא עבור '%1' ב- %2. - - - - &User: - &User: - - - - &Password: - &ססמה: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - לא הוגדר חשבון ownCloud - - - - The configured server for this client is too old - השרת המוגדר ללקוח זה ישן מדי - - - - Please update to the latest server and restart the client. - יש לעדכן לשרת האחרון ולהפעיל מחדש את הלקוח. - - - - Authentication error: Either username or password are wrong. - שגיאת אימות: שם משתמש או סיסמא שגויים. - - - - timeout - פסק זמן - - - - The provided credentials are not correct - האישורים שסופקו אינם נכונים - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - שגיאת שרת: תגובת PROPFIND אינה בפורמט XML! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - שגיאה בזמן פתיחת תיקייה %1 - - - - Directory not accessible on client, permission denied - התיקייה אינה זמינה בלקוח, הרשאות נדחו - - - - Directory not found: %1 - תיקייה לא נמצאה: %1 - - - - Filename encoding is not valid - קידוד שם קובץ אינו חוקי - - - - Error while reading directory %1 - שגיאה בזמן קריאת תיקייה %1 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - - - - - OCC::Folder - - - Local folder %1 does not exist. - תיקייה מקומית %1 אינה קיימת. - - - - %1 should be a folder but is not. - %1 אמור להיות תיקייה אבל אינו. - - - - %1 is not readable. - %1 אינו ניתן לקריאה. - - - - %1 has been removed. - %1 names a file. - %1 הוסר. - - - - %1 has been updated. - %1 names a file. - %1 עודכן. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 שונה לשם %2. - - - - %1 has been moved to %2. - %1 הועבר אל %2. - - - - %1 and %n other file(s) have been removed. - %1 וכן %n קובץ נוסף הוסר.%1 וכן %n קבצים נוספים הוסרו.%1 וכן %n קבצים נוספים הוסרו.%1 וכן %n קבצים נוספים הוסרו. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - %1 וכן %n קובץ אחר נוסף.%1 וכן %n קבצים נוספים נוספו.%1 וכן %n קבצים נוספים נוספו.%1 וכן %n קבצים נוספים נוספו. - - - - %1 has been added. - %1 names a file. - %1 נוסף. - - - - %1 and %n other file(s) have been updated. - %1 וכן %n קובץ נוסף עודכן.%1 וכן %n קבצים נוספים עודכנו.%1 וכן %n קבצים נוספים עודכנו.%1 וכן %n קבצים נוספים עודכנו. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - שונה השם של מ- %1 ל- %2 כמו כן שונה השם ל- %n קובץ נוסף.שונה השם של מ- %1 ל- %2 כמו כן שונה השם ל- %n קבצים נוספים.שונה השם של מ- %1 ל- %2 כמו כן שונה השם ל- %n קבצים נוספים.שונה השם של מ- %1 ל- %2 כמו כן שונה השם ל- %n קבצים נוספים. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 הועבר ל- %2 כמו כן הועבר %n קובץ נוסף.%1 הועבר ל- %2 כמו כן הועברו %n קבצים נוספים.%1 הועבר ל- %2 כמו כן הועברו %n קבצים נוספים.%1 הועבר ל- %2 כמו כן הועברו %n קבצים נוספים. - - - - %1 has and %n other file(s) have sync conflicts. - ל- %1 וכן ל- %n קובץ נוסף יש התנגשות סנכרון.ל- %1 וכן ל- %n קבצים נוספים יש התנגשות סנכרון.ל- %1 וכן ל- %n קבצים נוספים יש התנגשות סנכרון.ל- %1 וכן ל- %n קבצים נוספים יש התנגשות סנכרון. - - - - %1 has a sync conflict. Please check the conflict file! - ל- %1 יש התנגשות סנכרון. יש לבדוק את הקובץ הבעייתי! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - לא ניתן היה לסנכרן את %1 ו- %n קובץ נוסף בשל שגיאות. יש לבדוק את הלוג לפרטים.לא ניתן היה לסנכרן את %1 ו- %n קבצים נוספים בשל שגיאות. יש לבדוק את הלוג לפרטים.לא ניתן היה לסנכרן את %1 ו- %n קבצים נוספים בשל שגיאות. יש לבדוק את הלוג לפרטים.לא ניתן היה לסנכרן את %1 ו- %n קבצים נוספים בשל שגיאות. יש לבדוק את הלוג לפרטים. - - - - %1 could not be synced due to an error. See the log for details. - %1 לא ניתן היה לסנכרנו בשל שגיאה. ניתן לצפות בלוג לפרטים. - - - - Sync Activity - פעילות סנכרון - - - - Could not read system exclude file - לא ניתן היה לקרוא את קובץ הלא כלולים במערכת - - - - A new folder larger than %1 MB has been added: %2. - - תיקייה חדשה הגדולה מ- %1 MB נוספה: %2. - - - - - A folder from an external storage has been added. - - נוספה תיקייה של אחסון חיצוני. - - - - - Please go in the settings to select it if you wish to download it. - יש לעבור להגדרות בכדי לבחור בו אם ברצונך להוריד אותו. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - תיקייה %1 נוצרה אבל לא נכללה בסנכרון בעבר. מידע בתיקייה לא יסונכרן. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - קובץ %1 נוצר אבל לא נכלל בסנכרון בעבר. מידע בקובץ לא יסונכרן. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - לא ניתן לעקוב באופן מהימן אחר שינויים בתיקיות מסונכרנות. - -פירוש הדבר שלקוח הסינכרון לא יעלה שינויים מקומיים באופן מיידי, אלא רק יסרוק לשינויים מקומיים ויעלה אותם מדי פעם (כל שעתיים כברירת מחדל). - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - כל הקבצים בתיקיית הסנכרון '%1' נמחקו בשרת. -מחיקות אלו יסונכרנו לתיקיית הסנכרון המקומית, דבר שיגרום לקבצים להיות בלתי נגישים אלא אם כן יש לך הרשאה לשחזור. -אם קבלת החלטה לשמור את הקבצים, הם יסונכרנו מחדש עם השרת בהתניה שיש לך הרשאה לעשות כן. -אם קבלת החלטה למחוק את הקבצים, הם לא יהיו נגישים עבורך יותר, אלא אם כן הנך הבעלים שלהם. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - כל הקבצים בתיקיית הסנכרון '%1' המקומית נמחקו. מחיקות אלו יסונכרנו עם השרת, דבר שיגרום לקבצים להיות בלתי נגישים אלא אם כן ישוחזרו. -האם ברצונך לסנכרן פעולות אלו עם השרת? -אם מדובר בטעות וקבלת החלטה לשמור את הקבצים שלך, הם יסונכרנו מחדש מהשרת. - - - - Remove All Files? - הסרת כל הקבצים? - - - - Remove all files - הסרת כל הקבצים - - - - Keep files - השארת הקבצים - - - - OCC::FolderMan - - - Could not reset folder state - לא ניתן לאפס את מצב התיקייה - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - יומן סנכרון ישן '%1' אותר, אך לא ניתן להסירו. יש לוודא שאף יישום לא משתמש בו כרגע. - - - - (backup) - (גיבוי) - - - - (backup %1) - (גיבוי %1) - - - - Undefined State. - מצב בלתי מוגדר. - - - - Waiting to start syncing. - ממתין להתחלת סנכרון. - - - - Preparing for sync. - מכין סנכרון. - - - - Sync is running. - הסנכרון פועל כרגע. - - - - Sync was successful, unresolved conflicts. - הסנכרון עבר בהצלחה, קיימים קונפליקטים שלו יושרו. - - - - Last Sync was successful. - הסנכרון האחרון הושלם בהצלחה. - - - - Setup Error. - שגיאת התקנה. - - - - User Abort. - משתמש ביטל פעולה. - - - - Sync is paused. - סנכרון בהשהייה. - - - - %1 (Sync is paused) - %1 (סנכרון בהשהייה) - - - - No valid folder selected! - לא נבחרה תיקייה חוקית! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - הנתיב הנבחר אינו קיים! - - - - The selected path is not a folder! - הנתיב שנבחר אינו תיקייה! - - - - You have no permission to write to the selected folder! - אין לך הרשאות לכתיבה לתיקייה הנבחרת! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - כבר קיים סנכרון בין השרת לתיקייה המקומית. יש לבחור תיקייה מקומית אחרת! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - התיקייה המקומית %1 כבר מכילה תיקייה המשמשת לחיבור סנכרון תיקייה. יש לבחור תיקייה אחרת! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - התיקייה המקומית %1 כבר מכילה תיקייה המשמשת לחיבור סנכרון תיקייה. יש לבחור תיקייה אחרת! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - הוספת חיבור סנכרון לתיקייה - - - - File - קובץ - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - יש צורך להתחבר כדי להוסיף תיקייה - - - - Click this button to add a folder to synchronize. - יש ללחוץ על כפתור זה להוספת תיקייה לסנכרון. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - שגיאה בזמן טעינת רשימת התיקיות מהשרת. - - - - Virtual file support is enabled. - הופעלה תמיכה בקובץ וירטואלי. - - - - Signed out - יציאה - - - - Fetching folder list from server... - מושך רשימת תיקיות מהשרת... - - - - There are unresolved conflicts. Click for details. - קיימות סתירות לא פתורות. ניתן ללחוץ לפרטים. - - - - Checking for changes in remote '%1' - בודק לשינויים ב- '%1' מרוחק - - - - Checking for changes in local '%1' - בודק לשינויים ב- '%1' מקומי - - - - Reconciling changes - מסדיר שינויים - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - מסנכרן %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - בהורדה %1/s - - - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - בהעלאה %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 מתוך %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 נשארו, %1 מתוך %2, קובץ %3 מתוך %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 מתוך %2, קובץ %3 מתוך %4 - - - - file %1 of %2 - קובץ %1 מתוך %2 - - - - Waiting... - המתנה... - - - - Waiting for %n other folder(s)... - ממתין ל- %n תיקייה אחרת...ממתין ל- %n תיקיות אחרות...ממתין ל- %n תיקיות אחרות...ממתין ל- %n תיקיות אחרות... - - - - Preparing to sync... - מכין סנכרון... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - הצופה לא קיבל הודעת בדיקה. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - בעיה זו מתרחשת בדרך כלל כאשר שעוני ה- inotify מותשים. אפשר לגשת לשאלות הנפוצות לקבלת פרטים. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - הוספת חיבור סנכרון לתיקייההוספת חיבור סנכרון תיקייה - - - - Add Sync Connection - הוספת חיבור לסנכרון - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - לחיצה לבחירת תיקייה מקומית לסנכרון. - - - - Enter the path to the local folder. - הוספת הנתיב לתיקייה מקומית. - - - - Select the source folder - נא לבחור את תיקיית המקור - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - יצירת תיקייה מרוחקת - - - - Enter the name of the new folder to be created below '%1': - הכנסת השם של התיקייה החדשה שתיווצר תחת '%1': - - - - Folder was successfully created on %1. - תיקייה נוצרה בהצלחה ב- %1. - - - - Authentication failed accessing %1 - האימות נכשל בגישה ל- %1 - - - - Failed to create the folder on %1. Please check manually. - נכשל ביצירת תיקייה ב- %1. יש לבדוק באופן ידני. - - - - Failed to list a folder. Error: %1 - נכשל ברישום התיקייה. שגיאה: %1 - - - - Choose this to sync the entire account - בחירה בסנכרון זה עבור כל החשבון - - - - This folder is already being synced. - התיקייה כבר תחת סנכרון. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - הנך מסנכרן/ת כבר את <i>%1</i>, שהנה תיקיית אב של <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - המערכת מסנכרנת כבר את <i>%1</i>, תיקיית משנה של <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - שימוש בקבצים וירטואליים במקום הורדה מיידית של תוכן %1 - - - - (experimental) - (ניסיוני) - - - - Virtual files are not available for the selected folder - קבצים וירטואליים אינם זמינים לתיקייה הנבחרת - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>אזהרה:</b> %1 - - - - <b>Warning:</b> - <b>אזהרה:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - לא התקבל E-Tag מהשרת, יש לבדוק פרוקסי/שער כניסה - - - - We received a different E-Tag for resuming. Retrying next time. - התקבל E-Tag אחר לחידוש. ניסיון נוסף פעם באה. - - - - We received an unexpected download Content-Length. - התקבלה הורדה לא צפויה של Content-Length. - - - - Server returned wrong content-range - השרת החזיר טווח-תוכן שגוי - - - - OCC::GETJob - - - Connection Timeout - פסק זמן חיבור - - - - OCC::GeneralSettings - - - Form - טופס - - - - General Settings - הגדרות כלליות - - - - Language - שפה - - - - Sync hidden files - סנכרון קבצים מוסתרים - - - - Log Settings - הגדרות לוג - - - - Advanced - Approval of folder sync (Non virtual file mode only) - מתקדם - אישור לסינכרון תיקייה (מצב קבצים לא וירטואלים בלבד) - - - - Network - רשת - - - - Updates - עדכונים - - - - stable - יציב - - - - beta - בטא - - - - About - על אודות - - - - Advanced - מתקדם - - - - Use &Monochrome Icons in the system tray - שימוש בסמלוני צבע אחד במגש המערכת - - - - Ask for confirmation before synchronizing folders larger than - מבקש אישור לפני הורדת תיקיות גדולות מ- - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - מגה בייט - - - - Ask for confirmation before synchronizing external storages - מבקש אישור לפני סנכרון של אחסון חיצוני - - - - &Launch on System Startup - &הפעלה באתחול המערכת - - - - Show &Desktop Notifications - הצגת &הודעות שולחן עבודה - - - - &Update Channel - עדכון ערוץ - - - - Edit &Ignored Files - עריכת &קבצים שיש להתעלם מהם - - - - Show sync folders in &Explorer's Navigation Pane - הצגת תיקיית סינכרון בחלונית הניווט של הסייר - - - - S&how crash reporter - הצגת כותב לוגים של נפילות יישום - - - - &Restart && Update - &הפעלה מחדש && עדכן - - - - You cannot disable autostart because system-wide autostart is enabled. - לא ניתן להשבית את ההפעלה האוטומטית מכיוון שההפעלה האוטומטית של המערכת המלאה מופעלת. - - - - Change update channel? - שינוי ערוץ העדכון? - - - - Warning - אזהרה - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - ערוץ העדכון קובע אילו עדכוני לקוח יוצעו עבור ההתקנה. הערוץ "היציב" מכיל רק שדרוגים הנחשבים אמינים, ואילו הגרסאות בערוץ "ביתא" עשויות להכיל תכונות ותיקוני באגים חדשים יותר, אך כאלה שעדיין לא נבדקו ביסודיות. - -תשומת לב לכך שזה בוחר רק שדרוגים מהמאגר, וכי אין אפשרות לשדרוג לאחור: לפיכך לחזור מערוץ ביתא לערוץ יציב בדרך כלל לא ניתן לביצוע באופן מיידי ויש להמתין לגרסה יציבה שחדשה יותר מגירסת הביתא המותקנת כרגע. - - - - Change update channel - שינוי ערוץ העדכון - - - - Cancel - ביטול - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - יש להכניס %1 סימסא:<br><br>משתמש: %2<br>חשבון: %3<br> - - - - Reading from keychain failed with error: '%1' - קריאה מתוך מחזיק המפתחות נכשל עם שגיאה: '%1' - - - - Enter Password - הכנסת סיסמא - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">לחיצה כאן</a> לבקשת סיסמת יישום ממשק האינטרנט. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - עורך קבצים שיש להתעלם מהם - - - - Files Ignored by Patterns - הוספת תבנית התעלמות - - - - Add - הוספה - - - - Pattern - מערך - - - - Allow Deletion - אפשר מחיקה - - - - Remove - הסרה - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - קבצים או תיקיות שתואמים תבנית לא יסונכרנו. שינויים באים לידי ביטוי בסנכרון הבא של התיקיות. - -פריטים במחיקה אפשרית בהם ימחקו אם הם מונעים הסרת תיקייה. זה משמש למסד נתונים ראשי. - - - - Could not open file - לא ניתן לפתוח קובץ - - - - Cannot write changes to '%1'. - לא ניתן לכתוב שינויים אל '%1'. - - - - Add Ignore Pattern - הוספת תבנית התעלמות - - - - Add a new ignore pattern: - הוספת תבנית התעלמות חדשה: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - רשומה זו מסופקת על ידי המערכת ב- '%1' ולא ניתן לשנותה בתצוגה זו. - - - - OCC::IssuesWidget - - - Form - טופס - - - - List of issues - רשימת בעיות - - - - Filter - סינון - - - - There were too many issues. Not all will be visible here. - היו יותר מדי שגיאות. לא כולן יופיעו כאן. - - - - There were conflicts. Check the documentation on how to resolve them. - ארעו התנגשויות. יש לעיין בתיעוד כיצד לפתור אותן. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - קיימות התנגשויות. <a href="%1">יש לבדוק בתיעוד איך לפתור אותן.</a> - - - - Account - חשבון - - - - OCC::LibCloudProvidersPrivate - - - Settings - הגדרות - - - - OCC::LogBrowser - - - Log Output - פלט הרישום - - - - Enable logging to temporary folder - הפעלת תיעוד בתיקיה זמנית - - - - Delete logs older than %1 hours - מחיקת יומני תיעוד ישנים מ- %1 שעות - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">הלוגים כוללים מידע רגיש שאין להפוך כזמינים לציבור</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - הלקוח יכול לכתוב לתיקיה זמנית יומני תיעוד לבאגים. יומנים אלו מועילים מאוד לאבחון בעיות . -כיוון שקבצי תיעוד יכולים להיות גדולים, הלקוח יתחיל יומן חדש עבור כל הרצת סינכרון וידחוס את היומנים הישנים. מומלץ גם לאפשר מחיקת קובצי יומן לאחר מספר שעות, כדי למנוע צריכת שטח דיסק רב מדי. - - - - If enabled, logs will be written to: - אם מופעל, לוגים יכתבו ל: - - - - C:/log - C:/log - - - - Log Http traffic - תיעוד של תעבורת Http - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - הגדרות אלה ממשיכות להתרחש על פני הפעלה מחדש של לקוחות. -תשומת לב כי שימוש באפשרויות שורת פקודה לרישום יעקוף את ההגדרות. - - - - Open folder - פתיחת תיקייה - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - גרסה חדשה זמינה - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>הגרסה החדשה של לקוח %1 זמינה.</p><p><b>%2</b> זמינה להורדה. הגרסה המותקנת הנה %3.</p> - - - - - Skip this version - דלוג על גרסה זו - - - - Skip this time - דלוג בפעם זו - - - - Get update - הורדת העדכון - - - - Update Failed - עדכון נכשל - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>גרסה חדשה של לקוח %1 זמין אבל תהליך העדכון נכשל.</p><p><b>%2</b> ירד. הגרסה המותקנת הנה %3.</p> - - - - Ask again later - יש לבקש שוב מאוחר יותר - - - - Restart and update - יש להתחיל מחדש ולעדכן - - - - Update manually - יש לעדכן באופן ידני - - - - OCC::NetworkSettings - - - Form - טופס - - - - Proxy Settings - הגדרות מתווך - - - - No Proxy - ללא מתווך - - - - Use system proxy - שימוש במתווך המערכת - - - - Specify proxy manually as - הגדרת פרוקסי באופן ידני כ- - - - - Host - מארח - - - - : - : - - - - Proxy server requires authentication - שרת פרוקסי דורש אימות - - - - Note: proxy settings have no effects for accounts on localhost - הערה: הגדרות ה- proxy לא משפיעות על חשבונות ב- localhost - - - - Download Bandwidth - רוחב פס הורדה - - - - - Limit to - מוגבל ל- - - - - - KBytes/s - קילו בייט - - - - - No limit - אין הגבלה - - - - - Limit to 3/4 of estimated bandwidth - מגביל ל- 3/4 מרוחב הפס המוערך - - - - Upload Bandwidth - רוחב פס העלאה - - - - - Limit automatically - מוגבל באופן אוטומטי - - - - Hostname of proxy server - שם מארח לשרת הפרוקסי - - - - Username for proxy server - שם משתמש לשרת הפרוקסי - - - - Password for proxy server - סיסמא לשרת הפרוקסי - - - - HTTP(S) proxy - פרוקסי HTTP(S) - - - - SOCKS5 proxy - פרוקסי SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - נוצר ב- %1 - - - - Closing in a few seconds... - סגירה בעוד מספר שניות... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - הבקשה %1 נכשלה ב- %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' נבחרה ב- %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - הוחזרה שגיאה מהשרת: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - אירעה שגיאה בזמן גישה לנקודת קצה 'חתימת הייצוג': <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - התקבלה הודעה מהשרת שלא ניתן לנתח את ה- JSON: <br><em>%1</em> - - - - Unsupported token type: %1 - סוג חתימת ייצוג שאינה נתמכת: %1 - - - - The reply from the server did not contain all expected fields -:%1 - התשובה מהשרת לא הכילה את כל השדות הצפויים -:%1 - - - - Unknown Error - שגיאה לא מוכרת - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>שגיאת התחברות</h1><p>אחזור מידע משתמש נכשל</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>שגיאת התחברות</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - שגיאה בזמן טעינה מחדש של חתימת הייצוג: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - התשובה מהשרת לא הכילה את כל השדות הצפויים -:%1 -נתונים שהתקבלו: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>משתמש שגוי</h1><p>התחברת עם המשתמש <em>%1</em>, אבל יש צורך להתחבר עם משתמש <em>%2</em>.<br>יש להתנתק מ- %3 בטאב נפרד, אחר כך <a href='%4'>יש ללחוץ כאן</a> ולהתחבר כמשתמש %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - עדכון %1 חדש מוכן - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - עדכון חדש עבור %1 עומד להיות מותקן. יישום העדכון יכול לבקש -הרשאות נוספות במהלך התהליך. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - לא ניתן להוריד עדכון. יש ללחוץ <a href='%1'>כאן</a> להורדה של העדכון באופן ידני. - - - - Could not check for new updates. - לא ניתן לבדוק עדכונים חדשים. - - - - Downloading %1. Please wait... - מוריד %1. יש להמתין בבקשה... - - - - %1 available. Restart application to start the update. - %1 זמין. יש להפעיל מחדש את היישום להתחלת העדכון. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - זמין %1 חדש. יש להשתמש בבקשה בכלי עדכון מערכת להתקנה שלו. - - - - Checking update server... - שרת עדכונים בבדיקה... - - - - Update status is unknown: Did not check for new updates. - מצב העדכון לא ידוע: לא בוצעה בדיקה לעדכונים חדשים. - - - - No updates available. Your installation is at the latest version. - אין עדכונים זמינים. ההתקנה שלך עם הגרסה האחרונה. - - - - Update Check - בדיקת עדכונים - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - מחובר אל %1 - - - - Setup local folder options - אפשרויות הגדרת תיקייה מקומית - - - - Connect... - מחובר... - - - - (experimental) - (ניסיוני) - - - - Use &virtual files instead of downloading content immediately%1 - שימוש בקבצים וירטואליים במקום הורדה מיידית של תוכן %1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - סנכרון של תיקייה '%1' - - - - Virtual files are not available for the selected folder - קבצים וירטואליים אינם זמינים לתיקייה הנבחרת - - - - Error - שגיאה - - - - Local Sync Folder - תיקיית סנכרון מקומית - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Email - - - - Connect to %1 - מחובר אל %1 - - - - Enter user credentials - יש להכניס פרטי אימות משתמש - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - מחובר אל %1 - - - - Login in your browser - התחברות בדפדפן שלך - - - - Copy link to clipboard - העתקת קישור ללוח הגזירים - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - מחובר אל %1 - - - - Setup %1 server - הגדרת שרת %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - נתיב אינטרנט זה אינו מאובטח כיוון שאינו מקודד. -לא מומלץ להשתמש בו. - - - - This url is secure. You can use it. - נתיב אינטרנט זה מאובטח. ניתן להשתמש בו. - - - - &Next > - &Next > - - - - Invalid URL - נתיב אינטרנט לא חוקי - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - נכשל להתחבר ל- %1 ב- %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - פסק זמן בעת ניסיון לחיבור ל- %1 ב- %2. - - - - Invalid URL - נתיב אינטרנט לא חוקי - - - - Could not create local folder %1 - לא ניתן היה ליצור תיקיית מקומית %1 - - - - Error: %1 - שגיאה: %1 - - - - The folder creation resulted in HTTP error code %1 - יצירת התיקייה יצרה שגיאת קוד HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - יצירת תיקייה מרוחקת נכשלה כיוון שהאימות שניתן שגוי!<br/>יש לחזור אחרוה ולבדוק את פרטי האימות שלך.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - יצירת תיקייה מרוחקת %1 נכשלה עם שגיאה <tt>%2</tt>. - - - - Folder rename failed - שינוי שם התיקייה נכשל - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - לא יכול להסיר ולגבות את התיקייה כיוון שהתיקייה או קובץ בתוכה פתוחים בתוכנה אחרת. יש לסגור את התיקייה או הקובץ וללחוץ על ניסיון חוזר או לבטל את ההגדרה. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - אשף התחברות %1 - - - - Enable experimental feature? - הפעלת תכונה ניסיונית? - - - - Enable experimental placeholder mode - הפעלת מצב שמירת מקום ניסיוני - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - כאשר מצב "קבצים וירטואליים" מופעל קבצים לא יורדו מיידית. במקום זאת ייווצר קובץ "%1" קטנטן עבור כל קובץ שקיים בשרת. את תוכן הקבצים ניתן להוריד תוך הפעלת הקבצים או באמצעות תפריט ההקשר שלהם . - -מצב קבצים ווירטואליים הנו הדדי ובלעדי לסנכרון סלקטיבי. נכון להיום, תיקיות שלא נבחרו יתורגמו לתיקיות מקוונות בלבד והגדרות הסינכרון הסלקטיבי שלך יאופסו. - -מעבר למצב זה יבטל כל סנכרון שפועל כרגע. - -זהו מצב ניסיוני חדש. אם משתמשים בו, נשמח לדיווח על בעיות שעולות. - - - - Stay safe - נשארים בטוחים - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - קישורים סמליים אינם נתמכים לסנכרון. - - - - File is listed on the ignore list. - הקובץ מופיע ברשימת הקבצים שיש להתעלם מהם. - - - - File names ending with a period are not supported on this file system. - שמות קבצים המסתיימים בנקודה אינם נתמכים במערכת קבצים זו. - - - - File names containing the character '%1' are not supported on this file system. - שמות קבצים הכוללים את התו '%1' אינם נתמכים במערכת קבצים זו. - - - - File name contains at least one invalid character - שם קובץ כולל לפחות תו אחד לא חוקי - - - - The file name is a reserved name on this file system. - שם הקובץ הנו שם שמור במערכת קבצים זו. - - - - Filename contains trailing spaces. - שם הקובץ כולל רווחים. - - - - Filename is too long. - שם הקובץ ארוך מדי. - - - - File/Folder is ignored because it's hidden. - המערכת התעלמה מקובץ/תיקייה כיוון שהם מוסתרים. - - - - Stat failed. - ההתחלה נכשלה. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - סתירה: גרסת שרת הורדה, שונה השם לגרסה המקומית אך הגרסה לא נשלחה לשרת. - - - - The filename cannot be encoded on your file system. - לא ניתן להצפין את שם הקובץ במערכת הקבצים שלך. - - - - The filename is blacklisted on the server. - שם הקובץ נמצא ברשימה השחורה של השרת. - - - - size - גודל - - - - permissions - הרשאות - - - - etag - etag - - - - file id - מזהה קובץ - - - - server reported no %1 - שרת שמדווח ב- %1 - - - - Moved to invalid target, restoring - הועבר ליעד לא חוקי, משחזר - - - - Not allowed to remove, restoring - לא מורשה להסיר, משחזר - - - - Ignored because of the "choose what to sync" blacklist - המערכת התעלמה בשל הרשימה השחורה של "בחירה מה לסנכרן" - - - - Not allowed because you don't have permission to add subfolders to that folder - אסור כיוון שאין לך הרשאות להוספת תיקיית משנה לתיקייה זו - - - - Not allowed because you don't have permission to add files in that folder - אסור כיוון שאין לך הרשאות להוספת קבצים לתיקייה זו - - - - Not allowed to upload this file because it is read-only on the server, restoring - אסור להעלות קובץ זה כיוון שזה לקריאה בלבד בשרת, מאחזר - - - - Error while reading the database - שגיאה בזמן קריאת מסד הנתונים - - - - Server replied with an error while reading directory '%1' : %2 - השרת הגיב בשגיאה בזמן קריאת התיקייה '%1' : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - ההורדה תקטין את המקום על הזיכרון המקומי החופשי מתחת למגבלה - - - - Free space on disk is less than %1 - שטח הדיסק הפנוי מתחת ל- %1 - - - - File was deleted from server - הקובץ נמחק מהשרת - - - - The file could not be downloaded completely. - לא ניתן היה להוריד את הקובץ באופן מלא. - - - - The downloaded file is empty despite the server announced it should have been %1. - הקובץ שירד ריק למרות שהשרת דיווח שהוא היה אמור להיות %1. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - הקובץ שונה מאז גילויו - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; שחזור נכשל: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - לא ניתן למחוק קובץ %1, שגיאה: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - לא ניתן היה ליצור תיקייה %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - לא ניתן לשנות שם לקובץ %1 ל- %2 בשל התנגשות עם שם קובץ מקומי - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - שגיאה בזמן הגדרת מצב קוד - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - קוד HTTP שגוי התקבל מהשרת. היה צפוי 204, אבל התקבל "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - קוד HTTP שגוי התקבל מהשרת. היה צפוי 201, אבל התקבל "%1 %2". - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - לא ניתן היה לשנות שם %1 ל- %2, שגיאה: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - קוד HTTP שגוי התקבל מהשרת. היה צפוי 201, אבל התקבל "%1 %2". - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - שגיאה בזמן כתיבת מטא דאטה למסד הנתונים - - - - Error setting pin state - שגיאה בזמן הגדרת מצב קוד - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - לא ניתן לעדכן קובץ : %1 - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - הקובץ %1 לא ניתן להעלאה כיוון שכבר קיים קובץ עם אותו שם, שונה רק באותיות קטנות וגדולות - - - - File Removed - קובץ הוסר - - - - Local file changed during syncing. It will be resumed. - קובץ מקומי שונה בזמן הסנכרון. הסנכרון יחודש. - - - - Local file changed during sync. - קובץ מקומי שונה בזמן הסנכרון. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - העלאה של %1 חורגת מהמכסה של התיקייה - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - הקובץ המקומי הוסר בזמן הסנכרון. - - - - Local file changed during sync. - קובץ מקומי שונה בזמן הסנכרון. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - קוד תגובה לא צפוי מהשרת (%1) - - - - Missing File ID from server - חסר זיהוי קובץ מהשרת - - - - Missing ETag from server - חסר ETag מהשרת - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - הקובץ המקומי הוסר בזמן הסנכרון. - - - - Local file changed during sync. - קובץ מקומי שונה בזמן הסנכרון. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - הקובץ המקומי הוסר בזמן הסנכרון. - - - - Local file changed during sync. - קובץ מקומי שונה בזמן הסנכרון. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - השרת לא זיהה את נתח המידע האחרון. (לא כלל e-tag) - - - - OCC::ProtocolItemModel - - - Time - זמן - - - - File - קובץ - - - - Folder - תיקייה - - - - Issues - - - - - Action - פעולה - - - - Size - גודל - - - - Account - חשבון - - - - Status - מצב - - - - OCC::ProtocolWidget - - - Form - טופס - - - - Local sync protocol - פרוטוקול סנכרון מקומי - - - - Filter - סינון - - - - Account - חשבון - - - - Copy to clipboard - העתקה ללוח הגזירים - - - - Show in file browser - - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - נדרש אימות פרוקסי - - - - Username: - שם משתמש: - - - - Proxy: - פרוקסי: - - - - The proxy server needs a username and password. - שרת הפרוקסי זקוק לשם משתמש וסיסמא. - - - - Password: - ססמה: - - - - TextLabel - טקסט תווית - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - יש לבחור מה לסנכרן - - - - OCC::SelectiveSyncWidget - - - Loading ... - טוען... - - - - Deselect remote folders you do not wish to synchronize. - הסרת בחירה של תיקיות מרוחקות שאין ברצונך לסנכרן. - - - - Name - שם - - - - Size - גודל - - - - - No subfolders currently on the server. - אין תיקיות משנה כרגע על השרת. - - - - An error occurred while loading the list of sub folders. - שגיאה אירעה בזמן הטעינה של רשימת תיקיות המשנה. - - - - OCC::ServerNotificationHandler - - - Dismiss - שחרור - - - - OCC::SettingsDialog - - - Settings - הגדרות - - - - Activity - פעילות - - - - Add account - הוספת חשבון - - - - - Quit %1 - יציאה %1 - - - - Are you sure you want to quit %1? - האם ברצונך באמת לסגור %1? - - - - Account - חשבון - - - - MainWindow - חלון ראשי - - - - Hide - הסתרה - - - - OCC::ShareDialog - - - TextLabel - טקסט תווית - - - - share label - רמת שיתוף - - - - Dialog - דיאלוג - - - - ownCloud Path: - נתיב ownCloud: - - - - %1 Sharing - %1 שיתוף - - - - %1 - %1 - - - - Folder: %2 - תיקייה: %2 - - - - The server does not allow sharing - השרת אינו מאפשר שיתוף - - - - Retrieving maximum possible sharing permissions from server... - מאחזר מהשרת כמות מירבית אפשרית של הרשאות שיתוף... - - - - The file can not be shared because it was shared without sharing permission. - הקובץ לא ניתן לשיתוף כיוון שהוא שותף ללא הרשאות שיתוף. - - - - Users and Groups - משתמשים וקבוצות - - - - Public Links - קישורים ציבוריים - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - שיתוף NewDocument.odt - - - - Upload only (File Drop) - העלאה בלבד (גרירת קובץ) - - - - Receive files from others without revealing the contents of the folder. - ניתן לשתף קבצים עם אחרים מבלי לחשוף את תכני התיקייה. - - - - Download / View / Edit - הורדה / הצגה / עריכה - - - - Name: - שם: - - - - Enter name of new share... - הכנסת שם של שיתוף חדש... - - - - &Create - &Create - - - - TextLabel - טקסט תווית - - - - Set &password - הגדרת &סיסמא - - - - Set &expiration date - הגדרת &תאריך תפוגה - - - - Set password - הגדרת סיסמא - - - - Recipients can view or download contents. - נמענים יכולים לצפות התוכן או להורידו. - - - - Download / View - הורדה/הצגה - - - - Recipients can view, download, edit, delete and upload contents. - נמענים יכולים לצפות, להוריד, לערוך ולהעלות תכנים. - - - - Anyone with the link has access to the file/folder - כל מי שיש בידיו את הקישור יכול להיכנס לקובץ/תיקייה - - - - - P&assword protect - הגנה בסיסמא - - - - The file can not be shared because it was shared without sharing permission. - הקובץ לא ניתן לשיתוף כיוון שהוא שותף ללא הרשאות שיתוף. - - - - Link shares have been disabled - שיתופי קישור נוטרלו - - - - Create public link share - יצירת שיתוף קישור ציבורי - - - - - Delete - מחיקה - - - - Open link in browser - פתיחת קישור בדפדפן - - - - Copy link to clipboard - העתקת קישור ללוח הגזירים - - - - Copy link to clipboard (direct download) - העתקת קישור ללוח הגזירים (הורדה ישירה) - - - - Send link by email - שליחת הקישור בדוא״ל - - - - Send link by email (direct download) - שליחת קישור על בסיס דואר אלקטרוני (הורדה ישירה) - - - - Create new... - יצירת חדש... - - - - I shared %1 with you - שיתפתי את %1 אתך - - - - Confirm Link Share Deletion - אישור מחיקת קישור שיתוף - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>למחוק את הקישור הציבורי לשיתוף <i>%1</i>?</p><p>הערה: אין אפשרות לבטל פעולה זו.</p> - - - - Cancel - ביטול - - - - - Public link - קישור ציבורי - - - - Delete link share - מחיקת קישור שיתוף - - - - - Please Set Password - יש להגדיר סיסמא - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - שיתוף NewDocument.odt - - - - Share with users or groups ... - שיתוף עם משתמשים או קבוצות... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>ניתן לנתב אנשים לקובץ או תיקייה משותפים אלו <a href="private link menu"><span style=" text-decoration: underline">על בסיס מסירת קישור פרטי</span></a>.</p></body></html> - - - - The item is not shared with any users or groups - הפריט לא משותף עם אף משתמש או קבוצה - - - - Open link in browser - פתיחת קישור בדפדפן - - - - Copy link to clipboard - העתקת קישור ללוח הגזירים - - - - Send link by email - שליחת הקישור בדוא״ל - - - - No results for '%1' - אין תוצאות עבור '%1' - - - - I shared something with you - שיתפתי משהו אתך - - - - OCC::ShareUserLine - - - Form - טופס - - - - - TextLabel - טקסט תווית - - - - can edit - ניתן לערוך - - - - can share - ניתן לשתף - - - - ... - ... - - - - create - יצירה - - - - change - שינוי - - - - delete - מחיקה - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - משתף עם %1 - - - - Context menu share - שיתוף תפריט הקשר - - - - Context menu share %1 - שיתוף תפריט הקשר %1 - - - - Confirm deletion - אישור מחיקה - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - האם ברצונך למחוק לצמיתות את תיקייה <i>%1</i> ואת כל התוכן שבתוכה? - - - - Do you want to delete the file <i>%1</i> permanently? - האם למחוק את קובץ <i>%1</i> לצמיתות? - - - - Select new location... - בחירת מיקום חדש... - - - - Error - שגיאה - - - - Moving file failed: - -%1 - העברת הקובץ נכשלה: - -%1 - - - - I shared something with you - שיתפתי משהו איתך - - - - - Share... - שיתוף... - - - - - Copy private link to clipboard - העתקת קישור ציבורי ללוח הגזירים - - - - Send private link by email... - שליחת קישור ציבורי על בסיס דואר אלקטרוני... - - - - Resharing this file is not allowed - שיתוף מחדש של קובץ זה אסור - - - - Resharing this folder is not allowed - שיתוף מחדש של התיקייה אסור - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - העתקת קישור ציבורי ללוח הגזירים - - - - Open in browser - פתיחה בדפדפן - - - - Show file versions in browser - הצגת גרסת קובץ בדפדפן - - - - Rename... - שינוי שם... - - - - Rename and upload... - שינוי שם והעלאה... - - - - Move and rename... - העברה ושינוי שם... - - - - Move, rename and upload... - העברה, שינוי שם והעלאה... - - - - Delete local changes - מחיקת שינויים מקומיים - - - - Move and upload... - העברה והעלאה... - - - - Delete - מחיקה - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>פרטי תעודת אימות</h3> - - - - Common Name (CN): - שם נפוץ (שנ): - - - - Subject Alternative Names: - שמות חלופיים לנושא: - - - - Organization (O): - ארגון (א): - - - - Organizational Unit (OU): - יחידה ארגונית (יא): - - - - State/Province: - מדינה / מחוז - - - - Country: - ארץ: - - - - Serial: - סידורי: - - - - <h3>Issuer</h3> - <h3>מנפיק</h3> - - - - Issuer: - מנפיק: - - - - Issued on: - הונפק ב-: - - - - Expires on: - פג תוקף ב-: - - - - <h3>Fingerprints</h3> - <h3>טביעות אצבע</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>הערה:</b> תעודת האימות אושרה באופן ידני</p> - - - - %1 (self-signed) - %1 (נחתם-באופן-עצמי) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - חיבור זה מקודד על בסיס %1 ביט %2. - - - - - Server version: %1 - גרסת שרת: %1 - - - - No support for SSL session tickets/identifiers - אין תמיכה למזהי/כרטיסי שיחת SSL - - - - Certificate information: - מידע על תעודת אימות: - - - - The connection is not secure - החיבור אינו מאובטח - - - - This connection is NOT secure as it is not encrypted. - - קישור זה אינו מאובטח כיוון שאינו מקודד. - - - - - OCC::SslErrorDialog - - - Form - טופס - - - - Trust this certificate anyway - בטח בתעודת אימות זו בכל אופן - - - - Untrusted Certificate - תעודת אימות לא מהימנה - - - - Cannot connect securely to <i>%1</i>: - לא ניתן להתחבר באופן בטוח ל- <i>%1</i>: - - - - with Certificate %1 - עם תעודת אימות %1 - - - - - - &lt;not specified&gt; - &lt;לא צויין&gt; - - - - - Organization: %1 - ארגון: %1 - - - - - Unit: %1 - יחידה: %1 - - - - - Country: %1 - מדינה: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - טביעת אצבע (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - טביעת אצבע (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - המועד הקובע: %1 - - - - Expiration Date: %1 - תאריך תפוגה: %1 - - - - Issuer: %1 - מנפיק: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (דילג עקב שגיאה קודמת, מנסה שנית עוד %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - רק %1 זמינים, דרושים לפחות %2 להתחלה - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - לא ניתן היה לפתוח או ליצור את מסד הנתונים המומי לסנכרון. יש לוודא שיש לך הרשאת כתיבה לתיקיית הסנכרון. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - זיכרון נמוך: הורדות שיקטינו את הזיכרון הפנוי מתחת ל- %1 ידולגו. - - - - There is insufficient space available on the server for some uploads. - קיים זיכרון לא מספק על השרת למספר העלאות. - - - - Unresolved conflict. - התנגשות לא פתורה - - - - Using virtual files with suffix, but suffix is not set - שימוש בקבצים וירטואלים עם סיומת, אבל הסיומת לא הוגדרה - - - - Unable to read the blacklist from the local database - לא ניתן היה לקרוא את הרשימה השחורה ממסד הנתונים המקומי - - - - Unable to read from the sync journal. - לא ניתן לקרוא מיומן הסנכרון. - - - - Cannot open the sync journal - לא ניתן לפתוח את יומן הסנכרון - - - - Aborted - בוטל - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>גרסה %1. למידע נוסף אפשר לבקר ב- <a href="%2">https://%3</a></p><p>לבעיות מוכרות ועזרה, ניתן לבקר ב: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>על ידי Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, ואחרים.</small></p><p>זכויות יוצרים ownCloud GmbH</p><p>מופץ על ידי %4 וברישיון תחת GNU General Public License (GPL) Version 2.0.<br/>%5 וכן לוגו %5 רשומים כסימון רשום של %4 בארצות הברית ו/או מדינות אחרות. </p><p><small>%6</small></p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - כותרת סיכום הביקורת אינו תקין. - - - - The checksum header contained an unknown checksum type '%1' - כותרת סיכום הביקורת מכילה סוג סיכום ביקורת לא מוכר '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - הקובץ שהורד אינו זהה לסיכום הביקורת, ההורדה תחודש. '%1' != '%2' - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - מערכת הקבצים הוירטואלית דורשת מערכת קבצים מסוג NTFS , %1 משתמש ב- %2 - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - יש להתחבר - - - - Folder %1: %2 - תיקייה %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - לא מוגדרות תיקיות סנכרון. - - - - Open in browser - פתיחה בדפדפן - - - - - - Log in... - כניסה... - - - - - - Log out - התנתקות - - - - Recent Changes - שינויים אחרונים - - - - Managed Folders: - ניהול תיקיות: - - - - Open folder '%1' - פתיחת תיקייה '%1' - - - - Open %1 in browser - פתיחת %1 בדפדפן - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - השרת של חשבון % 1 מפעיל גרסה % 2 שאינה נתמכת. שימוש, בלקוח זה עם גרסת שרת שאינה נתמכת, לא נבדק ומסוכן. ניתן להמשיך על אחריותך בלבד. - - - - Unknown status - מצב לא ידוע - - - - Settings... - הגדרות... - - - - Details... - פרטים... - - - - Help - עזרה - - - - Quit %1 - יציאה %1 - - - - Disconnected from %1 - מנותק מ- %1 - - - - Unsupported Server Version - גרסת שרת לא נתמכת - - - - Disconnected - מנותק - - - - Disconnected from some accounts - נותק ממספר חשבונות - - - - Disconnected from accounts: - ניתוק חשבונות: - - - - Account %1: %2 - חשבון %1: %2 - - - - Signed out - יציאה - - - - Account synchronization is disabled - סנכרון חשבון מנוטרל - - - - Synchronization is paused - הסנכרון הושהה - - - - Up to date (%1) - - - - - No sync folders configured - לא הוגדרו תיקיות סנכרון - - - - Unpause all folders - ביטול השהיית כל התיקיות - - - - Pause all folders - השהיית כל התיקיות - - - - Unpause all synchronization - ביטול השהיית כל הסנכרון - - - - Unpause synchronization - ביטול השהיית סנכרון - - - - Pause all synchronization - השהיית כל הסנכרון - - - - Pause synchronization - השהיית סנכרון - - - - Log out of all accounts - יציאה מכל החשבונות - - - - Log in to all accounts... - כניסה לכל החשבונות... - - - - New account... - חשבון חדש... - - - - About %1 - על אודות %1 - - - - No items synced recently - אין פרטי סנכרון מהזמן האחרון - - - - Checking for changes in remote '%1' - בודק לשינויים ב- '%1' מרוחק - - - - Checking for changes in local '%1' - בודק לשינויים ב- '%1' מקומי - - - - Syncing %1 of %2 (%3 left) - מסנכרן %1 מתוך %2 (%3 נשארו) - - - - Syncing %1 of %2 - סנכרון %1 מתוך %2 - - - - Syncing %1 (%2 left) - מסנכרן %1 (%2 נשארו) - - - - Syncing %1 - מסנכרן %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - טופס - - - - - TextLabel - טקסט תווית - - - - Server - שרת - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>אם תיבה זו מסומנת, תוכן קיים בתיקייה המקומית ימחק להתחלת סנכרון נקי מהשרת.</p><p>אין לסמן תיבה זו אם התוכן המקומי צריך להיות מועלה לתיקיית השרת.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - מבקש אישור לפני סנכרון תיקיות גדולות מ- - - - - S&ynchronize everything from server - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - מגה בייט - - - - Ask for confirmation before synchronizing e&xternal storages - מבקש אישור לפני סנכרון של אחסון חיצוני - - - - Choose what to sync - יש לבחור מה לסנכרן - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>כאשר אפשרות זו מסומנת, האשף ייסגר מבלי לסנכרן דבר. ניתן להשתמש בכפתור & quot; הוספת חיבור סינכרון תיקיות & quot; מתוך הגדרות החשבון, כדי לבחור איזה זוג תיקיות מקומיות ומרוחקות ברצונך לסנכרן</p></body></html> - - - - Manually create folder sync connections - יצירת חיבור סנכרון תיקיות באופן ידני - - - - &Local Folder - &תיקייה מקומית - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - שמירת מידע מקומי - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - טופס - - - - &Username - &שם משתמש - - - - &Password - &סיסמא - - - - OwncloudOAuthCredsPage - - - Form - טופס - - - - Please switch to your browser to proceed. - יש לעבור לדפדפן להמשך. - - - - An error occurred while connecting. Please try again. - אירעה שגיאה בזמן החיבור. ניתן לנסות שוב. - - - - Re-open Browser - פתיחה מחדש של הדפדפן - - - - OwncloudSetupPage - - - Form - טופס - - - - Ser&ver Address - כתובת שרת - - - - https://... - https://... - - - - Error Label - תווית שגיאה - - - - QObject - - - in the future - בעתיד - - - - %n day(s) ago - לפני %n יוםלפני %n ימיםלפני %n ימיםלפני %n ימים - - - - %n hour(s) ago - לפני %n שעהלפני %n שעותלפני %n שעותלפני %n שעות - - - - now - עכשיו - - - - Less than a minute ago - לפני פחות מדקה - - - - %n minute(s) ago - לפני %n דקהלפני %n דקותלפני %n דקותלפני %n דקות - - - - Some time ago - בעבר - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - שגיאה מכרעת - - - - - Error - שגיאה - - - - Info - מידע - - - - Success - - - - - Conflict - התנגשות - - - - File Ignored - - - - - Restored - - - - - Blacklisted - - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 ג'יגה בייט - - - - %L1 MB - %L1 מגה בייט - - - - %L1 KB - %L1 קילו בייט - - - - %L1 B - %L1 בייט - - - - %n year(s) - %n שנה%n שנים%n שנים%n שנים - - - - %n month(s) - %n חודש%n חודשים%n חודשים%n חודשים - - - - %n day(s) - %n יום%n ימים%n ימים%n ימים - - - - %n hour(s) - %n שעה%n שעות%n שעות%n שעות - - - - %n minute(s) - %n דקה%n דקות%n דקות%n דקות - - - - %n second(s) - %n שניה%n שניות%n שניות%n שניות - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - מגש המערכת אינו קיים - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 מחויב על מגש מערכת עובד. אם מריצים XFCE, יש לעקוב אחר <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">הסברים אלו</a>. אחרת, יש להתקין יישום מגש מערכת כדוגמת 'trayer' ולנסות שוב. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - %1 (Built against Qt %1) - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - נוצר קובץ וירטואלי - - - - Replaced by virtual file - הוחלף בקובץ וירטואלי - - - - Downloaded - הורד - - - - Uploaded - הועלה - - - - Server version downloaded, copied changed local file into conflict file - הורדה גרסת שרת, הועתק קובץ מקומי שהשתנה לקובץ סתירה - - - - Deleted - נמחק - - - - Moved to %1 - הועבר אל %1 - - - - Ignored - מה שהתעלמו ממנו - - - - Filesystem access error - שגיאת כניסת קובץ מערכת - - - - Error - שגיאה - - - - Updated local metadata - מטא-דאטה (מידע אודות מידע) מקומי מעודכן - - - - - Unknown - לא ידוע - - - - downloading - הורדה - - - - uploading - העלאה - - - - deleting - במחיקה - - - - moving - העברה - - - - ignoring - מתעלם - - - - - error - שגיאה - - - - updating local metadata - מעדכן מטא-דאטה (מידע אודות מידע) מקומי - - - - theme - - - Status undefined - מצב לא מוגדר - - - - Waiting to start sync - ממתין להתחלת סנכרון - - - - Sync is running - סנכרון בפעולה - - - - Sync Success - הסנכרון הצליח - - - - Sync Success, some files were ignored. - סנכרון הצליח, התעלם ממספר קבצים. - - - - Sync Error - שגיאת סנכרון - - - - Setup Error - שגיאת התקנה - - - - Preparing to sync - מכין סנכרון - - - - Aborting... - מבטל... - - - - Sync is paused - סנכרון בהשהייה - - - - utility - - - Could not open browser - לא ניתן היה לפתוח את הדפדפן - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - אירעה שגיאה בזמן פתיחת הדפדפן לנתיב האינטרנט %1. יתכן ולא מוגדר דפדפן ברירת מחדל? - - - - Could not open email client - לא ניתן היה לפתוח לקוח דואר אלקטרוני - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - אירעה שגיאה בזמן פתיחת לקוח דואר אלקטרוני ליצירת הודעה חדשה. ייתכן ולא מוגדר לקוח ברירת מחדל לדואר אלקטרוני? - - - - Always available locally - תמיד זמין מקומית - - - - Some available online only - חלק זמינים באופן מקוון בלבד - - - - - Available online only - זמין באופן מקוון בלבד - - - - Currently available locally - נכון לעכשיו זמין מקומית - - - - Make always available locally - גורם לזמינות מקומית באופן קבוע - - - - Free up local space - מנקה את הזכרון המקומי - - - \ No newline at end of file diff --git a/translations/client_hu.ts b/translations/client_hu.ts deleted file mode 100644 index 71894497733..00000000000 --- a/translations/client_hu.ts +++ /dev/null @@ -1,4613 +0,0 @@ - - - FileSystem - - - Could not make directories in trash - A kukában nem hozható létre alkönyvtár - - - - - Could not move '%1' to '%2' - '%1' nem mozgatható ide: '%2' - - - - Moving to the trash is not implemented on this platform - A kukába mozgatás ezen a platformon nem lehetséges - - - - Error removing '%1': %2 - - - - - Could not remove folder '%1' - '%1' mappa nem távolítható el - - - - FolderWizardSourcePage - - - Form - Űrlap - - - - Pick a local folder on your computer to sync - A szinkronizáláshoz válasszon ki egy helyi mappát a számítógépen - - - - &Choose... - &Kiválasztás... - - - - FolderWizardTargetPage - - - Form - Űrlap - - - - Select a remote destination folder - Távoli célkönyvtár kiválasztása - - - - Create Folder - Mappa létrhozása - - - - Refresh - Frissítés - - - - Folders - Mappák - - - - TextLabel - Szövegcímke - - - - GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - A kiszolgálótól kapott E-Cimke nem található, ellenőrizze a Proxy/Átjáró szolgáltatást - - - - We received a different E-Tag for resuming. Retrying next time. - Másik E-Cimkét kaptunk egy folytatáshoz. A következő alkalommal újra próbálkozni. - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - A szerver rossz tartalomtartományt adott vissza - - - - NotificationWidget - - - Form - Űrlap - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Szövegcímke - - - - OCC::AbstractNetworkJob - - - Connection timed out - A kapcsolat időtúllépés miatt megszakadt - - - - Unknown error: network reply was deleted - Ismeretlen hiba: hálózati válasz törölve - - - - Server replied "%1 %2" to "%3 %4" - Szerver válasza "%1 %2" "%3 %4"-re - - - - OCC::AccountSettings - - - Form - Űrlap - - - - ... - ... - - - - Storage space: ... - Tárhely mérete: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - A jelöletlen könyvtárak <b>törlésre kerülnek</b> a helyi fájlrendszeredről és a továbbiakban nem lesz szinkronizálva ezzel a számítógéppel - - - - Synchronize all - Mindent szinkronizál - - - - Synchronize none - Semmit sem szinkronizál - - - - Apply manual changes - Kézi módosítások alkalmazása - - - - Apply - Alkalmaz - - - - - - - Cancel - Mégsem - - - - Connected with <server> as <user> - Csatlakoztatva <user> felhasználóval ehhez: <server> - - - - No account configured. - Nincs beállított fiók. - - - - Add new - Új hozzáadása - - - - Remove - Eltávolítás - - - - Account - Fiók - - - - Choose what to sync - Szinkronizálandó elemek kiválasztása - - - - Force sync now - Kényszerített szinkronizálás - - - - Restart sync - Szinkronizálás újraindítása - - - - Remove folder sync connection - Könyvtár szinkronizációs kapcsolat törlése - - - - Folder creation failed - Könyvtár létrehozása nem sikerült - - - - <p>Could not create local folder <i>%1</i>. - <p>Nem lehet helyi könyvtárat létrehozni: <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Könyvtár szinkronizációs kapcsolat törlésének megerősítése - - - - Remove Folder Sync Connection - Könyvtár szinkronizációs kapcsolat törlése - - - - Disable virtual file support? - - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - - - - - Sync Running - Szinkronizálás fut - - - - The syncing operation is running.<br/>Do you want to terminate it? - A szinkronizálás folyamatban van. <br/>Szakítsuk meg? - - - - %1 in use - %1 használatban - - - - %1 as <i>%2</i> - %1, mint <i>%2</i> - - - - Connected to %1. - Csatlakozva: %1. - - - - Server %1 is temporarily unavailable. - %1 szerver jelenleg nem elérhető. - - - - Server %1 is currently in maintenance mode. - A(z) %1 szerver jelenleg karbantartási módban van. - - - - Signed out from %1. - Kijelentkezve innen: %1. - - - - Obtaining authorization from the browser. <a href='%1'>Click here</a> to re-open the browser. - Engedély kérése a böngészőtől. <a href='%1'>Kattintson ide</a> a böngésző újranyitásához. - - - - Connecting to %1... - Csatlakozás ehhez: %1 - - - - No connection to %1 at %2. - Nincs kapcsolat ehhez: %1 ekkor: %2. - - - - Log in - Bejelentkezés - - - - There are folders that were not synchronized because they are too big: - Vannak mappák, amelyek nem szinkronizáltak, mert túl nagyok: - - - - There are folders that were not synchronized because they are external storages: - Vannak mappák, amelyek nem szinkronizáltak, mert külső tárolók: - - - - There are folders that were not synchronized because they are too big or external storages: - Vannak olyan mappák, amelyek nem szinkronizáltak, mert túl nagyok vagy külső tárolók: - - - - Confirm Account Removal - Fiók törlésének megerősítése - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Tényleg törölni szeretné a kapcsolatot <i>%1</i> fiókkal?</p><p><b>Megjegyzés:</b> Ez <b>nem</b> töröl fájlokat.</p> - - - - Remove connection - Kapcsolat törlése - - - - Open folder - Könyvtár megnyitása - - - - - Log out - Kilépés - - - - Open local folder - - - - - Open folder in browser - - - - - Resume sync - Szinkronizálás folytatása - - - - Pause sync - Szinkronizálás szüneteltetése - - - - Availability - - - - - Disable virtual file support... - - - - - Enable virtual file support (experimental)... - - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Tényleg leszeretné állítani <i>%1</i> könyvtár szinkronizálását?</p><p><b>Megjegyzés:</b> Ez <b>nem</b> töröl fájlokat.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) / %2 használatban. Néhány könyvtár - beleértve a hálózati megosztásokat és a megosztott könyvtárakat - eltérő korlátozással rendelkezhet. - - - - %1 of %2 in use - %1 / %2 használatban - - - - Currently there is no storage usage information available. - Jelenleg nem érhető el tárhelyhasználati információ. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - No %1 connection configured. - Nincs beállított kapcsolat: %1. - - - - OCC::AccountState - - - Signed out - Kijelentkezve - - - - Disconnected - Szétcsatlakozva - - - - Connected - Csatlakozva - - - - Service unavailable - Szolgáltatás nem érhető el - - - - Maintenance mode - Karbantartás Mód - - - - Network error - Hálózati hiba - - - - Configuration error - Beállítási hiba - - - - Asking Credentials - A hitelesítő adatok kérése - - - - Unknown account state - Ismeretlen fiók állapot - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 itt: %2 - - - - %1 on %2 (disconnected) - %1 itt: %2 (szétkapcsolt) - - - - OCC::ActivitySettings - - - - Server Activity - Szerver aktivitás - - - - Sync Protocol - Szinkronizációs protokoll - - - - Not Synced - Nincs szinkronizálva - - - - Not Synced (%1) - %1 is the number of not synced files. - Nincs szinkronizálva (%1) - - - - The server activity list has been copied to the clipboard. - A szerver aktivitási lista a vágólapra másolva. - - - - The sync activity list has been copied to the clipboard. - A szinkronizációs aktivitási lista a vágólapra másolva. - - - - The list of unsynced items has been copied to the clipboard. - A nem szinkronizált elemek listája átkerült a vágólapra. - - - - Copied to clipboard - Másolva a vágólapra - - - - OCC::ActivityWidget - - - Form - Űrlap - - - - - - TextLabel - TextLabel - - - - Server Activities - Szerver aktivitások - - - - Copy - Másolás - - - - Copy the activity list to the clipboard. - Aktivitási lista másolása a vágólapra. - - - - Action Required: Notifications - Beavatkozás szükséges: Értesítések - - - - <br/>Account %1 does not have activities enabled. - <br /> %1 fióknak nincsenek engedélyezve az aktivitások. - - - - You received %n new notification(s) from %2. - %n új értesítést kapott innen: %2.%n új értesítést kapott innen: %2. - - - - You received %n new notification(s) from %1 and %2. - %n új értesítést kapott innen: %1 és %2.%n új értesítést kapott innen: %1 és %2. - - - - You received new notifications from %1, %2 and other accounts. - Új értesítést kapott innen: %1, %2 és további fiókoktól. - - - - %1 Notifications - Action Required - %1 értesítések - Beavatkozás szükséges - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - SSL kulcs alapú kliens azonosítás - - - - This server probably requires a SSL client certificate. - Ennek a szervernek SSL kliens tanúsítványra van szüksége. - - - - Certificate & Key (pkcs12) : - Tanúsítvány és kulcs (pkcs12): - - - - Browse... - Tallózás... - - - - Certificate password : - Tanúsítvány jelszava: - - - - An encrypted pkcs12 bundle is strongly recommended as a copy will be stored in the configuration file. - - - - - Select a certificate - Válasszon tanúsítványt - - - - Certificate files (*.p12 *.pfx) - Tanúsítvány fájlok (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Kilépés - - - - Continue - Folytatás - - - - Error accessing the configuration file - Hiba történt egy konfigurációs fájl elérésében - - - - There was an error while accessing the configuration file at %1. - Hiba történt a (z) %1 konfigurációs fájl elérésekor. - - - - Quit ownCloud - Kilépés egy sajátCloud-ból - - - - OCC::AuthenticationDialog - - - Authentication Required - Hitelesítés szükséges - - - - Enter username and password for '%1' at %2. - Felhasználónév és jelszó beírása ehhez: %1 itt: %2. - - - - &User: - &Felhasználó: - - - - &Password: - &Jelszó: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Hiba történt a metaadatok megadása az adatbázisban - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Nincs ownCloud fiók beállítva - - - - The configured server for this client is too old - A beállított szerver ehhez a klienshez túl régi - - - - Please update to the latest server and restart the client. - Kérjük, frissítse a szervert az utolsó verzióra és indítsa újra a klienst. - - - - Authentication error: Either username or password are wrong. - Hitelesítési hiba: A felhasználónév vagy a jelszó hibás. - - - - timeout - időtúllépés - - - - The provided credentials are not correct - A megadott adatok helytelenek. - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - - - - - OCC::Folder - - - Local folder %1 does not exist. - %1 helyi mappa nem létezik. - - - - %1 should be a folder but is not. - %1 valószínűleg könyvtár, de nem az. - - - - %1 is not readable. - %1 nem olvasható. - - - - %1 has been removed. - %1 names a file. - %1 sikeresen törölve. - - - - %1 has been updated. - %1 names a file. - %1 sikeresen feltöltve. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 átnevezve erre: %2. - - - - %1 has been moved to %2. - %1 áthelyezve ide: %2. - - - - %1 and %n other file(s) have been removed. - %1 és %n további fájl törölve.%1 és %n további fájl törölve. - - - - %1 and %n other file(s) have been added. - - - - - %1 has been added. - %1 names a file. - - - - - %1 and %n other file(s) have been updated. - %1 és %n további fájl feltöltve.%1 és %n további fájl feltöltve. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 átnevezve erre: %2 és még %n további fájl átnevezve.%1 átnevezve erre: %2 és még %n további fájl átnevezve. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 áthelyezve ide: %2 és még %n további fájl áthelyezve.%1 áthelyezve ide: %2 és még %n további fájl áthelyezve. - - - - %1 has and %n other file(s) have sync conflicts. - %1 és %n további fájl szinkronizálási konfliktussal rendelkezik.%1 és %n további fájl szinkronizálási konfliktussal rendelkezik. - - - - %1 has a sync conflict. Please check the conflict file! - %1 fájl szinkronizálási konfliktussal rendelkezik. Kérjük ellenőrizze a konfliktus fájlt! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 és %n további fájlt nem sikerült szinkronizálni. Bővebb információ a naplófájlban.%1 és %n további fájlt nem sikerült szinkronizálni. Bővebb információ a naplófájlban. - - - - %1 could not be synced due to an error. See the log for details. - %1 nem sikerült szinkronizálni. Bővebb információ a naplófájlban. - - - - Sync Activity - Szinkronizálási aktivitás - - - - Could not read system exclude file - Nem sikerült olvasni a rendszer kizárási fájlját - - - - A new folder larger than %1 MB has been added: %2. - - A (z) % 1 MB-nál nagyobb új mappa hozzáadva: % 2. - - - - - A folder from an external storage has been added. - - Hozzáadt egy mappát egy külső tárból. - - - - Please go in the settings to select it if you wish to download it. - A beállítások megadásához válassza ki, ha szeretné letölteni. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - A kiszolgáló egy "% 1" mappában levő összes fájlt törölték.Ezek a törlések szinkronizálódnak a helyi szinkronizálási mappájukhoz, így az ilyen fájlok nem érhetők el, hacsak nem jogosult a visszaállításra. Ha úgy dönt, hogy megtartja a fájlokat, akkor újra szinkronizálódik a szerverrel, ha jogosult erre.Ha úgy dönt, hogy törli a fájlokat, akkor nem lesznek elérhetők, hacsak nem vagy egy tulajdonos. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - A (z) "% 1" helyi szinkronmappában található összes fájl törölve lett. Ezek a törlések szinkronizálásra kerülnek a kiszolgálóval, így az ilyen fájlok nem érhetők el, hacsak vissza nem állítják őket.Biztos benne, hogy ezeket a műveleteket szinkronizálni szeretné a kiszolgálóval?Ha ez baleset volt, és úgy döntenél, hogy megőrzi a fájlokat, újra szinkronizálódik a kiszolgálóról. - - - - Remove All Files? - Törli az összes fájlt? - - - - Remove all files - Összes fájl eltávolítása - - - - Keep files - Fájlok megtartása - - - - OCC::FolderMan - - - Could not reset folder state - A térkép állapotának visszaállítása nem sikerült - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Meglelt egy régi '%1' szinkronfüzet, de nem sikerült eltávolítani. Győződjön meg róla, hogy egyetlen alkalmazás sem használja azt. - - - - (backup) - (biztonsági mentés) - - - - (backup %1) - (biztonsági mentés: %1) - - - - Undefined State. - Ismeretlen állapot. - - - - Waiting to start syncing. - Várakozás a szinkronizálás elindítására. - - - - Preparing for sync. - Előkészítés szinkronizációhoz. - - - - Sync is running. - Szinkronizálás fut. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Legutolsó szinkronizálás sikeres volt. - - - - Setup Error. - Beállítás hiba. - - - - User Abort. - Felhasználó megszakította. - - - - Sync is paused. - Szinkronizálás megállítva. - - - - %1 (Sync is paused) - %1 (szinkronizálás megállítva) - - - - No valid folder selected! - Nincs érvényes könyvtár kiválasztva! - - - - The selected path does not exist! - - - - - The selected path is not a folder! - A kiválasztott elérési út nem könyvtár! - - - - You have no permission to write to the selected folder! - Nincs joga a kiválasztott könyvtár írásához! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Már van szinkronizálás a szerverről a helyi mappára. Kérjük, válasszon másik helyi mappát! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - A %1 helyi mappa már tartalmaz egy mappát a mappák szinkronizációjában. Kérem válasszon egyet! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - A %1 helyi mappa már tartalmazza a mappát és a szinkronizálási kapcsolatokat. Kérjük válasszon egyet! - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Mappaszinkronizáció hozzáadása - - - - Synchronizing with local folder - Szinkronizálás a helyi mappával - - - - File - Fájl - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - A mappák hozzáadásához kapcsolódnia kell - - - - Click this button to add a folder to synchronize. - Kattintson erre a gombra egy mappa szinkronizálásához. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Hiba történt a mappák listájának betöltése és a kiszolgálóról. - - - - Virtual file support is enabled. - - - - - Signed out - Kijelentkezve - - - - Fetching folder list from server... - A lekérdezés listája a kiszolgálóról ... - - - - There are unresolved conflicts. Click for details. - Vannak megoldatlan konfliktusok. Kattints a részletekért. - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - A változások összehangolása - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - %1 szinkronizálása - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - letöltés: %1/s - - - - ↓ %1/s - - - - - ↑ %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - feltöltés: %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 / %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 maradt, %1 / %2, %3 / %4 fájl - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 %2, fájl%3 a %4 - - - - file %1 of %2 - %1 / %2 fájl - - - - Waiting... - Várakozás... - - - - Waiting for %n other folder(s)... - Várakozás %n további könyvtárra...Várakozás %n további könyvtárra... - - - - Preparing to sync... - Felkészülés szinkronizálásra... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Mappaszinkronizáció hozzáadása - - - - Add Sync Connection - Szinkronizálási kapcsolat hozzáadása - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - A szinkronizáláshoz válasszon ki egy helyi mappát. - - - - Enter the path to the local folder. - Adjon meg a helyi könyvtár elérési útját. - - - - Select the source folder - Forrás könyvtár kiválasztása - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Távoli mappa létrehozása - - - - Enter the name of the new folder to be created below '%1': - Írjon be egy '%1' alatt létrehozandó új mappa nevét: - - - - Folder was successfully created on %1. - A mappa sikeresen létrehozva: %1. - - - - Authentication failed accessing %1 - Hitelesítés sikertelen a %1 elérésnél - - - - Failed to create the folder on %1. Please check manually. - Nem sikerült létrehozni a %1 mappát. Kérem, kézzel ellenőrizze. - - - - Failed to list a folder. Error: %1 - Nem sikerült listát trehozni . Hiba: %1 - - - - Choose this to sync the entire account - Válassz ez egy szinkronizáláshoz - - - - This folder is already being synced. - Ez a mappa már szinkronizálva van. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Már szinkronizálni <i>% 1</i>, amely egy szülő könyvtár <i>% 2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately (experimental) - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Figyelmeztetés:</b> %1 - - - - <b>Warning:</b> - <b>Figyelmeztetés:</b> - - - - OCC::GETFileZsyncJob - - - Unable to parse zsync. - - - - - Failed to get zsync byte ranges. - - - - - Failed to initialize zsync receive structure. - - - - - We received a different E-Tag for delta. Retrying next time. - - - - - OCC::GETJob - - - Connection Timeout - Kapcsolat időtúllépés - - - - OCC::GeneralSettings - - - Form - Űrlap - - - - General Settings - Általános beállítások - - - - For System Tray - Rendszertálcára - - - - stable - - - - - beta - - - - - Advanced - Haladó - - - - Ask for confirmation before synchronizing folders larger than - Kérjen megerősítést, mielőtt szinkronizálná a nagyobb könyvtárakat - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Kérjen megerősítést a külső tárolók szinkronizálása előtt - - - - &Launch on System Startup - Indítás a rendszerre&l - - - - Show &Desktop Notifications - Asztali &értesítések megjelenítése - - - - Use &Monochrome Icons - &Monokróm ikonok használata - - - - Experimental - Kísérleti - - - - Enable Delta-Synchronization for files larger than - - - - - Version - Verzió - - - - &Update Channel - - - - - • - - - - - Edit &Ignored Files - K&ihagyott fájlok szerkesztése - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - &Hibabejelentő megjelenítése - - - - &Restart && Update - Új&raindítás és frissítés - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Mégsee - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Kérjük, írja be a %1 jelszót:<br><br> Felhasználó: %2 <br> Fiók: %3<br> - - - - Reading from keychain failed with error: '%1' - A kulcstartó olvasása nem sikerült hiba esetén: '%1' - - - - Enter Password - Jelszómegadás - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Kattints ide</a> és kérheti az alkalmazás jelszavát a webes felületről. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Kihagyott fájlok szerkesztője - - - - Global Ignore Settings - Globális kihagyási beállítások - - - - Sync hidden files - Rejtett fájlok szinkronizálása - - - - Files Ignored by Patterns - A minták által figyelmen kívül hagyott fájlok - - - - Add - Hozzáadás - - - - Pattern - Minta - - - - Allow Deletion - Törlés engedélyezése - - - - Remove - Eltávolítás - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Nem sikerült a fájl megnyitása - - - - Cannot write changes to '%1'. - Nem lehet megváltoztatni a "%1" változatot. - - - - Add Ignore Pattern - Hozzáad Figyelmen kívül hagyás minta - - - - Add a new ignore pattern: - Új figyelmen kívül hagyott minta hozzáadása: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Ezt a bejegyzést a rendszer '%1' rendszerben adja meg, és nem módosítható ebben a nézetben. - - - - OCC::IssuesWidget - - - Form - Űrlap - - - - List of issues - A problémák listája - - - - Account - Fiók - - - - - <no filter> - <no filter> - - - - - Folder - Mappa - - - - Show warnings - Figyelmeztetések megjelenítése - - - - Show ignored files - A figyelmen kívülhagyott fájlok megjelenítése - - - - There were too many issues. Not all will be visible here. - Túl sok kérdés volt. Nem minden jelen lesz látható. - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - Másolja a problémák listáját a vágólapra. - - - - Copy - Másolás - - - - Time - Idő - - - - File - Fájl - - - - Issue - Probléma - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LibCloudProvidersPrivate - - - Settings - Beállítások - - - - OCC::LogBrowser - - - Log Output - Kimenet naplózása - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. -If enabled, logs will be written to %1 - - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Könyvtár megnyitása - - - - OCC::Logger - - - Error - Hiba - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>'%1' fájlt<br/>nem lehet írásra megnyitni.<br/><br/>A naplózás kimenete <b>nem</b> menthető!</nobr> - - - - OCC::NSISUpdater - - - New Version Available - Új verzió érhető el - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Egy új verzió érhető el a(z) %1 kliensből.</p><p><b>%2</b> elérhető letöltésre. A telepített verzió: %3.</p> - - - - - Skip this version - Verzió kihagyása - - - - Skip this time - Kihagyás ezalkalommal - - - - Get update - Frissítés beszerzése - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - Űrlap - - - - Proxy Settings - Proxy beállítások - - - - No Proxy - Nincs proxy - - - - Use system proxy - Rendszerproxy használata - - - - Specify proxy manually as - Proxy beállítása kézileg, mint - - - - Host - Kiszolgáló - - - - : - : - - - - Proxy server requires authentication - A proxy szerver azonosítást kér - - - - Download Bandwidth - Letöltési sávszélesség - - - - - Limit to - Korlátozva - - - - - KBytes/s - KBájt/mp - - - - - No limit - Nincs korlát - - - - - Limit to 3/4 of estimated bandwidth - Határozza meg a becsült sávszélesség 3/4-ét - - - - Upload Bandwidth - Feltöltési sávszélesség - - - - - Limit automatically - Korlátozás automatikusan - - - - Hostname of proxy server - Proxy kiszolgáló - - - - Username for proxy server - Proxy felhasználónév - - - - Password for proxy server - Proxy jelszó - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - % 1 létrehozva - - - - Closing in a few seconds... - Néhány másodpercen belül lezárva ... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 kérés sikertelen volt a következő helyen: %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' a '%2' kategóriában - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Hiba történt egy szerveren: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Hiba történt a 'token' végpont elérésében: <br><em>%1</em> - - - - Empty JSON from OAuth2 redirect - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Nem sikerült elemezni a JSON visszaküldését a kiszolgálóról: <br><em>%1</em> - - - - The reply from the server did not contain all expected fields - A kiszolgálóra adott válasz nem tartalmazta az összes várható mezőt - - - - <h1>Login Error</h1><p>%1</p> - <h1>Hibás Belépés</h1><p>%1</p> - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Helytelen felhasználó</h1><p>Ön bejelentkezett <em>%1</em> felhasználóval, de bekel jelentkezni <em>%2</em> felhasználóval.<br>Kérjük, jelentkezzen ki egy másik lapon %3-, majd <a href='%4'>kattintson ide</a> és jelentkezzen be %2</p> -ként - - - - OCC::OCUpdater - - - New %1 Update Ready - Elérhető új %1 frissítés - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - A %1 új frissítés telepítése folyamatban van. A frissítő kérheti -további jogosultságokat a folyamat során. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Nem lehetett letölteni a frissítést. Kérjük, kattints <a href='%1'>ide</a> a frissítés kézi letöltéséhez. - - - - Could not check for new updates. - Nem lehet ellenőrzi a frissítéseket. - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - Frissítési szerver ellenőrzése... - - - - Update status is unknown: Did not check for new updates. - Frissítési állapot ismeretlen: Nem lehet ellenőrizni a frissítéseket. - - - - No updates available. Your installation is at the latest version. - Nincs újabb kiadás. Ez a legfrissebb verzió. - - - - Update Check - Frissítés ellenőrzése - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Csatlakozás: %1 - - - - Setup local folder options - Helyi mappa beállítások megadása - - - - Connect... - Csatlakozás... - - - - %1 folder '%2' is synced to local folder '%3' - %1 mappa '%2' szinkronizálva van a '%3' helyi mappához - - - - Sync the folder '%1' - '%1' könyvtár szinkronizálása - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>Figyelem:</strong> A helyi mappa nem üres. Válassz egy másikat!</small></p> - - - - Local Sync Folder - Helyi Sync mappa - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - A csatlakozás sikertelen - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Nem sikerült csatlakozni a megadott biztonságos kiszolgáló címhez. Hogyan kívánja folytatni? - - - - Select a different URL - Válasszon egy másik URL-t - - - - Retry unencrypted over HTTP (insecure) - Nincs titkosítva újra próbálkozik HTTP-n (nem biztonságos) - - - - Configure client-side TLS certificate - Az ügyféloldali TLS-tanúsítvány konfigurálása - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Nem sikerült csatlakozni a biztonságos szerver címhez<em> %1</em>. Hogyan kívánja folytatni?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - &E-mail - - - - Connect to %1 - Csatlakozás: %1 - - - - Enter user credentials - Azonosítási adatok megadása - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Csatlakozás: %1 - - - - Login in your browser - Jelentkezzen be egy böngészőbe - - - - Copy link to clipboard - Link másolása egy vágólapra - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Csatlakozás: %1 - - - - Setup %1 server - Szerver %1 beállítás - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Ez az URL nem biztonságos, mivel nincs titkosítva. -Nem ajánlott használni. - - - - This url is secure. You can use it. - Ez az url biztonságos. - - - - &Next > - &Következő > - - - - Invalid URL - Érvénytelen URL - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">Sikeresen csatlakozott az %1-hoz: %2 verziószám %3 (%4)</font><br/><br/> - - - - Failed to connect to %1 at %2:<br/>%3 - Nem sikerült csatlakozni a következőhöz %1 a %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Időkorlát a %1 kapcsolódás a %2 alatt. - - - - Trying to connect to %1 at %2... - Próbál kapcsolódni az %1-hoz: %2... - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - A kiszolgáló hitelesített kérelme át lett irányítva '%1'. Az URL rossz, a kiszolgáló rosszul van beállítva. - - - - There was an invalid response to an authenticated webdav request - Érvénytelen válasz érkezett a hitelesített webdav kérelemre - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - A szerver tiltott hozzáférés. Annak ellenőrzése, hogy megfelelő hozzáféréssel rendelkezik-e,<a href="%1">kattints ide</a> egy szolgáltatás elérése egy böngészővel. - - - - Invalid URL - Érvénytelen URL - - - - The server reported the following error: - A kiszolgáló a következő hibát jelentette: - - - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - A helyi %1 mappa már létezik, állítsa be a szinkronizálódását.<br/><br/> - - - - Creating local sync folder %1... - Helyi szinkronizáló mappa létrehozása %1 ... - - - - ok - ok - - - - failed. - sikertelen. - - - - Could not create local folder %1 - Nem sikerült létrehozni a %1 helyi mappát - - - - No remote folder specified! - Nincs megadva távoli mappa! - - - - Error: %1 - Hiba: %1 - - - - creating folder on ownCloud: %1 - mappát létrehozta az ownCloud: %1 - - - - Remote folder %1 created successfully. - %1 távoli nappa sikeresen létrehozva. - - - - The remote folder %1 already exists. Connecting it for syncing. - A %1 távoli mappa már létezik. Csatlakoztassa a szinkronizációhoz. - - - - - The folder creation resulted in HTTP error code %1 - A könyvtár létrehozásakor keletkezett HTTP hibakód %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - A távoli mappa létrehozása nem sikerült, mert a megadott hitelesítő adatok hibásak!<br/>Kérem, térjen vissza és ellenőrizze a hitelesítő adatokat.</p> - - - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">A távoli mappa létrehozása sikertelen, valószínűleg mivel hibásak a megdott hitelesítési adatok.</font><br/>Lépjen vissza és ellenőrizze a belépési adatokat.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - A távoli %1 mappa létrehozása nem sikerült. Hibaüzenet: <tt>%2</tt>. - - - - A sync connection from %1 to remote directory %2 was set up. - A szinkronizációs kapcsolat a %1 és a %2 távoli mappa között létrejött. - - - - Successfully connected to %1! - Sikeresen csatlakozva: %1! - - - - Connection to %1 could not be established. Please check again. - A kapcsolat a %1 kiszolgálóhoz sikertelen. Ellenőrizze újra. - - - - Folder rename failed - A mappa átnevezése nem sikerült - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - A mappát nem lehet eltávolítani és biztonsági másolatot készíteni, mert a mappa vagy a benne található fájl megnyílik egy másik programban. Kérjük, zárjon be egy mappát vagy egy fájlt, és próbálja meg újra megpróbálni vagy törölni a beállítást. - - - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>Helyi %1 szinkronizációs mappa sikeresen létrehozva!</b></font> - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 kapcsolódási varázsló - - - - - Enable experimental feature? - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead a placeholder file will be created for each file that exists on the server. When a file is opened its contents will be downloaded automatically. Alternatively files can be downloaded manually by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Enable experimental mode - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Minden beállítva! - - - - Open Local Folder - Helyi mappa megnyitása - - - - Open %1 in Browser - %1 megnyitása böngészőben - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - Érvénytelen JSON válasz egy lekérési URL-ben - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - - - - - File is listed on the ignore list. - Fájl a kizárási listán. - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - - - - - File name contains at least one invalid character - A fájlnév legalább egy érvénytelen karaktert tartalmaz - - - - The file name is a reserved name on this file system. - - - - - Filename contains trailing spaces. - - - - - Filename is too long. - Fájlnév túl nagy. - - - - File/Folder is ignored because it's hidden. - A fájl / mappát figyelmen kívül hagyja, mert rejtett. - - - - Stat failed. - - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - The filename cannot be encoded on your file system. - - - - - The filename is blacklisted on the server. - - - - - File has extension reserved for virtual files. - - - - - size - - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - - Not allowed to remove, restoring - - - - - Ignored because of the "choose what to sync" blacklist - - - - - Not allowed because you don't have permission to add subfolders to that folder - - - - - Not allowed because you don't have permission to add files in that folder - - - - - Not allowed to upload this file because it is read-only on the server, restoring - - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - - - - - Error while opening directory %1 - - - - - Directory not accessible on client, permission denied - - - - - Directory not found: %1 - - - - - Filename encoding is not valid - - - - - Error while reading directory %1 - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Hiba történt a metaadatok megadásához a adatbázisban - - - - OCC::PropagateDownloadFile - - - File %1 can not be downloaded because of a local file name clash! - A %1 fájl nem tölthető le a helyi fájlnév ütközés miatt! - - - - The download would reduce free local disk space below the limit - A letöltés csökkentené a helyi lemezterületet a határérték alatt - - - - Free space on disk is less than %1 - Szabad lemezterület kevesebb, mint %1 - - - - Download slot finished, but there was no reply! - - - - - File was deleted from server - A fájl törlésre került a szerverről - - - - The file could not be downloaded completely. - A fájl nem tölthető le teljesen. - - - - The downloaded file is empty despite the server announced it should have been %1. - A letöltött fájl üres, annak ellenére, hogy a kiszolgáló bejelentette, hogy %1-nek kell lennie. - - - - File %1 cannot be saved because of a local file name clash! - A %1 fájl nem menthető el a helyi fájlnév ütközése miatt! - - - - - File has changed since discovery - A fájl felfedezése óta megváltozott - - - - Error writing metadata to the database - Hiba történt a metaadatok megadásához a adatbázisban - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - Helyreállítás sikertelen: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - A fájlt vagy mappát eltávolították egy csak olvasható megosztásból, de a helyreállítás sikertelen: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - nem sikerült törölni a %1 fájlt, hiba: %2 - - - - Attention, possible case sensitivity clash with %1 - Figyelmeztetés, lehetséges esetek érzékenysége ütközik a %1-vel - - - - could not create folder %1 - nem sikerült létrehozni a %1 mappát - - - - Error writing metadata to the database - Hiba történt a metaadatok megadásához a adatbázisban - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - - - - - - Error writing metadata to the database - Hiba történt a metaadatok megadásához a adatbázisban - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - Error writing metadata to the database - Hiba történt a metaadatok megadásához a adatbázisban - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - - Error writing metadata to the database - Hiba történt a metaadatok megadásához a adatbázisban - - - - Error setting pin state - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - Fájl törölve - - - - Local file changed during syncing. It will be resumed. - A helyi file a szinkronizálás során megváltozott. Visszaállításra kerül. - - - - Local file changed during sync. - A helyi file a szinkronizálás során megváltozott - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - Hiba történt a metaadatok megadásához a adatbázisban - - - - OCC::PropagateUploadFileNG - - - Failed to get zsync byte ranges. - - - - - Failed to generate zsync file. - - - - - The local file was removed during sync. - A helyi fájl el lett távolítva a szinkronizálás alatt. - - - - Local file changed during sync. - A helyi file a szinkronizálás során megváltozott - - - - Poll URL missing - - - - - Unexpected return code from server (%1) - - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileV1 - - - Poll URL missing - - - - - The local file was removed during sync. - A helyi fájl el lett távolítva a szinkronizálás alatt. - - - - Local file changed during sync. - A helyi file a szinkronizálás során megváltozott - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolWidget - - - Form - Űrlap - - - - TextLabel - TextLabel - - - - Open in browser - Megnyitás böngészőben - - - - Time - Idő - - - - File - Fájl - - - - Folder - Mappa - - - - Action - Művelet - - - - Size - Méret - - - - Local sync protocol - Helyi szinkronizálási protokoll - - - - Copy - Másolás - - - - Copy the activity list to the clipboard. - Az aktivitási lista másolása a vágólapra. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Proxy bejelentkezés szükséges - - - - Username: - Felhasználói név: - - - - Proxy: - Proxy: - - - - The proxy server needs a username and password. - A proxy kiszolgáló felhasználói nevet és jelszót kér. - - - - Password: - Jelszó: - - - - TextLabel - TextLabel - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Szinkronizálandó elemek kiválasztása - - - - OCC::SelectiveSyncWidget - - - Loading ... - Betöltés ... - - - - Deselect remote folders you do not wish to synchronize. - Távolítsa el a távoli mappákat, amelyeket nem kíván szinkronizálni. - - - - Name - Név - - - - Size - Méret - - - - - No subfolders currently on the server. - Jelenleg nincsenek almappák a kiszolgálón. - - - - An error occurred while loading the list of sub folders. - Hiba történt az almappák listájának betöltése. - - - - OCC::ServerNotificationHandler - - - Dismiss - Elutasít - - - - OCC::SettingsDialog - - - Settings - Beállítások - - - - Activity - Aktivitás - - - - General - Általános - - - - Network - Hálózat - - - - Account - Fiók - - - - OCC::SettingsDialogMac - - - %1 - %1 - - - - Activity - Aktivitás - - - - General - Általános - - - - Network - Hálózat - - - - - Account - Fiók - - - - OCC::ShareDialog - - - TextLabel - TextLabel - - - - share label - megosztási címke - - - - Dialog - - - - - ownCloud Path: - ownCloud útvonala: - - - - %1 Sharing - %1 megosztás - - - - %1 - %1 - - - - Folder: %2 - Könyvtár: %2 - - - - The server does not allow sharing - A szerver nem engedélyezi a megosztást - - - - Retrieving maximum possible sharing permissions from server... - A lehetséges maximális megosztási jogosultságok lekérése a kiszolgálóról ... - - - - The file can not be shared because it was shared without sharing permission. - A fájlt nem lehetett megosztani, mert megosztási jogosultság nélkül lett megosztva. - - - - Users and Groups - Felhasználók és csoportok - - - - Public Links - Publikus Hivatkozások - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Megosztás NewDocument.odt - - - - Upload only (File Drop) - Csak feltöltés (File Drop) - - - - Receive files from others without revealing the contents of the folder. - Fájlokat fogadhat másoktól anélkül, hogy felfedné a mappa tartalmát. 169 - - - - Name: - - - - - Enter name of new share... - - - - - &Create - - - - - TextLabel - TextLabel - - - - Set &password - &Jelszó beállítása - - - - Set &expiration date - &Lejárati dátum beállítása - - - - Set password - Jelszó beállítása - - - - Recipients can view or download contents. - Fogadók letölthetnek és megjeleníthetnek tartalmakat. - - - - Download / View - Letöltés / Megjelenítés - - - - Download / View / Upload - Letöltés / Megjelenítés / Feltöltés - - - - Recipients can view, download, edit, delete and upload contents. - Fogadók megjeleníthetnek, módosíthatnak, törölhetnek és feltölthetnek tartalmakat. - - - - Anyone with the link has access to the file/folder - Bárki a hivatkozással hozzáférhet a fájlhoz/mappához - - - - - P&assword protect - J&elszóval védve - - - - The file can not be shared because it was shared without sharing permission. - A fájlt nem lehetett megosztani, mert megosztási jogosultság nélkül lett megosztva. - - - - Link shares have been disabled - A link-megosztások letiltásra kerültek - - - - Create public link share - Hozzon létre nyilvános link megosztást - - - - - Delete - Törlés - - - - Open link in browser - Nyissa meg egy linket egy böngészőben - - - - Copy link to clipboard - Link másolása egy vágólapra - - - - Copy link to clipboard (direct download) - Link másolása a vágólapra (közvetlen letöltés) - - - - Send link by email - Link küldése e-mail-ben - - - - Send link by email (direct download) - Link elküldése e-mailben (közvetlen letöltés) - - - - Create new... - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - Egy link megosztásának törlési megerősítése - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Tényleg törölni szeretné a nyilvános link megosztását <i>%1</i>?</p><p>Megjegyzés: Ezt a műveletet nem lehet visszavonni.</p> - - - - Cancel - Mégsem - - - - - Public link - Nyilvános link - - - - Delete link share - Törölje a link megosztását - - - - - Please Set Password - Kérjük, állíts be egy jelszót - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Megosztás NewDocument.odt - - - - Share with users or groups ... - Megosztás felhasználókkal vagy csoportokkal ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Az embereket erre a megosztott fájlra vagy mappára irányíthatja egy <span style=" text-decoration: underline; color:#0000ff;">egy privát link által<a href="private link menu">.</p></body></html> - - - - The item is not shared with any users or groups - Az elemet nem osztják meg egyetlen felhasználóval sem - - - - Open link in browser - Nyissa meg egy linket egy böngészőben - - - - Copy link to clipboard - Link másolása egy vágólapra - - - - Send link by email - Link küldése e-mail-ben - - - - No results for '%1' - Nincs eredmény a '%1' - - - - I shared something with you - Megosztottam veled valamit - - - - OCC::ShareUserLine - - - Form - Űrlap - - - - - TextLabel - TextLabel - - - - can edit - szerkesztheti - - - - can share - megoszthatja - - - - ... - ... - - - - create - létrehozás - - - - change - módosít - - - - delete - töröl - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Megosztás vele: %1 - - - - Context menu share - - - - - Context menu share %1 - - - - - Confirm deletion - - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - - - - - Do you want to delete the file <i>%1</i> permanently? - - - - - Select new location... - - - - - Error - Hiba - - - - Moving file failed: - -%1 - - - - - I shared something with you - Megosztottam veled valamit - - - - - Share... - Ossza meg ... - - - - - Copy private link to clipboard - Privát hivatkozás másolás a vágólapra - - - - Send private link by email... - Privát e-mail küldése: ... - - - - Resharing this file is not allowed - - - - - Resharing this folder is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - Megnyitás böngészőben - - - - Show file versions in browser - - - - - Rename... - - - - - Rename and upload... - - - - - Move and rename... - - - - - Move, rename and upload... - - - - - Delete local changes - - - - - Move and upload... - - - - - Delete - Törlés - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Tanúsítvány részletei</h3> - - - - Common Name (CN): - Általános név (CN): - - - - Subject Alternative Names: - A tárgy további nevei: - - - - Organization (O): - Szervezet (O): - - - - Organizational Unit (OU): - Szervezeti egység (OU): - - - - State/Province: - Tagállam/Megye: - - - - Country: - Ország: - - - - Serial: - Sorozatszám: - - - - <h3>Issuer</h3> - <h3>Kibocsátó</h3> - - - - Issuer: - Aláíró: - - - - Issued on: - Kibocsátva: - - - - Expires on: - Lejárat: - - - - <h3>Fingerprints</h3> - <h3>Ujjlenyomatok</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Megjegyzés:</b> A tanúsítvány kézileg jóváhagyva</p> - - - - %1 (self-signed) - %1 (önaláírt) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Ez a kapcsolat %1 bittel van titkosítva: %2. - - - - - Server version: %1 - - - - - No support for SSL session tickets/identifiers - SSL munkamenetek / azonosítók támogatása nem támogatott - - - - Certificate information: - Tanúsítvány adatok: - - - - The connection is not secure - - - - - This connection is NOT secure as it is not encrypted. - - Ez a kapcsolat NEM biztonságos, mivel nem titkosított. - - - - OCC::SslErrorDialog - - - Form - Űrlap - - - - Trust this certificate anyway - Mindenképp fogadja el ezt a tanúsítványt - - - - Untrusted Certificate - Nem megbízható tanúsítvány - - - - Cannot connect securely to <i>%1</i>: - Nem sikerült biztonságosan csatlakozni ide: <i>%1</i>: - - - - with Certificate %1 - %1 tanúsítvánnyal - - - - - - &lt;not specified&gt; - &lt;nincs megadva&gt; - - - - - Organization: %1 - Szervezet: %1 - - - - - Unit: %1 - Egység: %1 - - - - - Country: %1 - Ország: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Ellenőrzőkód (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Ellenőrzőkód (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Tényleges dátum: %1 - - - - Expiration Date: %1 - Lejárati dátum: %1 - - - - Issuer: %1 - Kibocsátó: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (kihagyva korábbi hiba miatt, próbálkozva újra %2-ban) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - - - - - Unresolved conflict. - - - - - Could not update virtual file metadata: %1 - - - - - Using virtual files with suffix, but suffix is not set - - - - - Unable to read the blacklist from the local database - - - - - Unable to read from the sync journal. - - - - - Cannot open the sync journal - - - - - Aborted - - - - - OCC::SyncLogDialog - - - Synchronisation Log - Szinkronizálási napló - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p> - - - - - <p>Copyright ownCloud GmbH</p> - - - - - <p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%2 and the %2 logo are registered trademarks of %1 in the United States, other countries, or both.</p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - - - - - The checksum header contained an unknown checksum type '%1' - - - - - The downloaded file does not match the checksum, it will be resumed. - - - - - OCC::ZsyncGenerateRunnable - - - Failed to open input file %1: %2 - - - - - Failed to open input file: %1 - - - - - Failed to write block sums: - - - - - Failed to write zsync metadata file: - - - - - OCC::ZsyncSeedRunnable - - - Unable to parse zsync file. - - - - - Unable to open file: %1 - - - - - OCC::ownCloudGui - - - Please sign in - Belépés szükséges - - - - Folder %1: %2 - Mappa %1: %2 - - - - There are no sync folders configured. - Nincsenek megadva szinkronizálandó mappák. - - - - Open in browser - Megnyitás böngészőben - - - - - - Log in... - Bejelentkezés... - - - - - - Log out - Kilépés - - - - Recent Changes - Legutóbbi változások - - - - Managed Folders: - Kezelt mappák: - - - - Open folder '%1' - „%1” könyvtár megnyitása - - - - Open %1 in browser - %1 megnyitása böngészőben - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Ismeretlen állapot - - - - Settings... - Beállítások... - - - - Details... - Részletek... - - - - Help - Súgó - - - - Quit %1 - %1 kilépés - - - - Disconnected from %1 - Szétcsatlakozva innen: %1 - - - - Unsupported Server Version - - - - - Disconnected - Szétcsatlakozva - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - Szétcsatlakozva a fiókokból: ó - - - - Account %1: %2 - %1 fiók: %2 - - - - Signed out - Kijelentkezve - - - - Account synchronization is disabled - A fiókszinkronizálás le van tiltva - - - - - Synchronization is paused - A szinkronizálás szünetel - - - - Unresolved conflicts - - - - - Error during synchronization - Hiba történt egy szinkronizálás közben - - - - No sync folders configured - - - - - Unpause all folders - - - - - Pause all folders - - - - - Unpause all synchronization - Szüntesse meg az összes szinkronizálást - - - - Unpause synchronization - - - - - Pause all synchronization - - - - - Pause synchronization - - - - - Log out of all accounts - Jelentkezzen ki minden fiókból - - - - Log in to all accounts... - Jelentkezzen be az összes fiókba ... - - - - New account... - Új fiók ... - - - - - About %1 - %1 névjegye - - - - No items synced recently - Mostanában nincs szinkronizált elem - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - %1/%2 szinkronizálása (%3 maradt) - - - - Syncing %1 of %2 - - - - - Syncing %1 (%2 left) - %1 szinkronizálása (%2 maradt) - - - - Syncing %1 - %1 szinkronizálása - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Naprakész - - - - OwncloudAdvancedSetupPage - - - Form - Űrlap - - - - - - - - - - TextLabel - TextLabel - - - - Server - Kiszolgáló - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Ha ez a négyzet be van jelölve, akkor minden törölve lesz a helyi könyvtárból, amint elindul a tiszta szinkronizálás.</p><p>Ne jelöld be, ha a helyi fájljaidat felszeretnéd tölteni a szerver könyvtárába.</p></body></html> - - - - Start a &clean sync (Erases the local folder!) - &Tiszta szinkronizálás elindítása (Mindent töröl a helyi könyvtárból!) - - - - Ask for confirmation before synchroni&zing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - - - - - Choose what to sync - Szinkronizálandó elemek kiválasztása - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - &Helyi mappa - - - - pbSelectLocalFolder - - - - - &Keep local data - &Helyi adatok megtartása - - - - S&ynchronize everything from server (recommended) - - - - - Use virtual files instead of downloading content immediately (e&xperimental) - - - - - Status message - Állapotüzenet - - - - OwncloudHttpCredsPage - - - Form - Űrlap - - - - &Username - &Felhasználónév - - - - &Password - &Jelszó - - - - OwncloudOAuthCredsPage - - - Form - Űrlap - - - - Please switch to your browser to proceed. - - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - Nyisd meg újra egy böngészőt - - - - OwncloudSetupPage - - - Form - Űrlap - - - - - TextLabel - TextLabel - - - - Ser&ver Address - Szerver címe - - - - https://... - https://... - - - - Error Label - Hiba címke - - - - OwncloudWizardResultPage - - - Form - Űrlap - - - - TextLabel - TextLabel - - - - Your entire account is synced to the local folder - A teljes fiókod szinkronizálásra került a helyi könyvtárba. - - - - - PushButton - Nyomógomb - - - - QObject - - - in the future - - - - - %n day(s) ago - - - - - %n hour(s) ago - - - - - now - A legtöbb - - - - Less than a minute ago - Kevesebb, mint egy perce - - - - %n minute(s) ago - - - - - Some time ago - Kis idővel ezelött - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - - - - - %n month(s) - - - - - %n day(s) - - - - - %n hour(s) - - - - - %n minute(s) - - - - - %n second(s) - - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Rendszertálca nem elérhető - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 - szüksége van egy működő rendszertálcára. Ha XFCE-t használsz, akkor kövesd <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">ezt az útmutatót</a>. Egyébként, kérjük telepíts egy rendszertálca alkalmazást - mint például a 'trayer' - és próbáld meg újra. - - - - ownCloudTheme::about() - - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - <p><small>Fordítva a <a href="%1">%2</a> Git revizíóból, %3, %4 Qt %5, %6 felhasználásával</small></p> - - - - progress - - - Virtual file created - - - - - Replaced by virtual file - - - - - Downloaded - Letöltve - - - - Uploaded - Feltöltve - - - - Server version downloaded, copied changed local file into conflict file - - - - - Deleted - Törölve - - - - Moved to %1 - Áthelyezve ide: %1 - - - - Ignored - Kihagyva - - - - Filesystem access error - Fájlrendszer hozzáférési hiba - - - - Error - Hiba - - - - Updated local metadata - - - - - - Unknown - Ismeretlen - - - - downloading - letöltés - - - - uploading - feltöltés - - - - deleting - törlés - - - - moving - áthelyezés - - - - ignoring - kihagyás - - - - - error - hiba - - - - updating local metadata - - - - - theme - - - Status undefined - Meghatározatlan állapot - - - - Waiting to start sync - Várakozás a szinkronizálás elindulására - - - - Sync is running - Szinkronizálás fut - - - - Sync Success - Szinkronizálás sikeres - - - - Sync Success, some files were ignored. - Szinkronizálás sikeres, néhány fájl kihagyva - - - - Sync Error - Szinkronizálási hiba - - - - Setup Error - Telepítési hiba - - - - Preparing to sync - Felkészülés szinkronizálásra - - - - Aborting... - Megszakítás... - - - - Sync is paused - Szinkronizálás leállítva - - - - utility - - - Could not open browser - - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - - - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - - - - - Make always available locally - - - - - Free up local space - - - - \ No newline at end of file diff --git a/translations/client_is.ts b/translations/client_is.ts deleted file mode 100644 index 9b947a04a90..00000000000 --- a/translations/client_is.ts +++ /dev/null @@ -1,4744 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - Hætta við - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - Loka - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - Could not make directories in trash - Gat ekki útbúið möppur í ruslinu - - - - - Could not move '%1' to '%2' - Gat ekki fært '%1' í '%2' - - - - Moving to the trash is not implemented on this platform - Að færa í ruslið er ekki útfært í þessu stýrikerfi - - - - Error removing '%1': %2 - - - - - Could not remove folder '%1' - Gat ekki fjarlægt möppuna '%1' - - - - FolderWizardSourcePage - - - Form - Eyðublað - - - - Pick a local folder on your computer to sync - Veldu möppu á tölvunni þinni til að samstilla - - - - &Choose... - &Velja... - - - - FolderWizardTargetPage - - - Form - Eyðublað - - - - Select a remote destination folder - Veldu fjartengda móttökumöppu - - - - Create Folder - Búa til möppu - - - - Refresh - Endurlesa - - - - Folders - Möppur - - - - TextLabel - TextaMerking - - - - NotificationWidget - - - Form - Eyðublað - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - TextaMerking - - - - OCC::AboutDialog - - - About - Um hugbúnaðinn - - - - TextLabel - TextaMerking - - - - Versions - Útgáfur - - - - About %1 - Um %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Tenging féll á tíma - - - - Unknown error: network reply was deleted - Óþekkt villa: svari netkerfis var eytt - - - - Server replied "%1 %2" to "%3 %4" - Þjónn svaraði "%1 %2" til "%3 %4" - - - - OCC::Account - - - %1@%2 - - - - - OCC::AccountSettings - - - Form - Eyðublað - - - - Re-open Browser - - - - - ... - ... - - - - Storage space: ... - Geymslurými: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Ómerktar möppur verða <b>fjarlægðar</b> úr skráakerfi tölvunnar þinnar og því ekki samstilltar lengur við þessa tölvu - - - - Synchronize all - Samstilla allt - - - - Synchronize none - Samstilla ekkert - - - - Apply manual changes - Virkja handvirkar breytingar - - - - Apply - Virkja - - - - - - - Cancel - Hætta við - - - - Connected with <server> as <user> - Tengd/ur við <server> sem <user> - - - - No account configured. - Enginn aðgangur stilltur. - - - - Remove - Fjarlægja - - - - Account - Aðgangur - - - - Choose what to sync - Veldu það sem á að samstilla - - - - Force sync now - Þvinga samstillingu núna - - - - Restart sync - Endurræsa samstillingu - - - - Remove folder sync connection - Fjarlægja samstillingartengingu möppu - - - - Folder creation failed - Gerð möppu mistókst - - - - <p>Could not create local folder <i>%1</i>. - <p>Gat ekki búið til staðværa möppu <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Staðfesta fjarlægingu á samstillingartengingu möppu - - - - Remove Folder Sync Connection - Fjarlægja samstillingartengingu möppu - - - - Disable virtual file support? - - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - - - - - Sync Running - Samstilling er keyrandi - - - - The syncing operation is running.<br/>Do you want to terminate it? - Aðgerðin sem samstillir er í gangi.<br/>Viltu stöðva hana? - - - - %1 in use - %1 í notkun - - - - %1 as <i>%2</i> - %1 sem <i>%2</i> - - - - Connected to %1. - Tengdur við %1. - - - - Server %1 is temporarily unavailable. - Þjónninn %1 er ekki tiltækur í augnablikinu. - - - - Server %1 is currently in maintenance mode. - Þjónninn %1 er í viðhaldsham. - - - - Signed out from %1. - Skráður út af %1. - - - - Connecting to %1... - Tengist við %1... - - - - No connection to %1 at %2. - Engin tenging við %1 á %2. - - - - Log in - Skrá inn - - - - There are folders that were not synchronized because they are too big: - Það eru möppur sem ekki eru samstilltar því þær eru of stórar: - - - - There are folders that were not synchronized because they are external storages: - Það eru möppur sem ekki eru samstilltar því þær ytri eru gagnageymslur: - - - - There are folders that were not synchronized because they are too big or external storages: - Það eru möppur sem ekki eru samstilltar því þær eru of stórar eða eru ytri gagnageymslur: - - - - Confirm Account Removal - Staðfesta fjarlægingu aðgangs - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Viltu í alvörunni fjarlægja tenginguna við aðganginn <i>%1</i>?</p><p><b>Athugið:</b> Þetta mun <b>ekki</b> eyða neinum skrám.</p> - - - - Remove connection - Fjarlægja tengingu - - - - Open folder - Opna möppu - - - - - Log out - Skrá út - - - - Open local folder - - - - - Open folder in browser - - - - - Resume sync - Halda samstillingu áfram - - - - Pause sync - Gera hlé á samstillingu - - - - Availability - - - - - Disable virtual file support... - - - - - Enable virtual file support%1... - - - - - (experimental) - - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Viltu í alvörunni hætta að samstilla möppuna <i>%1</i>?</p><p><b>Athugið:</b> Þetta mun <b>ekki</b> eyða neinum skrám.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) af %2 í notkun. Sumar möppur, þar með taldar netmöppur tengdar í skráakerfið eða sameignarmöppur, gætu verið með önnur takmörk. - - - - %1 of %2 in use - %1 af %2 í notkun - - - - Currently there is no storage usage information available. - Það eru engar upplýsingar um gagnamagn fáanlegar í augnablikinu. - - - - The server version %1 is unsupported! Proceed at your own risk. - Þjónninn er af útgáfu %1 sem er ekki lengur studd! Ef þú heldur áfram er það á þína eigin ábyrgð. - - - - Obtaining authorization from the browser. - - - - - Server configuration error: %1 at %2. - - - - - No %1 connection configured. - Engin %1 tenging skilgreind. - - - - OCC::AccountState - - - Signed out - Skráð/ur út - - - - Disconnected - Aftengt - - - - Connected - Tengt - - - - Service unavailable - Þjónusta ekki tiltæk - - - - Maintenance mode - Viðhaldshamur - - - - Network error - Villa í netkerfi - - - - Configuration error - Villa í stillingum - - - - Asking Credentials - Spyr um auðkenni - - - - Unknown account state - Óþekkt staða notandaaðgangs - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 á %2 - - - - %1 on %2 (disconnected) - %1 á %2 (ótengt) - - - - OCC::ActivityListModel - - - %1 %2 on %3 - - - - - OCC::ActivitySettings - - - - Server Activity - Virkni þjóns - - - - Sync Protocol - Samskiptamáti samstillingar - - - - Not Synced - Ekki samstillt - - - - Not Synced (%1) - %1 is the number of not synced files. - Ekki samstillt (%1) - - - - The server activity list has been copied to the clipboard. - Virknilisti þjónsins hefur verið afritaður á klippispjaldið. - - - - The sync activity list has been copied to the clipboard. - Virknilisti samstillingar hefur verið afritaður á klippispjaldið. - - - - The list of unsynced items has been copied to the clipboard. - Listi yfir ósamstillt atriði hefur verið afritaður á klippispjaldið. - - - - Copied to clipboard - Afritað á klippispjaldið - - - - OCC::ActivityWidget - - - Form - Eyðublað - - - - - - TextLabel - TextaMerking - - - - Server Activities - Virkni þjóns - - - - Copy - Afrita - - - - Copy the activity list to the clipboard. - Afrita virknilistann á klippispjaldið. - - - - Notifications - Tilkynningar - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - - - - - Open the activity view for details. - - - - - <br/>Account %1 does not have activities enabled. - <br/>%1 aðgangurinn er ekki með virkniskráningu. - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Auðkenning SSL-biðlaraskilríkis - - - - This server probably requires a SSL client certificate. - Þessi þjónn krefst líklega SSL-biðlaraskilríkis. - - - - Certificate & Key (pkcs12) : - Skilríki & lykill (pkcs12) : - - - - Browse... - Flakka... - - - - Certificate password : - Lykilorð að skilríki : - - - - An encrypted pkcs12 bundle is strongly recommended as a copy will be stored in the configuration file. - - - - - Select a certificate - Veldu skilríki - - - - Certificate files (*.p12 *.pfx) - Skilríkjaskrár (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Hætta - - - - Continue - Halda áfram - - - - Error accessing the configuration file - Villa við að nálgast stillingaskrána - - - - There was an error while accessing the configuration file at %1. - Það kom upp villa við að nálgast stillingaskrána á %1. - - - - Quit ownCloud - Fara út úr ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - Auðkenningar krafist - - - - Enter username and password for '%1' at %2. - Gefðu upp notandanafn og lykilorð fyrir '%1' á %2. - - - - &User: - &Notandi: - - - - &Password: - &Lykilorð: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Villa við ritun lýsigagna í gagnagrunninn - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Enginn ownCloud aðgangur stilltur - - - - The configured server for this client is too old - Uppsettur þjónn fyrir þetta forrit er of gamall - - - - Please update to the latest server and restart the client. - Uppfærðu í nýjasta þjóninn og endurræstu forritið. - - - - Authentication error: Either username or password are wrong. - Villa í auðkenningu. Notandanafn eða lykilorð er rangt. - - - - timeout - tímamörk - - - - The provided credentials are not correct - Uppgefin auðkenni eru ekki rétt - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - - - - - Directory not accessible on client, permission denied - - - - - Directory not found: %1 - - - - - Filename encoding is not valid - Stafatafla skráarheitis er ekki gild - - - - Error while reading directory %1 - - - - - OCC::Folder - - - Local folder %1 does not exist. - Staðvær mappa %1 er ekki til. - - - - %1 should be a folder but is not. - %1 ætti að vera mappa en er það ekki. - - - - %1 is not readable. - %1 er ekki lesanleg. - - - - %1 has been removed. - %1 names a file. - %1 hefur verið fjarlægð. - - - - %1 has been updated. - %1 names a file. - %1 var uppfærð. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 hefur verið endurnefnd sem %2. - - - - %1 has been moved to %2. - %1 hefur verið færð í %2. - - - - %1 and %n other file(s) have been removed. - %1 og %n skrá til viðbótar hafa verið fjarlægðar.%1 og %n skrár til viðbótar hafa verið fjarlægðar. - - - - %1 and %n other file(s) have been added. - - - - - %1 has been added. - %1 names a file. - - - - - %1 and %n other file(s) have been updated. - %1 og %n skrá til viðbótar hafa verið uppfærðar.%1 og %n skrár til viðbótar hafa verið uppfærðar. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 var endurnefnd sem %2 og %n skrá til viðbótar var líka endurnefnd.%1 var endurnefnd sem %2 og %n skrár til viðbótar voru líka endurnefndar. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 var færð í %2 og %n skrá til viðbótar var líka færð.%1 var færð í %2 og %n skrár til viðbótar voru líka færðar. - - - - %1 has and %n other file(s) have sync conflicts. - %1 og %n skrá til viðbótar eru með árekstra við samstillingu.%1 og %n skrár til viðbótar eru með árekstra við samstillingu. - - - - %1 has a sync conflict. Please check the conflict file! - %1 er með árekstra við samstillingu. Skoðaðu yfirlitsskrána fyrir árekstra! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 og %n skrá til viðbótar var ekki hægt að samstilla vegna villu sem kom upp. Skoðaðu annálinn fyrir nánari upplýsingar.%1 og %n skrár til viðbótar var ekki hægt að samstilla vegna villu sem kom upp. Skoðaðu annálinn fyrir nánari upplýsingar. - - - - %1 could not be synced due to an error. See the log for details. - %1 var ekki hægt að samstilla vegna villu sem kom upp Skoðaðu annálinn fyrir nánari upplýsingar. - - - - Sync Activity - Virkni samstillingar - - - - Could not read system exclude file - Gat ekki lesið kerfisútilokunarskrána - - - - A new folder larger than %1 MB has been added: %2. - - Nýrri möppu stærri en %1 MB var bætt við: %2. - - - - - A folder from an external storage has been added. - - Möppu úr ytri gagnageymslu var bætt við. - - - - - Please go in the settings to select it if you wish to download it. - Farðu í stillingarnar til að velja hana ef þú vilt sækja hana. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - Fjarlægja allar skrár? - - - - Remove all files - Fjarlægja allar skrár - - - - Keep files - Halda skrám - - - - OCC::FolderMan - - - Could not reset folder state - Gat ekki núllstillt stöðu á möppu - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Gömul atvikaskrá samstillingar '%1' fannst, en var ekki hægt að fjarlægja. Vertu viss um að ekkert annað forrit sé ekki að nota hana. - - - - (backup) - (öryggisafrit) - - - - (backup %1) - (öryggisafrita %1) - - - - Undefined State. - Óskilgreind staða. - - - - Waiting to start syncing. - Bíð eftir að samstilling ræsist. - - - - Preparing for sync. - Undirbý samstillingu. - - - - Sync is running. - Samstilling er keyrandi. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Síðasta samstilling tókst. - - - - Setup Error. - Villa í uppsetningu. - - - - User Abort. - Notandi hætti við. - - - - Sync is paused. - Samstilling er í bið. - - - - %1 (Sync is paused) - %1 (samstilling er í bið) - - - - No valid folder selected! - Engin gild mappa valin! - - - - The selected path does not exist! - - - - - The selected path is not a folder! - Valin slóð er ekki mappa! - - - - You have no permission to write to the selected folder! - Þú hefur ekki réttindi til að skrifa í valda möppu! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Bæta við samstillingartengingu möppu - - - - File - Skrá - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Þú þarft að vera tengd(ur) til að bæta við möppu - - - - Click this button to add a folder to synchronize. - Smelltu á þennan hnapp til að bæta við möppu til samstillingar. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Villa við að hlaða inn lista yfir möppur frá vefþjóninum. - - - - Virtual file support is enabled. - - - - - Signed out - Skráður út - - - - Fetching folder list from server... - Sæki lista yfir möppur frá þjóni... - - - - There are unresolved conflicts. Click for details. - Það eru óleystir árekstrar. Smelltu fyrir nánari upplýsingar. - - - - Synchronizing VirtualFiles with local folder - - - - - Synchronizing with local folder - Samstilli við möppu á tölvunni - - - - Checking for changes in remote '%1' - Athuga með breytingar í fjartengdri '%1' - - - - Checking for changes in local '%1' - Athuga með breytingar í staðværri '%1' - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Samstilli %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - sæki %1/sek - - - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - sendi inn %1/sek - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 af %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 eftir, %1 af %2, skrá %3 af %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 af %2, skrá %3 af %4 - - - - file %1 of %2 - skrá %1 af %2 - - - - Waiting... - Bíð... - - - - Waiting for %n other folder(s)... - Bíð eftir %n möppu til viðbótar...Bíð eftir %n möppum til viðbótar... - - - - Preparing to sync... - Undirbý samstillingu... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Bæta við samstillingartengingu möppu - - - - Add Sync Connection - Bæta við samstillingartengingu - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Smelltu til að velja möppu á tölvunni þinni til samstillingar. - - - - Enter the path to the local folder. - Settu inn slóðina að staðværu möppunni. - - - - Select the source folder - Veldu upprunamöppu - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Búa til fjartengda möppu - - - - Enter the name of the new folder to be created below '%1': - Settu inn nafn á nýju möppuna sem á að útbúa undir '%1': - - - - Folder was successfully created on %1. - Það tókst að búa til staðværu möppuna á %1. - - - - Authentication failed accessing %1 - Auðkenning tókst ekki fyrir aðgang að %1 - - - - Failed to create the folder on %1. Please check manually. - Gat ekki búið til möppuna á %1. Athugaðu málið. - - - - Failed to list a folder. Error: %1 - Gat ekki fengið skráalista úr möppu. Villa: %1 - - - - Choose this to sync the entire account - Veldu þetta til að samstilla allan aðganginn - - - - This folder is already being synced. - Þessa möppu er nú þegar verið að samstilla. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Þú ert nú þegar að samstilla <i>%1</i>, sem er yfirmappa <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Aðvörun:</b> %1 - - - - <b>Warning:</b> - <b>Aðvörun:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - - - - - We received a different E-Tag for resuming. Retrying next time. - - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - - - - - OCC::GETJob - - - Connection Timeout - Tenging féll á tíma - - - - OCC::GeneralSettings - - - Form - Eyðublað - - - - General Settings - Almennar stillingar - - - - Sync hidden files - Samstilla faldar skrár - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Netkerfi - - - - Updates - Uppfærslur - - - - stable - stöðug - - - - beta - beta-prófunarútgáfa - - - - About - Um hugbúnaðinn - - - - Advanced - Ítarlegt - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Biðja um staðfestingu áður en samstilltar eru möppur stærri en - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Biðja um staðfestingu áður en samstilltar eru ytri gagnageymslur - - - - &Launch on System Startup - Ræsa þegar &kveikt á vél - - - - Show &Desktop Notifications - &Birta tilkynningar á skjáborði - - - - &Update Channel - &Uppfærslurás - - - - Edit &Ignored Files - Bre&yta hunsuðum skrám - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - Birta &hrunskýrslugerðartól - - - - &Restart && Update - Endu&rræsa && uppfæra... - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - Skipta um uppfærslurás? - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - Skipta um uppfærslurás - - - - Cancel - Hætta við - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Settu inn lykilorð %1:<br><br>Notandi: %2<br>Aðgangur: %3<br> - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - Settu inn lykilorð - - - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Ritill fyrir hunsaðar skrár - - - - Files Ignored by Patterns - Skrár hunsaðar eftir mynstrum - - - - Add - Bæta við - - - - Pattern - Mynstur - - - - Allow Deletion - Leyfa eyðingu - - - - Remove - Fjarlægja - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Gat ekki opnað skrá - - - - Cannot write changes to '%1'. - Get ekki skrifað breytingar í '%1'. - - - - Add Ignore Pattern - Bæta við hunsunarmynstri - - - - Add a new ignore pattern: - Bæta við nýju hunsunarmynstri: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - - - - - OCC::IssuesWidget - - - Form - Eyðublað - - - - List of issues - Listi yfir vandamál - - - - Account - Aðgangur - - - - - <no filter> - <no filter> - - - - - Folder - Mappa - - - - Show warnings - Birta aðvaranir - - - - Show ignored files - Sýna hunsaðar skrár - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - Afrita vandamálalistann á klippispjaldið. - - - - Copy - Afrita - - - - Time - Tími - - - - File - Skrá - - - - Issue - Vandamál - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LibCloudProvidersPrivate - - - Settings - Stillingar - - - - OCC::LogBrowser - - - Log Output - Úttak annáls - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Opna möppu - - - - OCC::NSISUpdater - - - New Version Available - Ný útgáfa tiltæk - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Ný útgáfa %1 forritsins er tiltæk.</p><p><b>%2</b> er tiltæk til niðurhals. Uppsetta útgáfan er %3.</p> - - - - - Skip this version - Sleppa þessari útgáfu - - - - Skip this time - Sleppa núna - - - - Get update - Ná í uppfærslu - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - Eyðublað - - - - Proxy Settings - Stillingar milliþjóns (proxy) - - - - No Proxy - Enginn milliþjónn - - - - Use system proxy - Nota milliþjón kerfis - - - - Specify proxy manually as - Skilgreina milliþjón handvirkt sem - - - - Host - Netþjónn - - - - : - : - - - - Proxy server requires authentication - Milliþjónn krefst auðkenningar - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Bandbreidd niðurhals - - - - - Limit to - Takmarka við - - - - - KBytes/s - KBæti/sek - - - - - No limit - Engin mörk - - - - - Limit to 3/4 of estimated bandwidth - Takmarka við 3/4 af áætlaðri bandbreidd - - - - Upload Bandwidth - Bandbreidd innsendinga - - - - - Limit automatically - Takmarka sjálfvirkt - - - - Hostname of proxy server - Vélarheiti fyrir milliþjón - - - - Username for proxy server - Notandanafn fyrir milliþjón - - - - Password for proxy server - Lykilorð fyrir milliþjón - - - - HTTP(S) proxy - HTTP(S) milliþjónn - - - - SOCKS5 proxy - SOCKS5 milliþjónn - - - - OCC::NotificationWidget - - - Created at %1 - Búið til %1 - - - - Closing in a few seconds... - Loka eftir nokkrar sekúndur... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 beiðni mistókst í %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' valið í %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Villumelding kom frá þjóninum: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Empty JSON from OAuth2 redirect - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Error: Missing field %1 - - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Villa í innskráningu</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - Ný %1 uppfærsla tiltæk - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - - - - - Could not check for new updates. - Gat ekki athugað með nýjar uppfærslur. - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - Athuga með uppfærsluþjón... - - - - Update status is unknown: Did not check for new updates. - Staða á uppfærslum er óþekkt: athugaði ekki með nýjar uppfærslur. - - - - No updates available. Your installation is at the latest version. - Engar uppfærslur tiltækar. Uppsetningin þín er af nýjustu gerð. - - - - Update Check - Athuga með uppfærslu - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Tengjast við %1 - - - - Setup local folder options - Setja upp stillingar fyrir staðværar möppur - - - - Connect... - Tengjast... - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - %1 folder '%2' is synced to local folder '%3' - %1 mappan '%2' er samstillt við staðværu möppuna '%3' - - - - Sync the folder '%1' - Samstilla möppuna '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>Aðvörun:</strong> Staðværa mappan er ekki tóm. Veldu aðgerð til að leysa málið!</small></p> - - - - Virtual files are not available for the selected folder - - - - - Local Sync Folder - Staðvær samstillingarmappa - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Tenging mistókst - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Mistókst að tengjast uppgefnu vistfangi örugga þjónsins. Hvað viltu gera í framhaldinu?</p></body></html> - - - - Select a different URL - Velja aðra slóð - - - - Retry unencrypted over HTTP (insecure) - Prófa aftur ódulritað yfir HTTP (óöruggt) - - - - Configure client-side TLS certificate - Setja upp TLS-biðlaraskilríki - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Mistókst að tengjast vistfangi örugga þjónsins á <em>%1</em>. Hvað viltu gera í framhaldinu?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - Tölvu&póstur - - - - Connect to %1 - Tengjast við %1 - - - - Enter user credentials - Settu inn auðkenni notandans - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Tengjast við %1 - - - - Login in your browser - Skráðu þig inn í vafranum þínum - - - - Copy link to clipboard - Afrita tengil á klippispjald - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Tengjast við %1 - - - - Setup %1 server - Stilla %1 þjóninn - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Þessi tenging er EKKI örugg því hún er ekki dulrituð. -Ekki er mælt með því að hún sé notuð.. - - - - This url is secure. You can use it. - Þessi slóð er örugg. Þú getur notað hana. - - - - &Next > - &Næsta > - - - - Invalid URL - Ógild slóð - - - - Could not load certificate. Maybe wrong password? - Gat ekki hlaðið inn skilríki. Kannski rangt lykilorð? - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Tókst ekki að tengjast %1 á %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Féll á tíma þegar reynt var að tengjast við %1 á %2. - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - - - - - There was an invalid response to an authenticated webdav request - - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - - - - - Invalid URL - Ógild slóð - - - - Could not create local folder %1 - Gat ekki búið til staðværu möppuna %1 - - - - No remote folder specified! - Engin fjartengd mappa tilgreind! - - - - Error: %1 - Villa: %1 - - - - The folder creation resulted in HTTP error code %1 - - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - - - - - Folder rename failed - Endurnefning möppu mistókst - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 tengingaleiðarvísir - - - - Enable experimental feature? - Virkja eiginleika á tilraunastigi? - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - Vertu örugg(ur) - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Allt er uppsett! - - - - Open Local Folder - Opna staðværa möppu - - - - Open %1 in Browser - Opna %1 í vafra - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Tákntengi eru ekki studd í samstillingu. - - - - File is listed on the ignore list. - Skráin er á listanum yfir skrár sem á að hunsa. - - - - File names ending with a period are not supported on this file system. - Skráarheiti sem enda á punkti eru ekki nothæf á þessu skráakerfi. - - - - File names containing the character '%1' are not supported on this file system. - Skráarheiti sem innihalda stafinn '%1' eru ekki nothæf á þessu skráakerfi. - - - - File name contains at least one invalid character - Skráarheitið inniheldur að minnsta kosti einn ógildan staf - - - - The file name is a reserved name on this file system. - Skráarheitið er frátekið fyrir annað í þessu skráakerfi. - - - - Filename contains trailing spaces. - Skráarheitið inniheldur bil aftan við línu. - - - - Filename is too long. - Skráarheitið er of langt. - - - - File/Folder is ignored because it's hidden. - Skrá/mappa er hunsuð vegna þess að hún er falin. - - - - Stat failed. - Mistókst að keyra stat. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - The filename cannot be encoded on your file system. - Ekki er hægt að finna rétta stafatöflu fyrir skráarheitið í skráakerfinu þínu. - - - - The filename is blacklisted on the server. - - - - - File has extension reserved for virtual files. - - - - - size - stærð - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - - - - - Ignored because of the "choose what to sync" blacklist - - - - - Not allowed because you don't have permission to add subfolders to that folder - Ekki leyft, því þú hefur ekki heimild til að bæta undirmöppum í þessa möppu - - - - Not allowed because you don't have permission to add files in that folder - Ekki leyft, því þú hefur ekki heimild til að bæta skrám í þessa möppu - - - - Not allowed to upload this file because it is read-only on the server, restoring - - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Villa við ritun lýsigagna í gagnagrunninn - - - - OCC::PropagateDownloadFile - - - - File %1 can not be downloaded because of a local file name clash! - - - - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - Laust pláss á diski er minna en %1 - - - - File was deleted from server - Skrá var eytt af þjóninum - - - - The file could not be downloaded completely. - Ekki var hægt að sækja skrána að fullu. - - - - The downloaded file is empty despite the server announced it should have been %1. - - - - - File %1 cannot be saved because of a local file name clash! - - - - - - File has changed since discovery - Skráin hefur breyst síðan hún fannst - - - - Error writing metadata to the database - Villa við ritun lýsigagna í gagnagrunninn - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Endurheimt mistókst: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - tókst ekki að eyða skránni %1, villa: %2 - - - - Attention, possible case sensitivity clash with %1 - - - - - could not create folder %1 - gat ekki búið til möppuna %1 - - - - Error writing metadata to the database - Villa við ritun lýsigagna í gagnagrunninn - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - - - - - - Error writing metadata to the database - Villa við ritun lýsigagna í gagnagrunninn - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - Error writing metadata to the database - Villa við ritun lýsigagna í gagnagrunninn - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - - - - - - Error writing metadata to the database - Villa við ritun lýsigagna í gagnagrunninn - - - - Error setting pin state - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - Skrá var fjarlægð - - - - Local file changed during syncing. It will be resumed. - Staðværu skránni var breytt við samstillingu. Henni verður haldið áfram. - - - - Local file changed during sync. - Staðværu skránni var breytt við samstillingu. - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - Villa við ritun lýsigagna í gagnagrunninn - - - - OCC::PropagateUploadFileNG - - - File is locked - - - - - The local file was removed during sync. - Staðværa skráin var fjarlægð við samstillingu. - - - - Local file changed during sync. - Staðværu skránni var breytt við samstillingu. - - - - Poll URL missing - Slóð á skoðanakönnun vantar - - - - Unexpected return code from server (%1) - Óvæntur svarkóði frá þjóni (%1) - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileTUS - - - File is locked - - - - - The local file was removed during sync. - Staðværa skráin var fjarlægð við samstillingu. - - - - Local file changed during sync. - Staðværu skránni var breytt við samstillingu. - - - - OCC::PropagateUploadFileV1 - - - File is locked - - - - - Poll URL missing - Slóð á skoðanakönnun vantar - - - - The local file was removed during sync. - Staðværa skráin var fjarlægð við samstillingu. - - - - Local file changed during sync. - Staðværu skránni var breytt við samstillingu. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolWidget - - - Form - Eyðublað - - - - TextLabel - TextaMerking - - - - Open in browser - Opna í vafra - - - - Time - Tími - - - - File - Skrá - - - - Folder - Mappa - - - - Action - Aðgerð - - - - Size - Stærð - - - - Local sync protocol - Samskiptamáti staðværrar samstillingar - - - - Copy - Afrita - - - - Copy the activity list to the clipboard. - Afrita virknilistann á klippispjaldið. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Auðkenningar krafist á milliþjóni - - - - Username: - Notandanafn: - - - - Proxy: - Milliþjónn: - - - - The proxy server needs a username and password. - Milliþjónninn þarf notandanafn og lykilorð. - - - - Password: - Lykilorð: - - - - TextLabel - TextaMerking - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Veldu það sem á að samstilla - - - - OCC::SelectiveSyncWidget - - - Loading ... - Hleð... - - - - Deselect remote folders you do not wish to synchronize. - Afveldu þær fjartengdu möppur sem þú vilt ekki samstilla. - - - - Name - Heiti - - - - Size - Stærð - - - - - No subfolders currently on the server. - Engar undirmöppur á þjóninum. - - - - An error occurred while loading the list of sub folders. - Villa átti sér stað við að hlaða inn lista yfir undirmöppur. - - - - OCC::ServerNotificationHandler - - - Dismiss - Hafna - - - - OCC::SettingsDialog - - - Settings - Stillingar - - - - Activity - Virkni - - - - Add account - Bæta við notandaaðgangi - - - - - Quit %1 - Hætta í %1 - - - - Are you sure you want to quit %1? - - - - - Account - Aðgangur - - - - MainWindow - - - - - Hide - Fela - - - - OCC::ShareDialog - - - TextLabel - TextaMerking - - - - share label - merki á sameign - - - - Dialog - Samskiptagluggi - - - - ownCloud Path: - ownCloud slóð: - - - - %1 Sharing - %1 deilir - - - - %1 - %1 - - - - Folder: %2 - Mappa: %2 - - - - The server does not allow sharing - Þjónninn leyfir ekki deilingu sameigna - - - - Retrieving maximum possible sharing permissions from server... - - - - - The file can not be shared because it was shared without sharing permission. - - - - - Users and Groups - Notendur og hópar - - - - Public Links - Opinberir tenglar - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Deila NýttSkjal.odt - - - - Upload only (File Drop) - Upload only (File Drop) - - - - Receive files from others without revealing the contents of the folder. - Fá skrár frá öðrum án þess að birta innihald möppunnar. - - - - Download / View / Edit - Sækja / Skoða / Breyta - - - - Name: - Heiti: - - - - Enter name of new share... - - - - - &Create - &Búa til - - - - TextLabel - TextaMerking - - - - Set &password - Setja ly&kilorð - - - - Set &expiration date - S&etja gildistíma - - - - Set password - Skrá lykilorð - - - - Recipients can view or download contents. - Viðtakendur geta skoðað eða sótt efni. - - - - Download / View - Sækja / Skoða - - - - Recipients can view, download, edit, delete and upload contents. - Viðtakendur geta skoðað, sótt, breytt, eytt eða sent inn efni. - - - - Anyone with the link has access to the file/folder - Hver sá sem hefur aðgang að skránni/möppunni - - - - - P&assword protect - Verj&a með lykilorði - - - - The file can not be shared because it was shared without sharing permission. - - - - - Link shares have been disabled - - - - - Create public link share - Búa til opinberan tengil á sameign - - - - - Delete - Eyða - - - - Open link in browser - Opna tengil í vafra - - - - Copy link to clipboard - Afrita tengil á klippispjald - - - - Copy link to clipboard (direct download) - Afrita tengil á klippispjald (beint niðurhal) - - - - Send link by email - Senda tengil með tölvupósti - - - - Send link by email (direct download) - Senda tengil með tölvupósti (beint niðurhal) - - - - Create new... - Búa til nýtt... - - - - I shared %1 with you - Ég deildi %1 með þér - - - - Confirm Link Share Deletion - Staðfestu eyðingu tengils á sameign - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Ertu viss um að þú viljir eyða opinbera tenglinum á sameignina <i>%1</i>?</p><p>Athugaðu: Þessi aðgerð er óafturkræf.</p> - - - - Cancel - Hætta við - - - - - Public link - Opinber tengill - - - - Delete link share - Eyða tengli á sameign - - - - - Please Set Password - Settu inn lykilorð - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Deila NýttSkjal.odt - - - - Share with users or groups ... - Deila með notendum eða hópum ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - Atriðinu er ekki deilt með neinum notendum eða hópum - - - - Open link in browser - Opna tengil í vafra - - - - Copy link to clipboard - Afrita tengil á klippispjald - - - - Send link by email - Senda tengil með tölvupósti - - - - No results for '%1' - Engar niðurstöður fyrir '%1' - - - - I shared something with you - Ég deildi einhverju með þér - - - - OCC::ShareUserLine - - - Form - Eyðublað - - - - - TextLabel - TextaMerking - - - - can edit - getur breytt - - - - can share - getur deilt - - - - ... - ... - - - - create - búið til - - - - change - breytt - - - - delete - eytt - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Deila með %1 - - - - Context menu share - Deiling með samhengisvalmynd - - - - Context menu share %1 - - - - - Confirm deletion - Staðfesta eyðingu - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - - - - - Do you want to delete the file <i>%1</i> permanently? - - - - - Select new location... - - - - - Error - Villa - - - - Moving file failed: - -%1 - - - - - I shared something with you - Ég deildi einhverju með þér - - - - - Share... - Deila... - - - - - Copy private link to clipboard - Afrita einkatengil á klippispjald - - - - Send private link by email... - Senda einkatengil með tölvupósti... - - - - Resharing this file is not allowed - Endurdeiling þessarar skráar er ekki leyfð - - - - Resharing this folder is not allowed - - - - - - Copy public link to clipboard - Afrita opinberan tengil á klippispjald - - - - Open in browser - Opna í vafra - - - - Show file versions in browser - - - - - Rename... - Endurnefna... - - - - Rename and upload... - Endurnefna og senda inn... - - - - Move and rename... - - - - - Move, rename and upload... - - - - - Delete local changes - - - - - Move and upload... - - - - - Delete - Eyða - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Upplýsingar um skilríki</h3> - - - - Common Name (CN): - Almennt heiti (CN): - - - - Subject Alternative Names: - Varanöfn: - - - - Organization (O): - Stofnun/Fyrirtæki/Félag (O): - - - - Organizational Unit (OU): - Deild (OU): - - - - State/Province: - Fylki/Svæði: - - - - Country: - Land: - - - - Serial: - Raðnúmer: - - - - <h3>Issuer</h3> - <h3>Útgefandi</h3> - - - - Issuer: - Útgefandi: - - - - Issued on: - Gefið út: - - - - Expires on: - Gildir til: - - - - <h3>Fingerprints</h3> - <h3>Fingraför</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Athugaðu:</b> Þetta skilríki var samþykkt handvirkt</p> - - - - %1 (self-signed) - %1 (undirritað af handhafa) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Þessi tenging er dulrituð með %1 bita %2. - - - - - Server version: %1 - - - - - No support for SSL session tickets/identifiers - - - - - Certificate information: - Upplýsingar um skilríki: - - - - The connection is not secure - - - - - This connection is NOT secure as it is not encrypted. - - Þessi tenging er EKKI örugg því hún er ekki dulrituð. - - - - - OCC::SslErrorDialog - - - Form - Eyðublað - - - - Trust this certificate anyway - Treysta samt áreiðanleika þessa skilríkis - - - - Untrusted Certificate - Vantreyst skilríki - - - - Cannot connect securely to <i>%1</i>: - Get ekki tengst á öruggan hátt við <i>%1</i>: - - - - with Certificate %1 - með skilríki %1 - - - - - - &lt;not specified&gt; - &lt;ótilgreint&gt; - - - - - Organization: %1 - Stofnun/Fyrirtæki/Félag (O): %1 - - - - - Unit: %1 - Eining: %1 - - - - - Country: %1 - Land: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Fingrafar (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Fingrafar (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Virkt þann: %1 - - - - Expiration Date: %1 - Gildir til dags: %1 - - - - Issuer: %1 - Útgefandi: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Einungis %1 eru tiltæk, þarf a.m.k. %2 til að ræsa - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Mistókst að opna eða búa til atvikaskrána. Gakktu úr skugga um að þú hafir les- og skrifheimildir í staðværu samstillingarmöppunni á tölvunni. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - - - - - Unresolved conflict. - Óleystur árekstur. - - - - Could not update file : %1 - - - - - Could not update virtual file metadata: %1 - - - - - Using virtual files with suffix, but suffix is not set - - - - - Unable to read the blacklist from the local database - - - - - Unable to read from the sync journal. - Tekst ekki að lesa úr atvikaskrá samstillingar. - - - - Cannot open the sync journal - Tekst ekki að opna atvikaskrá samstillingar - - - - Aborted - Hætt við - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - - - - - The checksum header contained an unknown checksum type '%1' - - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Skráðu þig inn - - - - Folder %1: %2 - Mappa %1: %2 - - - - There are no sync folders configured. - Það eru engar samstillingarmöppur stilltar. - - - - Open in browser - Opna í vafra - - - - - - Log in... - Skrá inn... - - - - - - Log out - Skrá út - - - - Recent Changes - Nýlegar breytingar - - - - Managed Folders: - Möppur í stýringu: - - - - Open folder '%1' - Opna möppu '%1' - - - - Open %1 in browser - Opna %1 í vafra - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Unknown status - - - - Settings... - Stillingar... - - - - Details... - Nánar... - - - - Help - Hjálp - - - - Quit %1 - Hætta í %1 - - - - Disconnected from %1 - Aftengdist %1 - - - - Unsupported Server Version - Óstudd útgáfa vefþjóns - - - - Disconnected - Aftengt - - - - Disconnected from some accounts - Aftengt frá sumum notendaaðgöngum - - - - Disconnected from accounts: - Aftengdist á notendareikningum: - - - - Account %1: %2 - Aðgangur %1: %2 - - - - Signed out - Skráð/ur út - - - - Account synchronization is disabled - Samstilling aðgangs er óvirk - - - - - Synchronization is paused - Samstilling er í bið - - - - Unresolved conflicts - Óleystir árekstrar - - - - Error during synchronization - Villa við samstillingu - - - - No sync folders configured - Engar samstillingarmöppur stilltar - - - - Unpause all folders - Taka allar möppur úr bið - - - - Pause all folders - Setja allar möppur í bið - - - - Unpause all synchronization - Taka allar samstillingar úr bið - - - - Unpause synchronization - Taka samstillingu úr bið - - - - Pause all synchronization - Gera hlé á öllum samstillingum - - - - Pause synchronization - Gera hlé á samstillingu - - - - Log out of all accounts - Skrá út af öllum aðgöngum - - - - Log in to all accounts... - Skrá inn á alla aðganga... - - - - New account... - Nýr aðgangur... - - - - About %1 - Um %1 - - - - No items synced recently - Ekkert samstillt nýlega - - - - Checking for changes in remote '%1' - Athuga með breytingar í fjartengdri '%1' - - - - Checking for changes in local '%1' - Athuga með breytingar í staðværri '%1' - - - - Syncing %1 of %2 (%3 left) - Samstilli %1 af %2 (%3 eftir) - - - - Syncing %1 of %2 - Samstilli %1 af %2 - - - - Syncing %1 (%2 left) - Samstilli %1 (%2 eftir) - - - - Syncing %1 - Samstilli %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Allt uppfært nú þegar - - - - OwncloudAdvancedSetupPage - - - Form - Eyðublað - - - - - - - - TextLabel - TextaMerking - - - - Server - Þjónn - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - - - - - Start a &clean sync (Erases the local folder!) - Ræsa &hreina samstillingu (eyðir staðværri möppu!) - - - - Ask for confirmation before synchroni&zing folders larger than - Biðja um staðfestingu áður en stærðarbreyting er afturkölluð - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Biðja um staðfestingu áður en samstilltar eru &ytri gagnageymslur - - - - Choose what to sync - Veldu það sem á að samstilla - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - &Staðvær mappa - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Halda staðværum gögnum - - - - S&ynchronize everything from server (recommended) - - - - - Status message - Stöðuskilaboð - - - - OwncloudHttpCredsPage - - - Form - Eyðublað - - - - &Username - N&otandanafn - - - - &Password - Ly&kilorð - - - - OwncloudOAuthCredsPage - - - Form - Eyðublað - - - - Please switch to your browser to proceed. - Skiptu yfir í vafrann þinn til að halda áfram. - - - - An error occurred while connecting. Please try again. - Villa kom upp við tengingu. Endilega reyndu aftur. - - - - Re-open Browser - Opna vafra aftur - - - - OwncloudSetupPage - - - Form - Eyðublað - - - - - TextLabel - TextaMerking - - - - Ser&ver Address - &Vistfang þjóns - - - - https://... - https://... - - - - Error Label - Villumerking - - - - OwncloudWizardResultPage - - - Form - Eyðublað - - - - TextLabel - TextaMerking - - - - Your entire account is synced to the local folder - Allt á notandaaðgangnum þínum er samstillt við staðværu möppuna - - - - - PushButton - ÝtiHnappur - - - - QObject - - - in the future - í framtíðinni - - - - %n day(s) ago - fyrir %n degi síðanfyrir %n dögum síðan - - - - %n hour(s) ago - fyrir %n klukkustund síðanfyrir %n klukkustundum síðan - - - - now - núna - - - - Less than a minute ago - Minna en mínútu síðan - - - - %n minute(s) ago - fyrir %n mínútu síðanfyrir %n mínútum síðan - - - - Some time ago - Fyrir nokkru síðan - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n ár%n ár - - - - %n month(s) - %n mánuður%n mánuðir - - - - %n day(s) - %n dagur%n dagar - - - - %n hour(s) - %n klukkustund%n klukkustundir - - - - %n minute(s) - %n mínúta%n mínútur - - - - %n second(s) - %n sekúnda%n sekúndur - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Kerfisbakki ekki tiltækur - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 krefst nothæfs kerfisbakka (system tray). Ef þú ert að keyra XFCE, farðu þá eftir <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">þessum leiðbeiningum</a>. Annars ættirðu að setja upp eitthvað kerfisbakkaforrit á borð við 'trayer' og reyna síðan aftur. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3 %4%8%9Libraries Qt %5, %6%8Using virtual files plugin: %7%8 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - - - - - Replaced by virtual file - - - - - Downloaded - Niðurhalað - - - - Uploaded - Hlaðið inn - - - - Server version downloaded, copied changed local file into conflict file - Sótti útgáfu þjóns, afritaði breytta staðværa skrá yfir í árekstraskrá - - - - Deleted - Eytt - - - - Moved to %1 - Fært í %1 - - - - Ignored - Hunsað - - - - Filesystem access error - Villa við aðgang að skráakerfi - - - - Error - Villa - - - - Updated local metadata - Uppfærði staðvær lýsigögn - - - - - Unknown - Óþekkt - - - - downloading - sæki - - - - uploading - sendi inn - - - - deleting - eyði - - - - moving - færi - - - - ignoring - hunsa - - - - - error - villa - - - - updating local metadata - uppfæri staðvær lýsigögn - - - - theme - - - Status undefined - Óskilgreind staða - - - - Waiting to start sync - Bíð eftir að samstilling ræsist - - - - Sync is running - Samstilling er keyrandi - - - - Sync Success - Samstilling tókst - - - - Sync Success, some files were ignored. - Samstilling tókst, einhverjar skrár voru hunsaðar. - - - - Sync Error - Samstillingarvilla - - - - Setup Error - Villa í uppsetningu - - - - Preparing to sync - Undirbý samstillingu - - - - Aborting... - Hætti við... - - - - Sync is paused - Samstilling er í bið - - - - utility - - - Could not open browser - Gat ekki opnað vafra - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - Villa átti sér stað við ræsingu á vafranum þegar opna átti slóðina %1. Er hugsanlegt að ekki sé búið að stilla neinn vafra sem sjálfgefinn? - - - - Could not open email client - Gat ekki opnað tölvupóstforrit - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Villa átti sér stað við ræsingu á póstforritinu þegar útbúa átti ný skilaboð. Er hugsanlegt að ekki sé búið að stilla neitt tölvupóstforrit sem sjálfgefið? - - - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - - - - - Make always available locally - - - - - Free up local space - - - - \ No newline at end of file diff --git a/translations/client_it.ts b/translations/client_it.ts index f4f6dae91e5..90d87fde5d3 100644 --- a/translations/client_it.ts +++ b/translations/client_it.ts @@ -1,226 +1,381 @@ - + - CrashReporter + AccountBar - - Tomahawk Crash Reporter + + Navigation bar + Barra di navigazione + + + + Add Account + Aggiungi account + + + + Activity + Attività + + + + Settings + Impostazioni + + + + Quit + Esci + + + + AppImageUpdateAvailableWidgetUi + + + Skip this version + Salta questa versione + + + + AppProvider + + + Failed to open %1 in web. Error: %2. + Impossibile aprire %1 su interfaccia web. Errore: %2. + + + + ChecksumHeader + + + The checksum header is malformed: %1 + L'intestazione del checksum non è corretta: %1 + + + + The checksum header contained an unknown checksum type '%1' + L'intestazione di controllo conteneva un tipo di codice di controllo '%1' sconosciuto + + + + CommandLine + + + %1 version %2 +File synchronization desktop utility. + %1 versione %2<byte value="xd"/> +Utilità desktop per la sincronizzazione dei file. + + + + For more information, see %1 + link to homepage + Per informazioni aggiuntive, vedi %1 + + + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Avvia con la finestra principale visibile, o se è già avviata la porta in primo piano. Di default il client viene lanciato in background. + + + + Quit the running instance. + Esci dall'istanza in esecuzione. + + + + Write log to file (use - to write to stdout). + Scrivi il registro su file (usa - per scrivere su stdout). + + + + Write each sync log output in a new file in folder. + Scrivi ogni output del log di sincronizzazione in un nuovo file nella cartella. + + + + Flush the log file after every write. + Svuota il file di log dopo ogni scrittura. + + + + Output debug-level messages in the log. + Genera messaggi a livello di debug nel log. + + + + Enable debug mode. + Abilita la modalità di debug. + + + + --logfile and --logdir are mutually exclusive + --logfile e --logdir si escludono a vicenda + + + + CommonStrings + + + file manager + gestore file + + + + Show in %1 + Mostra in %1 + + + + Show in web browser + Mostra nel browser + + + + Copy + Copia + + + + %n Filter(s) + %n Filtro%n Filtri%n Filtri + + + + folder + cartella + + + + space - - <html><head/><body><p><br/></p></body></html> + + folders + cartelle + + + + spaces - - Abort - Interrompi + + Folder + Cartella - - Send this report + + Space + + + FetchUserInfoJobFactory + + + Failed to retrieve user info + Impossibile recuperare le informazioni dell'utente + + + + FileSystem + + + + + + Can't rename %1, the file is currently in use + Impossibile rinominare %1, il file è al momento in uso + + + + Could not remove folder + Impossibile rimuovere la cartella + + + + FolderItem + + + + Offline + Offline + - - Don't send + + + Unavailable: the space is no longer available on the server - - You can disable sending crash reports in the configuration dialog. + + + Sync paused - - - - We cannot gather useful debug information on your system. + + Synced - - - - - - Close - Chiudi + + Sync failed + Sincronizzazione non riuscita - - Ready to send debug information (<a href="%1">view backtrace</a>). + + Sync pending - - Gathering debug information... + + Syncing %1 of %2 (%3 % - - Uploaded %L1 of %L2 KB. - + + , ⬇️ %1/s + , ⬇️ %1/s - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - + + , ⬆️ %1/s + , ⬆️ %1/s - - Failed to send crash info. + + %1, sync status: %2 + Accessible text, read out by a screen reader. + first argument is the displayed name of the folder. - FileSystem + FolderManagementUtils - - - - - Can't rename %1, the file is currently in use + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + Il percorso '%1' è troppo lungo. Abilita i percorsi lunghi nelle impostazioni di Windows o scegli una cartella diversa. + + + + Local folder %1 does not exist. + La cartella locale %1 non esiste. + + + + %1 is not a folder. - - Could not make directories in trash - Non posso creare cartelle nel cestino + + %1 is not readable. + %1 non è leggibile. + + + + %1 is not writable. + %1 non è scrivibile + + + FolderWizard - - - Could not move '%1' to '%2' - Impossibile muovere '%1' a '%2' + + <b>Error:</b> %1 + <b>Errore:</b> %1 - - Moving to the trash is not implemented on this platform - Spostare il cestino non è implementato in questa piattaforma + + <b>Warning:</b> %1 + <b>Avviso:</b> %1 - - Error removing '%1': %2 - Errore durante la rimozione di '%1': %2 + + <b>Error:</b><ul>%1</ul> + <b>Errore:</b><ul>%1</ul> - - Could not remove folder '%1' - Impossibile rimuovere la cartella '%1' + + <b>Warning:</b><ul>%1</ul> + <b>Attenzione:</b><ul>%1</ul> FolderWizardSourcePage - + Form Modulo - + Pick a local folder on your computer to sync Scegli una cartella locale sul tuo computer da sincronizzare - - &Choose... - S&cegli... + + Browse… + FolderWizardTargetPage - + Form Modulo - + Select a remote destination folder Seleziona una cartella remota di destinazione - + Create Folder Crea cartella - + Refresh Aggiorna - + Folders Cartelle - - - TextLabel - EtichettaTesto - - - - NotificationWidget - - - Form - Modulo - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - EtichettaTesto - - OCC::AboutDialog + OCC::AboutView - + About Informazioni - - TextLabel - EtichettaTesto - - - + Versions Versioni - - - About %1 - Informazioni su %1 - OCC::AbstractNetworkJob - + Connection timed out Connessione scaduta - + Unknown error: network reply was deleted Errore sconosciuto: la risposta di rete è stata eliminata - + Server replied "%1 %2" to "%3 %4" Il server ha risposto "%1 %2" a "%3 %4" @@ -228,711 +383,597 @@ OCC::Account - + %1@%2 %1@%2 - OCC::AccountSettings + OCC::AccountFoldersController - - Form - Modulo + + Choose what to sync + Scegli cosa sincronizzare + + + + + Sync now + + + + + Manage subfolder sync + - - Re-open Browser - Ri-apri browser + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + - - ... - ... + + Disable virtual file support? + Disabilitare supporto per file virtuali? - - Storage space: ... - Spazio di archiviazione: ... + + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. + +The only advantage of disabling virtual file support is that the selective sync feature will become available again. + +This action will abort any currently running synchronization. + Questa azione disabilita il supporto per i file virtuali. Di conseguenza, il contenuto delle cartelle attualmente contrassegnate come "disponibile solo online" verrà scaricato. + +L'unico vantaggio di disabilitare il supporto file virtuale è che la funzione di sincronizzazione selettiva sarà nuovamente disponibile. + +Questa azione interromperà qualsiasi sincronizzazione in corso. - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Le cartelle non marcate saranno <b>rimosse</b> dal file system locale e non saranno sincronizzate più con questo computer + + Disable support + Disabilita supporto - - Synchronize all - Sincronizza tutto + + Restart sync + Riavvia sincronizzazione - - Synchronize none - Non sincronizzare + + Resume sync + Riprendi la sincronizzazione - - Apply manual changes - Applica le modifiche manuali + + Pause sync + Sospendi la sincronizzazione - - Apply - Applica + + Deactivate virtual files + - - - - - Cancel - Annulla + + Activate virtual files + - - Connected with <server> as <user> - Connesso a <server> come <user> + + Internet connection is metered + La connessione Internet è a consumo - - No account configured. - Nessun account configurato. + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + La sincronizzazione è sospesa perché la connessione Internet è una connessione a consumo<p>Vuoi davvero forzare una sincronizzazione adesso? - - Remove - Rimuovi + + Sync Running + La sincronizzazione è in corso - - Account - Account + + The sync operation is running.<br/>Do you want to stop it? + L'operazione di sincronizzazione è in esecuzione.<br/>Vuoi fermarla? - - Choose what to sync - Scegli cosa sincronizzare + + Sync paused by user + Sincronizzazione interrotta dall'utente - - Force sync now - Forza ora la sincronizzazione + + Confirm Folder Sync Connection Removal + Conferma rimozione connessione di sincronizzazione cartelle - - Restart sync - Riavvia sincronizzazione + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Vuoi davvero fermare la sincronizzazione della cartella <i>%1</i>?</p><p><b>Nota:</b> ciò <b>non</b> eliminerà alcun file.</p> - - Remove folder sync connection + + Remove Folder Sync Connection Rimuovi connessione di sincronizzazione cartelle - - Folder creation failed - Creazione della cartella non riuscita + + + Cancel + Annulla + + + OCC::AccountFoldersView - - <p>Could not create local folder <i>%1</i>. - <p>Impossibile creare la cartella locale <i>%1</i>. + + Sync options menu + Menu delle opzioni di sincronizzazione - - Confirm Folder Sync Connection Removal - Conferma rimozione connessione di sincronizzazione cartelle + + %1 sync + This shows as 'Folder sync' or 'Space sync' + - - Remove Folder Sync Connection - Rimuovi connessione di sincronizzazione cartelle + + Manage your synced %1. + - - Disable virtual file support? - Disabilitare supporto per file virtuali? + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - Questa azione disabilita il supporto per i file virtuali. Di conseguenza, il contenuto delle cartelle attualmente contrassegnate come "disponibile solo online" verrà scaricato. - -L'unico vantaggio di disabilitare il supporto file virtuale è che la funzione di sincronizzazione selettiva sarà nuovamente disponibile. - -Questa azione interromperà qualsiasi sincronizzazione in corso. + + %1 list view + - - Disable support - Disabilita supporto + + Navigate the %1 list using the up and down arrows + - - Sync Running - La sincronizzazione è in corso + + %1 out of %2 %3 are synchronized + + + + OCC::AccountModalWidget - - The syncing operation is running.<br/>Do you want to terminate it? - L'operazione di sincronizzazione è in corso.<br/>Vuoi terminarla? + + + Form + Modulo + + + OCC::AccountPlaceholderWidget - - %1 in use - %1 in uso + + %1 logo + logo %1 - - %1 as <i>%2</i> - %1 come <i>%2</i> + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + - - Connected to %1. - Connesso a %1. + + Additional logo defined by the organization + + + + OCC::AccountView - - Server %1 is temporarily unavailable. - Il server %1 è temporaneamente non disponibile. + + Form + Modulo - - Server %1 is currently in maintenance mode. - Il Server %1 è attualmente in manutenzione + + Manage Account + Gestisci account - - Signed out from %1. - Disconnesso da %1. + + Preparing the account + Preparazione dell'account - - Connecting to %1... - Connessione con %1... + + Sync connections + Sincronizzazione delle connessioni - - No connection to %1 at %2. - Nessuna connessione a %1 su %2. + + Account options menu + Menu delle opzioni dell'account + + + OCC::AccountViewController - + + Log in Accedi - - There are folders that were not synchronized because they are too big: - Ci sono nuove cartelle che non sono state sincronizzate poiché sono troppo grandi: + + Reconnect + Riconnettiti - - There are folders that were not synchronized because they are external storages: - Ci sono nuove cartelle che non sono state sincronizzate poiché sono archiviazioni esterne: + + Remove + Rimuovi - - There are folders that were not synchronized because they are too big or external storages: - Ci sono nuove cartelle che non sono state sincronizzate poiché sono troppo grandi o archiviazioni esterne: + + Log out + Esci - + Confirm Account Removal Conferma rimozione account - + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> <p>Vuoi davvero eliminare la connessione all'account <i>%1</i>?</p><p><b>Nota:</b> ciò <b>non</b> eliminerà alcun file.</p> - + Remove connection Rimuovi connessione - - Open folder - Apri cartella + + Cancel + Annulla - - - Log out - Esci + + The server version %1 is unsupported! Proceed at your own risk. + La versione %1 del server non è supportata! Procedi a tuo rischio. - - Open local folder - Apri cartella locale + + Connected + Connesso - - Open folder in browser - Apri cartella nel browser + + Server is temporarily unavailable. + - - Resume sync - Riprendi la sincronizzazione + + Server is currently in maintenance mode. + - - Pause sync - Sospendi la sincronizzazione - - - - Availability - Disponibilità - - - - Disable virtual file support... - Disabilita supporto per file virtuali... - - - - Enable virtual file support%1... - Abilita supporto per file virtuali %1... - - - - (experimental) - (sperimentale) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Vuoi davvero fermare la sincronizzazione della cartella <i>%1</i>?</p><p><b>Nota:</b> ciò <b>non</b> eliminerà alcun file.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) di %2 in uso. Alcune cartelle, incluse quelle montate in rete o le cartelle condivise, potrebbero avere limiti diversi. - - - - %1 of %2 in use - %1 di %2 in uso - - - - Currently there is no storage usage information available. - Non ci sono informazioni disponibili sull'utilizzo dello spazio di archiviazione. - - - - The server version %1 is unsupported! Proceed at your own risk. - La versione %1 del server non è supportata! Procedi a tuo rischio. - - - - Obtaining authorization from the browser. - Ottenimento dell'autorizzazione dal browser. - - - - Server configuration error: %1 at %2. - Errore nella configurazione del server: %1 a %2 - - - - No %1 connection configured. - Nessuna connessione di %1 configurata. + + Signed out + Disconnesso - - - OCC::AccountState - - Url update requested for %1 + + Updating credentials… - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - + + Captive portal prevents connections to the server. + Il portale captive impedisce le connessioni al server. - - Change url permanently to %1 + + Sync is paused due to metered internet connection. - - Reject - + + Connecting… + Connessione in corso... - - Signed out - Disconnesso + + Server configuration error + Errore di configurazione del server - + Disconnected Disconnesso - - Connected - Connesso - - - - Service unavailable - Servizio non disponibile - - - - Maintenance mode - Modalità Manutenzione - - - - Network error - Errore di rete - - - - Configuration error - Errore di configurazione - - - - Asking Credentials - Richiesta credenziali - - - - Unknown account state - Stato dell'account sconosciuto + + Invalid connection status + - OCC::ActivityListModel + OCC::AccountsGuiController - - %1 %2 on %3 - %1 %2 su %3 + + + New account failure + - - Activity - Attività + + The information required to create a new account is incomplete. Please run the wizard again. + - - Account + + Accounts Account - - Time - Ora - - - - Local path + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. OCC::ActivitySettings - - - Server Activity - Attività del server - - - - Sync Protocol - Protocollo di sincronizzazione + + Local Activity + Attività Locali - + Not Synced Non sincronizzata - + Not Synced (%1) %1 is the number of not synced files. Non sincronizzata (%1) - OCC::ActivityWidget - - - Form - Modulo - - - - - - TextLabel - EtichettaTesto - - - - Filter - Filtro - - - - Server Activities - Attività del server - - - - Notifications - Notifiche - - - - %n notifications(s) for %1. - %n notifica per %1.%n notifiche per %1. - - - - %n notifications(s) for %1 and %2. - %n notifica per %1 e %2.%n notifiche per %1 e %2. - - - - New notifications for %1, %2 and other accounts. - Nuove notifiche per %1, %2 e altri account. - - - - Open the activity view for details. - Apri la vista attività per i dettagli. - + OCC::AdvancedSettingsPageController - - Copy to clipboard - Copia negli appunti + + Advanced settings + Impostazioni avanzate - - Show in file browser + + Sync and download options - - <br/>Account %1 does not have activities enabled. - <br/>L'account %1 non ha attività abilitate. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Continuare significherà <b>eliminare queste impostazioni</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Continuare significherà <b>ignorare queste impostazioni</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Alcune impostazioni erano state configurate in versioni più recenti di questo client ed usano funzioni non disponibili in questa.<br><br>%1<br><br>Il file di configurazione attuale è già stato copiato in <i>%2</i>. - - - - Quit - Esci - - - - Continue - Continua - - - - Error accessing the configuration file - Errore accedendo al file di configurazione + + Download location + - - There was an error while accessing the configuration file at %1. - Si è verificato un errore durante l'accesso al file di configurazione su %1. + + Sync and download files as you use them (saves hard drive space) + - - Quit %1 - Esci da %1 + + Automatically sync and download folders and files + - - %1 version %2 -File synchronization desktop utility. + + Sync and download folders manually - - For more information, see http://www.owncloud.org + + Download location on the local machine - - Show the settings dialog while starting. + + Browse… - - Quit the running instance. + + Browse for a download location - - Write log to file (use - to write to stdout). + + Invalid local download directory %1: %2 - - Write each sync log output in a new file in folder. + + your user directory may not be chosen as the sync root. - - Remove logs older than <hours> hours (to be used with --logdir). + + path is not located on a supported Apple File System. - - Flush the log file after every write. + + path must be absolute. - - Output debug-level messages in the log. + + selected path does not support using virtual file system. %1 - - - Override UI language. + + Select sync root + + + OCC::AppImageUpdateAvailableWidget - - Use the given configuration folder. - + + Installed version: %1 + Versione installata: %1 - - Enable debug mode. - + + Available update: %1 + Aggiornamento disponibile: %1 - - Virtual file system file to be opened (optional). - + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + È disponibile un aggiornamento per questa AppImage di %1. Vuoi installare questo aggiornamento? + +L'aggiornamento verrà eseguito in background e sovrascriverà il file AppImage corrente. È necessario riavviare l'app per completare l'aggiornamento. + + + OCC::Application - - [<vfs file>] - + + Quit + Esci + + + OCC::AuthSuccessPageController - - Invalid path passed to --confdir + + Application Name Logo %1 - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) + + You're all set! Open %1 and get started. - - Available translations: %1 + + Additional logo defined by the organization - OCC::AuthenticationDialog - - - Authentication Required - Autenticazione richiesta - + OCC::ButtonDelegate - - Enter username and password for '%1' at %2. - Digita nome utente e password per '%1' su %2. - - - - &User: - &Utente: + + %1 options button + - - &Password: - &Password: + + Menu button with %1 options. Use the space key to show the menu + OCC::ConnectionValidator - + No ownCloud account configured Nessun account ownCloud configurato. - - The configured server for this client is too old - Il server configurato per questo client è troppo datato - - - + Please update to the latest server and restart the client. Aggiorna all'ultima versione del server e riavvia il client. - + Authentication error: Either username or password are wrong. Errore di autenticazione: nome utente o password errati. - + timeout timeout - + + No account configured + + + + The provided credentials are not correct Le credenziali fornite non sono corrette + + + The configured server for this client is too old. + Il server configurato per questo client è troppo vecchio. + OCC::CredentialJob - + Failed to parse credentials %1 + Elaborazione delle credenziali %1 fallita + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + + + + + Server did not ask for authorization + Il server non ha richiesto l'autorizzazione + + + + Failed to determine authentication type: %1 + + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + + + + + Server error: PROPFIND reply is not XML formatted! Errore del server: La risposta di PROPFIND non è in formato XML! @@ -940,27 +981,22 @@ File synchronization desktop utility. OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 Errore durante l'apertura della cartella %1 - + Directory not accessible on client, permission denied Directory non accessibile sul client, autorizzazione negata - + Directory not found: %1 Cartella non trovata: %1 - - Filename encoding is not valid - La codifica del nome del file non è valida - - - + Error while reading directory %1 Errore durante la lettura della cartella %1 @@ -968,149 +1004,137 @@ File synchronization desktop utility. OCC::ExpandingHeaderView - + Reset column sizes - + Reimposta dimensioni colonne - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - La cartella locale %1 non esiste. + + server sent invalid content type: %1. + - - %1 should be a folder but is not. - %1 dovrebbe essere una cartella, ma non lo è. + + could not parse Capabilities from server. + - - %1 is not readable. - %1 non è leggibile. + + reply sent for different subject (server): %1 + + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 è stato rimosso. - + %1 has been updated. %1 names a file. %1 è stato aggiornato. - + %1 has been renamed to %2. %1 and %2 name files. %1 è stato rinominato in %2. - + %1 has been moved to %2. %1 è stato spostato in %2. - + %1 and %n other file(s) have been removed. - %1 e %n altro file sono stati rimossi.%1 e %n altri file sono stati rimossi. + %1 e %2 altri file sono stati rimossi.%1 e %2 altri file sono stati rimossi.%1 e %2 altri file sono stati rimossi. - - %1 is not writable. + + The folder has been disabled or removed from the server - + %1 and %n other file(s) have been added. - %1 e %n altri file(s) sono stati aggiunti.%1 e %n altri file(s) sono stati aggiunti. + %1 e %n altri file sono stati aggiunti.%1 e %n altri file sono stati aggiunti.%1 e %n altri file sono stati aggiunti. - + %1 has been added. %1 names a file. %1 è stato aggiunto. - + %1 and %n other file(s) have been updated. - %1 e %n altro file sono stati aggiornati.%1 e %n altri file sono stati aggiornati. + %1 e %2 altri file sono stati aggiornati.%1 e %2 altri file sono stati aggiornati.%1 e %2 altri file sono stati aggiornati. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 è stato rinominato in %2 e %n altro file sono stati rinominati.%1 è stato rinominato in %2 e %n altri file sono stati rinominati. + %1 è stato rinominato in %2 e %3 altri file sono stati rinominati.%1 è stato rinominato in %2 e %3 altri file sono stati rinominati.%1 è stato rinominato in %2 e %3 altri file sono stati rinominati. - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 è stato spostato in %2 e %n altro file sono stati spostati.%1 è stato spostato in %2 e %n altri file sono stati spostati. + %1 è stato spostato in %2 e %3 altri file sono stati spostati.%1 è stato spostato in %2 e %3 altri file sono stati spostati.%1 è stato spostato in %2 e %3 altri file sono stati spostati. - - %1 has and %n other file(s) have sync conflicts. - %1 e %n altro file hanno conflitti di sincronizzazione.%1 e %n altri file hanno conflitti di sincronizzazione. + + %1 and %n other file(s) have sync conflicts. + %1 e %n altro file hanno conflitti di sincronizzazione.%1 e %n altri file hanno conflitti di sincronizzazione.%1 e %n altri file hanno conflitti di sincronizzazione. - + %1 has a sync conflict. Please check the conflict file! %1 ha un conflitto di sincronizzazione. Controlla il file in conflitto! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - 1% e %n altro file non sono stati sincronizzati a causa di errori. Controlla il log per i dettagli.1% e %n altri file non sono stati sincronizzati a causa di errori. Controlla il log per i dettagli. + 1% e altri %2 file non sono stati sincronizzati a causa di errori. Controlla il log per i dettagli.1% e altri %2 file non sono stati sincronizzati a causa di errori. Controlla il log per i dettagli.1% e altri %2 file non sono stati sincronizzati a causa di errori. Controlla il log per i dettagli. - + %1 could not be synced due to an error. See the log for details. %1 non può essere sincronizzato a causa di un errore. Controlla il log per i dettagli. - + Sync Activity Sincronizza attività - - Could not read system exclude file - Impossibile leggere il file di esclusione di sistema - - - - A new folder larger than %1 MB has been added: %2. - - Una nuova cartella più grande di %1 MB è stata aggiunta: %2. - - - - - A folder from an external storage has been added. - - Una nuova cartella da un'archiviazione esterna è stata aggiunta. - - + + Switching VFS mode on folder '%1' + Cambio della modalità VFS sulla cartella '%1' - - Please go in the settings to select it if you wish to download it. - Vai nelle impostazioni e selezionala se vuoi scaricarla. + + Could not read system exclude file + Impossibile leggere il file di esclusione di sistema - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. La cattella %1 è stata creata ma esclusa dalla sincronizzazione in prevecedenza. I dati al suo interno non saranno sincronzzati. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. Il file %1 è stato creato ma escluso in precedenza dalla sincronizzazione. Non sarà quindi sincronizzato. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1122,3581 +1146,2398 @@ Ciò significa che il client di sincronizzazione potrebbe non inviare le modific %1 + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Tutti i file nella cartella sincronizzata '%1' sono stati eliminati sul server. -Queste eliminazioni verranno sincronizzate nella tua cartella locale, perdendo questi file a meno che tu non sia autorizzato al ripristino. -Se decidi di mantenere i file, saranno risincronizzati con il server se hai l'autorizzazione di farlo. -Se decidi di eliminarli, non ti saranno più disponibili, a meno che tu non sia il proprietario. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Tutti i file nella tua cartella locale sincronizzata '%1' sono stati eliminati. Queste eliminazioni verranno sincronizzate con il server, perdendo questi file a meno che non siano ripristinati. -Sei sicuro di voler sincronizzare queste azioni con il server? -Se è stato un errore e decidi di mantenere i tuoi file, verranno risincronizzati dal server. - - - - Remove All Files? - Vuoi rimuovere tutti i file? - - - - Remove all files - Rimuovi tutti i file + + There are unresolved conflicts. + Ci sono dei conflitti irrisolti. - - Keep files - Mantieni i file + + Sync error: %1 + OCC::FolderMan - + Could not reset folder state Impossibile ripristinare lo stato della cartella - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. È stato trovato un vecchio registro di sincronizzazione '%1', ma non può essere rimosso. Assicurati che nessuna applicazione lo stia utilizzando. - - (backup) - (copia di sicurezza) - - - - (backup %1) - (copia di sicurezza %1) - - - - Undefined State. - Stato non definito. - - - - Waiting to start syncing. - In attesa di iniziare la sincronizzazione. + + Unsupported filesystem + Filesystem non supportato - - Preparing for sync. - Preparazione della sincronizzazione. + + Account disconnected or paused + Account disconnesso o messo in pausa - - Sync is running. - La sincronizzazione è in corso. + + User triggered force sync + Sincronizzazione forzata dall'utente - - Sync was successful, unresolved conflicts. - La sincronia ha avuto successo, nessun conflitto. + + Folder is about to be removed + La cartella sta per essere rimossa - - Last Sync was successful. - L'ultima sincronizzazione è stata completata correttamente. + + (backup) + (copia di sicurezza) - - Setup Error. - Errore di configurazione. + + (backup %1) + (copia di sicurezza %1) - - User Abort. - Interrotto dall'utente. + + The selected path is not a folder. + - - Sync is paused. - La sincronizzazione è sospesa. + + The folder %1 is used in a folder sync connection. + - - %1 (Sync is paused) - %1 (La sincronizzazione è sospesa) + + Folder '%1' is already in use by application %2. + - - No valid folder selected! - Nessuna cartella valida selezionata! + + There is already a sync from the server to this local folder. + - - The folder %1 is used in a folder sync connection! + + The local folder %1 already contains a folder used in a folder sync connection. - - The selected path does not exist! - Il percorso selezionato non esiste! + + The local folder %1 is already contained in a folder used in a folder sync connection. + - - The selected path is not a folder! - Il percorso selezionato non è una cartella! + + No valid folder selected. + - - You have no permission to write to the selected folder! - Non hai i permessi di scrittura per la cartella selezionata! + + The selected path does not exist. + - - There is already a sync from the server to this local folder. Please pick another local folder! - Esiste già una sincronizzazione dal server a questa cartella locale. Seleziona un'altra cartella locale! + + You have no permission to write to the selected folder. + - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - La cartella locale %1 contiene già una cartella utilizzata in una connessione di sincronizzazione delle cartelle. Selezionane un'altra! + + Folder '%1' is already in use by another account. + La cartella '%1' è già utilizzata da un altro account. - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - La cartella locale %1 è già contenuta in una cartella utilizzata in una connessione di sincronizzazione delle cartelle. Selezionane un'altra! + + On macOS, only the Apple File System is supported. + In macOS, solo i file di sistema Apple sono supportati. + + + OCC::FolderModelController - - %1 Please pick another one! + + %1 options - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. + + Menu button with %1 options. Use the space key to pop the %2 options menu - OCC::FolderStatusDelegate + OCC::FolderWatcher - - Add Folder Sync Connection - Aggiungi connessioni di sincronizzazione cartelle + + The watcher did not receive a test notification. + Il watcher non ha ricevuto una notifica di prova. + + + OCC::FolderWatcherPrivate - - File - File + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Questo problema avviene di solito quando gli inotify watches sono esauriti, Controlla le FAQ per maggiori dettagli. - OCC::FolderStatusModel + OCC::FolderWizard - - You need to be connected to add a folder - Devi essere connesso per aggiungere una cartella + + Virtual files are not available for the selected folder + I file virtuali non sono disponibili per la cartella selezionata - - Click this button to add a folder to synchronize. - Fai clic su questo pulsante per aggiungere una cartella da sincronizzare. + + Add Folder Sync Connection + Aggiungi connessioni di sincronizzazione cartelle - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Add Sync Connection + Aggiungi connessione di sincronizzazione + + + OCC::FolderWizardLocalPath - - Error while loading the list of folders from the server. - Errore durante il caricamento dell'elenco delle cartelle dal server. + + Click to select a local folder to sync. + Fai clic per selezionare una cartella locale da sincronizzare. - - Virtual file support is enabled. - Il supporto per file virtuali è abilitato. + + Enter the path to the local folder. + Digita il percorso della cartella locale. - - Signed out - Disconnesso + + Select the local folder + Seleziona la cartella locale + + + OCC::FolderWizardSelectiveSync - - Fetching folder list from server... - Recupero dell'elenco delle cartelle dal server... + + Use virtual files instead of downloading content immediately + Usa i file virtuali invece di scaricare il contenuto immediatamente + + + OCC::GETFileJob - - There are unresolved conflicts. Click for details. - Ci sono conflitti irrisolti. Clicca per ulteriori dettagli. - - - - Checking for changes in remote '%1' - Controllo per cambiamenti in '%1' remoto + + Connection Timeout + Connessione scaduta - - Checking for changes in local '%1' - Controllo cambiamenti in '%1 ' locale + + No E-Tag received from server, check Proxy/Gateway + Nessun e-tag ricevuto dal server, controlla il proxy/gateway - - Reconciling changes - Riconciliazione dei cambiamenti + + We received a different E-Tag for resuming. Retrying next time. + Abbiamo ricevuto un e-tag diverso per il recupero. Riprova più tardi. - - , '%1' - Build a list of file names - , '%1' + + We received an unexpected download Content-Length. + Abbiamo ricevuto un inaspettato download Content-Length. - - '%1' - Argument is a file name - '%1' + + Server returned wrong content-range + Il server ha restituito un content-range errato + + + OCC::GraphApi::Space - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Sincronizzazione di %1 + + Personal + Personale - - - , - , + + Shares + Condivisioni + + + OCC::IgnoreListEditor - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - ricezione %1/s + + Ignored Files Editor + Editor dei file ignorati - - ↓ %1/s - ↓ %1/s + + Files Ignored by Patterns + File ignorati per modelli - - ↑ %1/s - ↑ %1/s + + Add + Aggiungi - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - invio %1/s + + Pattern + Modello - - Local folder: %1 - + + Allow Deletion + Consenti eliminazione - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 di %4) + + Remove + Rimuovi - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + I file e le cartelle corrispondenti a un modello non saranno sincronizzati. I cambiamenti avranno effetto alla prossima sincronizzazione delle cartelle. + +Gli elementi per i quali è consentita l'eliminazione, saranno eliminati se impediscono la rimozione di una cartella. Utile per i metadati. - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 rimanenti, %1 di %2, file %3 di %4 + + Could not open file + Impossibile aprire il file - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 di %2, file %3 di %4 + + Cannot write changes to '%1'. + Impossibile scrivere le modifiche in '%1'. - - file %1 of %2 - file %1 di %2 + + Add Ignore Pattern + Aggiungi modello Ignora - - Waiting... - Attendere... - - - - Waiting for %n other folder(s)... - In attesa di %n altra cartella...In attesa di %n altre cartelle... + + Add a new ignore pattern: + Aggiungi un nuovo modello di esclusione: - - Preparing to sync... - Preparazione della sincronizzazione... + + This entry is provided by the system at '%1' and cannot be modified in this view. + Questa voce è fornita dal sistema in '%1' e non può essere modificata in questa vista. - OCC::FolderWatcher + OCC::LocalActivityWidget - - The watcher did not receive a test notification. - Il watcher non ha ricevuto una notifica di prova. + + Filter + Filtro - - - OCC::FolderWatcherPrivate - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Questo problema avviene di solito quando gli inotify watches sono esauriti, Controlla le FAQ per maggiori dettagli. + + Local activity table + Tabella delle attività locali - - - OCC::FolderWizard - - Add Folder Sync Connection - Aggiungi connessioni di sincronizzazione cartelle + + + Account + Account - - Add Sync Connection - Aggiungi connessione di sincronizzazione + + Filter menu + Menu dei filtri - - - OCC::FolderWizardLocalPath - - Click to select a local folder to sync. - Fai clic per selezionare una cartella locale da sincronizzare. + + Actions menu + Menu azioni - - Enter the path to the local folder. - Digita il percorso della cartella locale. + + Sort ascending by %1 + Ordinamento crescente per %1 + + + + Sort descending by %1 + Ordinamento discendente per %1 - - Select the source folder - Seleziona la cartella di origine + + Retry sync + Riprova sincronizzazione - OCC::FolderWizardRemotePath + OCC::LogBrowser - - Create Remote Folder - Crea cartella remota + + Log Output + Risultato log - - Enter the name of the new folder to be created below '%1': - Digita il nome della nuova cartella da creare sotto a '%1': + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>I log contengono informazioni sensibili che non dovresti rendere disponibili pubblicamente</b></span></p></body></html> - - Folder was successfully created on %1. - La cartella è stata creata correttamente su %1. + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + Il client può scrivere i log di debug in una cartella temporanea. Questi log sono molto utili per diagnosticare i problemi. +Poiché i file di log possono diventare grandi, il client ne avvierà uno nuovo per ogni esecuzione di sincronizzazione e comprimerà quelli più vecchi. - - Authentication failed accessing %1 - Autenticazione non riuscita durante l'accesso a %1 + + Enable logging to temporary folder + Abilita la registrazione dei log nella cartella temporanea - - Failed to create the folder on %1. Please check manually. - Non è stato possibile creare la cartella su %1. Controlla manualmente. + + If enabled, logs will be written to: + Se abilitato i log saranno scritti in: - - Failed to list a folder. Error: %1 - Impossibile elencare una cartella. Errore: %1 + + C:/log + C:/log - - Choose this to sync the entire account - Selezionala per sincronizzare l'intero account + + Log Http traffic + Registra il traffico HTTP - - This folder is already being synced. - Questa cartella è già sincronizzata. + + Log files to keep: + File di log da mantenere: - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Stai già sincronizzando <i>%1</i>, che è la cartella superiore di <i>%2</i>. + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Queste impostazioni persistono tra i riavvii del client. +Tieni presente che l'utilizzo di qualsiasi opzione della riga di comando di registrazione sovrascriverà le impostazioni. - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Stai già sincronizzando <i>%1</i>, che è una sottocartella di <i>%2</i>. + + Open folder + Apri cartella - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Usa i file virtuali invece di scaricare il contenuto immediatamente %1 - + OCC::MainWindow - - (experimental) - (sperimentale) + + Hide + Nascondi - - Virtual files are not available for the selected folder + + Main toolbar for the application - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Avviso:</b> %1 - - - - <b>Warning:</b> - <b>Avviso:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Nessun e-tag ricevuto dal server, controlla il proxy/gateway - - - - We received a different E-Tag for resuming. Retrying next time. - Abbiamo ricevuto un e-tag diverso per il recupero. Riprova più tardi. - - - We received an unexpected download Content-Length. - Abbiamo ricevuto un inaspettato download Content-Length. + + Main toolbar + - - Server returned wrong content-range - Il server ha restituito un content-range errato + + + More + Altro - OCC::GETJob + OCC::MainWindowController - - Connection Timeout - Connessione scaduta + + Add account... + - - - OCC::GeneralSettings - - Form - Modulo + + Settings... + Impostazioni... - - General Settings - Impostazioni generali + + About... + Informazioni... - - Language - Lingua + + Quit + Esci - - Sync hidden files - Sincronizzati i file nascosti + + + Errors: %1 + - - Log Settings - Impostazioni Log + + Activity + Attività - - Advanced - Approval of folder sync (Non virtual file mode only) - + + Quit %1 + Esci da %1 - - Network - Rete + + Are you sure you want to quit %1? + Sei sicuro di voler terminare %1? + + + OCC::ModalWrapperWidget - - Updates - Aggiornamenti + + Close + Chiudi + + + OCC::Models - - stable - stabile + + %1 Filter: + %1 Filtro: - - beta - beta + + All + Tutti - - About - Informazioni + + Reset Filters + Reimposta filtri - - Advanced - Avanzate + + Status Filter: + Filtro stato: + + + OCC::NetworkSettings - - Use &Monochrome Icons in the system tray - Usa icone &Monocromatiche nella barra delle applicazioni + + Form + Modulo - - Ask for confirmation before synchronizing folders larger than - Chiedi conferma prima di scaricare cartelle più grandi di + + Pause synchronization when the Internet connection is metered + Sospende la sincronizzazione quando la connessione Internet è a consumo - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + Proxy Settings + Impostazioni proxy - - Ask for confirmation before synchronizing external storages - Chiedi conferma prima di sincronizzare storage esterni + + No Proxy + Nessun proxy - - &Launch on System Startup - Esegui a&ll'avvio del sistema + + Use system proxy + Usa proxy di sistema - - Show &Desktop Notifications - Mostra notifiche &del desktop + + Host + Host - - &Update Channel - &Canale di aggiornamento + + Proxy Hostname + Hostname del Proxy - - Edit &Ignored Files - Mod&ifica file ignorati + + : + : - - Show sync folders in &Explorer's Navigation Pane - Mostra le cartelle sincronizzate nel pannello di navigzoine di &Explorer + + Proxy Port Number + Numero della porta del Proxy - - S&how crash reporter - Mostra il rapporto di c&hiusura inattesa + + Proxy server requires authentication + Il server proxy richiede l'autenticazione - - &Restart && Update - &Riavvia e aggiorna + + Note: proxy settings have no effects for accounts on localhost + Nota: le impostazioni del proxy non hanno effetti per gli account su localhost - - You cannot disable autostart because system-wide autostart is enabled. - Non puoi disattivare l'avvio automatico perchè l'auto-avvio a livello di sistema è attivo. + + Specify proxy manually + Specifica manualmente il Proxy - - Change update channel? - Cambiare canale di aggiornamento? + + Proxy type + Tipo di Proxy - - Warning - Avviso + + + Hostname of proxy server + Nome host del server proxy - - Language changes require a restart of this application to take effect. - + + + Username for proxy server + Nome utente del server proxy - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Il canale di aggiornamento determina quali aggiornamenti del client saranno offerti da installare. Il canale "stabile" contiene solo aggiornamenti considerati affidabili, mentre le versioni nel canale "beta" possono contenere nuove funzionalità e correzioni, ma che non sono ancora state testate accuratamente. - -Nota che ciò seleziona solo da dove vengono presi gli aggiornamenti e che non c'è alcuna regressione: quindi tornare dal canale beta a quello stabile solitamente non può essere fatto immediatamente e significa attendere una versione stabile più recente della versione beta attualmente installata. + + + Password for proxy server + Password del server proxy - - Change update channel - Cambia canale di aggiornamento + + HTTP(S) proxy + Proxy HTTP(S) - - Cancel - Annulla + + SOCKS5 proxy + Proxy SOCKS5 + + + OCC::NewAccountBuilder - - unknown (%1) + + no error details are available - - (use default) + + Unable to connect to server during account creation: %1. - OCC::HttpCredentialsGui + OCC::NewAccountWizard - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Digita la password di %1:<br><br>Utente: '%2<br>Account: %3<br> + + Cancel Setup + Annulla la Configurazione - - Reading from keychain failed with error: '%1' - Lettura dal portachiavi non riuscita con errore: '%1' - - - - Enter Password - Digita password - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Fai clic qui</a> per richiedere una password dell'applicazione dall'interfaccia web. + + Do you really want to cancel the account setup? + Vuoi davvero annullare la configurazione dell'account? - OCC::IgnoreListEditor + OCC::NewAccountWizardController - - Ignored Files Editor - Editor dei file ignorati + + Add account… + - - Files Ignored by Patterns - File ignorati per modelli + + Back + Indietro - - Add - Aggiungi + + Advanced + Avanzate - - Pattern - Modello + + Sign in + Accedi - - Allow Deletion - Consenti eliminazione + + Open sign in again + + + + OCC::NewVersionAvailableWidget - - Remove - Rimuovi + + Skip this version + Salta questa versione - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - I file e le cartelle corrispondenti a un modello non saranno sincronizzati. I cambiamenti avranno effetto alla prossima sincronizzazione delle cartelle. - -Gli elementi per i quali è consentita l'eliminazione, saranno eliminati se impediscono la rimozione di una cartella. Utile per i metadati. - - - - Could not open file - Impossibile aprire il file - - - - Cannot write changes to '%1'. - Impossibile scrivere le modifiche in '%1'. - - - - Add Ignore Pattern - Aggiungi modello Ignora - - - - Add a new ignore pattern: - Aggiungi un nuovo modello di esclusione: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Questa voce è fornita dal sistema in '%1' e non può essere modificata in questa vista. - - - - OCC::IssuesWidget - - - Form - Modulo - - - - List of issues - Lista dei problemi - - - - Filter - Filtro - - - - There were too many issues. Not all will be visible here. - Ci sono stati troppi errori. Non saranno visibili tutti qui. - - - - There were conflicts. Check the documentation on how to resolve them. - Ci sono stati conflitti. Controlla la documentazione su come risolverli. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Ci sono stati conflitti. <a href="%1">Controlla la documentazione su come risolverli.</a> - - - - Account - Account - - - - OCC::LibCloudProvidersPrivate - - - Settings - Impostazioni - - - - OCC::LogBrowser - - - Log Output - Risultato log - - - - Enable logging to temporary folder - Abilita la registrazione dei log nella cartella temporanea - - - - Delete logs older than %1 hours - Elimina i log più vecchi di %1 ore - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">I log contengono informazioni sensibili che non dovresti rendere pubbliche</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Apri cartella - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Nuova versione disponibile - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Una nuova versione del client %1 è disponibile.</p><p><b>%2</b> è disponibile per lo scaricamento. La versione installata è %3.</p> - - - - - Skip this version - Salta questa versione - - - - Skip this time - Salta questa volta - - - - Get update - Ottieni l'aggiornamento - - - - Update Failed - Aggiornamento non riuscito - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Una nuova versione di %1 Client è disponibile ma l'aggiornamento è fallito. </p><p><b>%2</b> è stato scaricato. La versione installata e %3.</p> - - - - Ask again later - Chiedi ancora più tardi - - - - Restart and update - Riavvia e aggiorna - - - - Update manually - Aggiorna manualmente - - - - OCC::NetworkSettings - - - Form - Modulo - - - - Proxy Settings - Impostazioni proxy - - - - No Proxy - Nessun proxy - - - - Use system proxy - Usa proxy di sistema - - - - Specify proxy manually as - Specifica manualmente il proxy come - - - - Host - Host - - - - : - : - - - - Proxy server requires authentication - Il server proxy richiede l'autenticazione - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Ampiezza di banda in entrata - - - - - Limit to - Limita a - - - - - KBytes/s - KByte/s - - - - - No limit - Nessun limite - - - - - Limit to 3/4 of estimated bandwidth - Limita a 3/4 della banda stimata - - - - Upload Bandwidth - Ampiezza di banda in uscita - - - - - Limit automatically - Limita automaticamente - - - - Hostname of proxy server - Nome host del server proxy - - - - Username for proxy server - Nome utente del server proxy - - - - Password for proxy server - Password del server proxy - - - - HTTP(S) proxy - Proxy HTTP(S) - - - - SOCKS5 proxy - Proxy SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - Creato alle %1 - - - - Closing in a few seconds... - Chiusura tra pochi secondi... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 richiesta non riuscita alle %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' selezionato alle %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Messaggio di errore dal server: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Si è verificato un errore accedendo al 'token' endpoint: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Impossibile gestire la risposta JSON dal server: <br><em>%1</em> - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Errore di login</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Utente errato</h1><p>Sei entrato con l'utente <em>%1</em>, ma devi accedere con <em>%2</em>.<br>Disconnettiti da %3 in un'altra scheda, poi <a href='%4'>clicca qui</a> e accedi come utente %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Un nuovo aggiornamento di %1 è pronto - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Un nuovo aggiornamento di %1 sta per essere installato. L'aggiornamento potrebbe richiedere privilegi aggiuntivi durante l'avanzamento. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Impossibile scaricare l'aggiornamento. Fai clic <a href='%1'>qui</a> per scaricare l'aggiornamento manualmente. - - - - Could not check for new updates. - Impossibile verificare la presenza di nuovi aggiornamenti. - - - - Downloading %1. Please wait... - Scaricando %1. Attendi... - - - - %1 available. Restart application to start the update. - %1 è disponibile. Riavvia l'applicazione per iniziare l'aggiornamento. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - Controllo server degli aggiornamenti... - - - - Update status is unknown: Did not check for new updates. - Lo stato di aggiornamento è sconosciuto. Non è possibile verificare la presenza di nuovi aggiornamenti. - - - - No updates available. Your installation is at the latest version. - Non ci sono aggiornamenti disponibili. La tua installazione è aggiornata all'ultima versione. - - - - Update Check - Controllo aggiornamenti - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Connetti a %1 - - - - Setup local folder options - Configura le opzioni della cartella locale - - - - Connect... - Connetti... - - - - (experimental) - (sperimentale) - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Sincronizza la cartella '%1' - - - - Virtual files are not available for the selected folder - - - - - Error - Errore - - - - Local Sync Folder - Cartella locale di sincronizzazione - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - Posta &elettronica - - - - Connect to %1 - Connetti a %1 - - - - Enter user credentials - Digita le credenziali dell'utente - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Connetti a %1 - - - - Login in your browser - Accedi tramite il tuo browser - - - - Copy link to clipboard - Copia link negli appunti - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Connetti a %1 - - - - Setup %1 server - Configura il server %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Questo URL NON è sicuro poiché non è cifrato. -Non è consigliabile utilizzarlo. - - - - This url is secure. You can use it. - Questo URL è sicuro. Puoi utilizzarlo. - - - - &Next > - Ava&nti > - - - - Invalid URL - URL non valido - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Connessione a %1 su %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Tempo scaduto durante il tentativo di connessione a %1 su %2. - - - - Invalid URL - URL non valido - - - - Could not create local folder %1 - Impossibile creare la cartella locale %1 - - - - Error: %1 - Errore: %1 - - - - The folder creation resulted in HTTP error code %1 - La creazione della cartella ha restituito un codice di errore HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - La creazione della cartella remota non è riuscita poiché le credenziali fornite sono errate!<br/>Torna indietro e verifica le credenziali.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Creazione della cartella remota %1 non riuscita con errore <tt>%2</tt>. - - - - Folder rename failed - Rinomina della cartella non riuscita - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Impossibile rimuovere o creare una copia di sicurezza della cartella poiché la cartella o un file in essa contenuto è aperta in un altro programma. Chiudi la cartella o il file e premi Riprova o annulla la configurazione. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Procedura guidata di connessione di %1 - - - - Enable experimental feature? - Attivare la funzione sperimentale? - - - - Enable experimental placeholder mode - Abilita la modalità sperimentale placeholder - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Quando la modalità "file virtuali" è attiva nessun file verrà scaricato inizialmente. Invece, verrà creato un minuscolo file "%1" per ogni file esistente sul server. Il contenuto può essere scaricato aprendo questi file o usando il loro menù contestuale. - -La modalità "file virtuali" è esclusiva e vincolata alla sincronizzazione selettiva. Le cartelle attualmente non selezionate saranno spostate online e le tue impostazioni di sincronizzazione selettiva reimpostate. - -Passare a questa modalità interromperà qualsiasi sincronizzazione in corso - -Si tratta di una nuova modalità sperimentale. Se decidi di usarla, ti preghiamo di segnalare qualsiasi problema accada. - - - - Stay safe - Resta sicuro - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - I collegamenti simbolici non sono supportati dalla sincronizzazione. - - - - File is listed on the ignore list. - Il file è stato aggiunto alla lista ignorati. - - - - File names ending with a period are not supported on this file system. - I nomi del file che terminano con un punto non sono supportati su questo file system. - - - - File names containing the character '%1' are not supported on this file system. - I nomi del file che contengono il carattere '%1' non sono supportati su questo file system. - - - - File name contains at least one invalid character - Il nome del file contiene almeno un carattere non valido - - - - The file name is a reserved name on this file system. - Il nome del file è un nome riservato su questo file system. - - - - Filename contains trailing spaces. - Il nome del file contiene spazi alla fine. - - - - Filename is too long. - Il nome del file è troppo lungo. - - - - File/Folder is ignored because it's hidden. - Il file/cartella è ignorato poiché è nascosto. - - - - Stat failed. - Stat non riuscita. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Conflitto: versione del server scaricata, copia locale rinominata e non caricata - - - - The filename cannot be encoded on your file system. - Il nome del file non può essere codificato sul tuo file system. - - - - The filename is blacklisted on the server. - Il nome del file non è permesso su questo server. - - - - size - dimensione - - - - permissions - permessi - - - - etag - etag - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - Spostato a destinazione non consentita, elemento ripristinato - - - - Not allowed to remove, restoring - Rimozione non consentita, ripristino - - - - Ignored because of the "choose what to sync" blacklist - Ignorato in base alla lista nera per la scelta di cosa sincronizzare - - - - Not allowed because you don't have permission to add subfolders to that folder - Non consentito poiché non disponi dei permessi per aggiungere sottocartelle in quella cartella - - - - Not allowed because you don't have permission to add files in that folder - Non consentito poiché non disponi dei permessi per aggiungere file in quella cartella - - - - Not allowed to upload this file because it is read-only on the server, restoring - Il caricamento di questo file non è consentito poiché è in sola lettura sul server, ripristino - - - - Error while reading the database - Errore durante la lettura del database - - - - Server replied with an error while reading directory '%1' : %2 - Il sever ha riposto con un errore durante la lettura della cartella '%1' : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - Il download ridurrebbe lo spazio su disco al di sotto del limite - - - - Free space on disk is less than %1 - Lo spazio libero su disco è inferiore a %1 - - - - File was deleted from server - Il file è stato eliminato dal server - - - - The file could not be downloaded completely. - Il file non può essere scaricato completamente. - - - - The downloaded file is empty despite the server announced it should have been %1. - Il file scaricato è vuoto nonostante il server indicasse una dimensione di %1. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - Il file è stato modificato dal suo rilevamento - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Ripristino non riuscito: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - Impossibile eliminare il file %1, errore: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - impossibile creare la cartella %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Il file %1 non può essere rinominato in %2 a causa di un conflitto con il nome di un file locale - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - Errore ad impostare il pin state - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Codice HTTP errato restituito dal server. Atteso 204, ma ricevuto "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Codice HTTP errato restituito dal server. Atteso 201, ma ricevuto "%1 %2". - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Codice HTTP errato restituito dal server. Atteso 201, ma ricevuto "%1 %2". - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - Errore durante la scrittura dei metadati nel database - - - - Error setting pin state - Errore ad impostare il pin state - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Il file %1 non può essere caricato poiché esiste un altro file con lo stesso nome, ma con differenze tra maiuscole e minuscole - - - - File Removed - File rimosso - - - - Local file changed during syncing. It will be resumed. - Il file locale è stato modificato durante la sincronizzazione. Sarà ripristinato. - - - - Local file changed during sync. - Un file locale è cambiato durante la sincronizzazione. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - L'upload eccede di %1 la quota della cartella - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Il file locale è stato rimosso durante la sincronizzazione. - - - - Local file changed during sync. - Un file locale è cambiato durante la sincronizzazione. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - Codice di uscita inatteso dal server (%1) - - - - Missing File ID from server - File ID mancante dal server - - - - Missing ETag from server - ETag mancante dal server - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Il file locale è stato rimosso durante la sincronizzazione. + + Get update + Ottieni l'aggiornamento - - Local file changed during sync. - Un file locale è cambiato durante la sincronizzazione. + + Skip this time + Salta questa volta - OCC::PropagateUploadFileV1 + OCC::OAuth - - %1 the file is currently in use + + Successfully signed in - - The server did ask for a removed legacy feature(polling) + + Now, explore %1 on desktop. - - The local file was removed during sync. - Il file locale è stato rimosso durante la sincronizzazione. + + Error returned from the server: <em>%1</em> + Messaggio di errore dal server: <em>%1</em> - - Local file changed during sync. - Un file locale è cambiato durante la sincronizzazione. + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Si è verificato un errore accedendo al 'token' endpoint: <br><em>%1</em> - - The server did not acknowledge the last chunk. (No e-tag was present) - Il server non ha riconosciuto l'ultimo pezzo. (Non era presente alcun e-tag) + + Could not parse the JSON returned from the server: <br><em>%1</em> + Impossibile gestire la risposta JSON dal server: <br><em>%1</em> - - - OCC::ProtocolItemModel - - Time - Ora + + Unsupported token type: %1 + Tipo di token non supportato: %1 - - File - File + + The reply from the server did not contain all expected fields +:%1 + La risposta dal server non conteneva tutti i campi previsti +:%1 - - Folder - Cartella + + Unknown Error + Errore sconosciuto - - Issues + + + Login Error + Errore di accesso + + + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. - - Action - Azione + + Wrong user + Utente errato + + + OCC::OAuthPageController - - Size - Dimensione + + Application Name Logo %1 + - - Account - Account + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + - - Status - Stato + + Sign in URL + - - - OCC::ProtocolWidget - - Form - Modulo + + To copy the sign in URL to the clipboard, use the copy button + - - Local sync protocol - Protocollo di sincronizzazione locale + + Copy URL to sign in + - - Filter - Filtro + + Additional logo defined by the organization + - - Account - Account + + URL copied + - - Copy to clipboard - Copia negli appunti + + Copy URL + Copia l'URL - - Show in file browser + + Unable to connect to the login service. If the problem continues, please contact support. - - Show in web browser + + Please copy the authentication URL using the button above, and provide this information to support. - - Retry sync + + Failed to look up WebFinger instances: %1 - - - OCC::ProxyAuthDialog - - Proxy authentication required - Autenticazione proxy richiesta + + You are already connected to an account with these credentials. + - - Username: - Nome utente: + + Unable to retrieve capabilities from server. + - - Proxy: - Proxy: + + The server is not supported by this client. + - - The proxy server needs a username and password. - Il server proxy richiede un nome utente e una password. + + Error while trying to log in to OAuth2-enabled server. %1 + - - Password: - Password: + + Unable to open browser with provided URL. %1 + - - TextLabel - EtichettaTesto + + OAuth2 authentication requires a secured connection. %1 + - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Scegli cosa sincronizzare + + Authorization server unreachable. %1 + - OCC::SelectiveSyncWidget - - - Loading ... - Caricamento in corso... - + OCC::OCUpdater - - Deselect remote folders you do not wish to synchronize. - Deseleziona le cartelle remote che non desideri sincronizzare. + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Impossibile scaricare l'aggiornamento. Fai clic <a href='%1'>qui</a> per scaricare l'aggiornamento manualmente. - - Name - Nome + + Could not check for new updates. + Impossibile verificare la presenza di nuovi aggiornamenti. - - Size - Dimensione + + Downloading %1. Please wait… + - - - No subfolders currently on the server. - Attualmente non ci sono sottocartelle sul server. + + %1 installed successfully. Restart the application to finish installing the update. + %1 installato correttamente. Riavviare l'applicazione per completare l'installazione dell'aggiornamento. - - An error occurred while loading the list of sub folders. - Si è verificato un errore durante il caricamento dell'elenco delle sottocartelle. + + %1 available. Restart application to start the update. + %1 è disponibile. Riavvia l'applicazione per iniziare l'aggiornamento. - - - OCC::ServerNotificationHandler - - Dismiss - Annulla + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Nuovo %1 disponibile. Clicca <a href='%2'>qui</a> per scaricare manualmente il nuovo AppImage. - - - OCC::SettingsDialog - - Settings - Impostazioni + + New %1 available. Please use the system's update tool to install it. + Nuovo %1 disponibile. Utilizzare lo strumento di aggiornamento di sistema per installarlo. - - Activity - Attività + + Checking update server… + - - Add account - Aggiungi account + + Update status is unknown: Did not check for new updates. + Lo stato di aggiornamento è sconosciuto. Non è possibile verificare la presenza di nuovi aggiornamenti. - - - Quit %1 - Esci da %1 + + No updates available. Your installation is at the latest version. + Non ci sono aggiornamenti disponibili. La tua installazione è aggiornata all'ultima versione. + + + OCC::OwncloudPropagator - - Are you sure you want to quit %1? - Sei sicuro di voler terminare %1? + + File %1 is currently in use + Il file %1 è al momento utilizzato - - Account - Account + + vfs instance is not available. + + + + OCC::ProcessDirectoryJob - - MainWindow - + + Symbolic links are not supported in syncing. + I collegamenti simbolici non sono supportati dalla sincronizzazione. - - Hide - Nascondi + + File is listed on the ignore list. + Il file è stato aggiunto alla lista ignorati. - - - OCC::ShareDialog - - TextLabel - EtichettaTesto + + File names ending with a period are not supported on this file system. + I nomi del file che terminano con un punto non sono supportati su questo file system. - - share label - etichetta di condivisione + + File names containing the character '%1' are not supported on this file system. + I nomi del file che contengono il carattere '%1' non sono supportati su questo file system. - - Dialog - Finestra + + File name contains at least one invalid character + Il nome del file contiene almeno un carattere non valido - - ownCloud Path: - Percorso di ownCloud: + + The file name is a reserved name on this file system. + Il nome del file è un nome riservato su questo file system. - - %1 Sharing - Condivisione %1 + + Filename contains trailing spaces. + Il nome del file contiene spazi alla fine. - - %1 - %1 + + Filename is too long. + Il nome del file è troppo lungo. - - Folder: %2 - Cartella: %2 + + File/Folder is ignored because it's hidden. + Il file/cartella è ignorato poiché è nascosto. - - The server does not allow sharing - Il server non consente la condivisione + + Stat failed. + Stat non riuscita. - - Retrieving maximum possible sharing permissions from server... - Recupero di tutti i permessi di condivisione possibili dal server... + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Conflitto: versione del server scaricata, copia locale rinominata e non caricata - - The file can not be shared because it was shared without sharing permission. - Il file non può essere condiviso poiché è stato condiviso senza il permesso di condivisione. + + The filename cannot be encoded on your file system. + Il nome del file non può essere codificato sul tuo file system. - - Users and Groups - Utenti e Gruppi + + The filename is blacklisted on the server. + Il nome del file non è permesso su questo server. - - Public Links - Collegamenti pubblici + + + + + the VFS instance is missing. Unable to continue. + - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Condividi NuovoDocumento.odt + + server reported no %1 + Il server non ha riportato alcun %1 - - Upload only (File Drop) - Solo caricamento (drop di un file) + + + account was deleted. Unable to continue + - - Receive files from others without revealing the contents of the folder. - Ricevere files dagli altri senza rivelare il contenuto della cartella + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: Ignorato poiché il percorso è deselezionato - - Download / View / Edit - Scarica / Visiona / Modifica + + Moved to invalid target, restoring + Spostato a destinazione non consentita, elemento ripristinato - - Name: - Nome: + + Not allowed to remove, restoring + Rimozione non consentita, ripristino - - Enter name of new share... - Digita il nome della nuova condivisione: + + Error while reading the database. + - - &Create - &Crea + + account was deleted. Unable to continue. + - - TextLabel - EtichettaTesto + + Not allowed because you don't have permission to add subfolders to that folder + Non consentito poiché non disponi dei permessi per aggiungere sottocartelle in quella cartella - - Set &password - Imposta &password + + Error while doing a rename, unhandled status code: %1 + Errore durante la ridenominazione, codice di stato non gestito: %1 - - Set &expiration date - Imposta data di scad&enza + + Not allowed because you don't have permission to add files in that folder + Non consentito poiché non disponi dei permessi per aggiungere file in quella cartella - - Set password - imposta la password + + Not allowed to upload this file because it is read-only on the server, restoring + Il caricamento di questo file non è consentito poiché è in sola lettura sul server, ripristino - - Recipients can view or download contents. - I destinatari possono vedere o scaricare contenuti + + This Space is currently unavailable + Questo Spazio è momentaneamente indisponibile - - Download / View - Scarica / visiona + + Server replied with an error while reading directory '%1' : %2 + Il sever ha riposto con un errore durante la lettura della cartella '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - I destinatari possono vedere, scaricare, modificare, cancellare ed inviare contenuti + + Error updating metadata: %1 + Errore durante l'aggiornamento dei metadati: %1 - - Anyone with the link has access to the file/folder - Chiunque sia in possesso del collegamento ha accesso al file/cartella + + %1 the folder is currently in use + %1 la cartella è al momento in uso + + + OCC::PropagateDownloadFile - - - P&assword protect - Proteggi con p&assword + + Failed to free up space, the file %1 is currently in use + Impossibile liberare spazio, il file %1 è attualmente in uso - - The file can not be shared because it was shared without sharing permission. - Il file non può essere condiviso poiché è stato condiviso senza il permesso di condivisione. + + + File %1 can not be downloaded because of a local file name clash with %2! + Il file %1 non può essere scaricato a causa di un conflitto con il nome del file locale %2! - - Link shares have been disabled - La condivisione tramite link è stata disabilitata + + The download would reduce free local disk space below the limit + Il download ridurrebbe lo spazio su disco al di sotto del limite - - Create public link share - Crea collegamento pubblico + + Free space on disk is less than %1 + Lo spazio libero su disco è inferiore a %1 - - - Delete - Elimina + + File was deleted from server + Il file è stato eliminato dal server - - Open link in browser - Apri collegamento nel browser + + Broken webserver returned empty content length for non-empty file on resume + Il server web danneggiato ha restituito una lunghezza di contenuto vuota per un file non vuoto durante la ripresa del download - - Copy link to clipboard - Copia link negli appunti + + The file could not be downloaded completely. + Il file non può essere scaricato completamente. - - Copy link to clipboard (direct download) - Copia negli appunti (download diretto) + + The downloaded file is empty despite the server announced it should have been %1. + Il file scaricato è vuoto nonostante il server indicasse una dimensione di %1. - - Send link by email - Invia collegamento tramite email + + File %1 cannot be saved because of a local file name clash with %2! + Impossibile salvare il file %1 per un conflitto di nome del file locale %2! - - Send link by email (direct download) - Invia link tramite email (download diretto) + + Error updating metadata: %1 + Errore durante l'aggiornamento dei metadati: %1 - - Create new... - Crea nuovo... + + + + + The file %1 is currently in use + Il file %1 è al momento utilizzato - - I shared %1 with you - Ho condiviso %1 con te + + + File has changed since discovery + Il file è stato modificato dal suo rilevamento + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - Conferma la cancellazione del link di condivisione + + ; Restoration Failed: %1 + ; Ripristino non riuscito: %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Vuoi veramente eliminare il link di condivisione pubblico <i>%1</i>?</p><p>Nota: questa azione non può essere annullata.</p> + + could not delete file %1, error: %2 + Impossibile eliminare il file %1, errore: %2 - - Cancel - Annulla + + Can not create local folder %1 because of a local file name clash with %2 + Impossibile creare la cartella locale %1 per un conflitto di nome del file locale %2! - - - Public link - Collegamento pubblico + + Error updating metadata: %1 + Errore durante l'aggiornamento dei metadati: %1 - - Delete link share - Cancella link condiviso + + The file %1 is currently in use + Il file %1 è al momento utilizzato - - - Please Set Password - imposta la password + + could not create folder %1 + impossibile creare la cartella %1 - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove - - Share NewDocument.odt - Condividi NuovoDocumento.odt + + %1 failed with: %2 + %1 fallito con: %2 - - Share with users or groups ... - Condividi con utenti o gruppi... + + Could not remove %1 because of a local file name clash with %2! + Impossibile rimuovere %1 per un conflitto di nome del file locale %2! - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - + + Could not move '%1' to the trash bin + Impossibile spostare '%1' nel cestino + + + + + %1 the file is currently in use + %1 il file è al momento in uso + + + OCC::PropagateLocalRename - - The item is not shared with any users or groups - L'elemento non è condiviso con nessun utente o gruppo + + File %1 can not be renamed to %2 because of a local file name clash + Il file %1 non può essere rinominato in %2 a causa di un conflitto con il nome di un file locale - - Open link in browser - Apri collegamento nel browser + + Could not rename %1 to %2, the file is currently in use + Impossibile rinominare %1 in %2, il file è al momento in uso - - Copy link to clipboard - Copia link negli appunti + + Error updating metadata: %1 + Errore durante l'aggiornamento dei metadati: %1 - - Send link by email - Invia collegamento tramite email + + The file %1 is currently in use + Il file %1 è al momento utilizzato - - No results for '%1' - Nessun risultato per '%1' + + Failed to rename file + Impossibile rinominare il file - - I shared something with you - Ho condiviso qualcosa con te + + Error setting pin state + Errore ad impostare il pin state - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Modulo + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Codice HTTP errato restituito dal server. Atteso 204, ma ricevuto "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - EtichettaTesto + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Codice HTTP errato restituito dal server. Atteso 201, ma ricevuto "%1 %2". - - can edit - può modificare + + Error writing metadata to the database: %1 + Errore durante la scrittura dei metadati nel database: %1 - - can share - può condividere + + Setting file status failed due to file lock + L'impostazione dello stato del file non è riuscita a causa del blocco del file + + + OCC::PropagateRemoteMove - - ... - ... + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Codice HTTP errato restituito dal server. Atteso 201, ma ricevuto "%1 %2". - - create - crea + + Error updating metadata: %1 + Errore durante l'aggiornamento dei metadati: %1 - - change - cambia + + The file %1 is currently in use + Il file %1 è al momento utilizzato - - delete - elimina + + Error writing metadata to the database + Errore durante la scrittura dei metadati nel database - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Error setting pin state + Errore ad impostare il pin state - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Condividi con %1 + + Could not update file : %1 + Impossibile aggiornare il file : %1 - - Context menu share - Menu contestuale condivisione + + The file %1 is currently in use + Il file %1 è al momento utilizzato + + + OCC::PropagateUploadCommon - - Context menu share %1 - Menu contestuale condivisione %1 + + Local file changed during sync. It will be resumed. + File locale modificato durante la sincronizzazione. Sarà ripreso. - - Confirm deletion - Conferma l'eliminazione + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + Il file %1 non può essere caricato poiché esiste un altro file con lo stesso nome, ma con differenze tra maiuscole e minuscole - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Vuoi eliminare definitivamente la cartella <i>%1</i> e tutto ciò che contiene? + + + Upload of %1 exceeds the quota for the folder + L'upload eccede di %1 la quota della cartella - - Do you want to delete the file <i>%1</i> permanently? - Vuoi eliminare definitivamente il file <i>%1</i>? + + + %1 the file is currently in use + %1 il file è al momento in uso - - Select new location... - Seleziona una nuova locazione... + + File Removed + File rimosso - - Error - Errore + + The server did not provide the file permissions + Il server non ha fornito le autorizzazioni per il file - - Moving file failed: - -%1 - Spostamento del file non riuscito: - -%1 + + Error updating metadata: %1 + Errore durante l'aggiornamento dei metadati: %1 + + + OCC::PropagateUploadFile - - I shared something with you - Ho condiviso qualcosa con te + + %1 the file is currently in use + %1 il file è al momento in uso - - - Share... - Condividi... + + The server did ask for a removed legacy feature(polling) + Il server ha richiesto la rimozione di una funzionalità legacy (polling) - - - Copy private link to clipboard - Copia link privato negli appunti + + The local file was removed during sync. + Il file locale è stato rimosso durante la sincronizzazione. - - Send private link by email... - Invia link privato per email + + The server did not acknowledge the file upload. (No e-tag was present) + + + + OCC::PropagateUploadFileTUS - - Resharing this file is not allowed - Ricondividere questo file non è permesso + + %1 the file is currently in use + %1 il file è al momento in uso - - Resharing this folder is not allowed - Ricondividere questa cartella non è permesso + + Checksum computation failed + Calcolo del checksum fallito - - Create and copy public link to clipboard - + + The local file was removed during sync. + Il file locale è stato rimosso durante la sincronizzazione. + + + OCC::ProtocolItemModel - - Copy public link to clipboard - Copia link pubblico negli appunti + + Time + Ora - - Open in browser - Apri nel browser + + File + File - - Show file versions in browser - Mostra versioni del file nel browser + + Folder + Cartella - - Rename... - Rinomina... + + Issues + Problemi - - Rename and upload... - Rinomina e carica... + + Action + Azione - - Move and rename... - Sposta e rinomina... + + Size + Dimensione - - Move, rename and upload... - Sposta, rinomina e carica... + + Account + Account - - Delete local changes - Elimina le modifiche in locale + + Status + Stato + + + OCC::RequestAuthenticationController - - Move and upload... - Sposta e carica... + + IdP is unreachable. Contact your system administrator or try again later. + - - Delete - Elimina + + Authentication failed. + Autenticazione non riuscita. - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags + + Application Name Logo %1 - - Backup of %1 started + + Sign in required - - Failed to find %1 + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. - - Local path must be a an absolute path + + Sign in URL - - Failed to create temporary database + + To copy the sign in URL to the clipboard, use the copy button - - Backup of %1 succeeded + + Copy URL to sign in - - Failed to set success tag + + Additional logo defined by the organization - - Failed to create backup: %1 - + + Stay logged out + Rimani disconnesso - - Failed to file id tags - + + Sign in + Accedi - - Backup of %1 failed with: %2 + + URL copied - - %1 backup - + + Copy URL + Copia l'URL - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Dettagli del certificato</h3> - + OCC::ResolveUrlAdapter - - Common Name (CN): - Nome comune (CN): + + SSL failure when connecting to server at %1 + - - Subject Alternative Names: - Nomi alternativi soggetto (SAN): + + Could not detect compatible server at %1 + Impossibile rilevare il server compatibile su %1 - - Organization (O): - Organizzazione (O): + + Resolved url is invalid %1 + - - Organizational Unit (OU): - Unità organizzativa (OU): + + Rejected redirect from %1 to %2 + + + + OCC::SelectiveSyncWidget - - State/Province: - Stato/Regione: + + Loading … + Caricamento in corso... - - Country: - Nazione: + + Deselect remote folders you do not wish to synchronize. + Deseleziona le cartelle remote che non desideri sincronizzare. - - Serial: - Numero di serie: + + Name + Nome - - <h3>Issuer</h3> - <h3>Emittente</h3> + + Size + Dimensione - - Issuer: - Emittente: + + + Currently there are no subfolders on the server. + Al momento non sono presenti sottocartelle sul server. - - Issued on: - Emesso il: + + An error occurred while loading the list of subfolders. + Si è verificato un errore durante il caricamento dell'elenco delle sottocartelle. + + + OCC::SettingsView - - Expires on: - Scade il: + + Form + Modulo - - <h3>Fingerprints</h3> - <h3>Impronte digitali</h3> + + General Settings + Impostazioni generali - - SHA-256: - SHA-256: + + Use Monochrome Icons in the system tray + Usa Icone Monocromatiche nella barra delle applicazioni - - SHA-1: - SHA-1: + + Show Desktop Notifications + Mostra le notifiche del desktop - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Nota:</b> questo certificato è stato approvato manualmente</p> + + Start on Login + Avvia al Login - - %1 (self-signed) - %1 (autofirmato) + + Language + Lingua - - %1 - %1 + + Language selector + Selettore della lingua - - This connection is encrypted using %1 bit %2. - - Questa connessione è cifrata utilizzando %1 bit %2. - + + Advanced + Avanzate - - Server version: %1 - Versione del server: %1 + + Sync hidden files + Sincronizzati i file nascosti - - No support for SSL session tickets/identifiers - Nessun supporto per i ticket/identificatori di sessione SSL + + Show crash reporter + Mostra il rapporto di chiusura inattesa - - Certificate information: - Informazioni sul certificato: + + Move remotely deleted files to the local trash bin instead of deleting them + Sposta i file eliminati in remoto nel cestino locale invece di eliminarli - - The connection is not secure - La connessione non è sicura + + Edit Ignored Files + Modifica file ignorati - - This connection is NOT secure as it is not encrypted. - - Questa connessione NON è sicura poiché non è cifrata. - + + Log Settings + Impostazioni Log - - - OCC::SslErrorDialog - - Form - Modulo + + Network + Rete - - Trust this certificate anyway - Fidati comunque di questo certificato + + Warning + Avviso - - Untrusted Certificate - Certificato non attendibile + + Language changes require a restart of this application to take effect. + Le modifiche della lingua richiedono un riavvio dell'applicazione per essere effettuate. - - Cannot connect securely to <i>%1</i>: - Impossibile collegarsi in modo sicuro a <i>%1</i>: + + You cannot disable autostart because system-wide autostart is enabled. + Non puoi disattivare l'avvio automatico perchè l'auto-avvio a livello di sistema è attivo. - - with Certificate %1 - con certificato %1 + + (use default) + (usa predefinito) - - - - &lt;not specified&gt; - &lt;non specificato&gt; + + unknown (%1) + sconosciuto (%1) + + + OCC::SocketApi - - - Organization: %1 - Organizzazione: %1 + + Share with %1 + parameter is the application name + Condividi con %1 - - - Unit: %1 - Reparto: %1 + + Confirm deletion + Conferma l'eliminazione - - - Country: %1 - Nazione: %1 + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Vuoi eliminare definitivamente la cartella <i>%1</i> e tutto ciò che contiene? - - Fingerprint (MD5): <tt>%1</tt> - Impronta digitale (MD5): <tt>%1</tt> + + Do you want to delete the file <i>%1</i> permanently? + Vuoi eliminare definitivamente il file <i>%1</i>? - - Fingerprint (SHA1): <tt>%1</tt> - Impronta digitale (SHA1): <tt>%1</tt> + + Error + Errore - - Effective Date: %1 - Data effettiva: %1 + + Moving file failed: + +%1 + Spostamento del file non riuscito: + +%1 - - Expiration Date: %1 - Data di scadenza: %1 + + I shared something with you + Ho condiviso qualcosa con te - - Issuer: %1 - Emittente: %1 + + Select new location… + - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - %1 (saltato per un errore, prova ancora tra %2) + + + Share… + - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Sono disponibili solo %1, servono almeno %2 per iniziare + + + Copy private link to clipboard + Copia link privato negli appunti - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Non posso aprire o creare il database locale di sincronizzazione. Assicurarsi di avere il i permessi di scrittura nella cartella di sincronizzazione + + Resharing this file is not allowed + Ricondividere questo file non è permesso - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Lo spazio su disco è scarso: I downloads che ridurranno lo spazio libero al di sotto di %1 verranno saltati. + + Resharing this folder is not allowed + Ricondividere questa cartella non è permesso - - There is insufficient space available on the server for some uploads. - Non c'è spazio disponibile sul server per effettuare caricamenti. + + Open in %1 + Apri in %1 - - Unresolved conflict. - Conflitto irrisolto. + + Show file versions in web browser + Mostra versioni del file nel browser - - Using virtual files with suffix, but suffix is not set + + Rename… - - Unable to read the blacklist from the local database - Impossibile leggere la lista nera dal database locale - - - - Unable to read from the sync journal. - Impossibile leggere dal registro di sincronizzazione. + + Rename and upload… + - - Cannot open the sync journal - Impossibile aprire il registro di sincronizzazione + + + Move and rename… + - - Aborted - Interrotto + + Move, rename and upload… + - - - OCC::SyncJournalDb - - Failed to connect database. + + Move and upload… - - - OCC::Systray - - %1: %2 - %1: %2 + + Delete local changes + Elimina le modifiche in locale - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - + + Delete + Elimina - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - L'intestazione del codice di controllo non è valida. - + OCC::SpacesPage - - The checksum header contained an unknown checksum type '%1' - L'intestazione di controllo conteneva un tipo di codice di controllo '%1' sconosciuto + + Select a folder to sync it to your computer. + Seleziona una cartella per sincronizzarla con il tuo computer. - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - + + Select a Space to sync it to your computer. + Seleziona uno spazio da sincronizzare con il tuo computer. - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - + OCC::SyncEngine - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - + + %1 (skipped due to earlier error, trying again in %2) + %1 (saltato per un errore, prova ancora tra %2) - - The Virtual filesystem feature is not supported on network drives + + Unable to sync: the local folder is missing. - - - OCC::ownCloudGui - - Please sign in - Accedi + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Sono disponibili solo %1, servono almeno %2 per iniziare - - Folder %1: %2 - Cartella %1: %2 + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Non posso aprire o creare il database locale di sincronizzazione. Assicurarsi di avere il i permessi di scrittura nella cartella di sincronizzazione - - Unresolved %1 conflicts + + + + + Cannot open the sync journal. - - Ignored errors %1 - + + Aborted due to %1 + Annullato a causa di %1 - - There are no sync folders configured. - Non è stata configurata alcuna cartella per la sincronizzazione. + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Lo spazio su disco è scarso: I downloads che ridurranno lo spazio libero al di sotto di %1 verranno saltati. - - Open in browser - Apri nel browser + + There is insufficient space available on the server for some uploads. + Non c'è spazio disponibile sul server per effettuare caricamenti. - - - - Log in... - Accedi... + + application exit + abort reason + uscita dall'applicazione - - - - Log out - Esci + + Unable to read the blacklist from the local database + Impossibile leggere la lista nera dal database locale - - Recent Changes - Modifiche recenti + + Unable to read from the sync journal. + Impossibile leggere dal registro di sincronizzazione. + + + OCC::SyncErrorWidget - - Managed Folders: - Cartelle gestite: + + Issues table + Tabella dei problemi - - Open folder '%1' - Apri la cartella '%1' + + There were too many issues. Not all will be visible here. + Ci sono stati troppi errori. Non saranno visibili tutti qui. - - Open %1 in browser - Apri %1 nel browser... + + There were conflicts. Check the documentation on how to resolve them. + Ci sono stati conflitti. Controlla la documentazione su come risolverli. - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - Il server sull'account %1 esegue una versione %2 non supportata. L'uso di questo client con versioni di server non supportate non è stato testato ed è potenzialmente pericoloso. Procedi a tuo rischio. + + The file %1 was ignored as its name is reserved by %2 + Il file %1 è stato ignorato in quanto il suo nome è riservato da %2 + + + + Reset column sizes + Reimposta dimensioni colonne - - Unknown status - Stato sconosciuto + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Ci sono stati conflitti. <a href="%1">Controlla la documentazione su come risolverli.</a> - - Settings... - Impostazioni... + + Filter menu + Menu dei filtri - - Details... - Dettagli... + + Account + Account - - Help - Aiuto + + Filter + Filtro + + + OCC::SyncJournalDb - - Quit %1 - Esci da %1 + + Failed to connect database. + Impossibile collegarsi al database. + + + OCC::Theme - - Disconnected from %1 - Disconnesso dal %1 + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + + + + OCC::TlsErrorDialog - - Unsupported Server Version - Versione del server non supportata + + TLS Certificate Error + Errore del certificato TLS - - Disconnected - Disconnesso + + Do you want to trust this certificate anyway? + Vuoi fidarti comunque di questo certificato? - - Disconnected from some accounts - Disconnesso da un account + + Cannot connect securely to %1 + Impossibile collegarsi in modo sicuro a %1 - - Disconnected from accounts: - Disconnesso dagli account: + + &lt;not specified&gt; + &lt;non specificato&gt; - - Account %1: %2 - Account %1: %2 + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>con Certificato %1</h3><div id="ccert"><p>Organizzazione: %2<br/>Unità: %3<br/>Paese: %4</p><p>Impronta (MD5): <tt>%5</tt><br/>Impronta (SHA1): <tt>%6</tt><br/>Impronta (SHA256): <tt>%7</tt><br/><br/>Data di Emissione: %8Data di Scadenza: %9</div><h3>Emittente: %10</h3><div id="issuer"><p>Organizzazione: %11<br/>Unità: %12<br/>Paese: %13</p></div></div> + + + OCC::TrayMenuController - - Signed out - Disconnesso + + Unsupported Server Version + Versione del server non supportata - - Account synchronization is disabled - La sincronizzazione dell'account è disabilitata + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + Il server sull'account %1 esegue una versione %2 non supportata. L'uso di questo client con versioni di server non supportate non è stato testato ed è potenzialmente pericoloso. Procedi a tuo rischio. - - Synchronization is paused - La sincronizzazione è in pausa + + Show %1 + Mostra %1 - - Up to date (%1) - + + Help + Aiuto - - No sync folders configured - Nessuna cartella configurata per la sincronizzazione + + About %1 + Informazioni su %1 - - Unpause all folders - Riprendi tutte le cartelle + + Quit %1 + Esci da %1 + + + OCC::UpdateDownloadedWidget - - Pause all folders - Sospendi tutte le cartelle + + Restart later + Riavvia dopo - - Unpause all synchronization - Riprendi tutte le sincronizzazioni + + Restart now + Riavvia adesso + + + OCC::UpdaterScheduler - - Unpause synchronization - Riprendi la sincronizzazione + + Update available + Aggiornamento disponibile + + + OCC::UrlPageController - - Pause all synchronization - Sospendi tutte le sincronizzazioni + + Your web browser will be opened to complete sign in. + - - Pause synchronization - Sospendi la sincronizzazione + + %1 logo + logo %1 - - Log out of all accounts - Disconnetti tutti gli account + + Welcome to %1 + Benvenuti in %1 - - Log in to all accounts... - Accedi a tutti gli account... + + Enter your server address to get started. Your web browser will be opened to complete sign in. + - - New account... - Nuovo account... + + Server address field + - - About %1 - Informazioni su %1 + + Enter your server address here + - - No items synced recently - Nessun elemento sincronizzato di recente + + Additional logo defined by the organization + - - Checking for changes in remote '%1' - Controllo per cambiamenti in '%1' remoto + + Invalid server URL: %1 + - - Checking for changes in local '%1' - Controllo cambiamenti in '%1 ' locale + + Invalid URL scheme. Only https is accepted. + - - Syncing %1 of %2 (%3 left) - Sincronizzazione di %1 di %2 (%3 rimanenti) + + The requested URL failed with query value: %1 + Il URL richiesto ha fallito con valuta query: %1 + + + OCC::UserInfoAdapter - - Syncing %1 of %2 - Sincronizzazione di %1 di %2 + + Network request to collect user info failed + - - Syncing %1 (%2 left) - Sincronizzazione di %1 (%2 rimanenti) + + Unable to retrieve user info: invalid credentials. + - - Syncing %1 - Sincronizzazione di %1 + + Unexpected network response when retrieving user info. + - - %1 (%2, %3) - %1 (%2, %3) + + Retrieving user info failed with JSON error. + - OwncloudAdvancedSetupPage + OCC::ValidateChecksumHeader - - Form - Modulo + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + Il file scaricato non corrisponde al checksum, verrà ripristinato. '%1'!= '%2' + + + OCC::Vfs - - - TextLabel - EtichettaTesto + + The Virtual filesystem feature does not support a drive as sync root + La funzionalità File system virtuale non supporta un'unità come radice di sincronizzazione - - Server - Server + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + La funzionalità File system virtuale richiede un file system NTFS, %1 utilizza %2 - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Se questa casella è marcata, il contenuto della cartella locale sarà cancellato per avviare una nuova sincronizzazione dal server.</p><p>Non marcarla se il contenuto locale deve essere caricato nella cartella del server.</p></body></html> + + The Virtual filesystem feature is not supported on network drives + La funzionalità del file system virtuale non è supportata sulle unità di rete + + + OCC::VfsWinPrivate - - Ask for confirmation before synchroni&zing folders larger than - Chiedi conferma prima di sincroni&zzare cartelle più grandi di + + + %1 - %2 + %1 - %2 - - S&ynchronize everything from server + + Could not find StorageProviderState property %1 - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Chiedi conferma prima di sincroni&zzare archiviazioni esterne - - - - Choose what to sync - Scegli cosa sincronizzare + + Could not find StorageFolder for %1 error: %2 + - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Quando questa opzione è selezionata, la procedura guidata si chiuderà senza sincronizzare nulla. Puoi usare il pulsante &quot;Aggiungi connessioni di sincronizzazione cartelle&quot; dalle impostazioni dell'account per scegliere quale coppia di cartelle locali e remote vuoi sincronizzare</p></body></html> + + Could not retrieve StorageFolder for %1 %2 (0x%3) + - - Manually create folder sync connections - Crea manualmente le connessioni di sincronizzazione cartelle + + The folder is used by a different client: %1 + - - &Local Folder - Carte&lla locale + + Error registering StorageProvider for %1: %2 (0x%3) + - - pbSelectLocalFolder - pbSelectLocalFolder + + Unable to connect sync root: %1 error: %2 + - - &Keep local data - &Mantieni i dati locali + + %1 is managed by another sync client + - - Start a &clean sync (Crreate a backup and erases the local folder!) + + Downgrading the folder from %1 to %2 is not supported - OwncloudHttpCredsPage - - - Form - Modulo - + OCC::WebFingerLookupAdapter - - &Username - Nome &utente + + Failed to retrieve user info + Impossibile recuperare le informazioni dell'utente - - &Password - &Password + + WebFinger lookup returned no links + - OwncloudOAuthCredsPage - - - Form - Modulo - + OCC::WindowsUpdater - - Please switch to your browser to proceed. - Prego ritorna al tuo browser per procedere. + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Una nuova versione del client %1 è disponibile.</p><p><b>%2</b> è disponibile per lo scaricamento. La versione installata è %3.</p> - - An error occurred while connecting. Please try again. - Errore di connessione. Prova tra un po'. + + Skip this version + Salta questa versione - - Re-open Browser - Ri-apri browser + + Update Failed + Aggiornamento non riuscito - - - OwncloudSetupPage - - Form - Modulo + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Una nuova versione di %1 Client è disponibile ma l'aggiornamento è fallito. </p><p><b>%2</b> è stato scaricato. La versione installata e %3.</p> - - Ser&ver Address - Indirizzo ser&ver + + Ask again later + Chiedi ancora più tardi - - https://... - https://... + + Restart and update + Riavvia e aggiorna - - Error Label - Etichetta di errore + + Update manually + Aggiorna manualmente QObject - + in the future nel futuro - + %n day(s) ago - %n giorno fa%n giorni fa + %d giorno fa%n giorni fa%n giorni fa - + %n hour(s) ago - %n ora fa%n ore fa + %n ora fa%n ore fa%n ore fa - + now adesso - - Less than a minute ago - Meno di un minuto fa + + less than a minute ago + meno di un minuto fa - + %n minute(s) ago - %n minuto fa%n minuti fa + %n minuto fa%n minuti fa%n minuti fa - - Some time ago - Tempo fa - - - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 + + SpacesBrowser + + + Form + Modulo + + + + SpacesPage + + + Spaces list + Elenco degli Spazi + + + + SpacesView + + + Spaces + Spazi + + SyncFileItem::Status - + Undefined - + Indefinito - + Fatal Error Errore grave - - + + Error Errore - + Info Informazioni - + Success Riuscito - + Conflict Conflitto - + File Ignored - + File ignorato - + Restored - + Riprisrinato - + Blacklisted Sulla lista nera - + Excluded - - - - - Utility - - - %L1 GB - %L1 GB + Escluso - - %L1 MB - %L1 MB + + Message + Messaggio - - %L1 KB - %L1 KB + + Filename Reserved + Nome File Riservato + + + UpdateDownloadedWidget - - %L1 B - %L1 B + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">È necessario riavviare</span></p></body></html> + + + Utility - + %n year(s) - % anno%n anni + %n anno%n anni%n anni - + %n month(s) - %n mese%n mesi + %n mese%n mesi%n mesi - + %n day(s) - %n giorno%n giorni + %n giorno%n giorni%n giorni - + %n hour(s) - %n ora%n ore + %n ora%n ore%n ore - + %n minute(s) - %n minuto%n minuti + %n minuto%n minuti%n minuti - + %n second(s) - %n secondo%n secondi + %n secondo%n secondi%n secondi - + %1 %2 %1 %2 + + + Shutting down %1 + Spegnimento di %1 + - main.cpp + account loading - - System Tray not available - Il vassoio di sistema non è disponibile + + Error accessing the configuration file + Errore accedendo al file di configurazione - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 richiede un vassoio di sistema. Se stai usando XFCE, segui <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">queste istruzioni</a>. Altrimenti, installa un'applicazione vassoio di sistema come 'trayer' e riprova. + + There was an error while accessing the configuration file at %1. + Si è verificato un errore durante l'accesso al file di configurazione su %1. ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8LibrerieQt %3, %4%7Utilizzo del plugin per file virtuali: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - + + %1 (Built against Qt %2) + %1 (Costruito su Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Sempre disponibile in locale + + + + Currently available locally + Attualmente disponibile in locale + + + + Some available online only + Alcuni disponibili solo online + + + + Available online only + Disponibile solo online + + progress @@ -4719,30 +3560,30 @@ Si tratta di una nuova modalità sperimentale. Se decidi di usarla, ti preghiamo Uploaded Caricato - - - Server version downloaded, copied changed local file into conflict file - Versione del server scaricata, il file locale modificato è stato copiato come file di conflitto - Deleted Eliminato - - Moved to %1 - Spostato in %1 - - - + Ignored Ignorato - - Filesystem access error - Errore di accesso al filesystem + + Server version downloaded, local copy was backed up as conflict file + Versione del server scaricata, è stato eseguito il backup della copia locale come file in conflitto + + + + Renamed %1 to %2 + + + + + Moved %1 to %2 + @@ -4755,153 +3596,86 @@ Si tratta di una nuova modalità sperimentale. Se decidi di usarla, ti preghiamo Metadati locali aggiornati - - + + Unknown Sconosciuto - + downloading scaricamento in corso - + uploading caricamento in corso - + deleting eliminazione - + moving spostamento - + ignoring ignorare - - + error errore - + updating local metadata metadati locali aggiornati - - theme - - - Status undefined - Stato non definito - - - - Waiting to start sync - In attesa di sincronizzazione - - - - Sync is running - La sincronizzazione è in corso - - - - Sync Success - Sincronizzazione completata - - - - Sync Success, some files were ignored. - Sincronizzazione avvenuta, alcuni file sono stati ignorati. - - - - Sync Error - Errore di sincronizzazione - - - - Setup Error - Errore di configurazione - - - - Preparing to sync - Preparazione della sincronizzazione - - - - Aborting... - Interruzione in corso... - - - - Sync is paused - La sincronizzazione è sospesa - - utility - + Could not open browser Impossibile aprire il browser - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? Si è verificato un errore all'avvio del browser per accedere all'URL% 1. Forse non è stato configurato nessun browser predefinito? - + Could not open email client Impossibile aprire il client di posta - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? Si è verificato un errore durante l'avvio del client di posta per creare un nuovo messaggio. Forse non hai ancora configurato alcun client di posta predefinito? - - Always available locally - Sempre disponibile in locale - - - - Some available online only - - - - - - Available online only - Disponibile solo online - - - - Currently available locally - Attualmente disponibile in locale - - - + Make always available locally Rendi sempre disponibile in locale - + Free up local space Libera spazio in locale + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Alcune impostazioni configurate nelle versioni più recenti di questo client utilizzano delle funzionalità che non sono disponibili in questa versione + + \ No newline at end of file diff --git a/translations/client_ja.ts b/translations/client_ja.ts index 03d31ba123a..93ddde8345c 100644 --- a/translations/client_ja.ts +++ b/translations/client_ja.ts @@ -1,4465 +1,3554 @@ - + - FileSystem + AccountBar - - Could not make directories in trash - + + Navigation bar + ナビゲーションバー - - - Could not move '%1' to '%2' - + + Add Account + アカウントを追加 - - Moving to the trash is not implemented on this platform - + + Activity + アクティビティ - - Error removing '%1': %2 - + + Settings + 設定 - - Could not remove folder '%1' - フォルダー '%1'を削除できません + + Quit + 終了 - FolderWizardSourcePage + AppImageUpdateAvailableWidgetUi - - Form - フォーム + + Skip this version + 今回のバージョンをスキップ + + + AppProvider - - Pick a local folder on your computer to sync - 同期するコンピューターのローカルフォルダーを選択してください + + Failed to open %1 in web. Error: %2. + "%1" をウェブサイトで開くことができませんでした。エラー: %2 + + + + ChecksumHeader + + + The checksum header is malformed: %1 + チェックサムヘッダが不正です: %1 - - &Choose... - 選択(&C)... + + The checksum header contained an unknown checksum type '%1' + チェックサムヘッダーに '%1' という未知のチェックサムが含まれていました - FolderWizardTargetPage + CommandLine - - Form - フォーム + + %1 version %2 +File synchronization desktop utility. + %1バージョン%2<byte value="xd"/> +ファイル同期デスクトップユーティリティ。 - - Select a remote destination folder - リモートの指定先フォルダーを選択 + + For more information, see %1 + link to homepage + 詳細については、%1 を参照してください。 - - Create Folder - フォルダーを作成 + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + メインウィンドウを可視状態で起動します。既に起動している場合は、最前面ウィンドウに変更します。クライアントはデフォルトでバックグラウンドプロセスとして起動します。 - - Refresh - 同期 + + Quit the running instance. + 実行中のインスタンスを終了します。 - - Folders - フォルダー + + Write log to file (use - to write to stdout). + ログをファイルに書き込みます(標準出力に書くには、 - を使用します) - - TextLabel - テキストラベル + + Write each sync log output in a new file in folder. + 各同期ログをフォルダ内の新規ファイルに書き出す。 - - - NotificationWidget - - Form - フォーム + + Flush the log file after every write. + 書き込みのたびにログファイルに出力する。 - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet + + Output debug-level messages in the log. + デバッグレベルのメッセージをログに出力する。 - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + Enable debug mode. + デバッグモードを有効にする - - TextLabel - テキストラベル + + --logfile and --logdir are mutually exclusive + --logfileと--logdir は互いに排他的です - OCC::AboutDialog + CommonStrings - - About - バージョン情報 + + file manager + ファイルマネージャ - - TextLabel - テキストラベル + + Show in %1 + %1 で表示 - - Versions - バージョン + + Show in web browser + ウェブブラウザで表示 - - About %1 - %1 について + + Copy + コピー + + + + %n Filter(s) + %n 個のフィルター - - - OCC::AbstractNetworkJob - - Connection timed out - 接続タイムアウト + + folder + フォルダー - - Unknown error: network reply was deleted - 未知のエラーです:ネットワーク応答が削除されました + + space + スペース - - Server replied "%1 %2" to "%3 %4" - サーバの返答は "%1 %2" から "%3 %4"です + + folders + フォルダー - - - OCC::Account - - %1@%2 - %1@%2 + + spaces + スペース - - - OCC::AccountSettings - - Form - フォーム + + Folder + フォルダー - - Re-open Browser - + + Space + スペース + + + FetchUserInfoJobFactory - - ... - + + Failed to retrieve user info + ユーザー情報の取得に失敗した + + + FileSystem - - Storage space: ... - ストレージ空き容量: ... + + + + + Can't rename %1, the file is currently in use + %1 の名前を変更できません、ファイルは現在使用中です - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - チェックしていないフォルダーはローカルファイルシステムから <b>削除</b>され、このコンピューターと同期されなくなります。 + + Could not remove folder + フォルダーを削除できませんでした + + + FolderItem - - Synchronize all - すべてを同期 + + + Offline + オフライン - - Synchronize none - 同期なし + + + Unavailable: the space is no longer available on the server + 利用不可: サーバー上の空き容量がなくなりました - - Apply manual changes - 手動による変更を適用 + + + Sync paused + 同期の一時停止 - - Apply - 適用 + + Synced + 同期済み - - - - - Cancel - キャンセル + + Sync failed + 同期の失敗 - - Connected with <server> as <user> - <server> に <user> で接続中 + + Sync pending + 同期保留中 - - No account configured. - アカウントが未設定です。 + + Syncing %1 of %2 (%3 % + 同期中 %2 件中 %1 ( %3 % - - Remove - 削除 + + , ⬇️ %1/s + , ⬇️ %1/秒 - - Account - アカウント + + , ⬆️ %1/s + , ⬆️ %1/秒 - - Choose what to sync - 同期フォルダーを選択 + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1、同期ステータス: %2 + first argument is the displayed name of the folder. + + + FolderManagementUtils - - Force sync now - 今すぐ強制的に同期 + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + パス '%1' が長すぎます。Windowsの設定で長いパスを有効にするか、別のフォルダーを選択してください。 - - Restart sync - 同期を再実行 + + Local folder %1 does not exist. + ローカルフォルダー %1 は存在しません。 - - Remove folder sync connection - 同期フォルダー接続を削除 + + %1 is not a folder. + %1 はフォルダではありません。 - - Folder creation failed - フォルダーの作成に失敗しました + + %1 is not readable. + %1 は読み込み可能ではありません。 - - <p>Could not create local folder <i>%1</i>. - <p>ローカルフォルダー <i>%1</i> を作成できません。 + + %1 is not writable. + %1 は書き込み可能ではありません。 + + + FolderWizard - - Confirm Folder Sync Connection Removal - 同期フォルダー接続の削除を確認 + + <b>Error:</b> %1 + <b>エラー:</b> %1 - - Remove Folder Sync Connection - 同期フォルダー接続を削除 + + <b>Warning:</b> %1 + <b>警告:</b> %1 - - Disable virtual file support? - + + <b>Error:</b><ul>%1</ul> + <b>エラー:</b><ul>%1</ul> - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - + + <b>Warning:</b><ul>%1</ul> + <b>警告:</b><ul>%1</ul> + + + FolderWizardSourcePage - - Disable support - + + Form + フォーム - - Sync Running - 同期を実行中 + + Pick a local folder on your computer to sync + 同期するコンピューターのローカルフォルダーを選択してください - - The syncing operation is running.<br/>Do you want to terminate it? - 同期作業を実行中です。<br/>終了しますか? + + Browse… + 閲覧… + + + FolderWizardTargetPage - - %1 in use - %1 を使用中 + + Form + フォーム - - %1 as <i>%2</i> - <i>%2</i> の %1 + + Select a remote destination folder + リモートの指定先フォルダーを選択 - - Connected to %1. - %1 に接続 + + Create Folder + フォルダーを作成 - - Server %1 is temporarily unavailable. - サーバー %1 は一時的に利用できません + + Refresh + 同期 - - Server %1 is currently in maintenance mode. - サーバー %1 は現在メンテナンスモードです。 + + Folders + フォルダー + + + OCC::AboutView - - Signed out from %1. - %1 からサインアウトしました。 + + About + バージョン情報 - - Connecting to %1... - %1 に接続中... + + Versions + バージョン + + + OCC::AbstractNetworkJob - - No connection to %1 at %2. - %2 の %1 への接続がありません。 + + Connection timed out + 接続タイムアウト - - Log in - ログイン + + Unknown error: network reply was deleted + 未知のエラーです:ネットワーク応答が削除されました - - There are folders that were not synchronized because they are too big: - 大きすぎるため同期されなかったフォルダーがあります: + + Server replied "%1 %2" to "%3 %4" + サーバの返答は "%1 %2" から "%3 %4"です + + + OCC::Account - - There are folders that were not synchronized because they are external storages: - 外部ストレージにあるため同期されなかったフォルダーがあります: + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - There are folders that were not synchronized because they are too big or external storages: - 大きすぎたか、外部ストレージにあるため同期されなかったフォルダーがあります: + + Choose what to sync + 同期フォルダーを選択 - - Confirm Account Removal - アカウント削除確認 + + + Sync now + 今すぐ同期 - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p> アカウント <i>%1</i> を本当に削除しますか?</p><p><b>注意:</b> これによりファイルが一切削除されることはありません。</p> + + Manage subfolder sync + サブフォルダーの同期を管理する - - Remove connection - 接続削除 + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + %1 同期を削除 - - Open folder - フォルダーを開く + + Disable virtual file support? + 仮想ファイルを無効にしますか? - - - Log out - ログアウト + + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. + +The only advantage of disabling virtual file support is that the selective sync feature will become available again. + +This action will abort any currently running synchronization. + この操作により、仮想ファイルが無効になります。その結果、現在「オンラインでのみ利用可能」とマークされているフォルダーのコンテンツがダウンロードされます。 + +仮想ファイルを無効にすることの唯一の利点は、選択的同期機能が再び利用可能になることです。 + +この操作を行うと、現在実行中の同期が中止されます。 - - Open local folder - + + Disable support + サポートを無効にする - - Open folder in browser - フォルダをブラウザで開く + + Restart sync + 同期を再実行 - + Resume sync 再開 - + Pause sync 一時停止 - - Availability - - - - - Disable virtual file support... - + + Deactivate virtual files + 仮想ファイルの無効化 - - Enable virtual file support%1... - + + Activate virtual files + 仮想ファイルの有効化 - - (experimental) - + + Internet connection is metered + インターネット接続は従量制です - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>フォルダー<i>%1</i>の同期を本当に止めますか?</p><p><b>注:</b> これによりファイルが一切削除されることはありません。</p> + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + インターネット接続が従量制プランのため、<p>同期は一時停止中です。強制的に同期を開始しますか?  - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %2 の %1(%3%) 利用中。外部ネットワークストレージや共有フォルダーを含むフォルダーがある場合は、容量の上限値が異なる可能性があります。 + + Sync Running + 同期を実行中 - - %1 of %2 in use - %2 のうち %1 を使用中 + + The sync operation is running.<br/>Do you want to stop it? + 同期ジョブが実行中です。<br/>停止しますか? - - Currently there is no storage usage information available. - 現在、利用できるストレージ利用状況はありません。 + + Sync paused by user + ユーザーにより同期が一時停止されました - - The server version %1 is unsupported! Proceed at your own risk. - + + Confirm Folder Sync Connection Removal + 同期フォルダー接続の削除を確認 - - Obtaining authorization from the browser. - + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>フォルダー<i>%1</i>の同期を本当に止めますか?</p><p><b>注:</b> これによりファイルが一切削除されることはありません。</p> - - Server configuration error: %1 at %2. - + + Remove Folder Sync Connection + 同期フォルダー接続を削除 - - No %1 connection configured. - %1 の接続は設定されていません。 + + + Cancel + キャンセル - OCC::AccountState - - - Signed out - サインアウト - + OCC::AccountFoldersView - - Disconnected - 切断しました + + Sync options menu + 同期オプションメニュー - - Connected - 接続しました + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 同期 - - Service unavailable - サービスが利用できません + + Manage your synced %1. + 同期された %1 を管理します。 - - Maintenance mode - メンテナンスモード + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + 新しい %1 同期を追加… - - Network error - ネットワークエラー + + %1 list view + %1 一覧表示 - - Configuration error - 設定エラー + + Navigate the %1 list using the up and down arrows + 上下矢印キーを使って %1 一覧を移動します。 - - Asking Credentials - 証明書を要求 + + %1 out of %2 %3 are synchronized + %2 のうち %1、%3 が同期されています + + + OCC::AccountModalWidget - - Unknown account state - 不明なアカウント状態 + + + Form + フォーム - OCC::ActivityItemDelegate + OCC::AccountPlaceholderWidget - - %1 on %2 - %2 中 %1 + + %1 logo + %1 ロゴ - - %1 on %2 (disconnected) - %2 中 %1 (切断しました) + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + - - - OCC::ActivityListModel - - %1 %2 on %3 + + Additional logo defined by the organization - OCC::ActivitySettings + OCC::AccountView - - - Server Activity - サーバーアクティビティ + + Form + フォーム - - Sync Protocol - 同期状況 + + Manage Account + アカウントの管理 - - Not Synced - 同期対象外 + + Preparing the account + アカウントを修復 - - Not Synced (%1) - %1 is the number of not synced files. - 未同期 (%1) + + Sync connections + 同期接続 - - The server activity list has been copied to the clipboard. - サーバーアクティビティリストをクリップボードにコピーしました。 + + Account options menu + アカウントオプションメニュー + + + OCC::AccountViewController - - The sync activity list has been copied to the clipboard. - 同期状況をクリップボードにコピーしました。 + + + Log in + ログイン - - The list of unsynced items has been copied to the clipboard. - 同期されていないアイテムのリストがクリップボードにコピーされました。 + + Reconnect + リコネクト - - Copied to clipboard - クリップボードにコピー + + Remove + 削除 - - - OCC::ActivityWidget - - Form - フォーム + + Log out + ログアウト - - - - TextLabel - テキストラベル + + Confirm Account Removal + アカウント削除確認 - - Server Activities - サーバーアクティビティ + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p> アカウント <i>%1</i> を本当に削除しますか?</p><p><b>注意:</b> これによりファイルが一切削除されることはありません。</p> - - Copy - コピー + + Remove connection + 接続削除 - - Copy the activity list to the clipboard. - アクティビティ一覧をコピーする + + Cancel + キャンセル - - Notifications - 通知 + + The server version %1 is unsupported! Proceed at your own risk. + サーバーバージョン %1 は非サポートです! 自己責任で進めてください。 - - - %n notifications(s) for %1. - + + + Connected + 接続しました - - - %n notifications(s) for %1 and %2. - + + + Server is temporarily unavailable. + - - New notifications for %1, %2 and other accounts. + + Server is currently in maintenance mode. - - Open the activity view for details. + + Signed out + サインアウト + + + + Updating credentials… + 証明書を更新しています… + + + + Captive portal prevents connections to the server. + キャプティブポータルは、サーバーへの接続を防ぎます。 + + + + Sync is paused due to metered internet connection. - - <br/>Account %1 does not have activities enabled. - <br/>%1 アカウントは、 アクティビティを有効にしていません。 + + Connecting… + 接続中 … + + + + Server configuration error + サーバー設定エラー - - - OCC::AddCertificateDialog - - SSL client certificate authentication - SSLクライアント証明書認証 + + Disconnected + 切断しました - - This server probably requires a SSL client certificate. - おそらく、このサーバーにはSSLクライアント証明書が必要です。 + + Invalid connection status + + + + OCC::AccountsGuiController - - Certificate & Key (pkcs12) : - 証明書と鍵 (pkcs12) : + + + New account failure + - - Browse... - 参照... + + The information required to create a new account is incomplete. Please run the wizard again. + - - Certificate password : - 証明書のパスワード: + + Accounts + アカウント - - An encrypted pkcs12 bundle is strongly recommended as a copy will be stored in the configuration file. + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + + + OCC::ActivitySettings + + + Local Activity + ローカルの利用履歴 + - - Select a certificate - 証明書を選択 + + Not Synced + 同期対象外 - - Certificate files (*.p12 *.pfx) - 証明書ファイル (*.p12 *.pfx) + + Not Synced (%1) + %1 is the number of not synced files. + 未同期 (%1) - OCC::Application + OCC::AdvancedSettingsPageController - - Continuing will mean <b>deleting these settings</b>. - + + Advanced settings + 詳細設定 - - Continuing will mean <b>ignoring these settings</b>. - + + Sync and download options + 同期とダウンロードのオプション - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - + + Download location + ダウンロード場所 - - Quit - 終了 + + Sync and download files as you use them (saves hard drive space) + ファイル使用時に同期・ダウンロードします (ハードディスク容量を節約できます) - - Continue - 続ける + + Automatically sync and download folders and files + フォルダとファイルを自動的に同期およびダウンロードします - - Error accessing the configuration file - 設定ファイルのアクセスでエラーが発生しました + + Sync and download folders manually + フォルダを手動で同期およびダウンロードする - - There was an error while accessing the configuration file at %1. - 設定ファイルの %1 行目にアクセスしている時にエラーが発生しました。 + + Download location on the local machine + ローカルマシン上のダウンロード場所 + + + + Browse… + 閲覧… + + + + Browse for a download location + ダウンロード場所を参照する + + + + Invalid local download directory %1: %2 + 無効なローカルダウンロードディレクトリ %1: %2 + + + + your user directory may not be chosen as the sync root. + ユーザーディレクトリが同期ルートとして選択されない場合があります。 + + + + path is not located on a supported Apple File System. + パスはサポートされているAppleファイルシステム上に存在しません。 + + + + path must be absolute. + パスは絶対パスでなければなりません。 + + + + selected path does not support using virtual file system. %1 + 選択されたパスは仮想ファイルシステムの使用をサポートしていません。 %1 + + + + Select sync root + 同期ルートを選択 + + + + OCC::AppImageUpdateAvailableWidget + + + Installed version: %1 + インストールされているバージョン: %1 - - Quit ownCloud - ownCloudを終了 + + Available update: %1 + 利用可能な更新: %1 + + + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + %1 の AppImage 更新が検出されました。インストールしますか?  + +AppImage の更新はバックグラウンドプロセスで実行され、現在のファイルは上書きされます。アップデートを完了するには、アプリを再起動してください。 - OCC::AuthenticationDialog + OCC::Application - - Authentication Required - 認証が必要 + + Quit + 終了 + + + OCC::AuthSuccessPageController - - Enter username and password for '%1' at %2. - %2 で '%1' に対してユーザー名とパスワードを入力してください。 + + Application Name Logo %1 + アプリケーション名ロゴ %1 - - &User: - ユーザー(&U): + + You're all set! Open %1 and get started. + 準備完了です! %1 を開いて始めましょう。 - - &Password: - パスワード(&P): + + Additional logo defined by the organization + - OCC::CleanupPollsJob + OCC::ButtonDelegate - - Error writing metadata to the database - メタデータのデータベースへの書き込みに失敗 + + %1 options button + + + + + Menu button with %1 options. Use the space key to show the menu + メニューボタンには %1 のオプションがあります。スペースキーを押すとメニューが表示されます OCC::ConnectionValidator - + No ownCloud account configured ownCloudアカウントが設定されていません - - The configured server for this client is too old - このクライアントのサーバー設定は古すぎます。 - - - + Please update to the latest server and restart the client. サーバーを最新にアップデートして、クライアントを再起動してください。 - + Authentication error: Either username or password are wrong. 認証エラー: ユーザー名またはパスワードが間違っています。 - + timeout タイムアウト - + + No account configured + + + + The provided credentials are not correct 入力された資格情報が正しくありません + + + The configured server for this client is too old. + このクライアントに設定されているサーバーが古すぎます。 + + + + OCC::CredentialJob + + + Failed to parse credentials %1 + 資格情報 %1 の解析に失敗しました。 + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + + + + + Server did not ask for authorization + サーバーが認証を要求しませんでした + + + + Failed to determine authentication type: %1 + + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + + OCC::DiscoverySingleDirectoryJob - - - Server error: PROPFIND reply is not XML formatted! + + The account was deleted before we could start the propfind job + + + + Server error: PROPFIND reply is not XML formatted! + サーバーエラー: PROPFIND の応答が XML フォーマットではありません! + OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 - + ディレクトリ %1 を開くときにエラーが発生しました。 - + Directory not accessible on client, permission denied - + クライアントでディレクトリにアクセスできない、パーミッションが拒否されました。 - + Directory not found: %1 - + ディレクトリが見つかりません: %1 - - Filename encoding is not valid - ファイル名のエンコーディングが無効です。 + + Error while reading directory %1 + ディレクトリ %1 の読み込み中にエラーが発生しました。 + + + OCC::ExpandingHeaderView - - Error while reading directory %1 - + + Reset column sizes + カラムサイズのリセット - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - ローカルフォルダー %1 は存在しません。 + + server sent invalid content type: %1. + - - %1 should be a folder but is not. - %1 はフォルダーのはずですが、そうではないようです。 + + could not parse Capabilities from server. + - - %1 is not readable. - %1 は読み込み可能ではありません。 + + reply sent for different subject (server): %1 + + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 は削除されました。 - + %1 has been updated. %1 names a file. %1 が更新されました。 - + %1 has been renamed to %2. %1 and %2 name files. %1 の名前が %2 に変更されました。 - + %1 has been moved to %2. %1 は %2 に移動しました。 - + %1 and %n other file(s) have been removed. - %1 とその他 %n 個のファイルが削除されました。 + %1 と他 %n 個のファイルが削除されました。 + + + + The folder has been disabled or removed from the server + - + %1 and %n other file(s) have been added. - + %1 と %n 個の他のファイルが追加されました。 - + %1 has been added. %1 names a file. - + %1 が追加されました。 - + %1 and %n other file(s) have been updated. - %1 とその他 %n 個のファイルが更新されました。 + %1 と他 %n 個のファイルが更新されました。 - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 を %2 にファイル名を変更し、その他 %n 個のファイル名を変更しました。 + %1 の名前が %2 に変更され、他 %n 個のファイルの名前が変更されました。 - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 を %2 に移動し、その他 %n 個のファイルを移動しました。 + %1 が %2 に移され、他 %n 個のファイルが移されました。 - - %1 has and %n other file(s) have sync conflicts. - %1 と その他 %n 個のファイルが同期で衝突しました。 + + %1 and %n other file(s) have sync conflicts. + %1 とその他 %n 個のファイルが同期で衝突しました。 - + %1 has a sync conflict. Please check the conflict file! %1 が同期で衝突しています。コンフリクトファイルを確認してください。 - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - エラーにより、%1 と その他 %n 個のファイルが同期できませんでした。ログで詳細を確認してください。 + エラーにより、%1 と %n 個の他のファイルを同期できませんでした。ログで詳細を確認してください。 - + %1 could not be synced due to an error. See the log for details. エラーにより %1 が未同期です。ログで詳細を確認してください。 - + Sync Activity 同期アクティビティ - - Could not read system exclude file - システム上の除外ファイルを読み込めません - - - - A new folder larger than %1 MB has been added: %2. - - %1 MB より大きな新しいフォルダーが追加されました: %2 - + + Switching VFS mode on folder '%1' + フォルダー '%1' の VFS モードを切り替えています - - A folder from an external storage has been added. - - 外部ストレージからフォルダーが追加されました。 - - - - - Please go in the settings to select it if you wish to download it. - このフォルダーをダウンロードするには設定画面で選択してください。 + + Could not read system exclude file + システム上の除外ファイルを読み込めません - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - + フォルダ %1 は作成されましたが、以前は同期対象から外れていました。フォルダ内のデータは同期されません。 - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - + ファイル %1 は作成されましたが、以前は同期対象から外れていました。ファイルは同期されません。 - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). %1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - サーバー上のフォルダー '%1' 内に存在するすべてのファイルが削除されました。 -これらはローカルに存在する同期フォルダーにも適用され、復元する権利がない限り、ファイルは使用できなくなります。 -「ファイルを残す」を選択すると、復元可能な場合はサーバーと再同期されます。 -「すべてのファイルを削除」を選択すると、あなたが所有者でない限り、ファイルは使用できなくなります。 - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - ローカルの同期フォルダー '%1' 内に存在するすべてのファイルが削除されました。これらの削除はサーバーと同期され、復元されない限りファイルは使用できなくなります。 -これらのアクションをサーバーと同期してもよろしいですか? -「ファイルを残す」を選択した場合、ファイルはサーバーから再同期されます。 - - - - Remove All Files? - すべてのファイルを削除しますか? + 同期フォルダの変更を追跡できませんでした。 + +同期クライアントは、ローカルファイルの変更を検出するとすぐにアップロードせず、一定時間ごとにスキャンを実行して、そのときに変更があればアップロードするように動作します (デフォルトでは2時間ごと)。 + +%1 + + + OCC::FolderItemUpdater - - Remove all files - すべてのファイルを削除 + + There are unresolved conflicts. + 未解決の競合があります。 - - Keep files - ファイルを残す + + Sync error: %1 + 同期エラー: %1 OCC::FolderMan - + Could not reset folder state フォルダーの状態をリセットできませんでした - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. 古い同期ジャーナル '%1' が見つかりましたが、削除できませんでした。それを現在使用しているアプリケーションが存在しないか確認してください。 - - (backup) - (バックアップ) + + Unsupported filesystem + サポートされていないファイルシステム - - (backup %1) - (%1をバックアップ) + + Account disconnected or paused + アカウントが切断または一時停止されました - - Undefined State. - 未定義の状態。 + + User triggered force sync + ユーザーによる強制同期のトリガー - - Waiting to start syncing. - 同期開始を待機中 + + Folder is about to be removed + フォルダーが削除されようとしています - - Preparing for sync. - 同期の準備中。 + + (backup) + (バックアップ) - - Sync is running. - 同期を実行中です。 + + (backup %1) + (%1をバックアップ) - - Sync was successful, unresolved conflicts. + + The selected path is not a folder. - - Last Sync was successful. - 最後の同期は成功しました。 + + The folder %1 is used in a folder sync connection. + フォルダ %1 は、フォルダ同期接続で使用されます。 - - Setup Error. - 設定エラー。 + + Folder '%1' is already in use by application %2. + - - User Abort. - ユーザーによる中止。 + + There is already a sync from the server to this local folder. + サーバーからこのローカルのフォルダへの同期は既に行われています。 - - Sync is paused. - 同期を一時停止しました。 + + The local folder %1 already contains a folder used in a folder sync connection. + ローカルのフォルダ %1 には、既にフォルダ同期接続で使用されるフォルダが含まれています。 - - %1 (Sync is paused) - %1 (同期を一時停止) + + The local folder %1 is already contained in a folder used in a folder sync connection. + ローカルのフォルダ %1 は、フォルダ同期接続で使用されているフォルダ内に既に含まれています。 - - No valid folder selected! - 有効なフォルダーが選択されていません! + + No valid folder selected. + - - The selected path does not exist! + + The selected path does not exist. - - The selected path is not a folder! - 指定のパスは、フォルダーではありません! + + You have no permission to write to the selected folder. + - - You have no permission to write to the selected folder! - 選択されたフォルダーに書き込み権限がありません + + Folder '%1' is already in use by another account. + フォルダ '%1' は既に別のアカウントによって使用されています。 - - There is already a sync from the server to this local folder. Please pick another local folder! - すでに同期されたフォルダーがあります。別のフォルダーを選択してください! + + On macOS, only the Apple File System is supported. + macOS では、Apple ファイルシステムのみサポートしています。 + + + OCC::FolderModelController - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - ローカルフォルダー %1 にはすでに同期フォルダーとして利用されてるフォルダーを含んでいます。他のフォルダーを選択してください。 + + %1 options + - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - ローカルフォルダー %1 には同期フォルダーとして利用されているフォルダーがあります。他のフォルダーを選択してください。 + + Menu button with %1 options. Use the space key to pop the %2 options menu + %1 オプションのメニューボタン。スペースキーを押すと %2 オプションメニューが表示されます - OCC::FolderStatusDelegate + OCC::FolderWatcher - - Add Folder Sync Connection - 同期フォルダーを追加 + + The watcher did not receive a test notification. + ウォッチャーにテスト通知が届かなかった。 + + + OCC::FolderWatcherPrivate - - File - ファイル + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + この問題は、大抵の場合 inotify のウォッチを使い切ったときに発生します。詳しくはFAQをご覧ください。 - OCC::FolderStatusModel + OCC::FolderWizard - - You need to be connected to add a folder - フォルダーを追加するためには、接続している必要があります。 + + Virtual files are not available for the selected folder + 選択したフォルダーで仮想ファイルが利用できません - - Click this button to add a folder to synchronize. - このボタンをクリックして同期フォルダーを追加してください。 + + Add Folder Sync Connection + 同期フォルダーを追加 - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Add Sync Connection + 同期接続を追加 + + + OCC::FolderWizardLocalPath - - Error while loading the list of folders from the server. - サーバーからフォルダーのリスト取得時にエラー + + Click to select a local folder to sync. + 同期したいローカルフォルダーをクリックしてください。 - - Virtual file support is enabled. - + + Enter the path to the local folder. + ローカルフォルダーのパスを入力してください。 - - Signed out - サインアウト + + Select the local folder + ローカルフォルダーを選択 + + + OCC::FolderWizardSelectiveSync - - Fetching folder list from server... - サーバーからフォルダーリストを取得中... + + Use virtual files instead of downloading content immediately + コンテンツを直接ダウンロードするのではなく、仮想ファイル機能を利用してアクセスします。 + + + OCC::GETFileJob - - There are unresolved conflicts. Click for details. - 未解決の競合があります。クリックで詳細。 + + Connection Timeout + 接続タイムアウト - - Synchronizing VirtualFiles with local folder - + + No E-Tag received from server, check Proxy/Gateway + サーバーからE-Tagを受信できません。プロキシ/ゲートウェイを確認してください。 - - Synchronizing with local folder - ローカルフォルダーを同期中 + + We received a different E-Tag for resuming. Retrying next time. + 同期再開時に違う E-Tagを受信しました。次回リトライします。 - - Checking for changes in remote '%1' - + + We received an unexpected download Content-Length. + 予期しないダウンロードが発生しました。Content-Length. - - Checking for changes in local '%1' - + + Server returned wrong content-range + サーバーが間違ったcontent-rangeを返しました + + + OCC::GraphApi::Space - - Reconciling changes - + + Personal + 個人 - - , '%1' - Build a list of file names - , '%1' + + Shares + 共有 + + + OCC::IgnoreListEditor - - '%1' - Argument is a file name - '%1' + + Ignored Files Editor + 除外ファイルリストを編集 - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - 同期中 %1 + + Files Ignored by Patterns + パターンによって除外するファイル - - - , - , + + Add + 追加 - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - ダウンロード %1/s + + Pattern + パターン - - ↓ %1/s - + + Allow Deletion + 削除を許可 - - ↑ %1/s - + + Remove + 削除 - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - アップロード %1/s + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + パターンに一致するファイルやフォルダは同期されません。変更は次にフォルダが同期されるときに有効になります。 + +削除が許可されているアイテムは、ディレクトリの削除を妨げている場合に削除されます。これはメタデータに有効です。 - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%4 中 %3 完了) + + Could not open file + ファイルが開けませんでした - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 + + Cannot write changes to '%1'. + '%1'を更新できません。 - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - 残り%5、%2中%1完了 、ファイル%4個中%3個完了 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 of %2, ファイル数 %3 of %4 - - - - file %1 of %2 - %1 / %2 ファイル - - - - Waiting... - 待機中... - - - - Waiting for %n other folder(s)... - %n 他のフォルダーの完了待ち... - - - - Preparing to sync... - 同期の準備中... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - 同期フォルダーを追加 - - - - Add Sync Connection - 同期接続を追加 - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - 同期したいローカルフォルダーをクリックしてください。 - - - - Enter the path to the local folder. - ローカルフォルダーのパスを入力してください。 - - - - Select the source folder - ソースフォルダーを選択 - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - リモートフォルダーを追加 - - - - Enter the name of the new folder to be created below '%1': - '%1'の下に新しいフォルダーの名前を入力: - - - - Folder was successfully created on %1. - %1 にフォルダーが作成されました。 - - - - Authentication failed accessing %1 - %1 アクセス中に認証に失敗しました - - - - Failed to create the folder on %1. Please check manually. - %1 にフォルダーを作成できませんでした。手作業で確認してください。 - - - - Failed to list a folder. Error: %1 - フォルダーをリストアップできません。エラー: %1 - - - - Choose this to sync the entire account - アカウント全体を同期する場合はこちらを選択 - - - - This folder is already being synced. - このフォルダーはすでに同期されています。 - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - <i>%1</i>は、<i>%2</i>の親フォルダーですでに同期しています。 - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>警告:</b> %1 - - - - <b>Warning:</b> - <b>警告:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - サーバーからE-Tagを受信できません。プロキシ/ゲートウェイを確認してください。 - - - - We received a different E-Tag for resuming. Retrying next time. - 同期再開時に違う E-Tagを受信しました。次回リトライします。 - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - サーバーが間違ったcontent-rangeを返しました - - - - OCC::GETJob - - - Connection Timeout - 接続タイムアウト - - - - OCC::GeneralSettings - - - Form - フォーム - - - - General Settings - 一般設定 - - - - Sync hidden files - 隠しファイルを同期 - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - ネットワーク - - - - Updates - アップデート - - - - stable - 安定 - - - - beta - ベータ - - - - About - バージョン情報 - - - - Advanced - 詳細設定 - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - 指定された容量以上のフォルダーは同期前に確認する - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - 外部ストレージと同期する前に確認する - - - - &Launch on System Startup - システム開始時に起動(&L) - - - - Show &Desktop Notifications - デスクトップに通知を表示(&D) - - - - &Update Channel - - - - - Edit &Ignored Files - 除外ファイルリストを編集(&I) - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - クラッシュ報告を表示(&H) - - - - &Restart && Update - 再起動してアップデート(&R) - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - キャンセル - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - %1 のパスワードを入力してください:<br> <br>ユーザー:%2<br>アカウント:%3<br> - - - - Reading from keychain failed with error: '%1' - 鍵情報の読み込みに失敗しました。エラー: '%1' - - - - Enter Password - パスワードを入力してください - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">ここをクリック</a>してウェブインターフェースからアプリパスワードをリクエストしてください。 - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - 除外ファイルリストを編集 - - - - Files Ignored by Patterns - パターンによって除外するファイル - - - - Add - 追加 - - - - Pattern - パターン - - - - Allow Deletion - 削除を許可 - - - - Remove - 削除 - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - ファイルが開けませんでした - - - - Cannot write changes to '%1'. - '%1'を更新できません。 - - - - Add Ignore Pattern - 除外するファイルパターンを追加 - - - - Add a new ignore pattern: - 除外するファイルパターンを新しく追加: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - このエントリーは、システム '%1' から提供されています。この画面では変更できません。 - - - - OCC::IssuesWidget - - - Form - フォーム - - - - List of issues - 問題一覧 - - - - Account - アカウント - - - - - <no filter> - - - - - - Folder - フォルダー - - - - Show warnings - 警告を表示 - - - - Show ignored files - 除外ファイルを表示 - - - - There were too many issues. Not all will be visible here. - 問題が多すぎます。すべてはここに表示できません。 - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - 問題リストをクリップボードにコピーしてください。 - - - - Copy - コピー - - - - Time - 時刻 - - - - File - ファイル - - - - Issue - 問題 - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LibCloudProvidersPrivate - - - Settings - 設定 - - - - OCC::LogBrowser - - - Log Output - ログ出力 - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - フォルダーを開く - - - - OCC::NSISUpdater - - - New Version Available - 新しいバージョンが利用可能です - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>%1 クライアントの新しいバージョンが利用可能です。</p><p><b>%2</b> がダウンロード可能です。インストールされているバージョンは %3 です。<p> - - - - - Skip this version - 今回のバージョンをスキップ - - - - Skip this time - 今回はスキップ - - - - Get update - アップデートを取得 - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - フォーム - - - - Proxy Settings - プロキシ設定 - - - - No Proxy - プロキシを利用しない - - - - Use system proxy - システムのプロキシを利用 - - - - Specify proxy manually as - プロキシを手動設定 - - - - Host - ホスト - - - - : - : - - - - Proxy server requires authentication - 認証が必要なプロキシサーバー - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - ダウンロード帯域制限 - - - - - Limit to - 制限 - - - - - KBytes/s - KBytes/s - - - - - No limit - 無制限 - - - - - Limit to 3/4 of estimated bandwidth - 想定帯域の3/4に制限 - - - - Upload Bandwidth - アップロード帯域制限 - - - - - Limit automatically - 自動的に制限 - - - - Hostname of proxy server - プロキシサーバーのホスト名 - - - - Username for proxy server - プロキシサーバーのユーザー名 - - - - Password for proxy server - プロキシサーバーのパスワード - - - - HTTP(S) proxy - HTTP(S)プロキシ - - - - SOCKS5 proxy - SOCKS5プロキシ - - - - OCC::NotificationWidget - - - Created at %1 - %1 を作成しました。 - - - - Closing in a few seconds... - 数秒以内に接続を終了します。 - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - リクエスト %1 が %2 に失敗 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' が %2 に選択されました - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - サーバからエラーが返されました: <em>%1</em>  - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Empty JSON from OAuth2 redirect - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Error: Missing field %1 - - - - - - <h1>Login Error</h1><p>%1</p> - <h1>ログインエラー</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>ユーザの誤りです</h1><p><em>%1</em>としてログインされましたがm、<em>%2</em>でなければいけません。<br>別のタブで %3 からログアウトし、<a href='%4'>ここをクリック</a>して %2 としてログインしてください</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - 新しい %1 アップデートの準備完了 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - %1 の新しいアップデートのインストール準備ができました。アップデートのインストール中、追加の権限が必要な場合があります。 - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - アップデートをダウンロードできませんでした。手動でアップデートするには、<a href='%1'>ここ</a> をクリックしてください。 - - - - Could not check for new updates. - アップデートを確認できませんでした。 - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - アップデートをチェックしています.... - - - - Update status is unknown: Did not check for new updates. - 更新状況が不明です: 更新をチェックしていません。 - - - - No updates available. Your installation is at the latest version. - 利用可能なアップデートはありません。最新版がインストールされています。 - - - - Update Check - アップデートを確認 - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - %1 に接続中 - - - - Setup local folder options - ローカルフォルダーのオプションを設定 - - - - Connect... - 接続... - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - %1 folder '%2' is synced to local folder '%3' - %1 フォルダー '%2' はローカルフォルダー '%3' と同期しています - - - - Sync the folder '%1' - '%1' フォルダーを同期 - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>警告:</strong> ローカルフォルダーは空ではありません。解決方法を選択してください!</small></p> - - - - Virtual files are not available for the selected folder - + + Add Ignore Pattern + 除外するファイルパターンを追加 - - Local Sync Folder - ローカル同期フォルダー + + Add a new ignore pattern: + 除外するファイルパターンを新しく追加: - - - (%1) - (%1) + + This entry is provided by the system at '%1' and cannot be modified in this view. + このエントリーは、システム '%1' から提供されています。この画面では変更できません。 - OCC::OwncloudConnectionMethodDialog + OCC::LocalActivityWidget - - Connection failed - 接続に失敗 + + Filter + フィルター - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>指定された安全なサーバーアドレスに接続できませんでした。どのように進めますか?</p></body></html> + + Local activity table + ローカルの利用履歴表 - - Select a different URL - 異なるURLを選択 - - - - Retry unencrypted over HTTP (insecure) - HTTP経由で暗号化せずに再度行う(安全でない) + + + Account + アカウント - - Configure client-side TLS certificate - クライアントサイドTLS証明書を設定 + + Filter menu + フィルターメニュー - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>安全なサーバーアドレス <em>%1</em> に接続できませんでした。どのように進めますか?</p></body></html> + + Actions menu + アクションメニュー - - - OCC::OwncloudHttpCredsPage - - &Email - メール(&E) + + Sort ascending by %1 + %1 で昇順にソート - - Connect to %1 - %1 に接続中 + + Sort descending by %1 + %1 で降順にソート - - Enter user credentials - ユーザー資格情報を入力 + + Retry sync + 同期を再実行 - OCC::OwncloudOAuthCredsPage + OCC::LogBrowser - - Connect to %1 - %1 に接続中 + + Log Output + ログ出力 - - Login in your browser - 御自身のブラウザでログインしてください + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>ログファイルには機密情報が含まれており、公開しないでください。</b></span></p></body></html> - - Copy link to clipboard - クリップボードにリンクをコピー + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + クライアントは一時ディレクトリにデバッグログを出力可能です。これらのログは、問題の特定に非常に役立ちます。 +ログファイルは容量が大きくなるおそれがあるため、クライアントは同期ごとに新しいファイルを作成し、古いファイルは圧縮処理を行います。 - - - OCC::OwncloudSetupPage - - Connect to %1 - %1 に接続中 + + Enable logging to temporary folder + テンポラリーフォルダへのログ保存を有効にする - - Setup %1 server - %1 サーバーを設定 + + If enabled, logs will be written to: + ログ保存を有効にするとログが書き込まれます - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - このURLは暗号化されていないため安全ではありません。利用はお勧めしません。 + + C:/log + C:/log - - This url is secure. You can use it. - このURLは安全です。利用可能です。 + + Log Http traffic + Httpトラフィックのログ - - &Next > - 次(&N) > + + Log files to keep: + 保存するログファイル: - - Invalid URL - 無効なURL + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + これらの設定は、クライアントの再起動後も維持されます。 +ログのコマンドラインオプションを使用すると、設定が上書きされることに注意してください。 - - Could not load certificate. Maybe wrong password? - + + Open folder + フォルダーを開く - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - %2 の %1 に接続に失敗:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - %2 の %1 へ接続を試みた際にタイムアウトしました。 - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - サーバーへの認証リクエストは '%1' へリダイレクトされました。URLは不正です、サーバーの設定に誤りがあります。 - - - - There was an invalid response to an authenticated webdav request - 認証された WebDav リクエストに不正な応答がありました - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - サーバーによってアクセスが拒否されています。適切なアクセス権があるか検証するには、<a href="%1">ここをクリック</a>してブラウザーでサービスにアクセスしてください。 - - - - Invalid URL - 無効なURL - - - - Could not create local folder %1 - ローカルフォルダー %1 を作成できませんでした - - - - No remote folder specified! - リモートフォルダーが指定されていません! - - - - Error: %1 - エラー: %1 - - - - The folder creation resulted in HTTP error code %1 - フォルダーの作成はHTTPのエラーコード %1 で終了しました - + OCC::MainWindow - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - 指定された資格情報が間違っているため、リモートフォルダーの作成に失敗しました!<br/>前に戻って資格情報を確認してください。</p> + + Hide + 非表示 - - Remote folder %1 creation failed with error <tt>%2</tt>. - リモートフォルダー %1 の作成がエラーで失敗しました。<tt>%2</tt>. + + Main toolbar for the application + - - Folder rename failed - フォルダー名の変更に失敗しました。 + + Main toolbar + - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - フォルダーまたはその中にあるファイルが他のプログラムで開かれているため、フォルダーの削除やバックアップができません。フォルダーまたはファイルを閉じてから再試行するか、セットアップをキャンセルしてください。 + + + More + もっと見る - OCC::OwncloudWizard + OCC::MainWindowController - - %1 Connection Wizard - %1 接続ウィザード + + Add account... + - - Enable experimental feature? - + + Settings... + 設定... - - Enable experimental placeholder mode - + + About... + ownCloudについて... - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - + + Quit + 終了 - - Stay safe + + + Errors: %1 - - - OCC::OwncloudWizardResultPage - - Everything set up! - すべてセットアップされました! + + Activity + アクティビティ - - Open Local Folder - ローカルフォルダーを開く + + Quit %1 + %1 を終了 - - Open %1 in Browser - %1 をブラウザーで開く + + Are you sure you want to quit %1? + %1 を終了してもよろしいですか? - OCC::PollJob + OCC::ModalWrapperWidget - - Invalid JSON reply from the poll URL - 不正なJSONがポーリングURLから返りました + + Close + 閉じる - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - 同期機能はシンボリックリンクをサポートしていません。 - - - - File is listed on the ignore list. - ファイルは除外リストに登録されています。 - - - - File names ending with a period are not supported on this file system. - 末尾にピリオドを使うファイル名はサポートされていません - - - - File names containing the character '%1' are not supported on this file system. - ファイル名に使用できない文字列が含まれています: '%1' - - - - File name contains at least one invalid character - ファイル名に1文字以上の無効な文字が含まれています - - - - The file name is a reserved name on this file system. - ファイル名はこのファイルシステムで予約されている名前です。 - - - - Filename contains trailing spaces. - ファイル名末尾にスペースが含まれます。 - - - - Filename is too long. - ファイル名が長すぎます - - - - File/Folder is ignored because it's hidden. - 隠しファイル/フォルダーのため無視されました - - - - Stat failed. - 情報取得エラー - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - The filename cannot be encoded on your file system. - - - - - The filename is blacklisted on the server. - - - - - File has extension reserved for virtual files. - - - - - size - - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - 削除できないので復元しています - - - - Ignored because of the "choose what to sync" blacklist - "同期対象先" ブラックリストにより無視されました。 - - - - Not allowed because you don't have permission to add subfolders to that folder - そのフォルダーにサブフォルダーを追加する権限がありません - - - - Not allowed because you don't have permission to add files in that folder - そのフォルダーにファイルを追加する権限がありません - + OCC::Models - - Not allowed to upload this file because it is read-only on the server, restoring - サーバーでは読み取り専用となっているため、このファイルをアップロードすることはできません、復元しています + + %1 Filter: + %1 フィルター: - - Error while reading the database - + + All + すべて - - Server replied with an error while reading directory '%1' : %2 - + + Reset Filters + フィルターをリセット - - - OCC::PropagateDirectory - - Error writing metadata to the database - メタデータのデータベースへの書き込みに失敗 + + Status Filter: + フィルターの状態: - OCC::PropagateDownloadFile - - - - File %1 can not be downloaded because of a local file name clash! - ファイル %1 はローカルファイル名が衝突しているためダウンロードできません! - + OCC::NetworkSettings - - The download would reduce free local disk space below the limit - + + Form + フォーム - - Free space on disk is less than %1 - ディスク空き容量が %1 よりも少なくなっています + + Pause synchronization when the Internet connection is metered + インターネット接続が従量制の場合は同期を一時停止する - - File was deleted from server - ファイルはサーバーから削除されました + + Proxy Settings + プロキシ設定 - - The file could not be downloaded completely. - このファイルのダウンロードは完了しませんでした + + No Proxy + プロキシを利用しない - - The downloaded file is empty despite the server announced it should have been %1. - サーバーが通知しているファイルは %1 であるべきですが、ダウンロードファイルは空でした。 + + Use system proxy + システムのプロキシを利用 - - File %1 cannot be saved because of a local file name clash! - %1 はローカルファイル名が衝突しているため保存できません! + + Host + ホスト - - - File has changed since discovery - ファイルは発見以降に変更されました + + Proxy Hostname + プロキシー ホスト名 - - Error writing metadata to the database - メタデータのデータベースへの書き込みに失敗 + + : + : - - - OCC::PropagateItemJob - - ; Restoration Failed: %1 - ; 復元に失敗: %1 + + Proxy Port Number + プロキシー ポート番号 - - A file or folder was removed from a read only share, but restoring failed: %1 - ファイルまたはフォルダーが読み込み専用の共有から削除されましたが、復元に失敗しました: %1 + + Proxy server requires authentication + 認証が必要なプロキシサーバー - - - OCC::PropagateLocalMkdir - - could not delete file %1, error: %2 - ファイル %1 を削除できません。エラー: %2 + + Note: proxy settings have no effects for accounts on localhost + 注:プロキシ設定は、localhost上のアカウントには影響しません。 - - Attention, possible case sensitivity clash with %1 - 注意: %1 で大文字小文字が競合している可能性 + + Specify proxy manually + プロキシを手動設定 - - could not create folder %1 - フォルダー %1 を作成できません + + Proxy type + プロキシの種類 - - Error writing metadata to the database - メタデータのデータベースへの書き込みに失敗 + + + Hostname of proxy server + プロキシサーバーのホスト名 - - - OCC::PropagateLocalRemove - - Could not remove %1 because of a local file name clash - %1 はローカルファイル名が衝突しているため削除できませんでした + + + Username for proxy server + プロキシサーバーのユーザー名 - - - OCC::PropagateLocalRename - - File %1 can not be renamed to %2 because of a local file name clash - ファイル %1 はローカルファイル名が衝突しているため %2 に名前を変更できません + + + Password for proxy server + プロキシサーバーのパスワード - - - Error writing metadata to the database - メタデータのデータベースへの書き込みに失敗 + + HTTP(S) proxy + HTTP(S)プロキシ - - Error setting pin state - + + SOCKS5 proxy + SOCKS5プロキシ - OCC::PropagateRemoteDelete + OCC::NewAccountBuilder - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - 誤ったHTTPコードがサーバーから返されました。204のはずが、"%1 %2"が返りました。 + + no error details are available + + + + + Unable to connect to server during account creation: %1. + - OCC::PropagateRemoteMkdir + OCC::NewAccountWizard - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - 誤ったHTTPコードがサーバーから返されました。201のはずが、"%1 %2"が返りました。 + + Cancel Setup + セットアップのキャンセル - - Error writing metadata to the database - メタデータのデータベースへの書き込みに失敗 + + Do you really want to cancel the account setup? + 本当にアカウント設定をキャンセルしてもよろしいですか? - OCC::PropagateRemoteMove + OCC::NewAccountWizardController - - Could not rename %1 to %2, error: %3 + + Add account… - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - 誤ったHTTPコードがサーバーから返されました。201のはずが、"%1 %2"が返りました。 + + Back + 戻る - - - Error writing metadata to the database - メタデータのデータベースへの書き込みに失敗 + + Advanced + 詳細設定 - - Error setting pin state + + Sign in + ログイン + + + + Open sign in again - OCC::PropagateUploadFileCommon + OCC::NewVersionAvailableWidget - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - ファイル %1 は、大文字と小文字の区別が違う同じ名前のファイルが存在するためアップロードできません + + Skip this version + 今回のバージョンをスキップ - - File Removed - ファイルを削除しました + + Get update + アップデートを取得 - - Local file changed during syncing. It will be resumed. - ローカルファイルが同期中に変更されました。再開されます。 + + Skip this time + 今回はスキップ + + + OCC::OAuth - - Local file changed during sync. - ローカルのファイルが同期中に変更されました。 + + Successfully signed in + - - - Upload of %1 exceeds the quota for the folder + + Now, explore %1 on desktop. - - Error writing metadata to the database - メタデータのデータベースへの書き込みに失敗 + + Error returned from the server: <em>%1</em> + サーバからエラーが返されました: <em>%1</em>  - - - OCC::PropagateUploadFileNG - - The local file was removed during sync. - ローカルファイルを同期中に削除します。 + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + 'token' エンドポイントへのアクセスにエラーが発生しました: <br><em>%1</em> - - Local file changed during sync. - ローカルのファイルが同期中に変更されました。 + + Could not parse the JSON returned from the server: <br><em>%1</em> + サーバから返された JSON をパースできませんでした: <br><em>%1</em> - - Poll URL missing - ポーリングURLがありません + + Unsupported token type: %1 + サポートされていない形式のトークン: %1 - - Unexpected return code from server (%1) - サーバー (%1) からの予期しない戻りコード + + The reply from the server did not contain all expected fields +:%1 + サーバからの応答には、期待されたフィールドがすべて含まれていません。 +:%1 - - Missing File ID from server - サーバーからファイルIDの戻りがありません + + Unknown Error + 不明なエラー - - Missing ETag from server - サーバーからETagの戻りがありません + + + Login Error + ログインエラー - - - OCC::PropagateUploadFileTUS - - The local file was removed during sync. - ローカルファイルを同期中に削除します。 + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + - - Local file changed during sync. - ローカルのファイルが同期中に変更されました。 + + Wrong user + 間違ったユーザー - OCC::PropagateUploadFileV1 - - - Poll URL missing - ポーリングURLがありません - + OCC::OAuthPageController - - The local file was removed during sync. - ローカルファイルを同期中に削除します。 + + Application Name Logo %1 + アプリケーション名ロゴ %1 - - Local file changed during sync. - ローカルのファイルが同期中に変更されました。 + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + - - The server did not acknowledge the last chunk. (No e-tag was present) - サーバーは最終チャンクを認識しませんでした。(e-tag が存在しませんでした) + + Sign in URL + - - - OCC::ProtocolWidget - - Form - フォーム + + To copy the sign in URL to the clipboard, use the copy button + - - TextLabel - テキストラベル + + Copy URL to sign in + - - Open in browser - ブラウザーで開く + + Additional logo defined by the organization + - - Time - 時刻 + + URL copied + - - File - ファイル + + Copy URL + URLをコピー - - Folder - フォルダー + + Unable to connect to the login service. If the problem continues, please contact support. + - - Action - アクション + + Please copy the authentication URL using the button above, and provide this information to support. + - - Size - サイズ + + Failed to look up WebFinger instances: %1 + - - Local sync protocol - ローカルファイル同期状況 + + You are already connected to an account with these credentials. + - - Copy - コピー + + Unable to retrieve capabilities from server. + - - Copy the activity list to the clipboard. - アクティビティ一覧をコピーする + + The server is not supported by this client. + - - - OCC::ProxyAuthDialog - - Proxy authentication required - 認証が必要なプロキシ + + Error while trying to log in to OAuth2-enabled server. %1 + - - Username: - ユーザー名: + + Unable to open browser with provided URL. %1 + - - Proxy: - プロキシ: + + OAuth2 authentication requires a secured connection. %1 + - - The proxy server needs a username and password. - プロキシサーバーにはユーザー名とパスワードが必要です。 + + Authorization server unreachable. %1 + + + + OCC::OCUpdater - - Password: - パスワード: + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + アップデートをダウンロードできませんでした。手動でアップデートするには、<a href='%1'>ここ</a> をクリックしてください。 - - TextLabel - テキストラベル + + Could not check for new updates. + アップデートを確認できませんでした。 - - - OCC::SelectiveSyncDialog - - Choose What to Sync - 同期フォルダーを選択 + + Downloading %1. Please wait… + - - - OCC::SelectiveSyncWidget - - Loading ... - 読込中 ... + + %1 installed successfully. Restart the application to finish installing the update. + %1 のインストールが成功しました。アップデートを完了させるにはアプリケーションを再起動してください。 - - Deselect remote folders you do not wish to synchronize. - 同期したくないリモートのサブフォルダーは、同期対象から外せます。 + + %1 available. Restart application to start the update. + %1 が利用可能です。アプリケーションを再起動して、アップデートを開始してください。 - - Name - 名前 + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + 新しい %1 が利用可能です。 新しいAppImageを手動でダウンロードするには、<a href='%2'>here</a> をクリックしてください。 - - Size - サイズ + + New %1 available. Please use the system's update tool to install it. + 新しい %1 が利用可能です。インストールするには、システムのアップデートツールを利用してください。 - - - No subfolders currently on the server. - 現在サーバーにサブフォルダーはありません。 + + Checking update server… + - - An error occurred while loading the list of sub folders. - サーバーからフォルダーのリスト取得時にエラーが発生しました。 + + Update status is unknown: Did not check for new updates. + 更新状況が不明です: 更新をチェックしていません。 - - - OCC::ServerNotificationHandler - - Dismiss - 閉じる + + No updates available. Your installation is at the latest version. + 利用可能なアップデートはありません。最新版がインストールされています。 - OCC::SettingsDialog + OCC::OwncloudPropagator - - Settings - 設定 + + File %1 is currently in use + ファイル %1 は現在使用中です - - Activity - アクティビティ + + vfs instance is not available. + + + + OCC::ProcessDirectoryJob - - Add account - アカウントを追加 + + Symbolic links are not supported in syncing. + 同期機能はシンボリックリンクをサポートしていません。 - - - Quit %1 - %1 を終了 + + File is listed on the ignore list. + ファイルは除外リストに登録されています。 - - Are you sure you want to quit %1? - + + File names ending with a period are not supported on this file system. + 末尾にピリオドを使うファイル名はサポートされていません - - Account - アカウント + + File names containing the character '%1' are not supported on this file system. + ファイル名に使用できない文字列が含まれています: '%1' - - MainWindow - + + File name contains at least one invalid character + ファイル名に1文字以上の無効な文字が含まれています - - Hide - 非表示 + + The file name is a reserved name on this file system. + ファイル名はこのファイルシステムで予約されている名前です。 - - - OCC::ShareDialog - - TextLabel - テキストラベル + + Filename contains trailing spaces. + ファイル名末尾にスペースが含まれます。 - - share label - 共有ラベル + + Filename is too long. + ファイル名が長すぎます - - Dialog - ダイアログ + + File/Folder is ignored because it's hidden. + 隠しファイル/フォルダーのため無視されました - - ownCloud Path: - OwnCloudのパス: + + Stat failed. + 情報取得エラー - - %1 Sharing - %1 を共有中 + + Conflict: Server version downloaded, local copy renamed and not uploaded. + コンフリクト:サーバ側のバージョンをダウンロードしました。ローカル側は名前を変更してコピーされ、サーバへアップロードされませんでした。 - - %1 - %1 + + The filename cannot be encoded on your file system. + ファイルシステムでファイル名をエンコードできません。 - - Folder: %2 - フォルダー: %2 + + The filename is blacklisted on the server. + ファイル名がサーバーのブラックリストに登録されている。 - - The server does not allow sharing - サーバーで共有を許可していません + + + + + the VFS instance is missing. Unable to continue. + - - Retrieving maximum possible sharing permissions from server... - サーバーから共有権限を取得できる最大数で取得しています + + server reported no %1 + サーバは %1 がないことを報告しました - - The file can not be shared because it was shared without sharing permission. - 再共有権限がない共有ため、このファイルは共有できません。 + + + account was deleted. Unable to continue + - - Users and Groups - ユーザーとグループ + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: パスが選択解除されているため無視されます - - Public Links - 公開リンク + + Moved to invalid target, restoring + 無効なターゲットに移動し、復元する - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Share NewDocument.odt + + Not allowed to remove, restoring + 削除できないので復元しています - - Upload only (File Drop) + + Error while reading the database. - - Receive files from others without revealing the contents of the folder. + + account was deleted. Unable to continue. - - Download / View / Edit - ダウンロード/閲覧/編集 + + Not allowed because you don't have permission to add subfolders to that folder + そのフォルダーにサブフォルダーを追加する権限がありません - - Name: - + + Error while doing a rename, unhandled status code: %1 + 名前変更処理中にエラーが発生しました。未処理のステータスコードは %1 です。 - - Enter name of new share... - + + Not allowed because you don't have permission to add files in that folder + そのフォルダーにファイルを追加する権限がありません - - &Create - + + Not allowed to upload this file because it is read-only on the server, restoring + サーバーでは読み取り専用となっているため、このファイルをアップロードすることはできません、復元しています - - TextLabel - テキストラベル + + This Space is currently unavailable + このスペースは現在利用できません - - Set &password - パスワードを設定(&P) + + Server replied with an error while reading directory '%1' : %2 + ディレクトリ '%1' の読み取り中にサーバーがエラーで応答しました。 : %2 + + + OCC::PropagateDirectory - - Set &expiration date - 有効期限を設定(&E) + + Error updating metadata: %1 + メタデータの更新エラー: %1 - - Set password - パスワードを設定 + + %1 the folder is currently in use + フォルダ %1 は現在使用中です + + + OCC::PropagateDownloadFile - - Recipients can view or download contents. - 相手はコンテンツを表示またはダウンロードできます。 + + Failed to free up space, the file %1 is currently in use + 領域解放に失敗しました。ファイル %1 は現在使用中です - - Download / View - ダウンロード / 表示 + + + File %1 can not be downloaded because of a local file name clash with %2! + ファイル %1 は、ローカルファイル名 %2 と衝突しているため、ダウンロードできません! - - Recipients can view, download, edit, delete and upload contents. - 相手はコンテンツを表示、ダウンロード、編集、削除、およびアップロードできます。 + + The download would reduce free local disk space below the limit + ダウンロードによりローカルディスクの空き容量が制限値以下になります - - Anyone with the link has access to the file/folder - リンクを知っている人はファイル/フォルダーにアクセスできます + + Free space on disk is less than %1 + ディスク空き容量が %1 よりも少なくなっています - - - P&assword protect - パスワード保護(&A) + + File was deleted from server + ファイルはサーバーから削除されました - - The file can not be shared because it was shared without sharing permission. - 再共有権限がない共有ため、このファイルは共有できません。 + + Broken webserver returned empty content length for non-empty file on resume + 復元処理中のファイルに対して、不具合なウェブサーバが、空のコンテンツ長を返しました。 - - Link shares have been disabled - + + The file could not be downloaded completely. + このファイルのダウンロードは完了しませんでした - - Create public link share - + + The downloaded file is empty despite the server announced it should have been %1. + サーバーが通知しているファイルは %1 であるべきですが、ダウンロードファイルは空でした。 - - - Delete - 削除 + + File %1 cannot be saved because of a local file name clash with %2! + ファイル %1 は、ローカルファイル名 %2 と衝突しているため、保存できません! - - Open link in browser - ブラウザでリンクを開く + + Error updating metadata: %1 + メタデータの更新エラー: %1 - - Copy link to clipboard - クリップボードにリンクをコピー + + + + + The file %1 is currently in use + ファイル %1 は現在使用中です - - Copy link to clipboard (direct download) - + + + File has changed since discovery + ファイルは発見以降に変更されました + + + OCC::PropagateItemJob - - Send link by email - メールでリンクを送信 + + ; Restoration Failed: %1 + ; 復元に失敗: %1 + + + OCC::PropagateLocalMkdir - - Send link by email (direct download) - + + could not delete file %1, error: %2 + ファイル %1 を削除できません。エラー: %2 - - Create new... - + + Can not create local folder %1 because of a local file name clash with %2 + ローカルフォルダ %1 は、ローカルファイル名 %2 と衝突しているため作成できません。 - - I shared %1 with you - + + Error updating metadata: %1 + メタデータの更新エラー: %1 - - Confirm Link Share Deletion - + + The file %1 is currently in use + ファイル %1 は現在使用中です - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - + + could not create folder %1 + フォルダー %1 を作成できません + + + OCC::PropagateLocalRemove - - Cancel - キャンセル + + %1 failed with: %2 + %1 で失敗しました: %2 - - - Public link - + + Could not remove %1 because of a local file name clash with %2! + %1 はローカルファイル名 %2 が衝突しているため削除できませんでした! - - Delete link share - + + Could not move '%1' to the trash bin + '%1' をゴミ箱に移動できませんでした - - - Please Set Password - パスワードを入力してください + + + %1 the file is currently in use + ファイル %1 は現在使用中です - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Share NewDocument.odt - - - - Share with users or groups ... - ユーザーもしくはグループと共有 ... - + OCC::PropagateLocalRename - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - + + File %1 can not be renamed to %2 because of a local file name clash + ファイル %1 はローカルファイル名が衝突しているため %2 に名前を変更できません - - The item is not shared with any users or groups - + + Could not rename %1 to %2, the file is currently in use + %1 を %2 に名前変更できません、ファイルは現在使用中です - - Open link in browser - ブラウザでリンクを開く + + Error updating metadata: %1 + メタデータの更新エラー: %1 - - Copy link to clipboard - クリップボードにリンクをコピー + + The file %1 is currently in use + ファイル %1 は現在使用中です - - Send link by email - メールでリンクを送信 + + Failed to rename file + ファイル名の変更に失敗しました - - No results for '%1' - '%1' は見つかりませんでした + + Error setting pin state + ピン状態設定エラー + + + OCC::PropagateRemoteDelete - - I shared something with you - + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + 誤ったHTTPコードがサーバーから返されました。204のはずが、"%1 %2"が返りました。 - OCC::ShareUserLine + OCC::PropagateRemoteMkdir - - Form - フォーム + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + 誤ったHTTPコードがサーバーから返されました。201のはずが、"%1 %2"が返りました。 - - - TextLabel - テキストラベル + + Error writing metadata to the database: %1 + メタデータのデータベースへの書き込みに失敗 : %1 - - can edit - 編集を許可 + + Setting file status failed due to file lock + ファイルロックによりファイルステータスの設定に失敗しました + + + OCC::PropagateRemoteMove - - can share - 共有可 + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + 誤ったHTTPコードがサーバーから返されました。201のはずが、"%1 %2"が返りました。 - - ... - + + Error updating metadata: %1 + メタデータの更新エラー: %1 - - create - 作成 + + The file %1 is currently in use + ファイル %1 は現在使用中です - - change - 更新 + + Error writing metadata to the database + メタデータのデータベースへの書き込みに失敗 - - delete - 削除 + + Error setting pin state + ピン状態設定エラー - OCC::ShareeModel + OCC::PropagateUpdateMetaDataJob - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Could not update file : %1 + ファイルを更新できませんでした : %1 - - - OCC::SocketApi - - Share with %1 - parameter is ownCloud - %1 と共有 + + The file %1 is currently in use + ファイル %1 は現在使用中です + + + OCC::PropagateUploadCommon - - Context menu share - + + Local file changed during sync. It will be resumed. + ローカルファイルが同期中に変更されました。再開されます。 - - Context menu share %1 - + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + ファイル %1 は、大文字と小文字の区別が違う同じ名前のファイルが存在するためアップロードできません - - Confirm deletion - + + + Upload of %1 exceeds the quota for the folder + %1 のアップロードがフォルダのクォータを超えています - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - + + + %1 the file is currently in use + ファイル %1 は現在使用中です - - Do you want to delete the file <i>%1</i> permanently? - + + File Removed + ファイルを削除しました - - Select new location... - + + The server did not provide the file permissions + サーバーはファイル権限を提供しませんでした - - Error - エラー + + Error updating metadata: %1 + メタデータの更新エラー: %1 + + + OCC::PropagateUploadFile - - Moving file failed: - -%1 - + + %1 the file is currently in use + ファイル %1 は現在使用中です - - I shared something with you - + + The server did ask for a removed legacy feature(polling) + サーバーは削除されたレガシー機能(ポーリング)を要求した - - - Share... - + + The local file was removed during sync. + ローカルファイルを同期中に削除します。 - - - Copy private link to clipboard + + The server did not acknowledge the file upload. (No e-tag was present) + + + OCC::PropagateUploadFileTUS - - Send private link by email... - + + %1 the file is currently in use + フォイル %1 は現在使用中です - - Resharing this file is not allowed - + + Checksum computation failed + チェックサムの計算に失敗しました - - Resharing this folder is not allowed - + + The local file was removed during sync. + ローカルファイルを同期中に削除します。 + + + OCC::ProtocolItemModel - - - Copy public link to clipboard - + + Time + 時刻 - - Open in browser - ブラウザーで開く + + File + ファイル - - Show file versions in browser - + + Folder + フォルダー - - Rename... - + + Issues + 問題 - - Rename and upload... - + + Action + アクション - - Move and rename... - + + Size + サイズ - - Move, rename and upload... - + + Account + アカウント - - Delete local changes - + + Status + 状態 + + + OCC::RequestAuthenticationController - - Move and upload... + + IdP is unreachable. Contact your system administrator or try again later. - - Delete - 削除 + + Authentication failed. + 認証に失敗しました。 - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags - + + Application Name Logo %1 + アプリケーション名ロゴ %1 - - Backup of %1 started + + Sign in required - - Failed to find %1 + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. - - Local path must be a an absolute path + + Sign in URL - - Failed to create temporary database + + To copy the sign in URL to the clipboard, use the copy button - - Backup of %1 succeeded + + Copy URL to sign in - - Failed to set success tag + + Additional logo defined by the organization - - Failed to create backup: %1 - + + Stay logged out + ログアウトしたままにする - - Failed to file id tags - + + Sign in + ログイン - - Backup of %1 failed with: %2 + + URL copied - - %1 backup - + + Copy URL + URLをコピー - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>詳細認証情報</h3> - - - - Common Name (CN): - コモンネーム(CN): - - - - Subject Alternative Names: - サブジェクトの別名: - - - - Organization (O): - 組織(O): - + OCC::ResolveUrlAdapter - - Organizational Unit (OU): - 部門名(OU): - - - - State/Province: - 州/県: - - - - Country: - 国: - - - - Serial: - シリアル番号: - - - - <h3>Issuer</h3> - <h3>発行者</h3> - - - - Issuer: - 発行者: + + SSL failure when connecting to server at %1 + - - Issued on: - 発行日: + + Could not detect compatible server at %1 + %1 で互換性のあるサーバーを検出できませんでした。 - - Expires on: - 有効期限: + + Resolved url is invalid %1 + - - <h3>Fingerprints</h3> - <h3>フィンガープリント</h3> + + Rejected redirect from %1 to %2 + + + + OCC::SelectiveSyncWidget - - SHA-256: - SHA-256: + + Loading … + 読込中... - - SHA-1: - SHA-1: + + Deselect remote folders you do not wish to synchronize. + 同期したくないリモートのサブフォルダーは、同期対象から外せます。 - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>注意:</b> この認証は手動で承認されました</p> + + Name + 名前 - - %1 (self-signed) - %1 (自己証明書) + + Size + サイズ - - %1 - %1 + + + Currently there are no subfolders on the server. + 現在、サーバー上にサブフォルダーはありません。 - - This connection is encrypted using %1 bit %2. - - この接続は、%1 の %2 bit を使って暗号化されています。 - + + An error occurred while loading the list of subfolders. + サーバーからフォルダーの一覧取得時にエラーが発生しました。 + + + OCC::SettingsView - - Server version: %1 - + + Form + フォーム - - No support for SSL session tickets/identifiers - SSLセッションチケット/識別子はサポートされていません + + General Settings + 一般設定 - - Certificate information: - 認証情報: + + Use Monochrome Icons in the system tray + システムトレイにモノクロアイコンを使用する - - The connection is not secure - + + Show Desktop Notifications + デスクトップに通知を表示 - - This connection is NOT secure as it is not encrypted. - - 暗号化されていないので、この接続は安全ではありません。 - + + Start on Login + ログインして開始 - - - OCC::SslErrorDialog - - Form - フォーム + + Language + 言語 - - Trust this certificate anyway - この証明書を信用する + + Language selector + 言語選択 - - Untrusted Certificate - 信頼できない証明書 + + Advanced + 詳細設定 - - Cannot connect securely to <i>%1</i>: - <i>%1</i> にセキュアに接続できません: + + Sync hidden files + 隠しファイルを同期 - - with Certificate %1 - 証明書 %1 + + Show crash reporter + クラッシュ報告を表示 - - - - &lt;not specified&gt; - &lt;指定されていません&gt; + + Move remotely deleted files to the local trash bin instead of deleting them + リモート削除されたファイルを、永続的に削除する前に、ローカルマシンのゴミ箱に一時的に保存するように設定できます。 - - - Organization: %1 - 組織名: %1 + + Edit Ignored Files + 除外ファイルリストを編集 - - - Unit: %1 - 部門名: %1 + + Log Settings + ログ設定 - - - Country: %1 - 国: %1 + + Network + ネットワーク - - Fingerprint (MD5): <tt>%1</tt> - Fingerprint (MD5): <tt>%1</tt> + + Warning + 警告 - - Fingerprint (SHA1): <tt>%1</tt> - Fingerprint (SHA1): <tt>%1</tt> + + Language changes require a restart of this application to take effect. + 言語の変更を有効にするには、このアプリケーションの再起動が必要です。 - - Effective Date: %1 - 発効日: %1 + + You cannot disable autostart because system-wide autostart is enabled. + システム全体の自動起動が有効になっているため、自動起動を無効にすることはできません。 - - Expiration Date: %1 - 有効期限: %1 + + (use default) + (デフォルトを使用) - - Issuer: %1 - 発行者: %1 + + unknown (%1) + 不明 (%1) - OCC::SyncEngine + OCC::SocketApi - - %1 (skipped due to earlier error, trying again in %2) - + + Share with %1 + parameter is the application name + %1 と共有 - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - %1 しか空き容量がありません、開始するためには少なくとも %2 は必要です。 + + Confirm deletion + 削除の確認 - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + ディレクトリ <i>%1</i> とその内容をすべて完全に削除しますか? - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - + + Do you want to delete the file <i>%1</i> permanently? + ファイル <i>%1</i> を完全に削除しますか? - - There is insufficient space available on the server for some uploads. - + + Error + エラー - - Unresolved conflict. - + + Moving file failed: + +%1 + ファイルの移動に失敗: + +%1 - - Could not update file : %1 - + + I shared something with you + あなたと何かを共有しました - - Could not update virtual file metadata: %1 + + Select new location… - - Using virtual files with suffix, but suffix is not set + + + Share… - - Unable to read the blacklist from the local database - ローカルデータベースからブラックリストを読み込みできません + + + Copy private link to clipboard + クリップボードにプライベートリンクをコピー - - Unable to read from the sync journal. - 同期ジャーナルから読み込みできません + + Resharing this file is not allowed + このファイルの再共有は許可されていません - - Cannot open the sync journal - 同期ジャーナルを開くことができません + + Resharing this folder is not allowed + このフォルダの再共有は許可されていません - - Aborted - 中止しました + + Open in %1 + %1 を開く - - - OCC::Systray - - %1: %2 - %1: %2 + + Show file versions in web browser + Web ブラウザにファイルのバージョンを表示する - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + + Rename… - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - ヘッダーのチェックサムが異常です。 - - - - The checksum header contained an unknown checksum type '%1' - チェックサムヘッダーに '%1' という未知のチェックサムが含まれていました - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + + Rename and upload… - - - OCC::Vfs - - The Virtual filesystem feature does not support a drive as sync root + + + Move and rename… - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + + Move, rename and upload… - - The Virtual filesystem feature is not supported on network drives + + Move and upload… - - - OCC::ownCloudGui - - - Please sign in - サインインしてください - - - - Folder %1: %2 - フォルダー %1: %2 - - - - There are no sync folders configured. - 同期するフォルダーがありません。 - - - - Open in browser - ブラウザーで開く - - - - - - Log in... - ログイン... - - - - - - Log out - ログアウト - - - - Recent Changes - 最近変更されたファイル - - - - Managed Folders: - 管理フォルダー: - - - Open folder '%1' - フォルダー ’%1’ を開く + + Delete local changes + ローカルでの変更を削除する - - Open %1 in browser - %1をブラウザーで開く + + Delete + 削除 + + + OCC::SpacesPage - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - + + Select a folder to sync it to your computer. + フォルダーを選択してコンピューターに同期します。 - - Unknown status - 不明な状態 + + Select a Space to sync it to your computer. + スペースを選択してコンピューターに同期します。 + + + OCC::SyncEngine - - Settings... - 設定... + + %1 (skipped due to earlier error, trying again in %2) + %1 (前回のエラーでスキップ、%2 で再チャレンジ) - - Details... - 詳細... + + Unable to sync: the local folder is missing. + 同期できません: ローカルのフォルダが見つかりません。 - - Help - ヘルプ + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + %1 しか空き容量がありません、開始するためには少なくとも %2 は必要です。 - - Quit %1 - %1 を終了 + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + ローカルの同期データベースを開けないか作成ができません。同期フォルダーに書き込み権限があることを確認してください。 - - Disconnected from %1 - %1 から切断されました + + + + + Cannot open the sync journal. + 同期ジャーナルを開けません。 - - Unsupported Server Version - サポートされていないサーバーバージョン + + Aborted due to %1 + %1 により中止されました - - Disconnected - 切断しました + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + ディスクの空き容量が少なくなっています: 空き領域を %1 以下にするようなダウンロードはスキップされました。 - - Disconnected from some accounts - + + There is insufficient space available on the server for some uploads. + サーバーの空き容量が不足しているため、一部のアップロードができません。 - - Disconnected from accounts: - アカウントから切断: + + application exit + abort reason + アプリケーションの終了 - - Account %1: %2 - アカウント %1: %2 + + Unable to read the blacklist from the local database + ローカルデータベースからブラックリストを読み込みできません - - Signed out - サインアウト + + Unable to read from the sync journal. + 同期ジャーナルから読み込みできません + + + OCC::SyncErrorWidget - - Account synchronization is disabled - アカウントの同期は無効になっています + + Issues table + 問題表 - - - Synchronization is paused - + + There were too many issues. Not all will be visible here. + 問題が多すぎます。すべてはここに表示できません。 - - Unresolved conflicts - + + There were conflicts. Check the documentation on how to resolve them. + コンフリクトが発生しました。解決方法については、ドキュメントを確認してください。 - - Error during synchronization - + + The file %1 was ignored as its name is reserved by %2 + 名前が %2 によって予約されているため、ファイル %1 は無視されました - - No sync folders configured - + + Reset column sizes + カラムサイズのリセット - - Unpause all folders - すべてのフォルダーの同期を再開 + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + コンフリクトが発生しました。 <a href="%1">解決方法については、ドキュメントを確認してください。</a> - - Pause all folders - すべてのフォルダーの同期を一時停止 + + Filter menu + フィルターメニュー - - Unpause all synchronization - すべての同期を再開 + + Account + アカウント - - Unpause synchronization - 同期を再開 + + Filter + フィルター + + + OCC::SyncJournalDb - - Pause all synchronization - すべての同期を一時停止 + + Failed to connect database. + データベースへの接続に失敗しました。 + + + OCC::Theme - - Pause synchronization - 同期を一時停止 + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>バージョン %1 詳細については、<a href="%2">https://%3</a>をご覧ください</p><p>既知の問題とヘルプは、<a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a>をご覧ください。</p><p><small>Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller およびその他の貢献者。</small></p><p>著作権 ownCloud GmbH (A Kiteworks 社)</p><p>%4 によって配布され、GNU General Public License (GPL) バージョン 2.0 でライセンスされています。<br/>%5 および %5 ロゴは、アメリカ合衆国、その他の国、またはその両方における %4 の登録商標です。</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Log out of all accounts - 全てのアカウントからログアウト + + TLS Certificate Error + TLS 証明書エラー - - Log in to all accounts... - 全てのアカウントにログイン + + Do you want to trust this certificate anyway? + この証明書を信頼してもよろしいですか? - - New account... - 新規アカウント... + + Cannot connect securely to %1 + %1 にセキュアに接続できません - - About %1 - %1 について + + &lt;not specified&gt; + &lt;指定されていません&gt; - - No items synced recently - 最近同期されたアイテムはありません。 + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>証明書 %1 付き</h3><div id="ccert"><p>組織: %2<br/>ユニット%3<br/>国:%4</p><p>指紋 (MD5):<tt>%5</tt><br/>指紋 (SHA1):<tt>%6</tt><br/>指紋 (SHA256):<tt>%7</tt><br/><br/>発効日: %8有効期限: %9</div><h3>発行者: %10</h3><div id="issuer"><p>組織: %11<br/>ユニット%12<br/>国:%13</p></div></div> + + + OCC::TrayMenuController - - Checking for changes in remote '%1' - + + Unsupported Server Version + サポートされていないサーバーバージョン - - Checking for changes in local '%1' - + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + アカウント %1 のサーバーは サポートされていないバージョン %2 を実行しています。このクライアントをサポートされていないサーバーバージョンで使用することはテストされておらず、危険な状況です。自己責任で進めてください。 - - Syncing %1 of %2 (%3 left) - 同期中 %2 中 %1 (残り %3) + + Show %1 + %1を表示 - - Syncing %1 of %2 - %2 の %1 を同期しています + + Help + ヘルプ - - Syncing %1 (%2 left) - 同期中 %1 (残り %2) + + About %1 + %1 について - - Syncing %1 - 同期中 %1 + + Quit %1 + %1 を終了 + + + OCC::UpdateDownloadedWidget - - %1 (%2, %3) - %1 (%2, %3) + + Restart later + 後で再起動する - - Up to date - 最新です + + Restart now + 今すぐ再起動 - OwncloudAdvancedSetupPage + OCC::UpdaterScheduler - - Form - フォーム + + Update available + アップデートが利用できます + + + OCC::UrlPageController - - - - - - TextLabel - テキストラベル + + Your web browser will be opened to complete sign in. + - - Server - サーバー + + %1 logo + %1 ロゴ - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>チェックした場合、ローカルフォルダー内に存在するコンテンツはサーバーからクリーンな同期を開始するために削除されます。</p><p>もしローカルのコンテンツをサーバーのフォルダーにアップロードするなら、チェックしないでください。</p></body></html> + + Welcome to %1 + %1 へようこそ - - Start a &clean sync (Erases the local folder!) - クリーン同期を開始(ローカルフォルダーは削除されます!)(&C) + + Enter your server address to get started. Your web browser will be opened to complete sign in. + - - Ask for confirmation before synchroni&zing folders larger than - 指定された容量以上のフォルダーは同期前に確認する + + Server address field + - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + Enter your server address here + - - Ask for confirmation before synchronizing e&xternal storages - 外部ストレージと同期する前に確認する(&X) + + Additional logo defined by the organization + - - Choose what to sync - 同期フォルダーを選択 + + Invalid server URL: %1 + - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> + + Invalid URL scheme. Only https is accepted. - - Manually create folder sync connections - + + The requested URL failed with query value: %1 + 要求されたURLは、以下のクエリ値で失敗しました: %1 + + + OCC::UserInfoAdapter - - &Local Folder - ローカルフォルダー(&L) + + Network request to collect user info failed + - - pbSelectLocalFolder - pbSelectLocalFolder + + Unable to retrieve user info: invalid credentials. + - - &Keep local data - ローカルデータを保持(&K) + + Unexpected network response when retrieving user info. + - - S&ynchronize everything from server (recommended) + + Retrieving user info failed with JSON error. + + + OCC::ValidateChecksumHeader - - Status message - 状態メッセージ + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + ダウンロードされたファイルのチェックサムが不正です。ダウンロードを再開します。'%1' != '%2' - OwncloudHttpCredsPage + OCC::Vfs - - Form - フォーム + + The Virtual filesystem feature does not support a drive as sync root + 仮想ファイルシステム機能は、同期ルートとしてのドライブをサポートしていません - - &Username - ユーザー名(&U) + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + 仮想ファイルシステム機能を利用するには NTFS ファイルシステムが必要です。%1 は現在 %2 ファイルシステムを使用しています。 - - &Password - パスワード(&P) + + The Virtual filesystem feature is not supported on network drives + 仮想ファイルシステム機能はネットワークドライブではサポートされていません - OwncloudOAuthCredsPage + OCC::VfsWinPrivate - - Form - フォーム + + + %1 - %2 + %1 - %2 - - Please switch to your browser to proceed. + + Could not find StorageProviderState property %1 - - An error occurred while connecting. Please try again. + + Could not find StorageFolder for %1 error: %2 - - Re-open Browser + + Could not retrieve StorageFolder for %1 %2 (0x%3) - - - OwncloudSetupPage - - Form - フォーム + + The folder is used by a different client: %1 + - - - TextLabel - テキストラベル + + Error registering StorageProvider for %1: %2 (0x%3) + - - Ser&ver Address - サーバーアドレス(&V) + + Unable to connect sync root: %1 error: %2 + 同期ルートに接続できません: %1 エラー: %2 - - https://... - https://... + + %1 is managed by another sync client + %1 は別の同期クライアントによって管理されています - - Error Label - エラーラベル + + Downgrading the folder from %1 to %2 is not supported + - OwncloudWizardResultPage + OCC::WebFingerLookupAdapter - - Form - フォーム + + Failed to retrieve user info + ユーザー情報の取得に失敗しました + + + + WebFinger lookup returned no links + + + + + OCC::WindowsUpdater + + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>%1 クライアントの新しいバージョンが利用可能です。</p><p><b>%2</b> がダウンロード可能です。インストールされているバージョンは %3 です。<p> + + + + Skip this version + 今回のバージョンをスキップ + + + + Update Failed + アップデートに失敗 + + + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>%1 クライアントの新しいバージョンが利用可能ですが、更新プロセスに失敗しました。</p><p><b>%2</b> がダウンロードされました。インストールされているバージョンは %3 です。</p> - - TextLabel - テキストラベル + + Ask again later + 後でもう一度聞く - - Your entire account is synced to the local folder - すべてのアカウントはローカルフォルダーと同期されます + + Restart and update + 再起動してアップデート - - - PushButton - プッシュボタン + + Update manually + 手動で更新 QObject - + in the future 今後 - + %n day(s) ago - %n日前 + %n 日前 - + %n hour(s) ago %n 時間前 - + now - - Less than a minute ago - 1分以内 + + less than a minute ago + 1分も前に - + %n minute(s) ago - %n 分前 - - - - Some time ago - 数分前 + %n 分前 - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 - Utility + SpacesBrowser + + + Form + フォーム + + + + SpacesPage - - %L1 GB - %L1 GB + + Spaces list + スペース一覧 + + + + SpacesView + + + Spaces + スペース + + + + SyncFileItem::Status + + + Undefined + 未定義 + + + + Fatal Error + 致命的なエラー + + + + + Error + エラー + + + + Info + 情報 - - %L1 MB - %L1 MB + + Success + 成功 - - %L1 KB - %L1 KB + + Conflict + 競合 - - %L1 B - %L1 B + + File Ignored + ファイルは無視されました + + + Restored + Restored + + + + Blacklisted + ブロックされたサイト + + + + Excluded + 除外される + + + + Message + メッセージ + + + + Filename Reserved + ファイル名が予約されています + + + + UpdateDownloadedWidget + + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">ダウンロードされたアップデート</span></p></body></html> + + + + Utility - + %n year(s) %n 年 - + %n month(s) %n ヶ月 - + %n day(s) %n 日 - + %n hour(s) %n 時間 - + %n minute(s) %n 分 - + %n second(s) %n 秒 - + %1 %2 %1 %2 + + + Shutting down %1 + %1 をシャットダウンしています + - main.cpp + account loading - - System Tray not available - システムトレイ利用不可 + + Error accessing the configuration file + 設定ファイルのアクセスでエラーが発生しました - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1は動作しているシステムトレイで必要とします。XFCEを動作させている場合、<a href="http://docs.xfce.org/xfce/xfce4-panel/systray">これらのインストラクション</a>を追ってください。でなければ、「トレイヤー」のようなシステムトレイアプリケーションをインストールして、再度お試しください。 + + There was an error while accessing the configuration file at %1. + 設定ファイルの %1 行目にアクセスしている時にエラーが発生しました。 ownCloudTheme::aboutVersions() - - %1 %2 %3 %4%8%9Libraries Qt %5, %6%8Using virtual files plugin: %7%8 - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Libraries Qt %3, %4%7仮想ファイルプラグインの使用:%5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - + + %1 (Built against Qt %2) + %1 (Qt に対して構築 %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + 常にローカルで利用可能 + + + + Currently available locally + 現在ローカルで利用可能 + + + + Some available online only + オンラインのみで購入できるものもあります + + + + Available online only + オンラインのみ利用可能 + + progress Virtual file created - + 仮想ファイルが作成されました。 Replaced by virtual file - + 仮想ファイルに置き換えられる @@ -4471,30 +3560,30 @@ This is a new, experimental mode. If you decide to use it, please report any iss Uploaded アップロード済み - - - Server version downloaded, copied changed local file into conflict file - サーバー側バージョンがダウンロードされました。変更されたローカルファイルは、コンフリクトファイルにコピーしました。 - Deleted 削除済み - - Moved to %1 - %1に移動済み - - - + Ignored 除外しました - - Filesystem access error - ファイルシステムのアクセスエラー + + Server version downloaded, local copy was backed up as conflict file + サーバー側最新バージョンをダウンロードしました。競合が発生するため、ローカルコピーを競合ファイルとしてバックアップしました。 + + + + Renamed %1 to %2 + + + + + Moved %1 to %2 + @@ -4507,153 +3596,86 @@ This is a new, experimental mode. If you decide to use it, please report any iss ローカルメタデータが更新されました - - + + Unknown 不明 - + downloading ダウンロード中 - + uploading アップロード中 - + deleting 削除しています - + moving 移動しています - + ignoring 除外: - - + error エラー - + updating local metadata ローカルメタデータの更新中 - - theme - - - Status undefined - 未定義の状態 - - - - Waiting to start sync - 同期開始を待っています - - - - Sync is running - 同期を実行中 - - - - Sync Success - 同期成功 - - - - Sync Success, some files were ignored. - 同期が完了しました。一部のファイルが除外されました。 - - - - Sync Error - 同期エラー - - - - Setup Error - 設定エラー - - - - Preparing to sync - 同期の準備中 - - - - Aborting... - 中止しています... - - - - Sync is paused - 同期を一時停止 - - utility - + Could not open browser ブラウザを開くことができませんでした。 - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? URL %1 をブラウザで開く際にエラーが発生しました。おそらくデフォルトのブラウザが設定されていないのでは? - + Could not open email client 電子メールクライアントを開くことができませんでした - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? メールクライアントを起動して新しいメッセージを作成するときにエラーが発生しました。デフォルトのメールクライアントが設定されていませんか? - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - + + Make always available locally + 常にローカルで利用できるようにする - Make always available locally - + Free up local space + ローカルスペースを解放する + + + version check - - Free up local space - + + Some settings were configured in newer versions of this client and use features that are not available in this version + このクライアントの旧バージョン以降に追加された機能を利用する設定が検出されましたが、現在のバージョンではこれらの機能はサポートされていません。 \ No newline at end of file diff --git a/translations/client_ko.ts b/translations/client_ko.ts deleted file mode 100644 index 173cb680d80..00000000000 --- a/translations/client_ko.ts +++ /dev/null @@ -1,4891 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - 토마호크 충돌 보고서 - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - 중단 - - - - Send this report - 이 보고서 전송 - - - - Don't send - 전송 안함 - - - - You can disable sending crash reports in the configuration dialog. - 설정 대화상자에서 충돌 보고서 전송을 비활성화할 수 있습니다. - - - - - - We cannot gather useful debug information on your system. - 이 시스템에서 유용한 디버그 정보를 얻을 수 없습니다. - - - - - - - - Close - 닫기 - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - 디버그 정보 전송 준비 완료 (<a href="%1">백트레이스 보기</a>). - - - - Gathering debug information... - 디버그 정보 모으는 중... - - - - Uploaded %L1 of %L2 KB. - %L1 / %L2 KB 업로드됨. - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - 전송됨! <b>매우 감사합니다</b>. 버그 리포트에서 <a href="clipboard://%1"><b>%1</b></a> (클릭해서 복사) 충돌을 참조하기 바랍니다. - - - - Failed to send crash info. - 충돌 정보 전송 실패. - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - 현재 사용되고 있는 파일 명으로 %1 을(를) 개명할 수 없습니다. - - - - Could not make directories in trash - 휴지통에 디렉토리를 만들수 없습니다. - - - - - Could not move '%1' to '%2' - '%1' 을(를) '%2' 로 이동할 수 없습니다 - - - - Moving to the trash is not implemented on this platform - 이 플랫폼에서는 휴지통으로 이동하지 않습니다. - - - - Error removing '%1': %2 - '%1' 삭제 중 오류: %2 - - - - Could not remove folder '%1' - '%1' 폴더를 삭제할 수 없음 - - - - FolderWizardSourcePage - - - Form - - - - - Pick a local folder on your computer to sync - 컴퓨터와 동기화할 로컬 폴더를 선택하십시오 - - - - &Choose... - 선택(&C)... - - - - FolderWizardTargetPage - - - Form - - - - - Select a remote destination folder - 원격 대상 폴더 선택 - - - - Create Folder - 폴더 만들기 - - - - Refresh - 새로 고침 - - - - Folders - 폴더 - - - - TextLabel - 텍스트라벨 - - - - NotificationWidget - - - Form - - - - - Lorem ipsum dolor sit amet - 키스의 고유 조건은 입술끼리 - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - 키스의 고유 조건은 입술끼리 만나야 되고 특별한 요령은 필요치 않다 - - - - TextLabel - 텍스트라벨 - - - - OCC::AboutDialog - - - About - 정보 - - - - TextLabel - 텍스트라벨 - - - - Versions - 버전 - - - - About %1 - %1 정보 - - - - OCC::AbstractNetworkJob - - - Connection timed out - 연결 시간 초과됨 - - - - Unknown error: network reply was deleted - 알 수 없는 오류: 네트워크 응답이 삭제됨 - - - - Server replied "%1 %2" to "%3 %4" - 서버 응답: "%1 %2" - "%3 %4" - - - - OCC::Account - - - %1@%2 - - - - - OCC::AccountSettings - - - Form - - - - - Re-open Browser - 브라우저 다시 열기 - - - - ... - ... - - - - Storage space: ... - 저장 공간: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - 선택 해제된 폴더는 로컬 파일 시스템에서 <b>삭제</b>되며 이 컴퓨터와 동기화되지 않습니다 - - - - Synchronize all - 전체 동기화 - - - - Synchronize none - 동기화 안 함 - - - - Apply manual changes - 수동 변경 적용 - - - - Apply - 적용 - - - - - - - Cancel - 취소 - - - - Connected with <server> as <user> - <server>에 사용자 <user>(으)로 연결됨 - - - - No account configured. - 계정이 설정되지 않았습니다. - - - - Remove - 삭제 - - - - Account - 계정 - - - - Choose what to sync - 동기화할 항목 선택 - - - - Force sync now - 지금 강제 동기화 - - - - Restart sync - 동기화 재시작 - - - - Remove folder sync connection - 폴더 동기화 연결 제거 - - - - Folder creation failed - 폴더를 만들 수 없음 - - - - <p>Could not create local folder <i>%1</i>. - <p>로컬 폴더 <i>%1</i>을(를) 만들 수 없습니다. - - - - Confirm Folder Sync Connection Removal - 동기화 삭제 확인 - - - - Remove Folder Sync Connection - 폴더를 동기화에서 제거 - - - - Disable virtual file support? - 가상 파일 지원을 비활성화하겠습니까? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - 지원 비활성화 - - - - Sync Running - 동기화 진행 중 - - - - The syncing operation is running.<br/>Do you want to terminate it? - 동기화가 진행 중입니다.<br/>진행을 종료하시겠습니까? - - - - %1 in use - %1 사용 중 - - - - %1 as <i>%2</i> - %1에 <i>%2</i>(으)로 - - - - Connected to %1. - %1에 연결되었습니다. - - - - Server %1 is temporarily unavailable. - 서버 %1을(를) 임시로 사용할 수 없습니다. - - - - Server %1 is currently in maintenance mode. - 서버 %1이(가) 현재 관리 모드입니다. - - - - Signed out from %1. - %1에서 로그아웃했습니다. - - - - Connecting to %1... - %1에 연결 중... - - - - No connection to %1 at %2. - %2에 %1 연결이 없습니다. - - - - Log in - 로그인 - - - - There are folders that were not synchronized because they are too big: - 이 폴더는 너무 커서 동기화할 수 없습니다: - - - - There are folders that were not synchronized because they are external storages: - 이 폴더는 외부 저장소여서 동기화할 수 없습니다: - - - - There are folders that were not synchronized because they are too big or external storages: - 이 폴더는 너무 크거나 외부 저장소여서 동기화할 수 없습니다: - - - - Confirm Account Removal - 계정 삭제 확인 - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>계정 <i>%1</i>의 연결을 삭제하시겠습니까?</p><p><b>메모:</b> 이 작업은 파일을 삭제하지 <b>않습니다.</b></p> - - - - Remove connection - 연결 삭제 - - - - Open folder - 폴더 열기 - - - - - Log out - 로그아웃 - - - - Open local folder - 로컬 폴더 열기 - - - - Open folder in browser - 브라우저에서 폴더 열기 - - - - Resume sync - 동기화 다시 시작 - - - - Pause sync - 동기화 일시 정지 - - - - Availability - 가용성 - - - - Disable virtual file support... - 가상 파일 지원 비활성화... - - - - Enable virtual file support%1... - 가상 파일 지원 %1 활성화... - - - - (experimental) - (실험 기능) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>폴더 <i>%1</i>을(를) 동기화하지 않으시겠습니까?</p><p><b>메모:</b> 이 작업은 파일을 삭제하지 <b>않습니다.</b></p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %2 중 %1(%3%) 사용 중입니다. 네트워크 마운트 및 공유 폴더의 제한 사항은 다를 수 있습니다. - - - - %1 of %2 in use - %2 중 %1 사용 중 - - - - Currently there is no storage usage information available. - 저장소 사용량 정보가 없습니다. - - - - The server version %1 is unsupported! Proceed at your own risk. - 서버 버전 %1 은(는) 지원되지 않습니다! 자기 책임 하에 진행하기 바랍니다. - - - - Obtaining authorization from the browser. - 브라우저에서 인증 획득하는 중. - - - - Server configuration error: %1 at %2. - 서버 설정 에러: %2 의 %1. - - - - No %1 connection configured. - %1 연결이 설정되지 않았습니다. - - - - OCC::AccountState - - - Url update requested for %1 - %1 에 대한 url 갱신이 요청되었습니다 - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - url을 영구적으로 %1 로 변경 - - - - Reject - - - - - Signed out - 로그아웃됨 - - - - Disconnected - 연결 끊김 - - - - Connected - 연결됨 - - - - Service unavailable - 서비스를 사용할 수 없음 - - - - Maintenance mode - 관리 모드 - - - - Network error - 네트워크 오류 - - - - Configuration error - 설정 오류 - - - - Asking Credentials - 인증 정보 요청 중 - - - - Unknown account state - 알 수 없는 계정 상태 - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %3 에서 %1 %2 - - - - Activity - 활동 - - - - Account - 계정 - - - - Time - 시간 - - - - Local path - 로컬 경로 - - - - OCC::ActivitySettings - - - - Server Activity - 서버 활동 - - - - Sync Protocol - 동기화 프로토콜 - - - - Not Synced - 동기화되지 않음 - - - - Not Synced (%1) - %1 is the number of not synced files. - 동기화되지 않음 (%1) - - - - OCC::ActivityWidget - - - Form - - - - - - - TextLabel - 텍스트라벨 - - - - Filter - 필터 - - - - Server Activities - 서버 활동 - - - - Notifications - 알림 - - - - %n notifications(s) for %1. - %1 에 대한 %n개의 공지 사항. - - - - %n notifications(s) for %1 and %2. - %1 와(과) %2 에 대한 %n개의 공지 사항. - - - - New notifications for %1, %2 and other accounts. - %1, %2 와(과) 다른 계정에 대한 새 공지 사항들. - - - - Open the activity view for details. - - - - - Copy to clipboard - 클립보드로 복사 - - - - Show in file browser - 파일 브라우저에 보이기 - - - - <br/>Account %1 does not have activities enabled. - <br>%1 계정에 활동이 활성화되지 않았습니다. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - 끝내기 - - - - Continue - 계속 - - - - Error accessing the configuration file - 설정 파일 접근 오류 - - - - There was an error while accessing the configuration file at %1. - %1에 있는 설정 파일에 접근하는 중 오류가 발생했습니다. - - - - Quit %1 - %1 끝내기 - - - - %1 version %2 -File synchronization desktop utility. - %1 버전 %2<byte value="xd"/> -파일 동기화 데스크탑 유틸리티. - - - - For more information, see http://www.owncloud.org - 자세한 정보는 http://www.owncloud.org 를 참조하기 바랍니다 - - - - Show the settings dialog while starting. - 기동 시 설정 대화상자 보이기. - - - - Quit the running instance. - 실행 인스턴스 종료. - - - - Write log to file (use - to write to stdout). - 파일에 로그 기록하기(stdout에 기록하려면 - 이용). - - - - Write each sync log output in a new file in folder. - 각 동기화 로그 출력을 폴더의 새 파일에 기록. - - - - Remove logs older than <hours> hours (to be used with --logdir). - <hours> 시간 이상된 로그 삭제(--logdir 옵션과 함께 이용). - - - - Flush the log file after every write. - 모든 기록 후에 로그 파일을 만듭니다. - - - - Output debug-level messages in the log. - 로그에 디버그 레벨 메시지 출력합니다. - - - - - Override UI language. - UI 언어를 덮어씁니다. - - - - Use the given configuration folder. - 주어진 설정 폴더를 이용합니다. - - - - Enable debug mode. - 디버그 모드를 활성화합니다. - - - - Virtual file system file to be opened (optional). - 가상 파일 시스템 파일이 열립니다(옵션). - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - --confdir 옵션에 무효한 경로가 전달됨 - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - 이용 가능한 번역: %1 - - - - OCC::AuthenticationDialog - - - Authentication Required - 인증 필요함 - - - - Enter username and password for '%1' at %2. - %2의 '%1' 사용자 이름과 암호를 입력하십시오. - - - - &User: - 사용자(&U): - - - - &Password: - 암호(&P): - - - - OCC::ConnectionValidator - - - No ownCloud account configured - ownCloud 계정이 설정되지 않았음 - - - - The configured server for this client is too old - 이 클라이언트에 설정된 서버가 너무 오래됨 - - - - Please update to the latest server and restart the client. - 최신 서버로 업데이트한 다음 클라이언트를 다시 시작하십시오. - - - - Authentication error: Either username or password are wrong. - 인증 오류: 사용자 이름이나 암호가 잘못되었습니다. - - - - timeout - 시간 제한 - - - - The provided credentials are not correct - 지정한 인증 정보가 올바르지 않음 - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - 디렉토리 %1 열기 오류 - - - - Directory not accessible on client, permission denied - 권한이 거부되어 클라이언트에서 디렉토리를 접근할 수 없습니다 - - - - Directory not found: %1 - 디렉토리 찾지못함: %1 - - - - Filename encoding is not valid - 파일 이름 인코딩이 올바르지 않음 - - - - Error while reading directory %1 - 디렉토리 %1 읽기 오류 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - 칼럼 크기 재 설정 - - - - OCC::Folder - - - Local folder %1 does not exist. - 로컬 폴더 %1이(가) 존재하지 않습니다. - - - - %1 should be a folder but is not. - %1이(가) 폴더여야 하지만 그렇지 않습니다. - - - - %1 is not readable. - %1을(를) 읽을 수 없습니다. - - - - %1 has been removed. - %1 names a file. - %1이(가) 삭제되었습니다. - - - - %1 has been updated. - %1 names a file. - %1이(가) 업데이트되었습니다. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1이(가) %2(으)로 이름이 변경되었습니다. - - - - %1 has been moved to %2. - %1이(가) %2(으)로 이동되었습니다. - - - - %1 and %n other file(s) have been removed. - %1 및 %n개의 다른 파일이 삭제되었습니다. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - %1 와(과) %n 기타 파일(들)이 추가되었습니다. - - - - %1 has been added. - %1 names a file. - %1 추가됨. - - - - %1 and %n other file(s) have been updated. - %1 및 %n개 다른 파일이 업데이트되었습니다. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1이(가) %2(으)로 이름이 변경되었고 %n개의 다른 파일도 이름이 변경되었습니다. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1(이)가 %2(으)로 이동되었고 %n개의 다른 파일도 이동되었습니다. - - - - %1 has and %n other file(s) have sync conflicts. - %1 및 %n개 다른 파일이 동기화 시 충돌합니다. - - - - %1 has a sync conflict. Please check the conflict file! - %1 파일이 동기화 시 충돌합니다. 충돌 파일을 확인해 주십시오! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 및 %n개 다른 파일을 동기화하는 중 오류가 발생했습니다. 자세한 내용은 로그를 확인하십시오. - - - - %1 could not be synced due to an error. See the log for details. - 오류가 발생하여 %1을(를) 동기화할 수 없습니다. 자세한 정보는 로그를 참조하십시오. - - - - Sync Activity - 동기화 활동 - - - - Could not read system exclude file - 시스템 제외 파일을 읽을 수 없음 - - - - A new folder larger than %1 MB has been added: %2. - - %1MB 보다 큰 새 폴더 추가됨: %2. - - - - - A folder from an external storage has been added. - - 외부 저장소에 있는 폴더가 추가되었습니다. - - - - - Please go in the settings to select it if you wish to download it. - 다운로드하려면 설정에서 선택하십시오. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - 폴더 %1 이(가) 생성되었지만 이전에 동기화에서 제외되었습니다. 이 폴더 내부의 데이터는 동기화되지 않을 것입니다. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - 파일 %1 이(가) 생성되었지만 이전에 동기화에서 제외되었습니다. 이 파일은 동기화되지 않을 것입니다. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - 동기화 폴더 '%1'의 모든 파일이 서버에서 삭제되었습니다. -이 작업은 로컬 동기화 폴더와 동기화되기 때문에 파일을 복원할 수 없는 경우 더 이상 사용할 수 없습니다. -파일을 보존하려면 파일에 접근할 수 있는 권한이 있는 경우 서버와 다시 동기화됩니다. -파일을 삭제하려면 파일 소유자가 아니라면 해당 파일을 사용할 수 없습니다. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - 동기화 폴더 '%1'의 모든 파일이 삭제되었습니다. 이 작업은 서버와 동기화되기 때문에 서버에서 파일을 복원하지 않으면 더 이상 사용할 수 없습니다. -이 삭제 작업을 서버와 동기화 하시겠습니까? -만약 이 작업이 잘못되었고 파일을 보존하려면 서버에서 다시 동기화 됩니다. - - - - Remove All Files? - 모든 파일을 삭제하시겠습니까? - - - - Remove all files - 모든 파일 삭제 - - - - Keep files - 파일 유지 - - - - OCC::FolderMan - - - Could not reset folder state - 폴더 상태를 초기화할 수 없음 - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - 과거의 동기화 저널 '%1'을(를) 찾았으나 삭제할 수 없습니다. 사용 중인 프로그램이 없는지 확인하십시오. - - - - (backup) - (백업) - - - - (backup %1) - (백업 %1) - - - - Undefined State. - 정의되지 않은 상태입니다. - - - - Waiting to start syncing. - 동기화가 시작되기를 기다리는 중입니다. - - - - Preparing for sync. - 동기화 준비 중입니다. - - - - Sync is running. - 동기화 중입니다. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - 마지막 동기화가 성공했습니다. - - - - Setup Error. - 설치 오류입니다. - - - - User Abort. - 사용자가 중단했습니다. - - - - Sync is paused. - 동기화가 일시 정지되었습니다. - - - - %1 (Sync is paused) - %1(동기화 중지됨) - - - - No valid folder selected! - 올바른 폴더를 선택하지 않았습니다! - - - - The folder %1 is used in a folder sync connection! - %1 폴더는 폴더 동기화 연결에 이용됩니다! - - - - The selected path does not exist! - 선택된 경로가 존재하지 않습니다! - - - - The selected path is not a folder! - 선택한 경로가 폴더가 아닙니다! - - - - You have no permission to write to the selected folder! - 선택한 폴더에 쓸 수 있는 권한이 없습니다! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - 이미 서버와 이 로컬 폴더가 동기화되었습니다. 다른 로컬 폴더를 선택하십시오! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - 로컬 폴더 %1이(가) 폴더 동기화 연결에 사용 중인 폴더를 포함하고 있습니다. 다른 폴더를 선택하십시오! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - 로컬 폴더 %1이(가) 폴더 동기화 연결에 사용 중인 폴더에 포함되어 있습니다. 다른 폴더를 선택하십시오! - - - - %1 Please pick another one! - %1 다른 것을 선택하십시오! - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - 폴더 동기화 연결 추가 - - - - File - 파일 - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - 폴더를 추가하려면 연결해야 합니다 - - - - Click this button to add a folder to synchronize. - 동기화할 폴더를 추가하려면 이 단추를 누르십시오. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1(%2) - - - - Error while loading the list of folders from the server. - 서버에서 폴더 목록을 불러오는 중 오류가 발생했습니다. - - - - Virtual file support is enabled. - 가상 파일 지원이 활성화되었습니다. - - - - Signed out - 로그아웃됨 - - - - Fetching folder list from server... - 서버로부터 폴더 목록을 가져오고 있습니다... - - - - There are unresolved conflicts. Click for details. - 해결하지 못한 충돌 사항이 있습니다. 자세한 정보를 보려면 누르십시오. - - - - Checking for changes in remote '%1' - 원격 '%1' 에서의 변경 내용 점검 중 - - - - Checking for changes in local '%1' - 로컬 '%1' 에서의 변경 내용 점검 중 - - - - Reconciling changes - 변경 사항 조정 중 - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - %1 동기화 중 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - 다운로드 %1/초 - - - - ↓ %1/s - - - - - ↑ %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - 업로드 %1/초 - - - - Local folder: %1 - 로컬 폴더: %1 - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %2 %1 중(%4 중 %3) - - - - %1 %2 - Example text: "uploading foobar.png" - %2 %1 중 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 남음, %2 중 %1, %4 중 %3 파일 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %2 중 %1, 파일 %4 중 %3 - - - - file %1 of %2 - 파일 %2 중 %1 - - - - Waiting... - 대기 중... - - - - Waiting for %n other folder(s)... - 다른 폴더 %n개 대기 중... - - - - Preparing to sync... - 동기화 준비 중... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - 폴더 동기화 연결 추가 - - - - Add Sync Connection - 동기화 연결 추가 - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - 동기화할 로컬 폴더를 선택하려면 누르십시오. - - - - Enter the path to the local folder. - 로컬 폴더 경로를 입력하십시오. - - - - Select the source folder - 원본 폴더 선택 - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - 원격 폴더 만들기 - - - - Enter the name of the new folder to be created below '%1': - '%1' 아래에 생성할 새 폴더 이름을 입력하십시오: - - - - Folder was successfully created on %1. - %1에 폴더를 만들었습니다. - - - - Authentication failed accessing %1 - %1에 접근하는 중 인증 오류 발생 - - - - Failed to create the folder on %1. Please check manually. - %1에 폴더를 만들 수 없습니다. 직접 확인하십시오. - - - - Failed to list a folder. Error: %1 - 폴더 목록을 가져올 수 없습니다. 오류: %1 - - - - Choose this to sync the entire account - 모든 계정을 동기화하려면 선택하십시오 - - - - This folder is already being synced. - 이 폴더가 이미 동기화 중입니다. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - 이미 <i>%2</i>의 부모 폴더 <i>%1</i>을(를) 동기화하고 있습니다. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - 이미 <i>%2</i>의 하위 폴더인 <i>%1</i>을(를) 동기화하고 있습니다. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - (실험 기능) - - - - Virtual files are not available for the selected folder - 선택 폴더에서 가상 파일을 이용할 수 없습니다 - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>경고:</b> %1 - - - - <b>Warning:</b> - <b>경고:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - 서버에서 E-Tag를 받지 못했습니다. 프록시/게이트웨이를 확인하십시오 - - - - We received a different E-Tag for resuming. Retrying next time. - 계속 진행하기 위하여 받은 E-Tag가 다릅니다. 잠시 후 다시 시도합니다. - - - - We received an unexpected download Content-Length. - 예기치 않은 다운로드 컨텐트 길이를 전송 받았습니다. - - - - Server returned wrong content-range - 서버에서 잘못된 content-range를 반환함 - - - - OCC::GETJob - - - Connection Timeout - 연결 시간 초과됨 - - - - OCC::GeneralSettings - - - Form - - - - - General Settings - 일반 설정 - - - - Language - 언어 - - - - Sync hidden files - 숨김 파일 동기화 - - - - Log Settings - 로그 설정 - - - - Advanced - Approval of folder sync (Non virtual file mode only) - 고급 - 폴더 동기화 승인(비 가상 파일 모드로만) - - - - Network - 네트워크 - - - - Updates - 업데이트 - - - - stable - 안정 - - - - beta - 베타 - - - - About - 정보 - - - - Advanced - 고급 - - - - Use &Monochrome Icons in the system tray - 시스템 트레이에 모노크롬(&M) 아이콘 이용하기 - - - - Ask for confirmation before synchronizing folders larger than - 다음보다 큰 폴더 동기화 전에 확인 - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - 외부 저장소 동기화 전에 확인 - - - - &Launch on System Startup - 시스템 시작 시 실행(&L) - - - - Show &Desktop Notifications - 데스크톱 알림 표시(&D) - - - - &Update Channel - 채널 갱신(&U) - - - - Edit &Ignored Files - 무시된 파일 수정하기(&I) - - - - Show sync folders in &Explorer's Navigation Pane - 탐색기(&E)의 네비게이션 창에 동기화 폴더 보이기 - - - - S&how crash reporter - 충돌 보고 도구 보기(&H) - - - - &Restart && Update - 다시 시작하고 업데이트(&R) - - - - You cannot disable autostart because system-wide autostart is enabled. - 시스템 차원의 자동 시작이 활성화되었으므로 자동 시작을 비활성화할 수 없습니다. - - - - Change update channel? - 업데이트 채널 변경할까요? - - - - Warning - 경고 - - - - Language changes require a restart of this application to take effect. - 언어 변경은 응용프로그램을 다시 시작해야 적용됩니다. - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - 업데이트 채널 변경 - - - - Cancel - 취소 - - - - unknown (%1) - - - - - (use default) - (기본 값 사용) - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - %1 암호를 입력하십시오:<br> <br>사용자: %2<br>계정: %3<br> - - - - Reading from keychain failed with error: '%1' - 키 체인에서 읽어올 수 없음: '%1' - - - - Enter Password - 암호 입력 - - - - <a href="%1">Click here</a> to request an app password from the web interface. - 웹 인터페이스에서 앱 암호를 요청하려면 <a href="%1">여기</a>를 누르십시오. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - 무시된 파일 편집기 - - - - Files Ignored by Patterns - 패턴으로 파일 무시 - - - - Add - 추가 - - - - Pattern - 패턴 - - - - Allow Deletion - 삭제 허용 - - - - Remove - 삭제 - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - 파일을 열 수 없음 - - - - Cannot write changes to '%1'. - '%1'에 변경 사항을 저장할 수 없습니다. - - - - Add Ignore Pattern - 무시 패턴 추가 - - - - Add a new ignore pattern: - 새 무시 패턴 추가: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - 이 항목은 시스템의 '%1'에서 제공되며 여기에서 수정할 수 없습니다. - - - - OCC::IssuesWidget - - - Form - - - - - List of issues - 문제점 목록 - - - - Filter - 필터 - - - - There were too many issues. Not all will be visible here. - 문제점이 너무 많습니다. 일부 문제점만 표시합니다. - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - The file %1 was ignored as its name is reserved by %2 - %1 파일은 그 이름이 %2 에 의해 예약되어 있으므로 무시됩니다 - - - - Reset column sizes - 칼럼 크기 재 설정 - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - Account - 계정 - - - - OCC::LibCloudProvidersPrivate - - - Settings - 설정 - - - - OCC::LogBrowser - - - Log Output - 로그 출력 - - - - Enable logging to temporary folder - 임시 폴더에 로깅 활성화 - - - - Delete logs older than %1 hours - %1 시간 이상 된 로그 삭제 - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - 활성화하면 로그가 기록될 장소: - - - - C:/log - - - - - Log Http traffic - Http 트래픽 기록 - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - 폴더 열기 - - - - OCC::Models - - - %1 Filter: - - - - - No filter - 필터 없음 - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - 새 버전 사용 가능 - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>%1 클라이언트의 새 버전을 사용할 수 있습니다.</p><p><b>%2</b>을(를) 다운로드할 수 있습니다. 설치된 버전은 %3입니다.</p> - - - - - Skip this version - 이 버전 건너뛰기 - - - - Skip this time - 이번에만 건너뛰기 - - - - Get update - 업데이트하기 - - - - Update Failed - 업데이트 실패 - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - 나중에 다시 묻기 - - - - Restart and update - 재시작 및 업데이트 - - - - Update manually - 수동 업데이트 - - - - OCC::NetworkSettings - - - Form - - - - - Proxy Settings - 프록시 설정 - - - - No Proxy - 프록시 없음 - - - - Use system proxy - 시스템 프록시 사용 - - - - Specify proxy manually as - 수동으로 프록시 지정 - - - - Host - 호스트 - - - - : - : - - - - Proxy server requires authentication - 프록시 서버에 인증이 필요함 - - - - Note: proxy settings have no effects for accounts on localhost - 주의: 프록시 설정은 localhost에 있는 계정에는 효과가 없습니다 - - - - Download Bandwidth - 다운로드 대역폭 - - - - - Limit to - 다음으로 제한 - - - - - KBytes/s - KBytes/s - - - - - No limit - 제한 없음 - - - - - Limit to 3/4 of estimated bandwidth - 예상 대역폭의 3/4으로 제한 - - - - Upload Bandwidth - 업로드 대역폭 - - - - - Limit automatically - 자동 제한 - - - - Hostname of proxy server - 프록시 서버 호스트 이름 - - - - Username for proxy server - 프록시 서버 사용자 이름 - - - - Password for proxy server - 프록시 서버 암호 - - - - HTTP(S) proxy - HTTP(S) 프록시 - - - - SOCKS5 proxy - SOCKS5 프록시 - - - - OCC::NotificationWidget - - - Created at %1 - %1에 생성됨 - - - - Closing in a few seconds... - 잠시 후 닫음... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 요청이 %2에 실패함 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1'이(가) %2에 선택됨 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - 서버에서 오류 반환함: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - 'token' 엔드포인트에 접근하는 중 오류 발생: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - 서버에서 반환한 JSON을 처리할 수 없음: <br><em>%1</em> - - - - Unsupported token type: %1 - 지원되지 않는 토큰 타입: %1 - - - - The reply from the server did not contain all expected fields -:%1 - 서버의 응답에 기대되는 모든 필드가 포함되지 않았습니다 -:%1 - - - - Unknown Error - 알 수 없는 오류 - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>로그인 오류</h1><p>사용자 정보 가져오기 실패</p> - - - - Oauth2 Error - Oauth2 오류 - - - - Oauth2 authentication requires a secured connection. - Oauth2 인증은 보안 연결을 필요로 합니다. - - - - <h1>Login Error</h1><p>%1</p> - <h1>로그인 오류</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - 토큰 갱신 오류: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>잘못된 사용자</h1><p>현재 <em>%1</em> 사용자로 로그인되어 있지만, 계속 진행하려면 <em>%2</em> 사용자로 로그인해야 합니다.<br>다른 탭에서 %3에서 로그아웃한 다음 <a href='%4'>여기</a>를 눌러서 %2 사용자로 다시 로그인하십시오</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - 새 %1 업데이트 준비됨 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - %1의 새 버전을 설치하려고 합니다. 설치 과정 중 -업데이터에서 추가 권한을 요청할 수 있습니다. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - 업데이트를 다운로드할 수 없습니다. <a href='%1'>여기</a>에서 업데이트를 직접 다운로드하십시오. - - - - Could not check for new updates. - 새로운 업데이트를 확인할 수 없습니다. - - - - Downloading %1. Please wait... - %1 다운로드 중. 기다리기 바람... - - - - %1 available. Restart application to start the update. - %1 이용 가능. 업데이트하려면 응용프로그램을 재 시작하십시오. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - %1 새로 이용 가능. 설치하려면 시스템의 업데이트 도구를 이용하십시오. - - - - Checking update server... - 업데이트 서버 확인 중... - - - - Update status is unknown: Did not check for new updates. - 업데이트 상태를 알 수 없습니다: 새 업데이트를 확인하지 않았습니다. - - - - No updates available. Your installation is at the latest version. - 사용 가능한 업데이트가 없습니다. 최신 버전을 사용하고 있습니다. - - - - Update Check - 업데이트 확인 - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - %1에 연결 - - - - Setup local folder options - 로컬 폴더 설정 옵션 - - - - Connect... - 연결... - - - - (experimental) - (실험 기능) - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - <p><strong>경고:</strong> 로컬 폴더가 비어 있지 않습니다. 해결책을 선택하십시오!</p> - - - - Sync the folder '%1' - 폴더 '%1' 동기화 - - - - Virtual files are not available for the selected folder - 선택 폴더에서 가상 파일을 이용할 수 없습니다 - - - - Error - 오류 - - - - Local Sync Folder - 로컬 동기화 폴더 - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - 이메일(&E) - - - - Connect to %1 - %1에 연결 - - - - Enter user credentials - 사용자 인증 정보 입력 - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - %1에 연결 - - - - Login in your browser - 내 브라우저에 로그인 - - - - Copy link to clipboard - 클립보드에 링크 복사 - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - 파일 %1 은(는) 현재 이용 중입니다 - - - - OCC::OwncloudSetupPage - - - Connect to %1 - %1에 연결 - - - - Setup %1 server - %1 서버 설정 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - 이 URL은 암호화되어 있지 않기 때문에 안전하지 않습니다. -사용하는 것을 추천하지 않습니다. - - - - This url is secure. You can use it. - 이 URL은 안전합니다. 사용하셔도 됩니다. - - - - &Next > - 다음(&N) > - - - - Invalid URL - 잘못된 URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - %2의 %1에 연결할 수 없음:<br>%3 - - - - Timeout while trying to connect to %1 at %2. - %2의 %1에 연결하는 중 시간이 초과되었습니다. - - - - Invalid URL - 잘못된 URL - - - - Could not create local folder %1 - 로컬 폴더 %1을(를) 만들 수 없음 - - - - Error: %1 - 오류: %1 - - - - The folder creation resulted in HTTP error code %1 - 폴더 생성 작업이 HTTP 오류 %1을(를) 반환하였습니다 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - 입력한 인증 정보가 잘못되어 원격 폴더를 만들 수 없습니다!<br/>뒤로 돌아가서 인증 정보를 확인하십시오.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - 원격 폴더 %1을(를) 생성하는 중 오류 <tt>%2</tt>이(가) 발생했습니다. - - - - Folder rename failed - 폴더 이름 바꾸기 실패 - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - 폴더에 포함된 파일이나 다른 폴더를 다른 프로그램에서 사용하고 있어서 폴더를 삭제하고 백업할 수 없습니다. 폴더나 파일을 닫은 후 다시 시도를 누르거나 설치를 취소할 수 있습니다. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 연결 마법사 - - - - Enable experimental feature? - 실험 기능을 활성화하겠습니까? - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - 동기화 시 심볼릭 링크는 지원되지 않습니다. - - - - File is listed on the ignore list. - 파일이 무시 목록에 포함되어 있습니다. - - - - File names ending with a period are not supported on this file system. - 이 파일 시스템은 점으로 끝나는 파일 이름을 지원하지 않습니다. - - - - File names containing the character '%1' are not supported on this file system. - 이 파일 시스템은 '%1' 글자를 포함하는 파일 이름을 지원하지 않습니다. - - - - File name contains at least one invalid character - 파일 이름에 하나 이상의 사용할 수 없는 문자가 포함되어 있음 - - - - The file name is a reserved name on this file system. - 이 파일 이름은 현재 파일 시스템에서 예약된 이름입니다. - - - - Filename contains trailing spaces. - 파일 이름 끝에 공백이 있습니다. - - - - Filename is too long. - 파일 이름이 너무 깁니다. - - - - File/Folder is ignored because it's hidden. - 파일이나 폴더가 숨김 상태이기 때문에 무시합니다. - - - - Stat failed. - 파일 정보를 읽을 수 없습니다. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - 충돌: 서버 버전을 다운로드했고, 로컬 복사본의 이름을 변경했으며 업로드하지 않았습니다. - - - - The filename cannot be encoded on your file system. - 이 파일 이름을 현재 파일 시스템으로 인코딩할 수 없습니다. - - - - The filename is blacklisted on the server. - 이 파일 명은 서버의 블랙리스트에 들어 있습니다. - - - - size - 크기 - - - - permissions - 권한 - - - - etag - - - - - file id - 파일 id - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - 삭제가 허용되지 않음, 복원 중 - - - - Ignored because of the "choose what to sync" blacklist - "동기화할 항목 선택" 블랙리스트에 추가되어 있어서 거부됨 - - - - Not allowed because you don't have permission to add subfolders to that folder - 해당 폴더에 하위 폴더를 추가할 권한이 없어서 허용되지 않음 - - - - Not allowed because you don't have permission to add files in that folder - 해당 폴더에 파일을 추가할 권한이 없어서 허용되지 않음 - - - - Not allowed to upload this file because it is read-only on the server, restoring - 서버에서 읽기 전용으로 공유 중이어서 허용되지 않음, 복원 중 - - - - Error while reading the database - 데이터베이스 읽기 오류 - - - - Server replied with an error while reading directory '%1' : %2 - - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - 메타데이터 갱신 오류: %1 - - - - %1 the folder is currently in use - %1 폴더는 현재 이용 중입니다 - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - 공간 해제 실패, 파일 %1 은(는) 현재 이용 중입니다 - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - 이 파일을 다운로드하면 로컬 디스크 공간이 제한 이하로 남게 됨 - - - - Free space on disk is less than %1 - 디스크에 남은 공간이 %1보다 적음 - - - - File was deleted from server - 서버에서 파일이 삭제됨 - - - - The file could not be downloaded completely. - 파일을 완전히 다운로드할 수 없습니다. - - - - The downloaded file is empty despite the server announced it should have been %1. - 서버에서 알려 온 파일 크기가 %1이지만 다운로드한 파일이 비어 있습니다. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - 메타데이터 갱신 에러: %1 - - - - - - - The file %1 is currently in use - 파일 %1 은(는) 현재 이용 중입니다 - - - - - File has changed since discovery - 발견 이후 파일이 변경됨 - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - , 복원 실패: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - %1 파일을 삭제할 수 없음, 오류: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - 로컬 파일 명이 %2 와 충돌하기 때문에 로컬 폴더 %1 을(를) 생성할 수 없습니다 - - - - Error updating metadata: %1 - 메타데이터 갱신 에러: %1 - - - - The file %1 is currently in use - 파일 %1 은(는) 현재 이용 중입니다 - - - - could not create folder %1 - 폴더 %1을(를) 만들 수 없음 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - 로컬 파일 명이 %2 와 충돌하기 때문에 %1 을(를) 제거할 수 없습니다! - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - 로컬 파일 이름이 충돌하여 파일 %1의 이름을 %2(으)로 변경할 수 없음 - - - - Could not rename %1 to %2, the file is currently in use - %1 을(를) %2 로 개명할 수 없습니다, 이 파일이 현재 사용 중입니다 - - - - Error updating metadata: %1 - 메타데이터 갱신 에러: %1 - - - - The file %1 is currently in use - 파일 %1 은(는) 현재 이용 중입니다 - - - - Failed to rename file - 파일 개명에 실패했습니다 - - - - Error setting pin state - 핀 상태 설정 에러 - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - 서버에서 잘못된 HTTP 코드를 반환했습니다. 204를 예상했지만 '%1 %2'을(를) 받았습니다. - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - 서버에서 잘못된 HTTP 코드를 반환했습니다. 201을 예상했지만 '%1 %2'을(를) 받았습니다. - - - - Error writing metadata to the database: %1 - 데이터베이스에 메타데이터 기록 오류: %1 - - - - Setting file status failed due to file lock - 파일 잠금에 의한 파일 상태 설정 실패 - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - %1 을(를) %2 로 개명할 수 없습니다, 오류: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - 서버에서 잘못된 HTTP 코드를 반환했습니다. 201을 예상했지만 "%1 %2"을(를) 받았습니다. - - - - Error updating metadata: %1 - 메타데이터 갱신 에러: %1 - - - - The file %1 is currently in use - 파일 %1 은(는) 현재 이용 중입니다 - - - - Error writing metadata to the database - 데이터베이스에 메타데이터 기록 중 오류 - - - - Error setting pin state - 핀 상태 설정 에러 - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - 파일을 갱신할 수 없습니다 : %1 - - - - The file %1 is currently in use - 파일 %1 은(는) 현재 이용 중입니다 - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - 대소문자만 다른 파일 이름이 이미 존재하기 때문에 %1 파일을 업로드할 수 없습니다 - - - - File Removed - 파일이 제거됨 - - - - Local file changed during syncing. It will be resumed. - 로컬 파일이 동기화 중 변경되었습니다. 다시 시작됩니다. - - - - Local file changed during sync. - 로컬 파일이 동기화 중 변경되었습니다. - - - - Error updating metadata: %1 - 메타데이터 갱신 에러: %1 - - - - The file %1 is currently in use - 파일 %1 은(는) 현재 이용 중입니다 - - - - - Upload of %1 exceeds the quota for the folder - %1을(를) 업로드하면 폴더 할당량을 초과함 - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - %1 파일은 현재 이용 중입니다 - - - - The local file was removed during sync. - 로컬 파일이 동기화 중 삭제되었습니다. - - - - Local file changed during sync. - 로컬 파일이 동기화 중 변경되었습니다. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - 서버에서 예상하지 못한 응답 코드를 반환함(%1) - - - - Missing File ID from server - 서버에 파일 ID가 없음 - - - - Missing ETag from server - 서버에 ETag가 없음 - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - %1 파일은 현재 이용 중입니다 - - - - The local file was removed during sync. - 로컬 파일이 동기화 중 삭제되었습니다. - - - - Local file changed during sync. - 로컬 파일이 동기화 중 변경되었습니다. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - %1 파일은 현재 이용 중입니다 - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - 로컬 파일이 동기화 중 삭제되었습니다. - - - - Local file changed during sync. - 로컬 파일이 동기화 중 변경되었습니다. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - 서버에서 마지막 조각을 확인하지 않았습니다.(e-tag가 없음) - - - - OCC::ProtocolItemModel - - - Time - 시간 - - - - File - 파일 - - - - Folder - 폴더 - - - - Issues - 문제점 - - - - Action - 동작 - - - - Size - 크기 - - - - Account - 계정 - - - - Status - 상태 - - - - OCC::ProtocolWidget - - - Form - - - - - Local sync protocol - 로컬 동기화 프로토콜 - - - - Filter - 필터 - - - - Account - 계정 - - - - Copy to clipboard - 클립보드로 복사 - - - - Show in file browser - 파일 부라우저에 보이기 - - - - Show in web browser - 웹 브라우져에 보이기 - - - - Retry sync - 동기화 재시도 - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - 프록시 인증 필요함 - - - - Username: - 사용자 이름: - - - - Proxy: - 프록시: - - - - The proxy server needs a username and password. - 프록시 서버에 사용자 이름과 암호가 필요합니다. - - - - Password: - 암호: - - - - TextLabel - 텍스트라벨 - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - 동기화할 항목 선택 - - - - OCC::SelectiveSyncWidget - - - Loading ... - 불러오는 중... - - - - Deselect remote folders you do not wish to synchronize. - 동기화하지 않을 원격 폴더의 선택을 해제하십시오. - - - - Name - 이름 - - - - Size - 크기 - - - - - No subfolders currently on the server. - 현재 서버에 하위 디렉터리가 없습니다. - - - - An error occurred while loading the list of sub folders. - 하위 폴더 목록을 불러오는 중 오류가 발생했습니다. - - - - OCC::ServerNotificationHandler - - - Dismiss - 닫기 - - - - OCC::SettingsDialog - - - Settings - 설정 - - - - Activity - 활동 - - - - Add account - 계정 추가 - - - - - Quit %1 - %1 끝내기 - - - - Are you sure you want to quit %1? - %1 을(를) 종료하고 싶습니까? - - - - Account - 계정 - - - - MainWindow - 메인 창 - - - - Hide - 숨기기 - - - - OCC::ShareDialog - - - TextLabel - 텍스트라벨 - - - - share label - 공유 이름표 - - - - Dialog - 다이얼로그 - - - - ownCloud Path: - ownCloud 경로: - - - - %1 Sharing - %1 공유 - - - - %1 - %1 - - - - Folder: %2 - 폴더: %2 - - - - The server does not allow sharing - 서버에서 공유를 허용하지 않음 - - - - Retrieving maximum possible sharing permissions from server... - 서버에서 최대로 사용 가능한 공유 권한 확인하는 중... - - - - The file can not be shared because it was shared without sharing permission. - 공유 권한 없이 공유되었기 때문에 파일을 공유할 수 없습니다. - - - - Users and Groups - 사용자 및 그룹 - - - - Public Links - 공개 링크 - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - 공유된 새 문서.odt - - - - Upload only (File Drop) - 업로드만(파일 끌어다 놓기) - - - - Receive files from others without revealing the contents of the folder. - 폴더 내용을 공개하지 않고 다른 사용자로부터 파일을 받습니다. - - - - Download / View / Edit - 다운로드 / 보기 / 편집 - - - - Name: - 이름: - - - - Enter name of new share... - 새 공유 이름 입력... - - - - &Create - 생성(&C) - - - - TextLabel - 텍스트라벨 - - - - Set &password - 암호 설정(&P) - - - - Set &expiration date - 만료 날짜 설정(&E) - - - - Set password - 암호 설정 - - - - Recipients can view or download contents. - - - - - Download / View - 다운로드 / 보기 - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - 링크가 있는 모든 사용자는 파일/폴더에 접근할 수 있습니다 - - - - - P&assword protect - 암호 보호(&A) - - - - The file can not be shared because it was shared without sharing permission. - 공유 권한 없이 공유되었기 때문에 파일을 공유할 수 없습니다. - - - - Link shares have been disabled - 링크 공유가 비활성화됨 - - - - Create public link share - 공개 링크 공유 만들기 - - - - - Delete - 삭제 - - - - Open link in browser - 브라우저에서 링크 열기 - - - - Copy link to clipboard - 클립보드에 링크 복사 - - - - Copy link to clipboard (direct download) - 클립보드에 링크 복사(직접 다운로드) - - - - Send link by email - 이메일로 링크 보내기 - - - - Send link by email (direct download) - 이메일로 링크 보내기(직접 다운로드) - - - - Create new... - 새로 만들기... - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - 링크 공유 삭제 확인 - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>공개 링크 공유 <i>%1</i>을(를) 삭제하시겠습니까?</p><p>메모: 이 작업은 취소할 수 없습니다.</p> - - - - Cancel - 취소 - - - - - Public link - 공개 링크 - - - - Delete link share - 링크 공유 삭제 - - - - - Please Set Password - 암호를 설정하십시오 - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - 공유된 새 문서.odt - - - - Share with users or groups ... - 다음 사용자나 그룹에 공유하기 ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - 이 항목은 다른 사람이나 그룹과 공유되지 않았습니다 - - - - Open link in browser - 브라우저로 링크 열기 - - - - Copy link to clipboard - 클립보드에 링크 복사 - - - - Send link by email - 이메일로 링크 보내기 - - - - No results for '%1' - '%1'의 결과 없음 - - - - I shared something with you - 무언가를 여러분과 공유했습니다 - - - - OCC::ShareUserLine - - - Form - - - - - - TextLabel - 텍스트라벨 - - - - can edit - 편집 가능 - - - - can share - 공유 가능 - - - - ... - ... - - - - create - 생성 - - - - change - 변경 - - - - delete - 삭제 - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1(%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - %1(으)로 공유하기 - - - - Context menu share - - - - - Context menu share %1 - - - - - Confirm deletion - - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - - - - - Do you want to delete the file <i>%1</i> permanently? - - - - - Select new location... - 새로운 경로 선택... - - - - Error - 오류 - - - - Moving file failed: - -%1 - 파일 이동 실패: - -%1 - - - - I shared something with you - 무언가를 여러분과 공유했습니다 - - - - - Share... - 공유... - - - - - Copy private link to clipboard - 클립보드에 비밀 링크 복사 - - - - Send private link by email... - 이메일로 비밀 링크 보내기... - - - - Resharing this file is not allowed - 이 파일의 재 공유는 허용되지 않습니다 - - - - Resharing this folder is not allowed - 이 폴더는 재공유가 허가되지 않음 - - - - Create and copy public link to clipboard - 공공 링크를 생성하고 클립보드에 복사 - - - - Copy public link to clipboard - 공공 링크를 클립보드에 복사 - - - - Open in browser - 브라우저에서 열기 - - - - Show file versions in browser - 브라우저에 파일 버전 보이기 - - - - Rename... - 이름변경... - - - - Rename and upload... - 이름 변경 후 업로드... - - - - Move and rename... - 이동 + 이름변경... - - - - Move, rename and upload... - 이동, 개명과 업로드... - - - - Delete local changes - 로컬 변경 내용 삭제 - - - - Move and upload... - 이동과 업로드... - - - - Delete - 삭제 - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - %1 의 백업이 시작됨 - - - - Failed to find %1 - %1 발견 실패 - - - - Local path must be a an absolute path - 로컬 경로는 절대 경로여야 합니다 - - - - Failed to create temporary database - 임시 데이터베이스 생성에 실패했습니다 - - - - Backup of %1 succeeded - %1 의 백업이 성공함 - - - - Failed to set success tag - 성공 택 설정 실패 - - - - Failed to create backup: %1 - 백업 생성이 실패함: %1 - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>인증서 정보</h3> - - - - Common Name (CN): - 공통 이름(CN): - - - - Subject Alternative Names: - 대체 이름: - - - - Organization (O): - 조직(O): - - - - Organizational Unit (OU): - 조직 단위(OU): - - - - State/Province: - 도/광역시: - - - - Country: - 국가: - - - - Serial: - 일련 번호: - - - - <h3>Issuer</h3> - <h3>발급자</h3> - - - - Issuer: - 발급자: - - - - Issued on: - 발급 일시: - - - - Expires on: - 만료 일시: - - - - <h3>Fingerprints</h3> - <h3>지문</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>메모:</b> 이 인증서를 수동으로 인증했습니다</p> - - - - %1 (self-signed) - %1(자가 서명됨) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - 이 연결은 %1비트 %2(으)로 암호화됩니다. - - - - - Server version: %1 - 서버 버전: %1 - - - - No support for SSL session tickets/identifiers - SSL 세션 티켓/식별자 지원이 없음 - - - - Certificate information: - 인증서 정보: - - - - The connection is not secure - 연결이 안전하지 않습니다 - - - - This connection is NOT secure as it is not encrypted. - - 이 연결은 암호화되어 있지 않기 때문에 안전하지 않습니다. - - - - - OCC::SslErrorDialog - - - Form - - - - - Trust this certificate anyway - 이 인증서 신뢰하기 - - - - Untrusted Certificate - 신뢰할 수 없는 인증서 - - - - Cannot connect securely to <i>%1</i>: - <i>%1</i>에 안전하게 연결할 수 없음: - - - - with Certificate %1 - 인증서 %1 사용 - - - - - - &lt;not specified&gt; - &lt;지정되지 않음&gt; - - - - - Organization: %1 - 조직: %1 - - - - - Unit: %1 - 조직 단위: %1 - - - - - Country: %1 - 국가: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - 지문(MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - 지문(SHA1): <tt>%1</tt> - - - - Effective Date: %1 - 유효 기간 시작: %1 - - - - Expiration Date: %1 - 만료 날짜: %1 - - - - Issuer: %1 - 발급자: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1(이전 오류로 건너뜀, %2에 다시 시도) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - %1만 사용할 수 있으나 시작하려면 최소 %2이(가) 필요함 - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - 로컬 동기화 데이터베이스를 열 수 없습니다. 동기화 폴더에 쓰기 권한이 있는지 확인하십시오. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - 디스크 공간이 부족합니다: 남은 디스크 공간을 %1 이하로 떨어트리는 항목을 다운로드하지 않습니다. - - - - There is insufficient space available on the server for some uploads. - 서버에 일부 파일을 업로드할 공간이 부족합니다. - - - - Unresolved conflict. - 해결하지 못한 충돌입니다. - - - - Using virtual files with suffix, but suffix is not set - - - - - Unable to read the blacklist from the local database - 로컬 데이터베이스에서 블랙리스트를 읽을 수 없음 - - - - Unable to read from the sync journal. - 동기화 저널에서 읽을 수 없습니다. - - - - Cannot open the sync journal - 동기화 저널을 열 수 없음 - - - - Aborted - 중단됨 - - - - OCC::SyncJournalDb - - - Failed to connect database. - 데이터베이스 연결에 실패함 - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>버전 %1. 자세한 내용은 <a href="%2">https://%3</a> 을(를) 방문하시기 바랍니다</p><p>알려진 문제점이나 도움말을 원하시면 아래를 방문하십시오: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, 기타 등이 제작.</small></p><p>저작권 ownCloud GmbH</p><p> %4 이(가) 배포하고 GNU 일반 공중 사용 허가서(GPL) 버전 2.0이 적용됩니다.<br/>%5 와(과) %5 로고는 미국, 기타 제국 또는 모두에 있어서의 %4 의 등록된 상표입니다.</p><p><small>%6</small></p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - 체크섬 헤더가 잘못되었습니다. - - - - The checksum header contained an unknown checksum type '%1' - 체크섬 헤더에 알 수 없는 체크섬 형식 '%1'이(가) 있음 - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - 이 가상 파일 시스템 기능은 드라이브를 동기화 루트로서 지원하지 않습니다. - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - 이 가상 파일 시스템 기능은 NTFS 파일 시스템을 필요로 하는데, %1 i은(는) %2 을(를) 이용하고 있습니다 - - - - The Virtual filesystem feature is not supported on network drives - 네트워크 드라이버에서는 가상 파일 시스템 기능이 지원되지 않습니다 - - - - OCC::ownCloudGui - - - Please sign in - 로그인하십시오 - - - - Folder %1: %2 - 폴더 %1: %2 - - - - Unresolved %1 conflicts - 해결되지 않은 %1 충돌들 - - - - Ignored errors %1 - 무시된 오류 %1 - - - - There are no sync folders configured. - 동기화 폴더가 설정되지 않았습니다. - - - - Open in browser - 브라우저에서 열기 - - - - - - Log in... - 로그인... - - - - - - Log out - 로그아웃 - - - - Recent Changes - 최근 바뀜 - - - - Managed Folders: - 관리된 폴더: - - - - Open folder '%1' - 폴더 '%1' 열기 - - - - Open %1 in browser - 브라우저에 %1 열기 - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - 알 수 없는 상태 - - - - Settings... - 설정... - - - - Details... - 자세한 정보... - - - - Help - 도움말 - - - - Quit %1 - %1 끝내기 - - - - Disconnected from %1 - %1에서 연결 끊김 - - - - Unsupported Server Version - 저원되지 않는 서버 버전 - - - - Disconnected - 연결 끊김 - - - - Disconnected from some accounts - 일부 계정에서 연결 해제됨 - - - - Disconnected from accounts: - 다음 계정에서 연결 끊김: - - - - Account %1: %2 - 계정 %1: %2 - - - - Signed out - 로그아웃됨 - - - - Account synchronization is disabled - 계정 동기화 비활성화됨 - - - - Synchronization is paused - 동기화가 일시 정지됨 - - - - Up to date (%1) - 최신 상태 (%1) - - - - No sync folders configured - 동기화 폴더가 설정되지 않았음 - - - - Unpause all folders - 모든 폴더 다시 시작 - - - - Pause all folders - 모든 폴더 일시 정지 - - - - Unpause all synchronization - 모든 동기화 다시 시작 - - - - Unpause synchronization - 동기화 다시 시작 - - - - Pause all synchronization - 모든 동기화 일시 정지 - - - - Pause synchronization - 동기화 일시 정지 - - - - Log out of all accounts - 모든 계정에서 로그아웃 - - - - Log in to all accounts... - 모든 계정에 로그인... - - - - New account... - 새 계정... - - - - About %1 - %1 정보 - - - - No items synced recently - 최근에 동기화된 항목 없음 - - - - Checking for changes in remote '%1' - 원격 '%1' 에서의 변경 내용 점검 중 - - - - Checking for changes in local '%1' - 로컬 '%1' 에서의 변경 내용 점검 중 - - - - Syncing %1 of %2 (%3 left) - %2 중 %1 동기화 중(%3 남음) - - - - Syncing %1 of %2 - %2 중 %1 동기화 중 - - - - Syncing %1 (%2 left) - %1 동기화 중(%2 남음) - - - - Syncing %1 - %1 동기화 중 - - - - %1 (%2, %3) - %1(%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - - - - - - TextLabel - 텍스트라벨 - - - - Server - 서버 - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>이 상자를 선택하면 서버에서 동기화를 시작할 때 로컬 폴더의 모든 내용을 삭제합니다.</p><p>로컬 폴더의 내용을 서버에 업로드해야 하면 선택하지 마십시오.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - 다음 크기 이상의 폴더를 동기화하기 전에 확인(&Z) - - - - S&ynchronize everything from server - 서버에서 모두 동기화(&y) - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - 외부 저장소 동기화 전에 확인(&X) - - - - Choose what to sync - 동기화할 항목 선택 - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - 폴더 동기화 연결 수동 생성 - - - - &Local Folder - 로컬 폴더(&L) - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - 로컬 데이터 유지(&K) - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - 클린(&c) 동기화 시작(백업을 생성하고 로컬 폴더 삭제) - - - - OwncloudHttpCredsPage - - - Form - - - - - &Username - 사용자 이름(&U) - - - - &Password - 암호(&P) - - - - OwncloudOAuthCredsPage - - - Form - - - - - Please switch to your browser to proceed. - 계속 진행하려면 브라우저로 전환하십시오. - - - - An error occurred while connecting. Please try again. - 연결하는 중 오류가 발생했습니다. 다시 시도하시기 바랍니다. - - - - Re-open Browser - 브라우저 다시 열기 - - - - OwncloudSetupPage - - - Form - - - - - Ser&ver Address - 서버 주소(&V) - - - - https://... - https://... - - - - Error Label - 오류 라벨 - - - - QObject - - - in the future - 미래에 - - - - %n day(s) ago - %n 일 전 - - - - %n hour(s) ago - %n 시간 전 - - - - now - 지금 - - - - Less than a minute ago - 1분 미만 - - - - %n minute(s) ago - %n 분 전 - - - - Some time ago - 몇 초 전 - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - 정의되지 않음 - - - - Fatal Error - 치명적 오류 - - - - - Error - 오류 - - - - Info - 정보 - - - - Success - 성공 - - - - Conflict - 충돌 - - - - File Ignored - - - - - Restored - 복원됨 - - - - Blacklisted - 블랙리스트에 들어감 - - - - Excluded - 제외됨 - - - - Utility - - - %L1 GB - %L1GB - - - - %L1 MB - %L1MB - - - - %L1 KB - %L1KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n년 - - - - %n month(s) - %n개월 - - - - %n day(s) - %n일 - - - - %n hour(s) - %n시간 - - - - %n minute(s) - %n분 - - - - %n second(s) - %n초 - - - - %1 %2 - %2 %1 중 - - - - main.cpp - - - System Tray not available - 시스템 트레이를 사용할 수 없음 - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1을(를) 사용하려면 시스템 트레이가 필요합니다. XFCE를 사용 중이면 <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">이 작업</a>을 수행하십시오. 다른 경우에는 'trayer'와 같은 트레이 프로그램을 설치하고 다시 시도하십시오. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %2 %1 중 - - - - progress - - - Virtual file created - 가상 파일 생성됨 - - - - Replaced by virtual file - 가상 파일에 의해 대체됨 - - - - Downloaded - 다운로드됨 - - - - Uploaded - 업로드됨 - - - - Server version downloaded, copied changed local file into conflict file - 서버 버전을 다운로드했고, 변경된 로컬 파일을 충돌한 파일로 복사함 - - - - Deleted - 삭제됨 - - - - Moved to %1 - %1(으)로 이동됨 - - - - Ignored - 무시됨 - - - - Filesystem access error - 파일 시스템 접근 오류 - - - - Error - 오류 - - - - Updated local metadata - 로컬 메타데이터 갱신됨 - - - - - Unknown - 알 수 없음 - - - - downloading - 다운로드 중 - - - - uploading - 업로드 중 - - - - deleting - 삭제 중 - - - - moving - 이동 중 - - - - ignoring - 무시 중 - - - - - error - 오류 - - - - updating local metadata - 로컬 메타데이터 갱신 중 - - - - theme - - - Status undefined - 상태가 정의되지 않음 - - - - Waiting to start sync - 동기화 시작 대기 중 - - - - Sync is running - 동기화 진행 중 - - - - Sync Success - 동기화 성공 - - - - Sync Success, some files were ignored. - 동기화 성공, 일부 파일은 무시되었습니다. - - - - Sync Error - 동기화 오류 - - - - Setup Error - 설치 오류 - - - - Preparing to sync - 동기화 준비 중 - - - - Aborting... - 중단 중... - - - - Sync is paused - 동기화가 일시 정지됨 - - - - utility - - - Could not open browser - 브라우저를 열 수 없음 - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - URL %1을(를) 열기 위해서 브라우저를 실행하는 중 오류가 발생했습니다. 기본 브라우저를 설정하셨습니까? - - - - Could not open email client - 이메일 클라이언트를 열 수 없음 - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - 새 메시지를 작성하기 위한 이메일 클라이언트를 여는 중 오류가 발생했습니다. 기본 이메일 클라이언트를 설정하셨습니까? - - - - Always available locally - 항상 로컬에서만 이용 가능 - - - - Some available online only - 몇 가지는 온라인에서만 이용 가능 - - - - - Available online only - 온라인에서만 이용 가능 - - - - Currently available locally - 로컬에서 현재 이용 가능 - - - - Make always available locally - 항상 로컬에서만 이용 가능하게 함 - - - - Free up local space - 로컬 공간을 해제함 - - - \ No newline at end of file diff --git a/translations/client_lo.ts b/translations/client_lo.ts new file mode 100644 index 00000000000..609d85cacb1 --- /dev/null +++ b/translations/client_lo.ts @@ -0,0 +1,3683 @@ + + + AccountBar + + + Navigation bar + ແຖບນໍາທາງ + + + + Add Account + ເພີ່ມບັນຊີ + + + + Activity + ກິດຈະກຳ + + + + Settings + ການຕັ້ງຄ່າ + + + + Quit + ອອກ + + + + AppImageUpdateAvailableWidgetUi + + + Skip this version + ຂ້າມເວີຊັນນີ້ + + + + AppProvider + + + Failed to open %1 in web. Error: %2. + ເປີດ %1 ໃນເວັບບໍ່ສຳເລັດ. ຜິດພາດ: %2. + + + + ChecksumHeader + + + The checksum header is malformed: %1 + ສ່ວນຫົວຂອງ checksum ມີຮູບແບບບໍ່ຖືກຕ້ອງ: %1 + + + + The checksum header contained an unknown checksum type '%1' + ສ່ວນຫົວຂອງ checksum ມີປະເພດ checksum ທີ່ບໍ່ຮູ້ຈັກ '%1' + + + + CommandLine + + + %1 version %2 +File synchronization desktop utility. + %1 ເວີຊັນ %2<byte value="xd"/> +ເຄື່ອງມືຊິງໂຄໄນໄຟລ໌ສຳລັບເດັສທັອບ. + + + + For more information, see %1 + link to homepage + ສຳລັບຂໍ້ມູນເພີ່ມເຕີມ, ເບິ່ງທີ່ %1 + + + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + ເລີ່ມຕົ້ນດ້ວຍການສະແດງໜ້າຕ່າງຫຼັກ, ຫຼື ຖ້າກຳລັງເຮັດວຽກຢູ່ແລ້ວ, ໃຫ້ນຳມາໄວ້ທາງໜ້າ. ໂດຍປົກກະຕິ, ໂປຣແກຣມຈະເປີດໃນພື້ນຫຼັງ. + + + + Quit the running instance. + ອອກຈາກໂປຣແກຣມທີ່ກຳລັງເຮັດວຽກຢູ່. + + + + Write log to file (use - to write to stdout). + ຂຽນລັອກລົງໃນໄຟລ໌ (ໃຊ້ - ເພື່ອຂຽນໃສ່ stdout). + + + + Write each sync log output in a new file in folder. + ຂຽນຜົນການລັອກການຊິງແຕ່ລະຄັ້ງໃນໄຟລ໌ໃໝ່ໃນໂຟນເດີ. + + + + Flush the log file after every write. + ລ້າງໄຟລ໌ລັອກຫຼັງຈາກການຂຽນທຸກຄັ້ງ. + + + + Output debug-level messages in the log. + ສະແດງຂໍ້ຄວາມລະດັບດີບັກໃນລັອກ. + + + + Enable debug mode. + ເປີດໃຊ້ໂໝດດີບັກ. + + + + --logfile and --logdir are mutually exclusive + --logfile ແລະ --logdir ບໍ່ສາມາດໃຊ້ຮ່ວມກັນໄດ້ + + + + CommonStrings + + + file manager + ໂຕຈັດການໄຟລ໌ + + + + Show in %1 + ສະແດງໃນ %1 + + + + Show in web browser + ສະແດງໃນເວັບເບຣົາເຊີ + + + + Copy + ສຳເນົາ + + + + %n Filter(s) + %n ໂຕກອງ + + + + folder + ໂຟລເດີ + + + + space + ພື້ນທີ່ + + + + folders + ໂຟລເດີ + + + + spaces + ພື້ນທີ່ + + + + Folder + ໂຟເດີ + + + + Space + ພື້ນທີ່ + + + + FetchUserInfoJobFactory + + + Failed to retrieve user info + ດຶງຂໍ້ມູນຜູ້ໃຊ້ບໍ່ສຳເລັດ + + + + FileSystem + + + + + + Can't rename %1, the file is currently in use + ບໍ່ສາມາດປ່ຽນຊື່ %1, ໄຟລ໌ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + Could not remove folder + ບໍ່ສາມາດລຶບໂຟນເດີໄດ້ + + + + FolderItem + + + + Offline + ອອບໄລນ໌ + + + + + Unavailable: the space is no longer available on the server + ບໍ່ພ້ອມໃຊ້ງານ: ພື້ນທີ່ນີ້ບໍ່ມີຢູ່ໃນເຊີບເວີອີກຕໍ່ໄປ + + + + + Sync paused + ການຊິງຖືກຢຸດຊົ່ວຄາວ + + + + Synced + ຊິງແລ້ວ + + + + Sync failed + ການຊິງຄ໌ລົ້ມເຫຼວ + + + + Sync pending + ກຳລັງລໍຖ້າການຊິງ + + + + Syncing %1 of %2 (%3 % + ກຳລັງຊິງ %1 ຈາກ %2 (%3 % + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, ສະຖານະການຊິງ: %2 + first argument is the displayed name of the folder. + + + + FolderManagementUtils + + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + ເສັ້ນທາງ '%1' ຍາວເກີນໄປ. ກະລຸນາເປີດໃຊ້ເສັ້ນທາງຍາວໃນການຕັ້ງຄ່າຂອງ Windows ຫຼື ເລືອກໂຟນເດີອື່ນ. + + + + Local folder %1 does not exist. + ໂຟນເດີໃນເຄື່ອງ %1 ບໍ່ມີຢູ່. + + + + %1 is not a folder. + + + + + %1 is not readable. + ບໍ່ສາມາດອ່ານ %1 ໄດ້. + + + + %1 is not writable. + ບໍ່ສາມາດຂຽນໃສ່ %1 ໄດ້. + + + + FolderWizard + + + <b>Error:</b> %1 + <b>ຜິດພາດ:</b> %1 + + + + <b>Warning:</b> %1 + <b>ຄຳເຕືອນ:</b> %1 + + + + <b>Error:</b><ul>%1</ul> + <b>ຜິດພາດ:</b><ul>%1</ul> + + + + <b>Warning:</b><ul>%1</ul> + <b>ຄຳເຕືອນ:</b><ul>%1</ul> + + + + FolderWizardSourcePage + + + Form + ແບບຟອມ + + + + Pick a local folder on your computer to sync + ເລືອກໂຟນເດີໃນຄອມພິວເຕີຂອງທ່ານເພື່ອຊິງ + + + + Browse… + ເລືອກເບິ່ງ… + + + + FolderWizardTargetPage + + + Form + ແບບຟອມ + + + + Select a remote destination folder + ເລືອກໂຟນເດີປາຍທາງໃນເຊີບເວີ + + + + Create Folder + ສ້າງໂຟນເດີ + + + + Refresh + ໂຫຼດໃໝ່ + + + + Folders + ໂຟນເດີ + + + + OCC::AboutView + + + About + ກ່ຽວກັບ + + + + Versions + ເວີຊັນ + + + + OCC::AbstractNetworkJob + + + Connection timed out + ການເຊື່ອມຕໍ່ໝົດເວລາ + + + + Unknown error: network reply was deleted + ຜິດພາດທີ່ບໍ່ຮູ້ຈັກ: ການຕອບກັບຂອງເຄືອຂ່າຍຖືກລຶບ + + + + Server replied "%1 %2" to "%3 %4" + ເຊີບເວີຕອບກັບ "%1 %2" ຕໍ່ກັບ "%3 %4" + + + + OCC::Account + + + %1@%2 + %1@%2 + + + + OCC::AccountFoldersController + + + Choose what to sync + ເລືອກສິ່ງທີ່ຈະຊິງ + + + + + Sync now + ຊິງດຽວນີ້ + + + + Manage subfolder sync + ຈັດການການຊິງໂຟນເດີຍ່ອຍ + + + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + ລຶບການຊິງ %1 ອອກ + + + + Disable virtual file support? + ປິດການຮອງຮັບໄຟລ໌ສະເໝືອນບໍ່? + + + + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. + +The only advantage of disabling virtual file support is that the selective sync feature will become available again. + +This action will abort any currently running synchronization. + ການກະທຳນີ້ຈະປິດການຮອງຮັບໄຟລ໌ສະເໝືອນ. ຜົນທີ່ຕາມມາຄືເນື້ອໃນຂອງໂຟນເດີທີ່ກຳລັງໝາຍວ່າ 'ມີສະເພາະອອນລາຍ' ຈະຖືກດາວໂຫຼດ. + +ຂໍ້ດີພຽງຢ່າງດຽວຂອງການປິດການຮອງຮັບໄຟລ໌ສະເໝືອນແມ່ນຄຸນສົມບັດການຊິງແບບເລືອກໄດ້ຈະກັບມາໃຊ້ງານໄດ້ອີກຄັ້ງ. + +ການກະທຳນີ້ຈະຍົກເລີກການຊິງໂຄໄນທີ່ກຳລັງເຮັດວຽກຢູ່ທັງໝົດ. + + + + Disable support + ປິດການຮອງຮັບ + + + + Restart sync + ເລີ່ມການຊິງໃໝ່ + + + + Resume sync + ສືບຕໍ່ການຊິງ + + + + Pause sync + ຢຸດການຊິງຊົ່ວຄາວ + + + + Deactivate virtual files + ປິດໃຊ້ງານໄຟລ໌ສະເໝືອນ + + + + Activate virtual files + ເປີດໃຊ້ງານໄຟລ໌ສະເໝືອນ + + + + Internet connection is metered + ການເຊື່ອມຕໍ່ອິນເຕີເນັດມີການຈຳກັດຂໍ້ມູນ + + + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + ການຊິງໂຄໄນຖືກຢຸດຊົ່ວຄາວ ເພາະການເຊື່ອມຕໍ່ອິນເຕີເນັດມີການຈຳກັດຂໍ້ມູນ<p>ທ່ານຕ້ອງການບັງຄັບໃຫ້ຊິງໂຄໄນຕອນນີ້ແທ້ບໍ່? + + + + Sync Running + ກຳລັງຊິງ + + + + The sync operation is running.<br/>Do you want to stop it? + ການຊິງກຳລັງດຳເນີນການຢູ່.<br/>ທ່ານຕ້ອງການຢຸດມັນບໍ່? + + + + Sync paused by user + ການຊິງຖືກຢຸດຊົ່ວຄາວໂດຍຜູ້ໃຊ້ + + + + Confirm Folder Sync Connection Removal + ຢືນຢັນການລຶບການເຊື່ອມຕໍ່ການຊິງໂຟນເດີ + + + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>ທ່ານຕ້ອງການຢຸດການຊິງໂຟນເດີ <i>%1</i> ແທ້ບໍ່?</p><p><b>ໝາຍເຫດ:</b> ການກະທຳນີ້ຈະ<b>ບໍ່</b>ລຶບໄຟລ໌ໃດໆ.</p> + + + + Remove Folder Sync Connection + ລຶບການເຊື່ອມຕໍ່ການຊິງໂຟນເດີ + + + + + Cancel + ຍົກເລີກ + + + + OCC::AccountFoldersView + + + Sync options menu + ເມນູຕົວເລືອກການຊິງ + + + + %1 sync + This shows as 'Folder sync' or 'Space sync' + ການຊິງ %1 + + + + Manage your synced %1. + ຈັດການ %1 ທີ່ຊິງແລ້ວຂອງທ່ານ. + + + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + ເພີ່ມການຊິງ %1 ໃໝ່… + + + + %1 list view + ມຸມເບິ່ງລາຍການ %1 + + + + Navigate the %1 list using the up and down arrows + ນໍາທາງໃນລາຍການ %1 ໂດຍໃຊ້ລູກສອນຂຶ້ນ ແລະ ລົງ + + + + %1 out of %2 %3 are synchronized + %1 ຈາກ %2 %3 ຖືກຊິງແລ້ວ + + + + OCC::AccountModalWidget + + + + Form + ແບບຟອມ + + + + OCC::AccountPlaceholderWidget + + + %1 logo + ໂລໂກ້ %1 + + + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + + + + + Additional logo defined by the organization + ໂລໂກ້ເພີ່ມເຕີມທີ່ກຳນົດໂດຍອົງກອນ + + + + OCC::AccountView + + + Form + ຟອມ + + + + Manage Account + ຈັດການບັນຊີ + + + + Preparing the account + ກຳລັງກະກຽມບັນຊີ + + + + Sync connections + ການເຊື່ອມຕໍ່ການຊິງ + + + + Account options menu + ເມນູຕົວເລືອກບັນຊີ + + + + OCC::AccountViewController + + + + Log in + ເຂົ້າສູ່ລະບົບ + + + + Reconnect + ເຊື່ອມຕໍ່ໃໝ່ + + + + Remove + ລຶບ + + + + Log out + ອອກຈາກລະບົບ + + + + Confirm Account Removal + ຢືນຢັນການລຶບບັນຊີ + + + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>ທ່ານຕ້ອງການລຶບການເຊື່ອມຕໍ່ກັບບັນຊີ <i>%1</i> ແທ້ບໍ່?</p><p><b>ໝາຍເຫດ:</b> ການກະທຳນີ້ຈະ<b>ບໍ່</b>ລຶບໄຟລ໌ໃດໆ.</p> + + + + Remove connection + ລຶບການເຊື່ອມຕໍ່ + + + + Cancel + ຍົກເລີກ + + + + The server version %1 is unsupported! Proceed at your own risk. + ເວີຊັນເຊີບເວີ %1 ບໍ່ຮອງຮັບ! ກະລຸນາດຳເນີນການຕໍ່ດ້ວຍຄວາມສ່ຽງຂອງທ່ານເອງ. + + + + Connected + ເຊື່ອມຕໍ່ແລ້ວ + + + + Server is temporarily unavailable. + + + + + Server is currently in maintenance mode. + + + + + Signed out + ອອກຈາກລະບົບແລ້ວ + + + + Updating credentials… + ກຳລັງອັບເດດຂໍ້ມູນປະຈຳຕົວ… + + + + Captive portal prevents connections to the server. + Captive portal ກີດຂວາງການເຊື່ອມຕໍ່ກັບເຊີບເວີ. + + + + Sync is paused due to metered internet connection. + + + + + Connecting… + ກຳລັງເຊື່ອມຕໍ່… + + + + Server configuration error + ການຕັ້ງຄ່າເຊີບເວີຜິດພາດ + + + + Disconnected + ຕັດການເຊື່ອມຕໍ່ແລ້ວ + + + + Invalid connection status + + + + + OCC::AccountsGuiController + + + + New account failure + ການເພີ່ມບັນຊີໃໝ່ລົ້ມເຫຼວ + + + + The information required to create a new account is incomplete. Please run the wizard again. + ຂໍ້ມູນທີ່ຈຳເປັນໃນການສ້າງບັນຊີໃໝ່ບໍ່ຄົບຖ້ວນ. ກະລຸນາເລີ່ມຂັ້ນຕອນການຕັ້ງຄ່າໃໝ່ອີກຄັ້ງ. + + + + Accounts + ບັນຊີ + + + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + ບໍ່ສາມາດສ້າງບັນຊີໄດ້ເນື່ອງຈາກຂໍ້ຜິດພາດ: +%1 +ກະລຸນາກວດສອບການເຊື່ອມຕໍ່ເຊີບເວີ ແລ້ວເລີ່ມຂັ້ນຕອນການຕັ້ງຄ່າໃໝ່ອີກຄັ້ງ. + + + + OCC::ActivitySettings + + + Local Activity + ກິດຈະກຳໃນເຄື່ອງ + + + + Not Synced + ບໍ່ໄດ້ຊິງ + + + + Not Synced (%1) + %1 is the number of not synced files. + ບໍ່ໄດ້ຊິງ (%1) + + + + OCC::AdvancedSettingsPageController + + + Advanced settings + ການຕັ້ງຄ່າຂັ້ນສູງ + + + + Sync and download options + ຕົວເລືອກການຊິງ ແລະ ດາວໂຫຼດ + + + + Download location + ສະຖານທີ່ດາວໂຫຼດ + + + + Sync and download files as you use them (saves hard drive space) + ຊິງ ແລະ ດາວໂຫຼດໄຟລ໌ເມື່ອທ່ານໃຊ້ງານ (ປະຢັດພື້ນທີ່ໃນຮາດດິດ) + + + + Automatically sync and download folders and files + ຊິງ ແລະ ດາວໂຫຼດໂຟນເດີ ແລະ ໄຟລ໌ໂດຍອັດຕະໂນມັດ + + + + Sync and download folders manually + ຊິງ ແລະ ດາວໂຫຼດໂຟນເດີດ້ວຍຕົນເອງ + + + + Download location on the local machine + ສະຖານທີ່ດາວໂຫຼດໃນເຄື່ອງ + + + + Browse… + ເລືອກເບິ່ງ… + + + + Browse for a download location + ຄົ້ນຫາສະຖານທີ່ດາວໂຫຼດ + + + + Invalid local download directory %1: %2 + ໄດເຣັກທໍຣີດາວໂຫຼດໃນເຄື່ອງບໍ່ຖືກຕ້ອງ %1: %2 + + + + your user directory may not be chosen as the sync root. + ໄດເຣັກທໍຣີຜູ້ໃຊ້ຂອງທ່ານອາດຈະບໍ່ຖືກເລືອກໃຫ້ເປັນໂຟນເດີຫຼັກໃນການຊິງ. + + + + path is not located on a supported Apple File System. + ເສັ້ນທາງບໍ່ໄດ້ຕັ້ງຢູ່ໃນ Apple File System ທີ່ຮອງຮັບ. + + + + path must be absolute. + ເສັ້ນທາງຕ້ອງເປັນແບບເຕັມ (absolute). + + + + selected path does not support using virtual file system. %1 + ເສັ້ນທາງທີ່ເລືອກບໍ່ຮອງຮັບການໃຊ້ລະບົບໄຟລ໌ສະເໝືອນ. %1 + + + + Select sync root + ເລືອກໂຟນເດີຫຼັກສຳລັບການຊິງ + + + + OCC::AppImageUpdateAvailableWidget + + + Installed version: %1 + ເວີຊັນທີ່ຕິດຕັ້ງ: %1 + + + + Available update: %1 + ອັບເດດທີ່ມີ: %1 + + + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + ມີອັບເດດສຳລັບ AppImage ຂອງ %1. ທ່ານຕ້ອງການຕິດຕັ້ງອັບເດດນີ້ບໍ່? + +ການອັບເດດຈະດຳເນີນໃນພື້ນຫຼັງ ແລະ ຈະຂຽນທັບໄຟລ໌ AppImage ປັດຈຸບັນ. ທ່ານຕ້ອງເລີ່ມແອັບໃໝ່ເພື່ອໃຫ້ການອັບເດດສຳເລັດ. + + + + OCC::Application + + + Quit + ອອກ + + + + OCC::AuthSuccessPageController + + + Application Name Logo %1 + ໂລໂກ້ຊື່ແອັບພລິເຄຊັນ %1 + + + + You're all set! Open %1 and get started. + ຕັ້ງຄ່າຮຽບຮ້ອຍແລ້ວ! ເປີດ %1 ເພື່ອເລີ່ມຕົ້ນໃຊ້ງານ. + + + + Additional logo defined by the organization + ໂລໂກ້ເພີ່ມເຕີມທີ່ກຳນົດໂດຍອົງກອນ + + + + OCC::ButtonDelegate + + + %1 options button + ປຸ່ມຕົວເລືອກ %1 + + + + Menu button with %1 options. Use the space key to show the menu + ປຸ່ມເມນູທີ່ມີຕົວເລືອກ %1. ໃຊ້ປຸ່ມຍະຫວ່າງເພື່ອສະແດງເມນູ + + + + OCC::ConnectionValidator + + + No ownCloud account configured + ບໍ່ມີການຕັ້ງຄ່າບັນຊີ ownCloud + + + + Please update to the latest server and restart the client. + ກະລຸນາອັບເດດເປັນເຊີບເວີລ່າສຸດ ແລະ ເລີ່ມໂປຣແກຣມຄືນໃໝ່. + + + + Authentication error: Either username or password are wrong. + ການຢືນຢັນຕົວຕົນຜິດພາດ: ຊື່ຜູ້ໃຊ້ ຫຼື ລະຫັດຜ່ານບໍ່ຖືກຕ້ອງ. + + + + timeout + ໝົດເວລາ + + + + No account configured + ບໍ່ມີການຕັ້ງຄ່າບັນຊີ + + + + The provided credentials are not correct + ຂໍ້ມູນປະຈຳຕົວທີ່ປ້ອນບໍ່ຖືກຕ້ອງ + + + + The configured server for this client is too old. + ເຊີບເວີທີ່ຕັ້ງຄ່າໄວ້ສຳລັບໂປຣແກຣມນີ້ເກົ່າເກີນໄປ. + + + + OCC::CredentialJob + + + Failed to parse credentials %1 + ວິເຄາະຂໍ້ມູນປະຈຳຕົວ %1 ບໍ່ສຳເລັດ + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + ເຊີບເວີບໍ່ຮອງຮັບ OAuth + + + + Server did not ask for authorization + ເຊີບເວີບໍ່ໄດ້ຮ້ອງຂໍການອະນຸຍາດ + + + + Failed to determine authentication type: %1 + ກວດສອບປະເພດການຢືນຢັນຕົວຕົນບໍ່ສຳເລັດ: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + ໄດ້ຮັບການຕອບກັບທີ່ບໍ່ຖືກຕ້ອງຈາກເຊີບເວີ. + + + + OCC::DiscoverySingleDirectoryJob + + + The account was deleted before we could start the propfind job + ບັນຊີຖືກລຶບກ່ອນທີ່ພວກເຮົາຈະເລີ່ມວຽກ propfind + + + + + Server error: PROPFIND reply is not XML formatted! + ເຊີບເວີຜິດພາດ: ຄຳຕອບ PROPFIND ບໍ່ແມ່ນຮູບແບບ XML! + + + + OCC::DiscoverySingleLocalDirectoryJob + + + Error while opening directory %1 + ຜິດພາດຂະນະເປີດໂຟນເດີ %1 + + + + Directory not accessible on client, permission denied + ບໍ່ສາມາດເຂົ້າເຖິງໂຟນເດີໃນເຄື່ອງ, ບໍ່ໄດ້ຮັບອະນຸຍາດ + + + + Directory not found: %1 + ບໍ່ພົບໂຟນເດີ: %1 + + + + Error while reading directory %1 + ຜິດພາດຂະນະອ່ານໂຟນເດີ %1 + + + + OCC::ExpandingHeaderView + + + Reset column sizes + ຣີເຊັດຂະໜາດຖັນ + + + + OCC::FetchCapabilitiesAdapter + + + server sent invalid content type: %1. + ເຊີບເວີສົ່ງ content type ທີ່ບໍ່ຖືກຕ້ອງ: %1. + + + + could not parse Capabilities from server. + ບໍ່ສາມາດວິເຄາະ Capabilities ຈາກເຊີບເວີ. + + + + reply sent for different subject (server): %1 + ຄຳຕອບຖືກສົ່ງມາສຳລັບຫົວຂໍ້ (ເຊີບເວີ) ທີ່ຕ່າງກັນ: %1 + + + + OCC::Folder + + + %1 has been removed. + %1 names a file. + %1 ຖືກລຶບອອກແລ້ວ. + + + + %1 has been updated. + %1 names a file. + %1 ຖືກອັບເດດແລ້ວ. + + + + %1 has been renamed to %2. + %1 and %2 name files. + %1 ຖືກປ່ຽນຊື່ເປັນ %2. + + + + %1 has been moved to %2. + %1 ຖືກຍ້າຍໄປທີ່ %2. + + + + %1 and %n other file(s) have been removed. + %1 ແລະ ໄຟລ໌ອື່ນໆອີກ %n ໄຟລ໌ຖືກລຶບອອກແລ້ວ. + + + + The folder has been disabled or removed from the server + ໂຟນເດີຖືກປິດໃຊ້ງານ ຫຼື ຖືກລຶບອອກຈາກເຊີບເວີແລ້ວ + + + + %1 and %n other file(s) have been added. + %1 ແລະ ໄຟລ໌ອື່ນໆອີກ %n ໄຟລ໌ຖືກເພີ່ມເຂົ້າມາແລ້ວ. + + + + %1 has been added. + %1 names a file. + %1 ຖືກເພີ່ມເຂົ້າມາແລ້ວ. + + + + %1 and %n other file(s) have been updated. + %1 ແລະ ໄຟລ໌ອື່ນໆອີກ %n ໄຟລ໌ຖືກອັບເດດແລ້ວ. + + + + %1 has been renamed to %2 and %n other file(s) have been renamed. + %1 ຖືກປ່ຽນຊື່ເປັນ %2 ແລະ ໄຟລ໌ອື່ນໆອີກ %n ໄຟລ໌ຖືກປ່ຽນຊື່ແລ້ວ. + + + + %1 has been moved to %2 and %n other file(s) have been moved. + %1 ຖືກຍ້າຍໄປທີ່ %2 ແລະ ໄຟລ໌ອື່ນໆອີກ %n ໄຟລ໌ຖືກຍ້າຍແລ້ວ. + + + + %1 and %n other file(s) have sync conflicts. + %1 ແລະ ໄຟລ໌ອື່ນໆອີກ %n ໄຟລ໌ມີບັນຫາການຊິງຂັດກັນ. + + + + %1 has a sync conflict. Please check the conflict file! + %1 ມີບັນຫາການຊິງຂັດກັນ. ກະລຸນາກວດສອບໄຟລ໌ທີ່ຂັດກັນ! + + + + %1 and %n other file(s) could not be synced due to errors. See the log for details. + %1 ແລະ ໄຟລ໌ອື່ນໆອີກ %n ໄຟລ໌ບໍ່ສາມາດຊິງໄດ້ເນື່ອງຈາກຄວາມຜິດພາດ. ເບິ່ງລາຍລະອຽດໃນລັອກ. + + + + %1 could not be synced due to an error. See the log for details. + %1 ບໍ່ສາມາດຊິງໄດ້ເນື່ອງຈາກຄວາມຜິດພາດ. ເບິ່ງລາຍລະອຽດໃນລັອກ. + + + + Sync Activity + ກິດຈະກຳການຊິງ + + + + Switching VFS mode on folder '%1' + ກຳລັງປ່ຽນໂໝດ VFS ໃນໂຟນເດີ '%1' + + + + Could not read system exclude file + ບໍ່ສາມາດອ່ານໄຟລ໌ຍົກເວັ້ນຂອງລະບົບ + + + + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. + ໂຟນເດີ %1 ຖືກສ້າງຂຶ້ນແລ້ວ ແຕ່ຖືກຍົກເວັ້ນຈາກການຊິງໂຄໄນກ່ອນໜ້ານີ້. ຂໍ້ມູນພາຍໃນຈະບໍ່ຖືກຊິງໂຄໄນ. + + + + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. + ໄຟລ໌ %1 ຖືກສ້າງຂຶ້ນແລ້ວ ແຕ່ຖືກຍົກເວັ້ນຈາກການຊິງໂຄໄນກ່ອນໜ້ານີ້. ມັນຈະບໍ່ຖືກຊິງໂຄໄນ. + + + + Changes in synchronized folders could not be tracked reliably. + +This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). + +%1 + ບໍ່ສາມາດຕິດຕາມການປ່ຽນແປງໃນໂຟນເດີທີ່ຊິງໂຄໄນໄດ້ຢ່າງໜ້າເຊື່ອຖື. + +ນີ້ໝາຍຄວາມວ່າໂປຣແກຣມຊິງໂຄໄນອາດຈະບໍ່ອັບໂຫຼດການປ່ຽນແປງໃນເຄື່ອງທັນທີ ແລະ ຈະສະແກນຫາການປ່ຽນແປງໃນເຄື່ອງ ແລະ ອັບໂຫຼດເປັນບາງຄັ້ງຄາວແທນ (ໂດຍປົກກະຕິທຸກໆສອງຊົ່ວໂມງ). + +%1 + + + + OCC::FolderItemUpdater + + + There are unresolved conflicts. + ມີຂໍ້ຂັດແຍ່ງທີ່ຍັງບໍ່ໄດ້ແກ້ໄຂ. + + + + Sync error: %1 + ການຊິງຜິດພາດ: %1 + + + + OCC::FolderMan + + + Could not reset folder state + ບໍ່ສາມາດຣີເຊັດສະຖານະໂຟນເດີໄດ້ + + + + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. + ພົບເຫັນບັນທຶກການຊິງເກົ່າ '%1', ແຕ່ບໍ່ສາມາດລຶບອອກໄດ້. ກະລຸນາກວດສອບໃຫ້ແນ່ໃຈວ່າບໍ່ມີໂປຣແກຣມໃດກຳລັງນຳໃຊ້ມັນຢູ່. + + + + Unsupported filesystem + ລະບົບໄຟລ໌ບໍ່ຮອງຮັບ + + + + Account disconnected or paused + ບັນຊີຖືກຕັດການເຊື່ອມຕໍ່ ຫຼື ຢຸດຊົ່ວຄາວ + + + + User triggered force sync + ຜູ້ໃຊ້ສັ່ງບັງຄັບຊິງ + + + + Folder is about to be removed + ໂຟນເດີກຳລັງຈະຖືກລຶບ + + + + (backup) + (ສຳຮອງ) + + + + (backup %1) + (ສຳຮອງ %1) + + + + The selected path is not a folder. + ເສັ້ນທາງທີ່ເລືອກບໍ່ແມ່ນໂຟນເດີ. + + + + The folder %1 is used in a folder sync connection. + ໂຟນເດີ %1 ຖືກໃຊ້ໃນການເຊື່ອມຕໍ່ການຊິງໂຟນເດີ. + + + + Folder '%1' is already in use by application %2. + ໂຟນເດີ '%1' ຖືກນຳໃຊ້ໂດຍແອັບພລິເຄຊັນ %2 ຢູ່ແລ້ວ. + + + + There is already a sync from the server to this local folder. + ມີການຊິງຈາກເຊີບເວີມາຍັງໂຟນເດີໃນເຄື່ອງນີ້ຢູ່ແລ້ວ. + + + + The local folder %1 already contains a folder used in a folder sync connection. + ໂຟນເດີໃນເຄື່ອງ %1 ມີໂຟນເດີທີ່ຖືກໃຊ້ໃນການເຊື່ອມຕໍ່ການຊິງໂຟນເດີຢູ່ແລ້ວ. + + + + The local folder %1 is already contained in a folder used in a folder sync connection. + ໂຟນເດີໃນເຄື່ອງ %1 ຢູ່ໃນໂຟນເດີທີ່ຖືກໃຊ້ໃນການເຊື່ອມຕໍ່ການຊິງໂຟນເດີຢູ່ແລ້ວ. + + + + No valid folder selected. + ບໍ່ມີໂຟນເດີທີ່ຖືກຕ້ອງຖືກເລືອກ. + + + + The selected path does not exist. + ເສັ້ນທາງທີ່ເລືອກບໍ່ມີຢູ່. + + + + You have no permission to write to the selected folder. + ທ່ານບໍ່ມີສິດໃນການຂຽນລົງໃນໂຟນເດີທີ່ເລືອກ. + + + + Folder '%1' is already in use by another account. + ໂຟນເດີ '%1' ຖືກນຳໃຊ້ໂດຍບັນຊີອື່ນຢູ່ແລ້ວ. + + + + On macOS, only the Apple File System is supported. + ໃນ macOS, ຮອງຮັບສະເພາະ Apple File System ເທົ່ານັ້ນ. + + + + OCC::FolderModelController + + + %1 options + ຕົວເລືອກ %1 + + + + Menu button with %1 options. Use the space key to pop the %2 options menu + ປຸ່ມເມນູທີ່ມີຕົວເລືອກ %1. ໃຊ້ປຸ່ມຍະຫວ່າງເພື່ອສະແດງເມນູຕົວເລືອກ %2 + + + + OCC::FolderWatcher + + + The watcher did not receive a test notification. + ໂຕຕິດຕາມບໍ່ໄດ້ຮັບການແຈ້ງເຕືອນທົດສອບ. + + + + OCC::FolderWatcherPrivate + + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + ບັນຫານີ້ມັກຈະເກີດຂຶ້ນເມື່ອ inotify watches ຖືກໃຊ້ຈົນໝົດ. ກວດເບິ່ງ FAQ ສໍາລັບລາຍລະອຽດ. + + + + OCC::FolderWizard + + + Virtual files are not available for the selected folder + ໄຟລ໌ສະເໝືອນບໍ່ສາມາດໃຊ້ງານໄດ້ກັບໂຟນເດີທີ່ເລືອກ + + + + Add Folder Sync Connection + ເພີ່ມການເຊື່ອມຕໍ່ການຊິງໂຟນເດີ + + + + Add Sync Connection + ເພີ່ມການເຊື່ອມຕໍ່ການຊິງ + + + + OCC::FolderWizardLocalPath + + + Click to select a local folder to sync. + ກົດເພື່ອເລືອກໂຟນເດີໃນເຄື່ອງເພື່ອຊິງ. + + + + Enter the path to the local folder. + ປ້ອນເສັ້ນທາງໄປຍັງໂຟນເດີໃນເຄື່ອງ. + + + + Select the local folder + ເລືອກໂຟນເດີໃນເຄື່ອງ + + + + OCC::FolderWizardSelectiveSync + + + Use virtual files instead of downloading content immediately + ໃຊ້ໄຟລ໌ສະເໝືອນແທນການດາວໂຫຼດເນື້ອໃນທັນທີ + + + + OCC::GETFileJob + + + Connection Timeout + ການເຊື່ອມຕໍ່ໝົດເວລາ + + + + No E-Tag received from server, check Proxy/Gateway + ບໍ່ໄດ້ຮັບ E-Tag ຈາກເຊີບເວີ, ກວດສອບ Proxy/Gateway + + + + We received a different E-Tag for resuming. Retrying next time. + ພວກເຮົາໄດ້ຮັບ E-Tag ທີ່ແຕກຕ່າງສຳລັບການດາວໂຫຼດຕໍ່. ຈະລອງໃໝ່ໃນຄັ້ງຕໍ່ໄປ. + + + + We received an unexpected download Content-Length. + ພວກເຮົາໄດ້ຮັບ Content-Length ຂອງການດາວໂຫຼດທີ່ບໍ່ຄາດຄິດ. + + + + Server returned wrong content-range + ເຊີບເວີສົ່ງ content-range ທີ່ບໍ່ຖືກຕ້ອງກັບມາ + + + + OCC::GraphApi::Space + + + Personal + ສ່ວນບຸກຄົນ + + + + Shares + ການແຊຣ໌ + + + + OCC::IgnoreListEditor + + + Ignored Files Editor + ໂຕແກ້ໄຂໄຟລ໌ທີ່ບໍ່ສົນໃຈ + + + + Files Ignored by Patterns + ໄຟລ໌ທີ່ບໍ່ສົນໃຈຕາມຮູບແບບ + + + + Add + ເພີ່ມ + + + + Pattern + ຮູບແບບ + + + + Allow Deletion + ອະນຸຍາດການລຶບ + + + + Remove + ລຶບ + + + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + ໄຟລ໌ ຫຼື ໂຟນເດີທີ່ກົງກັບຮູບແບບຈະບໍ່ຖືກຊິງໂຄໄນ. ການປ່ຽນແປງຈະມີຜົນໃນການຊິງໂຄໄນໂຟນເດີຄັ້ງຕໍ່ໄປ. + +ລາຍການທີ່ອະນຸຍາດໃຫ້ລຶບໄດ້ຈະຖືກລຶບ ຖ້າຫາກມັນຂັດຂວາງການລຶບໂຟນເດີ. ນີ້ມີປະໂຫຍດສຳລັບຂໍ້ມູນເມຕາ. + + + + Could not open file + ບໍ່ສາມາດເປີດໄຟລ໌ໄດ້ + + + + Cannot write changes to '%1'. + ບໍ່ສາມາດຂຽນການປ່ຽນແປງໃສ່ '%1'. + + + + Add Ignore Pattern + ເພີ່ມຮູບແບບການບໍ່ສົນໃຈ + + + + Add a new ignore pattern: + ເພີ່ມຮູບແບບການບໍ່ສົນໃຈໃໝ່: + + + + This entry is provided by the system at '%1' and cannot be modified in this view. + ລາຍການນີ້ຖືກສະໜອງໂດຍລະບົບທີ່ '%1' ແລະ ບໍ່ສາມາດແກ້ໄຂໃນໜ້ານີ້ໄດ້. + + + + OCC::LocalActivityWidget + + + Filter + ໂຕກອງ + + + + Local activity table + ຕາຕະລາງກິດຈະກຳໃນເຄື່ອງ + + + + + Account + ບັນຊີ + + + + Filter menu + ເມນູໂຕກອງ + + + + Actions menu + ເມນູການກະທຳ + + + + Sort ascending by %1 + ຮຽງລຳດັບຈາກໜ້ອຍຫາຫຼາຍຕາມ %1 + + + + Sort descending by %1 + ຮຽງລຳດັບຈາກຫຼາຍຫາໜ້ອຍຕາມ %1 + + + + Retry sync + ລອງຊິ້ງໃໝ່ + + + + OCC::LogBrowser + + + Log Output + ຜົນການລັອກ + + + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>ລັອກຕ່າງໆມີຂໍ້ມູນທີ່ລະອຽດອ່ອນ ເຊິ່ງທ່ານບໍ່ຄວນເປີດເຜີຍຕໍ່ສາທາລະນະ</b></span></p></body></html> + + + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + ໂປຣແກຣມສາມາດຂຽນລັອກດີບັກໄປຍັງໂຟນເດີຊົ່ວຄາວ. ລັອກເຫຼົ່ານີ້ມີປະໂຫຍດຫຼາຍໃນການວິນິດໄສບັນຫາ. +ເນື່ອງຈາກໄຟລ໌ລັອກອາດມີຂະໜາດໃຫຍ່, ໂປຣແກຣມຈະເລີ່ມໄຟລ໌ໃໝ່ສຳລັບການຊິງແຕ່ລະຄັ້ງ ແລະ ບີບອັດໄຟລ໌ເກົ່າ. + + + + Enable logging to temporary folder + ເປີດການບັນທຶກລັອກໄປຍັງໂຟນເດີຊົ່ວຄາວ + + + + If enabled, logs will be written to: + ຖ້າເປີດໃຊ້, ລັອກຈະຖືກຂຽນໄປທີ່: + + + + C:/log + C:/log + + + + Log Http traffic + ບັນທຶກລັອກ Http traffic + + + + Log files to keep: + ຈຳນວນໄຟລ໌ລັອກທີ່ຈະເກັບໄວ້: + + + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + ການຕັ້ງຄ່າເຫຼົ່ານີ້ຈະຍັງຄົງຢູ່ເຖິງແມ່ນຈະເລີ່ມໂປຣແກຣມໃໝ່. +ໝາຍເຫດວ່າການໃຊ້ຕົວເລືອກບັນທຶກລັອກຜ່ານຄຳສັ່ງຈະຂຽນທັບການຕັ້ງຄ່າເຫຼົ່ານີ້. + + + + Open folder + ເປີດໂຟນເດີ + + + + OCC::MainWindow + + + Hide + ເຊື່ອງ + + + + Main toolbar for the application + + + + + Main toolbar + + + + + + More + ເພີ່ມເຕີມ + + + + OCC::MainWindowController + + + Add account... + + + + + Settings... + + + + + About... + + + + + Quit + ອອກ + + + + + Errors: %1 + + + + + Activity + ກິດຈະກຳ + + + + Quit %1 + ອອກຈາກ %1 + + + + Are you sure you want to quit %1? + ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການອອກຈາກ %1? + + + + OCC::ModalWrapperWidget + + + Close + ປິດ + + + + OCC::Models + + + %1 Filter: + ໂຕກອງ %1: + + + + All + ທັງໝົດ + + + + Reset Filters + ຣີເຊັດໂຕກອງ + + + + Status Filter: + ໂຕກອງສະຖານະ: + + + + OCC::NetworkSettings + + + Form + ແບບຟອມ + + + + Pause synchronization when the Internet connection is metered + ຢຸດການຊິງໂຄໄນຊົ່ວຄາວ ເມື່ອການເຊື່ອມຕໍ່ອິນເຕີເນັດມີການຈຳກັດຂໍ້ມູນ + + + + Proxy Settings + ການຕັ້ງຄ່າພຣັອກຊີ + + + + No Proxy + ບໍ່ມີພຣັອກຊີ + + + + Use system proxy + ໃຊ້ພຣັອກຊີຂອງລະບົບ + + + + Host + ໂຮສ + + + + Proxy Hostname + ຊື່ໂຮສຂອງພຣັອກຊີ + + + + : + : + + + + Proxy Port Number + ໝາຍເລກພອດຂອງພຣັອກຊີ + + + + Proxy server requires authentication + ເຊີບເວີພຣັອກຊີຕ້ອງການການຢືນຢັນຕົວຕົນ + + + + Note: proxy settings have no effects for accounts on localhost + ໝາຍເຫດ: ການຕັ້ງຄ່າພຣັອກຊີບໍ່ມີຜົນກັບບັນຊີທີ່ຢູ່ໃນ localhost + + + + Specify proxy manually + ລະບຸພຣັອກຊີດ້ວຍຕົນເອງ + + + + Proxy type + ປະເພດພຣັອກຊີ + + + + + Hostname of proxy server + ຊື່ໂຮສຂອງເຊີບເວີພຣັອກຊີ + + + + + Username for proxy server + ຊື່ຜູ້ໃຊ້ສຳລັບເຊີບເວີພຣັອກຊີ + + + + + Password for proxy server + ລະຫັດຜ່ານສຳລັບເຊີບເວີພຣັອກຊີ + + + + HTTP(S) proxy + ພຣັອກຊີ HTTP(S) + + + + SOCKS5 proxy + ພຣັອກຊີ SOCKS5 + + + + OCC::NewAccountBuilder + + + no error details are available + ບໍ່ມີລາຍລະອຽດຂໍ້ຜິດພາດ + + + + Unable to connect to server during account creation: %1. + ບໍ່ສາມາດເຊື່ອມຕໍ່ກັບເຊີບເວີຂະນະສ້າງບັນຊີ: %1. + + + + OCC::NewAccountWizard + + + Cancel Setup + ຍົກເລີກການຕັ້ງຄ່າ + + + + Do you really want to cancel the account setup? + ທ່ານຕ້ອງການຍົກເລີກການຕັ້ງຄ່າບັນຊີແທ້ບໍ່? + + + + OCC::NewAccountWizardController + + + Add account… + ເພີ່ມບັນຊີ… + + + + Back + ກັບຄືນ + + + + Advanced + ຂັ້ນສູງ + + + + Sign in + ລອງໃໝ່ + + + + Open sign in again + ເປີດການເຂົ້າສູ່ລະບົບອີກຄັ້ງ + + + + OCC::NewVersionAvailableWidget + + + Skip this version + ຂ້າມເວີຊັນນີ້ + + + + Get update + ຮັບການອັບເດດ + + + + Skip this time + ຂ້າມເທື່ອນີ້ + + + + OCC::OAuth + + + Successfully signed in + ເຂົ້າສູ່ລະບົບສຳເລັດແລ້ວ + + + + Now, explore %1 on desktop. + ດຽວນີ້, ສຳຫຼວດ %1 ໃນເດັສທັອບ. + + + + Error returned from the server: <em>%1</em> + ຂໍ້ຜິດພາດທີ່ສົ່ງຄືນຈາກເຊີບເວີ: <em>%1</em> + + + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + ເກີດຂໍ້ຜິດພາດໃນການເຂົ້າເຖິງຈຸດບໍລິການ 'token': <br><em>%1</em> + + + + Could not parse the JSON returned from the server: <br><em>%1</em> + ບໍ່ສາມາດວິເຄາະ JSON ທີ່ສົ່ງຄືນຈາກເຊີບເວີໄດ້: <br><em>%1</em> + + + + Unsupported token type: %1 + ປະເພດ token ບໍ່ຮອງຮັບ: %1 + + + + The reply from the server did not contain all expected fields +:%1 + ການຕອບກັບຈາກເຊີບເວີບໍ່ມີຂໍ້ມູນຄົບຖ້ວນຕາມທີ່ຄາດໄວ້ +:%1 + + + + Unknown Error + ຂໍ້ຜິດພາດທີ່ບໍ່ຮູ້ຈັກ + + + + + Login Error + ການເຂົ້າສູ່ລະບົບຜິດພາດ + + + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + ທ່ານເຂົ້າສູ່ລະບົບດ້ວຍຜູ້ໃຊ້ <em>%1</em>, ແຕ່ຕ້ອງເຂົ້າດ້ວຍຜູ້ໃຊ້ <em>%2</em>.<br>ກະລຸນາກັບຄືນໄປທີ່ %3 ແລະ ເລີ່ມການຢືນຢັນຕົວຕົນໃໝ່. + + + + Wrong user + ຜູ້ໃຊ້ບໍ່ຖືກຕ້ອງ + + + + OCC::OAuthPageController + + + Application Name Logo %1 + ໂລໂກ້ຊື່ແອັບພລິເຄຊັນ %1 + + + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + ເປີດໜ້ານີ້ຄ້າງໄວ້. ຈະມີໜ້າຕ່າງເຂົ້າສູ່ລະບົບປະກົດຂຶ້ນໃນເວັບເບຣົາເຊີຂອງທ່ານເພື່ອເຊື່ອມຕໍ່ກັບທີ່ຢູ່ຕໍ່ໄປນີ້. + + + + Sign in URL + URL ສຳລັບເຂົ້າສູ່ລະບົບ + + + + To copy the sign in URL to the clipboard, use the copy button + ເພື່ອສຳເນົາ URL ເຂົ້າສູ່ລະບົບໃສ່ຄລິບບອດ, ໃຫ້ໃຊ້ປຸ່ມສຳເນົາ + + + + Copy URL to sign in + ສຳເນົາ URL ເພື່ອເຂົ້າສູ່ລະບົບ + + + + Additional logo defined by the organization + ໂລໂກ້ເພີ່ມເຕີມທີ່ກຳນົດໂດຍອົງກອນ + + + + URL copied + ສຳເນົາ URL ແລ້ວ + + + + Copy URL + ສຳເນົາ URL + + + + Unable to connect to the login service. If the problem continues, please contact support. + + + + + Please copy the authentication URL using the button above, and provide this information to support. + ກະລຸນາສຳເນົາ URL ການຢືນຢັນຕົວຕົນໂດຍໃຊ້ປຸ່ມຂ້າງເທິງ, ແລະ ສົ່ງຂໍ້ມູນນີ້ໃຫ້ກັບຝ່າຍຊ່ວຍເຫຼືອ. + + + + Failed to look up WebFinger instances: %1 + ຄົ້ນຫາ WebFinger instances ບໍ່ສຳເລັດ: %1 + + + + You are already connected to an account with these credentials. + ທ່ານໄດ້ເຊື່ອມຕໍ່ກັບບັນຊີທີ່ມີຂໍ້ມູນປະຈຳຕົວນີ້ແລ້ວ. + + + + Unable to retrieve capabilities from server. + ບໍ່ສາມາດດຶງຂໍ້ມູນ Capabilities ຈາກເຊີບເວີ. + + + + The server is not supported by this client. + ເຊີບເວີນີ້ບໍ່ຮອງຮັບໂດຍໂປຣແກຣມນີ້. + + + + Error while trying to log in to OAuth2-enabled server. %1 + ເກີດຂໍ້ຜິດພາດຂະນະພະຍາຍາມເຂົ້າສູ່ລະບົບເຊີບເວີທີ່ເປີດໃຊ້ OAuth2. %1 + + + + Unable to open browser with provided URL. %1 + ບໍ່ສາມາດເປີດເບຣົາເຊີດ້ວຍ URL ທີ່ໃຫ້ມາ. %1 + + + + OAuth2 authentication requires a secured connection. %1 + ການຢືນຢັນຕົວຕົນ OAuth2 ຕ້ອງການການເຊື່ອມຕໍ່ທີ່ປອດໄພ. %1 + + + + Authorization server unreachable. %1 + ບໍ່ສາມາດເຂົ້າເຖິງເຊີບເວີການອະນຸຍາດໄດ້. %1 + + + + OCC::OCUpdater + + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + ບໍ່ສາມາດດາວໂຫຼດອັບເດດໄດ້. ກະລຸນາຄລິກ <a href='%1'>ທີ່ນີ້</a> ເພື່ອດາວໂຫຼດອັບເດດດ້ວຍຕົນເອງ. + + + + Could not check for new updates. + ບໍ່ສາມາດກວດສອບການອັບເດດໃໝ່ໄດ້. + + + + Downloading %1. Please wait… + ກຳລັງດາວໂຫຼດ %1. ກະລຸນາລໍຖ້າ… + + + + %1 installed successfully. Restart the application to finish installing the update. + ຕິດຕັ້ງ %1 ສຳເລັດແລ້ວ. ກະລຸນາເລີ່ມໂປຣແກຣມໃໝ່ເພື່ອສິ້ນສຸດການຕິດຕັ້ງອັບເດດ. + + + + %1 available. Restart application to start the update. + ມີ %1 ໃຫ້ອັບເດດ. ກະລຸນາເລີ່ມໂປຣແກຣມໃໝ່ເພື່ອເລີ່ມການອັບເດດ. + + + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + ມີ %1 ເວີຊັນໃໝ່. ກະລຸນາຄລິກ <a href='%2'>ທີ່ນີ້</a> ເພື່ອດາວໂຫຼດ AppImage ໃໝ່ດ້ວຍຕົນເອງ. + + + + New %1 available. Please use the system's update tool to install it. + ມີ %1 ເວີຊັນໃໝ່. ກະລຸນາໃຊ້ເຄື່ອງມືອັບເດດຂອງລະບົບເພື່ອຕິດຕັ້ງ. + + + + Checking update server… + ກຳລັງກວດສອບເຊີບເວີອັບເດດ… + + + + Update status is unknown: Did not check for new updates. + ສະຖານະການອັບເດດບໍ່ຊັດເຈນ: ບໍ່ໄດ້ກວດສອບການອັບເດດໃໝ່. + + + + No updates available. Your installation is at the latest version. + ບໍ່ມີການອັບເດດ. ໂປຣແກຣມຂອງທ່ານເປັນເວີຊັນຫຼ້າສຸດແລ້ວ. + + + + OCC::OwncloudPropagator + + + File %1 is currently in use + ໄຟລ໌ %1 ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + vfs instance is not available. + + + + + OCC::ProcessDirectoryJob + + + Symbolic links are not supported in syncing. + ບໍ່ຮອງຮັບການຊິ້ງຂໍ້ມູນຜ່ານລິ້ງສັນຍາລັກ. + + + + File is listed on the ignore list. + ໄຟລ໌ນີ້ຢູ່ໃນລາຍການທີ່ຈະບໍ່ສົນໃຈ. + + + + File names ending with a period are not supported on this file system. + ລະບົບໄຟລ໌ນີ້ບໍ່ຮອງຮັບຊື່ໄຟລ໌ທີ່ລົງທ້າຍດ້ວຍຈ້ຳ. + + + + File names containing the character '%1' are not supported on this file system. + ລະບົບໄຟລ໌ນີ້ບໍ່ຮອງຮັບຊື່ໄຟລ໌ທີ່ມີຕົວອັກສອນ '%1'. + + + + File name contains at least one invalid character + ຊື່ໄຟລ໌ມີຕົວອັກສອນທີ່ບໍ່ຖືກຕ້ອງຢ່າງໜ້ອຍໜຶ່ງຕົວ + + + + The file name is a reserved name on this file system. + ຊື່ໄຟລ໌ນີ້ເປັນຊື່ທີ່ສະຫງວນໄວ້ໃນລະບົບໄຟລ໌ນີ້. + + + + Filename contains trailing spaces. + ຊື່ໄຟລ໌ມີຍະຫວ່າງຢູ່ທ້າຍ. + + + + Filename is too long. + ຊື່ໄຟລ໌ຍາວເກີນໄປ. + + + + File/Folder is ignored because it's hidden. + ໄຟລ໌/ໂຟເດີຖືກບໍ່ສົນໃຈ ເນື່ອງຈາກມັນຖືກເຊື່ອງໄວ້. + + + + Stat failed. + ການກວດສອບສະຖານະຜິດພາດ. + + + + Conflict: Server version downloaded, local copy renamed and not uploaded. + ຂໍ້ຂັດແຍ່ງ: ດາວໂຫຼດເວີຊັນຈາກເຊີບເວີແລ້ວ, ສຳເນົາໃນເຄື່ອງໄດ້ຖືກປ່ຽນຊື່ ແລະ ບໍ່ໄດ້ອັບໂຫຼດ. + + + + The filename cannot be encoded on your file system. + ບໍ່ສາມາດເຂົ້າລະຫັດຊື່ໄຟລ໌ໃນລະບົບໄຟລ໌ຂອງທ່ານໄດ້. + + + + The filename is blacklisted on the server. + ຊື່ໄຟລ໌ນີ້ຖືກຫ້າມໃນເຊີບເວີ. + + + + + + + the VFS instance is missing. Unable to continue. + + + + + server reported no %1 + ເຊີບເວີລາຍງານວ່າບໍ່ມີ %1 + + + + + account was deleted. Unable to continue + ບັນຊີຖືກລຶບແລ້ວ. ບໍ່ສາມາດດຳເນີນການຕໍ່ໄດ້ + + + + SelectiveSync: Ignored because its path is deselected + ການຊິ້ງແບບເລືອກ: ບໍ່ສົນໃຈ ເນື່ອງຈາກເສັ້ນທາງຂອງມັນບໍ່ໄດ້ຖືກເລືອກ + + + + Moved to invalid target, restoring + ຍ້າຍໄປທີ່ປາຍທາງທີ່ບໍ່ຖືກຕ້ອງ, ກຳລັງກູ້ຄືນ + + + + Not allowed to remove, restoring + ບໍ່ໄດ້ຮັບອະນຸຍາດໃຫ້ລຶບ, ກຳລັງກູ້ຄືນ + + + + Error while reading the database. + + + + + account was deleted. Unable to continue. + + + + + Not allowed because you don't have permission to add subfolders to that folder + ບໍ່ໄດ້ຮັບອະນຸຍາດ ເພາະທ່ານບໍ່ມີສິດເພີ່ມໂຟເດີຍ່ອຍໃນໂຟເດີນັ້ນ + + + + Error while doing a rename, unhandled status code: %1 + ເກີດຂໍ້ຜິດພາດຂະນະປ່ຽນຊື່, ລະຫັດສະຖານະທີ່ບໍ່ຮອງຮັບ: %1 + + + + Not allowed because you don't have permission to add files in that folder + ບໍ່ໄດ້ຮັບອະນຸຍາດ ເພາະທ່ານບໍ່ມີສິດເພີ່ມໄຟລ໌ໃນໂຟເດີນັ້ນ + + + + Not allowed to upload this file because it is read-only on the server, restoring + ບໍ່ໄດ້ຮັບອະນຸຍາດໃຫ້ອັບໂຫຼດໄຟລ໌ນີ້ ເພາະມັນເປັນແບບອ່ານຢ່າງດຽວເທິງເຊີບເວີ, ກຳລັງກູ້ຄືນ + + + + This Space is currently unavailable + ພື້ນທີ່ນີ້ບໍ່ສາມາດໃຊ້ງານໄດ້ໃນຂະນະນີ້ + + + + Server replied with an error while reading directory '%1' : %2 + ເຊີບເວີຕອບກັບດ້ວຍຂໍ້ຜິດພາດຂະນະອ່ານໄດເຣັກທໍຣີ '%1' : %2 + + + + OCC::PropagateDirectory + + + Error updating metadata: %1 + ເກີດຂໍ້ຜິດພາດໃນການອັບເດດຂໍ້ມູນ Metadata: %1 + + + + %1 the folder is currently in use + %1 ໂຟເດີກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + OCC::PropagateDownloadFile + + + Failed to free up space, the file %1 is currently in use + ບໍ່ສາມາດສ້າງພື້ນທີ່ວ່າງໄດ້, ໄຟລ໌ %1 ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + + File %1 can not be downloaded because of a local file name clash with %2! + ບໍ່ສາມາດດາວໂຫຼດໄຟລ໌ %1 ໄດ້ ເນື່ອງຈາກຊື່ໄຟລ໌ຊ້ຳກັບ %2 ທີ່ມີຢູ່ໃນເຄື່ອງ! + + + + The download would reduce free local disk space below the limit + ການດາວໂຫຼດຈະເຮັດໃຫ້ພື້ນທີ່ວ່າງໃນດິສກ໌ເຫຼືອໜ້ອຍກວ່າທີ່ກຳນົດ + + + + Free space on disk is less than %1 + ພື້ນທີ່ວ່າງໃນດິສກ໌ເຫຼືອໜ້ອຍກວ່າ %1 + + + + File was deleted from server + ໄຟລ໌ຖືກລຶບອອກຈາກເຊີບເວີແລ້ວ + + + + Broken webserver returned empty content length for non-empty file on resume + ເວັບເຊີບເວີທີ່ຜິດພາດໄດ້ສົ່ງຄືນຄ່າຄວາມຍາວຂອງເນື້ອຫາເປັນສູນສຳລັບໄຟລ໌ທີ່ບໍ່ວ່າງໃນການດາວໂຫຼດຕໍ່ + + + + The file could not be downloaded completely. + ບໍ່ສາມາດດາວໂຫຼດໄຟລ໌ໄດ້ຢ່າງສົມບູນ. + + + + The downloaded file is empty despite the server announced it should have been %1. + ໄຟລ໌ທີ່ດາວໂຫຼດມາວ່າງເປົ່າ ທັງທີ່ເຊີບເວີແຈ້ງວ່າມັນຄວນມີຂະໜາດ %1. + + + + File %1 cannot be saved because of a local file name clash with %2! + ບໍ່ສາມາດບັນທຶກໄຟລ໌ %1 ໄດ້ ເນື່ອງຈາກຊື່ໄຟລ໌ຊ້ຳກັບ %2 ທີ່ມີຢູ່ໃນເຄື່ອງ! + + + + Error updating metadata: %1 + ເກີດຂໍ້ຜິດພາດໃນການອັບເດດຂໍ້ມູນ Metadata: %1 + + + + + + + The file %1 is currently in use + ໄຟລ໌ %1 ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + + File has changed since discovery + ໄຟລ໌ມີການປ່ຽນແປງຕັ້ງແຕ່ການກວດພົບຄັ້ງທຳອິດ + + + + OCC::PropagateItemJob + + + ; Restoration Failed: %1 + ; ການກູ້ຄືນລົ້ມເຫຼວ: %1 + + + + OCC::PropagateLocalMkdir + + + could not delete file %1, error: %2 + ບໍ່ສາມາດລຶບໄຟລ໌ %1 ໄດ້, ຂໍ້ຜິດພາດ: %2 + + + + Can not create local folder %1 because of a local file name clash with %2 + ບໍ່ສາມາດສ້າງໂຟເດີ %1 ໃນເຄື່ອງໄດ້ ເນື່ອງຈາກຊື່ຊ້ຳກັບ %2 + + + + Error updating metadata: %1 + ເກີດຂໍ້ຜິດພາດໃນການອັບເດດຂໍ້ມູນ Metadata: %1 + + + + The file %1 is currently in use + ໄຟລ໌ %1 ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + could not create folder %1 + ບໍ່ສາມາດສ້າງໂຟເດີ %1 ໄດ້ + + + + OCC::PropagateLocalRemove + + + %1 failed with: %2 + %1 ລົ້ມເຫຼວດ້ວຍ: %2 + + + + Could not remove %1 because of a local file name clash with %2! + ບໍ່ສາມາດລຶບ %1 ໄດ້ ເນື່ອງຈາກຊື່ໄຟລ໌ຊ້ຳກັບ %2 ທີ່ມີຢູ່ໃນເຄື່ອງ! + + + + Could not move '%1' to the trash bin + ບໍ່ສາມາດຍ້າຍ '%1' ໄປທີ່ຖັງຂີ້ເຫຍື້ອໄດ້ + + + + + %1 the file is currently in use + %1 ໄຟລ໌ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + OCC::PropagateLocalRename + + + File %1 can not be renamed to %2 because of a local file name clash + ບໍ່ສາມາດປ່ຽນຊື່ໄຟລ໌ %1 ເປັນ %2 ໄດ້ ເນື່ອງຈາກຊື່ຊ້ຳກັນໃນເຄື່ອງ + + + + Could not rename %1 to %2, the file is currently in use + ບໍ່ສາມາດປ່ຽນຊື່ %1 ເປັນ %2 ໄດ້, ໄຟລ໌ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + Error updating metadata: %1 + ເກີດຂໍ້ຜິດພາດໃນການອັບເດດຂໍ້ມູນ Metadata: %1 + + + + The file %1 is currently in use + ໄຟລ໌ %1 ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + Failed to rename file + ການປ່ຽນຊື່ໄຟລ໌ລົ້ມເຫຼວ + + + + Error setting pin state + ເກີດຂໍ້ຜິດພາດໃນການຕັ້ງຄ່າສະຖານະການປັກໝຸດ + + + + OCC::PropagateRemoteDelete + + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + ລະຫັດ HTTP ທີ່ບໍ່ຖືກຕ້ອງຖືກສົ່ງຄືນຈາກເຊີບເວີ. ຄາດວ່າຈະເປັນ 204, ແຕ່ໄດ້ຮັບ "%1 %2". + + + + OCC::PropagateRemoteMkdir + + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + ລະຫັດ HTTP ທີ່ບໍ່ຖືກຕ້ອງຖືກສົ່ງຄືນຈາກເຊີບເວີ. ຄາດວ່າຈະເປັນ 201, ແຕ່ໄດ້ຮັບ "%1 %2". + + + + Error writing metadata to the database: %1 + ເກີດຂໍ້ຜິດພາດໃນການຂຽນຂໍ້ມູນ Metadata ລົງໃນຖານຂໍ້ມູນ: %1 + + + + Setting file status failed due to file lock + ການຕັ້ງຄ່າສະຖານະໄຟລ໌ລົ້ມເຫຼວ ເນື່ອງຈາກໄຟລ໌ຖືກລັອກ + + + + OCC::PropagateRemoteMove + + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + ລະຫັດ HTTP ທີ່ບໍ່ຖືກຕ້ອງຖືກສົ່ງຄືນຈາກເຊີບເວີ. ຄາດວ່າຈະເປັນ 201, ແຕ່ໄດ້ຮັບ "%1 %2". + + + + Error updating metadata: %1 + ເກີດຂໍ້ຜິດພາດໃນການອັບເດດຂໍ້ມູນ Metadata: %1 + + + + The file %1 is currently in use + ໄຟລ໌ %1 ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + Error writing metadata to the database + ເກີດຂໍ້ຜິດພາດໃນການຂຽນຂໍ້ມູນ Metadata ລົງໃນຖານຂໍ້ມູນ + + + + Error setting pin state + ເກີດຂໍ້ຜິດພາດໃນການຕັ້ງຄ່າສະຖານະການປັກໝຸດ + + + + OCC::PropagateUpdateMetaDataJob + + + Could not update file : %1 + ບໍ່ສາມາດອັບເດດໄຟລ໌: %1 + + + + The file %1 is currently in use + ໄຟລ໌ %1 ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + OCC::PropagateUploadCommon + + + Local file changed during sync. It will be resumed. + ໄຟລ໌ໃນເຄື່ອງມີການປ່ຽນແປງຂະນະຊິ້ງ. ຈະດຳເນີນການຕໍ່. + + + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + ບໍ່ສາມາດອັບໂຫຼດໄຟລ໌ %1 ໄດ້ ເນື່ອງຈາກມີໄຟລ໌ອື່ນທີ່ມີຊື່ດຽວກັນແຕ່ຕົວພິມໃຫຍ່-ນ້ອຍຕ່າງກັນຢູ່ແລ້ວ + + + + + Upload of %1 exceeds the quota for the folder + ການອັບໂຫຼດ %1 ເກີນໂຄຕ້າຂອງໂຟເດີ + + + + + %1 the file is currently in use + %1 ໄຟລ໌ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + File Removed + ໄຟລ໌ຖືກລຶບອອກແລ້ວ + + + + The server did not provide the file permissions + ເຊີບເວີບໍ່ໄດ້ໃຫ້ສິດໃນການເຂົ້າເຖິງໄຟລ໌ + + + + Error updating metadata: %1 + ເກີດຂໍ້ຜິດພາດໃນການອັບເດດຂໍ້ມູນ Metadata: %1 + + + + OCC::PropagateUploadFile + + + %1 the file is currently in use + %1 ໄຟລ໌ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + The server did ask for a removed legacy feature(polling) + ເຊີບເວີໄດ້ຮ້ອງຂໍຄຸນສົມບັດເກົ່າທີ່ຖືກລຶບອອກໄປແລ້ວ (polling) + + + + The local file was removed during sync. + ໄຟລ໌ໃນເຄື່ອງຖືກລຶບອອກຂະນະກຳລັງຊິ້ງ. + + + + The server did not acknowledge the file upload. (No e-tag was present) + ເຊີບເວີບໍ່ໄດ້ຕອບຮັບການອັບໂຫຼດໄຟລ໌. (ບໍ່ມີ e-tag ປະກົດຂຶ້ນ) + + + + OCC::PropagateUploadFileTUS + + + %1 the file is currently in use + %1 ໄຟລ໌ກຳລັງຖືກນຳໃຊ້ຢູ່ + + + + Checksum computation failed + ການຄຳນວນ Checksum ລົ້ມເຫຼວ + + + + The local file was removed during sync. + ໄຟລ໌ໃນເຄື່ອງຖືກລຶບອອກຂະນະກຳລັງຊິ້ງ. + + + + OCC::ProtocolItemModel + + + Time + ເວລາ + + + + File + ໄຟລ໌ + + + + Folder + ໂຟເດີ + + + + Issues + ບັນຫາ + + + + Action + ການກະທຳ + + + + Size + ຂະໜາດ + + + + Account + ບັນຊີ + + + + Status + ສະຖານະ + + + + OCC::RequestAuthenticationController + + + IdP is unreachable. Contact your system administrator or try again later. + ບໍ່ສາມາດເຂົ້າເຖິງ IdP ໄດ້. ຕິດຕໍ່ຜູ້ດູແລລະບົບຂອງທ່ານ ຫຼື ລອງໃໝ່ພາຍຫຼັງ. + + + + Authentication failed. + ການຢືນຢັນຕົວຕົນລົ້ມເຫຼວ. + + + + OCC::RequestAuthenticationWidget + + + Application Name Logo %1 + ໂລໂກ້ຊື່ແອັບພລິເຄຊັນ %1 + + + + Sign in required + ຕ້ອງເຂົ້າສູ່ລະບົບ + + + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + ທ່ານອາດຈະຖືກຕັດການເຊື່ອມຕໍ່ໂດຍອັດຕະໂນມັດເນື່ອງຈາກບັນຫາເຊີບເວີ ຫຼື ໝົດເວລາ. ກະລຸນາເຂົ້າສູ່ລະບົບອີກຄັ້ງ. + + + + Sign in URL + URL ສຳລັບເຂົ້າສູ່ລະບົບ + + + + To copy the sign in URL to the clipboard, use the copy button + ເພື່ອສຳເນົາ URL ເຂົ້າສູ່ລະບົບໃສ່ຄລິບບອດ, ໃຫ້ໃຊ້ປຸ່ມສຳເນົາ + + + + Copy URL to sign in + ສຳເນົາ URL ເພື່ອເຂົ້າສູ່ລະບົບ + + + + Additional logo defined by the organization + ໂລໂກ້ເພີ່ມເຕີມທີ່ກຳນົດໂດຍອົງກອນ + + + + Stay logged out + ຢູ່ໃນສະຖານະອອກຈາກລະບົບ + + + + Sign in + ລອງໃໝ່ + + + + URL copied + ສຳເນົາ URL ແລ້ວ + + + + Copy URL + ສຳເນົາ URL + + + + OCC::ResolveUrlAdapter + + + SSL failure when connecting to server at %1 + ການເຊື່ອມຕໍ່ SSL ລົ້ມເຫຼວຂະນະເຊື່ອມຕໍ່ກັບເຊີບເວີທີ່ %1 + + + + Could not detect compatible server at %1 + ບໍ່ສາມາດກວດພົບເຊີບເວີທີ່ເຂົ້າກັນໄດ້ທີ່ %1 + + + + Resolved url is invalid %1 + URL ທີ່ກວດສອບແລ້ວບໍ່ຖືກຕ້ອງ %1 + + + + Rejected redirect from %1 to %2 + ປະຕິເສດການປ່ຽນເສັ້ນທາງ (redirect) ຈາກ %1 ໄປຫາ %2 + + + + OCC::SelectiveSyncWidget + + + Loading … + ກຳລັງໂຫຼດ… + + + + Deselect remote folders you do not wish to synchronize. + ຍົກເລີກການເລືອກໂຟເດີເທິງເຊີບເວີທີ່ທ່ານບໍ່ຕ້ອງການຊິ້ງ. + + + + Name + ຊື່ + + + + Size + ຂະໜາດ + + + + + Currently there are no subfolders on the server. + ຂະນະນີ້ບໍ່ມີໂຟເດີຍ່ອຍເທິງເຊີບເວີ. + + + + An error occurred while loading the list of subfolders. + ເກີດຂໍ້ຜິດພາດຂະນະກຳລັງໂຫຼດລາຍການໂຟເດີຍ່ອຍ. + + + + OCC::SettingsView + + + Form + ຟອມ + + + + General Settings + ການຕັ້ງຄ່າທົ່ວໄປ + + + + Use Monochrome Icons in the system tray + ໃຊ້ໄອຄອນສີດຽວໃນຖາດລະບົບ + + + + Show Desktop Notifications + ສະແດງການແຈ້ງເຕືອນເທິງເດັສທັອບ + + + + Start on Login + ເລີ່ມເມື່ອເຂົ້າສູ່ລະບົບ + + + + Language + ພາສາ + + + + Language selector + ໂຕເລືອກພາສາ + + + + Advanced + ຂັ້ນສູງ + + + + Sync hidden files + ຊິງໄຟລ໌ທີ່ເຊື່ອງໄວ້ + + + + Show crash reporter + ສະແດງໂຕລາຍງານຂໍ້ຜິດພາດ + + + + Move remotely deleted files to the local trash bin instead of deleting them + ຍ້າຍໄຟລ໌ທີ່ຖືກລຶບຈາກທາງໄກໄປທີ່ຖັງຂີ້ເຫຍື້ອໃນເຄື່ອງ ແທນການລຶບຖິ້ມ + + + + Edit Ignored Files + ແກ້ໄຂໄຟລ໌ທີ່ບໍ່ສົນໃຈ + + + + Log Settings + ການຕັ້ງຄ່າລັອກ + + + + Network + ເຄືອຂ່າຍ + + + + Warning + ຄຳເຕືອນ + + + + Language changes require a restart of this application to take effect. + ການປ່ຽນພາສາຕ້ອງການການເລີ່ມໃໝ່ຂອງໂປຣແກຣມນີ້ເພື່ອໃຫ້ມີຜົນ. + + + + You cannot disable autostart because system-wide autostart is enabled. + ທ່ານບໍ່ສາມາດປິດການເລີ່ມອັດຕະໂນມັດໄດ້ ເພາະການເລີ່ມອັດຕະໂນມັດທົ່ວລະບົບຖືກເປີດຢູ່. + + + + (use default) + (ໃຊ້ຄ່າເລີ່ມຕົ້ນ) + + + + unknown (%1) + ບໍ່ຮູ້ຈັກ (%1) + + + + OCC::SocketApi + + + Share with %1 + parameter is the application name + ແຊຣ໌ກັບ %1 + + + + Confirm deletion + ຢືນຢັນການລຶບ + + + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + ທ່ານຕ້ອງການລຶບໄດເຣັກທໍຣີ <i>%1</i> ແລະ ເນື້ອໃນທັງໝົດຂອງມັນຢ່າງຖາວອນບໍ່? + + + + Do you want to delete the file <i>%1</i> permanently? + ທ່ານຕ້ອງການລຶບໄຟລ໌ <i>%1</i> ຢ່າງຖາວອນບໍ່? + + + + Error + ຂໍ້ຜິດພາດ + + + + Moving file failed: + +%1 + ການຍ້າຍໄຟລ໌ລົ້ມເຫຼວ: + +%1 + + + + I shared something with you + ຂ້ອຍໄດ້ແຊຣ໌ບາງຢ່າງກັບເຈົ້າ + + + + Select new location… + ເລືອກສະຖານທີ່ໃໝ່… + + + + + Share… + ແຊຣ໌… + + + + + Copy private link to clipboard + ສຳເນົາລິ້ງສ່ວນຕົວໃສ່ຄລິບບອດ + + + + Resharing this file is not allowed + ບໍ່ອະນຸຍາດໃຫ້ແຊຣ໌ໄຟລ໌ນີ້ຕໍ່ + + + + Resharing this folder is not allowed + ບໍ່ອະນຸຍາດໃຫ້ແຊຣ໌ໂຟເດີນີ້ຕໍ່ + + + + Open in %1 + ເປີດໃນ %1 + + + + Show file versions in web browser + ສະແດງເວີຊັນຂອງໄຟລ໌ໃນເວັບເບຣົາເຊີ + + + + Rename… + ປ່ຽນຊື່… + + + + Rename and upload… + ປ່ຽນຊື່ ແລະ ອັບໂຫຼດ… + + + + + Move and rename… + ຍ້າຍ ແລະ ປ່ຽນຊື່… + + + + Move, rename and upload… + ຍ້າຍ, ປ່ຽນຊື່ ແລະ ອັບໂຫຼດ… + + + + Move and upload… + ຍ້າຍ ແລະ ອັບໂຫຼດ… + + + + Delete local changes + ລຶບການປ່ຽນແປງໃນເຄື່ອງ + + + + Delete + ລຶບ + + + + OCC::SpacesPage + + + Select a folder to sync it to your computer. + ເລືອກໂຟເດີເພື່ອຊິ້ງຂໍ້ມູນໃສ່ຄອມພິວເຕີຂອງທ່ານ. + + + + Select a Space to sync it to your computer. + ເລືອກພື້ນທີ່ (Space) ເພື່ອຊິ້ງຂໍ້ມູນໃສ່ຄອມພິວເຕີຂອງທ່ານ. + + + + OCC::SyncEngine + + + %1 (skipped due to earlier error, trying again in %2) + %1 (ຂ້າມໄປເນື່ອງຈາກຂໍ້ຜິດພາດກ່ອນໜ້າ, ຈະລອງໃໝ່ໃນ %2) + + + + Unable to sync: the local folder is missing. + + + + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + ມີພື້ນທີ່ວ່າງພຽງ %1, ຕ້ອງການຢ່າງໜ້ອຍ %2 ເພື່ອເລີ່ມຕົ້ນ + + + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + ບໍ່ສາມາດເປີດ ຫຼື ສ້າງຖານຂໍ້ມູນການຊິ້ງໃນເຄື່ອງໄດ້. ກະລຸນາກວດສອບໃຫ້ແນ່ໃຈວ່າທ່ານມີສິດໃນການຂຽນໃນໂຟເດີຊິ້ງ. + + + + + + + Cannot open the sync journal. + + + + + Aborted due to %1 + ຍົກເລີກເນື່ອງຈາກ %1 + + + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + ພື້ນທີ່ດິສກ໌ເຫຼືອໜ້ອຍ: ການດາວໂຫຼດທີ່ຈະເຮັດໃຫ້ພື້ນທີ່ວ່າງເຫຼືອໜ້ອຍກວ່າ %1 ໄດ້ຖືກຂ້າມໄປ. + + + + There is insufficient space available on the server for some uploads. + ມີພື້ນທີ່ບໍ່ພຽງພໍເທິງເຊີບເວີສຳລັບການອັບໂຫຼດບາງລາຍການ. + + + + application exit + abort reason + ການອອກຈາກໂປຣແກຣມ + + + + Unable to read the blacklist from the local database + ບໍ່ສາມາດອ່ານລາຍການທີ່ຖືກຫ້າມຈາກຖານຂໍ້ມູນໃນເຄື່ອງໄດ້ + + + + Unable to read from the sync journal. + ບໍ່ສາມາດອ່ານຈາກບັນທຶກການຊິ້ງໄດ້. + + + + OCC::SyncErrorWidget + + + Issues table + ຕາຕະລາງບັນຫາ + + + + There were too many issues. Not all will be visible here. + ມີບັນຫາຫຼາຍເກີນໄປ. ບໍ່ແມ່ນທັງໝົດຈະສະແດງຢູ່ທີ່ນີ້. + + + + There were conflicts. Check the documentation on how to resolve them. + ມີຂໍ້ຂັດແຍ່ງ. ກວດສອບເອກະສານກ່ຽວກັບວິທີແກ້ໄຂ. + + + + The file %1 was ignored as its name is reserved by %2 + ໄຟລ໌ %1 ຖືກບໍ່ສົນໃຈ ເນື່ອງຈາກຊື່ຂອງມັນຖືກສະຫງວນໂດຍ %2 + + + + Reset column sizes + ຣີເຊັດຂະໜາດຖັນ + + + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + ມີຂໍ້ຂັດແຍ່ງ. <a href="%1">ກວດສອບເອກະສານກ່ຽວກັບວິທີແກ້ໄຂ.</a> + + + + Filter menu + ເມນູໂຕກອງ + + + + Account + ບັນຊີ + + + + Filter + ໂຕກອງ + + + + OCC::SyncJournalDb + + + Failed to connect database. + ການເຊື່ອມຕໍ່ຖານຂໍ້ມູນລົ້ມເຫຼວ. + + + + OCC::Theme + + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>ເວີຊັນ %1. ສຳລັບຂໍ້ມູນເພີ່ມເຕີມເຂົ້າຊົມໄດ້ທີ່ <a href="%2">https://%3</a></p><p>ສຳລັບບັນຫາທີ່ຮູ້ຈັກ ແລະ ການຊ່ວຍເຫຼືອ, ກະລຸນາເຂົ້າຊົມ: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>ໂດຍ Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller ແລະ ທ່ານອື່ນໆ.</small></p><p>ສະຫງວນລິຂະສິດໂດຍ ownCloud GmbH (A Kiteworks Company)</p><p>ແຈກຢາຍໂດຍ %4 ແລະ ໄດ້ຮັບອະນຸຍາດພາຍໃຕ້ GNU General Public License (GPL) ເວີຊັນ 2.0.<br/>%5 ແລະ ໂລໂກ້ %5 ເປັນເຄື່ອງໝາຍການຄ້າທີ່ຈົດທະບຽນຂອງ %4 ໃນສະຫະລັດອາເມລິກາ, ປະເທດອື່ນໆ, ຫຼື ທັງສອງ.</p><p><small>%6</small></p> + + + + OCC::TlsErrorDialog + + + TLS Certificate Error + ຂໍ້ຜິດພາດໃບຮັບຮອງ TLS + + + + Do you want to trust this certificate anyway? + ທ່ານຍັງຕ້ອງການເຊື່ອຖືໃບຮັບຮອງນີ້ຢູ່ບໍ? + + + + Cannot connect securely to %1 + ບໍ່ສາມາດເຊື່ອມຕໍ່ກັບ %1 ຢ່າງປອດໄພໄດ້ + + + + &lt;not specified&gt; + <ບໍ່ໄດ້ລະບຸ> + + + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>ດ້ວຍໃບຮັບຮອງ %1</h3><div id="ccert"><p>ອົງກອນ: %2<br/>ໜ່ວຍງານ: %3<br/>ປະເທດ: %4</p><p>ລາຍນິ້ວມື (MD5): <tt>%5</tt><br/>ລາຍນິ້ວມື (SHA1): <tt>%6</tt><br/>ລາຍນິ້ວມື (SHA256): <tt>%7</tt><br/><br/>ວັນທີມີຜົນ: %8ວັນທີໝົດອາຍຸ: %9</div><h3>ຜູ້ອອກໃບຮັບຮອງ: %10</h3><div id="issuer"><p>ອົງກອນ: %11<br/>ໜ່ວຍງານ: %12<br/>ປະເທດ: %13</p></div></div> + + + + OCC::TrayMenuController + + + Unsupported Server Version + ເວີຊັນເຊີບເວີບໍ່ຮອງຮັບ + + + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + ເຊີບເວີໃນບັນຊີ %1 ໃຊ້ເວີຊັນ %2 ທີ່ບໍ່ຮອງຮັບ. ການໃຊ້ໂປຣແກຣມນີ້ກັບເວີຊັນເຊີບເວີທີ່ບໍ່ຮອງຮັບນັ້ນຍັງບໍ່ໄດ້ຜ່ານການທົດສອບ ແລະ ອາດເປັນອັນຕະລາຍ. ກະລຸນາດຳເນີນການຕໍ່ດ້ວຍຄວາມສ່ຽງຂອງທ່ານເອງ. + + + + Show %1 + ສະແດງ %1 + + + + Help + ຊ່ວຍເຫຼືອ + + + + About %1 + ກ່ຽວກັບ %1 + + + + Quit %1 + ອອກຈາກ %1 + + + + OCC::UpdateDownloadedWidget + + + Restart later + ເລີ່ມໃໝ່ພາຍຫຼັງ + + + + Restart now + ເລີ່ມໃໝ່ດຽວນີ້ + + + + OCC::UpdaterScheduler + + + Update available + ມີອັບເດດ + + + + OCC::UrlPageController + + + Your web browser will be opened to complete sign in. + ເວັບເບຣົາເຊີຂອງທ່ານຈະຖືກເປີດຂຶ້ນເພື່ອສຳເລັດການເຂົ້າສູ່ລະບົບ. + + + + %1 logo + ໂລໂກ້ %1 + + + + Welcome to %1 + ຍິນດີຕ້ອນຮັບສູ່ %1 + + + + Enter your server address to get started. Your web browser will be opened to complete sign in. + ປ້ອນທີ່ຢູ່ເຊີບເວີຂອງທ່ານເພື່ອເລີ່ມຕົ້ນ. ເວັບເບຣົາເຊີຂອງທ່ານຈະຖືກເປີດຂຶ້ນເພື່ອສຳເລັດການເຂົ້າສູ່ລະບົບ. + + + + Server address field + ຊ່ອງປ້ອນທີ່ຢູ່ເຊີບເວີ + + + + Enter your server address here + ປ້ອນທີ່ຢູ່ເຊີບເວີຂອງທ່ານຢູ່ບ່ອນນີ້ + + + + Additional logo defined by the organization + ໂລໂກ້ເພີ່ມເຕີມທີ່ກຳນົດໂດຍອົງກອນ + + + + Invalid server URL: %1 + URL ເຊີບເວີບໍ່ຖືກຕ້ອງ: %1 + + + + Invalid URL scheme. Only https is accepted. + ຮູບແບບ URL ບໍ່ຖືກຕ້ອງ. ຮອງຮັບສະເພາະ https ເທົ່ານັ້ນ. + + + + The requested URL failed with query value: %1 + URL ທີ່ຮ້ອງຂໍລົ້ມເຫຼວດ້ວຍຄ່າ query: %1 + + + + OCC::UserInfoAdapter + + + Network request to collect user info failed + ການຮ້ອງຂໍເຄືອຂ່າຍເພື່ອເກັບກຳຂໍ້ມູນຜູ້ໃຊ້ລົ້ມເຫຼວ + + + + Unable to retrieve user info: invalid credentials. + ບໍ່ສາມາດດຶງຂໍ້ມູນຜູ້ໃຊ້: ຂໍ້ມູນປະຈຳຕົວບໍ່ຖືກຕ້ອງ. + + + + Unexpected network response when retrieving user info. + ໄດ້ຮັບການຕອບກັບເຄືອຂ່າຍທີ່ບໍ່ຄາດຄິດຂະນະດຶງຂໍ້ມູນຜູ້ໃຊ້. + + + + Retrieving user info failed with JSON error. + ດຶງຂໍ້ມູນຜູ້ໃຊ້ບໍ່ສຳເລັດເນື່ອງຈາກຂໍ້ຜິດພາດ JSON. + + + + OCC::ValidateChecksumHeader + + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + ໄຟລ໌ທີ່ດາວໂຫຼດມາບໍ່ກົງກັບ checksum, ຈະດຳເນີນການດາວໂຫຼດຕໍ່. '%1' != '%2' + + + + OCC::Vfs + + + The Virtual filesystem feature does not support a drive as sync root + ຄຸນສົມບັດລະບົບໄຟລ໌ສະເໝືອນບໍ່ຮອງຮັບການໃຊ້ໄດຣຟ໌ເປັນໂຟເດີຫຼັກໃນການຊິ້ງ + + + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + ຄຸນສົມບັດລະບົບໄຟລ໌ສະເໝືອນຕ້ອງການລະບົບໄຟລ໌ແບບ NTFS, %1 ກຳລັງໃຊ້ %2 + + + + The Virtual filesystem feature is not supported on network drives + ຄຸນສົມບັດລະບົບໄຟລ໌ສະເໝືອນບໍ່ຮອງຮັບໃນໄດຣຟ໌ເຄືອຂ່າຍ + + + + OCC::VfsWinPrivate + + + + %1 - %2 + %1 - %2 + + + + Could not find StorageProviderState property %1 + ບໍ່ພົບຄຸນສົມບັດ StorageProviderState %1 + + + + Could not find StorageFolder for %1 error: %2 + ບໍ່ພົບ StorageFolder ສຳລັບ %1 ຂໍ້ຜິດພາດ: %2 + + + + Could not retrieve StorageFolder for %1 %2 (0x%3) + ບໍ່ສາມາດດຶງຂໍ້ມູນ StorageFolder ສຳລັບ %1 %2 (0x%3) + + + + The folder is used by a different client: %1 + ໂຟນເດີນີ້ຖືກໃຊ້ໂດຍໂປຣແກຣມອື່ນ: %1 + + + + Error registering StorageProvider for %1: %2 (0x%3) + ເກີດຂໍ້ຜິດພາດໃນການລົງທະບຽນ StorageProvider ສຳລັບ %1: %2 (0x%3) + + + + Unable to connect sync root: %1 error: %2 + ບໍ່ສາມາດເຊື່ອມຕໍ່ກັບໂຟນເດີຫຼັກການຊິງ: %1 ຂໍ້ຜິດພາດ: %2 + + + + %1 is managed by another sync client + %1 ຖືກຈັດການໂດຍໂປຣແກຣມຊິງອື່ນ + + + + Downgrading the folder from %1 to %2 is not supported + ບໍ່ຮອງຮັບການປ່ຽນລຸ້ນ (downgrade) ໂຟນເດີຈາກ %1 ໄປຫາ %2 + + + + OCC::WebFingerLookupAdapter + + + Failed to retrieve user info + ບໍ່ສາມາດດຶງຂໍ້ມູນຜູ້ໃຊ້ໄດ້ + + + + WebFinger lookup returned no links + ການຄົ້ນຫາ WebFinger ບໍ່ມີລິ້ງສົ່ງຄືນມາ + + + + OCC::WindowsUpdater + + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>ມີໂປຣແກຣມ %1 Client ເວີຊັນໃໝ່ໃຫ້ອັບເດດ.</p><p><b>%2</b> ສາມາດດາວໂຫຼດໄດ້ແລ້ວ. ເວີຊັນທີ່ຕິດຕັ້ງຢູ່ແມ່ນ %3.</p> + + + + Skip this version + ຂ້າມເວີຊັນນີ້ + + + + Update Failed + ການອັບເດດລົ້ມເຫຼວ + + + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>ມີໂປຣແກຣມ %1 Client ເວີຊັນໃໝ່ ແຕ່ຂະບວນການອັບເດດລົ້ມເຫຼວ.</p><p><b>%2</b> ໄດ້ຖືກດາວໂຫຼດແລ້ວ. ເວີຊັນທີ່ຕິດຕັ້ງຢູ່ແມ່ນ %3.</p> + + + + Ask again later + ຖາມໃໝ່ພາຍຫຼັງ + + + + Restart and update + ເລີ່ມໃໝ່ ແລະ ອັບເດດ + + + + Update manually + ອັບເດດດ້ວຍຕົນເອງ + + + + QObject + + + in the future + ໃນອະນາຄົດ + + + + %n day(s) ago + %n ມື້ກ່ອນ + + + + %n hour(s) ago + %n ຊົ່ວໂມງກ່ອນ + + + + now + ດຽວນີ້ + + + + less than a minute ago + ບໍ່ຮອດໜຶ່ງນາທີກ່ອນ + + + + %n minute(s) ago + %n ນາທີກ່ອນ + + + + %1: %2 + this displays an error string (%2) for a file %1 + %1: %2 + + + + SpacesBrowser + + + Form + ຟອມ + + + + SpacesPage + + + Spaces list + ລາຍການພື້ນທີ່ + + + + SpacesView + + + Spaces + ພື້ນທີ່ + + + + SyncFileItem::Status + + + Undefined + ບໍ່ໄດ້ກຳນົດ + + + + Fatal Error + ຂໍ້ຜິດພາດຮ້າຍແຮງ + + + + + Error + ຂໍ້ຜິດພາດ + + + + Info + ຂໍ້ມູນ + + + + Success + ສຳເລັດ + + + + Conflict + ຂໍ້ຂັດແຍ່ງ + + + + File Ignored + ໄຟລ໌ຖືກບໍ່ສົນໃຈ + + + + Restored + ກູ້ຄືນແລ້ວ + + + + Blacklisted + ຖືກຂຶ້ນບັນຊີດຳ + + + + Excluded + ຖືກຍົກເວັ້ນ + + + + Message + ຂໍ້ຄວາມ + + + + Filename Reserved + ຊື່ໄຟລ໌ຖືກສະຫງວນ + + + + UpdateDownloadedWidget + + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">ຕ້ອງການເລີ່ມໃໝ່</span></p></body></html> + + + + Utility + + + %n year(s) + %n ປີ + + + + %n month(s) + %n ເດືອນ + + + + %n day(s) + %n ມື້ + + + + %n hour(s) + %n ຊົ່ວໂມງ + + + + %n minute(s) + %n ນາທີ + + + + %n second(s) + %n ວິນາທີ + + + + %1 %2 + %1 %2 + + + + Shutting down %1 + ກຳລັງປປິດ %1 + + + + account loading + + + Error accessing the configuration file + ເກີດຂໍ້ຜິດພາດໃນການເຂົ້າເຖິງໄຟລ໌ການຕັ້ງຄ່າ + + + + There was an error while accessing the configuration file at %1. + ເກີດຂໍ້ຜິດພາດຂະນະກຳລັງເຂົ້າເຖິງໄຟລ໌ການຕັ້ງຄ່າທີ່ %1. + + + + ownCloudTheme::aboutVersions() + + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Libraries Qt %3, %4%7ກຳລັງໃຊ້ປລັກອິນໄຟລ໌ສະເໝືອນ: %5%7%6 + + + + ownCloudTheme::qtVer + + + %1 (Built against Qt %2) + %1 (ສ້າງດ້ວຍ Qt %2) + + + + ownCloudTheme::versionWithSha + + + %1 %2 + %1 %2 + + + + pinstate + + + Always available locally + ມີໃຫ້ໃຊ້ງານໃນເຄື່ອງສະເໝີ + + + + Currently available locally + ຂະນະນີ້ມີໃຫ້ໃຊ້ງານໃນເຄື່ອງ + + + + Some available online only + ບາງສ່ວນມີໃຫ້ໃຊ້ງານອອນລາຍເທົ່ານັ້ນ + + + + Available online only + ມີໃຫ້ໃຊ້ງານອອນລາຍເທົ່ານັ້ນ + + + + progress + + + Virtual file created + ສ້າງໄຟລ໌ສະເໝືອນແລ້ວ + + + + Replaced by virtual file + ຖືກແທນທີ່ດ້ວຍໄຟລ໌ສະເໝືອນ + + + + Downloaded + ດາວໂຫຼດແລ້ວ + + + + Uploaded + ອັບໂຫຼດແລ້ວ + + + + Deleted + ລຶບແລ້ວ + + + + Ignored + ບໍ່ສົນໃຈ + + + + Server version downloaded, local copy was backed up as conflict file + ດາວໂຫຼດເວີຊັນຈາກເຊີບເວີ, ສຳເນົາໃນເຄື່ອງຖືກສຳຮອງໄວ້ເປັນໄຟລ໌ຂໍ້ຂັດແຍ່ງ + + + + Renamed %1 to %2 + ປ່ຽນຊື່ %1 ເປັນ %2 ແລ້ວ + + + + Moved %1 to %2 + ຍ້າຍ %1 ໄປຫາ %2 ແລ້ວ + + + + Error + ຂໍ້ຜິດພາດ + + + + Updated local metadata + ອັບເດດຂໍ້ມູນ metadata ໃນເຄື່ອງແລ້ວ + + + + + Unknown + ບໍ່ຮູ້ຈັກ + + + + downloading + ກຳລັງດາວໂຫຼດ + + + + uploading + ກຳລັງອັບໂຫຼດ + + + + deleting + ກຳລັງລຶບ + + + + moving + ກຳລັງຍ້າຍ + + + + ignoring + ກຳລັງບໍ່ສົນໃຈ + + + + error + ຂໍ້ຜິດພາດ + + + + updating local metadata + ກຳລັງອັບເດດຂໍ້ມູນ metadata ໃນເຄື່ອງ + + + + utility + + + Could not open browser + ບໍ່ສາມາດເປີດເບຣົາເຊີໄດ້ + + + + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? + ເກີດຂໍ້ຜິດພາດຂະນະເປີດເບຣົາເຊີເພື່ອໄປທີ່ URL %1. ອາດຈະບໍ່ມີການຕັ້ງຄ່າເບຣົາເຊີເລີ່ມຕົ້ນ? + + + + Could not open email client + ບໍ່ສາມາດເປີດໂປຣແກຣມອີເມວໄດ້ + + + + There was an error when launching the email client to create a new message. Maybe no default email client is configured? + ເກີດຂໍ້ຜິດພາດຂະນະເປີດໂປຣແກຣມອີເມວເພື່ອສ້າງຂໍ້ຄວາມໃໝ່. ອາດຈະບໍ່ມີການຕັ້ງຄ່າໂປຣແກຣມອີເມວເລີ່ມຕົ້ນ? + + + + Make always available locally + ເຮັດໃຫ້ມີໃຫ້ໃຊ້ງານໃນເຄື່ອງສະເໝີ + + + + Free up local space + ສ້າງພື້ນທີ່ວ່າງໃນເຄື່ອງ + + + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + ການຕັ້ງຄ່າບາງຢ່າງຖືກຕັ້ງໃນໂປຣແກຣມເວີຊັນໃໝ່ກວ່າ ແລະ ໃຊ້ຄຸນສົມບັດທີ່ບໍ່ມີໃນເວີຊັນນີ້ + + + \ No newline at end of file diff --git a/translations/client_nb_NO.ts b/translations/client_nb_NO.ts deleted file mode 100644 index 9f2a8421b7d..00000000000 --- a/translations/client_nb_NO.ts +++ /dev/null @@ -1,4784 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - Avbryt - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - Lukk - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - Kunne ikke lage kataloger i søppelbøtten - - - - - Could not move '%1' to '%2' - Kunne ikke flytte '%1' til '%2' - - - - Moving to the trash is not implemented on this platform - Flytting til søppelbøtten er ikke støttet på denne plattformen - - - - Error removing '%1': %2 - Feil ved fjerning av '%1': %2; - - - - Could not remove folder '%1' - Klarte ikke å fjerne mappe '%1' - - - - FolderWizardSourcePage - - - Form - Skjema - - - - Pick a local folder on your computer to sync - Velg en lokal mappe på din maskin som skal synkroniseres - - - - &Choose... - &Velg... - - - - FolderWizardTargetPage - - - Form - Skjema - - - - Select a remote destination folder - Velg en ekstern mappe som målmappe - - - - Create Folder - Opprett mappe - - - - Refresh - Oppdater - - - - Folders - Mapper - - - - TextLabel - Tekst-etikett - - - - NotificationWidget - - - Form - Skjema - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Tekst-etikett - - - - OCC::AboutDialog - - - About - Om - - - - TextLabel - Tekst-etikett - - - - Versions - Versjoner - - - - About %1 - Om %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Forbindelsen fikk tidsavbrudd - - - - Unknown error: network reply was deleted - Ukjent feil: nettverkssvar ble slettet - - - - Server replied "%1 %2" to "%3 %4" - Server svarte "%1 %2" til "%3 %4" - - - - OCC::Account - - - %1@%2 - - - - - OCC::AccountSettings - - - Form - Skjema - - - - Re-open Browser - - - - - ... - ... - - - - Storage space: ... - Lagringsplass: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Umarkerte mapper vil bli <b>fjernet</b> fra ditt lokale filsystem og vil ikke bli synkronisert med denne maskinen lenger - - - - Synchronize all - Synkroniser alle - - - - Synchronize none - Synkroniser ingen - - - - Apply manual changes - Bruk manuelle endringer - - - - Apply - Bruk - - - - - - - Cancel - Avbryt - - - - Connected with <server> as <user> - Tilkoblet <server> som <user> - - - - No account configured. - Ingen konto konfigurert. - - - - Remove - Fjern - - - - Account - Konto - - - - Choose what to sync - Velg hva som synkroniseres - - - - Force sync now - Tving synkronisering nå - - - - Restart sync - Prøv synkronisering igjen - - - - Remove folder sync connection - Fjern tilkobling for mappe-synkronisering - - - - Folder creation failed - Oppretting av mappe feilet - - - - <p>Could not create local folder <i>%1</i>. - <p>Klarte ikke å opprette lokal mappe <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Bekreft fjerning av tilkobling for mappe-synkronisering - - - - Remove Folder Sync Connection - Fjern tilkobling for mappe-synkronisering - - - - Disable virtual file support? - - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - - - - - Sync Running - Synkroniserer... - - - - The syncing operation is running.<br/>Do you want to terminate it? - Synkronisering kjører.<br/>Vil du avbryte den? - - - - %1 in use - %1 i bruk - - - - %1 as <i>%2</i> - %1 som <i>%2</i> - - - - Connected to %1. - Tilkoblet %1. - - - - Server %1 is temporarily unavailable. - Server %1 er midlertidig utilgjengelig. - - - - Server %1 is currently in maintenance mode. - Server %1 er for øyeblikket i vedlikeholdsmodus. - - - - Signed out from %1. - Logget ut fra %1. - - - - Connecting to %1... - Kobler til %1... - - - - No connection to %1 at %2. - Ingen tilkobling til %1 på %2. - - - - Log in - Logg inn - - - - There are folders that were not synchronized because they are too big: - Noen mapper ble ikke synkronisert fordi de er for store - - - - There are folders that were not synchronized because they are external storages: - Noen mapper ble ikke synkronisert fordi de er eksterne lagringsplasser: - - - - There are folders that were not synchronized because they are too big or external storages: - Noen mapper ble ikke synkronisert fordi de er for store eller de er eksterne lagringsplasser: - - - - Confirm Account Removal - Bekreft fjerning av konto - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Vil du virkelig fjerne tilkoblingen til kontoen <i>%1</i>?</p><p><b>Merk:</b> Dette vil <b>ikke</b> slette noen filer.</p> - - - - Remove connection - Fjern tilkobling - - - - Open folder - Åpne mappe - - - - - Log out - Logg ut - - - - Open local folder - - - - - Open folder in browser - - - - - Resume sync - Fortsett synkronisering - - - - Pause sync - Sett synkronisering på pause - - - - Availability - - - - - Disable virtual file support... - - - - - Enable virtual file support%1... - - - - - (experimental) - - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Vil du virkelig stoppe synkronisering av mappen <i>%1</i>?</p><p><b>Merk:</b> Dette vil <b>ikke</b> slette noen filer.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) av %2 i bruk. Noen mapper, inkludert nettverkstilkoblede eller delte mapper, kan ha andre begrensninger. - - - - %1 of %2 in use - %1 av %2 i bruk - - - - Currently there is no storage usage information available. - Ingen informasjon om bruk av lagringsplass tilgjengelig for øyeblikket. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - Obtaining authorization from the browser. - - - - - Server configuration error: %1 at %2. - - - - - No %1 connection configured. - Ingen %1-forbindelse konfigurert. - - - - OCC::AccountState - - - Signed out - Logget ut - - - - Disconnected - Frakoblet - - - - Connected - Tilkoblet - - - - Service unavailable - Tjeneste utilgjengelig - - - - Maintenance mode - Vedlikeholdsmodus - - - - Network error - Nettverksfeil - - - - Configuration error - Konfigurasjonsfeil - - - - Asking Credentials - Be om legitimasjon - - - - Unknown account state - Ukjent kontotilstand - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 på %2 - - - - %1 on %2 (disconnected) - %1 på %2 (frakoblet) - - - - OCC::ActivityListModel - - - %1 %2 on %3 - - - - - OCC::ActivitySettings - - - - Server Activity - Server-aktivitet - - - - Sync Protocol - Synkroniseringsprotokoll - - - - Not Synced - Ikke synkronisert - - - - Not Synced (%1) - %1 is the number of not synced files. - Ikke synkronisert (%1) - - - - The server activity list has been copied to the clipboard. - Server-aktivitetslisten er kopiert til utklippstavlen. - - - - The sync activity list has been copied to the clipboard. - Synkroniserings-aktivitetslisten er kopiert til utklippstavlen. - - - - The list of unsynced items has been copied to the clipboard. - Listen med usynkroniserte elementer ble kopiert til utklippstavlen. - - - - Copied to clipboard - Kopiert til utklippstavlen - - - - OCC::ActivityWidget - - - Form - Skjema - - - - - - TextLabel - Tekst-etikett - - - - Server Activities - Server-aktiviteter - - - - Copy - Kopier - - - - Copy the activity list to the clipboard. - Kopier aktivitetslisten til utklippstavlen. - - - - Notifications - Varsler - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - - - - - Open the activity view for details. - - - - - <br/>Account %1 does not have activities enabled. - <br/>Konto %1 har ikke aktiviteter aktivert. - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Autentisering med SSL-klientsertifikat - - - - This server probably requires a SSL client certificate. - Denne serveren krever sannsynligvis et SSL-klientsertifikat - - - - Certificate & Key (pkcs12) : - Sertifikat & Nøkkel (pkcs12): - - - - Browse... - Bla... - - - - Certificate password : - Passord for sertifikatet : - - - - An encrypted pkcs12 bundle is strongly recommended as a copy will be stored in the configuration file. - - - - - Select a certificate - Velg et sertifikat - - - - Certificate files (*.p12 *.pfx) - Sertifikat-filer (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Å fortsette vil<b> slette disse innstillingene </b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Å fortsette vil <b> ignorere disse innstillingene </b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Avslutt - - - - Continue - Fortsett - - - - Error accessing the configuration file - Feil ved lesing av konfigurasjonsfil - - - - There was an error while accessing the configuration file at %1. - En feil oppstod ved aksessering av konfigurasjonsfilen på %1. - - - - Quit ownCloud - Avslutt ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - Autentisering kreves - - - - Enter username and password for '%1' at %2. - Legg inn brukernavn og passord for '%1' på %2. - - - - &User: - &Bruker: - - - - &Password: - &Passord - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Feil ved skriving av metadata til databasen - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Ingen ownCloud-konter konfigurert - - - - The configured server for this client is too old - Den konfigurerte serveren for denne klienten er for gammel - - - - Please update to the latest server and restart the client. - Vennligst oppdatert til den nyeste serveren og start klienten på nytt. - - - - Authentication error: Either username or password are wrong. - Autentiseringsfeil: Bruker navn eller passord er feil. - - - - timeout - tidsavbrudd - - - - The provided credentials are not correct - Oppgitt påloggingsinformasjon er feil - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - - - - - Directory not accessible on client, permission denied - - - - - Directory not found: %1 - - - - - Filename encoding is not valid - Filnavn-koding er ikke gyldig - - - - Error while reading directory %1 - - - - - OCC::Folder - - - Local folder %1 does not exist. - Lokal mappe %1 eksisterer ikke. - - - - %1 should be a folder but is not. - %1 skal være en mappe men er ikke det. - - - - %1 is not readable. - %1 kan ikke leses. - - - - %1 has been removed. - %1 names a file. - %1 har blitt fjernet. - - - - %1 has been updated. - %1 names a file. - %1 har blitt oppdatert. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 har blitt omdøpt til %2. - - - - %1 has been moved to %2. - %1 har blitt flyttet til %2. - - - - %1 and %n other file(s) have been removed. - %1 og %n annen fil har blitt fjernet.%1 og %n andre filer har blitt fjernet. - - - - %1 and %n other file(s) have been added. - - - - - %1 has been added. - %1 names a file. - - - - - %1 and %n other file(s) have been updated. - %1 og %n annen fil har blitt oppdatert.%1 og %n andre filer har blitt oppdatert. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 er blitt omdøpt til %2 og %n annen fil har blitt omdøpt.%1 er blitt omdøpt til %2 og %n andre filer har blitt omdøpt. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 er blitt flyttet til %2 og %n annen fil har blitt flyttet.%1 er blitt flyttet til %2 og %n andre filer har blitt flyttet. - - - - %1 has and %n other file(s) have sync conflicts. - %1 og %n andre filer har synkroniseringskonflikter.%1 og %n andre filer har synkroniseringskonflikter. - - - - %1 has a sync conflict. Please check the conflict file! - %1 har en synkroniseringskonflikt. Sjekk konflikt-filen. - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 og %n andre filer kunne ikke synkroniseres pga. feil. Se loggen for detaljer.%1 og %n andre filer kunne ikke synkroniseres pga. feil. Se loggen for detaljer. - - - - %1 could not be synced due to an error. See the log for details. - %1 kunne ikke synkroniseres pga. en feil. Se loggen for detaljer. - - - - Sync Activity - Synkroniseringsaktivitet - - - - Could not read system exclude file - Klarte ikke å lese systemets ekskluderingsfil - - - - A new folder larger than %1 MB has been added: %2. - - En ny mappe større enn %1 MB er blitt lagt til: %2. - - - - A folder from an external storage has been added. - - En mappe fra et eksternt lager er blitt lagt til. - - - - - Please go in the settings to select it if you wish to download it. - Gå til Innstillinger og velg den hvis du ønsker å laste den ned. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - Mappen %1  var laget, men har tidligere blitt ekskludert fra synkronisering. Data i mappen vil derfor ikke bli synkronisert. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - Filen %1 ble opprettet men har tidligere blitt ekskludert fra synkronisering. Den vil ikke bli synkronisert. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Alle filene i synkroniseringsmappen '%1' ble slettet på serveren. -Disse slettingene vil bli synkronisert til din lokale synkroniseringsmappe og gjøre filene utilgjengelige hvis du ikke har lov til å gjenopprette. -Hvis du velger å beholde filene, vil de bli synkronisert tilbake til serveren hvis du har lov til det. -Hvis du velger å slette filene, blir de utilgjengelige for deg hvis du ikke er eieren av filen. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Alle filene i din lokale synkroniseringsmappe '%1' ble slettet. Disse slettingene vil bli synkronisert med serveren og gjøre filene utilgjengelige hvis de ikke gjenopprettes. -Er du sikker på at du ønsker å synkronisere denne handlingen med serveren? -Hvis det var et uhell og du velger å beholde filene, vil de bli synkronisert tilbake fra serveren. - - - - Remove All Files? - Fjerne alle filer? - - - - Remove all files - Fjern alle filer - - - - Keep files - Behold filer - - - - OCC::FolderMan - - - Could not reset folder state - Klarte ikke å tilbakestille mappetilstand - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - En gammel synkroniseringsjournal '%1' ble funnet men kunne ikke fjernes. Pass på at ingen applikasjoner bruker den. - - - - (backup) - (sikkerhetskopi) - - - - (backup %1) - (sikkerhetskopi %1) - - - - Undefined State. - Udefinert tilstand. - - - - Waiting to start syncing. - Venter på å starte synkronisering. - - - - Preparing for sync. - Forbereder synkronisering. - - - - Sync is running. - Synkronisering kjører. - - - - Sync was successful, unresolved conflicts. - Synkronisering var vellykket, uløste konflikter. - - - - Last Sync was successful. - Siste synkronisering var vellykket. - - - - Setup Error. - Feil med oppsett. - - - - User Abort. - Brukeravbrudd. - - - - Sync is paused. - Synkronisering er satt på pause. - - - - %1 (Sync is paused) - %1 (Synkronisering er satt på pause) - - - - No valid folder selected! - Ingen gyldig mappe valgt! - - - - The selected path does not exist! - - - - - The selected path is not a folder! - Den valgte stien er ikke en mappe! - - - - You have no permission to write to the selected folder! - Du har ikke skrivetilgang til den valgte mappen! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Det er allerede en synkronisering fra serveren til denne lokale mappen. Velg en annen mappe! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Den lokale mappen %1 inneholder allerede en mappe brukt i en mappe-synkronisering. Velg en annen! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Den lokale mappen %1 er allerede en undermappe av en mappe brukt i en mappe-synkronisering. Velg en annen! - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Legg til mappe-synkronisering - - - - File - Fil - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Du må være tilkoblet for å legge til en mappe - - - - Click this button to add a folder to synchronize. - Klikk denne knappen for å legge til en mappe som skal synkroniseres. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Feil ved innlasting av listen av mapper fra serveren. - - - - Virtual file support is enabled. - - - - - Signed out - Logget ut - - - - Fetching folder list from server... - Henter mappeliste fra server.. - - - - There are unresolved conflicts. Click for details. - Det er uløste konflikter. Klikk for detaljer. - - - - Synchronizing VirtualFiles with local folder - - - - - Synchronizing with local folder - Synkroniserer med lokal mappe - - - - Checking for changes in remote '%1' - Sjekker for endringer i ekstern '%1' - - - - Checking for changes in local '%1' - Sjekker for endringer i lokal '%1' - - - - Reconciling changes - Sammenslår endringer - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synkroniserer %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - nedlasting %1/s - - - - ↓ %1/s - - - - - ↑ %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - opplasting %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 av %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 igjen, %1 av %2, fil %3 of %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 av %2, fil %3 av %4 - - - - file %1 of %2 - fil %1 av %2 - - - - Waiting... - Venter.. - - - - Waiting for %n other folder(s)... - Venter på %n annen mappe...Venter på %n andre mappe(r)... - - - - Preparing to sync... - Forbereder synkronisering... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Legg til mappe-synkronisering - - - - Add Sync Connection - Legg til tilkobling for synkronisering - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Klikk for å velge en lokal mappe å synkronisere. - - - - Enter the path to the local folder. - Legg inn stien til den lokale mappen. - - - - Select the source folder - Velg kildemappe - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Opprett ekstern mappe - - - - Enter the name of the new folder to be created below '%1': - Oppgi navnet på den nye mappen som skal opprettes under '%1': - - - - Folder was successfully created on %1. - Mappen ble opprettet på %1. - - - - Authentication failed accessing %1 - Autentisering feilet ved tilgang til %1 - - - - Failed to create the folder on %1. Please check manually. - Klarte ikke å opprette mappen på %1. Sjekk manuelt. - - - - Failed to list a folder. Error: %1 - Klarte ikke å liste en mappe. Feil: %1 - - - - Choose this to sync the entire account - Velg dette for å synkronisere en hel konto - - - - This folder is already being synced. - Denne mappen synkroniseres allerede. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Du synkroniserer allerede <i>%1</i>, som er foreldremappe for <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Advarsel:</b> %1 - - - - <b>Warning:</b> - <b>Advarsel:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Ingen E-Tag mottatt fra server. Sjekk proxy/gateway. - - - - We received a different E-Tag for resuming. Retrying next time. - Vi mottok en forskjellig E-Tag for å fortsette. Prøver igjen neste gang. - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - Serveren returnerte feil content-range - - - - OCC::GETJob - - - Connection Timeout - Tidsavbrudd ved tilkobling - - - - OCC::GeneralSettings - - - Form - Skjema - - - - General Settings - Vanlige innstillinger - - - - Sync hidden files - Synkroniser skjulte filer - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Nettverk - - - - Updates - Oppdateringer - - - - stable - Stabil - - - - beta - beta - - - - About - Om - - - - Advanced - Avansert - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Be om bekreftelse før synkronisering av mapper større enn - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Be om bekreftelse før synkronisering av eksterne lagre - - - - &Launch on System Startup - &Start når maskinen starter - - - - Show &Desktop Notifications - Vis &påminnelser på skrivebordet - - - - &Update Channel - - - - - Edit &Ignored Files - Rediger &ignorerte filer - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - Vis &krasj-rapportering - - - - &Restart && Update - &Omstart && Oppdater - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - Bytt oppdateringskanal? - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Oppdateringskanalen bestemmer hvilke klientoppdateringer som skal tilbys for installasjon. Den "stabile" kanalen inneholder bare oppgraderinger som betraktes som pålitelige, mens versjonene i "beta" -kanalen kan inneholde nyere funksjoner og feilrettinger, men har ennå ikke blitt testet grundig. - -Vær oppmerksom på at dette bare velger hvor oppgraderinger kommer ifra, og at nedgraderinger ikke er mulig. Så å gå tilbake fra beta-kanalen til den stabile kanalen, kan ikke gjøres umiddelbart og betyr å vente på en stabil versjon som er nyere enn den nåværende installerte betaen versjon. - - - - Change update channel - Bytt oppdateringskanal - - - - Cancel - Avbryt - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Legg inn %1-passord:<br><br>Bruker: %2<br>Konto: %3<br> - - - - Reading from keychain failed with error: '%1' - Lesing fra nøkkelring feilet med feil: '%1' - - - - Enter Password - Legg inn passord - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Klikk her</a> for å be om et app-passord fra web-grensesnittet. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Redigering av ignorerte filer - - - - Files Ignored by Patterns - Filer som ignorerers av mønstre - - - - Add - Legg til - - - - Pattern - Mønster - - - - Allow Deletion - Tillat sletting - - - - Remove - Fjern - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Klarte ikke å åpne fil - - - - Cannot write changes to '%1'. - Kan ikke skrive endringer til '%1'. - - - - Add Ignore Pattern - Nytt mønster - - - - Add a new ignore pattern: - Legg til ignoreringsmønster: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Denne oppføringen er gitt av systemet på '%1' og kan ikke endres i denne visningen. - - - - OCC::IssuesWidget - - - Form - Skjema - - - - List of issues - Liste over problemer - - - - - Account - Konto - - - - - <no filter> - <no filter> - - - - - Folder - Mappe - - - - Show warnings - Vis advarsler - - - - Show ignored files - Vis ignorerte filer - - - - There were too many issues. Not all will be visible here. - Det var for mange problemer. Ikke alle vil være synlige her. - - - - There were conflicts. Check the documentation on how to resolve them. - Det er konflikter. Vennligst sjekk dokumentasjonen på hvordan å løse dem. - - - - Copy the issues list to the clipboard. - Kopier problemlisten til utklippstavlen. - - - - Copy - Kopier - - - - Time - Tid - - - - File - Fil - - - - Issue - Problem - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Det er konflikter. <a href="%1">Vennligst sjekk dokumentasjonen på hvordan å løse dem. </a> - - - - OCC::LibCloudProvidersPrivate - - - Settings - Innstillinger - - - - OCC::LogBrowser - - - Log Output - Logg - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Åpne mappe - - - - OCC::NSISUpdater - - - New Version Available - Ny versjon tilgjengelig - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>En ny versjon av %1-klienten er tilgjengelig.</p><p><b>%2</b> er tilgjengelig for nedlasting. Installert versjon er %3.</p> - - - - - Skip this version - Hopp over denne versjonen - - - - Skip this time - Hopp over denne gangen - - - - Get update - Hent oppdatering - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - Skjema - - - - Proxy Settings - Proxy-innstillinger - - - - No Proxy - Ingen proxy - - - - Use system proxy - Bruk systemets proxy - - - - Specify proxy manually as - Spesifiser proxy manuelt som - - - - Host - Server - - - - : - : - - - - Proxy server requires authentication - Proxy-serveren krever pålogging - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Båndbredde for nedlasting - - - - - Limit to - Begrens til - - - - - KBytes/s - KByte/s - - - - - No limit - Ingen begrensning - - - - - Limit to 3/4 of estimated bandwidth - Begrens til 3/4 av estimert båndbredde - - - - Upload Bandwidth - Båndbredde for opplasting - - - - - Limit automatically - Begrens automatisk - - - - Hostname of proxy server - Servernavnet til proxy-serveren - - - - Username for proxy server - Brukernavn for proxy-server - - - - Password for proxy server - Passord for proxy-server - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - Opprettet %1 - - - - Closing in a few seconds... - Lukkes om noen sekunder... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 forespørsel feilet %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' valgt %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Feil returnert fra serveren: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Det oppsto en feil med tilgang til "token" endepunktet: <br><em>%1</em> - - - - Empty JSON from OAuth2 redirect - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Kunne ikke prosessere JSON returnert fra serveren:<br><em>%1</em> - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Error: Missing field %1 - - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Innloggingsfeil</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Feil bruker</h1><p>Du logget inn med bruker <em>%1</em>, men må logge inn med bruker <em>%2</em>.<br>Logg ut av %3 i en annen tab, <a href='%4'>klikk deretter her</a> og logg inn som bruker %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Ny %1-oppdatering er klar - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - En ny oppdatering for %1 vil bli installert. Oppdateringen -kan be om flere rettigheter underveis. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Klarte ikke å laste ned oppdateringen. Klikk <a href='%1'>her</a> for å laste ned oppdateringen manuelt. - - - - Could not check for new updates. - Klarte ikke å se etter nye oppdateringer. - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - Sjekker oppdaterings-server... - - - - Update status is unknown: Did not check for new updates. - Oppdateringsstatus er ukjent: Sjekket ikke for nye oppdateringer. - - - - No updates available. Your installation is at the latest version. - Ingen oppdateringer tilgjengelig. Du har den nyeste versjonen. - - - - Update Check - Oppdateringssjekk - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Koble til %1 - - - - Setup local folder options - Sett opp valg for lokal mappe - - - - Connect... - Koble til... - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - %1 folder '%2' is synced to local folder '%3' - %1 mappe '%2' er synkronisert til lokal mappe '%3' - - - - Sync the folder '%1' - Synkroniser mappen '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>Advarsel:</strong> Den lokale mappen er ikke tom. Velg en løsning!</small></p> - - - - Virtual files are not available for the selected folder - - - - - Local Sync Folder - Lokal synkroniseringsmappe - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Oppkobling feilet - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Klarte ikke å koble til den spesifiserte sikre server-addressen. Hvordan vil du gå videre?</p></body></html> - - - - Select a different URL - Velg en annen URL - - - - Retry unencrypted over HTTP (insecure) - Prøv igjen ukryptert over HTTP (usikret) - - - - Configure client-side TLS certificate - Konfigurer TLS-sertifikat på klientsiden - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Klarte ikke å koble til den sikre server-addressen <em>%1</em>. Hvordan vil du gå videre?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - &E-post - - - - Connect to %1 - Koble til %1 - - - - Enter user credentials - Legg inn påloggingsinformasjon - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Koble til %1 - - - - Login in your browser - Logg inn i nettleseren din - - - - Copy link to clipboard - Kopier link til utklippstavlen - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Koble til %1 - - - - Setup %1 server - Sett opp %1 server - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Denne url er IKKE sikker fordi den ikke er kryptert. -Det er ikke tilrådelig å bruke den. - - - - This url is secure. You can use it. - Denne url-en er sikker. Du kan bruke den. - - - - &Next > - &Neste > - - - - Invalid URL - Ugyldig URL - - - - Could not load certificate. Maybe wrong password? - Kunne ikke laste inn sertifikat. Kanskje feil passord? - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Klarte ikke å koble til %1 på %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Tidsavbrudd ved oppkobling mot %1 på %2. - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - Autentisert forespørsel til serveren ble omdirigert til '%1'. URL-en er ugyldig, serveren er feilkonfigurert. - - - - There was an invalid response to an authenticated webdav request - Det kom et uventet svar fra en autentisert webdav-forespørsel. - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - Tilgang forbudt av serveren. For å sjekke om du har gyldig tilgang, <a href="%1">klikk her</a> for å aksessere tjenesten med nettleseren din. - - - - Invalid URL - Ugyldig URL - - - - Could not create local folder %1 - Klarte ikke å opprette lokal mappe %1 - - - - No remote folder specified! - Ingen ekstern mappe spesifisert! - - - - Error: %1 - Feil: %1 - - - - The folder creation resulted in HTTP error code %1 - Oppretting av mappe resulterte i HTTP-feilkode %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Oppretting av ekstern mappe feilet fordi påloggingsinformasjonen er feil!<br/>Gå tilbake og sjekk brukernavnet og passordet ditt.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Oppretting av ekstern mappe %1 feilet med feil <tt>%2</tt>. - - - - Folder rename failed - Omdøping av mappe feilet - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Kan ikke fjerne og sikkerhetskopiere mappen fordi mappen eller en fil i mappen er åpen i et annet program. Lukk mappen eller filen og prøv igjen, eller avbryt oppsettet. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Veiviser for tilkobling - - - - Enable experimental feature? - Aktiver eksperimentelle funksjoner? - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - Hold deg trygg - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Alt er satt opp! - - - - Open Local Folder - Åpne lokal mappe - - - - Open %1 in Browser - Åpne %1 i nettleser - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - Ugyldig JSON-svar fra forespørsels-URL - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Symbolske lenker støttes ikke i synkronisering. - - - - File is listed on the ignore list. - Filen ligger på ignoreringslisten. - - - - File names ending with a period are not supported on this file system. - Filnavn som slutter med punktum er ikke tillatt på dette filsystemet - - - - File names containing the character '%1' are not supported on this file system. - Filnavn som inneholder '%1' er ikke tillatt på dette filsystemet - - - - File name contains at least one invalid character - Filnavnet inneholder minst ett ulovlig tegn - - - - The file name is a reserved name on this file system. - Filnavnet er et reservert navn på dette filsystemet. - - - - Filename contains trailing spaces. - Filnavn inneholder blanke på slutten. - - - - Filename is too long. - Filnavn er for langt. - - - - File/Folder is ignored because it's hidden. - Filen/mappen ignoreres fordi den er skjult. - - - - Stat failed. - Stat feilet. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Konflikt: Serverversjon lastet ned, lokal kopi har byttet navn og er ikke lastet opp. - - - - The filename cannot be encoded on your file system. - Filnavnet kan ikke kodes på filsystemet ditt. - - - - The filename is blacklisted on the server. - - - - - File has extension reserved for virtual files. - - - - - size - størrelse - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - Ikke tillatt å fjerne, gjenoppretter - - - - Ignored because of the "choose what to sync" blacklist - Ignorert på grunn av svartelisten "velg hva som skal synkroniseres" - - - - Not allowed because you don't have permission to add subfolders to that folder - Ikke tillatt fordi du ikke har lov til å lage undermapper i den mappen - - - - Not allowed because you don't have permission to add files in that folder - Ikke tillatt fordi du ikke har lov til å opprette filer i den mappen - - - - Not allowed to upload this file because it is read-only on the server, restoring - Ikke tillatt å laste opp denne filenfordi den er skrivebeskyttet på serveren, gjenoppretter - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Feil ved skriving av metadata til databasen - - - - OCC::PropagateDownloadFile - - - - File %1 can not be downloaded because of a local file name clash! - Fil %1 kan ikke lastes ned på grunn av lokalt sammenfall av filnavn! - - - - - %1 is currently in use - - - - - The download would reduce free local disk space below the limit - Nedlastingen ville ha redusert ledig lokal diskplass under grensen - - - - Free space on disk is less than %1 - Ledig plass på disk er mindre enn %1 - - - - File was deleted from server - Filen ble slettet fra serveren - - - - The file could not be downloaded completely. - Hele filen kunne ikke lastes ned. - - - - The downloaded file is empty despite the server announced it should have been %1. - Nedlastet fil er tom, selv om serveren annonserte at den skulle være %1. - - - - File %1 cannot be saved because of a local file name clash! - Fil %1 kan ikke lagres på grunn av lokal konflikt med filnavn. - - - - - File has changed since discovery - Filen er endret siden den ble oppdaget - - - - Failed to dehydrate %1, the file is currently in use - - - - - Error writing metadata to the database - Feil ved skriving av metadata til databasen - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Gjenoppretting feilet: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - En fil eller mappe ble fjernet fra en deling med lesetilgang, men gjenoppretting feilet: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - klarte ikke å slette fil %1, feil: %2 - - - - Attention, possible case sensitivity clash with %1 - Merk! mulig sammenfall med %1 pga. sensitivitet for store/små bokstaver - - - - could not create folder %1 - klarte ikke å opprette mappe %1 - - - - Error writing metadata to the database - Feil ved skriving av metadata til databasen - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash - Kunne ikke fjerne %1 på grunn av lokalt sammenfall av filnavn - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Fil %1 kan ikke omdøpes til %2 på grunn av lokalt sammenfall av filnavn - - - - Could not rename %1 to %2, the file is currently in use - - - - - - Error writing metadata to the database - Feil ved skriving av metadata til databasen - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Feil HTTP-kode returnert fra server. Ventet 204, men mottok "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Feil HTTP-kode returnert fra server. Ventet 201, men mottok "%1 %2". - - - - Error writing metadata to the database - Feil ved skriving av metadata til databasen - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Feil HTTP-kode returnert fra server. Ventet 201, men mottok "%1 %2". - - - - - Error writing metadata to the database - Feil ved skriving av metadata til databasen - - - - Error setting pin state - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Fil %1 kan ikke lastes opp fordi en annen fil eksisterer med samme navn, bare med forskjellige store og små bokstaver i navnet. - - - - File Removed - Fil fjernet - - - - Local file changed during syncing. It will be resumed. - Lokal fil endret under synkronisering. Den vil gjenopptas. - - - - Local file changed during sync. - Lokal fil endret under synkronisering. - - - - - Upload of %1 exceeds the quota for the folder - Opplasting av %1 overstiger kvoten for mappen - - - - Error writing metadata to the database - Feil ved skriving av metadata til databasen - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Den lokale filen ble fjernet under synkronisering. - - - - Local file changed during sync. - Lokal fil endret under synkronisering. - - - - Poll URL missing - Forespørsels-URL mangler - - - - Unexpected return code from server (%1) - Uventet returkode fra serveren (%1) - - - - Missing File ID from server - Mangler File ID fra server - - - - Missing ETag from server - Mangler ETag fra server - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Den lokale filen ble fjernet under synkronisering. - - - - Local file changed during sync. - Lokal fil endret under synkronisering. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - Poll URL missing - Forespørsels-URL mangler - - - - The local file was removed during sync. - Den lokale filen ble fjernet under synkronisering. - - - - Local file changed during sync. - Lokal fil endret under synkronisering. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Serveren godtok ikke den siste deloverføringen. (Ingen e-tag var tilstede) - - - - OCC::ProtocolWidget - - - Form - Skjema - - - - TextLabel - Tekst-etikett - - - - Open in browser - Åpne i nettleser - - - - Time - Tid - - - - File - Fil - - - - Folder - Mappe - - - - Action - Handling - - - - Size - Størrelse - - - - Local sync protocol - Lokal synkroniseringsprotokoll - - - - Copy - Kopier - - - - Copy the activity list to the clipboard. - Kopier aktivitetslisten til utklippstavlen. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Proxy-autentisering kreves - - - - Username: - Brukernavn: - - - - Proxy: - Proxy: - - - - The proxy server needs a username and password. - Proxy-serveren trenger et brukernavn og et passord. - - - - Password: - Passord: - - - - TextLabel - Tekst-etikett - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Velg hva som synkroniseres - - - - OCC::SelectiveSyncWidget - - - Loading ... - Laster ... - - - - Deselect remote folders you do not wish to synchronize. - Fjern valg for eksterne mapper som du ikke ønsker å synkronisere. - - - - Name - Navn - - - - Size - Størrelse - - - - - No subfolders currently on the server. - Ingen undermapper på serveren nå - - - - An error occurred while loading the list of sub folders. - Det oppstod en feil ved lasting av liten med undermapper. - - - - OCC::ServerNotificationHandler - - - Dismiss - Forkast - - - - OCC::SettingsDialog - - - Settings - Innstillinger - - - - Activity - Aktivitet - - - - Add account - Legg til en konto - - - - - Quit %1 - Avslutt %1 - - - - Are you sure you want to quit %1? - - - - - Account - Konto - - - - MainWindow - - - - - Hide - Skjul - - - - OCC::ShareDialog - - - TextLabel - Tekst-etikett - - - - share label - Tittel for deling - - - - Dialog - Dialog - - - - ownCloud Path: - ownCloud-sti: - - - - %1 Sharing - %1-deling - - - - %1 - %1 - - - - Folder: %2 - Mappe: %2 - - - - The server does not allow sharing - Serveren tillater ikke deling - - - - Retrieving maximum possible sharing permissions from server... - Henter maksimalt mulige delerettigheter fra serveren... - - - - The file can not be shared because it was shared without sharing permission. - Filen kan ikke deles fordi den ble delt uten adgang til å dele. - - - - Users and Groups - Brukere og grupper - - - - Public Links - Offentlige lenker - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Del NewDocument.odt - - - - Upload only (File Drop) - Kun opplasting (Fil Slipp) - - - - Receive files from others without revealing the contents of the folder. - Motta filer fra andre uten å avsløre innholdet i mappen. - - - - Download / View / Edit - - - - - Name: - - - - - Enter name of new share... - - - - - &Create - - - - - TextLabel - Tekst-etikett - - - - Set &password - Sett &passord - - - - Set &expiration date - Sett &utløpsdato - - - - Set password - Sett passord - - - - Recipients can view or download contents. - Mottakere kan se og laste ned innhold. - - - - Download / View - Last ned / Se - - - - Recipients can view, download, edit, delete and upload contents. - Mottakere kan se, laste ned, redigere og laste opp innhold. - - - - Anyone with the link has access to the file/folder - Alle med linken har tilgang til filen/mappen - - - - - P&assword protect - P&assordbeskyttelse - - - - The file can not be shared because it was shared without sharing permission. - Filen kan ikke deles fordi den ble delt uten adgang til å dele. - - - - Link shares have been disabled - Lenker har blitt deaktivert - - - - Create public link share - Opprett offentlig kobling deling - - - - - Delete - Slett - - - - Open link in browser - Åpne lenke i nettleser - - - - Copy link to clipboard - Kopier link til utklippstavlen - - - - Copy link to clipboard (direct download) - Kopier link til utklippstavlen (direkte nedlasting) - - - - Send link by email - Send link i e-post - - - - Send link by email (direct download) - Send link i e-post (direkte nedlasting) - - - - Create new... - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - Bekreft Sletting av Link - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Ønsker du virkelig å slette delingen av den offentlige lenken<i>%1</i>? </p> <p>Merk: Denne handlingen kan ikke omgjøres.</p> - - - - Cancel - Avbryt - - - - - Public link - Offentlig lenke - - - - Delete link share - Slett link - - - - - Please Set Password - Sett passord - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Del NewDocument.odt - - - - Share with users or groups ... - Del med brukere eller grupper ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - Elementet deles ikke med noen brukere eller grupper - - - - Open link in browser - Åpne lenke i nettleser - - - - Copy link to clipboard - Kopier URL til utklippstavlen - - - - Send link by email - Send link i e-post - - - - No results for '%1' - Ingen resultater for '%1' - - - - I shared something with you - Jeg delte noe med deg - - - - OCC::ShareUserLine - - - Form - Skjema - - - - - TextLabel - Tekst-etikett - - - - can edit - kan endre - - - - can share - kan dele - - - - ... - ... - - - - create - opprette - - - - change - endre - - - - delete - slett - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Del med %1 - - - - Context menu share - - - - - Context menu share %1 - - - - - Confirm deletion - - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - - - - - Do you want to delete the file <i>%1</i> permanently? - - - - - Select new location... - - - - - Error - Feil - - - - Moving file failed: - -%1 - - - - - I shared something with you - Jeg delte noe med deg - - - - - Share... - Del... - - - - - Copy private link to clipboard - Kopier privat URL til utklippstavlen - - - - Send private link by email... - Send privat link i e-post - - - - Resharing this file is not allowed - Videre deling av denne filen er ikke tillatt - - - - Resharing this folder is not allowed - - - - - - Copy public link to clipboard - Kopier offentlig link til utklippstavlen - - - - Open in browser - Åpne i nettleser - - - - Show file versions in browser - - - - - Rename... - - - - - Rename and upload... - - - - - Move and rename... - - - - - Move, rename and upload... - - - - - Delete local changes - - - - - Move and upload... - - - - - Delete - Slett - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Sertifikatdetaljer</h3> - - - - Common Name (CN): - Common Name (CN): - - - - Subject Alternative Names: - Subject Alternative Names: - - - - Organization (O): - Organization (O): - - - - Organizational Unit (OU): - Organizational Unit (OU): - - - - State/Province: - State/Province: - - - - Country: - Land: - - - - Serial: - Serial: - - - - <h3>Issuer</h3> - <h3>Utsteder</h3> - - - - Issuer: - Utsteder: - - - - Issued on: - Utstedt dato: - - - - Expires on: - Utløper dato: - - - - <h3>Fingerprints</h3> - <h3>Fingeravtrykk</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>NB:</b> Dette sertifikatet ble godkjent manuelt</p> - - - - %1 (self-signed) - %1 (egensignert) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Denne forbindelsen er kryptert med %1 bit %2. - - - - - Server version: %1 - - - - - No support for SSL session tickets/identifiers - Ingen støtte for billett/ID for SSL-økt - - - - Certificate information: - Sertifikatinformasjon: - - - - The connection is not secure - - - - - This connection is NOT secure as it is not encrypted. - - Denne forbindelsen er IKKE sikker da den ikke er kryptert. - - - - - OCC::SslErrorDialog - - - Form - Skjema - - - - Trust this certificate anyway - Stol på dette sertifikatet likevel - - - - Untrusted Certificate - Ikke-klarert sertifikat - - - - Cannot connect securely to <i>%1</i>: - Kan ikke koble sikkert til <i>%1</i>: - - - - with Certificate %1 - med sertifikat %1 - - - - - - &lt;not specified&gt; - &lt;ikke spesifisert&gt; - - - - - Organization: %1 - Organisasjon: %1 - - - - - Unit: %1 - Enhet: %1 - - - - - Country: %1 - Land: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Fingeravtrykk (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Fingeravtrykk (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Gyldig fra dato: %1 - - - - Expiration Date: %1 - Utløpsdato: %1 - - - - Issuer: %1 - Utsteder: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (hoppet over på grunn av tidligere feil, prøver igjen om %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Bare %1 er tilgjengelig, trenger minst %2 for å begynne - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Kan ikke åpne eller opprette den lokale synkroniseringsdatabasen. Kontroller at du har skrivetilgang i synkroniseringsmappen. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Diskplass er lav: Nedlastinger som ville redusere ledig plass under% 1 ble hoppet over. - - - - There is insufficient space available on the server for some uploads. - Det er ikke nok ledig plass på serveren for noen av opplastingene. - - - - Unresolved conflict. - Uløst konflikt - - - - Could not update file : %1 - - - - - Could not update virtual file metadata: %1 - - - - - Using virtual files with suffix, but suffix is not set - - - - - Unable to read the blacklist from the local database - Kan ikke lese svartelisten fra den lokale databasen - - - - Unable to read from the sync journal. - Kan ikke lese fra synkroniseringsjournalen - - - - Cannot open the sync journal - Kan ikke åpne synkroniseringsjournalen - - - - Aborted - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Sjekksum-headeren har feil format. - - - - The checksum header contained an unknown checksum type '%1' - Sjekksum-header inneholdt ukjent sjekksum-type '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Vennligst logg inn - - - - Folder %1: %2 - Mappe %1: %2 - - - - There are no sync folders configured. - Ingen synkroniseringsmapper er konfigurert. - - - - Open in browser - Åpne i nettleser - - - - - - Log in... - Logg inn.. - - - - - - Log out - Logg ut - - - - Recent Changes - Siste endringer - - - - Managed Folders: - Administrerte mapper: - - - - Open folder '%1' - Åpne mappe '%1' - - - - Open %1 in browser - Åpne %1 i nettleser - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Ukjent status - - - - Settings... - Innstillinger... - - - - Details... - Detaljer... - - - - Help - Hjelp - - - - Quit %1 - Avslutt %1 - - - - Disconnected from %1 - Koble fra %1 - - - - Unsupported Server Version - Server-versjonen støttes ikke - - - - Disconnected - Frakoblet - - - - Disconnected from some accounts - Frakoblet noen kontoer - - - - Disconnected from accounts: - Koblet fra kontoer: - - - - Account %1: %2 - Konto %1: %2 - - - - Signed out - Logget ut - - - - Account synchronization is disabled - Kontosynkronisering er deaktivert - - - - - Synchronization is paused - Synkronisering er satt på pause - - - - Unresolved conflicts - Uløste konflikter - - - - Error during synchronization - Feil under synkronisering - - - - No sync folders configured - Ingen synkronisering-mapper konfigurert. - - - - Unpause all folders - Fortsett alle mapper - - - - Pause all folders - Sett alle mapper på pause - - - - Unpause all synchronization - Fortsett all synkronisering - - - - Unpause synchronization - Fortsett synkronisering - - - - Pause all synchronization - Sett all synkronisering på pause - - - - Pause synchronization - Sett synkronisering på pause - - - - Log out of all accounts - Logg ut av alle kontoer - - - - Log in to all accounts... - Logg inn på alle kontoer... - - - - New account... - Ny konto... - - - - About %1 - Om %1 - - - - No items synced recently - Ingenting synkronisert nylig - - - - Checking for changes in remote '%1' - Sjekker for endringer i ekstern '%1' - - - - Checking for changes in local '%1' - Sjekker for endringer i lokal '%1' - - - - Syncing %1 of %2 (%3 left) - Synkroniserer %1 av %2 (%3 gjenstår) - - - - Syncing %1 of %2 - Synkroniserer %1 av %2 - - - - Syncing %1 (%2 left) - Synkroniserer %1 (%2 gjenstår) - - - - Syncing %1 - Synkroniserer %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Oppdatert - - - - OwncloudAdvancedSetupPage - - - Form - Skjema - - - - - - - - TextLabel - Tekst-etikett - - - - Server - Server - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Hvis det settes kryss i denne boksen, vil eksisterende innhold i den lokale mappen bli slettet for å starte en ren synkronisering fra serveren.</p><p>Ikke sett kryss for dette hvis det lokale innholdet skal lastes opp til mappen på serveren.</p></body></html> - - - - Start a &clean sync (Erases the local folder!) - Start en &ren synkronisering (sletter den lokale mappen!) - - - - Ask for confirmation before synchroni&zing folders larger than - Be om bekreftelse før synkronisering av mapper større enn - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Be om bekreftelse før synkronisering av e&ksterne lagre - - - - Choose what to sync - Velg hva som synkroniseres - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - Manuelt opprett mappesynk koblinger. - - - - &Local Folder - &Lokal mappe - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Behold lokale data - - - - S&ynchronize everything from server (recommended) - Synkroniser alt fra serveren (anbefalt) - - - - Status message - Statusmelding - - - - OwncloudHttpCredsPage - - - Form - Skjema - - - - &Username - &Brukernavn - - - - &Password - &Passord - - - - OwncloudOAuthCredsPage - - - Form - Skjema - - - - Please switch to your browser to proceed. - Vennligst bytt til nettleseren din for å fortsette. - - - - An error occurred while connecting. Please try again. - Det oppstod en feil under tilkobling. Vennligst prøv igjen. - - - - Re-open Browser - Gjenåpne nettleser - - - - OwncloudSetupPage - - - Form - Skjema - - - - - TextLabel - Tekst-etikett - - - - Ser&ver Address - Server adresse - - - - https://... - https://... - - - - Error Label - Feiletikett - - - - OwncloudWizardResultPage - - - Form - Skjema - - - - TextLabel - Tekst-etikett - - - - Your entire account is synced to the local folder - Hele kontoen din er synkronisert til den lokale mappen - - - - - PushButton - Trykknapp - - - - QObject - - - in the future - fram i tid - - - - %n day(s) ago - i gårfor %n dager siden - - - - %n hour(s) ago - for %n time sidenfor %n timer siden - - - - now - - - - - Less than a minute ago - For mindre enn et minutt siden - - - - %n minute(s) ago - for %n minutt sidenfor %n minutter siden - - - - Some time ago - For en stund siden - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n år%n år - - - - %n month(s) - %n måned%n måneder - - - - %n day(s) - %n dag%n dager - - - - %n hour(s) - %n time%n timer - - - - %n minute(s) - %n minutt%n minutter - - - - %n second(s) - %n sekund%n sekunder - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - System Tray er ikke tilgjengelig - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 trenger et fungerende 'system tray'. Hvis du kjører XFCE, følg <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">disse instruksene</a>. I andre tilfeller, installer en 'system tray'-applikasjon som f.eks. 'trayer' og prøv igjen. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3 %4%8%9Libraries Qt %5, %6%8Using virtual files plugin: %7%8 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - - - - - Replaced by virtual file - - - - - Downloaded - Nedlastet - - - - Uploaded - Opplastet - - - - Server version downloaded, copied changed local file into conflict file - Server-versjon lastet ned, kopierte endret lokal fil til konfliktfil - - - - Deleted - Slettet - - - - Moved to %1 - Flyttet til %1 - - - - Ignored - Ignorert - - - - Filesystem access error - Feil med tilgang til filsystemet - - - - Error - Feil - - - - Updated local metadata - Oppdaterte lokale metadata - - - - - Unknown - Ukjent - - - - downloading - laster ned - - - - uploading - laster opp - - - - deleting - sletter - - - - moving - flytter - - - - ignoring - ignorerer - - - - - error - feil - - - - updating local metadata - oppdaterer lokale metadata - - - - theme - - - Status undefined - Status udefinert - - - - Waiting to start sync - Venter på å starte synkronisering - - - - Sync is running - Synkronisering pågår - - - - Sync Success - Synkronisering vellykket - - - - Sync Success, some files were ignored. - Vellykket synkronisering. Noen filer ble ignorert. - - - - Sync Error - Synkroniseringsfeil - - - - Setup Error - Feil i oppsett - - - - Preparing to sync - Forbereder synkronisering - - - - Aborting... - Avbryter... - - - - Sync is paused - Synkronisering er satt på pause - - - - utility - - - Could not open browser - Kunne ikke åpne nettleseren - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - Det oppstod en feil ved start av nettleseren for å gå til URL %1. Kanskje ingen standard nettleser er konfigurert? - - - - Could not open email client - Klarte ikke å åpne epost-klient - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Det oppstod en feil ved oppstart av epost-klienten for å lage en ny melding. Kanskje ingen standard epost-klient er konfigurert? - - - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - - - - - Make always available locally - - - - - Free up local space - - - - \ No newline at end of file diff --git a/translations/client_nl.ts b/translations/client_nl.ts index fac422a15d2..52b0c30500f 100644 --- a/translations/client_nl.ts +++ b/translations/client_nl.ts @@ -1,360 +1,424 @@ - + - CrashReporter + AccountBar - - Tomahawk Crash Reporter - + + Navigation bar + Navigatiebalk - - <html><head/><body><p><br/></p></body></html> - + + Add Account + Voeg account toe - - Abort - Afbreken + + Activity + Activiteit - - Send this report - Rapport versturen + + Settings + Instellingen - - Don't send - Niet versturen + + Quit + Afsluiten + + + AppImageUpdateAvailableWidgetUi - - You can disable sending crash reports in the configuration dialog. - + + Skip this version + Deze versie overslaan + + + AppProvider - - - - We cannot gather useful debug information on your system. - + + Failed to open %1 in web. Error: %2. + Kon %1 niet openen in browser. Foutmelding: %2. + + + ChecksumHeader - - - - - - Close - Sluiten + + The checksum header is malformed: %1 + De checksum header is verkeerd geformatteerd: %1 - - Ready to send debug information (<a href="%1">view backtrace</a>). - + + The checksum header contained an unknown checksum type '%1' + Het header controlegetal bevat een onbekend controlegetal type '%1' + + + CommandLine - - Gathering debug information... - + + %1 version %2 +File synchronization desktop utility. + %1 versie %2<byte value="xd"/> +Bestandssynchronisatie desktop software tool. - - Uploaded %L1 of %L2 KB. - %L1 van %L2 KB geupload. + + For more information, see %1 + link to homepage + Voor moor informatie, zie %1 - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Start met het zichtbaar maken van het hoofdvenster, of als het al actief is, breng het naar voren. Standaard wordt de client op de achtergrond gestart. - - Failed to send crash info. - + + Quit the running instance. + Stop het lopende programma. - - - FileSystem - - - - - Can't rename %1, the file is currently in use - Kan %1 niet verplaatsen, het bestand is in gebruik + + Write log to file (use - to write to stdout). + Schrijf log naar bestand (gebruik - om naar stdout te schrijven). - - Could not make directories in trash - Kon geen mappen aanmaken in de prullenbak + + Write each sync log output in a new file in folder. + Schrijf elk synchronisatie log naar een nieuw bestand in de map. - - - Could not move '%1' to '%2' - Kon '%1' niet naar '%2' verplaatsen + + Flush the log file after every write. + Schrijf naar logbestand bij elke regel. - - Moving to the trash is not implemented on this platform - Naar de prullenbak verplaatsen is niet geïmplementeerd op dit platform + + Output debug-level messages in the log. + Schrijf debug-level berichten naar het logboek. - - Error removing '%1': %2 - Fout bij verwijderen '%1': %2 + + Enable debug mode. + Zet debug modus aan. - - Could not remove folder '%1' - Kon map '%1' niet verwijderen + + --logfile and --logdir are mutually exclusive + --logfile en --logdir kunnen niet tegelijkertijd gebruikt worden - FolderWizardSourcePage + CommonStrings - - Form - Formulier + + file manager + bestandsbeheer - - Pick a local folder on your computer to sync - Kies een map op uw computer om te synchroniseren + + Show in %1 + Toon in %1 - - &Choose... - &Kies... + + Show in web browser + Toon in browser - - - FolderWizardTargetPage - - Form - Formulier + + Copy + Kopiëren + + + + %n Filter(s) + %n Filter%n Filters - - Select a remote destination folder - Kies een externe doelmap + + folder + map - - Create Folder - Map aanmaken + + space + space - - Refresh - Vernieuwen + + folders + mappen - - Folders - Mappen + + spaces + spaces - - TextLabel - Tekstlabel + + Folder + Map - - - NotificationWidget - - Form - Formulier + + Space + Space + + + FetchUserInfoJobFactory - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet + + Failed to retrieve user info + Kon gebruiker informatie niet ophalen + + + FileSystem - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + + + + Can't rename %1, the file is currently in use + Kan %1 niet verplaatsen, het bestand is in gebruik - - TextLabel - Tekstlabel + + Could not remove folder + Map kan niet verwijderd worden - OCC::AboutDialog + FolderItem - - About - Over + + + Offline + Offline - - TextLabel - Tekstlabel + + + Unavailable: the space is no longer available on the server + De Space is niet langer beschikbaar op de server - - Versions - Versies + + + Sync paused + Synchronisatie gepauzeerd. - - About %1 - Over %1 + + Synced + Gesynchroniseerd - - - OCC::AbstractNetworkJob - - Connection timed out - Time-out verbinding + + Sync failed + Sync mislukt - - Unknown error: network reply was deleted - Onbekende fout: antwoord van het netwerk is verwijderd + + Sync pending + Gaat worden gesynchroniseerd - - Server replied "%1 %2" to "%3 %4" - Server antwoordde "%1 %2" naar "%3 %4" + + Syncing %1 of %2 (%3 % + Synchroniseren %1 van %2 (%3 % + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, Synchronisatiestatus: %2 + first argument is the displayed name of the folder. - OCC::Account + FolderManagementUtils - - %1@%2 - %1@%2 + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + Het pad '%1' is te lang. Schakel ondersteuning voor lange paden aan in de Windows instellingen, of kies een andere folder. + + + + Local folder %1 does not exist. + Lokale map %1 bestaat niet. + + + + %1 is not a folder. + + + + + %1 is not readable. + %1 is niet leesbaar. + + + + %1 is not writable. + %1 is niet schrijfbaar. - OCC::AccountSettings + FolderWizard - - Form - Formulier + + <b>Error:</b> %1 + <b>Foutmelding:</b> %1 - - Re-open Browser - Heropen Browser + + <b>Warning:</b> %1 + <b>Waarschuwing:</b> %1 - - ... - ... + + <b>Error:</b><ul>%1</ul> + <b>Foutmelding:</b><ul>%1</ul> - - Storage space: ... - Opslagruimte: ... + + <b>Warning:</b><ul>%1</ul> + <b>Waarschuwing:</b><ul>%1</ul> + + + FolderWizardSourcePage - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Niet geselecteerde mappen worden <b>verwijderd</b> van uw lokale bestandssysteem en worden niet meer gesynchroniseerd met deze computer + + Form + Formulier - - Synchronize all - Alles synchroniseren + + Pick a local folder on your computer to sync + Kies een map op uw computer om te synchroniseren - - Synchronize none - Niets synchroniseren + + Browse… + Bladeren... + + + FolderWizardTargetPage - - Apply manual changes - Toepassen handmatige wijzigingen + + Form + Formulier - - Apply - Toepassen + + Select a remote destination folder + Kies een externe doelmap - - - - - Cancel - Annuleren + + Create Folder + Map aanmaken - - Connected with <server> as <user> - Verbonden met <server> als <user> + + Refresh + Vernieuwen - - No account configured. - Geen account ingesteld. + + Folders + Mappen + + + OCC::AboutView - - Remove - Verwijder + + About + Over - - Account - Account + + Versions + Versies + + + OCC::AbstractNetworkJob - - Choose what to sync - Selectieve synchronisatie + + Connection timed out + Time-out verbinding - - Force sync now - Forceer sync nu + + Unknown error: network reply was deleted + Onbekende fout: antwoord van het netwerk is verwijderd - - Restart sync - Herstarten sync + + Server replied "%1 %2" to "%3 %4" + Server antwoordde "%1 %2" naar "%3 %4" + + + OCC::Account - - Remove folder sync connection - Verwijder map-synchronisatie verbinding + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - Folder creation failed - Maken map mislukt + + Choose what to sync + Selectieve synchronisatie - - <p>Could not create local folder <i>%1</i>. - <p>Kon lokale map <i>%1</i> niet maken. + + + Sync now + Synchroniseer nu - - Confirm Folder Sync Connection Removal - Bevestig verwijderen map-synchronisatie verbinding + + Manage subfolder sync + Beheer submap synchronisatie - - Remove Folder Sync Connection - Verwijder map-synchronisatie verbinding + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + Verwijder %1 sync - + Disable virtual file support? Ondersteuning voor virtuele bestanden uitschakelen? - + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. The only advantage of disabling virtual file support is that the selective sync feature will become available again. @@ -367,572 +431,549 @@ Het enige voordeel van het uitschakelen van virtuele bestanden ondersteuning is Deze actie annuleert alle huidig lopende synchronisatie. - + Disable support Ondersteuning uitschakelen - - Sync Running - Bezig met synchroniseren + + Restart sync + Herstarten sync - - The syncing operation is running.<br/>Do you want to terminate it? - Bezig met synchroniseren.<br/>Wilt u stoppen met synchroniseren? + + Resume sync + Hervatten sync + + + + Pause sync + Pauzeren sync - - %1 in use - %1 in gebruik + + Deactivate virtual files + Schakel virtuele bestanden ondersteuning uit - - %1 as <i>%2</i> - %1 als <i>%2</i> + + Activate virtual files + Schakel virtuele bestanden ondersteuning aan - - Connected to %1. - Verbonden met %1. + + Internet connection is metered + Internetverbinding heeft een datalimiet - - Server %1 is temporarily unavailable. - Server %1 is tijdelijk niet beschikbaar + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + Synchronisatie is gepauzeerd omdat de internetverbinding een verbinding met een datalimiet is.<p>Wilt u nu echt een synchronisatie forceren? - - Server %1 is currently in maintenance mode. - Server %1 is momenteel in onderhoudsmodus. + + Sync Running + Bezig met synchroniseren - - Signed out from %1. - Uitgelogd van %1. + + The sync operation is running.<br/>Do you want to stop it? + Bezig met synchroniseren.<br/>Wilt u stoppen met synchroniseren? - - Connecting to %1... - Verbinden met %1... + + Sync paused by user + Synchronisatie gepauzeerd door gebruiker - - No connection to %1 at %2. - Geen verbinding met %1 op %2. + + Confirm Folder Sync Connection Removal + Bevestig verwijderen map-synchronisatie verbinding - - Log in - Meld u aan + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Weet u zeker dat u de synchronisatie van map <i>%1</i> wilt stoppen?</p><p><b>Opmerking:</b> Dit zal <b>geen</b> bestanden verwijderen.</p> - - There are folders that were not synchronized because they are too big: - Er zijn mappen die niet gesynchroniseerd werden, omdat ze te groot zijn: + + Remove Folder Sync Connection + Verwijder map-synchronisatie verbinding - - There are folders that were not synchronized because they are external storages: - Er zijn mappen die niet gesynchroniseerd werden, omdat ze op externe opslag staan: + + + Cancel + Annuleer + + + OCC::AccountFoldersView - - There are folders that were not synchronized because they are too big or external storages: - Er zijn mappen die niet gesynchroniseerd werden, omdat ze te groot zijn of op externe opslag staan: + + Sync options menu + Menu met synchronisatie opties - - Confirm Account Removal - Bevestig verwijderen account + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 synchronisatie - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Wilt u echt de verbinding met het account <i>%1</i> verbreken?</p><p><b>Let op:</b> Hierdoor verwijdert u <b>geen</b> bestanden.</p> + + Manage your synced %1. + Beheer uw gesynchroniseerde %1. - - Remove connection - Verwijderen verbinding + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Nieuwe %1 synchonisatie toevoegen - - Open folder - Map openen + + %1 list view + %1 lijst - - - Log out - Afmelden + + Navigate the %1 list using the up and down arrows + Navigeer de %1 lijst met behulp van de omhoog/omlaag toetsen - - Open local folder - Open lokale map + + %1 out of %2 %3 are synchronized + %1 van %2 %3 worden gesynchroniseerd + + + OCC::AccountModalWidget - - Open folder in browser - Open map in browser + + + Form + Formulier + + + OCC::AccountPlaceholderWidget - - Resume sync - Hervatten sync + + %1 logo + %1 logo - - Pause sync - Pauzeren sync + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + Je hebt nog geen gekoppelde accounts. Maak een account aan door de optie 'Account toevoegen...' te selecteren in het menu van de knop 'Meer'. + + + + Additional logo defined by the organization + Extra logo gedefiniëerd door de organisatie + + + + OCC::AccountView + + + Form + Formulier - - Availability - Beschikbaarheid + + Manage Account + Beheer account - - Disable virtual file support... - Schakel virtuele bestanden ondersteuning uit... + + Preparing the account + Het account wordt voorbereid - - Enable virtual file support%1... - Inschakelen virtueel bestandsoverdracht%1... + + Sync connections + Sync verbindingen - - (experimental) - (experimenteel) + + Account options menu + Menu met account opties + + + OCC::AccountViewController - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Weet u zeker dat u de synchronisatie van map <i>%1</i> wilt stoppen?</p><p><b>Opmerking:</b> Dit zal <b>geen</b> bestanden verwijderen.</p> + + + Log in + Aanmelden - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) van %2 in gebruik. Sommige mappen, inclusief netwerkmappen en gedeelde mappen, kunnen andere limieten hebben. + + Reconnect + Opnieuw verbinden - - %1 of %2 in use - %1 van %2 in gebruik + + Remove + Verwijder - - Currently there is no storage usage information available. - Er is nu geen informatie over het gebruik van de opslagruimte beschikbaar. + + Log out + Afmelden - - The server version %1 is unsupported! Proceed at your own risk. - De server versie %1 is niet ondersteund! Ga door op eigen risico. + + Confirm Account Removal + Bevestig verwijderen account - - Obtaining authorization from the browser. - Verkrijgen van autorisatie van de browser + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Wilt u echt de verbinding met het account <i>%1</i> verbreken?</p><p><b>Let op:</b> Hierdoor verwijdert u <b>geen</b> bestanden.</p> - - Server configuration error: %1 at %2. - Serverconfiguratiefout: %1 op %2. + + Remove connection + Verwijderen verbinding - - No %1 connection configured. - Geen %1 connectie geconfigureerd. + + Cancel + Annuleer - - - OCC::AccountState - - Url update requested for %1 - + + The server version %1 is unsupported! Proceed at your own risk. + De server versie %1 is niet ondersteund! Ga door op eigen risico. - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - + + Connected + Verbonden - - Change url permanently to %1 + + Server is temporarily unavailable. - - Reject + + Server is currently in maintenance mode. - + Signed out Afgemeld - - Disconnected - Niet verbonden + + Updating credentials… + Inloggegevens worden bijgewerkt… - - Connected - Verbonden - - - - Service unavailable - Dienst niet beschikbaar + + Captive portal prevents connections to the server. + Een captive portal voorkomt verbindingen met de server. - - Maintenance mode - Onderhoudsmodus + + Sync is paused due to metered internet connection. + - - Network error - Netwerkfout + + Connecting… + Verbinden... - - Configuration error - Configuratiefout + + Server configuration error + Server configuratiefout - - Asking Credentials - Verzoek om inloggegevens + + Disconnected + Niet verbonden - - Unknown account state - Onbekende account-status + + Invalid connection status + - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 op %3 - + OCC::AccountsGuiController - - Activity - Activiteit + + + New account failure + Nieuw account fout - - Account - Account + + The information required to create a new account is incomplete. Please run the wizard again. + De informatie die nodig is om een ​​nieuw account aan te maken, is onvolledig. Voer de wizard opnieuw uit. - - Time - Tijd + + Accounts + Accounts - - Local path - + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + Het account kon niet worden aangemaakt vanwege een fout: %1 Controleer de beschikbaarheid van de server en voer de wizard vervolgens opnieuw uit OCC::ActivitySettings - - - Server Activity - Serveractiviteit - - - - Sync Protocol - Synchronisatiegeschiedenis + + Local Activity + Lokale activiteiten - + Not Synced Niet gesynchroniseerd - + Not Synced (%1) %1 is the number of not synced files. Niet gesynchroniseerd (%1) - OCC::ActivityWidget - - - Form - Formulier - - - - - - TextLabel - Tekstlabel - - - - Filter - Filter - - - - Server Activities - Serveractiviteiten - - - - Notifications - Meldingen - - - - %n notifications(s) for %1. - %n notificatie voor %1.%n notificaties voor %1. - - - - %n notifications(s) for %1 and %2. - %n notificatie voor %1 en %2.%n notificaties voor %1 en %2. - - - - New notifications for %1, %2 and other accounts. - Nieuwe notificaties voor %1, %2 en andere accounts. - - - - Open the activity view for details. - Open het activiteitsoverzicht voor details. - - - - Copy to clipboard - Kopiëren naar klembord - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>Account %1 heeft geen activiteiten ingeschakeld. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Doorgaan zal betekenen dat <b>deze instellingen worden gewist</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Doorgaan zal betekenen dat <b>deze instellingen worden genegeerd</b>. - + OCC::AdvancedSettingsPageController - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Sommige instellingen zijn geconfigureerd in nieuwere versies van deze applicatie en gebruiken features die niet beschikbaar zijn in deze versie.<br><br>%1<br><br>Het huidige configuratiebestand is reeds geback-upt naar <i>%2</i>. + + Advanced settings + Geavanceerde instellingen - - Quit - Afsluiten - - - - Continue - Verder + + Sync and download options + Synchronisatie en download opties - - Error accessing the configuration file - Fout bij benaderen configuratiebestand + + Download location + Download locatie - - There was an error while accessing the configuration file at %1. - Er trad een fout op bij het benaderen configuratiebestand op %1 + + Sync and download files as you use them (saves hard drive space) + Synchroniseer en download bestanden wanneer ze worden gebruikt (bespaart disk ruimte) - - Quit %1 - %1 afsluiten + + Automatically sync and download folders and files + Synchroniseer en download folders en files automatisch - - %1 version %2 -File synchronization desktop utility. - + + Sync and download folders manually + Synchroniseer en download folders handmatig - - For more information, see http://www.owncloud.org - + + Download location on the local machine + Download locatie op de lokale computer - - Show the settings dialog while starting. - + + Browse… + Bladeren... - - Quit the running instance. - + + Browse for a download location + Kies locale download folder - - Write log to file (use - to write to stdout). - + + Invalid local download directory %1: %2 + Ongeldige lokale download folder %1: %2 - - Write each sync log output in a new file in folder. - + + your user directory may not be chosen as the sync root. + Uw gebruikers folder kan niet gebruikt worden als basis voor synchronisatie - - Remove logs older than <hours> hours (to be used with --logdir). - + + path is not located on a supported Apple File System. + Pad bevind zich niet op een ondersteund Apple bestandssysteem - - Flush the log file after every write. - + + path must be absolute. + pad moet absoluut zijn. - - Output debug-level messages in the log. - + + selected path does not support using virtual file system. %1 + Het geselecteerde pad ondersteund geen virtuele bestanden. %1 - - - Override UI language. - + + Select sync root + Selecteer de synchronisatieroot. + + + OCC::AppImageUpdateAvailableWidget - - Use the given configuration folder. - + + Installed version: %1 + Geïnstalleerde versie: %1 - - Enable debug mode. - + + Available update: %1 + Beschikbare update: %1 - - Virtual file system file to be opened (optional). - + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Een update is beschikbaar voor deze AppImage van %1. Wilt u deze update installeren? + +De update wordt uitgevoerd op de achtergrond en zal het bestaande AppImage bestand overschrijven. U zult de applicatie moeten herstarten om de update te voltooien. + + + OCC::Application - - [<vfs file>] - + + Quit + Afsluiten + + + OCC::AuthSuccessPageController - - Invalid path passed to --confdir - + + Application Name Logo %1 + Logo van de applicatie %1 - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - + + You're all set! Open %1 and get started. + Alles is klaar! Open %1 om te starten. - - Available translations: %1 - + + Additional logo defined by the organization + Extra logo gedefiniëerd door de organisatie - OCC::AuthenticationDialog - - - Authentication Required - Authenticatie vereist - + OCC::ButtonDelegate - - Enter username and password for '%1' at %2. - Geef gebruikersnaam en wachtwoord op voor '%1' bij %2. + + %1 options button + %1 opties knop - - &User: - &Gebruiker: - - - - &Password: - &Wachtwoord: + + Menu button with %1 options. Use the space key to show the menu + Menu knop met %1 opties. Gebruik the spatiebalk om het menu te openen OCC::ConnectionValidator - + No ownCloud account configured Geen ownCloud-account geconfigureerd - - The configured server for this client is too old - De voor dit programma ingestelde server is te oud - - - + Please update to the latest server and restart the client. Werk de server bij naar de nieuwste versie en herstart het programma. - + Authentication error: Either username or password are wrong. Authenticatiefout: Gebruikersnaam of wachtwoord onjuist. - + timeout time-out - + + No account configured + Geen account geconfigureerd + + + The provided credentials are not correct De verstrekte inloggegevens zijn niet juist + + + The configured server for this client is too old. + De voor dit programma ingestelde server is te oud + OCC::CredentialJob - + Failed to parse credentials %1 - + Kon inloggegevens niet verwerken %1 + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + Server ondersteunt geen OAuth + + + + Server did not ask for authorization + Server vroeg niet naar autorisatie + + + + Failed to determine authentication type: %1 + Kon authenticatietype niet bepalen: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + Ongeldig antwoord ontvangen van de server. OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + Het account was verwijderd voordat we een propfind taak konden starten + + + + Server error: PROPFIND reply is not XML formatted! Serverfout: PROPFIND reactie is niet XML-geformatteerd! @@ -940,27 +981,22 @@ File synchronization desktop utility. OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 Fout bij het lezen van de map %1 - + Directory not accessible on client, permission denied Map niet toegankelijk met de applicatie, toegang gewijgerd - + Directory not found: %1 Map niet gevonden: %1 - - Filename encoding is not valid - Bestandsnaamcodering is niet geldig - - - + Error while reading directory %1 Fout bij het lezen van de map %1 @@ -968,148 +1004,138 @@ File synchronization desktop utility. OCC::ExpandingHeaderView - + Reset column sizes - + Kolom grootes terugzetten - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - Lokale map %1 bestaat niet. + + server sent invalid content type: %1. + server heeft een inhoud met een ongeldig type gestuurd: %1 - - %1 should be a folder but is not. - %1 zou een map moeten zijn, maar is dat niet. + + could not parse Capabilities from server. + De Capabilities van de server konden niet worden geanalyseerd. - - %1 is not readable. - %1 is niet leesbaar. + + reply sent for different subject (server): %1 + antwoord verzonden voor ander onderwerp (server): %1 +  + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 is verwijderd. - + %1 has been updated. %1 names a file. %1 is bijgewerkt. - + %1 has been renamed to %2. %1 and %2 name files. %1 is hernoemd naar %2. - + %1 has been moved to %2. %1 is verplaatst naar %2. - + %1 and %n other file(s) have been removed. - %1 en %n ander bestand(en) zijn verwijderd.%1 en %n andere bestand(en) zijn verwijderd. + %1 en één ander bestand zijn verwijderd.%1 en %n andere bestanden zijn verwijderd. - - %1 is not writable. - + + The folder has been disabled or removed from the server + De folder is uitgeschakeld of verwijderd van de server - + %1 and %n other file(s) have been added. - %1 en %n ander bestand is toegevoegd.%1 en %n ander(e) bestand(en) was / waren toegevoegd. + %1 en één ander bestand is toegevoegd.%1 en %n andere bestanden zijn toegevoegd. - + %1 has been added. %1 names a file. %1 is toegevoegd. - + %1 and %n other file(s) have been updated. - %1 en %n ander bestand(en) zijn bijgewerkt.%1 en %n andere bestand(en) zijn bijgewerkt. + %1 en één ander bestand zijn bijgewerkt.%1 en %2 andere bestanden zijn bijgewerkt. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 is hernoemd naar %2 en %n ander bestand(en) is hernoemd.%1 is hernoemd naar %2 en %n andere bestand(en) zijn hernoemd. + %1 is hernoemd naar %2 en één ander bestand zijn ook hernoemd.%1 is hernoemd naar %2 en %n andere bestanden zijn ook hernoemd. - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 is verplaatst naar %2 en %n ander bestand(en) is verplaatst.%1 is verplaatst naar %2 en %n andere bestand(en) zijn verplaatst. + %1 is verplaatst naar %2 en één ander bestand zijn ook verplaatst.%1 is verplaatst naar %2 en %n andere bestanden zijn ook verplaatst. - - %1 has and %n other file(s) have sync conflicts. - %1 en %n ander bestand(en) hebben een sync conflict.%1 en %n andere bestand(en) hebben sync conflicten. + + %1 and %n other file(s) have sync conflicts. + %1 en één ander bestand hebben synchronisatie conflicten.%1 en %n andere bestanden hebben synchronisatie conflicten. - + %1 has a sync conflict. Please check the conflict file! %1 heeft een sync conflict. Controleer het conflict bestand! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 en %n ander bestand(en) konden niet worden gesynchroniseerd wegens fouten. Bekijk het log voor details.%1 en %n andere bestand(en) konden niet worden gesynchroniseerd wegens fouten. Bekijk het log voor details. + %1 en één ander bestand konden niet worden gesynchroniseerd door fouten. Bekijk het log voor details.%1 en %n andere bestanden konden niet worden gesynchroniseerd door fouten. Bekijk het log voor details. - + %1 could not be synced due to an error. See the log for details. %1 kon niet worden gesynchroniseerd door een fout. Bekijk het log voor details. - + Sync Activity Synchronisatie-activiteit - - Could not read system exclude file - Kon het systeem-uitsluitingsbestand niet lezen - - - - A new folder larger than %1 MB has been added: %2. - - Er is een nieuwe map groter dan %1 MB toegevoegd: %2. - + + Switching VFS mode on folder '%1' + VFS-modus inschakelen in map '%1' - - A folder from an external storage has been added. - - Er is een map op externe opslag toegevoegd. - - - - - Please go in the settings to select it if you wish to download it. - Ga naar de instellingen om het te selecteren als u deze wilt downloaden. + + Could not read system exclude file + Kon het systeem-uitsluitingsbestand niet lezen - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. De map %1 werd aangemaakt maar eerder ook uitgesloten van synchronisatie. Data erin zullen niet worden gesynchroniseerd. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. Het bestand %1 is aangemaakt maar was eerder uitgesloten van synchronisatie. Dit bestand zal niet gesynchroniseerd worden. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1121,3593 +1147,2400 @@ Dit betekent dat de synchronisatie-applicatie lokale wijzigingen mogelijk niet d %1 + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Alle bestanden in de syncmap '%1' werden verwijderd van de server. -Deze verwijderingen worden gesynchroniseerd naar uw lokale syncmap, waardoor deze bestanden niet meer beschikbaar zijn, tenzij u het recht hebt om ze te herstellen. -Als u de bestanden wilt behouden, worden ze opnieuw gesynchroniseerd met de server als u die autorisatie hebt. -Als u de bestanden wilt verwijderen, worden ze niet beschikbaar, tenzij u de eigenaar bent. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Alle bestanden in de lokale syncmap '%1' werden verwijderd van de server. Deze verwijderingen worden gesynchroniseerd naar uw server, Weet u zeker dat ik deze acties wilt synchroniseren met de server? -Als dit een ongelukje was en u de bestanden wilt behouden, worden ze opnieuw gesynchroniseerd met de server. - - - - Remove All Files? - Verwijder alle bestanden? - - - - Remove all files - Verwijder alle bestanden + + There are unresolved conflicts. + Er zijn onopgeloste conflicten. - - Keep files - Bewaar bestanden + + Sync error: %1 + Synchronisatiefout: %1 OCC::FolderMan - + Could not reset folder state Kan de beginstaat van de map niet terugzetten - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Een oud synchronisatieverslag '%1' is gevonden maar kan niet worden verwijderd. Zorg ervoor dat geen applicatie dit bestand gebruikt. - - (backup) - (backup) - - - - (backup %1) - (backup %1) + + Unsupported filesystem + Niet ondersteund bestandssysteem - - Undefined State. - Ongedefiniëerde staat + + Account disconnected or paused + Account losgekoppeld of gepauzeerd - - Waiting to start syncing. - In afwachting van synchronisatie. + + User triggered force sync + Door de gebruiker geactiveerde geforceerde synchronisatie - - Preparing for sync. - Synchronisatie wordt voorbereid + + Folder is about to be removed + Map wordt zometeen verwijderd - - Sync is running. - Bezig met synchroniseren. + + (backup) + (backup) - - Sync was successful, unresolved conflicts. - Synchronisatie was succesvol, onopgeloste conflicten. + + (backup %1) + (backup %1) - - Last Sync was successful. - Laatste synchronisatie was geslaagd. + + The selected path is not a folder. + Het geselecteerde pad is geen folder. - - Setup Error. - Installatiefout. + + The folder %1 is used in a folder sync connection. + Map %1 wordt al gebruikt voor synchronisatie. - - User Abort. - Afgebroken door gebruiker. + + Folder '%1' is already in use by application %2. + Folder '%1' is al in gebruik door toepassing %2. - - Sync is paused. - Synchronisatie gepauzeerd. + + There is already a sync from the server to this local folder. + Er wordt vanaf de server al naar deze lokale map gesynchroniseerd. - - %1 (Sync is paused) - %1 (Synchronisatie onderbroken) + + The local folder %1 already contains a folder used in a folder sync connection. + Lokale map %1 bevat al een map die wordt gebruikt voor een map-synchronisatie verbinding. - - No valid folder selected! - Geen geldige map geselecteerd! + + The local folder %1 is already contained in a folder used in a folder sync connection. + Lokale map %1 zit al in een map die wordt gebruikt voor een map-synchronisatie verbinding. - - The folder %1 is used in a folder sync connection! - + + No valid folder selected. + Geen geldige map geselecteerd. - - The selected path does not exist! - Het geselecteerde pad bestaat niet! + + The selected path does not exist. + Het geselecteerde pad bestaat niet. - - The selected path is not a folder! - Het geselecteerde pad is geen map! + + You have no permission to write to the selected folder. + U heeft geen permissie om te schrijven naar de geselecteerde map. - - You have no permission to write to the selected folder! - U heeft geen permissie om te schrijven naar de geselecteerde map! + + Folder '%1' is already in use by another account. + Folder '%1' is al in gebruik door een ander account. - - There is already a sync from the server to this local folder. Please pick another local folder! - Er wordt vanaf de server al naar deze lokale map gesynchroniseerd. Kies een andere lokale map! + + On macOS, only the Apple File System is supported. + Op macOS wordt alleen het Apple File System ondersteund. + + + OCC::FolderModelController - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Lokale map %1 bevat al een map die wordt gebruikt voor een map-synchronisatie verbinding. Kies een andere! + + %1 options + %1 opties - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Lokale map %1 zit al in een map die wordt gebruikt voor een map-synchronisatie verbinding. Kies een andere! + + Menu button with %1 options. Use the space key to pop the %2 options menu + Menu knop met %1 opties. Gebruik the spatiebalk om het %2 menu te openen + + + OCC::FolderWatcher - - %1 Please pick another one! - + + The watcher did not receive a test notification. + De wacht heeft geen testnotificatie ontvangen. + + + OCC::FolderWatcherPrivate - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Dit probleem doet zich normaal gesproken voor wanneer de inotify wachten zijn uitgeput. Zie de veelgestelde vragen voor meer informatie. - OCC::FolderStatusDelegate + OCC::FolderWizard + + + Virtual files are not available for the selected folder + Virtuele bestanden zijn niet beschikbaar voor de geselecteerde map + - + Add Folder Sync Connection - Voeg mapsynchronisatieverbinding toe + Voeg map-synchronisatie verbinding toe - - File - Bestand + + Add Sync Connection + Sync verbinding toevoegen - OCC::FolderStatusModel - - - You need to be connected to add a folder - U moet verbonden zijn om een map toe te voegen - + OCC::FolderWizardLocalPath - - Click this button to add a folder to synchronize. - Klik op deze knop om een te synchroniseren map toe te voegen. + + Click to select a local folder to sync. + Klikken om een lokale map te selecteren voor synchronisatie - - - %1 (%2) - Example text: "File.txt (23KB)" - -%1 (%2) + + Enter the path to the local folder. + Geef het pad op naar de lokale map. - - Error while loading the list of folders from the server. - Fout bij ophalen mappenlijst van de server. + + Select the local folder + Selecteer de lokale folder + + + OCC::FolderWizardSelectiveSync - - Virtual file support is enabled. - Ondersteuning voor virtuele bestanden zijn ingeschakeld. + + Use virtual files instead of downloading content immediately + Gebruik virtuele bestanden in plaats van de inhoud meteen te downloaden + + + OCC::GETFileJob - - Signed out - Afgemeld + + Connection Timeout + Verbindingstime-out - - Fetching folder list from server... - Mappenlijst ophalen van de server... + + No E-Tag received from server, check Proxy/Gateway + Geen E-Tag ontvangen van de server, controleer Proxy/Gateway - - There are unresolved conflicts. Click for details. - Er zijn onopgeloste conflicten. Klik voor details. + + We received a different E-Tag for resuming. Retrying next time. + We ontvingen een afwijkende E-Tag om door te gaan. We proberen het later opnieuw. - - Checking for changes in remote '%1' - Aan het controleren op wijzigingen in afstandelijke locatie '%1' + + We received an unexpected download Content-Length. + We kregen een onverwachte download Content-Lengte. - - Checking for changes in local '%1' - Aan het controleren op veranderingen in lokale '%1' + + Server returned wrong content-range + Server retourneerde verkeerde content-bandbreedte + + + OCC::GraphApi::Space - - Reconciling changes - Wijzigingen doorvoeren + + Personal + Persoonlijk - - , '%1' - Build a list of file names - , '%1' + + Shares + Gedeeld + + + OCC::IgnoreListEditor - - '%1' - Argument is a file name - '%1' + + Ignored Files Editor + Genegeerde bestanden-editor - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synchroniseren %1 + + Files Ignored by Patterns + Bestanden volgens patroon genegeerd - - - , - , + + Add + Toevoegen - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - download %1/s + + Pattern + Patroon - - ↓ %1/s - ↓ %1/s + + Allow Deletion + Verwijderen toestaan - - ↑ %1/s - ↑ %1/s + + Remove + Verwijder - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - upload %1/s + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Bestanden of mappen die overeenkomen met een patroon zullen niet gesynchroniseerd worden. Wijzigingen worden de volgende keer dat mappen worden gesynchroniseerd toegepast. + +Items waar verwijderen is toegestaan zullen gewist worden als ze voorkomen dat een map wordt verwijderd. Dit is nuttig voor metadata. - - Local folder: %1 - + + Could not open file + Kon het bestand niet openen - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 van %4) + + Cannot write changes to '%1'. + Er kunnen geen wijzigingen worden geschreven naar %1 - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 over, %1 van %2, bestand %3 van %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 van %2, bestand %3 van %4 - - - - file %1 of %2 - bestand %1 van %2 - - - - Waiting... - Aan het wachten... - - - - Waiting for %n other folder(s)... - Wacht op %n andere map...Wacht op %n andere mappen... - - - - Preparing to sync... - Voorbereiden op sync... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - De wacht heeft geen testnotificatie ontvangen. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Dit probleem doet zich normaal gesproken voor wanneer de inotify wachten zijn uitgeput. Zie de veelgestelde vragen voor meer informatie. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Voeg map-synchronisatie verbinding toe - - - - Add Sync Connection - Sync verbinding toevoegen - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Klikken om een lokale map te selecteren voor synchronisatie - - - - Enter the path to the local folder. - Geef het pad op naar de lokale map. - - - - Select the source folder - Selecteer de bronmap - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Externe map aanmaken - - - - Enter the name of the new folder to be created below '%1': - Voer de naam van de hieronder te maken nieuwe map in '%1': - - - - Folder was successfully created on %1. - Map is succesvol aangemaakt op %1. - - - - Authentication failed accessing %1 - Authenticatie mislukt bij benaderen %1 - - - - Failed to create the folder on %1. Please check manually. - Aanmaken van de map op %1 mislukt. Controleer handmatig. - - - - Failed to list a folder. Error: %1 - Tonen mappenlijst mislukt. Fout: %1 - - - - Choose this to sync the entire account - Kies dit om uw volledige account te synchroniseren - - - - This folder is already being synced. - Deze map is al gesynchroniseerd. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - U synchroniseert <i>%1</i> al, dat is de bovenliggende map van <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Je bent <i>%1</i> al aan het synchroniseren, wat een submap is van <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Gebruik virtuele bestanden in plaats van het meteen downloaden van de inhoud%1 - - - - (experimental) - (experimenteel) - - - - Virtual files are not available for the selected folder - Virtuele bestanden zijn niet beschikbaar voor de geselecteerde map. - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Waarschuwing:</b> %1 - - - - <b>Warning:</b> - <b>Waarschuwing:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Geen E-Tag ontvangen van de server, controleer Proxy/Gateway - - - - We received a different E-Tag for resuming. Retrying next time. - We ontvingen een afwijkende E-Tag om door te gaan. We proberen het later opnieuw. - - - - We received an unexpected download Content-Length. - We kregen een onverwachte download Content-Lengte. - - - - Server returned wrong content-range - Server retourneerde verkeerde content-bandbreedte - - - - OCC::GETJob - - - Connection Timeout - Verbindingstime-out - - - - OCC::GeneralSettings - - - Form - Formulier - - - - General Settings - Algemene instellingen - - - - Language - Taal - - - - Sync hidden files - Synchroniseer verborgen bestanden - - - - Log Settings - Loginstellingen - - - - Advanced - Approval of folder sync (Non virtual file mode only) - Geavanceerd - Approval of folder sync (Alleen niet-virtuele bestandsmodus) - - - - Network - Netwerk - - - - Updates - Updates - - - - stable - stabiel - - - - beta - beta - - - - About - Over - - - - Advanced - Geavanceerd - - - - Use &Monochrome Icons in the system tray - Gebruik &Monochrome Iconen in het systeemvak - - - - Ask for confirmation before synchronizing folders larger than - Vraag bevestiging voordat mappen worden gedownload groter dan - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Vraag bevestiging voor synchronisatie van mappen op externe opslag - - - - &Launch on System Startup - &Starten bij systeemstart - - - - Show &Desktop Notifications - Tonen &desktopberichten - - - - &Update Channel - &Update Kanaal - - - - Edit &Ignored Files - &Genegeerde bestanden bewerken - - - - Show sync folders in &Explorer's Navigation Pane - Toon synchronisatiemappen in &Explorer's Navigatiepaneel - - - - S&how crash reporter - T&onen crash reporter - - - - &Restart && Update - &Herstarten en &Bijwerken - - - - You cannot disable autostart because system-wide autostart is enabled. - Je kunt autostart niet uitschakelen omdat autostart systeembreed is ingeschakeld. - - - - Change update channel? - Updatekanaal veranderen? - - - - Warning - Waarschuwing - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Het updatekanaal bepaalt welke client-updates voor installatie worden aangeboden. Het "stable" kanaal bevat alleen upgrades die als betrouwbaar worden beschouwd, terwijl de versies in het "beta" kanaal nieuwere mogelijkheden en bugfixes kunnen bevatten, maar nog niet grondig zijn getest. - -Let op dat dit alleen selecteert uit welke pool upgrades worden gehaald, en dat er geen downgrades zijn: dus teruggaan van het beta kanaal naar het stabiele kanaal kan meestal niet meteen en betekent dat je moet wachten op een stabiele versie die nieuwer is dan de huidig geïnstalleerde beta versie. - - - - Change update channel - Verander updatekanaal - - - - Cancel - Annuleren - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Voer %1 wachtwoord in: - -Gebruiker: %2 -Account: %3 - - - - - Reading from keychain failed with error: '%1' - Het lezen van de sleutelketen is mislukt met fout: '%1' - - - - Enter Password - Vul het wachtwoord in - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Klik hier</a> om een nieuw app wachtwoord via de web interface op te vragen. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Genegeerde bestanden-editor - - - - Files Ignored by Patterns - Bestanden volgens patroon genegeerd - - - - Add - Toevoegen - - - - Pattern - Patroon - - - - Allow Deletion - Verwijderen toestaan - - - - Remove - Verwijder - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Bestanden of mappen die overeenkomen met een patroon zullen niet gesynchroniseerd worden. Wijzigingen worden de volgende keer dat mappen worden gesynchroniseerd toegepast. - -Items waar verwijderen is toegestaan zullen gewist worden als ze voorkomen dat een map wordt verwijderd. Dit is nuttig voor metadata. - - - - Could not open file - Kon het bestand niet openen - - - - Cannot write changes to '%1'. - Er kunnen geen wijzigingen worden geschreven naar %1 - - - - Add Ignore Pattern - Negeerpatroon toevoegen - - - - Add a new ignore pattern: - Voeg nieuw negeerpatroon toe: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Deze entry is door het systeem geleverd op '%1' en kan niet worden aangepast in deze sessie. - - - - OCC::IssuesWidget - - - Form - Formulier - - - - List of issues - Lijst met problemen - - - - Filter - Filter - - - - There were too many issues. Not all will be visible here. - Er zijn teveel problemen. Niet alles zal worden vertoond. - - - - There were conflicts. Check the documentation on how to resolve them. - Er waren conflicten. Raadpleeg de documentatie om ze te verhelpen. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Er waren conflicten. <a href="%1">Raadpleeg de documentatie om ze te verhelpen.</a> - - - - Account - Account - - - - OCC::LibCloudProvidersPrivate - - - Settings - Instellingen - - - - OCC::LogBrowser - - - Log Output - Log output - - - - Enable logging to temporary folder - Loggen naar tijdelijke map inschakelen - - - - Delete logs older than %1 hours - Wis logs ouder dan %1 uur - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">De logs bevatten gevoelige informatie die je niet openbaar mag maken</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - De client kan debug logs wegschrijven naar een tijdelijke map. Deze logs zijn erg nuttig voor het diagnosticeren van problemen. - -Omdat logbestanden groot kunnen worden, zal de client voor elke synchronisatie een nieuwe starten en oudere logbestanden comprimeren. Het wordt ook aangeraden om het verwijderen van logbestanden na een paar uur aan te zetten om te voorkomen dat er te veel schijfruimte wordt gebruikt. - - - - If enabled, logs will be written to: - Indien ingeschakeld, zullen logs worden geschreven naar: - - - - C:/log - C:/log - - - - Log Http traffic - Log HTTP-verkeer - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Deze instellingen blijven ook bestaan na het herstarten van de applicatie. -Let op dat het gebruik van command line logopties deze instelling zullen overschrijven. - - - - Open folder - Map openen - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Nieuwe versie beschikbaar - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Er is een nieuwe versie van de %1 Client beschikbaar.</p><p><b>%2</b> is beschikbaar voor download. De geïnstalleerde versie is %3.</p> - - - - - Skip this version - Deze versie overslaan. - - - - Skip this time - Deze keer overslaan - - - - Get update - Ophalen update - - - - Update Failed - Bijwerken mislukt - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Een nieuwe versie van de %1 Applicatie is beschikbaar maar het updateproces is mislukt.</p><p><b>%2</b> is gedownload. De geïnstalleerde versie is %3.</p> - - - - Ask again later - Vraag later opnieuw - - - - Restart and update - Herstarten en bijwerken - - - - Update manually - Handmatig bijwerken - - - - OCC::NetworkSettings - - - Form - Formulier - - - - Proxy Settings - Proxy Instellingen - - - - No Proxy - Geen Proxy - - - - Use system proxy - Gebruik systeem proxy - - - - Specify proxy manually as - Specificeer proxy handmatig als - - - - Host - Server - - - - : - : - - - - Proxy server requires authentication - Proxyserver heeft verificatie nodig - - - - Note: proxy settings have no effects for accounts on localhost - Opmerking: proxy-instellingen hebben geen effect op accounts op localhost - - - - Download Bandwidth - Download bandbreedte - - - - - Limit to - Beperkt tot - - - - - KBytes/s - KBytes/s - - - - - No limit - Geen beperking - - - - - Limit to 3/4 of estimated bandwidth - Beperk tot 3/4 van de geschatte bandbreedte - - - - Upload Bandwidth - Upload bandbreedte - - - - - Limit automatically - Beperk automatisch - - - - Hostname of proxy server - Hostnaam van proxy server - - - - Username for proxy server - Gebruikersnaam voor proxy server - - - - Password for proxy server - Wachtwoord voor proxy server - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - Aangemaakt op %1 - - - - Closing in a few seconds... - Wordt afgesloten binnen enkele seconden... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 aanvraag mislukt om %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' geselecteerd om %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Fout gemeld door de server: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Er heeft zich een fout voorgedaan bij het verkrijgen van de 'token' eindpunt: 1 2 %1 2 - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Kon de JSON dat teruggekomen is van de server niet ontleden: 1 2 %1 2 - - - - Unsupported token type: %1 - Niet-ondersteund token type: %1 - - - - The reply from the server did not contain all expected fields -:%1 - Het antwoord van de server bevat niet alle verwachte velden -:%1 - - - - Unknown Error - Onbekende fout - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>Fout bij aanmelden</h1><p>Kan gebruikersinfo niet ophalen</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Inlog fout<p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Fout bij het verversen van token: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - Het antwoord van de server bevat niet alle verwachte velden -:%1 -Ontvangen data: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - 1 Verkeerde gebruiker 1 2 U bent ingelogd met user 3 %1 3, maar dient ingelogd te zijn met user 4 %2 4. 5 Log %3 alstublieft uit in een andere tabblad, vervolgens 6 klikt u hier 6 en log-in als user %2 2 - - - - OCC::OCUpdater - - - New %1 Update Ready - Nieuwe %1 update is klaar - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Er wordt een nieuwe update voor %1 geïnstalleerd. De updater kan -vragen om extra autorisaties tijdens installatie. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Kon de download niet bijwerken. Klik <a href='%1'>hier</a> om de download handmatig bij te werken. - - - - Could not check for new updates. - Kon niet controleren op updates. - - - - Downloading %1. Please wait... - %1 is aan het downloaden. Even geduld... - - - - %1 available. Restart application to start the update. - %1 is beschikbaar. Herstart de applicatie om de update te starten. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Nieuwe %1 beschikbaar. Gebruik de systeemupdate tool om te installeren. - - - - Checking update server... - Controleren updateserver... - - - - Update status is unknown: Did not check for new updates. - Update status is onbekend: niet gecontroleerd op nieuwe updates. - - - - No updates available. Your installation is at the latest version. - Geen updates beschikbaar. Uw installatie is al de laatste versie. - - - - Update Check - Controle update - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Verbinden met %1 - - - - Setup local folder options - Bepaal de instellingen voor de lokale map - - - - Connect... - Verbinden... - - - - (experimental) - (experimenteel) - - - - Use &virtual files instead of downloading content immediately%1 - Gebruik &virtual bestanden in plaats van het meteen downloaden van de inhoud%1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Sync map '%1' - - - - Virtual files are not available for the selected folder - Virtuele bestanden zijn niet beschikbaar voor de geselecteerde map - - - - Error - Fout - - - - Local Sync Folder - Lokale synchronisatiemap - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &E-mail - - - - Connect to %1 - Verbinden met %1 - - - - Enter user credentials - Vul uw inloggegevens in - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Verbinden met %1 - - - - Login in your browser - Login in uw browser - - - - Copy link to clipboard - Kopiëren link naar klembord - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - Bestand %1 is in gebruik - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Verbinden met %1 - - - - Setup %1 server - Instellen %1 server - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Deze url is NIET veilig omdat versleuteling ontbreekt. -We adviseren deze site niet te gebruiken. - - - - This url is secure. You can use it. - Deze url is veilig te gebruiken. - - - - &Next > - &Volgende > - - - - Invalid URL - Ongeldige URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Kon geen verbinding maken met %1 op %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Time-out bij verbinden met %1 om %2. - - - - Invalid URL - Ongeldige URL - - - - Could not create local folder %1 - Kon lokale map %1 niet aanmaken - - - - Error: %1 - Fout: %1 - - - - The folder creation resulted in HTTP error code %1 - Het aanmaken van de map resulteerde in HTTP foutcode %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Het aanmaken van de remote map is mislukt, waarschijnlijk omdat uw inloggegevens fout waren.<br/>Ga terug en controleer uw inloggegevens.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Aanmaken van remote map %1 mislukt met fout <tt>%2</tt>. - - - - Folder rename failed - Map hernoemen mislukt - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Kan de map niet verwijderen en backuppen, omdat de map of een bestand daarin, geopend is in een ander programma. Sluit de map of het bestand en drup op Opnieuw of annuleer de installatie. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Verbindingswizard - - - - Enable experimental feature? - Experimentele feature inschakelen? - - - - Enable experimental placeholder mode - Experimentele tijdelijke plaatsvervanger modus inschakelen? - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Als de "virtuele bestanden"-modus is ingeschakeld, worden in het begin geen bestanden gedownload. In plaats hiervan wordt een klein "%1" bestand aangemaakt voor elk bestand dat bestaat op de server. De inhoud kan worden gedownload door deze bestanden uit te voeren, of door hun contextmenu's te gebruiken. - -De virtuele bestanden-modus is wederzijds exclusief met selectieve synchronisatie. Op dit moment worden ongeselecteerde mappen vertaald naar alleen-online mappen en je selectieve synchronisatie-instellingen zullen worden gereset. - -Overschakelen naar deze modus zal alle op dit moment lopende synchronisaties onderbreken. - -Dit is een nieuwe, experimentele modus. Als je besluit deze te gebruiken, meld dan alsjeblieft alle problemen waar je tegenaan loopt. - - - - Stay safe - Blijf veilig - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Symbolische links worden niet ondersteund bij het synchroniseren. - - - - File is listed on the ignore list. - Het bestand is opgenomen op de negeerlijst. - - - - File names ending with a period are not supported on this file system. - Bestandsnamen die eindigen met een punt worden niet ondersteund door het bestandssysteem. - - - - File names containing the character '%1' are not supported on this file system. - Bestandsnamen met een '%1' symbool worden niet ondersteund door het bestandssysteem. - - - - File name contains at least one invalid character - De bestandsnaam bevat ten minste één ongeldig teken - - - - The file name is a reserved name on this file system. - De bestandsnaam is een gereserveerde naam op dit bestandssysteem. + + Add Ignore Pattern + Negeerpatroon toevoegen - - Filename contains trailing spaces. - De bestandsnaam bevat spaties achteraan. + + Add a new ignore pattern: + Voeg nieuw negeerpatroon toe: - - Filename is too long. - De bestandsnaam is te lang. + + This entry is provided by the system at '%1' and cannot be modified in this view. + Deze entry is door het systeem geleverd op '%1' en kan niet worden aangepast in deze sessie. + + + OCC::LocalActivityWidget - - File/Folder is ignored because it's hidden. - Bestand/Map is genegeerd omdat het verborgen is. + + Filter + Filter - - Stat failed. - Stat mislukt. + + Local activity table + Lokale activiteiten tabel - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Conflict: Serverversie gedownload, lokale versie hernoemd en niet geüpload. + + + Account + Account - - The filename cannot be encoded on your file system. - De bestandsnaam kan niet worden gecodeerd op jouw bestandssysteem. + + Filter menu + Filter menu - - The filename is blacklisted on the server. - De bestandsnaam staat op de zwarte lijst van de server. + + Actions menu + Actie menu - - size - grootte + + Sort ascending by %1 + Oplopend sorteren aan de hand van %1 - - permissions - rechten + + Sort descending by %1 + Aflopend sorteren aan de hand van %1 - - etag - etag + + Retry sync + Synchroniseer opnieuw + + + OCC::LogBrowser - - file id - bestands id + + Log Output + Log output - - server reported no %1 - Server vermeldde geen %1 + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>De logs bevatten gevoelige informatie die u niet openbaar mag maken</b></span></p></body></html> - - Moved to invalid target, restoring - Ongeldig doel verplaatst, herstellen + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + De client kan debug logs wegschrijven naar een tijdelijke map. Deze logs zijn erg nuttig voor het diagnosticeren van problemen. +Omdat logbestanden groot kunnen worden, zal de client voor elke synchronisatie een nieuwe starten en oudere logbestanden comprimeren. - - Not allowed to remove, restoring - Niet toegestaan om te verwijderen, herstellen + + Enable logging to temporary folder + Loggen naar tijdelijke map inschakelen - - Ignored because of the "choose what to sync" blacklist - Genegeerd vanwege de "wat synchroniseren" zwarte lijst + + If enabled, logs will be written to: + Indien ingeschakeld, zullen logs worden geschreven naar: - - Not allowed because you don't have permission to add subfolders to that folder - Niet toegestaan, omdat je geen permissies hebt om submappen aan die map toe te voegen + + C:/log + C:/log - - Not allowed because you don't have permission to add files in that folder - Niet toegestaan omdat u geen rechten hebt om bestanden in die map toe te voegen + + Log Http traffic + Log HTTP-verkeer - - Not allowed to upload this file because it is read-only on the server, restoring - Niet toegestaan om dit bestand te uploaden, omdat het alleen-lezen is op de server, herstellen + + Log files to keep: + Logbestanden die moeten worden behouden: - - Error while reading the database - Fout tijdens het uitlezen van de database + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Deze instellingen blijven ook bestaan na het herstarten van de applicatie. +Let op dat het gebruik van command line logopties deze instelling zullen overschrijven. - - Server replied with an error while reading directory '%1' : %2 - Server reageerde met een fout tijdens het uitlezen van map '%1' : %2 + + Open folder + Map openen - OCC::PropagateDirectory - - - Error updating metadata: %1 - - + OCC::MainWindow - - %1 the folder is currently in use - + + Hide + Verbergen - - - OCC::PropagateDownloadFile - - Failed to free up space, the file %1 is currently in use - + + Main toolbar for the application + Hoofdwerkbalk voor de applicatie +  - - - File %1 can not be downloaded because of a local file name clash with %2! - + + Main toolbar + Hoofdwerkbalk +  - - The download would reduce free local disk space below the limit - De download zal de vrije lokale schijfruimte reduceren tot onder het limiet + + + More + Meer + + + OCC::MainWindowController - - Free space on disk is less than %1 - Vrije schijfruimte is minder dan %1 + + Add account... + Account toevoegen... - - File was deleted from server - Bestand was verwijderd van de server + + Settings... + Instellingen... - - The file could not be downloaded completely. - Het bestand kon niet volledig worden gedownload. + + About... + About... - - The downloaded file is empty despite the server announced it should have been %1. - Het gedownloade bestand is leeg, hoewel de server meldde dat het %1 zou moeten zijn. + + Quit + Afsluiten - - File %1 cannot be saved because of a local file name clash with %2! - + + + Errors: %1 + Fout: %1 - - Error updating metadata: %1 - + + Activity + Activiteit - - - - - The file %1 is currently in use - + + Quit %1 + %1 afsluiten - - - File has changed since discovery - Het bestand is gewijzigd sinds het is gevonden + + Are you sure you want to quit %1? + Weet je zeker dat je %1 wilt afsluiten? - OCC::PropagateItemJob + OCC::ModalWrapperWidget - - ; Restoration Failed: %1 - ; Herstel mislukte: %1 + + Close + Sluiten - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - kon bestand file %1 niet verwijderen, fout: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - + OCC::Models - - Error updating metadata: %1 - + + %1 Filter: + %1 Filter: - - The file %1 is currently in use - + + All + Alle - - could not create folder %1 - kon map %1 niet maken + + Reset Filters + Reset Filters - - - OCC::PropagateLocalRemove - - Could not remove %1 because of a local file name clash with %2! - + + Status Filter: + Status Filter: - OCC::PropagateLocalRename + OCC::NetworkSettings - - File %1 can not be renamed to %2 because of a local file name clash - Bestand %1 kan niet worden hernoemd naar %2, omdat de naam conflicteert met een lokaal bestand + + Form + Formulier - - Could not rename %1 to %2, the file is currently in use - Kan %1 niet naar %2 verplaatsen, het bestand is in gebruik + + Pause synchronization when the Internet connection is metered + Synchronisatie onderbreken wanneer er een datalimiet is voor de internetverbinding - - Error updating metadata: %1 - + + Proxy Settings + Proxy Instellingen - - The file %1 is currently in use - + + No Proxy + Geen Proxy - - Failed to rename file - + + Use system proxy + Gebruik systeem proxy - - Error setting pin state - Fout bij het instellen van pin staat + + Host + Server - - - OCC::PropagateRemoteDelete - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Foutieve HTTP code ontvangen van de server. Verwacht was 204, maar ontvangen "%1 %2". + + Proxy Hostname + Proxy hostnaam - - - OCC::PropagateRemoteMkdir - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Foutieve HTTP code ontvangen van de server. Verwacht was 201, maar ontvangen "%1 %2". + + : + : - - Error writing metadata to the database: %1 - + + Proxy Port Number + Proxy poortnummer - - Setting file status failed due to file lock - + + Proxy server requires authentication + Proxyserver heeft verificatie nodig - - - OCC::PropagateRemoteMove - - Could not rename %1 to %2, error: %3 - Kon %1 niet hernoemen naar %2, fout: %3 + + Note: proxy settings have no effects for accounts on localhost + Opmerking: proxy-instellingen hebben geen effect op accounts op localhost - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Foutieve HTTP code ontvangen van de server. Verwacht werd 201, maar ontvangen "%1 %2". + + Specify proxy manually + Specificeer proxy handmatig - - Error updating metadata: %1 - + + Proxy type + Proxy type - - The file %1 is currently in use - + + + Hostname of proxy server + Hostnaam van proxy server - - Error writing metadata to the database - Fout bij schrijven van Metadata naar de database + + + Username for proxy server + Gebruikersnaam voor proxy server - - Error setting pin state - Fout bij het instellen van pin staat + + + Password for proxy server + Wachtwoord voor proxy server - - - OCC::PropagateUpdateMetaDataJob - - Could not update file : %1 - Kon bestand niet bijwerken : %1 + + HTTP(S) proxy + HTTP(S) proxy - - The file %1 is currently in use - + + SOCKS5 proxy + SOCKS5 proxy - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Bestand %1 kan niet worden geüpload omdat er al een ander bestand met dezelfde naam bestaan, al verschillen hoofd/kleine letters - - - - File Removed - Bestand verwijderd - - - - Local file changed during syncing. It will be resumed. - Lokaal bestand gewijzigd bij sync. Wordt opnieuw meengenomen. - + OCC::NewAccountBuilder - - Local file changed during sync. - Lokaal bestand gewijzigd bij sync. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - + + no error details are available + Geen andere versies beschikbaar - - - Upload of %1 exceeds the quota for the folder - Upload van %1 overtreft de quota van de folder + + Unable to connect to server during account creation: %1. + Niet in staat om verbinding te maken met de server tijdens het aanmaken van het account: %1 - OCC::PropagateUploadFileNG + OCC::NewAccountWizard - - %1 the file is currently in use - %1 is in gebruik + + Cancel Setup + Annuleer installatie - - The local file was removed during sync. - Het lokale bestand werd verwijderd tijdens sync. + + Do you really want to cancel the account setup? + Weet u zeker dat u het instellen van uw account wilt annuleren? + + + OCC::NewAccountWizardController - - Local file changed during sync. - Lokaal bestand gewijzigd bij sync. + + Add account… + Account toevoegen... - - The server did ask for a removed legacy feature(polling) - + + Back + Terug - - Unexpected return code from server (%1) - Onverwachte reactie van server (%1) + + Advanced + Geavanceerd - - Missing File ID from server - Ontbrekende File ID van de server + + Sign in + Inloggen - - Missing ETag from server - Ontbrekende ETag van de server + + Open sign in again + Open aanmelding opnieuw - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - %1 is in gebruik + OCC::NewVersionAvailableWidget + + + Skip this version + Deze versie overslaan - - The local file was removed during sync. - Het lokale bestand werd verwijderd tijdens sync. + + Get update + Ophalen update - - Local file changed during sync. - Lokaal bestand gewijzigd bij sync. + + Skip this time + Deze keer overslaan - OCC::PropagateUploadFileV1 + OCC::OAuth - - %1 the file is currently in use - %1 is in gebruik + + Successfully signed in + Succesvol aangemeld - - The server did ask for a removed legacy feature(polling) + + Now, explore %1 on desktop. - - The local file was removed during sync. - Het lokale bestand werd verwijderd tijdens sync. + + Error returned from the server: <em>%1</em> + Fout gemeld door de server: <em>%1</em> - - Local file changed during sync. - Lokaal bestand gewijzigd bij sync. + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Er heeft zich een fout voorgedaan bij het verkrijgen van de 'token' eindpunt: 1 2 %1 2 - - The server did not acknowledge the last chunk. (No e-tag was present) - De server heeft het laatste deel niet bevestigd (er was geen e-tag aanwezig) + + Could not parse the JSON returned from the server: <br><em>%1</em> + Kon de JSON dat teruggekomen is van de server niet ontleden: 1 2 %1 2 - - - OCC::ProtocolItemModel - - Time - Tijd + + Unsupported token type: %1 + Niet-ondersteund token type: %1 - - File - Bestand + + The reply from the server did not contain all expected fields +:%1 + Het antwoord van de server bevat niet alle verwachte velden +:%1 - - Folder - Map + + Unknown Error + Onbekende fout - - Issues - + + + Login Error + Inlogfout - - Action - Handeling + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + U bent ingelogd met de gebruiker <em>%1</em>, maar u moet inloggen met gebruiker <em>%2</em>.<br>Ga terug naar de %3 client en herstart de authenticatie. - - Size - Grootte + + Wrong user + Verkeerde gebruiker + + + OCC::OAuthPageController - - Account - Account + + Application Name Logo %1 + Logo van de applicatie - - Status - Status + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + - - - OCC::ProtocolWidget - - Form - Formulier + + Sign in URL + Inlog URL - - Local sync protocol - Lokaal sync protocol + + To copy the sign in URL to the clipboard, use the copy button + Om de inlog-URL naar het klembord te kopiëren, gebruikt u de kopieerknop. - - Filter - Filter + + Copy URL to sign in + Kopiëren URL naar klembord - - Account - Account + + Additional logo defined by the organization + Extra logo gedefiniëerd door de organisatie + + + + URL copied + URL is gekopiëerd - - Copy to clipboard - Kopiëren naar klembord + + Copy URL + Kopiëren URL - - Show in file browser + + Unable to connect to the login service. If the problem continues, please contact support. - - Show in web browser + + Please copy the authentication URL using the button above, and provide this information to support. - - Retry sync + + Failed to look up WebFinger instances: %1 - - - OCC::ProxyAuthDialog - - Proxy authentication required - Proxy-authenticatie vereist + + You are already connected to an account with these credentials. + Er is al een account met deze login gegevens. - - Username: - Gebruikersnaam: + + Unable to retrieve capabilities from server. + De Capabilities van de server konden niet worden opgehaald. - - Proxy: - Proxy: + + The server is not supported by this client. + De server wordt niet ondersteund door deze client - - The proxy server needs a username and password. - De proxyserver heeft een gebruikersnaam en wachtwoord nodig + + Error while trying to log in to OAuth2-enabled server. %1 + Fout bij het inloggen bij een OAuth2 server. %1 - - Password: - Wachtwoord: + + Unable to open browser with provided URL. %1 + - - TextLabel - Tekstlabel + + OAuth2 authentication requires a secured connection. %1 + OAuth2 vereist een beveiligde verbinding. %1 - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Kies wat te synchroniseren + + Authorization server unreachable. %1 + Autorisatie server is onbereikbaar. %1 - OCC::SelectiveSyncWidget - - - Loading ... - Laden ... - + OCC::OCUpdater - - Deselect remote folders you do not wish to synchronize. - Deselecteer de externe mappen die u niet wenst te synchroniseren. + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Kon de download niet bijwerken. Klik <a href='%1'>hier</a> om de download handmatig bij te werken. - - Name - Naam + + Could not check for new updates. + Kon niet controleren op updates. - - Size - Grootte + + Downloading %1. Please wait… + %1 wordt gedownload. Even geduld… - - - No subfolders currently on the server. - Momenteel geen submappen op de server. + + %1 installed successfully. Restart the application to finish installing the update. + %1 succesvol geïnstalleerd. Start de applicatie opnieuw om de installatie van de update te voltooien. - - An error occurred while loading the list of sub folders. - Er trad een fout op bij het laden van de lijst met submappen. + + %1 available. Restart application to start the update. + %1 is beschikbaar. Herstart de applicatie om de update te starten. - - - OCC::ServerNotificationHandler - - Dismiss - Terzijde leggen + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Een nieuw(e) %1 is beschikbaar. Klik <a href='%2'>here</a> on het nieuwe AppImage handmatig te downloaden. - - - OCC::SettingsDialog - - Settings - Instellingen + + New %1 available. Please use the system's update tool to install it. + Nieuwe %1 beschikbaar. Gebruik de systeemupdate tool om te installeren. - - Activity - Activiteit + + Checking update server… + Controleren updateserver... - - Add account - Account toevoegen + + Update status is unknown: Did not check for new updates. + Update status is onbekend: niet gecontroleerd op nieuwe updates. - - - Quit %1 - %1 afsluiten + + No updates available. Your installation is at the latest version. + Geen updates beschikbaar. Uw installatie is al de laatste versie. + + + OCC::OwncloudPropagator - - Are you sure you want to quit %1? - Weet je zeker dat je %1 wilt afsluiten? + + File %1 is currently in use + Bestand %1 is in gebruik - - Account - Account + + vfs instance is not available. + + + + OCC::ProcessDirectoryJob - - MainWindow - MainWindow + + Symbolic links are not supported in syncing. + Symbolische links worden niet ondersteund bij het synchroniseren. - - Hide - Verbergen + + File is listed on the ignore list. + Het bestand is opgenomen op de negeerlijst. - - - OCC::ShareDialog - - TextLabel - Tekstlabel + + File names ending with a period are not supported on this file system. + Bestandsnamen die eindigen met een punt worden niet ondersteund door het bestandssysteem. - - share label - deel label + + File names containing the character '%1' are not supported on this file system. + Bestandsnamen met een '%1' symbool worden niet ondersteund door het bestandssysteem. - - Dialog - Dialoog + + File name contains at least one invalid character + De bestandsnaam bevat ten minste één ongeldig teken - - ownCloud Path: - OwnCloud Pad: + + The file name is a reserved name on this file system. + De bestandsnaam is een gereserveerde naam op dit bestandssysteem. - - %1 Sharing - %1 Delen + + Filename contains trailing spaces. + De bestandsnaam bevat spaties achteraan. - - %1 - %1 + + Filename is too long. + De bestandsnaam is te lang. - - Folder: %2 - Map: %2 + + File/Folder is ignored because it's hidden. + Bestand/Map is genegeerd omdat het verborgen is. - - The server does not allow sharing - De server staat delen niet toe + + Stat failed. + Stat mislukt. - - Retrieving maximum possible sharing permissions from server... - Maximum aantal mogelijke permissies van de server ophalen... + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Conflict: Serverversie gedownload, lokale versie hernoemd en niet geüpload. - - The file can not be shared because it was shared without sharing permission. - Het bestand kan niet worden gedeeld, omdat het werd gedeeld zonder verder delen toestemming. + + The filename cannot be encoded on your file system. + De bestandsnaam kan niet worden gecodeerd op jouw bestandssysteem. - - Users and Groups - Gebruikers en Groepen + + The filename is blacklisted on the server. + De bestandsnaam staat op de zwarte lijst van de server. - - Public Links - Openbare Links + + + + + the VFS instance is missing. Unable to continue. + - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Delen NewDocument.odt + + server reported no %1 + Server vermeldde geen %1 - - Upload only (File Drop) - Alleen uploaden (File Drop) + + + account was deleted. Unable to continue + Het account was verwijderd. Voortgang is niet mogelijk - - Receive files from others without revealing the contents of the folder. - Ontvang bestanden van anderen zonder de inhoud van de map te tonen. + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: genegeerd omdat het pad gedeselecteerd is. - - Download / View / Edit - Download / Bekijk / Bewerk + + Moved to invalid target, restoring + Ongeldig doel verplaatst, herstellen - - Name: - Naam: + + Not allowed to remove, restoring + Niet toegestaan om te verwijderen, herstellen - - Enter name of new share... - Geef naam van nieuwe deellink... + + Error while reading the database. + - - &Create - &Create + + account was deleted. Unable to continue. + - - TextLabel - Tekstlabel + + Not allowed because you don't have permission to add subfolders to that folder + Niet toegestaan, omdat je geen permissies hebt om submappen aan die map toe te voegen - - Set &password - Instellen &wachtwoord + + Error while doing a rename, unhandled status code: %1 + Er is een fout opgetreden bij het veranderen van de naam, de niet afgehandelde status code is: %1 - - Set &expiration date - Stel &vervaldatum in + + Not allowed because you don't have permission to add files in that folder + Niet toegestaan omdat u geen rechten hebt om bestanden in die map toe te voegen - - Set password - Instellen wachtwoord + + Not allowed to upload this file because it is read-only on the server, restoring + Niet toegestaan om dit bestand te uploaden, omdat het alleen-lezen is op de server, herstellen - - Recipients can view or download contents. - Ontvangers kunnen de inhoud bekijken of downloaden. + + This Space is currently unavailable + Deze Space is op dit moment niet beschikbaar. - - Download / View - Download / Bekijk + + Server replied with an error while reading directory '%1' : %2 + Server reageerde met een fout tijdens het uitlezen van map '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - Ontvangers kunnen de inhoud bekijken, downloaden, bewerken, verwijderen en uploaden. + + Error updating metadata: %1 + Fout bij het bijwerken van de metadata: %1 - - Anyone with the link has access to the file/folder - Iedereen met de link heeft toegang tot het bestand of de map + + %1 the folder is currently in use + %1 de folder is op dit moment in gebruik + + + OCC::PropagateDownloadFile - - - P&assword protect - &Wachtwoord beveiligd + + Failed to free up space, the file %1 is currently in use + Er kan geen ruimte gemaakt worden, bestand %1 is in gebuik - - The file can not be shared because it was shared without sharing permission. - Het bestand kan niet worden gedeeld, omdat het werd gedeeld zonder verder delen toestemming. + + + File %1 can not be downloaded because of a local file name clash with %2! + Kon bestand %1 niet downloaden vanwege een lokaal bestandsnaam conflict met %2! - - Link shares have been disabled - Deellinks zijn uitgeschakeld + + The download would reduce free local disk space below the limit + De download zal de vrije lokale schijfruimte reduceren tot onder het limiet - - Create public link share - Maak een publieke deellink aan + + Free space on disk is less than %1 + Vrije schijfruimte is minder dan %1 - - - Delete - Verwijderen + + File was deleted from server + Bestand was verwijderd van de server - - Open link in browser - Open link in browser + + Broken webserver returned empty content length for non-empty file on resume + Kapotte webserver retourneerde een leeg antwoord voor een niet-leeg bestand bij het resumeren. - - Copy link to clipboard - Kopiëren link naar klembord + + The file could not be downloaded completely. + Het bestand kon niet volledig worden gedownload. - - Copy link to clipboard (direct download) - Kopieer de link naar het klembord (directe download) + + The downloaded file is empty despite the server announced it should have been %1. + Het gedownloade bestand is leeg, hoewel de server meldde dat het %1 zou moeten zijn. - - Send link by email - Versturen links via e-mail + + File %1 cannot be saved because of a local file name clash with %2! + Kon bestand %1 niet opslaan vanwege een lokaal bestandsconflict met %2! - - Send link by email (direct download) - Verzend link met email (directe download) + + Error updating metadata: %1 + Fout bij het bijwerken van de metadata: %1 - - Create new... - Maak nieuw... + + + + + The file %1 is currently in use + Bestand %1 is in gebruik - - I shared %1 with you - Ik heb %1 met je gedeeld + + + File has changed since discovery + Het bestand is gewijzigd sinds het is gevonden + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - Bevestig Gedeelde Link Verwijdering + + ; Restoration Failed: %1 + ; Herstel mislukte: %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Wilt u echt de publieke deellink <i>%1</i> verwijderen ?</p><p>Let op: deze actie kan niet ongedaan gemaakt worden.</p> + + could not delete file %1, error: %2 + kon bestand file %1 niet verwijderen, fout: %2 - - Cancel - Annuleren + + Can not create local folder %1 because of a local file name clash with %2 + Kon lokale map %1 niet aanmaken vanwege een lokaal bestandsnaam conflict met %2 - - - Public link - Openbare Link + + Error updating metadata: %1 + Fout bij het bijwerken van de metadata: %1 - - Delete link share - Verwijder deellink + + The file %1 is currently in use + Bestand %1 is in gebruik - - - Please Set Password - Stel uw wachtwoord in + + could not create folder %1 + kon map %1 niet maken - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove + + + %1 failed with: %2 + z%1 is mislukt: %2 + - - Share NewDocument.odt - Delen NewDocument.odt + + Could not remove %1 because of a local file name clash with %2! + Kon %1 niet verwijderen vanwege een lokaal bestandsnaam conflict met %2! - - Share with users or groups ... - Delen met gebruikers of groepen ... + + Could not move '%1' to the trash bin + Kan '%1' niet naar de prullenbak verplaatsen - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Je kunt mensen verwijzen naar dit bestand of deze map <a href="private link menu"><span style=" text-decoration: underline">door ze een privélink te geven</span></a>.</p></body></html> + + + %1 the file is currently in use + %1 is in gebruik + + + OCC::PropagateLocalRename - - The item is not shared with any users or groups - Het item is niet gedeeld met gebruikers of groepen + + File %1 can not be renamed to %2 because of a local file name clash + Bestand %1 kan niet worden hernoemd naar %2, omdat de naam conflicteert met een lokaal bestand - - Open link in browser - Open link in browser + + Could not rename %1 to %2, the file is currently in use + Kan %1 niet naar %2 verplaatsen, het bestand is in gebruik - - Copy link to clipboard - Kopiëren link naar klembord + + Error updating metadata: %1 + Fout bij het bijwerken van de metadata: %1 - - Send link by email - Versturen links via e-mail + + The file %1 is currently in use + Bestand %1 is in gebruik - - No results for '%1' - Geen resultaten voor '%1' + + Failed to rename file + Kan bestand niet hernoemen - - I shared something with you - Ik deelde iets met u + + Error setting pin state + Fout bij het instellen van pin staat - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Formulier + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Foutieve HTTP code ontvangen van de server. Verwacht was 204, maar ontvangen "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - Tekstlabel + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Foutieve HTTP code ontvangen van de server. Verwacht was 201, maar ontvangen "%1 %2". - - can edit - kan wijzigen + + Error writing metadata to the database: %1 + Fout bij schrijven van Metadata naar de database: %1 - - can share - kan delen + + Setting file status failed due to file lock + Kon een bestandsstatus niet zetten vanwege een bestandslock. + + + OCC::PropagateRemoteMove - - ... - ... + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Foutieve HTTP code ontvangen van de server. Verwacht werd 201, maar ontvangen "%1 %2". - - create - creëer + + Error updating metadata: %1 + Fout bij het bijwerken van de metadata: %1 - - change - wijzig + + The file %1 is currently in use + Bestand %1 is in gebruik - - delete - verwijderen + + Error writing metadata to the database + Fout bij schrijven van Metadata naar de database - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - -%1 (%2) + + Error setting pin state + Fout bij het instellen van pin staat - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Delen met %1 + + Could not update file : %1 + Kon bestand niet bijwerken : %1 - - Context menu share - Contextmenu delen + + The file %1 is currently in use + Bestand %1 is in gebruik + + + OCC::PropagateUploadCommon - - Context menu share %1 - Contextmenu delen %1 + + Local file changed during sync. It will be resumed. + Lokaal bestand gewijzigd bij sync. Het wordt opnieuw meegenomen. - - Confirm deletion - Bevestig verwijderen + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + Bestand %1 kan niet worden geüpload omdat er al een ander bestand met dezelfde naam bestaan, al verschillen hoofd/kleine letters - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Wil je de map <i>%1</i> en de inhoud daarvan permanent verwijderen? + + + Upload of %1 exceeds the quota for the folder + Upload van %1 overtreft de quota van de folder - - Do you want to delete the file <i>%1</i> permanently? - Wil je het bestand <i>%1</i> permanent verwijderen? + + + %1 the file is currently in use + %1 is in gebruik - - Select new location... - Selecteer nieuwe locatie... + + File Removed + Bestand verwijderd - - Error - Fout + + The server did not provide the file permissions + - - Moving file failed: - -%1 - Kon bestand niet verplaasen: - -%1 + + Error updating metadata: %1 + Fout bij het bijwerken van de metadata: %1 + + + OCC::PropagateUploadFile - - I shared something with you - Ik deelde iets met u + + %1 the file is currently in use + %1 is in gebruik - - - Share... - Delen... + + The server did ask for a removed legacy feature(polling) + De server vroeg om een verwijderde legacy feature (polling). - - - Copy private link to clipboard - Kopiëren privé-link naar klembord + + The local file was removed during sync. + Het lokale bestand werd verwijderd tijdens sync. - - Send private link by email... - Verstuur privélink per e-mail + + The server did not acknowledge the file upload. (No e-tag was present) + + + + OCC::PropagateUploadFileTUS - - Resharing this file is not allowed - Opnieuw delen van dit bestand is niet toegestaan + + %1 the file is currently in use + %1 is in gebruik - - Resharing this folder is not allowed - Opnieuw delen van deze map is niet toegestaan + + Checksum computation failed + Checksum berekening heeft gefaald - - Create and copy public link to clipboard - + + The local file was removed during sync. + Het lokale bestand werd verwijderd tijdens sync. + + + OCC::ProtocolItemModel - - Copy public link to clipboard - Kopieer openbare link naar klembord + + Time + Tijd - - Open in browser - Open in browser + + File + Bestand - - Show file versions in browser - Toon bestandsversies in browser + + Folder + Map - - Rename... - Hernoemen... + + Issues + Problemen - - Rename and upload... - Hernoemen en uploaden... + + Action + Handeling - - Move and rename... - Verplaatsen en hernoemen... + + Size + Grootte - - Move, rename and upload... - Verplaatsen, hernoemen en uploaden... + + Account + Account - - Delete local changes - Verwijder lokale wijzigingen + + Status + Status + + + OCC::RequestAuthenticationController - - Move and upload... - Verplaatsen en uploaden... + + IdP is unreachable. Contact your system administrator or try again later. + IdP is onbereikbaar. Neem contact op met uw systeembeheerder. - - Delete - Verwijderen + + Authentication failed. + Authenticatie mislukt. - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags - Kon tags niet ophalen + + Application Name Logo %1 + Logo van de applicatie - - Backup of %1 started - Back-up van %1 gestart + + Sign in required + Aanmelden vereist - - Failed to find %1 - Kon %1 niet vinden + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + - - Local path must be a an absolute path - Lokaal pad moet een absoluut pad zijn + + Sign in URL + Inlog URL - - Failed to create temporary database - Kon geen tijdelijke database aanmaken + + To copy the sign in URL to the clipboard, use the copy button + Om de inlog-URL naar het klembord te kopiëren, gebruikt u de kopieerknop. - - Backup of %1 succeeded - Back-up van %1 geslaagd + + Copy URL to sign in + Kopieer de URL om in te loggen - - Failed to set success tag - Kon geslaagd tag niet instellen + + Additional logo defined by the organization + Extra logo gedefiniëerd door de organisatie - - Failed to create backup: %1 - Kon geen back-up maken: %1 + + Stay logged out + Uitgelogd blijven - - Failed to file id tags - Kon id tags niet verwerken + + Sign in + Inloggen - - Backup of %1 failed with: %2 - Back-up van %1 mislukt met: %2 + + URL copied + URL is gekopiëerd - - %1 backup - %1 back-up + + Copy URL + Kopiëren URL - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Certificaat details</h3> - - - - Common Name (CN): - Common Name (CN): - + OCC::ResolveUrlAdapter - - Subject Alternative Names: - Alternatieve subject namen: + + SSL failure when connecting to server at %1 + - - Organization (O): - Organisatie (O): + + Could not detect compatible server at %1 + Kon geen compatibele server detecteren bij %1 - - Organizational Unit (OU): - Organisatie unit (OU): + + Resolved url is invalid %1 + - - State/Province: - Land/Provincie: + + Rejected redirect from %1 to %2 + + + + OCC::SelectiveSyncWidget - - Country: - Land: + + Loading … + Laden... - - Serial: - Serienummer: + + Deselect remote folders you do not wish to synchronize. + Deselecteer de externe mappen die u niet wenst te synchroniseren. - - <h3>Issuer</h3> - <h3>Uitgever</h3> + + Name + Naam - - Issuer: - Uitgever: + + Size + Grootte - - Issued on: - Uitgegeven op: + + + Currently there are no subfolders on the server. + - - Expires on: - Vervalt op: + + An error occurred while loading the list of subfolders. + + + + OCC::SettingsView - - <h3>Fingerprints</h3> - <h3>Vingerafdrukken</h3> + + Form + Formulier - - SHA-256: - SHA-256: + + General Settings + Algemene instellingen - - SHA-1: - SHA-1: + + Use Monochrome Icons in the system tray + Gebruik monochrome pictogrammen in het systeemvak - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Let op:</b> Dit certificaat werd handmatig goedgekeurd</p> + + Show Desktop Notifications + Toon desktopberichten - - %1 (self-signed) - %1 (zelf ondertekend) + + Start on Login + Bij inloggen starten - - %1 - %1 + + Language + Taal - - This connection is encrypted using %1 bit %2. - - Deze verbinding is versleuteld via %1 bit %2. - + + Language selector + Taal selectie - - Server version: %1 - Serverversie: %1 + + Advanced + Geavanceerd - - No support for SSL session tickets/identifiers - Geen ondersteuning voor SSL-sessie tickets/identifiers + + Sync hidden files + Synchroniseer verborgen bestanden - - Certificate information: - Certificaat informatie: + + Show crash reporter + Toon crash reporter - - The connection is not secure - De verbinding is niet veilig + + Move remotely deleted files to the local trash bin instead of deleting them + Verplaats op afstand verwijderde bestanden naar de lokale prullenbak in plaats van ze te verwijderen - - This connection is NOT secure as it is not encrypted. - - Deze verbinding is NIET veilig, omdat deze niet versleuteld is. - + + Edit Ignored Files + Bewerk genegeerde bestanden - - - OCC::SslErrorDialog - - Form - Formulier + + Log Settings + Loginstellingen - - Trust this certificate anyway - Vertrouw dit certificaat alsnog + + Network + Netwerk - - Untrusted Certificate - Niet vertrouwd certificaat + + Warning + Waarschuwing - - Cannot connect securely to <i>%1</i>: - Kan niet beveiligd verbinden met <i>%1</i>: + + Language changes require a restart of this application to take effect. + Verandering van taal vereist een herstart om effect te hebben. - - with Certificate %1 - met certificaat %1 + + You cannot disable autostart because system-wide autostart is enabled. + Je kunt autostart niet uitschakelen omdat autostart systeembreed is ingeschakeld. - - - - &lt;not specified&gt; - &lt;niet gespecificeerd&gt; + + (use default) + (gebruik standaard) - - - Organization: %1 - Organisatie: %1 + + unknown (%1) + onbekend (%1) + + + OCC::SocketApi - - - Unit: %1 - Unit: %1 + + Share with %1 + parameter is the application name + Delen met %1 - - - Country: %1 - Land: %1 + + Confirm deletion + Bevestig verwijderen - - Fingerprint (MD5): <tt>%1</tt> - Fingerprint (MD5): <tt>%1</tt> + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Wil je de map <i>%1</i> en de inhoud daarvan permanent verwijderen? - - Fingerprint (SHA1): <tt>%1</tt> - Fingerprint (SHA1): <tt>%1</tt> + + Do you want to delete the file <i>%1</i> permanently? + Wil je het bestand <i>%1</i> permanent verwijderen? - - Effective Date: %1 - Ingangsdatum: %1 + + Error + Fout - - Expiration Date: %1 - Vervaldatum: %1 + + Moving file failed: + +%1 + Kon bestand niet verplaasen: + +%1 - - Issuer: %1 - Uitgever: %1 + + I shared something with you + Ik deelde iets met u - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - %1 (overgeslagen wegens eerdere fout, opnieuw proberen in %2) + + Select new location… + Selecteer nieuwe locatie… - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Slechts %1 beschikbaar, maar heeft minimaal %2 nodig om te starten + + + Share… + Delen... - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Kan de lokale sync database niet openen of aanmaken. Controleer of je de juiste lees- en schrijfrechten in de lokale sync-map hebt. + + + Copy private link to clipboard + Kopiëren privé-link naar klembord - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Onvoldoende schijfruimte: Downloads dat je vrije schijfruime zou reduceren tot onder %1 zijn overgeslagen. + + Resharing this file is not allowed + Opnieuw delen van dit bestand is niet toegestaan - - There is insufficient space available on the server for some uploads. - Er is te weinig ruimte beschikbaar op de server voor sommige uploads. + + Resharing this folder is not allowed + Opnieuw delen van deze map is niet toegestaan - - Unresolved conflict. - Onopgelost conflict. + + Open in %1 + Open in %1 - - Using virtual files with suffix, but suffix is not set - Virtuele bestanden met een achtervoegsel aan het gebruiken, maar achtervoegsel is niet ingesteld + + Show file versions in web browser + Toon bestandsversies in browser - - Unable to read the blacklist from the local database - Kan de blacklist niet lezen uit de lokale database + + Rename… + Naam wijzigen... - - Unable to read from the sync journal. - Niet mogelijk om te lezen uit het synchronisatie verslag. + + Rename and upload… + Hernoemen en uploaden... - - Cannot open the sync journal - Kan het sync transactielog niet openen + + + Move and rename… + Verplaatsen en hernoemen… - - Aborted - Afgebroken + + Move, rename and upload… + Verplaatsen, hernoemen en uploaden... - - - OCC::SyncJournalDb - - Failed to connect database. - + + Move and upload… + Verplaatsen en uploaden… - - - OCC::Systray - - %1: %2 - %1: %2 + + Delete local changes + Verwijder lokale wijzigingen - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>Versie %1. Bezoek <a href="%2">https://%3</a> voor meer informatie.</p><p>Bezoek <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a> voor bekende problemen en hulp.</p><p><small>Door Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth en anderen.</small></p><p>Auteursrechten ownCloud GmbH</p><p>Gedistribueerd door %4 en gelicentieerd onder de GNU General Public License (GPL) Versie 2.0.<br/>%5 en het %5 logo zijn geregistreerde handelsmerken van %4 in de Verenigde Staten, andere landen, of allebei.</p><p><small>%6</small></p> + + Delete + Verwijderen - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - De header van het controlegetal is misvormd. - + OCC::SpacesPage - - The checksum header contained an unknown checksum type '%1' - Het header controlegetal bevat een onbekend controlegetal type '%1' + + Select a folder to sync it to your computer. + - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - Het gedownloade bestand komt niet overeen met de checksum, het wordt hervat. '%1' != '%2' + + Select a Space to sync it to your computer. + - OCC::Vfs + OCC::SyncEngine - - The Virtual filesystem feature does not support a drive as sync root - De Virtuele bestandssysteem-feature ondersteunt geen schijf als synchronisatiebron + + %1 (skipped due to earlier error, trying again in %2) + %1 (overgeslagen wegens eerdere fout, opnieuw proberen in %2) - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - De Virtuele bestandssysteem-feature vereist een NTFS-bestandssysteem, %1 gebruikt %2 + + Unable to sync: the local folder is missing. + - - The Virtual filesystem feature is not supported on network drives - De virtuele filesystem feature wordt niet ondersteund op netwerkschijven + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Slechts %1 beschikbaar, maar heeft minimaal %2 nodig om te starten - - - OCC::ownCloudGui - - Please sign in - Log alstublieft in + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Kan de lokale sync database niet openen of aanmaken. Controleer of je de juiste lees- en schrijfrechten in de lokale sync-map hebt. - - Folder %1: %2 - Map %1: %2 + + + + + Cannot open the sync journal. + - - Unresolved %1 conflicts - + + Aborted due to %1 + Afgebroken vanwege %1 - - Ignored errors %1 - + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Onvoldoende schijfruimte: Downloads dat je vrije schijfruime zou reduceren tot onder %1 zijn overgeslagen. - - There are no sync folders configured. - Er zijn geen synchronisatie-mappen geconfigureerd. + + There is insufficient space available on the server for some uploads. + Er is te weinig ruimte beschikbaar op de server voor sommige uploads. - - Open in browser - Open in browser + + application exit + abort reason + Applicatie afsluiten - - - - Log in... - Inloggen... + + Unable to read the blacklist from the local database + Kan de blacklist niet lezen uit de lokale database - - - - Log out - Afmelden + + Unable to read from the sync journal. + Niet mogelijk om te lezen uit het synchronisatie verslag. + + + OCC::SyncErrorWidget - - Recent Changes - Recente wijzigingen + + Issues table + Problemen tabel - - Managed Folders: - Beheerde mappen: + + There were too many issues. Not all will be visible here. + Er zijn teveel problemen. Niet alles zal worden vertoond. - - Open folder '%1' - Open map '%1' + + There were conflicts. Check the documentation on how to resolve them. + Er waren conflicten. Raadpleeg de documentatie om ze te verhelpen. - - Open %1 in browser - Open %1 in browser + + The file %1 was ignored as its name is reserved by %2 + Het bestand %1 is genegeerd, want de naam is gereserveerd door %2 - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - De server op account %1 gebruikt een niet-ondersteunde versie %2. Het gebruik van deze applicatie met niet-ondersteunde serverversies is niet getest en mogelijk gevaarlijk. Doorgaan is op eigen risico. + + Reset column sizes + Kolom grootes terugzetten - - Unknown status - Onbekende status + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Er waren conflicten. <a href="%1">Raadpleeg de documentatie om ze te verhelpen.</a> - - Settings... - Instellingen... + + Filter menu + Filter menu - - Details... - Details ... + + Account + Account - - Help - Help + + Filter + Filter + + + OCC::SyncJournalDb - - Quit %1 - %1 afsluiten + + Failed to connect database. + Kon geen tijdelijke database aanmaken + + + OCC::Theme - - Disconnected from %1 - Losgekoppeld van %1 + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + + + + OCC::TlsErrorDialog - - Unsupported Server Version - Niet-ondersteunde server versie + + TLS Certificate Error + TLS certificaat fout - - Disconnected - Niet verbonden + + Do you want to trust this certificate anyway? + Wil je dit certificaat alsnog vertrouwen? - - Disconnected from some accounts - Losgekoppeld van sommige accounts + + Cannot connect securely to %1 + Kan niet beveiligd verbinden met <i>%1</i>: - - Disconnected from accounts: - Losgekoppeld van account: + + &lt;not specified&gt; + &lt;niet gespecificeerd&gt; - - Account %1: %2 - Account %1: %2 + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>met certificaat %1</h3><div id="ccert"><p>Organisatie: %2<br/>Afdeling: %3<br/>Land: %4</p><p>Vingerafdruk (MD5): <tt>%5</tt><br/>Vingerafdruk (SHA1): <tt>%6</tt><br/>Vingerafdruk (SHA256): <tt>%7</tt><br/><br/>Effectief vanaf: %8Verval datum: %9</div><h3>Uitgever: %10</h3><div id="issuer"><p>Organisatie: %11<br/>Afdeling: %12<br/>Land: %13</p></div></div> + + + OCC::TrayMenuController - - Signed out - Afgemeld + + Unsupported Server Version + Niet-ondersteunde server versie - - Account synchronization is disabled - Account synchronisatie is uitgeschakeld + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + De server op account %1 gebruikt een niet-ondersteunde versie %2. Het gebruik van deze applicatie met niet-ondersteunde serverversies is niet getest en mogelijk gevaarlijk. Doorgaan is op eigen risico. - - Synchronization is paused - Synchronisatie is gepauzeerd + + Show %1 + Toon %1 - - Up to date (%1) - + + Help + Help - - No sync folders configured - Geen synchronisatie-mappen geconfigureerd. + + About %1 + Over %1 - - Unpause all folders - Hervat alle mappen + + Quit %1 + %1 afsluiten + + + OCC::UpdateDownloadedWidget - - Pause all folders - Pauzeer alle mappen + + Restart later + Later herstarten - - Unpause all synchronization - Hervat alle synchronisatie + + Restart now + Nu herstarten + + + OCC::UpdaterScheduler - - Unpause synchronization - Hervat synchronisatie + + Update available + Update beschikbaar + + + OCC::UrlPageController - - Pause all synchronization - Pauzeer alle synchronisatie + + Your web browser will be opened to complete sign in. + - - Pause synchronization - Pauzeer synchronisatie + + %1 logo + %1 logo - - Log out of all accounts - Uitloggen van alle accounts... + + Welcome to %1 + Welkom bij %1 - - Log in to all accounts... - Inloggen op alle accounts... + + Enter your server address to get started. Your web browser will be opened to complete sign in. + - - New account... - Nieuw account... + + Server address field + Serveradres veld - - About %1 - Over %1 + + Enter your server address here + - - No items synced recently - Recent niets gesynchroniseerd + + Additional logo defined by the organization + Extra logo gedefiniëerd door de organisatie - - Checking for changes in remote '%1' - Aan het controleren op wijzigingen op server '%1' + + Invalid server URL: %1 + Ongeldige server URL: %1 - - Checking for changes in local '%1' - Aan het controleren op veranderingen in lokale '%1' + + Invalid URL scheme. Only https is accepted. + Ongeldig URL schema. Alleen https wordt ondersteund. - - Syncing %1 of %2 (%3 left) - Sync %1 van %2 (%3 over) + + The requested URL failed with query value: %1 + + + + OCC::UserInfoAdapter - - Syncing %1 of %2 - Synchroniseren %1 van %2 + + Network request to collect user info failed + - - Syncing %1 (%2 left) - Sync %1 (%2 over) + + Unable to retrieve user info: invalid credentials. + - - Syncing %1 - Synchroniseren %1 + + Unexpected network response when retrieving user info. + - - %1 (%2, %3) - %1 (%2, %3) + + Retrieving user info failed with JSON error. + - OwncloudAdvancedSetupPage + OCC::ValidateChecksumHeader - - Form - Formulier + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + Het gedownloade bestand komt niet overeen met de checksum, het wordt hervat. '%1' != '%2' + + + OCC::Vfs - - - TextLabel - TekstLabel + + The Virtual filesystem feature does not support a drive as sync root + De Virtuele bestandssysteem-feature ondersteunt geen schijf als synchronisatiebron - - Server - Server + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + De Virtuele bestandssysteem-feature vereist een NTFS-bestandssysteem, %1 gebruikt %2 - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Als deze checkbox is aangevinkt zullen bestaande bestanden in de lokale map worden gewist om een schone sync vanaf de server te starten.</p><p>Vink dit niet aan als de lokale bestanden naar de map op de server zouden moeten worden geüploaded.</p></body></html> + + The Virtual filesystem feature is not supported on network drives + De virtuele filesystem feature wordt niet ondersteund op netwerkschijven + + + OCC::VfsWinPrivate - - Ask for confirmation before synchroni&zing folders larger than - Vraag bevestiging voor &synchronisatie van mappen groter dan + + + %1 - %2 + %1 - %2 - - S&ynchronize everything from server + + Could not find StorageProviderState property %1 - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Vraag bevestiging voor synchronisatie van e&xterne opslag - - - - Choose what to sync - Selectieve synchronisatie + + Could not find StorageFolder for %1 error: %2 + StorageFolder voor %1 kan niet gevonden worden: %2 - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Wanneer deze optie is geselecteerd, zal de wizard sluiten zonder iets te synchroniseren. Je kunt de &quot;Voeg mapsynchronisatieverbinding toe&quot;-knop in de accountinstellingen gebruiken om te kiezen welke lokale en afstandelijke map je met elkaar wilt synchroniseren</p></body></html> + + Could not retrieve StorageFolder for %1 %2 (0x%3) + - - Manually create folder sync connections - Handmatig map-synchronisatie verbindingen aanmaken + + The folder is used by a different client: %1 + De map is wordt al gebuikt door een andere applicatie: %1 - - &Local Folder - &Lokale map + + Error registering StorageProvider for %1: %2 (0x%3) + - - pbSelectLocalFolder - pbSelectLocalFolder + + Unable to connect sync root: %1 error: %2 + Niet in staat om verbinding te maken met de sync root: %1 fout: %2 - - &Keep local data - &Bewaar lokale gegevens + + %1 is managed by another sync client + %1 is in gebruik door een andere synchronisatie applicatie - - Start a &clean sync (Crreate a backup and erases the local folder!) + + Downgrading the folder from %1 to %2 is not supported - OwncloudHttpCredsPage - - - Form - Formulier - + OCC::WebFingerLookupAdapter - - &Username - &Gebruikersnaam + + Failed to retrieve user info + Kon gebruiker informatie niet ophalen - - &Password - &Wachtwoord + + WebFinger lookup returned no links + - OwncloudOAuthCredsPage - - - Form - Formulier - + OCC::WindowsUpdater - - Please switch to your browser to proceed. - Ga naar je browser om verder te gaan. + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Er is een nieuwe versie van de %1 Client beschikbaar.</p><p><b>%2</b> is beschikbaar voor download. De geïnstalleerde versie is %3.</p> - - An error occurred while connecting. Please try again. - Er is een fout opgetreden tijdens het verbinden. Probeer het opnieuw. + + Skip this version + Deze versie overslaan - - Re-open Browser - Heropen Browser + + Update Failed + Bijwerken mislukt - - - OwncloudSetupPage - - Form - Formulier + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Een nieuwe versie van de %1 Applicatie is beschikbaar maar het updateproces is mislukt.</p><p><b>%2</b> is gedownload. De geïnstalleerde versie is %3.</p> - - Ser&ver Address - Ser&veradres + + Ask again later + Vraag later opnieuw - - https://... - https://... + + Restart and update + Herstarten en bijwerken - - Error Label - Fout label + + Update manually + Handmatig bijwerken QObject - + in the future in de toekomst - + %n day(s) ago %n dag geleden%n dagen geleden - + %n hour(s) ago %n uur geleden%n uur geleden - + now nu - - Less than a minute ago - Minder dan een minuut geleden + + less than a minute ago + minder dan een minuut geleden - + %n minute(s) ago %n minuut geleden%n minuten geleden - - Some time ago - Even geleden - - - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 + + SpacesBrowser + + + Form + Formulier + + + + SpacesPage + + + Spaces list + Spaces lijst + + + + SpacesView + + + Spaces + Spaces + + SyncFileItem::Status - + Undefined - + ongedefiniëerd - + Fatal Error Fatale fout - - + + Error Fout - + Info Info - + Success Succes - + Conflict Conflict - + File Ignored - + genegeerd - + Restored - + hersteld - + Blacklisted - + op de zwarte lijst - + Excluded - - - - - Utility - - - %L1 GB - %L1 GB + uitgezonderd - - %L1 MB - %L1 MB + + Message + Bericht - - %L1 KB - %L1 KB + + Filename Reserved + Bestandsnaam is gereserveerd + + + UpdateDownloadedWidget - - %L1 B - %L1 B + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Herstart vereist</span></p></body></html> + + + Utility - + %n year(s) - %n jaar%n jaar + %n jaar%n jaren - + %n month(s) %n maand%n maanden - + %n day(s) %n dag%n dagen - + %n hour(s) - %n uur%n uur + %n uur%n uren - + %n minute(s) %n minuut%n minuten - + %n second(s) - %n seconde%n seconde(n) + %n seconde%n seconden - + %1 %2 %1 %2 + + + Shutting down %1 + %1 aan het afsluiten + - main.cpp + account loading - - System Tray not available - Systeemvak niet beschikbaar + + Error accessing the configuration file + Fout bij benaderen configuratiebestand - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 heeft een werkend systeemvak nodig. Als u XFCE draait volg <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">deze instructies</a>. Anders, installeer een systeemvak applicatie zoals 'trayer' and probeer het opnieuw. + + There was an error while accessing the configuration file at %1. + Er trad een fout op bij het benaderen configuratiebestand op %1 ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2 %7%8biliotheken Qt %3, %4%7Gebruikt virtuele bestandenplugin: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - %1 (Gebouwd met Qt %1) + + %1 (Built against Qt %2) + %1 (Gebouwd met Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Altijd lokaal beschikbaar + + + + Currently available locally + Momenteel lokaal beschikbaar + + + + Some available online only + Enkele alleen online beschikbaar + + + + Available online only + Alleen online beschikbaar + + progress @@ -4730,30 +3563,30 @@ Dit is een nieuwe, experimentele modus. Als je besluit deze te gebruiken, meld d Uploaded Geüpload - - - Server version downloaded, copied changed local file into conflict file - Serverversie gedownload, gewijzigde lokale bestand gekopieerd in conflictbestand - Deleted Verwijderd - - Moved to %1 - Verplaatst naar %1 - - - + Ignored Genegeerd - - Filesystem access error - Toegangsfout van het bestandssysteem + + Server version downloaded, local copy was backed up as conflict file + De versie van de server is gedownload en de lokale versie is hernoemd als een conflict bestand + + + + Renamed %1 to %2 + %1 is hernoemd naar %2 + + + + Moved %1 to %2 + %1 is verplaatst naar %2 @@ -4766,153 +3599,86 @@ Dit is een nieuwe, experimentele modus. Als je besluit deze te gebruiken, meld d Lokale metadata ge-uploaded - - + + Unknown Onbekend - + downloading downloaden - + uploading uploaden - + deleting verwijderen - + moving verplaatsen - + ignoring negerend - - + error fout - + updating local metadata Bijwerken lokale metadata - - theme - - - Status undefined - Ongedefinieerde status - - - - Waiting to start sync - In afwachting van synchronisatie - - - - Sync is running - Bezig met synchroniseren - - - - Sync Success - Synchronisatie geslaagd - - - - Sync Success, some files were ignored. - Synchronisatie geslaagd, sommige bestanden werden genegeerd. - - - - Sync Error - Synchronisatiefout - - - - Setup Error - Installatiefout - - - - Preparing to sync - Voorbereiden synchronisatie - - - - Aborting... - Aan het afbreken... - - - - Sync is paused - Synchronisatie is gepauzeerd - - utility - + Could not open browser Kon het browser niet openen - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? Er is een fout opgetreden bij het starten van de browser om naar %1 te gaan. Misschien is er geen standaard browser geconfigureerd? - + Could not open email client Kon e-mailclient niet openen - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? Er trad een fout op bij het starten van de e-mailclient om een nieuw bericht te maken. Misschien is er geen e-mailclient gedefinieerd? - - Always available locally - Altijd lokaal beschikbaar - - - - Some available online only - Enkele alleen online beschikbaar - - - - - Available online only - Alleen online beschikbaar - - - - Currently available locally - Momenteel lokaal beschikbaar - - - + Make always available locally Altijd lokaal beschikbaar maken - + Free up local space Lokale ruimte vrijmaken + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Enkele instellingen zijn geconfigureerd in een nieuwere versie van de applicatie en gebruiken functionaliteit die niet beschikbaar zijn in deze versie van de applicatie + + \ No newline at end of file diff --git a/translations/client_pl.ts b/translations/client_pl.ts index 9b5b3b24168..2e4bddedfc2 100644 --- a/translations/client_pl.ts +++ b/translations/client_pl.ts @@ -1,360 +1,424 @@ - + - CrashReporter + AccountBar - - Tomahawk Crash Reporter - + + Navigation bar + Pasek nawigacji - - <html><head/><body><p><br/></p></body></html> - + + Add Account + Dodaj konto - - Abort - Anuluj + + Activity + Aktywność - - Send this report - + + Settings + Ustawienia - - Don't send - + + Quit + Wyjdź + + + AppImageUpdateAvailableWidgetUi - - You can disable sending crash reports in the configuration dialog. - + + Skip this version + Pomiń tą wersję + + + AppProvider - - - - We cannot gather useful debug information on your system. - + + Failed to open %1 in web. Error: %2. + Nie udało się otworzyć %1 w sieci. Błąd: %2. + + + ChecksumHeader - - - - - - Close - Zamknij + + The checksum header is malformed: %1 + Nagłówek sumy kontrolnej jest zniekształcony: %1 - - Ready to send debug information (<a href="%1">view backtrace</a>). - + + The checksum header contained an unknown checksum type '%1' + Nagłówek sumy kontrolnej zawierał nieznany typ sumy kontrolnej „%1 + + + CommandLine - - Gathering debug information... - + + %1 version %2 +File synchronization desktop utility. + %1 wersja %2<byte value="xd"/> +Narzędzie pulpitu do synchronizacji plików. - - Uploaded %L1 of %L2 KB. - + + For more information, see %1 + link to homepage + Po dodatkowe informacje sprawdź %1 - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Rozpocznij z widocznym oknem głównym lub, jeśli jest już uruchomione, przenieś je na pierwszy plan. Domyślnie klient uruchamia się w tle. - - Failed to send crash info. - + + Quit the running instance. + Zakończenie uruchomionej instancji. - - - FileSystem - - - - - Can't rename %1, the file is currently in use - + + Write log to file (use - to write to stdout). + Zapis dziennika do pliku (użyj - do zapisu na stdout). - - Could not make directories in trash - Nie można utworzyć folderów w Koszu + + Write each sync log output in a new file in folder. + Zapisz dane wyjściowe każdego dziennika synchronizacji w nowym pliku w folderze. - - - Could not move '%1' to '%2' - Nie można przenieść '%1' do '%2' + + Flush the log file after every write. + Opróżnia plik dziennika po każdym zapisie. - - Moving to the trash is not implemented on this platform - Przenoszenie do Kosza nie jest dostępne na tej platformie + + Output debug-level messages in the log. + Wyświetla w dzienniku komunikaty na poziomie debugowania. - - Error removing '%1': %2 - Błąd usuwania '%1': %2 + + Enable debug mode. + Włącz tryb debugowania. - - Could not remove folder '%1' - Nie można usunąć folderu '%1' + + --logfile and --logdir are mutually exclusive + --logfile i --logdir wzajemnie się wykluczają - FolderWizardSourcePage + CommonStrings - - Form - Formularz + + file manager + Menadżer plików - - Pick a local folder on your computer to sync - Wybierz folder lokalny na komputerze do synchronizacji + + Show in %1 + Pokaż w %1 - - &Choose... - &Wybierz... + + Show in web browser + Pokaż w przeglądarce - - - FolderWizardTargetPage - - Form - Formularz + + Copy + Kopiuj + + + + %n Filter(s) + %n Filtr%n Filtry%n Filtry%n Filtry - - Select a remote destination folder - Wybierz zdalny folder docelowy + + folder + folder - - Create Folder - Utwórz katalog + + space + przestrzeń - - Refresh - Odśwież + + folders + foldery - - Folders - Katalogi + + spaces + przestrzenie - - TextLabel - Etykieta tekstowa + + Folder + Folder - - - NotificationWidget - - Form - Formularz + + Space + Przestrzeń + + + FetchUserInfoJobFactory - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet + + Failed to retrieve user info + Nie udało się pobrać informacji o użytkowniku + + + FileSystem - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + + + + Can't rename %1, the file is currently in use + Nie można zmienić nazwy %1, plik jest obecnie używany - - TextLabel - Etykieta + + Could not remove folder + Nie udało się usunąć folderu - OCC::AboutDialog + FolderItem - - About - O aplikacji + + + Offline + Offline - - TextLabel - Etykieta + + + Unavailable: the space is no longer available on the server + Niedostępny: miejsce nie jest już dostępne na serwerze - - Versions - Wersje + + + Sync paused + Synchronizacja wstrzymana - - About %1 - O %1 + + Synced + Zsynchronizowane - - - OCC::AbstractNetworkJob - - Connection timed out - Przekroczono czas odpowiedzi + + Sync failed + Nieudana synchronizacja - - Unknown error: network reply was deleted - Nieznany błąd: odpowiedź sieci została usunięta + + Sync pending + Synchronizacja w toku - - Server replied "%1 %2" to "%3 %4" - Serwer odpowiedział "%1 %2" do "%3 %4" + + Syncing %1 of %2 (%3 % + Synchronizacja %1 z %2 (%3 %) + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, status synchronizacji: %2 + first argument is the displayed name of the folder. - OCC::Account + FolderManagementUtils - - %1@%2 - %1@%2 + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + Ścieżka „%1” jest za długa. Włącz długie ścieżki w ustawieniach systemu Windows lub wybierz inny folder. + + + + Local folder %1 does not exist. + Folder lokalny %1 nie istnieje. + + + + %1 is not a folder. + %1 nie jest folderem. + + + + %1 is not readable. + %1 jest nie do odczytu. + + + + %1 is not writable. + %1 nie jest zapisywalny. - OCC::AccountSettings + FolderWizard - - Form - Formularz + + <b>Error:</b> %1 + <b>Błąd:</b> %1 - - Re-open Browser - Otwórz przeglądarkę ponownie + + <b>Warning:</b> %1 + <b>Ostrzeżenie:</b> %1 - - ... - ... + + <b>Error:</b><ul>%1</ul> + <b>Błąd:</b><ul>%1</ul> - - Storage space: ... - Przestrzeń: ... + + <b>Warning:</b><ul>%1</ul> + <b>Ostrzeżenie:</b><ul>%1</ul> + + + FolderWizardSourcePage - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Niezaznaczone katalogi zostaną <b>usunięte</b> z lokalnego systemu plików i nie będą już więcej synchronizowane na tym komputerze. + + Form + Formularz - - Synchronize all - Synchronizuj wszystko + + Pick a local folder on your computer to sync + Wybierz folder lokalny na komputerze do synchronizacji - - Synchronize none - Nie synchronizuj niczego + + Browse… + Przeglądać… + + + FolderWizardTargetPage - - Apply manual changes - Zastosuj ręczne zmiany + + Form + Formularz - - Apply - Zastosuj + + Select a remote destination folder + Wybierz zdalny folder docelowy - - - - - Cancel - Anuluj + + Create Folder + Utwórz katalog - - Connected with <server> as <user> - Połączony z <server> jako <user> + + Refresh + Odśwież - - No account configured. - Brak skonfigurowanych kont. + + Folders + Katalogi + + + OCC::AboutView - - Remove - Usuń + + About + O aplikacji - - Account - Konto + + Versions + Wersje + + + OCC::AbstractNetworkJob - - Choose what to sync - Wybierz co synchronizować + + Connection timed out + Przekroczono czas odpowiedzi - - Force sync now - Zsynchronizuj teraz + + Unknown error: network reply was deleted + Nieznany błąd: odpowiedź sieci została usunięta - - Restart sync - Uruchom ponownie synchronizację + + Server replied "%1 %2" to "%3 %4" + Serwer odpowiedział "%1 %2" do "%3 %4" + + + OCC::Account - - Remove folder sync connection - Usuń folder połączenia synchronizacji + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - Folder creation failed - Nie udało się stworzyć katalogu + + Choose what to sync + Wybierz co synchronizować - - <p>Could not create local folder <i>%1</i>. - <p>Nie można stworzyć lokalnego katalogu <i>%1</i>. + + + Sync now + Synchronizuj teraz - - Confirm Folder Sync Connection Removal - Potwierdź usunięcie połączenia folderów synchronizacji + + Manage subfolder sync + Zarządzaj synchronizacją podfolderów - - Remove Folder Sync Connection - Usuń folder połączenia synchronizacji + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + Usuń synchronizację %1 - + Disable virtual file support? Wyłączyć obsługę plików wirtualnych? - + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. The only advantage of disabling virtual file support is that the selective sync feature will become available again. @@ -368,572 +432,551 @@ Ta czynność spowoduje przerwanie obecnie działającej synchronizacji. - + Disable support Wyłącz wsparcie - - Sync Running - Synchronizacja uruchomiona + + Restart sync + Uruchom ponownie synchronizację - - The syncing operation is running.<br/>Do you want to terminate it? - Operacja synchronizacji jest uruchomiona.<br>Czy chcesz ją zakończyć? + + Resume sync + Przywróć synchronizację - - %1 in use - %1 w użyciu + + Pause sync + Zatrzymaj synchronizację - - %1 as <i>%2</i> - %1 jako <i>%2</i> + + Deactivate virtual files + Deactivate virtual files - - Connected to %1. - Podłączony do %1. + + Activate virtual files + Aktywuj pliki wirtualne - - Server %1 is temporarily unavailable. - Serwer %1 jest tymczasowo niedostępny. + + Internet connection is metered + Połączenie internetowe jest mierzone - - Server %1 is currently in maintenance mode. - Serwer %1 jest obecnie w trybie konserwacji. + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + Synchronizacja jest wstrzymana, ponieważ połączenie internetowe jest połączeniem taryfowym<p>Czy naprawdę chcesz teraz wymusić synchronizację? - - Signed out from %1. - Wylogowano z %1. + + Sync Running + Synchronizacja uruchomiona - - Connecting to %1... - Łączenie do %1... + + The sync operation is running.<br/>Do you want to stop it? + Operacja synchronizacji jest uruchomiona.<br/>Czy chcesz ją zatrzymać? - - No connection to %1 at %2. - Brak połączenia do %1 z %2. + + Sync paused by user + Synchronizacja wstrzymana przez użytkownika - - Log in - Zaloguj + + Confirm Folder Sync Connection Removal + Potwierdź usunięcie połączenia folderów synchronizacji - - There are folders that were not synchronized because they are too big: - Te foldery nie zostały zsynchronizowane ponieważ są zbyt duze: + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Czy na pewno chcesz zatrzymać synchronizację folderu <i>%1</i>?</p><p><b>Uwaga:</b> ta operacja <b>nie</b> usunie plików z klienta.</p> - - There are folders that were not synchronized because they are external storages: - Te foldery nie zostały zsynchronizowane ponieważ znajdują się w pamięci zewnętrznej: + + Remove Folder Sync Connection + Usuń folder połączenia synchronizacji - - There are folders that were not synchronized because they are too big or external storages: - Te foldery nie zostały zsynchronizowane ponieważ są zbyt duże lub znajdują się w pamięci zewnętrznej: + + + Cancel + Anuluj + + + OCC::AccountFoldersView - - Confirm Account Removal - Potwierdź usunięcie konta + + Sync options menu + Menu opcji synchronizacji - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Czy na pewno chcesz usunąć połączenie z kontem <i>%1</i>?</p><p><b>Uwaga:</b> ta operacja <b>nie</b> usunie plików klienta.</p> + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 synchronizacja - - Remove connection - Usuwanie połączenia + + Manage your synced %1. + Zarządzaj zsynchronizowanym %1. - - Open folder - Otwórz katalog + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Dodaj nową synchronizację %1… - - - Log out - Wyloguj + + %1 list view + %1 widok listy - - Open local folder - Otwórz folder lokalny + + Navigate the %1 list using the up and down arrows + Poruszaj się po liście %1 za pomocą strzałek w górę i w dół - - Open folder in browser - Otwórz folder w przeglądarce + + %1 out of %2 %3 are synchronized + %1 z %2 %3 jest zsynchronizowanych + + + OCC::AccountModalWidget - - Resume sync - Przywróć synchronizację + + + Form + Formularz + + + OCC::AccountPlaceholderWidget - - Pause sync - Zatrzymaj synchronizację + + %1 logo + %1 logo - - Availability - Dostępność + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + - - Disable virtual file support... - Wyłącz obsługę plików wirtualnych... + + Additional logo defined by the organization + Dodatkowe logo zdefiniowane przez organizację + + + OCC::AccountView - - Enable virtual file support%1... - Włącz obsługę wirtualną pliku %1 + + Form + Formularz - - (experimental) - (eksperymentalne) + + Manage Account + Zarządzaj kontem - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Czy na pewno chcesz zatrzymać synchronizację folderu <i>%1</i>?</p><p><b>Uwaga:</b> ta operacja <b>nie</b> usunie plików z klienta.</p> + + Preparing the account + Przygotowywanie konta - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) z %2 w użyciu. Niektóre foldery, w tym zamontowane lub udostępnione foldery w sieci, mogą mieć różne limity. + + Sync connections + Synchronizuj połączenia - - %1 of %2 in use - %1 z %2 w użyciu + + Account options menu + Menu opcji konta + + + OCC::AccountViewController - - Currently there is no storage usage information available. - Obecnie nie ma dostępnych informacji o wykorzystaniu pamięci masowej. + + + Log in + Zaloguj - - The server version %1 is unsupported! Proceed at your own risk. - Wersja %1 serwera nie jest obsługiwana! Wchodzisz na własne ryzyko. + + Reconnect + Połącz ponownie - - Obtaining authorization from the browser. - Uzyskaj autoryzację z przeglądarki + + Remove + Usuń - - Server configuration error: %1 at %2. - Błąd konfiguracji serwera: %1 w %2. + + Log out + Wyloguj - - No %1 connection configured. - Połączenie %1 nie skonfigurowane. + + Confirm Account Removal + Potwierdź usunięcie konta - - - OCC::AccountState - - Url update requested for %1 - + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Czy na pewno chcesz usunąć połączenie z kontem <i>%1</i>?</p><p><b>Uwaga:</b> ta operacja <b>nie</b> usunie plików klienta.</p> - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - + + Remove connection + Usuwanie połączenia - - Change url permanently to %1 - + + Cancel + Anuluj - - Reject - + + The server version %1 is unsupported! Proceed at your own risk. + Wersja %1 serwera nie jest obsługiwana! Wchodzisz na własne ryzyko. - - Signed out - Odłączony - - - - Disconnected - Rozłączony - - - + Connected Połączony - - Service unavailable - Usługa niedostępna - - - - Maintenance mode - Tryb konserwacji + + Server is temporarily unavailable. + - - Network error - Błąd sieci + + Server is currently in maintenance mode. + - - Configuration error - Błąd konfiguracji + + Signed out + Odłączony - - Asking Credentials - Zapytanie o poświadczenia + + Updating credentials… + Aktualizowanie danych uwierzytelniających… - - Unknown account state - Status konta nieznany + + Captive portal prevents connections to the server. + Portal autoryzacyjny uniemożliwia połączenie z serwerem. - - - OCC::ActivityListModel - - %1 %2 on %3 - %1 %2 na %3 + + Sync is paused due to metered internet connection. + - - Activity - Aktywność + + Connecting… + Łączenie... - - Account - Konto + + Server configuration error + Błąd konfiguracji serwera - - Time - Czas + + Disconnected + Rozłączony - - Local path + + Invalid connection status - OCC::ActivitySettings + OCC::AccountsGuiController - - - Server Activity - Aktywność serwera + + + New account failure + Błąd nowego konta - - Sync Protocol - Protokół synchronizacji + + The information required to create a new account is incomplete. Please run the wizard again. + Informacje wymagane do utworzenia nowego konta są niekompletne. Uruchom kreatora ponownie. - - Not Synced - Niezsynchronizowany + + Accounts + Konta - - Not Synced (%1) - %1 is the number of not synced files. - Niezsynchronizowany (%1) + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + Nie można utworzyć konta z powodu błędu: +%1 +Sprawdź dostępność serwera, a następnie ponownie uruchom kreatora. - OCC::ActivityWidget - - - Form - Formularz - - - - - - TextLabel - Etykieta - - - - Filter - Filtr - - - - Server Activities - Działania serwera - - - - Notifications - Powiadomienia - - - - %n notifications(s) for %1. - %n powiadomienie dla %1.%n powiadomienia dla %1.%n powiadomień dla %1.%n powiadomienie(a) dla %1. - - - - %n notifications(s) for %1 and %2. - %n powiadomienie dla %1 and %2.%n powiadomienia dla %1 and %2.%n powiadomień dla %1 and %2.%n powiadomienie(a) dla %1 and %2. - - - - New notifications for %1, %2 and other accounts. - Nowe powiadomienia dla %1, %2 i innych kont - - - - Open the activity view for details. - Otwórz aktywność dla szczegółów. - + OCC::ActivitySettings - - Copy to clipboard - Skopiuj do schowka + + Local Activity + Aktywność lokalna - - Show in file browser - + + Not Synced + Niezsynchronizowany - - <br/>Account %1 does not have activities enabled. - <br/>Użytkownik %1 nie ma włączonej historii aktywności. + + Not Synced (%1) + %1 is the number of not synced files. + Niezsynchronizowany (%1) - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Kontynuowanie oznacza <b> usunięcie tych ustawień </b> - - - - Continuing will mean <b>ignoring these settings</b>. - Kontynuowanie oznacza <b> zignorowanie tych ustawień </b> - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Część ustawień została skonfigurowana w nowszej wersji klienta i nie jest dostępna w tej wersji <br><br>%1<br><br> Aktualna konfiguracja została zarchiwizowana w <i>%2</i> - + OCC::AdvancedSettingsPageController - - Quit - Wyjdź + + Advanced settings + Ustawienia zaawansowane - - Continue - Kontynuuj + + Sync and download options + Opcje synchronizacji i pobierania - - Error accessing the configuration file - Błąd przy dostępie do pliku konfiguracji + + Download location + Lokalizacja pobierania - - There was an error while accessing the configuration file at %1. - Wystąpił błąd podczas próby dostępu do pliku konfiguracyjnego %1. + + Sync and download files as you use them (saves hard drive space) + Synchronizuj i pobieraj pliki w miarę ich używania (oszczędza miejsce na dysku twardym) - - Quit %1 - Wyjdź %1 + + Automatically sync and download folders and files + Automatyczna synchronizacja i pobieranie folderów i plików - - %1 version %2 -File synchronization desktop utility. - + + Sync and download folders manually + Synchronizuj i pobieraj foldery ręcznie - - For more information, see http://www.owncloud.org - + + Download location on the local machine + Lokalizacja pobierania na komputerze lokalnym - - Show the settings dialog while starting. - + + Browse… + Przeglądać… - - Quit the running instance. - + + Browse for a download location + Przeglądaj lokalizację pobierania - - Write log to file (use - to write to stdout). - + + Invalid local download directory %1: %2 + Nieprawidłowy lokalny katalog pobierania %1: %2 - - Write each sync log output in a new file in folder. - + + your user directory may not be chosen as the sync root. + twój katalog użytkownika może nie być wybrany jako katalog główny synchronizacji. - - Remove logs older than <hours> hours (to be used with --logdir). - + + path is not located on a supported Apple File System. + ścieżka nie znajduje się w obsługiwanym systemie plików Apple. - - Flush the log file after every write. - + + path must be absolute. + ścieżka musi być bezwzględna. - - Output debug-level messages in the log. - + + selected path does not support using virtual file system. %1 + wybrana ścieżka nie obsługuje używania wirtualnego systemu plików. %1 - - - Override UI language. - + + Select sync root + Wybierz synchronizację root + + + OCC::AppImageUpdateAvailableWidget - - Use the given configuration folder. - + + Installed version: %1 + Zainstalowana wersja: %1 - - Enable debug mode. - + + Available update: %1 + Dostępna aktualizacja: %1 - - Virtual file system file to be opened (optional). - + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Dostępna jest aktualizacja dla tego AppImage %1. Czy chcesz zainstalować tę aktualizację? + +Aktualizacja zostanie przeprowadzona w tle i zastąpi bieżący plik AppImage. Aby zakończyć aktualizację, należy ponownie uruchomić aplikację. + + + OCC::Application - - [<vfs file>] - + + Quit + Wyjdź + + + OCC::AuthSuccessPageController - - Invalid path passed to --confdir - + + Application Name Logo %1 + Nazwa aplikacji Logo %1 - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - + + You're all set! Open %1 and get started. + Wszystko gotowe! Otwórz %1 i zacznij. - - Available translations: %1 - + + Additional logo defined by the organization + Dodatkowe logo zdefiniowane przez organizację - OCC::AuthenticationDialog - - - Authentication Required - Wymagana autoryzacja - - - - Enter username and password for '%1' at %2. - Wprowadź użytkwownika i hasło dla '%1' w %2. - + OCC::ButtonDelegate - - &User: - &Użytkownik: + + %1 options button + %1 przycisk opcji - - &Password: - &Hasło + + Menu button with %1 options. Use the space key to show the menu + Przycisk menu z %1 opcjami. Użyj spacji, aby wyświetlić menu. OCC::ConnectionValidator - + No ownCloud account configured Nie skonfigurowano konta ownCloud - - The configured server for this client is too old - Konfigurowany serwer dla tego klienta jest za stary - - - + Please update to the latest server and restart the client. Proszę zaaktualizować serwer do najnowszej wersji i zrestartować klienta. - + Authentication error: Either username or password are wrong. Błąd autentykacji: nazwa użytkownika lub hasło są nieprawidłowe. - + timeout wygaśnięcie - + + No account configured + Brak skonfigurowanego konta + + + The provided credentials are not correct Podane dane logowania są nieprawidłowe + + + The configured server for this client is too old. + Skonfigurowany serwer dla tego klienta jest zbyt stary. + OCC::CredentialJob - + Failed to parse credentials %1 - + Nie udało się przeanalizować danych uwierzytelniających %1 + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + Serwer nie obsługuje protokołu OAuth + + + + Server did not ask for authorization + Serwer nie poprosił o autoryzację + + + + Failed to determine authentication type: %1 + Nie udało się określić typu uwierzytelniania: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + Otrzymano nieprawidłową odpowiedź z serwera. OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + Konto zostało usunięte zanim mogliśmy rozpocząć zadanie propfind + + + + Server error: PROPFIND reply is not XML formatted! Błąd serwera: Odpowiedź PROPFIND nie jest sformatowana w formacie XML! @@ -941,27 +984,22 @@ File synchronization desktop utility. OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 Błąd podczas otwierania katalogu %1 - + Directory not accessible on client, permission denied Katalog niedostępny dla klienta, brak zezwolenia - + Directory not found: %1 Nie znaleziono katalogu: %1  - - Filename encoding is not valid - Kodowanie nazw plików nie jest poprawne. - - - + Error while reading directory %1 Błąd podczas odczytu katalogu %1 @@ -969,148 +1007,137 @@ File synchronization desktop utility. OCC::ExpandingHeaderView - + Reset column sizes - + Zresetuj rozmiary kolumn - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - Folder lokalny %1 nie istnieje. + + server sent invalid content type: %1. + serwer wysłał nieprawidłowy typ zawartości: %1. - - %1 should be a folder but is not. - %1 powinien być katalogiem, ale nie jest. + + could not parse Capabilities from server. + Nie można przeanalizować możliwości serwera. - - %1 is not readable. - %1 jest nie do odczytu. + + reply sent for different subject (server): %1 + odpowiedź wysłana dla innego tematu (serwer): %1 + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 został usunięty. - + %1 has been updated. %1 names a file. %1 został uaktualniony. - + %1 has been renamed to %2. %1 and %2 name files. %1 zmienił nazwę na %2. - + %1 has been moved to %2. %1 został przeniesiony do %2. - + %1 and %n other file(s) have been removed. - %1 i %n inny plik został usunięty.%1 i %n inne pliki zostały usunięte.%1 i %n innych plików zostało usuniętych.%1 i %n innych plików zostało usuniętych. + - - %1 is not writable. - + + The folder has been disabled or removed from the server + Folder został wyłączony lub usunięty z serwera - + %1 and %n other file(s) have been added. - %1 i %n innych plik(ów) zostało dodanych.%1 i %n innych plik(ów) zostało dodanych.%1 i %n innych plik(ów) zostało dodanych.%1 i %n innych plik(ów) zostało dodanych. + - + %1 has been added. %1 names a file. %1 został dodany. - + %1 and %n other file(s) have been updated. - %1 i %n inny plik został zaktualizowany.%1 i %n inne pliki zostały zaktualizowane.%1 i %n innych plików zostało zaktualizowanych.%1 i %n innych plików zostało zaktualizowanych. + - + %1 has been renamed to %2 and %n other file(s) have been renamed. - Zmieniono nazwę %1 na %2 oraz %n innemu plikowi została zmieniona nazwa.Zmieniono nazwę %1 na %2 oraz %n innym plikom została zmieniona nazwa.%1 has been renamed to %2 and %n other file(s) have been renamed.%1 has been renamed to %2 and %n other file(s) have been renamed. + - + %1 has been moved to %2 and %n other file(s) have been moved. - Przeniesiono %1 do %2 oraz przeniesiono %n inny plik.Przeniesiono %1 do %2 oraz przeniesiono %n inne pliki.Przeniesiono %1 do %2 oraz przeniesiono %n innych plików.Przeniesiono %1 do %2 oraz przeniesiono %n innych plików. + - - %1 has and %n other file(s) have sync conflicts. - %1 plik ma błąd synchronizacji%1 i %n innych plików mają konflikt synchronizacji.%1 i %n innych plików mają konflikt synchronizacji.%1 i %n innych plików mają konflikt synchronizacji. + + %1 and %n other file(s) have sync conflicts. + - + %1 has a sync conflict. Please check the conflict file! %1 ma konflikt synchronizacji. Sprawdź konfliktujący plik! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - Plik %1 nie może zostać zsynchronizowany z powodu błędów. Sprawdź szczegóły w logu..%1 i %n innych plików nie mogą zostać zsynchronizowane z powodu błędów. Sprawdź szczegóły w logu..%1 i %n innych plików nie mogą zostać zsynchronizowane z powodu błędów. Sprawdź szczegóły w logu..%1 i %n innych plików nie mogą zostać zsynchronizowane z powodu błędów. Sprawdź szczegóły w logu.. + - + %1 could not be synced due to an error. See the log for details. %1 nie może zostać zsynchronizowany z powodu błędu. Zobacz szczegóły w logu. - + Sync Activity Aktywności synchronizacji - - Could not read system exclude file - Nie można przeczytać pliku wyłączeń - - - - A new folder larger than %1 MB has been added: %2. - - Nowy folder większy niż %1MB został dodany: %2 - - - - - A folder from an external storage has been added. - - Folder z pamięci zewnętrznej został dodany . - + + Switching VFS mode on folder '%1' + Przełączanie trybu VFS w folderze '%1' - - Please go in the settings to select it if you wish to download it. - Przejdź do ustawień żeby go zaznaczyć i pobrać. + + Could not read system exclude file + Nie można przeczytać pliku wyłączeń - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. Folder %1 został utworzony ale poprzednio został wykluczony z synchronizacji. Dane wewnątrz folderu nie będą zsynchronizowane. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. Plik %1 został utworzony ale poprzednio został wykluczony z synchronizacji. Nie będzie zsynchronizowany. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1120,3583 +1147,2409 @@ This means that the synchronization client might not upload local changes immedi Oznacza to, że klient synchronizacji może nie przesyłać zmian lokalnych od razu i zamiast tego będzie tylko skanować w poszukiwaniu zmian lokalnych i przesyłać je sporadycznie (domyślnie co dwie godziny). + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Wszystkie pliki z folderu '%1' zostały usunięte z serwera. -W momencie synchronizacji zostaną usunięte z lokalnego katalogu, co spowoduje ich niedostępność, chyba, że posiadasz prawo do przywracania. -Jeśli zdecydujesz się zatrzymać pliki i posiadasz odpowiednie uprawnienia, zostaną one ponownie przesłane na serwer. -Jeśli zdecydujesz je usunąć, nie będą więcej dostępne. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Wszystkie pliki w lokalnym folderze synchronizacji "%1" zostały usunięte. Te pliki zostaną zsynchronizowane z serwerem, przez co będą niedostępne, chyba że zostaną przywrócone. -Czy na pewno chcesz zsynchronizować te działania z serwerem? -Jeśli to był wypadek i zdecydujesz się zatrzymać pliki, zostaną one zsynchronizowane z serwera. - - - - Remove All Files? - Usunąć wszystkie pliki? - - - - Remove all files - Usuń wszystkie pliki + + There are unresolved conflicts. + Istnieją nierozwiązane konflikty. - - Keep files - Pozostaw pliki + + Sync error: %1 + Błąd synchronizacji: %1 OCC::FolderMan - + Could not reset folder state Nie udało się zresetować stanu folderu - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Stary sync journal '%1' został znaleziony, lecz nie mógł być usunięty. Proszę się upewnić, że żaden program go obecnie nie używa. - - (backup) - (kopia zapasowa) - - - - (backup %1) - (kopia zapasowa %1) + + Unsupported filesystem + Nieobsługiwany system plików - - Undefined State. - Niezdefiniowany stan + + Account disconnected or paused + Konto odłączone lub wstrzymane - - Waiting to start syncing. - Poczekaj na rozpoczęcie synchronizacji. + + User triggered force sync + Wymuszona synchronizacja uruchamiana przez użytkownika - - Preparing for sync. - Przygotowuję do synchronizacji + + Folder is about to be removed + Folder zostanie wkrótce usunięty - - Sync is running. - Synchronizacja w toku + + (backup) + (kopia zapasowa) - - Sync was successful, unresolved conflicts. - Synchronizacja udana, nierozwiązane konflikty. + + (backup %1) + (kopia zapasowa %1) - - Last Sync was successful. - Ostatnia synchronizacja zakończona powodzeniem. + + The selected path is not a folder. + Wybrana ścieżka nie jest folderem. - - Setup Error. - Błąd ustawień. + + The folder %1 is used in a folder sync connection. + Folder %1 jest używany w połączeniu synchronizacji folderów - - User Abort. - Użytkownik anulował. + + Folder '%1' is already in use by application %2. + Folder '%1' jest już używany przez aplikację %2. - - Sync is paused. - Synchronizacja wstrzymana + + There is already a sync from the server to this local folder. + Istnieje już synchronizacja z serwera do tego folderu lokalnego. - - %1 (Sync is paused) - %1 (Synchronizacja jest zatrzymana) + + The local folder %1 already contains a folder used in a folder sync connection. + Folder lokalny %1 zawiera już folder używany w połączeniu synchronizacji folderów. - - No valid folder selected! - Nie wybrano poprawnego folderu. + + The local folder %1 is already contained in a folder used in a folder sync connection. + Folder lokalny %1 znajduje się już w folderze używanym w połączeniu synchronizacji folderów. - - The folder %1 is used in a folder sync connection! - + + No valid folder selected. + Nie wybrano prawidłowego folderu. - - The selected path does not exist! - Wybrana ścieżka nie istnieje! + + The selected path does not exist. + Wybrana ścieżka nie istnieje. - - The selected path is not a folder! - Wybrana ścieżka nie jest katalogiem! + + You have no permission to write to the selected folder. + Nie masz uprawnień do zapisu w wybranym folderze. - - You have no permission to write to the selected folder! - Nie masz uprawnień, aby zapisywać w tym katalogu! + + Folder '%1' is already in use by another account. + Folder '%1' jest już używany przez inne konto. - - There is already a sync from the server to this local folder. Please pick another local folder! - Istnieje już synchronizacja z serwera do tego lokalnego folderu. Proszę wybrać inny folder lokalny! + + On macOS, only the Apple File System is supported. + W systemie macOS obsługiwany jest tylko system plików Apple. + + + OCC::FolderModelController - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Lokalny folder %1 już zawiera folder użyty na potrzeby synchronizacji. Proszę wybrać inny folder lokalny. + + %1 options + %1 opcji - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Lokalny folder %1 już zawiera folder użyty na potrzeby synchronizacji. Proszę wybrać inny folder lokalny. + + Menu button with %1 options. Use the space key to pop the %2 options menu + Przycisk menu z opcjami %1. Użyj spacji, aby wyświetlić menu opcji %2. + + + OCC::FolderWatcher - - %1 Please pick another one! - + + The watcher did not receive a test notification. + Obserwator nie otrzymał powiadomienia testowego. + + + OCC::FolderWatcherPrivate - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Ten problem zazwyczaj pojawia się, gdy zegarki inotify są rozładowane. Szczegóły znajdziesz w FAQ. - OCC::FolderStatusDelegate + OCC::FolderWizard + + + Virtual files are not available for the selected folder + Pliki wirtualne nie są dostępne dla wybranego folderu + - + Add Folder Sync Connection Dodaj folder połączenia synchronizacji - - File - Plik + + Add Sync Connection + Dodaj połączenie synchronizacji - OCC::FolderStatusModel + OCC::FolderWizardLocalPath - - You need to be connected to add a folder - Musisz być podłączony, by dodać folder. + + Click to select a local folder to sync. + Kliknij, aby wybrać folder lokalny do synchronizacji. - - Click this button to add a folder to synchronize. - Kliknij ten przycisk, by dodać folder do synchronizacji. + + Enter the path to the local folder. + Wpisz ścieżkę do folderu lokalnego. - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Select the local folder + Wybierz folder lokalny + + + OCC::FolderWizardSelectiveSync - - Error while loading the list of folders from the server. - Wystąpił błąd podczas pobierania listy folderów z serwera. + + Use virtual files instead of downloading content immediately + Zamiast od razu pobierać treści, korzystaj z plików wirtualnych + + + OCC::GETFileJob - - Virtual file support is enabled. - Obsługa plików wirtualnych jest włączona. + + Connection Timeout + Limit czasu połączenia - - Signed out - Odłączony + + No E-Tag received from server, check Proxy/Gateway + Nie otrzymano E-Tag z serwera, sprawdź Proxy/Bramę - - Fetching folder list from server... - Pobieranie listy folderów z serwera. + + We received a different E-Tag for resuming. Retrying next time. + Otrzymaliśmy inny E-Tag wznowienia. Spróbuje ponownie następnym razem. - - There are unresolved conflicts. Click for details. - Istnieją nierozwiązane konflikty. Kliknij aby poznać szczegóły. + + We received an unexpected download Content-Length. + Otrzymaliśmy nieoczekiwane pobranie Content-Length. - - Checking for changes in remote '%1' - Sprawdzanie zmian w lokalizacji zdalnej '%1' + + Server returned wrong content-range + Serwer zwrócił błędną zakres zawartości + + + OCC::GraphApi::Space - - Checking for changes in local '%1' - Sprawdzanie zmian w lokalizacji lokalnej '%1' + + Personal + Osobiste - - Reconciling changes - Uzgodnienie zmian + + Shares + Udziały + + + OCC::IgnoreListEditor - - , '%1' - Build a list of file names - , '%1' + + Ignored Files Editor + Edytor ignorowanych plików - - '%1' - Argument is a file name - '%1' + + Files Ignored by Patterns + Pliki ignorowane zgodnie z szablonem - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synchronizowanie %1 + + Add + Dodaj - - - , - , + + Pattern + Szablon - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - pobieranie %1/s + + Allow Deletion + Pozwól na usuwanie - - ↓ %1/s - ↓ %1/s + + Remove + Usuń - - ↑ %1/s - ↑ %1/s + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Pliki lub foldery pasujące do wzorca nie będą synchronizowane. Zmiany zaczną obowiązywać przy następnej synchronizacji folderów. + +Elementy, których usunięcie jest dozwolone, zostaną usunięte, jeśli uniemożliwią usunięcie katalogu. Jest to przydatne w przypadku metadanych. - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - wysyłanie %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 z %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - Plik %3 z %4, pozostało czasu %5 (%1 z %2) - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 z %2, plik %3 z %4 - - - - file %1 of %2 - plik %1 z %2 - - - - Waiting... - Czekaj... - - - - Waiting for %n other folder(s)... - Oczekiwanie na %n inny folder...Oczekiwanie na %n inne foldery...Oczekiwanie na %n innych folderów...Oczekiwanie na %n innych folderów... - - - - Preparing to sync... - Przygotowanie do synchronizacji ... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Dodaj folder połączenia synchronizacji - - - - Add Sync Connection - Dodaj połączenie synchronizacji - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Kliknij, aby wybrać folder lokalny do synchronizacji. - - - - Enter the path to the local folder. - Wpisz ścieżkę do folderu lokalnego. - - - - Select the source folder - Wybierz katalog źródłowy - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Dodaj zdalny katalog - - - - Enter the name of the new folder to be created below '%1': - Wpisz nazwę dla nowego katalogu, utworzonego poniżej '%1': - - - - Folder was successfully created on %1. - Folder został utworzony pomyślnie na %1 - - - - Authentication failed accessing %1 - Błąd autentykacji w dostępie do %1 - - - - Failed to create the folder on %1. Please check manually. - Nie udało się utworzyć folderu na %1. Proszę sprawdzić ręcznie. - - - - Failed to list a folder. Error: %1 - Nie udało się wyświetlić folderu. Błąd: %1 - - - - Choose this to sync the entire account - Wybierz to, aby zsynchronizować całe konto - - - - This folder is already being synced. - Ten katalog jest już synchronizowany. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Synchronizujesz już <i>%1</i>, który jest folderem nadrzędnym <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - (eksperymentalne) - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Ostrzeżenie:</b> %1 - - - - <b>Warning:</b> - <b>Ostrzeżenie:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Nie otrzymano E-Tag z serwera, sprawdź Proxy/Bramę - - - - We received a different E-Tag for resuming. Retrying next time. - Otrzymaliśmy inny E-Tag wznowienia. Spróbuje ponownie następnym razem. - - - - We received an unexpected download Content-Length. - Otrzymaliśmy nieoczekiwane pobranie Content-Length. - - - - Server returned wrong content-range - Serwer zwrócił błędną zakres zawartości - - - - OCC::GETJob - - - Connection Timeout - Limit czasu połączenia - - - - OCC::GeneralSettings - - - Form - Formularz - - - - General Settings - Ustawienia ogólne - - - - Language - Język - - - - Sync hidden files - Synchronizuj pliki ukryte - - - - Log Settings - Ustawienia Log - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Sieć - - - - Updates - Aktualizacje - - - - stable - stable - - - - beta - beta - - - - About - O aplikacji - - - - Advanced - Zaawansowane - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Zapytaj o potwierdzenie przed synchronizacją folderów większych niż - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Zapytaj o potwierdzenie przed synchronizacją zewnętrznych skladowisk plikow - - - - &Launch on System Startup - &Uruchamiaj podczas startu systemu - - - - Show &Desktop Notifications - Pokaż &powiadomienia na pulpicie - - - - &Update Channel - Kanał akt&ualizacji - - - - Edit &Ignored Files - Edytuj pliki &ignorowane (pomijane) - - - - Show sync folders in &Explorer's Navigation Pane - Pokaż synchronizowane katalogi w Oknie nawigacji &Eksploratora plików - - - - S&how crash reporter - Pokaż &raport awarii - - - - &Restart && Update - &Zrestartuj && Aktualizuj - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - Czy zmienić kanał aktualizacji? - - - - Warning - Ostrzeżenie - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - Zmiana kanału aktualizacji - - - - Cancel - Anuluj - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Wprowadź %1 hasło:<br><br>Użytkownik:%2<br>Konto:%3<br> - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - Wprowadź hasło - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Kliknij tutaj</a>aby zażądać hasła do aplikacji w przeglądarce. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Edytor ignorowanych plików - - - - Files Ignored by Patterns - Pliki ignorowane zgodnie z szablonem - - - - Add - Dodaj - - - - Pattern - Szablon - - - - Allow Deletion - Pozwól na usuwanie - - - - Remove - Usuń - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Pliki lub foldery pasujące do wzorca nie będą synchronizowane. Zmiany zaczną obowiązywać przy następnej synchronizacji folderów. - -Elementy, których usunięcie jest dozwolone, zostaną usunięte, jeśli uniemożliwią usunięcie katalogu. Jest to przydatne w przypadku metadanych. - - - - Could not open file - Nie można otworzyć plików - - - - Cannot write changes to '%1'. - Nie mogę zapisać zmian do '%1'. - - - - Add Ignore Pattern - Dodaj ignorowany - - - - Add a new ignore pattern: - Dodaj nowy ignorowany: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Ten wpis jest podawany przez system w '%1' i nie może być zmieniony w tym widoku. - - - - OCC::IssuesWidget - - - Form - Formularz - - - - List of issues - Lista zdarzeń - - - - Filter - Filtr - - - - There were too many issues. Not all will be visible here. - Wystąpiło bardzo wiele zdarzeń. Nie wszystkie będą tu widoczne - - - - There were conflicts. Check the documentation on how to resolve them. - Powstały konflikty. Sprawdź dokumentację żeby dowiedzieć się jak je rozwiązać. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Powstały konflikty. <a href="%1">Sprawdź dokumentację żeby dowiedzieć się jak je rozwiązać.</a> - - - - Account - Konto - - - - OCC::LibCloudProvidersPrivate - - - Settings - Ustawienia - - - - OCC::LogBrowser - - - Log Output - Treść dziennika - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - Usuń logi starsze niż %1 godzin - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Otwórz katalog - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Nowa wersja dostępna - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Nowa wersja klienta %1 jest dostępna.</p><p><b>%2</b>jest dostępna do pobrania. Zainstalowana wersja to %3.<p> - - - - - Skip this version - Pomiń tą wersję - - - - Skip this time - Pomiń tym razem - - - - Get update - Uaktualnij - - - - Update Failed - Błąd aktualizacji - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - Zapytaj ponownie później - - - - Restart and update - Zrestartuj i zaktualizuj - - - - Update manually - Zaktualizuj ręcznie - - - - OCC::NetworkSettings - - - Form - Formularz - - - - Proxy Settings - Ustawienia proxy - - - - No Proxy - bez proxy - - - - Use system proxy - Użyj proxy - - - - Specify proxy manually as - Określ serwer proxy ręcznie jako - - - - Host - Host - - - - : - : - - - - Proxy server requires authentication - Serwer proxy wymaga uwierzytelnienia - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Przepustowość pobierania - - - - - Limit to - Limit do - - - - - KBytes/s - KBajtów/s - - - - - No limit - Brak limitu - - - - - Limit to 3/4 of estimated bandwidth - Ogranicz do 3/4 szacowanego pasma - - - - Upload Bandwidth - Przepustowość wysyłania - - - - - Limit automatically - Limit automatyczniy - - - - Hostname of proxy server - Nazwa hosta serwera proxy - - - - Username for proxy server - Nazwa użytkownika serwera proxy - - - - Password for proxy server - Hasło użytkownika serwera proxy - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - Utworzono o %1 - - - - Closing in a few seconds... - Za kilka sekund nastąpi zamknięcie... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Błąd zwrócony przez serwer: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Błąd logowania</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - Nowe %1 uaktualnienie jest gotowe - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Nowe ualtualnienie dla %1 jest gotowe do instalacji. Aktualizator może zapytać -o dodatkowe uprawnienia podczas procesu aktualizacji. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Nie można pobrać aktualizacji. Proszę kliknąć <a href='%1'>tutaj</a>, aby ściągnąć aktualizację ręcznie - - - - Could not check for new updates. - Nie można sprawdzić dostępności nowych aktualizacji. - - - - Downloading %1. Please wait... - Pobieranie %1. Proszę czekać... - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - Sprawdzanie aktualizacji serwera... - - - - Update status is unknown: Did not check for new updates. - Status aktualizacji nieznany. Nie sprawdzono nowych aktualizacji. - - - - No updates available. Your installation is at the latest version. - Brak dostępnych aktualizacji. Twoja instalacja jest w najnowszej wersji. - - - - Update Check - Sprawdź aktualizację - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Podłącz do %1 - - - - Setup local folder options - Ustawienia opcji lokalnych katalogów - - - - Connect... - Połącz... - - - - (experimental) - (eksperymentalne) - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - Synchronizuj folder '%1' - - - - Virtual files are not available for the selected folder - - - - - Error - Błąd - - - - Local Sync Folder - Folder lokalnej synchronizacji - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &E-mail - - - - Connect to %1 - Podłącz do %1 - - - - Enter user credentials - Wprowadź poświadczenia użytkownika - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Podłącz do %1 - - - - Login in your browser - Zaloguj się w przeglądarce. - - - - Copy link to clipboard - Skopuj link to schowka - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Podłącz do %1 - - - - Setup %1 server - Ustaw serwer %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Ten adres url NIE jest bezpieczny, ponieważ jest nieszyfrowany. -Niezalecane jest jego użycie. - - - - This url is secure. You can use it. - Ten adres url jest bezpieczny. Można go użyć. - - - - &Next > - Następny - - - - Invalid URL - Błędny adres url. - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Nie udało się połączyć do %1 w %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Upłynął czas podczas próby połączenia do %1 na %2. - - - - Invalid URL - Błędny adres url. - - - - Could not create local folder %1 - Nie udało się utworzyć lokalnego folderu %1 - - - - Error: %1 - Błąd: %1 - - - - The folder creation resulted in HTTP error code %1 - Tworzenie folderu spowodowało kod błędu HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Nie udało się utworzyć zdalnego folderu ponieważ podane dane dostępowe są nieprawidłowe!<br/>Wróć i sprawdź podane dane dostępowe.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Tworzenie folderu zdalnego %1 nie powiodło się z powodu błędu <tt>%2</tt>. - - - - Folder rename failed - Zmiana nazwy folderu nie powiodła się - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Nie można usunąć i zarchiwizować folderu ponieważ znajdujący się w nim plik lub folder jest otwarty przez inny program. Proszę zamknąć folder lub plik albo kliknąć ponów lub anuluj setup. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Kreator połączeń + + Could not open file + Nie można otworzyć plików - - Enable experimental feature? - Włączyć funkcje eksperymentalne? + + Cannot write changes to '%1'. + Nie mogę zapisać zmian do '%1'. - - Enable experimental placeholder mode - + + Add Ignore Pattern + Dodaj ignorowany - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - + + Add a new ignore pattern: + Dodaj nowy ignorowany: - - Stay safe - + + This entry is provided by the system at '%1' and cannot be modified in this view. + Ten wpis jest podawany przez system w '%1' i nie może być zmieniony w tym widoku. - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Linki symboliczne nie są wspierane przy synchronizacji. - - - - File is listed on the ignore list. - Plik jest na liście plików ignorowanych. - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - Nazwy plików zawierające znaki '%1' nie są wspierane - - - - File name contains at least one invalid character - Nazwa pliku zawiera co najmniej jeden nieprawidłowy znak - - - - The file name is a reserved name on this file system. - Nazwa pliku jest zarezerwowana dla plików systemowych - + OCC::LocalActivityWidget - - Filename contains trailing spaces. - Nazwa pliku zawiera spacje + + Filter + Filtr - - Filename is too long. - Nazwa pliku zbyt długa + + Local activity table + Tabela aktywności lokalnej - - File/Folder is ignored because it's hidden. - Plik / katalog zostanie zignorowany, ponieważ jest ukryty. + + + Account + Konto - - Stat failed. - Błąd statystyk. + + Filter menu + Menu filtrowania - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Konflikt: Pobrano wersję z serwera, lokalna kopia została zapisana z nową nazwą pliku i nie została wysłana na serwer. + + Actions menu + Menu akcji - - The filename cannot be encoded on your file system. - + + Sort ascending by %1 + Sortuj rosnąco według %1 - - The filename is blacklisted on the server. - + + Sort descending by %1 + Sortuj malejąco według %1 - - size - rozmiar + + Retry sync + Ponów synchronizację + + + OCC::LogBrowser - - permissions - uprawnienia + + Log Output + Treść dziennika - - etag - + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>Dzienniki zawierają poufne informacje, których nie należy udostępniać publicznie</b></span></p></body></html> - - file id - id pliku + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + Klient może zapisywać logi debugowania w folderze tymczasowym. Logi te są bardzo pomocne w diagnozowaniu problemów. +Ponieważ pliki logów mogą być duże, klient będzie tworzył nowy plik dla każdej synchronizacji i kompresował starsze. - - server reported no %1 - + + Enable logging to temporary folder + Włącz rejestrowanie w folderze tymczasowym - - Moved to invalid target, restoring - + + If enabled, logs will be written to: + Jeżeli opcja jest włączona, dzienniki będą zapisywane w następującym miejscu: - - Not allowed to remove, restoring - Brak uprawnień by usunąć, przywracanie + + C:/log + C:/log - - Ignored because of the "choose what to sync" blacklist - + + Log Http traffic + Rejestruj ruch HTTP - - Not allowed because you don't have permission to add subfolders to that folder - Niedozwolone, ponieważ nie masz uprawnień do dodawania podkatalogów w tym katalogu + + Log files to keep: + Pliki dziennika, które należy zachować: - - Not allowed because you don't have permission to add files in that folder - Niedozwolone, ponieważ nie masz uprawnień do dodawania plików w tym katalogu + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Te ustawienia są zachowywane po ponownym uruchomieniu klienta. +Należy pamiętać, że użycie opcji wiersza poleceń rejestrowania spowoduje zastąpienie tych ustawień. - - Not allowed to upload this file because it is read-only on the server, restoring - Wgrywanie niedozwolone, ponieważ plik jest tylko do odczytu na serwerze, przywracanie + + Open folder + Otwórz katalog + + + OCC::MainWindow - - Error while reading the database - + + Hide + Ukryj - - Server replied with an error while reading directory '%1' : %2 + + Main toolbar for the application - - - OCC::PropagateDirectory - - Error updating metadata: %1 + + Main toolbar - - %1 the folder is currently in use - + + + More + Więcej - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - + OCC::MainWindowController - - - File %1 can not be downloaded because of a local file name clash with %2! + + Add account... - - The download would reduce free local disk space below the limit - Ściągnięcie danych spowoduje, że wolna przestrzeń dyskowa spadnie poniżej limitu. - - - - Free space on disk is less than %1 - Wolne miejsce na dysku jest mniejsze niż %1 - - - - File was deleted from server - Plik został usunięty z serwera + + Settings... + Ustawienia... - - The file could not be downloaded completely. - Ten plik nie mógł być całkowicie pobrany. + + About... + O... - - The downloaded file is empty despite the server announced it should have been %1. - Pobrany plik jest pusty pomimo tego, że według zapowiedzi serwera powinien mieć %1. + + Quit + Wyjdź - - File %1 cannot be saved because of a local file name clash with %2! + + + Errors: %1 - - Error updating metadata: %1 - + + Activity + Aktywność - - - - - The file %1 is currently in use - + + Quit %1 + Wyjdź %1 - - - File has changed since discovery - + + Are you sure you want to quit %1? + Czy na pewno chcesz zamknąć %1? - OCC::PropagateItemJob + OCC::ModalWrapperWidget - - ; Restoration Failed: %1 - + + Close + Zamknij - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - nie można skasować pliku %1, błąd: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - + OCC::Models - - Error updating metadata: %1 - + + %1 Filter: + %1 Filtr: - - The file %1 is currently in use - + + All + Wszystkie - - could not create folder %1 - nie można utworzyć folderu %1 + + Reset Filters + Resetuj filtry - - - OCC::PropagateLocalRemove - - Could not remove %1 because of a local file name clash with %2! - + + Status Filter: + Filtr statusu: - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Plik %1 nie może być nazwany %2 z powodu kolizji z lokalną nazwą pliku - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - + OCC::NetworkSettings - - Error setting pin state - + + Form + Formularz - - - OCC::PropagateRemoteDelete - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + + Pause synchronization when the Internet connection is metered + Wstrzymanie synchronizacji, gdy połączenie internetowe jest mierzone - - - OCC::PropagateRemoteMkdir - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - + + Proxy Settings + Ustawienia proxy - - Error writing metadata to the database: %1 - + + No Proxy + bez proxy - - Setting file status failed due to file lock - + + Use system proxy + Użyj proxy - - - OCC::PropagateRemoteMove - - Could not rename %1 to %2, error: %3 - + + Host + Host - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - + + Proxy Hostname + Nazwa hosta proxy - - Error updating metadata: %1 - + + : + : - - The file %1 is currently in use - + + Proxy Port Number + Numer portu proxy - - Error writing metadata to the database - Błąd podczas zapisu metadanych do bazy + + Proxy server requires authentication + Serwer proxy wymaga uwierzytelnienia - - Error setting pin state - + + Note: proxy settings have no effects for accounts on localhost + Uwaga: ustawienia proxy nie mają wpływu na konta na localhost - - - OCC::PropagateUpdateMetaDataJob - - Could not update file : %1 - + + Specify proxy manually + Określ serwer proxy ręcznie - - The file %1 is currently in use - + + Proxy type + Typ serwera proxy - - - OCC::PropagateUploadFileCommon - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - + + + Hostname of proxy server + Nazwa hosta serwera proxy - - File Removed - Usunięto plik + + + Username for proxy server + Nazwa użytkownika serwera proxy - - Local file changed during syncing. It will be resumed. - Lokalny plik uległ zmianie w trakcie synchronizacji. Zostanie wznowiony. + + + Password for proxy server + Hasło użytkownika serwera proxy - - Local file changed during sync. - Lokalny plik zmienił się podczas synchronizacji. + + HTTP(S) proxy + HTTP(S) proxy - - Error updating metadata: %1 - + + SOCKS5 proxy + SOCKS5 proxy + + + OCC::NewAccountBuilder - - The file %1 is currently in use - + + no error details are available + brak dostępnych szczegółów błędu - - - Upload of %1 exceeds the quota for the folder - + + Unable to connect to server during account creation: %1. + Nie można połączyć się z serwerem podczas tworzenia konta: %1. - OCC::PropagateUploadFileNG + OCC::NewAccountWizard - - %1 the file is currently in use - + + Cancel Setup + Anuluj konfigurację - - The local file was removed during sync. - Pliki lokalny został usunięty podczas synchronizacji. + + Do you really want to cancel the account setup? + Czy na pewno chcesz anulować konfigurację konta? + + + OCC::NewAccountWizardController - - Local file changed during sync. - Lokalny plik zmienił się podczas synchronizacji. + + Add account… + Dodaj konto… - - The server did ask for a removed legacy feature(polling) - + + Back + Wróć - - Unexpected return code from server (%1) - + + Advanced + Zaawansowane - - Missing File ID from server - + + Sign in + Zaloguj - - Missing ETag from server - + + Open sign in again + Otwórz ponownie i zaloguj się - OCC::PropagateUploadFileTUS + OCC::NewVersionAvailableWidget - - %1 the file is currently in use - + + Skip this version + Pomiń tą wersję - - The local file was removed during sync. - Pliki lokalny został usunięty podczas synchronizacji. + + Get update + Uaktualnij - - Local file changed during sync. - Lokalny plik zmienił się podczas synchronizacji. + + Skip this time + Pomiń tym razem - OCC::PropagateUploadFileV1 + OCC::OAuth - - %1 the file is currently in use - + + Successfully signed in + Pomyślnie zalogowano - - The server did ask for a removed legacy feature(polling) - + + Now, explore %1 on desktop. + Teraz poznaj %1 na komputerze. - - The local file was removed during sync. - Pliki lokalny został usunięty podczas synchronizacji. + + Error returned from the server: <em>%1</em> + Błąd zwrócony przez serwer: <em>%1</em> - - Local file changed during sync. - Lokalny plik zmienił się podczas synchronizacji. + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Wystąpił błąd podczas dostępu do punktu końcowego 'token': <br><em>%1</em> - - The server did not acknowledge the last chunk. (No e-tag was present) - + + Could not parse the JSON returned from the server: <br><em>%1</em> + Nie można przeanalizować danych JSON zwróconych przez serwer: <br><em>%1</em> - - - OCC::ProtocolItemModel - - Time - Czas + + Unsupported token type: %1 + Nieobsługiwany typ tokena: %1 - - File - Plik + + The reply from the server did not contain all expected fields +:%1 + Odpowiedź z serwera nie zawierała wszystkich oczekiwanych pól +:%1 - - Folder - Folder + + Unknown Error + Nieznany błąd - - Issues - + + + Login Error + Błąd logowania - - Action - Akcja + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + Zalogowano się jako użytkownik <em>%1</em>, ale należy zalogować się jako użytkownik <em>%2</em>. <br>Proszę wrócić do klienta %3 i ponownie uruchomić uwierzytelnianie. - - Size - Rozmiar + + Wrong user + Zły użytkownik + + + OCC::OAuthPageController - - Account - Konto + + Application Name Logo %1 + Nazwa aplikacji Logo %1 - - Status - Status + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Pozostaw ten ekran otwarty. W przeglądarce internetowej pojawi się monit o zalogowanie, który połączy Cię z poniższym adresem. - - - OCC::ProtocolWidget - - Form - Formularz + + Sign in URL + Adres URL logowania - - Local sync protocol - Lokalny protokół synchronizacji + + To copy the sign in URL to the clipboard, use the copy button + Aby skopiować adres URL logowania do schowka, użyj przycisku kopiowania - - Filter - Filtr + + Copy URL to sign in + Skopiuj adres URL, aby się zalogować - - Account - Konto + + Additional logo defined by the organization + Dodatkowe logo zdefiniowane przez organizację - - Copy to clipboard - Skopiuj do schowka + + URL copied + Skopiowano adres URL - - Show in file browser - + + Copy URL + Kopiuj URL - - Show in web browser + + Unable to connect to the login service. If the problem continues, please contact support. - - Retry sync - + + Please copy the authentication URL using the button above, and provide this information to support. + Skopiuj adres URL uwierzytelniania za pomocą przycisku powyżej i przekaż te informacje działowi pomocy technicznej. - - - OCC::ProxyAuthDialog - - Proxy authentication required - Wymagane uwierzytelnienie Proxy + + Failed to look up WebFinger instances: %1 + Nie udało się wyszukać instancji WebFinger: %1 - - Username: - Nazwa użutkownika: + + You are already connected to an account with these credentials. + Jesteś już połączony z kontem o tych danych logowania. - - Proxy: - Proxy: + + Unable to retrieve capabilities from server. + Nie można pobrać możliwości z serwera. - - The proxy server needs a username and password. - Serwer proxy wymaga nazwy użytkownika i hasła. + + The server is not supported by this client. + Serwer nie jest obsługiwany przez tego klienta. - - Password: - Hasło: + + Error while trying to log in to OAuth2-enabled server. %1 + Błąd podczas próby zalogowania się do serwera obsługującego OAuth2. %1 - - TextLabel - Etykieta + + Unable to open browser with provided URL. %1 + Nie można otworzyć przeglądarki z podanym adresem URL. %1 - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Wybierz co synchronizować + + OAuth2 authentication requires a secured connection. %1 + Uwierzytelnianie OAuth2 wymaga bezpiecznego połączenia. %1 - - - OCC::SelectiveSyncWidget - - Loading ... - Wczytuję ... + + Authorization server unreachable. %1 + Serwer autoryzacji jest niedostępny. %1 + + + OCC::OCUpdater - - Deselect remote folders you do not wish to synchronize. - Odznacz foldery zdalne, których nie chcesz synchronizować. + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Nie można pobrać aktualizacji. Proszę kliknąć <a href='%1'>tutaj</a>, aby ściągnąć aktualizację ręcznie - - Name - Nazwa + + Could not check for new updates. + Nie można sprawdzić dostępności nowych aktualizacji. - - Size - Rozmiar + + Downloading %1. Please wait… + Pobieranie %1. Proszę czekać… - - - No subfolders currently on the server. - Na serwerze nie ma w tej chwili żadnych podkatalogów. + + %1 installed successfully. Restart the application to finish installing the update. + %1 zainstalowano pomyślnie. Uruchom ponownie aplikację, aby dokończyć instalację aktualizacji. - - An error occurred while loading the list of sub folders. - Wystąpił błąd podczas wczytywania listy podfolderów + + %1 available. Restart application to start the update. + %1 jest dostępny. Uruchom ponownie aplikację, aby uruchomić aktualizację. - - - OCC::ServerNotificationHandler - - Dismiss - Anuluj + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Dostępny jest nowy %1. Kliknij <a href='%2'>tutaj</a>, aby ręcznie pobrać nowy AppImage. - - - OCC::SettingsDialog - - Settings - Ustawienia + + New %1 available. Please use the system's update tool to install it. + Dostępny jest nowy %1. Proszę użyć narzędzia do aktualizacji systemu, aby go zainstalować. - - Activity - Aktywność + + Checking update server… + Sprawdzanie serwera aktualizacji… - - Add account - Dodaj konto + + Update status is unknown: Did not check for new updates. + Status aktualizacji nieznany. Nie sprawdzono nowych aktualizacji. - - - Quit %1 - Wyjdź %1 + + No updates available. Your installation is at the latest version. + Brak dostępnych aktualizacji. Twoja instalacja jest w najnowszej wersji. + + + OCC::OwncloudPropagator - - Are you sure you want to quit %1? - Czy na pewno chcesz zamknąć %1? + + File %1 is currently in use + Plik %1 jest obecnie używany - - Account - Konto + + vfs instance is not available. + Instancja vfs jest niedostępna. + + + OCC::ProcessDirectoryJob - - MainWindow - + + Symbolic links are not supported in syncing. + Linki symboliczne nie są wspierane przy synchronizacji. - - Hide - Ukryj + + File is listed on the ignore list. + Plik jest na liście plików ignorowanych. - - - OCC::ShareDialog - - TextLabel - Etykieta + + File names ending with a period are not supported on this file system. + Nazwy plików kończące się kropką nie są obsługiwane w tym systemie plików. - - share label - etykieta udostępniania + + File names containing the character '%1' are not supported on this file system. + Nazwy plików zawierające znaki '%1' nie są wspierane - - Dialog - Okno dialogowe + + File name contains at least one invalid character + Nazwa pliku zawiera co najmniej jeden nieprawidłowy znak - - ownCloud Path: - Ścieżka ownCloud: + + The file name is a reserved name on this file system. + Nazwa pliku jest zarezerwowana dla plików systemowych - - %1 Sharing - %1 udostępnione + + Filename contains trailing spaces. + Nazwa pliku zawiera spacje - - %1 - %1 + + Filename is too long. + Nazwa pliku zbyt długa - - Folder: %2 - Folder: %2 + + File/Folder is ignored because it's hidden. + Plik / katalog zostanie zignorowany, ponieważ jest ukryty. - - The server does not allow sharing - Serwer nie zezwala na współdzielenie + + Stat failed. + Błąd statystyk. - - Retrieving maximum possible sharing permissions from server... - + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Konflikt: Pobrano wersję z serwera, lokalna kopia została zapisana z nową nazwą pliku i nie została wysłana na serwer. - - The file can not be shared because it was shared without sharing permission. - Plik nie może być udostępniony, ponieważ został udostępniony bez uprawnień. + + The filename cannot be encoded on your file system. + Nazwa pliku nie może zostać zakodowana w systemie plików. - - Users and Groups - Użytkownicy i grupy + + The filename is blacklisted on the server. + Nazwa pliku jest na czarnej liście na serwerze. - - Public Links - Linki publiczne + + + + + the VFS instance is missing. Unable to continue. + Brak instancji VFS. Nie można kontynuować. - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Udostępnij NewDocument.odt + + server reported no %1 + serwer zgłosił brak %1 - - Upload only (File Drop) - Tylko wysyłanie (upuszczanie pliku) + + + account was deleted. Unable to continue + Konto zostało usunięte. Nie można kontynuować. - - Receive files from others without revealing the contents of the folder. - + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: Zignorowano, ponieważ ścieżka nie została zaznaczona - - Download / View / Edit - Pobieranie / Przeglądanie / Edycja + + Moved to invalid target, restoring + Przeniesiono do nieprawidłowego celu, przywrócono - - Name: - Nazwa: + + Not allowed to remove, restoring + Brak uprawnień by usunąć, przywracanie - - Enter name of new share... - + + Error while reading the database. + Wystąpił błąd podczas odczytu bazy danych. - - &Create - &Utwórz + + account was deleted. Unable to continue. + Konto zostało usunięte. Nie można kontynuować. - - TextLabel - Etykieta + + Not allowed because you don't have permission to add subfolders to that folder + Niedozwolone, ponieważ nie masz uprawnień do dodawania podkatalogów w tym katalogu - - Set &password - Ustaw &hasło + + Error while doing a rename, unhandled status code: %1 + Błąd podczas zmiany nazwy, nieobsłużony kod stanu: %1 - - Set &expiration date - Ustaw &datę wygaśnięcia + + Not allowed because you don't have permission to add files in that folder + Niedozwolone, ponieważ nie masz uprawnień do dodawania plików w tym katalogu - - Set password - Ustaw hasło + + Not allowed to upload this file because it is read-only on the server, restoring + Wgrywanie niedozwolone, ponieważ plik jest tylko do odczytu na serwerze, przywracanie - - Recipients can view or download contents. - Odbiorcy mogą otwierać lub pobierać zawartość. + + This Space is currently unavailable + Ta przestrzeń jest obecnie niedostępna - - Download / View - Pobierz / Podgląd + + Server replied with an error while reading directory '%1' : %2 + Serwer odpowiedział błędem podczas odczytu katalogu '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - Odbiorcy mogą otwierać, pobierać, zmieniać, kasować i wysyłać zawartość. + + Error updating metadata: %1 + Błąd podczas aktualizacji metadanych: %1 - - Anyone with the link has access to the file/folder - Każdy posiadający link ma dostęp do pliku/katalogu. + + %1 the folder is currently in use + %1 folder jest obecnie używany + + + OCC::PropagateDownloadFile - - - P&assword protect - Z&abezpieczenie hasłem + + Failed to free up space, the file %1 is currently in use + Nie udało się zwolnić miejsca, plik %1 jest obecnie używany - - The file can not be shared because it was shared without sharing permission. - Plik nie może być udostępniony, ponieważ został udostępniony bez uprawnień. + + + File %1 can not be downloaded because of a local file name clash with %2! + Nie można pobrać pliku %1 z powodu konfliktu nazw plików lokalnych z %2! - - Link shares have been disabled - Link udostępniania został wyłączony + + The download would reduce free local disk space below the limit + Ściągnięcie danych spowoduje, że wolna przestrzeń dyskowa spadnie poniżej limitu. - - Create public link share - Utwórz publiczny link do udostępnienia + + Free space on disk is less than %1 + Wolne miejsce na dysku jest mniejsze niż %1 - - - Delete - Usuń + + File was deleted from server + Plik został usunięty z serwera - - Open link in browser - Otwórz link w przeglądarce + + Broken webserver returned empty content length for non-empty file on resume + Zepsuty serwer WWW zwrócił pustą długość zawartości dla niepustego pliku podczas wznawiania - - Copy link to clipboard - Skopuj link to schowka + + The file could not be downloaded completely. + Ten plik nie mógł być całkowicie pobrany. - - Copy link to clipboard (direct download) - Skopiuj link do schowa (pobieranie bezpośrednie) + + The downloaded file is empty despite the server announced it should have been %1. + Pobrany plik jest pusty pomimo tego, że według zapowiedzi serwera powinien mieć %1. - - Send link by email - Wyślij link mailem + + File %1 cannot be saved because of a local file name clash with %2! + Nie można zapisać pliku %1 z powodu konfliktu nazw plików lokalnych z %2! - - Send link by email (direct download) - Wyślij link e-mailem (pobieranie bezpośrednie) + + Error updating metadata: %1 + Błąd podczas aktualizacji metadanych: %1 - - Create new... - Utwórz nowy... + + + + + The file %1 is currently in use + Plik %1 jest obecnie w użyciu - - I shared %1 with you - Udostępniłem Tobie %1 + + + File has changed since discovery + Plik został zmieniony od momentu odkrycia + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - Potwierdź usunięcie linku do udostępniania + + ; Restoration Failed: %1 + ; Przywracanie nie powiodło się: %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Czy na pewno chcesz usunąć publiczny link do udostępnienia <i>%1</i>?</p><p>Uwaga: Tej operacji nie można cofnąć.</p> + + could not delete file %1, error: %2 + nie można skasować pliku %1, błąd: %2 - - Cancel - Anuluj + + Can not create local folder %1 because of a local file name clash with %2 + Nie można utworzyć folderu lokalnego %1 z powodu konfliktu nazw plików lokalnych z %2 - - - Public link - Link publiczny + + Error updating metadata: %1 + Błąd podczas aktualizacji metadanych: %1 - - Delete link share - Usuń link udostępniania + + The file %1 is currently in use + Plik %1 jest obecnie w użyciu - - - Please Set Password - Proszę podać hasło + + could not create folder %1 + nie można utworzyć folderu %1 - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove - - Share NewDocument.odt - Udostępnij NewDocument.odt + + %1 failed with: %2 + %1 nie powiodło się z: %2 - - Share with users or groups ... - Współdziel z użytkownikami lub grupami + + Could not remove %1 because of a local file name clash with %2! + Nie można usunąć %1 z powodu konfliktu nazw plików lokalnych z %2! - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - + + Could not move '%1' to the trash bin + Nie można przenieść '%1' do kosza - - The item is not shared with any users or groups - Ten element nie jest udostępniony żadnemu użytkownikowi ani grupie. + + + %1 the file is currently in use + %1 plik jest obecnie używany + + + + OCC::PropagateLocalRename + + + File %1 can not be renamed to %2 because of a local file name clash + Plik %1 nie może być nazwany %2 z powodu kolizji z lokalną nazwą pliku - - Open link in browser - Otwórz link w przeglądarce + + Could not rename %1 to %2, the file is currently in use + Nie można zmienić nazwy %1 na %2, plik jest obecnie używany - - Copy link to clipboard - Skopuj link to schowka + + Error updating metadata: %1 + Błąd podczas aktualizacji metadanych: %1 - - Send link by email - Wyślij link mailem + + The file %1 is currently in use + Plik %1 jest obecnie w użyciu - - No results for '%1' - Brak wyników dla '%1' + + Failed to rename file + Błąd przy zmianie nazwy pliku - - I shared something with you - Udostępniam Tobie + + Error setting pin state + Błąd podczas ustawiania stanu pinu - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Formularz + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Serwer zwrócił błędny kod HTTP. Oczekiwano 204, a otrzymano "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - Etykieta + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Serwer zwrócił błędny kod HTTP. Oczekiwano 201, ale otrzymano "%1 %2". - - can edit - może edytować + + Error writing metadata to the database: %1 + Błąd zapisu metadanych do bazy danych: %1 - - can share - może współdzielić + + Setting file status failed due to file lock + Ustawienie statusu pliku nie powiodło się z powodu blokady pliku + + + OCC::PropagateRemoteMove - - ... - ... + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Serwer zwrócił błędny kod HTTP. Oczekiwano 201, ale otrzymano "%1 %2". - - create - utwórz + + Error updating metadata: %1 + Błąd podczas aktualizacji metadanych: %1 - - change - zmiany + + The file %1 is currently in use + Plik %1 jest obecnie w użyciu - - delete - usuń + + Error writing metadata to the database + Błąd podczas zapisu metadanych do bazy - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Error setting pin state + Błąd podczas ustawiania stanu pinu - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Współdzielone z %1 + + Could not update file : %1 + Nie można zaktualizować pliku: %1 - - Context menu share - + + The file %1 is currently in use + Plik %1 jest obecnie w użyciu + + + OCC::PropagateUploadCommon - - Context menu share %1 - + + Local file changed during sync. It will be resumed. + Lokalny plik uległ zmianie w trakcie synchronizacji. Zostanie wznowiony. - - Confirm deletion - + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + Nie można przesłać pliku %1, ponieważ istnieje inny plik o tej samej nazwie, różniący się jedynie wielkością liter. - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - + + + Upload of %1 exceeds the quota for the folder + Przesłanie %1 przekracza limit dla folderu - - Do you want to delete the file <i>%1</i> permanently? - + + + %1 the file is currently in use + %1 plik jest obecnie używany - - Select new location... - + + File Removed + Usunięto plik - - Error - Błąd + + The server did not provide the file permissions + Serwer nie zapewnił uprawnień do pliku - - Moving file failed: - -%1 - + + Error updating metadata: %1 + Błąd podczas aktualizacji metadanych: %1 + + + OCC::PropagateUploadFile - - I shared something with you - Udostępniam Tobie + + %1 the file is currently in use + %1 plik jest obecnie używany - - - Share... - Udostępnij... + + The server did ask for a removed legacy feature(polling) + Serwer poprosił o usunięcie starszej funkcji (sondaż) - - - Copy private link to clipboard - Kopiuj link prywatny do schowka + + The local file was removed during sync. + Pliki lokalny został usunięty podczas synchronizacji. - - Send private link by email... - Wyślij link prywatny przez e-mail + + The server did not acknowledge the file upload. (No e-tag was present) + Serwer nie potwierdził przesłania pliku. (Brak e-taga) + + + OCC::PropagateUploadFileTUS - - Resharing this file is not allowed - + + %1 the file is currently in use + %1 plik jest obecnie używany - - Resharing this folder is not allowed - + + Checksum computation failed + Obliczanie sumy kontrolnej nie powiodło się - - Create and copy public link to clipboard - + + The local file was removed during sync. + Pliki lokalny został usunięty podczas synchronizacji. + + + OCC::ProtocolItemModel - - Copy public link to clipboard - Kopiuj link publiczny do schowka + + Time + Czas - - Open in browser - Otwórz w przeglądarce + + File + Plik - - Show file versions in browser - + + Folder + Folder - - Rename... - Zmień nazwę... + + Issues + Problemy - - Rename and upload... - + + Action + Akcja - - Move and rename... - + + Size + Rozmiar - - Move, rename and upload... - + + Account + Konto - - Delete local changes - + + Status + Status + + + OCC::RequestAuthenticationController - - Move and upload... - + + IdP is unreachable. Contact your system administrator or try again later. + Dostawca tożsamości jest niedostępny. Skontaktuj się z administratorem systemu lub spróbuj ponownie później. - - Delete - Usuń + + Authentication failed. + Uwierzytelnianie nieudane. - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags - + + Application Name Logo %1 + Nazwa aplikacji Logo %1 - - Backup of %1 started - + + Sign in required + Wymagane zalogowanie - - Failed to find %1 - + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + Możliwe, że nastąpiło automatyczne rozłączenie z powodu problemu z serwerem lub przekroczenia limitu czasu. Zaloguj się ponownie. - - Local path must be a an absolute path - + + Sign in URL + Adres URL logowania - - Failed to create temporary database - + + To copy the sign in URL to the clipboard, use the copy button + Aby skopiować adres URL logowania do schowka, użyj przycisku kopiowania - - Backup of %1 succeeded - + + Copy URL to sign in + Skopiuj adres URL, aby się zalogować - - Failed to set success tag - + + Additional logo defined by the organization + Dodatkowe logo zdefiniowane przez organizację - - Failed to create backup: %1 - + + Stay logged out + Pozostań wylogowany - - Failed to file id tags - + + Sign in + Zaloguj - - Backup of %1 failed with: %2 - + + URL copied + Skopiowano adres URL - - %1 backup - + + Copy URL + Kopiuj URL - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Szczegóły certyfikatu</h3> - - - - Common Name (CN): - Nazwa (CN): - - - - Subject Alternative Names: - Alternatywna nazwa tematu: - + OCC::ResolveUrlAdapter - - Organization (O): - Organizacja (O): + + SSL failure when connecting to server at %1 + Błąd SSL podczas łączenia się z serwerem w %1 - - Organizational Unit (OU): - Jednostka organizacyjna (OU): + + Could not detect compatible server at %1 + Nie można wykryć zgodnego serwera w %1 - - State/Province: - Województwo + + Resolved url is invalid %1 + Rozwiązany adres URL jest nieprawidłowy %1 - - Country: - Kraj: - - - - Serial: - Numer seryjny: - - - - <h3>Issuer</h3> - <h3>Wystawca</h3> + + Rejected redirect from %1 to %2 + Odrzucono przekierowanie z %1 do %2 + + + OCC::SelectiveSyncWidget - - Issuer: - Emitent: + + Loading … + Wczytuję ... - - Issued on: - Data wydania: + + Deselect remote folders you do not wish to synchronize. + Odznacz foldery zdalne, których nie chcesz synchronizować. - - Expires on: - Wygasa: + + Name + Nazwa - - <h3>Fingerprints</h3> - <h3>Odciski palców</h3> + + Size + Rozmiar - - SHA-256: - SHA-256: + + + Currently there are no subfolders on the server. + Obecnie na serwerze nie ma żadnych podfolderów. - - SHA-1: - SHA-1: + + An error occurred while loading the list of subfolders. + Wystąpił błąd podczas ładowania listy podfolderów. + + + OCC::SettingsView - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Uwaga:</b> Ten certyfikat został ręcznie zaakceptowany</p> + + Form + Formularz - - %1 (self-signed) - %1 (własnoręcznie podpisany) + + General Settings + Ustawienia ogólne - - %1 - %1 + + Use Monochrome Icons in the system tray + Użyj monochromatycznych ikon na pasku zadań - - This connection is encrypted using %1 bit %2. - - o połączenie jest szyfrowane przy użyciu %1 bit %2. - + + Show Desktop Notifications + Pokaż powiadomienia na pulpicie - - Server version: %1 - + + Start on Login + Rozpocznij od zalogowania - - No support for SSL session tickets/identifiers - + + Language + Język - - Certificate information: - Informacje Certyfikatu: + + Language selector + Selektor języka - - The connection is not secure - + + Advanced + Zaawansowane - - This connection is NOT secure as it is not encrypted. - - To połączenie NIE jest bezpieczne, ponieważ jest nieszyfrowane. - + + Sync hidden files + Synchronizuj pliki ukryte - - - OCC::SslErrorDialog - - Form - Formularz + + Show crash reporter + Pokaż raport awarii - - Trust this certificate anyway - Zaufaj temu zaświadczeniu mimo wszystko. + + Move remotely deleted files to the local trash bin instead of deleting them + Przenieś zdalnie usunięte pliki do lokalnego kosza zamiast je usuwać - - Untrusted Certificate - Certyfikat niezaufany + + Edit Ignored Files + Edytuj pliki ignorowane - - Cannot connect securely to <i>%1</i>: - Brak możliwości bezpiecznego połączenia z <i>%1</i>: + + Log Settings + Ustawienia Log - - with Certificate %1 - z certyfikatem %1 + + Network + Sieć - - - - &lt;not specified&gt; - &lt;nie określono&gt; + + Warning + Ostrzeżenie - - - Organization: %1 - Organizacja: %1 + + Language changes require a restart of this application to take effect. + Aby zmiany językowe zostały uwzględnione, konieczne jest ponowne uruchomienie aplikacji. - - - Unit: %1 - Jednostka: %1 + + You cannot disable autostart because system-wide autostart is enabled. + Nie można wyłączyć automatycznego uruchamiania, ponieważ włączony jest automatyczny rozruch w obrębie całego systemu. - - - Country: %1 - Kraj: %1 + + (use default) + (użyj domyślnego) - - Fingerprint (MD5): <tt>%1</tt> - Odcisk (MD5): <tt>%1</tt> + + unknown (%1) + nieznany (%1) + + + OCC::SocketApi - - Fingerprint (SHA1): <tt>%1</tt> - Odcisk (SHA1): <tt>%1</tt> + + Share with %1 + parameter is the application name + Współdzielone z %1 - - Effective Date: %1 - Data wejścia w życie: %1 + + Confirm deletion + Potwierdź usunięcie - - Expiration Date: %1 - Data wygaśnięcia: %1 + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Czy chcesz trwale usunąć katalog <i>%1</i> i całą jego zawartość? - - Issuer: %1 - Wystawca: %1 + + Do you want to delete the file <i>%1</i> permanently? + Czy chcesz trwale usunąć plik <i>%1</i> trwale? - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - + + Error + Błąd - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Tylko %1 są dostępne, potrzebujesz przynajmniej %2 aby rozpocząć + + Moving file failed: + +%1 + Przenoszenie pliku nie powiodło się: + +%1 - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - + + I shared something with you + Udostępniam Tobie - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - + + Select new location… + Wybierz nową lokalizację… - - There is insufficient space available on the server for some uploads. - + + + Share… + Współdzielić - - Unresolved conflict. - Nierozwiązany konfilkt. + + + Copy private link to clipboard + Kopiuj link prywatny do schowka - - Using virtual files with suffix, but suffix is not set - + + Resharing this file is not allowed + Współdzielenie tego pliku nie jest możliwe - - Unable to read the blacklist from the local database - Nie można odczytać czarnej listy z lokalnej bazy danych + + Resharing this folder is not allowed + Udostępnianie tego folderu jest niedozwolone - - Unable to read from the sync journal. - Nie można czytać z dziennika synchronizacji. + + Open in %1 + Otwórz w %1 - - Cannot open the sync journal - Nie można otworzyć dziennika synchronizacji + + Show file versions in web browser + Pokaż wersje pliku w przeglądarce internetowej - - Aborted - Anulowane + + Rename… + Przemianować… - - - OCC::SyncJournalDb - - Failed to connect database. - + + Rename and upload… + Zmień nazwę i prześlij… - - - OCC::Systray - - %1: %2 - %1: %2 + + + Move and rename… + Przenieś i zmień nazwę… - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - + + Move, rename and upload… + Przenieś, zmień nazwę i prześlij… - - - OCC::ValidateChecksumHeader - - The checksum header is malformed. - + + Move and upload… + Przenieś i prześlij… - - The checksum header contained an unknown checksum type '%1' - + + Delete local changes + Usuń lokalne zmiany - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - + + Delete + Usuń - OCC::Vfs + OCC::SpacesPage - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - + + Select a folder to sync it to your computer. + Wybierz folder, który chcesz zsynchronizować ze swoim komputerem. - - The Virtual filesystem feature is not supported on network drives - + + Select a Space to sync it to your computer. + Wybierz przestrzeń, aby zsynchronizować ją ze swoim komputerem. - OCC::ownCloudGui - - - Please sign in - Proszę się zalogować - + OCC::SyncEngine - - Folder %1: %2 - Folder %1: %2 + + %1 (skipped due to earlier error, trying again in %2) + %1 (pominięto z powodu wcześniejszego błędu, próba ponowna w %2) - - Unresolved %1 conflicts - + + Unable to sync: the local folder is missing. + Nie można zsynchronizować: brakuje folderu lokalnego. - - Ignored errors %1 - + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Tylko %1 są dostępne, potrzebujesz przynajmniej %2 aby rozpocząć - - There are no sync folders configured. - Nie skonfigurowano żadnych folderów synchronizacji. + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Nie można otworzyć ani utworzyć lokalnej bazy danych synchronizacji. Upewnij się, że masz uprawnienia do zapisu w folderze synchronizacji. - - Open in browser - Otwórz w przeglądarce + + + + + Cannot open the sync journal. + Nie można otworzyć dziennika synchronizacji. - - - - Log in... - Zaloguj... + + Aborted due to %1 + Przerwano z powodu %1 - - - - Log out - Wyloguj + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Mało miejsca na dysku: Pominięto pobieranie plików, które mogłyby zmniejszyć ilość wolnego miejsca poniżej %1. - - Recent Changes - Ostatnie zmiany + + There is insufficient space available on the server for some uploads. + Na serwerze jest za mało miejsca na niektóre pliki. - - Managed Folders: - Zarządzane foldery: + + application exit + abort reason + wyjście z aplikacji - - Open folder '%1' - Otwórz katalog '%1' + + Unable to read the blacklist from the local database + Nie można odczytać czarnej listy z lokalnej bazy danych - - Open %1 in browser - Otwórz %1 w przeglądarce + + Unable to read from the sync journal. + Nie można czytać z dziennika synchronizacji. + + + OCC::SyncErrorWidget - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - + + Issues table + Tabela problemów - - Unknown status - Nieznany status + + There were too many issues. Not all will be visible here. + Wystąpiło bardzo wiele zdarzeń. Nie wszystkie będą tu widoczne - - Settings... - Ustawienia... + + There were conflicts. Check the documentation on how to resolve them. + Powstały konflikty. Sprawdź dokumentację żeby dowiedzieć się jak je rozwiązać. - - Details... - Szczegóły... + + The file %1 was ignored as its name is reserved by %2 + Plik %1 został zignorowany, ponieważ jego nazwa jest zarezerwowana przez %2 - - Help - Pomoc + + Reset column sizes + Zresetuj rozmiary kolumn - - Quit %1 - Wyjdź %1 + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Powstały konflikty. <a href="%1">Sprawdź dokumentację żeby dowiedzieć się jak je rozwiązać.</a> - - Disconnected from %1 - Rozłączony z %1 + + Filter menu + Menu filtrowania - - Unsupported Server Version - Nie wspierana wersja serwera + + Account + Konto - - Disconnected - Rozłączony + + Filter + Filtr + + + OCC::SyncJournalDb - - Disconnected from some accounts - Rozłącz od wybranych kont + + Failed to connect database. + Nieudane połączenie z bazą danych. + + + OCC::Theme - - Disconnected from accounts: - Rozłączony z kontami: + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Wersja %1. Aby uzyskać więcej informacji, odwiedź stronę <a href="%2">https://%3</a></p>. <p>Aby zapoznać się ze znanymi problemami i uzyskać pomoc, odwiedź stronę: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p>. <p><small>Autorzy: Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br> Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller i inni. </small></p><p>Prawa autorskie: ownCloud GmbH (firma Kiteworks).</p> <p>Dystrybuowane przez %4 na licencji GNU General Public License (GPL) w wersji 2.0.<br/>%5 i logo %5 są zastrzeżonymi znakami towarowymi %4 w Stanach Zjednoczonych i/lub innych krajach.</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Account %1: %2 - Dostęp %1: %2 + + TLS Certificate Error + Błąd certyfikatu TLS - - Signed out - Odłączony + + Do you want to trust this certificate anyway? + Zaakceptować certyfikat mimo wszystko? - - Account synchronization is disabled - Konto synchronizacji jest wyłączone + + Cannot connect securely to %1 + Brak możliwości bezpiecznego połączenia z %1 - - Synchronization is paused - Synchronizacja wstrzymana + + &lt;not specified&gt; + &lt;nie określono&gt; - - Up to date (%1) - + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>z certyfikatem %1</h3><div id="ccert"><p>Organizacja: %2<br/>Jednostka: %3<br/>Kraj: %4</p><p>Odcisk palca (MD5): <tt>%5</tt><br/>Odcisk palca (SHA1): <tt>%6</tt><br/>Odcisk palca (SHA256): <tt>%7</tt><br/><br/>Data wejścia w życie: %8Data wygaśnięcia: %9</div><h3>Wydawca: %10</h3><div id="issuer"><p>Organizacja: %11<br/>Jednostka: %12<br/>Kraj: %13</p></div></div> + + + OCC::TrayMenuController - - No sync folders configured - Nie ustawiono katalogów do synchronizacji + + Unsupported Server Version + Nie wspierana wersja serwera - - Unpause all folders - Wznów wszystkie katalogi + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + Serwer na koncie %1 działa na nieobsługiwanej wersji %2. Korzystanie z tego klienta z nieobsługiwanymi wersjami serwera jest nieprzetestowane i potencjalnie niebezpieczne. Kontynuuj na własne ryzyko. - - Pause all folders - Wstrzymaj wszystkie katalogi + + Show %1 + Pokaż %1 - - Unpause all synchronization - Wznów wszystkie synchronizacje + + Help + Pomoc - - Unpause synchronization - Wznów synchronizację + + About %1 + O %1 - - Pause all synchronization - Wstrzymaj wszystkie synchronizacje + + Quit %1 + Wyjdź %1 + + + OCC::UpdateDownloadedWidget - - Pause synchronization - Wstrzymaj synchronizację + + Restart later + Restartuj później - - Log out of all accounts - Wyloguj się się ze wszystkich kont + + Restart now + Restartuj teraz + + + OCC::UpdaterScheduler - - Log in to all accounts... - Zaloguj się do wszystkich kont + + Update available + Aktualizacja jest dostępna + + + OCC::UrlPageController - - New account... - Nowe konto... + + Your web browser will be opened to complete sign in. + Przeglądarka internetowa zostanie otwarta w celu dokończenia logowania. - - About %1 - O %1 + + %1 logo + %1 logo - - No items synced recently - Brak ostatnich synchronizacji + + Welcome to %1 + Witaj w %1 - - Checking for changes in remote '%1' - Sprawdzanie zmian w lokalizacji zdalnej '%1' + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Wprowadź adres serwera, aby rozpocząć. Twoja przeglądarka internetowa zostanie otwarta, aby dokończyć logowanie. - - Checking for changes in local '%1' - Sprawdzanie zmian w lokalizacji lokalnej '%1' + + Server address field + Pole adresu serwera - - Syncing %1 of %2 (%3 left) - Synchronizacja %1 z %2 (%3 pozostało) + + Enter your server address here + Wpisz tutaj adres swojego serwera - - Syncing %1 of %2 - Synchronizowanie %1 of %2 + + Additional logo defined by the organization + Dodatkowe logo zdefiniowane przez organizację - - Syncing %1 (%2 left) - Synchronizuję %1 (%2 pozostało) + + Invalid server URL: %1 + Nieprawidłowy adres URL serwera: %1 - - Syncing %1 - Synchronizowanie %1 + + Invalid URL scheme. Only https is accepted. + Nieprawidłowy schemat adresu URL. Akceptowany jest tylko protokół https. - - %1 (%2, %3) - %1 (%2, %3) + + The requested URL failed with query value: %1 + Żądany adres URL nie powiódł się, a wartość zapytania wynosi: %1 - OwncloudAdvancedSetupPage - - - Form - Formularz - + OCC::UserInfoAdapter - - - TextLabel - Etykieta + + Network request to collect user info failed + Żądanie sieciowe dotyczące zebrania informacji o użytkowniku nie powiodło się - - Server - Serwer + + Unable to retrieve user info: invalid credentials. + Nie można pobrać informacji o użytkowniku: nieprawidłowe dane uwierzytelniające. - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - + + Unexpected network response when retrieving user info. + Nieoczekiwana odpowiedź sieci podczas pobierania informacji o użytkowniku. - - Ask for confirmation before synchroni&zing folders larger than - Zapytaj o potwierdzenie przed synchroni&zowaniem folderów większych niż + + Retrieving user info failed with JSON error. + Pobieranie informacji o użytkowniku nie powiodło się z powodu błędu JSON. + + + OCC::ValidateChecksumHeader - - S&ynchronize everything from server - + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + Pobrany plik nie pasuje do sumy kontrolnej, zostanie wznowiony. '%1' != '%2' + + + OCC::Vfs - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + The Virtual filesystem feature does not support a drive as sync root + Funkcja wirtualnego systemu plików nie obsługuje dysku jako katalogu głównego synchronizacji - - Ask for confirmation before synchronizing e&xternal storages - Pytaj o potwierdzenie przed synchronizacją napędów zewnętrznych + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + Funkcja wirtualnego systemu plików wymaga systemu plików NTFS, %1 używa %2 - - Choose what to sync - Wybierz co synchronizować + + The Virtual filesystem feature is not supported on network drives + Funkcja wirtualnego systemu plików nie jest obsługiwana na dyskach sieciowych + + + OCC::VfsWinPrivate - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - + + + %1 - %2 + %1 - %2 - - Manually create folder sync connections - + + Could not find StorageProviderState property %1 + Nie można znaleźć właściwości StorageProviderState %1 - - &Local Folder - &Lokalny folder + + Could not find StorageFolder for %1 error: %2 + Nie można znaleźć folderu StorageFolder dla błędu %1: %2 - - pbSelectLocalFolder - pbWybierzLokalnyFolder + + Could not retrieve StorageFolder for %1 %2 (0x%3) + Nie można pobrać folderu StorageFolder dla %1 %2 (0x%3) - - &Keep local data - &Zachowaj dane lokalne + + The folder is used by a different client: %1 + Folder jest używany przez innego klienta: %1 - - Start a &clean sync (Crreate a backup and erases the local folder!) - + + Error registering StorageProvider for %1: %2 (0x%3) + Błąd podczas rejestrowania StorageProvider dla %1: %2 (0x%3) - - - OwncloudHttpCredsPage - - Form - Formularz + + Unable to connect sync root: %1 error: %2 + Nie można połączyć się z rootem synchronizacji: błąd %1: %2 - - &Username - &Nazwa użytkownika + + %1 is managed by another sync client + %1 jest zarządzany przez innego klienta synchronizacji - - &Password - &Hasło + + Downgrading the folder from %1 to %2 is not supported + Obniżenie wersji folderu z %1 do %2 nie jest obsługiwane - OwncloudOAuthCredsPage + OCC::WebFingerLookupAdapter - - Form - Formularz + + Failed to retrieve user info + Nie udało się pobrać informacji o użytkowniku - - Please switch to your browser to proceed. - + + WebFinger lookup returned no links + Wyszukiwanie WebFinger nie zwróciło żadnych linków + + + OCC::WindowsUpdater - - An error occurred while connecting. Please try again. - + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Nowa wersja klienta %1 jest dostępna.</p><p><b>%2</b>jest dostępna do pobrania. Zainstalowana wersja to %3.<p> - - Re-open Browser - Otwórz przeglądarkę ponownie + + Skip this version + Pomiń tą wersję - - - OwncloudSetupPage - - Form - Formularz + + Update Failed + Błąd aktualizacji + + + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Dostępna jest nowa wersja klienta %1, ale proces aktualizacji nie powiódł się.</p> <p><b>Wersja %2</b> została pobrana. Zainstalowana wersja to %3.</p> - - Ser&ver Address - Adres ser&wera + + Ask again later + Zapytaj ponownie później - - https://... - https://... + + Restart and update + Zrestartuj i zaktualizuj - - Error Label - Etykieta błędu + + Update manually + Zaktualizuj ręcznie QObject - + in the future w przyszłości - + %n day(s) ago - %n dzień temu%n dni temu%n dni temu%n dni temu + %d dzień temu%n dni temu%n dni temu%n dni temu - + %n hour(s) ago - %n godzinę temu%n godziny temu%n godzin temu%n godzin temu + %n godzinę temu%n godzin temu%n godzin temu%n godzin temu - + now teraz - - Less than a minute ago + + less than a minute ago Mniej niż minutę temu - + %n minute(s) ago - %n minutę temu%n minuty temu%n minut temu%n minut temu - - - - Some time ago - Jakiś czas temu + %n minute temu%n minut temu%n minut temu%n minut temu - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 + + SpacesBrowser + + + Form + Formularz + + + + SpacesPage + + + Spaces list + Lista przestrzeni + + + + SpacesView + + + Spaces + Przestrzenie + + SyncFileItem::Status - + Undefined - + Niezdefiniowany - + Fatal Error Błąd systemu - - + + Error Błąd - + Info Info - + Success Sukces - + Conflict Konflikt - + File Ignored - + Plik zignorowany - + Restored - + Przywrócony - + Blacklisted - + Umieszczony na czarnej liście - + Excluded - - - - - Utility - - - %L1 GB - %L1 GB + Wykluczony - - %L1 MB - %L1 MB + + Message + Wiadomość - - %L1 KB - %L1 KB + + Filename Reserved + Nazwa pliku zarezerwowana + + + UpdateDownloadedWidget - - %L1 B - %L1 B + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style="font-size:11pt; font-weight:700;">Wymagane ponowne uruchomienie</span></p></body></html> + + + Utility - + %n year(s) - %n rok%n lata%n lat%n lat + - + %n month(s) - %n miesiąc%n miesiące%n miesięcy%n miesięcy + - + %n day(s) %n dzień%n dni%n dni%n dni - + %n hour(s) - %n godzina%n godziny%n godzin%n godzin + %n godzina%n godzin%n godzin%n godzin - + %n minute(s) - %n minuta%n minuty%n minut%n minut + %n minuta%n minut%n minut%n minut - + %n second(s) - %n sekunda%n sekundy%n sekund%n sekund + - + %1 %2 %1 %2 + + + Shutting down %1 + Zamykanie %1 + - main.cpp + account loading - - System Tray not available - Systemowy pasek nie dostępny + + Error accessing the configuration file + Błąd przy dostępie do pliku konfiguracji - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 potrzebuje na pracującej tacce systemu. Jeżeli używasz XFCE, proszę dostosować się <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">do tych instrukcji</a>. Alternatywnie, proszę zainstalować program tacki systemu jak np. 'trayer' i spróbować ponownie. + + There was an error while accessing the configuration file at %1. + Wystąpił błąd podczas próby dostępu do pliku konfiguracyjnego %1. ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Biblioteki Qt %3, %4%7Korzystanie z wtyczki plików wirtualnych: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - + + %1 (Built against Qt %2) + %1 (Zbudowano na podstawie Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Zawsze dostępny lokalnie + + + + Currently available locally + Obecnie dostępne lokalnie + + + + Some available online only + Niektóre dostępne tylko online + + + + Available online only + Dostępny tylko online + + progress Virtual file created - + Wirtualny plik utworzony Replaced by virtual file - + Zastąpiono plikiem wirtualnym @@ -4708,30 +3561,30 @@ This is a new, experimental mode. If you decide to use it, please report any iss Uploaded Wysłane - - - Server version downloaded, copied changed local file into conflict file - Pobrano wersję z serwera, zmienona wersja lokalna została skopiowana do pliku konfliktu - Deleted Usunięte - - Moved to %1 - Przeniesione do %1 - - - + Ignored Ignorowany - - Filesystem access error - Błąd dostępu do systemu plików + + Server version downloaded, local copy was backed up as conflict file + Pobrano wersję serwerową, kopia lokalna została zarchiwizowana jako plik konfliktu + + + + Renamed %1 to %2 + Zmieniono nazwę %1 na %2 + + + + Moved %1 to %2 + Przeniesiono %1 do %2 @@ -4744,153 +3597,86 @@ This is a new, experimental mode. If you decide to use it, please report any iss Zaktualizowano lokalne metadane - - + + Unknown Nieznany - + downloading pobieram - + uploading przesyłanie - + deleting usuwam - + moving przenoszę - + ignoring ignorowane - - + error błąd - + updating local metadata aktualizacja lokalnych metadanych - - theme - - - Status undefined - Stan niezdefiniowany - - - - Waiting to start sync - Trwa oczekiwanie na uruchomienie synchronizacji - - - - Sync is running - Synchronizacja uruchomiona - - - - Sync Success - Udana synchronizacja - - - - Sync Success, some files were ignored. - Synchronizacja ukończona, niektóre pliki zostały zignorowane. - - - - Sync Error - Błąd synchronizacji - - - - Setup Error - Błąd ustawień - - - - Preparing to sync - Przygotowuję do synchronizacji - - - - Aborting... - Anuluję... - - - - Sync is paused - Synchronizacja wstrzymana - - utility - + Could not open browser Nie można otworzyć przeglądarki - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? Podczas uruchamiania przeglądarki aby przejść do adresu URL %1 wystąpił problem. Czy skonfigurowano domyślną przeglądarkę? - + Could not open email client Nie można uruchomić klienta email - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? Podczas uruchamiania klienta poczty wystąpił problem. Czy skonfigurowano domyślnego klienta pocztowego? - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - Obecnie dostępne lokalnie - - - + Make always available locally - + Zrób zawsze dostępny lokalnie - + Free up local space - + Zwolnij przestrzeń lokalną + + + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Niektóre ustawienia zostały skonfigurowane w nowszych wersjach tego klienta i używają funkcji, które nie są dostępne w tej wersji \ No newline at end of file diff --git a/translations/client_pt.ts b/translations/client_pt.ts index 46c843fc84f..c596e54dd87 100644 --- a/translations/client_pt.ts +++ b/translations/client_pt.ts @@ -1,4467 +1,3559 @@ - + - FileSystem + AccountBar - - Could not make directories in trash - + + Navigation bar + Barra de navegação - - - Could not move '%1' to '%2' - Não foi possível mover '%1' para '%2' + + Add Account + Adicionar Conta - - Moving to the trash is not implemented on this platform - + + Activity + Actividade - - Error removing '%1': %2 - Erro ao remover '%1': %2; + + Settings + Definições - - Could not remove folder '%1' - Não foi possível remover a pasta '%1' + + Quit + Sair - FolderWizardSourcePage + AppImageUpdateAvailableWidgetUi - - Form - Formulário + + Skip this version + Ignorar esta versão + + + AppProvider - - Pick a local folder on your computer to sync - Escolha uma pasta local no seu computador para sincronizar + + Failed to open %1 in web. Error: %2. + Falha ao abrir %1 na web. Erro: %2. + + + + ChecksumHeader + + + The checksum header is malformed: %1 + Cabeçalho checksum inválido: %1 - - &Choose... - &Escolher... + + The checksum header contained an unknown checksum type '%1' + O header checksum continha um tipo de checksum desconhecido '%1' - FolderWizardTargetPage + CommandLine - - Form - Formulário + + %1 version %2 +File synchronization desktop utility. + %1 versão %2<byte value="xd"/> +Utilitário de sincronização de arquivos na área de trabalho. - - Select a remote destination folder - Selecione uma pasta de destino remota + + For more information, see %1 + link to homepage + Para mais informações, consulte %1 - - Create Folder - Criar Pasta + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Comece com a janela principal visível ou, se já estiver em execução, traga-a para a frente. Por padrão, o cliente é iniciado em segundo plano. - - Refresh - Atualizar + + Quit the running instance. + Saia da instância em execução. - - Folders - Pastas + + Write log to file (use - to write to stdout). + Grave o log no arquivo (use - para gravar no stdout). - - TextLabel - EtiquetaTexto + + Write each sync log output in a new file in folder. + Grave cada saída do log de sincronização em um novo arquivo na pasta. - - - NotificationWidget - - Form - Formulário + + Flush the log file after every write. + Libere o arquivo de log após cada gravação. - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet + + Output debug-level messages in the log. + Envie mensagens de nível de depuração no log. - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + Enable debug mode. + Ativar modo de depuração. - - TextLabel - EtiquetaTexto + + --logfile and --logdir are mutually exclusive + --logfile e --logdir são mutuamente exclusivos - OCC::AboutDialog + CommonStrings - - About - Sobre + + file manager + Gestor de ficheiros - - TextLabel - EtiquetaTexto + + Show in %1 + Mostrar em %1 - - Versions - Versões + + Show in web browser + Mostrar no browser - - About %1 - Sobre %1 + + Copy + Copiar + + + + %n Filter(s) + %n Filtro%n Filtros%n Filtros - - - OCC::AbstractNetworkJob - - Connection timed out - A ligação expirou + + folder + pasta - - Unknown error: network reply was deleted - + + space + espaço - - Server replied "%1 %2" to "%3 %4" - O Servidor respondeu "%1 %2" até "%3 %4" + + folders + pastas - - - OCC::Account - - %1@%2 - %1@%2 + + spaces + espaços - - - OCC::AccountSettings - - Form - Formulário + + Folder + Pasta - - Re-open Browser - + + Space + Espaço + + + FetchUserInfoJobFactory - - ... - ... + + Failed to retrieve user info + Falha ao recuperar informações do utilizador + + + FileSystem - - Storage space: ... - Espaço de armazenamento: ... + + + + + Can't rename %1, the file is currently in use + Não foi possível renomear %1, o arquivo está atualmente em uso - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - As pastas não selecionadas serão <b>removidas</b> do seu sistema de ficheiros local e já não serão sincronizadas para este computador + + Could not remove folder + Não foi possível remover a pasta + + + FolderItem - - Synchronize all - Sincronizar todos + + + Offline + Desconectado - - Synchronize none - Sincronizar nenhum + + + Unavailable: the space is no longer available on the server + Indisponível: o espaço não está mais disponível no servidor. - - Apply manual changes - Aplicar alterações manuais + + + Sync paused + Sincronização pausada - - Apply - Aplicar + + Synced + Sincronizado - - - - - Cancel - Cancelar + + Sync failed + Falha na sincronização - - Connected with <server> as <user> - Ligado ao <server> como <user> + + Sync pending + Sincronização pendente - - No account configured. - Nenhuma conta configurada. + + Syncing %1 of %2 (%3 % + Sincronizando %1 de %2 (%3 % - - Remove - Remover + + , ⬇️ %1/s + , ⬇️ %1/s - - Account - Conta + + , ⬆️ %1/s + , ⬆️ %1/s - - Choose what to sync - Escolher o que sincronizar + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, status de sincronização: %2 + first argument is the displayed name of the folder. + + + FolderManagementUtils - - Force sync now - Forçar a sincronização + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + O caminho '%1' é demasiado longo. Ative caminhos longos nas configurações do Windows ou escolha uma pasta diferente. - - Restart sync - Retomar sincronização + + Local folder %1 does not exist. + A pasta local %1 não existe. - - Remove folder sync connection - Remover ligação de sincronização de pasta + + %1 is not a folder. + %1 is não é uma pasta. - - Folder creation failed - Não foi possível criar a pasta + + %1 is not readable. + %1 não é legível. - - <p>Could not create local folder <i>%1</i>. - <p>Não foi possível criar a pasta local<i>%1</i>. + + %1 is not writable. + %1 não pode ser escrita. + + + FolderWizard - - Confirm Folder Sync Connection Removal - Confirmar Remoção da Ligação de Sincronização de Pasta + + <b>Error:</b> %1 + <b>Erro:</b> %1 - - Remove Folder Sync Connection - Remover Ligação da Sincronização de Pasta + + <b>Warning:</b> %1 + <b>Aviso:</b> %1 - - Disable virtual file support? - + + <b>Error:</b><ul>%1</ul> + <b>Erro:</b><ul>%1</ul> - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - + + <b>Warning:</b><ul>%1</ul> + <b>Alerta:</b><ul>%1</ul> + + + FolderWizardSourcePage - - Disable support - Desativar suporte + + Form + Formulário - - Sync Running - Sincronização em Execução + + Pick a local folder on your computer to sync + Escolha uma pasta local no seu computador para sincronizar - - The syncing operation is running.<br/>Do you want to terminate it? - A operação de sincronização está em execução.<br/>Deseja terminá-la? + + Browse… + Navegar… + + + FolderWizardTargetPage - - %1 in use - %1 em utilização + + Form + Formulário - - %1 as <i>%2</i> - %1 como <i>%2</i> + + Select a remote destination folder + Selecione uma pasta de destino remota - - Connected to %1. - Ligado a %1. + + Create Folder + Criar Pasta - - Server %1 is temporarily unavailable. - O servidor %1 está temporariamente indisponível. + + Refresh + Atualizar - - Server %1 is currently in maintenance mode. - O Servidor %1 encontra-se em manutenção + + Folders + Pastas + + + OCC::AboutView - - Signed out from %1. - Terminou a sessão de %1. + + About + Sobre - - Connecting to %1... - A Conectar a %1... + + Versions + Versões + + + OCC::AbstractNetworkJob - - No connection to %1 at %2. - Sem ligação para %1 em %2. + + Connection timed out + A ligação expirou - - Log in - Iniciar Sessão + + Unknown error: network reply was deleted + Erro desconhecido: a resposta da rede foi excluída - - There are folders that were not synchronized because they are too big: - Existem pastas que não foram sincronizadas por serem demasiado grandes: + + Server replied "%1 %2" to "%3 %4" + O Servidor respondeu "%1 %2" até "%3 %4" + + + OCC::Account - - There are folders that were not synchronized because they are external storages: - Existem pastas que não foram sincronizadas por serem armazenamento externo: + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - There are folders that were not synchronized because they are too big or external storages: - Existem pastas que não foram sincronizadas por serem demasiado grandes ou armazenamento externo: + + Choose what to sync + Escolha o que sincronizar - - Confirm Account Removal - Confirmar Remoção da Conta + + + Sync now + Sincronizar agora - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Deseja mesmo remover a ligação da conta <i>%1</i>?</p><p><b>Nota:</b> isto <b>não</b> irá eliminar quaisquer ficheiros.</p> + + Manage subfolder sync + Gerenciar sincronização de subpastas - - Remove connection - Remover ligação + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + Remover sincronização de %1 - - Open folder - Abrir pasta + + Disable virtual file support? + Desativar o suporte a arquivos virtuais? - - - Log out - Terminar sessão + + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. + +The only advantage of disabling virtual file support is that the selective sync feature will become available again. + +This action will abort any currently running synchronization. + Esta ação desativará o suporte a arquivos virtuais. Como consequência, o conteúdo das pastas que estão marcadas como "disponíveis somente online" será baixado. + +A única vantagem de desativar o suporte a arquivos virtuais é que o recurso de sincronização seletiva voltará a estar disponível. + +Esta ação interromperá qualquer sincronização em andamento. - - Open local folder - + + Disable support + Desativar suporte - - Open folder in browser - + + Restart sync + Reiniciar sincronização - + Resume sync Retomar sincronização - + Pause sync Pausar sincronização - - Availability - - - - - Disable virtual file support... - + + Deactivate virtual files + Desativar arquivos virtuais - - Enable virtual file support%1... - + + Activate virtual files + Ativar arquivos virtuais - - (experimental) - (experimental) + + Internet connection is metered + A conexão de internet é tarifada. - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Deseja mesmo parar a sincronização da pasta <i>%1</i>?</p><p><b>Nota:</b> isto <b>não</b> irá eliminar qualquer ficheiro.</p> + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + A sincronização está pausada porque a conexão com a Internet é limitada.<p>Tem mesmo vontade de forçar uma sincronização agora? - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) de %2 em utilização. Algumas pastas, incluindo a rede montada ou as pastas partilhadas, podem ter limites diferentes. + + Sync Running + Sincronização em execução - - %1 of %2 in use - %1 de %2 em utilização + + The sync operation is running.<br/>Do you want to stop it? + A operação de sincronização está em andamento. Deseja interrompê-la? - - Currently there is no storage usage information available. - Atualmente não está disponível nenhuma informação da utilização do armazenamento. + + Sync paused by user + Sincronização pausada pelo usuário - - The server version %1 is unsupported! Proceed at your own risk. - + + Confirm Folder Sync Connection Removal + Confirmar remoção da conexão de sincronização de pastas - - Obtaining authorization from the browser. - + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + Tem mesmo vontade de parar a sincronização da pasta 2%12?134Nota:4 Isso não excluirá nenhum arquivo.3 - - Server configuration error: %1 at %2. - + + Remove Folder Sync Connection + Remover conexão de sincronização de pastas - - No %1 connection configured. - %1 sem ligação configurada. + + + Cancel + Cancelar - OCC::AccountState - - - Signed out - Sessão terminada - + OCC::AccountFoldersView - - Disconnected - Desligado + + Sync options menu + Menu de opções de sincronização - - Connected - Ligado + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 sincronizar - - Service unavailable - Serviço indisponível + + Manage your synced %1. + Gerencie seus %1 sincronizados. - - Maintenance mode - Modo de Manutenção + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Adicionar nova sincronização %1… - - Network error - Erro de rede + + %1 list view + %1 visualização em lista - - Configuration error - Erro de configuração + + Navigate the %1 list using the up and down arrows + Navegue pela lista %1 usando as setas para cima e para baixo. - - Asking Credentials - + + %1 out of %2 %3 are synchronized + %1 de %2 %3 estão sincronizados + + + OCC::AccountModalWidget - - Unknown account state - Estado de conta desconhecido + + + Form + Formulário - OCC::ActivityItemDelegate + OCC::AccountPlaceholderWidget - - %1 on %2 - %1 em %2 + + %1 logo + %1 logotipo - - %1 on %2 (disconnected) - %1 em %2 (desligado) + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + Você ainda não possui nenhuma conta conectada. + +Crie uma conta selecionando a opção "Adicionar conta..." no menu do botão Mais. - - - OCC::ActivityListModel - - %1 %2 on %3 - + + Additional logo defined by the organization + Logotipo adicional definido pela organização - OCC::ActivitySettings + OCC::AccountView - - - Server Activity - Atividade do Servidor + + Form + Forma - - Sync Protocol - Protocolo de Sincronização + + Manage Account + Gerenciar conta - - Not Synced - Não Sincronizado + + Preparing the account + Preparando a conta - - Not Synced (%1) - %1 is the number of not synced files. - Não Sincronizado (%1) + + Sync connections + Sincronizar conexões - - The server activity list has been copied to the clipboard. - A lista de atividades do servidor foi copiada para a área de transferência. + + Account options menu + Menu de opções da conta + + + OCC::AccountViewController - - The sync activity list has been copied to the clipboard. - A lista de atividades de sincronização foi copiada para a área de transferência. + + + Log in + Conectar - - The list of unsynced items has been copied to the clipboard. - A lista de itens não sincronizados foi copiada para a área de transferência. + + Reconnect + Reconectar - - Copied to clipboard - Copiado para a área de transferência + + Remove + Remover - - - OCC::ActivityWidget - - Form - Formulário + + Log out + Sair - - - - TextLabel - EtiquetaTexto + + Confirm Account Removal + Confirmar Remoção da Conta - - Server Activities - Atividades do Servidor + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Deseja mesmo remover a ligação da conta <i>%1</i>?</p><p><b>Nota:</b> isto <b>não</b> irá eliminar quaisquer ficheiros.</p> - - Copy - Copiar + + Remove connection + Remover ligação - - Copy the activity list to the clipboard. - Copiar a lista de actividades para a área de transferência. + + Cancel + Cancelar - - Notifications - Notificações - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - + + The server version %1 is unsupported! Proceed at your own risk. + A versão do servidor %1 não é suportada! Prossiga por sua conta e risco. - - New notifications for %1, %2 and other accounts. - + + Connected + Conectado - - Open the activity view for details. + + Server is temporarily unavailable. - - <br/>Account %1 does not have activities enabled. - <br/>A conta %1 não tem atividades ativadas. + + Server is currently in maintenance mode. + - - - OCC::AddCertificateDialog - - SSL client certificate authentication - Autenticação do certificado de cliente SSL + + Signed out + Saiu - - This server probably requires a SSL client certificate. - Este servidor provavelmente requer um certificado de cliente SSL. + + Updating credentials… + Atualizando credenciais… - - Certificate & Key (pkcs12) : - Certificado e Chave (pkcs12) : + + Captive portal prevents connections to the server. + O portal cativo impede as conexões com o servidor. - - Browse... - Explorar... + + Sync is paused due to metered internet connection. + - - Certificate password : - Palavra-passe do certificado: + + Connecting… + Conectando… - - An encrypted pkcs12 bundle is strongly recommended as a copy will be stored in the configuration file. - + + Server configuration error + Erro de configuração do servidor - - Select a certificate - Selecionar um certificado + + Disconnected + Desligado - - Certificate files (*.p12 *.pfx) - Ficheiros de certificado (*.p12 *.pfx) + + Invalid connection status + - OCC::Application + OCC::AccountsGuiController - - Continuing will mean <b>deleting these settings</b>. - + + + New account failure + Falha na criação da conta - - Continuing will mean <b>ignoring these settings</b>. - + + The information required to create a new account is incomplete. Please run the wizard again. + As informações necessárias para criar uma nova conta estão incompletas. Execute o assistente novamente. - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - + + Accounts + Contas - - Quit - Sair + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + Não foi possível criar a conta devido a um erro: +%1 +Verifique a disponibilidade do servidor e execute o assistente novamente. - - - Continue - Continuar + + + OCC::ActivitySettings + + + Local Activity + Atividade local - - Error accessing the configuration file - Erro a aceder ao ficheiro de configuração + + Not Synced + Não Sincronizado - - There was an error while accessing the configuration file at %1. - Ocorreu um erro enquanto se acedia ao ficheiro de configuração em %1. + + Not Synced (%1) + %1 is the number of not synced files. + Não Sincronizado (%1) + + + + OCC::AdvancedSettingsPageController + + + Advanced settings + Configurações avançadas + + + + Sync and download options + Opções de sincronização e download + + + + Download location + Local de download + + + + Sync and download files as you use them (saves hard drive space) + Sincronize e baixe arquivos conforme os utiliza (economiza espaço no disco rígido). + + + + Automatically sync and download folders and files + Sincronize e baixe pastas e arquivos automaticamente. + + + + Sync and download folders manually + Sincronizar e baixar pastas manualmente + + + + Download location on the local machine + Local para baixar na máquina local + + + + Browse… + Navegar… + + + + Browse for a download location + Procure um local para salvar o que baixar. + + + + Invalid local download directory %1: %2 + Diretório local para baixar é inválido %1: %2 + + + + your user directory may not be chosen as the sync root. + Seu diretório de usuário pode não ter sido escolhido como raiz de sincronização. + + + + path is not located on a supported Apple File System. + O caminho não está localizado em um sistema de arquivos Apple compatível. + + + + path must be absolute. + O caminho deve ser absoluto. + + + + selected path does not support using virtual file system. %1 + O caminho selecionado não suporta o uso de sistema de arquivos virtual. %1 + + + + Select sync root + Selecione a raiz de sincronização + + + + OCC::AppImageUpdateAvailableWidget + + + Installed version: %1 + Versão instalada: %1 + + + + Available update: %1 + Atualização disponível: %1 - - Quit ownCloud - Sair do ownCloud + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Uma atualização está disponível para este AppImage de %1. Deseja instalar esta atualização? + +A atualização será executada em segundo plano e substituirá o arquivo AppImage atual. Você precisa reiniciar o aplicativo para concluir a atualização. - OCC::AuthenticationDialog + OCC::Application - - Authentication Required - Autenticação Requerida + + Quit + Sair + + + OCC::AuthSuccessPageController - - Enter username and password for '%1' at %2. - Insira o nome de utilizador e a palavra-passe para '%1' em %2. + + Application Name Logo %1 + Logotipo do nome do aplicativo %1 - - &User: - &Utilizador: + + You're all set! Open %1 and get started. + Está tudo pronto! Abra %1 e comece. - - &Password: - &Palavra-passe: + + Additional logo defined by the organization + Logotipo adicional definido pela organização - OCC::CleanupPollsJob + OCC::ButtonDelegate - - Error writing metadata to the database - Erro ao gravar os metadados para a base de dados + + %1 options button + Botão de opções %1 + + + + Menu button with %1 options. Use the space key to show the menu + Botão de menu com opções %1. Use a tecla espaço para exibir o menu. OCC::ConnectionValidator - + No ownCloud account configured Não foi configurada nenhuma conta ownCloud - - The configured server for this client is too old - O servidor configurado para este cliente é muito antigo - - - + Please update to the latest server and restart the client. Por favor, atualize para a ultima versão do servidor e reinicie o cliente. - + Authentication error: Either username or password are wrong. Erro de autenticação: o nome de utilizador ou a palavra-passe estão errados. - + timeout tempo expirado - + + No account configured + Nenhuma conta configurada + + + The provided credentials are not correct As credenciais fornecidas não estão corretas + + + The configured server for this client is too old. + O servidor configurado para este cliente é muito antigo. + + + + OCC::CredentialJob + + + Failed to parse credentials %1 + Falha ao analisar as credenciais %1 + + + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + O servidor não suporta OAuth. + + + + Server did not ask for authorization + Servidor não pediu autorização + + + + Failed to determine authentication type: %1 + Não foi possível determinar o tipo de autenticação: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + Resposta errada recebida do servidor + OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + A conta foi excluída antes que pudéssemos iniciar a busca de propriedades. + + + + Server error: PROPFIND reply is not XML formatted! - + Erro do servidor: resposta PROPFIND não está formatada em XML! OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 - + Erro ao abrir o diretório %1 - + Directory not accessible on client, permission denied - + Diretório não acessível no cliente, permissão negada - + Directory not found: %1 Diretoria não encontrada: %1 - - Filename encoding is not valid - Codificação de nome de ficheiro não é válida + + Error while reading directory %1 + Erro ao ler o diretório %1 + + + OCC::ExpandingHeaderView - - Error while reading directory %1 - + + Reset column sizes + Redefinir tamanhos de coluna - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - A pasta local %1 não existe. + + server sent invalid content type: %1. + O servidor enviou um tipo de conteúdo inválido: %1. - - %1 should be a folder but is not. - %1 deveria ser uma pasta, mas não é. + + could not parse Capabilities from server. + Não foi possível analisar as capacidades do servidor. - - %1 is not readable. - %1 não é legível. + + reply sent for different subject (server): %1 + Resposta enviada para assunto diferente (servidor): %1 + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 foi removido. - + %1 has been updated. %1 names a file. %1 foi atualizado. - + %1 has been renamed to %2. %1 and %2 name files. %1 foi renomeado para %2 - + %1 has been moved to %2. %1 foi movido para %2 - + %1 and %n other file(s) have been removed. - '%1' e %n outro(s) ficheiro(s) foram removidos'%1' e %n outros ficheiros foram removidos. + %1 e %n Outros arquivos foram removidos.%1 e %n Outros arquivos foram removidos.%1 e %n Outros arquivos foram removidos. + + + + The folder has been disabled or removed from the server + A pasta foi desabilitada ou removida do servidor - + %1 and %n other file(s) have been added. - + %1 e %n Outros arquivos foram adicionados.%1 e %n Outros arquivos foram adicionados.%1 e %n Outros arquivos foram adicionados. - + %1 has been added. %1 names a file. - + %1 foi adicionado. - + %1 and %n other file(s) have been updated. - %1 e %n outro ficheiro foram actualizados.%1 e %n outros ficheiros foram actualizados. + %1 e %n Outros arquivos foram atualizados.%1 e %n Outros arquivos foram atualizados.%1 e %n Outros arquivos foram atualizados. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 foi renomeado para %2 e %n outro ficheiro foi renomeado.%1 foi renomeado para %2 e %n outros ficheiros foram renomeados. + %1 foi renomeado para %2 e %n Outros arquivos foram renomeados.%1 foi renomeado para %2 e %n Outros arquivos foram renomeados.%1 foi renomeado para %2 e %n Outros arquivos foram renomeados. - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 foi movido para %2 e %n outro ficheiro foi movido.%1 foi movido para %2 e %n outros ficheiros foram movidos. + %1 foi movido para %2 e %n Outros arquivos foram movidos.%1 foi movido para %2 e %n Outros arquivos foram movidos.%1 foi movido para %2 e %n Outros arquivos foram movidos. - - %1 has and %n other file(s) have sync conflicts. - %1 tem e %n outro ficheiro têm problemas de sincronização.%1 tem e %n outros ficheiros têm problemas de sincronização. + + %1 and %n other file(s) have sync conflicts. + %1 e %n Outros arquivos têm conflitos de sincronização.%1 e %n Outros arquivos têm conflitos de sincronização.%1 e %n Outros arquivos têm conflitos de sincronização. - + %1 has a sync conflict. Please check the conflict file! %1 tem um problema de sincronização. Por favor, verifique o ficheiro com conflito! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 e %n outro ficheiro não podem ser sincronizados devido a erros. Consulte o registo de eventos para mais detalhes.%1 e %n outros ficheiros não podem ser sincronizados devido a erros. Consulte o registo de eventos para mais detalhes. + %1 e %n Outros arquivos não puderam ser sincronizados devido a erros. Veja o log para obter detalhes.%1 e %n Outros arquivos não puderam ser sincronizados devido a erros. Veja o log para obter detalhes.%1 e %n Outros arquivos não puderam ser sincronizados devido a erros. Veja o log para obter detalhes. - + %1 could not be synced due to an error. See the log for details. Não foi possível sincronizar %1 devido a um erro. Consulte o registo para detalhes. - + Sync Activity Atividade de Sincronização - - Could not read system exclude file - Não foi possível ler o ficheiro excluir do sistema - - - - A new folder larger than %1 MB has been added: %2. - - Foi adicionada uma nova pasta maior que %1 MB: %2. - + + Switching VFS mode on folder '%1' + Alternando o modo VFS na pasta '%1' - - A folder from an external storage has been added. - - Foi adicionada uma pasta vinda de armazenamento externo. - - - - - Please go in the settings to select it if you wish to download it. - Por favor, vá às configurações para a selecionar se a desejar transferir. + + Could not read system exclude file + Não foi possível ler o ficheiro excluir do sistema - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - + A pasta %1 foi criada, mas foi excluída da sincronização anteriormente. Os dados dentro dela não serão sincronizados. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - + A arquivo %1 foi criado, mas foi excluído da sincronização anteriormente. Ele não será sincronizado. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). %1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Todos os ficheiros na pasta sincronizada '%1' foram apagados do servidor. -Estas deleções irão ser sincronizadas para a sua pasta local de sincronização, fazendo com que os ficheiros fiquem indisponíveis, a não ser que tenha permissões para restaurar os mesmos . -Se decidir manter os ficheiros, eles serão sincronizados novamento para o servidor se tiver permissões para tal. -Se decidir apagar os ficheiros, eles ficaram indisponíveis para si, a não ser que seja o seu proprietário. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Todos os ficheiros na pasta local de sincronização '%1' foram apagados. Estas deleções irão ser sincronizadas com o servidor, fazendo com que os ficheiros fiquem indisponíveis, a não ser que sejam restaurados. -Tem a certeza que deseja sincronizar essas ações com o servidor? -Se foi acidental e decidir manter os seus ficheiros, eles serão sincronizados novamente apartir do servidor. - - - - Remove All Files? - Remover todos os ficheiros? + As alterações nas pastas sincronizadas não puderam ser rastreadas de maneira confiável. + +Isso significa que o cliente de sincronização pode não fazer envios de alterações locais imediatamente e, em vez disso, só varre as alterações locais e as carrega ocasionalmente (a cada duas horas, por padrão). + +%1 + + + OCC::FolderItemUpdater - - Remove all files - Remover todos os ficheiros + + There are unresolved conflicts. + Existem conflitos não resolvidos. - - Keep files - Manter ficheiros + + Sync error: %1 + Erro de sincronização: %1 OCC::FolderMan - + Could not reset folder state Não foi possível reiniciar o estado da pasta - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Foi encontrado um 'journal' de sincronização, mas não foi possível removê-lo. Por favor, certifique-se que nenhuma aplicação o está a utilizar. - - (backup) - (cópia de segurança) + + Unsupported filesystem + Sistema de arquivos não suportado - - (backup %1) - (cópia de segurança %1) + + Account disconnected or paused + Conta desconectada ou pausada + + + + User triggered force sync + Sincronização forçada acionada pelo usuário - - Undefined State. - Estado indefinido. + + Folder is about to be removed + A pasta está prestes a ser removida - - Waiting to start syncing. - A aguardar para iniciar a sincronização. + + (backup) + (cópia de segurança) - - Preparing for sync. - A preparar para sincronizar. + + (backup %1) + (cópia de segurança %1) - - Sync is running. - A sincronização está em execução. + + The selected path is not a folder. + O caminho selecionado não é uma pasta! - - Sync was successful, unresolved conflicts. - + + The folder %1 is used in a folder sync connection. + A pasta %1 é usada em uma conexão de sincronização de pasta! - - Last Sync was successful. - A última sincronização foi bem sucedida. + + Folder '%1' is already in use by application %2. + A pasta '%1' já está em uso pelo aplicativo %2! - - Setup Error. - Erro de instalação. + + There is already a sync from the server to this local folder. + Já existe uma sincronização do servidor para esta pasta local. - - User Abort. - Abortado pelo utilizador. + + The local folder %1 already contains a folder used in a folder sync connection. + A pasta local %1 já contém uma pasta utilizada numa ligação de sincronização de pasta. - - Sync is paused. - A sincronização está pausada. + + The local folder %1 is already contained in a folder used in a folder sync connection. + A pasta local %1 já contém uma pasta utilizada numa ligação de sincronização de pasta. - - %1 (Sync is paused) - %1 (A sincronização está pausada) + + No valid folder selected. + Nenhuma pasta válida selecionada! - - No valid folder selected! - Não foi selecionada nenhuma pasta válida! + + The selected path does not exist. + O caminho selecionado não existe! - - The selected path does not exist! - + + You have no permission to write to the selected folder. + Voce não tem permissão para escrita na pasta selecionada! - - The selected path is not a folder! - O caminho selecionado não é uma pasta! + + Folder '%1' is already in use by another account. + Pasta '%1' já está em uso por outra conta. + + + + On macOS, only the Apple File System is supported. + No macOS, somente o sistema de arquivos Apple File System é suportado. + + + OCC::FolderModelController - - You have no permission to write to the selected folder! - Não tem permissão para gravar para a pasta selecionada! + + %1 options + %1 opções - - There is already a sync from the server to this local folder. Please pick another local folder! - Já existe uma sincronização do servidor para esta pasta local. Por favor escolha outra pasta local! + + Menu button with %1 options. Use the space key to pop the %2 options menu + Botão de menu com %1 opções. Use a tecla espaço para abrir o menu de opções %2. + + + OCC::FolderWatcher - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - A pasta local %1 já contém uma pasta utilizada numa ligação de sincronização de pasta. Por favor, escolha outra! + + The watcher did not receive a test notification. + O observador não recebeu uma notificação de teste. + + + OCC::FolderWatcherPrivate - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - A pasta local %1 já contém uma pasta usada numa ligação de sincronização de pasta. Por favor, escolha outra! + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Esse problema geralmente acontece quando o inotify esgotou as notificações. Verifique o FAQ para mais detalhes. - OCC::FolderStatusDelegate + OCC::FolderWizard - + + Virtual files are not available for the selected folder + Arquivos virtuais não estão disponíveis para a pasta selecionada + + + Add Folder Sync Connection Adicionar Ligação de Sincronização de Pasta - - File - Ficheiro + + Add Sync Connection + Adicionar Ligação de Sincronização - OCC::FolderStatusModel + OCC::FolderWizardLocalPath - - You need to be connected to add a folder - Precisa de estar ligado para adicionar uma pasta + + Click to select a local folder to sync. + Clique para selecionar a pasta local para sincronizar. - - Click this button to add a folder to synchronize. - Clique neste botão para adicionar uma pasta para sincronizar. + + Enter the path to the local folder. + Insira o caminho para a pasta local. - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Select the local folder + Selecione a pasta local + + + OCC::FolderWizardSelectiveSync - - Error while loading the list of folders from the server. - Erro durante o carregamento da lista de pastas a partir do servidor. + + Use virtual files instead of downloading content immediately + Use arquivos virtuais em vez de baixar o conteúdo imediatamente + + + OCC::GETFileJob - - Virtual file support is enabled. - + + Connection Timeout + O tempo de ligação expirou - - Signed out - Sessão terminada + + No E-Tag received from server, check Proxy/Gateway + Nenhum E-Tag recebida do servidor, verifique Proxy / Acesso - - Fetching folder list from server... - A obter a lista de pastas do servidor... + + We received a different E-Tag for resuming. Retrying next time. + Nós recebemos uma E-Tag diferente para retomar. Tentar da próxima vez. - - There are unresolved conflicts. Click for details. - + + We received an unexpected download Content-Length. + Recebemos um download de Comprimento do Conteúdo inesperado. . - - Synchronizing VirtualFiles with local folder - + + Server returned wrong content-range + O servidor devolveu o alcance-conteúdo errado + + + OCC::GraphApi::Space - - Synchronizing with local folder - A sincronizar com a pasta local + + Personal + Pessoal - - Checking for changes in remote '%1' - + + Shares + Partilhas + + + OCC::IgnoreListEditor - - Checking for changes in local '%1' - + + Ignored Files Editor + Editor de Ficheiros Ignorados - - Reconciling changes - + + Files Ignored by Patterns + Ficheiros Ignorados por Padrões - - , '%1' - Build a list of file names - , '%1' + + Add + Adicionar - - '%1' - Argument is a file name - '%1' + + Pattern + Padrão - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - A sincronizar %1 + + Allow Deletion + Permitir Eliminação - - - , - , + + Remove + Remover - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - transferir %s + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Arquivos ou pastas correspondentes a um padrão não serão sincronizados. As alterações terão efeito na próxima vez que as pastas forem sincronizadas. + +Os itens em que a exclusão é permitida serão excluídos se impedirem que um diretório seja removido. Isso é útil para metadados. - - ↓ %1/s - + + Could not open file + Não foi possível abrir o ficheiro - - ↑ %1/s - + + Cannot write changes to '%1'. + Não foi possível gravar as alterações para '%1' - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - enviar %1/s + + Add Ignore Pattern + Adicione Padrão de ignorar - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 de %4) + + Add a new ignore pattern: + Adicione um novo padrão de ignorar: - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 + + This entry is provided by the system at '%1' and cannot be modified in this view. + Esta entrada é fornecida pelo sistema em '%1' e não pode ser modificada nesta visualização. + + + OCC::LocalActivityWidget - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 restante, %1 de %2, ficheiro %3 de %4 + + Filter + Filtro - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 de %2, ficheiro %3 de %4 + + Local activity table + Tabela de atividade local - - file %1 of %2 - ficheiro %1 de %2 + + + Account + Conta - - Waiting... - A aguardar... + + Filter menu + Menu do filtro - - - Waiting for %n other folder(s)... - A aguardar por %n outra pasta...A aguardar por %n outra(s) pasta(s)... + + + Actions menu + Menu de ações - - Preparing to sync... - A preparar para sincronizar... + + Sort ascending by %1 + Classificar ascendente por %1 - - - OCC::FolderWatcher - - The watcher did not receive a test notification. - + + Sort descending by %1 + Classificar descendo por %1 - - - OCC::FolderWatcherPrivate - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - + + Retry sync + Tentar sincronizar novamente - OCC::FolderWizard + OCC::LogBrowser - - Add Folder Sync Connection - Adicionar Ligação de Sincronização de Pasta + + Log Output + Registo de Saída - - Add Sync Connection - Adicionar Ligação de Sincronização - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Clique para selecionar a pasta local para sincronizar. - - - - Enter the path to the local folder. - Insira o caminho para a pasta local. - - - - Select the source folder - Selecione a pasta de origem - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Criar Pasta Remota - - - - Enter the name of the new folder to be created below '%1': - Insira o nome da nova pasta para ser criada em baixo '%1': - - - - Folder was successfully created on %1. - A pasta foi criada com sucesso em %1. - - - - Authentication failed accessing %1 - A autenticação falhou ao aceder %1 - - - - Failed to create the folder on %1. Please check manually. - Não foi possível criar a pasta em %1. Por favor, verifique manualmente. - - - - Failed to list a folder. Error: %1 - Não foi possível listar uma pasta. Erro: %1 - - - - Choose this to sync the entire account - Escolha isto para sincronizar a conta completa - - - - This folder is already being synced. - Esta pasta já foi sincronizada. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Já está a sincronizar <i>%1</i>, que é uma pasta de origem de <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - (experimental) - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Aviso:</b> %1 - - - - <b>Warning:</b> - <b>Aviso:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Nenhum E-Tag recebida do servidor, verifique Proxy / Acesso - - - - We received a different E-Tag for resuming. Retrying next time. - Nós recebemos uma E-Tag diferente para retomar. Tentar da próxima vez. - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - O servidor devolveu o alcance-conteúdo errado - - - - OCC::GETJob - - - Connection Timeout - O tempo de ligação expirou - - - - OCC::GeneralSettings - - - Form - Formulário - - - - General Settings - Configuração Geral - - - - Sync hidden files - Sincronizar os ficheiros ocultados - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Rede - - - - Updates - Atualizações - - - - stable - - - - - beta - - - - - About - Sobre - - - - Advanced - Avançada - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Pedir confirmação antes de sincronizar pastas maiores que - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Pedir confirmação antes de sincronizar armazenamentos externos - - - - &Launch on System Startup - &Iniciar no Arranque do Sistema - - - - Show &Desktop Notifications - Mostrar Notificações no &Ambiente de Trabalho - - - - &Update Channel - - - - - Edit &Ignored Files - Editar Ficheiros &Ignorados - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - M&ostrar relatório de crache - - - - &Restart && Update - &Reiniciar e Atualizar - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Cancelar - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Por favor, insira a palavra-passe %1:<br><br>Utilizador: %2<br>Conta: %3<br> - - - - Reading from keychain failed with error: '%1' - A leitura da cadeia de dados falhou com um erro: '%1' - - - - Enter Password - Insira a Palavra-passe - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Clique aqui</a> para solicitar uma palavra-passe da aplicação a partir da interface da Web. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor de Ficheiros Ignorados - - - - Files Ignored by Patterns - Ficheiros Ignorados por Padrões - - - - Add - Adicionar - - - - Pattern - Padrão - - - - Allow Deletion - Permitir Eliminação - - - - Remove - Remover - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Não foi possível abrir o ficheiro - - - - Cannot write changes to '%1'. - Não foi possível gravar as alterações para '%1' - - - - Add Ignore Pattern - Adicione Padrão de ignorar - - - - Add a new ignore pattern: - Adicione um novo padrão de ignorar: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Esta entrada é fornecida pelo sistema em '%1' e não pode ser modificada nesta visualização. - - - - OCC::IssuesWidget - - - Form - Formulário - - - - List of issues - Lista de situações - - - - Account - Conta - - - - - <no filter> - <no filter> - - - - - Folder - Pasta - - - - Show warnings - Mostrar Avisos - - - - Show ignored files - Mostrar ficheiros ignorados - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - Copiar a lista de situações para a área de transferência - - - - Copy - Copiar - - - - Time - Tempo - - - - File - Ficheiro - - - - Issue - Situação - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LibCloudProvidersPrivate - - - Settings - Definições - - - - OCC::LogBrowser - - - Log Output - Registo de Saída - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Abrir pasta - - - - OCC::NSISUpdater - - - New Version Available - Disponível Nova Versão - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Está disponível uma nova versão do Cliente %1.</p><p><b>%2</b> está disponível para transferência. A versão instalada é a %3.</p> - - - - - Skip this version - Ignorar esta versão - - - - Skip this time - Ignorar desta vez - - - - Get update - Obter atualização - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - Formulário - - - - Proxy Settings - Definições de Proxy - - - - No Proxy - Sem Proxy - - - - Use system proxy - Utilizar proxy do sistema - - - - Specify proxy manually as - Especificar proxy manualmente como - - - - Host - Anfitrião - - - - : - : - - - - Proxy server requires authentication - O servidor de proxy requer autenticação - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Largura de Banda de Transferência - - - - - Limit to - Limitar para - - - - - KBytes/s - KBytes/s - - - - - No limit - Sem limite - - - - - Limit to 3/4 of estimated bandwidth - Limitar para 3/4 da largura de banda estimada - - - - Upload Bandwidth - Largura de Banda de Envio - - - - - Limit automatically - Limitar automaticamente - - - - Hostname of proxy server - Nome de anfitrião do servidor proxy - - - - Username for proxy server - Nome de utilizador para o servidor proxy - - - - Password for proxy server - Palavra-passe para o servidor proxy. - - - - HTTP(S) proxy - Proxy de HTTP(S) - - - - SOCKS5 proxy - Proxy de SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - Criado às %1 - - - - Closing in a few seconds... - A fechar dentro de alguns segundos... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 pedido falhou a %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' selecionado a %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Empty JSON from OAuth2 redirect - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Não foi possível processar a resposta JSON retornada pelo servidor: <br><em>%1</em> - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Error: Missing field %1 - - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Erro no login</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - Nova Atualização %1 Pronta - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Está prestes a ser instalada uma nova atualização para %1. O atualizador -poderá pedir por privilégios adicionais durante o processo. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Não foi possível transferir a atualização. Por favor, clique <a href='%1'>aqui</a> para a transferir manualmente. - - - - Could not check for new updates. - Não foi possível procurar por novas atualizações. - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - A procurar por atualizações no servidor... - - - - Update status is unknown: Did not check for new updates. - O estado de atualização é desconhecido: não foi possível procurar por novas atualizações. - - - - No updates available. Your installation is at the latest version. - Não estão disponíveis atualizações. A sua instalação já está com a última versão. - - - - Update Check - Procurar por Atualizações - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Ligar a %1 - - - - Setup local folder options - Configurar as opções de pasta local - - - - Connect... - Ligar... - - - - (experimental) - (experimental) - - - - Use &virtual files instead of downloading content immediately%1 - - - - - %1 folder '%2' is synced to local folder '%3' - %1 pasta '%2' é sincronizada para pasta local '%3' - - - - Sync the folder '%1' - Sincronizar a pasta '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>Aviso:</strong> a pasta local não está vazia. Escolha uma resolução!</small></p> - - - - Virtual files are not available for the selected folder - - - - - Local Sync Folder - Pasta de Sincronização Local - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Ligação falhou - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Não foi possível ligar ao endereço do servidor seguro especificado. Como deseja proceder?</p></body></html> - - - - Select a different URL - Selecionar um URL diferente - - - - Retry unencrypted over HTTP (insecure) - Repetir não encriptado sobre HTTP (inseguro) - - - - Configure client-side TLS certificate - Configurar certificado TLS do lado do cliente - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Não foi possível ligar ao endereço do servidor seguro <em>%1</em>. Como deseja proceder?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Correio Eletrónico - - - - Connect to %1 - Ligar a %1 - - - - Enter user credentials - Insira as credenciais do utilizador - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Ligar a %1 - - - - Login in your browser - Faça login através do browser + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>Os registros contêm informações confidenciais que você não deve disponibilizar publicamente</b></span></p></body></html> - - Copy link to clipboard - Copiar ligação para a área de transferência + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + O cliente pode gravar logs de depuração em uma pasta temporária. Esses logs são muito úteis para diagnosticar problemas. +Como os arquivos de log podem ficar grandes, o cliente iniciará um novo para cada execução de sincronização e compactará os mais antigos. - - - OCC::OwncloudSetupPage - - Connect to %1 - Ligar a %1 + + Enable logging to temporary folder + Ativar o log para a pasta temporária - - Setup %1 server - Configurar servidor %1 + + If enabled, logs will be written to: + Se habilitado, os logs serão escritos para: - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Este url NÃO é seguro, porque este não está encriptado. -Não é aconselhada a sua utilização. + + C:/log + C:/log - - This url is secure. You can use it. - Este url é seguro. Pode utilizá-lo. + + Log Http traffic + Log de tráfego Http - - &Next > - &Seguinte > + + Log files to keep: + Arquivos de registro a serem mantidos: - - Invalid URL - URL inválido + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Essas configurações persistem nas reinicializações do cliente. +Observe que o uso de qualquer opção de linha de comando de registro substituirá as configurações. - - Could not load certificate. Maybe wrong password? - + + Open folder + Abrir pasta - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Não foi possível ligar a %1 em %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Tempo expirou enquanto tentava ligar a %1 em %2. - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - O pedido de autenticação para o servidor foi redirecionado para '%1'. O URL é mau, o servidor está mal configurado. - - - - There was an invalid response to an authenticated webdav request - Houve uma resposta inválida para o pedido de autenticação webdav - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - Acesso proibido pelo servidor. Para verificar que tem o acesso adequado, <a href="%1">clique aqui</a> para aceder ao serviço com o seu navegador. - - - - Invalid URL - URL inválido - - - - Could not create local folder %1 - Não foi possível criar a pasta local %1 - - - - No remote folder specified! - Não foi indicada a pasta remota! - - - - Error: %1 - Erro: %1 - - - - The folder creation resulted in HTTP error code %1 - A criação da pasta resultou num erro HTTP com o código %1 - + OCC::MainWindow - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - A criação da pasta remota falhou, provavelmente por ter introduzido as credenciais erradas.<br/>Por favor, verifique as suas credenciais.</p> + + Hide + Ocultar - - Remote folder %1 creation failed with error <tt>%2</tt>. - A criação da pasta remota %1 falhou com o erro <tt>%2</tt>. + + Main toolbar for the application + Barra de ferramentas principal do aplicativo - - Folder rename failed - Erro ao renomear a pasta + + Main toolbar + Barra de ferramentas principal - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Não é possível remover e fazer backup à pasta porque a pasta ou um ficheiro nesta está aberto em outro programa. Por favor, feche a pasta ou o ficheiro e clique novamente ou cancele a configuração. + + + More + Mais - OCC::OwncloudWizard + OCC::MainWindowController - - %1 Connection Wizard - Assistente de ligação %1 + + Add account... + Adicionar conta... - - Enable experimental feature? - Ativar funcionalidades experimentais + + Settings... + Configurações... - - Enable experimental placeholder mode - + + About... + Sobre... - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - + + Quit + Sair - - Stay safe - + + + Errors: %1 + Erros: %1 - - - OCC::OwncloudWizardResultPage - - Everything set up! - Configuração concluida! + + Activity + Actividade - - Open Local Folder - Abrir a pasta local + + Quit %1 + Sair do %1 - - Open %1 in Browser - Abrir %1 no Navegador + + Are you sure you want to quit %1? + Você tem certeza que deseja sair %1? - OCC::PollJob + OCC::ModalWrapperWidget - - Invalid JSON reply from the poll URL - Resposta JSON inválida do URL poll + + Close + Fechar - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Hiperligações simbólicas não são suportadas em sincronização. - - - - File is listed on the ignore list. - O ficheiro está na lista de ficheiros a ignorar. - - - - File names ending with a period are not supported on this file system. - Nomes de ficheiros acabados com um ponto final não são suportados neste sistema de ficheiros. - - - - File names containing the character '%1' are not supported on this file system. - Nomes de ficheiros que contêm o caractér '%1' não são suportados neste sistema de ficheiros. - - - - File name contains at least one invalid character - O nome de ficheiro contém pelo menos um carácter inválido - - - - The file name is a reserved name on this file system. - O nome de ficheiro é um nome reservado neste sistema de ficheiros. - - - - Filename contains trailing spaces. - Nome de ficheiro contém espaços em branco seguidos. - - - - Filename is too long. - O nome do ficheiro é muito grande - - - - File/Folder is ignored because it's hidden. - O ficheiro/pasta foi ignorado porque está oculto. - - - - Stat failed. - Estado falhou. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - The filename cannot be encoded on your file system. - - - - - The filename is blacklisted on the server. - - - - - File has extension reserved for virtual files. - - - - - size - tamanho - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - + OCC::Models - - Not allowed to remove, restoring - Não autorizado para remoção, restaurando - - - - Ignored because of the "choose what to sync" blacklist - Ignorado devido à blacklist de escolha para sincronização - - - - Not allowed because you don't have permission to add subfolders to that folder - Não permitido, porque não tem permissão para adicionar as subpastas nessa pasta - - - - Not allowed because you don't have permission to add files in that folder - Não permitido, porque não tem permissão para adicionar os ficheiros nessa pasta - - - - Not allowed to upload this file because it is read-only on the server, restoring - Não é permitido enviar este ficheiro porque este é só de leitura no servidor, a restaurar - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - + + %1 Filter: + %1 Filtro: - - - OCC::PropagateDirectory - - Error writing metadata to the database - Erro ao escrever a meta-informação par a base de dados + + All + Todas - - - OCC::PropagateDownloadFile - - - File %1 can not be downloaded because of a local file name clash! - Não foi possível transferir o ficheiro %1 devido a um conflito com o nome de ficheiro local! + + Reset Filters + Redefinir Filtros - - The download would reduce free local disk space below the limit - + + Status Filter: + Status do Filtro: + + + OCC::NetworkSettings - - Free space on disk is less than %1 - O Espaço livre no disco é inferior a %1 + + Form + Formulário - - File was deleted from server - O ficheiro foi eliminado do servidor + + Pause synchronization when the Internet connection is metered + Pausar a sincronização quando a conexão com a Internet for medida - - The file could not be downloaded completely. - Não foi possível transferir o ficheiro na totalidade. + + Proxy Settings + Definições de Proxy - - The downloaded file is empty despite the server announced it should have been %1. - O ficheiro transferido está vazio, apesar do servidor indicar que este deveria ter %1. + + No Proxy + Sem Proxy - - File %1 cannot be saved because of a local file name clash! - Ficheiro %1 não pode ser guardado devido à existência de um ficheiro local com o mesmo nome. + + Use system proxy + Utilizar proxy do sistema - - - File has changed since discovery - O ficheiro alterou-se desde a sua descoberta + + Host + Anfitrião - - Error writing metadata to the database - Erro ao gravar os metadados para a base de dados + + Proxy Hostname + Nome do Host Proxy - - - OCC::PropagateItemJob - - ; Restoration Failed: %1 - ; Restauro Falhou: %1 + + : + : - - A file or folder was removed from a read only share, but restoring failed: %1 - Um ficheiro ou pasta foi removido de uma partilha só de leitura, mas o restauro falhou: %1 + + Proxy Port Number + Número da porta proxy - - - OCC::PropagateLocalMkdir - - could not delete file %1, error: %2 - Não foi possivel eliminar o ficheiro %1, erro: %2 + + Proxy server requires authentication + O servidor de proxy requer autenticação - - Attention, possible case sensitivity clash with %1 - Atenção, possível sensibilidade a maiúsculas em conflito com %1 + + Note: proxy settings have no effects for accounts on localhost + Nota: as configurações de proxy não têm efeito para contas no host local - - could not create folder %1 - Não foi possivel criar a pasta %1 + + Specify proxy manually + Especifique proxy manualmente - - Error writing metadata to the database - Erro ao escrever a meta-informação par a base de dados + + Proxy type + Tipo de proxy - - - OCC::PropagateLocalRemove - - Could not remove %1 because of a local file name clash - Nao foi possivel remover %1 devido a conflito local com nome de ficheiro + + + Hostname of proxy server + Nome de anfitrião do servidor proxy - - - OCC::PropagateLocalRename - - File %1 can not be renamed to %2 because of a local file name clash - O ficheiro %1 nao pode ser renomeado para %2 devido a conflito com nome de ficheiro local + + + Username for proxy server + Nome de utilizador para o servidor proxy - - - Error writing metadata to the database - Erro ao escrever a meta-informação par a base de dados + + + Password for proxy server + Palavra-passe para o servidor proxy. - - Error setting pin state - + + HTTP(S) proxy + Proxy de HTTP(S) - - - OCC::PropagateRemoteDelete - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Código HTTP errado devolvido pelo servidor. Esperado 204, mas foi recebido "%1 %2". + + SOCKS5 proxy + Proxy de SOCKS5 - OCC::PropagateRemoteMkdir + OCC::NewAccountBuilder - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Código HTTP errado devolvido pelo servidor. Esperado 201, mas foi recebido "%1 %2". + + no error details are available + Não há detalhes disponíveis sobre o erro. - - Error writing metadata to the database - Erro ao escrever a meta-informação par a base de dados + + Unable to connect to server during account creation: %1. + Não foi possível conectar ao servidor durante a criação da conta: %1. - OCC::PropagateRemoteMove + OCC::NewAccountWizard - - Could not rename %1 to %2, error: %3 - + + Cancel Setup + Cancelar Configuração - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Código HTTP errado devolvido pelo servidor. Esperado 201, mas foi recebido "%1 %2". + + Do you really want to cancel the account setup? + Você realmente deseja cancelar a configuração da conta? + + + OCC::NewAccountWizardController - - - Error writing metadata to the database - Erro ao escrever a meta-informação par a base de dados + + Add account… + Adicionar conta… - - Error setting pin state - + + Back + Anterior - - - OCC::PropagateUploadFileCommon - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Não foi possível transferir o ficheiro %1 devido a existir um ficheiro com o mesmo nome, diferenciando apenas as maiúsculas ou minúsculas. + + Advanced + Avançadas - - File Removed - Ficheiro Eliminado + + Sign in + Iniciar sessão - - Local file changed during syncing. It will be resumed. - O ficheiro local foi alterado durante a sincronização. Vai ser finalizado. + + Open sign in again + Abrir sessão novamente + + + OCC::NewVersionAvailableWidget - - Local file changed during sync. - Ficheiro local alterado durante a sincronização. + + Skip this version + Ignorar esta versão - - - Upload of %1 exceeds the quota for the folder - + + Get update + Obter atualização - - Error writing metadata to the database - Erro ao gravar os metadados para a base de dados + + Skip this time + Ignorar desta vez - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - O arquivo local foi removido durante a sincronização. - + OCC::OAuth - - Local file changed during sync. - Ficheiro local alterado durante a sincronização. + + Successfully signed in + Login efetuado com sucesso. - - Poll URL missing - URL poll em falta + + Now, explore %1 on desktop. + Agora, explore %1 no computador. - - Unexpected return code from server (%1) - Código de resposta inesperado do servidor (%1) + + Error returned from the server: <em>%1</em> + Erro retornado do servidor: <em>%1</em> - - Missing File ID from server - ID do ficheiro no servidor em falta + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Ocorreu um erro ao acessar o ponto final do token: <br><em>%1</em> - - Missing ETag from server - ETag do servidor em falta + + Could not parse the JSON returned from the server: <br><em>%1</em> + Não foi possível processar a resposta JSON retornada pelo servidor: <br><em>%1</em> - - - OCC::PropagateUploadFileTUS - - The local file was removed during sync. - O arquivo local foi removido durante a sincronização. + + Unsupported token type: %1 + Tipo de token não suportado: %1 - - Local file changed during sync. - Ficheiro local alterado durante a sincronização. + + The reply from the server did not contain all expected fields +:%1 + A resposta do servidor não continha todos os campos esperados +:%1 - - - OCC::PropagateUploadFileV1 - - Poll URL missing - URL poll em falta + + Unknown Error + Erro Desconhecido - - The local file was removed during sync. - O arquivo local foi removido durante a sincronização. + + + Login Error + Erro de login - - Local file changed during sync. - Ficheiro local alterado durante a sincronização. + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + Você fez login com o usuário 1%11, mas deve fazer login com o usuário 2%22.3Por favor, retorne ao cliente %3 e reinicie a autenticação. - - The server did not acknowledge the last chunk. (No e-tag was present) - O servidor não reconheceu a última parte. (Nenhuma e-tag estava presente) + + Wrong user + Utilizador errado - OCC::ProtocolWidget + OCC::OAuthPageController - - Form - Formulário + + Application Name Logo %1 + Logotipo do nome do aplicativo %1 - - TextLabel - TextLabel + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Deixe esta tela aberta. Uma solicitação de login aparecerá no seu navegador para conectá-lo ao seguinte endereço. - - Open in browser - Abrir no navegador + + Sign in URL + URL de login - - Time - Tempo + + To copy the sign in URL to the clipboard, use the copy button + Para copiar o URL de login para a área de transferência, use o botão de copiar. - - File - Ficheiro + + Copy URL to sign in + Copie o URL para fazer login. - - Folder - Pasta + + Additional logo defined by the organization + Logotipo adicional definido pela organização - - Action - Ação + + URL copied + URL copiada - - Size - Tamanho + + Copy URL + Copiar Hiperligação - - Local sync protocol - Protocolo de sincronização local + + Unable to connect to the login service. If the problem continues, please contact support. + Não foi possível conectar-se ao serviço de login. Se o problema persistir, entre em contato com o suporte. - - Copy - Copiar + + Please copy the authentication URL using the button above, and provide this information to support. + Por favor, copie o URL de autenticação usando o botão acima e forneça essas informações ao suporte. - - Copy the activity list to the clipboard. - Copiar lista de actividades para a área de transferência. + + Failed to look up WebFinger instances: %1 + Falha ao localizar instâncias do WebFinger: %1 - - - OCC::ProxyAuthDialog - - Proxy authentication required - Obrigatória a autenticação de proxy + + You are already connected to an account with these credentials. + Você já está conectado a uma conta com essas credenciais. - - Username: - Nome de Utilizador: + + Unable to retrieve capabilities from server. + Não foi possível recuperar as capacidades do servidor. - - Proxy: - Proxy: + + The server is not supported by this client. + Este cliente não suporta o servidor. - - The proxy server needs a username and password. - O servidor proxy precisa de um nome de utilizador e palavra-passe. + + Error while trying to log in to OAuth2-enabled server. %1 + Erro ao tentar fazer login em um servidor habilitado para OAuth2. %1 - - Password: - Palavra-passe: + + Unable to open browser with provided URL. %1 + Não foi possível abrir o navegador com o URL fornecido. %1 - - TextLabel - TextLabel + + OAuth2 authentication requires a secured connection. %1 + A autenticação OAuth2 requer uma conexão segura. %1 - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Escolher o Que Sincronizar + + Authorization server unreachable. %1 + Servidor de autorização inacessível. %1 - OCC::SelectiveSyncWidget - - - Loading ... - A carregar... - + OCC::OCUpdater - - Deselect remote folders you do not wish to synchronize. - Desmarcar pastas remotas que não deseja sincronizar. + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Não foi possível transferir a atualização. Por favor, clique <a href='%1'>aqui</a> para a transferir manualmente. - - Name - Nome + + Could not check for new updates. + Não foi possível procurar por novas atualizações. - - Size - Tamanho + + Downloading %1. Please wait… + Baixando 1%. Aguarde… - - - No subfolders currently on the server. - Atualmente não há sub-pastas no servidor. + + %1 installed successfully. Restart the application to finish installing the update. + %1 instalado com sucesso. Reinicie o aplicativo para concluir a instalação da atualização. - - An error occurred while loading the list of sub folders. - Ocorreu um erro ao carregar a lista das sub pastas. + + %1 available. Restart application to start the update. + %1 disponível. Reinicie o aplicativo para iniciar a atualização. - - - OCC::ServerNotificationHandler - - Dismiss - Rejeitar + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Novo %1 disponível. Por favor click <a href='%2'>aqui</a> para baixar o novo AppImage manualmente. - - - OCC::SettingsDialog - - Settings - Definições + + New %1 available. Please use the system's update tool to install it. + Novo %1 disponível. Por favor, use a ferramenta de atualização do sistema para instalar. - - Activity - Atividade + + Checking update server… + Verificando o servidor de atualizações… - - Add account - Adicionar conta + + Update status is unknown: Did not check for new updates. + O estado de atualização é desconhecido: não foi possível procurar por novas atualizações. - - - Quit %1 - Sair do %1 + + No updates available. Your installation is at the latest version. + Não estão disponíveis atualizações. A sua instalação já está com a última versão. + + + OCC::OwncloudPropagator - - Are you sure you want to quit %1? - + + File %1 is currently in use + O arquivo %1 está atualmente em uso - - Account - Conta + + vfs instance is not available. + A instância VFS não está disponível. + + + OCC::ProcessDirectoryJob - - MainWindow - + + Symbolic links are not supported in syncing. + Hiperligações simbólicas não são suportadas em sincronização. - - Hide - Ocultar + + File is listed on the ignore list. + O ficheiro está na lista de ficheiros a ignorar. - - - OCC::ShareDialog - - TextLabel - TextLabel + + File names ending with a period are not supported on this file system. + Nomes de ficheiros acabados com um ponto final não são suportados neste sistema de ficheiros. - - share label - partilhar etiqueta + + File names containing the character '%1' are not supported on this file system. + Nomes de ficheiros que contêm o caractér '%1' não são suportados neste sistema de ficheiros. - - Dialog - Diálogo + + File name contains at least one invalid character + O nome de ficheiro contém pelo menos um carácter inválido - - ownCloud Path: - Caminho OwnCloud: + + The file name is a reserved name on this file system. + O nome de ficheiro é um nome reservado neste sistema de ficheiros. - - %1 Sharing - %1 Partilhando + + Filename contains trailing spaces. + Nome de ficheiro contém espaços em branco seguidos. - - %1 - %1 + + Filename is too long. + O nome do ficheiro é muito grande - - Folder: %2 - Pasta: %2 + + File/Folder is ignored because it's hidden. + O ficheiro/pasta foi ignorado porque está oculto. - - The server does not allow sharing - O servidor não permite partilhas + + Stat failed. + Estado falhou. - - Retrieving maximum possible sharing permissions from server... - A obter do servidor as permissões máximas de partilha... + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Conflito: Versão do servidor baixada, cópia local renomeada e não carregada. - - The file can not be shared because it was shared without sharing permission. - O ficheiro não pode ser partilhado porque foi partilhado sem permissão de partilha. + + The filename cannot be encoded on your file system. + O nome do arquivo não pode ser codificado em seu sistema de arquivos. - - Users and Groups - Utilizadores e Grupos + + The filename is blacklisted on the server. + O nome do arquivo está na lista negra no servidor. - - Public Links - Ligações públicas + + + + + the VFS instance is missing. Unable to continue. + A instância VFS está ausente. Não é possível continuar. - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Partilhar NovoDocumento.odt + + server reported no %1 + servidor relatou não %1 - - Upload only (File Drop) - + + + account was deleted. Unable to continue + A conta foi excluída. Não é possível continuar. - - Receive files from others without revealing the contents of the folder. - + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: ignorado porque seu caminho está desmarcado - - Download / View / Edit - + + Moved to invalid target, restoring + Movido para destino inválido, restaurando - - Name: - + + Not allowed to remove, restoring + Não autorizado para remoção, restaurando - - Enter name of new share... - + + Error while reading the database. + Erro ao ler o banco de dados. - - &Create - + + account was deleted. Unable to continue. + A conta foi excluída. Não é possível continuar. - - TextLabel - TextLabel + + Not allowed because you don't have permission to add subfolders to that folder + Não permitido, porque não tem permissão para adicionar as subpastas nessa pasta - - Set &password - Defina a &senha + + Error while doing a rename, unhandled status code: %1 + Erro ao renomear, código de status não tratado:%1 - - Set &expiration date - Defina a data de &expiração + + Not allowed because you don't have permission to add files in that folder + Não permitido, porque não tem permissão para adicionar os ficheiros nessa pasta - - Set password - Defina a senha + + Not allowed to upload this file because it is read-only on the server, restoring + Não é permitido enviar este ficheiro porque este é só de leitura no servidor, a restaurar - - Recipients can view or download contents. - + + This Space is currently unavailable + This Space is currently unavailable - - Download / View - + + Server replied with an error while reading directory '%1' : %2 + Servidor respondeu com um erro ao ler o diretório '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - + + Error updating metadata: %1 + Erro ao atualizar metadados : %1 - - Anyone with the link has access to the file/folder - Qualquer pessoa com a hiperligação terá acesso ao ficheiro/pasta + + %1 the folder is currently in use + A pasta %1 está atualmente em utilização + + + OCC::PropagateDownloadFile - - - P&assword protect - Protegida por senha + + Failed to free up space, the file %1 is currently in use + Falha ao liberar espaço, o arquivo %1 está atualmente em uso - - The file can not be shared because it was shared without sharing permission. - O ficheiro não pode ser partilhado porque foi partilhado sem permissão de partilha. + + + File %1 can not be downloaded because of a local file name clash with %2! + O arquivo %1 não pode ser baixado por causa de um conflito de nome de arquivo local com %2! - - Link shares have been disabled - + + The download would reduce free local disk space below the limit + O download reduziria o espaço livre no disco local abaixo do limite - - Create public link share - + + Free space on disk is less than %1 + O Espaço livre no disco é inferior a %1 - - - Delete - Eliminar + + File was deleted from server + O ficheiro foi eliminado do servidor - - Open link in browser - Abrir ligação no navegador + + Broken webserver returned empty content length for non-empty file on resume + O servidor da web desligado retornou comprimento de conteúdo vazio para arquivo não vazio na retomada - - Copy link to clipboard - Copiar endereço para a área de transferência + + The file could not be downloaded completely. + Não foi possível transferir o ficheiro na totalidade. - - Copy link to clipboard (direct download) - + + The downloaded file is empty despite the server announced it should have been %1. + O ficheiro transferido está vazio, apesar do servidor indicar que este deveria ter %1. - - Send link by email - Enviar hiperligação por correio eletrónico + + File %1 cannot be saved because of a local file name clash with %2! + O arquivo %1 não pode ser salvo devido a um conflito de nome de arquivo local com %2! - - Send link by email (direct download) - + + Error updating metadata: %1 + Erro ao atualizar metadados : %1 - - Create new... - + + + + + The file %1 is currently in use + O arquivo %1 está atualmente em uso - - I shared %1 with you - + + + File has changed since discovery + O ficheiro alterou-se desde a sua descoberta + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - + + ; Restoration Failed: %1 + ; Restauro Falhou: %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - + + could not delete file %1, error: %2 + Não foi possivel eliminar o ficheiro %1, erro: %2 - - Cancel - Cancelar + + Can not create local folder %1 because of a local file name clash with %2 + Não foi possível criar a pasta local %1 devido a um conflito de nome de arquivo local com %2 - - - Public link - Hiperligação Pública + + Error updating metadata: %1 + Erro ao atualizar metadados : %1 - - Delete link share - + + The file %1 is currently in use + O arquivo %1 está atualmente em uso - - - Please Set Password - Por favor, Definir Senha + + could not create folder %1 + Não foi possivel criar a pasta %1 - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove - - Share NewDocument.odt - Partilhar NovoDocumento.odt + + %1 failed with: %2 + %1 falhou com: %2 - - Share with users or groups ... - Partilhar com utilizadores ou grupos... + + Could not remove %1 because of a local file name clash with %2! + Não foi possível remover %1 por causa de um conflito de nome de arquivo local com %2! - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - + + Could not move '%1' to the trash bin + Não foi possível mover '%1' para a lixeira - - The item is not shared with any users or groups - + + + %1 the file is currently in use + O arquivo %1 está atualmente em uso + + + OCC::PropagateLocalRename - - Open link in browser - Abrir ligação no navegador + + File %1 can not be renamed to %2 because of a local file name clash + O ficheiro %1 nao pode ser renomeado para %2 devido a conflito com nome de ficheiro local - - Copy link to clipboard - Copiar endereço para a área de transferência + + Could not rename %1 to %2, the file is currently in use + Não foi possível renomear %1 para %2, o arquivo está em uso - - Send link by email - Enviar hiperligação por correio eletrónico + + Error updating metadata: %1 + Erro ao atualizar metadados: %1 - - No results for '%1' - Sem resultados para '%1' + + The file %1 is currently in use + O arquivo %1 está atualmente em uso - - I shared something with you - + + Failed to rename file + Falha ao renomear o ficheiro + + + + Error setting pin state + Erro ao definir o estado do pin - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Formulário + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Código HTTP errado devolvido pelo servidor. Esperado 204, mas foi recebido "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - EtiquetaTexto + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Código HTTP errado devolvido pelo servidor. Esperado 201, mas foi recebido "%1 %2". - - can edit - pode editar + + Error writing metadata to the database: %1 + Erro ao gravar metadados no banco de dados:% 1 - - can share - pode partilhar + + Setting file status failed due to file lock + A configuração do status do arquivo falhou devido ao bloqueio do arquivo + + + OCC::PropagateRemoteMove - - ... - ... + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Código HTTP errado devolvido pelo servidor. Esperado 201, mas foi recebido "%1 %2". - - create - criar + + Error updating metadata: %1 + Erro ao atualizar metadados : %1 - - change - alterar + + The file %1 is currently in use + O arquivo %1 está atualmente em uso - - delete - eliminar + + Error writing metadata to the database + Erro ao escrever a meta-informação par a base de dados - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Error setting pin state + Erro ao definir o estado do pin - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Partilhar com %1 - - - - Context menu share - + + Could not update file : %1 + Não foi possível atualizar o arquivo: %1 - - Context menu share %1 - + + The file %1 is currently in use + O arquivo %1 está atualmente em uso + + + OCC::PropagateUploadCommon - - Confirm deletion - + + Local file changed during sync. It will be resumed. + Arquivo local alterado durante a sincronização. Será retomada. - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + Não foi possível transferir o ficheiro %1 devido a existir um ficheiro com o mesmo nome, diferenciando apenas as maiúsculas ou minúsculas. - - Do you want to delete the file <i>%1</i> permanently? - + + + Upload of %1 exceeds the quota for the folder + O envio de %1 excede a quota da pasta - - Select new location... - + + + %1 the file is currently in use + O arquivo %1 está atualmente em uso - - Error - Erro + + File Removed + Ficheiro Eliminado - - Moving file failed: - -%1 - + + The server did not provide the file permissions + O servidor não forneceu as permissões de arquivo - - I shared something with you - + + Error updating metadata: %1 + Erro ao atualizar metadados : %1 + + + OCC::PropagateUploadFile - - - Share... - Partilhar... + + %1 the file is currently in use + O arquivo %1 está atualmente em uso - - - Copy private link to clipboard - + + The server did ask for a removed legacy feature(polling) + O servidor solicitou a remoção de um recurso legado (pesquisa) - - Send private link by email... - Enviar hiperligação privada por correio eletrónico... + + The local file was removed during sync. + O arquivo local foi removido durante a sincronização. - - Resharing this file is not allowed - + + The server did not acknowledge the file upload. (No e-tag was present) + O servidor não reconheceu o envio do arquivo. (Nenhuma tag eletrônica estava presente) + + + OCC::PropagateUploadFileTUS - - Resharing this folder is not allowed - + + %1 the file is currently in use + %1 o arquivo está atualmente em uso - - - Copy public link to clipboard - + + Checksum computation failed + Checksum computation failed - - Open in browser - Abrir no navegador + + The local file was removed during sync. + O arquivo local foi removido durante a sincronização. + + + OCC::ProtocolItemModel - - Show file versions in browser - + + Time + Tempo - - Rename... - + + File + Ficheiro - - Rename and upload... - + + Folder + Pasta - - Move and rename... - + + Issues + Problemas - - Move, rename and upload... - + + Action + Ação - - Delete local changes - + + Size + Tamanho - - Move and upload... - + + Account + Conta - - Delete - Eliminar + + Status + Estado - OCC::SocketUploadJob - - - Failed to rerieve tags - - + OCC::RequestAuthenticationController - - Backup of %1 started - + + IdP is unreachable. Contact your system administrator or try again later. + O IdP está inacessível. Contate o administrador do sistema ou tente novamente mais tarde. - - Failed to find %1 - + + Authentication failed. + Falhou a autenticação. + + + OCC::RequestAuthenticationWidget - - Local path must be a an absolute path - + + Application Name Logo %1 + Logotipo do nome do aplicativo %1 - - Failed to create temporary database - + + Sign in required + É necessário fazer login. - - Backup of %1 succeeded - + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + Você pode ter sido desconectado automaticamente devido a um problema no servidor ou tempo limite excedido. Por favor, faça login novamente. - - Failed to set success tag - + + Sign in URL + URL de login - - Failed to create backup: %1 - + + To copy the sign in URL to the clipboard, use the copy button + Para copiar o URL de login para a área de transferência, use o botão de copiar. - - Failed to file id tags - + + Copy URL to sign in + Copie o URL para fazer login. - - Backup of %1 failed with: %2 - + + Additional logo defined by the organization + Logotipo adicional definido pela organização - - %1 backup - + + Stay logged out + Permanecer desconectado - - - OCC::SslButton - - <h3>Certificate Details</h3> - <h3>Detalhes do Certificado</h3> + + Sign in + Iniciar sessão - - Common Name (CN): - Nome Comum (NC): + + URL copied + URL copiada - - Subject Alternative Names: - Nomes alternativos + + Copy URL + Copiar Hiperligação + + + OCC::ResolveUrlAdapter - - Organization (O): - Organização (O): + + SSL failure when connecting to server at %1 + Falha de SSL ao conectar ao servidor em %1 - - Organizational Unit (OU): - Unidade Organizacional (UO): + + Could not detect compatible server at %1 + Não foi possível detectar o servidor compatível em %1 - - State/Province: - Estado/Distrito: + + Resolved url is invalid %1 + O endereço URL é inválido %1 - - Country: - País: + + Rejected redirect from %1 to %2 + Redirecionamento rejeitado de %1 para %2 + + + OCC::SelectiveSyncWidget - - Serial: - Serial: + + Loading … + A carregar... - - <h3>Issuer</h3> - <h3>Emissor</h3> + + Deselect remote folders you do not wish to synchronize. + Desmarcar pastas remotas que não deseja sincronizar. - - Issuer: - Emissor: + + Name + Nome - - Issued on: - Emitido em: + + Size + Tamanho - - Expires on: - Expira em: + + + Currently there are no subfolders on the server. + Atualmente, não há subpastas no servidor. - - <h3>Fingerprints</h3> - <h3>Impressões digitais</h3> + + An error occurred while loading the list of subfolders. + Ocorreu um erro ao carregar a lista de subpastas. + + + OCC::SettingsView - - SHA-256: - SHA-256: + + Form + Forma - - SHA-1: - SHA-1: + + General Settings + Configurações Gerais - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Nota:</b> Este certificado foi aprovado manualmente</p> + + Use Monochrome Icons in the system tray + Use ícones monocromáticos na bandeja do sistema - - %1 (self-signed) - %1 (auto-assinado) + + Show Desktop Notifications + Mostrar notificações do ambiente de trabalho - - %1 - %1 + + Start on Login + Comece no login - - This connection is encrypted using %1 bit %2. - - Esta ligação é encriptada a %1 bit %2. - + + Language + Idioma - - Server version: %1 - + + Language selector + Seletor de idiomas - - No support for SSL session tickets/identifiers - Nenhum suporte para tickets/identificadores de sessão SSL + + Advanced + Avançadas - - Certificate information: - Informação do certificado: + + Sync hidden files + Sincronizar os ficheiros ocultados - - The connection is not secure - + + Show crash reporter + Visualizar registo de crash - - This connection is NOT secure as it is not encrypted. - - Esta ligação NÃO é segura uma vez que não está encriptada. - + + Move remotely deleted files to the local trash bin instead of deleting them + Mova os arquivos excluídos remotamente para a lixeira local em vez de excluí-los - - - OCC::SslErrorDialog - - Form - Formulário + + Edit Ignored Files + Editar Ficheiros Ignorados - - Trust this certificate anyway - Confiar na mesma neste certificado. + + Log Settings + Registro de Configurações - - Untrusted Certificate - Certificado Não Confiável + + Network + Rede - - Cannot connect securely to <i>%1</i>: - Não é possível ligar com segurança a <i>%1</i>: + + Warning + Alerta - - with Certificate %1 - com o certificado %1 + + Language changes require a restart of this application to take effect. + As alterações de idioma exigem a reinicialização deste aplicativo para ter efeito. - - - - &lt;not specified&gt; - &lt;não especificado&gt; + + You cannot disable autostart because system-wide autostart is enabled. + Não é possível desativar a inicialização automática porque a inicialização automática em todo o sistema está ativada. - - - Organization: %1 - Organização: %1 + + (use default) + (use o padrão) - - - Unit: %1 - Unidade: %1 + + unknown (%1) + desconhecido (%1) + + + OCC::SocketApi - - - Country: %1 - País: %1 + + Share with %1 + parameter is the application name + Partilhar com %1 - - Fingerprint (MD5): <tt>%1</tt> - Chave(MD5): <tt>%1</tt> + + Confirm deletion + Confirme a exclusão - - Fingerprint (SHA1): <tt>%1</tt> - Chave(SHA1): <tt>%1</tt> + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Você deseja excluir o diretório <i>%1</i> e todo o seu conteúdo permanentemente? - - Effective Date: %1 - Data efectiva: %1 + + Do you want to delete the file <i>%1</i> permanently? + Você quer apagar o arquivo <i>%1</i> permanentemente? - - Expiration Date: %1 - Data de Expiração: %1 + + Error + Erro - - Issuer: %1 - Emissor: %1 + + Moving file failed: + +%1 + Falha em mover o arquivo: + +%1 - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - + + I shared something with you + Eu compartilhei algo com você - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Apenas %1 estão disponíveis, é preciso um mínimo de %2 para começar + + Select new location… + Selecione um novo local… - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - + + + Share… + Compartilhar… - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - + + + Copy private link to clipboard + Copie o linque privado para a área de transferência - - There is insufficient space available on the server for some uploads. - + + Resharing this file is not allowed + Não é permitido compartilhar novamente este arquivo - - Unresolved conflict. - Conflito por resolver. + + Resharing this folder is not allowed + Não é permitido compartilhar novamente esta pasta - - Could not update file : %1 - + + Open in %1 + Aberto em %1 - - Could not update virtual file metadata: %1 - + + Show file versions in web browser + Mostrar versões de arquivos no navegador da web - - Using virtual files with suffix, but suffix is not set - + + Rename… + Renomear… - - Unable to read the blacklist from the local database - Não foi possível ler a lista negra a partir da base de dados local + + Rename and upload… + Renomear e carregar… - - Unable to read from the sync journal. - Não foi possível ler a partir do jornal de sincronização. + + + Move and rename… + Mover e renomear… - - Cannot open the sync journal - Impossível abrir o jornal de sincronismo + + Move, rename and upload… + Mover, renomear e carregar… - - Aborted - + + Move and upload… + Mover e carregar… - - - OCC::Systray - - %1: %2 - %1: %2 + + Delete local changes + Excluir alterações locais - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - + + Delete + Eliminar - OCC::ValidateChecksumHeader + OCC::SpacesPage - - The checksum header is malformed. - O cabeçalho de "checksum" está com problemas. - - - - The checksum header contained an unknown checksum type '%1' - O header checksum continha um tipo de checksum desconhecido '%1' + + Select a folder to sync it to your computer. + Selecione uma pasta para sincronizar ao seu computador. - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - + + Select a Space to sync it to your computer. + Selecione um Espaço para sincronizar com o seu computador. - OCC::Vfs + OCC::SyncEngine - - The Virtual filesystem feature does not support a drive as sync root - + + %1 (skipped due to earlier error, trying again in %2) + %1 (ignorado devido a um erro anterior, tentando novamente em %2) - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - + + Unable to sync: the local folder is missing. + Não foi possível sincronizar: a pasta local está ausente. - - The Virtual filesystem feature is not supported on network drives - + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Apenas %1 estão disponíveis, é preciso um mínimo de %2 para começar - - - OCC::ownCloudGui - - Please sign in - Por favor inicie a sessão + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Não é possível abrir ou criar o banco de dados de sincronização local. Certifique-se de ter acesso de gravação na pasta de sincronização. - - Folder %1: %2 - Pasta %1: %2 + + + + + Cannot open the sync journal. + Não foi possível abrir o diário de sincronização. - - There are no sync folders configured. - Não há pastas de sincronização configurado. + + Aborted due to %1 + Abortado devido a %1 - - Open in browser - Abrir no navegador + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + O espaço em disco é pequeno: Os downloads que reduzam o espaço livre abaixo de %1 foram ignorados. - - - - Log in... - Iniciar sessão... + + There is insufficient space available on the server for some uploads. + Há espaço disponível no servidor para alguns envios. - - - - Log out - Terminar sessão + + application exit + abort reason + saída do aplicativo - - Recent Changes - Alterações recentes + + Unable to read the blacklist from the local database + Não foi possível ler a lista negra a partir da base de dados local - - Managed Folders: - Pastas Geridas: + + Unable to read from the sync journal. + Não foi possível ler a partir do jornal de sincronização. + + + OCC::SyncErrorWidget - - Open folder '%1' - Abrir pasta '%1' + + Issues table + Tabela de problemas - - Open %1 in browser - Abrir %1 no navegador + + There were too many issues. Not all will be visible here. + Havia muitos problemas. Nem todos serão visíveis aqui. - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - + + There were conflicts. Check the documentation on how to resolve them. + Houve conflitos. Verifique a documentação sobre como resolvê-los. - - Unknown status - Estado desconhecido + + The file %1 was ignored as its name is reserved by %2 + O arquivo %1 foi ignorado porque este nome foi reservado por %2 - - Settings... - Configurações... + + Reset column sizes + Redefinir tamanhos de coluna - - Details... - Detalhes... + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Houve conflitos.<a href="%1">Verifique a documentação sobre como resolvê-los.</a> - - Help - Ajuda + + Filter menu + Menu do filtro - - Quit %1 - Sair do %1 + + Account + Conta - - Disconnected from %1 - Desconetado de %1 + + Filter + Filtro + + + OCC::SyncJournalDb - - Unsupported Server Version - Versão de servidor não suportada + + Failed to connect database. + Falha ao conectar o banco de dados. + + + OCC::Theme - - Disconnected - Desligado + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Versão %1. Para obter mais informações, acesse <a href="%2">https://%3</a></p><p>Para obter ajuda e problemas conhecidos, acesse: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>Por Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller e outros.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distribuído por %4 e licenciado sob a GNU General Public License (GPL) Versão 2.0.<br/>%5 e o logotipo %5 são marcas registradas de %4 nos Estados Unidos, em outros países ou em ambos.</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Disconnected from some accounts - + + TLS Certificate Error + Erro de certificado TLS - - Disconnected from accounts: - Desconetado das contas: + + Do you want to trust this certificate anyway? + Mesmo assim, quer confiar neste certificado? - - Account %1: %2 - Conta %1: %2 + + Cannot connect securely to %1 + Não é possível conectar-se com segurança a %1 - - Signed out - Sessão terminada + + &lt;not specified&gt; + &lt;não especificado&gt; - - Account synchronization is disabled - A sincronização de contas está desactivada + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>com certificado %1</h3><div id="ccert"><p>Organização %2<br/>Unidade: %3<br/>País: %4</p><p>Impressão digital (MD5): <tt>%5</tt><br/>Impressão digital (SHA1): <tt>%6</tt><br/>Impressão digital (SHA256): <tt>%7</tt><br/><br/>Data efetiva: %8Data de validade: %9</div><h3>Emissor: %10</h3><div id="issuer"><p>Organização: %11<br/>Unidade: %12<br/>Pàis: %13</p></div></div> + + + OCC::TrayMenuController - - - Synchronization is paused - + + Unsupported Server Version + Versão de servidor não suportada - - Unresolved conflicts - + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + O servidor na conta %1 executa uma versão não suportada %2. A utilização deste cliente com versões de servidor não suportadas não foi testada e é potencialmente perigosa. Prossiga por sua conta e risco. - - Error during synchronization - Erro durante a sincronização + + Show %1 + Mostrar %1 - - No sync folders configured - + + Help + Ajuda - - Unpause all folders - Continuar todas as pastas + + About %1 + Sobre %1 - - Pause all folders - Pausar todas as pastas + + Quit %1 + Sair do %1 + + + OCC::UpdateDownloadedWidget - - Unpause all synchronization - Continuar toda a sincronização + + Restart later + Reiniciar mais tarde - - Unpause synchronization - Continuar sincronização + + Restart now + Reiniciar agora + + + OCC::UpdaterScheduler - - Pause all synchronization - Pausar toda a sincronização + + Update available + Atualização disponível + + + OCC::UrlPageController - - Pause synchronization - Pausar sincronização + + Your web browser will be opened to complete sign in. + Seu navegador será aberto para concluir o login. - - Log out of all accounts - Sair de todas as contas + + %1 logo + %1 logotipo - - Log in to all accounts... - Entrar em todas as contas... + + Welcome to %1 + Bem-Vindo a %1 ! - - New account... - Nova conta... + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Digite o endereço do seu servidor para começar. Seu navegador será aberto para concluir o login. - - About %1 - Sobre %1 + + Server address field + Campo de endereço do servidor - - No items synced recently - Sem itens sincronizados recentemente + + Enter your server address here + Insira o endereço do seu servidor aqui. - - Checking for changes in remote '%1' - + + Additional logo defined by the organization + Logotipo adicional definido pela organização - - Checking for changes in local '%1' - + + Invalid server URL: %1 + URL do servidor inválida: %1 - - Syncing %1 of %2 (%3 left) - Sincronizar %1 de %2 (%3 faltando) + + Invalid URL scheme. Only https is accepted. + Esquema de URL inválido. Somente https é aceito. - - Syncing %1 of %2 - A sincronizar %1 de %2 + + The requested URL failed with query value: %1 + A URL solicitada falhou com o seguinte valor: %1 + + + OCC::UserInfoAdapter - - Syncing %1 (%2 left) - A sincronizar %1 (%2 em falta) + + Network request to collect user info failed + A solicitação de rede para coletar informações do usuário falhou. - - Syncing %1 - A sincronizar %1 + + Unable to retrieve user info: invalid credentials. + Não foi possível recuperar as informações do usuário: credenciais inválidas. - - %1 (%2, %3) - %1 (%2, %3) + + Unexpected network response when retrieving user info. + Resposta inesperada da rede ao recuperar informações do usuário. - - Up to date - Atualizado + + Retrieving user info failed with JSON error. + A recuperação das informações do usuário falhou com um erro JSON. - OwncloudAdvancedSetupPage - - - Form - Formulário - + OCC::ValidateChecksumHeader - - - - - - TextLabel - TextLabel + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + O arquivo baixado não corresponde à soma de verificação, ele será retomado. '%1' != '%2' + + + OCC::Vfs - - Server - Servidor + + The Virtual filesystem feature does not support a drive as sync root + O recurso Sistema de Arquivos Virtual não suporta uma unidade de drive como raiz de sincronização - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Se esta caixa estiver selecionada, o conteúdo existente na pasta local será eliminado e será iniciada uma nova sincronização a partir dos dados do servidor.</p><p>Não selecione esta caixa se os dados locais tiverem de ser enviados para a pasta do servidor.</p></body></html> + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + O recurso de sistema de arquivos virtual requer um sistema de arquivos NTFS, %1 está usando %2 - - Start a &clean sync (Erases the local folder!) - &Iniciar uma nova sincronização (Apaga a pasta local) + + The Virtual filesystem feature is not supported on network drives + O recurso de sistema de arquivos virtual não é compatível com unidades de rede + + + OCC::VfsWinPrivate - - Ask for confirmation before synchroni&zing folders larger than - Pedir confirmação antes de sincronizar pastas maiores que + + + %1 - %2 + %1 - %2 - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + Could not find StorageProviderState property %1 + Não foi possível encontrar a propriedade StorageProviderState %1 - - Ask for confirmation before synchronizing e&xternal storages - Pedir confirmação antes de sincronizar armazenamentos e&xteriores + + Could not find StorageFolder for %1 error: %2 + Não foi possível encontrar a pasta de armazenamento para %1 erro: %2 - - Choose what to sync - Escolher o que sincronizar + + Could not retrieve StorageFolder for %1 %2 (0x%3) + Não foi possível recuperar a pasta de armazenamento para %1 %2 (0x%3) - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - + + The folder is used by a different client: %1 + A pasta é usada por um cliente diferente %1 - - Manually create folder sync connections - + + Error registering StorageProvider for %1: %2 (0x%3) + Erro ao registrar a Pasta de Armazenamento para %1: %2 %3 - - &Local Folder - Pasta &Local + + Unable to connect sync root: %1 error: %2 + Impossível se conectar a %1 erro: %2 - - pbSelectLocalFolder - Seleccione Pasta local + + %1 is managed by another sync client + %1 é gerenciado por outro cliente de sincronização - - &Keep local data - &Manter dados locais + + Downgrading the folder from %1 to %2 is not supported + Não é possível fazer o downgrade da pasta de %1 para %2. + + + OCC::WebFingerLookupAdapter - - S&ynchronize everything from server (recommended) - + + Failed to retrieve user info + Falha ao recuperar informações do utilizador - - Status message - Mensagem de estado + + WebFinger lookup returned no links + A pesquisa do WebFinger não retornou nenhum link - OwncloudHttpCredsPage + OCC::WindowsUpdater - - Form - Formulário + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Está disponível uma nova versão do Cliente %1.</p><p><b>%2</b> está disponível para transferência. A versão instalada é a %3.</p> - - &Username - Nome de &utilizador + + Skip this version + Ignorar esta versão - - &Password - &Palavra-passe + + Update Failed + A Atualização Falhou - - - OwncloudOAuthCredsPage - - Form - Formulário + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Uma nova versão do %1 Cliente está disponível, mas o processo de atualização falhou.</p><p><b>%2</b>   +foi baixado. A versão instalada é %3.</p> - - Please switch to your browser to proceed. - + + Ask again later + Pergunte novamente mais tarde - - An error occurred while connecting. Please try again. - + + Restart and update + Reinicie e atualize - - Re-open Browser - + + Update manually + Atualizar manualmente - OwncloudSetupPage + QObject - - Form - Formulário + + in the future + no futuro - - - - TextLabel - TextLabel + + + %n day(s) ago + %n dia atrás%n dias atrás%n dias atrás + + + + %n hour(s) ago + %n hora atrás%n horas atrás%n horas atrás - - Ser&ver Address - Endereço do Ser&vidor + + now + agora - - https://... - https://... + + less than a minute ago + menos de um minuto atrás + + + + %n minute(s) ago + %n minutos atrás%n minutos atrás%n minutos atrás - - Error Label - Erro Etiqueta + + %1: %2 + this displays an error string (%2) for a file %1 + %1: %2 - OwncloudWizardResultPage + SpacesBrowser - + Form Formulário + + + SpacesPage - - TextLabel - TextLabel - - - - Your entire account is synced to the local folder - A sua conta está sincronizada na pasta local + + Spaces list + Lista de espaços + + + SpacesView - - - PushButton - Botão + + Spaces + Espaços - QObject + SyncFileItem::Status - - in the future - no futuro + + Undefined + Indefinido - - - %n day(s) ago - %n dia atrás%n dias atrás + + + Fatal Error + Erro Grave - - - %n hour(s) ago - %n hora atrás%n horas atrás + + + + Error + Erro - - now - agora + + Info + Informação - - Less than a minute ago - Menos de um minuto atrás + + Success + Sucesso - - - %n minute(s) ago - %n minuto atrás%n minutos atrás + + + Conflict + Conflito - - Some time ago - Algum tempo atrás + + File Ignored + Arquivo Ignorado - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 + + Restored + Restaurado - - - Utility - - %L1 GB - %L1 GB + + Blacklisted + Lista Negra - - %L1 MB - %L1 MB + + Excluded + Excluído - - %L1 KB - %L1 KB + + Message + Mensagem - - %L1 B - %L1 B + + Filename Reserved + Nome do Arquivo Reservado + + + + UpdateDownloadedWidget + + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">é necessário reiniciar</span></p></body></html> + + + Utility - + %n year(s) - %n ano%n ano(s) + %n ano%n anos%n anos - + %n month(s) - %n mês%n meses + %n mês%n meses%n meses - + %n day(s) - %n dia%n dias + %n dia%n dias%n dias - + %n hour(s) - %n hora%n horas + %n hora%n horas%n horas - + %n minute(s) - %n minuto%n minutos + %n minuto%n minutos%n minutos - + %n second(s) - %n segundo%n segundos + %n segundo%n segundos%n segundos - + %1 %2 %1 %2 + + + Shutting down %1 + Desligando %1 + - main.cpp + account loading - - System Tray not available - Barra de sistema indisponível + + Error accessing the configuration file + Erro a aceder ao ficheiro de configuração - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 necessita de uma Barra de Tarefas. Se está a utilizar XFCE, por favor siga as seguintes <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">instruções</a>. Caso contrário, por favor instale uma Barra de Tarefas, por exemplo 'trayer', e tente novamente. + + There was an error while accessing the configuration file at %1. + Ocorreu um erro enquanto se acedia ao ficheiro de configuração em %1. ownCloudTheme::aboutVersions() - - %1 %2 %3 %4%8%9Libraries Qt %5, %6%8Using virtual files plugin: %7%8 - + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Bibliotecas Qt %3, %4%7Usando o plug-in de arquivos virtuais: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - + + %1 (Built against Qt %2) + %1 (Construído contra Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Sempre disponível localmente + + + + Currently available locally + Atualmente disponível localmente + + + + Some available online only + Alguns disponíveis apenas online + + + + Available online only + Apenas disponíveis online + + progress Virtual file created - + Arquivo virtual criado Replaced by virtual file - + Substituído por arquivo virtual @@ -4473,30 +3565,30 @@ This is a new, experimental mode. If you decide to use it, please report any iss Uploaded Enviado - - - Server version downloaded, copied changed local file into conflict file - A versão do servidor transferida, o ficheiro local alterado foi copiado para o ficheiro de conflito - Deleted Eliminado - - Moved to %1 - Movido para %1 - - - + Ignored Ignorado - - Filesystem access error - Erro ao acesso do sistema de ficheiros + + Server version downloaded, local copy was backed up as conflict file + Versão do servidor baixada, backup da cópia local como arquivo de conflito + + + + Renamed %1 to %2 + Renomeado %1 para %2 + + + + Moved %1 to %2 + Movido %1 para %2 @@ -4509,153 +3601,86 @@ This is a new, experimental mode. If you decide to use it, please report any iss Metadados locais atualizados - - + + Unknown Desconhecido - + downloading A transferir - + uploading a enviar - + deleting a eliminar - + moving a mover - + ignoring a ignorar - - + error erro - + updating local metadata a atualizar metadados locais - - theme - - - Status undefined - Estado indefinido - - - - Waiting to start sync - A aguardar o início da sincronização. - - - - Sync is running - A sincronização está a decorrer - - - - Sync Success - Sincronização bem sucedida - - - - Sync Success, some files were ignored. - Sincronizado bem sucedida, foram ignorados alguns ficheiros. - - - - Sync Error - Erro de Sincronização - - - - Setup Error - Erro de Configuração - - - - Preparing to sync - A preparar para sincronizar - - - - Aborting... - A abortar... - - - - Sync is paused - A sincronização está pausada - - utility - + Could not open browser Não foi possível abrir o browser - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - + Ocorreu um erro ao iniciar o navegador para ir ao endereço URL %1. Talvez nenhum navegador padrão esteja configurado? - + Could not open email client Não foi possivel abrir o cliente de email - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? Ocorreu um erro ao lançar o cliente de email para criar uma nova mensagem. Talvez nenhum cliente de email esteja configurado? - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - + + Make always available locally + Deixar sempre disponível localmente - Make always available locally - + Free up local space + Libere espaço local + + + version check - - Free up local space - + + Some settings were configured in newer versions of this client and use features that are not available in this version + Algumas configurações foram definidas em versões mais recentes deste cliente e utilizam recursos que não estão disponíveis nesta versão \ No newline at end of file diff --git a/translations/client_pt_BR.ts b/translations/client_pt_BR.ts index aece289dba0..453879b7324 100644 --- a/translations/client_pt_BR.ts +++ b/translations/client_pt_BR.ts @@ -1,226 +1,381 @@ - + - CrashReporter + AccountBar - - Tomahawk Crash Reporter - Relsatório de erro Tomahawk + + Navigation bar + Barra de navegação - - <html><head/><body><p><br/></p></body></html> - <html><head/><body><p><br/></p></body></html> + + Add Account + Adicionar conta - - Abort - Abortar + + Activity + Atividade - - Send this report - Enviar este relatório + + Settings + Configurações - - Don't send - Não envie + + Quit + Sair + + + AppImageUpdateAvailableWidgetUi - - You can disable sending crash reports in the configuration dialog. - Você pode desativar o envio de relatórios de falha na caixa de diálogo de configuração. + + Skip this version + Pule esta versão + + + AppProvider - - - - We cannot gather useful debug information on your system. - Não podemos coletar informações úteis de depuração em seu sistema. + + Failed to open %1 in web. Error: %2. + Falha ao abrir %1 na web. Erro: %2. + + + ChecksumHeader - - - - - - Close - Fechar + + The checksum header is malformed: %1 + Cabeçalho checksum inválido: %1 + + + + The checksum header contained an unknown checksum type '%1' + Cabeçalho de verificação checksum contém um tipo de checksum desconhecido '%1' + + + + CommandLine + + + %1 version %2 +File synchronization desktop utility. + %1 versão %2<byte value="xd"/> +Utilitário de sincronização de arquivos na área de trabalho. + + + + For more information, see %1 + link to homepage + Para mais informações, veja %1 + + + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Comece com a janela principal visível ou, se já estiver em execução, traga-a para a frente. Por padrão, o cliente é iniciado em segundo plano. + + + + Quit the running instance. + Saia da instância em execução. + + + + Write log to file (use - to write to stdout). + Grave o log no arquivo (use - para gravar no stdout). + + + + Write each sync log output in a new file in folder. + Grave cada saída do log de sincronização em um novo arquivo na pasta. + + + + Flush the log file after every write. + Libere o arquivo de log após cada gravação. + + + + Output debug-level messages in the log. + Envie mensagens de nível de depuração no log. + + + + Enable debug mode. + Ative o modo de depuração. + + + + --logfile and --logdir are mutually exclusive + --logfile e --logdir são mutuamente exclusivos + + + + CommonStrings + + + file manager + gerência de arquivo + + + + Show in %1 + Mostrar em %1 + + + + Show in web browser + Mostrar no navegador da web + + + + Copy + Copiar + + + + %n Filter(s) + %n Filtros%n Filtros%n Filtros + + + + folder + pasta + + + + space + espaço - - Ready to send debug information (<a href="%1">view backtrace</a>). - Pronto para enviar informações de depuração (<a href="%1">visualização das últimas funções executadas</a>). + + folders + pastas - - Gathering debug information... - Coletando informações de depuração... + + spaces + espaços - - Uploaded %L1 of %L2 KB. - Enviado %L1 de %L2 KB. + + Folder + Pasta - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - Enviei! <b>Muito obrigado.</b>. Consulte a falha <a href="clipboard://%1"><b>%1</b></a>(clique para copiar) nos relatórios de bug. + + Space + Espaço + + + FetchUserInfoJobFactory - - Failed to send crash info. - Falha ao enviar informações de erro. + + Failed to retrieve user info + Falha ao recuperar informações do usuário FileSystem - - - - + + + + Can't rename %1, the file is currently in use Não foi possível renomear %1, o arquivo está atualmente em uso - - Could not make directories in trash - Não foi possível criar diretórios na lixeira + + Could not remove folder + Não foi possível remover a pasta + + + + FolderItem + + + + Offline + Desconectado + + + + + Unavailable: the space is no longer available on the server + Indisponível: o espaço não está mais disponível no servidor. + + + + + Sync paused + Sincronização pausada + + + + Synced + Sincronizado + + + + Sync failed + Falha na sincronização + + + + Sync pending + Sincronização pendente + + + + Syncing %1 of %2 (%3 % + Sincronizando %1 de %2 (%3 % - - - Could not move '%1' to '%2' - Não foi possível mover '%1' para '%2' + + , ⬇️ %1/s + , ⬇️ %1/s - - Moving to the trash is not implemented on this platform - Mover para o lixo não está implementado nesta plataforma + + , ⬆️ %1/s + , ⬆️ %1/s - - Error removing '%1': %2 - Erro ao remover '%1': %2 + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, status de sincronização: %2 + first argument is the displayed name of the folder. + + + + FolderManagementUtils + + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + O caminho '%1' é demasiado longo. Ative caminhos longos nas configurações do Windows ou escolha uma pasta diferente. + + + + Local folder %1 does not exist. + A pasta local %1 não existe. + + + + %1 is not a folder. + %1 is não é uma pasta. + + + + %1 is not readable. + %1 não pode ser lido. + + + + %1 is not writable. + %1 não pode ser escrita. + + + + FolderWizard + + + <b>Error:</b> %1 + <b>Erro:</b> %1 + + + + <b>Warning:</b> %1 + <b>Alerta:</b> %1 + + + + <b>Error:</b><ul>%1</ul> + <b>Erro:</b><ul>%1</ul> - - Could not remove folder '%1' - Não foi possível remover a pasta '%1' + + <b>Warning:</b><ul>%1</ul> + <b>Alerta:</b><ul>%1</ul> FolderWizardSourcePage - + Form Formulário - + Pick a local folder on your computer to sync Selecione um diretório no seu computador para ser sincronizado - - &Choose... - &Escolha... + + Browse… + Navegar… FolderWizardTargetPage - + Form Formulário - + Select a remote destination folder Selecionar uma pasta de destino remota - + Create Folder Criar uma Pasta - + Refresh Atualizar - + Folders Pastas - - - TextLabel - TextLabel - - - - NotificationWidget - - - Form - Formulário - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - RótuloTexto - - OCC::AboutDialog + OCC::AboutView - + About Sobre - - TextLabel - RótuloTexto - - - + Versions Versões - - - About %1 - Sobre %1 - OCC::AbstractNetworkJob - + Connection timed out Conexão expirou - + Unknown error: network reply was deleted Erro desconhecido: a resposta da rede foi excluída - + Server replied "%1 %2" to "%3 %4" Resposta do servidor "%1 %2" to "%3 %4" @@ -228,712 +383,601 @@ OCC::Account - + %1@%2 %1@%2 - OCC::AccountSettings + OCC::AccountFoldersController - - Form - Formulário + + Choose what to sync + Escolha o que sincronizar - - Re-open Browser - Reabrir o navegador + + + Sync now + Sincronizar agora - - ... - ... + + Manage subfolder sync + Gerenciar sincronização de subpastas - - Storage space: ... - Espaço de armazenamento: ... + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + Remover sincronização de %1 - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Pastas não marcadas serão <b>removidos</b> de seu sistema de arquivos local e não serão mais sincronizadas com este computador + + Disable virtual file support? + Desativar o suporte a arquivos virtuais? - - Synchronize all - Sincronizar tudo + + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. + +The only advantage of disabling virtual file support is that the selective sync feature will become available again. + +This action will abort any currently running synchronization. + Esta ação desativará o suporte a arquivos virtuais. Como consequência, o conteúdo das pastas que estão marcadas como "disponíveis somente online" será baixado. + +A única vantagem de desativar o suporte a arquivos virtuais é que o recurso de sincronização seletiva voltará a estar disponível. + +Esta ação interromperá qualquer sincronização em andamento. - - Synchronize none - Não sincronizar + + Disable support + Desativar suporte - - Apply manual changes - Aplicar alterações manuais + + Restart sync + Reiniciar sincronização - - Apply - Aplicar + + Resume sync + Retomar sincronização - - - - - Cancel - Cancelar + + Pause sync + Pausar sincronização - - Connected with <server> as <user> - Conectado com <server> como <user> + + Deactivate virtual files + Desativar arquivos virtuais - - No account configured. - Nenhuma conta configurada. + + Activate virtual files + Ativar arquivos virtuais - - Remove - Remover + + Internet connection is metered + A conexão de internet é tarifada. - - Account - Conta + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + A sincronização está pausada porque a conexão com a Internet é limitada.<p>Tem mesmo vontade de forçar uma sincronização agora? - - Choose what to sync - Escolha o que quer sincronizar + + Sync Running + Sincronização em execução - - Force sync now - Forçar sincronização agora + + The sync operation is running.<br/>Do you want to stop it? + A operação de sincronização está em andamento.<br/>Você quer impedir isso? - - Restart sync - Reiniciar a sincronização + + Sync paused by user + Sincronização pausada pelo usuário - - Remove folder sync connection - Remover a conexão de sincronização de pastas + + Confirm Folder Sync Connection Removal + Confirmar remoção da conexão de sincronização de pastas - - Folder creation failed - Falha na criação de pasta + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Você realmente deseja interromper a sincronização da pasta? <i>%1</i>?</p><p><b>Nota:</b>Isto <b>não</b> irá remover nenhum arquivo.</p> - - <p>Could not create local folder <i>%1</i>. - <p>Não foi possível criar pasta local <i>%1</i>. + + Remove Folder Sync Connection + Remover conexão de sincronização de pastas - - Confirm Folder Sync Connection Removal - Confirme a Remoção de Sincronização de Pasta + + + Cancel + Cancelar + + + OCC::AccountFoldersView - - Remove Folder Sync Connection - Remover a Conexão de Sincronização de pasta + + Sync options menu + Menu de opções de sincronização - - Disable virtual file support? - Desativar o suporte a arquivos virtuais? + + %1 sync + This shows as 'Folder sync' or 'Space sync' + %1 sincronizar - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - Esta ação desabilitará o suporte a arquivos virtuais. Como consequência, o conteúdo das pastas atualmente marcadas como 'disponíveis apenas online' será baixado. - -A única vantagem de desativar o suporte a arquivos virtuais é que o recurso de sincronização seletiva ficará disponível novamente. - -Esta ação irá anular qualquer sincronização atualmente em execução. + + Manage your synced %1. + Gerencie seus %1 sincronizados. - - Disable support - Desativar suporte + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Adicionar nova sincronização %1… - - Sync Running - Sincronização Acontecendo + + %1 list view + %1 visualização em lista + + + + Navigate the %1 list using the up and down arrows + Navegue pela lista %1 usando as setas para cima e para baixo. + + + + %1 out of %2 %3 are synchronized + %1 de %2 %3 estão sincronizados + + + OCC::AccountModalWidget - - The syncing operation is running.<br/>Do you want to terminate it? - A operação de sincronização está acontecendo.<br/>Você deseja finaliza-la? + + + Form + Formulário + + + OCC::AccountPlaceholderWidget - - %1 in use - %1 em uso + + %1 logo + %1 logotipo - - %1 as <i>%2</i> - %1 como <i>%2</i> + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + Você ainda não possui nenhuma conta conectada. + +Crie uma conta selecionando a opção "Adicionar conta..." no menu do botão Mais. - - Connected to %1. - Conectado a %1. + + Additional logo defined by the organization + Logotipo adicional definido pela organização + + + OCC::AccountView - - Server %1 is temporarily unavailable. - O Servidor %1 está temporariamente indisponível. + + Form + Forma - - Server %1 is currently in maintenance mode. - Servidor %1 está atualmente em modo de manutenção. + + Manage Account + Gerenciar conta - - Signed out from %1. - Desconectado de %1. + + Preparing the account + Preparando a conta - - Connecting to %1... - Conectando a %1... + + Sync connections + Sincronizar conexões - - No connection to %1 at %2. - Sem conexão para %1 em %2. + + Account options menu + Menu de opções da conta + + + OCC::AccountViewController - + + Log in - Entrar + Conectar - - There are folders that were not synchronized because they are too big: - Existem pastas que não foram sincronizadas porque são muito grandes: + + Reconnect + Reconectar - - There are folders that were not synchronized because they are external storages: - Existem pastas que não foram sincronizadas porque são armazenamentos externos: + + Remove + Remover - - There are folders that were not synchronized because they are too big or external storages: - Existem pastas que não foram sincronizadas porque são muito grandes ou armazenamentos externos: + + Log out + Sair - + Confirm Account Removal Confirmar a Remoção da Conta - + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> <p>Você realmente deseja remover a conexão desta conta<i>%1</i>?</p><p><b>Nota:</b> Isto <b>não</b> irá deletar nenhum arquivo.</p> - + Remove connection Remover conexão - - Open folder - Abrir pasta - - - - - Log out - Sair + + Cancel + Cancelar - - Open local folder - Abrir pasta local + + The server version %1 is unsupported! Proceed at your own risk. + A versão do servidor %1 não é suportada! Prossiga por sua conta e risco. - - Open folder in browser - Abra a pasta no navegador + + Connected + Conectado - - Resume sync - Resumir sincronização + + Server is temporarily unavailable. + - - Pause sync - Pausar sincronização + + Server is currently in maintenance mode. + - - Availability - Disponibilidade + + Signed out + Saiu - - Disable virtual file support... - Desativar o suporte a arquivos virtuais... + + Updating credentials… + Atualizando credenciais… - - Enable virtual file support%1... - Habilitar suporte de arquivo virtual%1... + + Captive portal prevents connections to the server. + O portal cativo impede as conexões com o servidor. - - (experimental) - (experimental) + + Sync is paused due to metered internet connection. + - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Você realmente deseja para a sincronização desta pasta <i>%1</i>?</p><p><b>Nota:</b> Isto <b>não</b> vai deletar qualquer arquivo.</p> + + Connecting… + Conectando… - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) de%2 em uso. Algumas pastas, incluindo montadas na rede ou pastas compartilhadas, podem ter limites diferenes. + + Server configuration error + Erro de configuração do servidor - - %1 of %2 in use - %1 de %2 em uso + + Disconnected + Desconectado - - Currently there is no storage usage information available. - Atualmente, não há informações de uso de armazenamento disponível. + + Invalid connection status + + + + OCC::AccountsGuiController - - The server version %1 is unsupported! Proceed at your own risk. - A versão do servidor %1 não é suportada! Prossiga por sua conta e risco. + + + New account failure + Falha na criação da conta - - Obtaining authorization from the browser. - Obtendo autorização do navegador. + + The information required to create a new account is incomplete. Please run the wizard again. + As informações necessárias para criar uma nova conta estão incompletas. Execute o assistente novamente. - - Server configuration error: %1 at %2. - Erro na configuração do servidor: %1 em %2. + + Accounts + Contas - - No %1 connection configured. - Nenhuma %1 conexão configurada. + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + Não foi possível criar a conta devido a um erro: +%1 +Verifique a disponibilidade do servidor e execute o assistente novamente. - OCC::AccountState + OCC::ActivitySettings - - Url update requested for %1 - A atualização do URL foi solicitada para %1 + + Local Activity + Atividade local - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - A url de %1 mudou de %2 para %3, deseja aceitar a url alterada? + + Not Synced + Não Sincronizado - - Change url permanently to %1 - Mudar a url permanentemente para %1 + + Not Synced (%1) + %1 is the number of not synced files. + Não sincronizada (%1) + + + OCC::AdvancedSettingsPageController - - Reject - Rejeitar + + Advanced settings + Configurações avançadas - - Signed out - Desconectado + + Sync and download options + Opções para baixar e sincronizar - - Disconnected - Desconectado + + Download location + Local para baixar - - Connected - Conectado + + Sync and download files as you use them (saves hard drive space) + Sincronize e baixe arquivos conforme os utiliza (economiza espaço no disco rígido). - - Service unavailable - Serviço indisponível + + Automatically sync and download folders and files + Sincronize e baixe pastas e arquivos automaticamente. - - Maintenance mode - Modo de manutenção + + Sync and download folders manually + Sincronizar e baixar pastas manualmente - - Network error - Erro de rede + + Download location on the local machine + Local para baixar na máquina local - - Configuration error - Erro de configuração + + Browse… + Navegar… - - Asking Credentials - Solicitando Credenciais + + Browse for a download location + Procure um local para salvar o que baixar. - - Unknown account state - Estado da conta desconhecido + + Invalid local download directory %1: %2 + Diretório local para baixar é inválido %1: %2 - - - OCC::ActivityListModel - - %1 %2 on %3 - %1 %2 em %3 + + your user directory may not be chosen as the sync root. + Seu diretório de usuário pode não ter sido escolhido como raiz de sincronização. - - Activity - Atividade + + path is not located on a supported Apple File System. + O caminho não está localizado em um sistema de arquivos Apple compatível. - - Account - Conta - - - - Time - Horário - - - - Local path - Caminho local - - - - OCC::ActivitySettings - - - - Server Activity - Atividade do Servidor - - - - Sync Protocol - Protocolo de Sincronização + + path must be absolute. + O caminho deve ser absoluto. - - Not Synced - Não Sincronizado + + selected path does not support using virtual file system. %1 + O caminho selecionado não suporta o uso de sistema de arquivos virtual. %1 - - Not Synced (%1) - %1 is the number of not synced files. - Não sincronizada (%1) + + Select sync root + Selecione a raiz de sincronização - OCC::ActivityWidget - - - Form - Formulário - - - - - - TextLabel - RótuloTexto - - - - Filter - Filtro - - - - Server Activities - Atividades do Servidor - + OCC::AppImageUpdateAvailableWidget - - Notifications - Notificações - - - - %n notifications(s) for %1. - %n notificação(ões) for %1.%n notificação(ões) for %1. - - - - %n notifications(s) for %1 and %2. - %n notificação(ões) para %1 e %2.%n notificação(ões) para %1 e %2. - - - - New notifications for %1, %2 and other accounts. - Novas notificações para %1, %2 e outras contas. - - - - Open the activity view for details. - Abrir a visualização detalhada das atividades. + + Installed version: %1 + Versão instalada: %1 - - Copy to clipboard - Copiar para área de transferência + + Available update: %1 + Atualização disponível:%1 - - Show in file browser - Mostrar no navegador de arquivos - - - - <br/>Account %1 does not have activities enabled. - <br/>A conta %1 não tem atividades ativadas. + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Uma atualização está disponível para este AppImage de %1. Deseja instalar esta atualização? + +A atualização será executada em segundo plano e substituirá o arquivo AppImage atual. Você precisa reiniciar o aplicativo para concluir a atualização. OCC::Application - - Continuing will mean <b>deleting these settings</b>. - Continuar significa <b>excluir essas configurações</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Continuar significa <b>ignorar essas configurações</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Algumas configurações foram realizadas em versões mais recentes deste cliente e usam recursos que não estão disponíveis nesta versão.<br><br>%1<br><br>O arquivo de configuração atual já foi feito backup em <i>%2</i>. - - - + Quit Sair + + + OCC::AuthSuccessPageController - - Continue - Continuar - - - - Error accessing the configuration file - Erro acessando o arquivo de configuração - - - - There was an error while accessing the configuration file at %1. - Ocorreu um erro ao acessar o arquivo de configuração em %1. - - - - Quit %1 - Sair %1 - - - - %1 version %2 -File synchronization desktop utility. - %1 versão %2<byte value="xd"/> -Utilitário de sincronização de arquivos na área de trabalho. - - - - For more information, see http://www.owncloud.org - Para obter mais informações, consulte http://www.owncloud.org - - - - Show the settings dialog while starting. - Mostrar a caixa de diálogo de configurações ao iniciar. - - - - Quit the running instance. - Saia da instância em execução. - - - - Write log to file (use - to write to stdout). - Grave o log no arquivo (use - para gravar no stdout). - - - - Write each sync log output in a new file in folder. - Grave cada saída do log de sincronização em um novo arquivo na pasta. - - - - Remove logs older than <hours> hours (to be used with --logdir). - Remova os registros com mais de <hours> horas (para ser usado com --logdir). - - - - Flush the log file after every write. - Libere o arquivo de log após cada gravação. - - - - Output debug-level messages in the log. - Envie mensagens de nível de depuração no log. - - - - - Override UI language. - Substitua o idioma da IU. - - - - Use the given configuration folder. - Use a pasta de configuração fornecida. - - - - Enable debug mode. - Ative o modo de depuração. - - - - Virtual file system file to be opened (optional). - Arquivo do sistema de arquivos virtual a ser aberto (opcional). - - - - [<vfs file>] - [<vfs file>] - - - - Invalid path passed to --confdir - Caminho inválido passado para --confdir + + Application Name Logo %1 + Logotipo do nome do aplicativo %1 - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - Erro: idioma desconhecido "%1" (use --list-languages para obter uma lista completa das traduções suportadas) + + You're all set! Open %1 and get started. + Está tudo pronto! Abra %1 e comece. - - Available translations: %1 - Traduções disponíveis: %1 + + Additional logo defined by the organization + Logotipo adicional definido pela organização - OCC::AuthenticationDialog - - - Authentication Required - Autenticação é Requerida - + OCC::ButtonDelegate - - Enter username and password for '%1' at %2. - Entrar com o nome do usuário e senha para '%1' em %2. + + %1 options button + Botão de opções %1 - - &User: - &Usuário: - - - - &Password: - &Senha: + + Menu button with %1 options. Use the space key to show the menu + Botão de menu com opções %1. Use a tecla espaço para exibir o menu. OCC::ConnectionValidator - + No ownCloud account configured Nenhuma conta ownCloud configurada - - The configured server for this client is too old - O servidor configurado para este cliente é muito antigo - - - + Please update to the latest server and restart the client. Por favor, atualize para o último servidor e reinicie o cliente. - + Authentication error: Either username or password are wrong. Erro de autenticação. Ou nome de usuário ou senha está errada. - + timeout tempo limite - + + No account configured + Nenhuma conta configurada + + + The provided credentials are not correct As credenciais fornecidas não estão corretas + + + The configured server for this client is too old. + O servidor configurado para este cliente é muito antigo. + OCC::CredentialJob - + Failed to parse credentials %1 Falha ao analisar as credenciais %1 + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + O servidor não suporta OAuth. + + + + Server did not ask for authorization + Servidor não pediu autorização + + + + Failed to determine authentication type: %1 + Não foi possível determinar o tipo de autenticação: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + Resposta errada recebida do servidor + + OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + A conta foi excluída antes que pudéssemos iniciar a busca de propriedades. + + + + Server error: PROPFIND reply is not XML formatted! Erro do servidor: resposta PROPFIND não está formatada em XML! @@ -941,27 +985,22 @@ Utilitário de sincronização de arquivos na área de trabalho. OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 Erro ao abrir o diretório %1 - + Directory not accessible on client, permission denied Diretório não acessível no cliente, permissão negada - + Directory not found: %1 Diretório não encontrado: %1 - - Filename encoding is not valid - A codificação do nome do arquivo não é válida - - - + Error while reading directory %1 Erro ao ler o diretório %1 @@ -969,148 +1008,137 @@ Utilitário de sincronização de arquivos na área de trabalho. OCC::ExpandingHeaderView - + Reset column sizes Redefinir tamanhos de coluna - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - A pasta local %1 não existe. + + server sent invalid content type: %1. + O servidor enviou um tipo de conteúdo inválido: %1. - - %1 should be a folder but is not. - %1 deve ser uma pasta, mas não é. + + could not parse Capabilities from server. + Não foi possível analisar as capacidades do servidor. - - %1 is not readable. - %1 não pode ser lido. + + reply sent for different subject (server): %1 + Resposta enviada para assunto diferente (servidor): %1 + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 foi removido. - + %1 has been updated. %1 names a file. %1 foi atualizado. - + %1 has been renamed to %2. %1 and %2 name files. %1 foi renomeado para %2. - + %1 has been moved to %2. %1 foi movido para %2. - + %1 and %n other file(s) have been removed. - %1 e %n outro arquivo foi removido.%1 e %n outros arquivos foram removidos. + %1 e %n Outros arquivos foram removidos.%1 e %n Outros arquivos foram removidos.%1 e %n Outros arquivos foram removidos. - - %1 is not writable. - %1 não pode ser escrita. + + The folder has been disabled or removed from the server + A pasta foi desabilitada ou removida do servidor - + %1 and %n other file(s) have been added. - %1 e %n outros arquivo(s) foram adicionados.%1 e %n outros arquivo(s) foram adicionados. + %1 e %n Outros arquivos foram adicionados.%1 e %n Outros arquivos foram adicionados.%1 e %n Outros arquivos foram adicionados. - + %1 has been added. %1 names a file. %1 foi adicionado. - + %1 and %n other file(s) have been updated. - %1 e %n outro arquivo foi atualizado.%1 e %n outros arquivos foram atualizados. + %1 e %n Outros arquivos foram atualizados.%1 e %n Outros arquivos foram atualizados.%1 e %n Outros arquivos foram atualizados. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 foi renomeado para %2 e %n outro arquivo foi renomeado.%1 foi renomeado para %2 e %n outros arquivos foram renomeados. + %1 foi renomeado para %2 e %n Outros arquivos foram renomeados.%1 foi renomeado para %2 e %n Outros arquivos foram renomeados.%1 foi renomeado para %2 e %n Outros arquivos foram renomeados. - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 foi movido para %2 e %n outro arquivo foi movido.%1 foi movido para %2 e %n outros arquivos foram movidos. + %1 foi movido para %2 e %n Outros arquivos foram movidos.%1 foi movido para %2 e %n Outros arquivos foram movidos.%1 foi movido para %2 e %n Outros arquivos foram movidos. - - %1 has and %n other file(s) have sync conflicts. - %1 tem e %n outro arquivo tem conflito na sincronização.%1 tem e %n outros arquivos teem conflito na sincronização. + + %1 and %n other file(s) have sync conflicts. + %1 e %n Outros arquivos têm conflitos de sincronização.%1 e %n Outros arquivos têm conflitos de sincronização.%1 e %n Outros arquivos têm conflitos de sincronização. - + %1 has a sync conflict. Please check the conflict file! %1 tem um conflito na sincronização. Por favor verifique o arquivo de conflito! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 e %n outro arquivo não pode ser sincronizado devido a erros. Veja o log para detalhes.%1 e %n outros arquivo(s) não puderam ser sincronizados devido a erros. Veja o log para detalhes. + %1 e %n Outros arquivos não puderam ser sincronizados devido a erros. Veja o log para obter detalhes.%1 e %n Outros arquivos não puderam ser sincronizados devido a erros. Veja o log para obter detalhes.%1 e %n Outros arquivos não puderam ser sincronizados devido a erros. Veja o log para obter detalhes. - + %1 could not be synced due to an error. See the log for details. %1 não pode ser sincronizado devido a um erro. Veja o log para obter detalhes. - + Sync Activity Atividade de Sincronização - - Could not read system exclude file - Não foi possível ler o sistema de arquivo de exclusão - - - - A new folder larger than %1 MB has been added: %2. - - Uma nova pasta maior que %1 MB foi adicionada: %2 - + + Switching VFS mode on folder '%1' + Alternando o modo VFS na pasta '%1' - - A folder from an external storage has been added. - - Uma pasta de um armazenamento externo foi adicionada. - - - - - Please go in the settings to select it if you wish to download it. - Por favor, vá nas configurações para selecioná-lo se você deseja baixá-lo. + + Could not read system exclude file + Não foi possível ler o sistema de arquivo de exclusão - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. A pasta %1 foi criada, mas foi excluída da sincronização anteriormente. Os dados dentro dela não serão sincronizados. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. A arquivo %1 foi criado, mas foi excluído da sincronização anteriormente. Ele não será sincronizado. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1122,3586 +1150,2399 @@ Isso significa que o cliente de sincronização pode não fazer envios de altera %1 + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Todos os arquivos na pasta '%1' de sincronização foram excluídos no servidor. - Essas exclusões serão sincronizadas com a pasta de sincronização local, tornando esses arquivos indisponíveis, a menos que você tenha o direito de restaurar. - Se você decidir manter os arquivos, eles serão re-sincronizados com o servidor se você tiver direitos para fazê-lo. -Se você decidir excluir os arquivos, eles não estarão disponíveis para você, a menos que você seja o proprietário. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Todos os arquivos na pasta de sincronização local '%1' foram excluídos. Essas exclusões serão sincronizadas com o servidor, tornando tais arquivos indisponíveis, a menos que restaurados.Tem certeza de que deseja sincronizar essas ações com o servidor?Se isso foi um acidente e você decidir manter seus arquivos, eles serão re-sincronizados a partir do servidor. - - - - Remove All Files? - Deseja Remover Todos os Arquivos? - - - - Remove all files - Remover todos os arquivos + + There are unresolved conflicts. + Existem conflitos não resolvidos. - - Keep files - Manter arquivos + + Sync error: %1 + Erro de sincronização: %1 OCC::FolderMan - + Could not reset folder state Não foi possível redefinir o estado da pasta - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. Uma velha revista de sincronização '%1' foi encontrada, mas não pôde ser removida. Por favor, certifique-se de que nenhuma aplicação está a usá-la. - - (backup) - (backup) + + Unsupported filesystem + Sistema de arquivos não suportado - - (backup %1) - (backup %1) + + Account disconnected or paused + Conta desconectada ou pausada - - Undefined State. - Estado indefinido. + + User triggered force sync + Sincronização forçada acionada pelo usuário - - Waiting to start syncing. - À espera do início da sincronização. + + Folder is about to be removed + A pasta está prestes a ser removida - - Preparing for sync. - Preparando para sincronização. + + (backup) + (backup) - - Sync is running. - A sincronização está ocorrendo. + + (backup %1) + (backup %1) - - Sync was successful, unresolved conflicts. - A sincronização foi bem-sucedida, conflitos não resolvidos. + + The selected path is not a folder. + O caminho selecionado não é uma pasta! - - Last Sync was successful. - A última sincronização foi feita com sucesso. + + The folder %1 is used in a folder sync connection. + A pasta %1 é usada em uma conexão de sincronização de pasta! - - Setup Error. - Erro de Configuração. + + Folder '%1' is already in use by application %2. + A pasta '%1' já está em uso pelo aplicativo %2! - - User Abort. - Usuário Abortou. + + There is already a sync from the server to this local folder. + Já existe uma sincronização do servidor para esta pasta local. - - Sync is paused. - Sincronização pausada. + + The local folder %1 already contains a folder used in a folder sync connection. + A pasta local %1 já contém uma pasta utilizada numa ligação de sincronização de pasta. - - %1 (Sync is paused) - %1 (Pausa na Sincronização) + + The local folder %1 is already contained in a folder used in a folder sync connection. + A pasta local %1 já contém uma pasta utilizada numa ligação de sincronização de pasta. - - No valid folder selected! + + No valid folder selected. Nenhuma pasta válida selecionada! - - The folder %1 is used in a folder sync connection! - A pasta %1 é usada em uma conexão de sincronização de pasta! - - - - The selected path does not exist! + + The selected path does not exist. O caminho selecionado não existe! - - The selected path is not a folder! - O caminho selecionado não é uma pasta! - - - - You have no permission to write to the selected folder! + + You have no permission to write to the selected folder. Voce não tem permissão para escrita na pasta selecionada! - - There is already a sync from the server to this local folder. Please pick another local folder! - Já existe uma sincronização do servidor para esta pasta local. Por favor, escolha uma outra pasta local! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - A pasta local %1 já contém uma pasta utilizada numa ligação de sincronização de pasta. Por favor, escolha outra! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - A pasta local %1 já está contida em uma pasta usada em uma conexão de sincronização de pastas. Por favor, escolha outra! + + Folder '%1' is already in use by another account. + Pasta '%1' já está em uso por outra conta. - - %1 Please pick another one! - %1 Por favor, escolha outro! - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - Várias contas estão compartilhando a pasta %1. -Esta configuração é conhecida por causar perda de dados e não é mais suportada. -Considere remover esta pasta da conta e adicioná-la novamente. + + On macOS, only the Apple File System is supported. + No macOS, somente o sistema de arquivos Apple File System é suportado. - OCC::FolderStatusDelegate + OCC::FolderModelController - - Add Folder Sync Connection - Adicionar Pasta a Sincronizar + + %1 options + %1 opções - - File - Arquivo + + Menu button with %1 options. Use the space key to pop the %2 options menu + Botão de menu com %1 opções. Use a tecla espaço para abrir o menu de opções %2. - OCC::FolderStatusModel + OCC::FolderWatcher - - You need to be connected to add a folder - Você precisa estar conectado para adicionar uma pasta + + The watcher did not receive a test notification. + O observador não recebeu uma notificação de teste. + + + OCC::FolderWatcherPrivate - - Click this button to add a folder to synchronize. - Clique nesse botão para adicionar uma pasta para sincronizar. + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + Esse problema geralmente acontece quando o inotify esgotou as notificações. Verifique o FAQ para mais detalhes. + + + OCC::FolderWizard - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Virtual files are not available for the selected folder + Arquivos virtuais não estão disponíveis para a pasta selecionada - - Error while loading the list of folders from the server. - Erro enquanto carregava a lista de pastas do servidor. + + Add Folder Sync Connection + Adicionar Conexão de Sincronização de pasta - - Virtual file support is enabled. - O suporte a arquivos virtuais está ativado. + + Add Sync Connection + Adicionar Conexão de Sincronização + + + OCC::FolderWizardLocalPath - - Signed out - Desconectado + + Click to select a local folder to sync. + Clique para selecionar uma pasta local para sincronização. - - Fetching folder list from server... - Obtendo lista de pastas do servidor... + + Enter the path to the local folder. + Entre com o caminha para a pasta local. - - There are unresolved conflicts. Click for details. - Existem conflitos não resolvidos. Clique para detalhes. + + Select the local folder + Selecione a pasta local + + + OCC::FolderWizardSelectiveSync - - Checking for changes in remote '%1' - Verificando alterações remotamente '%1' + + Use virtual files instead of downloading content immediately + Use arquivos virtuais em vez de baixar o conteúdo imediatamente + + + OCC::GETFileJob - - Checking for changes in local '%1' - Verificação de alterações no local '%1' + + Connection Timeout + Conexão Finalizada - - Reconciling changes - Reconciliando mudanças + + No E-Tag received from server, check Proxy/Gateway + Nenhuma E-Tag recebida do servidor, verifique Proxy / gateway - - , '%1' - Build a list of file names - , '%1' + + We received a different E-Tag for resuming. Retrying next time. + Recebemos um e-Tag diferente para resumir. Tente uma próxima vez. - - '%1' - Argument is a file name - '%1' + + We received an unexpected download Content-Length. + Recebemos um download de Comprimento do Conteúdo inesperado. . - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Sincronizando %1 + + Server returned wrong content-range + O servidor retornou erro numa série-de-conteúdo + + + OCC::GraphApi::Space - - - , - , + + Personal + Pessoal - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - baixar %1/s + + Shares + Compartilhamentos + + + OCC::IgnoreListEditor - - ↓ %1/s - ↓ %1/s + + Ignored Files Editor + Editor de Arquivos Ignorado - - ↑ %1/s - ↑ %1/s + + Files Ignored by Patterns + Arquivos Ignorados por Padrões - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - enviar %1/s + + Add + Adicionar - - Local folder: %1 - Pasta local: %1 + + Pattern + Padrão - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 de %4) + + Allow Deletion + Permitir Exclusão - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 + + Remove + Remover - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 restando, %1 de %2, arquivo %3 de %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 de%2, arquivo %3 de %4 - - - - file %1 of %2 - arquivo %1 de %2 - - - - Waiting... - Esperando... - - - - Waiting for %n other folder(s)... - Esperando por %n outra pasta...Esperando por %n outras pastas... - - - - Preparing to sync... - Preparando para sincronizar... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - O observador não recebeu uma notificação de teste. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Esse problema geralmente acontece quando o inotify esgotou as notificações. Verifique o FAQ para mais detalhes. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Adicionar Conexão de Sincronização de pasta - - - - Add Sync Connection - Adicionar Conexão de Sincronização - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Clique para selecionar uma pasta local para sincronização. - - - - Enter the path to the local folder. - Entre com o caminha para a pasta local. - - - - Select the source folder - Selecione a pasta de origem - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Criar uma Pasta Remota - - - - Enter the name of the new folder to be created below '%1': - Entre com o nome da nova pasta a ser criada abaixo '%1': - - - - Folder was successfully created on %1. - Pasta foi criada com sucesso em %1. - - - - Authentication failed accessing %1 - Falha na autenticação acessando %1 - - - - Failed to create the folder on %1. Please check manually. - Falha ao criar a pasta em %1. Por favor, verifique manualmente. - - - - Failed to list a folder. Error: %1 - Falha ao listar uma pasta. Erro: %1 - - - - Choose this to sync the entire account - Escolha esta opção para sincronizar a conta inteira - - - - This folder is already being synced. - Esta pasta já está sendo sincronizada. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Você já está sincronizando <i>%1</i>, que é uma pasta mãe de <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Você já está sincronizando <i>%1</i>, que é uma subpasta de <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Use arquivos virtuais em vez de baixar o conteúdo imediatamente%1 - - - - (experimental) - (experimental) - - - - Virtual files are not available for the selected folder - Arquivos virtuais não estão disponíveis para a pasta selecionada - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Alerta:</b> %1 - - - - <b>Warning:</b> - <b>Alerta:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Nenhuma E-Tag recebida do servidor, verifique Proxy / gateway - - - - We received a different E-Tag for resuming. Retrying next time. - Recebemos um e-Tag diferente para resumir. Tente uma próxima vez. - - - - We received an unexpected download Content-Length. - Recebemos um download de Comprimento do Conteúdo inesperado. . - - - - Server returned wrong content-range - O servidor retornou erro numa série-de-conteúdo - - - - OCC::GETJob - - - Connection Timeout - Conexão Finalizada - - - - OCC::GeneralSettings - - - Form - Formulário - - - - General Settings - Configuração Geral - - - - Language - Idioma - - - - Sync hidden files - Sincronizar arquivos escondidos - - - - Log Settings - Registro de Configurações - - - - Advanced - Approval of folder sync (Non virtual file mode only) - Avançado - Aprovação de sincronização de pasta (somente em modo de arquivo não virtual) - - - - Network - Rede - - - - Updates - Atualizações - - - - stable - estável - - - - beta - beta - - - - About - Sobre - - - - Advanced - Avançado - - - - Use &Monochrome Icons in the system tray - Usar Ícones &Monocromáticos na bandeja do sistema - - - - Ask for confirmation before synchronizing folders larger than - Solicite confirmação antes de sincronizar pastas maiores que - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Pedir confirmação antes de sincronizar os armazenamentos externos - - - - &Launch on System Startup - &Executar na Inicialização do Sistema - - - - Show &Desktop Notifications - Mostrar Notificações no &Desktop - - - - &Update Channel - &Atualizar o Canal - - - - Edit &Ignored Files - Editar Arquivos &Ignorados - - - - Show sync folders in &Explorer's Navigation Pane - Mostrar pastas de sincronização no &Painel de navegação do Explorer - - - - S&how crash reporter - M&ostrar relatório de acidente - - - - &Restart && Update - &Reiniciar && Atualização - - - - - You cannot disable autostart because system-wide autostart is enabled. - Não é possível desativar a inicialização automática porque a inicialização automática em todo o sistema está ativada. - - - - Change update channel? - Alterar canal de atualização? - - - - Warning - Aviso - - - - Language changes require a restart of this application to take effect. - As alterações de idioma exigem a reinicialização deste aplicativo para ter efeito. - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - O canal de atualização determina quais atualizações de cliente serão oferecidas para instalação. O canal "estável" contém apenas atualizações consideradas confiáveis, enquanto as versões no canal "beta" podem conter recursos e correções de bugs mais recentes, mas ainda não foram totalmente testados. -Observe que isso seleciona apenas de qual conjunto de atualizações são retiradas e que não há rebaixamentos: Portanto, voltar do canal beta para o canal estável geralmente não pode ser feito imediatamente e significa esperar por uma versão estável que seja mais recente do que a versão beta atualmente instalada. - - - - Change update channel - Alterar canal de atualização - - - - Cancel - Cancelar - - - - unknown (%1) - desconhecido (%1) - - - - (use default) - (use o padrão) - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Por favor entre uma senha %1:<br><br>Usuário: %2<br>Conta: %3<br> - - - - Reading from keychain failed with error: '%1' - Leitura de chaveiro falhou com o erro: '%1' - - - - Enter Password - Entrar Senha - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Clique aqui</a> para solicitar uma senha de aplicativo na interface da web. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor de Arquivos Ignorado - - - - Files Ignored by Patterns - Arquivos Ignorados por Padrões - - - - Add - Adicionar - - - - Pattern - Padrão - - - - Allow Deletion - Permitir Exclusão - - - - Remove - Remover - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Arquivos ou pastas correspondentes a um padrão não serão sincronizados. As alterações terão efeito na próxima vez que as pastas forem sincronizadas. - -Os itens em que a exclusão é permitida serão excluídos se impedirem que um diretório seja removido. Isso é útil para metadados. - - - - Could not open file - Não foi possível abrir o arquivo - - - - Cannot write changes to '%1'. - Não é possível gravar as alterações em '%1'. - - - - Add Ignore Pattern - Adicionar Ignorar Padrão - - - - Add a new ignore pattern: - Adicionar um novo padrão ignorar: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Esta entrada é fornecida pelo sistema em '%1' e não pode ser modificado aqui. - - - - OCC::IssuesWidget - - - Form - Formulário - - - - List of issues - Lista de problemas - - - - Filter - Filtro - - - - There were too many issues. Not all will be visible here. - Havia muitos problemas. Nem todos serão visíveis aqui. - - - - There were conflicts. Check the documentation on how to resolve them. - Houve conflitos. Verifique a documentação sobre como resolvê-los. - - - - The file %1 was ignored as its name is reserved by %2 - O arquivo %1 foi ignorado porque este nome foi reservado por %2 - - - - Reset column sizes - Redefinir tamanhos de coluna - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Houve conflitos.<a href="%1">Verifique a documentação sobre como resolvê-los.</a> - - - - Account - Conta - - - - OCC::LibCloudProvidersPrivate - - - Settings - Configurações - - - - OCC::LogBrowser - - - Log Output - Saída de Log - - - - Enable logging to temporary folder - Ativar o log para a pasta temporária - - - - Delete logs older than %1 hours - Excluir logs com mais de %1 horas - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">Os logs contêm informações confidenciais que você não deve disponibilizar publicamente</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - O cliente pode gravar logs de depuração em uma pasta temporária. Esses logs são muito úteis para diagnosticar problemas. -Como os arquivos de log podem ficar grandes, o cliente iniciará um novo para cada execução de sincronização e compactará os mais antigos. Também é recomendável habilitar a exclusão de arquivos de log após algumas horas para evitar o consumo de muito espaço em disco. - - - - If enabled, logs will be written to: - Se habilitado, os logs serão escritos para: - - - - C:/log - C:/log - - - - Log Http traffic - Log de tráfego Http - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Essas configurações persistem nas reinicializações do cliente. -Observe que o uso de qualquer opção de linha de comando de registro substituirá as configurações. - - - - Open folder - Abrir pasta - - - - OCC::Models - - - %1 Filter: - %1 Filtro: - - - - No filter - Sem filtro - - - - Reset Filters - Redefinir Filtros - - - - Status Filter: - Status do Filtro: - - - - OCC::NSISUpdater - - - New Version Available - Nova Versão Disponínel - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Uma nova versão %1 de Ciente está disponível.</p><p><b>%2</b> está disponível para baixar. A versão instalada é a %3.</p> - - - - - Skip this version - Pule esta versão - - - - Skip this time - Pular desta vez - - - - Get update - Atualizar - - - - Update Failed - Atualização Falhou - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Uma nova versão do %1 Cliente está disponível, mas o processo de atualização falhou.</p><p><b>%2</b>   -foi baixado. A versão instalada é %3.</p> - - - - Ask again later - Pergunte novamente mais tarde - - - - Restart and update - Reinicie e atualize - - - - Update manually - Atualizar manualmente - - - - OCC::NetworkSettings - - - Form - Formulário - - - - Proxy Settings - Ajustes de Proxy - - - - No Proxy - Sem proxy - - - - Use system proxy - Usar proxy do sistema - - - - Specify proxy manually as - Especifique o proxy manualmente como - - - - Host - Host - - - - : - : - - - - Proxy server requires authentication - Servidor proxy requer autenticação - - - - Note: proxy settings have no effects for accounts on localhost - Nota: as configurações de proxy não têm efeito para contas no host local - - - - Download Bandwidth - Baixar Banda - - - - - Limit to - Limitar a - - - - - KBytes/s - KBytes/s - - - - - No limit - Sem Limite - - - - - Limit to 3/4 of estimated bandwidth - Limite a 3/4 da largura de banda estimada - - - - Upload Bandwidth - Enviar Banda - - - - - Limit automatically - Limite automático - - - - Hostname of proxy server - Hostname do servidor proxy - - - - Username for proxy server - Nome de usuário para servidor proxy - - - - Password for proxy server - Senha para servidor proxy - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - Criada em %1 - - - - Closing in a few seconds... - Fechando em poucos segundos... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - requisição %1 falhou em %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' selecionada em %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Erro retornado do servidor: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Ocorreu um erro ao acessar o ponto final do token: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Não foi possível analisar o JSON retornado do servidor: <br><em>%1</em> - - - - Unsupported token type: %1 - Tipo de token não suportado: %1 - - - - The reply from the server did not contain all expected fields -:%1 - A resposta do servidor não continha todos os campos esperados -:%1 - - - - Unknown Error - Erro Desconhecido - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>Erro de conexão</h1><p>Falhou ao recuperar informações do usuário</p> - - - - Oauth2 Error - Erro Oauth2! - - - - Oauth2 authentication requires a secured connection. - A autenticação Oauth2 requer uma conexão segura. - - - - <h1>Login Error</h1><p>%1</p> - <h1>Erro de Login</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Erro ao atualizar o token: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - A resposta do servidor não continha todos os campos esperados -:%1 -Dados recebidos: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Usuário errado</h1><p>Você fez logon com o usuário <em>%1</em>, mas deve fazer login com o usuário <em>%2</em>.<br>Faça o logout de %3 em outra guia, então<a href='%4'>clique aqui</a> e faça o login como usuário%2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Nova Atualização %1 Pronta - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Uma nova atualização para %1 está para ser instalada. O atualizador pode solicitar por privilégios adicionais durante o processo. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Não foi possível baixar atualização. Por favor, clique <a href='%1'>aqui</ a> para baixar a atualização manualmente. - - - - Could not check for new updates. - Não foi possível verificar a existência de novas atualizações. - - - - Downloading %1. Please wait... - Baixando %1. Favor aguardar... - - - - %1 available. Restart application to start the update. - %1 disponível. Reinicie o aplicativo para iniciar a atualização. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - Novo %1 disponível. Por favor click <a href='%2'>aqui</a> para baixar o novo AppImage manualmente. - - - - New %1 available. Please use the system's update tool to install it. - Novo %1 disponível. Por favor, use a ferramenta de atualização do sistema para instalar. - - - - Checking update server... - Verificando servidor de atualização... - - - - Update status is unknown: Did not check for new updates. - Status de atualização é desconhecida: Não verifique para novas atualizações. - - - - No updates available. Your installation is at the latest version. - Não há atualizações disponíveis. Sua instalação é a versão mais recente. - - - - Update Check - Verificação de Atualização - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Conectar a %1 - - - - Setup local folder options - Configurar opções de pastas locais - - - - Connect... - Conectar... - - - - (experimental) - (experimental) - - - - Use &virtual files instead of downloading content immediately%1 - Use arquivo &virtual no lugar de baixar conteúdo imediatamente%1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - <p><strong>Aviso:</strong> A pasta local não está vazia. Escolha uma resolução!</p> - - - - Sync the folder '%1' - Sincronizar a pasta '%1' - - - - Virtual files are not available for the selected folder - Arquivos virtuais não estão disponíveis para a pasta selecionada - - - - Error - Erro - - - - Local Sync Folder - Sincronizar Pasta Local - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Email - - - - Connect to %1 - Conectar a %1 - - - - Enter user credentials - Entre com as credenciais do usuário - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Conectar a %1 - - - - Login in your browser - Faça login no seu navegador - - - - Copy link to clipboard - Copiar o linque para a área de transferência - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - O arquivo %1 está atualmente em uso - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Conectar a %1 - - - - Setup %1 server - Configurar servidor %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Esta URL não é segura, pois não é criptografada. Não é aconselhável usá-lo. - - - - This url is secure. You can use it. - Esta url é segura. Você pode usá-la. - - - - &Next > - &Póximo > - - - - Invalid URL - URL inválida - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Falha ao conectar a %1 em %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - O tempo expirou ao tentar contactar %1 e %2. - - - - Invalid URL - URL inválida - - - - Could not create local folder %1 - Não foi possível criar pasta local %1 - - - - Error: %1 - Erro: %1 - - - - The folder creation resulted in HTTP error code %1 - A criação da pasta resultou em um erro do código HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - A criação da pasta remota falhou porque as credenciais fornecidas estão erradas!<br/>Por favor, volte e verifique suas credenciais.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Falha na criação da pasta remota %1 com erro <tt>%2</tt>. - - - - Folder rename failed - Falha no nome da pasta - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Não é possível remover e fazer backup da pasta porque a pasta ou um arquivo que está nesta pasta está aberto em outro programa. Por favor, feche a pasta ou arquivo e clique tentar novamente ou cancelar a operação. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Assistente de Conexões do %1 - - - - Enable experimental feature? - Ativar recurso experimental? - - - - Enable experimental placeholder mode - Ativar modo experimental de espaço reservado - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Quando o modo "arquivos virtuais" estiver habilitado, nenhum arquivo será baixado inicialmente. Em vez disso, um pequeno arquivo "%1" será criado para cada arquivo existente no servidor. O conteúdo pode ser baixado executando esses arquivos ou usando seu menu de contexto. - -O modo de arquivos virtuais é mutuamente exclusivo com a sincronização seletiva. As pastas atualmente não selecionadas serão traduzidas para pastas somente on-line e suas configurações de sincronização seletiva serão redefinidas. - -Mudar para este modo abortará qualquer sincronização em execução no momento. - -Este é um novo modo experimental. Se você decidir usá-lo, relate quaisquer problemas que surgirem. - - - - Stay safe - Fique seguro - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Linques simbólicos não são suportados em sincronização. - - - - File is listed on the ignore list. - O arquivo está listado na lista de ignorados. - - - - File names ending with a period are not supported on this file system. - Os nomes de arquivos que terminam com um ponto não são suportados neste sistema de arquivos. + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Arquivos ou pastas correspondentes a um padrão não serão sincronizados. As alterações terão efeito na próxima vez que as pastas forem sincronizadas. + +Os itens em que a exclusão é permitida serão excluídos se impedirem que um diretório seja removido. Isso é útil para metadados. - - File names containing the character '%1' are not supported on this file system. - Os nomes de arquivos que contêm o caractere '%1' não são suportados neste sistema de arquivos. + + Could not open file + Não foi possível abrir o arquivo - - File name contains at least one invalid character - O nome do arquivo contem pelo menos um caractere inválido + + Cannot write changes to '%1'. + Não é possível gravar as alterações em '%1'. - - The file name is a reserved name on this file system. - O nome do arquivo é um nome reservado neste sistema de arquivos. + + Add Ignore Pattern + Adicionar Ignorar Padrão - - Filename contains trailing spaces. - O nome do arquivo contém espaços deixados para trás. + + Add a new ignore pattern: + Adicionar um novo padrão ignorar: - - Filename is too long. - O nome do arquivo é muito longo. + + This entry is provided by the system at '%1' and cannot be modified in this view. + Esta entrada é fornecida pelo sistema em '%1' e não pode ser modificado aqui. + + + OCC::LocalActivityWidget - - File/Folder is ignored because it's hidden. - Arquivo/pasta ignorado porque porque está escondido. + + Filter + Filtro - - Stat failed. - Stat falhou. + + Local activity table + Tabela de atividade local - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Conflito: Versão do servidor baixada, cópia local renomeada e não carregada. + + + Account + Conta - - The filename cannot be encoded on your file system. - O nome do arquivo não pode ser codificado em seu sistema de arquivos. + + Filter menu + Menu do filtro - - The filename is blacklisted on the server. - O nome do arquivo está na lista negra no servidor. + + Actions menu + Menu de ações - - size - tamanho + + Sort ascending by %1 + Classificar ascendente por %1 - - permissions - permissões + + Sort descending by %1 + Classificar descendo por %1 - - etag - etag + + Retry sync + Tentar sincronizar novamente + + + OCC::LogBrowser - - file id - id do arquivo + + Log Output + Saída de Log - - server reported no %1 - servidor relatou não %1 + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>Os registros contêm informações confidenciais que você não deve disponibilizar publicamente</b></span></p></body></html> - - Moved to invalid target, restoring - Movido para destino inválido, restaurando + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + O cliente pode gravar logs de depuração em uma pasta temporária. Esses logs são muito úteis para diagnosticar problemas. +Como os arquivos de log podem ficar grandes, o cliente iniciará um novo para cada execução de sincronização e compactará os mais antigos. - - Not allowed to remove, restoring - Não é permitido remover, restaurando + + Enable logging to temporary folder + Ativar o log para a pasta temporária - - Ignored because of the "choose what to sync" blacklist - Ignorado por causa da lista negra "escolher o que sincronizar" + + If enabled, logs will be written to: + Se habilitado, os logs serão escritos para: - - Not allowed because you don't have permission to add subfolders to that folder - Não permitido porque você não tem permissão para adicionar subpastas para essa pasta + + C:/log + C:/log - - Not allowed because you don't have permission to add files in that folder - Não permitido porque você não tem permissão para adicionar arquivos na pasta + + Log Http traffic + Log de tráfego Http - - Not allowed to upload this file because it is read-only on the server, restoring - Não é permitido fazer o upload deste arquivo porque ele é somente leitura no servidor, restaurando + + Log files to keep: + Arquivos de registro a serem mantidos: - - Error while reading the database - Erro ao ler o banco de dados + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Essas configurações persistem nas reinicializações do cliente. +Observe que o uso de qualquer opção de linha de comando de registro substituirá as configurações. - - Server replied with an error while reading directory '%1' : %2 - Servidor respondeu com um erro ao ler o diretório '%1' : %2 + + Open folder + Abrir pasta - OCC::PropagateDirectory - - - Error updating metadata: %1 - Erro ao atualizar metadados : %1 - + OCC::MainWindow - - %1 the folder is currently in use - A pasta %1 está atualmente em uso + + Hide + Ocultar - - - OCC::PropagateDownloadFile - - Failed to free up space, the file %1 is currently in use - Falha ao liberar espaço, o arquivo %1 está atualmente em uso + + Main toolbar for the application + Barra de ferramentas principal do aplicativo - - - File %1 can not be downloaded because of a local file name clash with %2! - O arquivo %1 não pode ser baixado por causa de um conflito de nome de arquivo local com %2! + + Main toolbar + Barra de ferramentas principal - - The download would reduce free local disk space below the limit - O download reduziria o espaço livre no disco local abaixo do limite + + + More + Mais + + + OCC::MainWindowController - - Free space on disk is less than %1 - O espaço livre no disco é inferior a %1 + + Add account... + Adicionar conta... - - File was deleted from server - O arquivo foi eliminado do servidor + + Settings... + Configurações... - - The file could not be downloaded completely. - O arquivo não pode ser baixado completamente. + + About... + Sobre... - - The downloaded file is empty despite the server announced it should have been %1. - O arquivo baixado está vazio apesar do servidor anunciou que deveria ter %1. + + Quit + Sair - - File %1 cannot be saved because of a local file name clash with %2! - O arquivo %1 não pode ser salvo devido a um conflito de nome de arquivo local com %2! + + + Errors: %1 + Erros: %1 - - Error updating metadata: %1 - Erro ao atualizar metadados : %1 + + Activity + Atividade - - - - - The file %1 is currently in use - O arquivo %1 está atualmente em uso + + Quit %1 + Sair %1 - - - File has changed since discovery - Arquivo foi alterado desde a descoberta + + Are you sure you want to quit %1? + Você tem certeza que deseja sair %1? - OCC::PropagateItemJob + OCC::ModalWrapperWidget - - ; Restoration Failed: %1 - ; Falha na Restauração: %1 + + Close + Fechar - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - não foi possível apagar o arquivo %1, erro: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - Não foi possível criar a pasta local %1 devido a um conflito de nome de arquivo local com %2 - + OCC::Models - - Error updating metadata: %1 - Erro ao atualizar metadados : %1 + + %1 Filter: + %1 Filtro: - - The file %1 is currently in use - O arquivo %1 está atualmente em uso + + All + Todos - - could not create folder %1 - não foi possível criar a pasta %1 + + Reset Filters + Redefinir Filtros - - - OCC::PropagateLocalRemove - - Could not remove %1 because of a local file name clash with %2! - Não foi possível remover %1 por causa de um conflito de nome de arquivo local com %2! + + Status Filter: + Status do Filtro: - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - O arquivo %1 não pode ser renomeado para %2 por causa de um choque com nome de arquivo local - - - - Could not rename %1 to %2, the file is currently in use - Não foi possível renomear %1 para %2, o arquivo está em uso - - - - Error updating metadata: %1 - Erro ao atualizar metadados: %1 - - - - The file %1 is currently in use - O arquivo %1 está atualmente em uso - - - - Failed to rename file - Falha ao renomear arquivo - + OCC::NetworkSettings - - Error setting pin state - Erro ao definir o estado do pin + + Form + Formulário - - - OCC::PropagateRemoteDelete - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Código HTTP retornado errado pelo servidor. 204 esperado, mas recebeu "%1 %2". + + Pause synchronization when the Internet connection is metered + Pausar a sincronização quando a conexão com a Internet for medida - - - OCC::PropagateRemoteMkdir - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Código HTTP retornado errado pelo servidor. 201 esperado, mas recebeu "%1 %2". + + Proxy Settings + Ajustes de Proxy - - Error writing metadata to the database: %1 - Erro ao gravar metadados no banco de dados:% 1 + + No Proxy + Sem proxy - - Setting file status failed due to file lock - A configuração do status do arquivo falhou devido ao bloqueio do arquivo + + Use system proxy + Usar proxy do sistema - - - OCC::PropagateRemoteMove - - Could not rename %1 to %2, error: %3 - Não foi possível renomear %1 para %2, erro: %3 + + Host + Host - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Código HTTP retornado errado pelo servidor. 201 esperado, mas recebeu "%1 %2". + + Proxy Hostname + Nome do Host Proxy - - Error updating metadata: %1 - Erro ao atualizar metadados : %1 + + : + : - - The file %1 is currently in use - O arquivo %1 está atualmente em uso + + Proxy Port Number + Número da porta proxy - - Error writing metadata to the database - Ocorreu um erro ao escrever metadados ao banco de dados + + Proxy server requires authentication + Servidor proxy requer autenticação - - Error setting pin state - Erro ao definir o estado do pin + + Note: proxy settings have no effects for accounts on localhost + Nota: as configurações de proxy não têm efeito para contas no host local - - - OCC::PropagateUpdateMetaDataJob - - Could not update file : %1 - Não foi possível atualizar o arquivo: %1 + + Specify proxy manually + Especifique proxy manualmente - - The file %1 is currently in use - O arquivo %1 está atualmente em uso + + Proxy type + Tipo de proxy - - - OCC::PropagateUploadFileCommon - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - O arquivo %1 não pode ser enviado porque um outro arquivo com o mesmo nome, diferenciando apenas letras maiúsculas e minúsculas, existe + + + Hostname of proxy server + Hostname do servidor proxy - - File Removed - Arquivo Removido + + + Username for proxy server + Nome de usuário para servidor proxy - - Local file changed during syncing. It will be resumed. - Arquivo local alterado durante a sincronização. Ele será retomado. + + + Password for proxy server + Senha para servidor proxy - - Local file changed during sync. - Arquivo local modificado durante a sincronização. + + HTTP(S) proxy + HTTP(S) proxy - - Error updating metadata: %1 - Erro ao atualizar metadados : %1 + + SOCKS5 proxy + SOCKS5 proxy + + + OCC::NewAccountBuilder - - The file %1 is currently in use - O arquivo %1 está atualmente em uso + + no error details are available + Não há detalhes disponíveis sobre o erro. - - - Upload of %1 exceeds the quota for the folder - O envio de %1 excede a quota da pasta + + Unable to connect to server during account creation: %1. + Não foi possível conectar ao servidor durante a criação da conta: %1. - OCC::PropagateUploadFileNG + OCC::NewAccountWizard - - %1 the file is currently in use - %1 o arquivo está atualmente em uso + + Cancel Setup + Cancelar Configuração - - The local file was removed during sync. - O arquivo local foi removido durante a sincronização. + + Do you really want to cancel the account setup? + Você realmente deseja cancelar a configuração da conta? + + + OCC::NewAccountWizardController - - Local file changed during sync. - Arquivo local modificado durante a sincronização. + + Add account… + Adicionar conta… - - The server did ask for a removed legacy feature(polling) - O servidor solicitou a remoção de um recurso legado (pesquisa) + + Back + Voltar - - Unexpected return code from server (%1) - Código de retorno inesperado do servidor (%1) + + Advanced + Avançado - - Missing File ID from server - Falta ID do arquivo do servidor + + Sign in + Entrar - - Missing ETag from server - Falta ETag do servidor + + Open sign in again + Abrir sessão novamente - OCC::PropagateUploadFileTUS + OCC::NewVersionAvailableWidget - - %1 the file is currently in use - %1 o arquivo está atualmente em uso + + Skip this version + Pule esta versão - - The local file was removed during sync. - O arquivo local foi removido durante a sincronização. + + Get update + Atualizar - - Local file changed during sync. - Arquivo local modificado durante a sincronização. + + Skip this time + Pular desta vez - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - %1 o arquivo está atualmente em uso - - - - The server did ask for a removed legacy feature(polling) - O servidor solicitou a remoção de um recurso legado (pesquisa) - + OCC::OAuth - - The local file was removed during sync. - O arquivo local foi removido durante a sincronização. + + Successfully signed in + Login efetuado com sucesso. - - Local file changed during sync. - Arquivo local modificado durante a sincronização. + + Now, explore %1 on desktop. + Agora, explore %1 no computador. - - The server did not acknowledge the last chunk. (No e-tag was present) - O servidor não reconheceu o último pedaço. (Nenhuma e-tag estava presente) + + Error returned from the server: <em>%1</em> + Erro retornado do servidor: <em>%1</em> - - - OCC::ProtocolItemModel - - Time - Horário + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Ocorreu um erro ao acessar o ponto final do token: <br><em>%1</em> - - File - Arquivo + + Could not parse the JSON returned from the server: <br><em>%1</em> + Não foi possível analisar o JSON retornado do servidor: <br><em>%1</em> - - Folder - Pasta + + Unsupported token type: %1 + Tipo de token não suportado: %1 - - Issues - Problemas + + The reply from the server did not contain all expected fields +:%1 + A resposta do servidor não continha todos os campos esperados +:%1 - - Action - Ação + + Unknown Error + Erro Desconhecido - - Size - Tamanho + + + Login Error + Erro de Login - - Account - Conta + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + Você fez login com o usuário <em>%1</em>, mas deve fazer login com o usuário<em>%2</em>.<br>Por favor, retorne ao cliente %3 e reinicie a autenticação. - - Status - Status + + Wrong user + usuário errado - OCC::ProtocolWidget + OCC::OAuthPageController - - Form - Formulário + + Application Name Logo %1 + Logotipo do nome do aplicativo %1 - - Local sync protocol - Protocolo de sincronização local + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Deixe esta tela aberta. Uma solicitação de login aparecerá no seu navegador para conectá-lo ao seguinte endereço. - - Filter - Filtro + + Sign in URL + URL de login - - Account - Conta + + To copy the sign in URL to the clipboard, use the copy button + Para copiar o URL de login para a área de transferência, use o botão de copiar. - - Copy to clipboard - Copiar para área de transferência + + Copy URL to sign in + Copie o URL para fazer login. - - Show in file browser - Mostrar no navegador de arquivos + + Additional logo defined by the organization + Logotipo adicional definido pela organização - - Show in web browser - Mostrar no navegador da web + + URL copied + URL copiada - - Retry sync - Tentar sincronizar novamente + + Copy URL + Copie a URL - - - OCC::ProxyAuthDialog - - Proxy authentication required - Autenticação via proxy é exigida + + Unable to connect to the login service. If the problem continues, please contact support. + Não foi possível conectar-se ao serviço de login. Se o problema persistir, entre em contato com o suporte. - - Username: - Nome de usuário: + + Please copy the authentication URL using the button above, and provide this information to support. + Por favor, copie o URL de autenticação usando o botão acima e forneça essas informações ao suporte. - - Proxy: - Proxy: + + Failed to look up WebFinger instances: %1 + Falha ao localizar instâncias do WebFinger: %1 - - The proxy server needs a username and password. - O servidor de proxy necessita de usuário e senha. + + You are already connected to an account with these credentials. + Você já está conectado a uma conta com essas credenciais. - - Password: - Senha: + + Unable to retrieve capabilities from server. + Não foi possível recuperar as capacidades do servidor. - - TextLabel - TextLabel + + The server is not supported by this client. + Este cliente não suporta o servidor. - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Escolher o que Sincronizar + + Error while trying to log in to OAuth2-enabled server. %1 + Erro ao tentar fazer login em um servidor habilitado para OAuth2. %1 - - - OCC::SelectiveSyncWidget - - Loading ... - Carregando... + + Unable to open browser with provided URL. %1 + Não foi possível abrir o navegador com o URL fornecido. %1 - - Deselect remote folders you do not wish to synchronize. - Desmarque as pastas remotas que não deseja sincronizar. + + OAuth2 authentication requires a secured connection. %1 + A autenticação OAuth2 requer uma conexão segura. %1 - - Name - Nome + + Authorization server unreachable. %1 + Servidor de autorização inacessível. %1 + + + OCC::OCUpdater - - Size - Tamanho + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + Não foi possível baixar atualização. Por favor, clique <a href='%1'>aqui</ a> para baixar a atualização manualmente. - - - No subfolders currently on the server. - Nenhuma sub-pasta atualmente no servidor. + + Could not check for new updates. + Não foi possível verificar a existência de novas atualizações. - - An error occurred while loading the list of sub folders. - Ocorreu um erro enquanto carregava a lista de subpastas. + + Downloading %1. Please wait… + Baixando 1%. Aguarde… - - - OCC::ServerNotificationHandler - - Dismiss - Dispensar + + %1 installed successfully. Restart the application to finish installing the update. + %1 instalado com sucesso. Reinicie o aplicativo para concluir a instalação da atualização. - - - OCC::SettingsDialog - - Settings - Configurações + + %1 available. Restart application to start the update. + %1 disponível. Reinicie o aplicativo para iniciar a atualização. - - Activity - Atividade + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Novo %1 disponível. Por favor click <a href='%2'>aqui</a> para baixar o novo AppImage manualmente. - - Add account - Adicionar uma conta + + New %1 available. Please use the system's update tool to install it. + Novo %1 disponível. Por favor, use a ferramenta de atualização do sistema para instalar. - - - Quit %1 - Sair %1 + + Checking update server… + Verificando o servidor de atualizações… - - Are you sure you want to quit %1? - Você tem certeza que deseja sair %1? + + Update status is unknown: Did not check for new updates. + Status de atualização é desconhecida: Não verifique para novas atualizações. - - Account - Conta + + No updates available. Your installation is at the latest version. + Não há atualizações disponíveis. Sua instalação é a versão mais recente. + + + OCC::OwncloudPropagator - - MainWindow - JanelaPrincipal + + File %1 is currently in use + O arquivo %1 está atualmente em uso - - Hide - Ocultar + + vfs instance is not available. + A instância VFS não está disponível. - OCC::ShareDialog + OCC::ProcessDirectoryJob + + + Symbolic links are not supported in syncing. + Linques simbólicos não são suportados em sincronização. + + + + File is listed on the ignore list. + O arquivo está listado na lista de ignorados. + - - TextLabel - TextLabel + + File names ending with a period are not supported on this file system. + Os nomes de arquivos que terminam com um ponto não são suportados neste sistema de arquivos. - - share label - compartilhar etiqueta + + File names containing the character '%1' are not supported on this file system. + Os nomes de arquivos que contêm o caractere '%1' não são suportados neste sistema de arquivos. - - Dialog - Diálogo + + File name contains at least one invalid character + O nome do arquivo contem pelo menos um caractere inválido - - ownCloud Path: - Caminho ownCloud: + + The file name is a reserved name on this file system. + O nome do arquivo é um nome reservado neste sistema de arquivos. - - %1 Sharing - Compartilhamento %1 + + Filename contains trailing spaces. + O nome do arquivo contém espaços deixados para trás. - - %1 - %1 + + Filename is too long. + O nome do arquivo é muito longo. - - Folder: %2 - Pasta: %2 + + File/Folder is ignored because it's hidden. + Arquivo/pasta ignorado porque porque está escondido. - - The server does not allow sharing - O servidor não permitir o compartilhamento + + Stat failed. + Stat falhou. - - Retrieving maximum possible sharing permissions from server... - Recuperando máximo possível de permissões de compartilhamento do servidor... + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Conflito: Versão do servidor baixada, cópia local renomeada e não carregada. - - The file can not be shared because it was shared without sharing permission. - O arquivo não pode ser partilhado, pois foi compartilhado sem permissão de compartilhamento. + + The filename cannot be encoded on your file system. + O nome do arquivo não pode ser codificado em seu sistema de arquivos. - - Users and Groups - Usuários e Grupos + + The filename is blacklisted on the server. + O nome do arquivo está na lista negra no servidor. - - Public Links - Linques Públicos + + + + + the VFS instance is missing. Unable to continue. + A instância VFS está ausente. Não é possível continuar. - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Compartilhar NewDocument.odt + + server reported no %1 + servidor relatou não %1 - - Upload only (File Drop) - Apenas enviar (Deixar um Arquivo) + + + account was deleted. Unable to continue + A conta foi excluída. Não é possível continuar. - - Receive files from others without revealing the contents of the folder. - Receba arquivos de outros sem revelar o conteúdo da pasta. + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: ignorado porque seu caminho está desmarcado - - Download / View / Edit - Baixar / Ver / Editar + + Moved to invalid target, restoring + Movido para destino inválido, restaurando - - Name: - Nome: + + Not allowed to remove, restoring + Não é permitido remover, restaurando - - Enter name of new share... - Digite o nome do novo compartilhamento... + + Error while reading the database. + Erro ao ler o banco de dados. - - &Create - &Criar + + account was deleted. Unable to continue. + A conta foi excluída. Não é possível continuar. - - TextLabel - RótuloTexto + + Not allowed because you don't have permission to add subfolders to that folder + Não permitido porque você não tem permissão para adicionar subpastas para essa pasta - - Set &password - Configurar &senha + + Error while doing a rename, unhandled status code: %1 + Erro ao renomear, código de status não tratado:%1 - - Set &expiration date - Configurar data de &expiração + + Not allowed because you don't have permission to add files in that folder + Não permitido porque você não tem permissão para adicionar arquivos na pasta - - Set password - Configurar senha + + Not allowed to upload this file because it is read-only on the server, restoring + Não é permitido fazer o upload deste arquivo porque ele é somente leitura no servidor, restaurando - - Recipients can view or download contents. - Os destinatários podem visualizar ou baixar o conteúdo. + + This Space is currently unavailable + This Space is currently unavailable - - Download / View - Baixar / Visualizar + + Server replied with an error while reading directory '%1' : %2 + Servidor respondeu com um erro ao ler o diretório '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - Os destinatários podem visualizar, baixar, editar, excluir e enviar conteúdos. + + Error updating metadata: %1 + Erro ao atualizar metadados : %1 - - Anyone with the link has access to the file/folder - Qualquer pessoa com o link tem acesso ao arquivo/pasta + + %1 the folder is currently in use + A pasta %1 está atualmente em uso + + + OCC::PropagateDownloadFile - - - P&assword protect - S&enha de proteção + + Failed to free up space, the file %1 is currently in use + Falha ao liberar espaço, o arquivo %1 está atualmente em uso - - The file can not be shared because it was shared without sharing permission. - O arquivo não pode ser partilhado, pois foi compartilhado sem permissão de compartilhamento. + + + File %1 can not be downloaded because of a local file name clash with %2! + O arquivo %1 não pode ser baixado por causa de um conflito de nome de arquivo local com %2! - - Link shares have been disabled - Os compartilhamentos de linque foram desativados + + The download would reduce free local disk space below the limit + O download reduziria o espaço livre no disco local abaixo do limite - - Create public link share - Criar linque de compartilhamento público + + Free space on disk is less than %1 + O espaço livre no disco é inferior a %1 - - - Delete - Excluir + + File was deleted from server + O arquivo foi eliminado do servidor - - Open link in browser - Abrir linque no navegador + + Broken webserver returned empty content length for non-empty file on resume + O servidor da web desligado retornou comprimento de conteúdo vazio para arquivo não vazio na retomada - - Copy link to clipboard - Copiar o linque para a área de transferência + + The file could not be downloaded completely. + O arquivo não pode ser baixado completamente. - - Copy link to clipboard (direct download) - Copiar o linque para a área de transferência (download direto) + + The downloaded file is empty despite the server announced it should have been %1. + O arquivo baixado está vazio apesar do servidor anunciou que deveria ter %1. - - Send link by email - Enviar linque por e-mail + + File %1 cannot be saved because of a local file name clash with %2! + O arquivo %1 não pode ser salvo devido a um conflito de nome de arquivo local com %2! - - Send link by email (direct download) - Enviar linque por e-mail (download direto) + + Error updating metadata: %1 + Erro ao atualizar metadados : %1 - - Create new... - Criar novo... + + + + + The file %1 is currently in use + O arquivo %1 está atualmente em uso - - I shared %1 with you - Eu compartilhei %1 com você + + + File has changed since discovery + Arquivo foi alterado desde a descoberta + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - Confirmar o Link de Eliminação de Compartilhamento + + ; Restoration Failed: %1 + ; Falha na Restauração: %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Você realmente deseja excluir o compartilhamento de links públicos <i>%1</i>?</p><p>Nota: Esta ação não pode ser desfeita.</p> + + could not delete file %1, error: %2 + não foi possível apagar o arquivo %1, erro: %2 - - Cancel - Cancelar + + Can not create local folder %1 because of a local file name clash with %2 + Não foi possível criar a pasta local %1 devido a um conflito de nome de arquivo local com %2 - - - Public link - Linque público + + Error updating metadata: %1 + Erro ao atualizar metadados : %1 - - Delete link share - Excluir linque de compartilhamento + + The file %1 is currently in use + O arquivo %1 está atualmente em uso - - - Please Set Password - Por favor, Definir senha + + could not create folder %1 + não foi possível criar a pasta %1 - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove + + + %1 failed with: %2 + %1 falhou com: %2 + - - Share NewDocument.odt - Compartilhar NewDocument.odt + + Could not remove %1 because of a local file name clash with %2! + Não foi possível remover %1 por causa de um conflito de nome de arquivo local com %2! - - Share with users or groups ... - Compartilhar com usuários ou grupos ... + + Could not move '%1' to the trash bin + Não foi possível mover '%1' para a lixeira - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Você pode direcionar pessoas para esse arquivo ou pasta compartilhada <a href="private link menu"><span style=" text-decoration: underline">dando a eles um link privado</span></a>.</p></body></html> + + + %1 the file is currently in use + O arquivo %1 está atualmente em uso + + + OCC::PropagateLocalRename - - The item is not shared with any users or groups - O item não está compartilhado com usuários ou grupos + + File %1 can not be renamed to %2 because of a local file name clash + O arquivo %1 não pode ser renomeado para %2 por causa de um choque com nome de arquivo local - - Open link in browser - Abrir linque no navegador + + Could not rename %1 to %2, the file is currently in use + Não foi possível renomear %1 para %2, o arquivo está em uso - - Copy link to clipboard - Copiar o linque para a área de transferência + + Error updating metadata: %1 + Erro ao atualizar metadados: %1 - - Send link by email - Enviar linque por e-mail + + The file %1 is currently in use + O arquivo %1 está atualmente em uso - - No results for '%1' - Sem resultados para '%1' + + Failed to rename file + Falha ao renomear arquivo - - I shared something with you - Eu compartilhei algo com você + + Error setting pin state + Erro ao definir o estado do pin - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Formulário + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Código HTTP retornado errado pelo servidor. 204 esperado, mas recebeu "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - RótuloTexto + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Código HTTP retornado errado pelo servidor. 201 esperado, mas recebeu "%1 %2". - - can edit - pode editar + + Error writing metadata to the database: %1 + Erro ao gravar metadados no banco de dados:% 1 - - can share - pode compartilhar + + Setting file status failed due to file lock + A configuração do status do arquivo falhou devido ao bloqueio do arquivo + + + OCC::PropagateRemoteMove - - ... - ... + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Código HTTP retornado errado pelo servidor. 201 esperado, mas recebeu "%1 %2". - - create - criar + + Error updating metadata: %1 + Erro ao atualizar metadados : %1 - - change - mudar + + The file %1 is currently in use + O arquivo %1 está atualmente em uso - - delete - excluir + + Error writing metadata to the database + Ocorreu um erro ao escrever metadados ao banco de dados - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Error setting pin state + Erro ao definir o estado do pin - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Compartilhar com %1 + + Could not update file : %1 + Não foi possível atualizar o arquivo: %1 - - Context menu share - Compartilhar contexto do menu + + The file %1 is currently in use + O arquivo %1 está atualmente em uso + + + OCC::PropagateUploadCommon - - Context menu share %1 - Compartilhar o contexto do menu %1 + + Local file changed during sync. It will be resumed. + Arquivo local alterado durante a sincronização. Será retomada. - - Confirm deletion - Confirme a exclusão + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + O arquivo %1 não pode ser enviado porque um outro arquivo com o mesmo nome, diferenciando apenas letras maiúsculas e minúsculas, existe - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Você deseja excluir o diretório <i>%1</i> e todo o seu conteúdo permanentemente? + + + Upload of %1 exceeds the quota for the folder + O envio de %1 excede a quota da pasta - - Do you want to delete the file <i>%1</i> permanently? - Você quer apagar o arquivo <i>%1</i> permanentemente? + + + %1 the file is currently in use + O arquivo %1 está atualmente em uso - - Select new location... - Selecione um novo local... + + File Removed + Arquivo Removido - - Error - Erro + + The server did not provide the file permissions + O servidor não forneceu as permissões de arquivo - - Moving file failed: - -%1 - Falha em mover o arquivo: - -%1 + + Error updating metadata: %1 + Erro ao atualizar metadados: %1 + + + OCC::PropagateUploadFile - - I shared something with you - Eu compartilhei algo com você + + %1 the file is currently in use + O arquivo %1 está atualmente em uso - - - Share... - Compartilhar... + + The server did ask for a removed legacy feature(polling) + O servidor solicitou a remoção de um recurso legado (pesquisa) - - - Copy private link to clipboard - Copie o linque privado para a área de transferência + + The local file was removed during sync. + O arquivo local foi removido durante a sincronização. - - Send private link by email... - Envie o linque privado por e-mail... + + The server did not acknowledge the file upload. (No e-tag was present) + O servidor não reconheceu o envio do arquivo. (Nenhuma etiqueta eletrônica estava presente) + + + OCC::PropagateUploadFileTUS - - Resharing this file is not allowed - Não é permitido compartilhar novamente este arquivo + + %1 the file is currently in use + %1 o arquivo está atualmente em uso - - Resharing this folder is not allowed - Não é permitido compartilhar novamente esta pasta + + Checksum computation failed + Checksum computation failed - - Create and copy public link to clipboard - Criar e copiar linque público para a área de transferência + + The local file was removed during sync. + O arquivo local foi removido durante a sincronização. + + + OCC::ProtocolItemModel - - Copy public link to clipboard - Copiar linque público para a área de transferência + + Time + Horário - - Open in browser - Abrir no navegador + + File + Arquivo - - Show file versions in browser - Mostrar versões de arquivos no navegador + + Folder + Pasta - - Rename... - Renomear + + Issues + Problemas - - Rename and upload... - Renomeie e faça o upload... + + Action + Ação - - Move and rename... - Mover e renomear... + + Size + Tamanho - - Move, rename and upload... - Mova, renomeie e faça o upload... + + Account + Conta - - Delete local changes - Excluir alterações locais + + Status + Status + + + OCC::RequestAuthenticationController - - Move and upload... - Mover e enviar... + + IdP is unreachable. Contact your system administrator or try again later. + O IdP está inacessível. Contate o administrador do sistema ou tente novamente mais tarde. - - Delete - Excluir + + Authentication failed. + Falha na autenticação. - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags - Falha ao recuperar etiquetas + + Application Name Logo %1 + Logotipo do nome do aplicativo %1 - - Backup of %1 started - Cópia de segurança de %1 iniciada + + Sign in required + É necessário fazer login. - - Failed to find %1 - Falha para encontrar %1 + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + Você pode ter sido desconectado automaticamente devido a um problema no servidor ou tempo limite excedido. Por favor, faça login novamente. - - Local path must be a an absolute path - O caminho local deve ser um caminho absoluto + + Sign in URL + URL de login - - Failed to create temporary database - Falha ao criar banco de dados temporário + + To copy the sign in URL to the clipboard, use the copy button + Para copiar o URL de login para a área de transferência, use o botão de copiar. - - Backup of %1 succeeded - Cópia de segurança de %1 bem sucedida + + Copy URL to sign in + Copie o URL para fazer login. - - Failed to set success tag - Falha ao definir etiqueta de sucesso + + Additional logo defined by the organization + Logotipo adicional definido pela organização - - Failed to create backup: %1 - Falha ao criar backup: %1 + + Stay logged out + Permanecer desconectado - - Failed to file id tags - Falha ao arquivar as etiquetas de identificação + + Sign in + Entrar - - Backup of %1 failed with: %2 - Cópia de segurança de %1 falhou com: %2 + + URL copied + URL copiada - - %1 backup - 1% cópia de segurança + + Copy URL + Copie a URL - OCC::SslButton + OCC::ResolveUrlAdapter - - <h3>Certificate Details</h3> - <h3> Detalhes do Certificado </h3> + + SSL failure when connecting to server at %1 + Falha de SSL ao conectar ao servidor em %1 - - Common Name (CN): - Nome Comum (CN): + + Could not detect compatible server at %1 + Não foi possível detectar o servidor compatível em %1 - - Subject Alternative Names: - Assunto Nomes Alternativos: + + Resolved url is invalid %1 + O endereço URL é inválido %1 - - Organization (O): - Organização (O): - - - - Organizational Unit (OU): - Unidade Organizacional (OU): - - - - State/Province: - Estado/Província: + + Rejected redirect from %1 to %2 + Redirecionamento rejeitado de %1 para %2 + + + OCC::SelectiveSyncWidget - - Country: - País: + + Loading … + Carregando ... - - Serial: - Série: + + Deselect remote folders you do not wish to synchronize. + Desmarque as pastas remotas que não deseja sincronizar. - - <h3>Issuer</h3> - <h3>Emissor</h3> + + Name + Nome - - Issuer: - Emissor: + + Size + Tamanho - - Issued on: - Emitido em: + + + Currently there are no subfolders on the server. + Atualmente, não há subpastas no servidor. - - Expires on: - Expira em: + + An error occurred while loading the list of subfolders. + Ocorreu um erro ao carregar a lista de subpastas. + + + OCC::SettingsView - - <h3>Fingerprints</h3> - <h3>Impressões Digitais</h3> + + Form + Forma - - SHA-256: - SHA-256: + + General Settings + Configuração Geral - - SHA-1: - SHA-1: + + Use Monochrome Icons in the system tray + Use ícones monocromáticos na bandeja do sistema - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b> Nota:</b>Este certificado foi aprovado manualmente</p> + + Show Desktop Notifications + Mostrar Notificações de Desktop - - %1 (self-signed) - %1 (auto-assinado) + + Start on Login + Comece no login - - %1 - %1 + + Language + Idioma - - This connection is encrypted using %1 bit %2. - - Esta conexão é criptografada usando %1 bit %2. - + + Language selector + Seletor de idiomas - - Server version: %1 - Versão do servidor: %1 + + Advanced + Avançado - - No support for SSL session tickets/identifiers - Não há suporte para sessão SSL tickets/identificadores + + Sync hidden files + Sincronizar arquivos escondidos - - Certificate information: - Informações do certificado: + + Show crash reporter + Mostrar relatório de acidente - - The connection is not secure - A conexão não é segura + + Move remotely deleted files to the local trash bin instead of deleting them + Mova os arquivos excluídos remotamente para a lixeira local em vez de excluí-los - - This connection is NOT secure as it is not encrypted. - - Esta conexão não é segura, uma vez que não é criptografada. - + + Edit Ignored Files + Editar Arquivos Ignorados - - - OCC::SslErrorDialog - - Form - Formulário + + Log Settings + Registro de Configurações - - Trust this certificate anyway - Confiar neste certificado mesmo assim + + Network + Rede - - Untrusted Certificate - Certificado Não Confiável + + Warning + Aviso - - Cannot connect securely to <i>%1</i>: - Não é possível conectar com segurança a <i>%1</i>: + + Language changes require a restart of this application to take effect. + As alterações de idioma exigem a reinicialização deste aplicativo para ter efeito. - - with Certificate %1 - com Certificado %1 + + You cannot disable autostart because system-wide autostart is enabled. + Não é possível desativar a inicialização automática porque a inicialização automática em todo o sistema está ativada. - - - - &lt;not specified&gt; - &lt;não especificado&gt; + + (use default) + (use o padrão) - - - Organization: %1 - Organização: %1 + + unknown (%1) + desconhecido (%1) + + + OCC::SocketApi - - - Unit: %1 - Unidade: %1 + + Share with %1 + parameter is the application name + Compartilhar com %1 - - - Country: %1 - País: %1 + + Confirm deletion + Confirme a exclusão - - Fingerprint (MD5): <tt>%1</tt> - Fingerprint/Identificação (MD5): <tt>%1</tt> + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Você deseja excluir o diretório <i>%1</i> e todo o seu conteúdo permanentemente? - - Fingerprint (SHA1): <tt>%1</tt> - Fingerprint/Identificação (SHA1): <tt>%1</tt> + + Do you want to delete the file <i>%1</i> permanently? + Você quer apagar o arquivo <i>%1</i> permanentemente? - - Effective Date: %1 - Data efetiva: %1 + + Error + Erro - - Expiration Date: %1 - Data de Vencimento: %1 + + Moving file failed: + +%1 + Falha em mover o arquivo: + +%1 - - Issuer: %1 - Emissor: %1 + + I shared something with you + Eu compartilhei algo com você - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - %1 (ignorado devido a um erro anterior, tentando novamente em %2) + + Select new location… + Selecione um novo local… - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Apenas %1 estão disponíveis, precisamos de pelo menos %2 para começar + + + Share… + Compartilhar… - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Não é possível abrir ou criar o banco de dados de sincronização local. Certifique-se de ter acesso de gravação na pasta de sincronização. + + + Copy private link to clipboard + Copie o linque privado para a área de transferência - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - O espaço em disco é pequeno: Os downloads que reduzam o espaço livre abaixo de %1 foram ignorados. + + Resharing this file is not allowed + Não é permitido compartilhar novamente este arquivo - - There is insufficient space available on the server for some uploads. - Há espaço disponível no servidor para alguns envios. + + Resharing this folder is not allowed + Não é permitido compartilhar novamente esta pasta - - Unresolved conflict. - Conflito não solucionado. + + Open in %1 + Aberto em %1 - - Using virtual files with suffix, but suffix is not set - Usando arquivos virtuais com sufixo, mas o sufixo não está definido + + Show file versions in web browser + Mostrar versões de arquivos no navegador da web - - Unable to read the blacklist from the local database - Não é possível ler a lista negra a partir do banco de dados local + + Rename… + Renomear… - - Unable to read from the sync journal. - Não é possível ler a partir do relatório de sincronização. + + Rename and upload… + Renomear e carregar… - - Cannot open the sync journal - Não é possível abrir o arquivo de sincronização + + + Move and rename… + Mover e renomear… - - Aborted - Abortado + + Move, rename and upload… + Mover, renomear e carregar… - - - OCC::SyncJournalDb - - Failed to connect database. - Falha ao conectar o banco de dados. + + Move and upload… + Mover e carregar… - - - OCC::Systray - - %1: %2 - %1: %2 + + Delete local changes + Excluir alterações locais - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>Versão %1. Para mais informações, visite <a href="%2">https://%3</a></p><p>Para obter ajuda e problemas conhecidos, visite: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Por Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, e outros.</small></p><p>Copyright ownCloud GmbH</p><p>Distribuído por %4 e licenciado sob a GNU General Public License (GPL) Versão 2.0.<br/>%5 e a %5 logomarca são marcas registradas de %4 nos Estados Unidos, outros países, ou ambos.</p><p><small>%6</small></p> + + Delete + Excluir - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - O cabeçalho da soma de verificação está incorreto. - + OCC::SpacesPage - - The checksum header contained an unknown checksum type '%1' - A cabeçalho da soma de verificação continha um tipo de soma de verificação desconhecido '%1' + + Select a folder to sync it to your computer. + Selecione uma pasta para sincronizar ao seu computador. - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - O arquivo baixado não corresponde à soma de verificação, ele será retomado. '%1' != '%2' + + Select a Space to sync it to your computer. + Selecione um Espaço para sincronizar com o seu computador. - OCC::Vfs + OCC::SyncEngine - - The Virtual filesystem feature does not support a drive as sync root - O recurso Sistema de Arquivos Virtual não suporta uma unidade de drive como raiz de sincronização + + %1 (skipped due to earlier error, trying again in %2) + %1 (ignorado devido a um erro anterior, tentando novamente em %2) - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - O recurso de sistema de arquivos virtual requer um sistema de arquivos NTFS, %1 está usando %2 + + Unable to sync: the local folder is missing. + Não foi possível sincronizar: a pasta local está ausente. - - The Virtual filesystem feature is not supported on network drives - O recurso de sistema de arquivos virtual não é compatível com unidades de rede + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Apenas %1 estão disponíveis, precisamos de pelo menos %2 para começar - - - OCC::ownCloudGui - - Please sign in - Favor conectar + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + Não é possível abrir ou criar o banco de dados de sincronização local. Certifique-se de ter acesso de gravação na pasta de sincronização. - - Folder %1: %2 - Pasta %1: %2 + + + + + Cannot open the sync journal. + Não foi possível abrir o diário de sincronização. - - Unresolved %1 conflicts - Conflito %1 não resolvidos + + Aborted due to %1 + Abortado devido a %1 - - Ignored errors %1 - Erros ignorados %1 + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + O espaço em disco é pequeno: Os downloads que reduzam o espaço livre abaixo de %1 foram ignorados. - - There are no sync folders configured. - Não há pastas de sincronização configuradas. + + There is insufficient space available on the server for some uploads. + Há espaço disponível no servidor para alguns envios. - - Open in browser - Abrir no navegador + + application exit + abort reason + saída do aplicativo - - - - Log in... - Entrar... + + Unable to read the blacklist from the local database + Não é possível ler a lista negra a partir do banco de dados local - - - - Log out - Sair + + Unable to read from the sync journal. + Não é possível ler a partir do relatório de sincronização. + + + OCC::SyncErrorWidget - - Recent Changes - Alterações Recentes + + Issues table + Tabela de problemas - - Managed Folders: - Pastas Gerenciadas: + + There were too many issues. Not all will be visible here. + Havia muitos problemas. Nem todos serão visíveis aqui. - - Open folder '%1' - Abrir pasta '%1' + + There were conflicts. Check the documentation on how to resolve them. + Houve conflitos. Verifique a documentação sobre como resolvê-los. - - Open %1 in browser - Abrir %1 no navegador + + The file %1 was ignored as its name is reserved by %2 + O arquivo %1 foi ignorado porque este nome foi reservado por %2 - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - O servidor na conta %1 executa uma versão não suportada %2. A utilização deste cliente com versões de servidor não suportadas não foi testada e é potencialmente perigosa. Prossiga por sua conta e risco. + + Reset column sizes + Redefinir tamanhos de coluna - - Unknown status - Status desconhecido + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Houve conflitos.<a href="%1">Verifique a documentação sobre como resolvê-los.</a> - - Settings... - Configurações... + + Filter menu + Menu do filtro - - Details... - Detalhes... + + Account + Conta - - Help - Ajuda + + Filter + Filtro + + + OCC::SyncJournalDb - - Quit %1 - Sair %1 + + Failed to connect database. + Falha ao conectar o banco de dados. + + + OCC::Theme - - Disconnected from %1 - Desconectado de %1 + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Versão %1. Para obter mais informações, acesse <a href="%2">https://%3</a></p><p>Para obter ajuda e problemas conhecidos, acesse: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>Por Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller e outros.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distribuído por %4 e licenciado sob a GNU General Public License (GPL) Versão 2.0.<br/>%5 e o logotipo %5 são marcas registradas de %4 nos Estados Unidos, em outros países ou em ambos.</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Unsupported Server Version - Versão do Servidor Não Suportada + + TLS Certificate Error + Erro de certificado TLS - - Disconnected - Desconectado + + Do you want to trust this certificate anyway? + Você mesmo assim confia nesse certificado? - - Disconnected from some accounts - Desconectado de algumas contas + + Cannot connect securely to %1 + Não é possível conectar-se com segurança a %1 - - Disconnected from accounts: - Desconectado de contas: + + &lt;not specified&gt; + &lt;não especificado&gt; - - Account %1: %2 - Conta %1: %2 + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>com certificado %1</h3><div id="ccert"><p>Organização %2<br/>Unidade: %3<br/>País: %4</p><p>Impressão digital (MD5): <tt>%5</tt><br/>Impressão digital (SHA1): <tt>%6</tt><br/>Impressão digital (SHA256): <tt>%7</tt><br/><br/>Data efetiva: %8Data de validade: %9</div><h3>Emissor: %10</h3><div id="issuer"><p>Organização: %11<br/>Unidade: %12<br/>Pàis: %13</p></div></div> + + + OCC::TrayMenuController - - Signed out - Desconectado + + Unsupported Server Version + Versão do Servidor Não Suportada - - Account synchronization is disabled - A sincronização de conta está desativada + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + O servidor na conta %1 executa uma versão não suportada %2. A utilização deste cliente com versões de servidor não suportadas não foi testada e é potencialmente perigosa. Prossiga por sua conta e risco. - - Synchronization is paused - A sincronização está pausada + + Show %1 + Mostrar %1 - - Up to date (%1) - Atualizado (%1) + + Help + Ajuda - - No sync folders configured - Nenhuma pasta de sincronização configurada + + About %1 + Sobre %1 - - Unpause all folders - Retomar todas as pastas + + Quit %1 + Sair %1 + + + OCC::UpdateDownloadedWidget - - Pause all folders - Pausar todas as pastas + + Restart later + Reiniciar mais tarde - - Unpause all synchronization - Retomar toda a sincronização + + Restart now + Reinicie agora + + + OCC::UpdaterScheduler - - Unpause synchronization - Retomar sincronização + + Update available + Atualização disponível + + + OCC::UrlPageController - - Pause all synchronization - Dar uma pausa em toda a sincronização + + Your web browser will be opened to complete sign in. + Seu navegador será aberto para concluir o login. - - Pause synchronization - Dar uma pausa na sincronização + + %1 logo + %1 logotipo - - Log out of all accounts - Desconectar todas as contas + + Welcome to %1 + Bem vindo a %1 - - Log in to all accounts... - Conectar todas as contas... + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Digite o endereço do seu servidor para começar. Seu navegador será aberto para concluir o login. - - New account... - Nova conta... + + Server address field + Campo de endereço do servidor - - About %1 - Sobre %1 + + Enter your server address here + Insira o endereço do seu servidor aqui. - - No items synced recently - Não há itens sincronizados recentemente + + Additional logo defined by the organization + Logotipo adicional definido pela organização - - Checking for changes in remote '%1' - Verificando alterações remotamente '%1' + + Invalid server URL: %1 + URL do servidor inválida: %1 - - Checking for changes in local '%1' - Verificação de alterações local '%1' + + Invalid URL scheme. Only https is accepted. + Esquema de URL inválido. Somente https é aceito. - - Syncing %1 of %2 (%3 left) - Sincronizar %1 de %2 (%3 faltando) + + The requested URL failed with query value: %1 + A URL solicitada falhou com o seguinte valor: %1 + + + OCC::UserInfoAdapter - - Syncing %1 of %2 - Sincronizando %1 de %2 + + Network request to collect user info failed + A solicitação de rede para coletar informações do usuário falhou. - - Syncing %1 (%2 left) - Sincronizando %1 (%2 faltando) + + Unable to retrieve user info: invalid credentials. + Não foi possível recuperar as informações do usuário: credenciais inválidas. - - Syncing %1 - Sincronizando %1 + + Unexpected network response when retrieving user info. + Resposta inesperada da rede ao recuperar informações do usuário. - - %1 (%2, %3) - %1 (%2, %3) + + Retrieving user info failed with JSON error. + A recuperação das informações do usuário falhou com um erro JSON. - OwncloudAdvancedSetupPage - - - Form - Formulário - - - - - TextLabel - TextLabel - + OCC::ValidateChecksumHeader - - Server - Servidor + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + O arquivo baixado não corresponde à soma de verificação, ele será retomado. '%1' != '%2' + + + OCC::Vfs - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Se esta opção estiver marcada, os conteúdos existentes na pasta local serão apagados para iniciar uma sincronização limpa a partir do servidor.</p><p>Não marque esta opção se os conteúdos locais devem ser enviados para a pasta de servidores.</p></body></html> + + The Virtual filesystem feature does not support a drive as sync root + O recurso Sistema de Arquivos Virtual não suporta uma unidade de drive como raiz de sincronização - - Ask for confirmation before synchroni&zing folders larger than - Solicite confirmação antes de sincroni&zar pastas maiores que + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + O recurso de sistema de arquivos virtual requer um sistema de arquivos NTFS, %1 está usando %2 - - S&ynchronize everything from server - S&incronizar tudo com o servidor + + The Virtual filesystem feature is not supported on network drives + O recurso de sistema de arquivos virtual não é compatível com unidades de rede + + + OCC::VfsWinPrivate - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + + %1 - %2 + %1 - %2 - - Ask for confirmation before synchronizing e&xternal storages - Solicitar confirmação antes de sincronizar os armazenamentos e&xternos + + Could not find StorageProviderState property %1 + Não foi possível encontrar a propriedade StorageProviderState %1 - - Choose what to sync - Escolha o que quer sincronizar + + Could not find StorageFolder for %1 error: %2 + Não foi possível encontrar a pasta de armazenamento para %1 erro: %2 - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Quando esta opção é selecionada, o assistente será fechado sem sincronizar nada. Você pode usar a &quot; Adicionar conexão de sincronização de pastas &quot; botão das configurações da conta para escolher qual par de pasta local e remota você deseja sincronizar</p></body></html> + + Could not retrieve StorageFolder for %1 %2 (0x%3) + Não foi possível recuperar a pasta de armazenamento para %1 %2 (0x%3) - - Manually create folder sync connections - Criar manualmente conexões de sincronização de pasta + + The folder is used by a different client: %1 + A pasta é usada por um cliente diferente %1 - - &Local Folder - &Pasta Local + + Error registering StorageProvider for %1: %2 (0x%3) + Erro ao registrar a Pasta de Armazenamento para %1: %2 (0x%3) - - pbSelectLocalFolder - pbSelectLocalFolder + + Unable to connect sync root: %1 error: %2 + Impossível se conectar a %1 erro: %2 - - &Keep local data - &Manter dados locais + + %1 is managed by another sync client + %1 é gerenciado por outro cliente de sincronização - - Start a &clean sync (Crreate a backup and erases the local folder!) - Inicie uma sincronização &limpa (crie um backup e apague a pasta local!) + + Downgrading the folder from %1 to %2 is not supported + Não é possível fazer o downgrade da pasta de %1 para %2. - OwncloudHttpCredsPage - - - Form - Formulário - + OCC::WebFingerLookupAdapter - - &Username - &Nome do Usuário + + Failed to retrieve user info + Falha ao recuperar informações do usuário - - &Password - &Senha + + WebFinger lookup returned no links + A pesquisa do WebFinger não retornou nenhum link - OwncloudOAuthCredsPage - - - Form - Formulário - + OCC::WindowsUpdater - - Please switch to your browser to proceed. - Mude para o seu navegador para prosseguir. + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Uma nova versão %1 de Ciente está disponível.</p><p><b>%2</b> está disponível para baixar. A versão instalada é a %3.</p> - - An error occurred while connecting. Please try again. - Ocorreu um erro durante a conexão. Por favor, tente novamente. + + Skip this version + Pule esta versão - - Re-open Browser - Reabrir o navegador + + Update Failed + Atualização Falhou - - - OwncloudSetupPage - - Form - Formulário + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Uma nova versão do %1 Cliente está disponível, mas o processo de atualização falhou.</p><p><b>%2</b>   +foi baixado. A versão instalada é %3.</p> - - Ser&ver Address - Endereço do Ser&vidor + + Ask again later + Pergunte novamente mais tarde - - https://... - https://... + + Restart and update + Reinicie e atualize - - Error Label - Rótulo de Erro + + Update manually + Atualizar manualmente QObject - + in the future no futuro - + %n day(s) ago - %n dia(s) atrás%n dia(s) atrás + %n dia atrás%n dias atrás%n dias atrás - + %n hour(s) ago - %n hora(s) atrás%n hora(s) atrás + há %n hora atráshá %n horas atráshá %n horas atrás - + now agora - - Less than a minute ago - A menos de um minuto atrás + + less than a minute ago + a menos de um minuto atras - + %n minute(s) ago - %n minuto(s) atrás%n minuto(s) atrás + há %n minuto atráshá %n minutos atráshá %n minutos atrás - - Some time ago - Algum tempo atrás - - - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 + + SpacesBrowser + + + Form + Formulário + + + + SpacesPage + + + Spaces list + Lista de espaços + + + + SpacesView + + + Spaces + Espaços + + SyncFileItem::Status - + Undefined Indefinido - + Fatal Error Erro Fatal - - + + Error Erro - + Info Informação - + Success Sucesso - + Conflict Conflito - + File Ignored Arquivo Ignorado - + Restored Restaurado - + Blacklisted Lista Negra - + Excluded Excluído - - - Utility - - - %L1 GB - %L1 GB - - - %L1 MB - %L1 MB + + Message + Mensagem - - %L1 KB - %L1 KB + + Filename Reserved + Nome do Arquivo Reservado + + + UpdateDownloadedWidget - - %L1 B - %L1 B + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">é necessário reiniciar</span></p></body></html> + + + Utility - + %n year(s) - %n ano%n ano(s) + %n ano%n anos%n anos - + %n month(s) - %n mês%n mês(es) + %n mês%n meses%n meses - + %n day(s) - %n day%n dia(s) + %n dia%n dias%n dias - + %n hour(s) - %n hora%n hora(s) + %n hora%n horas%n horas - + %n minute(s) - %n minuto%n minuto(s) + %n minuto%n minutos%n minutos - + %n second(s) - %n segundo%n segundo(s) + %n segundo%n segundos%n segundos - + %1 %2 %1 %2 + + + Shutting down %1 + Desligando %1 + - main.cpp + account loading - - System Tray not available - Área de notificação não disponível + + Error accessing the configuration file + Erro acessando o arquivo de configuração - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 requer uma área de notificação em funcionamento. Se você estiver executando o XFCE, siga <a href="http://docs.xfce.org/xfce/xfce4-panel/systray"> estas instruções</a>. Caso contrário, instale um aplicativo de área de notificação, como o 'trayer' e tente novamente. + + There was an error while accessing the configuration file at %1. + Ocorreu um erro ao acessar o arquivo de configuração em %1. ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - %1 %2 %3%8%9Bibliotecas Qt %4, %5%8Usando o plugin de arquivos virtuais: %6%8%7 + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Bibliotecas Qt %3, %4%7Usando o plug-in de arquivos virtuais: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - %1 (Compilado com o Qt %1) + + %1 (Built against Qt %2) + %1 (Construído contra Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Sempre disponível localmente + + + + Currently available locally + Atualmente disponível localmente + + + + Some available online only + Alguns disponíveis apenas online + + + + Available online only + Apenas disponíveis online + + progress @@ -4724,30 +3565,30 @@ Este é um novo modo experimental. Se você decidir usá-lo, relate quaisquer pr Uploaded Enviado - - - Server version downloaded, copied changed local file into conflict file - Versão do servidor baixada, copiada a modificação do local do arquivo dentro do arquivo de conflito - Deleted Excluído - - Moved to %1 - Movido para %1 - - - + Ignored Ignorado - - Filesystem access error - Erro ao acesso do sistema de arquivos + + Server version downloaded, local copy was backed up as conflict file + Versão do servidor baixada, backup da cópia local como arquivo de conflito + + + + Renamed %1 to %2 + Renomeado %1 para %2 + + + + Moved %1 to %2 + Movido %1 para %2 @@ -4760,153 +3601,86 @@ Este é um novo modo experimental. Se você decidir usá-lo, relate quaisquer pr Metadado local enviado - - + + Unknown Desconhecido - + downloading baixando - + uploading enviando - + deleting eliminando - + moving movendo - + ignoring ignorando - - + error erro - + updating local metadata Atualizando metadados locais - - theme - - - Status undefined - Status indefinido - - - - Waiting to start sync - Aguardando para iniciar sincronização - - - - Sync is running - Sincronização em andamento - - - - Sync Success - Sucesso na Sincronização - - - - Sync Success, some files were ignored. - Sincronizado com Sucesso, alguns arquivos foram ignorados. - - - - Sync Error - Erro de sincronização - - - - Setup Error - Erro na Configuração - - - - Preparing to sync - Preparando para sincronização - - - - Aborting... - Abortando... - - - - Sync is paused - Sincronização em pausa - - utility - + Could not open browser Não foi possível abrir o navegador - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? Ocorreu um erro ao iniciar o navegador para ir ao endereço URL %1. Talvez nenhum navegador padrão esteja configurado? - + Could not open email client Não foi possível abrir o cliente de e-mail - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? Ocorreu um erro ao iniciar o cliente de e-mail para criar uma nova mensagem. Talvez nenhum cliente de e-mail padrão esteja configurado? - - Always available locally - Sempre disponível localmente - - - - Some available online only - Alguns disponíveis apenas online - - - - - Available online only - Apenas disponíveis online - - - - Currently available locally - Atualmente disponível localmente - - - + Make always available locally Deixar sempre disponível localmente - + Free up local space Libere espaço local + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Algumas configurações foram definidas em versões mais recentes deste cliente e utilizam recursos que não estão disponíveis nesta versão + + \ No newline at end of file diff --git a/translations/client_ru.ts b/translations/client_ru.ts deleted file mode 100644 index 50bc1365e46..00000000000 --- a/translations/client_ru.ts +++ /dev/null @@ -1,4913 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - Средство отчётов об ошибках Tomahawk - - - - <html><head/><body><p><br/></p></body></html> - <html><head/><body><p><br/></p></body></html> - - - - Abort - Отмена - - - - Send this report - Отправить этот отчёт - - - - Don't send - Не отправлять - - - - You can disable sending crash reports in the configuration dialog. - Вы можете отключить отправку отчётов об ошибках в диалоге настроек. - - - - - - We cannot gather useful debug information on your system. - Не удалось собрать полезную информацию в вашей системе. - - - - - - - - Close - Закрыть - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - Отладочная информация готова к отправке (<a href="%1">посмотреть трассировку</a>). - - - - Gathering debug information... - Сбор отладочной информации… - - - - Uploaded %L1 of %L2 KB. - Закачано %L1 из %L2 КБ. - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - Ушло! <b>Большое спасибо</b>. Пожалуйста ссылайтесь в отчётах на ошибку <a href="clipboard://%1"><b>%1</b></a> (кликните для копирования). - - - - Failed to send crash info. - Не удалось отправить отчёт об ошибке. - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - Невозможно переименовать %1, файл сейчас используется - - - - Could not make directories in trash - Не удалось создать каталоги в корзине - - - - - Could not move '%1' to '%2' - Не удалось переместить '%1' в '%2' - - - - Moving to the trash is not implemented on this platform - Перемещение в корзину не реализовано на данной платформе - - - - Error removing '%1': %2 - Ошибка при удалении '%1': %2 - - - - Could not remove folder '%1' - Не удается удалить папку '%1' - - - - FolderWizardSourcePage - - - Form - Форма - - - - Pick a local folder on your computer to sync - Выберите локальный каталог на вашем компьютере для синхронизации - - - - &Choose... - &Выбрать... - - - - FolderWizardTargetPage - - - Form - Форма - - - - Select a remote destination folder - Выберите удалённый каталог назначения - - - - Create Folder - Создать каталог - - - - Refresh - Обновить - - - - Folders - Каталоги - - - - TextLabel - TextLabel - - - - NotificationWidget - - - Form - Форма - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - TextLabel - - - - OCC::AboutDialog - - - About - О программе - - - - TextLabel - TextLabel - - - - Versions - Версии - - - - About %1 - Про %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Время ожидания соединения превышено - - - - Unknown error: network reply was deleted - Неизвестная ошибка: сетевой ответ был удален - - - - Server replied "%1 %2" to "%3 %4" - Сервер ответил от "%1 %2" до "%3 %4" - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - Форма - - - - Re-open Browser - Повторно открыть браузер - - - - ... - ... - - - - Storage space: ... - Размер хранилища: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Неотмеченные каталоги будут <b>удалены</b> из вашей локальной файловой системы и больше не будут синхронизироваться на этом компьютере - - - - Synchronize all - Синхронизировать всё - - - - Synchronize none - Не синхронизировать ничего - - - - Apply manual changes - Применить ручные изменения - - - - Apply - Применить - - - - - - - Cancel - Отмена - - - - Connected with <server> as <user> - Подключен к <server> как <user> - - - - No account configured. - Учётная запись не настроена. - - - - Remove - Удалить - - - - Account - Уч.запись - - - - Choose what to sync - Выбрать объекты для синхронизации - - - - Force sync now - Принудительно синхронизовать сейчас - - - - Restart sync - Перезапустить синхронизацию - - - - Remove folder sync connection - Удалить подключение синхронизации каталога - - - - Folder creation failed - Ошибка создания каталога - - - - <p>Could not create local folder <i>%1</i>. - <p>Не удалось создать локальный каталог <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Подтвердите удаление подключения синхронизации каталога - - - - Remove Folder Sync Connection - Удалить подключение синхронизации каталога - - - - Disable virtual file support? - Отключить поддержку виртуальных файлов? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - Это действие отключит поддержку виртуальных файлов. Из-за этого содержимое каталогов, которые сейчас помечены, как 'доступные только при сети', придётся скачать. - -Единственное преимущество от отключения поддержки виртуальных файлов состоит в том, что станет снова доступной возможность выборочной синхронизации. - -Это действие прервёт любую работающую сейчас синхронизацию. - - - - Disable support - Отключить поддержку - - - - Sync Running - Синхронизация запущена - - - - The syncing operation is running.<br/>Do you want to terminate it? - Выполняется синхронизация.<br/>Вы хотите её остановить? - - - - %1 in use - %1 используется - - - - %1 as <i>%2</i> - %1 как <i>%2</i> - - - - Connected to %1. - Соединен с %1. - - - - Server %1 is temporarily unavailable. - Сервер %1 временно недоступен. - - - - Server %1 is currently in maintenance mode. - Сервер %1 в настоящее время находится в режиме технического обслуживания. - - - - Signed out from %1. - Успешно вышли из %1. - - - - Connecting to %1... - Соединение с %1... - - - - No connection to %1 at %2. - Нет соединения с %1 в %2. - - - - Log in - Войти - - - - There are folders that were not synchronized because they are too big: - Есть каталоги, которые не были синхронизированы, так как они слишком большие: - - - - There are folders that were not synchronized because they are external storages: - Есть каталоги, которые не были синхронизированы, так как они являются внешними хранилищами: - - - - There are folders that were not synchronized because they are too big or external storages: - Есть каталоги, которые не были синхронизированы, так как они слишком велики или являются внешними хранилищами: - - - - Confirm Account Removal - Подтверждение удаления учетной записи - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Вы действительно желаете удалить подключение к учетной записи <i>%1</i>?</p><p><b>Примечание:</b> Это действие <b>НЕ</b> удалит ваши файлы.</p> - - - - Remove connection - Удалить подключение - - - - Open folder - Открыть папку - - - - - Log out - Выйти - - - - Open local folder - Открыть локальный каталог - - - - Open folder in browser - Открыть каталог в браузере - - - - Resume sync - Возобновить синхронизацию - - - - Pause sync - Приостановить синхронизацию - - - - Availability - Доступность - - - - Disable virtual file support... - Отключить поддержку виртуальных файлов… - - - - Enable virtual file support%1... - Включить поддержку виртуальных файлов%1… - - - - (experimental) - (экспериментально) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Вы действительно желаете остановить синхронизацию папки <i>%1</i>?</p><p><b>Примечание:</b> Это действие <b>НЕ</b> удалит ваши файлы.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) из %2 используется. Некоторые папки, включая сетевые или общие, могут иметь свои собственные ограничения. - - - - %1 of %2 in use - %1 из %2 используется - - - - Currently there is no storage usage information available. - В данный момент информация о заполненности хранилища недоступна. - - - - The server version %1 is unsupported! Proceed at your own risk. - Версия сервера %1 не поддерживается! Продолжайте на свой страх и риск. - - - - Obtaining authorization from the browser. - Получение авторизации от браузера. - - - - Server configuration error: %1 at %2. - Ошибка конфигурации сервера: %1 на %2. - - - - No %1 connection configured. - Нет настроенного подключения %1. - - - - OCC::AccountState - - - Url update requested for %1 - Для %1 запрошена смена адреса - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - Адрес для %1 изменён с %2 на %3, вы хотите принять изменённый адрес? - - - - Change url permanently to %1 - Изменить адрес окончательно на %1 - - - - Reject - Отклонить - - - - Signed out - Вышли из аккаунта - - - - Disconnected - Отключено - - - - Connected - Подключено - - - - Service unavailable - Сервис недоступен - - - - Maintenance mode - Режим технического обслуживания. - - - - Network error - Ошибка сети - - - - Configuration error - Ошибка конфигурации - - - - Asking Credentials - Запросить учётных данных - - - - Unknown account state - Неизвестное состояние учетной записи - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 на %3 - - - - Activity - События - - - - Account - Уч.запись - - - - Time - Время - - - - Local path - Локальный путь - - - - OCC::ActivitySettings - - - - Server Activity - Действия Сервера - - - - Sync Protocol - Протокол синхронизации - - - - Not Synced - Не синхронизировано - - - - Not Synced (%1) - %1 is the number of not synced files. - Не синхронизировано (%1) - - - - OCC::ActivityWidget - - - Form - Форма - - - - - - TextLabel - TextLabel - - - - Filter - Фильтр - - - - Server Activities - Действия Сервера - - - - Notifications - Уведомления - - - - %n notifications(s) for %1. - %n уведомление для %1.%n уведомления для %1.%n уведомлений для %1.%n уведомлений для %1. - - - - %n notifications(s) for %1 and %2. - %n уведомление для %1 и %2.%n уведомления для %1 и %2.%n уведомлений для %1 и %2.%n уведомлений для %1 и %2. - - - - New notifications for %1, %2 and other accounts. - Новые уведомления для %1, %2 и других учётных записей. - - - - Open the activity view for details. - Чтобы посмотреть подробности, откройте просмотр событий. - - - - Copy to clipboard - Скопировать в буфер обмена - - - - Show in file browser - Показать в файловом браузере - - - - <br/>Account %1 does not have activities enabled. - <br/>У учетной записи %1 не включены события. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Продолжение будет означать <b>удаление этих настроек</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Продолжение будет означать <b>игнорирование этих настроек</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Некоторые настройки были сделаны в клиенте более новой версии, чем данный, и используют возможности, которые не доступны в этой версии.<br><br>%1<br><br>Резервная копия текущей конфигурации уже сохранена в <i>%2</i>. - - - - Quit - Выход - - - - Continue - Продолжить - - - - Error accessing the configuration file - Ошибка при доступе к файлу конфигурации - - - - There was an error while accessing the configuration file at %1. - При обращении к файлу конфигурации %1 произошла ошибка. - - - - Quit %1 - Закрыть %1 - - - - %1 version %2 -File synchronization desktop utility. - %1 версии %2<byte value="xd"/> -Средство синхронизации файлов для настольных систем. - - - - For more information, see http://www.owncloud.org - Для дальнейшей информации посетите http://www.owncloud.org - - - - Show the settings dialog while starting. - Показать диалог настроек во время запуска. - - - - Quit the running instance. - Завершить запущенный экземпляр. - - - - Write log to file (use - to write to stdout). - Писать журнал в файл (используйте «-» для записи на стандартный выход). - - - - Write each sync log output in a new file in folder. - Писать вывод журнала каждой синхронизации в новый файл в каталоге. - - - - Remove logs older than <hours> hours (to be used with --logdir). - Удалять журналы старше <hours> часов (для использования с --logdir). - - - - Flush the log file after every write. - Сбрасывать журнал на диск после каждой записи. - - - - Output debug-level messages in the log. - Выводить в журнал сообщения отладочного уровня. - - - - - Override UI language. - Заменить язык интерфейса. - - - - Use the given configuration folder. - Использовать заданный каталог конфигурации. - - - - Enable debug mode. - Разрешить отладочный режим. - - - - Virtual file system file to be opened (optional). - Виртуальная файловая система для открытия (не обязательно). - - - - [<vfs file>] - [<vfs file>] - - - - Invalid path passed to --confdir - В --confdir передан недопустимый путь - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - Ошибка: неизвестный язык «%1» (чтобы получить полный список поддерживаемых языков, используйте --list-languages) - - - - Available translations: %1 - Доступные переводы: %1 - - - - OCC::AuthenticationDialog - - - Authentication Required - Требуется аутентификация - - - - Enter username and password for '%1' at %2. - Введите имя пользователя и пароль для '%1' в %2. - - - - &User: - &Пользователь: - - - - &Password: - &Пароль: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Учётная запись OwnCloud не настроена - - - - The configured server for this client is too old - Настроенный сервер слишком стар для этого клиента - - - - Please update to the latest server and restart the client. - Обновите сервер до последней версии и перезапустите клиент. - - - - Authentication error: Either username or password are wrong. - Ошибка авторизации: Имя пользователя или пароль не верны. - - - - timeout - тайм-аут - - - - The provided credentials are not correct - Введённые учётные данные не верны - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - Не удалось разобрать учётные данные %1 - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - Ошибка сервера: Ответ на PROPFIND не в формате XML! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - Ошибка при открытии каталога %1 - - - - Directory not accessible on client, permission denied - Каталог не доступен на клиенте, доступ запрещён - - - - Directory not found: %1 - Каталог не найден: %1 - - - - Filename encoding is not valid - Кодировка имени файла не верна - - - - Error while reading directory %1 - Ошибка при чтении каталога %1 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - Сбросить размеры колонок - - - - OCC::Folder - - - Local folder %1 does not exist. - Локальный каталог %1 не существует. - - - - %1 should be a folder but is not. - %1 должен быть папкой, но ей не является. - - - - %1 is not readable. - %1 не может быть прочитан. - - - - %1 has been removed. - %1 names a file. - '%1' был удалён. - - - - %1 has been updated. - %1 names a file. - %1 был обновлён. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 был переименован в %2. - - - - %1 has been moved to %2. - %1 был перемещён в %2. - - - - %1 and %n other file(s) have been removed. - %1 и ещё %n другой файл был удалён.%1 и ещё %n других файла было удалено.%1 и ещё %n других файлов были удалены.%1 и ещё %n других файлов были удалены. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - Добавлено %1 и ещё %n другой файл.Добавлено %1 и ещё %n других файла.Добавлено %1 и ещё %n других файлов.Добавлено %1 и ещё %n других файлов. - - - - %1 has been added. - %1 names a file. - Добавлено %1. - - - - %1 and %n other file(s) have been updated. - %1 и ещё %n другой файл были обновлены.%1 и ещё %n других файла были обновлены.%1 и ещё %n других файлов были обновлены.%1 и ещё %n других файлов были обновлены. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 был переименован в %2, и ещё %n другой файл был переименован.%1 был переименован в %2, и ещё %n других файла были переименованы.%1 был переименован в %2, и ещё %n других файлов были переименованы.%1 был переименован в %2, и ещё %n других файлов были переименованы. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 был перемещён в %2, и ещё %n другой файл был перемещён.%1 был перемещён в %2, и ещё %n других файла были перемещены.%1 был перемещён в %2, и ещё %n других файла были перемещены.%1 был перемещён в %2, и ещё %n других файла были перемещены. - - - - %1 has and %n other file(s) have sync conflicts. - У %1 и ещё у %n другого файла есть конфликты синхронизации.У %1 и ещё у %n других файлов есть конфликты синхронизации.У %1 и ещё у %n других файлов есть конфликты синхронизации.У %1 и ещё у %n других файлов есть конфликты синхронизации. - - - - %1 has a sync conflict. Please check the conflict file! - У %1 есть конфликт синхронизации. Пожалуйста, проверьте конфликтный файл! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 и ещё %n другой файл не удалось синхронизировать из-за ошибок. Подробности смотрите в журнале.%1 и ещё %n других файла не удалось синхронизировать из-за ошибок. Подробности смотрите в журнале.%1 и ещё %n других файлов не удалось синхронизировать из-за ошибок. Подробности смотрите в журнале.%1 и ещё %n других файлов не удалось синхронизировать из-за ошибок. Подробности смотрите в журнале. - - - - %1 could not be synced due to an error. See the log for details. - %1 не может быть синхронизирован из-за ошибки. Подробности смотрите в журнале. - - - - Sync Activity - Журнал синхронизации - - - - Could not read system exclude file - Невозможно прочесть системный файл исключений - - - - A new folder larger than %1 MB has been added: %2. - - Был добавлен новый каталог размером более %1 МБ: %2. - - - - - A folder from an external storage has been added. - - Добавлен каталог из внешнего хранилища. - - - - - Please go in the settings to select it if you wish to download it. - Пожалуйста, перейдите в настройки, чтобы выбрать его, если вы хотите его скачать. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - Каталог %1 был создан, но ранее исключён из синхронизации. Данные внутри него не буду синхронизироваться. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - Файл %1 был создан, но был ранее исключён из синхронизации. Он не будет синхронизироваться. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - Невозможно надёжно отслеживать изменения в синхронизируемых каталогах. - -Это означает, что клиент синхронизации возможно не сможет сразу закачать локальные изменения, и вместо этого будет периодически проверять локальные изменения и закачивать их по мере возможности (по умолчанию каждые два часа). - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Все фйлы в синхронизируемом каталоге '%1' были удалены на сервере. -Эти удаления будут переданы в ваш локальный синхронизируемый каталог, так что файлы станут недоступны, если только у вас нет права на восстановление. -Если вы решите сохранить эти файлы, то они будут повторно синхронизированы с сервером, при наличии у вас прав на это. -Если вы решили удалить файлы, они станут вам недоступны, крмое случая, когда вы сам владелец. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Все файлы в локальном синхронизируемом каталоге '%1' были удалены. Эти удаления будут отправлены на ваш сервер, сделав таким образом файлы совсем недоступными, если только не восстанавливать их из резервной копии. -Вы уверены, что хотите засинхронизировать сервер со всеми этими изменениями? -Если это произошло случайно и вы решите сохранить файлы, они будут перезакачаны с сервера. - - - - Remove All Files? - Удалить все файлы? - - - - Remove all files - Удалить все файлы - - - - Keep files - Сохранить файлы - - - - OCC::FolderMan - - - Could not reset folder state - Невозможно сбросить состояние каталога - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Найден старый журнал синхронизации '%1', и он не может быть удалён. Убедитесь что он не открыт в другом приложении. - - - - (backup) - (резервная копия) - - - - (backup %1) - (резервная копия %1) - - - - Undefined State. - Неопределенное состояние. - - - - Waiting to start syncing. - Ожидание запуска синхронизации. - - - - Preparing for sync. - Подготовка к синхронизации. - - - - Sync is running. - Идет синхронизация. - - - - Sync was successful, unresolved conflicts. - Синхронизация успешна, есть неразрешённые конфликты. - - - - Last Sync was successful. - Последняя синхронизация прошла успешно. - - - - Setup Error. - Ошибка установки. - - - - User Abort. - Отмена пользователем. - - - - Sync is paused. - Синхронизация приостановлена. - - - - %1 (Sync is paused) - %! (синхронизация приостановлена) - - - - No valid folder selected! - Не выбран валидный каталог! - - - - The folder %1 is used in a folder sync connection! - Каталог %1 используется в соединении синхронизации каталогов! - - - - The selected path does not exist! - Выбранный путь не существует! - - - - The selected path is not a folder! - Выбранный путь не является каталогом! - - - - You have no permission to write to the selected folder! - У вас недостаточно прав для записи в выбранный каталог! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Уже есть синхронизация с сервера в этот локальный каталог. Пожалуйста, выберите другой локальный каталог! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Локальная директория %1 уже содержит папку, которая используется для синхронизации. Пожалуйста выберите другую! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Локальная директория %1 уже содержит директорию, которая используется для синхронизации. Пожалуйста выберите другую! - - - - %1 Please pick another one! - %1 Пожалуйста, сделайте другой выбор! - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Добавить папку для синхронизации - - - - File - Файл - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Необходимо подключиться, чтобы добавить каталог - - - - Click this button to add a folder to synchronize. - Нажмите на эту кнопку для добавления каталога к синхронизации. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Ошибка загрузки списка папок с сервера. - - - - Virtual file support is enabled. - Поддержка виртуальных файлов включена. - - - - Signed out - Вышли из аккаунта - - - - Fetching folder list from server... - Извлечение списка папок с сервера... - - - - There are unresolved conflicts. Click for details. - Есть неразрешенные конфликты. Нажми для просмотра подробностей. - - - - Checking for changes in remote '%1' - Проверяю измененеия в удалённом '%1' - - - - Checking for changes in local '%1' - Проверяю изменения в локальном '%1' - - - - Reconciling changes - Согласование изменений - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Синхронизация %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - скачивание %1/с - - - - ↓ %1/s - ↓ %1/с - - - - ↑ %1/s - ↑ %1/с - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - загрузка %1/с - - - - Local folder: %1 - Локальный каталог: %1 - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 из %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - Осталось %5, %1 из %2, файл %3 из %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 из %2, файл %3 из %4 - - - - file %1 of %2 - файл %1 из %2 - - - - Waiting... - Ожидание... - - - - Waiting for %n other folder(s)... - Ожидание %n директории...Ожидание %n директорий...Ожидание %n директорий...Ожидание %n директорий... - - - - Preparing to sync... - Подготовка к синхронизации... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - Наблюдатель не получил тестовое уведомление. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Эта проблема обычно связана с механизмом оповещений inotify. Подробнее - см. FAQ. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Добавить папку для синхронизации - - - - Add Sync Connection - Добавить подключение для синхронизации - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Кликните, чтобы выбрать локальный каталог для синхронизации. - - - - Enter the path to the local folder. - Укажите путь к локальному каталогу. - - - - Select the source folder - Выберите исходный каталог - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Создать Удалённый Каталог - - - - Enter the name of the new folder to be created below '%1': - Введите имя каталога, который должен быть создан под '%1': - - - - Folder was successfully created on %1. - Каталог успешно создан на %1. - - - - Authentication failed accessing %1 - Ошибка авторизации при доступе к %1 - - - - Failed to create the folder on %1. Please check manually. - Невозможно создать каталог по пути %1. Попробуйте создать его вручную. - - - - Failed to list a folder. Error: %1 - Ошибка просмотра папки. Ошибка: %1 - - - - Choose this to sync the entire account - Нажмите здесь для синхронизации всей учётной записи - - - - This folder is already being synced. - Этот каталог уже выбран для синхронизации. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Каталог <i>%1</i> уже настроен для синхронизации, и он является родительским для каталога <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Вы уже синхронизируете <i>%1</i>, а это подпапка для <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Использовать виртуальные файлы вместо немедленного скачивания содержимого%1 - - - - (experimental) - (экспериментально) - - - - Virtual files are not available for the selected folder - Виртуальные файлы не доступны для выбранного каталога - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Предупреждение:</b> %1 - - - - <b>Warning:</b> - <b>Предупреждение:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - E-Tag от сервера не получен, проверьте настройки прокси/шлюза. - - - - We received a different E-Tag for resuming. Retrying next time. - Мы получили другой E-Tag для возобновления. Повторите попытку позже. - - - - We received an unexpected download Content-Length. - Было получено неожиданное значение Content-Length. - - - - Server returned wrong content-range - Сервер вернул неверный диапазон содержимого - - - - OCC::GETJob - - - Connection Timeout - Время ожидания подключения истекло - - - - OCC::GeneralSettings - - - Form - Форма - - - - General Settings - Основные настройки - - - - Language - Язык - - - - Sync hidden files - Синхронизировать скрытые файлы - - - - Log Settings - Настройки журнала - - - - Advanced - Approval of folder sync (Non virtual file mode only) - Дополнительно — подтверждение синхронизации каталогов (только для невиртуальных файлов) - - - - Network - Сеть - - - - Updates - Обновления - - - - stable - стабильный - - - - beta - бета - - - - About - О программе - - - - Advanced - Дополнительно - - - - Use &Monochrome Icons in the system tray - Использовать &Монохромные значки в системном лотке - - - - Ask for confirmation before synchronizing folders larger than - Спрашивать подтверждение перед синхронизацией каталогов размером больше чем - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - МБ - - - - Ask for confirmation before synchronizing external storages - Спрашивать подтверждение перед синхронизацией внешних хранилищ - - - - &Launch on System Startup - &Запустить при старте системы - - - - Show &Desktop Notifications - Показать уведомления на рабочем столе - - - - &Update Channel - Канал обновлений: - - - - Edit &Ignored Files - Редактировать &Ignored файлы - - - - Show sync folders in &Explorer's Navigation Pane - Показать папки синхронизации в навигационной панели Проводника - - - - S&how crash reporter - Показать отчёты об ошибках - - - - &Restart && Update - &Перезапуск и обновление - - - - You cannot disable autostart because system-wide autostart is enabled. - Нельзя отключить автозапуск, так как включён общесистемный автозапуск. - - - - Change update channel? - Изменить канал обновлений? - - - - Warning - Предупреждение - - - - Language changes require a restart of this application to take effect. - Чтобы изменение языка применилось, данное приложение нужно перезапустить. - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Канал обновлений задаёт, какие обновления клиента будут предлагаться для установки. «Стабильный» канал содержит только обновления, считающиеся надёжными, в то время как версии в канале «бета» могут содержать более новые возможности и исправления ошибок, которые ещё не были тщательно протестированы. - -Заметьте, что это задаёт лишь место, откуда берутся обновления, нет возможности их отката назад. Таким образом, переключение канала с беты на стабильный не будет выполнено немедленно, оно подразумевает ожидание, когда выйдет стабильная версия, которая будет более свежей, чем установленная в данный момент бета-версия. - - - - Change update channel - Изменить канал обновлений - - - - Cancel - Отмена - - - - unknown (%1) - неизвестно (%1) - - - - (use default) - (использовать умолчание) - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Пожалуйста введите пароль для %1:<br><br>Пользователь: %2<br>Учётная запись: %3<br> - - - - Reading from keychain failed with error: '%1' - Чтение из брелока завершилось с ошибкой: '%1' - - - - Enter Password - Введите пароль - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Кликните здесь</a> чтобы запросить пароль приложения через веб-интерфейс. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Редактор игнорированных файлов - - - - Files Ignored by Patterns - Файлы, игнорируемые по шаблонам - - - - Add - Добавить - - - - Pattern - Шаблон - - - - Allow Deletion - Разрешить удаление - - - - Remove - Удалить - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Соответствующие шаблону папки и файлы синхронизироваться не будут. Изменения вступят в силу в следующий раз, когда папки будут синхронизированы. - -Там, где удаление разрешено, элементы будут удаляться, если они не дают удалить каталог. Это полезно для метаданных. - - - - Could not open file - Невозможно открыть файл - - - - Cannot write changes to '%1'. - Невозможно записать изменения в '%1'. - - - - Add Ignore Pattern - Добавить шаблон игнорирования - - - - Add a new ignore pattern: - Добавить новый шаблон игнорирования: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Эта запись сделана системой в '%1' и не может быть изменена в этом представлении. - - - - OCC::IssuesWidget - - - Form - Форма - - - - List of issues - Список проблем - - - - Filter - Фильтр - - - - There were too many issues. Not all will be visible here. - Было слишком много проблем. Не все будут видны здесь. - - - - There were conflicts. Check the documentation on how to resolve them. - Имеются конфликты. См. документацию по их разрешению. - - - - The file %1 was ignored as its name is reserved by %2 - Файл %1 был проигнорирован, так как его имя зарезервировано %2 - - - - Reset column sizes - Сбросить размеры колонок - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Имеются конфликты. <a href="%1">См. документацию по их разрешению.</a> - - - - Account - Уч.запись - - - - OCC::LibCloudProvidersPrivate - - - Settings - Настройки - - - - OCC::LogBrowser - - - Log Output - Вывод журнала - - - - Enable logging to temporary folder - Включить протоколирование во временный каталог - - - - Delete logs older than %1 hours - Удалить логи старше %1 часов - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">Журналы содержат чувствительную информацию и не должны быть доступны посторонним</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - Клиент может записывать отладочные журналы во временный каталог. Эти журналы очень полезны для разбирательства с проблемами. -Поскольку журнальные файлы могут стать очень большими, клиент будет начинать новый журнал при каждом запуске, и сжимать предыдущие. Также рекомендуется включить удаление журнальных файлов через пару часов, чтобы не тратить слишком много места. - - - - If enabled, logs will be written to: - Если включено, то журналы будут записываться в: - - - - C:/log - C:/log - - - - Log Http traffic - Записывать трафик HTTP - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Эти настройки остаются после перезапуска клиента. -Учтите, что параметры командной строки, задающие протоколирование, перекрывают эти значения. - - - - Open folder - Открыть папку - - - - OCC::Models - - - %1 Filter: - - - - - No filter - Без фильтра - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Доступна новая версия - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Доступна новая версия приложения %1.</p><p><b>%2</b> доступна для загрузки. Установленная версия: %3.</p> - - - - - Skip this version - Пропустить эту версию - - - - Skip this time - Пропустить в этот раз - - - - Get update - Получить обновление - - - - Update Failed - Обновление не удалось - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Доступна новая версия клиента %1, но процесс обновления не удался. </p><p>Было скачано <b>%2</b>. Установленная версия — %3.</p> - - - - Ask again later - Попросить снова позже - - - - Restart and update - Перезапустить и продолжить - - - - Update manually - Обновить вручную - - - - OCC::NetworkSettings - - - Form - Форма - - - - Proxy Settings - Настройки прокси - - - - No Proxy - Без прокси - - - - Use system proxy - Использовать системный прокси - - - - Specify proxy manually as - Задать настройки прокси вручную как - - - - Host - Сервер - - - - : - : - - - - Proxy server requires authentication - Прокси-сервер требует авторизации - - - - Note: proxy settings have no effects for accounts on localhost - Замечание: настройки прокси не действуют для учётных записей на localhost - - - - Download Bandwidth - Скорость загрузки с сервера - - - - - Limit to - Ограничить до - - - - - KBytes/s - Кбайт/сек - - - - - No limit - Без ограничений - - - - - Limit to 3/4 of estimated bandwidth - Ограничить до 3/4 предполагаемой ширины канала - - - - Upload Bandwidth - Скорость закачки на сервер - - - - - Limit automatically - Ограничивать автоматически - - - - Hostname of proxy server - Адрес прокси сервера - - - - Username for proxy server - Пользователь прокси-сервера - - - - Password for proxy server - Пароль прокси-сервера - - - - HTTP(S) proxy - HTTP(S)-прокси - - - - SOCKS5 proxy - SOCKS5-прокси - - - - OCC::NotificationWidget - - - Created at %1 - Создано %1 - - - - Closing in a few seconds... - Закроется через несколько секунд… - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1: запрос выполнен неудачно в %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' выбрано в %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Сервер вернул ошибку: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - При доступе к конечной точке 'токена' произошла ошибка: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Не удалось разобрать JSON, пришедший с сервера: <br><em>%1</em> - - - - Unsupported token type: %1 - Неподдерживаемый тип токена: %1 - - - - The reply from the server did not contain all expected fields -:%1 - Ответ от сервера не содержал в себе все ожидаемые поля -:%1 - - - - Unknown Error - Неизвестная ошибка - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>Ошибка входа</h1><p>Не удалось извлечь информацию о пользователе</p> - - - - Oauth2 Error - Ошибка Oauth2 - - - - Oauth2 authentication requires a secured connection. - Для аутентификации по Oauth2 требуется безопасное соединение. - - - - <h1>Login Error</h1><p>%1</p> - <h1>Ошибка входа</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Ошибка при обновлении токена: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - Ответ от сервера не содержал в себе все ожидаемые поля -:%1 -Полученные данные: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Неверный пользователь</h1><p>Вы вошли, как пользователь <em>%1</em>, но нужно войти под пользователем <em>%2</em>.<br>Пожалуйста, выйдите из %3 в другой вкладке, а затем <a href='%4'>кликните здесь</a> и войдите, как пользователь %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Доступно обновление %1 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Новое обновление %1 в процессе установки. Установщик может запросить -дополнительные разрешения во время обновления. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Не удалось загрузить обновление. Для скачивания файлов вручную, нажмите <a href='%1'>здесь</a> - - - - Could not check for new updates. - Не удалось проверить наличие новых обновлений. - - - - Downloading %1. Please wait... - Скачивается %1. Пожалуйста, подождите… - - - - %1 available. Restart application to start the update. - Доступно %1. Перезапустите приложение, чтобы начать обновление. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Доступна новая версия %1. Для установки пожалуйста используйте системные средства обновления. - - - - Checking update server... - Проверка наличия обновлений на сервере... - - - - Update status is unknown: Did not check for new updates. - Статус обновления неизвестен: Не удалось проверить наличие обновлений. - - - - No updates available. Your installation is at the latest version. - Обновлений в настоящий момент нет. У вас установлена самая последняя версия программы. - - - - Update Check - Проверка Обновлений - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Подключиться к %1 - - - - Setup local folder options - Изменить настройки локальных каталогов - - - - Connect... - Соединение... - - - - (experimental) - (экспериментально) - - - - Use &virtual files instead of downloading content immediately%1 - Использовать &виртуальные файлы вместо скачивания содержимого немедленно%1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - <p><strong>Предупреждение:</strong> Локальный каталог не пуст. Выберите решение!</p> - - - - Sync the folder '%1' - Синхронизация папки '%1' - - - - Virtual files are not available for the selected folder - Виртуальные файлы не доступны для выбранного каталога - - - - Error - Ошибка - - - - Local Sync Folder - Локальный каталог синхронизации - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Почта - - - - Connect to %1 - Подключиться к %1 - - - - Enter user credentials - Ввести учётные данные - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Подключиться к %1 - - - - Login in your browser - Вход в ваш браузер - - - - Copy link to clipboard - Копировать ссылку в буфер обмена - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - Файл %1 сейчас используется - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Подключиться к %1 - - - - Setup %1 server - Настроить сервер %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Эта ссылка НЕ безопасна, так как не зашифрована. -Мы не рекомендуем её использовать. - - - - This url is secure. You can use it. - Данная ссылка безопасна. Вы можете её использовать. - - - - &Next > - &Далее > - - - - Invalid URL - Неверная ссылка - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Не удалось подключиться к %1 в %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Превышено время ожидания соединения к %1 на %2. - - - - Invalid URL - Неверная ссылка - - - - Could not create local folder %1 - Не удалось создать локальный каталог синхронизации %1 - - - - Error: %1 - Ошибка: %1 - - - - The folder creation resulted in HTTP error code %1 - Создание каталога завершилось с HTTP-ошибкой %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Не удалось создать удаленный каталог, так как представленные параметры доступа неверны!<br/>Пожалуйста, вернитесь назад и проверьте учетные данные.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Удаленный каталог %1 не создан из-за ошибки <tt>%2</tt>. - - - - Folder rename failed - Ошибка переименования каталога - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Невозможно удалить каталог и создать его резервную копию, каталог или файл в ней открыт в другой программе. Закройте каталог или файл и нажмите "Повторить попытку", либо прервите мастер настройки. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Мастер подключения %1 - - - - Enable experimental feature? - Включить экспериментальную возможность? - - - - Enable experimental placeholder mode - Включить экспериментальный режим заглушек - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Когда включён режим «виртуальных файлов», никакие файлы не скачиваются сразу. Вместо этого для каждого файла, существующего на сервере, создаётся маленький файл "%1". Содержимое можно скачать, запустив эти файлы через их контекстное меню. - -Режим виртуальных файлов не совместим с выборочной синхронизацией. Не выбранные в данный момент каталоги будут преобразованы в каталоги, доступные только при сети, и ваши настройки синхронизации будут сброшены. - -Включение этого режима прервёт любую идущую сейчас синхронизацию. - -Это новый, экспериментальный режим. Если вы решите его использовать, пожалуйста сообщайте о любых проявляющихся проблемах. - - - - Stay safe - Отказаться от эксперимента - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Синхронизация символических ссылок не поддерживается. - - - - File is listed on the ignore list. - Файл присутствует в списке игнорируемых. - - - - File names ending with a period are not supported on this file system. - Эта файловая система не поддерживает имена файлов, оканчивающиеся на точку. - - - - File names containing the character '%1' are not supported on this file system. - Эта файловая система не поддерживает имена файлов, содержащие символ '%1'. - - - - File name contains at least one invalid character - Имя файла содержит по крайней мере один некорректный символ - - - - The file name is a reserved name on this file system. - Данное имя файла зарезервировано в данной файловой системе. - - - - Filename contains trailing spaces. - Имя файла содержит пробелы на конце. - - - - Filename is too long. - Имя файла слишком длинное. - - - - File/Folder is ignored because it's hidden. - Файл/папка проигнорированы, так как являются скрытыми. - - - - Stat failed. - Не удалось загрузить статистику. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Конфликт: Серверная версия скачана, локальная копия переименована и не закачана. - - - - The filename cannot be encoded on your file system. - Невозможно перекодировать имя файла для вашей системы. - - - - The filename is blacklisted on the server. - Имя файла находится в чёрном списке сервера. - - - - size - размеру - - - - permissions - разрешения - - - - etag - etag - - - - file id - код файла - - - - server reported no %1 - Сервер вернул отсутствие %1 - - - - Moved to invalid target, restoring - Перемещение в недопустимое место, восстанавливаем - - - - Not allowed to remove, restoring - Не допускается удаление, восстанавливаем - - - - Ignored because of the "choose what to sync" blacklist - Игнорируется из-за черного списка в "что синхронизировать" - - - - Not allowed because you don't have permission to add subfolders to that folder - Не разрешается, так как у вас нет полномочий на добавление подпапок в папку. - - - - Not allowed because you don't have permission to add files in that folder - Не разрешается, так как у вас нет полномочий на добавление файлов в эту папку - - - - Not allowed to upload this file because it is read-only on the server, restoring - Не допускается загрузка этого файла, так как на сервере он помечен только для чтения, восстанавливаем - - - - Error while reading the database - Ошибка при чтении базы данных - - - - Server replied with an error while reading directory '%1' : %2 - Сервер ответил ошибкой при чтении каталога '%1' : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - Ошибка обновления метаданных: %1 - - - - %1 the folder is currently in use - %1 каталог сейчас занят - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - Не удалось освободить место, файл %1 сейчас занят - - - - - File %1 can not be downloaded because of a local file name clash with %2! - Нельзя скачать файл %1 из-за пересечения локального имени с %2! - - - - The download would reduce free local disk space below the limit - Скачивание сократит доступное пространство на локальном диске ниже допустимого предела - - - - Free space on disk is less than %1 - Свободное место на диске меньше, чем %1 - - - - File was deleted from server - Файл был удален с сервера - - - - The file could not be downloaded completely. - Невозможно полностью загрузить файл. - - - - The downloaded file is empty despite the server announced it should have been %1. - Скачанный файл пуст, хотя сервер заявил, что он должен быть %1. - - - - File %1 cannot be saved because of a local file name clash with %2! - Нельзя сохранить файл %1 из-за пересечения локального имени файла с %2! - - - - Error updating metadata: %1 - Ошибка обновления метаданных: %1 - - - - - - - The file %1 is currently in use - Файл %1 сейчас занят - - - - - File has changed since discovery - После обнаружения файл был изменен - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Восстановление не удалось: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - не удалось удалить файл %1, ошибка: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - Не могу создать локальный каталог %1 из-за совпадения локального имени файла с %2 - - - - Error updating metadata: %1 - Ошибка обновления метаданных: %1 - - - - The file %1 is currently in use - Файл %1 сейчас занят - - - - could not create folder %1 - не удается создать папку %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - Не удалось удалить %1 из-за пересечения локального имени файла с %2! - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Файл %1 не может быть переименован в %2 из-за локального конфликта имен - - - - Could not rename %1 to %2, the file is currently in use - Не удалось переименовать %1 в %2, файл сейчас используется - - - - Error updating metadata: %1 - Ошибка обновления метаданных: %1 - - - - The file %1 is currently in use - Файл %1 сейчас занят - - - - Failed to rename file - Не удалось переименовать файл - - - - Error setting pin state - Ошибка при установке состояния прикреплённости - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Сервер ответил не правильным HTTP кодом. Ожидался 204, но получен "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Сервер ответил не правильным HTTP кодом. Ожидался 201, но получен "%1 %2". - - - - Error writing metadata to the database: %1 - Не удалось записать метаданные в базу данных: %1 - - - - Setting file status failed due to file lock - Установка состояния файла не прошла из-за блокировки файла - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - Не удалось переименовать %1 в %2, ошибка: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Сервер ответил не правильным HTTP кодом. Ожидался 201, но получен "%1 %2". - - - - Error updating metadata: %1 - Ошибка обновления метаданных: %1 - - - - The file %1 is currently in use - Файл %1 сейчас занят - - - - Error writing metadata to the database - Ошибка записи метаданных в базу данных - - - - Error setting pin state - Ошибка при установке состояния прикреплённости - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - Не удалось обновить файл : %1 - - - - The file %1 is currently in use - Файл %1 сейчас занят - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Невозможно загрузить файл %1, так как уже есть другой файл с тем же именем, отличающимся только регистром символов - - - - File Removed - Файл Перемещён - - - - Local file changed during syncing. It will be resumed. - Локальный файл изменился в процессе синхронизации. Операция будет возобновлена. - - - - Local file changed during sync. - Локальный файл изменился в процессе синхронизации. - - - - Error updating metadata: %1 - Ошибка обновления метаданных: %1 - - - - The file %1 is currently in use - Файл %1 сейчас занят - - - - - Upload of %1 exceeds the quota for the folder - Закачка %1 превышает квоту для каталога - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - Файл %1 сейчас используется - - - - The local file was removed during sync. - Локальный файл был удалён в процессе синхронизации. - - - - Local file changed during sync. - Локальный файл изменился в процессе синхронизации. - - - - The server did ask for a removed legacy feature(polling) - Сервер запросил удалённую устаревшую возможность (опрос) - - - - Unexpected return code from server (%1) - Неожиданный код завершения от сервера (%1) - - - - Missing File ID from server - Отсутствует код файла от сервера - - - - Missing ETag from server - Отсутствует ETag с сервера - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - Файл %1 сейчас используется - - - - The local file was removed during sync. - Локальный файл был удалён в процессе синхронизации. - - - - Local file changed during sync. - Локальный файл изменился в процессе синхронизации. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - Файл %1 сейчас используется - - - - The server did ask for a removed legacy feature(polling) - Сервер запросил удалённую устаревшую возможность (опрос) - - - - The local file was removed during sync. - Локальный файл был удалён в процессе синхронизации. - - - - Local file changed during sync. - Локальный файл изменился в процессе синхронизации. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Сервер не смог подтвердить последнюю часть данных.(Отсутствовали теги e-tag) - - - - OCC::ProtocolItemModel - - - Time - Время - - - - File - Файл - - - - Folder - Папка - - - - Issues - Проблемы - - - - Action - Действие - - - - Size - Размер - - - - Account - Уч.запись - - - - Status - Статус - - - - OCC::ProtocolWidget - - - Form - Форма - - - - Local sync protocol - Локальный протокол синхронизации - - - - Filter - Фильтр - - - - Account - Уч.запись - - - - Copy to clipboard - Скопировать в буфер обмена - - - - Show in file browser - Показать в файловом браузере - - - - Show in web browser - Показать в веб-барузере - - - - Retry sync - Повторить попытку синхронизации - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Требуется авторизация Прокси - - - - Username: - Пользователь: - - - - Proxy: - Прокси: - - - - The proxy server needs a username and password. - Прокси сервер требует имя пользователя и пароль - - - - Password: - Пароль: - - - - TextLabel - TextLabel - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Уточнить объекты - - - - OCC::SelectiveSyncWidget - - - Loading ... - Загрузка ... - - - - Deselect remote folders you do not wish to synchronize. - Снимите выбор с удалённых папок, котрые вы не хотите синхронизировать. - - - - Name - Название - - - - Size - Размер - - - - - No subfolders currently on the server. - Нет подкаталогов на сервере на данный момент. - - - - An error occurred while loading the list of sub folders. - Произошла ошибка во время загрузки списка подпапок. - - - - OCC::ServerNotificationHandler - - - Dismiss - Убрать - - - - OCC::SettingsDialog - - - Settings - Настройки - - - - Activity - События - - - - Add account - Добавить учетную запись - - - - - Quit %1 - Закрыть %1 - - - - Are you sure you want to quit %1? - Вы уверены, что хотите выйти из? - - - - Account - Уч.запись - - - - MainWindow - ГлавноеОкно - - - - Hide - Скрыть - - - - OCC::ShareDialog - - - TextLabel - TextLabel - - - - share label - поделиться меткой - - - - Dialog - Диалог - - - - ownCloud Path: - Путь к OwnCloud: - - - - %1 Sharing - Вы поделились %1 - - - - %1 - %1 - - - - Folder: %2 - Папка: %2 - - - - The server does not allow sharing - Сервер не разрешает обмен - - - - Retrieving maximum possible sharing permissions from server... - Запрос максимально возможных прав для предоставления доступа с сервера… - - - - The file can not be shared because it was shared without sharing permission. - Невозможно предоставить общий доступ к файлу: нет разрешения на предоставление общего доступа. - - - - Users and Groups - Пользователи и группы - - - - Public Links - Общедоступные ссылки - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Поделиться NewDocument.odt - - - - Upload only (File Drop) - Только закачка (закинуть файл) - - - - Receive files from others without revealing the contents of the folder. - Получение файлов от других без раскрытия содержимого каталога. - - - - Download / View / Edit - Скачивание / Просмотр / Правка - - - - Name: - Имя: - - - - Enter name of new share... - Введите имя нового общего доступа… - - - - &Create - &Создать - - - - TextLabel - TextLabel - - - - Set &password - Установить &пароль - - - - Set &expiration date - Установить срок &доступа - - - - Set password - Установить пароль - - - - Recipients can view or download contents. - Получатели могут просматривать или скачивать содержимое. - - - - Download / View - Скачивание / Просмотр - - - - Recipients can view, download, edit, delete and upload contents. - Получатели могут просматривать, скачивать, править, удалять и закачивать содержимое. - - - - Anyone with the link has access to the file/folder - Каждый, у кого есть эта ссылка, имеет доступ к файлу/каталогу - - - - - P&assword protect - Защитить паролем - - - - The file can not be shared because it was shared without sharing permission. - Невозможно предоставить общий доступ к файлу: нет разрешения на предоставление общего доступа. - - - - Link shares have been disabled - Доступ по ссылкам был отключён - - - - Create public link share - Создать ссылку общего доступа - - - - - Delete - Удалить - - - - Open link in browser - Открыть ссылку в браузере - - - - Copy link to clipboard - Копировать ссылку в буфер обмена - - - - Copy link to clipboard (direct download) - Скопировать ссылку в буфер обмена (прямое скачивание) - - - - Send link by email - Отправить ссылку по email - - - - Send link by email (direct download) - Отправить ссылку по почте (прямое скачивание) - - - - Create new... - Создать новое… - - - - I shared %1 with you - Я даю вам доступ к %1 - - - - Confirm Link Share Deletion - Подтвердите удаление доступа по ссылке - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Вы действтиельно хотите удалить доступ по общей ссылке <i>%1</i>?</p><p>Внимание: это действие будет невозможно отменить.</p> - - - - Cancel - Отмена - - - - - Public link - Общедоступная ссылка - - - - Delete link share - Удалить доступ по ссылке - - - - - Please Set Password - Пожалуйста, установите пароль - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Поделиться NewDocument.odt - - - - Share with users or groups ... - Поделиться с пользователями или группами ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Можно вывести людей на этот файл или каталог с общим доступом, <a href="private link menu"><span style=" text-decoration: underline">передав им приватную ссылку</span></a>.</p></body></html> - - - - The item is not shared with any users or groups - К этому элементу не предоставлен доступ никаким пользователям или группам - - - - Open link in browser - Открыть ссылку в браузере - - - - Copy link to clipboard - Копировать ссылку в буфер обмена - - - - Send link by email - Отправить ссылку по email - - - - No results for '%1' - Нет результатов для '%1' - - - - I shared something with you - Я поделился с тобой - - - - OCC::ShareUserLine - - - Form - Форма - - - - - TextLabel - TextLabel - - - - can edit - можно редактировать - - - - can share - можно поделиться - - - - ... - ... - - - - create - создать - - - - change - изменить - - - - delete - удалить - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Поделиться с %1 - - - - Context menu share - Доступ через контекстное меню - - - - Context menu share %1 - Доступ через контекстное меню к %1 - - - - Confirm deletion - Подтвердите удаление - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Хотите окончательно удалить каталог <i>%1</i>, и всё его содержимое? - - - - Do you want to delete the file <i>%1</i> permanently? - Хотите окончательно удалить файл <i>%1</i>? - - - - Select new location... - Выберите новое местоположение… - - - - Error - Ошибка - - - - Moving file failed: - -%1 - Перемещение не удалось: - -%1 - - - - I shared something with you - Я поделился с тобой - - - - - Share... - Поделиться... - - - - - Copy private link to clipboard - Копировать приватную ссылку в буфер обмена... - - - - Send private link by email... - Отправить приватную ссылку по email... - - - - Resharing this file is not allowed - Повторное предоставление доступа к этому файлу не разрешено - - - - Resharing this folder is not allowed - Повторное предоставление доступа к этому каталогу не разрешено - - - - Create and copy public link to clipboard - Создать общедоступную ссылку и скопировать её в буфер обмена - - - - Copy public link to clipboard - Копировать общую ссылку в буфер обмена - - - - Open in browser - Открыть в браузере - - - - Show file versions in browser - Показать версии файла в браузере - - - - Rename... - Переименовать… - - - - Rename and upload... - Переименовать и закачать… - - - - Move and rename... - Переместить и переименовать… - - - - Move, rename and upload... - Переместить, переименовать, и закачать… - - - - Delete local changes - Удалить локальные изменения - - - - Move and upload... - Переместить и закачать… - - - - Delete - Удалить - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - Не удалось извлечь метки - - - - Backup of %1 started - Начато резервное копирование %1 - - - - Failed to find %1 - Не удалось найти %1 - - - - Local path must be a an absolute path - Локальный путь должен быть абсолютным путём - - - - Failed to create temporary database - Не удалось создать временную базу данных - - - - Backup of %1 succeeded - Резервное копирование %1 завершено успешно - - - - Failed to set success tag - Не удалось установить метку успешности - - - - Failed to create backup: %1 - Не удалось создать резервную копию: %1 - - - - Failed to file id tags - Не удалось сохранить метки - - - - Backup of %1 failed with: %2 - Не удалось создать резервную копию %1 с: %2 - - - - %1 backup - Резервная копия %1 - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Данные сертификата:</h3> - - - - Common Name (CN): - Общее имя (CN): - - - - Subject Alternative Names: - Альтернативное имя субъекта: - - - - Organization (O): - Организация (О): - - - - Organizational Unit (OU): - Организационное подразделение (OU): - - - - State/Province: - Область/район: - - - - Country: - Страна: - - - - Serial: - Номер: - - - - <h3>Issuer</h3> - <h3>Выдан:</h3> - - - - Issuer: - Издатель: - - - - Issued on: - Выдан: - - - - Expires on: - Истекает: - - - - <h3>Fingerprints</h3> - <h3>Отпечаток</ h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Заметка:</b> Этот сертификат был одобрен вручную</p> - - - - %1 (self-signed) - %1 (самоподписанный) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Это соединение зашифровано %1-битным %2. - - - - - Server version: %1 - Версия сервера: %1 - - - - No support for SSL session tickets/identifiers - Нет поддержки для тикетов/идентификаторов SSL сессии - - - - Certificate information: - Информация о TLS-сертификатах: - - - - The connection is not secure - Соединение не безопасно - - - - This connection is NOT secure as it is not encrypted. - - Это соединение НЕ безопасно, используется протокол без шифрования. - - - - - OCC::SslErrorDialog - - - Form - Форма - - - - Trust this certificate anyway - Доверять этому сертификату в любом случае - - - - Untrusted Certificate - Сертификат без доверия - - - - Cannot connect securely to <i>%1</i>: - Не удалось осуществить безопасное подключение к <i>%1</i>: - - - - with Certificate %1 - Сертификат %1 - - - - - - &lt;not specified&gt; - &lt;не указано&gt; - - - - - Organization: %1 - Организация: %1 - - - - - Unit: %1 - Подразделение: %1 - - - - - Country: %1 - Страна: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Отпечаток (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Отпечаток (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Дата вступления в силу: %1 - - - - Expiration Date: %1 - Дата окончания: %1 - - - - Issuer: %1 - Издатель: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (пропущено из-за предыдущей ошибки, повторная попытка через %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Только %1 доступно, нужно как минимум %2 чтобы начать - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Не могу открыть или создать локальную базу данных синхронизации. Удостоверьтесь, что у вас есть доступ на запись в каталог синхронизации. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Мало места на диске: Скачивания, которые сократят свободное место ниже %1, будут пропущены. - - - - There is insufficient space available on the server for some uploads. - На сервере недостаточно места для некоторых закачек. - - - - Unresolved conflict. - Неразрешённый конфликт. - - - - Using virtual files with suffix, but suffix is not set - Используются виртуальные файлы с суффиксом, но суффикс не задан - - - - Unable to read the blacklist from the local database - Не удалось прочитать файл чёрного списка из локальной базы данных. - - - - Unable to read from the sync journal. - Не удалось прочитать из журнала синхронизации. - - - - Cannot open the sync journal - Не удаётся открыть журнал синхронизации - - - - Aborted - Прервано - - - - OCC::SyncJournalDb - - - Failed to connect database. - Не удалось подключиться к базе данных. - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>Версия %1. Более подробная информация по адресу <a href="%2">https://%3</a></p><p>Для сведений об известных проблемах и помощи, пожалуйста посетите: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Авторы — Клаас Фрейтаг, Даниэль Молкентин, Оливье Гоффар, Маркус Гётц, Жан-Кристоф Бошар, Томас Мюллер, Доминик Шмидт, Михаэль Штингль, Ханна фон Рет, и другие.</small></p><p>Авторское право ownCloud GmbH</p><p>Распространяется %4 и лицензируется согласно Общей Публичной Лицензии GNU (GPL) версии 2.0.<br/>Логотипы %5 и %5 являются зарегистрированными торговыми марками %4 в США, других странах, или и там, и там.</p><p><small>%6</small></p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Некорректная контрольная сумма заголовка - - - - The checksum header contained an unknown checksum type '%1' - Заголовок контрольной суммы содержал неизвестный тип '%1' контрольной суммы - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - Скачанный файл не соответствует по контрольной сумме, скачивание будет возобновлено. '%1' != '%2' - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - Возможность Виртуальная файловая система не поддерживает диск в качестве корня синхронизации - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - Поддержка виртуальной файловой системы требует файловую систему NTFS, %1 использует %2 - - - - The Virtual filesystem feature is not supported on network drives - Возможность Виртуальная файловая система не поддерживается на сетевых дисках - - - - OCC::ownCloudGui - - - Please sign in - Пожалуйста войдите в систему - - - - Folder %1: %2 - Каталог %1: %2 - - - - Unresolved %1 conflicts - Неразрешённые конфликты %1 - - - - Ignored errors %1 - Проигнорировано ошибок %1 - - - - There are no sync folders configured. - Нет настроенных каталогов для синхронизации - - - - Open in browser - Открыть в браузере - - - - - - Log in... - Вход... - - - - - - Log out - Выйти - - - - Recent Changes - Недавние изменения - - - - Managed Folders: - Управляемые каталоги: - - - - Open folder '%1' - Открыть каталог '%1' - - - - Open %1 in browser - Открыть %1 в браузере - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - Учётная запись сервера %1 работает с неподдерживаемой версией %2. Использование клиента с неподдерживаемой версией сервера не тестировалось и потенциально опасно. Продолжайте на свой страх и риск. - - - - Unknown status - Неизвестный статус - - - - Settings... - Настройки... - - - - Details... - Детали... - - - - Help - Помощь - - - - Quit %1 - Закрыть %1 - - - - Disconnected from %1 - Отключен от %1 - - - - Unsupported Server Version - Версия сервера не поддерживается - - - - Disconnected - Отключено - - - - Disconnected from some accounts - Отключено от некоторых учётных записей - - - - Disconnected from accounts: - Отключен от учетных записей: - - - - Account %1: %2 - Учетная запись %1: %2 - - - - Signed out - Вышли из аккаунта - - - - Account synchronization is disabled - Синхронизация учётной записи отключена - - - - Synchronization is paused - Синхронизация приостановлена - - - - Up to date (%1) - Синхронизировано (%1) - - - - No sync folders configured - Не настроено ни одного каталога для синхронизации - - - - Unpause all folders - Снять с паузы все папки - - - - Pause all folders - Поставить на паузу все папки - - - - Unpause all synchronization - Возобновить все синхронизации - - - - Unpause synchronization - Возобновить синхронизацию - - - - Pause all synchronization - Приостановить все синхронизации - - - - Pause synchronization - Приостановить синхронизацию - - - - Log out of all accounts - Выйти из всех учетных записей - - - - Log in to all accounts... - Войти во все учетные записи... - - - - New account... - Новая учётная запись… - - - - About %1 - Про %1 - - - - No items synced recently - Недавно ничего не синхронизировалось - - - - Checking for changes in remote '%1' - Проверяю измененеия в удалённом '%1' - - - - Checking for changes in local '%1' - Проверяю изменения в локальном '%1' - - - - Syncing %1 of %2 (%3 left) - Синхронизация %1 из %2 (осталось %3) - - - - Syncing %1 of %2 - Синхронизирую %1 из %2 - - - - Syncing %1 (%2 left) - Синхронизация %1 (осталось %2) - - - - Syncing %1 - Синхронизация %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - Форма - - - - - TextLabel - TextLabel - - - - Server - Сервер - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Если этот флажок отмечен, то существующее содержимое локальной папки будет удалено и будет запущен процесс синхронизации с сервера.</p><p>Не отмечайте, если содержимое должно быть загружено на сервер.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - Спрашивать подтверждение перед синхрони&зацией папок, по размеру больше чем - - - - S&ynchronize everything from server - С&инхронизировать всё с сервера - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - МБ - - - - Ask for confirmation before synchronizing e&xternal storages - Спрашивать подтверждение перед синхронизацией вне&шних хранилищ - - - - Choose what to sync - Уточнить объекты - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Если выбрать эту настройку, мастер закроется, ничего не синхроризируя. Вы можете использовать кнопку &quot;Добавить папку для синхронизации&quot; из настроек учётной записи, чтобы выбрать, какая пару локального и удалённого каталога нужно синхронизировать</p></body></html> - - - - Manually create folder sync connections - Создать синхронизации папок вруную - - - - &Local Folder - &Локальный каталог - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Сохранить локальные данные - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - Форма - - - - &Username - &Имя пользователя - - - - &Password - &Пароль - - - - OwncloudOAuthCredsPage - - - Form - Форма - - - - Please switch to your browser to proceed. - Пожалуйста, перейдите в браузер, чтобы продолжить. - - - - An error occurred while connecting. Please try again. - Во время соединения произошла ошибка. Пожалуйста попробуйте ещё раз. - - - - Re-open Browser - Повторно открыть браузер - - - - OwncloudSetupPage - - - Form - Форма - - - - Ser&ver Address - Адрес сер&вера - - - - https://... - https://... - - - - Error Label - Ярлык Ошибки - - - - QObject - - - in the future - в будущем - - - - %n day(s) ago - %n день назад%n дня назад%n дней назад%n дней назад - - - - %n hour(s) ago - %n час назад%n часа назад%n часов назад%n часов назад - - - - now - сейчас - - - - Less than a minute ago - Меньше минуты назад - - - - %n minute(s) ago - %n минута назад%n минуты назад%n минут назад%n минут назад - - - - Some time ago - Некоторое время назад - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - Неисправимая ошибка - - - - - Error - Ошибка - - - - Info - Инфо - - - - Success - Успешно - - - - Conflict - Конфликт - - - - File Ignored - - - - - Restored - - - - - Blacklisted - - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 ГБ - - - - %L1 MB - %L1 МБ - - - - %L1 KB - %L1 КБ - - - - %L1 B - %L1 Б - - - - %n year(s) - %n год%n года%n лет%n лет - - - - %n month(s) - %n месяц%n месяца%3 месяцев%n месяцев - - - - %n day(s) - %n день%n дня%n дней%n дней - - - - %n hour(s) - %n час%n часа%n часов%n часов - - - - %n minute(s) - %n минута%n минуты%n минут%n минут - - - - %n second(s) - %n секунда%n секунды%n секунд%n секунд - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Панель системных значков недоступна - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 требует работающей панели системных значков. Если вы используете XFCE, следуйте <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">этим инструкциям</a>. В противном случае, установите приложение панели системных значков, например, 'trayer', и попробуйте ещё раз. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - %1 (Собрано с Qt %1) - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - Виртуальный файл создан - - - - Replaced by virtual file - Заменено виртуальным файлом - - - - Downloaded - Скачано - - - - Uploaded - Загружено - - - - Server version downloaded, copied changed local file into conflict file - Серверная версия скачана, изменённый локальный файл скопирован в файл конфликта. - - - - Deleted - Удалено - - - - Moved to %1 - Перемещено в %1 - - - - Ignored - Проигнорирован - - - - Filesystem access error - Ошибка доступа к файловой системе - - - - Error - Ошибка - - - - Updated local metadata - Обновлены локальные метаданные - - - - - Unknown - Неизвестно - - - - downloading - скачивание - - - - uploading - загрузка - - - - deleting - удаление - - - - moving - перемещение - - - - ignoring - игнорирование - - - - - error - ошибка - - - - updating local metadata - обновление локальных метаданных - - - - theme - - - Status undefined - Статус не определён - - - - Waiting to start sync - Ожидание начала синхронизации - - - - Sync is running - Синхронизация запущена - - - - Sync Success - Синхронизация прошла успешно - - - - Sync Success, some files were ignored. - Синхронизация прошла успешно, некоторые файлы были проигнорированы. - - - - Sync Error - Ошибка синхронизации - - - - Setup Error - Ошибка установки - - - - Preparing to sync - Подготовка к синхронизации - - - - Aborting... - Прерывание... - - - - Sync is paused - Синхронизация приостановлена - - - - utility - - - Could not open browser - Невозможно открыть браузер - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - При запуске браузера чтобы открыть адрес %1 произошла ошибка. Может быть не настроен браузер по умолчанию? - - - - Could not open email client - Не удалось открыть почтового клиента - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - При запуске почтового клиента для создания нового сообщения произошла ошибка. Возможно, почтовый клиент по умолчанию не настроен? - - - - Always available locally - Всегда доступно локально - - - - Some available online only - Часть доступна локально - - - - - Available online only - Доступно только при наличии сети - - - - Currently available locally - Сейчас доступно локально - - - - Make always available locally - Сделать всегда доступным локально - - - - Free up local space - Освободить локальное место - - - \ No newline at end of file diff --git a/translations/client_sk.ts b/translations/client_sk.ts deleted file mode 100644 index c9dac0a0c01..00000000000 --- a/translations/client_sk.ts +++ /dev/null @@ -1,4420 +0,0 @@ - - - FileSystem - - - Could not make directories in trash - - - - - - Could not move '%1' to '%2' - - - - - Moving to the trash is not implemented on this platform - - - - - FolderWizardSourcePage - - - Form - Formulár - - - - Pick a local folder on your computer to sync - Vyberte lokálny priečinok v počítači pre synchronizáciu - - - - &Choose... - &Zvoliť - - - - FolderWizardTargetPage - - - Form - Formulár - - - - Select a remote destination folder - Vyberte vzdialený cieľový priečinok - - - - Create Folder - Vytvoriť priečinok - - - - Refresh - Obnoviť - - - - Folders - Priečinky - - - - TextLabel - Štítok - - - - NotificationWidget - - - Form - Formulár - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Štítok - - - - OCC::AbstractNetworkJob - - - Connection timed out - Pripojenie expirovalo - - - - Unknown error: network reply was deleted - Neznáma chyba: odpoveď siete bola odstránená - - - - Server replied "%1 %2" to "%3 %4" - Server odpovedal "%1 %2" na "%3 %4" - - - - OCC::AccountSettings - - - Form - Formulár - - - - ... - ... - - - - Storage space: ... - Úložný priestor: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Neoznačené priečinky budú <b>odstránené</b> z lokálneho systému a nebudú už synchronizované na tento počítač - - - - Synchronize all - Synchronizovať všetky - - - - Synchronize none - Synchronizovať žiadny - - - - Apply manual changes - Aplikovať manuálne zmeny - - - - Apply - Aplikovať - - - - - - Cancel - Zrušiť - - - - Connected with <server> as <user> - Spojené z <server> ako <user> - - - - No account configured. - Žiadny účet nie je nastavený. - - - - Add new - Pridať nový účet - - - - Remove - Odobrať - - - - Account - Účet - - - - Choose what to sync - Vybrať čo synchronizovať - - - - Force sync now - Vynútiť synchronizáciu teraz - - - - Restart sync - - - - - Remove folder sync connection - Odstrániť prepojenie priečinka - - - - Create virtual files for new files (Experimental) - - - - - Folder creation failed - Vytvorenie priečinka zlyhalo - - - - <p>Could not create local folder <i>%1</i>. - <p>Nemožno vytvoriť lokálny priečinok <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Potvrdiť odstránenie prepojenia priečinka - - - - Remove Folder Sync Connection - Odstrániť prepojenie priečinka - - - - Sync Running - Prebiehajúca synchronizácia - - - - The syncing operation is running.<br/>Do you want to terminate it? - Proces synchronizácie práve prebieha.<br/>Chcete ho ukončiť? - - - - %1 in use - %1 sa používa - - - - %1 as <i>%2</i> - %1 ako <i>%2</i> - - - - Connected to %1. - Pripojené k %1 - - - - Server %1 is temporarily unavailable. - Server %1 je dočasne nedostupný. - - - - Server %1 is currently in maintenance mode. - - - - - Signed out from %1. - Odhlásený z %1. - - - - Obtaining authorization from the browser. <a href='%1'>Click here</a> to re-open the browser. - - - - - Connecting to %1... - - - - - No connection to %1 at %2. - Žiadne pripojenie k %1 na %2. - - - - Log in - Prihlásiť sa - - - - There are folders that were not synchronized because they are too big: - - - - - There are folders that were not synchronized because they are external storages: - - - - - There are folders that were not synchronized because they are too big or external storages: - - - - - Confirm Account Removal - Potvrďte ostránenie účtu - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Naozaj chcete odstrániť pripojenie k účtu <i>%1</i>?</p><p><b>Poznámka:</b> Tým sa <b>nevymažú</b> žiadne súbory.</p> - - - - Remove connection - Vymazať pripojenie - - - - - Open folder - Otvoriť priečinok - - - - - Log out - Odhlásiť - - - - Resume sync - Obnoviť synchronizáciu - - - - Pause sync - Pozastaviť synchronizáciu - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Naozaj si prajete zastaviť synchronizácu priečinka <i>%1</i>?</p><p><b>Poznámka:</b> Toto <b>nevymaže</b> žiadne súbory.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) z %2 je využitých. Niektoré priečinky, vrátane sieťových a zdieľaných, môžu mať iné limity. - - - - %1 of %2 in use - %1 z %2 je využitých - - - - Currently there is no storage usage information available. - Teraz nie sú k dispozícii žiadne informácie o využití úložiska. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - No %1 connection configured. - Žiadne nakonfigurované %1 spojenie - - - - OCC::AccountState - - - Signed out - Odhlásený - - - - Disconnected - Odpojený - - - - Connected - Pripojené - - - - Service unavailable - Služba nedostupná - - - - Maintenance mode - - - - - Network error - Chyba siete - - - - Configuration error - Chyba nastavenia - - - - Asking Credentials - - - - - Unknown account state - Neznámy stav účtu - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 na %2 - - - - %1 on %2 (disconnected) - %1 na %2 (odpojené) - - - - OCC::ActivitySettings - - - - Server Activity - Aktivita servera - - - - Sync Protocol - Záznam synchronizácie - - - - Not Synced - Nezosynchronizované - - - - Not Synced (%1) - %1 is the number of not synced files. - Nezosynchronizované (%1) - - - - The server activity list has been copied to the clipboard. - Zoznam aktivít servera bol skopírovaný do schránky. - - - - The sync activity list has been copied to the clipboard. - Zoznam aktivít synchronizácie bol skopírovaný do schránky. - - - - The list of unsynced items has been copied to the clipboard. - - - - - Copied to clipboard - Skopírované do schránky - - - - OCC::ActivityWidget - - - Form - Formulár - - - - - - TextLabel - Štítok - - - - Server Activities - Aktivita servera - - - - Copy - Kopírovať - - - - Copy the activity list to the clipboard. - Skopírovať zoznam aktivít do schránky. - - - - Action Required: Notifications - Požaduje sa činnosť: oznámenia - - - - <br/>Account %1 does not have activities enabled. - <br/>Účet %1 nemá zapnuté aktivity. - - - - You received %n new notification(s) from %2. - - - - - You received %n new notification(s) from %1 and %2. - - - - - You received new notifications from %1, %2 and other accounts. - - - - - %1 Notifications - Action Required - - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Overenie prostredníctvom klientského SSL certifikátu - - - - This server probably requires a SSL client certificate. - Tento server pravdepodobne vyžaduje klientský SSL certifikát. - - - - Certificate & Key (pkcs12) : - - - - - Browse... - Prechádzať.. - - - - Certificate password : - Heslo certifikátu: - - - - Select a certificate - Vybrať certifikát - - - - Certificate files (*.p12 *.pfx) - Súbory certifikátu (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Koniec - - - - Continue - Pokračovať - - - - Error accessing the configuration file - - - - - There was an error while accessing the configuration file at %1. - - - - - Quit ownCloud - - - - - OCC::AuthenticationDialog - - - Authentication Required - Vyžaduje sa overenie - - - - Enter username and password for '%1' at %2. - Zadajte používateľské meno a heslo pre '%1' na %2. - - - - &User: - &Používateľ: - - - - &Password: - &Heslo: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Chyba pri zápise metadát do databázy - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Žiadny účet v ownCloude nie je nastavený - - - - The configured server for this client is too old - Server nakonfigurovaný pre tohto klienta je príliš starý - - - - Please update to the latest server and restart the client. - Prosím aktualizujte na najnovšiu verziu servera a reštartujte klienta. - - - - Authentication error: Either username or password are wrong. - Chyba overenia: Používateľské meno alebo heslo nie je správne. - - - - timeout - vypršal časový limit - - - - The provided credentials are not correct - Poskytnuté poverenia nie sú správne - - - - OCC::DiscoveryMainThread - - - Aborted by the user - Zrušené používateľom - - - - OCC::DiscoverySingleDirectoryJob - - - The server file discovery reply is missing data. - - - - - OCC::Folder - - - Local folder %1 does not exist. - Lokálny priečinok %1 neexistuje. - - - - %1 should be a folder but is not. - %1 by mal byť priečinok, avšak nie je. - - - - %1 is not readable. - %1 nie je čitateľný. - - - - %1 has been removed. - %1 names a file. - %1 bol zmazaný. - - - - %1 has been downloaded. - %1 names a file. - %1 bol stiahnutý. - - - - %1 has been updated. - %1 names a file. - %1 bol aktualizovaný. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 bol premenovaný na %2. - - - - %1 has been moved to %2. - %1 bol presunutý do %2. - - - - %1 and %n other file(s) have been removed. - - - - - %1 and %n other file(s) have been downloaded. - - - - - %1 and %n other file(s) have been updated. - - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - - - - - %1 has been moved to %2 and %n other file(s) have been moved. - - - - - %1 has and %n other file(s) have sync conflicts. - - - - - %1 has a sync conflict. Please check the conflict file! - - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - - - - - %1 could not be synced due to an error. See the log for details. - %1 nemôže byť synchronizovaný kvôli chybe. Pozrite sa do logu pre podrobnosti. - - - - Sync Activity - Aktivita synchronizácie - - - - Could not read system exclude file - Nemožno čítať systémový exclude file - - - - A new folder larger than %1 MB has been added: %2. - - - - - - A folder from an external storage has been added. - - - - - - Please go in the settings to select it if you wish to download it. - - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - Odstrániť všetky súbory? - - - - Remove all files - Odstrániť všetky súbory - - - - Keep files - Ponechať súbory - - - - This sync would reset the files to an earlier time in the sync folder '%1'. -This might be because a backup was restored on the server. -Continuing the sync as normal will cause all your files to be overwritten by an older file in an earlier state. Do you want to keep your local most recent files as conflict files? - - - - - Backup detected - Záloha je dostupná - - - - Normal Synchronisation - Bežná synchronizácia - - - - Keep Local Files as Conflict - Ponechať lokálne súbory ako konfliktné - - - - OCC::FolderMan - - - Could not reset folder state - Nemožno resetovať stav priečinka - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Starý synchronizačný žurnál '%1' nájdený, avšak neodstrániteľný. Prosím uistite sa, že žiadna aplikácia ho práve nevyužíva. - - - - (backup) - (záloha) - - - - (backup %1) - (záloha %1) - - - - Undefined State. - Nedefinovaný stav. - - - - Waiting to start syncing. - Čaká sa na začiatok synchronizácie - - - - Preparing for sync. - Príprava na synchronizáciu. - - - - Sync is running. - Synchronizácia prebieha. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Posledná synchronizácia sa úspešne skončila. - - - - Setup Error. - Chyba pri inštalácii. - - - - User Abort. - Zrušené používateľom. - - - - Sync is paused. - Synchronizácia je pozastavená. - - - - %1 (Sync is paused) - %1 (Synchronizácia je pozastavená) - - - - No valid folder selected! - Nebol zvolený platný priečinok. - - - - The selected path is not a folder! - Zvolená cesta nie je priečinok. - - - - You have no permission to write to the selected folder! - Nemáte oprávnenia pre zápis do daného priečinka! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - - - - - Synchronizing with local folder - Synchronizujem lokálny priečinok - - - - File - Súbor - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Na pridanie priečinku je nutné byť pripojený - - - - Click this button to add a folder to synchronize. - - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - - - - - Signed out - Odhlásený - - - - Fetching folder list from server... - Načítavam zoznam priečinkov zo servera... - - - - There are unresolved conflicts. Click for details. - - - - - New files are being created as virtual files. - - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - - - - - '%1' - Argument is a file name - - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synchronizuje sa %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - stiahnuť %1/s - - - - u2193 %1/s - u2193 %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - nahrať %1/s - - - - u2191 %1/s - u2191 %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 of %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 z %2, súbor %3 z %4 - - - - file %1 of %2 - súbor %1 z %2 - - - - Waiting... - Čakajte... - - - - Waiting for %n other folder(s)... - - - - - Preparing to sync... - Príprava na synchronizáciu... - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - - - - - Add Sync Connection - - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Kliknutím vyberte lokálny priečinok, ktorý chcete synchronizovať. - - - - Enter the path to the local folder. - Zadajte cestu do lokálneho priečinka. - - - - Select the source folder - Vyberte zdrojový priečinok - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Vytvoriť vzdialený priečinok - - - - Enter the name of the new folder to be created below '%1': - Zadajte názov nového priečinka, ktorý bude vytvorený v '%1': - - - - Folder was successfully created on %1. - Priečinok bol úspešne vytvorený na %1. - - - - Authentication failed accessing %1 - Zlyhalo overenie pri pripojení %1 - - - - Failed to create the folder on %1. Please check manually. - Na %1 zlyhalo vytvorenie priečinka. Skontrolujte to, prosím, ručne. - - - - Failed to list a folder. Error: %1 - - - - - Choose this to sync the entire account - Zvoľte pre synchronizáciu celého účtu - - - - This folder is already being synced. - Tento priečinok sa už synchronizuje. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Priečinok <i>%1</i> už synchronizujete a je nadradený priečinku <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately (experimental) - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Upozornenie:</b> %1 - - - - <b>Warning:</b> - <b>Upozornenie:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Zo servera nebol prijatý E-Tag, skontrolujte proxy/bránu - - - - We received a different E-Tag for resuming. Retrying next time. - Prijali sme iný E-Tag pre pokračovanie. Skúsim to neskôr znovu. - - - - Server returned wrong content-range - Server vrátil nesprávnu hodnotu Content-range - - - - Connection Timeout - Spojenie vypršalo - - - - OCC::GeneralSettings - - - Form - Formulár - - - - General Settings - Všeobecné nastavenia - - - - For System Tray - - - - - stable - - - - - beta - - - - - Advanced - Rozšírené - - - - Ask for confirmation before synchronizing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - - - - - &Launch on System Startup - - - - - Show &Desktop Notifications - - - - - Use &Monochrome Icons - - - - - Version - Verzia - - - - &Update Channel - - - - - • - - - - - Edit &Ignored Files - - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - - - - - &Restart && Update - &Restart && aktualizácia - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Zrušiť - - - - OCC::GetOrCreatePublicLinkShare - - - Sharing error - - - - - Could not retrieve or create the public link share. Error: - -%1 - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - Vložte heslo - - - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Editor ignorovaných súborov - - - - Global Ignore Settings - - - - - Sync hidden files - Synchronizovať skryté súbory - - - - Files Ignored by Patterns - - - - - Add - Pridať - - - - Pattern - Vzor - - - - Allow Deletion - Povoliť vymazanie - - - - Remove - Odobrať - - - - Files or folders matching a pattern will not be synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Nemožno otvoriť súbor - - - - Cannot write changes to '%1'. - Nemožno zapísať zmeny do '%1'. - - - - Add Ignore Pattern - Pridať vzor ignorovaného súboru - - - - Add a new ignore pattern: - Pridať nový vzor ignorovaného súboru: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Táto položka je poskytovaná systémom na '%1' a nesmie byť modifikovaná v tomto zobrazení. - - - - OCC::IssuesWidget - - - Form - Formulár - - - - List of issues - - - - - Account - Účet - - - - - <no filter> - - - - - - Folder - Priečinok - - - - Show warnings - - - - - Show ignored files - - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - - - - - Copy - Kopírovať - - - - Time - Čas - - - - File - Súbor - - - - Issue - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LogBrowser - - - Log Output - Systémový záznam výstupu - - - - &Search: - &Hľadať: - - - - &Find - &Nájsť - - - - &Capture debug messages - - - - - Permanently save logs - - - - - When this option is enabled and no other logging is configured, logs will be written to a temporary folder and expire after a few hours. This setting persists across client restarts. - -Logs will be written to %1 - - - - - Clear - Vyčistiť - - - - Clear the log display. - Vyčistiť zobrazenie záznamu (logu). - - - - S&ave - &Uložiť - - - - Save the log file to a file on disk for debugging. - Uložiť log súbor do súboru na disk pre proces debugovania. - - - - Save log file - Uložiť súbor so záznamami (log) - - - - Error - Chyba - - - - Could not write to log file %1 - Nemožno zapísať do záznamového súboru - logu %1 - - - - OCC::Logger - - - Error - Chyba - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>Súbor '%1'<br/>nesmie byť otvorený pre proces úprav.</br/><br/>Systémový záznam (log) <b>nemôže</b> byť uložený!</nobr> - - - - OCC::NSISUpdater - - - New Version Available - Nová verzia je k dispozícii - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Je dostupná nová verzia klienta %1.</p><p><b>%2</b> je dostupná na stiahnutie. Nainštalovaná verzia je %3.</p> - - - - Skip this version - Preskočiť túto verziu - - - - Skip this time - Tentoraz preskočiť - - - - Get update - Aktualizovať teraz - - - - OCC::NetworkSettings - - - Form - Formulár - - - - Proxy Settings - Nastavenia proxy - - - - No Proxy - Žiadna proxy - - - - Use system proxy - Použiť systémové nastavenia proxy - - - - Specify proxy manually as - Špecifikovať proxy manuálne ako - - - - Host - Hostiteľ - - - - : - : - - - - Proxy server requires authentication - Proxy server vyžaduje overenie - - - - Download Bandwidth - Dátová šírka pre download - - - - - Limit to - Limitovať do - - - - - KBytes/s - KBytov/s - - - - - No limit - Žiadny limit - - - - - Limit to 3/4 of estimated bandwidth - - - - - Upload Bandwidth - Dátová šírka pre upload - - - - - Limit automatically - Limitovať automaticky - - - - Hostname of proxy server - Hostname (hostiteľské meno) proxy servera - - - - Username for proxy server - Používateľské meno pre proxy server - - - - Password for proxy server - Heslo pre proxy server - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - Vytvorený o %1 - - - - Closing in a few seconds... - Ukončenie o pár sekúnd... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 požiadavka zlyhala o %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - The reply from the server did not contain all expected fields - - - - - <h1>Login Error</h1><p>%1</p> - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - Pripravená je nová aktualizácia %1 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Nainštaluje sa nová aktualizácia pre %1. V priebehu aktualizácie -môžu byť vyžadované dodatočné oprávnenia. - - - - Downloading version %1. Please wait... - Sťahujem verziu %1. Čakajte prosím... - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Nemôžem stiahnuť aktualizáciu. Kliknite, prosím, na <a href='%1'>tento odkaz</a> pre ručné stiahnutie aktualizácie. - - - - Could not check for new updates. - Nemôžem skontrolovať dostupnosť aktualizácie. - - - - %1 version %2 available. Restart application to start the update. - - - - - New %1 version %2 available. Please use the system's update tool to install it. - - - - - Checking update server... - Kontrolujem aktualizačný server... - - - - Update status is unknown: Did not check for new updates. - Stav aktualizácie nie je známy: Neboli skontrolované nové aktualizácie. - - - - No updates available. Your installation is at the latest version. - Žiadne aktualizácie nie sú k dispozícii. Používate aktuálnu verziu. - - - - Update Check - Kontrola aktualizácií - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Pripojiť sa k %1 - - - - Setup local folder options - Nastaviť možnosti lokálneho priečinka - - - - Connect... - Pripojiť... - - - - %1 folder '%2' is synced to local folder '%3' - %1 priečinok '%2' je zosynchronizovaný do lokálneho priečinka '%3' - - - - Sync the folder '%1' - Sychronizovať priečinok '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - - - - - Local Sync Folder - Lokálny synchronizačný priečinok - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Spojenie zlyhalo - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Nepodarilo sa pripojenie k tomuto zabezpečenému serveru. Ako chcete pokračovať?</p></body></html> - - - - Select a different URL - Vybrať inú URL - - - - Retry unencrypted over HTTP (insecure) - Skúsiť bez šifrovania cez HTTP (nezabezpečené) - - - - Configure client-side TLS certificate - Nakonfigurovať klientský TLS certifikát - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Nepodarilo sa pripojenie k zabezpečenému serveru <em>%1</em>. Ako chcete pokračovať?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - - - - - Connect to %1 - Pripojiť sa k %1 - - - - Enter user credentials - Vložte prihlasovacie údaje - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Pripojiť sa k %1 - - - - Login in your browser - - - - - Copy link to clipboard - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Pripojiť sa k %1 - - - - Setup %1 server - Nastaviť server %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Táto adresa nie je bezpečná, pretože nie je šifrovaná. -Nie je vhodné ju používať. - - - - This url is secure. You can use it. - Táto URL je bezpečná. Môžete ju používať. - - - - &Next > - &Ďalšia > - - - - Invalid URL - - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">Úspešne pripojené k %1: %2 verzie %3 (%4)</font><br/><br/> - - - - Failed to connect to %1 at %2:<br/>%3 - Zlyhalo spojenie s %1 o %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Časový limit vypršal pri pokuse o pripojenie k %1 na %2. - - - - Trying to connect to %1 at %2... - Pokúšam sa o pripojenie k %1 na %2... - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - Overená požiadavka na server bola presmerovaná na '%1'. URL je zlá, server nie je správne nakonfigurovaný. - - - - There was an invalid response to an authenticated webdav request - Neplatná odpoveď na overenú webdav požiadavku - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - Prístup zamietnutý serverom. Po overení správnych prístupových práv, <a href="%1">kliknite sem</a> a otvorte službu v svojom prezerači. - - - - Invalid URL - - - - - The server reported the following error: - - - - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - Lokálny synchronizačný priečinok %1 už existuje, prebieha jeho nastavovanie pre synchronizáciu.<br/><br/> - - - - Creating local sync folder %1... - Vytváranie lokálneho synchronizačného priečinka %1 ... - - - - ok - v poriadku - - - - failed. - neúspešné. - - - - Could not create local folder %1 - Nemožno vytvoriť lokálny priečinok %1 - - - - No remote folder specified! - Vzdialený priečinok nie je nastavený! - - - - Error: %1 - Chyba: %1 - - - - creating folder on ownCloud: %1 - vytváram priečinok v ownCloude: %1 - - - - Remote folder %1 created successfully. - Vzdialený priečinok %1 bol úspešne vytvorený. - - - - The remote folder %1 already exists. Connecting it for syncing. - Vzdialený priečinok %1 už existuje. Prebieha jeho pripájanie pre synchronizáciu. - - - - - The folder creation resulted in HTTP error code %1 - Vytváranie priečinka skončilo s HTTP chybovým kódom %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Proces vytvárania vzdialeného priečinka zlyhal, lebo použité prihlasovacie údaje nie sú správne!<br/>Prosím skontrolujte si vaše údaje a skúste to znovu.</p> - - - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">Vytvorenie vzdialeného priečinka pravdepodobne zlyhalo kvôli nesprávnym prihlasovacím údajom.</font><br/>Prosím choďte späť a skontrolujte ich.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Vytvorenie vzdialeného priečinka %1 zlyhalo s chybou <tt>%2</tt>. - - - - A sync connection from %1 to remote directory %2 was set up. - Synchronizačné spojenie z %1 do vzdialeného priečinka %2 bolo práve nastavené. - - - - Successfully connected to %1! - Úspešne pripojené s %1! - - - - Connection to %1 could not be established. Please check again. - Pripojenie k %1 nemohlo byť iniciované. Prosím skontrolujte to znovu. - - - - Folder rename failed - Premenovanie priečinka zlyhalo - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Nemožno odstrániť a zazálohovať priečinok, pretože priečinok alebo súbor je otvorený v inom programe. Prosím zatvorte priečinok nebo súbor a skúste to znovu alebo zrušte akciu. - - - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>Lokálny synchronizačný priečinok %1 bol úspešne vytvorený!</b></font> - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Asistent pripojenia - - - - Enable experimental feature? - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Enable experimental mode - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Všetko je nastavené! - - - - Open Local Folder - Otvoriť lokálny priečinok - - - - Open %1 in Browser - Otvoriť %1 v prehliadači - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - Neplatná JSON odpoveď z URL adresy - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Chyba pri zápise metadát do databázy - - - - OCC::PropagateDownloadFile - - - File %1 can not be downloaded because of a local file name clash! - Súbor %1 nie je možné stiahnuť, pretože súbor s rovnakým menom už existuje! - - - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - - - - - File was deleted from server - Súbor bol vymazaný zo servera - - - - The file could not be downloaded completely. - Súbor sa nedá stiahnuť úplne. - - - - The downloaded file is empty despite the server announced it should have been %1. - - - - - File %1 cannot be saved because of a local file name clash! - Súbor %1 nie je možné uložiť, pretože jeho názov koliduje s názvom lokálneho súboru! - - - - File has changed since discovery - Súbor sa medzitým zmenil - - - - Error writing metadata to the database - Chyba pri zápise metadát do databázy - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Obnovenie zlyhalo: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - - - - - Attention, possible case sensitivity clash with %1 - Pozor, možná kolízia z dôvodu veľkosti písmen s %1 - - - - could not create folder %1 - nemožno vytvoriť priečinok %1 - - - - Error writing metadata to the database - Chyba pri zápise metadát do databázy - - - - OCC::PropagateLocalRemove - - - Error removing '%1': %2; - Chyba pri odstraňovaní '%1': %2; - - - - Could not remove folder '%1' - - - - - Could not remove %1 because of a local file name clash - Nemožno odstrániť %1 z dôvodu kolízie názvu súboru s lokálnym súborom - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Súbor %1 nemôže byť premenovaný na %2 z dôvodu, že tento názov je už použitý - - - - - Error writing metadata to the database - Chyba pri zápise metadát do databázy - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Server vrátil neplatný HTTP kód. Očakávaný bol 204, ale vrátený bol "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Server vrátil neplatný HTTP kód. Očakávaný bol 201, ale vrátený bol "%1 %2". - - - - Error writing metadata to the database - Chyba pri zápise metadát do databázy - - - - OCC::PropagateRemoteMove - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Server vrátil neplatný HTTP kód. Očakávaný bol 201, ale vrátený bol "%1 %2". - - - - - Error writing metadata to the database - Chyba pri zápise metadát do databázy - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - Súbor zmazaný - - - - Local file changed during syncing. It will be resumed. - Lokálny súbor bol zmenený počas synchronizácie. Bude obnovený. - - - - Local file changed during sync. - Lokálny súbor bol zmenený počas synchronizácie. - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - Chyba pri zápise metadát do databázy - - - - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - Lokálny súbor bol odstránený počas synchronizácie. - - - - Local file changed during sync. - Lokálny súbor bol zmenený počas synchronizácie. - - - - Unexpected return code from server (%1) - - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileV1 - - - Poll URL missing - Chýba URL adresa - - - - The local file was removed during sync. - Lokálny súbor bol odstránený počas synchronizácie. - - - - Local file changed during sync. - Lokálny súbor bol zmenený počas synchronizácie. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolWidget - - - Form - Formulár - - - - TextLabel - Štítok - - - - Open in browser - Otvoriť v prehliadači - - - - Time - Čas - - - - File - Súbor - - - - Folder - Priečinok - - - - Action - Akcia - - - - Size - Veľkosť - - - - Local sync protocol - Lokálny protokol synchronizácie - - - - Copy - Kopírovať - - - - Copy the activity list to the clipboard. - Skopírovať zoznam aktivít do schránky. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Vyžaduje sa proxy autentifikácia - - - - Username: - Meno: - - - - Proxy: - Proxy: - - - - The proxy server needs a username and password. - Proxy server vyžaduje používateľské meno a heslo. - - - - Password: - Heslo: - - - - TextLabel - Štítok - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Vybrať čo synchronizovať - - - - OCC::SelectiveSyncWidget - - - Loading ... - Nahrávam... - - - - Deselect remote folders you do not wish to synchronize. - - - - - Name - Názov - - - - Size - Veľkosť - - - - - No subfolders currently on the server. - Na serveri teraz nie sú žiadne podpriečinky. - - - - An error occurred while loading the list of sub folders. - - - - - OCC::ServerNotificationHandler - - - Dismiss - Odmietnuť - - - - OCC::SettingsDialog - - - Settings - Nastavenia - - - - Activity - Aktivita - - - - General - Všeobecné - - - - Network - Sieť - - - - Account - Účet - - - - OCC::SettingsDialogMac - - - %1 - %1 - - - - Activity - Aktivita - - - - General - Všeobecné - - - - Network - Sieť - - - - - Account - Účet - - - - OCC::ShareDialog - - - TextLabel - Štítok - - - - share label - zdieľať značku - - - - Dialog - Dialóg - - - - ownCloud Path: - ownCloud cesta: - - - - %1 Sharing - %1 zdieľanie - - - - %1 - %1 - - - - Folder: %2 - Priečinok: %2 - - - - The server does not allow sharing - - - - - Retrieving maximum possible sharing permissions from server... - - - - - The file can not be shared because it was shared without sharing permission. - Tento súbor nemožno zdieľať, lebo bol vyzdieľaný bez možnosti ďalšieho zdieľania. - - - - Users and Groups - - - - - Public Links - - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Zdieľať NewDocument.odt - - - - Upload only (File Drop) - - - - - Receive files from others without revealing the contents of the folder. - - - - - TextLabel - Štítok - - - - Set &password - Nastaviť &heslo - - - - Enter a name to create a new public link... - - - - - &Create new - - - - - Set &expiration date - Nastaviť dátum &expirácie - - - - Set password - Nastaviť heslo - - - - Recipients can view or download contents. - - - - - Download / View - - - - - Download / View / Upload - - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - - - - - - P&assword protect - Ch&rániť heslom - - - - The file can not be shared because it was shared without sharing permission. - Tento súbor nemožno zdieľať, lebo bol vyzdieľaný bez možnosti ďalšieho zdieľania. - - - - Link shares have been disabled - - - - - Create public link share - - - - - - Delete - Zmazať - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Copy link to clipboard (direct download) - - - - - Send link by email - Odoslať odkaz emailom - - - - Send link by email (direct download) - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - - - - - Cancel - Zrušiť - - - - - Public link - - - - - Delete link share - - - - - Public sh&aring requires a password - Verejné z&dieľanie vyžaduje heslo - - - - Please Set Password - Prosím nastavte si heslo - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Zdieľať NewDocument.odt - - - - Share with users or groups ... - Zdieľať s používateľmi alebo skupinami ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Send link by email - Odoslať odkaz emailom - - - - No results for '%1' - - - - - I shared something with you - - - - - OCC::ShareUserLine - - - Form - Formulár - - - - - TextLabel - Štítok - - - - can edit - môže upraviť - - - - can share - môže zdieľať - - - - ... - ... - - - - create - vytvoriť - - - - change - zmeniť - - - - delete - vymazať - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::ShibbolethCredentials - - - Login Error - Chybné prihlásenie - - - - You must sign in as user %1 - Musíte sa prihlásiť ako používateľ %1 - - - - OCC::ShibbolethWebView - - - %1 - Authenticate - %1 - overenie - - - - SSL Chipher Debug View - - - - - Reauthentication required - Vyžaduje sa opätovné overenie - - - - Your session has expired. You need to re-login to continue to use the client. - Platnosť relácie uplynula. Musíte sa znovu prihlásiť, ak chcete pokračovať v používaní klienta. - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Zdieľať s %1 - - - - Context menu share - - - - - I shared something with you - - - - - - Share... - - - - - - Copy private link to clipboard - - - - - Send private link by email... - - - - - Resharing this file is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - Otvoriť v prehliadači - - - - Download file(s) - - - - - Replace file(s) by virtual file - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Podrobnosti certifikátu</h3> - - - - Common Name (CN): - Bežné meno (CN): - - - - Subject Alternative Names: - Alternatívne meno subjektu: - - - - Organization (O): - Organizácia (O): - - - - Organizational Unit (OU): - Organizačná jednotka (OU): - - - - State/Province: - Štát/Provincia: - - - - Country: - Krajina: - - - - Serial: - Sériové číslo: - - - - <h3>Issuer</h3> - <h3>Vydavateľ</h3> - - - - Issuer: - Vydavateľ: - - - - Issued on: - Vydané: - - - - Expires on: - Platí do: - - - - <h3>Fingerprints</h3> - <h3>Odtlačky</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Poznámka:</b> Tento certifikát bol ručne schválený</p> - - - - %1 (self-signed) - %1 (podpísaný sám sebou) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Toto spojenie je šifrované pomocou %1 bitovej šifry %2. - - - - - No support for SSL session tickets/identifiers - - - - - Certificate information: - Informácie o certifikáte: - - - - This connection is NOT secure as it is not encrypted. - - Toto spojenie NIE JE bezpečné, pretože nie je šifrované. - - - - - OCC::SslErrorDialog - - - Form - Formulár - - - - Trust this certificate anyway - Dôverovať danému certifikátu v každom prípade - - - - Untrusted Certificate - Nedôveryhodný certifikát - - - - Cannot connect securely to <i>%1</i>: - - - - - with Certificate %1 - s certifikátom %1 - - - - - - &lt;not specified&gt; - &lt;nešpecifikované&gt; - - - - - Organization: %1 - Organizácia: %1 - - - - - Unit: %1 - Jednotka: %1 - - - - - Country: %1 - Krajina: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Odtlačok (MD5 hash): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Odtlačok (SHA1 hash): <tt>%1</tt> - - - - Effective Date: %1 - Dátum účinnosti: %1 - - - - Expiration Date: %1 - Koniec platnosti: %1 - - - - Issuer: %1 - Vydavateľ: %1 - - - - OCC::SyncEngine - - - Success. - Úspech. - - - - A network connection timeout happened. - Skončil časový limit sieťového spojenia. - - - - A HTTP transmission error happened. - Chyba HTTP prenosu. - - - - The mounted folder is temporarily not available on the server - - - - - An error occurred while opening a folder - Nastala chyba počas otvárania priečinka - - - - Error while reading folder. - Chyba pri čítaní adresára - - - - %1 (skipped due to earlier error, trying again in %2) - - - - - File/Folder is ignored because it's hidden. - - - - - Folder hierarchy is too deep - - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Not allowed because you don't have permission to add parent folder - - - - - Not allowed because you don't have permission to add files in that folder - - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - - - - - CSync unspecified error. - CSync nešpecifikovaná chyba. - - - - Aborted by the user - Zrušené používateľom - - - - Failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder. - - - - - Discovery step failed. - - - - - Permission denied. - - - - - File or directory not found: - - - - - Tried to create a folder that already exists. - - - - - No space on %1 server available. - - - - - The service is temporarily unavailable - Služba je dočasne nedostupná - - - - Access is forbidden - Prístup odmietnutý - - - - An internal error number %1 occurred. - Vyskytla sa interná chyba číslo %1. - - - - Symbolic links are not supported in syncing. - Symbolické odkazy nie sú podporované pri synchronizácii. - - - - File is listed on the ignore list. - Súbor je zapísaný na zozname ignorovaných. - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - - - - - The file name is a reserved name on this file system. - - - - - Filename contains trailing spaces. - - - - - Filename is too long. - Meno súboru je veľmi dlhé. - - - - The filename cannot be encoded on your file system. - - - - - Unresolved conflict. - - - - - Stat failed. - - - - - Filename encoding is not valid - Kódovanie znakov názvu súboru je neplatné - - - - Invalid characters, please rename "%1" - Neplatné znaky, premenujte prosím "%1" - - - - Using virtual files but suffix is not set - - - - - Unable to read the blacklist from the local database - - - - - Unable to read from the sync journal. - Nemožno čítať zo synchronizačného žurnálu - - - - Cannot open the sync journal - Nemožno otvoriť sync žurnál - - - - File name contains at least one invalid character - Názov súboru obsahuje nevhodný znak - - - - - Ignored because of the "choose what to sync" blacklist - Ignorované podľa nastavenia "vybrať čo synchronizovať" - - - - Not allowed because you don't have permission to add subfolders to that folder - Nie je dovolené, lebo nemáte oprávnenie pridávať podpriečinky do tohto priečinka - - - - Not allowed to upload this file because it is read-only on the server, restoring - Nie je dovolené tento súbor nahrať, pretože je na serveri iba na čítanie. Obnovuje sa. - - - - - Not allowed to remove, restoring - Nie je dovolené odstrániť. Obnovuje sa. - - - - Local files and share folder removed. - Lokálne súbory a zdieľaný priečinok boli odstránené. - - - - Move not allowed, item restored - Presunutie nie je dovolené. Položka obnovená. - - - - Move not allowed because %1 is read-only - Presunutie nie je dovolené, pretože %1 je na serveri iba na čítanie - - - - the destination - cieľ - - - - the source - zdroj - - - - OCC::SyncLogDialog - - - Synchronisation Log - Záznam synchronizácie - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p> - - - - - <p>Copyright ownCloud GmbH</p> - - - - - <p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%2 and the %2 logo are registered trademarks of %1 in the United States, other countries, or both.</p> - <p>Šíri %1 pod licenciou GNU General Public License (GPL) Verzia 2.0.<br/>%2 a %2 logo sú registrované známky %1 v USA, ostatných krajinách, alebo oboje.</p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Hlavička kontrolného súčtu je poškodená. - - - - The checksum header contained an unknown checksum type '%1' - Hlavička kontrolného súčtu obsahovala neznámy typ kontrolného súčtu „%1“ - - - - The downloaded file does not match the checksum, it will be resumed. - Stiahnutý súbor nemá správny kontrolný súčet, bude stiahnutý znovu. - - - - OCC::ownCloudGui - - - Please sign in - Prihláste sa prosím - - - - Folder %1: %2 - Priečinok %1: %2 - - - - There are no sync folders configured. - Nie sú nastavené žiadne priečinky na synchronizáciu. - - - - Open in browser - Otvoriť v prehliadači - - - - - - Log in... - Prihlásiť sa... - - - - - - Log out - Odhlásiť - - - - Recent Changes - Nedávne zmeny - - - - Managed Folders: - Spravované priečinky: - - - - Open folder '%1' - Otvoriť priečinok '%1' - - - - Open %1 in browser - Otvoriť %1 v prehliadači - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Neznámy stav - - - - Settings... - Nastavenia... - - - - Details... - Podrobnosti... - - - - Help - Pomoc - - - - Quit %1 - Ukončiť %1 - - - - Disconnected from %1 - Odpojený od %1 - - - - Unsupported Server Version - Nepodporovaná verzia servera - - - - Disconnected - Odpojený - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - Odpojené od účtov: - - - - Account %1: %2 - Účet %1: %2 - - - - Signed out - Odhlásený - - - - Account synchronization is disabled - Synchronizácia účtu je vypnutá - - - - - Synchronization is paused - - - - - Unresolved conflicts - - - - - Error during synchronization - - - - - No sync folders configured - - - - - Unpause all folders - Zrušiť pozastavenie všetkých priečinkov - - - - Pause all folders - Pozastaviť všetky priečinky - - - - Unpause all synchronization - Zrušiť pozastavenie všetkej synchronizácie - - - - Unpause synchronization - Zrušiť pozastavenie synchronizácie - - - - Pause all synchronization - Pozastaviť všetku synchronizáciu - - - - Pause synchronization - Pozastaviť synchronizáciu - - - - Log out of all accounts - Odhlásiť sa zo všetkých účtov - - - - Log in to all accounts... - Prihlásiť sa do všetkých účtov... - - - - New account... - - - - - - About %1 - O %1 - - - - No items synced recently - Žiadne nedávno synchronizované položky - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - Synchronizuje sa %1 z %2 (zostáva %3) - - - - Syncing %1 of %2 - - - - - Syncing %1 (%2 left) - Synchronizuje sa %1 (zostáva %2) - - - - Syncing %1 - Synchronizuje sa %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Až do dnešného dňa - - - - OwncloudAdvancedSetupPage - - - Form - Formulár - - - - - - - - - - TextLabel - Štítok - - - - Server - Server - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - - - - - Start a &clean sync (Erases the local folder!) - Spustiť novú synchronizáciu (Vymaže obsah lokálneho priečinka!) - - - - Ask for confirmation before synchroni&zing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - - - - - Choose what to sync - Vybrať čo synchronizovať - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - &Lokálny priečinok - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Nechať lokálne dáta - - - - S&ynchronize everything from server (recommended) - - - - - Use virtual files instead of downloading content immediately (e&xperimental) - - - - - Status message - Správa o stave - - - - OwncloudHttpCredsPage - - - Form - Formulár - - - - &Username - &Používateľské meno - - - - &Password - &Heslo - - - - OwncloudOAuthCredsPage - - - Form - Formulár - - - - Please switch to your browser to proceed. - - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - - - - - OwncloudSetupPage - - - Form - Formulár - - - - - TextLabel - Štítok - - - - Ser&ver Address - - - - - https://... - https://... - - - - Error Label - Popisok chyby - - - - OwncloudWizardResultPage - - - Form - Formulár - - - - TextLabel - Štítok - - - - Your entire account is synced to the local folder - Celý váš účet bol zosynchronizovaný z lokálnym priečinkom - - - - - PushButton - Tlačidlo - - - - QObject - - - in the future - v budúcnosti - - - - %n day(s) ago - pred %n dňompred %n dňamipred %n dňamipred %n dňami - - - - %n hour(s) ago - pred %n hodinoupred %n hodinamipred %n hodinamipred %n hodinami - - - - now - teraz - - - - Less than a minute ago - Menej ako pred minútou - - - - %n minute(s) ago - pred %n minútoupred %n minútamipred %n minútamipred %n minútami - - - - Some time ago - Pred istým časom - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n rok%n roky%n rokov%n rokov - - - - %n month(s) - %n mesiac%n mesiace%n mesiacov%n mesiacov - - - - %n day(s) - %n deň%n dni%n dní%n dní - - - - %n hour(s) - %n hodina%n hodiny%n hodín%n hodín - - - - %n minute(s) - %n minúta%n minúty%n minút%n minút - - - - %n second(s) - %n sekunda%n sekundy%n sekúnd%n sekúnd - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Systémová lišta "tray" neprístupná - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 vyžaduje funkčnú systémovú lištu "tray". Pokiaľ používate prostredie XFCE, prosím pokračujte <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">podľa týchto inštrukcií</a>. Inak si prosím nainštalujte aplikáciu systémovej lišty "tray" ako napr. 'trayer' a skúste to znova. - - - - ownCloudTheme::about() - - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - <p><small>Zostavené z Git revízie <a href="%1">%2</a> na %3, %4 s použitím Qt %5, %6</small></p> - - - - progress - - - Downloaded - Stiahnuté - - - - Uploaded - Odoslané - - - - Server version downloaded, copied changed local file into conflict file - Verzia zo servera bola stiahnutá, kópia zmenila lokálny súbor na konfliktný súbor - - - - Deleted - Zmazané - - - - Moved to %1 - Presunuté do %1 - - - - Ignored - Ignorované - - - - Filesystem access error - Chyba prístupu k súborovému systému - - - - Error - Chyba - - - - Updated local metadata - - - - - - Unknown - Neznámy - - - - downloading - sťahujem - - - - uploading - nahrávam - - - - deleting - vymazávanie - - - - moving - presúvanie - - - - ignoring - ignorujem - - - - - error - chyba - - - - updating local metadata - - - - - theme - - - Status undefined - Nedefinovaný status - - - - Waiting to start sync - Čakanie na začiatok synchronizácie - - - - Sync is running - Prebieha synchronizácia - - - - Sync Success - Úspešná synchronizácia - - - - Sync Success, some files were ignored. - Synchronizácia prebehla úspešne, niektoré súbory boli ignorované. - - - - Sync Error - Chyba synchronizácie - - - - Setup Error - Chyba inštalácie - - - - Preparing to sync - Príprava na synchronizáciu - - - - Aborting... - Ruším... - - - - Sync is paused - Synchronizácia je pozastavená - - - - utility - - - Could not open browser - - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - - - \ No newline at end of file diff --git a/translations/client_sl.ts b/translations/client_sl.ts deleted file mode 100644 index 2a275e9fd9e..00000000000 --- a/translations/client_sl.ts +++ /dev/null @@ -1,4661 +0,0 @@ - - - FileSystem - - - Could not make directories in trash - V smeteh ni mogoče ustvarjati map - - - - - Could not move '%1' to '%2' - Premikanje »%1« v »%2« ni mogoče. - - - - Moving to the trash is not implemented on this platform - Premikanje v smeti v tem okolju ni podprto - - - - Error removing '%1': %2 - - - - - Could not remove folder '%1' - Ni mogoče odstraniti mape '%1' - - - - FolderWizardSourcePage - - - Form - Obrazec - - - - Pick a local folder on your computer to sync - Izberite krajevno mapo za usklajevanje. - - - - &Choose... - &Izberi ... - - - - FolderWizardTargetPage - - - Form - Obrazec - - - - Select a remote destination folder - Izberite oddaljeno ciljno mapo. - - - - Create Folder - Ustvari mapo - - - - Refresh - Osveži - - - - Folders - Mape - - - - TextLabel - Oznaka - - - - NotificationWidget - - - Form - Obrazec - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Besedilna oznaka - - - - OCC::AboutDialog - - - About - O programu... - - - - TextLabel - Besedilna oznaka - - - - Versions - Različice - - - - About %1 - O %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Povezava je časovno potekla - - - - Unknown error: network reply was deleted - Neznana napaka: odziv strežnika je bil izbrisan - - - - Server replied "%1 %2" to "%3 %4" - Strežnik je odgovoril "%1 %2" v "%3 %4" - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - Obrazec - - - - Re-open Browser - - - - - ... - ... - - - - Storage space: ... - Prostor: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Neizbrane mape bodo <b>odstranjene</b> iz krajevnega datotečnega sistema in s tem računalnikom ne bodo več usklajevane! - - - - Synchronize all - Uskladi vse - - - - Synchronize none - Ne uskladi ničesar - - - - Apply manual changes - Uveljavi ročno narejene spremembe - - - - Apply - Uveljavi - - - - - - - Cancel - Prekliči - - - - Connected with <server> as <user> - Vzpostavljena je povezava s strežnikom <server> kot <user> - - - - No account configured. - Ni nastavljenega računa. - - - - Remove - Odstrani - - - - Account - Račun - - - - Choose what to sync - Izbor predmetov za usklajevanje - - - - Force sync now - Vsili takojšnje usklajevanje - - - - Restart sync - Ponovno zaženi usklajevanje - - - - Remove folder sync connection - Odstrani povezavo mape usklajevanja - - - - Folder creation failed - Ustvarjanje mape je spodletelo - - - - <p>Could not create local folder <i>%1</i>. - <p>Ni mogoče ustvariti krajevne mape <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Potrdi odstranjevanje povezave usklajevanja mape - - - - Remove Folder Sync Connection - Odstrani povezavo usklajevanja mape - - - - Disable virtual file support? - - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - - - - - Sync Running - Usklajevanje je v teku - - - - The syncing operation is running.<br/>Do you want to terminate it? - Izvaja se usklajevanje.<br/>Ali želite opravilo prekiniti? - - - - %1 in use - %1 v uporabi - - - - %1 as <i>%2</i> - %1 kot <i>%2</i> - - - - Connected to %1. - Povezano z %1. - - - - Server %1 is temporarily unavailable. - Strežnik %1 trenutno ni dosegljiv. - - - - Server %1 is currently in maintenance mode. - Strežnik %1 je trenutno v vzdrževalnem načinu. - - - - Signed out from %1. - Uspešno odjavljeno iz %1. - - - - Connecting to %1... - Poteka vzpostavljanje povezave s strežnikom %1 ... - - - - No connection to %1 at %2. - Ni povezave z %1 pri %2. - - - - Log in - Prijava - - - - There are folders that were not synchronized because they are too big: - Zaznane so mape, ki zaradi omejitve velikosti niso bile usklajene: - - - - There are folders that were not synchronized because they are external storages: - Zaznane so mape, ki zaradi pripadnosti zunanji shrambi niso bile usklajene: - - - - There are folders that were not synchronized because they are too big or external storages: - Zaznane so mape, ki zaradi omejitve velikosti ali pripadnosti zunanji shrambi niso bile usklajene: - - - - Confirm Account Removal - Potrdi odstranitev računa - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Ali res želite odstraniti povezavo z računom <i>%1</i>?</p><p><b>Opomba:</b> S tem <b>ne bo</b> izbrisana nobena datoteka.</p> - - - - Remove connection - Odstrani povezavo - - - - Open folder - Odpri mapo - - - - - Log out - Odjava - - - - Open local folder - - - - - Open folder in browser - - - - - Resume sync - Nadaljuj z usklajevanjem - - - - Pause sync - Premor usklajevanja - - - - Availability - - - - - Disable virtual file support... - - - - - Enable virtual file support%1... - - - - - (experimental) - - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Ali res želite zaustaviti usklajevanje mape <i>%1</i>?</p><p><b>Opomba:</b> s tem datoteke iz odjemalca <b>ne bodo</b> odstranjene.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) od %2 v uporabi. Nekatere mape, vključno s priklopljenimi mapami in mapami v souporabi, imajo morda različne omejitve. - - - - %1 of %2 in use - %1 od %2 v uporabi - - - - Currently there is no storage usage information available. - Trenutno ni na voljo nobenih podatkov o porabi prostora. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - Obtaining authorization from the browser. - - - - - Server configuration error: %1 at %2. - - - - - No %1 connection configured. - Ni nastavljene povezave %1. - - - - OCC::AccountState - - - Signed out - Odjavljeno - - - - Disconnected - Brez povezave - - - - Connected - Povezano - - - - Service unavailable - Storitev ni na voljo - - - - Maintenance mode - Vzdrževalni način - - - - Network error - Omrežna napaka - - - - Configuration error - Napaka nastavitve - - - - Asking Credentials - Zahteva po poverilih - - - - Unknown account state - Neznano stanje računa - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 na %2 - - - - %1 on %2 (disconnected) - %1 na %2 (brez povezave) - - - - OCC::ActivityListModel - - - %1 %2 on %3 - - - - - OCC::ActivitySettings - - - - Server Activity - Dejavnost strežnika - - - - Sync Protocol - Protokol usklajevanja - - - - Not Synced - Ni usklajeno - - - - Not Synced (%1) - %1 is the number of not synced files. - Ni usklajeno (%1) - - - - The server activity list has been copied to the clipboard. - Seznam opravil strežnika je kopiran v odložišče. - - - - The sync activity list has been copied to the clipboard. - Seznam opravil usklajevanja je kopiran v odložišče. - - - - The list of unsynced items has been copied to the clipboard. - Seznam neusklajenih predmetov je kopiran v odložišče. - - - - Copied to clipboard - Kopirano v odložišče - - - - OCC::ActivityWidget - - - Form - Obrazec - - - - - - TextLabel - Besedilna oznaka - - - - Server Activities - Dejavnosti strežnika - - - - Copy - Kopiraj - - - - Copy the activity list to the clipboard. - Kopiraj seznam opravil v odložišče. - - - - Notifications - Obvestila - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - - - - - Open the activity view for details. - - - - - <br/>Account %1 does not have activities enabled. - <br/>Za račun %1 možnosti opravil niso omogočene. - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Overitev odjemalca s potrdilom SSL - - - - This server probably requires a SSL client certificate. - Ta strežnik verjetno zahteva potrdilo SSL odjemalca. - - - - Certificate & Key (pkcs12) : - Potrdilo in ključ (pkcs12) : - - - - Browse... - Prebrskaj ... - - - - Certificate password : - Geslo potrdila: - - - - An encrypted pkcs12 bundle is strongly recommended as a copy will be stored in the configuration file. - - - - - Select a certificate - Izberi potrdilo - - - - Certificate files (*.p12 *.pfx) - Vrste potrdil (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Končaj - - - - Continue - Nadaljuj - - - - Error accessing the configuration file - Napaka dostopa do nastavitvene datoteke - - - - There was an error while accessing the configuration file at %1. - Med dostopom do nastavitvene datoteke na %1 je prišlo do napake. - - - - Quit ownCloud - Končaj ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - Zahtevana je overitev - - - - Enter username and password for '%1' at %2. - Vpišite uporabniško ime in geslo za '%1' pri %2. - - - - &User: - &Uporabnik: - - - - &Password: - &Geslo: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Napaka zapisovanja metapodatkov v podatkovno zbirko - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Ni nastavljenega računa v oblaku ownCloud - - - - The configured server for this client is too old - Nastavljen strežnik tega odjemalca je prestar. - - - - Please update to the latest server and restart the client. - Posodobite strežnik in ponovno zaženite odjemalca. - - - - Authentication error: Either username or password are wrong. - Napaka overitve: uporabniško ime ali geslo je napačno. - - - - timeout - časovni zamik - - - - The provided credentials are not correct - Podana poverila niso pravilna. - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - - - - - Directory not accessible on client, permission denied - - - - - Directory not found: %1 - - - - - Filename encoding is not valid - Kodni zapis imena datoteke ni veljaven. - - - - Error while reading directory %1 - - - - - OCC::Folder - - - Local folder %1 does not exist. - Krajevna mapa %1 ne obstaja. - - - - %1 should be a folder but is not. - %1 bi morala biti mapa, pa ni. - - - - %1 is not readable. - %1 ni mogoče brati. - - - - %1 has been removed. - %1 names a file. - Datoteka %1 je odstranjena. - - - - %1 has been updated. - %1 names a file. - Datoteka %1 je posodobljena. - - - - %1 has been renamed to %2. - %1 and %2 name files. - Datoteka %1 je preimenovana v %2. - - - - %1 has been moved to %2. - Datoteka %1 je premaknjena v %2. - - - - %1 and %n other file(s) have been removed. - Datoteka %1 in še %n druga datoteka je bila izbrisana.Datoteka %1 in še %n drugi datoteki sta bili izbrisani.Datoteka %1 in še %n druge datoteke so bile izbrisane.Datoteka %1 in še %n drugih datotek je bilo izbrisanih. - - - - %1 and %n other file(s) have been added. - - - - - %1 has been added. - %1 names a file. - - - - - %1 and %n other file(s) have been updated. - Datoteka %1 in še %n druga datoteka je bila posodobljena.Datoteka %1 in še %n drugi datoteki sta bili posodobljeni.Datoteka %1 in še %n druge datoteke so bile posodobljene.Datoteka %1 in še %n drugih datotek je bilo posodobljenih. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - Datoteka %1 je bila preimenovana v %2 in še %n druga datoteka je bila preimenovana.Datoteka %1 je bila preimenovana v %2 in še %n drugi datoteki sta bili preimenovani.Datoteka %1 je bila preimenovana v %2 in še %n druge datoteke so bile preimenovane.Datoteka %1 je bila preimenovana v %2 in še %n drugih datotek je bilo preimenovanih. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - Datoteka %1 je bila premaknjena v %2 in še %n druga datoteka je bila premaknjena.Datoteka %1 je bila premaknjena v %2 in še %n drugi datoteki sta bili premaknjeni.Datoteka %1 je bila premaknjena v %2 in še %n druge datoteke so bile premaknjene.Datoteka %1 je bila premaknjena v %2 in še %n drugih datotek je bilo premaknjenih. - - - - %1 has and %n other file(s) have sync conflicts. - Pri datoteki %1 in še %n drugi datoteki je zaznan spor usklajevanja.Pri datoteki %1 in še %n drugih datotekah je zaznan spor usklajevanja.Pri datoteki %1 in še %n drugih datotekah je zaznan spor usklajevanja.Pri datoteki %1 in še %n drugih datotekah je zaznan spor usklajevanja. - - - - %1 has a sync conflict. Please check the conflict file! - Pri datoteki %1 je zaznan spor usklajevanja. Preverite datoteko! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - Datoteke %1 in še %n druge datoteke ni mogoče uskladiti zaradi napak. Podrobnosti so zapisane v dnevniški datoteki.Datoteke %1 in še %n drugih datotek ni mogoče uskladiti zaradi napak. Podrobnosti so zapisane v dnevniški datoteki.Datoteke %1 in še %n drugih datotek ni mogoče uskladiti zaradi napak. Podrobnosti so zapisane v dnevniški datoteki.Datoteke %1 in še %n drugih datotek ni mogoče uskladiti zaradi napak. Podrobnosti so zapisane v dnevniški datoteki. - - - - %1 could not be synced due to an error. See the log for details. - Datoteke %1 zaradi napake ni mogoče uskladiti. Več podrobnosti je zabeleženih v dnevniški datoteki. - - - - Sync Activity - Dejavnost usklajevanja - - - - Could not read system exclude file - Ni mogoče prebrati sistemske izločitvene datoteke - - - - A new folder larger than %1 MB has been added: %2. - - Dodana je nova mapa, ki presega %1 MB: %2. - - - - - A folder from an external storage has been added. - - Dodana je mapa iz zunanje shrambe. - - - - - Please go in the settings to select it if you wish to download it. - Med nastavitvami jo lahko izberete in označite za prejem. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Vse datoteke v usklajevani mapi »%1« so bile na strežniku izbrisane. -Sprememba bo usklajena tudi s krajevno mapo na disku, zato bodo te datoteke, če ni ustreznih dovoljenj za obnovitev, izgubljene. -V kolikor se odločite te datoteke ohraniti, in so na voljo ustrezna dovoljenja, bodo spet usklajene s strežnikom. -Nasprotno, če potrdite izbris in niste lastnik datotek, te ne bodo več na voljo. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Vse datoteke v usklajevani mapi »%1« so bile izbrisane. Sprememba bo usklajena s strežnikom, zato bodo datoteke, če ni ustreznih dovoljenj za obnovitev, nedostopne. -Ali ste prepričani, da želite posodobiti spremembe s strežnikom? -Če je prišlo do napake in se odločite datoteke ohraniti, bodo te ponovno usklajene s strežnika. - - - - Remove All Files? - Ali naj bodo odstranjene vse datoteke? - - - - Remove all files - Odstrani vse datoteke - - - - Keep files - Ohrani datoteke - - - - OCC::FolderMan - - - Could not reset folder state - Ni mogoče ponastaviti stanja mape - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Obstaja starejši dnevnik usklajevanja '%1', vendar ga ni mogoče odstraniti. Preverite, ali je datoteka v uporabi. - - - - (backup) - (varnostna kopija) - - - - (backup %1) - (varnostna kopija %1) - - - - Undefined State. - Nedoločeno stanje. - - - - Waiting to start syncing. - Čakanje začetek usklajevanja - - - - Preparing for sync. - Poteka priprava za usklajevanje. - - - - Sync is running. - Usklajevanje je v teku. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Zadnje usklajevanje je bilo uspešno končano. - - - - Setup Error. - Napaka nastavitve. - - - - User Abort. - Uporabniška prekinitev. - - - - Sync is paused. - Usklajevanje je začasno v premoru. - - - - %1 (Sync is paused) - %1 (usklajevanje je v premoru) - - - - No valid folder selected! - Ni izbrane veljavne mape! - - - - The selected path does not exist! - - - - - The selected path is not a folder! - Izbrana pot ni mapa! - - - - You have no permission to write to the selected folder! - Ni ustreznih dovoljenj za pisanje v izbrano mapo! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Za to krajevno pot je že ustvarjeno mesto za usklajevanje. Izbrati je treba drugo. - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Krajevna mapa %1 že vključuje mapo, ki je določena za usklajevanje. Izbrati je treba drugo. - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Krajevna mapa %1 je že v določena za usklajevanje. Izbrati je treba drugo. - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Dodaj povezavo za usklajevanje mape - - - - File - Datoteka - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Za dodajanje mape mora biti vzpostavljea povezava - - - - Click this button to add a folder to synchronize. - Kliknite za dodajanje mape za usklajevanje. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Prišlo je do napake med nalaganjem datotek s strežnika. - - - - Virtual file support is enabled. - - - - - Signed out - Odjavljeno - - - - Fetching folder list from server... - Poteka pridobivanje seznama map s strežnika ... - - - - There are unresolved conflicts. Click for details. - - - - - Synchronizing VirtualFiles with local folder - - - - - Synchronizing with local folder - Poteka usklajevanje s krajevno mapo - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Usklajevanje %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - prejemanje %1/s - - - - ↓ %1/s - - - - - ↑ %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - pošiljanje %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 od %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - Preostalo še %5, %1 od %2, datoteka %3 od %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 od %2, datoteka %3 od %4 - - - - file %1 of %2 - datoteka %1 od %2 - - - - Waiting... - Čakanje na povezavo ... - - - - Waiting for %n other folder(s)... - V pripravi je %n druga map ...V pripravi sta %n drugi mapi ...V pripravi so %n druge mape ...V pripravi je %n drugih map ... - - - - Preparing to sync... - Priprava na usklajevanje ... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Dodaj povezavo za usklajevanje mape - - - - Add Sync Connection - Dodaj povezavo za usklajevanje - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Kliknite za izbor krajevne mape za usklajevanje. - - - - Enter the path to the local folder. - Vpišite pot do krajevne mape. - - - - Select the source folder - Izbor izvorne mape - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Ustvari oddaljeno mapo - - - - Enter the name of the new folder to be created below '%1': - Vnesite ime nove mape, ki bo ustvarjena kot podrejena '%1': - - - - Folder was successfully created on %1. - Mapa je uspešno ustvarjena na %1. - - - - Authentication failed accessing %1 - Overitev dostopa do %1 je spodletela. - - - - Failed to create the folder on %1. Please check manually. - Ustvarjanje mape na %1 je spodletelo. Morda jo je mogoče ustvariti ročno. - - - - Failed to list a folder. Error: %1 - Napaka izpisovanja datotek mape. Napaka: %1 - - - - Choose this to sync the entire account - Izberite možnost za usklajevanje celotnega računa. - - - - This folder is already being synced. - Ta mapa je že določena za usklajevanje. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Datoteke se že usklajujejo na ravni mape <i>%1</i>, ki je nadrejena mapi <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Opozorilo:</b> %1 - - - - <b>Warning:</b> - <b>Opozorilo:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Ni prejete oznake s strežnika. Preveriti je treba podatke posredovalnega strežnika ali prehoda. - - - - We received a different E-Tag for resuming. Retrying next time. - Prejeta je različna oznaka za nadaljevanje opravila. Ponovni poskus bo izveden kasneje. - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - Odziv strežnika kaže na neveljaven obseg vsebine - - - - OCC::GETJob - - - Connection Timeout - Povezava je časovno potekla - - - - OCC::GeneralSettings - - - Form - Obrazec - - - - General Settings - Splošne nastavitve - - - - Sync hidden files - Uskladi tudi skrite datoteke - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Omrežje - - - - Updates - Posodobitve - - - - stable - - - - - beta - - - - - About - O programu... - - - - Advanced - Napredne možnosti - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Vprašaj za potrditev pred usklajevanjem map, večjih od - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Vprašaj za potrditev pred usklajevanjem zunanjih shramb - - - - &Launch on System Startup - Zaženi ob &zagonu sistema - - - - Show &Desktop Notifications - Pokaži opozorila na namizju - - - - &Update Channel - - - - - Edit &Ignored Files - Uredi &prezrte datoteke - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - Pokaži &poročilo sesutja - - - - &Restart && Update - &Ponovno zaženi && posodobi - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Prekliči - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Vnesite geslo %1:<br><br>Uporabnik: %2<br>Račun: %3<br> - - - - Reading from keychain failed with error: '%1' - Branje iz ključa je spodletelo z napako: '%1' - - - - Enter Password - Vnos gesla - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Kliknite tu</a> za dodelitev gesla za program prek spletnega vmesnika. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Urejevalnik prezrtih datotek - - - - Files Ignored by Patterns - Datoteke, prezrte po vzorcu - - - - Add - Dodaj - - - - Pattern - Vzorec - - - - Allow Deletion - Dovoli brisanje - - - - Remove - Odstrani - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Datoteke ni mogoče odpreti - - - - Cannot write changes to '%1'. - Ni mogoče zapisati sprememb v '%1'. - - - - Add Ignore Pattern - Dodaj vzorec za izpuščanje - - - - Add a new ignore pattern: - Dodaj nov vzorec za izpuščanje: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Ta vnos je ponujen pri '%1' in ga v tem pogledu ni mogoče spreminjati. - - - - OCC::IssuesWidget - - - Form - Obrazec - - - - List of issues - Seznam napak in neskladij - - - - Account - Račun - - - - - <no filter> - <no filter> - - - - - Folder - Mapa - - - - Show warnings - Pokaži opozorila - - - - Show ignored files - Pokaži prezrte datoteke - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - Kopiraj seznam napak v odložišče. - - - - Copy - Kopiraj - - - - Time - Čas - - - - File - Datoteka - - - - Issue - Napaka - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LibCloudProvidersPrivate - - - Settings - Nastavitve - - - - OCC::LogBrowser - - - Log Output - Beleži odvod - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Odpri mapo - - - - OCC::NSISUpdater - - - New Version Available - Na voljo je novejša različica - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Na voljo je nova različica odjemalca %1.</p><p><b>%2</b> je na voljo za prejem. Trenutno je nameščena različica %3.</p> - - - - - Skip this version - Preskoči to različico - - - - Skip this time - Posodobitev tokrat preskoči - - - - Get update - Pridobi posodobitve - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - Obrazec - - - - Proxy Settings - Nastavitve posredniškega strežnika - - - - No Proxy - Brez posredniškega strežnika - - - - Use system proxy - Uporabi sistemski posredniški strežnik - - - - Specify proxy manually as - Določi posredniški strežnik ročno kot - - - - Host - Gostitelj - - - - : - : - - - - Proxy server requires authentication - Posredniški strežnik zahteva overitev - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Pasovna širina prejemanja - - - - - Limit to - Omeji na - - - - - KBytes/s - KBajtov/s - - - - - No limit - Ni omejitve - - - - - Limit to 3/4 of estimated bandwidth - Omeji prenos na 3/4 ocenjene pasovne širine - - - - Upload Bandwidth - Pasovna širina pošiljanja - - - - - Limit automatically - Samodejno omeji - - - - Hostname of proxy server - Ime gostitelja posredniškega strežnika - - - - Username for proxy server - Uporabniško ime za posredniški strežnik - - - - Password for proxy server - Geslo za posredniški strežnik - - - - HTTP(S) proxy - Posredniški strežnik HTTP(S) - - - - SOCKS5 proxy - Posredniški strežnik SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - Ustvarjeno ob %1$s - - - - Closing in a few seconds... - V nekaj sekundah bo izvedeno zapiranje ... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 – spodletelo ob %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' izbrano na %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Empty JSON from OAuth2 redirect - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Error: Missing field %1 - - - - - - <h1>Login Error</h1><p>%1</p> - - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - Na voljo je posodobitev %1 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Nameščena bo najnovejša posodobitev %1. Program lahko -zahteva skrbniška dovoljenja za dokončanje opravila. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Ni mogoče prejeti posodobitve. Kliknite <a href='%1'>here</a> za ročni prejem in namestitev. - - - - Could not check for new updates. - Ni mogoče preveriti za nove posodobitve. - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - Preverjanje strežnika za posodabljanje ... - - - - Update status is unknown: Did not check for new updates. - Stanje posodobitve ni znano: ni mogoče preveriti za nove posodobitve. - - - - No updates available. Your installation is at the latest version. - Na voljo ni novih posodobitev. Nameščena je najnovejša različica. - - - - Update Check - Preverjanje za posodobitve - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Vzpostavi povezavo s strežnikom %1 - - - - Setup local folder options - Nastavi možnosti krajevne mape - - - - Connect... - Vzpostavi povezavo ... - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - %1 folder '%2' is synced to local folder '%3' - %1 mapa '%2' je usklajena s krajevno mapo '%3' - - - - Sync the folder '%1' - Uskladi mapo '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>Opozorilo:</strong> krajevna mapa ni prazna. Izberite možnost za razrešitev problema!</small></p> - - - - Virtual files are not available for the selected folder - - - - - Local Sync Folder - Krajevna mapa usklajevanja - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Povezava je spodletela - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Povezovanje z navedenim naslovom varnega strežnika je spodletelo. Kako želite nadaljevati?</p></body></html> - - - - Select a different URL - Vpisati je treba drug naslov URL. - - - - Retry unencrypted over HTTP (insecure) - Ponovno poskusi nešifriran prenos prek HTTP (ni varna povezava) - - - - Configure client-side TLS certificate - Nastavitev odjemalčevega potrdila TLS - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Povezovanje z navedenim naslovom varnega strežnika <em>%1</em> je spodletelo. Kako želite nadaljevati?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Elektronski naslov - - - - Connect to %1 - Vzpostavi povezavo s strežnikom %1 - - - - Enter user credentials - Vpiši uporabniška poverila - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Vzpostavi povezavo s strežnikom %1 - - - - Login in your browser - Prijava prek brskalnika - - - - Copy link to clipboard - Kopiraj povezavo v odložišče - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Vzpostavi povezavo s strežnikom %1 - - - - Setup %1 server - Nastavi strežnik %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Na naslov URL ni varen, saj ni šifriran. -Uporaba ni priporočljiva. - - - - This url is secure. You can use it. - Ta naslov URL je varen za uporabo. - - - - &Next > - &Naslednja > - - - - Invalid URL - Neveljaven naslov URL - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Povezava z %1 pri %2 je spodletela:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Prekinitev med poskusom povezave na %1 pri %2. - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - Zahteva za overitev s strežnikom je bila preusmerjena na '%1'. Naslov URL ni veljaven ali pa strežnik ni ustrezno nastavljen. - - - - There was an invalid response to an authenticated webdav request - Prejet je neveljaven odziv na zahtevo overitve webdav - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - Strežnik ne dovoli dostopa. Če želite preveriti, ali imate ustrezen dostop, <a href="%1">kliknite tu</a> za dostop do te storitve z brskalnikom. - - - - Invalid URL - Neveljaven naslov URL - - - - Could not create local folder %1 - Krajevne mape %1 ni mogoče ustvariti. - - - - No remote folder specified! - Ni navedenega oddaljenega strežnika! - - - - Error: %1 - Napaka: %1 - - - - The folder creation resulted in HTTP error code %1 - Ustvarjanje mape je povzročilo napako HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Ustvarjanje mape na oddaljenem naslovu je spodletelo zaradi napačnih poveril. <br/>Vrnite se in preverite zahtevana gesla.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Ustvarjanje oddaljene mape %1 je spodletelo z napako <tt>%2</tt>. - - - - Folder rename failed - Preimenovanje mape je spodletelo - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Mape ni mogoče odstraniti niti ni mogoče ustvariti varnostne kopije, saj je mapa oziroma dokument v njej odprt z drugim programom. Zaprite mapo/dokument ali prekinite namestitev. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Čarovnik za povezavo %1 - - - - Enable experimental feature? - - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Vse je pripravljeno za uporabo! - - - - Open Local Folder - Odpri krajevno mapo - - - - Open %1 in Browser - Odpri %1 v brskalniku - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - Neveljaven odziv JSON preverjanja naslova URL - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Usklajevanje simbolnih povezav ni podprto. - - - - File is listed on the ignore list. - Datoteka je na seznamu prezrtih datotek. - - - - File names ending with a period are not supported on this file system. - Imena datotek, ki vsebujejo piko, na tem sistemu niso podprta. - - - - File names containing the character '%1' are not supported on this file system. - Imena datotek, ki vsebujejo znak »%1«, na tem sistemu niso podprta. - - - - File name contains at least one invalid character - Ime datoteke vsebuje vsaj en neveljaven znak. - - - - The file name is a reserved name on this file system. - Ime datoteke je na tem sistemu zadržano za sistemsko datoteko. - - - - Filename contains trailing spaces. - Datoteka vsebuje pripete presledne znake - - - - Filename is too long. - Ime datoteke je predolgo. - - - - File/Folder is ignored because it's hidden. - Datoteka/Mapa je prezrta, ker je skrita. - - - - Stat failed. - Določanje statističnih podatkov je spodletelo. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - The filename cannot be encoded on your file system. - - - - - The filename is blacklisted on the server. - - - - - File has extension reserved for virtual files. - - - - - size - - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - Odstranitev ni dovoljena, datoteka bo obnovljena. - - - - Ignored because of the "choose what to sync" blacklist - Prezrto, ker je predmet označen na črni listi za usklajevanje - - - - Not allowed because you don't have permission to add subfolders to that folder - Dejanje ni dovoljeno! Ni ustreznih dovoljenj za dodajanje podmap v to mapo. - - - - Not allowed because you don't have permission to add files in that folder - Dejanje ni dovoljeno, ker ni ustreznih dovoljenj za dodajanje datotek v to mapo - - - - Not allowed to upload this file because it is read-only on the server, restoring - Ni dovoljeno pošiljati te datoteke, ker ima določena dovoljenja le za branje. Datoteka bo obnovljena na izvorno različico. - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Napaka zapisovanja metapodatkov v podatkovno zbirko - - - - OCC::PropagateDownloadFile - - - - File %1 can not be downloaded because of a local file name clash! - Datoteke %1 ni mogoče prejeti zaradi neskladja z imenom krajevne datoteke! - - - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - Na disku je prostora manj kot %1 - - - - File was deleted from server - Datoteka je izbrisana s strežnika - - - - The file could not be downloaded completely. - Datoteke ni mogoče prejeti v celoti. - - - - The downloaded file is empty despite the server announced it should have been %1. - Prejeta datoteka je prazna, čeprav je na strežniku velikosti %1. - - - - File %1 cannot be saved because of a local file name clash! - Datoteke %1 ni mogoče shraniti zaradi neskladja z imenom obstoječe datoteke! - - - - - File has changed since discovery - Datoteka je bila spremenjena po usklajevanju seznama datotek - - - - Error writing metadata to the database - Napaka zapisovanja metapodatkov v podatkovno zbirko - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; obnovitev je spodletela: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - Datoteka ali mapa je bila odstranjena iz mesta v souporabi, ki je nastavljeno le za branje, obnavljanje pa je spodletelo: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - ni mogoče izbrisati datoteke %1, napaka: %2 - - - - Attention, possible case sensitivity clash with %1 - Pozor, mogoče je neskladje v velikosti črk imena %1 - - - - could not create folder %1 - ni mogoče ustvariti mape %1 - - - - Error writing metadata to the database - Napaka zapisovanja metapodatkov v podatkovno zbirko - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash - Ni mogoče odstraniti %1 zaradi neskladja s krajevnim imenom datoteke - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Datoteke %1 ni mogoče preimenovati v %2 zaradi že obstoječe datoteke s tem imenom. - - - - - Error writing metadata to the database - Napaka zapisovanja metapodatkov v podatkovno zbirko - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - S strežnika je vrnjen neveljaven odziv HTTP. Pričakovan je 204, prejet pa je bil "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - S strežnika je vrnjen neveljaven odziv HTTP. Pričakovan je 201, prejet pa je bil "%1 %2". - - - - Error writing metadata to the database - Napaka zapisovanja metapodatkov v podatkovno zbirko - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - S strežnika je vrnjen neveljaven odziv HTTP. Pričakovan je 201, prejet pa je bil "%1 %2". - - - - - Error writing metadata to the database - Napaka zapisovanja metapodatkov v podatkovno zbirko - - - - Error setting pin state - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Datoteke %1 ni mogoče naložiti, saj obstaja druga, istoimenska datoteka, ki se od nje razlikuje le po velikih črkah v imenu. - - - - File Removed - Datoteka je odstranjena - - - - Local file changed during syncing. It will be resumed. - Krajevna datoteka je bila med usklajevanjem spremenjena. Usklajena bo, ko bo shranjena. - - - - Local file changed during sync. - Krajevna datoteka je bila med usklajevanjem spremenjena. - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - Napaka zapisovanja metapodatkov v podatkovno zbirko - - - - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - Krajevna datoteka je bila med usklajevanjem odstranjena. - - - - Local file changed during sync. - Krajevna datoteka je bila med usklajevanjem spremenjena. - - - - Poll URL missing - Preveri manjkajoči naslov URL - - - - Unexpected return code from server (%1) - Napaka: nepričakovan odziv s strežnika (%1). - - - - Missing File ID from server - Na strežniku manjka ID datoteke - - - - Missing ETag from server - Na strežniku manjka ETag datoteke - - - - OCC::PropagateUploadFileTUS - - - The local file was removed during sync. - Krajevna datoteka je bila med usklajevanjem odstranjena. - - - - Local file changed during sync. - Krajevna datoteka je bila med usklajevanjem spremenjena. - - - - OCC::PropagateUploadFileV1 - - - Poll URL missing - Preveri manjkajoči naslov URL - - - - The local file was removed during sync. - Krajevna datoteka je bila med usklajevanjem odstranjena. - - - - Local file changed during sync. - Krajevna datoteka je bila med usklajevanjem spremenjena. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Strežnik ne sprejme zadnjega paketa (ni navedene e-oznake) - - - - OCC::ProtocolWidget - - - Form - Obrazec - - - - TextLabel - Besedilna oznaka - - - - Open in browser - Odpri v brskalniku - - - - Time - Čas - - - - File - Datoteka - - - - Folder - Mapa - - - - Action - Dejanje - - - - Size - Velikost - - - - Local sync protocol - Krajevni protokol usklajevanja - - - - Copy - Kopiraj - - - - Copy the activity list to the clipboard. - Kopiraj seznam opravil v odložišče. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Zahtevana je overitev posredniškega strežnika - - - - Username: - Uporabniško ime: - - - - Proxy: - Posredniški strežnik: - - - - The proxy server needs a username and password. - Posredniški strežnik zahteva uporabniško ime in geslo. - - - - Password: - Geslo: - - - - TextLabel - Besedilna oznaka - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Izbor map za usklajevanje - - - - OCC::SelectiveSyncWidget - - - Loading ... - Poteka nalaganje ... - - - - Deselect remote folders you do not wish to synchronize. - Odstranite izbor oddaljenih map, ki jih ne želite usklajevati. - - - - Name - Ime - - - - Size - Velikost - - - - - No subfolders currently on the server. - Na strežniku trenutno ni podrejenih map. - - - - An error occurred while loading the list of sub folders. - Prišlo je do napake med nalaganjem seznama podrejenih map. - - - - OCC::ServerNotificationHandler - - - Dismiss - Opusti - - - - OCC::SettingsDialog - - - Settings - Nastavitve - - - - Activity - Dejavnosti - - - - Add account - Dodaj račun - - - - - Quit %1 - Končaj %1 - - - - Are you sure you want to quit %1? - - - - - Account - Račun - - - - MainWindow - - - - - Hide - Skrij - - - - OCC::ShareDialog - - - TextLabel - Besedilna oznaka - - - - share label - deli oznako - - - - Dialog - Pogovorno okno - - - - ownCloud Path: - Pot do OwnCloud: - - - - %1 Sharing - %1 souporaba datotek - - - - %1 - %1 - - - - Folder: %2 - Mapa: %2 - - - - The server does not allow sharing - Strežnik ne podpira souporabe - - - - Retrieving maximum possible sharing permissions from server... - Pridobivanje največjega mogočega števila dovoljenj za souporabo prek strežnika ... - - - - The file can not be shared because it was shared without sharing permission. - Datoteke ni mogoče dodeliti v souporabo, ker ni navedenih ustreznih dovoljenj. - - - - Users and Groups - Uporabniki in skupine - - - - Public Links - Javne povezave - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Souporaba datoteke NovDokument.odt - - - - Upload only (File Drop) - - - - - Receive files from others without revealing the contents of the folder. - - - - - Download / View / Edit - - - - - Name: - - - - - Enter name of new share... - - - - - &Create - - - - - TextLabel - Besedilna oznaka - - - - Set &password - Nastavi &geslo - - - - Set &expiration date - Nastavi datum &preteka - - - - Set password - Nastavi geslo - - - - Recipients can view or download contents. - - - - - Download / View - - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - Vsak, ki ima povezavo ima dostop do datoteke ali mape - - - - - P&assword protect - &Zaščiti z geslom - - - - The file can not be shared because it was shared without sharing permission. - Datoteke ni mogoče dodeliti v souporabo, ker je ni navedenih ustreznih dovoljenj. - - - - Link shares have been disabled - - - - - Create public link share - - - - - - Delete - Izbriši - - - - Open link in browser - Odpri povezavo v brskalniku - - - - Copy link to clipboard - Kopiraj povezavo v odložišče - - - - Copy link to clipboard (direct download) - - - - - Send link by email - Pošlji povezavo po elektronski pošti - - - - Send link by email (direct download) - - - - - Create new... - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - - - - - Cancel - Prekliči - - - - - Public link - Javna povezava - - - - Delete link share - - - - - - Please Set Password - Določite geslo - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Souporaba datoteke NovDokument.odt - - - - Share with users or groups ... - Souporaba z uporabniki ali skupinami ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - - - - - Open link in browser - Odpri povezavo v brskalniku - - - - Copy link to clipboard - Kopiraj povezavo v odložišče - - - - Send link by email - Pošlji povezavo po elektronski pošti - - - - No results for '%1' - Ni zadetkov za '%1' - - - - I shared something with you - - - - - OCC::ShareUserLine - - - Form - Obrazec - - - - - TextLabel - Besedilna oznaka - - - - can edit - lahko ureja - - - - can share - lahko omogoči souporabo - - - - ... - ... - - - - create - ustvari - - - - change - spremeni - - - - delete - izbriši - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Omogoči souporabo z %1 - - - - Context menu share - - - - - Context menu share %1 - - - - - Confirm deletion - - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - - - - - Do you want to delete the file <i>%1</i> permanently? - - - - - Select new location... - - - - - Error - Napaka - - - - Moving file failed: - -%1 - - - - - I shared something with you - - - - - - Share... - Omogoči souporabo ... - - - - - Copy private link to clipboard - - - - - Send private link by email... - - - - - Resharing this file is not allowed - - - - - Resharing this folder is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - Odpri v brskalniku - - - - Show file versions in browser - - - - - Rename... - - - - - Rename and upload... - - - - - Move and rename... - - - - - Move, rename and upload... - - - - - Delete local changes - - - - - Move and upload... - - - - - Delete - Izbriši - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Podrobnosti potrdila</h3> - - - - Common Name (CN): - Splošno ime (CN): - - - - Subject Alternative Names: - Druga imena: - - - - Organization (O): - Ustanova (U): - - - - Organizational Unit (OU): - Organizacijska enota (OE): - - - - State/Province: - Okraj: - - - - Country: - Država: - - - - Serial: - Zaporedna številka: - - - - <h3>Issuer</h3> - <h3>Izdaljatelj</h3> - - - - Issuer: - Izdaljatelj: - - - - Issued on: - Izdano na: - - - - Expires on: - Poteče na: - - - - <h3>Fingerprints</h3> - <h3>Prstni odtisi</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Opomba:</b> to potrdilo je bilo ročno odobreno</p> - - - - %1 (self-signed) - %1 (samopodpisano) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Ta povezava je šifrirana z %1 bitnim %2. - - - - - Server version: %1 - - - - - No support for SSL session tickets/identifiers - Ni podpore za določila in karte ključe SSL - - - - Certificate information: - Podrobnosti potrdila: - - - - The connection is not secure - - - - - This connection is NOT secure as it is not encrypted. - - Ta povezava NI varna, saj ni šifrirana. - - - - - OCC::SslErrorDialog - - - Form - Obrazec - - - - Trust this certificate anyway - Vseeno zaupaj digitalnemu potrdilu - - - - Untrusted Certificate - Potrdilo ni vredno zaupanja - - - - Cannot connect securely to <i>%1</i>: - Ni mogoče vzpostaviti varne povezave z <i>%1</i>: - - - - with Certificate %1 - s potrdilom %1 - - - - - - &lt;not specified&gt; - &lt;ni podano&gt; - - - - - Organization: %1 - Ustanova: %1 - - - - - Unit: %1 - Enota: %1 - - - - - Country: %1 - Država: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Prstni odtis (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Prstni odtis (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Začetek veljavnosti: %1 - - - - Expiration Date: %1 - Datum preteka: %1 - - - - Issuer: %1 - Izdajatelj: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Le %1 je na voljo, zahtevanih pa je vaj %2 za zagon - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - - - - - Unresolved conflict. - Nerazrešen spor - - - - Could not update file : %1 - - - - - Could not update virtual file metadata: %1 - - - - - Using virtual files with suffix, but suffix is not set - - - - - Unable to read the blacklist from the local database - Ni mogoče prebrati črnega seznama iz krajevne mape - - - - Unable to read from the sync journal. - Ni mogoče brati iz dnevnika usklajevanja - - - - Cannot open the sync journal - Ni mogoče odpreti dnevnika usklajevanja - - - - Aborted - Prekinjeno - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Glava nadzorne vsote je napačno oblikovana. - - - - The checksum header contained an unknown checksum type '%1' - Glava nadzorne vsote vsebuje neznano vrsto zapisa '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Pred nadaljevanjem je zahtevana prijava - - - - Folder %1: %2 - Mapa %1: %2 - - - - There are no sync folders configured. - Ni nastavljenih map za usklajevanje. - - - - Open in browser - Odpri v brskalniku - - - - - - Log in... - Prijava ... - - - - - - Log out - Odjava - - - - Recent Changes - Nedavne spremembe - - - - Managed Folders: - Upravljane mape: - - - - Open folder '%1' - Odpri mapo '%1' - - - - Open %1 in browser - Odpri %1 v brskalniku - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Neznano stanje - - - - Settings... - Nastavitve ... - - - - Details... - Podrobnosti ... - - - - Help - Pomoč - - - - Quit %1 - Končaj %1 - - - - Disconnected from %1 - Prekinjena povezava z %1 - - - - Unsupported Server Version - Nepodprta različica strežnika - - - - Disconnected - Brez povezave - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - Prekinjena je povezava z računi: - - - - Account %1: %2 - Račun %1: %2 - - - - Signed out - Odjavljeno - - - - Account synchronization is disabled - Usklajevanje računa je onemogočeno - - - - - Synchronization is paused - Usklajevanje je v premoru - - - - Unresolved conflicts - - - - - Error during synchronization - Med usklajevanjem je prišlo do napake - - - - No sync folders configured - Ni nastavljenih map za usklajevanje. - - - - Unpause all folders - Nadaljuj usklajevanje vseh map - - - - Pause all folders - Ustavi usklajevanje vseh map - - - - Unpause all synchronization - Nadaljuj usklajevanje vsega - - - - Unpause synchronization - Nadaljuj usklajevanje - - - - Pause all synchronization - Ustavi vse usklajevanje - - - - Pause synchronization - Ustavi usklajevanje - - - - Log out of all accounts - Odjavi vse račune - - - - Log in to all accounts... - Prijavi z vsemi računi ... - - - - New account... - Nov račun ... - - - - About %1 - O %1 - - - - No items synced recently - Ni nedavno usklajenih predmetov - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - Poteka usklajevanje %1 od %2 (preostaja %3) - - - - Syncing %1 of %2 - Poteka usklajevanje %1 od %2 - - - - Syncing %1 (%2 left) - Usklajevanje %1 (%2 do konca) - - - - Syncing %1 - Usklajevanje %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Ni posodobitev - - - - OwncloudAdvancedSetupPage - - - Form - Obrazec - - - - - - - - TextLabel - Besedilna oznaka - - - - Server - Strežnik - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Izbrana možnost omogoči brisanje celotne krajevne mape z vsebino in začenjanje svežega usklajevanja s strežnika.</p><p>Te možnosti ne izberite, če želite vsebino krajevne mape poslati na strežnik v oblak.</p></body></html> - - - - Start a &clean sync (Erases the local folder!) - Začni s &svežim usklajevanjem (izbriše krajevno mapo!) - - - - Ask for confirmation before synchroni&zing folders larger than - Vprašaj za potrditev pred usklajevan&jem map, večjih od - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Vprašaj za potrditev pred usklajevanjem zunanji&h shramb - - - - Choose what to sync - Izbor datotek za usklajevanje - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - &Krajevna mapa - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Ohrani krajevno shranjene podatke - - - - S&ynchronize everything from server (recommended) - - - - - Status message - Sporočilo stanja - - - - OwncloudHttpCredsPage - - - Form - Obrazec - - - - &Username - &Uporabniško ime - - - - &Password - &Geslo - - - - OwncloudOAuthCredsPage - - - Form - Obrazec - - - - Please switch to your browser to proceed. - Preklopite na brskalnik za nadaljevanje. - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - Ponovno odpri brskalnik - - - - OwncloudSetupPage - - - Form - Obrazec - - - - - TextLabel - Besedilna oznaka - - - - Ser&ver Address - Naslo&v strežnika - - - - https://... - https:// ... - - - - Error Label - Oznaka napake - - - - OwncloudWizardResultPage - - - Form - Obrazec - - - - TextLabel - Besedilna oznaka - - - - Your entire account is synced to the local folder - Naveden račun je v celoti usklajen s krajevno mapo - - - - - PushButton - Potisni gumb - - - - QObject - - - in the future - v prihodnje - - - - %n day(s) ago - pred %n dnevompred %n dnevomapred %n dnevipred %n dnevi - - - - %n hour(s) ago - pred %n uropred %n uramapred %n uramipred %n urami - - - - now - zdaj - - - - Less than a minute ago - Pred manj kot minuto - - - - %n minute(s) ago - pred %n minutopred %n minutamapred %n minutamipred %n minutami - - - - Some time ago - Pred nekaj časa - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n leto%n leti%n leta%n let - - - - %n month(s) - %n mesec%n meseca%n meseci%n mesecev - - - - %n day(s) - %n dan%n dneva%n dnevi%n dni - - - - %n hour(s) - %n ura%n uri%n ure%n ur - - - - %n minute(s) - %n minuta%n minuti%n minute%n minut - - - - %n second(s) - %n sekunda%n sekundi%n sekunde%n sekund - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Sistemska vrstica ni na voljo - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 zahteva uporabo sistemske obvestilne vrstice. Pri uporabnikih namizja XFCE je treba upoštevati <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">posebne nastavitve</a>. V kolikor to ni mogoče, je treba namestiti katerikoli sistem obveščanja, kot je na primer program 'trayer'. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3 %4%8%9Libraries Qt %5, %6%8Using virtual files plugin: %7%8 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - - - - - Replaced by virtual file - - - - - Downloaded - Prejeto - - - - Uploaded - Poslano - - - - Server version downloaded, copied changed local file into conflict file - Strežniška različica je prejeta, kopirana v datoteko sporov pa je bila tudi spremenjena krajevna datoteka - - - - Deleted - Izbrisano - - - - Moved to %1 - Premaknjeno v %1 - - - - Ignored - Prezrto - - - - Filesystem access error - Napaka dostopa do datotečnega sistema - - - - Error - Napaka - - - - Updated local metadata - Posodobljeni krajevni metapodatki - - - - - Unknown - Neznano - - - - downloading - prejemanje - - - - uploading - pošiljanje - - - - deleting - brisanje - - - - moving - premikanje - - - - ignoring - Prezrto - - - - - error - napaka - - - - updating local metadata - posodabljanje krajevnih metapodatkov - - - - theme - - - Status undefined - Stanje je nedoločeno - - - - Waiting to start sync - Čakanje začetek usklajevanja - - - - Sync is running - Usklajevanje v teku - - - - Sync Success - Usklajevanje je uspešno končano - - - - Sync Success, some files were ignored. - Usklajevanje je končano, ostajajo pa nerešene težave s posameznimi datotekami. - - - - Sync Error - Napaka usklajevanja - - - - Setup Error - Napaka nastavitve - - - - Preparing to sync - Priprava na usklajevanje - - - - Aborting... - Poteka prekinjanje ... - - - - Sync is paused - Usklajevanje je ustavljeno - - - - utility - - - Could not open browser - Brskalnika ni mogoče odpreti - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - Ni mogoče odpreti odjemalca elektronske pošte - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Prišlo je do napake med zaganjanjem odjemalca elektronske pošte za ustvarjanje novega sporočila. Najverjetneje ni nastavljen privzet programski paket. - - - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - - - - - Make always available locally - - - - - Free up local space - - - - \ No newline at end of file diff --git a/translations/client_sq.ts b/translations/client_sq.ts index a5f424f0cea..040f2168ba6 100644 --- a/translations/client_sq.ts +++ b/translations/client_sq.ts @@ -1,939 +1,983 @@ - + - CrashReporter + AccountBar - - Tomahawk Crash Reporter - Raportues Vithisjesh Tomahawk + + Navigation bar + Shtyllë lëvizjesh - - <html><head/><body><p><br/></p></body></html> - <html><head/><body><p><br/></p></body></html> + + Add Account + Shtoni Llogari - - Abort - Ndërprite + + Activity + Veprimtari - - Send this report - Dërgoje këtë raport + + Settings + Rregullime - - Don't send - Mos e dërgo + + Quit + Dilni + + + AppImageUpdateAvailableWidgetUi - - You can disable sending crash reports in the configuration dialog. - Dërgimin e raporteve mbi vithisje mund ta çaktivizoni te dialogu i formësimit. + + Skip this version + Anashkaloje këtë version + + + AppProvider - - - - We cannot gather useful debug information on your system. - S’mbledhim dot të dhëna të dobishme diagnostikimi mbi sistemin tuaj. + + Failed to open %1 in web. Error: %2. + S’u arrit të hapej %1 në web. Gabim: %2. + + + ChecksumHeader - - - - - - Close - Mbylle + + The checksum header is malformed: %1 + Kryet e checksum-it janë të keqformuara: %1 - - Ready to send debug information (<a href="%1">view backtrace</a>). - Gati për dërgim të dhënash diagnostikimi (<a href="%1">shihni backtrace-in</a>). + + The checksum header contained an unknown checksum type '%1' + Kryet e checksum-it përmbanin një lloj të panjohur checksum-i '%1' + + + CommandLine - - Gathering debug information... - Po mblidhen të dhëna diagnostikmi… + + %1 version %2 +File synchronization desktop utility. + %1 version %2<byte value="xd"/> +Mjet njëkohësimi kartelash për desktop. - - Uploaded %L1 of %L2 KB. - U ngarkuan %L1 prej %L2 KB. + + For more information, see %1 + link to homepage + Për më tepër hollësi, shihni %1 - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - U dërgua! <b>Shumë faleminderit</b>. Ju lutemi, te raporte vithisjesh, referojeni si vithisja <a href="clipboard://%1"><b>%1</b></a> (klikoni që të kopjohet). + + Start with the main window visible, or if it is already running, bring it to the front. By default, the client launches in the background. + Niseni me dritaren kryesore të dukshme, ose, nëse është tashmë duke xhiruar, silleni përpara dritareve të tjera. Si parazgjedhje, klienti niset në prapaskenë. - - Failed to send crash info. - S’u arrit të dërgohen të dhëna mbi vithisjen. + + Quit the running instance. + Dil nga instanca që është duke xhiruar. - - - FileSystem - - - - - Can't rename %1, the file is currently in use - S’riemërtohet dot %1, kartela është aktualisht në përdorim + + Write log to file (use - to write to stdout). + Shkruaje regjistrimin te kartelë (përdorni - që të shkruhet në stdout). - - Could not make directories in trash - S’u krijuan dot drejtori në hedhurina + + Write each sync log output in a new file in folder. + Shkruaje çdo përfundim regjistrimi njëkohësimesh në një kartelë të re në dosje. - - - Could not move '%1' to '%2' - S’u kalua dot '%1' te '%2' + + Flush the log file after every write. + Zbraze kartelën regjistër pas çdo shkrimi. - - Moving to the trash is not implemented on this platform - Shpënia te hedhurinat s’është e sendërtuar në këtë platformë + + Output debug-level messages in the log. + Kaloji te regjistri mesazhet debug-level. - - Error removing '%1': %2 - Gabim gjatë lëvizjes së '%1': %2 + + Enable debug mode. + Aktivizo mënyrën diagnostikim. - - Could not remove folder '%1' - S’u hoq dot dosja '%1' + + --logfile and --logdir are mutually exclusive + --logfile dhe --logdir përjashtojnë reciprokisht njëra-tjetrën - FolderWizardSourcePage + CommonStrings - - Form - Formular + + file manager + përgjegjës kartelash - - Pick a local folder on your computer to sync - Zgjidhni te kompjuteri juaj një dosje që të njëkohësohet + + Show in %1 + Shfaqe në %1 + + + + Show in web browser + Shfaqe në shfletues - - &Choose... - &Zgjidhni… + + Copy + Kopjoje + + + + %n Filter(s) + %n Filtër%n Filtra - - - FolderWizardTargetPage - - Form - Formular + + folder + dosje - - Select a remote destination folder - Përzgjidhni një dosje të largët vendmbërritje + + space + hapësirë - - Create Folder - Krijoni Dosje + + folders + dosje - - Refresh - Rifreskoje + + spaces + hapësira - - Folders + + Folder Dosje - - TextLabel - EtiketëTeksti + + Space + Hapësirë - NotificationWidget - - - Form - Formular - + FetchUserInfoJobFactory - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet + + Failed to retrieve user info + S’u arrit të merren hollësi përdoruesi + + + FileSystem - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm + + + + + Can't rename %1, the file is currently in use + S’riemërtohet dot %1, kartela është aktualisht në përdorim - - TextLabel - EtiketëTeksti + + Could not remove folder + S’u hoq dot dosje - OCC::AboutDialog + FolderItem - - About - Rreth + + + Offline + Jashtë linje - - TextLabel - EtiketëTeksti + + + Unavailable: the space is no longer available on the server + S’mund të kihet: hapësira s’është më e pranishme te shërbyesi - - Versions - Versione + + + Sync paused + Njëkohësimi u ndal - - About %1 - Rreth %1 + + Synced + Të njëkohësuara - - - OCC::AbstractNetworkJob - - Connection timed out - Lidhjes i mbaroi koha + + Sync failed + Njëkohësimi dështoi - - Unknown error: network reply was deleted - Gabim i panjohur: përgjigja e rrjetit qe fshirë + + Sync pending + Njëkohësim pezull - - Server replied "%1 %2" to "%3 %4" - Shërbyesi u përgjigj "%1 %2" për "%3 %4" + + Syncing %1 of %2 (%3 % + Po njëkohësohen %1 nga %2 (%3 % + + + + , ⬇️ %1/s + , ⬇️ %1/s + + + + , ⬆️ %1/s + , ⬆️ %1/s + + + + %1, sync status: %2 + Accessible text, read out by a screen reader. + %1, gjendje njëkohësimi: %2 + first argument is the displayed name of the folder. - OCC::Account + FolderManagementUtils - - %1@%2 - %1@%2 + + The path '%1' is too long. Please enable long paths in the Windows settings or choose a different folder. + Shtegu “%1” është shumë i gjatë. Ju lutemi, aktivizoni shtigje të gjatë te rregullimet e Windows-it, ose zgjidhni një dosje tjetër. + + + + Local folder %1 does not exist. + Dosja vendore %1 s’ekziston. + + + + %1 is not a folder. + %1 s’është dosje. + + + + %1 is not readable. + %1 s’është i lexueshëm. + + + + %1 is not writable. + %1 s’është e shkrueshme. - OCC::AccountSettings + FolderWizard - - Form - Formular + + <b>Error:</b> %1 + <b>Gabim:</b> %1 - - Re-open Browser - Rihap Shfletuesin + + <b>Warning:</b> %1 + <b>Kujdes:</b> %1 - - ... - + + <b>Error:</b><ul>%1</ul> + <b>Gabim:</b><ul>%1</ul> - - Storage space: ... - Hapësirë depozite: … + + <b>Warning:</b><ul>%1</ul> + <b>Sinjalizim:</b><ul>%1</ul> + + + FolderWizardSourcePage - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Dosjeve që iu hoq shenja do të <b>hiqen</b> nga sistemi juaj vendor i kartelave dhe s’do të njëkohësohen më në këtë kompjuter + + Form + Formular - - Synchronize all - Njëkohësoji krejt + + Pick a local folder on your computer to sync + Zgjidhni te kompjuteri juaj një dosje që të njëkohësohet - - Synchronize none - Mos njëkohëso gjë + + Browse… + Shfletoni… + + + FolderWizardTargetPage - - Apply manual changes - Zbato ndryshimet e bëra dorazi + + Form + Formular - - Apply - Zbatoje + + Select a remote destination folder + Përzgjidhni një dosje të largët vendmbërritje - - - - - Cancel - Anuloje + + Create Folder + Krijoni Dosje - - Connected with <server> as <user> - I lidhur me <server> si <user> + + Refresh + Rifreskoje - - No account configured. - Pa llogari të formësuar. + + Folders + Dosje + + + OCC::AboutView - - Remove - Hiqe + + About + Mbi - - Account - Llogari + + Versions + Versione + + + OCC::AbstractNetworkJob - - Choose what to sync - Zgjidhni çfarë të njëkohësohet + + Connection timed out + Lidhjes i mbaroi koha - - Force sync now - Detyro njëkohësim tani + + Unknown error: network reply was deleted + Gabim i panjohur: përgjigja e rrjetit qe fshirë - - Restart sync - Rinise njëkohësimin + + Server replied "%1 %2" to "%3 %4" + Shërbyesi u përgjigj "%1 %2" për "%3 %4" + + + OCC::Account - - Remove folder sync connection - Hiqe lidhjen për njëkohësim dosjeje + + %1@%2 + %1@%2 + + + OCC::AccountFoldersController - - Folder creation failed - Krijimi i dosjes dështoi + + Choose what to sync + Zgjidhni çfarë të njëkohësohet - - <p>Could not create local folder <i>%1</i>. - <p>S’u krijua dot dosja vendore <i>%1</i>. + + + Sync now + Njëkohësoje tani - - Confirm Folder Sync Connection Removal - Ripohoni Heqje Lidhjeje Njëkohësimi Dosjeje + + Manage subfolder sync + Administroni njëkohësim nëndosjesh - - Remove Folder Sync Connection - Hiqni Lidhjen Për Njëkohësim Dosjeje + + Remove %1 sync + This shows as 'Remove folder sync' or 'Remove Space sync' + Hiqe njëkohësimin e %1 - + Disable virtual file support? Të çaktivizohet mbulimi për kartela virtuale? - + This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. The only advantage of disabling virtual file support is that the selective sync feature will become available again. This action will abort any currently running synchronization. - Ky veprim do të çaktivizojë mbulimin për kartela virtuale. Si pasojë, lëndë e dosjeve të cilave u është vënë shenjë si “të passhme vetëm në linjë” do të shkarkohen. + Ky veprim do të çaktivizojë mbulimin për kartela virtuale. Si pasojë, do të shkarkohet lëndë e dosjeve të cilave u është vënë shenjë si “të passhme vetëm në linjë”. Përfitimi i vetëm i çaktivizimit të mbulimit për kartela virtuale është se veçoria e njëkohësimit përzgjedhës do të jetë sërish e përdorshme. Ky veprim do të shkaktojë ndërprerjen e çfarëdo njëkohësimi në kryerje e sipër atë çast. - + Disable support Çaktivizoje mbulimin - - Sync Running - Njëkohësimi Po Kryhet - - - - The syncing operation is running.<br/>Do you want to terminate it? - Veprimi i njëkohësimit po kryhet.<br/>Doni ta përfundoni? - - - - %1 in use - %1 në përdorim + + Restart sync + Rinise njëkohësimin - - %1 as <i>%2</i> - %1 si <i>%2</i> + + Resume sync + Rimerre njëkohësimin - - Connected to %1. - I lidhur tek %1. + + Pause sync + Ndale njëkohësimin - - Server %1 is temporarily unavailable. - Shërbyesi %1 është përkohësisht i pakapshëm. + + Deactivate virtual files + Çaktivizo kartela virtuale - - Server %1 is currently in maintenance mode. - Shërbyesi %1 tani gjendet nën mënyrën mirëmbajtje. + + Activate virtual files + Aktivizo kartela virtuale - - Signed out from %1. - U dol nga %1. + + Internet connection is metered + Lidhja Internet është me racion - - Connecting to %1... - Po lidhet te %1… + + Synchronization is paused because the Internet connection is a metered connection<p>Do you really want to force a Synchronization now? + Njëkohësimi është ndalur, ngaqë lidhja Internet është një lidhje me racion<p>Doni vërtet të detyrohet Njëkohësimi tani? - - No connection to %1 at %2. - Pa lidhje me %1 në %2. + + Sync Running + Njëkohësimi Po Kryhet - - Log in - Hyni + + The sync operation is running.<br/>Do you want to stop it? + Veprimi i njëkohësimit po kryhet.<br/>Doni të ndalet? - - There are folders that were not synchronized because they are too big: - Ka dosje që s’u njëkohësuan, ngaqë janë shumë të mëdha: + + Sync paused by user + Njëkohësim i ndalur nga përdoruesi - - There are folders that were not synchronized because they are external storages: - Ka dosje që s’u njëkohësuan, ngaqë janë depozita të jashtme: + + Confirm Folder Sync Connection Removal + Ripohoni Heqje Lidhjeje Njëkohësimi Dosjeje - - There are folders that were not synchronized because they are too big or external storages: - Ka dosje që s’u njëkohësuan, ngaqë janë shumë të mëdha ose janë depozita të jashtme: + + <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Doni vërtet të ndalet njëkohësimi i dosjes <i>%1</i>?</p><p><b>Shënim:</b> Kjo <b>nuk</b> do të fshijë ndonjë kartelë.</p> - - Confirm Account Removal - Ripohoni Heqjen e Llogarisë + + Remove Folder Sync Connection + Hiqe Lidhjen Për Njëkohësim Dosjeje - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Doni vërtet të hiqet lidhja për te llogaria <i>%1</i>?</p><p><b>Shënim:</b> Kjo <b>nuk</b> do të fshijë ndonjë kartelë.</p> + + + Cancel + Anuloje + + + OCC::AccountFoldersView - - Remove connection - Hiqe lidhjen + + Sync options menu + Menu mundësish njëkohësimi - - Open folder - Hap dosje + + %1 sync + This shows as 'Folder sync' or 'Space sync' + Njëkohësim %1 - - - Log out - Dilni + + Manage your synced %1. + Administroni %1 tuaj të njëkohësuar. - - Open local folder - Hap dosje vendore + + Add new %1 sync… + This shows as 'Add new folder sync…' or 'Add new Space sync…' + Shtoni njëkohësim të ri për %1… - - Open folder in browser - Hape dosjen në shfletues + + %1 list view + Parje liste %1 - - Resume sync - Rimerre njëkohësimin + + Navigate the %1 list using the up and down arrows + Lëvizni nëpër listën %1 duke përdorur tastet shigjetë lart dhe poshtë - - Pause sync - Ndale njëkohësimin + + %1 out of %2 %3 are synchronized + Janë njëkohësuar %1 nga %2 %3 gjithsej + + + OCC::AccountModalWidget - - Availability - Pasje + + + Form + Formular + + + OCC::AccountPlaceholderWidget - - Disable virtual file support... - Çaktivizoni mbulim kartelash virtuale… + + %1 logo + Stemë e %1 - - Enable virtual file support%1... - Aktivizoni mbulim kartelash virtuale%1… + + You don't have any connected accounts yet. + +Please create an account by selecting the "Add account..." option on the More button menu. + Ende s’keni ndonjë llogari të lidhur. + +Ju lutemi, krijoni një llogari duke përzgjedhur mundësinë “Shtoni llogari…” te menuja e butoni “Më tepër”. - - (experimental) - (eksperimentale) + + Additional logo defined by the organization + Stemë shtesë e përcaktuar nga enti + + + OCC::AccountView - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Doni vërtet të ndalet njëkohësimi i dosjes <i>%1</i>?</p><p><b>Shënim:</b> Kjo <b>nuk</b> do të fshijë ndonjë kartelë.</p> + + Form + Formular - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - Ka në përdorim %1 (%3%) nga %2. Disa dosje, përfshi dosje të montuara në rrjet apo të ndara me të tjerë, mund të kenë kufij të ndryshëm. + + Manage Account + Administroni Llogari - - %1 of %2 in use - %1 nga %2 në përdorim + + Preparing the account + Po përgatitet llogaria - - Currently there is no storage usage information available. - Hëpërhë s’ka gati të dhëna përdorimi depozite. + + Sync connections + Njëkohëso lidhje - - The server version %1 is unsupported! Proceed at your own risk. - Versioni %1 i shërbyesit nuk mbulohet! Qari dhe zarari i juaj. + + Account options menu + Menu mundësish veprimesh + + + OCC::AccountViewController - - Obtaining authorization from the browser. - Po merret autorizim nga shfletuesi. + + + Log in + Hyni - - Server configuration error: %1 at %2. - Gabim formësimi shërbyesi: %1 në %2. + + Reconnect + Rilidhu - - No %1 connection configured. - S’ka lidhje %1 të formësuar. + + Remove + Hiqe - - - OCC::AccountState - - Url update requested for %1 - U kërkua përditësim URL-je për %1 + + Log out + Dilni - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - URL-ja për %1 ndryshoi nga %2 në %3, doni të pranohet URL-ja e ndryshuar? + + Confirm Account Removal + Ripohoni Heqjen e Llogarisë - - Change url permanently to %1 - Ndryshoje përgjithmonë URL-në në %1 + + <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> + <p>Doni vërtet të hiqet lidhja për te llogaria <i>%1</i>?</p><p><b>Shënim:</b> Kjo <b>nuk</b> do të fshijë ndonjë kartelë.</p> - - Reject - Hidheni tej + + Remove connection + Hiqe lidhjen - - Signed out - I dalë + + Cancel + Anuloje - - Disconnected - I shkëputur + + The server version %1 is unsupported! Proceed at your own risk. + Versioni %1 i shërbyesit nuk mbulohet! Qari dhe zarari i juaj. - + Connected I lidhur - - Service unavailable - Shërbimi nuk po punon + + Server is temporarily unavailable. + Shërbyes aktualisht i pakapshëm. - - Maintenance mode - Mënyra mirëmbajtje + + Server is currently in maintenance mode. + Shërbyesi gjendet nën mënyrën mirëmbajtje. - - Network error - Gabim rrjeti + + Signed out + I dalë - - Configuration error - Gabim formësimi + + Updating credentials… + Po përditësohen kredenciale… - - Asking Credentials - Po kërkohen Kredencialet + + Captive portal prevents connections to the server. + Portali rrëmbyes pengon lidhjet me shërbyesin. - - Unknown account state - Gjendje e panjohur llogarie + + Sync is paused due to metered internet connection. + Njëkohësimi është ndalur, për shkak lidhje interneti me pagesë. - - - OCC::ActivityListModel - - %1 %2 on %3 - %1 %2 në %3 + + Connecting… + Po bëhet lidhja… - - Activity - Veprimtari + + Server configuration error + Gabim formësimi shërbyesi - - Account - Llogari - - - - Time - Kohë + + Disconnected + I shkëputur - - Local path - Shteg vendor + + Invalid connection status + Gjendje e pavlefshme lidhjeje - OCC::ActivitySettings + OCC::AccountsGuiController - - - Server Activity - Veprimtari Shërbyesi + + + New account failure + Dështim llogarie të re - - Sync Protocol - Protokoll Njëkohësimesh + + The information required to create a new account is incomplete. Please run the wizard again. + Informacioni i domosdoshëm për të krijuar një llogari të re është i paplotë. Ju lutemi, rixhironi ndihmësin. - - Not Synced - Të panjëkohësuara + + Accounts + Llogari - - Not Synced (%1) - %1 is the number of not synced files. - Të panjëkohësuara (%1) + + The account could not be created due to an error: +%1 +Please check the server's availability then run the wizard again. + Llogaria s’u krijua dot, për shkak të një gabimi: +%1 +Ju lutemi, kontrolloni qenien në punë të shërbyesit dhe rixhironi ndihmësin. - OCC::ActivityWidget - - - Form - Formular - - - - - - TextLabel - EtiketëTeksti - - - - Filter - Filtër - - - - Server Activities - Veprimtari Shërbyesi - - - - Notifications - Njoftime - - - - %n notifications(s) for %1. - %n njoftim për %1.%n njoftime për %1. - - - - %n notifications(s) for %1 and %2. - %n njoftim për %1 dhe %2.%n njoftime për %1 dhe %2. - - - - New notifications for %1, %2 and other accounts. - Njoftime të reja për %1, %2 dhe llogari të tjera. - - - - Open the activity view for details. - Për hollësi, hapni skenën e veprimtarisë. - + OCC::ActivitySettings - - Copy to clipboard - Kopjoje në të papastër + + Local Activity + Veprimtari Vendore - - Show in file browser - Shfaqe në shfletues kartelash + + Not Synced + Të panjëkohësuara - - <br/>Account %1 does not have activities enabled. - <br/>Llogaria %1 s’i ka të aktivizuara veprimtaritë. + + Not Synced (%1) + %1 is the number of not synced files. + Të panjëkohësuara (%1) - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Vazhdimi do të thotë <b>fshirje e këtyre rregullimeve</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Vazhdimi do të thotë <b>shpërfillje e këtyre rregullimeve</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Disa rregullime janë formësuar në versione më të rinj të këtij klienti dhe përdorin veçori që s’gjenden në këtë version.<br><br>%1<br><br>Kartela e tanishme e formësimit është kopjeruajtur tashmë si <i>%2</i>. - + OCC::AdvancedSettingsPageController - - Quit - Dilni + + Advanced settings + Rregullime të mëtejshme - - Continue - Vazhdo + + Sync and download options + Mundësi njëkohësimi dhe shkarkimi - - Error accessing the configuration file - Gabim gjatë hyrjes në kartelën e formësimit + + Download location + Vendndodhje shkarkimi - - There was an error while accessing the configuration file at %1. - Pati një gabim teksa hyhej te kartela e formësimit në %1. + + Sync and download files as you use them (saves hard drive space) + Njëkohësoni dhe shkarkoni kartela dora-dorës që i përdorni (kursen hapësirë disku) drive space) - - Quit %1 - Dilni nga %1 + + Automatically sync and download folders and files + Njëkohëso dhe shkarko automatikisht dosje dhe kartela - - %1 version %2 -File synchronization desktop utility. - %1 version %2<byte value="xd"/> -Mjet njëkohësimi kartelash për desktop. + + Sync and download folders manually + Njëkohësoni dhe shkarkoni dosje dhe kartela dorazi - - For more information, see http://www.owncloud.org - Për më tepër hollësi, shihni te http://www.owncloud.org + + Download location on the local machine + Vendndodhje shakrkimesh te makina vendore - - Show the settings dialog while starting. - Shfaq dialogun e rregullimeve teksa niset. + + Browse… + Shfletoni… - - Quit the running instance. - Dil nga instanca që është duke xhiruar. + + Browse for a download location + Shfletoni për një vendndodhje shkarkimesh - - Write log to file (use - to write to stdout). - Shkruaje regjistrimin te kartelë (përdorni - që të shkruhet në stdout). + + Invalid local download directory %1: %2 + Drejtori vendore shkarkimesh e pavlefshme %1: %2 - - Write each sync log output in a new file in folder. - Shkruaje çdo përfundim regjistrimi njëkohësimesh në një kartelë të re në dosje. + + your user directory may not be chosen as the sync root. + drejtoria juaj si përdorues s’duhet zgjedhur si rrënjë njëkohësimesh. - - Remove logs older than <hours> hours (to be used with --logdir). - Hiqi regjistrat më të vjetër se <hours> orë (për t’u përdorur me --logdir). + + path is not located on a supported Apple File System. + shtegu s’gjendet në një Apple File System që mbulohet. - - Flush the log file after every write. - Zbraze kartelën regjistër pas çdo shkrimi. + + path must be absolute. + shtegu duhet të jetë absolut. - - Output debug-level messages in the log. - Kaloji te regjistri mesazhet debug-level. + + selected path does not support using virtual file system. %1 + shtegu i përzgjedhur s’mbulon përdorim sistemi virtual kartelash. %1 - - - Override UI language. - Anashkalo gjuhë UI. + + Select sync root + Përzgjidhni rrënjë njëkohësimi + + + OCC::AppImageUpdateAvailableWidget - - Use the given configuration folder. - Përdor dosjen e dhënë të formësimit. + + Installed version: %1 + Version i instaluar: %1 - - Enable debug mode. - Aktivizo mënyrën diagnostikim. + + Available update: %1 + Përditësim i gatshëm: %1 - - Virtual file system file to be opened (optional). - Kartelë sistemi virtual kartelash për t’u hapur (opsionale). + + An update is available for this AppImage of %1. Do you want to install this update? + +The update will be performed in the background, and overwrite the current AppImage file. You need to restart the app to complete the update. + Për këtë AppImage të %1 ka një përditësim gati. Doni të instalohet ky përditësim? + +Përditësimi do të kryhet në prapaskenë dhe do të mbishkruajë kartelën e tanishme AppImage. Që të plotësohet përditësimi, lypset të rinisni aplikacionin. + + + OCC::Application - - [<vfs file>] - [<kartelë vfs>] + + Quit + Dilni + + + OCC::AuthSuccessPageController - - Invalid path passed to --confdir - U dha shteg i pavlefshëm te --confdir + + Application Name Logo %1 + Stemë Emri Aplikacioni %1 - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - Gabim: gjuhë "%1" e panjohur (përdorni --list-languages që të merrni një listë të plotë të përkthimeve që mbulohen) + + You're all set! Open %1 and get started. + Jeni gati! Hapni %1 dhe fillojani. - - Available translations: %1 - Përkthime të gatshme: %1 + + Additional logo defined by the organization + Stemë shtesë e përcaktuar nga enti - OCC::AuthenticationDialog - - - Authentication Required - Lypset Mirëfilltësim - - - - Enter username and password for '%1' at %2. - Jepni emër përdoruesi dhe fjalëkalim për '%1' te %2. - + OCC::ButtonDelegate - - &User: - &Përdorues: + + %1 options button + Buton mundësish për %1 - - &Password: - &Fjalëkalim: + + Menu button with %1 options. Use the space key to show the menu + Buton menuje me mundësi për %1. Që të shfaqet menuja, shtypni tastin Hapësirë OCC::ConnectionValidator - + No ownCloud account configured Nuk ka llogari ownCloud të formësuar - - The configured server for this client is too old - Shërbyesi i formësuar për këtë klient është shumë i vjetër - - - + Please update to the latest server and restart the client. Ju lutemi, përditësoni shërbyesin me versionin më të ri dhe rinisni klientin. - + Authentication error: Either username or password are wrong. Gabim mirëfilltësimi: Ose emri i përdoruesit, ose fjalëkalimi është i gabuar. - + timeout mbarim kohe - + + No account configured + S’ka llogari të formësuar + + + The provided credentials are not correct Kredencialet e dhëna s’janë të sakta + + + The configured server for this client is too old. + Shërbyesi i formësuar për këtë klient është shumë i vjetër. + OCC::CredentialJob - + Failed to parse credentials %1 S’u arrit të përtypen kredencialet %1 + + OCC::DetermineAuthTypeAdapter + + + Server does not support OAuth + Shërbyesi nuk mbulon OAuth + + + + Server did not ask for authorization + Shërbyesi s’kërkoi mirëfilltësim + + + + Failed to determine authentication type: %1 + S’u arrit të përcaktohej lloj mirëfilltësimi: %1 + + + + OCC::DiscoverWebFingerServiceAdapter + + + Invalid reply received from server. + U mor përgjigje e pavlefshme nga shërbyesi. + + OCC::DiscoverySingleDirectoryJob - - + + The account was deleted before we could start the propfind job + Llogari qe fshirë para se të mund të nisnin aktin “propfind” + + + + Server error: PROPFIND reply is not XML formatted! Gabim shërbyesi: përgjigja PROPFIND s’është e formatuar si XML! @@ -941,27 +985,22 @@ Mjet njëkohësimi kartelash për desktop. OCC::DiscoverySingleLocalDirectoryJob - + Error while opening directory %1 Gabim gjatë hapjes së drejtorisë %1 - + Directory not accessible on client, permission denied Drejtori jo e përdorshme nën klient, u mohua hyrja - + Directory not found: %1 Drejtori që s’gjendet: %1 - - Filename encoding is not valid - Kodimi i emrit të kartelës s’është i vlefshëm - - - + Error while reading directory %1 Gabim gjatë leximit të drejtorisë %1 @@ -969,148 +1008,137 @@ Mjet njëkohësimi kartelash për desktop. OCC::ExpandingHeaderView - + Reset column sizes Ricaktoni madhësi shtyllash - OCC::Folder + OCC::FetchCapabilitiesAdapter - - Local folder %1 does not exist. - Dosja vendore %1 s’ekziston. + + server sent invalid content type: %1. + shërbyesi dërgoi lloj të pavlefshëm lënde: %1. - - %1 should be a folder but is not. - %1 duhej të ishte dosje, por ja që s’është. + + could not parse Capabilities from server. + s’u analizuan dot Aftësi nga shërbyesi. - - %1 is not readable. - %1 s’është i lexueshëm. + + reply sent for different subject (server): %1 + përgjigje e dërguar për tjetër subjekt (shërbyes): %1 + + + OCC::Folder - + %1 has been removed. %1 names a file. %1 u hoq. - + %1 has been updated. %1 names a file. %1 u përditësua. - + %1 has been renamed to %2. %1 and %2 name files. %1 u riemërtua si %2. - + %1 has been moved to %2. %1 u kalua te %2. - + %1 and %n other file(s) have been removed. - U hoqën %1 dhe %n kartelë tjetër.U hoqën %1 dhe %n kartela të tjera. + Janë hequr %1 dhe %n kartelë tjetër.Janë hequr %1 dhe %n kartela të tjera. - - %1 is not writable. - %1 s’është e shkrueshme. + + The folder has been disabled or removed from the server + Dosja është çaktivizuar, ose hequr nga shërbyesi - + %1 and %n other file(s) have been added. - Është shtuar %1 dhe %n kartelë tjetër.Është shtuar %1 dhe %n kartela të tjera. + Janë shtuar %1 dhe %n kartelë tjetër.Janë shtuar %1 dhe %n kartela të tjera. - + %1 has been added. %1 names a file. U shtua %1. - + %1 and %n other file(s) have been updated. - U përditësua %1 dhe %n kartelë tjetër.U përditësua %1 dhe %n kartela të tjera. + U përditësuan %1 dhe %n kartelë tjetër.U përditësuan %1 dhe %n kartela të tjera. - + %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 u riemërtua si %2 dhe u riemërtua dhe %n kartelë tjetër.%1 u riemërtua si %2 dhe u riemërtuan dhe %n kartela të tjera. + %1 u riemërtua si %2 dhe u riemërtuan %n kartela të tjera.%1 u riemërtua si %2 dhe u riemërtuan %n kartela të tjera. - + %1 has been moved to %2 and %n other file(s) have been moved. - %1 u kalua te %2 dhe u lëviz edhe %n kartelë tjetër.%1 u kalua te %2 dhe u lëvizën edhe %n kartela të tjera. + %1 u kalua te %2 dhe u zhvendos %n kartelë tjetër.%1 u kalua te %2 dhe u zhvendosën %n kartela të tjera. - - %1 has and %n other file(s) have sync conflicts. + + %1 and %n other file(s) have sync conflicts. %1 dhe %n kartelë tjetër kanë përplasje njëkohësimi.%1 dhe %n kartela të tjera kanë përplasje njëkohësimi. - + %1 has a sync conflict. Please check the conflict file! %1 ka një përplasje njëkohësimi. Ju lutemi, kontrolloni kartelën e përplasjes! - + %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 dhe %n kartelë tjetër s’u njëkohësuan dot për shkak gabimesh. Për hollësi, shihni regjistrin.%1 dhe %n kartela të tjera s’u njëkohësuan dot për shkak gabimesh. Për hollësi, shihni regjistrin. + %1 dhe %n kartelë tjetër s’u njëkohësuan dot, për shkak gabimesh. Për hollësi shihni regjistrin.%1 dhe %n kartela të tjera s’u njëkohësuan dot, për shkak gabimesh. Për hollësi shihni regjistrin. - + %1 could not be synced due to an error. See the log for details. %1 s’u njëkohësua dot, për shkak gabimesh. Për hollësi, shihni regjistrin. - + Sync Activity Veprimtari Njëkohësimesh - - Could not read system exclude file - S’u lexua dot kartelë përjashtimi sistemi - - - - A new folder larger than %1 MB has been added: %2. - - U shtua një dosje më e madhe se %1 MB: %2. - - - - - A folder from an external storage has been added. - - U shtua një dosje nga një depozitë e jashtme. - + + Switching VFS mode on folder '%1' + Po ndërrohet mënyrë VFS në dosjen “%1” - - Please go in the settings to select it if you wish to download it. - Ju lutemi, shkoni te rregullimet që ta përzgjidhni, nëse doni ta shkarkoni. + + Could not read system exclude file + S’u lexua dot kartelë përjashtimi sistemi - + The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. Dosja %1 qe krijuar, por qe përjashtuar më herët nga njëkohësimi. Të dhënat brenda saj nuk do të njëkohësohen. - + The file %1 was created but was excluded from synchronization previously. It will not be synchronized. Dosja %1 qe krijuar, por qe përjashtuar më herët nga njëkohësimi. S’do të njëkohësohet. - + Changes in synchronized folders could not be tracked reliably. This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). @@ -1122,3589 +1150,2398 @@ Kjo do të thotë që klienti i njëkohësimeve mund të mos ngarkojë menjëher %1 + + + OCC::FolderItemUpdater - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Krejt kartelat te dosja e njëkohësimit '%1' u fshinë te shërbyesi. -Këto fshirje do të njëkohësohen te dosja juaj vendore e njëkohësimeve, duke jua hequr mundësinë e përdorimit të këtyre kartelave, hiq rastin kur keni të drejta rikthimi. -Nëse vendosni t’i mbani këto kartela, ato do të rinjëkohësohen me shërbyesin, nëse keni të drejta për të bërë kështu. -Nëse vendosni t’i fshini këto kartela, s’do të mund t’i keni, veç në mos qofshi i zoti. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Krejt kartelat te dosja juaj vendore e njëkohësimit '%1' u fshinë. Këto fshirje do të njëkohësohen me shërbyesin tuaj, duke jua hequr mundësinë e përdorimit të këtyre kartelave, veç në mos i rikthefshi. -Jeni i sigurt se doni të njëkohësohen këto veprime me shërbyesin? -Nëse ky qe aksident dhe vendosni t’i mbani kartelat tuaja, ato do të rinjëkohësohen me shërbyesin. - - - - Remove All Files? - Të hiqen Krejt Kartelat? - - - - Remove all files - Hiqi krejt kartelat + + There are unresolved conflicts. + Ka përplasje të pazgjidhura. - - Keep files - Mbaji kartelat + + Sync error: %1 + Gabim njëkohësimi: %1 OCC::FolderMan - + Could not reset folder state Gjendja e dosjes s’u ricaktua dot - + An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. U gjet një regjistër i vjetër njëkohësimesh '%1', por s’u hoq dot. Ju lutemi, sigurohuni që s’po e përdor ndonjë aplikacion. - - (backup) - (kopjeruajtje) - - - - (backup %1) - (kopjeruajtja %1) + + Unsupported filesystem + Sistem kartelash që s’mbulohet - - Undefined State. - Gjendje e Papërkufizuar. + + Account disconnected or paused + Llogari e shkëputur, ose e pezulluar - - Waiting to start syncing. - Po pritet të fillohet njëkohësimi. + + User triggered force sync + Përdoruesi shkaktoi njëkohësim të detyruar - - Preparing for sync. - Po përgatitet për njëkohësim. + + Folder is about to be removed + Dosja është gati për t’u hequr - - Sync is running. - Njëkohësimi po kryhet. + + (backup) + (kopjeruajtje) - - Sync was successful, unresolved conflicts. - Njëkohësimi qe i suksesshëm, ka përplasje të pazgjidhura. + + (backup %1) + (kopjeruajtja %1) - - Last Sync was successful. - Njëkohësimi i Fundit qe i suksesshëm. + + The selected path is not a folder. + Shtegu i përzgjedhur s’shpie te një dosje. - - Setup Error. - Gabim te Rregullimet. + + The folder %1 is used in a folder sync connection. + Dosja %1 përdoret në një lidhje njëkohësimi dosjesh. - - User Abort. - Ndërprerje Nga Përdoruesi. + + Folder '%1' is already in use by application %2. + Dosja “%1” përdoret tashmë nga aplikacioni %2. - - Sync is paused. - Njëkohësimi është ndalur. + + There is already a sync from the server to this local folder. + Ka tashmë një njëkohësim prej shërbyesit te kjo dosje vendore. - - %1 (Sync is paused) - %1 (Njëkohësimi është ndalur) + + The local folder %1 already contains a folder used in a folder sync connection. + Dosja vendore %1 përmban tashmë një dosje të përdorur në një lidhje njëkohësimi dosjesh. - - No valid folder selected! - S’është përzgjedhur dosje e vlefshme! + + The local folder %1 is already contained in a folder used in a folder sync connection. + Dosja vendore %1 gjendet tashmë në një dosje të përdorur në një lidhje njëkohësimi dosjesh. - - The folder %1 is used in a folder sync connection! - Dosja %1 është e përdorur në një lidhje njëkohësimi dosjesh! + + No valid folder selected. + U përzgjodh dosje jo e vlefshme. - - The selected path does not exist! - Shtegu i përzgjedhur s’ekziston! + + The selected path does not exist. + Shtegu i përzgjedhur s’ekziston. - - The selected path is not a folder! - Shtegu i përzgjedhur s’ka dosje! + + You have no permission to write to the selected folder. + S’keni leje të shkruani te dosja e përzgjedhur. - - You have no permission to write to the selected folder! - S’keni leje për shkrim te dosja e përzgjedhur! + + Folder '%1' is already in use by another account. + Dosja “%1” është tashmë në përdorim nga një tjetër llogari. - - There is already a sync from the server to this local folder. Please pick another local folder! - Ka tashmë një njëkohësim prej shërbyesi te kjo dosje vendore. Ju lutemi, përzgjidhni një tjetër dosje vendore! + + On macOS, only the Apple File System is supported. + Në macOS, mbulohet vetëm Apple File System. + + + OCC::FolderModelController - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Dosja vendore %1 përmban tashmë një dosje të përdorur në një lidhje njëkohësimi dosjeje. Ju lutemi, zgjidhni një tjetër! + + %1 options + Mundësi %1 - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Dosja vendore %1 përmbahet tashmë në një dosje të përdorur një lidhje njëkohësimi dosjeje. Ju lutemi, zgjidhni një tjetër! + + Menu button with %1 options. Use the space key to pop the %2 options menu + Buton menuje me mundësi %1. Që të shfaqet menuja e mundësive për %2, shtypni tastin Hapësirë + + + OCC::FolderWatcher - - %1 Please pick another one! - %1 Ju lutemi, zgjidhni një tjetër! + + The watcher did not receive a test notification. + Vëzhguesi s’mori njoftim testi. + + + OCC::FolderWatcherPrivate - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - Dosja %1 është e përbashkët për disa llogari. -Ky formësim dihet që shpie në humbje të dhënash dhe nuk mbulohet më. -Ju lutemi, shihni mundësinë e heqjes së kësaj dosjeje nga llogaria dhe të shtimit të saj sërish. + + This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. + - OCC::FolderStatusDelegate + OCC::FolderWizard + + + Virtual files are not available for the selected folder + S’ka kartela virtuale për dosjen e përzgjedhur + - + Add Folder Sync Connection Shtoni Lidhje Njëkohësimi Dosjesh - - File - Kartelë + + Add Sync Connection + Shtoni Lidhje Njëkohësimesh - OCC::FolderStatusModel + OCC::FolderWizardLocalPath + + + Click to select a local folder to sync. + Klikoni që të përzgjidhni një dosje vendore për njëkohësim. + - - You need to be connected to add a folder - Lypset të jeni i lidhur që të shtoni një dosje + + Enter the path to the local folder. + Jepni shtegun për te dosja vendore. - - Click this button to add a folder to synchronize. - Klikoni mbi këtë buton që të shtoni një dosje për njëkohësim. + + Select the local folder + Përzgjidhni dosjen vendore + + + OCC::FolderWizardSelectiveSync - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) + + Use virtual files instead of downloading content immediately + Përdorni kartela virtuale, në vend se të shkarkoni lëndë menjëherë + + + OCC::GETFileJob - - Error while loading the list of folders from the server. - Gabim gjatë ngarkimit të listës së dosjeve prej shërbyesit. + + Connection Timeout + Lidhjes i Mbaroi Koha - - Virtual file support is enabled. - Mbulimi i kartelave virtuale është i aktivizuar. + + No E-Tag received from server, check Proxy/Gateway + S’u mor E-Tag prej shërbyesit, kontrolloni Ndërmjetësin/Kanalin - - Signed out - Dolët + + We received a different E-Tag for resuming. Retrying next time. + Morëm një tjetër E-Tag për rimarrje të procesit. Do të provohet herës tjetër. - - Fetching folder list from server... - Po sillet prej shërbyesi listë dosjesh… + + We received an unexpected download Content-Length. + Morëm një Content-Length shkarkimi të papritur. - - There are unresolved conflicts. Click for details. - Ka përplasje të pazgjidhura. Klikoni për hollës. + + Server returned wrong content-range + Shërbyesi u përgjigj me content-range të gabuar + + + OCC::GraphApi::Space - - Checking for changes in remote '%1' - Po kërkohet për ndryshime te shërbyesi i largët '%1' + + Personal + Personale - - Checking for changes in local '%1' - Po kërkohet për ndryshime te shërbyesi vendor '%1' + + Shares + Pjesë + + + OCC::IgnoreListEditor - - Reconciling changes - Po pajtohen ndryshimet mes tyre + + Ignored Files Editor + Përpunues Kartelash të Shpërfillura - - , '%1' - Build a list of file names - , '%1' + + Files Ignored by Patterns + Kartela të Shpërfillura nga Mostrat - - '%1' - Argument is a file name - '%1' + + Add + Shtoje - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Po njëkohësohet %1 + + Pattern + Mostër - - - , - , + + Allow Deletion + Lejoni Fshirje - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - shkarko %1/s + + Remove + Hiqe - - ↓ %1/s - ↓ %1/s + + Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. + +Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. + Kartela ose dosje me përputhje me një rregullsi s’do të njëkohësohen. Ndryshimet hyjnë në fuqi herën pasuese që njëkohësohen dosjet. + +Objektet mbi të cilët lejohet fshirja do të fshihen, nëse pengojnë heqjen e një drejtorie. Kjo është e dobishme për të dhëna meta. - - ↑ %1/s - ↑ %1/s + + Could not open file + S'u hap dot kartela - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - ngarko %1/s + + Cannot write changes to '%1'. + S’shkruhen dot ndryshimet te '%1'. - - Local folder: %1 - Dosje vendore: %1 + + Add Ignore Pattern + Shtoni Mostër Shpërfilljeje - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 nga %4) + + Add a new ignore pattern: + Shtoni një mostër të re shpërfilljeje: - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 + + This entry is provided by the system at '%1' and cannot be modified in this view. + Ky zë sillet nga sistemi në '%1' dhe s’mund të modifikohet në këtë skenë. + + + OCC::LocalActivityWidget - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - edhe %5, %1 nga %2, kartela %3 nga %4 + + Filter + Filtroni - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 nga %2, kartela %3 nga %4 + + Local activity table + Tabelë veprimtarish vendore - - file %1 of %2 - kartela %1 nga %2 + + + Account + Llogari - - Waiting... - Po pritet… + + Filter menu + Menu filtrimesh - - - Waiting for %n other folder(s)... - Po pritet për %n dosje tjetër…Po pritet për %n dosje të tjera… + + + Actions menu + Menu veprimesh - - Preparing to sync... - Po përgatitet për njëkohësim… + + Sort ascending by %1 + Renditi në rend rritës sipas %1 - - - OCC::FolderWatcher - - The watcher did not receive a test notification. - Vëzhguesi s’mori njoftim testi. + + Sort descending by %1 + Renditi në rend zbritës sipas %1 - - - OCC::FolderWatcherPrivate - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - + + Retry sync + Riprovo njëkohësimin - OCC::FolderWizard - - - Add Folder Sync Connection - Shtoni Lidhje Njëkohësimi Dosjesh - - - - Add Sync Connection - Shtoni Lidhje Njëkohësimesh - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Klikoni që të përzgjidhni një dosje vendore për njëkohësim. - - - - Enter the path to the local folder. - Jepni shtegun për te dosja vendore. - - - - Select the source folder - Përzgjidhni dosjen burim - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Krijoni Dosje të Largët - - - - Enter the name of the new folder to be created below '%1': - Jepni më poshtë emrin e dosjes së re që duhet krijuar '%1': - - - - Folder was successfully created on %1. - Dosja u krijua me sukses te %1. - - - - Authentication failed accessing %1 - Dështoi mirëfilltësimi gjatë hyrjes te %1 - - - - Failed to create the folder on %1. Please check manually. - S’u arrit të krijohej dosja te %1. Ju lutemi, kontrollojeni dorazi. - - - - Failed to list a folder. Error: %1 - Dështoi në paraqitjen e një dosjeje. Gabim: %1 - - - - Choose this to sync the entire account - Zgjidhni këtë që të njëkohësohet krejt lënda e llogarisë - - - - This folder is already being synced. - Kjo dosje është duke u njëkohësuar. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Po e njëkohësoni tashmë <i>%1</i>, që është dosja mëmë e <i>%2</i>. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - Po e njëkohësoni tashmë <i>%1</i>, që është një nëndosje e <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Përdor kartela virtuale, në vend të shkarkimit të menjëhershëm të lëndës%1 - - - - (experimental) - (eksperimentale) - - - - Virtual files are not available for the selected folder - S’ka kartela virtuale për dosjen e përzgjedhur - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Kujdes:</b> %1 - - - - <b>Warning:</b> - <b>Kujdes:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - S’u mor E-Tag prej shërbyesit, kontrolloni Ndërmjetësin/Kanalin - - - - We received a different E-Tag for resuming. Retrying next time. - Morëm një tjetër E-Tag për rimarrje të procesit. Do të provohet herës tjetër. - - - - We received an unexpected download Content-Length. - Morëm një Content-Length shkarkimi të papritur. - - - - Server returned wrong content-range - Shërbyesi u përgjigj me content-range të gabuar - - - - OCC::GETJob - - - Connection Timeout - Lidhjes i Mbaroi Koha - - - - OCC::GeneralSettings - - - Form - Formular - - - - General Settings - Rregullime të Përgjithshme - - - - Language - Gjuhë - - - - Sync hidden files - Njëkohëso kartela të fshehura - - - - Log Settings - Rregullime Regjistrimi - - - - Advanced - Approval of folder sync (Non virtual file mode only) - Të mëtejshme - Miratim njëkohësimi dosjesh (Vetëm nën mënyrën pa kartelë virtuale) - - - - Network - Rrjet - - - - Updates - Përditësime - - - - stable - i qëndrueshëm - - - - beta - beta - - - - About - Rreth - - - - Advanced - Të mëtejshme - - - - Use &Monochrome Icons in the system tray - Te paneli i sistemit përdor Ikona &Njëngjyrëshe - - - - Ask for confirmation before synchronizing folders larger than - Kërko ripohim përpara njëkohësimi dosjesh më të mëdha se - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Kërko ripohim përpara njëkohësimi depozitash të jashtme - - - - &Launch on System Startup - &Hape gjatë Nisjes së Sistemit - - - - Show &Desktop Notifications - Shfaq Njoftime &Desktopi - - - - &Update Channel - Kanal &Përditësimesh - - - - Edit &Ignored Files - Përpunoni Kartela të &Shpërfillura - - - - Show sync folders in &Explorer's Navigation Pane - Shfaq dosje njëkohësimi te Kuadrati i Lëvizjeve te Explorer - - - - S&how crash reporter - S&hfaq njoftues vithisjesh - - - - &Restart && Update - &Rinise && Përditësoje - - - - You cannot disable autostart because system-wide autostart is enabled. - S’mund ta çaktivizoni vetënisjen, ngaqë është e aktivizuar vetënisja për krejt sistemin. - - - - Change update channel? - Të ndryshohet kanali i përditësimit? - - - - Warning - Kujdes - - - - Language changes require a restart of this application to take effect. - Ndryshimet e gjuhës lypin rinisje të këtij aplikacioni, që të hyjnë në fuqi. - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Kanali i përditësimeve përcakton se cilat përditësime të klientit do të ofrohen për instalim. Kanali "i qëndrueshëm" përmban vetëm përmirësime që konsiderohen të qëndrueshme, ndërsa versionet në kanalin "beta" mund të përmbajnë veçori të reja dhe ndreqje të metash, por s’janë testuar plotësisht. - -Kini parasysh që kjo përzgjedh vetëm se nga cili thes merren përmirësimet, dhe që nuk ka kthim mbrapsht: Me një fjalë, kalimi nga kanali beta te kanali i qëndrueshëm, zakonisht, s’mund të bëhet menjëherë dhe do të thotë të pritet për një version të qëndrueshëm që është më i ri se versioni beta i instaluar. - - - - Change update channel - Ndryshoje kanalin e përditësimit - - - - Cancel - Anuloje - - - - unknown (%1) - e panjohur (%1) - - - - (use default) - (përdor parazgjedhjen) - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Ju lutemi, jepni fjalëkalimin %1:<br><br>Përdorues: %2<br>Llogari: %3<br> - - - - Reading from keychain failed with error: '%1' - Leximi prej vargut të kyçeve dështoi me gabimin: '%1' - - - - Enter Password - Jepni Fjalëkalimin - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Klikoni këtu</a> që të kërkoni një fjalëkalim aplikacioni prej ndërfaqes web. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Përpunues Kartelash të Shpërfillura - - - - Files Ignored by Patterns - Kartela të Shpërfillura nga Mostrat - - - - Add - Shtoje - - - - Pattern - Mostër - - - - Allow Deletion - Lejoni Fshirje - - - - Remove - Hiqe - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Kartela ose dosje me përputhje me një rregullsi s’do të njëkohësohen. Ndryshimet hyjnë në fuqi herën pasuese që njëkohësohen dosjet. - -Objektet mbi të cilët lejohet fshirja do të fshihen, nëse pengojnë heqjen e një drejtorie. Kjo është e dobishme për të dhëna meta. - - - - Could not open file - S'u hap dot kartela - - - - Cannot write changes to '%1'. - S’shkruhen dot ndryshimet te '%1'. - - - - Add Ignore Pattern - Shtoni Mostër Shpërfilljeje - - - - Add a new ignore pattern: - Shtoni një mostër të re shpërfilljeje: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Ky zë sillet nga sistemi në '%1' dhe s’mund të modifikohet në këtë skenë. - - - - OCC::IssuesWidget - - - Form - Formular - - - - List of issues - Listë Problemesh - - - - Filter - Filtër - - - - There were too many issues. Not all will be visible here. - Ka shumë probleme. Jo të gjithë do të jenë të dukshëm këtu. - - - - There were conflicts. Check the documentation on how to resolve them. - Pati përplasje. Se si t’i zgjidhni, shihni dokumentimin. - - - - The file %1 was ignored as its name is reserved by %2 - Kartela %1 u shpërfill, ngaqë emri i saj është i rezervuar nga2 - - - - Reset column sizes - Ricaktoni madhësi shtyllash - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Pati përplasje. <a href="%1">Se si t’i zgjidhni, shihni dokumentimin.</a> - - - - Account - Llogari - - - - OCC::LibCloudProvidersPrivate - - - Settings - Rregullime - - - - OCC::LogBrowser - - - Log Output - Përgjigje Regjistri - - - - Enable logging to temporary folder - Aktivizo regjistrim për dosje të përkohshme - - - - Delete logs older than %1 hours - Fshi regjistra më të vjetër se %1 orë - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">Regjistrat përmbajnë të dhëna rezervat të cilat s’duhet t’i bëni të passhme publikisht</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - Klienti mund të shkruajë regjistra diagnostikimi te një dosje e përkohshme. Këta regjistra janë shumë të dobishëm për diagnostikim problemesh. -Ngaqë kartelat regjistër mund të bëhen të mëdha, klienti do të fillojë një të re për çdo xhirim njëkohësimi dhe të vjetrat do t’i ngjeshë. Rekomandohet gjithashtu të aktivizohet fshirja e kartelave regjistër pas ca orësh, për të shmangur konsum të lartë hapësire në disk. - - - - If enabled, logs will be written to: - Në u aktivizoftë, regjistrat do të shkruhen te: - - - - C:/log - C:/log - - - - Log Http traffic - Regjistro trafik Http - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Këto rregullime ruhen gjatë rinisjesh klienti. -Kini parasysh se përdorimi i çfarëdo mundësie regjistrimi që nga rresht urdhrash do të anashkalojë rregullimet. - - - - Open folder - Hap dosje - - - - OCC::Models - - - %1 Filter: - Filtër %1: - - - - No filter - Pa filtrim - - - - Reset Filters - Ktheji Filtrat Te Parazgjedhjet - - - - Status Filter: - Filtër Gjendjesh: - - - - OCC::NSISUpdater - - - New Version Available - Ka Gati Version të Ri - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Ka të gatshëm një version të ri të Klientit %1.</p><p><b>%2</b> është gati për shkarkim. Versioni i instaluar është %3.</p> - - - - - Skip this version - Anashkaloje këtë version - - - - Skip this time - Anashkaloje këtë herë - - - - Get update - Merre përditësimin - - - - Update Failed - Përditësimi Dështoi - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>Ka të gatshëm një version të ri të Klientit %1, por procesi i përditësimit dështoi</p><p><b>%2</b> është shkarkuar. Versioni i instaluar është %3.</p> - - - - Ask again later - Ripyet më vonë - - - - Restart and update - Rinise dhe përditësoje - - - - Update manually - Përditësojeni dorazi - - - - OCC::NetworkSettings - - - Form - Formular - - - - Proxy Settings - Rregullime Ndërmjetësi - - - - No Proxy - Pa Ndërmjetës - - - - Use system proxy - Përdor ndërmjetësin e sistemit - - - - Specify proxy manually as - Përcaktojeni ndërmjetësin dorazi si - - - - Host - Strehë - - - - : - : - - - - Proxy server requires authentication - Shërbyesi ndërmjetës lyp mirëfilltësim - - - - Note: proxy settings have no effects for accounts on localhost - Shënim: Rregullimet e ndërmjetësit nuk kanë efekt për llogari në localhost - - - - Download Bandwidth - Gjerësi Bande Shkarkimi - - - - - Limit to - Kufizoje deri në - - - - - KBytes/s - KBajte/s - - - - - No limit - Pa kufi - - - - - Limit to 3/4 of estimated bandwidth - Kufizoje te 3/4 e gjerësisë së bandës së pritshme - - - - Upload Bandwidth - Gjerësi Bande Ngarkimi - - - - - Limit automatically - Kufizoje vetvetiu - - - - Hostname of proxy server - Strehëemër për shërbyesin ndërmjetës - - - - Username for proxy server - Emër përdoruesi për shërbyesin ndërmjetës - - - - Password for proxy server - Fjalëkalim për shërbyesin ndërmjetës - - - - HTTP(S) proxy - Ndërmjetës HTTP(S) - - - - SOCKS5 proxy - Ndërmjetës SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - Krijuar më %1 - - - - Closing in a few seconds... - Do të mbyllet pas pak sekondash… - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - Kërkesa %1 dështoi në %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' përzgjedhur në %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Gabim i kthyer nga shërbyesi: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Pati një gabim në përdorimin e pikëmbarimit të 'tokenit': <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - S’u përtyp dot JSON-i i kthyer nga shërbyesi: <br><em>%1</em> - - - - Unsupported token type: %1 - Lloj token-i i pambuluar: %1 - - - - The reply from the server did not contain all expected fields -:%1 - Përgjigja prej shërbyesit s’përmbante krejt fushat e pritura -:%1 - - - - Unknown Error - Gabim i Panjohur - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>Gabim Hyrjeje</h1><p>S’u arrit të merreshin të dhëna përdoruesi</p> - - - - Oauth2 Error - Gabim OAuth2 - - - - Oauth2 authentication requires a secured connection. - Mirëfilltësimi Oauth2 lyp një lidhje të siguruar. - - - - <h1>Login Error</h1><p>%1</p> - <h1>Gabim Hyrjeje</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Gabim gjatë rifreskimit të token-it: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - Përgjigja prej shërbyesit nuk përmbante krejt fushat e pritura -:%1 -Të dhëna të marra: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Përdorues i gabuar</h1><p>U futët si përdoruesi <em>%1</em>, por duhet të hyni si përdoruesi <em>%2</em>.<br>Ju lutemi, dilni prej %3 që nga një skedë tjetër, mandej <a href='%4'>klikoni këtu</a> dhe bëni hyrjen si përdoruesi %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Përditësim i Ri %1 Gati - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Ju ndan një hap nga instalimi i një përditësimi të ri për %1. -Gjatë procesit përditësuesi mund të kërkojë privilegje shtesë. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - S’u shkarkua dot përditësimi. Ju lutemi, klikoni <a href='%1'>këtu</a> që ta shkarkoni përditësimin dorazi. - - - - Could not check for new updates. - S’u kontrollua dot për përditësime të reja. - - - - Downloading %1. Please wait... - Po shkarkohet %1. Ju lutemi, pritni… - - - - %1 available. Restart application to start the update. - %1 i gatshëm. Rinisni aplikacionin që të nisë përditësimi. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - Ka gati %1 të ri. Ju lutemi, klikoni <a href='%2'>këtu</a> që të shkarkoni dorazi AppImage-in e ri. - - - - New %1 available. Please use the system's update tool to install it. - %1 i ri i gatshëm. Ju lutemi, për ta instaluar, përdorni mjetin e sistemit për përditësime. - - - - Checking update server... - Po kontrollohet shërbyesi i përditësimeve… - - - - Update status is unknown: Did not check for new updates. - Gjendja e përditësimit është e panjohur: S’u kontrollua për përditësime të rinj. - - - - No updates available. Your installation is at the latest version. - S’ka përditësime gati. Instalimi juaj është versioni më i ri. - - - - Update Check - Kontroll Përditësimesh - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Lidhu te %1 - - - - Setup local folder options - Mundësi rregullimesh për dosjet vendore - - - - Connect... - Lidhuni… - - - - (experimental) - (eksperimentale) - - - - Use &virtual files instead of downloading content immediately%1 - Përdor kartela &virtuale, në vend të shkarkimit të menjëhershëm të lëndës%1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - <p><strong>Kujdes:</strong> Dosja vendore s’është e zbrazët. Vendosni një zgjidhje!</p> - - - - Sync the folder '%1' - Njëkohëso dosjen '%1' - - - - Virtual files are not available for the selected folder - S’ka kartela virtuale për dosjen e përzgjedhur - - - - Error - Gabim - - - - Local Sync Folder - Dosje Vendore Njëkohësimi - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Email - - - - Connect to %1 - Lidhu te %1 - - - - Enter user credentials - Jepni kredenciale përdoruesi - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Lidhu te %1 - - - - Login in your browser - Hyrje që nga shfletuesi juaj - - - - Copy link to clipboard - Kopjoja lidhjen në të papastër - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - Kartela %1 është aktualisht në përdorim - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Lidhu te %1 - - - - Setup %1 server - Rregulloni shërbyes %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Kjo URL NUK është e sigurt, ngaqë s’është e fshehtëzuar. -Nuk është e këshillueshme të përdoret. - - - - This url is secure. You can use it. - Kjo URL është e sigurt. Mund ta përdorni. - - - - &Next > - &Pasuesi > - - - - Invalid URL - URL e pavlefshme - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - S’arriti të lidhej te %1 në %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Mbaroi koha teksa përpiqej të lidhej te %1 në %2. - - - - Invalid URL - URL e pavlefshme - - - - Could not create local folder %1 - S’u krijua dot dosja vendore %1 - - - - Error: %1 - Gabim: %1 - - - - The folder creation resulted in HTTP error code %1 - Krijimi i dosjes ktheu kod gabimi HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Krijimi i dosjes së largët dështoi, ngaqë kredencialet e dhëna janë të gabuara!<br/>Ju lutemi, kthehuni mbrapsht dhe kontrolloni kredencialet tuaja.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Krijimi i dosjes %1 dështoi me gabimin <tt>%2</tt>. - - - - Folder rename failed - Riemërtimi i dosjes dështoi - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - S’hiqet dot dhe kopjeruhet dosja, ngaqë dosja ose një kartelë në të është e hapur në një program tjetër. Ju lutemi, mbylleni dosjen ose kartelën dhe klikoni mbi Riprovo, ose anuloni rregullimin. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Ndihmësi i Lidhjeve %1 - - - - Enable experimental feature? - Të aktivizohet veçoria eksperimentale? - - - - Enable experimental placeholder mode - Aktivizoni mënyrë eksperimentale vendëmbajtësish - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - Kur është e aktivizuar mënyra "kartela virtuale", nuk do të shkarkohen kartela fillimisht. Në vend të kësaj, do të krijohet një kartelë e vockël "%1" për çdo kartelë që gjendet në shërbyes. Lënda mund të shkarkohet duke xhiruar këto kartela ose duke përdorur për to menunë e kontekstit. - -Mënyra e kartelave virtuale është reciprokisht përjashtuese me njëkohësimin përzgjedhës. Faktikisht, dosjet e përzgjedhura do të shndërrohen në dosje vetëm-lexim dhe rregullimet tuaja për njëkohësim përzgjedhës do të rikthehen te parazgjedhjet. - -Kalimi në këtë mënyrë do të sjellë ndërprerjen e çfarëdo njëkohësimi në zhvillim e sipër. - -Kjo është një mënyrë e re, eksperimentale. Nëse vendosni ta përdorni, ju lutemi, na njoftoni për çfarëdo problemesh që shfaqen.<br> - - - - Stay safe - Rrini i parrezikuar - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Njëkohësimi s’i mbulon lidhjet simbolike. - - - - File is listed on the ignore list. - Kartela bën pjesë te lista e shpërfilljeve. - - - - File names ending with a period are not supported on this file system. - Në këtë sistem kartelash nuk mbulohen emra kartelash që mbarojnë me një pikë. - - - - File names containing the character '%1' are not supported on this file system. - Në këtë sistem kartelash nuk mbulohen emra kartelash që përmbajnë shenjën '%1'. - - - - File name contains at least one invalid character - Emri i kartelës përmban të paktën një shenjë të pavlefshme - - - - The file name is a reserved name on this file system. - Në këtë sistem kartelash, emri i kartelës është një emër i rezervuar. - - - - Filename contains trailing spaces. - Emri i kartelës përmban shenja të zbrazëta në fund. - - - - Filename is too long. - Emri i kartelës është shumë i gjatë. - - - - File/Folder is ignored because it's hidden. - Kartela/Dosja është shpërfillur, ngaqë është e fshehur. - - - - Stat failed. - Statistikat dështuan. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Përplasje: Versioni i shërbyesit u shkarkua, kopja vendore u riemërtua dhe s’është ngarkuar. - - - - The filename cannot be encoded on your file system. - Emri i kartelës s’mund të kodohet në sistemin tuaj të kartelave. - - - - The filename is blacklisted on the server. - Emri i kartelës gjendet në listë bllokimesh te shërbyesi. - - - - size - madhësi - - - - permissions - leje - - - - etag - etag - + OCC::LogBrowser - - file id - ID kartele + + Log Output + Përgjigje Regjistri - - server reported no %1 - shërbyesi s’raportoi %1 + + <html><head/><body><p><b>The logs contain sensitive information which you should not make publicly available</b></span></p></body></html> + <html><head/><body><p><b>Regjistrat përmbajnë të dhëna rezervat të cilat s’duhet t’i bëni të passhme publikisht</b></span></p></body></html> - - Moved to invalid target, restoring - U shpu te vendmbërritje e pavlefshme, po rikthehet + + The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. +Since log files can get large, the client will start a new one for each sync run and compress older ones. + Klienti mund të shkruajë regjistra diagnostikimi te një dosje e përkohshme. Këta regjistra janë shumë të dobishëm për diagnostikim problemesh. +Ngaqë kartelat regjistër mund të bëhen shumë të mëdha, klienti do të fillojë një të re, për çdo xhirim të njëkohësimit dhe të vjetrat do t’i ngjeshë. - - Not allowed to remove, restoring - S’lejohet të hiqet, po rikthehet + + Enable logging to temporary folder + Aktivizo regjistrim për dosje të përkohshme - - Ignored because of the "choose what to sync" blacklist - U shpërfill për shkak të listës së bllokimeve "zgjidhni ç’doni të njëkohësoni" + + If enabled, logs will be written to: + Në u aktivizoftë, regjistrat do të shkruhen te: - - Not allowed because you don't have permission to add subfolders to that folder - S’lejohet, ngaqë s’keni leje për shtim nëndosjesh te ajo dosje + + C:/log + C:/log - - Not allowed because you don't have permission to add files in that folder - S’lejohet, ngaqë s’keni leje të shtoni kartela te ajo dosje + + Log Http traffic + Regjistro trafik Http - - Not allowed to upload this file because it is read-only on the server, restoring - S’lejohet të ngarkohet kjo kartelë, ngaqë është vetëm për lexim te shërbyesi, po rikthehet + + Log files to keep: + Kartela regjistër për t’u mbajtur: - - Error while reading the database - Gabim gjatë leximit të bazës së të dhënave + + These settings persist across client restarts. +Note that using any logging command line options will override the settings. + Këto rregullime ruhen gjatë rinisjesh klienti. +Kini parasysh se përdorimi i çfarëdo mundësie regjistrimi që nga rresht urdhrash do të anashkalojë rregullimet. - - Server replied with an error while reading directory '%1' : %2 - Shërbyesi u përgjigj me një gabim teksa lexohej drejtoria '%1' : %2 + + Open folder + Hap dosje - OCC::PropagateDirectory - - - Error updating metadata: %1 - Gabim në përditësim tejtëdhënash: %1 - + OCC::MainWindow - - %1 the folder is currently in use - + + Hide + Fshihe - - - OCC::PropagateDownloadFile - - Failed to free up space, the file %1 is currently in use - S’u arrit të lirohet hapësirë, kartela %1 është aktualisht në përdorim + + Main toolbar for the application + Paneli kryesor për aplikacionin - - - File %1 can not be downloaded because of a local file name clash with %2! - Kartela %1 s’mund shkarkohet, për shkak të një përplasjeje emrash kartele vendore me %2! + + Main toolbar + Paneli kryesor - - The download would reduce free local disk space below the limit - Shkarkimi mund të ulë nën kufi sasinë e hapësirës së lirë në disk + + + More + Më tepër + + + OCC::MainWindowController - - Free space on disk is less than %1 - Hapësira e lirë në disk është nën %1 + + Add account... + Shtoni llogari… - - File was deleted from server - Kartela është fshirë te shërbyesi + + Settings... + Rregullime… - - The file could not be downloaded completely. - Kartela s’u shkarkua plotësisht. + + About... + Mbi… - - The downloaded file is empty despite the server announced it should have been %1. - Kartela e shkarkuar është e zbrazët, edhe pse shërbyesi njoftoi se duhej të ishte %1. + + Quit + Dilni - - File %1 cannot be saved because of a local file name clash with %2! - Kartela %1 s’mund të ruhet, për shkak të një përplasjeje emrash kartele vendore me %2! + + + Errors: %1 + Gabime: %1 - - Error updating metadata: %1 - Gabim në përditësim tejtëdhënash: %1 + + Activity + Veprimtari - - - - - The file %1 is currently in use - Kartela %1 është aktualisht në përdorim + + Quit %1 + Dilni nga %1 - - - File has changed since discovery - Kartela ka ndryshuar, qëkurse u zbulua + + Are you sure you want to quit %1? + Jeni i sigurt se doni të dilet nga %1? - OCC::PropagateItemJob + OCC::ModalWrapperWidget - - ; Restoration Failed: %1 - ; Rikthimi Dështoi: %1 + + Close + Mbylle - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - s’u fshi dot kartela %1, gabim: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - S’krijohet dot dosje vendore %1, për shkak të një përplasje emrash kartele vendore me %2 - + OCC::Models - - Error updating metadata: %1 - Gabim në përditësim tejtëdhënash: %1 + + %1 Filter: + Filtër %1: - - The file %1 is currently in use - Kartela %1 është aktualisht në përdorim + + All + Krejt - - could not create folder %1 - s’u krijua dot dosja %1 + + Reset Filters + Ktheji Filtrat Te Parazgjedhjet - - - OCC::PropagateLocalRemove - - Could not remove %1 because of a local file name clash with %2! - S’u hoq dot %1, për shkak të një përplasjeje emrash kartele vendore me %2! + + Status Filter: + Filtër Gjendjesh: - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Kartela %1 s’mund të riemërtohet si %2, për shkak të një përplasje emrash kartele vendore - - - - Could not rename %1 to %2, the file is currently in use - S’u riemërtua dot %1 si %2, kartela është aktualisht në përdorim - - - - Error updating metadata: %1 - Gabim në përditësim tejtëdhënash: %1 - - - - The file %1 is currently in use - Kartela %1 është aktualisht në përdorim - - - - Failed to rename file - S’u arrit të riemërtohej kartela - + OCC::NetworkSettings - - Error setting pin state - Gabim në caktim gjendjeje pini + + Form + Formular - - - OCC::PropagateRemoteDelete - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Shërbyesi u përgjigj me kod HTTP të gabuar. Pritej 204, por u mor "%1 %2". + + Pause synchronization when the Internet connection is metered + Ndale njëkohësimin, kur lidhja Internet është me masë - - - OCC::PropagateRemoteMkdir - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Shërbyesi u përgjigj me kod HTTP të gabuar. Pritej 201, por u mor "%1 %2". + + Proxy Settings + Rregullime Ndërmjetësi - - Error writing metadata to the database: %1 - Gabim në shkrim tejtëdhënash te baza e të dhënave: %1 + + No Proxy + Pa Ndërmjetës - - Setting file status failed due to file lock - Caktimi i gjendjes së kartelës dështoi për shkak të një kyçjeje kartele + + Use system proxy + Përdor ndërmjetësin e sistemit - - - OCC::PropagateRemoteMove - - Could not rename %1 to %2, error: %3 - S’u emërtua dot %1 si %2, gabim: %3 + + Host + Strehë - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Shërbyesi u përgjigj me kod HTTP të gabuar. Pritej 201, por u mor "%1 %2". + + Proxy Hostname + Strehëemër Ndërmjetësi - - Error updating metadata: %1 - Gabim në përditësim tejtëdhënash: %1 + + : + : - - The file %1 is currently in use - Kartela %1 është aktualisht në përdorim + + Proxy Port Number + Numër Porte Ndërmjetësi - - Error writing metadata to the database - Gabim në shkrim tejtëdhënash te baza e të dhënave + + Proxy server requires authentication + Shërbyesi ndërmjetës lyp mirëfilltësim - - Error setting pin state - Gabim në caktim gjendjeje pini + + Note: proxy settings have no effects for accounts on localhost + Shënim: Rregullimet e ndërmjetësit nuk kanë efekt për llogari në localhost - - - OCC::PropagateUpdateMetaDataJob - - Could not update file : %1 - S’u përditësua dot kartela : %1 + + Specify proxy manually + Jepenni ndërmjetësin dorazi - - The file %1 is currently in use - Kartela %1 është aktualisht në përdorim + + Proxy type + Llok ndërmjetësi - - - OCC::PropagateUploadFileCommon - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Kartela %1 s’mund të ngarkohet, ngaqë ka një tjetër kartelë me të njëjtin emër, me ndryshim vetëm në shkronjë të madhe-shkronjë të vogël + + + Hostname of proxy server + Strehëemër për shërbyesin ndërmjetës - - File Removed - Kartela u Hoq + + + Username for proxy server + Emër përdoruesi për shërbyesin ndërmjetës - - Local file changed during syncing. It will be resumed. - Kartela vendore ndryshoi gjatë njëkohësimit. Do të rimerret. + + + Password for proxy server + Fjalëkalim për shërbyesin ndërmjetës - - Local file changed during sync. - Kartela vendore ndryshoi gjatë njëkohësimit. + + HTTP(S) proxy + Ndërmjetës HTTP(S) - - - Error updating metadata: %1 - Gabim në përditësim tejtëdhënash: %1 + + + SOCKS5 proxy + Ndërmjetës SOCKS5 + + + OCC::NewAccountBuilder - - The file %1 is currently in use - Kartela %1 është aktualisht në përdorim + + no error details are available + s’ka hollësi gabimi - - - Upload of %1 exceeds the quota for the folder - Ngarkimi i %1 i tejkalon kuotat për dosjen + + Unable to connect to server during account creation: %1. + S’arrihet të lidhet te shërbyesi gjatë krijimit të llogarisë: %1. - OCC::PropagateUploadFileNG + OCC::NewAccountWizard - - %1 the file is currently in use - %1 kartela është aktualisht në përdorim + + Cancel Setup + Anuloje Ujdisjen - - The local file was removed during sync. - Kartela vendore u hoq gjatë njëkohësimit. + + Do you really want to cancel the account setup? + Doni vërtet të anulohet ujdisja e llogarisë? + + + OCC::NewAccountWizardController - - Local file changed during sync. - Kartela vendore ndryshoi gjatë njëkohësimit. + + Add account… + Shtoni llogari… - - The server did ask for a removed legacy feature(polling) - Shërbyesi kërkoi një veçori të dikurshme që është hequr (vjelje) + + Back + Mbrapsht - - Unexpected return code from server (%1) - Kod i papritur përgjigjeje nga shërbyesi (%1) + + Advanced + Të mëtejshme - - Missing File ID from server - Mungon ID kartele nga shërbyesi + + Sign in + Hyni - - Missing ETag from server - Mungon ETag nga shërbyesi + + Open sign in again + Hapni sërish hyrjen - OCC::PropagateUploadFileTUS + OCC::NewVersionAvailableWidget - - %1 the file is currently in use - %1 kartela është aktualisht në përdorim + + Skip this version + Anashkaloje këtë version - - The local file was removed during sync. - Kartela vendore u hoq gjatë njëkohësimit. + + Get update + Merre përditësimin - - Local file changed during sync. - Kartela vendore ndryshoi gjatë njëkohësimit. + + Skip this time + Anashkaloje këtë herë - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - %1 kartela është aktualisht në përdorim - - - - The server did ask for a removed legacy feature(polling) - Shërbyesi kërkoi një veçori të dikurshme që është hequr (vjelje) - + OCC::OAuth - - The local file was removed during sync. - Kartela vendore u hoq gjatë njëkohësimit. + + Successfully signed in + U hy me sukses - - Local file changed during sync. - Kartela vendore ndryshoi gjatë njëkohësimit. + + Now, explore %1 on desktop. + Tani, eksploroni %1 në desktop. - - The server did not acknowledge the last chunk. (No e-tag was present) - Shërbyesi nuk e njohu copën e fundit. (S’kishte të pranishëm e-tag) + + Error returned from the server: <em>%1</em> + Gabim i kthyer nga shërbyesi: <em>%1</em> - - - OCC::ProtocolItemModel - - Time - Kohë + + There was an error accessing the 'token' endpoint: <br><em>%1</em> + Pati një gabim në përdorimin e pikëmbarimit të 'tokenit': <br><em>%1</em> - - File - Kartelë + + Could not parse the JSON returned from the server: <br><em>%1</em> + S’u përtyp dot JSON-i i kthyer nga shërbyesi: <br><em>%1</em> - - Folder - Dosje + + Unsupported token type: %1 + Lloj token-i i pambuluar: %1 - - Issues - Probleme + + The reply from the server did not contain all expected fields +:%1 + Përgjigja prej shërbyesit s’përmbante krejt fushat e pritura +:%1 - - Action - Veprim + + Unknown Error + Gabim i Panjohur - - Size - Madhësi + + + Login Error + Gabim Hyrjeje - - Account - Llogari + + You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please return to the %3 client and restart the authentication. + Hytë si përdoruesi <em>%1</em>, por duhet të hyni si përdoruesi <em>%2</em>.<br>Ju lutemi, kthehuni te klienti %3 dhe rinisni mirëfilltësimin. - - Status - Gjendje + + Wrong user + Përdorues i gabuar - OCC::ProtocolWidget + OCC::OAuthPageController - - Form - Formular + + Application Name Logo %1 + Stemë Emri Aplikacioni %1 - - Local sync protocol - Protokoll vendor njëkohësimesh + + Leave this screen open. A sign in prompt will appear in your web browser to connect you to the following address. + Lëreni hapur këtë skenë. Te shfletuesi juaj do të duket një kërkesë për hyrje, për t’ju lidhur te adresa vijuese. - - Filter - Filtër + + Sign in URL + URL Hyrjeje - - Account - Llogari + + To copy the sign in URL to the clipboard, use the copy button + Që të kopjoni në të papastër URL-në e hyrjes, përdorni butonin Kopjoje - - Copy to clipboard - Kopjoje në të papastër + + Copy URL to sign in + Kopjo URL-në për hyrje - - Show in file browser - Me këtë hedhje në qarkullim Clementine-a përfiton një dialog fringo të ri përpunimi etiketash, me vetëplotësim dhe me aftësinë të identifikojë automatikisht muzikë dhe të sjellë etiketa që mungojnë që nga MusicBrainz. Tani mbulohen “CUE sheets” - këto pikasen automatikisht kur skanohet mediateka juaj dhe çdo pjesë do të shfaqet veçmas. Kemi bërë edhe shumë përmirësime të vogla, të tilla si shfaqja e kopertinës së albumit te skeda Mediatekë, duke ngjyrosur gri këngët e fshira, shtimi i një mundësie “Shfaqe në shfletues kartelash”, mbulim për ndërmjetës rrjeti, shtimi i një mundësie “Riskanim i plotë i mediatekës”, dhe një ndihmëz të re për rrëshqitësin e pjesë + + Additional logo defined by the organization + Stemë shtesë e përcaktuar nga enti - - Show in web browser - Shfaqe në shfletues + + URL copied + URL-ja u kopjua - - Retry sync - Riprovo njëkohësim + + Copy URL + Kopjoji URL-në - - - OCC::ProxyAuthDialog - - Proxy authentication required - Lypset mirëfilltësim ndërmjetësi + + Unable to connect to the login service. If the problem continues, please contact support. + S’arrihet të bëhet lidhja me shërbimin e hyrjeve. Nëse problemi vazhdon, ju lutemi, lidhuni me asistencën. - - Username: - Emër përdoruesi: + + Please copy the authentication URL using the button above, and provide this information to support. + Ju lutemi kopjoni URL-në e mirëfilltësimit duke përdorur butonin më sipër dhe jepeni këtë informacion për asistencën. - - Proxy: - Ndërmjetës: + + Failed to look up WebFinger instances: %1 + S’u arrit të kërkohet për instanca WebFinger: %1 - - The proxy server needs a username and password. - Shërbyesi ndërmjetës lyp një emër përdoruesi dhe fjalëkalim. + + You are already connected to an account with these credentials. + Jeni tashmë i lidhur te një llogari me këto kredenciale. - - Password: - Fjalëkalim: + + Unable to retrieve capabilities from server. + S’arrihet të merren aftësi prej shërbyesit. - - TextLabel - EtiketëTeksti + + The server is not supported by this client. + Shërbyesi nuk mbulohet nga ky klient. - - - OCC::SelectiveSyncDialog - - Choose What to Sync - Zgjidhni Çfarë të Njëkohësohet + + Error while trying to log in to OAuth2-enabled server. %1 + Gabim teksa provohej të bëhej hyrja te një shërbyes me OAuth2-të aktivizuar. %1 - - - OCC::SelectiveSyncWidget - - Loading ... - Po ngarkohet … + + Unable to open browser with provided URL. %1 + S’arrihet të hapet shfletuesi me URL-në që u dha. %1 - - Deselect remote folders you do not wish to synchronize. - Shpërzgjidhni dosje të largëta që nuk doni të njëkohësohen. + + OAuth2 authentication requires a secured connection. %1 + Mirëfilltësimi OAuth2 lyp një lidhje të siguruar. %1 - - Name - Emër + + Authorization server unreachable. %1 + S’kapet dot shërbyes mirëfilltësimesh. %1 + + + OCC::OCUpdater - - Size - Madhësi + + Could not download update. Please click <a href='%1'>here</a> to download the update manually. + S’u shkarkua dot përditësimi. Ju lutemi, klikoni <a href='%1'>këtu</a> që ta shkarkoni përditësimin dorazi. - - - No subfolders currently on the server. - Hëpërhë s’ka nëndosje te shërbyesi. + + Could not check for new updates. + S’u kontrollua dot për përditësime të reja. - - An error occurred while loading the list of sub folders. - Ndodhi një gabim teksa ngarkohej lista e nëndosjeve. + + Downloading %1. Please wait… + Po shkarkohet %1. Ju lutemi, prisni… - - - OCC::ServerNotificationHandler - - Dismiss - Mos e merr parasysh + + %1 installed successfully. Restart the application to finish installing the update. + %1 u instalua me sukses. Rinisni aplikacioni, që të përfundohet instalimi i përditësimit. - - - OCC::SettingsDialog - - Settings - Rregullime + + %1 available. Restart application to start the update. + %1 i gatshëm. Rinisni aplikacionin që të nisë përditësimi. - - Activity - Veprimtari + + New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. + Ka gati %1 të ri. Ju lutemi, klikoni <a href='%2'>këtu</a> që të shkarkoni dorazi AppImage-in e ri. - - Add account - Shtoni llogari + + New %1 available. Please use the system's update tool to install it. + %1 i ri i gatshëm. Ju lutemi, për ta instaluar, përdorni mjetin e sistemit për përditësime. - - - Quit %1 - Dilni nga %1 + + Checking update server… + Po kontrollohet shërbyesi i përditësimeve… - - Are you sure you want to quit %1? - Jeni i sigurt se doni të dilet nga %1? + + Update status is unknown: Did not check for new updates. + Gjendja e përditësimit është e panjohur: S’u kontrollua për përditësime të rinj. - - Account - Llogari + + No updates available. Your installation is at the latest version. + S’ka përditësime gati. Instalimi juaj është versioni më i ri. + + + OCC::OwncloudPropagator - - MainWindow - + + File %1 is currently in use + Kartela %1 është aktualisht në përdorim - - Hide - Fshihe + + vfs instance is not available. + instanca vfs s’është e përdorshme. - OCC::ShareDialog + OCC::ProcessDirectoryJob + + + Symbolic links are not supported in syncing. + Njëkohësimi s’i mbulon lidhjet simbolike. + + + + File is listed on the ignore list. + Kartela bën pjesë te lista e shpërfilljeve. + - - TextLabel - EtiketëTeksti + + File names ending with a period are not supported on this file system. + Në këtë sistem kartelash nuk mbulohen emra kartelash që mbarojnë me një pikë. - - share label - etiketë ndarjeje + + File names containing the character '%1' are not supported on this file system. + Në këtë sistem kartelash nuk mbulohen emra kartelash që përmbajnë shenjën '%1'. - - Dialog - Dialog + + File name contains at least one invalid character + Emri i kartelës përmban të paktën një shenjë të pavlefshme - - ownCloud Path: - Shteg ownCloud-i: + + The file name is a reserved name on this file system. + Në këtë sistem kartelash, emri i kartelës është një emër i rezervuar. - - %1 Sharing - Ndarje e %1 + + Filename contains trailing spaces. + Emri i kartelës përmban shenja të zbrazëta në fund. - - %1 - %1 + + Filename is too long. + Emri i kartelës është shumë i gjatë. - - Folder: %2 - Dosje: %2 + + File/Folder is ignored because it's hidden. + Kartela/Dosja është shpërfillur, ngaqë është e fshehur. - - The server does not allow sharing - Shërbyesi nuk lejon ndarjet + + Stat failed. + Statistikat dështuan. - - Retrieving maximum possible sharing permissions from server... - Po merren nga shërbyesi lejet maksimum të mundshme për ndarje… + + Conflict: Server version downloaded, local copy renamed and not uploaded. + Përplasje: Versioni i shërbyesit u shkarkua, kopja vendore u riemërtua dhe s’është ngarkuar. - - The file can not be shared because it was shared without sharing permission. - Kartela s’mund të ndahet me të tjerët, ngaqë qe ndarë pa leje për ndarje. + + The filename cannot be encoded on your file system. + Emri i kartelës s’mund të kodohet në sistemin tuaj të kartelave. - - Users and Groups - Përdorues dhe Grupe + + The filename is blacklisted on the server. + Emri i kartelës gjendet në listë bllokimesh te shërbyesi. - - Public Links - Lidhje Publike + + + + + the VFS instance is missing. Unable to continue. + instanca VFS mungon. S’arrihet të vazhdohet. - - - OCC::ShareLinkWidget - - Share NewDocument.odt - Ndani DokumentiRi.odt + + server reported no %1 + shërbyesi s’raportoi %1 - - Upload only (File Drop) - Vetëm ngarkim (Lënie Kartele) + + + account was deleted. Unable to continue + llogaria qe fshirë. S’është në gjendje të vazhdohet - - Receive files from others without revealing the contents of the folder. - Merr kartela prej të tjerësh pa e zbuluar lëndën e dosjes. + + SelectiveSync: Ignored because its path is deselected + SelectiveSync: U shpërfill, pasi shtegu i vet qe shpërzgjedhur - - Download / View / Edit - Të shkarkojë / Të shohë / Të përpunojë + + Moved to invalid target, restoring + U shpu te vendmbërritje e pavlefshme, po rikthehet - - Name: - Emër: + + Not allowed to remove, restoring + S’lejohet të hiqet, po rikthehet - - Enter name of new share... - Jepni emër të ndarjes së re… + + Error while reading the database. + Gabim teksa lexohej baza e të dhënave. - - &Create - &Krijo + + account was deleted. Unable to continue. + llogari u fshi. S’arrihet të vazhdohet. - - TextLabel - EtiketëTeksti + + Not allowed because you don't have permission to add subfolders to that folder + S’lejohet, ngaqë s’keni leje për shtim nëndosjesh te ajo dosje - - Set &password - Caktoni &fjalëkalim + + Error while doing a rename, unhandled status code: %1 + Gabim teksa bëhej një riemërtim, kod gjendjeje të patrajtuar: %1 - - Set &expiration date - Caktoni datë &skadimi + + Not allowed because you don't have permission to add files in that folder + S’lejohet, ngaqë s’keni leje të shtoni kartela te ajo dosje - - Set password - Caktoni fjalëkalimin + + Not allowed to upload this file because it is read-only on the server, restoring + S’lejohet të ngarkohet kjo kartelë, ngaqë është vetëm për lexim te shërbyesi, po rikthehet - - Recipients can view or download contents. - Marrësit mund të shohin dhe shkarkojnë lëndë. + + This Space is currently unavailable + Kjo hapësirë aktualisht është e papërdorshme - - Download / View - Të shkarkojë / Të shohë + + Server replied with an error while reading directory '%1' : %2 + Shërbyesi u përgjigj me një gabim teksa lexohej drejtoria '%1' : %2 + + + OCC::PropagateDirectory - - Recipients can view, download, edit, delete and upload contents. - Marrësit mund të shohin shkarkojnë, përpunojnë, fshijnë dhe ngarkojnë lëndë. + + Error updating metadata: %1 + Gabim në përditësim tejtëdhënash: %1 - - Anyone with the link has access to the file/folder - Cilido që zotëron lidhjen, ka hyrje te kartela/dosja + + %1 the folder is currently in use + + + + OCC::PropagateDownloadFile - - - P&assword protect - Mbroje me fj&alëkalim + + Failed to free up space, the file %1 is currently in use + S’u arrit të lirohet hapësirë, kartela %1 është aktualisht në përdorim - - The file can not be shared because it was shared without sharing permission. - Kartela s’mund të ndahet me të tjerët, ngaqë qe ndarë pa leje për ndarje. + + + File %1 can not be downloaded because of a local file name clash with %2! + Kartela %1 s’mund shkarkohet, për shkak të një përplasjeje emrash kartele vendore me %2! - - Link shares have been disabled - Ndarjet përmes lidhjesh janë çaktivizuar + + The download would reduce free local disk space below the limit + Shkarkimi mund të ulë nën kufi sasinë e hapësirës së lirë në disk - - Create public link share - Krijo lidhje publike ndarjesh + + Free space on disk is less than %1 + Hapësira e lirë në disk është nën %1 - - - Delete - Fshije + + File was deleted from server + Kartela është fshirë te shërbyesi - - Open link in browser - Hape lidhjen në shfletues + + Broken webserver returned empty content length for non-empty file on resume + Shërbyes i dëmtuar u përgjigj me gjatësi lënde të zbrazët për kartelë jo të zbrazët, gjatë rimarrjeje - - Copy link to clipboard - Kopjoja lidhjen në të papastër + + The file could not be downloaded completely. + Kartela s’u shkarkua plotësisht. - - Copy link to clipboard (direct download) - Kopjoja lidhjen në të papastër (shkarkim i drejtpërdrejtë) + + The downloaded file is empty despite the server announced it should have been %1. + Kartela e shkarkuar është e zbrazët, edhe pse shërbyesi njoftoi se duhej të ishte %1. - - Send link by email - Dërgoje lidhjen me email + + File %1 cannot be saved because of a local file name clash with %2! + Kartela %1 s’mund të ruhet, për shkak të një përplasjeje emrash kartele vendore me %2! - - Send link by email (direct download) - Dërgoje lidhjen me email (shkarkim i drejtpërdrejt) + + Error updating metadata: %1 + Gabim në përditësim tejtëdhënash: %1 - - Create new... - Krijoni të re… + + + + + The file %1 is currently in use + Kartela %1 është aktualisht në përdorim - - I shared %1 with you - Ndava %1 me ju + + + File has changed since discovery + Kartela ka ndryshuar, qëkurse u zbulua + + + OCC::PropagateItemJob - - Confirm Link Share Deletion - Ripohoni Fshirje Lidhjeje Ndarjesh + + ; Restoration Failed: %1 + ; Rikthimi Dështoi: %1 + + + OCC::PropagateLocalMkdir - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Doni vërtet të fshihet lidhja publike e ndarjes <i>%1</i>?</p><p>Shënim: Ky veprim s’mund të zhbëhet.</p> + + could not delete file %1, error: %2 + s’u fshi dot kartela %1, gabim: %2 - - Cancel - Anuloje + + Can not create local folder %1 because of a local file name clash with %2 + S’krijohet dot dosje vendore %1, për shkak të një përplasje emrash kartele vendore me %2 - - - Public link - Lidhje publike + + Error updating metadata: %1 + Gabim në përditësim tejtëdhënash: %1 - - Delete link share - Fshi lidhje ndarjesh + + The file %1 is currently in use + Kartela %1 është aktualisht në përdorim - - - Please Set Password - Ju lutemi, Caktoni Fjalëkalim + + could not create folder %1 + s’u krijua dot dosja %1 - OCC::ShareUserGroupWidget + OCC::PropagateLocalRemove + + + %1 failed with: %2 + %1 dështoi me: %2 + - - Share NewDocument.odt - Ndani DokumentiRi.odt + + Could not remove %1 because of a local file name clash with %2! + S’u hoq dot %1, për shkak të një përplasjeje emrash kartele vendore me %2! - - Share with users or groups ... - Ndajeni me përdorues ose grupe … + + Could not move '%1' to the trash bin + S’u kalua dot “%1” te koshi i hedhurinave - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>Mund t’i drejtoni njerëzit te kjo kartelë ose dosje e ndarë <a href="private link menu"><span style=" text-decoration: underline">duke u dhënë atyre një lidhje private</span></a>.</p></body></html> + + + %1 the file is currently in use + %1 kartela është aktualisht në përdorim + + + OCC::PropagateLocalRename - - The item is not shared with any users or groups - Ky objekt s’është ndarë me ndonjë përdorues apo grup + + File %1 can not be renamed to %2 because of a local file name clash + Kartela %1 s’mund të riemërtohet si %2, për shkak të një përplasje emrash kartele vendore - - Open link in browser - Hape lidhjen në shfletues + + Could not rename %1 to %2, the file is currently in use + S’u riemërtua dot %1 si %2, kartela është aktualisht në përdorim - - Copy link to clipboard - Kopjoja lidhjen në të papastër + + Error updating metadata: %1 + Gabim në përditësim tejtëdhënash: %1 - - Send link by email - Dërgoje lidhjen me email + + The file %1 is currently in use + Kartela %1 është aktualisht në përdorim - - No results for '%1' - Pa përfundime për '%1' + + Failed to rename file + S’u arrit të riemërtohej kartela - - I shared something with you - Ndava diçka me ju + + Error setting pin state + Gabim në caktim gjendjeje pini - OCC::ShareUserLine + OCC::PropagateRemoteDelete - - Form - Formular + + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". + Shërbyesi u përgjigj me kod HTTP të gabuar. Pritej 204, por u mor "%1 %2". + + + OCC::PropagateRemoteMkdir - - - TextLabel - EtiketëTeksti + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Shërbyesi u përgjigj me kod HTTP të gabuar. Pritej 201, por u mor "%1 %2". - - can edit - mund të përpunojnë + + Error writing metadata to the database: %1 + Gabim në shkrim tejtëdhënash te baza e të dhënave: %1 - - can share - mund të ndajnë + + Setting file status failed due to file lock + Caktimi i gjendjes së kartelës dështoi për shkak të një kyçjeje kartele + + + OCC::PropagateRemoteMove - - ... - + + Wrong HTTP code returned by server. Expected 201, but received "%1 %2". + Shërbyesi u përgjigj me kod HTTP të gabuar. Pritej 201, por u mor "%1 %2". - - create - krijoje + + Error updating metadata: %1 + Gabim në përditësim tejtëdhënash: %1 - - change - ndrysho + + The file %1 is currently in use + Kartela %1 është aktualisht në përdorim - - delete - fshi + + Error writing metadata to the database + Gabim në shkrim tejtëdhënash te baza e të dhënave - - - OCC::ShareeModel - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) + + Error setting pin state + Gabim në caktim gjendjeje pini - OCC::SocketApi + OCC::PropagateUpdateMetaDataJob - - Share with %1 - parameter is ownCloud - Ndajeni me %1 + + Could not update file : %1 + S’u përditësua dot kartela : %1 - - Context menu share - Ndarje përmes menuje konteksti + + The file %1 is currently in use + Kartela %1 është aktualisht në përdorim + + + OCC::PropagateUploadCommon - - Context menu share %1 - Ndarje përmes menuje konteksti %1 + + Local file changed during sync. It will be resumed. + Kartela vendore ndryshoi gjatë njëkohësimit. Do të rimerret. - - Confirm deletion - Ripohoni fshirjen + + File %1 cannot be uploaded because another file with the same name, differing only in case, exists + Kartela %1 s’mund të ngarkohet ngaqë ekziston një kartelë tjetër me të njëjtin emër, që ndryshon vetëm në shkrim me të mëdha a të vogla - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - Doni të fshihet përgjithmonë drejtoria <i>%1</i> dhe krejt lënda e saj? + + + Upload of %1 exceeds the quota for the folder + Ngarkimi i %1 tejkalon kuotat për dosjen - - Do you want to delete the file <i>%1</i> permanently? - Doni të fshihet përgjithnjë kartela <i>%1</i>? + + + %1 the file is currently in use + %1 kartela është aktualisht në përdorim - - Select new location... - Përzgjidhni vendndodhje të re… + + File Removed + Kartela u Hoq - - Error - Gabim + + The server did not provide the file permissions + Shërbyesi s’dha lejet për kartelën - - Moving file failed: - -%1 - Dështoi zhvendosja e kartelës: - -%1 + + Error updating metadata: %1 + Gabim në përditësim tejtëdhënash: %1 + + + OCC::PropagateUploadFile - - I shared something with you - Ndava diçka me ju + + %1 the file is currently in use + %1 kartela është aktualisht në përdorim - - - Share... - Ndani me të tjerë… + + The server did ask for a removed legacy feature(polling) + Shërbyesi kërkoi një veçori të dikurshme të hequr (polling) - - - Copy private link to clipboard - Kopjoje lidhjen private në të papastër + + The local file was removed during sync. + Kartela vendore u hoq gjatë njëkohësimit. - - Send private link by email... - Dërgoni lidhje private me email… + + The server did not acknowledge the file upload. (No e-tag was present) + Shërbyesi nuk njohu ngarkimin e kartelës. (S’pati e-tag të pranishëm) + + + OCC::PropagateUploadFileTUS - - Resharing this file is not allowed - Rindarja e kësaj kartelë s’është e lejuar + + %1 the file is currently in use + %1 kartela është aktualisht në përdorim - - Resharing this folder is not allowed - Nuk lejohet rindarja e kësaj dosjeje + + Checksum computation failed + Njehsimi i checksum-it dështoi - - Create and copy public link to clipboard - Krijoni dhe kopjojeni lidhjen publike në të papastër + + The local file was removed during sync. + Kartela vendore u hoq gjatë njëkohësimit. + + + OCC::ProtocolItemModel - - Copy public link to clipboard - Kopjoje lidhjen publike në të papastër + + Time + Kohë - - Open in browser - Hape në shfletues + + File + Kartelë - - Show file versions in browser - Shfaq në shfletues versione kartele + + Folder + Dosje - - Rename... - Riemërtoni… + + Issues + Probleme - - Rename and upload... - Riemërtoni dhe ngarkoni… + + Action + Veprim - - Move and rename... - Zhvendosni dhe riemërtoni… + + Size + Madhësi - - Move, rename and upload... - Zhvendosni, riemërtoni dhe ngarkoni… + + Account + Llogari - - Delete local changes - Fshiji ndryshimet vendore + + Status + Gjendje + + + OCC::RequestAuthenticationController - - Move and upload... - Zhvendosni dhe ngarkoni… + + IdP is unreachable. Contact your system administrator or try again later. + IdP s’kapet dot. Lidhuni me përgjegjësin e sistemit tuaj, ose riprovoni më vonë. - - Delete - Fshije + + Authentication failed. + Mirëfilltësimi fështoi. - OCC::SocketUploadJob + OCC::RequestAuthenticationWidget - - Failed to rerieve tags - S’u arrit të merreshin etiketa + + Application Name Logo %1 + Stemë Emri Aplikacioni %1 - - Backup of %1 started - Filloi kopjeruajtja e %1 + + Sign in required + Lypset të bëni hyrjen - - Failed to find %1 - S’u arrit të gjendet %1 + + You may have been automatically disconnected due to a server issue or time out. Please sign in again. + Mund të jeni shkëputur automatikisht, për shkak të një problemi shërbyesi, ose mbarimi kohe. Ju lutemi, ribëni hyrjen. - - Local path must be a an absolute path - Shtegu vendor duhet të jetë një shteg absolut + + Sign in URL + URL Hyrjeje - - Failed to create temporary database - S’u arrit të krijohet bazë e përkohshme të dhënash + + To copy the sign in URL to the clipboard, use the copy button + Që të kopjoni në të papastër URL-në e hyrjes, përdorni butonin Kopjoje - - Backup of %1 succeeded - Kopjeruajtja e %1 doli me sukses + + Copy URL to sign in + Kopjo URL-në për hyrje - - Failed to set success tag - S’u arrit të caktohet etiketë suksesi + + Additional logo defined by the organization + Stemë shtesë e përcaktuar nga enti - - Failed to create backup: %1 - S’u arrit të krijohet kopjeruajtje: %1 + + Stay logged out + Rrini i dalë - - Failed to file id tags - S’u arrit të skedohen etiketa ID + + Sign in + Hyni - - Backup of %1 failed with: %2 - Kopjeruajta e %1 dështoi me: %2 + + URL copied + URL-ja u kopjua - - %1 backup - %1 kopjeruajtje + + Copy URL + Kopjoji URL-në - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Hollësi Dëshmie</h3> - + OCC::ResolveUrlAdapter - - Common Name (CN): - Emër i Zakonshëm (CN) + + SSL failure when connecting to server at %1 + Dështim SSL-je kur lidhet me shërbyesin te %1 - - Subject Alternative Names: - Emra Alternativë Subjekti: + + Could not detect compatible server at %1 + S’u pikas dot shërbyes i përputhshmë te %1 - - Organization (O): - Organizëm (O): + + Resolved url is invalid %1 + URL-ja e ftilluar është %1 e pavlefshme - - Organizational Unit (OU): - Njësi Organizative (NjO): - - - - State/Province: - Shtet/Provincë: + + Rejected redirect from %1 to %2 + U hodh poshtë ridrejtim nga %1 te %2 + + + OCC::SelectiveSyncWidget - - Country: - Vend: + + Loading … + Po ngarkohet… - - Serial: - Serial: + + Deselect remote folders you do not wish to synchronize. + Shpërzgjidhni dosje të largëta që nuk doni të njëkohësohen. - - <h3>Issuer</h3> - <h3>Lëshues</h3> + + Name + Emër - - Issuer: - Lëshues: + + Size + Madhësi - - Issued on: - Lëshuar më: + + + Currently there are no subfolders on the server. + Aktualisht te shërbyesi s’ka nëndosje. - - Expires on: - Skadon më: + + An error occurred while loading the list of subfolders. + Ndodhi një gabim teksa ngarkohej lista e nëndosjeve. + + + OCC::SettingsView - - <h3>Fingerprints</h3> - <h3>Shenja gishtash</h3> + + Form + Formular - - SHA-256: - SHA-256: + + General Settings + Rregullime të Përgjithshme - - SHA-1: - SHA-1: + + Use Monochrome Icons in the system tray + Përdor Ikona Njëngjyrëshe te paneli i sistemit - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Shënim:</b> Kjo dëshmi qe miratuar dorazi</p> + + Show Desktop Notifications + Shfaq Njoftime Desktopi - - %1 (self-signed) - %1 (e vetënënshkruar) + + Start on Login + Nisu gjatë Hyrjes Në Llogari - - %1 - %1 + + Language + Gjuhë - - This connection is encrypted using %1 bit %2. - - Kjo lidhje është fshehtëzuar duke përdorur %1 bit %2. - + + Language selector + Përzgjedhës gjuhe - - Server version: %1 - Version shërbyesi: %1 + + Advanced + Të mëtejshme - - No support for SSL session tickets/identifiers - Pa mbulim pusullash/identifikuesish sesioni SSL + + Sync hidden files + Njëkohëso kartela të fshehura - - Certificate information: - Të dhëna dëshmie: + + Show crash reporter + Shfaq njoftues vithisjesh - - The connection is not secure - Lidhja s’është e siguruar + + Move remotely deleted files to the local trash bin instead of deleting them + Shpjeri kartelat e fshira te koshi lokal i hedhurinave, në vend se t’i fshish - - This connection is NOT secure as it is not encrypted. - - Kjo lidhje NUK është e sigurt, ngaqë s’është e fshehtëzuar. - + + Edit Ignored Files + Përpunoni Kartelat e Shpërfillura - - - OCC::SslErrorDialog - - Form - Formular + + Log Settings + Rregullime Regjistrimi - - Trust this certificate anyway - Besoje këtë dëshmi, sido qoftë + + Network + Rrjet - - Untrusted Certificate - Dëshmi e Pabesuar + + Warning + Kujdes - - Cannot connect securely to <i>%1</i>: - S’lidhet dot në mënyrë të sigurt te <i>%1</i>: + + Language changes require a restart of this application to take effect. + Ndryshimet e gjuhës lypin rinisje të këtij aplikacioni, që të hyjnë në fuqi. - - with Certificate %1 - me Dëshmi %1 + + You cannot disable autostart because system-wide autostart is enabled. + S’mund ta çaktivizoni vetënisjen, ngaqë është e aktivizuar vetënisja për krejt sistemin. - - - - &lt;not specified&gt; - &lt;e papërcaktuar&gt; + + (use default) + (përdor parazgjedhjen) - - - Organization: %1 - Organizëm: %1 + + unknown (%1) + e panjohur (%1) + + + OCC::SocketApi - - - Unit: %1 - Njësi: %1 + + Share with %1 + parameter is the application name + Ndaje me %1 - - - Country: %1 - Vend: %1 + + Confirm deletion + Ripohoni fshirjen - - Fingerprint (MD5): <tt>%1</tt> - Shenja gishtash (MD5): <tt>%1</tt> + + Do you want to delete the directory <i>%1</i> and all its contents permanently? + Doni të fshihet përgjithmonë drejtoria <i>%1</i> dhe krejt lënda e saj? - - Fingerprint (SHA1): <tt>%1</tt> - Shenja gishtash (SHA1): <tt>%1</tt> + + Do you want to delete the file <i>%1</i> permanently? + Doni të fshihet përgjithnjë kartela <i>%1</i>? - - Effective Date: %1 - Datë Hyrjeje Në Fuqi: %1 + + Error + Gabim - - Expiration Date: %1 - Datë skadimi: %1 + + Moving file failed: + +%1 + Dështoi zhvendosja e kartelës: + +%1 - - Issuer: %1 - Lëshues: %1 + + I shared something with you + Ndava diçka me ju - - - OCC::SyncEngine - - %1 (skipped due to earlier error, trying again in %2) - %1 (u anashkalua për shkak të një gabimi më të hershëm, do të riprovohet për %2) + + Select new location… + Përzgjidhni vendndodhje të re… - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Ka të lira vetëm %1, që të fillohet lypsen të paktën %2 + + + Share… + Ndani me të tjerë… - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - S’arrihet të hapet apo të krijohet një bazë vendore të dhënash njëkohësimi. Sigurohuni që keni të drejta shkrimi te dosja e njëkohësimeve. + + + Copy private link to clipboard + Kopjoje lidhjen private në të papastër - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Hapësira në disk është e pakët: Shkarkimet që do ta ulnin hapësirën e lirë nën %1 janë anashkaluar. + + Resharing this file is not allowed + Rindarja e kësaj kartelë s’është e lejuar - - There is insufficient space available on the server for some uploads. - Ka hapësirë të pamjaftueshme në shërbyes për disa nga ngarkimet. + + Resharing this folder is not allowed + Nuk lejohet rindarja e kësaj dosjeje - - Unresolved conflict. - Përplasje e pazgjidhur. + + Open in %1 + Hape me %1 - - Using virtual files with suffix, but suffix is not set - Po përdoren kartela virtuale me prapashtesë, por s’është caktuar prapashtesë + + Show file versions in web browser + Shfaq në shfletues versione kartelash - - Unable to read the blacklist from the local database - S’arrihet të lexohet lista e bllokimeve prej bazës vendore të të dhënave + + Rename… + Riemërtojeni… - - Unable to read from the sync journal. - S’arrihet të lexohet prej ditari njëkohësimesh. + + Rename and upload… + Riemërtoni dhe ngarkoni… - - Cannot open the sync journal - S’hapet dot regjistri i njëkohësimeve. + + + Move and rename… + Lëvizni dhe riemërtoni… - - Aborted - U ndërpre + + Move, rename and upload… + Lëvizni, riemërtoni dhe ngarkoni… - - - OCC::SyncJournalDb - - Failed to connect database. - S’u arrit të lidhej baza e të dhënave. + + Move and upload… + Lëvizni dhe ngarkoni… - - - OCC::Systray - - %1: %2 - %1: %2 + + Delete local changes + Fshiji ndryshimet vendore - - - OCC::Theme - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>Version %1. Për më tepër të dhëna, vizitoni <a href="%2">https://%3</a></p><p>Për probleme të ditura dhe ndihmë, ju lutemi, vizitoni: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>Nga Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, dhe të tjerë.</small></p><p>Të drejta kopjimi ownCloud GmbH</p><p>Shpërndarë sipas %4 dhe licencuar sipas GNU General Public License (GPL) Version 2.0.<br/>%5 dhe stema e %5 janë shenja tregtare të regjistruara të %4 në Shtetet e Bashkuara, vende të tjera, ose që të dyja rastet bashkë.</p><p><small>%6</small></p> + + Delete + Fshije - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Kryet e checksum-it janë të keqformuara. - + OCC::SpacesPage - - The checksum header contained an unknown checksum type '%1' - Kryet e checksum-it përmbanin një lloj të panjohur checksum-i '%1' + + Select a folder to sync it to your computer. + Përzgjidhni një dosje që të njëkohësohet me kompjuterin tuaj. - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - Kartela e shkarkuar nuk përputhet me vlerën e llogaritur të checksum-it, do të rimerret. '%1' != '%2' + + Select a Space to sync it to your computer. + Përzgjidhni një Hapësirë që të njëkohësohet me kompjuterin tuaj. - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - Veçoria për sistem Virtual kartelash nuk mbulon disk si “sync root” - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - Veçoria sistem Virtual kartelash lyp një sistem NTFS kartelash, %1 po përdor %2 - + OCC::SyncEngine - - The Virtual filesystem feature is not supported on network drives - Veçoria e sistemit Virtual të kartelave nuk mbulohet në disqe rrjeti + + %1 (skipped due to earlier error, trying again in %2) + %1 (u anashkalua për shkak të një gabimi më të hershëm, do të riprovohet për %2) - - - OCC::ownCloudGui - - Please sign in - Ju lutemi, bëni hyrjen + + Unable to sync: the local folder is missing. + S’arrihet të bëhet njëkohësim: mungon dosja vendore. - - Folder %1: %2 - Dosja %1: %2 + + Only %1 are available, need at least %2 to start + Placeholders are postfixed with file sizes using Utility::octetsToString() + Ka të lira vetëm %1, që të fillohet lypsen të paktën %2 - - Unresolved %1 conflicts - %1 përplasje të pazgjidhura + + Unable to open or create the local sync database. Make sure you have write access in the sync folder. + S’arrihet të hapet apo të krijohet një bazë vendore të dhënash njëkohësimi. Sigurohuni që keni të drejta shkrimi te dosja e njëkohësimeve. - - Ignored errors %1 - Gabime të shpërfillur %1 + + + + + Cannot open the sync journal. + S’hapet dot regjistër njëkohësimesh. - - There are no sync folders configured. - S’ka të formësuara dosje njëkohësimi. + + Aborted due to %1 + U ndërpre për shkak të %1 - - Open in browser - Hape në shfletues + + Disk space is low: Downloads that would reduce free space below %1 were skipped. + Hapësira në disk është e pakët: Shkarkimet që do ta ulnin hapësirën e lirë nën %1 janë anashkaluar. - - - - Log in... - Hyni… + + There is insufficient space available on the server for some uploads. + Ka hapësirë të pamjaftueshme në shërbyes për disa nga ngarkimet. - - - - Log out - Dilni + + application exit + abort reason + dalje nga aplikacioni - - Recent Changes - Ndryshime Së Fundi + + Unable to read the blacklist from the local database + S’arrihet të lexohet lista e bllokimeve prej bazës vendore të të dhënave - - Managed Folders: - Dosje të Administruara: + + Unable to read from the sync journal. + S’arrihet të lexohet prej ditari njëkohësimesh. + + + OCC::SyncErrorWidget - - Open folder '%1' - Hapni dosjen '%1' + + Issues table + Tabelë problemesh - - Open %1 in browser - Hapeni %1 në shfletues + + There were too many issues. Not all will be visible here. + Pati shumë probleme. Këtu s’do të duket të tërë. - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - Shërbyesi në llogarinë %1 xhiron një version të pambuluar të %2. Përdorimi i këtij klienti me versione të pambuluar të shërbyesit është i patestuar dhe potencialisht i rrezikshëm. Qari dhe zarari i juaj. + + There were conflicts. Check the documentation on how to resolve them. + Pati përplasje. Shihni te dokumentimi se si t’i zgjidhni. - - Unknown status - Gjendje e panjohur + + The file %1 was ignored as its name is reserved by %2 + Kartela %1 qe shpërfilluar, ngaqë emri i saj është i rezervuar nga %2 - - Settings... - Rregullime… + + Reset column sizes + Ricaktoni madhësi shtyllash - - Details... - Hollësi… + + There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> + Pati përplasje. <a href="%1">Shihni te dokumentimi se si t’i zgjidhni.</a> - - Help - Ndihmë + + Filter menu + Menu filtrimesh - - Quit %1 - Dilni nga %1 + + Account + Llogari - - Disconnected from %1 - U shkëput prej %1 + + Filter + Filtroni + + + OCC::SyncJournalDb - - Unsupported Server Version - Version i Pambuluar Shërbyesi + + Failed to connect database. + S’u arrit të lidhej baza e të dhënave. + + + OCC::Theme - - Disconnected - I shkëputur + + <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller and others.</small></p><p>Copyright ownCloud GmbH (A Kiteworks Company)</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> + <p>Version %1. Për më tepër hollësi, vizitoni <a href="%2">https://%3</a></p><p>Për probleme të ditura dhe ndihmë, ju lutemi, vizitoni: <a href="https://central.owncloud.com/c/desktop-client">https://central.owncloud.com</a></p><p><small>Nga Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller,<br>Dominik Schmidt, Michael Stingl, Hannah von Reth, Fabian Müller dhe të tjerë.</small></p><p>Të drejta kopjimi ownCloud GmbH (A Kiteworks Company)</p><p>Shpërndarë nga %4 dhe licencuar sipas GNU General Public License (GPL) Version 2.0.<br/>%5 dhe stema e %5 janë shenja tregtare të regjistruara të %4 në Shtetet e Bashkuara, vende të tjera, ose te që të dy palët.</p><p><small>%6</small></p> + + + OCC::TlsErrorDialog - - Disconnected from some accounts - I shkëputur prej disa llogarish + + TLS Certificate Error + Gabim Dëshmie TLS - - Disconnected from accounts: - U shkëput prej llogarive: + + Do you want to trust this certificate anyway? + Doni të besohet kjo dëshmi sido qoftë? - - Account %1: %2 - Llogaria %1: %2 + + Cannot connect securely to %1 + S’mund të lidhet në mënyrë të parrezik te %1 - - Signed out - Dolët + + &lt;not specified&gt; + &lt;e papërcaktuar&gt; - - Account synchronization is disabled - Njëkohësimi i llogarisë është çaktivizuar + + <div id="cert"><h3>with Certificate %1</h3><div id="ccert"><p>Organization: %2<br/>Unit: %3<br/>Country: %4</p><p>Fingerprint (MD5): <tt>%5</tt><br/>Fingerprint (SHA1): <tt>%6</tt><br/>Fingerprint (SHA256): <tt>%7</tt><br/><br/>Effective Date: %8Expiration Date: %9</div><h3>Issuer: %10</h3><div id="issuer"><p>Organization: %11<br/>Unit: %12<br/>Country: %13</p></div></div> + <div id="cert"><h3>me Dëshmi %1</h3><div id="ccert"><p>Ent: %2<br/>Njësi: %3<br/>Vend: %4</p><p>Shenja gishtash (MD5): <tt>%5</tt><br/>Shenja gishtash (SHA1): <tt>%6</tt><br/>Shenja gishtash (SHA256): <tt>%7</tt><br/><br/>Datë Hyrjeje Në Fuqi: %8Datë Skadimi: %9</div><h3>Lëshues: %10</h3><div id="issuer"><p>Ent: %11<br/>Njësi: %12<br/>Vend: %13</p></div></div> + + + OCC::TrayMenuController - - Synchronization is paused - Njëkohësimi është ndalur + + Unsupported Server Version + Version i Pambuluar Shërbyesi - - Up to date (%1) - Të përditësuar (%1) + + The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. + Shërbyesi në llogarinë %1 xhiron një version të pambuluar të %2. Përdorimi i këtij klienti me versione të pambuluar të shërbyesit është i patestuar dhe potencialisht i rrezikshëm. Qari dhe zarari i juaj. - - No sync folders configured - S’ka të formësuara dosje njëkohësimesh + + Show %1 + Shfaq %1 - - Unpause all folders - Hiqe ndalesën për krejt dosjet + + Help + Ndihmë - - Pause all folders - Ndaloje për krejt dosjet + + About %1 + Rreth %1 - - Unpause all synchronization - Hiqe ndalesën për çdo njëkohësim + + Quit %1 + Dilni nga %1 + + + OCC::UpdateDownloadedWidget - - Unpause synchronization - Hiqe ndalesën për njëkohësimin + + Restart later + Riniseni më vonë - - Pause all synchronization - Ndali krejt njëkohësimet + + Restart now + Riniseni tani + + + OCC::UpdaterScheduler - - Pause synchronization - Ndale njëkohësimin + + Update available + Ka një përditësim gati + + + OCC::UrlPageController - - Log out of all accounts - Dil nga krejt llogaritë + + Your web browser will be opened to complete sign in. + Shfletuesi juaj do të hapet që të plotësoni hyrjen. - - Log in to all accounts... - Futu në krejt llogaritë… + + %1 logo + Stemë e %1 - - New account... - Llogari e re… + + Welcome to %1 + Mirë se vini te %1 - - About %1 - Rreth %1 + + Enter your server address to get started. Your web browser will be opened to complete sign in. + Që t’ia filloni, jepni adresën e shërbyesit tuaj. Shfletuesi juaj do të hapet që të plotësoni hyrjen. - - No items synced recently - Pa objekte të njëkohësuar së fundi + + Server address field + Fushë adresë shërbyesi - - Checking for changes in remote '%1' - Po kërkohet për ndryshime te shërbyesi i largët '%1' + + Enter your server address here + Jepni këtu adresën e shërbyesit tuaj - - Checking for changes in local '%1' - Po kërkohet për ndryshime te shërbyesi vendor '%1' + + Additional logo defined by the organization + Stemë shtesë e përcaktuar nga enti - - Syncing %1 of %2 (%3 left) - Po njëkohësohet %1 nga %2 (mbeten edhe %3) + + Invalid server URL: %1 + URL e pavlefshme shërbyesi: %1 - - Syncing %1 of %2 - Po njëkohësohet %1 nga %2 + + Invalid URL scheme. Only https is accepted. + Skemë URL-sh e pavlefshme. Pranohet vetëm https. - - Syncing %1 (%2 left) - Po njëkohësohet %1 (mbeten edhe %2) + + The requested URL failed with query value: %1 + URL-ja e kërkuar dështoi me vlerë kërkese: %1 + + + OCC::UserInfoAdapter - - Syncing %1 - Po njëkohësohet %1 + + Network request to collect user info failed + Dështoi kërkesë rrjeti për të grumbulluar informacion mbi përdoruesin - - %1 (%2, %3) - %1 (%2, %3) + + Unable to retrieve user info: invalid credentials. + S;arrihet të merren informacion përdoruesi: kredenciale të pavlefshme. - - - OwncloudAdvancedSetupPage - - Form - Formular + + Unexpected network response when retrieving user info. + Përgjigje e papritur rrjeti, kur merrej informacion përdoruesi. - - - TextLabel - EtiketëTeksti + + Retrieving user info failed with JSON error. + Marrja e informacionit mbi përdoruesin dështoi me një gabim JSON. + + + OCC::ValidateChecksumHeader - - Server - Shërbyes + + The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' + Kartela e shkarkuar nuk përputhet me vlerën e llogaritur të checksum-it, do të rimerret. '%1' != '%2' + + + OCC::Vfs - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Nëse i vihet shenja kësaj kutize, lënda ekzistuese në dosjen vendore do të fshihet për të nisur një njëkohësim të pastër nga shërbyesi.</p><p>Mos i vini shenjë, nëse lënda vendore duhet të ngarkohet te dosja e shërbyesit.</p></body></html> + + The Virtual filesystem feature does not support a drive as sync root + Veçoria për sistem Virtual kartelash nuk mbulon disk si “sync root” - - Ask for confirmation before synchroni&zing folders larger than - Kërko ripohim përpara njëkohës&imi dosjesh më të mëdha se + + The Virtual filesystem feature requires a NTFS file system, %1 is using %2 + Veçoria sistem Virtual kartelash lyp një sistem NTFS kartelash, %1 po përdor %2 - - S&ynchronize everything from server - &Njëkohëso gjithçka prej shërbyesi + + The Virtual filesystem feature is not supported on network drives + Veçoria e sistemit Virtual të kartelave nuk mbulohet në disqe rrjeti + + + OCC::VfsWinPrivate - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB + + + %1 - %2 + %1 - %2 - - Ask for confirmation before synchronizing e&xternal storages - Kërko ripohim përpara njëkohësimi depozitash të jash&tme + + Could not find StorageProviderState property %1 + S’u gjet dot veti StorageProviderState %1 - - Choose what to sync - Zgjidhni çfarë të njëkohësohet + + Could not find StorageFolder for %1 error: %2 + S’u gjet dot StorageFolder për gabim %1: %2 - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Kur përzgjidhet kjo mundësi, ndihmësi do të mbyllet pa njëkohësuar gjë. Për të zgjedhur cila dyshe dosjesh vendore dhe të largët dëshironi të njëkohësohet, mund të përdorni butonin &quot;Shtoni Lidhje Njëkohësimesh Dosjeje&quot; që nga rregullimet mbi llogarinë</p></body></html> + + Could not retrieve StorageFolder for %1 %2 (0x%3) + S’u mor dot StorageFolder për %1 %2 (0x%3) - - Manually create folder sync connections - Krijoni lidhje njëkohësimesh dosjesh dorazi + + The folder is used by a different client: %1 + Dosja po përdoret nga një tjetër klient: %1 - - &Local Folder - Dosje &Vendore + + Error registering StorageProvider for %1: %2 (0x%3) + Gabim në regjistrim StorageProvider-i për %1: %2 (0x%3) - - pbSelectLocalFolder - pbSelectLocalFolder + + Unable to connect sync root: %1 error: %2 + S’arrihet të lidhet rrënjë njëkohësimi: gabim %1: %2 - - &Keep local data - &Mbaj të dhëna vendore + + %1 is managed by another sync client + %1 administrohet nga një tjetër klient njëkohësimesh - - Start a &clean sync (Crreate a backup and erases the local folder!) - Nis një njëkohësim të &pastër (Krijon një kopjeruajtje dhe fshin dosjen vendore!) + + Downgrading the folder from %1 to %2 is not supported + S’mbulohet ulja e dosjes nga %1 në %2 - OwncloudHttpCredsPage - - - Form - Formular - + OCC::WebFingerLookupAdapter - - &Username - &Emër përdoruesi + + Failed to retrieve user info + S’u arrit të merren hollësi përdoruesi - - &Password - &Fjalëkalim + + WebFinger lookup returned no links + Kërkimi WebFinger s’dha ndonjë lidhje - OwncloudOAuthCredsPage - - - Form - Formular - + OCC::WindowsUpdater - - Please switch to your browser to proceed. - Ju lutemi, që të vazhdohet, kaloni te shfletuesi juaj. + + <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> + <p>Ka të gatshëm një version të ri të Klientit %1.</p><p><b>%2</b> është gati për shkarkim. Versioni i instaluar është %3.</p> - - An error occurred while connecting. Please try again. - Ndodhi një gabim teksa bëhej lidhja. Ju lutemi, riprovoni. + + Skip this version + Anashkaloje këtë version - - Re-open Browser - Rihap Shfletuesin + + Update Failed + Përditësimi Dështoi - - - OwncloudSetupPage - - Form - Formular + + <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> + <p>Ka të gatshëm një version të ri të Klientit %1, por procesi i përditësimit dështoi</p><p><b>%2</b> është shkarkuar. Versioni i instaluar është %3.</p> - - Ser&ver Address - Adresë Shër&byesi + + Ask again later + Ripyet më vonë - - https://... - https://… + + Restart and update + Rinise dhe përditësoje - - Error Label - Etiketë Gabimi + + Update manually + Përditësojeni dorazi QObject - + in the future në të ardhmen - + %n day(s) ago %n ditë më parë%n ditë më parë - + %n hour(s) ago %n orë më parë%n orë më parë - + now tani - - Less than a minute ago - Më pak se një minutë më parë + + less than a minute ago + më pak se një minutë më parë - + %n minute(s) ago %n minutë më parë%n minuta më parë - - Some time ago - Ca kohë më parë - - - + %1: %2 this displays an error string (%2) for a file %1 %1: %2 + + SpacesBrowser + + + Form + Formular + + + + SpacesPage + + + Spaces list + Listë Hapësirash + + + + SpacesView + + + Spaces + Hapësira + + SyncFileItem::Status - + Undefined I papërcaktuar - + Fatal Error Gabim Fatal - - + + Error Gabim - + Info - Të dhëna + Hollësi - + Success Sukses - + Conflict Përplasje - + File Ignored Kartela u Shpërfill - + Restored - + E rikthyer - + Blacklisted - + Në listë bllokimesh - + Excluded - - - - - Utility - - - %L1 GB - %L1 GB + E përjashtuar - - %L1 MB - %L1 MB + + Message + Mesazh - - %L1 KB - %L1 KB + + Filename Reserved + Emër i Rezervuar Kartele + + + UpdateDownloadedWidget - - %L1 B - %L1 B + + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Restart required</span></p></body></html> + <html><head/><body><p><span style=" font-size:11pt; font-weight:700;">Lypset rinisje</span></p></body></html> + + + Utility - + %n year(s) %n vit%n vjet - + %n month(s) %n muaj%n muaj - + %n day(s) %n ditë%n ditë - + %n hour(s) %n orë%n orë - + %n minute(s) %n minutë%n minuta - + %n second(s) %n sekondë%n sekonda - + %1 %2 %1 %2 + + + Shutting down %1 + Po fiket %1 + - main.cpp + account loading - - System Tray not available - Nuk ka Panel Sistemi + + Error accessing the configuration file + Gabim gjatë hyrjes në kartelën e formësimit - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 lyp një panel sistemi funksional. Nëse xhironi XFCE, ju lutemi, ndiqni <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">këto udhëzime</a>. Përndryshe, ju lutemi, instaloni një aplikacion paneli sistemi, bie fjala, 'trayer' dhe riprovoni. + + There was an error while accessing the configuration file at %1. + Pati një gabim teksa hyhej te kartela e formësimit në %1. ownCloudTheme::aboutVersions() - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - %1 %2 %3%8%9Biblioteka Qt %4, %5%8Me përdorim shtojce kartelash virtuale: %6%8%7 + + %1 %2%7%8Libraries Qt %3, %4%7Using virtual files plugin: %5%7%6 + %1 %2%7%8Biblioteka Qt %3, %4%7Po përdoret shtojcë kartelash virtuale: %5%7%6 ownCloudTheme::qtVer - - %1 (Built against Qt %1) - %1 (Montuar kundrejt Qt %1) + + %1 (Built against Qt %2) + %1 (Montuar me Qt %2) ownCloudTheme::versionWithSha - + %1 %2 %1 %2 + + pinstate + + + Always available locally + Përherë i passhëm lokalisht + + + + Currently available locally + Aktualisht i passhëm lokalisht + + + + Some available online only + Disa të passhëm vetëm <em>online</em> + + + + Available online only + I passhëm vetëm <em>online</em> + + progress @@ -4727,30 +3564,30 @@ Kjo është një mënyrë e re, eksperimentale. Nëse vendosni ta përdorni, ju Uploaded U ngarkua - - - Server version downloaded, copied changed local file into conflict file - U shkarkua versioni i shërbyesit, kartela vendore e ndryshuar u kopjua te një kartelë përplasjeje - Deleted U fshi - - Moved to %1 - U kalua te %1 - - - + Ignored U shpërfill - - Filesystem access error - Gabim hyrjeje në sistem kartelash + + Server version downloaded, local copy was backed up as conflict file + U shkarkua version shërbyesi, kopja vendore u kopjeruajt, për shkak përplasje kartelash + + + + Renamed %1 to %2 + U riemërtua nga %1 në %2 + + + + Moved %1 to %2 + %1 u kalua te %2 @@ -4763,153 +3600,86 @@ Kjo është një mënyrë e re, eksperimentale. Nëse vendosni ta përdorni, ju U përditësuan tejtëdhëna vendore - - + + Unknown E panjohur - + downloading po shkarkohet - + uploading po ngarkohet - + deleting po fshihet - + moving po zhvendoset - + ignoring po shpërfillet - - + error gabim - + updating local metadata po përditësohen tejtëdhëna vendore - - theme - - - Status undefined - Gjendje e papërcaktuar - - - - Waiting to start sync - Po pritet të fillohet njëkohësimi - - - - Sync is running - Njëkohësimi po kryhet - - - - Sync Success - Sukses Njëkohësimi - - - - Sync Success, some files were ignored. - Sukses njëkohësimi, disa prej kartelave u shpërfillën. - - - - Sync Error - Gabim Njëkohësimi - - - - Setup Error - Gabim Rregullimi - - - - Preparing to sync - Po përgatitet për njëkohësim - - - - Aborting... - Po ndërpritet… - - - - Sync is paused - Njëkohësimi është ndalur - - utility - + Could not open browser S’hapet dot shfletuesi - + There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? Pati një gabim gjatë nisjes së shfletuesit për të hapur URL-në %1. Mos vallë nuk ka shfletues parazgjedhje të formësuar? - + Could not open email client S’u hap dot klienti email - + There was an error when launching the email client to create a new message. Maybe no default email client is configured? Pati një gabim gjatë nisjes së klientit email për të krijuar një mesazh të ri. Mos vallë s’ka të formësuar klient parazgjedhje për email-in? - - Always available locally - Përherë i passhëm lokalisht - - - - Some available online only - Disa të passhëm vetëm <em>online</em> - - - - - Available online only - I passhëm vetëm <em>online</em> - - - - Currently available locally - Aktualisht i passhëm lokalisht - - - + Make always available locally Bëje përherë të passhëm lokalisht - + Free up local space Liro hapësirë vendore + + version check + + + Some settings were configured in newer versions of this client and use features that are not available in this version + Disa rregullime qenë formësuar në versione më të rinj të këtij klienti dhe përdorin veçori që s’janë të pranishme në këtë version + + \ No newline at end of file diff --git a/translations/client_sr.ts b/translations/client_sr.ts deleted file mode 100644 index eade013fd2b..00000000000 --- a/translations/client_sr.ts +++ /dev/null @@ -1,4420 +0,0 @@ - - - FileSystem - - - Could not make directories in trash - - - - - - Could not move '%1' to '%2' - - - - - Moving to the trash is not implemented on this platform - - - - - FolderWizardSourcePage - - - Form - Образац - - - - Pick a local folder on your computer to sync - Изаберите локалну фасциклу за синхронизацију - - - - &Choose... - &Изабери… - - - - FolderWizardTargetPage - - - Form - Образац - - - - Select a remote destination folder - Изаберите удаљену одредишну фасциклу - - - - Create Folder - Направи фасциклу - - - - Refresh - Освежи - - - - Folders - Фасцикле - - - - TextLabel - Текст ознака - - - - NotificationWidget - - - Form - Образац - - - - Lorem ipsum dolor sit amet - - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - - TextLabel - Текст ознака - - - - OCC::AbstractNetworkJob - - - Connection timed out - Време повезивања истекло - - - - Unknown error: network reply was deleted - - - - - Server replied "%1 %2" to "%3 %4" - - - - - OCC::AccountSettings - - - Form - Образац - - - - ... - ... - - - - Storage space: ... - Простор у складишту: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Неозначене фасцикле биће <b>уклоњене</b> из локалног фајл-система и више се неће синхронизовати на овом рачунару - - - - Synchronize all - - - - - Synchronize none - - - - - Apply manual changes - - - - - Apply - Примени - - - - - - Cancel - Одустани - - - - Connected with <server> as <user> - Повезан са <server> као <user> - - - - No account configured. - Није подешен налог. - - - - Add new - Додај нови - - - - Remove - Уклони - - - - Account - Налог - - - - Choose what to sync - Изаберите шта синхронизовати - - - - Force sync now - - - - - Restart sync - - - - - Remove folder sync connection - - - - - Create virtual files for new files (Experimental) - - - - - Folder creation failed - Прављење фасцикле није успело - - - - <p>Could not create local folder <i>%1</i>. - <p>Не могу да направим локалну фасциклу <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - - - - - Remove Folder Sync Connection - - - - - Sync Running - Синхронизација у току - - - - The syncing operation is running.<br/>Do you want to terminate it? - Синхронизација је у току.<br/>Желите ли да је прекинете? - - - - %1 in use - %1 искоришћено - - - - %1 as <i>%2</i> - %1 као <i>%2</i> - - - - Connected to %1. - Повезан са %1. - - - - Server %1 is temporarily unavailable. - Сервер %1 је привремено недоступан. - - - - Server %1 is currently in maintenance mode. - - - - - Signed out from %1. - Одјављен са %1. - - - - Obtaining authorization from the browser. <a href='%1'>Click here</a> to re-open the browser. - - - - - Connecting to %1... - - - - - No connection to %1 at %2. - - - - - Log in - Пријава - - - - There are folders that were not synchronized because they are too big: - - - - - There are folders that were not synchronized because they are external storages: - - - - - There are folders that were not synchronized because they are too big or external storages: - - - - - Confirm Account Removal - Потврда уклањања налога - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - - - - Remove connection - Уклони везу - - - - - Open folder - Отвори фасциклу - - - - - Log out - Одјава - - - - Resume sync - Настави синхронизацију - - - - Pause sync - Паузирај синхронизацију - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Желите ли заиста да престанете са синхронизацијом фасцикле <i>%1</i>?</p><p><b>Напомена:</b> Ово <b>неће</b> обрисати ниједан фајл.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) од %2 искоришћено. Неке фасцикле, укључујући мрежно монтиране или дељене фасцикле, могу имати друга ограничења. - - - - %1 of %2 in use - %1% од %2 искоришћено - - - - Currently there is no storage usage information available. - Тренутно нема доступних података о заузећу складишта. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - No %1 connection configured. - Нема подешене %1 везе. - - - - OCC::AccountState - - - Signed out - Одјављен - - - - Disconnected - Неповезан - - - - Connected - Повезан - - - - Service unavailable - Услуга није доступна - - - - Maintenance mode - - - - - Network error - Грешка мреже - - - - Configuration error - Грешка подешавања - - - - Asking Credentials - - - - - Unknown account state - Непознато стање налога - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 на %2 - - - - %1 on %2 (disconnected) - %1 на %2 (неповезан) - - - - OCC::ActivitySettings - - - - Server Activity - Активност сервера - - - - Sync Protocol - Протокол синхронизације - - - - Not Synced - Несинхронизовано - - - - Not Synced (%1) - %1 is the number of not synced files. - - - - - The server activity list has been copied to the clipboard. - - - - - The sync activity list has been copied to the clipboard. - - - - - The list of unsynced items has been copied to the clipboard. - - - - - Copied to clipboard - Копирано у клипборд - - - - OCC::ActivityWidget - - - Form - Образац - - - - - - TextLabel - Текст ознака - - - - Server Activities - Активности сервера - - - - Copy - Копирај - - - - Copy the activity list to the clipboard. - Копирај активност у клипборд. - - - - Action Required: Notifications - - - - - <br/>Account %1 does not have activities enabled. - <br/>Налог %1 нема укључене активности. - - - - You received %n new notification(s) from %2. - - - - - You received %n new notification(s) from %1 and %2. - - - - - You received new notifications from %1, %2 and other accounts. - - - - - %1 Notifications - Action Required - - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Аутентификација сертификата ССЛ клијента - - - - This server probably requires a SSL client certificate. - Овај сервер вероватно захтева сертификат ССЛ клијента. - - - - Certificate & Key (pkcs12) : - - - - - Browse... - Прегледај... - - - - Certificate password : - Лозинка сертификата: - - - - Select a certificate - Изаберите сертификат - - - - Certificate files (*.p12 *.pfx) - Фајлови сертификата (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Напусти - - - - Continue - Настави - - - - Error accessing the configuration file - - - - - There was an error while accessing the configuration file at %1. - - - - - Quit ownCloud - - - - - OCC::AuthenticationDialog - - - Authentication Required - Потребна аутентификација - - - - Enter username and password for '%1' at %2. - Унесите корисничко име и лозинку за „%1“ на %2. - - - - &User: - &Корисник: - - - - &Password: - &Лозинка: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Нема подешеног оунКлауд налога - - - - The configured server for this client is too old - Подешени сервер је сувише стар за ову верзију клијента - - - - Please update to the latest server and restart the client. - Ажурирајте сервер и поново покрените клијента. - - - - Authentication error: Either username or password are wrong. - Грешка аутентификације: лозинка или корисничко име су погрешни. - - - - timeout - истек времена - - - - The provided credentials are not correct - Дати акредитиви нису исправни - - - - OCC::DiscoveryMainThread - - - Aborted by the user - Прекинуо корисник - - - - OCC::DiscoverySingleDirectoryJob - - - The server file discovery reply is missing data. - - - - - OCC::Folder - - - Local folder %1 does not exist. - Локална фасцикла %1 не постоји. - - - - %1 should be a folder but is not. - %1 би требало да је фасцикла али није. - - - - %1 is not readable. - %1 није читљив. - - - - %1 has been removed. - %1 names a file. - %1 је уклоњен. - - - - %1 has been downloaded. - %1 names a file. - %1 је преузет. - - - - %1 has been updated. - %1 names a file. - %1 је ажуриран. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 је преименован у %2. - - - - %1 has been moved to %2. - %1 је премештен у %2. - - - - %1 and %n other file(s) have been removed. - - - - - %1 and %n other file(s) have been downloaded. - - - - - %1 and %n other file(s) have been updated. - - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - - - - - %1 has been moved to %2 and %n other file(s) have been moved. - - - - - %1 has and %n other file(s) have sync conflicts. - - - - - %1 has a sync conflict. Please check the conflict file! - - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - - - - - %1 could not be synced due to an error. See the log for details. - %1 није синхронизован због грешке. Погледајте записник за детаље. - - - - Sync Activity - Активност синхронизације - - - - Could not read system exclude file - Не могу да прочитам системски списак за игнорисање - - - - A new folder larger than %1 MB has been added: %2. - - - - - - A folder from an external storage has been added. - - - - - - Please go in the settings to select it if you wish to download it. - - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - Уклонити све фајлове? - - - - Remove all files - Уклони све фајлове - - - - Keep files - Остави фајлове - - - - This sync would reset the files to an earlier time in the sync folder '%1'. -This might be because a backup was restored on the server. -Continuing the sync as normal will cause all your files to be overwritten by an older file in an earlier state. Do you want to keep your local most recent files as conflict files? - - - - - Backup detected - - - - - Normal Synchronisation - - - - - Keep Local Files as Conflict - - - - - OCC::FolderMan - - - Could not reset folder state - Не могу да ресетујем стање фасцикле - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Пронађен је стари журнал синхронизације „%1“ али се не може уклонити. Проверите да га нека апликација тренутно не користи. - - - - (backup) - (резерва) - - - - (backup %1) - (резерва %1) - - - - Undefined State. - Неодређено стање. - - - - Waiting to start syncing. - - - - - Preparing for sync. - Припремам синхронизацију. - - - - Sync is running. - Синхронизација у току. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Последња синхронизација је била успешна. - - - - Setup Error. - Грешка подешавања. - - - - User Abort. - Корисник прекинуо. - - - - Sync is paused. - Синхронизација је паузирана. - - - - %1 (Sync is paused) - %1 (синхронизација паузирана) - - - - No valid folder selected! - - - - - The selected path is not a folder! - - - - - You have no permission to write to the selected folder! - Немате дозволе за упис у изабрану фасциклу! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Додај везу синхронизације фасцикле - - - - Synchronizing with local folder - - - - - File - Фајл - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - - - - - Click this button to add a folder to synchronize. - - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - - - - - Signed out - Одјављен - - - - Fetching folder list from server... - Добављам списак фасцикли са сервера... - - - - There are unresolved conflicts. Click for details. - - - - - New files are being created as virtual files. - - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - „%1“ - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Синхронизујем %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - преузми %1/s - - - - u2193 %1/s - u2193 %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - отпреми %1/s - - - - u2191 %1/s - u2191 %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 од %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - - - - - file %1 of %2 - фајл %1 од %2 - - - - Waiting... - Чекам... - - - - Waiting for %n other folder(s)... - - - - - Preparing to sync... - Припремам синхронизацију... - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Додај везу синхронизације фасцикле - - - - Add Sync Connection - Додај везу синхронизације - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Клик за избор локалне фасцикле за синхронизацију. - - - - Enter the path to the local folder. - Унесите путању до локалне фасцикле. - - - - Select the source folder - Изаберите изворну фасциклу - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Прављење удаљене фасцикле - - - - Enter the name of the new folder to be created below '%1': - Унесите назив нове фасцикле која ће бити направљена у %1: - - - - Folder was successfully created on %1. - Фасцикла је успешно направљена на %1 - - - - Authentication failed accessing %1 - Неуспела аутентификација при приступу %1 - - - - Failed to create the folder on %1. Please check manually. - Нисам успео да направим фасциклу на %1. Проверите ручно. - - - - Failed to list a folder. Error: %1 - Неуспех излиставања фасцикле. Грешка: %1 - - - - Choose this to sync the entire account - Ово изаберите да синхронизујете целокупан налог - - - - This folder is already being synced. - Ова фасцикла се већ синхронизује. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Већ синхронизујете <i>%1</i>, која садржи фасциклу <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately (experimental) - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Упозорење:</b> %1 - - - - <b>Warning:</b> - <b>Упозорење:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Нема е-ознаке са сервера. Проверите прокси или мрежни излаз - - - - We received a different E-Tag for resuming. Retrying next time. - Добијена је различита е-ознака за наставак преноса. Покушаћу поново следећи пут. - - - - Server returned wrong content-range - Сервер је вратио погрешан опсег садржаја - - - - Connection Timeout - Време за повезивање је истекло - - - - OCC::GeneralSettings - - - Form - Образац - - - - General Settings - Опште поставке - - - - For System Tray - - - - - stable - - - - - beta - - - - - Advanced - Напредно - - - - Ask for confirmation before synchronizing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - - - - - &Launch on System Startup - Покрени при покретању &система - - - - Show &Desktop Notifications - Прикажи обавештења на &радној површи - - - - Use &Monochrome Icons - Користи &једнобојне иконе - - - - Version - Верзија - - - - &Update Channel - - - - - • - - - - - Edit &Ignored Files - Уреди &занемарене фајлове - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - - - - - &Restart && Update - &Поново покрени и ажурирај - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Откажи - - - - OCC::GetOrCreatePublicLinkShare - - - Sharing error - - - - - Could not retrieve or create the public link share. Error: - -%1 - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - Унесите лозинку - - - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Уређивање фајлова за игнорисање - - - - Global Ignore Settings - Опште поставке занемаривања - - - - Sync hidden files - Синхронизуј скривене фајлове - - - - Files Ignored by Patterns - - - - - Add - Додај - - - - Pattern - шаблон - - - - Allow Deletion - брисање - - - - Remove - Уклони - - - - Files or folders matching a pattern will not be synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Не могу да отворим фајл - - - - Cannot write changes to '%1'. - Не могу да упишем измене у „%1“ - - - - Add Ignore Pattern - Додавање шаблона за игнорисање - - - - Add a new ignore pattern: - Додајте нови шаблон за игнорисање: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Овај унос је системски из „%1“ и не може се овде изменити. - - - - OCC::IssuesWidget - - - Form - Образац - - - - List of issues - - - - - Account - Налог - - - - - <no filter> - - - - - - Folder - фасцикла - - - - Show warnings - - - - - Show ignored files - - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - - - - - Copy - Копирај - - - - Time - време - - - - File - фајл - - - - Issue - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LogBrowser - - - Log Output - Бележи излаз - - - - &Search: - &Тражи: - - - - &Find - &Нађи - - - - &Capture debug messages - - - - - Permanently save logs - - - - - When this option is enabled and no other logging is configured, logs will be written to a temporary folder and expire after a few hours. This setting persists across client restarts. - -Logs will be written to %1 - - - - - Clear - Очисти - - - - Clear the log display. - Очисти приказ записника. - - - - S&ave - С&ачувај - - - - Save the log file to a file on disk for debugging. - Сачувајте фајл записника на диску. - - - - Save log file - Сачувај фајл записника - - - - Error - Грешка - - - - Could not write to log file %1 - Не могу да пишем у записник %1 - - - - OCC::Logger - - - Error - Грешка - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>Фајл „%1“<br/>није уписив.<br/><br/>Записник се <b>не може</b> сачувати!</nobr> - - - - OCC::NSISUpdater - - - New Version Available - Нова верзија је доступна - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Нова верзија %1 клијента је доступна.</p><p><b>%2</b> је доступна за преузимање. Инсталирана је %3.</p> - - - - Skip this version - Прескочи ову верзију - - - - Skip this time - Прескочи сада - - - - Get update - Ажурирај - - - - OCC::NetworkSettings - - - Form - Образац - - - - Proxy Settings - Поставке проксија - - - - No Proxy - без проксија - - - - Use system proxy - системски прокси - - - - Specify proxy manually as - ручно наведи прокси - - - - Host - Домаћин - - - - : - : - - - - Proxy server requires authentication - прокси захтева аутентификацију - - - - Download Bandwidth - Брзина преузимања - - - - - Limit to - ограничи на - - - - - KBytes/s - KB/s - - - - - No limit - без ограничења - - - - - Limit to 3/4 of estimated bandwidth - - - - - Upload Bandwidth - Брзина отпремања - - - - - Limit automatically - сам ограничи - - - - Hostname of proxy server - назив прокси сервера - - - - Username for proxy server - корисничко име за прокси - - - - Password for proxy server - лозинка за прокси - - - - HTTP(S) proxy - ХТТП(С) прокси - - - - SOCKS5 proxy - СОКС-5 прокси - - - - OCC::NotificationWidget - - - Created at %1 - - - - - Closing in a few seconds... - - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - The reply from the server did not contain all expected fields - - - - - <h1>Login Error</h1><p>%1</p> - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - Ново %1 ажурирање спремно - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Ново ажурирање за %1 ће се инсталирати. Можда ће бити -потребне додатне дозволе током процеса. - - - - Downloading version %1. Please wait... - Преузимам верзију %1. Сачекајте... - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Не могу да преузмем ажурирање. Кликните <a href='%1'>овде</a> да бисте га преузели ручно. - - - - Could not check for new updates. - Не могу да проверим нова ажурирања. - - - - %1 version %2 available. Restart application to start the update. - - - - - New %1 version %2 available. Please use the system's update tool to install it. - - - - - Checking update server... - Проверавам сервер ажурирања... - - - - Update status is unknown: Did not check for new updates. - Стање ажурирања је непознато. Нисам проверио нова ажурирања. - - - - No updates available. Your installation is at the latest version. - Нема доступних ажурирања. Имате последњу верзију. - - - - Update Check - Провера надоградњи - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Повежи се са %1 - - - - Setup local folder options - Опције поставке локалне фасцикле - - - - Connect... - Повежи се... - - - - %1 folder '%2' is synced to local folder '%3' - %1 фасцикла „%2“ је сингронизована са локалном „%3“ - - - - Sync the folder '%1' - Синхронизуј фасциклу „%1“ - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - - - - - Local Sync Folder - Синхронизација локалне фасцикле - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Неуспешно повезивање - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Не могу да се повежем на наведену сигурну адресу сервера. Како желите да наставите?</p></body></html> - - - - Select a different URL - Изабери други УРЛ - - - - Retry unencrypted over HTTP (insecure) - Покушај нешифровано преко ХТТП (несигурно) - - - - Configure client-side TLS certificate - Подеси клијентски ТЛС сертификат - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Не могу да се повежем на сигурну адресу сервера <em>%1</em>. Како желите да наставите?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - - - - - Connect to %1 - Повежи %1 - - - - Enter user credentials - Унесите корисничке акредитиве - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Повежи %1 - - - - Login in your browser - - - - - Copy link to clipboard - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Повежи %1 - - - - Setup %1 server - Подеси %1 сервер - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Ова адреса НИЈЕ безбедна јер није шифрована. -Не препоручује се њено коришћење. - - - - This url is secure. You can use it. - Ова адреса је безбедна. Можете је користити. - - - - &Next > - &Следеће > - - - - Invalid URL - - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">Успешно повезан са %1: %2 верзија %3 (%4)</font><br/><br/> - - - - Failed to connect to %1 at %2:<br/>%3 - Неуспешно повезивање са %1 на %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Време је истекло у покушају повезивања са %1 на %2. - - - - Trying to connect to %1 at %2... - Покушавам да се повежем са %1 на %2... - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - Аутентификован захтев серверу је преусмерен на %1. УРЛ је лош, сервер је лоше подешен. - - - - There was an invalid response to an authenticated webdav request - Добијен је неисправан одговор на аутентификовани ВебДАВ захтев - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - Сервер није дозволио приступ. Да проверите имате ли исправан приступ, <a href="%1">кликните овде</a> да бисте приступили услузи из прегледача. - - - - Invalid URL - - - - - The server reported the following error: - - - - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - Локална фасцикла %1 већ постоји. Одређујем је за синхронизацију.<br/><br/> - - - - Creating local sync folder %1... - Правим локалну фасциклу синхронизације %1... - - - - ok - у реду - - - - failed. - неуспешно - - - - Could not create local folder %1 - Не могу да направим локалну фасциклу %1 - - - - No remote folder specified! - Није наведена удаљена фасцикла! - - - - Error: %1 - Грешка: %1 - - - - creating folder on ownCloud: %1 - правим фасциклу у облаку: % 1 - - - - Remote folder %1 created successfully. - Удаљена фасцикла %1 је успешно направљена. - - - - The remote folder %1 already exists. Connecting it for syncing. - Удаљена фасцикла %1 већ постоји. Повезујем се ради синхронизовања. - - - - - The folder creation resulted in HTTP error code %1 - Прављење фасцикле довело је до ХТТП грешке са кодом %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Прављење удаљене фасцикле није успело због погрешних акредитива!<br/>Идите назад и проверите ваше акредитиве.</p> - - - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">Прављење удаљене фасцикле није успело због погрешних акредитива.</font><br/>Идите назад и проверите ваше акредитиве.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Прављење удаљене фасцикле %1 није успело због грешке <tt>%2</tt>. - - - - A sync connection from %1 to remote directory %2 was set up. - Веза за синхронизацију %1 до удаљеног директоријума %2 је подешена. - - - - Successfully connected to %1! - Успешно повезан са %1! - - - - Connection to %1 could not be established. Please check again. - Не може се успоставити веза са %1. Проверите поново. - - - - Folder rename failed - Преименовање није успело - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Не могу да уклоним и направим резервну копију фасцикле јер су фасцикла или фајл отворени у другом програму. Затворите фасциклу или фајл и покушајте поново или одустаните од подешавања. - - - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>Локална фасцикла за синхронизовање %1 је успешно направљена!</b></font> - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 чаробњак повезивања - - - - Enable experimental feature? - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Enable experimental mode - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Све је подешено! - - - - Open Local Folder - Отвори локалну фасциклу - - - - Open %1 in Browser - Отвори %1 у прегледачу - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - Неисправан ЈСОН одговор са адресе упита - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - - - - - OCC::PropagateDownloadFile - - - File %1 can not be downloaded because of a local file name clash! - Фајл %1 се не може преузети јер се судара са називом локалног фајла! - - - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - - - - - File was deleted from server - Фајл је обрисан са сервера - - - - The file could not be downloaded completely. - Фајл није могао бити преузет у потпуности. - - - - The downloaded file is empty despite the server announced it should have been %1. - - - - - File %1 cannot be saved because of a local file name clash! - Фајл %1 се не може сачувати јер се судара са називом локалног фајла! - - - - File has changed since discovery - Фајл је измењен у међувремену - - - - Error writing metadata to the database - - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Враћање није успело: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - не могу да обришем фајл %1, грешка: %2 - - - - Attention, possible case sensitivity clash with %1 - Пажња! Могуће сударање због величине слова са %1 - - - - could not create folder %1 - не могу да направим фасциклу %1 - - - - Error writing metadata to the database - - - - - OCC::PropagateLocalRemove - - - Error removing '%1': %2; - Грешка при уклањању %1: %2; - - - - Could not remove folder '%1' - Не могу да уклоним фасциклу „%1“ - - - - Could not remove %1 because of a local file name clash - Не могу да уклоним %1 због сударања са називом локалног фајла - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Фајл %1 се не може преименовати у %2 због сударања са називом локалног фајла - - - - - Error writing metadata to the database - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Сервер је вратио лош ХТТП код. Очекивано је 204 али је примљено „%1 %2“. - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Сервер је вратио лош ХТТП код. Очекивано је 201 али је примљено „%1 %2“. - - - - Error writing metadata to the database - - - - - OCC::PropagateRemoteMove - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Сервер је вратио лош ХТТП код. Очекивано је 201 али је примљено „%1 %2“. - - - - - Error writing metadata to the database - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - Фајл уклоњен - - - - Local file changed during syncing. It will be resumed. - Локални фајл је измењен током синхронизације. Биће настављена. - - - - Local file changed during sync. - Локални фајл измењен током синхронизације. - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - - - - - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - Локални фајл је уклоњен током синхронизације. - - - - Local file changed during sync. - Локални фајл измењен током синхронизације. - - - - Unexpected return code from server (%1) - - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileV1 - - - Poll URL missing - Адреса упита недостаје - - - - The local file was removed during sync. - Локални фајл је уклоњен током синхронизације. - - - - Local file changed during sync. - Локални фајл измењен током синхронизације. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolWidget - - - Form - Образац - - - - TextLabel - Текст ознака - - - - Open in browser - Отвори у прегледачу - - - - Time - време - - - - File - фајл - - - - Folder - фасцикла - - - - Action - радња - - - - Size - величина - - - - Local sync protocol - Локални протокол синхронизације - - - - Copy - Копирај - - - - Copy the activity list to the clipboard. - Копирај активност у клипборд. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - - - - - Username: - Корисничко име: - - - - Proxy: - Прокси: - - - - The proxy server needs a username and password. - - - - - Password: - Лозинка: - - - - TextLabel - Текст ознака - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Изаберите шта синхронизовати - - - - OCC::SelectiveSyncWidget - - - Loading ... - Учитавам ... - - - - Deselect remote folders you do not wish to synchronize. - - - - - Name - назив - - - - Size - величина - - - - - No subfolders currently on the server. - На серверу тренутно нема потфасцикли. - - - - An error occurred while loading the list of sub folders. - - - - - OCC::ServerNotificationHandler - - - Dismiss - Откажи - - - - OCC::SettingsDialog - - - Settings - Поставке - - - - Activity - Активност - - - - General - Опште - - - - Network - Мрежа - - - - Account - Налог - - - - OCC::SettingsDialogMac - - - %1 - %1 - - - - Activity - Активност - - - - General - Опште - - - - Network - Мрежа - - - - - Account - Налог - - - - OCC::ShareDialog - - - TextLabel - Текст ознака - - - - share label - ознака дељења - - - - Dialog - Дијалог - - - - ownCloud Path: - оунКлауд путања: - - - - %1 Sharing - %1 дељење - - - - %1 - %1 - - - - Folder: %2 - Фасцикла: %2 - - - - The server does not allow sharing - - - - - Retrieving maximum possible sharing permissions from server... - - - - - The file can not be shared because it was shared without sharing permission. - Фајл се не може делити јер је подељен без дозволе за поновно дељење. - - - - Users and Groups - - - - - Public Links - - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Подели NewDocument.odt - - - - Upload only (File Drop) - - - - - Receive files from others without revealing the contents of the folder. - - - - - TextLabel - Текст ознака - - - - Set &password - Постави &лозинку - - - - Enter a name to create a new public link... - - - - - &Create new - - - - - Set &expiration date - Датум &истека - - - - Set password - Постави лозинку - - - - Recipients can view or download contents. - - - - - Download / View - - - - - Download / View / Upload - - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - - - - - - P&assword protect - &Заштићено лозинком - - - - The file can not be shared because it was shared without sharing permission. - Фајл се не може делити јер је подељен без дозволе за поновно дељење. - - - - Link shares have been disabled - - - - - Create public link share - - - - - - Delete - Обриши - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Copy link to clipboard (direct download) - - - - - Send link by email - Пошаљи везу е-поштом - - - - Send link by email (direct download) - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - - - - - Cancel - Откажи - - - - - Public link - - - - - Delete link share - - - - - Public sh&aring requires a password - Јавно дељење з&хтева лозинку - - - - Please Set Password - Поставите лозинку - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Подели NewDocument.odt - - - - Share with users or groups ... - Дели са корисницима или групама... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Send link by email - Пошаљи везу е-поштом - - - - No results for '%1' - - - - - I shared something with you - - - - - OCC::ShareUserLine - - - Form - Образац - - - - - TextLabel - Текст ознака - - - - can edit - може да мења - - - - can share - може да дели - - - - ... - ... - - - - create - направи - - - - change - измени - - - - delete - обриши - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::ShibbolethCredentials - - - Login Error - Грешка пријављивања - - - - You must sign in as user %1 - Морате се пријавити као %1 - - - - OCC::ShibbolethWebView - - - %1 - Authenticate - %1 - аутентификација - - - - SSL Chipher Debug View - - - - - Reauthentication required - Неопходна поновна аутентификација - - - - Your session has expired. You need to re-login to continue to use the client. - Ваша сесија је истекла. Поново се пријавите да бисте наставили да користите клијента. - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Подели са %1 - - - - Context menu share - - - - - I shared something with you - - - - - - Share... - - - - - - Copy private link to clipboard - - - - - Send private link by email... - - - - - Resharing this file is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - Отвори у прегледачу - - - - Download file(s) - - - - - Replace file(s) by virtual file - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Детаљи сертификата</h3> - - - - Common Name (CN): - Уобичајено име: - - - - Subject Alternative Names: - Алтернативно име: - - - - Organization (O): - Организација: - - - - Organizational Unit (OU): - Организациона јединица: - - - - State/Province: - Покрајина/провинција: - - - - Country: - Земља: - - - - Serial: - Серијски број: - - - - <h3>Issuer</h3> - <h3>Издавач</h3> - - - - Issuer: - Издавач: - - - - Issued on: - Издат: - - - - Expires on: - Истиче: - - - - <h3>Fingerprints</h3> - <h3>Отисци</h3> - - - - SHA-256: - СХА-256: - - - - SHA-1: - СХА-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Напомена:</b> Овај сертификат је ручно одобрен</p> - - - - %1 (self-signed) - %1 (самопотписан) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Ова веза је шифрована %1-битним %2. - - - - - No support for SSL session tickets/identifiers - - - - - Certificate information: - Подаци о сертификату: - - - - This connection is NOT secure as it is not encrypted. - - Ова веза НИЈЕ безбедна јер није шифрована. - - - - - OCC::SslErrorDialog - - - Form - Образац - - - - Trust this certificate anyway - Ипак веруј сертификату - - - - Untrusted Certificate - - - - - Cannot connect securely to <i>%1</i>: - - - - - with Certificate %1 - са сертификатом %1 - - - - - - &lt;not specified&gt; - &lt;није наведено&gt; - - - - - Organization: %1 - Организација: %1 - - - - - Unit: %1 - Јединица: %1 - - - - - Country: %1 - Држава: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Отисак (МД5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Отисак (СХА1): <tt>%1</tt> - - - - Effective Date: %1 - Важи од: %1 - - - - Expiration Date: %1 - Истиче : %1 - - - - Issuer: %1 - Издавач: %1 - - - - OCC::SyncEngine - - - Success. - Успешно. - - - - A network connection timeout happened. - Истекло је време за повезивање. - - - - A HTTP transmission error happened. - Дошло је до грешке ХТТП преноса. - - - - The mounted folder is temporarily not available on the server - - - - - An error occurred while opening a folder - - - - - Error while reading folder. - - - - - %1 (skipped due to earlier error, trying again in %2) - - - - - File/Folder is ignored because it's hidden. - - - - - Folder hierarchy is too deep - - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Not allowed because you don't have permission to add parent folder - - - - - Not allowed because you don't have permission to add files in that folder - - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - - - - - CSync unspecified error. - CSync недефинисана грешка. - - - - Aborted by the user - Прекинуо корисник - - - - Failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder. - - - - - Discovery step failed. - - - - - Permission denied. - - - - - File or directory not found: - - - - - Tried to create a folder that already exists. - - - - - No space on %1 server available. - - - - - The service is temporarily unavailable - Услуга је привремено недоступна - - - - Access is forbidden - Приступ је забрањен - - - - An internal error number %1 occurred. - Десила се интерна грешка број %1. - - - - Symbolic links are not supported in syncing. - Симболичке везе нису подржане у синхронизацији. - - - - File is listed on the ignore list. - Фајл се налази на листи за игнорисање. - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - - - - - The file name is a reserved name on this file system. - - - - - Filename contains trailing spaces. - - - - - Filename is too long. - Назив фајла је предугачак. - - - - The filename cannot be encoded on your file system. - - - - - Unresolved conflict. - - - - - Stat failed. - - - - - Filename encoding is not valid - Кодирање назива фајла није исправно - - - - Invalid characters, please rename "%1" - - - - - Using virtual files but suffix is not set - - - - - Unable to read the blacklist from the local database - - - - - Unable to read from the sync journal. - - - - - Cannot open the sync journal - Не могу да отворим дневник синхронизације - - - - File name contains at least one invalid character - Назив садржи бар један недозвољен карактер - - - - - Ignored because of the "choose what to sync" blacklist - Игнорисано јер се не налази на листи за синхронизацију - - - - Not allowed because you don't have permission to add subfolders to that folder - - - - - Not allowed to upload this file because it is read-only on the server, restoring - Није могуће отпремити овај фајл јер је на серверу само за читање. Враћам - - - - - Not allowed to remove, restoring - Није могуће уклањање. Враћам - - - - Local files and share folder removed. - Локални фајлови и дељена фасцикла су уклоњени. - - - - Move not allowed, item restored - Премештање није дозвољено. Ставка је враћена - - - - Move not allowed because %1 is read-only - Премештање није дозвољено јер %1 је само за читање - - - - the destination - одредиште - - - - the source - извор - - - - OCC::SyncLogDialog - - - Synchronisation Log - Дневник синхронизације - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p> - - - - - <p>Copyright ownCloud GmbH</p> - - - - - <p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%2 and the %2 logo are registered trademarks of %1 in the United States, other countries, or both.</p> - <p>Дистрибуира %1 под ГНУ општом јавном лиценцом (ОЈЛ) верзија 2.0.<br/>%2 и %2 лого су регистроване марке %1 у САД, другим земљама или обоје</p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Заглавље контролне суме је лоше формирано. - - - - The checksum header contained an unknown checksum type '%1' - - - - - The downloaded file does not match the checksum, it will be resumed. - Преузети фајл се не поклапа с контролном сумом. Биће настављено. - - - - OCC::ownCloudGui - - - Please sign in - Пријавите се - - - - Folder %1: %2 - Фасцикла %1: %2 - - - - There are no sync folders configured. - Нема подешених фасцикли за синхронизацију. - - - - Open in browser - Отвори у прегледачу - - - - - - Log in... - Пријави се... - - - - - - Log out - Одјава - - - - Recent Changes - Недавне измене - - - - Managed Folders: - Управљане фасцикле: - - - - Open folder '%1' - Отвори фасциклу „%1“ - - - - Open %1 in browser - Отвори %1 у прегледачу - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Непознато стање - - - - Settings... - Поставке... - - - - Details... - Детаљи... - - - - Help - Помоћ - - - - Quit %1 - Напусти %1 - - - - Disconnected from %1 - Одјављен са %1 - - - - Unsupported Server Version - - - - - Disconnected - Неповезан - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - Одјављен са налога: - - - - Account %1: %2 - Налог %1: %2 - - - - Signed out - Одјављен - - - - Account synchronization is disabled - - - - - - Synchronization is paused - - - - - Unresolved conflicts - - - - - Error during synchronization - - - - - No sync folders configured - - - - - Unpause all folders - - - - - Pause all folders - - - - - Unpause all synchronization - - - - - Unpause synchronization - - - - - Pause all synchronization - - - - - Pause synchronization - - - - - Log out of all accounts - Одјави се са свих налога - - - - Log in to all accounts... - Пријави се на све налоге... - - - - New account... - - - - - - About %1 - - - - - No items synced recently - Ништа није недавно синхронизовано - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - Синхронизујем %1 од %2 (преостало %3) - - - - Syncing %1 of %2 - - - - - Syncing %1 (%2 left) - Синхронизујем %1 (преостало %2) - - - - Syncing %1 - Синхронизујем %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Ажурно - - - - OwncloudAdvancedSetupPage - - - Form - Образац - - - - - - - - - - TextLabel - Текст ознака - - - - Server - Сервер - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Ако је ово поље означено, постојећи садржај локалне фасцикле биће обрисан да би започела чиста синхронизација са сервера.</p><p>Не означавајте ако локални садржај треба отпремити у фасцикле на серверу.</p></body></html> - - - - Start a &clean sync (Erases the local folder!) - &Почни чисту синхронизацију (брише локалну фасциклу!) - - - - Ask for confirmation before synchroni&zing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - - - - - Choose what to sync - Изаберите шта синхронизовати - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - &Локална фасцикла - - - - pbSelectLocalFolder - Избор локалне фасцикле - - - - &Keep local data - &Остави локалне податке - - - - S&ynchronize everything from server (recommended) - - - - - Use virtual files instead of downloading content immediately (e&xperimental) - - - - - Status message - Порука стања - - - - OwncloudHttpCredsPage - - - Form - Образац - - - - &Username - &Корисничко име - - - - &Password - &Лозинка - - - - OwncloudOAuthCredsPage - - - Form - Образац - - - - Please switch to your browser to proceed. - - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - - - - - OwncloudSetupPage - - - Form - Образац - - - - - TextLabel - Текст ознака - - - - Ser&ver Address - - - - - https://... - https://... - - - - Error Label - Ознака грешке - - - - OwncloudWizardResultPage - - - Form - Образац - - - - TextLabel - Текст ознака - - - - Your entire account is synced to the local folder - Целокупан налог је синхронизован са локалном фасциклом - - - - - PushButton - Тастер - - - - QObject - - - in the future - - - - - %n day(s) ago - - - - - %n hour(s) ago - - - - - now - - - - - Less than a minute ago - пре мање од минут - - - - %n minute(s) ago - - - - - Some time ago - пре неког времена - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - - - - - %n month(s) - - - - - %n day(s) - - - - - %n hour(s) - - - - - %n minute(s) - - - - - %n second(s) - - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Системска касета није доступна - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 захтева системску касету. Ако сте у ИксФЦЕ, испратите <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">ова упутства</a>, или инсталирајте апликацију системске касете и покушајте поново. - - - - ownCloudTheme::about() - - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - <p><small>Направљено од ГИТ ревизије <a href="%1">%2</a> %3, %4 користећи КуТ %5, %6</small></p> - - - - progress - - - Downloaded - Преузето - - - - Uploaded - Отпремљено - - - - Server version downloaded, copied changed local file into conflict file - - - - - Deleted - Обрисано - - - - Moved to %1 - Премештено у %1 - - - - Ignored - Игнорисано - - - - Filesystem access error - Грешка приступа фајл-систему - - - - Error - Грешка - - - - Updated local metadata - - - - - - Unknown - Непознато - - - - downloading - преузимам - - - - uploading - отпремам - - - - deleting - бришем - - - - moving - премештам - - - - ignoring - игноришем - - - - - error - грешка - - - - updating local metadata - - - - - theme - - - Status undefined - Неодређено стање - - - - Waiting to start sync - Чекам почетак синхронизације - - - - Sync is running - Синхронизација у току - - - - Sync Success - Успешна синхронизација - - - - Sync Success, some files were ignored. - Синхронизација успешна. Неки фајлови су игнорисани. - - - - Sync Error - Грешка синхронизације - - - - Setup Error - Грешка подешавања - - - - Preparing to sync - Припремам синхронизацију - - - - Aborting... - Прекидам... - - - - Sync is paused - Синхронизација паузирана - - - - utility - - - Could not open browser - - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - - - \ No newline at end of file diff --git a/translations/client_sv.ts b/translations/client_sv.ts deleted file mode 100644 index ec31463fb2e..00000000000 --- a/translations/client_sv.ts +++ /dev/null @@ -1,4777 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - Avbryt - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - Stäng - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - - - - - - Could not move '%1' to '%2' - - - - - Moving to the trash is not implemented on this platform - - - - - Error removing '%1': %2 - - - - - Could not remove folder '%1' - Kunde inte ta bort mapp '%1' - - - - FolderWizardSourcePage - - - Form - Formulär - - - - Pick a local folder on your computer to sync - Välj en lokal mapp på din dator för synkronisering - - - - &Choose... - &Välj... - - - - FolderWizardTargetPage - - - Form - Formulär - - - - Select a remote destination folder - Välj en fjärr målmapp - - - - Create Folder - Skapa mapp - - - - Refresh - Uppdatera - - - - Folders - Mappar - - - - TextLabel - Textetikett - - - - NotificationWidget - - - Form - Form - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Textetikett - - - - OCC::AboutDialog - - - About - Om - - - - TextLabel - Textetikett - - - - Versions - Versioner - - - - About %1 - Om %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Tidsgräns för anslutningen överskreds - - - - Unknown error: network reply was deleted - Okänt fel: nätverkssvaret raderades - - - - Server replied "%1 %2" to "%3 %4" - Servern svarade "%1 %2" på "%3 %4" - - - - OCC::Account - - - %1@%2 - - - - - OCC::AccountSettings - - - Form - Form - - - - Re-open Browser - - - - - ... - ... - - - - Storage space: ... - Lagringsutrymme: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - De mappar som inte väljs kommer att <b>raderas</b> från det lokala filsystemet och kommer inte att synkroniseras till den här datorn längre. - - - - Synchronize all - Synkronisera alla - - - - Synchronize none - Synkronisera inga - - - - Apply manual changes - Spara manuella ändringar - - - - Apply - Tillämpa - - - - - - - Cancel - Avbryt - - - - Connected with <server> as <user> - Ansluten till <server> som <user> - - - - No account configured. - Inget konto är konfigurerat. - - - - Remove - Radera - - - - Account - Konto - - - - Choose what to sync - Välj vad som ska synkas - - - - Force sync now - Tvinga synkning - - - - Restart sync - Starta om synkning - - - - Remove folder sync connection - Ta bort anslutning till mappsynkronisering - - - - Folder creation failed - Kunde inte skapa mappen - - - - <p>Could not create local folder <i>%1</i>. - <p>Kunde inte skapa lokal mapp <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Bekräfta radering av anslutning till mappsynkronisering - - - - Remove Folder Sync Connection - Ta bort anslutning till mappsynkronisering - - - - Disable virtual file support? - - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - - - - - Sync Running - Synkronisering pågår - - - - The syncing operation is running.<br/>Do you want to terminate it? - En synkronisering pågår.<br/>Vill du avbryta den? - - - - %1 in use - %1 används - - - - %1 as <i>%2</i> - %1 som <i>%2</i> - - - - Connected to %1. - Ansluten till %1. - - - - Server %1 is temporarily unavailable. - Servern %1 är för tillfället inte tillgänglig. - - - - Server %1 is currently in maintenance mode. - Servern %1 är för närvarande i underhållsläge. - - - - Signed out from %1. - Utloggad från %1. - - - - Connecting to %1... - Ansluter till %1... - - - - No connection to %1 at %2. - Ingen anslutning till %1 vid %2. - - - - Log in - Logga in - - - - There are folders that were not synchronized because they are too big: - Dessa mappar har inte synkroniserats för att de är för stora: - - - - There are folders that were not synchronized because they are external storages: - Det finns mappar som inte synkroniserats för att de är externa lagringsytor: - - - - There are folders that were not synchronized because they are too big or external storages: - Det finns mappar som inte blivit synkroniserade på grund av att de är för stora eller är externa lagringsytor: - - - - Confirm Account Removal - Bekräfta radering an kontot - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Vill du verkligen avsluta anslutningen till kontot <i>%1</i>?</p><p><b>Observera:</b> Detta kommer <b>inte</b> radera några filer.</p> - - - - Remove connection - Radera anslutning - - - - Open folder - Öppna mapp - - - - - Log out - Logga ut - - - - Open local folder - - - - - Open folder in browser - - - - - Resume sync - Återuppta synkronisering - - - - Pause sync - Pausa synkronisering - - - - Availability - - - - - Disable virtual file support... - - - - - Enable virtual file support%1... - - - - - (experimental) - - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Vill du verkligen avbryta synkronisering av mappen <i>%1</i>?</p><p><b>Observera:</b> Detta kommer <b>inte</b> radera några filer.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) av %2 används. Vissa mappar, inklusive nätverks- eller delade mappar, kan ha andra begränsningar. - - - - %1 of %2 in use - %1 av %2 används - - - - Currently there is no storage usage information available. - Just nu finns ingen utrymmes information tillgänglig - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - Obtaining authorization from the browser. - - - - - Server configuration error: %1 at %2. - - - - - No %1 connection configured. - Ingen %1 anslutning konfigurerad. - - - - OCC::AccountState - - - Signed out - Utloggad - - - - Disconnected - Bortkopplad - - - - Connected - Ansluten - - - - Service unavailable - Tjänsten är inte tillgänglig - - - - Maintenance mode - Underhållsläge - - - - Network error - Nätverksfel - - - - Configuration error - Konfigurationsfel - - - - Asking Credentials - - - - - Unknown account state - Okänd kontostatus - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 på %2 - - - - %1 on %2 (disconnected) - %1 på %2 (frånkopplad) - - - - OCC::ActivityListModel - - - %1 %2 on %3 - - - - - OCC::ActivitySettings - - - - Server Activity - Serveraktivitet - - - - Sync Protocol - Synkprotokoll - - - - Not Synced - Inte synkroniserad - - - - Not Synced (%1) - %1 is the number of not synced files. - Inte synkad (%1) - - - - The server activity list has been copied to the clipboard. - Listan på serveraktivitet har kopierats till urklipp. - - - - The sync activity list has been copied to the clipboard. - Listan på synkaktivitet har kopierats till urklipp. - - - - The list of unsynced items has been copied to the clipboard. - Listan över ej synkroniserat har kopierats till klippbordet. - - - - Copied to clipboard - Kopierat till urklipp - - - - OCC::ActivityWidget - - - Form - Form - - - - - - TextLabel - Textetikett - - - - Server Activities - Serveraktivitet - - - - Copy - Kopiera - - - - Copy the activity list to the clipboard. - Kopiera aktivitetslistan till urklipp. - - - - Notifications - Notifieringar - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - - - - - Open the activity view for details. - - - - - <br/>Account %1 does not have activities enabled. - <br/>Kontot %1 har inte aktiviteter aktiverade - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Klient-SSL-certifikatauktorisering - - - - This server probably requires a SSL client certificate. - Denna server kräver förmodligen Klient-SSL-certifikat - - - - Certificate & Key (pkcs12) : - Certifikat och nyckel (pkcs12) : - - - - Browse... - Bläddra... - - - - Certificate password : - Certifikatlösenord : - - - - An encrypted pkcs12 bundle is strongly recommended as a copy will be stored in the configuration file. - - - - - Select a certificate - Välj ett certifikat - - - - Certificate files (*.p12 *.pfx) - Certifikatfiler (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Avsluta - - - - Continue - Fortsätt - - - - Error accessing the configuration file - Kunde inte komma åt konfigurationsfilen - - - - There was an error while accessing the configuration file at %1. - Ett fel uppstod vid läsning av konfigurationsfil vid %1. - - - - Quit ownCloud - Avsluta ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - Autentisering krävs - - - - Enter username and password for '%1' at %2. - Ange användarnamn och lösenord för '%1' i %2 - - - - &User: - &Användare: - - - - &Password: - &Lösenord: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Fel vid skrivning av metadata till databasen - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Inget ownCloud konto konfigurerat - - - - The configured server for this client is too old - Den konfigurerade servern är för den här klienten är för gammal - - - - Please update to the latest server and restart the client. - Uppgradera servern och starta sedan om klienten. - - - - Authentication error: Either username or password are wrong. - Autentiseringsfel: Användarnamn eller lösenord är felaktigt - - - - timeout - timeout - - - - The provided credentials are not correct - De angivna uppgifterna stämmer ej - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - - - - - Directory not accessible on client, permission denied - - - - - Directory not found: %1 - - - - - Filename encoding is not valid - Filnamnskodning är inte giltig - - - - Error while reading directory %1 - - - - - OCC::Folder - - - Local folder %1 does not exist. - Den lokala mappen %1 finns inte. - - - - %1 should be a folder but is not. - %1 borde vara en mapp, men är inte det. - - - - %1 is not readable. - %1 är inte läsbar. - - - - %1 has been removed. - %1 names a file. - %1 har tagits bort. - - - - %1 has been updated. - %1 names a file. - %1 har uppdaterats. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 har döpts om till %2. - - - - %1 has been moved to %2. - %1 har flyttats till %2. - - - - %1 and %n other file(s) have been removed. - %1 och %n andra filer har tagits bort.%1 och %n andra filer har tagits bort. - - - - %1 and %n other file(s) have been added. - - - - - %1 has been added. - %1 names a file. - - - - - %1 and %n other file(s) have been updated. - %1 och %n andra filer har uppdaterats.%1 och %n andra filer har uppdaterats. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 har döpts om till %2 och %n andra filer har döpts om.%1 har döpts om till %2 och %n andra filer har döpts om. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 har flyttats till %2 och %n andra filer har flyttats.%1 har flyttats till %2 och %n andra filer har flyttats. - - - - %1 has and %n other file(s) have sync conflicts. - %1 och %n andra filer har synk-konflikter.%1 och %n andra filer har synk-konflikter. - - - - %1 has a sync conflict. Please check the conflict file! - %1 har en synk-konflikt. Vänligen kontrollera konfliktfilen! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 och %n andra filer kunde inte synkas på grund av fel. Se loggen för detaljer.%1 och %n andra filer kunde inte synkas på grund av fel. Se loggen för detaljer. - - - - %1 could not be synced due to an error. See the log for details. - %1 kunde inte synkroniseras på grund av ett fel. Kolla loggen för ytterligare detaljer. - - - - Sync Activity - Synk aktivitet - - - - Could not read system exclude file - Kunde inte läsa systemets exkluderings-fil - - - - A new folder larger than %1 MB has been added: %2. - - En ny mapp större än %1 MB har lagts till: %2. - - - - - A folder from an external storage has been added. - - En mapp från en extern lagringsyta har lagts till. - - - - - Please go in the settings to select it if you wish to download it. - Vänligen gå till inställningar och välj den om du önskar att ladda ner den. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - Ta bort alla filer? - - - - Remove all files - Ta bort alla filer - - - - Keep files - Behåll filer - - - - OCC::FolderMan - - - Could not reset folder state - Kunde inte återställa mappens skick - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - En gammal synkroniseringsjournal '%1' hittades, men kunde inte raderas. Vänligen se till att inga program för tillfället använder den. - - - - (backup) - (säkerhetskopia) - - - - (backup %1) - (säkerhetkopia %1) - - - - Undefined State. - Okänt tillstånd. - - - - Waiting to start syncing. - Väntar på att starta synkronisering. - - - - Preparing for sync. - Förbereder synkronisering - - - - Sync is running. - Synkronisering pågår. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Senaste synkronisering lyckades. - - - - Setup Error. - Inställningsfel. - - - - User Abort. - Användare Avbryt - - - - Sync is paused. - Synkronisering är pausad. - - - - %1 (Sync is paused) - %1 (Synk är stoppad) - - - - No valid folder selected! - Ingen giltig mapp markerad! - - - - The selected path does not exist! - - - - - The selected path is not a folder! - Den markerade sökvägen är inte en mapp! - - - - You have no permission to write to the selected folder! - Du har inga skrivrättigheter till den valda mappen! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Det pågår redan en synkronisering från servern till denna lokala mappen. Vänligen välj en annan lokal mapp. - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Den lokala mappen %1 innehåller redan en mapp som synkas. Var god välj en annan! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Den lokala mappen %1 finns redan inuti en mapp som synkas. Var god välj en annan! - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Lägg till mapp att synka. - - - - File - Fil - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Du måste vara ansluten för att lägga till en mapp - - - - Click this button to add a folder to synchronize. - Klicka här för att lägga till en mapp att synka. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Ett fel uppstod när mapplistan försökte laddas från servern. - - - - Virtual file support is enabled. - - - - - Signed out - Utloggad - - - - Fetching folder list from server... - Hämtar mapplistan från servern... - - - - There are unresolved conflicts. Click for details. - Det finns olösta konflikter. Klicka för detaljer. - - - - Synchronizing VirtualFiles with local folder - - - - - Synchronizing with local folder - Synkroniserar med lokal mapp - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Synkroniserar %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - ladda ner %1/s - - - - ↓ %1/s - - - - - ↑ %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - ladda upp %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 av %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - %5 kvar, %1 av %2, fil %3 av %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 av %2, fil %3 av %4 - - - - file %1 of %2 - fil %1 av %2 - - - - Waiting... - Väntar... - - - - Waiting for %n other folder(s)... - Väntat på %n annan mapp...Väntat på %n andra mappar... - - - - Preparing to sync... - Förbereder för att synkronisera... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Lägg till mapp att synka. - - - - Add Sync Connection - Lägg till anslutning. - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Klicka för att välja en lokal mapp att synka. - - - - Enter the path to the local folder. - Ange sökvägen till den lokala mappen. - - - - Select the source folder - Välj källmapp - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Skapa fjärrmapp - - - - Enter the name of the new folder to be created below '%1': - Ange namnet på den nya mappen som skall skapas under '%1': - - - - Folder was successfully created on %1. - Mappen skapades på %1. - - - - Authentication failed accessing %1 - Autentisering misslyckades att komma åt %1 - - - - Failed to create the folder on %1. Please check manually. - Det gick inte att skapa mappen på %1. Kontrollera manuellt. - - - - Failed to list a folder. Error: %1 - Kunde inte lista en mapp. Felkod: %1 - - - - Choose this to sync the entire account - Välj detta för att synka allt - - - - This folder is already being synced. - Denna mappen synkas redan. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Du synkar redan <i>%1</i>, vilket är övermapp till <i>%2</i> - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Varning:</b> %1 - - - - <b>Warning:</b> - <b>Varning:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Ingen E-Tag mottogs från servern, kontrollera proxy/gateway - - - - We received a different E-Tag for resuming. Retrying next time. - Vi mottog en helt annan e-tag för att återuppta. Försök igen nästa gång. - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - Servern returnerade felaktig content-range - - - - OCC::GETJob - - - Connection Timeout - Anslutningen avbröts på grund av timeout - - - - OCC::GeneralSettings - - - Form - Form - - - - General Settings - Generella inställningar - - - - Sync hidden files - Synkronisera dolda filer - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Nätverk - - - - Updates - Uppdateringar - - - - stable - - - - - beta - - - - - About - Om - - - - Advanced - Avancerad - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Fråga innan synkronisering av mappar större än - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Fråga innan synkronisering av externa lagringsytor - - - - &Launch on System Startup - &Starta vid uppstart - - - - Show &Desktop Notifications - Visa &skrivbordsnotifikationer - - - - &Update Channel - - - - - Edit &Ignored Files - Ändra &ignorerade filer - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - Visa krashrapporteraren - - - - &Restart && Update - %Starta om && Uppdatera - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Avbryt - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Vänligen ange %1 lösenord:<br><br>Användare: %2<br>Konto: %3<br> - - - - Reading from keychain failed with error: '%1' - Avläsning från kedjan misslyckades med felkod: '%1' - - - - Enter Password - Ange lösenord - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">Klicka här</a> för att begära ett app-lösenord från webbgränssnittet. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Ändra ignorerade filer - - - - Files Ignored by Patterns - Filer ignorerade av mönster - - - - Add - Lägg till - - - - Pattern - Mönster - - - - Allow Deletion - Tillåt radering - - - - Remove - Radera - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Kunde inte öppna fil - - - - Cannot write changes to '%1'. - Kan inte skriva förändringar till '%1'. - - - - Add Ignore Pattern - Lägg till synk-filter - - - - Add a new ignore pattern: - Lägg till ett nytt synk-filter: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Denna post hanteras av systemet på '%1' och kan inte ändras här. - - - - OCC::IssuesWidget - - - Form - Form - - - - List of issues - Lista över fel - - - - Account - Konto - - - - - <no filter> - <no filter> - - - - - Folder - Mapp - - - - Show warnings - Visa varningar - - - - Show ignored files - Visa ignorerade filer - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - Kopiera fellistan till klippbordet. - - - - Copy - Kopiera - - - - Time - Tid - - - - File - Fil - - - - Issue - Fel - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LibCloudProvidersPrivate - - - Settings - Inställningar - - - - OCC::LogBrowser - - - Log Output - Loggdata - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Öppna mapp - - - - OCC::NSISUpdater - - - New Version Available - Ny version tillgänglig - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>En ny version av %1 klienten är tillgänglig.</p><p><b>%2</b> är tillgänglig för nedladdning. Den installerade versionen är %3.</p> - - - - - Skip this version - Hoppa över denna version - - - - Skip this time - Hoppa över denna gång - - - - Get update - Hämta uppdatering - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - Form - - - - Proxy Settings - Proxyinställningar - - - - No Proxy - Ingen proxy - - - - Use system proxy - Använd systemets proxyinställning - - - - Specify proxy manually as - Ange proxy manuellt - - - - Host - Server - - - - : - : - - - - Proxy server requires authentication - Proxyservern kräver autentisering - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Bandbredd för nedladdning - - - - - Limit to - Begränsa till - - - - - KBytes/s - KBytes/s - - - - - No limit - Ingen gräns - - - - - Limit to 3/4 of estimated bandwidth - Begränsa till 3/4 av uppskattad bandbredd - - - - Upload Bandwidth - Bandbredd för uppladdning - - - - - Limit automatically - Begränsa automatiskt - - - - Hostname of proxy server - Hostnamn för proxyserver - - - - Username for proxy server - Användarnamn för proxyserver - - - - Password for proxy server - Lösenord för proxyanvändare - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - Skapad %1 - - - - Closing in a few seconds... - Stänger om ett par sekunder... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 begäran misslyckades %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' vald %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Fel returnerat från server: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Empty JSON from OAuth2 redirect - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Kunde inte para JSON som returnerades från servern: <br><em>%1</em> - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Error: Missing field %1 - - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Inloggningsfel</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - Fel användare</h1><p> Du har loggat in med fel användare <em>%1</em>, men måste logga in med <em>%2</em>.<br>Vänligen logga ut från %3 i en annan flik, sen <a href='%4'>klicka här </a> och logga in som användare %2</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Ny %1 uppdatering tillgänglig - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - En ny uppdatering för %1 kommer installeras. Programmet kan be om -ytterligare rättigheter under processen. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Kunde inte ladda ner uppdateringen. Klicka <a href='%1'>här</a> för att ladda ner uppdateringen manuellt. - - - - Could not check for new updates. - Kunde inte söka efter uppdateringar. - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - Kontrollerar uppdateringsservern... - - - - Update status is unknown: Did not check for new updates. - Uppdateringsstatus är okänd: Gjorde ingen kontroll av nya uppdateringar. - - - - No updates available. Your installation is at the latest version. - Inga uppdateringar tillgängliga. Din installation är redan den senaste versionen. - - - - Update Check - Uppdateringskontroll - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Anslut till %1 - - - - Setup local folder options - Inställningar för lokala mappar. - - - - Connect... - Anslut... - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - %1 folder '%2' is synced to local folder '%3' - %1 mappen '%2' är synkroniserad mot den lokala mappen '%3' - - - - Sync the folder '%1' - Synka mappen '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>Varning:</strong> Den lokala mappen är inte tom. Välj alternativ!</small></p> - - - - Virtual files are not available for the selected folder - - - - - Local Sync Folder - Lokal mapp för synkning - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Anslutningen misslyckades - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Misslyckades med att upprätta anslutning till den angivna servern. Hur vill du fortsätta?</p></body></html> - - - - Select a different URL - Välj en annan URL - - - - Retry unencrypted over HTTP (insecure) - Försök igen okrypterat över HTTP (osäkert) - - - - Configure client-side TLS certificate - Konfigurera TLS klient-certifikat - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Misslyckades med att ansluta till den säkra serveradressen <em>%1</em>. Hur vill du gå vidare?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - &E-post - - - - Connect to %1 - Anslut till %1 - - - - Enter user credentials - Ange inloggningsuppgifter - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Anslut till %1 - - - - Login in your browser - Logga in i din webbläsare - - - - Copy link to clipboard - Kopiera länk till urklipp - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Anslut till %1 - - - - Setup %1 server - Installerar %1 server - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Denna URL är INTE säker eftersom den inte är krypterad. -Det är inte lämpligt använda den. - - - - This url is secure. You can use it. - Denna url är säker. Du kan använda den. - - - - &Next > - &Nästa > - - - - Invalid URL - Ogiltig URL - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Misslyckades att ansluta till %1 vid %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Försök att ansluta till %1 på %2 tog för lång tid. - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - Den autentiserade begäran till servern omdirigerades till '%1'. Den URLen är ogiltig, server är felkonfigurerad. - - - - There was an invalid response to an authenticated webdav request - Det kom ett ogiltigt svar på en autentiserad webdav-begäran - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - Åtkomst förbjuden av servern. För att bekräfta att du har korrekta rättigheter, <a href="%1">klicka här</a> för att ansluta till tjänsten med din webb-läsare. - - - - Invalid URL - Ogiltig URL - - - - Could not create local folder %1 - Kunde inte skapa lokal mapp %1 - - - - No remote folder specified! - Ingen fjärrmapp specificerad! - - - - Error: %1 - Fel: %1 - - - - The folder creation resulted in HTTP error code %1 - Skapande av mapp resulterade i HTTP felkod %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Det gick inte att skapa mappen efter som du inte har tillräckliga rättigheter!<br/>Vänligen återvänd och kontrollera dina rättigheter. - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Misslyckades skapa fjärrmapp %1 med fel <tt>%2</tt>. - - - - Folder rename failed - Omdöpning av mapp misslyckades - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Kan inte ta bort och göra en säkerhetskopia av mappen på grund av att mappen eller en fil i den används av ett annat program. Vänligen stäng mappen eller filen och försök igen eller avbryt installationen. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Anslutningsguiden - - - - Enable experimental feature? - - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Allt är konfigurerat! - - - - Open Local Folder - Öppnar lokal mapp - - - - Open %1 in Browser - Öppna %1 i webbläsare - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - Ogiltigt JSON-svar från hämtnings-URLen - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Symboliska länkar stöds ej i synkningen. - - - - File is listed on the ignore list. - Filen är listad i ignorerings listan. - - - - File names ending with a period are not supported on this file system. - Filnamn som slutar med punkt stöds inte i det här filsystemet. - - - - File names containing the character '%1' are not supported on this file system. - Filnamn som innehåller tecknet '%1' stöds inte i det här filsystemet. - - - - File name contains at least one invalid character - Filnamnet innehåller minst ett ogiltigt tecken - - - - The file name is a reserved name on this file system. - Filnamnet är ett reserverat namn i detta filsystemet. - - - - Filename contains trailing spaces. - Filnamn innehåller mellanslag i slutet. - - - - Filename is too long. - Filnamnet är för långt. - - - - File/Folder is ignored because it's hidden. - Filen/Mappen är ignorerad för att den är dold. - - - - Stat failed. - Stat misslyckades. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Konflikt: Serverversion nedladdad, lokal kopia omdöpt och ej uppladdad. - - - - The filename cannot be encoded on your file system. - - - - - The filename is blacklisted on the server. - - - - - File has extension reserved for virtual files. - - - - - size - - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - Inte behörig att radera, återställer - - - - Ignored because of the "choose what to sync" blacklist - Ignorerad eftersom den är svartlistad i "välj vad som ska synkas" - - - - Not allowed because you don't have permission to add subfolders to that folder - Otillåtet eftersom du inte har rättigheter att lägga till undermappar i den mappen. - - - - Not allowed because you don't have permission to add files in that folder - Otillåtet eftersom du inte har rättigheter att lägga till filer i den mappen. - - - - Not allowed to upload this file because it is read-only on the server, restoring - Inte behörig att ladda upp denna fil då den är skrivskyddad på servern, återställer - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Fel vid skrivning av metadata till databasen - - - - OCC::PropagateDownloadFile - - - - File %1 can not be downloaded because of a local file name clash! - Fil %1 kan inte laddas ner på grund av namnkonflikt med en lokal fil! - - - - - %1 is currently in use - - - - - The download would reduce free local disk space below the limit - Nedladdningen skulle reducera det fria diskutrymmet under gränsen - - - - Free space on disk is less than %1 - Ledigt utrymme är under %1 - - - - File was deleted from server - Filen har tagits bort från servern - - - - The file could not be downloaded completely. - Filen kunde inte laddas ner fullständigt. - - - - The downloaded file is empty despite the server announced it should have been %1. - Den nedladdade filen är tom men servern sa att den skulle vara %1. - - - - File %1 cannot be saved because of a local file name clash! - Fil %1 kan inte sparas eftersom namnet krockar med en lokal fil! - - - - - File has changed since discovery - Filen har ändrats sedan upptäckten - - - - Failed to dehydrate %1, the file is currently in use - - - - - Error writing metadata to the database - Fel vid skrivning av metadata till databasen - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Återställning misslyckades: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - En fil eller mapp togs bort från en skrivskyddad delning, men återställning misslyckades: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - kunde inte ta bort fil %1, fel: %2 - - - - Attention, possible case sensitivity clash with %1 - Observera, eventuell skiftlägeskänslig konflikt med %1 - - - - could not create folder %1 - kunde inte skapa mapp %1 - - - - Error writing metadata to the database - Fel vid skrivning av metadata till databasen - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash - Det gick inte att ta bort %1 på grund av ett lokalt filnamn - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Filen %1 kan inte döpas om till %2 på grund av ett lokalt filnamn - - - - Could not rename %1 to %2, the file is currently in use - - - - - - Error writing metadata to the database - Fel vid skrivning av metadata till databasen - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Felaktig HTTP-kod i svaret från servern. '204' förväntades, men "%1 %2" mottogs. - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Felaktig HTTP-kod i svaret från servern. '201' förväntades, men "%1 %2" mottogs. - - - - Error writing metadata to the database - Fel vid skrivning av metadata till databasen - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Felaktig HTTP-kod i svaret från servern. '201' förväntades, men "%1 %2" mottogs. - - - - - Error writing metadata to the database - Fel vid skrivning av metadata till databasen - - - - Error setting pin state - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - Filen Raderad - - - - Local file changed during syncing. It will be resumed. - Lokal fil ändrades under synkningen. Den kommer återupptas. - - - - Local file changed during sync. - Lokal fil ändrades under synk. - - - - - Upload of %1 exceeds the quota for the folder - Uppladdningen av %1 överstiger kvoten för mappen - - - - Error writing metadata to the database - Fel vid skrivning av metadata till databasen - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Den lokala filen togs bort under synkronisering. - - - - Local file changed during sync. - Lokal fil ändrades under synk. - - - - Poll URL missing - Hämtnings-URL saknas - - - - Unexpected return code from server (%1) - Oväntad svarskod från servern (%1) - - - - Missing File ID from server - Saknar Fil-ID från servern - - - - Missing ETag from server - Saknar ETag från servern - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Den lokala filen togs bort under synkronisering. - - - - Local file changed during sync. - Lokal fil ändrades under synk. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - Poll URL missing - Hämtnings-URL saknas - - - - The local file was removed during sync. - Den lokala filen togs bort under synkronisering. - - - - Local file changed during sync. - Lokal fil ändrades under synk. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Servern bekräftade inte senaste leveransen. (Ingen e-tagg fanns) - - - - OCC::ProtocolWidget - - - Form - Form - - - - TextLabel - Textetikett - - - - Open in browser - Öppna i webbläsare - - - - Time - Tid - - - - File - Fil - - - - Folder - Mapp - - - - Action - Ågärd - - - - Size - Storlek - - - - Local sync protocol - Lokalt synkprotokoll - - - - Copy - Kopiera - - - - Copy the activity list to the clipboard. - Kopiera aktivitetslistan till urklipp. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Proxy-autentisering krävs - - - - Username: - Användarnamn: - - - - Proxy: - Proxy: - - - - The proxy server needs a username and password. - Proxy-servern behöver ett användarnamn och lösenord. - - - - Password: - Lösenord: - - - - TextLabel - Textetikett - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Välj vad som ska synkas - - - - OCC::SelectiveSyncWidget - - - Loading ... - Laddar ... - - - - Deselect remote folders you do not wish to synchronize. - Avmarkera mappar du inte vill synkronisera. - - - - Name - Namn - - - - Size - Storlek - - - - - No subfolders currently on the server. - Inga undermappar på servern för närvarande. - - - - An error occurred while loading the list of sub folders. - Ett fel uppstod när listan för submappar laddades. - - - - OCC::ServerNotificationHandler - - - Dismiss - Avfärda - - - - OCC::SettingsDialog - - - Settings - Inställningar - - - - Activity - Aktivitet - - - - Add account - Lägg till konto - - - - - Quit %1 - Avsluta %1 - - - - Are you sure you want to quit %1? - - - - - Account - Konto - - - - MainWindow - - - - - Hide - Göm - - - - OCC::ShareDialog - - - TextLabel - Textetikett - - - - share label - delningsetikett - - - - Dialog - Dialog - - - - ownCloud Path: - Sökväg till ownCloud: - - - - %1 Sharing - %1 Delning - - - - %1 - %1 - - - - Folder: %2 - Mapp: %2 - - - - The server does not allow sharing - Servern tillåter inte delning - - - - Retrieving maximum possible sharing permissions from server... - Tar emot maximal delningsbehörighet från servern... - - - - The file can not be shared because it was shared without sharing permission. - Filen kan inte delas eftersom den delades utan delningsrättigheter. - - - - Users and Groups - Användare och grupper - - - - Public Links - Publika länkar - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Dela NewDocument.odt - - - - Upload only (File Drop) - - - - - Receive files from others without revealing the contents of the folder. - - - - - Download / View / Edit - - - - - Name: - - - - - Enter name of new share... - - - - - &Create - - - - - TextLabel - Textetikett - - - - Set &password - Sätt &lösenord - - - - Set &expiration date - Sätt datum för &utgång - - - - Set password - Ange lösenord - - - - Recipients can view or download contents. - - - - - Download / View - Ladda ner / Granska - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - Vem som helst med länken kan komma åt filen eller mappen - - - - - P&assword protect - L&ösenordsskydda - - - - The file can not be shared because it was shared without sharing permission. - Filen kan inte delas eftersom den delades utan delningsrättigheter. - - - - Link shares have been disabled - Delningslänkar har inaktiverats - - - - Create public link share - Skapa publik delningslänk - - - - - Delete - Ta bort - - - - Open link in browser - Öppna länk i webbläsare - - - - Copy link to clipboard - Kopiera länk till urklipp - - - - Copy link to clipboard (direct download) - Kopiera länk till urklipp (direktnedladdning) - - - - Send link by email - Skicka länk via e-post - - - - Send link by email (direct download) - Skicka länk med e-post (direktnedladdning) - - - - Create new... - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - - - - - Cancel - Avbryt - - - - - Public link - Publik länk - - - - Delete link share - - - - - - Please Set Password - Var vänlig sätt lösenord - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Dela NewDocument.odt - - - - Share with users or groups ... - Dela med användare eller grupper ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - - - - - Open link in browser - Öppna länk i webbläsaren - - - - Copy link to clipboard - Kopiera länk till urklipp - - - - Send link by email - Skicka länk via e-post - - - - No results for '%1' - Inga resultat för '%1' - - - - I shared something with you - Jag delade något med dig - - - - OCC::ShareUserLine - - - Form - Form - - - - - TextLabel - Textetikett - - - - can edit - kan redigera - - - - can share - får dela - - - - ... - ... - - - - create - skapa - - - - change - ändra - - - - delete - radera - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Dela med %1 - - - - Context menu share - - - - - Context menu share %1 - - - - - Confirm deletion - - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - - - - - Do you want to delete the file <i>%1</i> permanently? - - - - - Select new location... - - - - - Error - Fel - - - - Moving file failed: - -%1 - - - - - I shared something with you - Jag delade något med dig - - - - - Share... - Dela... - - - - - Copy private link to clipboard - Kopiera privat länk till urklipp - - - - Send private link by email... - Skicka privat länk med e-post... - - - - Resharing this file is not allowed - - - - - Resharing this folder is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - Öppna i webbläsare - - - - Show file versions in browser - - - - - Rename... - - - - - Rename and upload... - - - - - Move and rename... - - - - - Move, rename and upload... - - - - - Delete local changes - - - - - Move and upload... - - - - - Delete - Ta bort - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Certifikatdetaljer</h3> - - - - Common Name (CN): - Common Name (CN): - - - - Subject Alternative Names: - Alternativnamn: - - - - Organization (O): - Organisation (O): - - - - Organizational Unit (OU): - Organisationsenhet (OU): - - - - State/Province: - Stat/Provins: - - - - Country: - Land: - - - - Serial: - Serienummer: - - - - <h3>Issuer</h3> - <h3>Utfärdare</h3> - - - - Issuer: - Utfärdare: - - - - Issued on: - Utfärdat den: - - - - Expires on: - Upphör den: - - - - <h3>Fingerprints</h3> - <h3>Fingeravtryck</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Notera:</b> Detta certifikat var manuellt godkänt.</p> - - - - %1 (self-signed) - %1 (självsignerat certifikat) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Denna anslutningen är krypterad med %1 bit %2 - - - - - Server version: %1 - - - - - No support for SSL session tickets/identifiers - Inget stöd för biljetter/identifikationer för SSL sessioner - - - - Certificate information: - Certifikatinformation: - - - - The connection is not secure - - - - - This connection is NOT secure as it is not encrypted. - - Denna anslutningen är INTE säker eftersom den inte är krypterad. - - - - - OCC::SslErrorDialog - - - Form - Form - - - - Trust this certificate anyway - Lita på detta certifikat i alla fall - - - - Untrusted Certificate - Ej betrott certifikat - - - - Cannot connect securely to <i>%1</i>: - Kan inte ansluta säkert till <i>%1</i>: - - - - with Certificate %1 - med Certifikat %1 - - - - - - &lt;not specified&gt; - &lt;inte angivet&gt; - - - - - Organization: %1 - Organisation: %1 - - - - - Unit: %1 - Enhet: %1 - - - - - Country: %1 - Land: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Fingeravtryck (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Fingeravtryck (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Giltigt datum: %1 - - - - Expiration Date: %1 - Utgångsdatum: %1 - - - - Issuer: %1 - Utfärdare: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (skippad på grund av ett tidigare fel, försök igen om %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Endast %1 tillgängligt, behöver minst %2 för att starta - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Kunde inte öppna eller återskapa den lokala synkroniseringsdatabasen. Säkerställ att du har skrivrättigheter till synkroniseringsmappen. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Diskutrymmet är lågt: Nedladdningar som reduceringar det fria utrymmet under %1 skippades. - - - - There is insufficient space available on the server for some uploads. - Det finns inte tillräckligt med utrymme på servern för vissa uppladdningar. - - - - Unresolved conflict. - Olöst konflikt. - - - - Could not update file : %1 - - - - - Could not update virtual file metadata: %1 - - - - - Using virtual files with suffix, but suffix is not set - - - - - Unable to read the blacklist from the local database - Kunde inte läsa svartlistan från den lokala databasen - - - - Unable to read from the sync journal. - Kunde inte läsa från synk-journalen. - - - - Cannot open the sync journal - Kunde inte öppna synk journalen - - - - Aborted - Avbrutits - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Checksummans huvud är felformaterad. - - - - The checksum header contained an unknown checksum type '%1' - Checksummans huvud innehåller en okänd checksumma av typ '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Vänliga logga in - - - - Folder %1: %2 - Mapp %1: %2 - - - - There are no sync folders configured. - Det finns inga synkmappar konfigurerade. - - - - Open in browser - Öppna i webbläsare - - - - - - Log in... - Logga in... - - - - - - Log out - Logga ut - - - - Recent Changes - Senaste ändringar - - - - Managed Folders: - Hanterade mappar: - - - - Open folder '%1' - Öppna mapp '%1' - - - - Open %1 in browser - Öppna %1 i webbläsaren - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Okänd status - - - - Settings... - Inställningar... - - - - Details... - Detaljer... - - - - Help - Hjälp - - - - Quit %1 - Avsluta %1 - - - - Disconnected from %1 - Koppla från %1 - - - - Unsupported Server Version - Serverversion stöds inte - - - - Disconnected - Bortkopplad - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - Bortkopplad från dessa konton: - - - - Account %1: %2 - Konto %1: %2 - - - - Signed out - Utloggad - - - - Account synchronization is disabled - Synkronisering för konto är avstängd - - - - - Synchronization is paused - - - - - Unresolved conflicts - - - - - Error during synchronization - Fel vid synkronisering - - - - No sync folders configured - Inga mappar valda för synkronisering - - - - Unpause all folders - Sluta pausa alla mappar - - - - Pause all folders - Pausa alla mappar - - - - Unpause all synchronization - Sluta pausa all synkroinisering - - - - Unpause synchronization - Sluta pausa synkronisering - - - - Pause all synchronization - Pausa all synkronisering - - - - Pause synchronization - Pausa synkronisering - - - - Log out of all accounts - Logga ut från alla konton - - - - Log in to all accounts... - Logga in på alla konton... - - - - New account... - Nytt konto... - - - - About %1 - Om %1 - - - - No items synced recently - Inga filer har synkroniseras nyligen - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - Synkroniserar %1 av %2 (%3 kvar) - - - - Syncing %1 of %2 - Synkroniserar %1 av %2 - - - - Syncing %1 (%2 left) - Synkroniserar %1 (%2 kvar) - - - - Syncing %1 - Synkroniserar %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Aktuell version - - - - OwncloudAdvancedSetupPage - - - Form - Form - - - - - - - - TextLabel - Textetikett - - - - Server - Server - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Om denna ruta är kryssad så kommer befintligt innehåll i den lokala mappen tas bort så att en ren synkronisering från servern kan startas.</p><p>Kryssa inte i denna ruta om du vill ladda upp det lokala innehållet till serverns mapp.</p></body></html> - - - - Start a &clean sync (Erases the local folder!) - Starta en %ren synkning (tar bort den lokala mappen!) - - - - Ask for confirmation before synchroni&zing folders larger than - Fråga innan synkronise&ring av mappar större än - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Fråga innan synkronisering av e&xterna lagringsytor - - - - Choose what to sync - Välj vad som ska synkas - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - &Lokal mapp - - - - pbSelectLocalFolder - pbVäljLokalMapp - - - - &Keep local data - &Behåll lokal data - - - - S&ynchronize everything from server (recommended) - - - - - Status message - Statusmeddelande - - - - OwncloudHttpCredsPage - - - Form - Form - - - - &Username - &Användarnamn - - - - &Password - &Lösenord - - - - OwncloudOAuthCredsPage - - - Form - Form - - - - Please switch to your browser to proceed. - Vänligen växla till webbläsaren för att fortsätta. - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - Återöppna webbläsaren - - - - OwncloudSetupPage - - - Form - Formulär - - - - - TextLabel - Textetikett - - - - Ser&ver Address - Ser&veradress - - - - https://... - https://... - - - - Error Label - Error Label - - - - OwncloudWizardResultPage - - - Form - Formulär - - - - TextLabel - Textetikett - - - - Your entire account is synced to the local folder - Hela ditt konto är synkroniserat mot den lokala mappen - - - - - PushButton - PushButton - - - - QObject - - - in the future - i framtiden - - - - %n day(s) ago - %n dag sedan%n dag(ar) sedan - - - - %n hour(s) ago - %n timme sedan%n timmar sedan - - - - now - nu - - - - Less than a minute ago - Mindre än en minut sedan - - - - %n minute(s) ago - %n minut sedan%n minut(er) sedan - - - - Some time ago - En stund sedan - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n år%n år - - - - %n month(s) - %n månad(er)%n månad(er) - - - - %n day(s) - %n dag(ar)%n dag(ar) - - - - %n hour(s) - %n timme/timmar%n timme/timmar - - - - %n minute(s) - %n minut(er)%n minut(er) - - - - %n second(s) - %n sekund(er)%n sekund(er) - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Systemfältet är inte tillgängligt - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 krävs på ett fungerande systemfält. Om du kör XFCE, vänligen följ <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">dessa instruktioner</a>. Annars, vänligen installera ett systemfälts-program som 'trayer' och försök igen. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3 %4%8%9Libraries Qt %5, %6%8Using virtual files plugin: %7%8 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - - - - - Replaced by virtual file - - - - - Downloaded - Nedladdats - - - - Uploaded - Uppladdad - - - - Server version downloaded, copied changed local file into conflict file - Serverversionen nedladdad, kopierade den ändrade lokala filen till konfliktfil - - - - Deleted - Raderad - - - - Moved to %1 - Flyttad till %1 - - - - Ignored - Ignorerad - - - - Filesystem access error - Åtkomstfel till filsystemet - - - - Error - Fel - - - - Updated local metadata - Uppdaterade lokal metadata - - - - - Unknown - Okänt - - - - downloading - laddar ner - - - - uploading - laddar upp - - - - deleting - raderar - - - - moving - flyttar - - - - ignoring - ignorerar - - - - - error - fel - - - - updating local metadata - uppdaterar lokal metadata - - - - theme - - - Status undefined - Odefinierad status - - - - Waiting to start sync - Väntar på att starta synkronisering - - - - Sync is running - Synkronisering är aktiv - - - - Sync Success - Lyckad synkronisering - - - - Sync Success, some files were ignored. - Synk lyckades, men vissa filer ignorerades. - - - - Sync Error - Synkfel - - - - Setup Error - Inställningsfel - - - - Preparing to sync - Förbereder synkronisering - - - - Aborting... - Avbryter - - - - Sync is paused - Synk är pausad - - - - utility - - - Could not open browser - Kunde inte öppna webbläsaren - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - Det uppstod ett fel när webbläsaren öppnades för URL %1. Kanske det inte finns någon standard webbläsare vald? - - - - Could not open email client - Kunde inte öppna e-postklient - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Det uppstod ett fel när e-postklienten skulle startas för att skapa ett nytt meddelande. Kanske är ingen standard-e-postklient konfigurerad? - - - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - - - - - Make always available locally - - - - - Free up local space - - - - \ No newline at end of file diff --git a/translations/client_th.ts b/translations/client_th.ts deleted file mode 100644 index 39395266351..00000000000 --- a/translations/client_th.ts +++ /dev/null @@ -1,4905 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - Tomahawk Crash Reporter - - - - <html><head/><body><p><br/></p></body></html> - <html><head/><body><p><br/></p></body></html> - - - - Abort - ยกเลิก - - - - Send this report - ส่งรายงานนี้ - - - - Don't send - ไม่ส่ง - - - - You can disable sending crash reports in the configuration dialog. - คุณสามารถปิดใช้งานการส่งรายงานข้อผิดพลาดในการตั้งค่า - - - - - - We cannot gather useful debug information on your system. - เราไม่สามารถรวบรวมข้อมูลการดีบักที่เป็นประโยชน์ในระบบของคุณได้ - - - - - - - - Close - ปิด - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - พร้อมส่งข้อมูลการดีบัก (<a href="%1">ดูย้อนหลัง</a>) - - - - Gathering debug information... - กำลังรวบรวมข้อมูลการดีบัก... - - - - Uploaded %L1 of %L2 KB. - อัปโหลดแล้ว %L1 จาก %L2 กิโลไบต์ - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - <b>ส่งเรียบร้อยแล้ว</b>รายงานข้อผิดพลาด: <a href="clipboard://%1"><b>%1</b></a> (คลิกเพื่อคัดลอก) - - - - Failed to send crash info. - ไม่สามารถส่งข้อมูลข้อผิดพลาด - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - ไม่สามารถเปลี่ยนชื่อ %1 ได้เนื่องจากกำลังมีการใช้งานไฟล์นี้อยู่ - - - - Could not make directories in trash - ไม่สามารถสร้างไดเรกทอรีในถังขยะ - - - - - Could not move '%1' to '%2' - ย้ายข้อมูลจาก '%1' ไป '%1' ไม่ได้ - - - - Moving to the trash is not implemented on this platform - การย้ายไปที่ถังขยะยังใช้งานไม่ได้บนแพลตฟอร์มนี้ - - - - Error removing '%1': %2 - เกิดข้อผิดพลาดขณะกำลังลบ '%1': %2 - - - - Could not remove folder '%1' - ไม่สามารถลบโฟลเดอร์ '%1' - - - - FolderWizardSourcePage - - - Form - แบบฟอร์ม - - - - Pick a local folder on your computer to sync - เลือกโฟลเดอร์ในเครื่องคอมพิวเตอร์ของคุณเพื่อประสานข้อมูล - - - - &Choose... - และเลือก... - - - - FolderWizardTargetPage - - - Form - แบบฟอร์ม - - - - Select a remote destination folder - เลือกรีโมทโฟลเดอร์ปลายทาง - - - - Create Folder - สร้างโฟลเดอร์ - - - - Refresh - รีเฟรช - - - - Folders - สร้างโฟลเดอร์ใหม่ - - - - TextLabel - ป้ายข้อความ - - - - NotificationWidget - - - Form - แบบฟอร์ม - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - ป้ายข้อความ - - - - OCC::AboutDialog - - - About - เกี่ยวกับเรา - - - - TextLabel - ป้ายข้อความ - - - - Versions - เวอร์ชัน - - - - About %1 - เกี่ยวกับ %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - หมดเวลาการเชื่อมต่อ - - - - Unknown error: network reply was deleted - ข้อผิดพลาดที่ไม่รู้จัก: การตอบกลับของเครือข่ายถูกลบแล้ว - - - - Server replied "%1 %2" to "%3 %4" - เซิร์ฟเวอร์ตอบกลับ "%1 %2" ถึง "%3 %4" - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - แบบฟอร์ม - - - - Re-open Browser - เปิดเบราเซอร์อีกครั้ง - - - - ... - ... - - - - Storage space: ... - พื้นที่จัดเก็บข้อมูล: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - โฟลเดอร์ที่ไม่ถูกตรวจสอบจะถูก <b>ลบ</b> จากไฟล์ในเครื่องของคุณและจะไม่ประสานข้อมูลกับคอมพิวเตอร์เครื่องนี้อีกต่อไป - - - - Synchronize all - ประสานข้อมูลทั้งหมด - - - - Synchronize none - ไม่ต้องประสานข้อมูล - - - - Apply manual changes - ใช้การเปลี่ยนแปลงด้วยตนเอง - - - - Apply - นำไปใช้ - - - - - - - Cancel - ยกเลิก - - - - Connected with <server> as <user> - เชื่อมต่อกับ <server> ด้วยผู้ใช้ <user> - - - - No account configured. - ไม่มีการกำหนดค่าบัญชี - - - - Remove - ลบออก - - - - Account - บัญชี - - - - Choose what to sync - เลือกข้อมูลที่ต้องการประสาน - - - - Force sync now - บังคับประสานข้อมูลเดี๋ยวนี้ - - - - Restart sync - ประสานข้อมูลอีกครั้ง - - - - Remove folder sync connection - ลบโฟลเดอร์ที่ประสานข้อมูลออก - - - - Folder creation failed - สร้างโฟลเดอร์ล้มเหลว - - - - <p>Could not create local folder <i>%1</i>. - <p>ไม่สามารถสร้างโฟลเดอร์ในเครื่อง <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - ยืนยันการลบโฟลเดอร์ที่ประสานข้อมูลออก - - - - Remove Folder Sync Connection - ลบโฟลเดอร์ที่ประสานข้อมูล - - - - Disable virtual file support? - ปิดใช้งานการสนับสนุนไฟล์เสมือน - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - การกระทำนี้จะปิดใช้งานการสนับสนุนไฟล์เสมือน เนื่องจากเนื้อหาของโฟลเดอร์มีการทำเครื่องหมายว่า 'พร้อมใช้งานขณะออนไลน์เท่านั้น' จะถูกดาวน์โหลด - -ข้อดีของการปิดใช้งานการสนับสนุนไฟล์เสมือนคือคุณสมบัติการประสานข้อมูลแบบเลือกจะสามารถกลับมาใช้ได้อีก - -การดำเนินการนี้จะยกเลิกการประสานข้อมูลที่กำลังทำงานอยู่ - - - - Disable support - ปิดใช้งานการสนับสนุน - - - - Sync Running - กำลังประสานข้อมูล - - - - The syncing operation is running.<br/>Do you want to terminate it? - กำลังดำเนินการประสานข้อมูลอยู่ <br/>คุณต้องการสิ้นสุดการทำงาน? - - - - %1 in use - %1 กำลังถูกใช้งาน - - - - %1 as <i>%2</i> - %1 เช่น <i>%2</i> - - - - Connected to %1. - เชื่อมต่อกับ %1 - - - - Server %1 is temporarily unavailable. - เซิร์ฟเวอร์ %1 ไม่สามารถใช้ได้ชั่วคราว - - - - Server %1 is currently in maintenance mode. - เซิร์ฟเวอร์ %1 กำลังอยู่ในโหมดการบำรุงรักษา - - - - Signed out from %1. - ลงชื่อออกจาก %1 - - - - Connecting to %1... - กำลังเชื่อมต่อไปยัง %1... - - - - No connection to %1 at %2. - ไม่มีการเชื่อมต่อไปยัง %1 ที่ %2 - - - - Log in - เข้าสู่ระบบ - - - - There are folders that were not synchronized because they are too big: - บางโฟลเดอร์จะไม่ถูกประสานข้อมูลเพราะขนาดของมันใหญ่เกินไป: - - - - There are folders that were not synchronized because they are external storages: - มีบางโฟลเดอร์จะไม่ถูกประสานข้อมูลเพราะเป็นพื้นที่จัดเก็บข้อมูลภายนอก - - - - There are folders that were not synchronized because they are too big or external storages: - มีบางโฟลเดอร์จะไม่ถูกประสานข้อมูลเพราะเป็นพื้นที่จัดเก็บข้อมูลภายนอกหรือมีขนาดที่ใหญ่เกินไป - - - - Confirm Account Removal - ยืนยันการลบบัญชี - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>คุณต้องการลบการเชื่อมต่อกับบัญชี<i>%1</i>?</p><p><b>หมายเหตุ:</b> นี้จะ <b>ไม่</b> ลบไฟล์ใดๆ</p> - - - - Remove connection - ลบการเชื่อมต่อ - - - - Open folder - เปิดโฟลเดอร์ - - - - - Log out - ออกจากระบบ - - - - Open local folder - เปิดโฟลเดอร์ในเครื่อง - - - - Open folder in browser - เปิดโฟลเดอร์ในเบราว์เซอร์ - - - - Resume sync - ประสานข้อมูลอีกครั้ง - - - - Pause sync - หยุดประสานข้อมูลชั่วคราว - - - - Availability - ความพร้อมใช้งาน - - - - Disable virtual file support... - ปิดใช้งานการสนับสนุนไฟล์เสมือน... - - - - Enable virtual file support%1... - เปิดใช้งานการสนับสนุนไฟล์เสมือน %1... - - - - (experimental) - (โหมดเบต้า) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>คุณต้องการหยุดการประสานข้อมูลโฟลเดอร์<i>%1</i>?</p><p><b>หมายเหตุ:</b> นี้จะ <b>ไม่</b> ลบไฟล์ใดๆ</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - มีการใช้งาน %1 (%3%) จาก %2 บางโฟลเดอร์รวมทั้งเครือข่ายที่ติดตั้งหรือโฟลเดอร์ที่แชร์อาจมีข้อจำกัดที่แตกต่างกัน - - - - %1 of %2 in use - มีการใช้งาน %1 จาก %2 - - - - Currently there is no storage usage information available. - ขณะนี้ไม่มีพื้นที่จัดเก็บข้อมูลที่ใช้งานได้ - - - - The server version %1 is unsupported! Proceed at your own risk. - เซิร์ฟไม่สนับสนุนเวอร์เวอร์ชัน %1 นี่เป็นความเสี่ยงของคุณเอง - - - - Obtaining authorization from the browser. - กำลังขออนุมัติจากเบราว์เซอร์ - - - - Server configuration error: %1 at %2. - ข้อผิดพลาดการกำหนดค่าเซิร์ฟเวอร์: %1 ที่ %2 - - - - No %1 connection configured. - ไม่มีการเชื่อมต่อ %1 ที่ถูกกำหนดค่า - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - - - - - Signed out - ออกจากระบบ - - - - Disconnected - ยกเลิกการเชื่อมต่อ - - - - Connected - เชื่อมต่อ - - - - Service unavailable - ไม่สามารถให้บริการได้ - - - - Maintenance mode - โหมดบำรุงรักษา - - - - Network error - ข้อผิดพลาดของเครือข่าย - - - - Configuration error - กำหนดค่าผิดพลาด - - - - Asking Credentials - กำลังขอข้อมูลการรับรอง - - - - Unknown account state - ไม่ระบุสถานะบัญชี - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 บน %3 - - - - Activity - กิจกรรม - - - - Account - บัญชี - - - - Time - เวลา - - - - Local path - - - - - OCC::ActivitySettings - - - - Server Activity - กิจกรรมของเซิร์ฟเวอร์ - - - - Sync Protocol - โปรโตคอลที่ใช้ในการผสานข้อมูล - - - - Not Synced - ไม่ถูกประสานข้อมูลให้ตรงกัน - - - - Not Synced (%1) - %1 is the number of not synced files. - ไม่ถูกประสานข้อมูล (%1) - - - - OCC::ActivityWidget - - - Form - แบบฟอร์ม - - - - - - TextLabel - ป้ายข้อความ - - - - Filter - ตัวกรอง - - - - Server Activities - กิจกรรมของเซิร์ฟเวอร์ - - - - Notifications - การแจ้งเตือน - - - - %n notifications(s) for %1. - มี %n การแจ้งเตือนสำหรับ %1 - - - - %n notifications(s) for %1 and %2. - มี %n การแจ้งเตือนสำหรับ %1 และ %2 - - - - New notifications for %1, %2 and other accounts. - มีการแจ้งเตือนใหม่สำหรับ %1, %2 และบัญชีอื่น ๆ - - - - Open the activity view for details. - เปิดมุมมองกิจกรรมเพื่อดูรายละเอียด - - - - Copy to clipboard - คัดลอกลิงก์ไปยังคลิปบอร์ด - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br />บัญชี %1 ไม่มีกิจกรรมที่เปิดใช้งาน - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - กำลังทำต่อหมายถึง <b>กำลังลบการตั้งค่าเหล่านี้</b> - - - - Continuing will mean <b>ignoring these settings</b>. - กำลังทำต่อหมายถึง <b>กำลังละเว้นการตั้งค่าเหล่านี้</b> - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - การตั้งค่าบางอย่างได้ถูกกำหนดค่าในเวอร์ชันที่ใหม่กว่าของไคลเอ็นต์นี้และเปิดใช้คุณลักษณะที่ไม่สามารถใช้ได้ในเวอร์ชันนี้ <br><br>%1<br><br>ไฟล์การตั้งค่าปัจจุบันได้ถูกสำรองข้อมูลไปที่ <i>%2</i> แล้ว - - - - Quit - ออก - - - - Continue - ดำเนินการต่อ - - - - Error accessing the configuration file - เกิดข้อผิดพลาดขณะกำลังเข้าถึงไฟล์กำหนดค่า - - - - There was an error while accessing the configuration file at %1. - เกิดข้อผิดพลาดขณะกำลังเข้าถึงไฟล์กำหนดค่า %1 - - - - Quit %1 - ออก %1 - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - ต้องใช้การตรวจสอบสิทธิ์ - - - - Enter username and password for '%1' at %2. - ป้อนชื่อผู้ใช้และรหัสผ่านสำหรับ '%1' ที่ %2 - - - - &User: - และผู้ใช้งาน: - - - - &Password: - และรหัสผ่าน: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - ไม่มีการกำหนดค่าบัญชี ownCloud - - - - The configured server for this client is too old - การกำหนดค่าเซิร์ฟเวอร์สำหรับไคลเอ็นต์นี้เก่าเกินไป - - - - Please update to the latest server and restart the client. - กรุณาอัปเดตเซิร์ฟเวอร์ใหม่ใหม่ที่สุดและรีสตาร์ทไคลเอนต์ - - - - Authentication error: Either username or password are wrong. - ข้อผิดพลาดในการตรวจสอบ: ทั้งชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง - - - - timeout - หมดเวลา - - - - The provided credentials are not correct - ระบุหนังสือรับรองไม่ถูกต้อง - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - ข้อผิดพลาดเซิร์ฟเวอร์: การตอบกลับ PROPFIND ไม่ใช่รูปแบบ XML! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - เกิดข้อผิดพลาดขณะเปิดไดเรกทอรี %1 - - - - Directory not accessible on client, permission denied - การเข้าถึงถูกปฏิเสธไม่สามารถเข้าถึงไดเรกทอรีบนไคลเอนต์ - - - - Directory not found: %1 - ไม่พบไดเรกทอรี: %1 - - - - Filename encoding is not valid - การเข้ารหัสชื่อไฟล์ไม่ถูกต้อง - - - - Error while reading directory %1 - เกิดข้อผิดพลาดขณะอ่านไดเรกทอรี %1 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - - - - - OCC::Folder - - - Local folder %1 does not exist. - โฟลเดอร์ %1 ไม่มีอยู่ในเครื่อง - - - - %1 should be a folder but is not. - %1 ควรจะเป็นโฟลเดอร์ แต่ทำไม่ได้ - - - - %1 is not readable. - ไม่สามารถอ่านข้อมูล %1 ได้ - - - - %1 has been removed. - %1 names a file. - %1 ได้ถูกลบออก - - - - %1 has been updated. - %1 names a file. - %1 ได้ถูกอัปเดตเรียบร้อยแล้ว - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 ได้ถูกเปลี่ยนชื่อเป็น %2 - - - - %1 has been moved to %2. - %1 ได้ถูกย้ายไปยัง %2 - - - - %1 and %n other file(s) have been removed. - %1 และ %n ไฟล์อื่น ๆ ได้ถูกลบออก - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - %1 และ %n - - - - %1 has been added. - %1 names a file. - %1 ได้ถูกเพิ่มแล้ว - - - - %1 and %n other file(s) have been updated. - %1 และ %n ไฟล์อื่น ๆ ได้รับการอัปเดต - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 และไฟล์อื่น ๆ อีก %n ไฟล์ได้ถูกเปลี่ยนชื่อเป็น %2 - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 และไฟล์อื่น ๆ อีก %n ไฟล์ได้ถูกย้ายไปยัง %2 - - - - %1 has and %n other file(s) have sync conflicts. - %1 และ %n ไฟล์อื่น ๆ เกิดปัญหาขณะประสานข้อมูล - - - - %1 has a sync conflict. Please check the conflict file! - %1 มีปัญหาขณะประสานข้อมูล กรุณาตรวจสอบไฟล์ที่มีปัญหานั้น - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 และไฟล์อื่น ๆ อีก %n ไฟล์ไม่สามารถประสานข้อมูลเนื่องจากเกิดข้อผิดพลาด กรุณาดูไฟล์ log สำหรับรายละเอียดเพิ่มเติม - - - - %1 could not be synced due to an error. See the log for details. - %1 ไม่สามารถประสานข้อมูลเนื่องจากมีข้อผิดพลาด สามารถดูไฟล์ log สำหรับรายละเอียดเพิ่มเติม - - - - Sync Activity - ความเคลื่อนไหวของการประสานข้อมูล - - - - Could not read system exclude file - ไม่สามารถอ่าน ยกเว้นไฟล์ระบบ - - - - A new folder larger than %1 MB has been added: %2. - - โฟลเดอร์ใหม่มีขนาดใหญ่กว่า %1 เมกะไบต์ ได้ถูกเพิ่ม: %2 - - - - - A folder from an external storage has been added. - - โฟลเดอร์ที่มีพื้นที่จัดเก็บข้อมูลภายนอกได้ถูกเพิ่ม - - - - - Please go in the settings to select it if you wish to download it. - กรุณาไปในส่วนของการตั้งค่าเพื่อเลือก ถ้าคุณต้องการจะดาวน์โหลด - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - โฟลเดอร์ %1 ได้ถูกสร้างขึ้นแล้วแต่ยังไม่ได้ประสานข้อมูล - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - ไฟล์ %1 ได้ถูกสร้างขึ้นแล้วแต่ยังไม่ได้ประสานข้อมูล - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - ไม่สามารถติดตามการเปลี่ยนแปลงโฟลเดอร์ที่ประสานข้อมูลได้ - -หมายความว่าการประสานข้อมูลของไคลเอ็นต์อาจยังไม่ได้อัปโหลดการเปลี่ยนแปลงในระบบทันทีและจะสแกนเฉพาะการเปลี่ยนแปลงในเครื่องและอัปโหลดไฟล์เหล่านั้นเป็นครั้งคราว (ทุกสองชั่วโมงตามค่าเริ่มต้น) - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - ไฟล์ทั้งหมดในโฟลเดอร์ '%1' ที่ประสานข้อมูลเอาไว้ในเซิฟเวอร์จะถูกลบด้วย -การลบข้อมูลนี้จะถูกประสานข้อมูลกับโฟลเดอร์ในเครื่อง ทำให้ไฟล์ดังกล่าวไม่สามารถใช้งานได้จนกว่าจะถูกกู้คืน -ถ้าคุณตัดสินใจเก็บไฟล์เอาไว้ ไฟล์ของคุณก็จะถูกประสานข้อมูลใหม่อีกครั้ง -หากคุณตัดสินใจลบไฟล์ก็จะทำให้ไม่มีใครสามารถใช้งานโฟลเดอร์นี้ได้เพราะคุณเป็นเจ้าของ - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - ไฟล์ทั้งหมดในโฟลเดอร์ '%1' ที่ประสานข้อมูลเอาไว้ในเซิร์ฟเวอร์จะถูกลบด้วย -การลบข้อมูลนี้จะถูกประสานข้อมูลกับเซิฟเวอร์ ทำให้ไฟล์ดังกล่าวไม่สามารถใช้งานได้จนกว่าจะถูกกู้คืน -คุณแน่ใจหรือว่าต้องการประสานข้อมูลการกระทำนี้กับเซิร์ฟเวอร์? -ถ้าเรื่องนี้เป็นอุบัติเหตุและคุณตัดสินใจเก็บไฟล์ของคุณ ไฟล์ของคุณก็จะถูกประสานข้อมูลใหม่อีกครั้ง - - - - Remove All Files? - ลบไฟล์ทั้งหมด? - - - - Remove all files - ลบไฟล์ทั้งหมด - - - - Keep files - เก็บไฟล์เอาไว้ - - - - OCC::FolderMan - - - Could not reset folder state - ไม่สามารถรีเซ็ตสถานะโฟลเดอร์ - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - บนบันทึกการประสานข้อมูลเก่า '%1' แต่ไม่สามารถลบออกได้ กรุณาตรวจสอบให้แน่ใจว่าไม่มีแอปฯ หรือการทำงานใดๆที่ใช้มันอยู่ - - - - (backup) - (สำรองข้อมูล) - - - - (backup %1) - (สำรองข้อมูล %1) - - - - Undefined State. - สถานะที่ยังไม่ได้ถูกกำหนด - - - - Waiting to start syncing. - กำลังรอเริ่มต้นการประสานข้อมูล - - - - Preparing for sync. - กำลังเตรียมการประสานข้อมูล - - - - Sync is running. - การประสานข้อมูลกำลังทำงาน - - - - Sync was successful, unresolved conflicts. - ประสานข้อมูลสำเร็จแต่ยังมีข้อขัดแย้งที่ยังไม่ได้รับการแก้ไข - - - - Last Sync was successful. - ประสานข้อมูลครั้งล่าสุดเสร็จเรียบร้อยแล้ว - - - - Setup Error. - เกิดข้อผิดพลาดในการติดตั้ง - - - - User Abort. - ยกเลิกผู้ใช้ - - - - Sync is paused. - การประสานข้อมูลถูกหยุดไว้ชั่วคราว - - - - %1 (Sync is paused) - %1 (การประสานข้อมูลถูกหยุดชั่วคราว) - - - - No valid folder selected! - เลือกโฟลเดอร์ไม่ถูกต้อง! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - เส้นทางที่เลือกไม่มีอยู่! - - - - The selected path is not a folder! - เส้นทางที่เลือกไม่ใช่โฟลเดอร์! - - - - You have no permission to write to the selected folder! - คุณมีสิทธิ์เขียนโฟลเดอร์ที่เลือกนี้! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - โฟลเดอร์นี้ได้ถูกประสานข้อมูลกับเซิร์ฟเวอร์แล้ว โปรดเลือกโฟลเดอร์อื่น ๆ - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - เนื้อหาโฟลเดอร์ในเครื่อง %1 ได้ถูกใช้ไปแล้วในโฟลเดอร์ที่ประสานข้อมูล กรุณาเลือกอีกอันหนึ่ง! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - เนื้อหาของโฟลเดอร์ในเครื่อง %1 ไดถูกใช้ไปแล้วในโฟลเดอร์ที่ประสานข้อมูล กรุณาเลือกอีกอันหนึ่ง! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - เพิ่มโฟลเดอร์ที่ต้องการประสานข้อมูล - - - - File - ไฟล์ - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - คุณจะต้องเชื่อมต่อก่อนที่จะเพิ่มโฟลเดอร์ - - - - Click this button to add a folder to synchronize. - คลิกที่ปุ่มนี้เพื่อเพิ่มโฟลเดอร์ที่ต้องการประสานข้อมูล - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - ข้อผิดพลาดในขณะที่โหลดรายชื่อโฟลเดอร์จากเซิร์ฟเวอร์ - - - - Virtual file support is enabled. - เปิดใช้งานการรองรับไฟล์เสมือนแล้ว - - - - Signed out - ออกจากระบบ - - - - Fetching folder list from server... - กำลังดึงรายการโฟลเดอร์จากเซิร์ฟเวอร์ ... - - - - There are unresolved conflicts. Click for details. - มีข้อขัดแย้งที่ยังไม่ได้รับการแก้ไข คลิกเพื่อดูรายละเอียด - - - - Checking for changes in remote '%1' - กำลังตรวจหาการเปลี่ยนแปลงในรีโมต '%1' - - - - Checking for changes in local '%1' - กำลังตรวจหาการเปลี่ยนแปลงในเครื่อง '%1' - - - - Reconciling changes - คืนค่าการเปลี่ยนแปลง - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - กำลังประสานข้อมูล %1 - - - - - , - หรือ - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - ดาวน์โหลด %1/วินาที - - - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/วินาที - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - อัปโหลด %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 ของ %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - เหลืออีก %5 ไฟล์, %1 ไฟล์จาก %2, %3 ไฟล์จาก %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 จาก %2, %3 จาก %4 ไฟล์ - - - - file %1 of %2 - ไฟล์ %1 จาก %2 - - - - Waiting... - กรุณารอซักครู่... - - - - Waiting for %n other folder(s)... - กำลังรออีก (%n) โฟลเดอร์... - - - - Preparing to sync... - กำลังเตรียมพร้อมในการประสานข้อมูล - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - ผู้มาเยือนไม่ได้รับการทดสอบแจ้งเตือน - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - ปัญหานี้มักเกิดขึ้นเมื่อใช้ inotify watches ครบจำนวน ตรวจสอบรายละเอียดเพิ่มเติมได้ที่ คำถามที่พบบ่อย - - - - OCC::FolderWizard - - - Add Folder Sync Connection - เพิ่มโฟลเดอร์ที่ต้องการประสานข้อมูล - - - - Add Sync Connection - เพิ่มการประสานข้อมูลให้ตรงกัน - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - คลิกเพื่อเลือกโฟลเดอร์ในเครื่องสำหรับประสานข้อมูล - - - - Enter the path to the local folder. - ป้อนพาธไปยังโฟลเดอร์ในเครื่อง - - - - Select the source folder - เลือกโฟลเดอร์ต้นฉบับ - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - สร้างโฟลเดอร์รีโมท - - - - Enter the name of the new folder to be created below '%1': - กรอกชื่อของโฟลเดอร์ใหม่ที่จะถูกสร้างขึ้นดังต่อไปนี้ '%1': - - - - Folder was successfully created on %1. - โฟลเดอร์ถูกสร้างขึ้นเรียบร้อยแล้วเมื่อ %1... - - - - Authentication failed accessing %1 - รับรองความถูกต้องล้มเหลวขณะกำลังเข้าถึง %1 - - - - Failed to create the folder on %1. Please check manually. - ไม่สามารถสร้างโฟลเดอร์บน %1 กรุณาตรวจสอบด้วยตนเอง - - - - Failed to list a folder. Error: %1 - รายการโฟลเดอร์ล้มเหลว ข้อผิดพลาด: %1 - - - - Choose this to sync the entire account - เลือกตัวเลือกนี้เพื่อประสานข้อมูลบัญชีทั้งหมด - - - - This folder is already being synced. - โฟลเดอร์นี้ถูกประสานข้อมูลอยู่แล้ว - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - คุณประสานข้อมูล <i>%1</i> อยู่แล้ว ซึ่งมีโฟลเดอร์หลักเป็น <i>%2</i> - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - คุณได้ประสานข้อมูล <i>%1</i>ที่อยู่ภายในโฟลเดอร์ย่อยของ <i>%2</i> แล้ว - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - ใช้ไฟล์เสมือนแทนการดาวน์โหลดเนื้อหาทันที %1 - - - - (experimental) - (โหมดเบต้า) - - - - Virtual files are not available for the selected folder - ไฟล์เสมือนไม่พร้อมใช้งานสำหรับโฟลเดอร์ที่เลือก - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>คําเตือน:</b> %1 - - - - <b>Warning:</b> - <b>คําเตือน:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - ไม่มี E-Tag ที่ได้รับจากเซิร์ฟเวอร์ กรุณาตรวจสอบ พร็อกซี่หรือเกตเวย์ - - - - We received a different E-Tag for resuming. Retrying next time. - เราได้รับ E-Tag ที่แตกต่างกันสำหรับการทำงาน กรุณาลองอีกครั้งในเวลาถัดไป - - - - We received an unexpected download Content-Length. - เราได้รับการดาวน์โหลดความยาวของเนื้อหาโดยไม่คาดคิด - - - - Server returned wrong content-range - เซิร์ฟเวอร์ส่งคืนช่วงของเนื้อหาที่ผิด - - - - OCC::GETJob - - - Connection Timeout - หมดเวลาการเชื่อมต่อ - - - - OCC::GeneralSettings - - - Form - แบบฟอร์ม - - - - General Settings - การตั้งค่าทั่วไป - - - - Language - ภาษา - - - - Sync hidden files - ประสานข้อมูลไฟล์ที่ถูกซ่อนอยู่ - - - - Log Settings - การตั้งค่าบันทึก - - - - Advanced - Approval of folder sync (Non virtual file mode only) - ขั้นสูง - การอนุมัติการซิงค์โฟลเดอร์ (โหมดอื่น ๆ สามารถใช้ได้) - - - - Network - เครือข่าย - - - - Updates - อัปเดต - - - - stable - เวอร์ชันเสถียร - - - - beta - เวอร์ชันเบต้า - - - - About - เกี่ยวกับเรา - - - - Advanced - ขั้นสูง - - - - Use &Monochrome Icons in the system tray - ใช้ไอคอนขาวดำในถาดระบบ - - - - Ask for confirmation before synchronizing folders larger than - ถามก่อนที่จะประสานข้อมูลกับโฟลเดอร์ที่มีขนาดใหญ่กว่า - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - เมกะไบต์ - - - - Ask for confirmation before synchronizing external storages - ถามก่อนที่จะประสานข้อมูลกับพื้นที่จัดเก็บข้อมูลภายนอก - - - - &Launch on System Startup - และเปิดใช้งานเมื่อเริ่มต้นระบบ - - - - Show &Desktop Notifications - แสดงและแจ้งเตือนบนเดสก์ทอป - - - - &Update Channel - และอัปเดตแชนแนล - - - - Edit &Ignored Files - แก้ไขและละเว้นการแก้ไขไฟล์ - - - - Show sync folders in &Explorer's Navigation Pane - แสดงการประสานข้อมูลโฟลเดอร์ และหน้าต่างการนำทางของ Explorer - - - - S&how crash reporter - แสดงรายงานความผิดพลาด - - - - &Restart && Update - และเริ่มต้นใหม่ && อัปเดต - - - - You cannot disable autostart because system-wide autostart is enabled. - คุณไม่สามารถปิดใช้งานระบบเริ่มต้นอัตโนมัติได้เนื่องจาก system-wide ได้ถูกเปิดใช้งานแล้ว - - - - Change update channel? - เปลี่ยนช่องทางการอัปเดต? - - - - Warning - คำเตือน - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - กำหนดช่องทางอัปเดตการติดตั้งใดสำหรับไคลเอ็นต์ "เวอร์ชันเสถียร" จะอัปเกรดที่เชื่อถือได้จาก "เวอร์ชันเบต้า" อาจมีคุณลักษณะใหม่และการแก้ไขปัญหาแต่ยังไม่ได้รับการทดสอบอย่างละเอียด - -โปรดทราบว่าการเลือกนี้จะไม่สามารถดาวน์เกรดจากเวอร์ชันเบต้าไปยังเวอร์ชันเสถียรได้ ต้องรอให้เวอร์ชันเสถียรใหม่กว่าเวอร์ชันเบต้าก่อนถึงจะอัปเกรดเป็นเวอร์ชันเสถียรได้ - - - - Change update channel - ตกลง - - - - Cancel - ยกเลิก - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - กรุณากรอกรหัสผ่าน %1:<br><br>ผู้ใช้: %2<br>บัญชี: %3<br> - - - - Reading from keychain failed with error: '%1' - อ่านจาก Keychain ล้มเหลวด้วยข้อผิดพลาด: '%1' - - - - Enter Password - ป้อนรหัสผ่าน - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">คลิกที่นี่</a> เพื่อขอรหัสผ่านแอปฯ จากเว็บอินเตอร์เฟส - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - ละเว้นการแก้ไขไฟล์ - - - - Files Ignored by Patterns - ข้ามไฟล์โดยรูปแบบ - - - - Add - เพิ่ม - - - - Pattern - รูปแบบ - - - - Allow Deletion - อนุญาตให้ลบ - - - - Remove - ลบออก - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - ไฟล์หรือโฟลเดอร์ที่ตรงกับรูปแบบจะไม่ถูกประสานข้อมูล การเปลี่ยนแปลงจะมีผลเมื่อมีการประสานข้อมูลโฟลเดอร์ในครั้งถัดไป - -รายการที่อนุญาตให้ลบจะถูกลบถึงแม้ว่าจะมีการป้องกันการลบไดเรกทอรีออก สิ่งนี้มีประโยชน์มากสำหรับข้อมูลเมตา - - - - Could not open file - ไม่สามารถเปิดไฟล์ - - - - Cannot write changes to '%1'. - ไม่สามารถเขียนเปลี่ยนเป็น '%1' - - - - Add Ignore Pattern - เพิ่มการละเว้นรูปแบบ - - - - Add a new ignore pattern: - เพิ่มการละเว้นรูปแบบใหม่: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - รายการนี้ถูกระบุโดยระบบที่ '%1' และไม่สามารถแก้ไขได้ในมุมมองนี้ - - - - OCC::IssuesWidget - - - Form - แบบฟอร์ม - - - - List of issues - รายการปัญหา - - - - Filter - ตัวกรอง - - - - There were too many issues. Not all will be visible here. - มีปัญหามากเกินไป ทั้งหมดจะไม่ปรากฏที่นี่ - - - - There were conflicts. Check the documentation on how to resolve them. - มีข้อขัดแย้งเกิดขึ้น โปรดตรวจสอบเอกสารเกี่ยวกับวิธีแก้ปัญหา - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - มีข้อขัดแย้งเกิดขึ้น <a href="%1">ตรวจสอบเอกสารเกี่ยวกับวิธีแก้ปัญหา</a> - - - - Account - บัญชี - - - - OCC::LibCloudProvidersPrivate - - - Settings - ตั้งค่า - - - - OCC::LogBrowser - - - Log Output - ผลลัพธ์ของไฟล์ log - - - - Enable logging to temporary folder - เปิดใช้งานการเก็บบันทึกไปยังโฟลเดอร์ชั่วคราว - - - - Delete logs older than %1 hours - ลบ log ที่เก่ากว่า %1 ชั่วโมง - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">บันทึกมีข้อมูลที่ละเอียดอ่อนซึ่งคุณไม่ควรเปิดเผยต่อสาธารณะ</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - ไคลเอนต์สามารถเขียนบันทึกการแก้ไขข้อบกพร่องไปยังโฟลเดอร์ชั่วคราว บันทึกเหล่านี้มีประโยชน์มากสำหรับการวินิจฉัยปัญหา -เนื่องจากไฟล์บันทึกมีขนาดใหญ่ไคลเอ็นต์จะเริ่มต้นไฟล์ใหม่สำหรับการซิงค์แต่ละครั้งและบีบอัดไฟล์เก่า ขอแนะนำให้เปิดใช้งานการลบไฟล์บันทึกหลังจากผ่านไปสองสามชั่วโมงเพื่อหลีกเลี่ยงการใช้พื้นที่ดิสก์มากเกินไป - - - - If enabled, logs will be written to: - หากเปิดใช้งานบันทึกจะถูกเขียนไปยัง: - - - - C:/log - C:/log - - - - Log Http traffic - บันทึกการรับส่งข้อมูล HTTP - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - การตั้งค่าเหล่านี้ยังคงมีอยู่ระหว่างการรีสตาร์ทไคลเอ็นต์ -โปรดทราบว่าการใช้ตัวเลือกการเก็บบันทึกคำสั่งจะแทนที่การตั้งค่า - - - - Open folder - เปิดโฟลเดอร์ - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - มีเวอร์ชันใหม่ที่สามารถใช้งานได้ - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>%1 มีเวอร์ชันใหม่ของไคลเอ็นต์แล้ว</p><p><b>%2</b> สามารถดาวน์โหลดได้ เวอร์ชันที่ติดตั้งคือ %3 - - - - - Skip this version - ข้ามเวอร์ชันนี้ - - - - Skip this time - ข้ามเวลานี้ - - - - Get update - ได้รับการอัปเดต - - - - Update Failed - อัพเดทล้มเหลว - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>มีไคลเอ็นต์เวอร์ชันใหม่พร้อมใช้งาน %1 แต่กระบวนการอัปเดตล้มเหลว</p><p><b>%2</b>ได้ถูกดาวน์โหลดแล้ว เวอร์ชันที่ติดตั้งคือ %3</p> - - - - Ask again later - ถามอีกครั้งในภายหลัง - - - - Restart and update - รีสตาร์ทและอัปเดต - - - - Update manually - อัปเดตด้วยตนเอง - - - - OCC::NetworkSettings - - - Form - แบบฟอร์ม - - - - Proxy Settings - ตั้งค่าพร็อกซี่ - - - - No Proxy - ไม่มีพร็อกซี่ - - - - Use system proxy - ใช้พร็อกซี่จากระบบ - - - - Specify proxy manually as - ระบุพร็อกซีด้วยตนเอง - - - - Host - โฮสต์ - - - - : - : - - - - Proxy server requires authentication - ต้องตรวจสอบพร็อกซีเซิร์ฟเวอร์ - - - - Note: proxy settings have no effects for accounts on localhost - หมายเหตุ: การตั้งค่าพร็อกซีไม่มีผลกับบัญชีใน localhost - - - - Download Bandwidth - ดาวน์โหลดแบนด์วิดธ์ - - - - - Limit to - จำกัดถึง - - - - - KBytes/s - กิโลไบต์/วินาที - - - - - No limit - ไม่จำกัด - - - - - Limit to 3/4 of estimated bandwidth - จำกัดแบนด์วิดธ์ถึง 3/4 โดยประมาณ - - - - Upload Bandwidth - แบนด์วิดธ์ของการอัปโหลด - - - - - Limit automatically - จำกัดโดยอัตโนมัติ - - - - Hostname of proxy server - ชื่อโฮสต์ของเซิร์ฟเวอร์พร็อกซี่ - - - - Username for proxy server - ชื่อผู้ใช้ของเซิร์ฟเวอร์พร็อกซี่ - - - - Password for proxy server - รหัสผ่านของเซิร์ฟเวอร์พร็อกซี่ - - - - HTTP(S) proxy - พร็อกซี HTTP(S) - - - - SOCKS5 proxy - พร็อกซี SOCKS5 - - - - OCC::NotificationWidget - - - Created at %1 - ถูกสร้างแล้วที่ %1 - - - - Closing in a few seconds... - กำลังจะปิดในอีกไม่กี่วินาที ... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 ร้องขอล้มเหลวที่ %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' ได้เลือกแล้วที่ %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - เกิดข้อผิดพลาดจากเซิร์ฟเวอร์: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - เกิดข้อผิดพลาดในการเข้าถึง 'โทเค็น' ปลายทาง: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - ไม่สามารถแยกวิเคราะห์ JSON ที่ส่งมาจากเซิร์ฟเวอร์: <br><em>%1</em> - - - - Unsupported token type: %1 - ประเภทโทเค็นที่ไม่รองรับ: %1 - - - - The reply from the server did not contain all expected fields -:%1 - ข้อมูลที่ตอบกลับจากเซิร์ฟเวอร์ไม่พบฟิลด์ที่ต้องการ: %1 - - - - Unknown Error - ข้อผิดพลาดที่ไม่รู้จัก - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>ข้อผิดพลาดในการเข้าสู่ระบบ</h1><p>เรียกข้อมูลผู้ใช้ไม่สำเร็จ</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>เข้าสู่ระบบล้มเหลว</h1>%1<p> - - - - - - Error while refreshing the token: %1 : %2 - เกิดข้อผิดพลาดขณะรีเฟรชโทเค็น: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - ข้อมูลที่ตอบกลับจากเซิร์ฟเวอร์ไม่พบฟิลด์ที่ต้องการ: %1 -ข้อมูลที่ได้รับแล้ว: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>บัญผู้ใช้ไม่ถูกต้อง</h1> <p>คุณลงชื่อเข้าใช้ด้วยบัญชีผู้ใช้ <em>%1</em> คุณจะต้องลงชื่อเข้าใช้ด้วยบัญชี <em>%2</em> <br>โปรดออกจากระบบ %3 ในแท็บอื่นแล้ว <a href='%4'>คลิกที่นี่</a> เพื่อเข้าสู่ระบบในฐานะผู้ใช้ </p> - - - - OCC::OCUpdater - - - New %1 Update Ready - มี %1 พร้อมอัปเดตใหม่ - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - มีการอัปเดตใหม่สำหรับ %1 มันเกี่ยวกับการติดตั้ง การอัปเดตอาจจะถามสิทธิประโยชน์เพิ่มเติมในระหว่างกระบวนการ - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - ไม่สามารถดาวน์โหลดการอัปเดต กรุณาคลิก<a href='%1'>ที่นี่</a> เพื่อดาวน์โหลดการอัปเดตด้วยตนเอง - - - - Could not check for new updates. - ไม่สามารถตรวจสอบการอัปเดตใหม่ - - - - Downloading %1. Please wait... - กำลังดาวน์โหลด %1 โปรดรอ... - - - - %1 available. Restart application to start the update. - %1 พร้อมใช้งานแล้ว รีสตาร์ทแอปพลิเคชันเพื่อเริ่มการอัปเดต - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - ตรวจพบ %1 อัปเดต โปรดใช้เครื่องมืออัปเดตระบบเพื่อติดตั้ง - - - - Checking update server... - กำลังตรวจสอบการอัปเดตเซิร์ฟเวอร์... - - - - Update status is unknown: Did not check for new updates. - สถานะการอัปเดตที่ไม่รู้จัก: จะไม่มีการตรวจสอบการอัปเดตใหม่ - - - - No updates available. Your installation is at the latest version. - ไม่พบการอัปเดต ตัวที่ติดตั้งเป็นเวอร์ชั่นล่าสุด - - - - Update Check - ตรวจสอบการอัปเดต - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - เชื่อมต่อไปยัง %1 - - - - Setup local folder options - ตั้งค่าตัวเลือกโฟลเดอร์ในเครื่อง - - - - Connect... - เชื่อมต่อ... - - - - (experimental) - (โหมดเบต้า) - - - - Use &virtual files instead of downloading content immediately%1 - ใช้ไฟล์เสมือนแทนการดาวน์โหลดเนื้อหาแบบทันที %1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - ประสานข้อมูลโฟลเดอร์ '%1' - - - - Virtual files are not available for the selected folder - ไฟล์เสมือนไม่พร้อมใช้งานสำหรับโฟลเดอร์ที่เลือก - - - - Error - ข้อผิดพลาด - - - - Local Sync Folder - ประสานโฟลเดอร์ในเครื่อง - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - และอีเมล - - - - Connect to %1 - เชื่อมต่อไปยัง %1 - - - - Enter user credentials - ป้อนหนังสือรับรองของผู้ใช้ - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - เชื่อมต่อไปยัง %1 - - - - Login in your browser - เข้าสู่ระบบในเบราเซอร์ของคุณ - - - - Copy link to clipboard - คัดลอกลิงก์ไปยังคลิปบอร์ด - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - ไฟล์ %1 กำลังถูกใช้งานอยู่ - - - - OCC::OwncloudSetupPage - - - Connect to %1 - เชื่อมต่อไปยัง %1 - - - - Setup %1 server - ติดตั้งเซิร์ฟเวอร์ %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - URL นี้ไม่มีความปลอดภัยเพราะมันไม่ถูกเข้ารหัส -ไม่แนะนำให้ใช้ - - - - This url is secure. You can use it. - URL นี้มีความปลอดภัย คุณสามารถใช้มัน - - - - &Next > - และถัดไป > - - - - Invalid URL - URL ไม่ถูกต้อง - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - ล้มเหลวในการเชื่อมต่อไปยัง %1 ที่ %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - หมดเวลาขณะที่พยายามเชื่อมต่อไปยัง %1 ที่ %2 - - - - Invalid URL - URL ไม่ถูกต้อง - - - - Could not create local folder %1 - ไม่สามารถสร้างผสานข้อมูลโฟลเดอร์ในเครื่อง %1... - - - - Error: %1 - ข้อผิดพลาด: %1 - - - - The folder creation resulted in HTTP error code %1 - การสร้างโฟลเดอร์ดังกล่าวส่งผลให้เกิดรหัสข้อผิดพลาด HTTP error code %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - สร้างโฟลเดอร์รีโมทล้มเหลวเนื่องจากมีข้อมูลผิดพลาด! - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - การสร้างโฟลเดอร์รีโมท %1 ล้มเหลวเนื่องข้อผิดพลาด <tt>%2</tt> - - - - Folder rename failed - เปลี่ยนชื่อโฟลเดอร์ล้มเหลว - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - ไม่สามารถลบและสำรองข้อมูลโฟลเดอร์เพราะโฟลเดอร์หรือไฟล์ในนั้นจะเปิดในโปรแกรมอื่นอยู่ กรุณาปิดโฟลเดอร์หรือไฟล์และกดลองใหม่อีกครั้งหรือยกเลิกการติดตั้ง - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 ตัวช่วยสร้างการเชื่อมต่อ - - - - Enable experimental feature? - เปิดใช้งานโหมดเบต้า? - - - - Enable experimental placeholder mode - เปิดใช้งานโหมดเบต้า - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - เมื่อเปิดใช้งานโหมด "ไฟล์เสมือน" จะไม่มีการดาวน์โหลดไฟล์ใดๆ ในตอนแรก แต่จะมีการสร้างไฟล์ "%1" สำหรับแต่ละไฟล์ที่มีอยู่บนเซิร์ฟเวอร์ สามารถดาวน์โหลดเนื้อหาโดยใช้ไฟล์เหล่านี้หรือใช้เมนูบริบท - -โหมดไฟล์เสมือนคือหากลบไฟล์หรือโฟลเดอร์ในระบบออนไลน์ไฟล์หรือโฟลเดอร์ในเครื่องของเราก็จะหายไปด้วย - -การสลับไปที่โหมดนี้จะยกเลิกการซิงค์ที่กำลังทำงานอยู่ และตอนนี้ยังอยู่ในช่วงทดลองใช้หากพบเจอปัญหาโปรดรายงานปัญหาด้วย - - - - Stay safe - ไว้คราวหน้า - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - ลิงก์สัญลักษณ์จะไม่ได้รับการสนับสนุนในการประสานข้อมูล - - - - File is listed on the ignore list. - ไฟล์อยู่ในรายการที่ละเว้น - - - - File names ending with a period are not supported on this file system. - ชื่อไฟล์ที่ลงท้ายด้วยระยะเวลา ยังไม่ได้รับการสนับสนุนบนระบบไฟล์นี้ - - - - File names containing the character '%1' are not supported on this file system. - ชื่อไฟล์ที่มีตัวอักษร '%1' ยังไม่ได้รับการสนับสนุนบนระบบไฟล์นี้ - - - - File name contains at least one invalid character - มีชื่อแฟ้มอย่างน้อยหนึ่งตัวอักษรที่ไม่ถูกต้อง - - - - The file name is a reserved name on this file system. - ชื่อไฟล์นี้เป็นชื่อที่ถูกสงวนไว้ - - - - Filename contains trailing spaces. - ชื่อไฟล์มีช่องว่างต่อท้าย - - - - Filename is too long. - ชื่อไฟล์ยาวเกินไป - - - - File/Folder is ignored because it's hidden. - ไฟล์/โฟลเดอร์ ที่ซ่อนอยู่จะถูกละเว้น - - - - Stat failed. - สถิติความล้มเหลว - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - เกิดปัญหาความขัดแย้ง: สำเนาที่อยู่ในเครื่องถูกเปลี่ยนชื่อและไม่ได้ถูกอัปโหลด เลยไม่ตรงกับข้อมูลที่เคยอัปโหลดไว้ในเซิฟเวอร์ - - - - The filename cannot be encoded on your file system. - ชื่อไฟล์ไม่สามารถเข้ารหัสในระบบไฟล์ของคุณได้ - - - - The filename is blacklisted on the server. - ชื่อไฟล์นี้เป็นชื่อต้องห้าม - - - - size - ขนาด - - - - permissions - สิทธิ์ - - - - etag - etag - - - - file id - รหัสไฟล์ - - - - server reported no %1 - เซิร์ฟเวอร์รายงานว่าไม่มี %1 - - - - Moved to invalid target, restoring - ย้ายไปยังเป้าหมายที่ไม่ถูกต้อง กำลังดำเนินการกู้คืน - - - - Not allowed to remove, restoring - ไม่อนุญาตให้ลบเพราะกำลังฟื้นฟู - - - - Ignored because of the "choose what to sync" blacklist - ถูกละเว้นเพราะ "ข้อมูลที่เลือกประสาน" ติดบัญชีดำ - - - - Not allowed because you don't have permission to add subfolders to that folder - ไม่อนุญาตเพราะคุณไม่มีสิทธิ์ที่จะเพิ่มโฟลเดอร์ย่อยของโฟลเดอร์นั้น - - - - Not allowed because you don't have permission to add files in that folder - ไม่ได้รับอนุญาต เพราะคุณไม่มีสิทธิ์ที่จะเพิ่มไฟล์ในโฟลเดอร์นั้น - - - - Not allowed to upload this file because it is read-only on the server, restoring - ไม่อนุญาตให้อัปโหลดไฟล์นี้เพราะมันจะอ่านได้เพียงอย่างเดียวบนเซิร์ฟเวอร์ กำลังกู้คืน - - - - Error while reading the database - เกิดข้อผิดพลาดขณะอ่านฐานข้อมูล - - - - Server replied with an error while reading directory '%1' : %2 - เซิร์ฟเวอร์ตอบกลับพร้อมข้อผิดพลาดขณะอ่านไดเรกทอรีแล้ว '%1' : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - การดาวน์โหลดจะช่วยลดพืนที่จัดเก็บภายในเครื่องที่ต่ำกว่าขีดจำกัด - - - - Free space on disk is less than %1 - พื้นที่ว่างในดิสก์น้อยกว่า %1 - - - - File was deleted from server - ไฟล์ถูกลบออกจากเซิร์ฟเวอร์ - - - - The file could not be downloaded completely. - ดาวน์โหลดไฟล์ไม่สำเร็จ - - - - The downloaded file is empty despite the server announced it should have been %1. - ไฟล์ที่ดาวน์โหลดว่างเปล่าแม้ว่าเซิร์ฟเวอร์ประกาศว่าควรจะเป็น %1 - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - ไฟล์มีการเปลี่ยนแปลงตั้งแต่ถูกพบ - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; ฟื้นฟูล้มเหลว: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - ไม่สามารถลบไฟล์ %1, ข้อผิดพลาด: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - ไม่สามารถสร้างโฟลเดอร์ %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - ไม่สามารถเปลี่ยนชื่อไฟล์ %1 เป็น %2 เพราะไฟล์ชื่อนี้มีอยู่แล้ว - - - - Could not rename %1 to %2, the file is currently in use - ไม่สามารถเปลี่ยนชื่อ %1 เป็น %2 ได้เนื่องจากกำลังมีการใช้งานไฟล์นี้อยู่ - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - เกิดข้อผิดพลาดขณะตั้งค่าสถานะพิน - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - รหัส HTTP ผิดพลาด โดยเซิร์ฟเวอร์คาดว่าจะได้รับรหัส 204 แต่กลับได้รับ "%1 %2" - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - รหัส HTTP ผิดพลาด โดยเซิร์ฟเวอร์คาดว่าจะได้รับรหัส 201 แต่กลับได้รับ "%1 %2" - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - ไม่สามารถเปลี่ยนชื่อ %1 เป็น %2, ข้อผิดพลาด: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - รหัส HTTP ผิดพลาด โดยเซิร์ฟเวอร์คาดว่าจะได้รับรหัส 201 แต่กลับได้รับ "%1 %2" - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - ข้อผิดพลาดในการเขียน metadata ไปยังฐานข้อมูล - - - - Error setting pin state - เกิดข้อผิดพลาดขณะตั้งค่าสถานะพิน - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - ไม่สามารถอัปเดตไฟล์: %1 - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - ไม่สามารถอัปโหลดไฟล์ %1 เนื่องจากมีไฟล์อื่นที่มีชื่อเดียวกันอยู่แล้ว - - - - File Removed - ไฟล์ถูกลบไปแล้ว - - - - Local file changed during syncing. It will be resumed. - ไฟล์ในเครื่องถูกเปลี่ยนแปลงในระหว่างการประสานข้อมูล - - - - Local file changed during sync. - ไฟล์ในเครื่องถูกเปลี่ยนแปลงขณะกำลังประสานข้อมูล - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - การอัปโหลด %1 เกินโควต้าของโฟลเดอร์ - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - ไฟล์ %1 กำลังถูกใช้งานอยู่ - - - - The local file was removed during sync. - ไฟล์ในเครื่องถูกลบออกในระหว่างการประสานข้อมูล - - - - Local file changed during sync. - ไฟล์ในเครื่องถูกเปลี่ยนแปลงขณะกำลังประสานข้อมูล - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - มีรหัสข้อผิดพลาดตอบกลับมาจากเซิร์ฟเวอร์ (%1) - - - - Missing File ID from server - ไฟล์ไอดีได้หายไปจากเซิร์ฟเวอร์ - - - - Missing ETag from server - ETag ได้หายไปจากเซิร์ฟเวอร์ - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - ไฟล์ %1 กำลังถูกใช้งานอยู่ - - - - The local file was removed during sync. - ไฟล์ในเครื่องถูกลบออกในระหว่างการประสานข้อมูล - - - - Local file changed during sync. - ไฟล์ในเครื่องถูกเปลี่ยนแปลงขณะกำลังประสานข้อมูล - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - ไฟล์ %1 กำลังถูกใช้งานอยู่ - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - ไฟล์ในเครื่องถูกลบออกในระหว่างการประสานข้อมูล - - - - Local file changed during sync. - ไฟล์ในเครื่องถูกเปลี่ยนแปลงขณะกำลังประสานข้อมูล - - - - The server did not acknowledge the last chunk. (No e-tag was present) - เซิร์ฟเวอร์ไม่ยอมรับส่วนสุดท้าย (ไม่มี e-tag ในปัจจุบัน) - - - - OCC::ProtocolItemModel - - - Time - เวลา - - - - File - ไฟล์ - - - - Folder - สร้างโฟลเดอร์ใหม่ - - - - Issues - ปัญหา - - - - Action - การกระทำ - - - - Size - ขนาด - - - - Account - บัญชี - - - - Status - สถานะ - - - - OCC::ProtocolWidget - - - Form - แบบฟอร์ม - - - - Local sync protocol - โปรโตคอลการประสานข้อมูลในเครื่อง - - - - Filter - ตัวกรอง - - - - Account - บัญชี - - - - Copy to clipboard - คัดลอกลิงก์ไปยังคลิปบอร์ด - - - - Show in file browser - - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - การตรวจสอบพร็อกซีที่จำเป็น - - - - Username: - ชื่อผู้ใช้งาน - - - - Proxy: - พร็อกซี: - - - - The proxy server needs a username and password. - พร็อกซีเซิร์ฟเวอร์ต้องการชื่อผู้ใช้และรหัสผ่าน - - - - Password: - รหัสผ่าน: - - - - TextLabel - ป้ายข้อความ - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - เลือกสิ่งที่ต้องการประสานข้อมูล - - - - OCC::SelectiveSyncWidget - - - Loading ... - กำลังโหลด ... - - - - Deselect remote folders you do not wish to synchronize. - ไม่ต้องเลือกรีโมทโฟลเดอร์ที่คุณไม่ต้องการประสานข้อมูล - - - - Name - ชื่อ - - - - Size - ขนาด - - - - - No subfolders currently on the server. - ไม่มีโฟลเดอร์ย่อยอยู่บนเซิร์ฟเวอร์ - - - - An error occurred while loading the list of sub folders. - เกิดข้อผิดพลาดขณะโหลดรายชื่อของโฟลเดอร์ย่อย - - - - OCC::ServerNotificationHandler - - - Dismiss - ยกเลิก - - - - OCC::SettingsDialog - - - Settings - ตั้งค่า - - - - Activity - กิจกรรม - - - - Add account - เพิ่มบัญชี - - - - - Quit %1 - ออก %1 - - - - Are you sure you want to quit %1? - แน่ใจหรือไม่ว่าคุณต้องการออกจาก %1? - - - - Account - บัญชี - - - - MainWindow - หน้าจอหลัก - - - - Hide - ซ๋อน - - - - OCC::ShareDialog - - - TextLabel - ป้ายข้อความ - - - - share label - แชร์ป้ายกำกับ - - - - Dialog - กล่องโต้ตอบ - - - - ownCloud Path: - พาธ ownCloud: - - - - %1 Sharing - กำลังแชร์ %1 - - - - %1 - %1 - - - - Folder: %2 - โฟลเดอร์: %2 - - - - The server does not allow sharing - เซิร์ฟเวอร์ไม่อนุญาตให้แชร์ - - - - Retrieving maximum possible sharing permissions from server... - คุณใช้สิทธิ์การเรียกข้อมูลจากเซิฟเวอร์สูงสุดแล้ว - - - - The file can not be shared because it was shared without sharing permission. - ไม่สามารถแชร์ไฟล์เพราะไม่ได้รับอนุญาต - - - - Users and Groups - ผู้ใช้และกลุ่ม - - - - Public Links - ลิงก์สาธารณะ - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - แชร์ NewDocument.odt - - - - Upload only (File Drop) - เฉพาะอัปโหลด (วางไฟล์) - - - - Receive files from others without revealing the contents of the folder. - รับไฟล์จากบุคคลอื่นโดยไม่ต้องเปิดเผยเนื้อหาของโฟลเดอร์ - - - - Download / View / Edit - ดาวน์โหลด / ดู / แก้ไข - - - - Name: - ชื่อ: - - - - Enter name of new share... - ป้อนชื่อการแชร์ใหม่... - - - - &Create - และสร้าง - - - - TextLabel - ป้ายข้อความ - - - - Set &password - ตั้ง &รหัสผ่าน - - - - Set &expiration date - ตั้ง &วันหมดอายุ - - - - Set password - ตั้งรหัสผ่าน - - - - Recipients can view or download contents. - ผู้รับสามารถดูหรือดาวน์โหลดเนื้อหา - - - - Download / View - ดาวน์โหลดหรือดู - - - - Recipients can view, download, edit, delete and upload contents. - ผู้รับสามารถดู ดาวน์โหลด แก้ไข ลบและอัปโหลดเนื้อหา - - - - Anyone with the link has access to the file/folder - ทุกคนที่มีลิงก์สามารถเข้าถึงไฟล์หรือโฟลเดอร์ได้ - - - - - P&assword protect - ป้องกันด้วยรหัสผ่าน - - - - The file can not be shared because it was shared without sharing permission. - ไม่สามารถแชร์ไฟล์เพราะไม่ได้รับอนุญาต - - - - Link shares have been disabled - แชร์ลิงก์แล้วถูกปิดใช้งาน - - - - Create public link share - สร้างแชร์ลิงก์สาธารณะ - - - - - Delete - ลบ - - - - Open link in browser - เปิดลิงก์ในเบราว์เซอร์ - - - - Copy link to clipboard - คัดลอกลิงก์ไปยังคลิปบอร์ด - - - - Copy link to clipboard (direct download) - คัดลอกลิงก์ทางอีเมล (ดาวน์โหลดโดยตรง) - - - - Send link by email - ส่งลิงก์ทางอีเมล - - - - Send link by email (direct download) - ส่งลิงก์ทางอีเมล (ดาวน์โหลดโดยตรง) - - - - Create new... - สร้างใหม่... - - - - I shared %1 with you - ฉันได้แชร์ %1 กับคุณ - - - - Confirm Link Share Deletion - ยืนยันการลบลิงก์ที่แชร์ - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>คุณต้องการลบลิงก์ที่แชร์แบบสาธารณะ<i>%1</i>?</p><p>หมายเหตุ: ไม่สามารถยกเลิกการดำเนินการนี้ได้</p> - - - - Cancel - ยกเลิก - - - - - Public link - ลิงก์สาธารณะ - - - - Delete link share - ลบลิงก์ที่แชร์ - - - - - Please Set Password - กรุณาตั้งรหัสผ่าน - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - แชร์ NewDocument.odt - - - - Share with users or groups ... - แชร์กับผู้ใช้หรือกลุ่ม ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>คุณสามารถให้คนอื่นเข้าถึงไฟล์หรือโฟลเดอร์ที่คุณแชร์นี้ได้<a href="private link menu"><span style=" text-decoration: underline">โดยส่งลิงก์ส่วนตัวไปให้คนที่คุณต้องการแชร์</span></a></p></body></html> - - - - The item is not shared with any users or groups - รายการนี้ไม่ได้แชร์กับผู้ใช้หรือกลุ่มใดๆ - - - - Open link in browser - คัดลอกลิงก์ไปยังคลิปบอร์ด - - - - Copy link to clipboard - คัดลอกลิงก์ไปยังคลิปบอร์ด - - - - Send link by email - ส่งลิงก์ทางอีเมล - - - - No results for '%1' - ไม่มีผลลัพธ์สำหรับ '%1' - - - - I shared something with you - ฉันแชร์บางอย่างกับคุณ - - - - OCC::ShareUserLine - - - Form - แบบฟอร์ม - - - - - TextLabel - ป้ายข้อความ - - - - can edit - สามารถแก้ไข - - - - can share - สามารถแชร์ได้ - - - - ... - ... - - - - create - สร้าง - - - - change - เปลี่ยนแปลง - - - - delete - ลบ - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - แชร์กับ %1 - - - - Context menu share - แชร์เมนูบริบท - - - - Context menu share %1 - แชร์เมนูบริบท %1 - - - - Confirm deletion - ยืนยันการลบ - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - คุณต้องการลบไดเรกทอรี <i>%1</i> และเนื้อหาทั้งหมดอย่างถาวร? - - - - Do you want to delete the file <i>%1</i> permanently? - คุณต้องการลบไฟล์ <i>%1</i> แบบถาวร? - - - - Select new location... - เลือกตำแหน่งใหม่... - - - - Error - ข้อผิดพลาด - - - - Moving file failed: - -%1 - ย้ายไฟล์ล้มเหลว: - -%1 - - - - I shared something with you - ฉันแชร์บางอย่างกับคุณ - - - - - Share... - แชร์... - - - - - Copy private link to clipboard - คัดลอกลิงก์ส่วนตัวไปยังคลิปบอร์ด - - - - Send private link by email... - ส่งลิงก์ส่วนตัวทางอีเมล... - - - - Resharing this file is not allowed - การแชร์ไฟล์นี้ต่อไม่ได้รับอนุญาต - - - - Resharing this folder is not allowed - ไม่อนุญาตให้แชร์โฟลเดอร์นี้ต่อ - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - คัดลอกลิงก์สาธารณะไปยังคลิปบอร์ด - - - - Open in browser - เปิดในเบราว์เซอร์ - - - - Show file versions in browser - แสดงเวอร์ชันของไฟล์ในเบราว์เซอร์ - - - - Rename... - เปลี่ยนชื่อ... - - - - Rename and upload... - ปลี่ยนชื่อและอัปโหลด... - - - - Move and rename... - ย้ายและเปลี่ยนชื่อ... - - - - Move, rename and upload... - ย้าย เปลี่ยนชื่อและอัปโหลด... - - - - Delete local changes - ลบการเปลี่ยนแปลงในเครื่อง - - - - Move and upload... - ย้ายและอัปโหลด... - - - - Delete - ลบ - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - เรียกข้อมูลแท็กไม่สำเร็จ - - - - Backup of %1 started - เริ่มการสำรองข้อมูลของ %1 แล้ว - - - - Failed to find %1 - ค้นห้า %1 ไม่พบ - - - - Local path must be a an absolute path - พาธต้นทางต้องเป็นเส้นทางที่แน่นอน - - - - Failed to create temporary database - ไม่สามารถสร้างฐานข้อมูลชั่วคราว - - - - Backup of %1 succeeded - สำรองข้อมูลของ %1 สำเร็จแล้ว - - - - Failed to set success tag - ไม่สามารถตั้งค่าแท็กความสำเร็จ - - - - Failed to create backup: %1 - สร้างการสำรองข้อมูลไม่สำเร็จ: %1 - - - - Failed to file id tags - ไม่พบแท็ก ID - - - - Backup of %1 failed with: %2 - สำรองข้อมูล %1 ล้มเหลว: %2 - - - - %1 backup - สำรอง %1 - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>รายละเอียดใบรับรองความถูกต้อง</h3> - - - - Common Name (CN): - ชื่อทั่วไป (Common Name): - - - - Subject Alternative Names: - ชื่อเรื่องทางเลือก: - - - - Organization (O): - องค์กร (Organization): - - - - Organizational Unit (OU): - หน่วยองค์กร (Organizational Unit): - - - - State/Province: - รัฐ/จังหวัด: - - - - Country: - ประเทศ: - - - - Serial: - Serial: - - - - <h3>Issuer</h3> - <h3>แจ้งปัญหา</h3> - - - - Issuer: - ผู้แจ้งปัญหา: - - - - Issued on: - ออกเมื่อวันที่: - - - - Expires on: - หมดอายุในวันที่: - - - - <h3>Fingerprints</h3> - <h3>ลายนิ้วมือ</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>หมายเหตุ:</b> ใบรับรองนี้ได้รับการอนุมัติด้วยตนเอง</p> - - - - %1 (self-signed) - %1 (ลงนามด้วยตนเอง) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - การเชื่อมต่อนี้ถูกเข้ารหัสโดยใช้ %1 บิต %2 - - - - Server version: %1 - เวอร์ชันเซิร์ฟเวอร์: %1 - - - - No support for SSL session tickets/identifiers - ไม่มีการสนับสนุนสำหรับเซสชั่นตั๋ว SSL/ตัวบ่งชี้ - - - - Certificate information: - ข้อมูลการรับรอง: - - - - The connection is not secure - การเชื่อมต่อไม่ปลอดภัย - - - - This connection is NOT secure as it is not encrypted. - - การเชื่อมต่อนี้ไม่มีความปลอดภัยเพราะมันไม่ได้เข้ารหัส - - - - OCC::SslErrorDialog - - - Form - แบบฟอร์ม - - - - Trust this certificate anyway - เชื่อถือในใบรับรองความปลอดภัยนี้ไม่ว่าอย่างไร - - - - Untrusted Certificate - ใบรับรองไม่น่าเชื่อถือ - - - - Cannot connect securely to <i>%1</i>: - ไม่สามารถเชื่อมต่อแบบปลอดภัยไปยัง <i>%1</i>: - - - - with Certificate %1 - ด้วยใบรับรองความปลอดภัย %1 - - - - - - &lt;not specified&gt; - &lt;ยังไม่ได้ถูกระบุ&gt; - - - - - Organization: %1 - หน่วยงาน: %1 - - - - - Unit: %1 - หน่วย: %1 - - - - - Country: %1 - ประเทศ: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - ลายนิ้วมือ (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - ลายนิ้วมือ (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - วันที่บังคับใช้: %1 - - - - Expiration Date: %1 - หมดอายุวันที่: %1 - - - - Issuer: %1 - ผู้รับรอง: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (ข้ามไปเนื่องจากข้อผิดพลาดก่อนหน้านี้ กำลังลองอีกครั้งใน %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - มีเพียง %1 ที่พร้อมใช้งาน คุณจำเป็นต้องมีไม่น้อยกว่า %2 เพื่อเริ่มใช้งาน - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - ไม่สามารถเปิดหรือสร้างฐานข้อมูลการประสานข้อมูลในเครื่อง ตรวจสอบว่าคุณมีสิทธิ์การเขียนในโฟลเดอร์ประสานข้อมูล - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - พื้นที่จัดเก็บเหลือน้อย: การดาวน์โหลดจะช่วยลดพื้นที่ว่างด้านล่าง %1 ที่ถูกข้ามไป - - - - There is insufficient space available on the server for some uploads. - มีพื้นที่ว่างไม่เพียงพอบนเซิร์ฟเวอร์สำหรับการอัปโหลดบางรายการ - - - - Unresolved conflict. - ข้อขัดแย้งที่ยังไม่ได้แก้ไข - - - - Using virtual files with suffix, but suffix is not set - กำลังใช้ไฟล์เสมือนที่มีคำต่อท้าย แต่ไม่ได้ตั้งคำต่อท้าย - - - - Unable to read the blacklist from the local database - ไม่สามารถอ่านบัญชีดำจากฐานข้อมูลในเครื่อง - - - - Unable to read from the sync journal. - ไม่สามารถอ่านจากบันทึกการประสานข้อมูล - - - - Cannot open the sync journal - ไม่สามารถเปิดการผสานข้อมูลเจอร์นัล - - - - Aborted - ยกเลิกแล้ว - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>เวอร์ชัน %1 สามารถดูข้อมูลเพิ่มเติมได้ที่ <a href="%2">https://%3</a></p><p>สำหรับแจ้งปัญหาที่พบและขอความช่วยเหลือโปรดไปที่:<a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, Sunchai Paejaroenchai และคนอื่น ๆ ที่ไม่ได้กล่าวถึง</small></p><p>ลิขสิทธิ์ ownCloud GmbH</p><p>จัดจำหน่ายโดย %4 และได้รับอนุญาตภายใต้ GNU General Public License (GPL) เวอร์ชัน 2.0<br/>%5 และโลโก้ %5 เป็นเครื่องหมายการค้าจดทะเบียนของ %4 ในสหรัฐอเมริกาประเทศอื่น ๆ หรือทั้งสองอย่าง</p><p><small>%6</small></p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - การตรวจสอบส่วนหัวผิดรูปแบบ - - - - The checksum header contained an unknown checksum type '%1' - จากการตรวจสอบส่วนหัวมีประเภทที่ไม่รู้จัก '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - ไฟล์ที่ดาวน์โหลดหากตรวจพบ checksum ไม่ตรงกันจะถูกเรียกคืน '%1' != '%2' - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - ฟีดเจอร์ระบบไฟล์เสมือนไม่สนับสนุนไดรฟ์ที่เป็นซิงค์รูท - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - คุณลักษณะระบบไฟล์เสมือนต้องการระบบไฟล์ NTFS, %1 กำลังใช้งาน %2 - - - - The Virtual filesystem feature is not supported on network drives - ไม่รองรับฟีเจอร์ระบบไฟล์เสมือนบนไดรฟ์เครือข่าย - - - - OCC::ownCloudGui - - - Please sign in - กรุณาเข้าสู่ระบบ - - - - Folder %1: %2 - โฟลเดอร์ %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - ไม่มีการกำหนดค่าการประสานข้อมูลโฟลเดอร์ - - - - Open in browser - เปิดในเบราว์เซอร์ - - - - - - Log in... - เข้าสู่ระบบ... - - - - - - Log out - ออกจากระบบ - - - - Recent Changes - การเปลี่ยนแปลงล่าสุด - - - - Managed Folders: - โฟลเดอร์ที่มีการจัดการแล้ว: - - - - Open folder '%1' - เปิดโฟลเดอร์ '%1' - - - - Open %1 in browser - เปิด %1 ในเบราว์เซอร์ - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - เซิร์ฟเวอร์ในบัญชี %1 ไม่รองรับการทำงานกับ %2 ซึ่งอาจเป็นอันตราย ดำเนินการตามความเสี่ยงของคุณเอง - - - - Unknown status - สถานะที่ไม่รู้จัก - - - - Settings... - ตั้งค่า... - - - - Details... - รายละเอียด... - - - - Help - ช่วยเหลือ - - - - Quit %1 - ออก %1 - - - - Disconnected from %1 - ถูกตัดการเชื่อมต่อจาก %1 - - - - Unsupported Server Version - ไม่สนับสนุนเวอร์ชันของ Server - - - - Disconnected - ยกเลิกการเชื่อมต่อ - - - - Disconnected from some accounts - ยกเลิกการเชื่อมต่อจากบางบัญชี - - - - Disconnected from accounts: - ยกเลิกการเชื่อมต่อจากบัญชี: - - - - Account %1: %2 - บัญชี %1: %2 - - - - Signed out - ออกจากระบบ - - - - Account synchronization is disabled - บัญชีประสานข้อมูลถูกปิดใช้งาน - - - - Synchronization is paused - หยุดการประสานข้อมูลแล้ว - - - - Up to date (%1) - - - - - No sync folders configured - ไม่ได้กำหนดค่าโฟลเดอร์ที่จะประสานข้อมูล - - - - Unpause all folders - ทำโฟลเดอร์ทั้งหมดต่อ - - - - Pause all folders - หยุดโฟลเดอร์ทั้งหมดชั่วคราว - - - - Unpause all synchronization - ประสานข้อมูลทั้งหมดต่อ - - - - Unpause synchronization - ประสานข้อมูลต่อ - - - - Pause all synchronization - หยุดการประสานข้อมูลทั้งหมดชั่วคราว - - - - Pause synchronization - หยุดการประสานข้อมูลชั่วคราว - - - - Log out of all accounts - ออกจากระบบของบัญชีทั้งหมด - - - - Log in to all accounts... - เข้าสู่ระบบของบัญชีทั้งหมด... - - - - New account... - สร้างบัญชีใหม่... - - - - About %1 - เกี่ยวกับ %1 - - - - No items synced recently - ไม่มีรายการที่ถูกประสานข้อมูลเมื่อเร็วๆ นี้ - - - - Checking for changes in remote '%1' - กำลังตรวจหาการเปลี่ยนแปลงในรีโมต '%1' - - - - Checking for changes in local '%1' - กำลังตรวจหาการเปลี่ยนแปลงในเครื่อง '%1' - - - - Syncing %1 of %2 (%3 left) - กำลังประสานข้อมูล %1 จาก %2 (เหลือ %3) - - - - Syncing %1 of %2 - กำลังประสานข้อมูล %1 จากทั้งหมด %2 - - - - Syncing %1 (%2 left) - กำลังประสานข้อมูล %1 (เหลือ %2) - - - - Syncing %1 - กำลังประสานข้อมูล %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - แบบฟอร์ม - - - - - TextLabel - ป้ายข้อความ - - - - Server - เซิร์ฟเวอร์ - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>ถ้ากล่องนี้ถูกตรวจสอบ โฟลเดอร์ในเครื่องจะถูกลบเพื่อเริ่มต้นล้างการประสานข้อมูลจากเซิร์ฟเวอร์</p><p>ไม่ต้องตรวจสอบนี้หากเนื้อหาต้นทางได้รับการอัปโหลดไปยังโฟลเดอร์เซิร์ฟเวอร์</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - ถามก่อนที่จะประสานข้อมูลกับโฟลเดอร์ที่มีขนาดใหญ่กว่า - - - - S&ynchronize everything from server - ประสานข้อมูลทั้งหมดจากเซิร์ฟเวอร์ - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - เมกะไบต์ - - - - Ask for confirmation before synchronizing e&xternal storages - ถามก่อนที่จะประสานข้อมูลกับพื้นที่จัดเก็บข้อมูลภายนอก - - - - Choose what to sync - เลือกข้อมูลที่ต้องการประสาน - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>เมื่อตัวเลือกนี้ถูกเลือก วิซาร์ดจะปิดโดยไม่ได้ประสานข้อมูล คุณสามารกดเพิ่มโฟลเดอร์ที่ต้องการการประสานข้อมูลในการตั้งค่าบัญชี</p></body></html> - - - - Manually create folder sync connections - สร้างการประสานข้อมูลโฟลเดอร์ด้วยตัวเอง - - - - &Local Folder - และโฟลเดอร์ในเครื่อง - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - และเก็บข้อมูลในเครื่อง - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - แบบฟอร์ม - - - - &Username - และชื่อผู้ใช้ - - - - &Password - และหรัสผ่าน - - - - OwncloudOAuthCredsPage - - - Form - แบบฟอร์ม - - - - Please switch to your browser to proceed. - โปรดเปลี่ยนไปใช้เบราว์เซอร์เพื่อดำเนินการต่อ - - - - An error occurred while connecting. Please try again. - เกิดข้อผิดพลาดขณะเชื่อมต่อ กรุณาลองอีกครั้ง - - - - Re-open Browser - เปิดเบราเซอร์อีกครั้ง - - - - OwncloudSetupPage - - - Form - แบบฟอร์ม - - - - Ser&ver Address - ที่อยู่เซิฟเวอร์ - - - - https://... - https://... - - - - Error Label - ป้ายกำกับผิดพลาด - - - - QObject - - - in the future - ในอนาคต - - - - %n day(s) ago - %n วันที่ผ่านมา - - - - %n hour(s) ago - %n ชั่วโมงที่ผ่านมา - - - - now - ตอนนี้ - - - - Less than a minute ago - ไม่กี่นาทีที่ผ่านมา - - - - %n minute(s) ago - %n นาทีที่ผ่านมา - - - - Some time ago - บางเวลาที่ผ่านมา - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - ข้อผิดพลาดร้ายแรง - - - - - Error - ข้อผิดพลาด - - - - Info - ข้อมูล - - - - Success - เสร็จสิ้น - - - - Conflict - ปัญหา - - - - File Ignored - - - - - Restored - - - - - Blacklisted - - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 กิกะไบต์ - - - - %L1 MB - %L1 เมกะไบต์ - - - - %L1 KB - %L1 กิโลไบต์ - - - - %L1 B - %L1 B - - - - %n year(s) - %n ปี - - - - %n month(s) - %n เดือน - - - - %n day(s) - %n วัน - - - - %n hour(s) - %n ชั่วโมง - - - - %n minute(s) - %n นาที - - - - %n second(s) - %n วินาที - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - ถาดระบบไม่สามารถใช้ได้ - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 จำเป็นต้องทำงานบนถาดระบบ ถ้าคุณใช้ XFCE โปรดปฏิบัติตาม <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">คำแนะนำเหล่านี้</a> อย่างไรก็ตาม กรุณาติดตั้งแอปฯถาดระบบเช่น 'trayer' และลองอีกครั้ง - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - %1 (สร้างขึ้นเพื่อต่อต้าน Qt %1) - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - สร้างไฟล์เสมือนแล้ว - - - - Replaced by virtual file - แทนที่ด้วยไฟล์เสมือนแล้ว - - - - Downloaded - ถูกดาวน์โหลด - - - - Uploaded - อัปโหลดแล้ว - - - - Server version downloaded, copied changed local file into conflict file - ดาวน์โหลดเวอร์ชันของเซิฟเวอร์แล้ว และได้ถูกคัดลอกและเปลี่ยนแปลงไฟล์ในเครื่องแทนไฟล์ที่มีปัญหา - - - - Deleted - ลบเมื่อ - - - - Moved to %1 - ถูกย้ายไปยัง %1 - - - - Ignored - ถูกละเว้น - - - - Filesystem access error - ข้อผิดพลาดในการเข้าถึงระบบไฟล์ - - - - Error - ข้อผิดพลาด - - - - Updated local metadata - อัปเดต metadata ในเครื่องแล้ว - - - - - Unknown - ไม่ทราบ - - - - downloading - กำลังดาวน์โหลด - - - - uploading - กำลังอัปโหลด - - - - deleting - กำลังลบ - - - - moving - กำลังย้าย - - - - ignoring - กำลังละเว้น - - - - - error - ข้อผิดพลาด - - - - updating local metadata - กำลังอัปเดต metadata ในเครื่อง - - - - theme - - - Status undefined - สถานะไม่สามารถระบุได้ - - - - Waiting to start sync - กำลังรอการเริ่มต้นประสานข้อมูล - - - - Sync is running - การประสานข้อมูลกำลังทำงาน - - - - Sync Success - การประสานข้อมูลเสร็จสิ้น - - - - Sync Success, some files were ignored. - ประสานข้อมูลสำเร็จ บางไฟล์ถูกละเลย - - - - Sync Error - ประสานข้อมูลผิดพลาด - - - - Setup Error - เกิดข้อผิดพลาดในการตั้งค่า - - - - Preparing to sync - เตรียมความพร้อมในการประสานข้อมูล - - - - Aborting... - กำลังยกเลิก... - - - - Sync is paused - การประสานข้อมูลถูกหยุดชั่วคราว - - - - utility - - - Could not open browser - ไม่สามารถเปิดเบราเซอร์ - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - เกิดข้อผิดพลาดขณะเปิดเบราว์เซอร์เพื่อไปที่ URL %1 อาจยังไม่มีการกำหนดค่าเบราเซอร์เริ่มต้น - - - - Could not open email client - ไม่สามารถเปิดไคลเอนต์อีเมล - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - เกิดข้อผิดพลาดเมื่อเปิดไคลเอ็นต์อีเมลเพื่อสร้างข้อความใหม่ บางทีไคลเอ็นต์อีเมลอาจยังไม่ได้ตั้งค่า? - - - - Always available locally - พร้อมใช้งานแบบในเครื่องเสมอ - - - - Some available online only - พร้อมใช้งานแบบออนไลน์แค่บางอย่างเท่านั้น - - - - - Available online only - พร้อมใช้งานแบบออนไลน์เท่านั้น - - - - Currently available locally - ปัจจุบันสามารถใช้งานแบบในเครื่องได้ - - - - Make always available locally - ทำให้ใช้งานแบบในเครื่องเสมอ - - - - Free up local space - เพิ่มพื้นที่ว่าง - - - \ No newline at end of file diff --git a/translations/client_tr.ts b/translations/client_tr.ts deleted file mode 100644 index 29334a6d844..00000000000 --- a/translations/client_tr.ts +++ /dev/null @@ -1,4910 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - İptal Et - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - Kapat - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - Çöpte klasör oluşturulamaz. - - - - - Could not move '%1' to '%2' - %1, %2 ye taşınamadı. - - - - Moving to the trash is not implemented on this platform - Çöp kutusuna taşıma işlemi bu platformda sağlanmıyor. - - - - Error removing '%1': %2 - '%1' kaldırılırken hata: %2 - - - - Could not remove folder '%1' - '%1' klasörü silinemiyor - - - - FolderWizardSourcePage - - - Form - Form - - - - Pick a local folder on your computer to sync - Eşitlemek için bilgisayarınızdan yerel bir klasör seçin - - - - &Choose... - &Seç... - - - - FolderWizardTargetPage - - - Form - Form - - - - Select a remote destination folder - Bir uzak hedef klasörü seçin - - - - Create Folder - Klasör Oluştur - - - - Refresh - Yenile - - - - Folders - Klasörler - - - - TextLabel - MetinEtiketi - - - - NotificationWidget - - - Form - Form - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - MetinEtiketi - - - - OCC::AboutDialog - - - About - Hakkında - - - - TextLabel - MetinEtiketi - - - - Versions - Sürümler - - - - About %1 - %1 hakkında - - - - OCC::AbstractNetworkJob - - - Connection timed out - Bağlantı zaman aşımına uğradı - - - - Unknown error: network reply was deleted - Bilinmeyen hata: ağdan gelen yanıt silindi - - - - Server replied "%1 %2" to "%3 %4" - Sunucu "%1 %2" tarafından "%3 %4" tarafına cevap verdi. - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - Form - - - - Re-open Browser - Tarayıcıyı Yeniden Aç - - - - ... - ... - - - - Storage space: ... - Depolama alanı: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Seçilmemiş klasörler yerel dosya sisteminizden <b>kaldırılacak</b> ve bir daha bu bilgisayarla eşitlenmeyecektir - - - - Synchronize all - Tümünü eşitle - - - - Synchronize none - Hiçbirini eşitleme - - - - Apply manual changes - Elle yapılan değişiklikleri uygula - - - - Apply - Uygula - - - - - - - Cancel - İptal - - - - Connected with <server> as <user> - <server> ile <user> olarak bağlantı kuruldu - - - - No account configured. - Hiçbir hesap yapılandırılmamış. - - - - Remove - Kaldır - - - - Account - Hesap - - - - Choose what to sync - Ne eşitleneceğini seçin - - - - Force sync now - Şimdi eşitlemeye zorla - - - - Restart sync - Eşitlemeyi yeniden başlat - - - - Remove folder sync connection - Klasör eşitleme bağlantısını sil - - - - Folder creation failed - Klasör oluşturma başarısız oldu - - - - <p>Could not create local folder <i>%1</i>. - <p><i>%1</i> yerel klasörü oluşturulamadı. - - - - Confirm Folder Sync Connection Removal - Klasör Eşitleme Bağlantısının Silinmesini Onaylayın - - - - Remove Folder Sync Connection - Klasör Eşitleme Bağlantısını Sil - - - - Disable virtual file support? - Sanal dosya desteği devre dışı bırakılsın mı? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - Bu eylem sanal dosya sistemi desteğini devre dışı bırakır. Sonuç olarak, şu anda "yalnızca çevrimiçi kullanılabilir" olarak işaretlenmiş klasörlerin içeriği indirilecektir. - -Sanal dosya sistemi desteğini devre dışı bırakmanın tek avantajı, seçmeli senkronizasyon özelliğinin tekrar kullanılabilir hale gelmesidir. - -Bu eylem, o anda çalışan herhangi senkronizasyon işlemini iptal edecektir. - - - - Disable support - Desteği devre dışı bırak - - - - Sync Running - Eşitleme Çalışıyor - - - - The syncing operation is running.<br/>Do you want to terminate it? - Eşitleme işlemi devam ediyor.<br/>Durdurmak istiyor musunuz? - - - - %1 in use - %1 kullanımda - - - - %1 as <i>%2</i> - %1 <i>%2</i> - - - - Connected to %1. - %1 ile bağlı. - - - - Server %1 is temporarily unavailable. - %1 sunucusu geçici olarak ulaşılamaz durumda. - - - - Server %1 is currently in maintenance mode. - Sunucu %1 bakım modunda. - - - - Signed out from %1. - %1 oturumu sonlandırıldı. - - - - Connecting to %1... - %1 e bağlanılıyor... - - - - No connection to %1 at %2. - %1 ile %2 bağlantısı yok. - - - - Log in - Giriş yap - - - - There are folders that were not synchronized because they are too big: - Çok büyük oldukları için eşitlenmeyen klasörler var: - - - - There are folders that were not synchronized because they are external storages: - Harici depolama diskinde oldukları için eşitlenmeyen klasörler var: - - - - There are folders that were not synchronized because they are too big or external storages: - Çok büyük oldukları ya da harici depolama alanında oldukları için eşitlenmeyen klasörler var: - - - - Confirm Account Removal - Hesap Silinmesini Onaylayın - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p><i>%1</i> hesabının bağlantısını kaldırmayı gerçekten istiyor musunuz?</p><p><b>Not:</b> Bu işlem herhangi bir dosyayı <b>silmeyecektir</b>.</p> - - - - Remove connection - Bağlantıyı kaldır - - - - Open folder - Klasörü aç - - - - - Log out - Çıkış yap - - - - Open local folder - Yerel klasörü aç - - - - Open folder in browser - Klasörü tarayıcıda aç - - - - Resume sync - Eşitlemeye devam et - - - - Pause sync - Eşitlemeyi duraklat - - - - Availability - Uygunluk - - - - Disable virtual file support... - Sanal dosya desteğini devre dışı bırak... - - - - Enable virtual file support%1... - Enable virtual file support%1... - - - - (experimental) - (experimental) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p><i>%1</i> klasörünün eşitlemesini durdurmayı gerçekten istiyor musunuz?</p><p><b>Not:</b> Bu işlem herhangi bir dosyayı <b>silmeyecektir</b>.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) / %2 kullanımda. Ağdan bağlanmış veya paylaşılan dizinlerin farklı sınırları olabilir. - - - - %1 of %2 in use - %1 / %2 kullanımda - - - - Currently there is no storage usage information available. - Şu anda depolama kullanım bilgisi mevcut değil. - - - - The server version %1 is unsupported! Proceed at your own risk. - Sunucu versiyonu %1 desteklenmiyor! Sorumluluk size ait. - - - - Obtaining authorization from the browser. - Obtaining authorization from the browser. - - - - Server configuration error: %1 at %2. - Server configuration error: %1 at %2. - - - - No %1 connection configured. - Hiç %1 bağlantısı yapılandırılmamış. - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - - - - - Signed out - Oturum sonlandırıldı - - - - Disconnected - Bağlantı kesildi - - - - Connected - Bağlandı - - - - Service unavailable - Hizmet kullanılamıyor - - - - Maintenance mode - Bakım modu - - - - Network error - Ağ hatası - - - - Configuration error - Yapılandırma hatası - - - - Asking Credentials - Kimlik bilgileri alma - - - - Unknown account state - Bilinmeyen hesap durumu - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %3 üzerinde %1 %2 - - - - Activity - Etkinlik - - - - Account - Hesap - - - - Time - Zaman - - - - Local path - - - - - OCC::ActivitySettings - - - - Server Activity - Sunucu Etkinliği - - - - Sync Protocol - Eşitleme Protokolü - - - - Not Synced - Eşitlenmedi - - - - Not Synced (%1) - %1 is the number of not synced files. - Eşitlenmedi (%1) - - - - OCC::ActivityWidget - - - Form - Form - - - - - - TextLabel - MetinEtiketi - - - - Filter - Filtre - - - - Server Activities - Sunucu Etkinlikleri - - - - Notifications - Bildirimler - - - - %n notifications(s) for %1. - %1 için %n bildirim var.%1 için %n bildirim var. - - - - %n notifications(s) for %1 and %2. - %1 ve %2 için %n bildirim var.%1 ve %2 için %n bildirim var. - - - - New notifications for %1, %2 and other accounts. - %1, %2 ve diğer hesaplar için yeni bildirim - - - - Open the activity view for details. - Ayrıntılar için etkinlik görünümünü açın. - - - - Copy to clipboard - Panoya kopyala - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/>%1 hesabının geçerli kılınmış etkinlikleri bulunmamakta. - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - Devam etmek <b>bu ayarları silmek anlamına gelir</b>. - - - - Continuing will mean <b>ignoring these settings</b>. - Devam etmek <b>bu ayarları yoksaymak anlamına gelir</b>. - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - Bazı ayarlar bu istemcinin daha yeni sürümlerinde yapılandırıldığından ötürü bu sürümde bulunmayan özellikleri kullanıyor. <br><br>%1 <br><br> Geçerli yapılandırma dosyası <i>%2</i> olarak yedeklendi. - - - - Quit - Çıkış - - - - Continue - Devam et - - - - Error accessing the configuration file - Konfigürasyon dosyasına erişimde hata - - - - There was an error while accessing the configuration file at %1. - Konfigürasyon dosyası %1 satırına erişimde hata - - - - Quit %1 - %1'tan çık - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - Yetkilendirme Gerekli - - - - Enter username and password for '%1' at %2. - Lütfen %2 üzerindeki, '%1' için kullanıcı adı ve parolayı girin. - - - - &User: - &Kullanıcı: - - - - &Password: - &Parola: - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Hiçbir ownCloud hesabı yapılandırılmamış - - - - The configured server for this client is too old - Yapılandırılmış sunucu, bu istemci için çok eski - - - - Please update to the latest server and restart the client. - Lütfen en son sunucuya güncelleyin veya istemciyi yeniden başlatın - - - - Authentication error: Either username or password are wrong. - Giriş hatası: Kullanıcı adı veya parola hatalı. - - - - timeout - zaman aşımı - - - - The provided credentials are not correct - Sağlanan kimlik bilgileri doğru değil - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - Sunucu hatası: PROPFIND cevabı XML formatında değil! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - Dizin %1 açılırken hata oluştu - - - - Directory not accessible on client, permission denied - İstemci klasöre erişemiyor, izin reddedildi. - - - - Directory not found: %1 - Dizin bulunamadı: %1 - - - - Filename encoding is not valid - Dosya adı kodlaması geçerli değil - - - - Error while reading directory %1 - Dizin %1 okunurken hata oluştu - - - - OCC::ExpandingHeaderView - - - Reset column sizes - - - - - OCC::Folder - - - Local folder %1 does not exist. - %1 yerel klasörü mevcut değil. - - - - %1 should be a folder but is not. - %1 bir dizin olmalı, ancak değil. - - - - %1 is not readable. - %1 okunabilir değil. - - - - %1 has been removed. - %1 names a file. - %1 kaldırıldı. - - - - %1 has been updated. - %1 names a file. - %1 güncellendi. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1, %2 olarak adlandırıldı. - - - - %1 has been moved to %2. - %1, %2 konumuna taşındı. - - - - %1 and %n other file(s) have been removed. - %1 ve diğer %n dosya kaldırıldı.%1 ve diğer %n dosya kaldırıldı. - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - %1 ve %n diğer dosya eklendi.%1 ve %n diğer dosya eklendi. - - - - %1 has been added. - %1 names a file. - %1 eklendi. - - - - %1 and %n other file(s) have been updated. - '%1' ve diğer %n dosya güncellendi.%1 ve diğer %n dosya güncellendi. - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1, %2 olarak yeniden adlandırıldı ve %n diğer dosyanın adı değiştirildi.%1, %2 olarak yeniden adlandırıldı ve %n diğer dosyanın adı değiştirildi. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1, %2 konumuna taşındı ve %n diğer dosya taşındı.%1, %2 konumuna taşındı ve %n diğer dosya taşındı. - - - - %1 has and %n other file(s) have sync conflicts. - %1 ve %n diğer dosya eşitleme çakışması bulunduruyor.%1 ve %n diğer dosya eşitleme çakışması bulunduruyor. - - - - %1 has a sync conflict. Please check the conflict file! - %1 bir eşitleme çakışması bulunduruyor. Lütfen çakışan dosyayı kontrol edin! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 ve diğer %n dosya hatalar nedeniyle eşlenemedi. Ayrıntılar için ayıt dosyasına bakın.%1 ve diğer %n dosya hatalar nedeniyle eşlenemedi. Ayrıntılar için günlük dosyasına bakın. - - - - %1 could not be synced due to an error. See the log for details. - %1 bir hata nedeniyle eşitlenemedi. Ayrıntılar için günlüğe bakın. - - - - Sync Activity - Eşitleme Etkinliği - - - - Could not read system exclude file - Sistem hariç tutulma dosyası okunamadı - - - - A new folder larger than %1 MB has been added: %2. - - %1 MB dan büyük yeni klasör eklendi: %2 - - - - - A folder from an external storage has been added. - - Harici depolama ünitesinden bir klasör eklendi. - - - - - Please go in the settings to select it if you wish to download it. - İndirmek isterseniz, lütfen "Ayarlar" bölümünden seçin. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - %1 klasörü yaratıldı ama daha önce senkronizasyondan hariç tutuldu. İçerik eşitlenmeyecek. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - %1 dosyası yaratıldı ama daha önce senkronizasyondan hariç tutuldu, İçerik eşitlenmeyecek. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - Senkronize edilen klasörlerdeki değişiklikler tam olarak izlenemedi. - -Bu yüzden senkronizasyon yerel değişiklikler anlık olarak karşı tarafa yükleyemeyecek ve sadece yereldeki değişimler taranacak ve belli periyotlarda karşı tarafa yükleyecek (varsayılan ayar 2 saatte bir). - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Senkronizasyon klasörlerinden '%1' klasörü, sunucudan silindi. -Bu silinmeler yereldeki klasörlerinize de senkronizasyonla yansıyacak, geri yükleme hakkınız olmadığı sürece dosyalar kullanım dışıdır. -Dosyalar silinmesin istiyorsanız, yetkiniz olduğu sürece sunucu ile tekrar senkronize olacaklardır. -Silmeye karar verdiyseniz, sahibi olana kadar dosyalar kullanım dışıdır. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Senkronizasyon klasörlerinden '%1' içindeki tüm dosyalar, yerelden silindi. Bu silinmeler sunucuya senkronizasyonla yansıyacak, geri yüklenmediği sürece dosyalar kullanım dışıdır. -Bu işlemlerin sunucuda da gerçekleşmesini istiyor musunuz? -Eğer istenmeden bu durum gerçekleştiyse ve dosyaları tutmak istiyorsanız, dosyalar sunucudan tekrar senkronize edilecektir. - - - - Remove All Files? - Tüm Dosyalar Kaldırılsın mı? - - - - Remove all files - Tüm dosyaları kaldır - - - - Keep files - Dosyaları koru - - - - OCC::FolderMan - - - Could not reset folder state - Klasör durumu sıfırılanamadı - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Eski eşitleme günlüğü '%1' bulundu ancak kaldırılamadı. Başka bir uygulama tarafından kullanılmadığından emin olun. - - - - (backup) - (yedek) - - - - (backup %1) - (yedek %1) - - - - Undefined State. - Tanımlanmamış Durum. - - - - Waiting to start syncing. - Eşitlemenin başlanması bekleniyor. - - - - Preparing for sync. - Eşitleme için hazırlanıyor. - - - - Sync is running. - Eşitleme çalışıyor. - - - - Sync was successful, unresolved conflicts. - Senkronizasyon başarılıydı, çözüm gerektiren eşleşmeler var. - - - - Last Sync was successful. - Son Eşitleme başarılı oldu. - - - - Setup Error. - Kurulum Hatası. - - - - User Abort. - Kullanıcı İptal Etti. - - - - Sync is paused. - Eşitleme duraklatıldı. - - - - %1 (Sync is paused) - %1 (Eşitleme duraklatıldı) - - - - No valid folder selected! - Geçerli klasör seçilmedi! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - Seçilen dosya yolu mevcut değil! - - - - The selected path is not a folder! - Seçilen yol bir klasör değil! - - - - You have no permission to write to the selected folder! - Seçilen klasöre yazma izniniz yok! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Yerel klasörde bir sunucu senkronizasyonu zaten mevcut. Lütfen başka bir yerel klasör seçin! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - %1 yerel klasörü zaten bir eşitleme klasörü içermektedir. Lütfen farklı bir seçim yapın! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - %1 yerel klasörü zaten bir eşitleme klasörü içindedir. Lütfen farklı bir seçim yapın! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Klasör Eşitleme Bağlantısı Ekle - - - - File - Dosya - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Bir klasör eklemek için bağlı olmanız gerekir - - - - Click this button to add a folder to synchronize. - Bir klasörü eşitlemeye dahil etmek için bu düğmeye tıklayın. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Sunucudan klasörlerin listesi yüklenirken hata oluştu. - - - - Virtual file support is enabled. - Sanal dosya sistemi desteği etkinleştirildi. - - - - Signed out - Oturum sonlandırıldı - - - - Fetching folder list from server... - Sunucudan klasör listesi alınıyor... - - - - There are unresolved conflicts. Click for details. - Çözüm gereken eşleşmeler var. Detaylar için tıklayın. - - - - Checking for changes in remote '%1' - Uzaktaki '%1' için değişiklikler kontrol ediliyor. - - - - Checking for changes in local '%1' - Yereldeki '%1' için değişiklikler kontrol ediliyor. - - - - Reconciling changes - Değişikliklerin bağlama kavuşturulması - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - %1 eşitleniyor - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - indirme %1/s - - - - ↓ %1/s - ↓ %1/sn - - - - ↑ %1/s - ↑ %1/sn - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - gönderme %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3/%4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - Kalan %5, %1/%2, dosya %3/%4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1/%2, %3/%4 dosya - - - - file %1 of %2 - dosya %1/%2 - - - - Waiting... - Bekleniyor... - - - - Waiting for %n other folder(s)... - Diğer %n klasör bekleniyor...Diğer %n klasör bekleniyor... - - - - Preparing to sync... - Eşitleme için hazırlanıyor... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - İzleyici bir test bildirimi almamıştır. - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - Bu problem genellikle "inotfy" yoğunlaştığında meydana gelir . SSS üzerinden detayları inceleyin. - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Klasör Eşitleme Bağlantısı Ekle - - - - Add Sync Connection - Eşitleme Bağlantısı Ekle - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Eşitlemek için yerel bir klasör seçmek üzere tıklayın. - - - - Enter the path to the local folder. - Yerel klasörün yolunu girin. - - - - Select the source folder - Kaynak klasörünü seçin - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Uzak Klasör Oluştur - - - - Enter the name of the new folder to be created below '%1': - '%1' altında oluşturulacak yeni klasörün adını girin: - - - - Folder was successfully created on %1. - Klasör %1 üzerinde başarıyla oluşturuldu. - - - - Authentication failed accessing %1 - %1 erişimi için giriş başarısız - - - - Failed to create the folder on %1. Please check manually. - %1 üzerinde klasör oluşturma başarısız. Lütfen elle denetleyin. - - - - Failed to list a folder. Error: %1 - Bir klasörün listelenmesi başarısız oldu. Hata: %1 - - - - Choose this to sync the entire account - Tüm hesabı eşitlemek için bunu seçin - - - - This folder is already being synced. - Bu klasör zaten eşitleniyor. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - <i>%1</i> zaten eşitleniyor. Bu, <i>%2</i> klasörünün üst klasörü. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - <i>%2</i> klasörünün alt klasörü olan <i>%1</i> klasörünü zaten eşitliyorsunuz. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - Use virtual files instead of downloading content immediately%1 - - - - (experimental) - (experimental) - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Uyarı:</b> %1 - - - - <b>Warning:</b> - <b>Uyarı:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Sunucudan E-Tag alınamadı, Vekil Sunucu/Ağ Geçidi'ni denetleyin. - - - - We received a different E-Tag for resuming. Retrying next time. - Devam etmek üzere farklı bir E-Etiket aldık. Sonraki işlemde yeniden denenecek. - - - - We received an unexpected download Content-Length. - Beklenmeyen bir içerik veya uzunlukta indirme alındı. - - - - Server returned wrong content-range - Sunucu yanlış içerik aralığı döndürdü - - - - OCC::GETJob - - - Connection Timeout - Bağlantı Zaman Aşımı - - - - OCC::GeneralSettings - - - Form - Form - - - - General Settings - Genel Ayarlar - - - - Language - Dil - - - - Sync hidden files - Gizli dosyaları eşitle - - - - Log Settings - Log Settings - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - - - - - Updates - Güncellemeler - - - - stable - stabil - - - - beta - beta - - - - About - Hakkında - - - - Advanced - Gelişmiş - - - - Use &Monochrome Icons in the system tray - Use &Monochrome Icons in the system tray - - - - Ask for confirmation before synchronizing folders larger than - Belirtilen boyuttan daha büyük klasörler için onay iste - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Harici depolamalardan senkronizasyon yapmadan onay al - - - - &Launch on System Startup - &Sistem Başlangıcında Çalıştır - - - - Show &Desktop Notifications - Masaüstü &Bildirimlerini Göster - - - - &Update Channel - Güncelleme Kanalı - - - - Edit &Ignored Files - Yoksayılan &Dosyaları Düzenle - - - - Show sync folders in &Explorer's Navigation Pane - &Explorer Navigasyon Panelinde senkronizasyon klasörlerini göster - - - - S&how crash reporter - Ç&ökme bildiricisini göster - - - - &Restart && Update - &Yeniden Başlat ve Güncelle - - - - You cannot disable autostart because system-wide autostart is enabled. - Otomatik başlatmayı kapatamazsınız, çünkü sistem bütününde aktif edildi. - - - - Change update channel? - Güncelleme kanalı değiştirilsin mi? - - - - Warning - Uyarı - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - Güncelleme kanalı, kurulum için hangi istemci güncellemelerinin sunulacağını belirler. "Kararlı" kanal yalnızca güvenilir kabul edilen yükseltmeleri içerirken "Beta" kanalındaki sürümler daha yeni özellikler ve hata düzeltmeleri içerebilir, ancak henüz tam olarak test edilmemiştir. - -Bunun yalnızca hangi güncelleme kanalından alınacağını seçtiğini ve herhangi bir sürüm indirgeme olmadığını seçtiğini unutmayın. Bu nedenle beta kanalından kararlı kanala geri dönmek genellikle hemen yapılamaz ve bu, şu anda yüklü olan betadan daha yeni kararlı bir sürümü beklemek anlamına gelir. - - - - Change update channel - Güncelleme kanalını değiştir - - - - Cancel - İptal - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - %1, lütfen şifrenizi girin:<br><br>Kullanıcı: %2<br>Hesap: %3<br> - - - - Reading from keychain failed with error: '%1' - Anahtar zinciri okuması hatayla sonuçlandı: '%1' - - - - Enter Password - Parolayı Girin - - - - <a href="%1">Click here</a> to request an app password from the web interface. - Web arayüzünden uygulama şifresi talep etmek için<a href="%1">lütfen tıklayınız</a> - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Yoksayılan Dosya Düzenleyici - - - - Files Ignored by Patterns - Desene Göre Yoksayılan Dosyalar - - - - Add - Ekle - - - - Pattern - Desen - - - - Allow Deletion - Silmeye İzin Ver - - - - Remove - Kaldır - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - Bir kalıpla eşleşen dosya veya klasörler eşitlenmez. Değişiklikler klasörlerin bir sonraki eşitlenmesinde geçerli olur. - -Bir dizinin kaldırılmasını engelliyorlarsa işaretli ögeler de silinecektir. Bu, üst veri için kullanışlıdır. - - - - Could not open file - Dosya açılamadı - - - - Cannot write changes to '%1'. - Değişiklikler '%1' üzerine yazılamıyor. - - - - Add Ignore Pattern - Yoksayma Deseni Ekle - - - - Add a new ignore pattern: - Yeni bir yoksayma deseni ekle: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Bu girdi '%1' üzerinde sistem tarafından sağlanmaktadır ve bu görünümde değiştirilemez. - - - - OCC::IssuesWidget - - - Form - Form - - - - List of issues - Sorunların listesi - - - - Filter - Filtre - - - - There were too many issues. Not all will be visible here. - Çok fazla sorun mevcut. Hepsi burada görünmeyecek. - - - - There were conflicts. Check the documentation on how to resolve them. - Çakışmalar mevcut. Nasıl çözüleceğini dokümantasyondan kontrol edin. - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Çakışmalar mevcut. <a href="%1">Nasıl çözüleceğini dokümantasyondan kontrol edin.</a> - - - - Account - Hesap - - - - OCC::LibCloudProvidersPrivate - - - Settings - Ayarlar - - - - OCC::LogBrowser - - - Log Output - Günlük Çıktısı - - - - Enable logging to temporary folder - Günlüğü geçici klasöre kaydetmeyi etkinleştir - - - - Delete logs older than %1 hours - %1 saatten eski günlükleri sil - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - Bu ayarlar istemci yeniden başlatmalarında devam eder. -Günlüğe kaydetme komut satırı seçeneklerinin kullanılmasının bu ayarları geçersiz kılacağını unutmayın. - - - - Open folder - Klasörü aç - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - Yeni Sürüm Mevcut - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>%1 İstemcisi yeni sürümü mevcut.</p><p><b>%2</b> artık indirilebilir. Kurulu sürüm %3.</p> - - - - - Skip this version - Bu sürümü atla - - - - Skip this time - Bu kez atla - - - - Get update - Güncellemeyi al - - - - Update Failed - Güncelleme Başarısız - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>%1 İstemcisinin yeni bir sürümü mevcut ancak güncelleme başarısız oldu.</p><p><b>%2</b> indirildi. Yüklü sürüm %3.</p> - - - - Ask again later - Sonra tekrar deneyin - - - - Restart and update - Yeniden başlat ve güncelle - - - - Update manually - Elle güncelle - - - - OCC::NetworkSettings - - - Form - Form - - - - Proxy Settings - Vekil Sunucu Ayarları - - - - No Proxy - Vekil sunucu yok - - - - Use system proxy - Sistem vekil sunucusunu kullan - - - - Specify proxy manually as - Vekil sunucusunu el ile belirle: - - - - Host - Sunucu - - - - : - : - - - - Proxy server requires authentication - Vekil sunucusu kimlik doğrulama gerektiriyor - - - - Note: proxy settings have no effects for accounts on localhost - Not: Proxy ayarlarının localhost üzerindeki hesaplar için hiçbir etkisi yoktur - - - - Download Bandwidth - İndirme Bant Genişliği - - - - - Limit to - Şuna sınırla: - - - - - KBytes/s - KBayt/s - - - - - No limit - Sınır yok - - - - - Limit to 3/4 of estimated bandwidth - Tahmini bant genişliğinin 3/4'ü ile sınırla - - - - Upload Bandwidth - Gönderme Bant Genişliği - - - - - Limit automatically - Otomatik sınırla - - - - Hostname of proxy server - Vekil sunucu makine adı - - - - Username for proxy server - Vekil sunucu kullanıcı adı - - - - Password for proxy server - Vekil sunucu parolası - - - - HTTP(S) proxy - HTTP(S) vekil sunucusu - - - - SOCKS5 proxy - SOCKS5 vekil sunucusu - - - - OCC::NotificationWidget - - - Created at %1 - Oluşturulma %1 - - - - Closing in a few seconds... - Saniyeler içinde kapatılıyor... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 isteği %2 üzerinde başarısız oldu - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - %2 üzerinde '%1' seçildi - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Sunucudan hata döndü: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - 'token' uç noktası erişimi sırasında hata meydana geldi: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - Sunucudan gelen JSON ayrıştırılamadı: <br><em>%1</em> - - - - Unsupported token type: %1 - Unsupported token type: %1 - - - - The reply from the server did not contain all expected fields -:%1 - The reply from the server did not contain all expected fields -:%1 - - - - Unknown Error - Unknown Error - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Sisteme Giriş Hatası</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - Error while refreshing the token: %1 : %2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Yanlış kullanıcı</h1><p>Sisteme giriş yapmış olan kullanıcı<em>%1</em>, ama giriş yapması gereken kullanıcı <em>%2</em>.<br>Lütfen, başka bir sekmedeki %3 üzerinden sistemden çıkın, sonra <a href='%4'>buraya tıklayın </a> ve %2 olarak giriş yapın</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Yeni %1 Güncellemesi Hazır - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - %1 için yeni bir güncelleme yüklenmek üzere. İşlem sürerken güncelleyici başka yetkiler isteyebilir. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Güncelleme indirilemedi. Lütfen güncellemeyi elle indirmek için <a href='%1'>buraya</a> tıklayın. - - - - Could not check for new updates. - Yeni güncellemeler denetlenemedi. - - - - Downloading %1. Please wait... - %1 yükleniyor. Lütfen bekleyin... - - - - %1 available. Restart application to start the update. - %1 sürümü mevcut. Güncellemeyi başlatmak için uygulamayı yeniden başlatın. - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - Yeni %1 sürümü mevcut. Lütfen kurulum için sistem güncelleştirme aracını kullanın. - - - - Checking update server... - Güncelleme sunucusu denetleniyor... - - - - Update status is unknown: Did not check for new updates. - Güncelleme durumu bilinmiyor: Yeni güncellemeler denetlenemedi. - - - - No updates available. Your installation is at the latest version. - Güncelleme yok. Kurulumunuz son sürümde. - - - - Update Check - Güncelleme Denetimi - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - %1 bağlantısını yap - - - - Setup local folder options - Yerel klasör seçeneklerini ayarla - - - - Connect... - Bağlan... - - - - (experimental) - (experimental) - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - '%1' klasörünü eşitle - - - - Virtual files are not available for the selected folder - - - - - Error - Hata - - - - Local Sync Folder - Yerel Eşitleme Klasörü - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &E-posta - - - - Connect to %1 - %1 bağlantısını yap - - - - Enter user credentials - Kullanıcı kimlik bilgilerini gir - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - %1 bağlantısını yap - - - - Login in your browser - Tarayıcından giriş yap - - - - Copy link to clipboard - Bağlantıyı panoya kopyala - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - %1 bağlantısını yap - - - - Setup %1 server - %1 sunucusunu ayarla - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Bu adres şifrelenmediğinden güvenli DEĞİL. -Kullanmanız önerilmez. - - - - This url is secure. You can use it. - Bu adres güvenli. Kullanabilirsiniz. - - - - &Next > - &İleri > - - - - Invalid URL - Geçersiz URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - %2 üzerinde %1 adresine bağlanılamadı:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - %2 üzerinde %1 bağlantısı yapılırken zaman aşımı. - - - - Invalid URL - Geçersiz URL - - - - Could not create local folder %1 - %1 yerel klasörü oluşturulamadı - - - - Error: %1 - Hata: %1 - - - - The folder creation resulted in HTTP error code %1 - Klasör oluşturma %1 HTTP hata kodu ile sonuçlandı - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Uzak klasör oluşturması, geçersiz kimlik bilgileri nedeniyle başarısız!<br/>Lütfen geri gidin ve bilgileri denetleyin.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Uzak klasör %1 oluşturma işlemi <tt>%2</tt> hatası ile başarısız oldu. - - - - Folder rename failed - Klasör adlandırma başarısız - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Klasör veya içerisindeki bir dosya farklı bir program içerisinde açık olduğundan, kaldırma ve yedekleme işlemi yapılamıyor. Lütfen klasör veya dosyayı kapatıp yeniden deneyin veya kurulumu iptal edin. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Bağlantı Sihirbazı - - - - Enable experimental feature? - Deneysel özellik etkinleştirilsin mi? - - - - Enable experimental placeholder mode - Deneysel yer tutucu modunu etkinleştir - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - "Sanal dosyalar" modu etkinleştirildiğinde, başlangıçta hiçbir dosya indirilmez. Bunun yerine, sunucuda bulunan her dosya için küçük bir "%1" dosyası oluşturulur. İçindekiler bu dosyalar çalıştırılarak veya içerik menüleri kullanılarak indirilebilir. - -Sanal dosyalar modu, seçici eşleme ile beraber çalışır. Şu anda seçili olmayan klasörler yalnızca çevrimiçi sanal klasörlere çevrilecek ve seçici senkronizasyon ayarlarınız sıfırlanacaktır. - -Bu moda geçmek, o anda çalışan eşlemeyi iptal edecektir. - -Bu yeni, deneysel bir moddur. Kullanmaya karar verirseniz, lütfen ortaya çıkan sorunları bildirin. - - - - Stay safe - Güvenli kal - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Sembolik bağlantılar eşitlemede desteklenmiyor. - - - - File is listed on the ignore list. - Dosya yoksayma listesinde. - - - - File names ending with a period are not supported on this file system. - Nokta ile biten dosya adları bu dosya sisteminde desteklenmez. - - - - File names containing the character '%1' are not supported on this file system. - '%1' karakterini içeren dosya adları bu dosya sisteminde desteklenmiyor. - - - - File name contains at least one invalid character - Dosya adı en az bir geçersiz karakter içeriyor - - - - The file name is a reserved name on this file system. - Dosya adı bu dosya sisteminde ayrılmış bir addır. - - - - Filename contains trailing spaces. - Dosya adı sonunda boşluk mevcut. - - - - Filename is too long. - Dosya adı çok uzun. - - - - File/Folder is ignored because it's hidden. - Dosya/Klasör gizli olduğu için yoksayıldı. - - - - Stat failed. - Durum alma başarısız. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Çakışma: Sunucu sürümü indirildi, yerel kopya yeniden adlandırıldı ve yüklenmedi. - - - - The filename cannot be encoded on your file system. - Dosya adı dosya sisteminizde görüntülenemeyecek karakterler içeriyor. - - - - The filename is blacklisted on the server. - Dosya adı sunucuda kara listeye alınmıştır. - - - - size - boyut - - - - permissions - izinler - - - - etag - e-etiket - - - - file id - dosya kimliği - - - - server reported no %1 - sunucu bildirimi no %1 - - - - Moved to invalid target, restoring - Geçersiz hedefe taşıma yapıldı, geri yükleniyor - - - - Not allowed to remove, restoring - Kaldırmaya izin verilmedi, geri alınıyor - - - - Ignored because of the "choose what to sync" blacklist - "Eşitlenecekleri seçin" kara listesinde olduğundan yoksayıldı. - - - - Not allowed because you don't have permission to add subfolders to that folder - Bu dizine alt dizin ekleme yetkiniz olmadığından izin verilmedi - - - - Not allowed because you don't have permission to add files in that folder - Bu klasöre dosya ekleme yetkiniz olmadığından izin verilmedi - - - - Not allowed to upload this file because it is read-only on the server, restoring - Bu dosya sunucuda salt okunur olduğu için yüklenemedi, geri alınıyor - - - - Error while reading the database - Veri tabanını okunurken hata oluştu. - - - - Server replied with an error while reading directory '%1' : %2 - Dizin '%1' okunurken sunucu hata cevabı verdi: %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - Dosyanın indirilmesi boş disk alanını belirlenen limitin altına düşürecektir. - - - - Free space on disk is less than %1 - Boş disk alanı %1 altında - - - - File was deleted from server - Dosya sunucudan silindi - - - - The file could not be downloaded completely. - Dosya tamamıyla indirilemedi. - - - - The downloaded file is empty despite the server announced it should have been %1. - Sunucu boyutunu %1 olarak duyurmasına rağmen indirilen dosya boş. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - Dosya, bulunduğundan itibaren değişmiş - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Geri Yükleme Başarısız: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - %1 dosyası silinemedi, hata: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - %1 klasörü oluşturulamadı - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Yerel bir dosya adı çakışması nedeniyle %1 dosyası %2 olarak adlandırılamadı - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - Pin durumu ayarlanırken hata oluştu - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Sunucudan yanlış HTTP kodu döndü. 204 bekleniyordu, ancak "%1 %2" geldi. - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Sunucudan yanlış HTTP kodu döndü. 201 bekleniyordu, ancak "%1 %2" geldi. - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - %1, %2 şeklinde yeniden isimlendirilemedi, hata: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Sunucudan yanlış HTTP kodu döndü. 201 bekleniyordu, ancak "%1 %2" geldi. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - Veritabanına üstveri yazma hatası - - - - Error setting pin state - Pin durumu ayarlanırken hata oluştu - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - %1 dosyası karşı tarafa yüklenemez, çünkü aynı isimde dosya zaten mevcut. - - - - File Removed - Dosya Kaldırıldı - - - - Local file changed during syncing. It will be resumed. - Eşitleme sırasında yerel dosya değişti. Devam edilecek. - - - - Local file changed during sync. - Eşitleme sırasında yerel dosya değişti. - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - %1 karşı tarafa yüklenmesi klasörün kotasını aşıyor. - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Eşitleme sırasında yerel dosya kaldırıldı. - - - - Local file changed during sync. - Eşitleme sırasında yerel dosya değişti. - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - (%1) Sunucusundan bilinmeyen dönüş kodu - - - - Missing File ID from server - Dosya ID sunucuda eksik - - - - Missing ETag from server - ETag sunucuda eksik - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Eşitleme sırasında yerel dosya kaldırıldı. - - - - Local file changed during sync. - Eşitleme sırasında yerel dosya değişti. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - Eşitleme sırasında yerel dosya kaldırıldı. - - - - Local file changed during sync. - Eşitleme sırasında yerel dosya değişti. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Sunucu son yığını onaylamadı. (Mevcut e-etiket bulunamadı) - - - - OCC::ProtocolItemModel - - - Time - Zaman - - - - File - Dosya - - - - Folder - Klasör - - - - Issues - - - - - Action - Eylem - - - - Size - Boyut - - - - Account - Hesap - - - - Status - Durum - - - - OCC::ProtocolWidget - - - Form - Form - - - - Local sync protocol - Yerel eşitleme protokolü - - - - Filter - Filtre - - - - Account - Hesap - - - - Copy to clipboard - Panoya kopyala - - - - Show in file browser - - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Vekil sunucu kimlik doğrulama gerektiriyor - - - - Username: - Kullanıcı Adı: - - - - Proxy: - Vekil Sunucu: - - - - The proxy server needs a username and password. - Vekil sunucu bir kullanıcı adı ve parolası gerektiriyor. - - - - Password: - Parola: - - - - TextLabel - MetinEtiketi - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Ne Eşitleneceğini Seçin - - - - OCC::SelectiveSyncWidget - - - Loading ... - Yükleniyor... - - - - Deselect remote folders you do not wish to synchronize. - Senkronize etmek istemediğiniz uzak klasörlerin seçimini kaldırın - - - - Name - Ad - - - - Size - Boyut - - - - - No subfolders currently on the server. - Sunucuda şu anda alt dizin bulunmuyor. - - - - An error occurred while loading the list of sub folders. - Alt klasör listesi alınırken bir hata oluştu. - - - - OCC::ServerNotificationHandler - - - Dismiss - İptal et - - - - OCC::SettingsDialog - - - Settings - Ayarlar - - - - Activity - Etkinlik - - - - Add account - Hesap ekle - - - - - Quit %1 - %1'tan çık - - - - Are you sure you want to quit %1? - %1 çıkmak istediğinizden emin misiniz? - - - - Account - Hesap - - - - MainWindow - MainWindow - - - - Hide - Gizle - - - - OCC::ShareDialog - - - TextLabel - MetinEtiketi - - - - share label - paylaşma etiketi - - - - Dialog - Diyalog - - - - ownCloud Path: - ownCloud Yolu: - - - - %1 Sharing - %1 Paylaşma - - - - %1 - %1 - - - - Folder: %2 - Klasör: %2 - - - - The server does not allow sharing - Sunucu, paylaşımı desteklemiyor - - - - Retrieving maximum possible sharing permissions from server... - Sunucudan mümkün olan en fazla paylaşım hakları alınıyor... - - - - The file can not be shared because it was shared without sharing permission. - Dosya paylaşılamaz, çünkü sizinle paylaşım izni olmaksızın paylaşılmış. - - - - Users and Groups - Kullanıcılar ve Gruplar - - - - Public Links - Herkese açık bağlantılar - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - YeniBelge.odt dosyasını paylaş - - - - Upload only (File Drop) - Sadece karşı tarafa yükle (Dosya bırak) - - - - Receive files from others without revealing the contents of the folder. - Klasörün içeriğini göstermeden başkalarından dosya alın. - - - - Download / View / Edit - İndir / Görüntüle / Düzenle - - - - Name: - Ad: - - - - Enter name of new share... - Yeni paylaşımın ismini girin... - - - - &Create - &Oluştur - - - - TextLabel - MetinEtiketi - - - - Set &password - &Parola belirle - - - - Set &expiration date - Son kullanma tarihi b&elirle - - - - Set password - Parolayı ayarla - - - - Recipients can view or download contents. - Alıcılar içeriği görüntüleyebilir ve indirebilir - - - - Download / View - İndir / Görüntüle - - - - Recipients can view, download, edit, delete and upload contents. - Alıcılar içerik görüntüleyebilir, düzenleyebilir, silebilir ve karşı tarafa yükleyebilir. - - - - Anyone with the link has access to the file/folder - Dosya/klasör linkine sahip herkes erişebilir - - - - - P&assword protect - &Parola koruması - - - - The file can not be shared because it was shared without sharing permission. - Dosya paylaşılamaz, çünkü sizinle paylaşım izni olmaksızın paylaşılmış. - - - - Link shares have been disabled - Bağlantı paylaşımı devre dışı bırakıldı - - - - Create public link share - Herkese açık bağlantı paylaşımı oluştur - - - - - Delete - Sil - - - - Open link in browser - Bağlantıyı tarayıcıda aç - - - - Copy link to clipboard - Linki panoya kopyala - - - - Copy link to clipboard (direct download) - Bağlantıyı panoya kopyala (doğrudan indirme) - - - - Send link by email - Bağlantıyı e-posta ile gönder - - - - Send link by email (direct download) - Bağlantıyı e-postayla gönder (doğrudan indirme) - - - - Create new... - Yeni oluştur... - - - - I shared %1 with you - Seninle %1 paylaştım - - - - Confirm Link Share Deletion - Paylaşım Bağlantısını Silmeyi Onaylayın - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Paylaşım bağlantısını gerçekten silmek istiyor musun <i>%1</i>?</p><p>Not: Bu işlem geri alınamaz.</p> - - - - Cancel - İptal - - - - - Public link - Herkese açık bağlantı - - - - Delete link share - Bağlantı paylaşımını sil - - - - - Please Set Password - Lütfen Parola Atayın - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - YeniBelge.odt dosyasını paylaş - - - - Share with users or groups ... - Kullanıcı ve gruplarla paylaş... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p> <a href="private link menu"><span style=" text-decoration: underline">Kişilere gizli bir bağlantı vererek</span></a> bu paylaşılmış dosya veya klasöre yönlendirebilirsiniz.</p></body></html> - - - - The item is not shared with any users or groups - Bu öge hiçbir kullanıcı veya grupla paylaşılmıyor - - - - Open link in browser - Bağlantıyı tarayıcıda aç - - - - Copy link to clipboard - Linki panoya kopyala - - - - Send link by email - Bağlantıyı e-posta ile gönder - - - - No results for '%1' - '%1' için sonuç yok - - - - I shared something with you - Seninle bir şey paylaştım - - - - OCC::ShareUserLine - - - Form - Form - - - - - TextLabel - MetinEtiketi - - - - can edit - düzenleyebilir - - - - can share - paylaşabilir - - - - ... - ... - - - - create - oluştur - - - - change - değiştir - - - - delete - sil - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - %1 ile paylaş - - - - Context menu share - İçerik menüsü paylaşımı - - - - Context menu share %1 - İçerik menüsü paylaşımı %1 - - - - Confirm deletion - Silmeyi onayla - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - <i>%1</i> dizinini ve tüm içeriğini kalıcı olarak silmek istiyor musunuz? - - - - Do you want to delete the file <i>%1</i> permanently? - <i>%1</i> dosyasını kalıcı olarak silmek istiyor musunuz? - - - - Select new location... - Yeni konum seçin... - - - - Error - Hata - - - - Moving file failed: - -%1 - Dosya taşınamadı: - -%1 - - - - I shared something with you - Seninle bir şey paylaştım - - - - - Share... - Paylaş... - - - - - Copy private link to clipboard - Özel bağlantıyı panoya kopyala - - - - Send private link by email... - Özel bağlantıyı e-posta ile gönder... - - - - Resharing this file is not allowed - Bu dosyanın yeniden paylaşılmasına izin verilmiyor - - - - Resharing this folder is not allowed - Bu klasörün yeniden paylaşılmasına izin verilmiyor - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - Herkese açık bağlantıyı panoya kopyala - - - - Open in browser - Tarayıcıda aç - - - - Show file versions in browser - Dosya sürümlerini tarayıcıda göster - - - - Rename... - Yeniden adlandır... - - - - Rename and upload... - Yeniden adlandır ve yükle... - - - - Move and rename... - Taşı ve yeniden adlandır... - - - - Move, rename and upload... - Taşı, yeniden adlandır ve yükle... - - - - Delete local changes - Yerel değişiklikleri sil - - - - Move and upload... - Taşı ve yükle... - - - - Delete - Sil - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Sertifika Ayrıntıları</h3> - - - - Common Name (CN): - Ortak Ad (CN): - - - - Subject Alternative Names: - Konu Alternatif İsimleri: - - - - Organization (O): - Kurum (O): - - - - Organizational Unit (OU): - Kuruluş Birimi (OU): - - - - State/Province: - Bölge/İlçe: - - - - Country: - Ülke: - - - - Serial: - Seri No: - - - - <h3>Issuer</h3> - <h3>Veren</h3> - - - - Issuer: - Veren: - - - - Issued on: - Verilme: - - - - Expires on: - Bitiş: - - - - <h3>Fingerprints</h3> - <h3>Parmak İzleri</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Not:</b> Bu sertifika el ile onaylandı</p> - - - - %1 (self-signed) - %1 (kendinden imzalı) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Bu bağlantı %1 bit %2 kullanılarak şifrelenmiştir. - - - - - Server version: %1 - Sunucu sürümü: %1 - - - - No support for SSL session tickets/identifiers - SSL oturum biletleri/tanımlayıcıları desteklenmiyor - - - - Certificate information: - Sertifika bilgisi: - - - - The connection is not secure - Bu bağlantı güvenli değil - - - - This connection is NOT secure as it is not encrypted. - - Bu bağlantı şifrelenmediğinden güvenli DEĞİL. - - - - - OCC::SslErrorDialog - - - Form - Form - - - - Trust this certificate anyway - Yine de bu sertifikaya güven - - - - Untrusted Certificate - Güvensiz Sertifika - - - - Cannot connect securely to <i>%1</i>: - <i>%1</i> sunucusuna güvenli şekilde bağlanılamıyor: - - - - with Certificate %1 - Sertifika: %1 - - - - - - &lt;not specified&gt; - &lt;belirtilmemiş&gt; - - - - - Organization: %1 - Kurum: %1 - - - - - Unit: %1 - Birim: %1 - - - - - Country: %1 - Ülke: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Parmak izi (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Parmak izi (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Etkili Tarih: %1 - - - - Expiration Date: %1 - Son Kullanım Tarihi: %1 - - - - Issuer: %1 - Veren: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (daha önceki hatadan ötürü atlandı, %2 içinde tekrar deneniyor) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Sadece %1 mevcut, Çalıştırmak için en az %2 gerekmektedir - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Yerel eşleme veritabanı açılamıyor veya oluşturulamıyor. Eşleme klasöründe yazma iznine sahip olduğunuzdan emin olun. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Disk alanı az: Boş alanı %1 altına indirecek indirmeler atlandı. - - - - There is insufficient space available on the server for some uploads. - Bazı yüklemeler için sunucuda yeterli alan yok. - - - - Unresolved conflict. - Çözümlenmemiş çakışma mevcut. - - - - Using virtual files with suffix, but suffix is not set - Sanal dosyalar uzantılarla kullanılıyor, ancak henüz uzantı ayarlanmadı - - - - Unable to read the blacklist from the local database - Yerel veritabanından kara liste okunamadı - - - - Unable to read from the sync journal. - Eşitleme günlüğünden okunamadı. - - - - Cannot open the sync journal - Eşitleme günlüğü açılamıyor - - - - Aborted - İptal Edildi - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Sağlama toplam başlığı bozulmuş. - - - - The checksum header contained an unknown checksum type '%1' - Sağlama başlığı bilinmeyen '%1' sağlama tipi içeriyor - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Lütfen oturum açın - - - - Folder %1: %2 - Klasör %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - Yapılandırılmış eşitleme klasörü yok. - - - - Open in browser - Tarayıcıda aç - - - - - - Log in... - Giriş yap... - - - - - - Log out - Çıkış yap - - - - Recent Changes - Son Değişiklikler - - - - Managed Folders: - Yönetilen Klasörler: - - - - Open folder '%1' - '%1' klasörünü aç - - - - Open %1 in browser - %1'ı tarayıcıda aç - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - %1 hesabındaki sunucu desteklenmeyen %2 sürümünde. Bu istemciyi desteklenmeyen sunucu sürümlerinde kullanmak hem test edilmemiştir hem de tehlikelidir. Kullanmak kendi sorumluluğunuzdadır. - - - - Unknown status - Bilinmeyen durum - - - - Settings... - Ayarlar... - - - - Details... - Ayrıntılar... - - - - Help - Yardım - - - - Quit %1 - %1'tan çık - - - - Disconnected from %1 - %1 ile bağlantı kesildi - - - - Unsupported Server Version - Desteklenmeyen Sunucu Sürümü - - - - Disconnected - Bağlantı kesildi - - - - Disconnected from some accounts - Bazı hesapların bağlantısı kesildi - - - - Disconnected from accounts: - Bu hesapların bağlantıları kesildi: - - - - Account %1: %2 - Hesap %1: %2 - - - - Signed out - Oturum sonlandırıldı - - - - Account synchronization is disabled - Hesap eşitlemesi devre dışı bırakıldı. - - - - Synchronization is paused - Eşitleme duraklatıldı - - - - Up to date (%1) - - - - - No sync folders configured - Yapılandırılmış eşitleme klasörü yok - - - - Unpause all folders - Tüm klasörleri devam ettir - - - - Pause all folders - Tüm klasörleri durdur - - - - Unpause all synchronization - Tüm eşitlemeleri devam ettir - - - - Unpause synchronization - Eşitlemeyi devam ettir - - - - Pause all synchronization - Tüm eşitlemeleri durdur - - - - Pause synchronization - Eşitlemeyi durdur - - - - Log out of all accounts - Tüm hesaplardan çıkış yap - - - - Log in to all accounts... - Tüm hesaplara giriş yap... - - - - New account... - Yeni hesap... - - - - About %1 - %1 hakkında - - - - No items synced recently - Yakın zamanda eşitlenen öge yok - - - - Checking for changes in remote '%1' - Uzaktaki '%1' için değişiklikler kontrol ediliyor. - - - - Checking for changes in local '%1' - Yereldeki '%1' için değişiklikler kontrol ediliyor. - - - - Syncing %1 of %2 (%3 left) - Eşitlenen %1/%2 (%3 kaldı) - - - - Syncing %1 of %2 - %1 / %2 eşitleniyor - - - - Syncing %1 (%2 left) - Eşitlenen %1 (%2 kaldı) - - - - Syncing %1 - %1 eşitleniyor - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - Form - - - - - TextLabel - MetinEtiketi - - - - Server - Sunucu - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Eğer bu kutu işaretlenmişse, yerel dizindeki tüm içerik sunucudan temiz bir eşitleme yapılabilmesi için silinecektir.</p><p>Eğer yerel içeriğin sunucu dizininden güncellenmesini istemiyorsanız, bunu işaretlemeyin.</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - Bu değerden büyük klasörleri eşlemeden önce onay isteyin: - - - - S&ynchronize everything from server - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Harici depoları eşlemeden önce onay isteyin - - - - Choose what to sync - Ne eşitleneceğini seçin - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Bu seçenek seçildiğinde ayarlama sihirbazı hiçbir şey eşitlemeden kapanır. Hesap ayarlarından yerel ve uzak klasörü eşitlemek için &quot;Klasör Eşleme Bağlantısı Ekle&quot; seçeneğini kullanabilirsiniz</p></body></html> - - - - Manually create folder sync connections - El ile klasör eşitleme bağlantıları oluştur - - - - &Local Folder - &Yerel Klasör - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Yerel veriyi sakla - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - Form - - - - &Username - &Kullanıcı Adı - - - - &Password - &Parola - - - - OwncloudOAuthCredsPage - - - Form - Form - - - - Please switch to your browser to proceed. - Devam etmek için lütfen tarayıcınıza geçin. - - - - An error occurred while connecting. Please try again. - Bağlanırken bir hata oluştu. Lütfen tekrar deneyin. - - - - Re-open Browser - Tarayıcıyı Yeniden Aç - - - - OwncloudSetupPage - - - Form - Form - - - - Ser&ver Address - Sunucu Adresi - - - - https://... - https://... - - - - Error Label - Hata Etiketi - - - - QObject - - - in the future - gelecekte - - - - %n day(s) ago - %n gün önce%n gün önce - - - - %n hour(s) ago - %n saat önce%n saat önce - - - - now - şimdi - - - - Less than a minute ago - 1 dakika önce - - - - %n minute(s) ago - %n dakika önce%n dakika önce - - - - Some time ago - Bir süre önce - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - Ölümcül Hata - - - - - Error - Hata - - - - Info - Bilgi - - - - Success - Başarılı - - - - Conflict - Çakışma - - - - File Ignored - - - - - Restored - - - - - Blacklisted - Kara listede - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n yıl%n yıl - - - - %n month(s) - %n ay%n ay - - - - %n day(s) - %n gün%n gün - - - - %n hour(s) - %n saat%n saat - - - - %n minute(s) - %n dakika%n dakika - - - - %n second(s) - %n saniye%n saniye - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Sistem Çekmecesi kullanılamıyor - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 çalışan bir sistem çekmecesi gerektirir. Eğer XFCE kullanıyorsanız lütfen <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">bu talimatları</a> takip edin. Aksi halde 'trayer' benzeri bir sistem çekmece uygulaması kurun ve yeniden deneyin. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - Sanal dosya oluşturuldu - - - - Replaced by virtual file - Sanal dosya ile değiştirilmiştir - - - - Downloaded - İndirildi - - - - Uploaded - Yüklendi - - - - Server version downloaded, copied changed local file into conflict file - Sunucu sürümü indirildi, çakışan dosya içine değiştirilmiş yerel dosya kopyalandı - - - - Deleted - Silindi - - - - Moved to %1 - %1 konumuna taşındı - - - - Ignored - Yoksayıldı - - - - Filesystem access error - Dosya sistemi erişim hatası - - - - Error - Hata - - - - Updated local metadata - Yerel üstveri güncellendi - - - - - Unknown - Bilinmeyen - - - - downloading - indirilen: - - - - uploading - yüklenen: - - - - deleting - silinen: - - - - moving - taşınan: - - - - ignoring - yoksayılan: - - - - - error - hata - - - - updating local metadata - yerel üstveri güncelleniyor - - - - theme - - - Status undefined - Tanımsız durum - - - - Waiting to start sync - Eşitlemenin başlanması bekleniyor - - - - Sync is running - Eşitleme çalışıyor - - - - Sync Success - Eşitleme Başarılı - - - - Sync Success, some files were ignored. - Eşitleme Başarılı, bazı dosyalar yoksayıldı. - - - - Sync Error - Eşitleme Hatası - - - - Setup Error - Kurulum Hatası - - - - Preparing to sync - Eşitleme için hazırlanıyor - - - - Aborting... - İptal ediliyor... - - - - Sync is paused - Eşitleme duraklatıldı - - - - utility - - - Could not open browser - Tarayıcı açılamadı - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - %1 bağlantısına gitmek için tarayıcı başlatılırken bir hata oluştu. Herhangi bir varsayılan tarayıcı yapılandırılmamış olabilir. - - - - Could not open email client - E-posta istemcisi açılamadı - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Yeni mesaj oluşturmak için eposta istemcisini çalıştırıken bir hata oluştu. Belki varsayılan eposta istemcisi ayarlanmamıştır? - - - - Always available locally - Yerel olarak her zaman kullanılabilir - - - - Some available online only - Yalnızca bazıları çevrimiçi kullanılabilir - - - - - Available online only - Sadece çevrimiçi kullanılabilir - - - - Currently available locally - Şu anda yerel olarak kullanılabilir - - - - Make always available locally - Her zaman yerel olarak erişilebilir yapın - - - - Free up local space - Yerel kapasite alanında yer açın - - - \ No newline at end of file diff --git a/translations/client_uk.ts b/translations/client_uk.ts deleted file mode 100644 index a1b2d36ee53..00000000000 --- a/translations/client_uk.ts +++ /dev/null @@ -1,4419 +0,0 @@ - - - FileSystem - - - Could not make directories in trash - Неможливо створити теку в смітнику - - - - - Could not move '%1' to '%2' - Не вдалося перемістити '%1' в '%2' - - - - Moving to the trash is not implemented on this platform - Переміщення в смітник неможливе на цій платформі - - - - FolderWizardSourcePage - - - Form - Форма - - - - Pick a local folder on your computer to sync - Оберіть локальний каталог на Вашому ПК для синхронізації: - - - - &Choose... - &Вибір... - - - - FolderWizardTargetPage - - - Form - Форма - - - - Select a remote destination folder - Оберіть віддалену теку для зберігання даних - - - - Create Folder - Створити теку - - - - Refresh - Оновити - - - - Folders - Теки - - - - TextLabel - Мітка - - - - NotificationWidget - - - Form - Форма - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - Мітка - - - - OCC::AbstractNetworkJob - - - Connection timed out - Час очікування з'єднання вичерпано - - - - Unknown error: network reply was deleted - Незнайома помилка: відповідь мережі була видалена - - - - Server replied "%1 %2" to "%3 %4" - Сервер відповів "%1 %2" на "%3 %4" - - - - OCC::AccountSettings - - - Form - Форма - - - - ... - ... - - - - Storage space: ... - Простір сховища: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Не позначені теки будуть <b>видалені</b> з вашої системи та більше не будуть синхронізуватися - - - - Synchronize all - Синхронізувати все - - - - Synchronize none - Не синхронізувати нічого - - - - Apply manual changes - Застосувати ручні зміни - - - - Apply - Застосувати - - - - - - Cancel - Скасувати - - - - Connected with <server> as <user> - Підключено до <server> як <user> - - - - No account configured. - Обліковий запис не налаштовано. - - - - Add new - Додати - - - - Remove - Видалити - - - - Account - Обліковий запис - - - - Choose what to sync - Оберіть, що хочете синхронізувати - - - - Force sync now - Примусово синхронізувати зараз - - - - Restart sync - Перезапустити синхронізацію - - - - Remove folder sync connection - Видалити синхронізацію теки - - - - Create virtual files for new files (Experimental) - Створити віртуальні файли для нових файлів (Експриментально) - - - - Folder creation failed - Не вдалося створити теку - - - - <p>Could not create local folder <i>%1</i>. - <p>Не вдалося створити локальну теку <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Підтвердьте видалення синхронізації теки - - - - Remove Folder Sync Connection - Видалити синхронізацію теки - - - - Sync Running - Виконується синхронізація - - - - The syncing operation is running.<br/>Do you want to terminate it? - Виконується процедура синхронізації.<br/>Бажаєте зупинити? - - - - %1 in use - %1 використовується - - - - %1 as <i>%2</i> - %1 як <i>%2</i> - - - - Connected to %1. - Підключено до %1. - - - - Server %1 is temporarily unavailable. - Сервер %1 тимчасово недоступний. - - - - Server %1 is currently in maintenance mode. - Сервер %1 наразі в режимі технічного обслуговування - - - - Signed out from %1. - Вийшов із %1. - - - - Obtaining authorization from the browser. <a href='%1'>Click here</a> to re-open the browser. - - - - - Connecting to %1... - З'єднання з %1... - - - - No connection to %1 at %2. - - - - - Log in - Увійти - - - - There are folders that were not synchronized because they are too big: - - - - - There are folders that were not synchronized because they are external storages: - - - - - There are folders that were not synchronized because they are too big or external storages: - - - - - Confirm Account Removal - Підтвердіть видалення облікового запису - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - - - - Remove connection - Видалити з'єднання - - - - - Open folder - Відкрити каталог - - - - - Log out - Вихід - - - - Resume sync - Відновити синхронізацію - - - - Pause sync - Призупинити синхронізацію - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - Використовується %1 (%3%) з %2. Деякі теки, включаючи мережеві змонтовані чи спільні, можуть мати інші обмеження. - - - - %1 of %2 in use - Використовується %1 з %2 - - - - Currently there is no storage usage information available. - На даний час немає відомостей про наповнення сховища. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - No %1 connection configured. - Жодного %1 підключення не налаштовано. - - - - OCC::AccountState - - - Signed out - Вийшов - - - - Disconnected - Від'єднаний - - - - Connected - З'єднаний - - - - Service unavailable - Сервіс недоступний - - - - Maintenance mode - Режим технічного обслуговування - - - - Network error - Помилка мережі - - - - Configuration error - Помилка конфігурації - - - - Asking Credentials - Запит облікових даних - - - - Unknown account state - Невідомий стан облікового запису - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 на %2 - - - - %1 on %2 (disconnected) - %1 на %2 (від'єднаний) - - - - OCC::ActivitySettings - - - - Server Activity - Серверна активність - - - - Sync Protocol - Протокол Синхронізації - - - - Not Synced - Не синхронізовано - - - - Not Synced (%1) - %1 is the number of not synced files. - Not Synced (%1) - - - - The server activity list has been copied to the clipboard. - Список серверних операцій скопійовано до буферу обміну. - - - - The sync activity list has been copied to the clipboard. - Список подій синхронізації скопійовано до буферу обміну. - - - - The list of unsynced items has been copied to the clipboard. - - - - - Copied to clipboard - Скопійовано в буфер обміну - - - - OCC::ActivityWidget - - - Form - Форма - - - - - - TextLabel - Мітка - - - - Server Activities - Серверні операції - - - - Copy - Копіювати - - - - Copy the activity list to the clipboard. - Скопіювати протокол синхронізації до буферу обміну. - - - - Action Required: Notifications - Необхідна Дія: Сповіщення - - - - <br/>Account %1 does not have activities enabled. - - - - - You received %n new notification(s) from %2. - Ви отримали %n нове сповіщення від %2.Ви отримали %n нових сповіщень від %2.Ви отримали %n нових сповіщень від %2.Ви отримали %n нових сповіщень від %2. - - - - You received %n new notification(s) from %1 and %2. - Ви отримали %n нове сповіщення від %1 та %2.Ви отримали %n нових сповіщень від %1 та %2.Ви отримали %n нових сповіщень від %1 та %2.Ви отримали %n нових сповіщень від %1 та %2. - - - - You received new notifications from %1, %2 and other accounts. - Ви отримали нові сповіщення від %1, %2 та інших облікових записів. - - - - %1 Notifications - Action Required - %1 Сповіщень - Необхідна Дія - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Аутентифікація за допомогою SSL сертифікату користувача - - - - This server probably requires a SSL client certificate. - Цей сервер ймовірно вимагає SSL сертифікат користувача - - - - Certificate & Key (pkcs12) : - - - - - Browse... - Переглянути... - - - - Certificate password : - Пароль сертіфікату : - - - - Select a certificate - Оберіть сертифікат - - - - Certificate files (*.p12 *.pfx) - Файли сертіфікатів (*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Вийти - - - - Continue - Продовжити - - - - Error accessing the configuration file - Помилка доступу до файлу конфігурації - - - - There was an error while accessing the configuration file at %1. - - - - - Quit ownCloud - Вийти з ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - Потрібна аутентифікація - - - - Enter username and password for '%1' at %2. - Введіть ім'я та пароль для '%1' в %2. - - - - &User: - &Користувач: - - - - &Password: - &Пароль: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Помилка запису метаданих до бази даних - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Обліковий запис ownCloud не налаштовано - - - - The configured server for this client is too old - Налаштований сервер застарий для цього клієнта - - - - Please update to the latest server and restart the client. - Будь ласка, оновіть сервер до останньої версії та перезавантажте клієнт. - - - - Authentication error: Either username or password are wrong. - Помилка автентифікації: ім'я користувача або пароль невірні. - - - - timeout - час вичерпано - - - - The provided credentials are not correct - Введені дані не вірні - - - - OCC::DiscoveryMainThread - - - Aborted by the user - Скасовано користувачем - - - - OCC::DiscoverySingleDirectoryJob - - - The server file discovery reply is missing data. - - - - - OCC::Folder - - - Local folder %1 does not exist. - Локальна тека %1 не існує. - - - - %1 should be a folder but is not. - %1 повинна бути текою, але нею не є. - - - - %1 is not readable. - %1 не читається. - - - - %1 has been removed. - %1 names a file. - %1 видалено. - - - - %1 has been downloaded. - %1 names a file. - %1 завантажено. - - - - %1 has been updated. - %1 names a file. - %1 оновлено. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 перейменовано на %2 - - - - %1 has been moved to %2. - %1 переміщено в %2. - - - - %1 and %n other file(s) have been removed. - - - - - %1 and %n other file(s) have been downloaded. - - - - - %1 and %n other file(s) have been updated. - - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - - - - - %1 has been moved to %2 and %n other file(s) have been moved. - - - - - %1 has and %n other file(s) have sync conflicts. - - - - - %1 has a sync conflict. Please check the conflict file! - - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - - - - - %1 could not be synced due to an error. See the log for details. - %1 не може синхронізуватися через помилки. Дивіться деталі в журналі. - - - - Sync Activity - Журнал синхронізації - - - - Could not read system exclude file - Неможливо прочитати виключений системний файл - - - - A new folder larger than %1 MB has been added: %2. - - - - - - A folder from an external storage has been added. - - - - - - Please go in the settings to select it if you wish to download it. - - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - - - - - Remove All Files? - Видалити усі файли? - - - - Remove all files - Видалити усі файли - - - - Keep files - Зберегти файли - - - - This sync would reset the files to an earlier time in the sync folder '%1'. -This might be because a backup was restored on the server. -Continuing the sync as normal will cause all your files to be overwritten by an older file in an earlier state. Do you want to keep your local most recent files as conflict files? - - - - - Backup detected - Резервну копію знайдено - - - - Normal Synchronisation - - - - - Keep Local Files as Conflict - - - - - OCC::FolderMan - - - Could not reset folder state - Не вдалося скинути стан теки - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Знайдено старий журнал синхронізації '%1', його неможливо видалити. Будь ласка, впевніться що він не відкритий в іншій програмі. - - - - (backup) - (Резервна копія) - - - - (backup %1) - (Резервна копія %1) - - - - Undefined State. - Невизначений стан. - - - - Waiting to start syncing. - Очікування початку синхронізації. - - - - Preparing for sync. - Підготовка до синхронізації - - - - Sync is running. - Синхронізація запущена. - - - - Sync was successful, unresolved conflicts. - - - - - Last Sync was successful. - Остання синхронізація була успішною. - - - - Setup Error. - Помилка встановлення. - - - - User Abort. - Скасовано користувачем. - - - - Sync is paused. - Синхронізація призупинена. - - - - %1 (Sync is paused) - %1 (Синхронізація призупинена) - - - - No valid folder selected! - - - - - The selected path is not a folder! - - - - - You have no permission to write to the selected folder! - У вас немає прав на запис в цю теку! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - - - - - Synchronizing with local folder - Синхронізується з локальною текою - - - - File - Файл - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - - - - - Click this button to add a folder to synchronize. - - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - - - - - Signed out - Вийшов - - - - Fetching folder list from server... - - - - - There are unresolved conflicts. Click for details. - - - - - New files are being created as virtual files. - - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Reconciling changes - - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Синхронізація %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - завантаження %1/с - - - - u2193 %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - відвантаження %1/с - - - - u2191 %1/s - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 of %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - - - - - file %1 of %2 - файл %1 з %2 - - - - Waiting... - Очікування... - - - - Waiting for %n other folder(s)... - - - - - Preparing to sync... - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - - - - - Add Sync Connection - - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Натисніть, щоб обрати локальну теку для синхронізації. - - - - Enter the path to the local folder. - Введіть шлях до локальної теки. - - - - Select the source folder - Оберіть початкову теку - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Створити Віддалену Теку - - - - Enter the name of the new folder to be created below '%1': - Введіть ім'я нової теки, яка буде створена тут '%1': - - - - Folder was successfully created on %1. - Теку успішно створено на %1. - - - - Authentication failed accessing %1 - Помилка аутентифікації при доступі до %1 - - - - Failed to create the folder on %1. Please check manually. - Не вдалося створити теку на %1. Будь ласка, перевірте вручну. - - - - Failed to list a folder. Error: %1 - - - - - Choose this to sync the entire account - Оберіть це для синхронізації всього облікового запису - - - - This folder is already being synced. - Тека вже синхронізується. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Тека <i>%1</i> вже синхронізується, та вона є батьківською для <i>%2</i>. - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately (experimental) - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Попередження</b> %1 - - - - <b>Warning:</b> - <b>Попередження</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Не отримано E-Tag від серверу, перевірте мережеві налаштування (проксі, шлюз) - - - - We received a different E-Tag for resuming. Retrying next time. - Ми отримали інший E-Tag для відновлення. Спробуйте ще раз пізніше. - - - - Server returned wrong content-range - Сервер повернув невірний content-range - - - - Connection Timeout - Час з'єднання вичерпано - - - - OCC::GeneralSettings - - - Form - Форма - - - - General Settings - Загальні Налаштування - - - - For System Tray - - - - - stable - - - - - beta - - - - - Advanced - Додатково - - - - Ask for confirmation before synchronizing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - - - - - &Launch on System Startup - &Запускати при старті Системи - - - - Show &Desktop Notifications - Показувати &сповіщення - - - - Use &Monochrome Icons - Використовувати &монохромні піктограми - - - - Version - Версія - - - - &Update Channel - - - - - • - - - - - Edit &Ignored Files - Редагувати &ігноровані файли - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - - - - - &Restart && Update - &Перезавантаження && Оновлення - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Скасувати - - - - OCC::GetOrCreatePublicLinkShare - - - Sharing error - - - - - Could not retrieve or create the public link share. Error: - -%1 - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - - - - - Reading from keychain failed with error: '%1' - - - - - Enter Password - Введіть Пароль - - - - <a href="%1">Click here</a> to request an app password from the web interface. - - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Редактор ігнорованих файлів - - - - Global Ignore Settings - - - - - Sync hidden files - Синхронізувати приховані файли - - - - Files Ignored by Patterns - Файли ігноровані за шаблоном - - - - Add - Додати - - - - Pattern - Шаблон - - - - Allow Deletion - Дозволити видалення - - - - Remove - Видалити - - - - Files or folders matching a pattern will not be synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Не вдалося відкрити файл - - - - Cannot write changes to '%1'. - Неможливо запиасати зміни до '%1'. - - - - Add Ignore Pattern - Додати шаблон ігнорування - - - - Add a new ignore pattern: - Додати новий шаблон ігнорування: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Цей запис створено системою в '%1', тут змінити його не можна. - - - - OCC::IssuesWidget - - - Form - Форма - - - - List of issues - Перелік проблем - - - - Account - Обліковий запис - - - - - <no filter> - - - - - - Folder - Тека - - - - Show warnings - - - - - Show ignored files - - - - - There were too many issues. Not all will be visible here. - - - - - There were conflicts. Check the documentation on how to resolve them. - - - - - Copy the issues list to the clipboard. - - - - - Copy - Копіювати - - - - Time - Час - - - - File - Файл - - - - Issue - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - - - - - OCC::LogBrowser - - - Log Output - Вивід журналу - - - - &Search: - &Пошук: - - - - &Find - &Знайти - - - - &Capture debug messages - - - - - Permanently save logs - - - - - When this option is enabled and no other logging is configured, logs will be written to a temporary folder and expire after a few hours. This setting persists across client restarts. - -Logs will be written to %1 - - - - - Clear - Очистити - - - - Clear the log display. - Очистити дисплей журналу. - - - - S&ave - З&берегти - - - - Save the log file to a file on disk for debugging. - Зберегти файл журналу у файл на диску для обробки. - - - - Save log file - Зберегти файл журналу - - - - Error - Помилка - - - - Could not write to log file %1 - Не вдалося записати в файл журналу %1 - - - - OCC::Logger - - - Error - Помилка - - - - <nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>The log output can <b>not</b> be saved!</nobr> - <nobr>Файл '%1'<br/>не вдається відкрити на запис.<br/><br/>Журнал <b>не</b> можливо зберегти!</nobr> - - - - OCC::NSISUpdater - - - New Version Available - Доступна Нова Версія - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Доступна нова версія додатку %1.</p><p><b>%2</b> доступна для завантаження. Встановлена версія: %3.</p> - - - - Skip this version - Пропустити цю версію - - - - Skip this time - Пропустити цього разу - - - - Get update - Отримати оновлення - - - - OCC::NetworkSettings - - - Form - Форма - - - - Proxy Settings - Налаштування проксі - - - - No Proxy - Без проксі - - - - Use system proxy - Використовувати системні налаштування проксі - - - - Specify proxy manually as - Вказати проксі вручну - - - - Host - Хост - - - - : - : - - - - Proxy server requires authentication - Проксі-сервер вимагає пароль - - - - Download Bandwidth - Швидкість завантаження - - - - - Limit to - Обмежити до - - - - - KBytes/s - Кбайт/сек - - - - - No limit - Без обмежень - - - - - Limit to 3/4 of estimated bandwidth - - - - - Upload Bandwidth - Швидкість Завантаження - - - - - Limit automatically - Автоматичне обмеження - - - - Hostname of proxy server - Ім'я хоста проксі-сервера - - - - Username for proxy server - Ім'я користувача проксі-сервера - - - - Password for proxy server - Пароль для проксі-сервера - - - - HTTP(S) proxy - HTTP(S) проксі - - - - SOCKS5 proxy - SOCKS5 проксі - - - - OCC::NotificationWidget - - - Created at %1 - - - - - Closing in a few seconds... - - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - The reply from the server did not contain all expected fields - - - - - <h1>Login Error</h1><p>%1</p> - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - - - - - OCC::OCUpdater - - - New %1 Update Ready - Доступне оновлення %1 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Буде встановлено оновлення для %1. Програма оновлення може запитати додаткові привілеї. - - - - Downloading version %1. Please wait... - Завантаження файлів %1. Будь ласка, почекайте... - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Неможливо завантажити оновлення. Будь ласка, натисніть <a href='%1'>тут</a>, щоб самостійно завантажити оновлення. - - - - Could not check for new updates. - Не вдалося перевірити наявність оновлень. - - - - %1 version %2 available. Restart application to start the update. - - - - - New %1 version %2 available. Please use the system's update tool to install it. - - - - - Checking update server... - Перевірка оновлень на сервері... - - - - Update status is unknown: Did not check for new updates. - Статус оновлення невідомий: Не вдалося перевірити наявність оновлень. - - - - No updates available. Your installation is at the latest version. - У вас встановленно останню версію. - - - - Update Check - - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - З'єднати з %1 - - - - Setup local folder options - Налаштування локальної теки - - - - Connect... - Підключення ... - - - - %1 folder '%2' is synced to local folder '%3' - %1 тека '%2' синхронізована з локальним каталогом '%3' - - - - Sync the folder '%1' - - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - - - - - Local Sync Folder - Локальна Тека для Синхронізації - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Збій підключення - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p> Не вдалося підключитися до безпечного серверу за наданною адресою. Як Ви хочете продовжити? </p></body></html> - - - - Select a different URL - Оберіть інший URL - - - - Retry unencrypted over HTTP (insecure) - Спробувати без шифрування через HTTP (небезпечно) - - - - Configure client-side TLS certificate - Налаштувати TLS сертифікат клієнта - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Не вдалося підключитися до безпечного серверу за адресою <em>%1</em>. Як Ви хочете продовжити?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - - - - - Connect to %1 - З'єднати з %1 - - - - Enter user credentials - Вказати облікові дані - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - З'єднати з %1 - - - - Login in your browser - - - - - Copy link to clipboard - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - З'єднати з %1 - - - - Setup %1 server - Налаштувати %1 сервер - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - Це посилання НЕ зашифроване. -Ми не рекомендуємо відкривати такі посилання. - - - - This url is secure. You can use it. - Ця адреса безпечна. Її можна використовувати. - - - - &Next > - &Наступний> - - - - Invalid URL - Невірний URL - - - - Could not load certificate. Maybe wrong password? - - - - - OCC::OwncloudSetupWizard - - - <font color="green">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/> - <font color="green">Успішно підключено до %1: %2 версія %3 (%4)</font><br/><br/> - - - - Failed to connect to %1 at %2:<br/>%3 - Не вдалося з'єднатися з %1 в %2:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - Перевищено час очікування з'єднання до %1 на %2. - - - - Trying to connect to %1 at %2... - Спроба підключення до %1 на %2... - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - Запит аутентифікації до серверу було переадресовано до '%1'. Поганий URL, сервер сконфігуровано неправильно. - - - - There was an invalid response to an authenticated webdav request - Неправильна відповідь на автентифікований запит webdav - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - Доступ заборонений сервером. Щоб довести, що у Вас є права доступу, <a href="%1">клікніть тут</a> для входу через Ваш браузер. - - - - Invalid URL - Невірний URL - - - - The server reported the following error: - - - - - Local sync folder %1 already exists, setting it up for sync.<br/><br/> - Локальна тека синхронізації %1 вже існує, налаштування її для синхронізації.<br/><br/> - - - - Creating local sync folder %1... - Створення локальної теки для синхронізації %1... - - - - ok - ok - - - - failed. - не вдалося. - - - - Could not create local folder %1 - Не вдалося створити локальну теку $1 - - - - No remote folder specified! - Не вказано віддалену теку! - - - - Error: %1 - Помилка: %1 - - - - creating folder on ownCloud: %1 - створення теки на ownCloud: %1 - - - - Remote folder %1 created successfully. - Віддалена тека %1 успішно створена. - - - - The remote folder %1 already exists. Connecting it for syncing. - Віддалена тека %1 вже існує. Під'єднання для синхронізації. - - - - - The folder creation resulted in HTTP error code %1 - Створення теки завершилось HTTP помилкою %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Створити віддалену теку не вдалося через невірно вказані облікові дані.<br/>Поверніться назад та перевірте облікові дані.</p> - - - - <p><font color="red">Remote folder creation failed probably because the provided credentials are wrong.</font><br/>Please go back and check your credentials.</p> - <p><font color="red">Створити віддалену теку не вдалося, можливо, через невірно вказані облікові дані.</font><br/>Будь ласка, поверніться назад та перевірте облікові дані.</p> - - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Не вдалося створити віддалену теку %1 через помилку <tt>%2</tt>. - - - - A sync connection from %1 to remote directory %2 was set up. - З'єднання для синхронізації %1 з віддаленою текою %2 було встановлено. - - - - Successfully connected to %1! - Успішно під'єднано до %1! - - - - Connection to %1 could not be established. Please check again. - Підключення до %1 встановити не вдалося. Будь ласка, перевірте ще раз. - - - - Folder rename failed - Не вдалося перейменувати теку - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Неможливо видалити теку та створити її резервну копію, оскільки тека або файли, що в ній розташовані, використовуються. Будь ласка, закрийте всі програми, що можуть використовувати цю теку та спробуйте ще раз, або скасуйте встановлення. - - - - <font color="green"><b>Local sync folder %1 successfully created!</b></font> - <font color="green"><b>Локальна тека синхронізації %1 успішно створена!</b></font> - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - Майстер з'єднання %1 - - - - Enable experimental feature? - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Enable experimental mode - - - - - Stay safe - - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Усе налаштовано! - - - - Open Local Folder - Відкрити локальну теку - - - - Open %1 in Browser - Відкрити %1 в Браузері - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - Неправильна JSON відповідь на сформований URL - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Помилка запису метаданих до бази даних - - - - OCC::PropagateDownloadFile - - - File %1 can not be downloaded because of a local file name clash! - Файл %1 не може бути завантажено через локальний конфлікт назви файлу! - - - - The download would reduce free local disk space below the limit - - - - - Free space on disk is less than %1 - - - - - File was deleted from server - Файл видалено з сервера - - - - The file could not be downloaded completely. - Файл не може бути завантажений повністю. - - - - The downloaded file is empty despite the server announced it should have been %1. - - - - - File %1 cannot be saved because of a local file name clash! - Файл %1 не збережено через локальний конфлікт назви файлу! - - - - File has changed since discovery - Файл змінився з моменту знаходження - - - - Error writing metadata to the database - Помилка запису метаданих до бази даних - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Відновлення не вдалося: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - - - - - Attention, possible case sensitivity clash with %1 - Увага, можливий конфлікт чутливості до реєстру з %1 - - - - could not create folder %1 - - - - - Error writing metadata to the database - Помилка запису метаданих до бази даних - - - - OCC::PropagateLocalRemove - - - Error removing '%1': %2; - Помилка видалення '%1': %2; - - - - Could not remove folder '%1' - - - - - Could not remove %1 because of a local file name clash - Неможливо видалити %1 через локальний конфлікт назви файлу - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - %1 не можна перейменувати на %2 через локальний конфлікт назви файлу - - - - - Error writing metadata to the database - Помилка запису метаданих до бази даних - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Сервер відповів неправильним HTTP кодом. Очікувався 204, але отримано "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Сервер відповів неправильним HTTP кодом. Очікувався 201, але отримано "%1 %2". - - - - Error writing metadata to the database - Помилка запису метаданих до бази даних - - - - OCC::PropagateRemoteMove - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Сервер відповів неправильним HTTP кодом. Очікувався 201, але отримано "%1 %2". - - - - - Error writing metadata to the database - Помилка запису метаданих до бази даних - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - - - - - File Removed - Файл видалено - - - - Local file changed during syncing. It will be resumed. - Локальний файл змінився під час синхронізації. Його буде відновлено. - - - - Local file changed during sync. - Локальний файл змінився під час синхронізації. - - - - - Upload of %1 exceeds the quota for the folder - - - - - Error writing metadata to the database - Помилка запису метаданих до бази даних - - - - OCC::PropagateUploadFileNG - - - The local file was removed during sync. - Локальний файл було видалено під час синхронізації. - - - - Local file changed during sync. - Локальний файл змінився під час синхронізації. - - - - Unexpected return code from server (%1) - - - - - Missing File ID from server - - - - - Missing ETag from server - - - - - OCC::PropagateUploadFileV1 - - - Poll URL missing - Не вистачає сформованого URL - - - - The local file was removed during sync. - Локальний файл було видалено під час синхронізації. - - - - Local file changed during sync. - Локальний файл змінився під час синхронізації. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - - - - - OCC::ProtocolWidget - - - Form - Форма - - - - TextLabel - Мітка - - - - Open in browser - Відкрити у переглядачі - - - - Time - Час - - - - File - Файл - - - - Folder - Тека - - - - Action - Дія - - - - Size - Розмір - - - - Local sync protocol - - - - - Copy - Копіювати - - - - Copy the activity list to the clipboard. - Скопіювати протокол синхронізації до буферу обміну. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - - - - - Username: - Ім'я користувача - - - - Proxy: - Проксі: - - - - The proxy server needs a username and password. - - - - - Password: - Пароль: - - - - TextLabel - Мітка - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Оберіть, що хочете синхронізувати - - - - OCC::SelectiveSyncWidget - - - Loading ... - Завантаження ... - - - - Deselect remote folders you do not wish to synchronize. - - - - - Name - Ім’я - - - - Size - Розмір - - - - - No subfolders currently on the server. - На сервері зараз немає підтек. - - - - An error occurred while loading the list of sub folders. - - - - - OCC::ServerNotificationHandler - - - Dismiss - Припинити - - - - OCC::SettingsDialog - - - Settings - Налаштування - - - - Activity - Активність - - - - General - Загалом - - - - Network - Мережа - - - - Account - Обліковий запис - - - - OCC::SettingsDialogMac - - - %1 - %1 - - - - Activity - Активність - - - - General - Загалом - - - - Network - Мережа - - - - - Account - Обліковий запис - - - - OCC::ShareDialog - - - TextLabel - Мітка - - - - share label - поділитися міткою - - - - Dialog - - - - - ownCloud Path: - Шлях до ownCloud: - - - - %1 Sharing - Ви поділилися %1 - - - - %1 - %1 - - - - Folder: %2 - Тека: %2 - - - - The server does not allow sharing - - - - - Retrieving maximum possible sharing permissions from server... - - - - - The file can not be shared because it was shared without sharing permission. - Цей файл неможливо поширити, бо ним поділилися без права на поширення. - - - - Users and Groups - Користувачі та групи - - - - Public Links - Публічні посилання - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Поділитися NewDocument.odt - - - - Upload only (File Drop) - - - - - Receive files from others without revealing the contents of the folder. - - - - - TextLabel - Мітка - - - - Set &password - Встановити &пароль - - - - Enter a name to create a new public link... - - - - - &Create new - - - - - Set &expiration date - Встановити т&ермін дії - - - - Set password - Встановіть пароль - - - - Recipients can view or download contents. - - - - - Download / View - Завантаження / Перегляд - - - - Download / View / Upload - Завантаження / Перегляд / Вивантаження - - - - Recipients can view, download, edit, delete and upload contents. - - - - - Anyone with the link has access to the file/folder - Будь хто з цім посиланням має доступ до файлу/теки - - - - - P&assword protect - &Захистити паролем - - - - The file can not be shared because it was shared without sharing permission. - Цей файл неможливо поширити, бо ним поділилися без права на поширення. - - - - Link shares have been disabled - - - - - Create public link share - - - - - - Delete - Видалити - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Copy link to clipboard (direct download) - - - - - Send link by email - Надіслати посилання по електронній пошті - - - - Send link by email (direct download) - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - - - - - Cancel - Скасувати - - - - - Public link - - - - - Delete link share - - - - - Public sh&aring requires a password - Публічне по&ширення вимагає пароль - - - - Please Set Password - - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Поділитися NewDocument.odt - - - - Share with users or groups ... - Поширити серед користувачів або груп ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline; color:#0000ff;">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - - - - - Open link in browser - - - - - Copy link to clipboard - - - - - Send link by email - Надіслати посилання по електронній пошті - - - - No results for '%1' - - - - - I shared something with you - - - - - OCC::ShareUserLine - - - Form - Форма - - - - - TextLabel - Мітка - - - - can edit - може редагувати - - - - can share - може ділитися з іншими - - - - ... - ... - - - - create - створити - - - - change - змінити - - - - delete - видалити - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::ShibbolethCredentials - - - Login Error - Помилка входу - - - - You must sign in as user %1 - Ви маєте увійти як %1 - - - - OCC::ShibbolethWebView - - - %1 - Authenticate - %1 - Аутентифікація - - - - SSL Chipher Debug View - - - - - Reauthentication required - Потрібна повторна аутентифікація - - - - Your session has expired. You need to re-login to continue to use the client. - Ваша сесія скінчилася. Вам потрібно ввійти знову, щоб продовжити користуватися програмою. - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Поділитися з %1 - - - - Context menu share - - - - - I shared something with you - - - - - - Share... - - - - - - Copy private link to clipboard - - - - - Send private link by email... - - - - - Resharing this file is not allowed - - - - - - Copy public link to clipboard - - - - - Open in browser - Відкрити у переглядачі - - - - Download file(s) - Завантажити файлЗавантажити файлиЗавантажити файлиЗавантажити файли - - - - Replace file(s) by virtual file - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Деталі сертифікату</h3> - - - - Common Name (CN): - Загальне Ім'я (CN): - - - - Subject Alternative Names: - Альтернативне Ім'я: - - - - Organization (O): - Організація (O): - - - - Organizational Unit (OU): - Підрозділ (OU): - - - - State/Province: - Область: - - - - Country: - Країна: - - - - Serial: - Серія: - - - - <h3>Issuer</h3> - <h3>Видавець</h3> - - - - Issuer: - Видавець: - - - - Issued on: - Видано для: - - - - Expires on: - Закінчується: - - - - <h3>Fingerprints</h3> - <h3>Відбитки пальців</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Примітка:</b> Цей сертифікат був схвалений вручну</p> - - - - %1 (self-signed) - %1 (самопідписаний) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - З'єднання зашифроване %1-бітним %2. - - - - - No support for SSL session tickets/identifiers - - - - - Certificate information: - Інформація про сертифікат: - - - - This connection is NOT secure as it is not encrypted. - - З'єднання НЕ зашифровано. - - - - - OCC::SslErrorDialog - - - Form - Форма - - - - Trust this certificate anyway - Все одно довіряти цьому сертифікату - - - - Untrusted Certificate - Недовірений сертифікат - - - - Cannot connect securely to <i>%1</i>: - - - - - with Certificate %1 - з Сертифікатом %1 - - - - - - &lt;not specified&gt; - &lt;не вказано&gt; - - - - - Organization: %1 - Організація: %1 - - - - - Unit: %1 - Підрозділ: %1 - - - - - Country: %1 - Країна: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - Відбиток (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - Відбиток (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Дата введення в дію: %1 - - - - Expiration Date: %1 - Термін Дії: %1 - - - - Issuer: %1 - Емітент: %1 - - - - OCC::SyncEngine - - - Success. - Успішно. - - - - A network connection timeout happened. - Час під'єднання до мережі вичерпався. - - - - A HTTP transmission error happened. - Сталася помилка передачі даних по HTTP. - - - - The mounted folder is temporarily not available on the server - - - - - An error occurred while opening a folder - - - - - Error while reading folder. - - - - - %1 (skipped due to earlier error, trying again in %2) - - - - - File/Folder is ignored because it's hidden. - - - - - Folder hierarchy is too deep - - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Доступно лише %1, для початку необхідно хоча б %2 - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - - - - - Not allowed because you don't have permission to add parent folder - - - - - Not allowed because you don't have permission to add files in that folder - - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - - - - - CSync unspecified error. - Невизначена помилка CSync. - - - - Aborted by the user - Скасовано користувачем - - - - Failed to load or create the journal file. Make sure you have read and write permissions in the local sync folder. - - - - - Discovery step failed. - - - - - Permission denied. - - - - - File or directory not found: - - - - - Tried to create a folder that already exists. - - - - - No space on %1 server available. - - - - - The service is temporarily unavailable - Служба тимчасово недоступна - - - - Access is forbidden - Доступ заборонений - - - - An internal error number %1 occurred. - Виникла внутрішня помилка за номером %1. - - - - Symbolic links are not supported in syncing. - Синхронізація символічних посилань не підтримується. - - - - File is listed on the ignore list. - Файл присутній у списку ігнорованих. - - - - File names ending with a period are not supported on this file system. - - - - - File names containing the character '%1' are not supported on this file system. - - - - - The file name is a reserved name on this file system. - - - - - Filename contains trailing spaces. - - - - - Filename is too long. - Шлях до файлу занадто довгий. - - - - The filename cannot be encoded on your file system. - - - - - Unresolved conflict. - - - - - Stat failed. - - - - - Filename encoding is not valid - Кодування файлу не припустиме - - - - Invalid characters, please rename "%1" - - - - - Using virtual files but suffix is not set - - - - - Unable to read the blacklist from the local database - - - - - Unable to read from the sync journal. - - - - - Cannot open the sync journal - Не вдається відкрити протокол синхронізації - - - - File name contains at least one invalid character - Ім’я файлу містить принаймні один некоректний символ - - - - - Ignored because of the "choose what to sync" blacklist - Ігнорується через чорний список в "обрати що синхронізувати" - - - - Not allowed because you don't have permission to add subfolders to that folder - Заборонено через відсутність прав додавання підкаталогів в цю теку. - - - - Not allowed to upload this file because it is read-only on the server, restoring - Не дозволено завантажувати цей файл, оскільки він має дозвіл лише на перегляд, відновлюємо - - - - - Not allowed to remove, restoring - Не дозволено видаляти, відновлюємо - - - - Local files and share folder removed. - Локальні файли та теки в загальному доступі було видалено. - - - - Move not allowed, item restored - Переміщення не дозволено, елемент відновлено - - - - Move not allowed because %1 is read-only - Переміщення не дозволено, оскільки %1 помічений тільки для перегляду - - - - the destination - призначення - - - - the source - джерело - - - - OCC::SyncLogDialog - - - Synchronisation Log - Журнал синхронізації - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %2. For more information visit <a href="%3">https://%4</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, and others.</small></p> - - - - - <p>Copyright ownCloud GmbH</p> - - - - - <p>Distributed by %1 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%2 and the %2 logo are registered trademarks of %1 in the United States, other countries, or both.</p> - <p>Поширюється через %1 і під ліцензією GNU General Public License (GPL) версії 2.0<br/>%2 логотип %2 є зареєстрованими торговими марками %1 у Сполучених Штатах та інших країнах, або обох.</p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Заголовок контрольної суми пошкоджено. - - - - The checksum header contained an unknown checksum type '%1' - - - - - The downloaded file does not match the checksum, it will be resumed. - Завантажений файл не відповідає контрольній сумі, його буде відновлено. - - - - OCC::ownCloudGui - - - Please sign in - Увійдіть будь ласка - - - - Folder %1: %2 - Тека %1: %2 - - - - There are no sync folders configured. - Немає налаштованих тек для синхронізації. - - - - Open in browser - Відкрити у переглядачі - - - - - - Log in... - Увійти... - - - - - - Log out - Вихід - - - - Recent Changes - Недавні зміни - - - - Managed Folders: - Керовані теки: - - - - Open folder '%1' - Відкрити теку '%1' - - - - Open %1 in browser - Відкрити %1 в браузері - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Невідомий статус - - - - Settings... - Налаштування... - - - - Details... - Деталі... - - - - Help - Допомога - - - - Quit %1 - Закрити %1 - - - - Disconnected from %1 - - - - - Unsupported Server Version - - - - - Disconnected - Від'єднаний - - - - Disconnected from some accounts - - - - - Disconnected from accounts: - - - - - Account %1: %2 - Обліковий запис %1: %2 - - - - Signed out - Вийшов - - - - Account synchronization is disabled - - - - - - Synchronization is paused - - - - - Unresolved conflicts - - - - - Error during synchronization - - - - - No sync folders configured - - - - - Unpause all folders - - - - - Pause all folders - - - - - Unpause all synchronization - - - - - Unpause synchronization - - - - - Pause all synchronization - - - - - Pause synchronization - - - - - Log out of all accounts - Вийти зі всіх облікових записів - - - - Log in to all accounts... - Увійти до всіх облікових записів... - - - - New account... - Новий обліковий запис... - - - - - About %1 - Про %1 - - - - No items synced recently - Нещодавно нічого не синхронізувалося - - - - Checking for changes in remote '%1' - - - - - Checking for changes in local '%1' - - - - - Syncing %1 of %2 (%3 left) - Синхронізовано %1 з %2 (залишилося %3) - - - - Syncing %1 of %2 - Синхронізовано %1 з %2 - - - - Syncing %1 (%2 left) - Синхронізовано %1 (залишилося %2) - - - - Syncing %1 - Синхронізація %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Оновлено - - - - OwncloudAdvancedSetupPage - - - Form - Форма - - - - - - - - - - TextLabel - Мітка - - - - Server - Сервер - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Якщо встановлено цю позначку, існуючі дані в локальній директорії будуть видалені, щоб розпочати чисту синхронізацію з сервером.</p><p>Не ставте її, якщо локальні дані мають бути завантажені на сервер.</p></body></html> - - - - Start a &clean sync (Erases the local folder!) - Почати синхронізацію &заново (Видалить локальну теку!) - - - - Ask for confirmation before synchroni&zing folders larger than - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - - - - - Choose what to sync - Оберіть, що хочете синхронізувати - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - - - - - &Local Folder - &Локальна Тека - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Зберегти локальні дані - - - - S&ynchronize everything from server (recommended) - - - - - Use virtual files instead of downloading content immediately (e&xperimental) - - - - - Status message - Повідомлення про статус - - - - OwncloudHttpCredsPage - - - Form - Форма - - - - &Username - &Ім'я користувача - - - - &Password - &Пароль - - - - OwncloudOAuthCredsPage - - - Form - Форма - - - - Please switch to your browser to proceed. - - - - - An error occurred while connecting. Please try again. - - - - - Re-open Browser - - - - - OwncloudSetupPage - - - Form - Форма - - - - - TextLabel - Мітка - - - - Ser&ver Address - - - - - https://... - https://... - - - - Error Label - Помилка - - - - OwncloudWizardResultPage - - - Form - Форма - - - - TextLabel - Мітка - - - - Your entire account is synced to the local folder - Ваш запис цілком синхронізовано із локальною текою - - - - - PushButton - НатиснітьКнопку - - - - QObject - - - in the future - у майбутньому - - - - %n day(s) ago - - - - - %n hour(s) ago - - - - - now - зараз - - - - Less than a minute ago - Менше хвилини тому - - - - %n minute(s) ago - - - - - Some time ago - Деякий час тому - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 ГБ - - - - %L1 MB - %L1 МБ - - - - %L1 KB - %L1 КБ - - - - %L1 B - %L1 Б - - - - %n year(s) - - - - - %n month(s) - - - - - %n day(s) - - - - - %n hour(s) - - - - - %n minute(s) - - - - - %n second(s) - - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - Системний Трей не доступний - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 вимагає працюючий системний трей. Якщо ви використовуєте XFCE, будь ласка перегляньте <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">інструкцію</a>. В інакшому випадку, встановіть системний трей (наприклад trayer), та спробуйте ще раз. - - - - ownCloudTheme::about() - - - <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - <p><small>Зібрано з Git ревізії <a href="%1">%2</a> %3, %4, використовуючи Qt %5, %6</small></p> - - - - progress - - - Downloaded - Завантажено - - - - Uploaded - Вивантажено - - - - Server version downloaded, copied changed local file into conflict file - - - - - Deleted - Видалено - - - - Moved to %1 - Переміщено до %1 - - - - Ignored - Проігноровано - - - - Filesystem access error - Помилка доступу до файлової системи - - - - Error - Помилка - - - - Updated local metadata - - - - - - Unknown - Невідомо - - - - downloading - зкачування - - - - uploading - завантаження - - - - deleting - видалення - - - - moving - переміщення - - - - ignoring - ігнорування - - - - - error - помилка - - - - updating local metadata - - - - - theme - - - Status undefined - Статус не визначено - - - - Waiting to start sync - Очікування початку синхронізації - - - - Sync is running - Проводиться синхронізація - - - - Sync Success - Синхронізація успішно виконана - - - - Sync Success, some files were ignored. - Синхронізацію завершено, деякі файли проігноровано. - - - - Sync Error - Помилка синхронізації - - - - Setup Error - Помилка налаштування - - - - Preparing to sync - Підготовка до синхронізації - - - - Aborting... - Відміна... - - - - Sync is paused - Синхронізація призупинена - - - - utility - - - Could not open browser - - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - - - - - Could not open email client - - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - - - - \ No newline at end of file diff --git a/translations/client_vi.ts b/translations/client_vi.ts deleted file mode 100644 index fece9f24434..00000000000 --- a/translations/client_vi.ts +++ /dev/null @@ -1,4785 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - Hủy bỏ - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - Đóng - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - Không thể tạo thư mục trong thùng rác - - - - - Could not move '%1' to '%2' - Không thể chuyển '%1' đến '%2' - - - - Moving to the trash is not implemented on this platform - Chuyển đến thùng rác không được hỗ trợ - - - - Error removing '%1': %2 - - - - - Could not remove folder '%1' - Không thể xóa thư mục '%1' - - - - FolderWizardSourcePage - - - Form - Biểu mẫu - - - - Pick a local folder on your computer to sync - Chọn một thư mục trên máy tính của bạn để đồng bộ hóa - - - - &Choose... - &Chọn... - - - - FolderWizardTargetPage - - - Form - Biểu mẫu - - - - Select a remote destination folder - Chọn một thư mục đích từ xa - - - - Create Folder - Tạo thư mục - - - - Refresh - Làm mới - - - - Folders - Thư mục - - - - TextLabel - Tên nhãn - - - - NotificationWidget - - - Form - Biểu mẫu - - - - Lorem ipsum dolor sit amet - - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - - TextLabel - Tên nhãn - - - - OCC::AboutDialog - - - About - Giới thiệu - - - - TextLabel - Tên nhãn - - - - Versions - Phiên bản - - - - About %1 - Về %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - Kết nối hết thời gian - - - - Unknown error: network reply was deleted - Lỗi không xác định: Kết nối đã bị xóa - - - - Server replied "%1 %2" to "%3 %4" - Máy chủ trả về "%1 %2" đến "%3 %4" - - - - OCC::Account - - - %1@%2 - - - - - OCC::AccountSettings - - - Form - Mẫu - - - - Re-open Browser - - - - - ... - ... - - - - Storage space: ... - Không gian lưu trữ: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - Những thư mục không được chọn sẽ bị <b>xóa</b> khỏi hệ thống lưu trữ của bạn và không được đồng bộ về máy tính cá nhân - - - - Synchronize all - Đồng bộ hóa tất cả - - - - Synchronize none - Đồng bộ hóa không có - - - - Apply manual changes - Áp dụng tự thay đổi - - - - Apply - Áp dụng - - - - - - - Cancel - Hủy - - - - Connected with <server> as <user> - Kết nối với <server> với <user> - - - - No account configured. - Không thành viên nào được thiết lập. - - - - Remove - Xóa - - - - Account - Tài khoản - - - - Choose what to sync - Chọn thứ cần đồng bộ - - - - Force sync now - Ép đồng bộ ngay lập tức - - - - Restart sync - Khởi động lại đồng bộ - - - - Remove folder sync connection - Hủy kết nối đồng bộ thư mục - - - - Folder creation failed - Tạo thư mục thất bại - - - - <p>Could not create local folder <i>%1</i>. - <p>Không thể tạo thư mục <i>%1</i>. - - - - Confirm Folder Sync Connection Removal - Xác nhận hủy kết nối đồng bộ thư mục - - - - Remove Folder Sync Connection - Hủy kết nối đồng bộ thư mục - - - - Disable virtual file support? - - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - - - - - Sync Running - Đang đồng bộ - - - - The syncing operation is running.<br/>Do you want to terminate it? - Hoạt động đồng bộ đang được thực hiện.<br/>Bạn có muốn hủy nó? - - - - %1 in use - %1 đang sử dụng - - - - %1 as <i>%2</i> - %1 với <i>%2</i> - - - - Connected to %1. - Đã kết nối tới %1 - - - - Server %1 is temporarily unavailable. - Server %1 tạm thời không khả dụng. - - - - Server %1 is currently in maintenance mode. - Máy chủ %1 đang trong chế độ bảo trì. - - - - Signed out from %1. - Đã đăng xuất từ %1. - - - - Connecting to %1... - Kết nối đến %1... - - - - No connection to %1 at %2. - Không có kết nối tới %1 ở %2. - - - - Log in - Đăng nhập - - - - There are folders that were not synchronized because they are too big: - Thư mục không được đồng bộ vì kích thước quá lớn: - - - - There are folders that were not synchronized because they are external storages: - Thư mục không được đồng bộ vì nằm ngoài không gian lưu trữ: - - - - There are folders that were not synchronized because they are too big or external storages: - Thư mục không được đồng bộ vì kích thước quá lớn hoặc nằm ngoài không gian lưu trữ: - - - - Confirm Account Removal - Xác nhận xóa tài khoản - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Bạn có thực sự muốn xóa kết nối đến tài khoản <i>%1</i>?</p><p><b>Chú ý:</b>Việc này sẽ <b>không</b>xóa bất cứ tệp tin nào.</p> - - - - Remove connection - Xóa kết nối - - - - Open folder - Mở thư mục - - - - - Log out - Đăng xuất - - - - Open local folder - - - - - Open folder in browser - - - - - Resume sync - Tiếp tục đồng bộ - - - - Pause sync - Tạm dừng đồng bộ - - - - Availability - - - - - Disable virtual file support... - - - - - Enable virtual file support%1... - - - - - (experimental) - - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>Bạn có thực sự muốn dừng đồng bộ thư mục <i>%1</i>?</p><p><b>Chú ý:</b> Việc này sẽ <b>không</b> xóa bất cứ tệp tin nào.</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - - - - - %1 of %2 in use - %1 của %2 đang sử dụng - - - - Currently there is no storage usage information available. - Hiện tại không có thông tin sử dụng bộ nhớ. - - - - The server version %1 is unsupported! Proceed at your own risk. - - - - - Obtaining authorization from the browser. - - - - - Server configuration error: %1 at %2. - - - - - No %1 connection configured. - Không có %1 kết nối được cấu hình - - - - OCC::AccountState - - - Signed out - Đăng xuất - - - - Disconnected - Đã ngắt kết nối - - - - Connected - Đã kết nối - - - - Service unavailable - Dịch vụ không sẵn sàng - - - - Maintenance mode - Chế độ bảo trì - - - - Network error - Lỗi kết nối - - - - Configuration error - Lỗi cấu hình - - - - Asking Credentials - Yêu cầu thông tin xác thực - - - - Unknown account state - Trạng thái tài khoản không xác định - - - - OCC::ActivityItemDelegate - - - %1 on %2 - %1 trên %2 - - - - %1 on %2 (disconnected) - %1 trên %2 (đã ngắt kết nối) - - - - OCC::ActivityListModel - - - %1 %2 on %3 - - - - - OCC::ActivitySettings - - - - Server Activity - Hoạt động của máy chủ - - - - Sync Protocol - Giao thức đồng bộ - - - - Not Synced - Chưa được đồng bộ - - - - Not Synced (%1) - %1 is the number of not synced files. - (%1) chưa được đồng bộ - - - - The server activity list has been copied to the clipboard. - Danh sách các hoạt động của máy chủ đã được sao chép vào bộ đệm. - - - - The sync activity list has been copied to the clipboard. - Danh sách các hoạt động đồng bộ đã được sao chép vào bộ đệm. - - - - The list of unsynced items has been copied to the clipboard. - Danh sách các tệp tin chưa đồng bộ đã được sao chép vào bộ đệm. - - - - Copied to clipboard - Đã sao chép vào bộ đệm - - - - OCC::ActivityWidget - - - Form - Mẫu - - - - - - TextLabel - Tên nhãn - - - - Server Activities - Hoạt động của máy chủ - - - - Copy - Sao chép - - - - Copy the activity list to the clipboard. - Chép danh sách hoạt động vào clipboard. - - - - Notifications - Thông b - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - - - - - Open the activity view for details. - - - - - <br/>Account %1 does not have activities enabled. - <br/>Tài khoản %1 không có hoạt động nào được bật. - - - - OCC::AddCertificateDialog - - - SSL client certificate authentication - Xác thực chứng chỉ SSL máy khách - - - - This server probably requires a SSL client certificate. - Máy chủ có thể yêu cầu chứng chỉ SSL máy khách - - - - Certificate & Key (pkcs12) : - Chứng chỉ & Khóa (pkcs12) : - - - - Browse... - Duyệt... - - - - Certificate password : - Mật khẩu chứng chỉ : - - - - An encrypted pkcs12 bundle is strongly recommended as a copy will be stored in the configuration file. - - - - - Select a certificate - Chọn chứng chỉ - - - - Certificate files (*.p12 *.pfx) - Tệp tin chứng chỉ(*.p12 *.pfx) - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - - - - - Continuing will mean <b>ignoring these settings</b>. - - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - Thoát - - - - Continue - Tiếp tục - - - - Error accessing the configuration file - Lỗi truy cập vào tệp tin cấu hình - - - - There was an error while accessing the configuration file at %1. - Xảy ra lỗi khi truy cập vào tệp tin cấu hình tại %1. - - - - Quit ownCloud - Thoát ownCloud - - - - OCC::AuthenticationDialog - - - Authentication Required - Yêu cầu xác thực - - - - Enter username and password for '%1' at %2. - Nhập tên người dùng và mật khẩu cho '%1' tại '%2'. - - - - &User: - &Người dùng: - - - - &Password: - &Mật khẩu: - - - - OCC::CleanupPollsJob - - - Error writing metadata to the database - Xảy ra lỗi khi ghi metadata vào cơ sở dữ liệu - - - - OCC::ConnectionValidator - - - No ownCloud account configured - Không có tài khoản ownCloud được cấu hình - - - - The configured server for this client is too old - Cấu hình máy chủ cho máy khách quá cũ - - - - Please update to the latest server and restart the client. - Vui lòng cập nhật máy chủ lên bản mới nhất và khởi động lại máy khách - - - - Authentication error: Either username or password are wrong. - Lỗi xác thực: Sai tên người dùng hoặc mật khẩu. - - - - timeout - hết thời gian! - - - - The provided credentials are not correct - Thông tin xác thực không đúng - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - - - - - Directory not accessible on client, permission denied - - - - - Directory not found: %1 - - - - - Filename encoding is not valid - Tên tệp tin không hợp lệ - - - - Error while reading directory %1 - - - - - OCC::Folder - - - Local folder %1 does not exist. - Thư mục %1 không tồn tại - - - - %1 should be a folder but is not. - %1 nên là một thư mục nhưng không phải. - - - - %1 is not readable. - %1 không thể liên kết. - - - - %1 has been removed. - %1 names a file. - %1 đã bị xóa. - - - - %1 has been updated. - %1 names a file. - %1 đã được cập nhật. - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 đã được đổi tên thành %2. - - - - %1 has been moved to %2. - %1 đã được di chuyển đến %2. - - - - %1 and %n other file(s) have been removed. - %1 và %n tệp tin khác đã được xóa. - - - - %1 and %n other file(s) have been added. - - - - - %1 has been added. - %1 names a file. - - - - - %1 and %n other file(s) have been updated. - %1 và %n tệp tin khác đã được cập nhật - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 đã được đổi tên thành %2 và %n tệp tin khác đã được đổi tên. - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 đã được di chuyển đến %2 và %n tệp tin khác đã được di chuyển. - - - - %1 has and %n other file(s) have sync conflicts. - %1 và %n tệp tin khác xảy ra xung đột khi đồng bộ. - - - - %1 has a sync conflict. Please check the conflict file! - %1 có xung đột. Vui lòng kiểm tra tệp tin bị xung đột. - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 và %n tệp tin khác có thể không đồng bộ được do xảy ra lỗi. Kiểm tra log để biết thêm chi tiết - - - - %1 could not be synced due to an error. See the log for details. - %1 có thể không đồng bộ được do xảy ra lỗi. Kiểm tra log để biết thêm chi tiết. - - - - Sync Activity - Hoạt động đồng bộ - - - - Could not read system exclude file - Không thể đọc tệp tin đã bị loại trừ của hệ thống - - - - A new folder larger than %1 MB has been added: %2. - - Một thư mục mới lớn hơn %1 MB đã được thêm vào: %2. - - - - - A folder from an external storage has been added. - - A folder from an external storage has been added. - - - - - Please go in the settings to select it if you wish to download it. - Vui lòng vào phần cài đặt chọn tệp tin bạn muốn tải về. - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - Thư mục %1 đã được tạo nhưng đã bị loại trừ khỏi lần đồng bộ trước. Dữ liệu bên trong nó sẽ không được đồng bộ. - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - Tệp tin %1 đã được tạo nhưng đã bị loại trừ khỏi lần đồng bộ trước. Nó sẽ không được đồng bộ. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - Không thể theo dõi một cách đáng tin cậy sự đồng bộ của thư mục. - -Điều này có nghĩa là ứng dụng có thể không tải lên ngay lập tức những thay đổi mà sẽ thỉnh thoảng quét và tải những thay đổi đó lên máy chủ (Mặc định 2 giờ 1 lần). - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - Tất cả các tệp tin trong thư mục đồng bộ '%1' đã được xóa trên máy chủ. -Việc này sẽ đồng bộ với thư mục trên máy tính cá nhân của bạn, các tệp tin sẽ bị xóa trừ khi bạn có quyền khôi phục chúng. -Nếu bạn quyết định giữ lại các tệp tin này, chúng sẽ được đồng bộ lại lên máy chủ nếu bạn có quyền. -Nếu bạn quyết định xóa các tệp tin này, chúng sẽ không khả dụng với bạn, trừ khi bạn là chủ sở hữu chúng. - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - Tất cả tệp tin trong thư mục cục bộ '%1' của bạn đã bị xóa. Việc này sẽ được đồng bộ với máy chủ, làm những tệp tin này không khả dụng trừ khi chúng được khôi phục. -Bạn có chắc chắn muốn đồng bộ lên máy chủ không? -Nếu đây là sự cố và bạn muốn giữ lại các tệp tin của mình, chúng sẽ được đồng bộ lại từ máy chủ. - - - - Remove All Files? - Xóa hết tệp tin ? - - - - Remove all files - Xóa tất cả tệp tin - - - - Keep files - Giữ các tệp tin - - - - OCC::FolderMan - - - Could not reset folder state - Không thể đặt lại trạng thái thư mục - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - Một nhật ký đồng bộ cũ '%1' được tìm thấy, nhưng không thể xóa. Vui lòng chắc chắn rằng không có ứng dụng nào đang sử dụng nó. - - - - (backup) - (sao lưu) - - - - (backup %1) - (sao lưu %1) - - - - Undefined State. - Tình trạng không xác định. - - - - Waiting to start syncing. - Đang đợi để bắt đầu đồng bộ. - - - - Preparing for sync. - Chuẩn bị đồng bộ. - - - - Sync is running. - Đang đồng bộ. - - - - Sync was successful, unresolved conflicts. - Đồng bộ thành công, chưa giải quyết xung đột. - - - - Last Sync was successful. - Lần đồng bộ sau cùng thành công - - - - Setup Error. - Thiết lập lỗi. - - - - User Abort. - Người dùng hủy bỏ. - - - - Sync is paused. - Đồng bộ đang được tạm dừng - - - - %1 (Sync is paused) - %1 (Đồng bộ được tạm dừng) - - - - No valid folder selected! - Không có thư mục được lựa chọn! - - - - The selected path does not exist! - - - - - The selected path is not a folder! - Đường dẫn lựa chọn không phải thư mục! - - - - You have no permission to write to the selected folder! - Bạn không có quyền ghi vào thư mục! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - Thư mục đã được đồng bộ từ máy chủ. Vui lòng chọn thư mục khác! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - Thư mục %1 đã chứa 1 thư mục được sử dụng trong kết nối đồng bộ hóa. Vui lòng chọn thư mục khác! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - Thư mục %1 nằm trong một thư mục được sử dụng trong kết nối đồng bộ hóa. Vui lòng chọn thư mục khác! - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - Thêm thư mục đồng bộ - - - - File - Tệp tin - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - Bạn cần kết nối để thêm thư mục - - - - Click this button to add a folder to synchronize. - Bấm chọn thư mục đồng bộ. - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - Có lỗi xảy ra khi tải về danh sách thư mục từ máy chủ. - - - - Virtual file support is enabled. - - - - - Signed out - Đăng xuất - - - - Fetching folder list from server... - Tải danh sách thư mục từ máy chủ... - - - - There are unresolved conflicts. Click for details. - Xảy ra xung đột. Bấm để xem chi tiết. - - - - Synchronizing VirtualFiles with local folder - - - - - Synchronizing with local folder - Đang đồng bộ thư mục - - - - Checking for changes in remote '%1' - Đang kiểm tra những thay đổi trên máy chủ '%1' - - - - Checking for changes in local '%1' - Đang kiểm tra những thay đổi ở máy cục bộ '%1' - - - - Reconciling changes - Dàn xếp thay đổi - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - Đang đồng bộ %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - tải về %1/s - - - - ↓ %1/s - - - - - ↑ %1/s - - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - tải lên %1/s - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 của %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - Còn lại %5, %1 của %2, tệp tin %3 của %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 của %2, tệp tin %3 của %4 - - - - file %1 of %2 - tệp tin %1 của %2 - - - - Waiting... - Đang chờ... - - - - Waiting for %n other folder(s)... - Đang chờ %n thư mục khác... - - - - Preparing to sync... - Chuẩn bị đồng bộ... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - - - - - OCC::FolderWizard - - - Add Folder Sync Connection - Thêm thư mục đồng bộ - - - - Add Sync Connection - Thêm kết nối đồng bộ - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - Bấm chọn thư mục để đồng bộ - - - - Enter the path to the local folder. - Nhập đường dẫn thư mục cục bộ - - - - Select the source folder - Chọn thư mục nguồn - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - Tạo thư mục trên máy chủ - - - - Enter the name of the new folder to be created below '%1': - Nhập tên thư mục mới '%1' - - - - Folder was successfully created on %1. - Thư mục được tạo thành công trong %1. - - - - Authentication failed accessing %1 - Xác thực không thành công khi truy cập %1 - - - - Failed to create the folder on %1. Please check manually. - Không thể tạo thư mục trong %1. Vui lòng kiểm tra thủ công. - - - - Failed to list a folder. Error: %1 - Không thể lấy danh sách thư mục. Lỗi %1 - - - - Choose this to sync the entire account - Chọn tùy chọn này nếu bạn muốn đồng bộ mọi tài khoản - - - - This folder is already being synced. - Thư mục này đã được đồng bộ. - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - Bạn đã đồng bộ hóa '%1' nó là thư mục cha của '%2'. - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - - - - - Virtual files are not available for the selected folder - - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>Cảnh báo:</b> %1 - - - - <b>Warning:</b> - <b>Cảnh báo:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - Không nhận được E-Tag từ máy chủ, kiểm tra lại Proxy/Gateway - - - - We received a different E-Tag for resuming. Retrying next time. - Nhận được E-Tag khác khi tiếp tục đồng bộ. Thử lại lần sau. - - - - We received an unexpected download Content-Length. - - - - - Server returned wrong content-range - Máy chủ trả về content-range sai - - - - OCC::GETJob - - - Connection Timeout - Kết nối hết thời gian - - - - OCC::GeneralSettings - - - Form - Mẫu - - - - General Settings - Cài đặt chung - - - - Sync hidden files - Đồng bộ tệp tin ẩn - - - - Log Settings - - - - - Advanced - Approval of folder sync (Non virtual file mode only) - - - - - Network - Mạng lưới - - - - Updates - Cập nhật - - - - stable - - - - - beta - - - - - About - Giới thiệu - - - - Advanced - Nâng cao - - - - Use &Monochrome Icons in the system tray - - - - - Ask for confirmation before synchronizing folders larger than - Yêu cầu xác nhận trước khi đồng bộ thư mục có kích thước lớn hơn - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - Yêu cầu xác nhận trước khi đồng bộ với bộ nhớ ngoài - - - - &Launch on System Startup - &Khởi động cùng hệ thống - - - - Show &Desktop Notifications - &Hiển thị thông báo trên màn hình - - - - &Update Channel - - - - - Edit &Ignored Files - &Chỉnh sửa các tệp tin bị loại trừ - - - - Show sync folders in &Explorer's Navigation Pane - - - - - S&how crash reporter - Báo cáo sự cố - - - - &Restart && Update - Khởi động lại và cập nhật - - - - You cannot disable autostart because system-wide autostart is enabled. - - - - - Change update channel? - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - - - - - Change update channel - - - - - Cancel - Hủy - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - Vui lòng nhập mật khẩu: %1<br><br>Người dùng: %2<br>Tài khoản: %3<br> - - - - Reading from keychain failed with error: '%1' - Không thể đọc Keychain. Lỗi: '%1' - - - - Enter Password - Nhập mật khẩu - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href=%1>Bấm vào đây</a> để yêu cầu mật khẩu từ giao diện web. - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - Chỉnh sửa các tệp tin bị loại trừ - - - - Files Ignored by Patterns - Tệp tin loại trừ theo mẫu - - - - Add - Thêm - - - - Pattern - Mẫu - - - - Allow Deletion - Cho phép xóa - - - - Remove - Xóa - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - Không thể mở tệp tin - - - - Cannot write changes to '%1'. - Không thể ghi sự thay đổi vào '%1' - - - - Add Ignore Pattern - Thêm mẫu bỏ qua - - - - Add a new ignore pattern: - Thêm mẫu bỏ qua mới: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - Mục này được cung cấp bởi hệ thống tại '%1' và nó không thể sửa. - - - - OCC::IssuesWidget - - - Form - Biểu mẫu - - - - List of issues - Danh sách sự cố - - - - Account - Tài khoản - - - - - <no filter> - - - - - - Folder - Thư mục - - - - Show warnings - Hiển thị cảnh báo - - - - Show ignored files - Hiển thị các tệp tin bị bỏ qua - - - - There were too many issues. Not all will be visible here. - Có quá nhiều sự cố. Không thể hiển thị hết. - - - - There were conflicts. Check the documentation on how to resolve them. - Xảy ra xung đột. Kiểm tra tài liệu để xử lý. - - - - Copy the issues list to the clipboard. - Sao chép danh sách sự cố vào bộ đệm. - - - - Copy - Sao chép - - - - Time - Thời gian - - - - File - Tệp tin - - - - Issue - Sự cố - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - Đã xảy ra xung đột. <a href=%1>Kiểm tra tài liệu để xử lý.</a> - - - - OCC::LibCloudProvidersPrivate - - - Settings - Thiết lập - - - - OCC::LogBrowser - - - Log Output - Log Output - - - - Enable logging to temporary folder - - - - - Delete logs older than %1 hours - - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - - - - - If enabled, logs will be written to: - - - - - C:/log - - - - - Log Http traffic - - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - - - - - Open folder - Mở thư mục - - - - OCC::NSISUpdater - - - New Version Available - Đã có phiên bản mới - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>Có phiên bản mới của máy khách %1 đã sẵn sàng.</p><p><b>%2</b> đã sẵn sàng để tải về. Phiên bản đã cài đặt là %3.</p> - - - - - Skip this version - Bỏ qua phiên bản này - - - - Skip this time - Bỏ qua lần này - - - - Get update - Lấy bản cập nhật - - - - Update Failed - - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - - - - - Ask again later - - - - - Restart and update - - - - - Update manually - - - - - OCC::NetworkSettings - - - Form - Mẫu - - - - Proxy Settings - Cài đặt Proxy - - - - No Proxy - Không sử dụng proxy - - - - Use system proxy - Sử dụng hệ thống proxy - - - - Specify proxy manually as - Cài đặt Proxy thủ công - - - - Host - Máy chủ - - - - : - : - - - - Proxy server requires authentication - Máy chủ proxy yêu cầu xác thực - - - - Note: proxy settings have no effects for accounts on localhost - - - - - Download Bandwidth - Băng thông tải về - - - - - Limit to - Hạn chế tối đa - - - - - KBytes/s - KBytes/s - - - - - No limit - Không giới hạn - - - - - Limit to 3/4 of estimated bandwidth - Giới hạn tối đa 3/4 băng thông - - - - Upload Bandwidth - Băng thông tải lên - - - - - Limit automatically - Tự động giới hạn - - - - Hostname of proxy server - Tên miền của máy chủ proxy - - - - Username for proxy server - Tên truy nhập cho máy chủ proxy - - - - Password for proxy server - Mật khẩu cho máy chủ proxy - - - - HTTP(S) proxy - HTTP(S) proxy - - - - SOCKS5 proxy - SOCKS5 proxy - - - - OCC::NotificationWidget - - - Created at %1 - Tạo tại %1 - - - - Closing in a few seconds... - Kết nối sẽ đóng trong vài giây... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 yêu cầu thất bại tại %2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' được chọn tại %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - Lỗi được trả về từ máy chủ: <em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - Xảy ra lỗi khi truy cập vào 'token' enpoint: <br><em>%1</em> - - - - Empty JSON from OAuth2 redirect - - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Error: Missing field %1 - - - - - - <h1>Login Error</h1><p>%1</p> - <h1>Lỗi đăng nhập</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>Sai người dùng</h1><p>Bạn đăng nhập với người dùng <em>%1</em> tuy nhiên bạn phải đăng nhập với người dùng <em>%2</em>. <br>Vui lòng đăng xuất %3 ở tab khác, sau đó <a href='%4'>bấm vào đây</a> và đăng nhập với người dùng </p> - - - - OCC::OCUpdater - - - New %1 Update Ready - Bản cập nhật mới %1 sẵn sàng - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - Bản cập nhật mới cho %1 đã sẵn sàng. Các quyền bổ sung có thể được yêu cầu trong quá trình cài đặt. - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - Không thể tải về bản cập nhật. Vui lòng bấm <a href='%1'>vào đây</a> để tải về thủ công. - - - - Could not check for new updates. - Không thể xác nhận bản cập nhật mới. - - - - Downloading %1. Please wait... - - - - - %1 available. Restart application to start the update. - - - - - New %1 available. Please use the system's update tool to install it. - - - - - Checking update server... - Kiểm tra cập nhật máy chủ... - - - - Update status is unknown: Did not check for new updates. - Trạng thái cập nhật không xác định: Không thể xác nhận phiên bản cập nhật mới. - - - - No updates available. Your installation is at the latest version. - Không có phiên bản mới. Phiên bản của bạn đang là phiên bản mới nhất. - - - - Update Check - Kiểm tra phiên bản mới - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - Kết nối đến %1 - - - - Setup local folder options - Đặt tùy chọn thư mục cục bộ - - - - Connect... - Kết nối... - - - - (experimental) - - - - - Use &virtual files instead of downloading content immediately%1 - - - - - %1 folder '%2' is synced to local folder '%3' - Thư mục %1 '%2' đã được đồng bộ với thư mục cục bộ '%3' - - - - Sync the folder '%1' - Đồng bộ thư mục '%1' - - - - <p><small><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</small></p> - <p><small><strong>Cảnh báo: </strong> Thư mục cục bộ không trống. Vui lòng chọn 1 giải pháp!</small></p> - - - - Virtual files are not available for the selected folder - - - - - Local Sync Folder - Thư mục đồng bộ cục bộ - - - - - (%1) - (%1) - - - - OCC::OwncloudConnectionMethodDialog - - - Connection failed - Kết nối thất bại - - - - <html><head/><body><p>Failed to connect to the secure server address specified. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Không thể kết nối đến địa chỉ máy chủ an toàn<em>%1</em>. Bạn có muốn tiếp tục không ?</p></body></html> - - - - Select a different URL - Chọn một URL khác - - - - Retry unencrypted over HTTP (insecure) - Thử lại mật mã qua HTTP (không an toàn) - - - - Configure client-side TLS certificate - Cấu hình chứng chỉ TLS client-side - - - - <html><head/><body><p>Failed to connect to the secure server address <em>%1</em>. How do you wish to proceed?</p></body></html> - <html><head/><body><p>Không thể kết nối đến địa chỉ máy chủ an toàn<em>%1</em>. Bạn có muốn tiếp tục không ?</p></body></html> - - - - OCC::OwncloudHttpCredsPage - - - &Email - &Email - - - - Connect to %1 - Kết nối đến %1 - - - - Enter user credentials - Nhập thông tin xác thực người dùng - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - Kết nối đến %1 - - - - Login in your browser - Đăng nhập bằng trình duyệt - - - - Copy link to clipboard - Sao chép liên kết vào bộ đệm - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - Kết nối đến %1 - - - - Setup %1 server - Cài đặt máy chủ %1 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - URL này không an toàn vì không được mã hóa. -Chúng tôi khuyên bạn không nên sử dụng. - - - - This url is secure. You can use it. - Đường dẫn này đã bảo mật. Bạn có thể sử dụng nó. - - - - &Next > - &Tiếp tục> - - - - Invalid URL - URL không hợp lệ - - - - Could not load certificate. Maybe wrong password? - Không thể tải thông tin xác thực. Có thể sai mật khẩu? - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - Không thể kết nối với %1 tại %2:<br> %3 - - - - Timeout while trying to connect to %1 at %2. - Hết thời gian khi kết nối đên %1 tại %2. - - - - The authenticated request to the server was redirected to '%1'. The URL is bad, the server is misconfigured. - Yêu cầu xác thực đến máy chủ đã được chuyển hướng đến '%1'. URL không hợp lệ, xảy ra lỗi khi cài đặt máy chủ. - - - - There was an invalid response to an authenticated webdav request - Phản hồi không hợp lệ với yêu cầu webdav đã được xác thực - - - - Access forbidden by server. To verify that you have proper access, <a href="%1">click here</a> to access the service with your browser. - Truy cập bị chặn bởi máy chủ. Để xác minh bạn có quyền truy cập phù hợp, <a href='%1'>bấm vào đây</a> để truy cập dịch vụ bằng trình duyệt. - - - - Invalid URL - URL không hợp lệ - - - - Could not create local folder %1 - Không thể tạo thư mục đồng bộ cục bộ %1 - - - - No remote folder specified! - Không có thư mục được chỉ định trên máy chủ! - - - - Error: %1 - Lỗi: %1 - - - - The folder creation resulted in HTTP error code %1 - Việc tạo thư mục đã dẫn đến mã lỗi HTTP %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - Tạo thư mục trên máy chủ không thành công do thông tin xác thực sai!<br/> Vui lòng kiểm tra lại thông tin xác thực.</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - Truy cập thư mục %1 tạo thất bại với lỗi <tt>%2</tt>. - - - - Folder rename failed - Đổi tên thư mục không thành công - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - Không thể xóa hoặc sao lưu các thư mục do thư mục hoặc tệp tin đang được mở bằng ứng dụng khác. Vui lòng đóng thư mục hoặc file và thử lại hoặc hủy cài đặt. - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 Trình hướng dẫn kết nối - - - - Enable experimental feature? - Cho phép tính năng thử nghiệm? - - - - Enable experimental placeholder mode - - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - Giữ an toàn - - - - OCC::OwncloudWizardResultPage - - - Everything set up! - Đã thiết lập xong! - - - - Open Local Folder - Mở thư mục cục bộ - - - - Open %1 in Browser - Mở %1 bằng trình duyệt - - - - OCC::PollJob - - - Invalid JSON reply from the poll URL - - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - Chức năng đồng bộ không hỗ trợ các liên kết tượng trưng. - - - - File is listed on the ignore list. - Tệp tin đã được đăng ký trong danh sách loại trừ. - - - - File names ending with a period are not supported on this file system. - Tên tệp tin kết thúc bởi ký tự không được hệ thống hỗ trợ. - - - - File names containing the character '%1' are not supported on this file system. - Tên tệp tin chứa ký tự không được hệ thống hỗ trợ '%1' - - - - File name contains at least one invalid character - Tên tệp tin chứa ít nhất một ký tự không hợp lệ - - - - The file name is a reserved name on this file system. - Tên tệp tin trùng với tên dành riêng cho hệ thống. - - - - Filename contains trailing spaces. - Tên tệp tin chứa dấu cách ở cuối. - - - - Filename is too long. - Tên tệp tin quá dài. - - - - File/Folder is ignored because it's hidden. - Đã bỏ qua tệp tin/thư mục vì bị ẩn. - - - - Stat failed. - Lỗi thu thập thông tin. - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - Xảy ra xung đột: Đã tải xuống phiên bản mới của máy chủ, bản sao cục bộ đã đổi tên nhưng chưa được tải lên. - - - - The filename cannot be encoded on your file system. - Tên tệp tin không được mã hóa bởi hệ thống. - - - - The filename is blacklisted on the server. - - - - - File has extension reserved for virtual files. - - - - - size - - - - - permissions - - - - - etag - - - - - file id - - - - - server reported no %1 - - - - - Moved to invalid target, restoring - - - - - Not allowed to remove, restoring - Bạn không thể xóa, vì vậy tôi đang khôi phục lại - - - - Ignored because of the "choose what to sync" blacklist - Bỏ qua do "chọn trong danh sách" cấm - - - - Not allowed because you don't have permission to add subfolders to that folder - Bạn không có quyền thêm thư mục con vào thư mục này - - - - Not allowed because you don't have permission to add files in that folder - Bạn không có quyền thêm tệp tin vào thư mục - - - - Not allowed to upload this file because it is read-only on the server, restoring - Bạn không có quyền tải lên do bạn chỉ có quyền đọc trên máy chủ, vì vậy tôi đang khôi phục lại - - - - Error while reading the database - - - - - Server replied with an error while reading directory '%1' : %2 - - - - - OCC::PropagateDirectory - - - Error writing metadata to the database - Xảy ra lỗi khi ghi metadata vào cơ sở dữ liệu - - - - OCC::PropagateDownloadFile - - - - File %1 can not be downloaded because of a local file name clash! - Tệp tin %1 không thể tải xuống vì xung đột với tên tệp tin cục bộ! - - - - - %1 is currently in use - - - - - The download would reduce free local disk space below the limit - Tải xuống có thể sẽ làm giảm dung lượng bộ nhớ trống xuống dưới mức giới hạn - - - - Free space on disk is less than %1 - Dung lượng bộ nhớ trống nhỏ hơn %1 - - - - File was deleted from server - Tệp tin đã bị xóa trên máy chủ - - - - The file could not be downloaded completely. - Chưa hoàn thành tải xuống tệp tin. - - - - The downloaded file is empty despite the server announced it should have been %1. - Tệp tin được tải xuống trống mặc dù máy chủ thông báo nó là %1. - - - - File %1 cannot be saved because of a local file name clash! - Tệp tin %1 không thể lưu do xung đột tên! - - - - - File has changed since discovery - Tệp tin đã thay đổi từ khi khám phá - - - - Failed to dehydrate %1, the file is currently in use - - - - - Error writing metadata to the database - Xảy ra lỗi khi ghi metadata vào cơ sở dữ liệu - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; Khôi phục không thành công: %1 - - - - A file or folder was removed from a read only share, but restoring failed: %1 - Tệp tin hoặc thư mục đã bị xóa khỏi thư mục chia sẻ chỉ cho phép đọc, nhưng khôi phục không thành công: %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - Không thể xóa tệp tin %1, lỗi: %2 - - - - Attention, possible case sensitivity clash with %1 - Chú ý, có thể xảy ra xung đột chữ cái viết hoa viết thường với %1 - - - - could not create folder %1 - Không thể tạo thư mục %1 - - - - Error writing metadata to the database - Xảy ra lỗi khi ghi metadata vào cơ sở dữ liệu - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash - Không thể xóa %1 do xung đột tên - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - Tệp tin %1 không thể đổi tên thành %2 do xung đột tên - - - - Could not rename %1 to %2, the file is currently in use - - - - - - Error writing metadata to the database - Xảy ra lỗi khi ghi metadata vào cơ sở dữ liệu - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - Mã lỗi HTTP trả về bởi máy chủ sai. Mong muốn là 204, nhưng nhận được "%1 %2" - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Mã lỗi HTTP trả về bởi máy chủ sai. Mong muốn 201, nhưng nhận được "%1 %2" - - - - Error writing metadata to the database - Xảy ra lỗi khi ghi metadata vào cơ sở dữ liệu - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - Mã lỗi HTTP trả về bởi máy chủ sai. Mong muốn 201, nhưng nhận được "%1 %2" - - - - - Error writing metadata to the database - Xảy ra lỗi khi ghi metadata vào cơ sở dữ liệu - - - - Error setting pin state - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - Tệp tin %1 không thể tải lên do có 1 tệp tin khác trùng tên, chỉ khác nhau chữ hoa chữ thường đã tồn tại - - - - File Removed - Tệp tin đã xóa - - - - Local file changed during syncing. It will be resumed. - Tệp tin đã bị thay đổi trong khi đang đồng bộ. Việc đồng bộ sẽ được tiếp tục. - - - - Local file changed during sync. - Tệp tin đã bị thay đổi trong khi đang đồng bộ. - - - - - Upload of %1 exceeds the quota for the folder - Tải lên của %1 vượt quá giới hạn của thư mục - - - - Error writing metadata to the database - Xảy ra lỗi khi ghi metadata vào cơ sở dữ liệu - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Tệp tin đã bị xóa trong khi đồng bộ. - - - - Local file changed during sync. - Tệp tin đã bị thay đổi trong khi đang đồng bộ. - - - - Poll URL missing - Thiếu Poll URL - - - - Unexpected return code from server (%1) - Mã trả về từ máy chủ không mong muốn (%1) - - - - Missing File ID from server - Thiếu ID của tệp tin từ máy chủ - - - - Missing ETag from server - Thiếu ETag từ máy chủ - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - Tệp tin đã bị xóa trong khi đồng bộ. - - - - Local file changed during sync. - Tệp tin đã bị thay đổi trong khi đang đồng bộ. - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - Poll URL missing - Thiếu Poll URL - - - - The local file was removed during sync. - Tệp tin đã bị xóa trong khi đồng bộ. - - - - Local file changed during sync. - Tệp tin đã bị thay đổi trong khi đang đồng bộ. - - - - The server did not acknowledge the last chunk. (No e-tag was present) - Máy chủ không nhận ra đoạn cuối của dữ liệu. (Không có e-tag) - - - - OCC::ProtocolWidget - - - Form - Mẫu - - - - TextLabel - Tên nhãn - - - - Open in browser - Mở bằng trình duyệt - - - - Time - Thời gian - - - - File - Tệp tin - - - - Folder - Thư mục - - - - Action - Hành động - - - - Size - Kích cỡ - - - - Local sync protocol - Trạng thái đồng bộ - - - - Copy - Sao chép - - - - Copy the activity list to the clipboard. - Chép danh sách hoạt động vào clipboard. - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - Yêu cầu xác thực proxy - - - - Username: - Tên người dùng: - - - - Proxy: - Proxy: - - - - The proxy server needs a username and password. - Máy chủ proxy yêu cầu tên người dùng và mật khẩu. - - - - Password: - Mật khẩu: - - - - TextLabel - Tên nhãn - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - Chọn những thứ cần đồng bộ - - - - OCC::SelectiveSyncWidget - - - Loading ... - Đang tải ... - - - - Deselect remote folders you do not wish to synchronize. - Bỏ chọn những thư mục trên máy chủ bạn không muốn đồng bộ. - - - - Name - Tên - - - - Size - Kích cỡ - - - - - No subfolders currently on the server. - Hiện tại không có thư mục con nào trên máy chủ. - - - - An error occurred while loading the list of sub folders. - Xảy ra lỗi khi tải danh sách thư mục con. - - - - OCC::ServerNotificationHandler - - - Dismiss - Đóng - - - - OCC::SettingsDialog - - - Settings - Cài đặt - - - - Activity - Hoạt động - - - - Add account - - - - - - Quit %1 - Thoát %1 - - - - Are you sure you want to quit %1? - - - - - Account - Tài khoản - - - - MainWindow - - - - - Hide - Ẩn - - - - OCC::ShareDialog - - - TextLabel - Tên nhãn - - - - share label - Nhãn chia sẻ - - - - Dialog - Hộp thoại - - - - ownCloud Path: - Đường dẫn ownCloud: - - - - %1 Sharing - Chia sẻ %1 - - - - %1 - %1 - - - - Folder: %2 - Thư mục: %2 - - - - The server does not allow sharing - Máy chủ không cho phép chia sẻ - - - - Retrieving maximum possible sharing permissions from server... - Đang lấy tối đa quyền chia sẻ trên máy chủ... - - - - The file can not be shared because it was shared without sharing permission. - Tệp tin không thể chia sẻ vì không có quyền. - - - - Users and Groups - Người dùng và Nhóm - - - - Public Links - Liên kết công khai - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - Share NewDocument.odt - - - - Upload only (File Drop) - Chỉ tải lên(File Drop) - - - - Receive files from others without revealing the contents of the folder. - Nhận tệp tin từ người khác không kèm thông tin của thư mục. - - - - Download / View / Edit - - - - - Name: - - - - - Enter name of new share... - - - - - &Create - - - - - TextLabel - Tên nhãn - - - - Set &password - Đặt &mật khẩu - - - - Set &expiration date - &Đặt ngày hết hạn - - - - Set password - Đặt mật khẩu - - - - Recipients can view or download contents. - Người nhận chỉ có thể xem hoặc tải nội dung. - - - - Download / View - Tải về / Xem - - - - Recipients can view, download, edit, delete and upload contents. - Người nhận có thể xem, tải về, chỉnh sửa, xóa và tải lên nội dung. - - - - Anyone with the link has access to the file/folder - Bất cứ ai liên kết đều có quyền truy cập tệp tin/thư mục - - - - - P&assword protect - B&ảo vệ bằng mật khẩu - - - - The file can not be shared because it was shared without sharing permission. - Tệp tin không thể chia sẻ vì không có quyền. - - - - Link shares have been disabled - Liên kết chia sẻ đã bị đóng - - - - Create public link share - Tạo liên kết chia sẻ công khai - - - - - Delete - Xóa - - - - Open link in browser - Mở liên kết bằng trình duyệt - - - - Copy link to clipboard - Sao chép liên kết vào bộ đệm - - - - Copy link to clipboard (direct download) - Sao chép liên kết vào bộ đệm (tải về trực tiếp) - - - - Send link by email - Gửi liên kết bằng email - - - - Send link by email (direct download) - Gửi liên kết bằng email (tải về trực tiếp) - - - - Create new... - - - - - I shared %1 with you - - - - - Confirm Link Share Deletion - Xác nhận xóa liên kết chia sẻ - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>Bạn có chắc chắn muốn xóa liên kết công khai <i>%1</i></p>Chú ý: Việc này không thể không hoàn tất.</p> - - - - Cancel - Hủy - - - - - Public link - Liên kết công khai - - - - Delete link share - Xóa liên kết chia sẻ - - - - - Please Set Password - Vui lòng đặt mật khẩu - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - Share NewDocument.odt - - - - Share with users or groups ... - Chia sẻ với Người dùng hoặc Nhóm ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - Mục này chưa được chia sẻ với bất cứ người dùng hoặc nhóm nào - - - - Open link in browser - Mở liên kết bằng trình duyệt - - - - Copy link to clipboard - Sao chép liên kết vào bộ đệm - - - - Send link by email - Gửi liên kết bằng email - - - - No results for '%1' - Không có kết quả cho '%1' - - - - I shared something with you - Tôi đã chia sẻ một vài thứ với bạn - - - - OCC::ShareUserLine - - - Form - Biểu mẫu - - - - - TextLabel - Tên nhãn - - - - can edit - có thể chỉnh sửa - - - - can share - có thể chia sẽ - - - - ... - ... - - - - create - tạo - - - - change - thay đổi - - - - delete - xóa - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - Chia sẻ với %1 - - - - Context menu share - Menu chia sẻ - - - - Context menu share %1 - - - - - Confirm deletion - - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - - - - - Do you want to delete the file <i>%1</i> permanently? - - - - - Select new location... - - - - - Error - Lỗi - - - - Moving file failed: - -%1 - - - - - I shared something with you - Tôi đã chia sẻ một vài thứ với bạn - - - - - Share... - Chia sẻ... - - - - - Copy private link to clipboard - Sao chép liên kết riêng vào bộ đệm - - - - Send private link by email... - Gửi liên kết riêng qua email... - - - - Resharing this file is not allowed - Chia sẻ lại tệp tin không được cho phép - - - - Resharing this folder is not allowed - - - - - - Copy public link to clipboard - Sao chép liên kết công khai vào bộ đệm - - - - Open in browser - Mở bằng trình duyệt - - - - Show file versions in browser - - - - - Rename... - - - - - Rename and upload... - - - - - Move and rename... - - - - - Move, rename and upload... - - - - - Delete local changes - - - - - Move and upload... - - - - - Delete - Xóa - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>Chi tiết chứng chỉ</h3> - - - - Common Name (CN): - Tên chung (CN): - - - - Subject Alternative Names: - Tên bí danh chủ đề: - - - - Organization (O): - Tổ chức (O): - - - - Organizational Unit (OU): - Đơn vị của Tổ chức (OU): - - - - State/Province: - Tỉnh/Thành Phố: - - - - Country: - Quốc gia : - - - - Serial: - Số sê-ri: - - - - <h3>Issuer</h3> - <h3>Nhà phát hành</h3> - - - - Issuer: - Nhà phát hành: - - - - Issued on: - Ngày phát hành: - - - - Expires on: - Ngày hết hạn: - - - - <h3>Fingerprints</h3> - <h3>Dấu vân tay</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>Chú ý: </b> Chứng chỉ này cần được phê duyệt theo cách thủ công</p> - - - - %1 (self-signed) - %1 (tự chứng nhận) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - Kết nối được mã hóa sử dụng %1 bit %2. - - - - - Server version: %1 - - - - - No support for SSL session tickets/identifiers - Phiên làm việc/nhận dạng SSL không được hỗ trợ - - - - Certificate information: - Thông tin chứng chỉ: - - - - The connection is not secure - - - - - This connection is NOT secure as it is not encrypted. - - Kết nối không an toàn do không được mã hóa. - - - - - OCC::SslErrorDialog - - - Form - Mẫu - - - - Trust this certificate anyway - Tin tưởng chứng chỉ mọi lúc - - - - Untrusted Certificate - Chứng chỉ không tin tưởng - - - - Cannot connect securely to <i>%1</i>: - Không thể thiết lập kết nối an toàn tới <i>%1</i> - - - - with Certificate %1 - Với xác thực %1 - - - - - - &lt;not specified&gt; - &lt;không được chỉ định&gt; - - - - - Organization: %1 - Tổ chức : %1 - - - - - Unit: %1 - Đơn vị : %1 - - - - - Country: %1 - Quốc gia : %1 - - - - Fingerprint (MD5): <tt>%1</tt> - dấu vân tay (MD5): <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - dấu vân tay (SHA1): <tt>%1</tt> - - - - Effective Date: %1 - Ngày có hiệu lực: %1 - - - - Expiration Date: %1 - Ngày hết hiệu lực: %1 - - - - Issuer: %1 - Nơi cấp : %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (bị bỏ qua do lỗi trước đó, thử lại ở %2) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - Chỉ %1 khả dụng, cần ít nhất %2 để bắt đầu - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - Không thể mở hoặc tạo cơ sở dữ liệu đồng bộ cục bộ. Hãy chắc chắn rằng bạn có quyền ghi vào thư mục đồng bộ. - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - Sắp hết dung lượng bộ nhớ trống: Tải về sẽ làm giảm dung lượng bộ nhớ trống, %1 đã được bỏ qua - - - - There is insufficient space available on the server for some uploads. - Không đủ bộ nhớ trên máy chủ để tải lên. - - - - Unresolved conflict. - Xung đột chưa được xử lý. - - - - Could not update file : %1 - - - - - Could not update virtual file metadata: %1 - - - - - Using virtual files with suffix, but suffix is not set - - - - - Unable to read the blacklist from the local database - Không thể đọc danh sách cấm từ cơ sở dữ liệu cục bộ - - - - Unable to read from the sync journal. - Không thể đọc từ tài liệu đồng bộ. - - - - Cannot open the sync journal - Không thể mở tài liệu đồng bộ - - - - Aborted - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - Checksum header định dạng không đúng. - - - - The checksum header contained an unknown checksum type '%1' - Checksum header không xác định '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - Vui lòng đăng nhập - - - - Folder %1: %2 - Thư mục %1: %2 - - - - There are no sync folders configured. - Không có thư mục đồng bộ được thiết lập. - - - - Open in browser - Mở bằng trình duyệt - - - - - - Log in... - Đăng nhập... - - - - - - Log out - Đăng xuất - - - - Recent Changes - Thay đổi gần đây - - - - Managed Folders: - Quản lý thư mục: - - - - Open folder '%1' - Mở thư mục '%1' - - - - Open %1 in browser - Mở %1 bằng trình duyệt - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - - - - - Unknown status - Trạng thái không xác định - - - - Settings... - Cài đặt... - - - - Details... - Chi tiết... - - - - Help - Giúp đỡ - - - - Quit %1 - Thoát %1 - - - - Disconnected from %1 - Ngắt kết nối với %1 - - - - Unsupported Server Version - Phiên bản máy chủ không được hỗ trợ - - - - Disconnected - Đã ngắt kết nối - - - - Disconnected from some accounts - Ngắt kết nối với một số tài khoản - - - - Disconnected from accounts: - Ngắt kết nối với tài khoản: - - - - Account %1: %2 - Tài khoản %1: %2 - - - - Signed out - Đăng xuất - - - - Account synchronization is disabled - Đồng bộ hóa tài khoản bị vô hiệu - - - - - Synchronization is paused - Tạm dừng quá trình đồng bộ - - - - Unresolved conflicts - Xung đột chưa được xử lý - - - - Error during synchronization - Xảy ra lỗi khi đồng bộ - - - - No sync folders configured - Không có thư mục đồng bộ được thiết lập - - - - Unpause all folders - Tiếp tục đồng bộ tất cả thư mục - - - - Pause all folders - Tạm dừng đồng bộ tất cả thư mục - - - - Unpause all synchronization - Tiếp tục tất cả các đồng bộ - - - - Unpause synchronization - Tiếp tục đồng bộ - - - - Pause all synchronization - Tạm dừng tất cả các đồng bộ - - - - Pause synchronization - Tạm dừng đồng bộ - - - - Log out of all accounts - Đăng xuất tất cả tài khoản - - - - Log in to all accounts... - Đăng nhập tất cả tài khoản... - - - - New account... - Tài khoản mới... - - - - About %1 - Về %1 - - - - No items synced recently - Không có mục nào được đồng bộ gần đây - - - - Checking for changes in remote '%1' - Đang kiểm tra những thay đổi trên máy chủ '%1' - - - - Checking for changes in local '%1' - Kiểm tra những thay đổi cục bộ '%1' - - - - Syncing %1 of %2 (%3 left) - Đang đồng bộ %1 của %2 (Còn lại %3) - - - - Syncing %1 of %2 - Đang đồng bộ %1 của %2 - - - - Syncing %1 (%2 left) - Đang đồng bộ %1 (Còn lại %2) - - - - Syncing %1 - Đang đồng bộ %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - Up to date - Đã cập nhật phiên bản mới nhất - - - - OwncloudAdvancedSetupPage - - - Form - Mẫu - - - - - - - - TextLabel - Tên nhãn - - - - Server - Máy chủ - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>Nếu bạn chọn tùy chọn này, nội dung trong thư mục sẽ bị xóa để bắt đầu đồng bộ lại với máy chủ. </p><p>Vui lòng không chọn tùy chọn này nếu nội dung trong thư mục có thể được tải lên máy chủ.</p></body></html> - - - - Start a &clean sync (Erases the local folder!) - Bắt đầu đồng bộ hóa sạch (Thư mục cục bộ sẽ bị xóa!) - - - - Ask for confirmation before synchroni&zing folders larger than - Yêu cầu xác nhận trước khi đồng bộ thư mục kích thước lớn hơn - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - Yêu cầu xác nhận trước khi đồng bộ từ bộ nhớ ngoài - - - - Choose what to sync - Chọn thứ cần đồng bộ - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>Khi tùy chọn này được chọn, trình hướng dẫn sẽ đóng mà không đồng bộ bất cứ thứ gì. Bạn có thể sử dụng tùy chọn "Thêm kết nối đồng bộ" trong phần thiết lập tài khoản để chọn thư mục cục bộ và thư mục trên máy chủ để đồng bộ</p></body></html> - - - - Manually create folder sync connections - Tạo thư mục đồng bộ thủ công - - - - &Local Folder - &Thư mục cục bộ - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - &Giữ lại dữ liệu cục bộ - - - - S&ynchronize everything from server (recommended) - - - - - Status message - Tình trạng - - - - OwncloudHttpCredsPage - - - Form - Mẫu - - - - &Username - &Tài khoản - - - - &Password - &Mật khẩu - - - - OwncloudOAuthCredsPage - - - Form - Biểu mẫu - - - - Please switch to your browser to proceed. - Vui lòng sử dụng bằng trình duyệt. - - - - An error occurred while connecting. Please try again. - Xảy ra lỗi khi kết nối. Vui lòng thử lại. - - - - Re-open Browser - Mở lại trình duyệt - - - - OwncloudSetupPage - - - Form - Form - - - - - TextLabel - Tên nhãn - - - - Ser&ver Address - &Địa chỉ máy chủ - - - - https://... - https://... - - - - Error Label - Lỗi nhãn - - - - OwncloudWizardResultPage - - - Form - Form - - - - TextLabel - Tên nhãn - - - - Your entire account is synced to the local folder - Toàn bộ tài khoản của bạn được đồng bộ hóa với các thư mục - - - - - PushButton - Nút bấm - - - - QObject - - - in the future - trong tương lai - - - - %n day(s) ago - cách đây %n ngày - - - - %n hour(s) ago - cách đây %n giờ - - - - now - ngay bây giờ - - - - Less than a minute ago - cách đây chưa đến 1 phút - - - - %n minute(s) ago - cách đây %n phút - - - - Some time ago - Một thời gian trước - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B%L1 B - - - - %n year(s) - %n năm - - - - %n month(s) - %n tháng - - - - %n day(s) - %n ngày - - - - %n hour(s) - %n giờ - - - - %n minute(s) - %n phút - - - - %n second(s) - %n giây - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - System Tray không có sẵn - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 yêu cầu hoạt động ở system tray. Nếu bạn đang chạy XFCE, vui lòng làm theo hướng dẫn tại <a href="http://docs.xfce.org/xfce/xfce4-panel/systray"></a>. Nếu không vui lòng cài đặt ứng dụng system tray như &apos;trayer&apos; và thử lại. - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3 %4%8%9Libraries Qt %5, %6%8Using virtual files plugin: %7%8 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - - - - - Replaced by virtual file - - - - - Downloaded - Đã tải xuống - - - - Uploaded - Đã tải lên - - - - Server version downloaded, copied changed local file into conflict file - Phiên bản máy chủ đã được tải xuống, đã sao chép các tệp tin thay đổi vào các tệp tin xung đột - - - - Deleted - Đã xóa - - - - Moved to %1 - Chuyển đến 1% - - - - Ignored - bỏ qua - - - - Filesystem access error - Lỗi truy cập tập tin hệ thống - - - - Error - Lỗi - - - - Updated local metadata - Cập nhật metadata cục bộ - - - - - Unknown - Chưa biết - - - - downloading - đang tải xuống - - - - uploading - đang tải lên - - - - deleting - đang xóa - - - - moving - đang di chuyển - - - - ignoring - bỏ qua - - - - - error - lỗi - - - - updating local metadata - Đang cập nhật metadata cục bộ - - - - theme - - - Status undefined - Trạng thái không xác định - - - - Waiting to start sync - Vui lòng chờ để bắt đầu đồng bộ - - - - Sync is running - Đồng bộ đạng chạy - - - - Sync Success - Đồng bộ thành công - - - - Sync Success, some files were ignored. - Đồng bộ thành công, một số tập tin đã được bỏ qua. - - - - Sync Error - Đồng bộ lỗi - - - - Setup Error - Cài đặt bị lỗi - - - - Preparing to sync - Chuẩn bị đồng bộ - - - - Aborting... - Hủy bỏ... - - - - Sync is paused - Đồng bộ đang được tạm dừng - - - - utility - - - Could not open browser - Không thể mở trình duyệt - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - Xảy ra lỗi khi mở trình duyệt đến URL %1. Có thể do không có trình duyệt mặc định nào được thiết lập? - - - - Could not open email client - Không thể mở email - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - Xảy ra lỗi khi mở email để gửi. Có thể do không có email mặc định nào được thiết lập? - - - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - - - - - Make always available locally - - - - - Free up local space - - - - \ No newline at end of file diff --git a/translations/client_zh_CN.ts b/translations/client_zh_CN.ts deleted file mode 100644 index ee6f6746c55..00000000000 --- a/translations/client_zh_CN.ts +++ /dev/null @@ -1,4892 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - 取消 - - - - Send this report - 发送报告 - - - - Don't send - 不发送 - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - 关闭 - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - 发送崩溃信息失败。 - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - 无法在回收站创建目录 - - - - - Could not move '%1' to '%2' - 无法移动 '%1' 到 '%2' - - - - Moving to the trash is not implemented on this platform - 移动到回收站无法在此平台执行 - - - - Error removing '%1': %2 - 删除 '%1' 时发生错误 :%2 - - - - Could not remove folder '%1' - 无法删除文件夹 %1 - - - - FolderWizardSourcePage - - - Form - 窗体 - - - - Pick a local folder on your computer to sync - 选择一个进行同步的本地文件夹: - - - - &Choose... - 选择 (&C)... - - - - FolderWizardTargetPage - - - Form - 窗体 - - - - Select a remote destination folder - 选择远端目标文件夹 - - - - Create Folder - 创建文件夹 - - - - Refresh - 刷新 - - - - Folders - 文件夹 - - - - TextLabel - 文本标签 - - - - NotificationWidget - - - Form - 窗体 - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - 文本标签 - - - - OCC::AboutDialog - - - About - 关于 - - - - TextLabel - 文本标签 - - - - Versions - 版本 - - - - About %1 - 关于%1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - 连接超时 - - - - Unknown error: network reply was deleted - 未知错误:网络相应被删除 - - - - Server replied "%1 %2" to "%3 %4" - 服务器返回"%1 %2" to "%3 %4"  - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - 窗体 - - - - Re-open Browser - 重新打开浏览器 - - - - ... - ... - - - - Storage space: ... - 存储空间:... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - 取消选中的文件夹将会从本地<b>删除</b>,并不再同步到这台电脑上。 - - - - Synchronize all - 同步全部 - - - - Synchronize none - 都不同步 - - - - Apply manual changes - 应用手工修改 - - - - Apply - 应用 - - - - - - - Cancel - 取消 - - - - Connected with <server> as <user> - <user> 已经连接到 <server> - - - - No account configured. - 没有配置的帐号。 - - - - Remove - 移除 - - - - Account - 账户 - - - - Choose what to sync - 选择同步内容 - - - - Force sync now - 强制同步 - - - - Restart sync - 重新开始同步 - - - - Remove folder sync connection - 断开文件夹同步 - - - - Folder creation failed - 文件夹创建失败 - - - - <p>Could not create local folder <i>%1</i>. - <p>无法创建文件夹 <i>%1</i>。 - - - - Confirm Folder Sync Connection Removal - 确定断开文件夹同步 - - - - Remove Folder Sync Connection - 断开文件夹同步 - - - - Disable virtual file support? - 是否取消虚拟文件? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - - - - - Disable support - 取消支持 - - - - Sync Running - 正在同步 - - - - The syncing operation is running.<br/>Do you want to terminate it? - 正在执行同步。<br />您确定要关闭它吗? - - - - %1 in use - %1 使用中 - - - - %1 as <i>%2</i> - %1, <i>%2</i> - - - - Connected to %1. - 连接到 %1。 - - - - Server %1 is temporarily unavailable. - 远程服务器%1暂时不可用。 - - - - Server %1 is currently in maintenance mode. - 服务器%1处于维护模式。 - - - - Signed out from %1. - 从 %1 退出 - - - - Connecting to %1... - 正在连接到 %1 - - - - No connection to %1 at %2. - 没有到位于%2中的%1的连接 - - - - Log in - 登录 - - - - There are folders that were not synchronized because they are too big: - 以下目录由于太大而没有同步: - - - - There are folders that were not synchronized because they are external storages: - 以下目录由于是外部存储而没有同步: - - - - There are folders that were not synchronized because they are too big or external storages: - 以下目录由于太大或是外部存储而没有同步: - - - - Confirm Account Removal - 确认删除账号 - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>你确定要删除账号的连接? <i>%1</i>?</p><p><b>Note:</b> 这 <b>不会</b> 删除任何文件</p> - - - - Remove connection - 删除连接 - - - - Open folder - 打开文件夹 - - - - - Log out - 注销 - - - - Open local folder - 打开本地文件夹 - - - - Open folder in browser - 在浏览器中打开文件夹 - - - - Resume sync - 恢复同步 - - - - Pause sync - 暂停同步 - - - - Availability - 可用性 - - - - Disable virtual file support... - 关闭虚拟文件 - - - - Enable virtual file support%1... - - - - - (experimental) - (试验性) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>你确定要停止文件夹同步? <i>%1</i>?</p><p><b>Note:</b> 这 <b>不会</b> 删除任何文件</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) of %2 使用中。一些文件夹,例如网络挂载的和共享的文件夹,可能有不同的限制。 - - - - %1 of %2 in use - 使用量 %1 / %2 - - - - Currently there is no storage usage information available. - 目前没有储存使用量信息可用。 - - - - The server version %1 is unsupported! Proceed at your own risk. - 不支持服务器版本 %1!继续操作将自行承担风险。 - - - - Obtaining authorization from the browser. - 从浏览器获取授权。 - - - - Server configuration error: %1 at %2. - 服务器配置错误:%1,位于%2。 - - - - No %1 connection configured. - 没有 %1 连接配置。 - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - 拒绝 - - - - Signed out - 已退出 - - - - Disconnected - 连接已断开 - - - - Connected - 已连接 - - - - Service unavailable - 服务不可用 - - - - Maintenance mode - 维护模式 - - - - Network error - 网络错误 - - - - Configuration error - 配置错误 - - - - Asking Credentials - 请求证书 - - - - Unknown account state - 未知的账户状态 - - - - OCC::ActivityListModel - - - %1 %2 on %3 - - - - - Activity - 动态 - - - - Account - 账户 - - - - Time - 时间 - - - - Local path - 本地路径 - - - - OCC::ActivitySettings - - - - Server Activity - 服务器动态 - - - - Sync Protocol - 同步协议 - - - - Not Synced - 未同步 - - - - Not Synced (%1) - %1 is the number of not synced files. - 未同步 (%1) - - - - OCC::ActivityWidget - - - Form - 窗体 - - - - - - TextLabel - 文本标签 - - - - Filter - 过滤器 - - - - Server Activities - 服务器动态 - - - - Notifications - 通知 - - - - %n notifications(s) for %1. - - - - - %n notifications(s) for %1 and %2. - - - - - New notifications for %1, %2 and other accounts. - - - - - Open the activity view for details. - 打开活动视图了解详情。 - - - - Copy to clipboard - 复制到剪贴板 - - - - Show in file browser - 在文件浏览器中显示 - - - - <br/>Account %1 does not have activities enabled. - <br/>帐户%1没有同步活动。 - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - 继续操作意味着<b>删除这些设置</b> - - - - Continuing will mean <b>ignoring these settings</b>. - 继续操作意味着<b>忽略这些设置</b> - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - - - - - Quit - 退出 - - - - Continue - 继续 - - - - Error accessing the configuration file - 访问配置文件时发生错误 - - - - There was an error while accessing the configuration file at %1. - 访问配置文件 %1 时发生错误。 - - - - Quit %1 - 退出 %1 - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - 更多信息,见 http://www.owncloud.org - - - - Show the settings dialog while starting. - 在启动时显示设置对话框。 - - - - Quit the running instance. - 退出正在运行的实例。 - - - - Write log to file (use - to write to stdout). - 将日志写到文件中(用 - 来写到 stdout)。 - - - - Write each sync log output in a new file in folder. - 将每个同步日志输出到文件夹中的一个新文件里。 - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - 每次写完后都刷新日志文件。 - - - - Output debug-level messages in the log. - 在日志中输出调试级别的信息。 - - - - - Override UI language. - 覆盖用户界面语言。 - - - - Use the given configuration folder. - 使用给定的配置文件夹。 - - - - Enable debug mode. - 启用调试模式。 - - - - Virtual file system file to be opened (optional). - 要打开的虚拟文件系统文件(可选)。 - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - 传递给 --confdir 的路径无效 - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - 错误:未知的语言“%1”(使用 --list-languages 来获得支持的翻译语言的完整列表)。 - - - - Available translations: %1 - 可用的翻译: %1 - - - - OCC::AuthenticationDialog - - - Authentication Required - 需要认证 - - - - Enter username and password for '%1' at %2. - 输入 %1 (%2)的用户名和密码 - - - - &User: - 用户名 (&U): - - - - &Password: - 密码 (&P): - - - - OCC::ConnectionValidator - - - No ownCloud account configured - 没有已经配置的 ownCloud 帐号 - - - - The configured server for this client is too old - 此客户端连接到的服务器版本过旧 - - - - Please update to the latest server and restart the client. - 请更新到最新的服务器版本然后重启客户端。 - - - - Authentication error: Either username or password are wrong. - 认证失败:用户名或密码错误 - - - - timeout - 超时 - - - - The provided credentials are not correct - 提供的证书不正确 - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - 解析凭证 %1 失败 - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - 服务器错误: PROPFIND 返回的不是XML格式! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - 打开目录 %1 时出现错误 - - - - Directory not accessible on client, permission denied - 无法在客户端上访问目录,没有权限 - - - - Directory not found: %1 - 无法找到目录:%1 - - - - Filename encoding is not valid - 文件名编码无效 - - - - Error while reading directory %1 - 读取目录 %1 时出错 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - 重置列的大小 - - - - OCC::Folder - - - Local folder %1 does not exist. - 本地文件夹 %1 不存在。 - - - - %1 should be a folder but is not. - %1 应该是一个文件夹,但是它现在不是文件夹 - - - - %1 is not readable. - %1 不可读。 - - - - %1 has been removed. - %1 names a file. - %1 已移除。 - - - - %1 has been updated. - %1 names a file. - %1 已更新。 - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 已更名为 %2。 - - - - %1 has been moved to %2. - %1 已移动至 %2。 - - - - %1 and %n other file(s) have been removed. - %1 和 %n 其它文件已被移除。 - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - %1 文件等 %n个文件已经成功添加 - - - - %1 has been added. - %1 names a file. - %1 成功添加 - - - - %1 and %n other file(s) have been updated. - %1 和 %n 其它文件已更新。 - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1 已经更名为 %2,其它 %3 文件也已更名。 - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1 已移动到 %2,其它 %3 文件也已移动。 - - - - %1 has and %n other file(s) have sync conflicts. - %1 和 %n 其他文件有同步冲突。 - - - - %1 has a sync conflict. Please check the conflict file! - %1 有同步冲突。请检查冲突文件! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - %1 和 %n 其他文件由于错误不能同步。详细信息请查看日志。 - - - - %1 could not be synced due to an error. See the log for details. - %1 同步出错。详情请查看日志。 - - - - Sync Activity - 同步活动 - - - - Could not read system exclude file - 无法读取系统排除的文件 - - - - A new folder larger than %1 MB has been added: %2. - - 一个大于 %1 MB 的新文件夹 %2 已被添加。 - - - - - A folder from an external storage has been added. - - 一个来自外部存储的文件夹已被添加。 - - - - - Please go in the settings to select it if you wish to download it. - 如果您想下载,请到设置页面选择它。 - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - %1 文件被创建但在之前的同步中被拒绝了.它将不会被同步. - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - 同步文件夹'%1'的所有文件已经在服务器端删除。 -删除动作会被同步到本地的同步文件夹,内部的文件将不可用,您需要有相应权限来恢复。 -如果您想保留这些文件,同时您有相应权限的话,它们将被重新同步到服务器。 -如果您决定删除这些文件,它们将不再可用,除非您是其所有者。 - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - 本地文件夹'%1'的所有文件都已经被删除。这些删除动作会被同步到您的服务器,相应的文件将不再可用,除非被回复。 -确定要把这些动作同步到服务器吗? -如果这是一个意外而您想要保留这些文件,他们会被重新从服务器同步过来。 - - - - Remove All Files? - 删除所有文件? - - - - Remove all files - 删除所有文件 - - - - Keep files - 保持所有文件 - - - - OCC::FolderMan - - - Could not reset folder state - 不能重置文件夹状态 - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - 一个旧的同步日志 '%1' 被找到,但是不能被移除。请确定没有应用程序正在使用它。 - - - - (backup) - (备份) - - - - (backup %1) - (备份 %1) - - - - Undefined State. - 未知状态。 - - - - Waiting to start syncing. - 等待启动同步。 - - - - Preparing for sync. - 准备同步。 - - - - Sync is running. - 同步正在运行。 - - - - Sync was successful, unresolved conflicts. - 同步成功,但有未解决的冲突。 - - - - Last Sync was successful. - 最后一次同步成功。 - - - - Setup Error. - 安装失败 - - - - User Abort. - 用户撤销。 - - - - Sync is paused. - 同步已暂停。 - - - - %1 (Sync is paused) - %1 (同步已暂停) - - - - No valid folder selected! - 没有选择有效的文件夹! - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - 您选择的目录不存在! - - - - The selected path is not a folder! - 选择的路径不是一个文件夹! - - - - You have no permission to write to the selected folder! - 你没有写入所选文件夹的权限! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - 已经有一个从服务器到此文件夹的同步设置。请选择其他本地文件夹! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - 本地文件夹 %1 包含有正在使用的同步文件夹,请选择另一个! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - 本地文件夹 %1 是正在使用的同步文件夹,请选择另一个! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - 添加同步文件夹 - - - - File - 文件 - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - 请先登录后再添加文件夹 - - - - Click this button to add a folder to synchronize. - 点击选择进行同步的本地文件夹。 - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - 载入文件夹列表时发生错误。 - - - - Virtual file support is enabled. - 虚拟文件功能已开启 - - - - Signed out - 已登出 - - - - Fetching folder list from server... - 获取文件夹列表... - - - - There are unresolved conflicts. Click for details. - 有未解决的冲突。点击显示详细信息。 - - - - Checking for changes in remote '%1' - 检查远端文件 '%1' 的变动 - - - - Checking for changes in local '%1' - 检查本地文件 '%1' 的变动 - - - - Reconciling changes - 调整变更 - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - 正在同步 %1 - - - - - , - - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - 下载 %1/s - - - - ↓ %1/s - ↓ %1/秒 - - - - ↑ %1/s - ↑ %1/秒 - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - 上传 %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 / %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - 剩余: %5,%1 / %2, 文件数量 %3 / %4 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 of %2, file %3 of %4 - - - - file %1 of %2 - 第 %1 个文件,共 %2 个 - - - - Waiting... - 请稍等... - - - - Waiting for %n other folder(s)... - 等待 %n 个其他文件(文件夹) - - - - Preparing to sync... - 准备同步... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - 监控没有收到测试通知。 - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - 这个问题通常发生在 inotify 监控耗尽时。请查看常见问题了解详情。 - - - - OCC::FolderWizard - - - Add Folder Sync Connection - 添加同步文件夹 - - - - Add Sync Connection - 添加同步连接 - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - 点击选择进行同步的本地文件夹。 - - - - Enter the path to the local folder. - 输入本地文件夹的路径。 - - - - Select the source folder - 选择源目录 - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - 创建远程文件夹 - - - - Enter the name of the new folder to be created below '%1': - 输入 %1 中的新文件夹的名称: - - - - Folder was successfully created on %1. - 文件夹在 %1 上创建成功。 - - - - Authentication failed accessing %1 - 访问 %1 时认证失败 - - - - Failed to create the folder on %1. Please check manually. - 无法在 %1 处创建文件夹。请自行检查。 - - - - Failed to list a folder. Error: %1 - 列表失败。错误: %1 - - - - Choose this to sync the entire account - 选择此项以同步整个账户 - - - - This folder is already being synced. - 文件夹已在同步中。 - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - 你已经在同步 <i>%1</i>,<i>%2</i> 是它的一个子文件夹。 - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - - - - - (experimental) - (试验性) - - - - Virtual files are not available for the selected folder - 所选文件夹没有虚拟文件 - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>警告:</b>%1 - - - - <b>Warning:</b> - <b>警告:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - 未能收到来自服务器的 E-Tag,请检查代理/网关 - - - - We received a different E-Tag for resuming. Retrying next time. - 我们收到了不同的恢复 E-Tag,将在下次尝试。 - - - - We received an unexpected download Content-Length. - 我们收到了一个意外的下载Content-Length - - - - Server returned wrong content-range - 服务器返回了错误的内容长度 - - - - OCC::GETJob - - - Connection Timeout - 连接超时 - - - - OCC::GeneralSettings - - - Form - 窗体 - - - - General Settings - 常规设置 - - - - Language - 语言 - - - - Sync hidden files - 同步隐藏文件 - - - - Log Settings - 日志设置 - - - - Advanced - Approval of folder sync (Non virtual file mode only) - 高级 - 批准文件夹同步(仅适用于非虚拟文件模式)。 - - - - Network - 网络 - - - - Updates - 更新 - - - - stable - 稳定的 - - - - beta - 公测的 - - - - About - 关于 - - - - Advanced - 高级 - - - - Use &Monochrome Icons in the system tray - 在系统托盘中使用单色图标 (&M) - - - - Ask for confirmation before synchronizing folders larger than - 请询问确认同步,若同步文件夹大于 - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - 请询问确认同步,若涉及外部存储 - - - - &Launch on System Startup - 在系统启动时启动 - - - - Show &Desktop Notifications - 显示桌面提醒 - - - - &Update Channel - &更新通道 - - - - Edit &Ignored Files - 编辑忽略的文件 - - - - Show sync folders in &Explorer's Navigation Pane - 在资源管理器的导航窗格中显示同步文件夹 (&E) - - - - S&how crash reporter - 显示崩溃报告器 - - - - &Restart && Update - 重启并更新 (&R) - - - - You cannot disable autostart because system-wide autostart is enabled. - 你不能禁用自动启动,因为全系统的自动启动已启用。 - - - - Change update channel? - 更改升级渠道? - - - - Warning - 警告 - - - - Language changes require a restart of this application to take effect. - 语言的改变需要重新启动这个应用程序才能生效。 - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - 更新通道决定了将提供哪些客户端更新来进行安装。“稳定”通道只包含被认为是可靠的升级,而“测试”通道中的版本可能包含较新的功能和错误修复,但尚未经过全面测试。 - -请注意,这只会从中选择进行池升级的版本,且不包含可降级的:因此,通常无法立即从测试通道回到稳定通道,这意味着要等待比当前安装的测试版本更新的稳定版本。 - - - - Change update channel - 更改升级渠道 - - - - Cancel - 取消 - - - - unknown (%1) - - - - - (use default) - (使用默认值) - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - 请输入 %1 密码:<br><br>用户:%2<br>账户:%3<br> - - - - Reading from keychain failed with error: '%1' - 获取密钥链失败,错误: '%1' - - - - Enter Password - 输入密码 - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">点击这里</a>从 web 界面请求一个 app 密码。 - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - 忽略的文件编辑器 - - - - Files Ignored by Patterns - 文件忽略模式: - - - - Add - 增加 - - - - Pattern - 模式 - - - - Allow Deletion - 允许删除 - - - - Remove - 移除 - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - - - - - Could not open file - 不能打开文件 - - - - Cannot write changes to '%1'. - 无法向 %1 中写入修改。 - - - - Add Ignore Pattern - 增加忽略模式 - - - - Add a new ignore pattern: - 增加新的忽略模式: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - 此项目由系统在 %1 处提供,不能在这里被修改。 - - - - OCC::IssuesWidget - - - Form - 窗体 - - - - List of issues - 问题列表 - - - - Filter - 过滤器 - - - - There were too many issues. Not all will be visible here. - 有太多问题,这里并不能看见所有的问题. - - - - There were conflicts. Check the documentation on how to resolve them. - 有冲突。查看有关如何解决它们的文档 - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - 重置列的大小 - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - 有冲突.<a href="%1">查阅相关解决问题的文档</a> - - - - Account - 账户 - - - - OCC::LibCloudProvidersPrivate - - - Settings - 设置 - - - - OCC::LogBrowser - - - Log Output - 日志输出 - - - - Enable logging to temporary folder - 启用日志记录到临时文件夹 - - - - Delete logs older than %1 hours - 删除 %1 小时以前的日志 - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - 客户端可以将调试日志写到一个临时文件夹中。这些日志对诊断问题非常有帮助。 -由于日志文件可能会变大,客户端会在每次同步运行时启动一个新的日志,并压缩旧的日志。也建议启用在几个小时后删除日志文件,以避免消耗太多的磁盘空间。 - - - - If enabled, logs will be written to: - 如果启用,日志将被写到: - - - - C:/log - C:/log - - - - Log Http traffic - 记录 Http 流量 - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - 这些设置在客户端重启后会持续存在。 -注意,使用任何日志命令行选项将覆盖这些设置。 - - - - Open folder - 打开文件夹 - - - - OCC::Models - - - %1 Filter: - - - - - No filter - 没有筛选 - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - 新版本可用 - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>新版本的 %1 客户端可用。</p><p><b>%2</b> 已经开放下载。已安装的版本是 %3。</p> - - - - - Skip this version - 跳过这个版本 - - - - Skip this time - 本次跳过 - - - - Get update - 获取更新 - - - - Update Failed - 更新失败 - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>新版本的 %1 的客户端可用,但是更新失败</p><p><b>%2</b> 已经下载完毕. 已安装的版本是 %3.</p> - - - - Ask again later - 以后询问我 - - - - Restart and update - 重启以更新 - - - - Update manually - 手动更新 - - - - OCC::NetworkSettings - - - Form - 窗体 - - - - Proxy Settings - 代理设置 - - - - No Proxy - 无代理 - - - - Use system proxy - 使用系统代理 - - - - Specify proxy manually as - 手动设置代理为 - - - - Host - 主机 - - - - : - - - - - Proxy server requires authentication - 代理服务器需要认证 - - - - Note: proxy settings have no effects for accounts on localhost - 注意:代理设置对本地主机上的账户没有影响 - - - - Download Bandwidth - 下载带宽 - - - - - Limit to - 限制为 - - - - - KBytes/s - KBytes/s - - - - - No limit - 无限制 - - - - - Limit to 3/4 of estimated bandwidth - 限制使用 3/4 的带宽 - - - - Upload Bandwidth - 上传带宽 - - - - - Limit automatically - 自动限制 - - - - Hostname of proxy server - 代理服务器主机名 - - - - Username for proxy server - 代理服务器用户名 - - - - Password for proxy server - 代理服务器密码 - - - - HTTP(S) proxy - HTTP(S) 代理 - - - - SOCKS5 proxy - SOCKS5 代理 - - - - OCC::NotificationWidget - - - Created at %1 - 创建于 %1 - - - - Closing in a few seconds... - 几秒钟后关闭... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1 请求失败于 %2 - - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' 选定于 %2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - 来自服务器返回的错误<em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - 在'token' 结尾有个错误: <br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - 不能解析来自服务器端的JSON:<br><em>%1</em> - - - - Unsupported token type: %1 - - - - - The reply from the server did not contain all expected fields -:%1 - - - - - Unknown Error - 未知错误 - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - - - - - Oauth2 Error - Oauth2 错误 - - - - Oauth2 authentication requires a secured connection. - Oauth2认证需要一个安全的连接。 - - - - <h1>Login Error</h1><p>%1</p> - <h1>登录错误</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>错误用户</h1><p>你登陆的用户<em>%1</em>,但必须以用户<em>%2</em>的身份登录.<br>请在另一个标签 %3登出,然后<a href='%4'>点击这</a>并以用户%2登录</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - 新的 %1 更新就绪 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - 新版本&1已经可以更新。更新过程会请求额外的权限。 - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - 无法下载更新,请点击<a href='%1'>此处</a>手动下载更新。 - - - - Could not check for new updates. - 无法检查新更新。 - - - - Downloading %1. Please wait... - 正在下载文件 %1,请稍候.... - - - - %1 available. Restart application to start the update. - %1 是可用的,请重启软件来开始升级 - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - 新的 %1 是可用的,请使用升级工具来安装它 - - - - Checking update server... - 检查更新服务器 - - - - Update status is unknown: Did not check for new updates. - 还没有检查过更新。 - - - - No updates available. Your installation is at the latest version. - 没有可用更新。您的安装已为最新版本。 - - - - Update Check - 检查更新 - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - 连接到 %1 - - - - Setup local folder options - 设置本地文件夹 - - - - Connect... - 连接... - - - - (experimental) - (试验性) - - - - Use &virtual files instead of downloading content immediately%1 - - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - 同步文件夹 %1 - - - - Virtual files are not available for the selected folder - 所选文件夹没有虚拟文件 - - - - Error - 错误 - - - - Local Sync Folder - 本地同步文件夹 - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &电子邮件 - - - - Connect to %1 - 连接到 %1 - - - - Enter user credentials - 输入用户密码 - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - 连接到 %1 - - - - Login in your browser - 在浏览器中登录 - - - - Copy link to clipboard - 复制链接到剪贴板 - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - 连接到 %1 - - - - Setup %1 server - 设置 %1 服务器 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - 这个地址没有使用加密,不够安全,不建议使用。 - - - - This url is secure. You can use it. - 此地址是安全的。您可以使用它 - - - - &Next > - 下一步 (&N) > - - - - Invalid URL - 无效URL - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - 连接到 %1 (%2)失败:<br />%3 - - - - Timeout while trying to connect to %1 at %2. - 连接到 %1 (%2) 时超时。 - - - - Invalid URL - 无效URL - - - - Could not create local folder %1 - 不能创建本地文件夹 %1 - - - - Error: %1 - 错误:%1 - - - - The folder creation resulted in HTTP error code %1 - 创建文件夹出现 HTTP 错误代码 %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - 远程文件夹创建失败,因为提供的凭证有误!<br/>请返回并检查您的凭证。</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - 创建远程文件夹 %1 失败,错误为 <tt>%2</tt>。 - - - - Folder rename failed - 文件夹更名失败 - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - 无法移除和备份文件夹,由于文件夹或文件正在被另一程序占用。请关闭程序后重试,或取消安装。 - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1 链接向导 - - - - Enable experimental feature? - 启用实验性功能? - - - - Enable experimental placeholder mode - 启用实验性占位符模式 - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - - - - - Stay safe - 保持安全 - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - 符号链接不被同步支持。 - - - - File is listed on the ignore list. - 文件在忽略列表中。 - - - - File names ending with a period are not supported on this file system. - 文件名结尾不可以为“.”。 - - - - File names containing the character '%1' are not supported on this file system. - 此文件系统不支持包含字符 '%1' 的文件名。 - - - - File name contains at least one invalid character - 文件名中存在至少一个非法字符 - - - - The file name is a reserved name on this file system. - 文件名为系统保留文件名。 - - - - Filename contains trailing spaces. - 文件名尾部含有空格 - - - - Filename is too long. - 文件名过长。 - - - - File/Folder is ignored because it's hidden. - 已忽略隐藏的文件和文件夹。 - - - - Stat failed. - 状态失败。 - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - 冲突:服务器版本已下载,本地版本被重命名但未上传 - - - - The filename cannot be encoded on your file system. - 文件名在你的文件系统中不能编码 - - - - The filename is blacklisted on the server. - 该文件名在服务器的黑名单上 - - - - size - 大小 - - - - permissions - 权限 - - - - etag - etag - - - - file id - 文件ID - - - - server reported no %1 - 服务器回复没有 %1 - - - - Moved to invalid target, restoring - 已转移到无效目标,正在恢复 - - - - Not allowed to remove, restoring - 无法删除,正在回退 - - - - Ignored because of the "choose what to sync" blacklist - 已忽略(“选择同步内容”黑名单) - - - - Not allowed because you don't have permission to add subfolders to that folder - 你没有权限增加子目录 - - - - Not allowed because you don't have permission to add files in that folder - 你没有权限增加文件 - - - - Not allowed to upload this file because it is read-only on the server, restoring - 无法上传文件,因为服务器端此文件为只读,正在回退 - - - - Error while reading the database - 读取数据库是出错 - - - - Server replied with an error while reading directory '%1' : %2 - 服务器读取目录时出错,目录 '%1' : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - 在当前限制下,下载将会减少本地磁盘的剩余空间. - - - - Free space on disk is less than %1 - 空闲磁盘空间少于 %1 - - - - File was deleted from server - 已从服务器删除文件 - - - - The file could not be downloaded completely. - 文件无法完整下载。 - - - - The downloaded file is empty despite the server announced it should have been %1. - 虽然服务器宣称已完成 %1,但实际下载文件为空。 - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - 自从发现文件以来,它已经被改变了 - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ;恢复失败:%1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - 不能删除文件 %1, 错误: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - 无法创建文件夹 %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - 由于本地文件名冲突,文件 %1 无法更名为 %2 - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - 服务器返回的 HTTP 状态错误,应返回 204,但返回的是“%1 %2”。 - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - 服务器返回的 HTTP 状态错误,应返回 201,但返回的是“%1 %2”。 - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - 由于文件锁定,设置文件状态失败 - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - 无法重命名 %1 变 %2,错误原因: %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - 服务器返回的 HTTP 状态错误,应返回 201,但返回的是“%1 %2”。 - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - 向数据库写入元数据错误 - - - - Error setting pin state - - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - 文件 %1 无法上传,因为存在同名文件,文件名仅有大小写不同。 - - - - File Removed - 已移除文件 - - - - Local file changed during syncing. It will be resumed. - 本地文件在同步时已修改,完成后会再次同步 - - - - Local file changed during sync. - 本地文件在同步时已修改。 - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - 上传 %1 超出了此文件夹的限额 - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - 本地文件在同步时已删除。 - - - - Local file changed during sync. - 本地文件在同步时已修改。 - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - 从服务器得到了意外的返回值(%1) - - - - Missing File ID from server - 服务器端文件 ID缺失 - - - - Missing ETag from server - 服务器端ETag缺失 - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - 本地文件在同步时已删除。 - - - - Local file changed during sync. - 本地文件在同步时已修改。 - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - 本地文件在同步时已删除。 - - - - Local file changed during sync. - 本地文件在同步时已修改。 - - - - The server did not acknowledge the last chunk. (No e-tag was present) - 服务器未确认上一分块。(找不到 E-tag) - - - - OCC::ProtocolItemModel - - - Time - 时间 - - - - File - 文件 - - - - Folder - 文件夹 - - - - Issues - - - - - Action - 动作 - - - - Size - 大小 - - - - Account - 账户 - - - - Status - 状态 - - - - OCC::ProtocolWidget - - - Form - 窗体 - - - - Local sync protocol - 本地同步协议 - - - - Filter - 过滤器 - - - - Account - 账户 - - - - Copy to clipboard - 复制到剪贴板 - - - - Show in file browser - 在文件浏览器中显示 - - - - Show in web browser - 在网络浏览器中显示 - - - - Retry sync - 重试同步 - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - 代理需要认证 - - - - Username: - 用户名: - - - - Proxy: - 代理: - - - - The proxy server needs a username and password. - 代理服务器需要输入用户名和密码。 - - - - Password: - 密码: - - - - TextLabel - 文本标签 - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - 选择同步内容 - - - - OCC::SelectiveSyncWidget - - - Loading ... - 加载中... - - - - Deselect remote folders you do not wish to synchronize. - 反选您不想同步的那些远端文件夹 - - - - Name - 名称 - - - - Size - 大小 - - - - - No subfolders currently on the server. - 这个服务器上暂不存在子文件夹。 - - - - An error occurred while loading the list of sub folders. - 载入子文件夹列表时发生错误。 - - - - OCC::ServerNotificationHandler - - - Dismiss - 忽略 - - - - OCC::SettingsDialog - - - Settings - 设置 - - - - Activity - 动态 - - - - Add account - 添加账号 - - - - - Quit %1 - 退出 %1 - - - - Are you sure you want to quit %1? - 你确定要退出 %1 吗? - - - - Account - 账户 - - - - MainWindow - - - - - Hide - 隐藏 - - - - OCC::ShareDialog - - - TextLabel - 文本标签 - - - - share label - 分享标签 - - - - Dialog - 对话框 - - - - ownCloud Path: - ownCloud 路径: - - - - %1 Sharing - %1 分享 - - - - %1 - %1 - - - - Folder: %2 - 文件夹:%2 - - - - The server does not allow sharing - 服务器不支持共享 - - - - Retrieving maximum possible sharing permissions from server... - 从服务器获取最大可能的共享权限... - - - - The file can not be shared because it was shared without sharing permission. - 未分配共享权限,无法共享文件。 - - - - Users and Groups - 用户和组 - - - - Public Links - 公共链接 - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - 分享 NewDocument.odt - - - - Upload only (File Drop) - 仅上传 (File Drop) - - - - Receive files from others without revealing the contents of the folder. - 接收他人的文件而不透露文件夹的内容。 - - - - Download / View / Edit - 下载 / 查看 / 编辑 - - - - Name: - 名字: - - - - Enter name of new share... - 请输入新分享的名字 - - - - &Create - &创造 - - - - TextLabel - 文本标签 - - - - Set &password - 设置密码(&p) - - - - Set &expiration date - 设置过期日期(&e) - - - - Set password - 设置密码 - - - - Recipients can view or download contents. - 接收者可查看或下载内容。 - - - - Download / View - 下载 / 查看 - - - - Recipients can view, download, edit, delete and upload contents. - 接收者可查看、下载、编辑、删除以及上传内容。 - - - - Anyone with the link has access to the file/folder - 任何查看此链接的人都可以访问文件、文件夹 - - - - - P&assword protect - 密码保护(&a) - - - - The file can not be shared because it was shared without sharing permission. - 未分配共享权限,无法共享文件。 - - - - Link shares have been disabled - 分享链接已被关闭 - - - - Create public link share - 创建公共分享链接 - - - - - Delete - 删除 - - - - Open link in browser - 在浏览器中打来链接 - - - - Copy link to clipboard - 复制链接到剪贴板 - - - - Copy link to clipboard (direct download) - 复制链接到剪贴板 (直接下载) - - - - Send link by email - 通过邮件发送链接 - - - - Send link by email (direct download) - 通过电子邮件发送链接 (直接下载) - - - - Create new... - 创造新的.... - - - - I shared %1 with you - 我向你分享了 %1  - - - - Confirm Link Share Deletion - 确认删除分享链接 - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>你真的想删除一个公共分享链接 <i>%1</i>?</p><p>提示:这个操作不可逆.</p> - - - - Cancel - 取消 - - - - - Public link - 公开链接 - - - - Delete link share - 删除链接分享 - - - - - Please Set Password - 请设置密码 - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - 分享 NewDocument.odt - - - - Share with users or groups ... - 分享给其他用户或组 ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - - - - - The item is not shared with any users or groups - 此项目没有与任何用户或组共享 - - - - Open link in browser - 在浏览器中打来链接 - - - - Copy link to clipboard - 复制链接到剪贴板 - - - - Send link by email - 通过邮件发送链接 - - - - No results for '%1' - 没有 '%1' 相关结果 - - - - I shared something with you - 我给你分享了一些东西 - - - - OCC::ShareUserLine - - - Form - 窗体 - - - - - TextLabel - 文本标签 - - - - can edit - 可编辑 - - - - can share - 可共享 - - - - ... - ... - - - - create - 创建 - - - - change - 更改 - - - - delete - 删除 - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - 使用 %1 共享 - - - - Context menu share - 上下文菜单分享 - - - - Context menu share %1 - - - - - Confirm deletion - 确认删除 - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - - - - - Do you want to delete the file <i>%1</i> permanently? - - - - - Select new location... - 选择一个新的位置.... - - - - Error - 错误 - - - - Moving file failed: - -%1 - 移动文件失败: - -%1 - - - - I shared something with you - 我给你分享了一些东西 - - - - - Share... - 分享到... - - - - - Copy private link to clipboard - 将私有链接复制到剪切板 - - - - Send private link by email... - 通过email发送私有链接 - - - - Resharing this file is not allowed - 再次分享这个文件是不被允许的 - - - - Resharing this folder is not allowed - 再次分享这个文件夹是不被允许的 - - - - Create and copy public link to clipboard - 创建并复制公共链接到剪贴板 - - - - Copy public link to clipboard - 复制公共链接到剪切板 - - - - Open in browser - 在浏览器中打开 - - - - Show file versions in browser - 在浏览器中查看这个版本 - - - - Rename... - 重命名... - - - - Rename and upload... - 重命名并上传 - - - - Move and rename... - 重命名并移动... - - - - Move, rename and upload... - 重命名,移动并上传... - - - - Delete local changes - 删除本地改动 - - - - Move and upload... - 移动并上传... - - - - Delete - 删除 - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - 重新获取标签失败 - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - 本地路径必须是绝对路径 - - - - Failed to create temporary database - 创建临时数据库失败 - - - - Backup of %1 succeeded - - - - - Failed to set success tag - 设置成功标签失败 - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>证书信息</h3> - - - - Common Name (CN): - 常用名 (CN): - - - - Subject Alternative Names: - 主体备用名称: - - - - Organization (O): - 组织 (O): - - - - Organizational Unit (OU): - 单位 (OU): - - - - State/Province: - 州/省: - - - - Country: - 国家: - - - - Serial: - 序列号: - - - - <h3>Issuer</h3> - <h3>颁发者</h3> - - - - Issuer: - 颁发者: - - - - Issued on: - 颁发于: - - - - Expires on: - 过期于: - - - - <h3>Fingerprints</h3> - <h3>证书指纹</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>注意:</b>此证书经手动批准</p> - - - - %1 (self-signed) - %1 (自签署) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - 此连接通过 %1 位的 %2 加密。 - - - - - Server version: %1 - 服务器版本: %1 - - - - No support for SSL session tickets/identifiers - 没有支持的 SSL 标识 - - - - Certificate information: - 证书信息: - - - - The connection is not secure - 这是一个不安全的连接 - - - - This connection is NOT secure as it is not encrypted. - - 此连接未经过加密。 - - - - - OCC::SslErrorDialog - - - Form - 窗体 - - - - Trust this certificate anyway - 总是信任该证书 - - - - Untrusted Certificate - 不被信任的证书 - - - - Cannot connect securely to <i>%1</i>: - 无法安全连接到 <i>%1</i>: - - - - with Certificate %1 - 使用证书 %1 - - - - - - &lt;not specified&gt; - &lt;未指定&gt; - - - - - Organization: %1 - 组织:%1 - - - - - Unit: %1 - 单位:%1 - - - - - Country: %1 - 国家: %1 - - - - Fingerprint (MD5): <tt>%1</tt> - MD5指纹: <tt>%1</tt> - - - - Fingerprint (SHA1): <tt>%1</tt> - SHA1指纹: <tt>%1</tt> - - - - Effective Date: %1 - 有效日期:%1 - - - - Expiration Date: %1 - 过期日期:%1 - - - - Issuer: %1 - 签发人:%1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1 (因早先的错误而跳过,将在 %2 后重试) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - 仅有 %1 有效,至少需要 %2 才能开始 - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - 无法打开或创建本地同步数据库,请保证你有多同步文件夹的访问和写入的权限 - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - - - - - There is insufficient space available on the server for some uploads. - 该文件在服务器上不可用 - - - - Unresolved conflict. - 尚未解决的冲突 - - - - Using virtual files with suffix, but suffix is not set - 使用有后缀的虚拟文件,但后缀没有设置 - - - - Unable to read the blacklist from the local database - 无法从本地数据库读取黑名单 - - - - Unable to read from the sync journal. - 无法读取同步日志。 - - - - Cannot open the sync journal - 无法打开同步日志 - - - - Aborted - 已取消 - - - - OCC::SyncJournalDb - - - Failed to connect database. - 连接数据库失败。 - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - 校验异常 - - - - The checksum header contained an unknown checksum type '%1' - 校验头包含未知的校验类型 '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - 虚拟文件系统功能不支持将一个驱动器作为同步根目录 - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - - - - - The Virtual filesystem feature is not supported on network drives - 网络驱动器上不支持虚拟文件系统功能 - - - - OCC::ownCloudGui - - - Please sign in - 请登录 - - - - Folder %1: %2 - 文件夹 %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - 没有已配置的同步文件夹。 - - - - Open in browser - 在浏览器中打开 - - - - - - Log in... - 登录 - - - - - - Log out - 注销 - - - - Recent Changes - 最近修改 - - - - Managed Folders: - 管理的文件夹: - - - - Open folder '%1' - 打开文件夹“%1” - - - - Open %1 in browser - 在浏览器中打开%1 - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - 账户 %1 的服务器运行着不受支持的版本 %2。使用该客户端未支持的服务器版本未经测试并且可能存在潜在危险。继续操作需要自行承担风险。 - - - - Unknown status - 未知状态 - - - - Settings... - 设置... - - - - Details... - 细节... - - - - Help - 帮助 - - - - Quit %1 - 退出 %1 - - - - Disconnected from %1 - 已从服务器断开 %1 - - - - Unsupported Server Version - 不支持的服务器版本 - - - - Disconnected - 连接已断开 - - - - Disconnected from some accounts - 与一些账号断开连接 - - - - Disconnected from accounts: - 已断开账户: - - - - Account %1: %2 - 账户 %1: %2 - - - - Signed out - 已退出 - - - - Account synchronization is disabled - 帐户同步被禁用 - - - - Synchronization is paused - 同步操作被暂停 - - - - Up to date (%1) - - - - - No sync folders configured - 未设置同步文件夹 - - - - Unpause all folders - 解除暂停所有文件夹 - - - - Pause all folders - 暂停所有文件夹 - - - - Unpause all synchronization - 解除暂停所有同步 - - - - Unpause synchronization - 解除暂停同步 - - - - Pause all synchronization - 暂停所有同步 - - - - Pause synchronization - 暂停同步 - - - - Log out of all accounts - 注销所有账户 - - - - Log in to all accounts... - 登录所有账户 - - - - New account... - 新账号…… - - - - About %1 - 关于%1 - - - - No items synced recently - 近期没有项目被同步 - - - - Checking for changes in remote '%1' - 检查远端文件 '%1' 的变动 - - - - Checking for changes in local '%1' - 检查本地文件 '%1' 的变动 - - - - Syncing %1 of %2 (%3 left) - 同步 %2 中的 %1 (剩余 %3) - - - - Syncing %1 of %2 - 正在同步 %1,共 %2 - - - - Syncing %1 (%2 left) - 同步 %1 (剩余 %2) - - - - Syncing %1 - 正在同步 %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - 窗体 - - - - - TextLabel - 文本标签 - - - - Server - 服务器 - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>当该选项被勾选,当前目录的内容将被删除,并开始同步服务器内容。</p><p>如果本地内容要被上传到服务器,不要勾选该选项。</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - 询问确认,若同步 (^z) 文件夹大于 - - - - S&ynchronize everything from server - 同步服务器的所有内容 (&y) - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - 在同步外部存储时,询问确认 (&x) - - - - Choose what to sync - 选择同步内容 - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - - - - - Manually create folder sync connections - 手动创建文件夹同步连接 - - - - &Local Folder - 本地文件夹 (&L) - - - - pbSelectLocalFolder - pbSelectLocalFolder - - - - &Keep local data - 保留本地数据 (&K) - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - 开始一个全新同步 (创建一个备份并删除本地文件夹!)(&c) - - - - OwncloudHttpCredsPage - - - Form - 窗体 - - - - &Username - 用户名 (&U) - - - - &Password - 密码 (&P) - - - - OwncloudOAuthCredsPage - - - Form - 窗体 - - - - Please switch to your browser to proceed. - 请切换到你的浏览器以便继续操作. - - - - An error occurred while connecting. Please try again. - 连接中发生错误.请再次尝试. - - - - Re-open Browser - 重新打开浏览器 - - - - OwncloudSetupPage - - - Form - 窗体 - - - - Ser&ver Address - 服务器地址 (&v) - - - - https://... - https://... - - - - Error Label - 错误标签 - - - - QObject - - - in the future - 将来 - - - - %n day(s) ago - %n 天前 - - - - %n hour(s) ago - %n 小时前 - - - - now - 现在 - - - - Less than a minute ago - 刚刚 - - - - %n minute(s) ago - %n 分钟前 - - - - Some time ago - 之前 - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - 致命错误 - - - - - Error - 错误 - - - - Info - 信息 - - - - Success - 成功 - - - - Conflict - 冲突 - - - - File Ignored - - - - - Restored - - - - - Blacklisted - - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n 年 - - - - %n month(s) - %n 月 - - - - %n day(s) - %n 天 - - - - %n hour(s) - %n 小时 - - - - %n minute(s) - %n 分 - - - - %n second(s) - %n 秒 - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - 系统托盘不可用 - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1 依赖于系统托盘程序。如果你在运行 XFCE,请按 <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">这个指南(英文)</a> 来设置。否则,请安装一个系统托盘程序,比如 trayer,然后再试。 - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - 已创建虚拟文件 - - - - Replaced by virtual file - - - - - Downloaded - 已下载 - - - - Uploaded - 已上传 - - - - Server version downloaded, copied changed local file into conflict file - 服务器版本已下载,复制并更改本地冲突文件 - - - - Deleted - 已删除 - - - - Moved to %1 - 已移动到 %1 - - - - Ignored - 已忽略 - - - - Filesystem access error - 文件系统访问错误 - - - - Error - 错误 - - - - Updated local metadata - 已更新本地元数据 - - - - - Unknown - 未知 - - - - downloading - 正在下载 - - - - uploading - 正在上传 - - - - deleting - 删除 - - - - moving - 移动 - - - - ignoring - 忽略 - - - - - error - 错误 - - - - updating local metadata - 正在更新本地元数据 - - - - theme - - - Status undefined - 状态未定义 - - - - Waiting to start sync - 等待同步启动 - - - - Sync is running - 同步运行中 - - - - Sync Success - 同步成功 - - - - Sync Success, some files were ignored. - 同步成功,部分文件被忽略。 - - - - Sync Error - 同步错误 - - - - Setup Error - 关闭 - - - - Preparing to sync - 正在准备同步 - - - - Aborting... - 正在撤销... - - - - Sync is paused - 同步被暂停 - - - - utility - - - Could not open browser - 不能打开浏览器 - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - 打开链接 %1 时发生错误,是不是默认浏览器没有配置? - - - - Could not open email client - 无法打开邮件客户端 - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - 启动email客户端并创建新消息时发生错误。是不是没有设定默认的email客户端? - - - - Always available locally - - - - - Some available online only - - - - - - Available online only - - - - - Currently available locally - - - - - Make always available locally - - - - - Free up local space - - - - \ No newline at end of file diff --git a/translations/client_zh_TW.ts b/translations/client_zh_TW.ts deleted file mode 100644 index f0f8101c5ad..00000000000 --- a/translations/client_zh_TW.ts +++ /dev/null @@ -1,4912 +0,0 @@ - - - CrashReporter - - - Tomahawk Crash Reporter - - - - - <html><head/><body><p><br/></p></body></html> - - - - - Abort - 中斷 - - - - Send this report - - - - - Don't send - - - - - You can disable sending crash reports in the configuration dialog. - - - - - - - We cannot gather useful debug information on your system. - - - - - - - - - Close - 關閉 - - - - Ready to send debug information (<a href="%1">view backtrace</a>). - - - - - Gathering debug information... - - - - - Uploaded %L1 of %L2 KB. - - - - - Sent! <b>Many thanks</b>. Please refer to crash <a href="clipboard://%1"><b>%1</b></a> (click to copy) in bug reports. - - - - - Failed to send crash info. - - - - - FileSystem - - - - - - Can't rename %1, the file is currently in use - - - - - Could not make directories in trash - 無法將資料夾放入垃圾箱 - - - - - Could not move '%1' to '%2' - 無法將'%1'移動'%2' - - - - Moving to the trash is not implemented on this platform - 此平台上未實現移至回收站 - - - - Error removing '%1': %2 - 錯誤清除 '%1': %2 - - - - Could not remove folder '%1' - 無法移除資料夾 '%1' - - - - FolderWizardSourcePage - - - Form - 表單 - - - - Pick a local folder on your computer to sync - 在您的電腦中選擇一個本地資料夾用來同步 - - - - &Choose... - 選擇 (&C)... - - - - FolderWizardTargetPage - - - Form - 表單 - - - - Select a remote destination folder - 選擇一個遠端的目標資料夾 - - - - Create Folder - 建立資料夾 - - - - Refresh - 重新整理 - - - - Folders - 資料夾 - - - - TextLabel - 文字標籤 - - - - NotificationWidget - - - Form - 表單 - - - - Lorem ipsum dolor sit amet - Lorem ipsum dolor sit amet - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod temporm - - - - TextLabel - 文字標籤 - - - - OCC::AboutDialog - - - About - 關於 - - - - TextLabel - 文字標籤 - - - - Versions - 版本 - - - - About %1 - 關於 %1 - - - - OCC::AbstractNetworkJob - - - Connection timed out - 連線逾時 - - - - Unknown error: network reply was deleted - 未知錯誤:網絡回覆已刪除 - - - - Server replied "%1 %2" to "%3 %4" - 伺服器回覆 "%1 %2" 到 "%3 %4" - - - - OCC::Account - - - %1@%2 - %1@%2 - - - - OCC::AccountSettings - - - Form - 表單 - - - - Re-open Browser - 重新開啟瀏覽器 - - - - ... - ... - - - - Storage space: ... - 儲存空間容量: ... - - - - Unchecked folders will be <b>removed</b> from your local file system and will not be synchronized to this computer anymore - 未標示的資料夾將會從這台電腦被 <b>刪除</b> 而且不會再被同步到這台電腦 - - - - Synchronize all - 全部同步 - - - - Synchronize none - 全部不同步 - - - - Apply manual changes - 套用手動改變 - - - - Apply - 套用 - - - - - - - Cancel - 取消 - - - - Connected with <server> as <user> - 以 <user> 的身分連接 <server> - - - - No account configured. - 沒有設置帳號。 - - - - Remove - 移除 - - - - Account - 帳號 - - - - Choose what to sync - 選擇要同步的項目 - - - - Force sync now - 強制同步 - - - - Restart sync - 重新啟動同步 - - - - Remove folder sync connection - 移除資料夾同步連線 - - - - Folder creation failed - 資料夾建立失敗 - - - - <p>Could not create local folder <i>%1</i>. - <p>無法建立本地資料夾 <i>%1</i> - - - - Confirm Folder Sync Connection Removal - 確認移除資料夾同步連線 - - - - Remove Folder Sync Connection - 移除資料夾同步連線 - - - - Disable virtual file support? - 停用虛擬檔案支持? - - - - This action will disable virtual file support. As a consequence contents of folders that are currently marked as 'available online only' will be downloaded. - -The only advantage of disabling virtual file support is that the selective sync feature will become available again. - -This action will abort any currently running synchronization. - 此操作將停用虛擬檔案支持。 結果,將下載當前標記為“僅在線可用的”的目錄的內容。 - -禁用虛擬檔案支持的唯一好處是選擇性同步功能將再次可用的。 - -此操作將中止任何當前正在運行的同步。 - - - - Disable support - 停用支持 - - - - Sync Running - 正在同步中 - - - - The syncing operation is running.<br/>Do you want to terminate it? - 正在同步中<br/>你真的想要中斷? - - - - %1 in use - %1 正在使用 - - - - %1 as <i>%2</i> - %1 如 <i>%2<i> - - - - Connected to %1. - 已連線到 %1 - - - - Server %1 is temporarily unavailable. - 伺服器 %1 暫時無法使用。 - - - - Server %1 is currently in maintenance mode. - 伺服器 %1 現正處於維護模式 - - - - Signed out from %1. - 從 %1 登出 - - - - Connecting to %1... - 正在連接到 %1 ... - - - - No connection to %1 at %2. - 沒有從 %2 連線到 %1 - - - - Log in - 登入 - - - - There are folders that were not synchronized because they are too big: - 有部份的資料夾因為容量太大沒有辦法同步: - - - - There are folders that were not synchronized because they are external storages: - 有部分資料夾因為是外部存儲沒有辦法同步: - - - - There are folders that were not synchronized because they are too big or external storages: - 有部分資料夾因為容量太大或是外部存儲沒有辦法同步: - - - - Confirm Account Removal - 確認移除帳號 - - - - <p>Do you really want to remove the connection to the account <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>您確定要中斷此帳號 <i>%1</i> 的連線?</p><p><b>注意:</b>此操作 <b>不會</b> 刪除任何的檔案。</p> - - - - Remove connection - 移除連線 - - - - Open folder - 開啟資料夾 - - - - - Log out - 登出 - - - - Open local folder - 開啟本地端目錄 - - - - Open folder in browser - 在瀏覽器中開啟目錄 - - - - Resume sync - 繼續同步 - - - - Pause sync - 暫停同步 - - - - Availability - 可用性 - - - - Disable virtual file support... - 停用虛擬檔案支持... - - - - Enable virtual file support%1... - 啟用虛擬檔案支持%1... - - - - (experimental) - (實驗性的) - - - - <p>Do you really want to stop syncing the folder <i>%1</i>?</p><p><b>Note:</b> This will <b>not</b> delete any files.</p> - <p>您確定要停止同步資料夾 <i>%1</i>?</p><p><b>注意:</b> 此操作 <b>不會</b> 刪除任何檔案</p> - - - - %1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits. - %1 (%3%) 中的 %2 正在使用, 有些資料夾,包括網路掛載或分享資料夾,可能有不同的限制。 - - - - %1 of %2 in use - 已使用 %2 中的 %1% - - - - Currently there is no storage usage information available. - 目前無法查詢儲存空間使用資訊。 - - - - The server version %1 is unsupported! Proceed at your own risk. - 不支持伺服器版本%1!繼續需要您自擔風險。 - - - - Obtaining authorization from the browser. - 從瀏覽器獲取授權。 - - - - Server configuration error: %1 at %2. - 伺服器配置錯誤:%1,位於%2。 - - - - No %1 connection configured. - 沒有 %1 連線設置。 - - - - OCC::AccountState - - - Url update requested for %1 - - - - - The url for %1 changed from %2 to %3, do you want to accept the changed url? - - - - - Change url permanently to %1 - - - - - Reject - - - - - Signed out - 已登出 - - - - Disconnected - 已經離線 - - - - Connected - 已連線 - - - - Service unavailable - 服務無法使用 - - - - Maintenance mode - 維護模式 - - - - Network error - 網路異常 - - - - Configuration error - 設定錯誤 - - - - Asking Credentials - 詢問憑證 - - - - Unknown account state - 未知的帳號狀態 - - - - OCC::ActivityListModel - - - %1 %2 on %3 - %1 %2 上 %3 - - - - Activity - 活動 - - - - Account - 帳號 - - - - Time - 時間 - - - - Local path - - - - - OCC::ActivitySettings - - - - Server Activity - 伺服器活動 - - - - Sync Protocol - 同步協定 - - - - Not Synced - 尚未同步 - - - - Not Synced (%1) - %1 is the number of not synced files. - 未同步(%1) - - - - OCC::ActivityWidget - - - Form - 表單 - - - - - - TextLabel - 文字標籤 - - - - Filter - 篩選器 - - - - Server Activities - 伺服器活動 - - - - Notifications - 通知 - - - - %n notifications(s) for %1. - %1的(s)%n個通知。 - - - - %n notifications(s) for %1 and %2. - %n和%1的%2個通知(s)。 - - - - New notifications for %1, %2 and other accounts. - 新%1,%2和其他帳戶的通知。 - - - - Open the activity view for details. - 開啟活動查看詳細信息。 - - - - Copy to clipboard - 複製到剪貼簿 - - - - Show in file browser - - - - - <br/>Account %1 does not have activities enabled. - <br/> 帳號 %1 尚未啟用紀錄行為功能 - - - - OCC::Application - - - Continuing will mean <b>deleting these settings</b>. - 繼續意味著<b>刪除這些設定</b>。 - - - - Continuing will mean <b>ignoring these settings</b>. - 繼續意味著 <b>忽略這些設定</b>。 - - - - Some settings were configured in newer versions of this client and use features that are not available in this version.<br><br>%1<br><br>The current configuration file was already backed up to <i>%2</i>. - 一些設定是在此客戶端的較新版本中配置的,並且使用了此版本中不可用的功能。<br><br>%1<br><br>當前配置檔案已經備份到<i>%2</i>。 - - - - Quit - 離開 - - - - Continue - 繼續 - - - - Error accessing the configuration file - 存取配置文件時錯誤 - - - - There was an error while accessing the configuration file at %1. - 訪問%1處的配置檔案時錯誤。 - - - - Quit %1 - 離開 %1 - - - - %1 version %2 -File synchronization desktop utility. - - - - - For more information, see http://www.owncloud.org - - - - - Show the settings dialog while starting. - - - - - Quit the running instance. - - - - - Write log to file (use - to write to stdout). - - - - - Write each sync log output in a new file in folder. - - - - - Remove logs older than <hours> hours (to be used with --logdir). - - - - - Flush the log file after every write. - - - - - Output debug-level messages in the log. - - - - - - Override UI language. - - - - - Use the given configuration folder. - - - - - Enable debug mode. - - - - - Virtual file system file to be opened (optional). - - - - - [<vfs file>] - - - - - Invalid path passed to --confdir - - - - - Error: unknown language "%1" (use --list-languages to get a complete list of supported translations) - - - - - Available translations: %1 - - - - - OCC::AuthenticationDialog - - - Authentication Required - 必須驗證 - - - - Enter username and password for '%1' at %2. - 輸入 '%1' 在 %2 使用者名稱 和 密碼 - - - - &User: - 使用者 (&U): - - - - &Password: - 密碼 (&P): - - - - OCC::ConnectionValidator - - - No ownCloud account configured - 沒有設置 ownCloud 帳號 - - - - The configured server for this client is too old - 設置的伺服器對這個客戶端來說太舊了 - - - - Please update to the latest server and restart the client. - 請將伺服器端更新到最新版並重新啟動客戶端 - - - - Authentication error: Either username or password are wrong. - 驗證錯誤︰使用者名稱或是密碼錯誤 - - - - timeout - 逾時 - - - - The provided credentials are not correct - 提供的憑證不正確 - - - - OCC::CredentialJob - - - Failed to parse credentials %1 - - - - - OCC::DiscoverySingleDirectoryJob - - - - Server error: PROPFIND reply is not XML formatted! - 伺服器錯誤:PROPFIND回覆格式為XML! - - - - OCC::DiscoverySingleLocalDirectoryJob - - - Error while opening directory %1 - 打開資料夾%1時錯誤 - - - - Directory not accessible on client, permission denied - 無法在客戶端上訪問資料夾,權限被拒絕 - - - - Directory not found: %1 - 未找到資料夾: %1 - - - - Filename encoding is not valid - 檔案名稱編碼是無效的 - - - - Error while reading directory %1 - 讀取資料夾%1時錯誤 - - - - OCC::ExpandingHeaderView - - - Reset column sizes - - - - - OCC::Folder - - - Local folder %1 does not exist. - 本地資料夾 %1 不存在 - - - - %1 should be a folder but is not. - 資料夾不存在, %1 必須是資料夾 - - - - %1 is not readable. - %1 是不可讀的 - - - - %1 has been removed. - %1 names a file. - %1 已被移除。 - - - - %1 has been updated. - %1 names a file. - %1 已被更新。 - - - - %1 has been renamed to %2. - %1 and %2 name files. - %1 已被重新命名為 %2。 - - - - %1 has been moved to %2. - %1 已被搬移至 %2。 - - - - %1 and %n other file(s) have been removed. - %1 跟 %n 其他檔案已經被刪除 - - - - %1 is not writable. - - - - - %1 and %n other file(s) have been added. - 已添加%1和%n其他檔案(s)。 - - - - %1 has been added. - %1 names a file. - %1 已添加。 - - - - %1 and %n other file(s) have been updated. - %1 跟 %n 其他檔案已經被修改 - - - - %1 has been renamed to %2 and %n other file(s) have been renamed. - %1已重命名為%2,%n其他檔案(s)已重命名。 - - - - %1 has been moved to %2 and %n other file(s) have been moved. - %1已移至%2,%n其他檔案(s)已移。 - - - - %1 has and %n other file(s) have sync conflicts. - %1和其他%n個檔案(s) 存在同步衝突。 - - - - %1 has a sync conflict. Please check the conflict file! - %1 有同步衝突。 請檢查衝突檔案! - - - - %1 and %n other file(s) could not be synced due to errors. See the log for details. - 由於發生錯誤,無法同步%1和%n其他檔案(s)。 有關詳細信息,請參見記錄。 - - - - %1 could not be synced due to an error. See the log for details. - %1 因為錯誤無法被同步。請從紀錄檔觀看細節。 - - - - Sync Activity - 同步活動 - - - - Could not read system exclude file - 無法讀取系統的排除檔案 - - - - A new folder larger than %1 MB has been added: %2. - - 添加了大於%1 MB的新目錄:%2. - - - - - A folder from an external storage has been added. - - 已添加來自外部儲存空間的目錄。 - - - - - Please go in the settings to select it if you wish to download it. - 如果您要下載它,請進入設定以選擇它。 - - - - The folder %1 was created but was excluded from synchronization previously. Data inside it will not be synchronized. - 目錄%1已創建,但先前已從同步中排除。其中的數據將不會同步。 - - - - The file %1 was created but was excluded from synchronization previously. It will not be synchronized. - 已創建檔案%1但先前已將其排除在同步之外。它不會同步。 - - - - Changes in synchronized folders could not be tracked reliably. - -This means that the synchronization client might not upload local changes immediately and will instead only scan for local changes and upload them occasionally (every two hours by default). - -%1 - 無法同步跟踪同步目錄中的更改。 - -這意味著同步客戶端可能不會立即上傳本地端更改,而只會掃描本地端更改並偶爾上傳(默認情況下每兩個小時一次)。 - -%1 - - - - All files in the sync folder '%1' folder were deleted on the server. -These deletes will be synchronized to your local sync folder, making such files unavailable unless you have a right to restore. -If you decide to keep the files, they will be re-synced with the server if you have rights to do so. -If you decide to delete the files, they will be unavailable to you, unless you are the owner. - 伺服器上已刪除同步目錄'%1目錄中的所有文件。 -這些刪除將同步到本地端同步目錄,除非您有權恢復,否則將使此類文件不可用。 -如果您決定保留文件,您則有權將它們與伺服器重新同步。 -如果您決定刪除文件,除非您是所有者,否則您將無法使用它們。 - - - - All the files in your local sync folder '%1' were deleted. These deletes will be synchronized with your server, making such files unavailable unless restored. -Are you sure you want to sync those actions with the server? -If this was an accident and you decide to keep your files, they will be re-synced from the server. - 本地端同步目錄'%1'中的所有文件都已刪除。 這些刪除將與您的伺服器同步,除非恢復,否則此類文件將不可用。 -您確定您要與伺服器同步這些操作嗎? -如果這是偶然事件,並且您決定保留文件,則將從伺服器重新同步它們。 - - - - Remove All Files? - 移除所有檔案? - - - - Remove all files - 移除所有檔案 - - - - Keep files - 保留檔案 - - - - OCC::FolderMan - - - Could not reset folder state - 無法重置資料夾狀態 - - - - An old sync journal '%1' was found, but could not be removed. Please make sure that no application is currently using it. - 發現較舊的同步處理日誌'%1',但無法移除。請確認沒有應用程式正在使用它。 - - - - (backup) - (備份) - - - - (backup %1) - (備份 %1) - - - - Undefined State. - 未知狀態 - - - - Waiting to start syncing. - 正在等待同步開始 - - - - Preparing for sync. - 正在準備同步。 - - - - Sync is running. - 同步執行中 - - - - Sync was successful, unresolved conflicts. - 同步成功,未解決衝突。 - - - - Last Sync was successful. - 最後一次同步成功 - - - - Setup Error. - 安裝失敗 - - - - User Abort. - 使用者中斷。 - - - - Sync is paused. - 同步已暫停 - - - - %1 (Sync is paused) - %1 (同步暫停) - - - - No valid folder selected! - 沒有選擇有效的資料夾 - - - - The folder %1 is used in a folder sync connection! - - - - - The selected path does not exist! - 所選路徑不存在! - - - - The selected path is not a folder! - 所選的路徑並非資料夾! - - - - You have no permission to write to the selected folder! - 您沒有權限來寫入被選取的資料夾! - - - - There is already a sync from the server to this local folder. Please pick another local folder! - 從伺服器到此本地端目錄已經存在同步。 請選擇另一個本地端目錄! - - - - The local folder %1 already contains a folder used in a folder sync connection. Please pick another one! - 本地資料夾 %1 裡已經有被資料夾同步功能使用的資料夾,請選擇其他資料夾! - - - - The local folder %1 is already contained in a folder used in a folder sync connection. Please pick another one! - 本地資料夾 %1 是被包含在一個已經被資料夾同步功能使用的資料夾,請選擇其他資料夾! - - - - %1 Please pick another one! - - - - - Multiple accounts are sharing the folder %1. -This configuration is know to lead to dataloss and is no longer supported. -Please consider removing this folder from the account and adding it again. - - - - - OCC::FolderStatusDelegate - - - Add Folder Sync Connection - 新增資料夾同步功能的連線 - - - - File - 檔案 - - - - OCC::FolderStatusModel - - - You need to be connected to add a folder - 您必須連上伺服器才能新增資料夾 - - - - Click this button to add a folder to synchronize. - 點擊此按鈕來新增同步資料夾 - - - - - %1 (%2) - Example text: "File.txt (23KB)" - %1 (%2) - - - - Error while loading the list of folders from the server. - 從伺服器端同步資料夾清單時發生錯誤。 - - - - Virtual file support is enabled. - 虛擬檔案支持已啟用。 - - - - Signed out - 已登出 - - - - Fetching folder list from server... - 從伺服器抓取資料夾清單中... - - - - There are unresolved conflicts. Click for details. - 有未解決的衝突。 點擊查看更多細節。 - - - - Checking for changes in remote '%1' - 檢查遠端'%1'中的更改 - - - - Checking for changes in local '%1' - 檢查本地端'%1'中的更改 - - - - Reconciling changes - 協調變更 - - - - , '%1' - Build a list of file names - , '%1' - - - - '%1' - Argument is a file name - '%1' - - - - Syncing %1 - Example text: "Syncing 'foo.txt', 'bar.txt'" - 同步 %1 - - - - - , - , - - - - download %1/s - Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated)) - 下載 %1/s - - - - ↓ %1/s - ↓ %1/s - - - - ↑ %1/s - ↑ %1/s - - - - upload %1/s - Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated)) - 上傳 %1/s - - - - Local folder: %1 - - - - - %1 %2 (%3 of %4) - Example text: "uploading foobar.png (2MB of 2MB)" - %1 %2 (%3 的 %4) - - - - %1 %2 - Example text: "uploading foobar.png" - %1 %2 - - - - %5 left, %1 of %2, file %3 of %4 - Example text: "5 minutes left, 12 MB of 345 MB, file 6 of 7" - 還剩%5,%2的%1,%4檔案%3 - - - - %1 of %2, file %3 of %4 - Example text: "12 MB of 345 MB, file 6 of 7" - %1 的 %2, 檔案 %3 的 %4 - - - - file %1 of %2 - 檔案 %1 的 %2 - - - - Waiting... - 等待中... - - - - Waiting for %n other folder(s)... - 正在等候 %n 的資料夾(可能不只一個) - - - - Preparing to sync... - 正在準備同步... - - - - OCC::FolderWatcher - - - The watcher did not receive a test notification. - 觀察者未收到測試通知。 - - - - OCC::FolderWatcherPrivate - - - This problem usually happens when the inotify watches are exhausted. Check the FAQ for details. - 當精化手錶用盡時,通常會發生此問題。查看常見問題解答以了解詳細信息。 - - - - OCC::FolderWizard - - - Add Folder Sync Connection - 新增資料夾同步功能的連線 - - - - Add Sync Connection - 新增同步連線 - - - - OCC::FolderWizardLocalPath - - - Click to select a local folder to sync. - 請選擇要同步的本地資料夾 - - - - Enter the path to the local folder. - 請輸入本地資料夾的路徑 - - - - Select the source folder - 選擇來源資料夾 - - - - OCC::FolderWizardRemotePath - - - Create Remote Folder - 建立遠端資料夾 - - - - Enter the name of the new folder to be created below '%1': - 請輸入欲創建在 '%1' 底下的新資料夾名稱: - - - - Folder was successfully created on %1. - 資料夾成功建立在%1 - - - - Authentication failed accessing %1 - 存取 %1 認証失敗 - - - - Failed to create the folder on %1. Please check manually. - 在 %1 建立資料夾失敗,請手動檢查 - - - - Failed to list a folder. Error: %1 - 取得資料夾清單失敗,錯誤: %1 - - - - Choose this to sync the entire account - 請選擇這個功能用來同步整個帳號 - - - - This folder is already being synced. - 這個資料夾已經被同步了。 - - - - You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>. - 您已經同步了 <i>%1</i>, 這個資料夾是 <i>%2</i> 的母資料夾。 - - - - You are already syncing <i>%1</i>, which is a subfolder of <i>%2</i>. - 您已經在同步<i>%1</i>,它是的子文件夾<i>%2</i> - - - - OCC::FolderWizardSelectiveSync - - - Use virtual files instead of downloading content immediately%1 - 使用虛擬文件,而不是立即下載內容%1 - - - - (experimental) - (實驗性的) - - - - Virtual files are not available for the selected folder - 虛擬文件不可用的於所選目錄 - - - - OCC::FormatWarningsWizardPage - - - <b>Warning:</b> %1 - <b>警告:</b> %1 - - - - <b>Warning:</b> - <b>警告:</b> - - - - OCC::GETFileJob - - - No E-Tag received from server, check Proxy/Gateway - 沒有收到來自伺服器的 E-Tag,請檢查代理伺服器或網路閘道 - - - - We received a different E-Tag for resuming. Retrying next time. - 在復原時收到了不同的 E-Tag,將在下一次重新嘗試取得 - - - - We received an unexpected download Content-Length. - 們收到了意外下載的Content-Length。 - - - - Server returned wrong content-range - 伺服器回應錯誤的內容長度 - - - - OCC::GETJob - - - Connection Timeout - 連線逾時 - - - - OCC::GeneralSettings - - - Form - 表單 - - - - General Settings - 一般設定 - - - - Language - 語言 - - - - Sync hidden files - 同步隱藏檔案 - - - - Log Settings - 記錄設定 - - - - Advanced - Approval of folder sync (Non virtual file mode only) - 高級-批准目錄同步(僅適用於非虛擬檔案模式) - - - - Network - 網路 - - - - Updates - 更新 - - - - stable - 穩定 - - - - beta - 資料 - - - - About - 關於 - - - - Advanced - 進階 - - - - Use &Monochrome Icons in the system tray - 使用系統任務欄中的&單色圖示 - - - - Ask for confirmation before synchronizing folders larger than - 同步大於的目錄之前要求確認 - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing external storages - 在同步外部存儲之前要求確認 - - - - &Launch on System Startup - &系統啟動時啟動 - - - - Show &Desktop Notifications - &桌面顯示通知 - - - - &Update Channel - &更新管道 - - - - Edit &Ignored Files - 編輯&被忽略的檔案 - - - - Show sync folders in &Explorer's Navigation Pane - 顯示同步目錄到 &資源管理器的導航窗格 - - - - S&how crash reporter - &顯示意外回報器 - - - - &Restart && Update - 重新啟動並更新 (&R) - - - - You cannot disable autostart because system-wide autostart is enabled. - 您無法停用自動啟動,因為啟用了系統範圍的自動已啟用。 - - - - Change update channel? - 更改更新頻道? - - - - Warning - 警告 - - - - Language changes require a restart of this application to take effect. - - - - - The update channel determines which client updates will be offered for installation. The "stable" channel contains only upgrades that are considered reliable, while the versions in the "beta" channel may contain newer features and bugfixes, but have not yet been tested thoroughly. - -Note that this selects only what pool upgrades are taken from, and that there are no downgrades: So going back from the beta channel to the stable channel usually cannot be done immediately and means waiting for a stable version that is newer than the currently installed beta version. - 更新通道確定將提供哪些客戶端更新來進行安裝。 “穩定”通道僅包含被認為可靠的升級,而“測試版”通道中的版本可能包含較新的功能和錯誤修正,但尚未經過全面測試。 - -請注意,這只會選擇從中進行池升級的版本,並且不會降級:因此,通常無法立即從Beta通道返回到穩定通道,這意味著要等待比當前安裝的Beta更新的穩定版本 版。 - - - - Change update channel - 更改更新頻道 - - - - Cancel - 取消 - - - - unknown (%1) - - - - - (use default) - - - - - OCC::HttpCredentialsGui - - - Please enter %1 password:<br><br>User: %2<br>Account: %3<br> - 請輸入 %1 密碼<br><br>使用者:%2<br>帳戶 %3<br> - - - - Reading from keychain failed with error: '%1' - 從授權碼讀取資料時失敗,錯誤: '%1' - - - - Enter Password - 輸入密碼 - - - - <a href="%1">Click here</a> to request an app password from the web interface. - <a href="%1">點擊這裡</a>從Web介面請求軟體密碼。 - - - - OCC::IgnoreListEditor - - - Ignored Files Editor - 忽略的檔案編輯器 - - - - Files Ignored by Patterns - 根據模式套用什麼檔案要被忽略 - - - - Add - 增加 - - - - Pattern - 模式 - - - - Allow Deletion - 允許刪除 - - - - Remove - 移除 - - - - Files or folders matching a pattern will not be synchronized. Changes take effect the next time folders are synchronized. - -Items where deletion is allowed will be deleted if they prevent a directory from being removed. This is useful for meta data. - 匹配模式的文件或目錄將不會同步。 更改將在下次同步目錄時生效。 - -如果允許刪除的項目阻止資料夾被刪除,則會被刪除。 這對於元數據很有用。 - - - - Could not open file - 無法開啟檔案 - - - - Cannot write changes to '%1'. - %1 無法寫入變更。 - - - - Add Ignore Pattern - 增加忽略格式 - - - - Add a new ignore pattern: - 增加一個新的忽略格式: - - - - This entry is provided by the system at '%1' and cannot be modified in this view. - 這個項目是由系統提供在 '%1' 且不能在這個頁面被修改 - - - - OCC::IssuesWidget - - - Form - 表單 - - - - List of issues - 問題清單 - - - - Filter - 篩選器 - - - - There were too many issues. Not all will be visible here. - 問題太多了。並非在此處全部可見。 - - - - There were conflicts. Check the documentation on how to resolve them. - 有衝突。查看有關如何解決它們的文檔。 - - - - The file %1 was ignored as its name is reserved by %2 - - - - - Reset column sizes - - - - - There were conflicts. <a href="%1">Check the documentation on how to resolve them.</a> - 有衝突<a href="%1">查看有關如何解決它們的文檔。</a> - - - - Account - 帳號 - - - - OCC::LibCloudProvidersPrivate - - - Settings - 設定 - - - - OCC::LogBrowser - - - Log Output - 記錄輸出 - - - - Enable logging to temporary folder - 啟用記錄到臨時目錄 - - - - Delete logs older than %1 hours - 刪除記錄年長過 %1 小時 - - - - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The logs contain sensitive information which you should not make publicly available</span></p></body></html> - <head/><body><p><span style=" font-weight:600; color:#ff0000;"><html>記錄包含敏感信息,您不應將這些信息公開可用的</span></p></body></html> - - - - The client can write debug logs to a temporary folder. These logs are very helpful for diagnosing problems. -Since log files can get large, the client will start a new one for each sync run and compress older ones. It is also recommended to enable deleting log files after a couple of hours to avoid consuming too much disk space. - 客戶端可以將除錯記錄寫入臨時目錄。 這些記錄對於診斷問題非常有用。 -由於記錄文件會變大,因此客戶端將為每次同步運行啟動一個新文件,並壓縮較舊的文件。 還建議啟用幾個小時後刪除記錄文件,以避免佔用過多的磁盤空間。 - - - - If enabled, logs will be written to: - 如果已啟用,記錄將被寫入 - - - - C:/log - C:/記錄 - - - - Log Http traffic - 記錄說明交通 - - - - These settings persist across client restarts. -Note that using any logging command line options will override the settings. - 這些設定在客戶端重新啟動後仍然存在。 -請注意,使用任何日誌記錄命令行選項都將覆蓋設定。 - - - - Open folder - 開啟資料夾 - - - - OCC::Models - - - %1 Filter: - - - - - No filter - - - - - Reset Filters - - - - - Status Filter: - - - - - OCC::NSISUpdater - - - New Version Available - 有新版本 - - - - <p>A new version of the %1 Client is available.</p><p><b>%2</b> is available for download. The installed version is %3.</p> - <p>%1 客戶端有新版本了。</p><p><b>%2</b> 可供下載, 目前安裝的版本是 %3.</p> - - - - - Skip this version - 跳過這個版本 - - - - Skip this time - 跳過這次更新 - - - - Get update - 取得更新 - - - - Update Failed - 更新失敗 - - - - <p>A new version of the %1 Client is available but the updating process failed.</p><p><b>%2</b> has been downloaded. The installed version is %3.</p> - <p>%1 客戶端的新版本可用的,但更新處理失敗。</p><p><b>%2</b>已下載。 安裝的版本為%3。</p> - - - - Ask again later - 稍後再問 - - - - Restart and update - 重啟並更新 - - - - Update manually - 手動更新 - - - - OCC::NetworkSettings - - - Form - 表單 - - - - Proxy Settings - 代理伺服器設定 - - - - No Proxy - 不使用代理伺服器 - - - - Use system proxy - 使用系統預設代理伺服器 - - - - Specify proxy manually as - 手動指定代理伺服器為 - - - - Host - 主機 - - - - : - : - - - - Proxy server requires authentication - 代理伺服器要求認證 - - - - Note: proxy settings have no effects for accounts on localhost - 注意:代理設定對本地主機上的帳戶沒有影響 - - - - Download Bandwidth - 下載頻寬 - - - - - Limit to - 限制到 - - - - - KBytes/s - KBytes/s - - - - - No limit - 沒有限制 - - - - - Limit to 3/4 of estimated bandwidth - 限制預估頻寬的 3/4 - - - - Upload Bandwidth - 上傳頻寬 - - - - - Limit automatically - 自動限制 - - - - Hostname of proxy server - 代理伺服器主機名稱 - - - - Username for proxy server - 代理伺服器使用者名稱 - - - - Password for proxy server - 代理伺服器密碼 - - - - HTTP(S) proxy - HTTP(S) 代理伺服器 - - - - SOCKS5 proxy - SOCKS5 代理伺服器 - - - - OCC::NotificationWidget - - - Created at %1 - 創建於 %1 - - - - Closing in a few seconds... - 幾秒鐘後關閉... - - - - %1 request failed at %2 - The second parameter is a time, such as 'failed at 09:58pm' - %1請求失敗於%2 - - - - '%1' selected at %2 - The second parameter is a time, such as 'selected at 09:58pm' - '%1' 在選擇%2 - - - - OCC::OAuth - - - Error returned from the server: <em>%1</em> - 伺服器返回錯誤:<em>%1</em> - - - - There was an error accessing the 'token' endpoint: <br><em>%1</em> - 訪問“令牌”端點時錯誤:<br><em>%1</em> - - - - Could not parse the JSON returned from the server: <br><em>%1</em> - 無法解析從伺服器返回的JSON:<br><em>%1</em> - - - - Unsupported token type: %1 - 不支持的令牌類型:%1 - - - - The reply from the server did not contain all expected fields -:%1 - 伺服器的回覆未包含所有預期字段 -:%1 - - - - Unknown Error - 未知錯誤 - - - - <h1>Login Error</h1><p>Failed to retrieve user info</p> - <h1>登錄錯誤</h1><p>檢索使用者信息失敗</p> - - - - Oauth2 Error - - - - - Oauth2 authentication requires a secured connection. - - - - - <h1>Login Error</h1><p>%1</p> - <h1>登錄錯誤</h1><p>%1</p> - - - - - - Error while refreshing the token: %1 : %2 - 刷新令牌時錯誤:%1:%2 - - - - The reply from the server did not contain all expected fields -:%1 -Received data: %2 - 伺服器的回覆未包含所有預期字段 -:%1 -收到的數據:%2 - - - - <h1>Wrong user</h1><p>You logged-in with user <em>%1</em>, but must login with user <em>%2</em>.<br>Please log out of %3 in another tab, then <a href='%4'>click here</a> and log in as user %2</p> - <h1>錯誤使用者</h1><p>您與使用者身份登錄<em>%1</em>,與必須使用者登錄<em>%2</em>。<br>請在另一個標籤中登出%3,然後<a href='%4'>點擊這裡</a>並以使用者%2身份登入</p> - - - - OCC::OCUpdater - - - New %1 Update Ready - 新 %1 更新已經就緒 - - - - A new update for %1 is about to be installed. The updater may ask -for additional privileges during the process. - 一個新的 %1 更新已經安裝完成。更新也許會詢問 -要求給予權限在安裝的過程中。 - - - - Could not download update. Please click <a href='%1'>here</a> to download the update manually. - 無法下載更新檔,請點選連結 <a href='%1'>按這裡</a> 手動下載更新檔。 - - - - Could not check for new updates. - 無法檢查是否有新更新檔。 - - - - Downloading %1. Please wait... - 正在下載 %1 請稍候... - - - - %1 available. Restart application to start the update. - %1 可用的重啟應用程式開始更新 - - - - New %1 available. Please click <a href='%2'>here</a> to download the new AppImage manually. - - - - - New %1 available. Please use the system's update tool to install it. - 新的%1可用的。 請使用系統的更新工具進行安裝。 - - - - Checking update server... - 檢查更新伺服器中... - - - - Update status is unknown: Did not check for new updates. - 無法取得更新狀態: 無法檢查是否有新更新檔。 - - - - No updates available. Your installation is at the latest version. - 目前沒有可用的更新檔,您安裝的版本是最新的了。 - - - - Update Check - 更新檢查 - - - - OCC::OwncloudAdvancedSetupPage - - - Connect to %1 - 連線到 %1 - - - - Setup local folder options - 設定本地資料夾選項 - - - - Connect... - 連線中... - - - - (experimental) - (實驗性的) - - - - Use &virtual files instead of downloading content immediately%1 - 使用&虛擬文件,而不是立即下載內容%1 - - - - <p><strong>Warning:</strong> The local folder is not empty. Pick a resolution!</p> - - - - - Sync the folder '%1' - 同步資料夾 '%1' - - - - Virtual files are not available for the selected folder - 虛擬文件不可用的於所選目錄 - - - - Error - 錯誤 - - - - Local Sync Folder - 本地同步資料夾 - - - - - (%1) - (%1) - - - - OCC::OwncloudHttpCredsPage - - - &Email - &電子郵件 - - - - Connect to %1 - 連線到 %1 - - - - Enter user credentials - 請輸入使用者憑證 - - - - OCC::OwncloudOAuthCredsPage - - - Connect to %1 - 連線到 %1 - - - - Login in your browser - 登錄您的 瀏覽器 - - - - Copy link to clipboard - 複製連結到剪貼簿 - - - - OCC::OwncloudPropagator - - - File %1 is currently in use - - - - - OCC::OwncloudSetupPage - - - Connect to %1 - 連線到 %1 - - - - Setup %1 server - 設定 %1 伺服器 - - - - This url is NOT secure as it is not encrypted. -It is not advisable to use it. - 這個url沒有加密,是不安全的, -不建議您使用此url。 - - - - This url is secure. You can use it. - 這個 URL 是安全的,您可以使用它。 - - - - &Next > - 下一步 (&N) > - - - - Invalid URL - 無效的超連結 - - - - OCC::OwncloudSetupWizard - - - Failed to connect to %1 at %2:<br/>%3 - 從 %2 連線到 %1 失敗:<br/>%3 - - - - Timeout while trying to connect to %1 at %2. - 從 %2 嘗試連線到 %1 逾時。 - - - - Invalid URL - 無效的超連結 - - - - Could not create local folder %1 - 無法建立本地資料夾 %1 - - - - Error: %1 - 錯誤: %1 - - - - The folder creation resulted in HTTP error code %1 - 在HTTP建立資料夾失敗, error code %1 - - - - The remote folder creation failed because the provided credentials are wrong!<br/>Please go back and check your credentials.</p> - 由於帳號或密碼錯誤,遠端資料夾建立失敗<br/>請檢查您的帳號密碼。</p> - - - - Remote folder %1 creation failed with error <tt>%2</tt>. - 建立遠端資料夾%1發生錯誤<tt>%2</tt>失敗 - - - - Folder rename failed - 重新命名資料夾失敗 - - - - Can't remove and back up the folder because the folder or a file in it is open in another program. Please close the folder or file and hit retry or cancel the setup. - 無法移除與備份此資料夾,因為有其他的程式正在使用其中的資料夾或者檔案。請關閉使用中的資料夾或檔案並重試或者取消設定。 - - - - OCC::OwncloudWizard - - - %1 Connection Wizard - %1連線精靈 - - - - Enable experimental feature? - 啟用實驗功能? - - - - Enable experimental placeholder mode - 啟用實驗佔位符模式 - - - - When the "virtual files" mode is enabled no files will be downloaded initially. Instead, a tiny "%1" file will be created for each file that exists on the server. The contents can be downloaded by running these files or by using their context menu. - -The virtual files mode is mutually exclusive with selective sync. Currently unselected folders will be translated to online-only folders and your selective sync settings will be reset. - -Switching to this mode will abort any currently running synchronization. - -This is a new, experimental mode. If you decide to use it, please report any issues that come up. - 已啟用“虛擬文件”模式時,最初不會下載任何檔案。 相反,將為伺服器上存在的每個檔案創建一個微小的“%1”文件。 可以通過運行這些文件或使用其上下文選單來下載內容。 - -虛擬文件模式與選擇性同步是互斥的。 當前未選擇的目錄將轉換為僅在線目錄,並且您的選擇性同步設定將被重設。 - -切換到此模式將中止任何當前正在運行的同步。 - -這是一種新的實驗模式。 如果您決定使用它,請報告出現的任何問題。 - - - - Stay safe - 注意安全 - - - - OCC::ProcessDirectoryJob - - - Symbolic links are not supported in syncing. - 同步不支援捷徑連結 - - - - File is listed on the ignore list. - 檔案被列在忽略清單。 - - - - File names ending with a period are not supported on this file system. - 該檔案系統不支持與句點結尾的檔案名。 - - - - File names containing the character '%1' are not supported on this file system. - 該檔案系統不支持包含字符'%1'的檔案名。 - - - - File name contains at least one invalid character - 檔案名稱含有不合法的字元 - - - - The file name is a reserved name on this file system. - 檔案名稱是此檔案系統上的保留名稱。 - - - - Filename contains trailing spaces. - 文件名包含尾隨空格。 - - - - Filename is too long. - 檔案名稱太長了。 - - - - File/Folder is ignored because it's hidden. - 檔案/目錄被忽略,因為它是隱藏的。 - - - - Stat failed. - 狀態失敗。 - - - - Conflict: Server version downloaded, local copy renamed and not uploaded. - 衝突:伺服器版本已下載,本地端副本已重命名且未上傳。 - - - - The filename cannot be encoded on your file system. - 文件名不能在檔案系統上編碼。 - - - - The filename is blacklisted on the server. - 件名在伺服器上被列入黑名單。 - - - - size - 容量 - - - - permissions - 權限 - - - - etag - 標籤 - - - - file id - 檔案id - - - - server reported no %1 - 伺服器報告沒有 %1 - - - - Moved to invalid target, restoring - 已移至無效的目標,正在還原 - - - - Not allowed to remove, restoring - 不允許刪除,復原中 - - - - Ignored because of the "choose what to sync" blacklist - 已忽略。根據 "選擇要同步的項目"的黑名單 - - - - Not allowed because you don't have permission to add subfolders to that folder - 拒絕此操作,您沒有在此新增子資料夾的權限。 - - - - Not allowed because you don't have permission to add files in that folder - 拒絕此操作,您沒有新增檔案在此資料夾的權限。 - - - - Not allowed to upload this file because it is read-only on the server, restoring - 拒絕上傳此檔案,此檔案在伺服器是唯讀檔,復原中 - - - - Error while reading the database - 讀取資料庫時錯誤 - - - - Server replied with an error while reading directory '%1' : %2 - 伺服器回覆與一個錯誤而讀資料夾 '%1' : %2 - - - - OCC::PropagateDirectory - - - Error updating metadata: %1 - - - - - %1 the folder is currently in use - - - - - OCC::PropagateDownloadFile - - - Failed to free up space, the file %1 is currently in use - - - - - - File %1 can not be downloaded because of a local file name clash with %2! - - - - - The download would reduce free local disk space below the limit - 下載將減少可用的本地端磁盤空間,使其低於限制 - - - - Free space on disk is less than %1 - 可用的硬碟空間已經少於 %1 - - - - File was deleted from server - 檔案已從伺服器被刪除 - - - - The file could not be downloaded completely. - 檔案下載無法完成。 - - - - The downloaded file is empty despite the server announced it should have been %1. - 儘管伺服器宣布它應該已經下載,但下載的檔案為空 %1. - - - - File %1 cannot be saved because of a local file name clash with %2! - - - - - Error updating metadata: %1 - - - - - - - - The file %1 is currently in use - - - - - - File has changed since discovery - 尋找的過程中檔案已經被更改 - - - - OCC::PropagateItemJob - - - ; Restoration Failed: %1 - ; 重新儲存失敗 %1 - - - - OCC::PropagateLocalMkdir - - - could not delete file %1, error: %2 - 無法刪除檔案 %1,錯誤: %2 - - - - Can not create local folder %1 because of a local file name clash with %2 - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - could not create folder %1 - 無法建立資料夾 %1 - - - - OCC::PropagateLocalRemove - - - Could not remove %1 because of a local file name clash with %2! - - - - - OCC::PropagateLocalRename - - - File %1 can not be renamed to %2 because of a local file name clash - 檔案 %1 無法更名成 %2,因為本地端的檔案名稱已毀損 - - - - Could not rename %1 to %2, the file is currently in use - - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Failed to rename file - - - - - Error setting pin state - 設定引腳狀態時錯誤 - - - - OCC::PropagateRemoteDelete - - - Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - 從伺服器端回傳錯誤的 HTTP 代碼, 預期是 204, 但是接收到的是 "%1 %2". - - - - OCC::PropagateRemoteMkdir - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - 從伺服器端回傳錯誤的 HTTP 代碼, 預期是 201, 但是接收到的是 "%1 %2". - - - - Error writing metadata to the database: %1 - - - - - Setting file status failed due to file lock - - - - - OCC::PropagateRemoteMove - - - Could not rename %1 to %2, error: %3 - 無法重命名 %1 到 %2 錯誤 %3 - - - - Wrong HTTP code returned by server. Expected 201, but received "%1 %2". - 從伺服器端回傳錯誤的 HTTP 代碼, 預期是 201, 但是接收到的是 "%1 %2". - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - Error writing metadata to the database - 寫入後設資料(metadata) 時發生錯誤 - - - - Error setting pin state - 設定引腳狀態時錯誤 - - - - OCC::PropagateUpdateMetaDataJob - - - Could not update file : %1 - 無法更新檔案:%1 - - - - The file %1 is currently in use - - - - - OCC::PropagateUploadFileCommon - - - File %1 cannot be uploaded because another file with the same name, differing only in case, exists - 無法上傳檔案%1,因為存在另一個具有相同名稱僅大小寫不同與檔案 - - - - File Removed - 檔案已移除 - - - - Local file changed during syncing. It will be resumed. - 本地端的檔案在同步的過程中被更改,此檔案將會被還原。 - - - - Local file changed during sync. - 本地端的檔案在同步過程中被更改。 - - - - Error updating metadata: %1 - - - - - The file %1 is currently in use - - - - - - Upload of %1 exceeds the quota for the folder - 上傳的 %1 超出目錄的配額 - - - - OCC::PropagateUploadFileNG - - - %1 the file is currently in use - - - - - The local file was removed during sync. - 本地端的檔案在同步過程中被刪除。 - - - - Local file changed during sync. - 本地端的檔案在同步過程中被更改。 - - - - The server did ask for a removed legacy feature(polling) - - - - - Unexpected return code from server (%1) - 來自的意外返回碼伺服器(%1) - - - - Missing File ID from server - 伺服器缺少檔案ID - - - - Missing ETag from server - 來自的ETag缺失伺服器 - - - - OCC::PropagateUploadFileTUS - - - %1 the file is currently in use - - - - - The local file was removed during sync. - 本地端的檔案在同步過程中被刪除。 - - - - Local file changed during sync. - 本地端的檔案在同步過程中被更改。 - - - - OCC::PropagateUploadFileV1 - - - %1 the file is currently in use - - - - - The server did ask for a removed legacy feature(polling) - - - - - The local file was removed during sync. - 本地端的檔案在同步過程中被刪除。 - - - - Local file changed during sync. - 本地端的檔案在同步過程中被更改。 - - - - The server did not acknowledge the last chunk. (No e-tag was present) - 伺服器不承認檔案的最後一個分割檔。(e-tag不存在) - - - - OCC::ProtocolItemModel - - - Time - 時間 - - - - File - 檔案 - - - - Folder - 資料夾 - - - - Issues - - - - - Action - 動作 - - - - Size - 容量 - - - - Account - 帳號 - - - - Status - 狀態 - - - - OCC::ProtocolWidget - - - Form - 表單 - - - - Local sync protocol - 本機同步協定 - - - - Filter - 篩選器 - - - - Account - 帳號 - - - - Copy to clipboard - 複製到剪貼簿 - - - - Show in file browser - - - - - Show in web browser - - - - - Retry sync - - - - - OCC::ProxyAuthDialog - - - Proxy authentication required - 代理伺服器要求驗證 - - - - Username: - 使用者名稱: - - - - Proxy: - 代理伺服器: - - - - The proxy server needs a username and password. - 代理伺服器需要使用者帳號以及密碼。 - - - - Password: - 密碼: - - - - TextLabel - 文字標籤 - - - - OCC::SelectiveSyncDialog - - - Choose What to Sync - 選擇要同步的項目 - - - - OCC::SelectiveSyncWidget - - - Loading ... - 載入中… - - - - Deselect remote folders you do not wish to synchronize. - 取消選擇您不想同步的遠端目錄。 - - - - Name - 名稱 - - - - Size - 大小 - - - - - No subfolders currently on the server. - 目前沒有子資料夾在伺服器上。 - - - - An error occurred while loading the list of sub folders. - 載入子目錄列表時發生錯誤。 - - - - OCC::ServerNotificationHandler - - - Dismiss - 忽略 - - - - OCC::SettingsDialog - - - Settings - 設定 - - - - Activity - 活動 - - - - Add account - 增加帳號 - - - - - Quit %1 - 離開 %1 - - - - Are you sure you want to quit %1? - 您確定您要退出嗎 %1? - - - - Account - 帳號 - - - - MainWindow - 主窗口 - - - - Hide - 隱藏 - - - - OCC::ShareDialog - - - TextLabel - 文字標籤 - - - - share label - 分享標籤 - - - - Dialog - 對白 - - - - ownCloud Path: - ownCloud 路徑: - - - - %1 Sharing - %1 分享 - - - - %1 - %1 - - - - Folder: %2 - 資料夾: %2 - - - - The server does not allow sharing - 伺服器不允許共享 - - - - Retrieving maximum possible sharing permissions from server... - 正在從伺服器檢索最大可能的共享權限... - - - - The file can not be shared because it was shared without sharing permission. - 這個檔案無法被分享,並沒有分享此檔案的權限。 - - - - Users and Groups - 使用者及群組 - - - - Public Links - 公共連結 - - - - OCC::ShareLinkWidget - - - Share NewDocument.odt - 分享 新文件.odt - - - - Upload only (File Drop) - 僅上傳(檔案刪除) - - - - Receive files from others without revealing the contents of the folder. - 在不洩露文件夾內容的情況下從其他人接收目錄。 - - - - Download / View / Edit - 下載 / 查看 / 編輯 - - - - Name: - 名稱: - - - - Enter name of new share... - 輸入新分享的名稱... - - - - &Create - &創造 - - - - TextLabel - 文字標籤 - - - - Set &password - 設定密碼 - - - - Set &expiration date - 設定到期日 - - - - Set password - 設定密碼 - - - - Recipients can view or download contents. - 收件者可以查看或下載內容。 - - - - Download / View - 下載 / 檢視 - - - - Recipients can view, download, edit, delete and upload contents. - 收件者可以查看、下載、編輯、刪除和上傳內容。 - - - - Anyone with the link has access to the file/folder - 任何有連結的人都能存取目錄/檔案 - - - - - P&assword protect - &密碼保護 - - - - The file can not be shared because it was shared without sharing permission. - 這個檔案無法被分享,並沒有分享此檔案的權限。 - - - - Link shares have been disabled - 連結共享已停用 - - - - Create public link share - 建立公開的連結分享 - - - - - Delete - 刪除 - - - - Open link in browser - 在瀏覽器中開啟連結 - - - - Copy link to clipboard - 複製連結到剪貼簿 - - - - Copy link to clipboard (direct download) - 複製連結到剪貼簿(直接下載) - - - - Send link by email - 使用電子郵件傳送連結 - - - - Send link by email (direct download) - 通過電子郵件發送連結(直接下載) - - - - Create new... - 建立新... - - - - I shared %1 with you - 已分享的%1 與您 - - - - Confirm Link Share Deletion - 確認刪除連結分享 - - - - <p>Do you really want to delete the public link share <i>%1</i>?</p><p>Note: This action cannot be undone.</p> - <p>您是否真的要刪除公開的連結分享<i>%1</i>?</p><p>注意:此操作無法撤消。</p> - - - - Cancel - 取消 - - - - - Public link - 公開的連結 - - - - Delete link share - 刪除連結分享 - - - - - Please Set Password - 請設定密碼 - - - - OCC::ShareUserGroupWidget - - - Share NewDocument.odt - 分享 新文件.odt - - - - Share with users or groups ... - 與用戶或群組分享 ... - - - - <html><head/><body><p>You can direct people to this shared file or folder <a href="private link menu"><span style=" text-decoration: underline">by giving them a private link</span></a>.</p></body></html> - <html><head/><body><p>您可以將人們定向到此已分享的檔案或目錄<a href="private link menu"><span style=" text-decoration: underline">通過給他們一個私人連結</span></a>。</p></body></html> - - - - The item is not shared with any users or groups - 該項目未與任何用戶或組已分享的 - - - - Open link in browser - 開啟在瀏覽器中開啟連結 - - - - Copy link to clipboard - 複製連結到剪貼簿 - - - - Send link by email - 使用電子郵件傳送連結 - - - - No results for '%1' - '%1' 沒有結果 - - - - I shared something with you - 我與您已分享的一些東西 - - - - OCC::ShareUserLine - - - Form - 表單 - - - - - TextLabel - 文字標籤 - - - - can edit - 可編輯 - - - - can share - 可分享 - - - - ... - ... - - - - create - 建立 - - - - change - 更動 - - - - delete - 刪除 - - - - OCC::ShareeModel - - - %1 (%2) - sharee (shareWithAdditionalInfo) - %1 (%2) - - - - OCC::SocketApi - - - Share with %1 - parameter is ownCloud - 與 %1 分享 - - - - Context menu share - 語境選單分享 - - - - Context menu share %1 - 上下文選單分享%1 - - - - Confirm deletion - 確認刪除 - - - - Do you want to delete the directory <i>%1</i> and all its contents permanently? - 您是否要刪除資料夾<i>%1</i>及其所有內容永久? - - - - Do you want to delete the file <i>%1</i> permanently? - 您要刪除檔案嗎<i>%1</i>永久性? - - - - Select new location... - 選擇新位置... - - - - Error - 錯誤 - - - - Moving file failed: - -%1 - 移動檔案失敗: - -%1 - - - - I shared something with you - 我與您已分享的了一些東西 - - - - - Share... - 分享... - - - - - Copy private link to clipboard - 將私人連結複製到剪貼簿 - - - - Send private link by email... - 通過電子郵件發送私人連結... - - - - Resharing this file is not allowed - 不允許重新共享此檔案 - - - - Resharing this folder is not allowed - 不允許重新共享此目錄 - - - - Create and copy public link to clipboard - - - - - Copy public link to clipboard - 複製公開的連結到剪貼簿 - - - - Open in browser - 用瀏覽器開啟 - - - - Show file versions in browser - 在瀏覽器中顯示檔案版本 - - - - Rename... - 改名... - - - - Rename and upload... - 重命名並上傳... - - - - Move and rename... - 移動並重命名... - - - - Move, rename and upload... - 移動,重命名和上傳... - - - - Delete local changes - 刪除本地端更改 - - - - Move and upload... - 移動並上傳... - - - - Delete - 刪除 - - - - OCC::SocketUploadJob - - - Failed to rerieve tags - - - - - Backup of %1 started - - - - - Failed to find %1 - - - - - Local path must be a an absolute path - - - - - Failed to create temporary database - - - - - Backup of %1 succeeded - - - - - Failed to set success tag - - - - - Failed to create backup: %1 - - - - - Failed to file id tags - - - - - Backup of %1 failed with: %2 - - - - - %1 backup - - - - - OCC::SslButton - - - <h3>Certificate Details</h3> - <h3>憑證細節</h3> - - - - Common Name (CN): - (通用名): - - - - Subject Alternative Names: - 主題備用名稱: - - - - Organization (O): - 組織 (O): - - - - Organizational Unit (OU): - 組織部門 (OU): - - - - State/Province: - 州或省: - - - - Country: - 國家: - - - - Serial: - 序號: - - - - <h3>Issuer</h3> - <h3>簽發者</h3> - - - - Issuer: - 簽發者: - - - - Issued on: - 簽發於: - - - - Expires on: - 過期於: - - - - <h3>Fingerprints</h3> - <h3>指紋</h3> - - - - SHA-256: - SHA-256: - - - - SHA-1: - SHA-1: - - - - <p><b>Note:</b> This certificate was manually approved</p> - <p><b>注意:</b> 此憑證已被手動核准</p> - - - - %1 (self-signed) - %1 (自我簽章) - - - - %1 - %1 - - - - This connection is encrypted using %1 bit %2. - - 這個連線已經使用 %1 bit %2 加密。 - - - - - Server version: %1 - 伺服器版本: %1 - - - - No support for SSL session tickets/identifiers - 不支持SSL會話票證/標識符 - - - - Certificate information: - 憑證資訊: - - - - The connection is not secure - 連接不安全 - - - - This connection is NOT secure as it is not encrypted. - - 這個連線沒有經過加密,是不安全的。 - - - - - OCC::SslErrorDialog - - - Form - 表單 - - - - Trust this certificate anyway - 信任此憑證 - - - - Untrusted Certificate - 不信任的憑證 - - - - Cannot connect securely to <i>%1</i>: - 無法安全的連線到 <i>%1</i>: - - - - with Certificate %1 - 與憑證 %1 - - - - - - &lt;not specified&gt; - &lt;未指定&gt; - - - - - Organization: %1 - 組織:%1 - - - - - Unit: %1 - 單位:%1 - - - - - Country: %1 - 國家:%1 - - - - Fingerprint (MD5): <tt>%1</tt> - 指紋 (MD5): &lt;tt&gt;%1&lt;/tt&gt; - - - - Fingerprint (SHA1): <tt>%1</tt> - 指紋 (SHA1): &lt;tt&gt;%1&lt;/tt&gt; - - - - Effective Date: %1 - 有效日期:%1 - - - - Expiration Date: %1 - 到期日: %1 - - - - Issuer: %1 - 簽發者: %1 - - - - OCC::SyncEngine - - - %1 (skipped due to earlier error, trying again in %2) - %1(由於先前的錯誤而跳過,請在%2中重試) - - - - Only %1 are available, need at least %2 to start - Placeholders are postfixed with file sizes using Utility::octetsToString() - 目前僅有 %1 可以使用,至少需要 %2 才能開始 - - - - Unable to open or create the local sync database. Make sure you have write access in the sync folder. - 無法開啟或建立本地端同步資料庫。 確保您在同步目錄中具有寫訪問權限。 - - - - Disk space is low: Downloads that would reduce free space below %1 were skipped. - 磁盤空間不足:下載將減少以下可用空間 %1 被跳過了。 - - - - There is insufficient space available on the server for some uploads. - 伺服器上沒有可用的空間用於某些上載。 - - - - Unresolved conflict. - 未解決的衝突。 - - - - Using virtual files with suffix, but suffix is not set - 使用帶後綴與虛擬文件,但未設置後綴 - - - - Unable to read the blacklist from the local database - 無法從本地端資料庫讀取黑名單 - - - - Unable to read from the sync journal. - 無法從同步日誌中讀取。 - - - - Cannot open the sync journal - 同步處理日誌無法開啟 - - - - Aborted - 中止 - - - - OCC::SyncJournalDb - - - Failed to connect database. - - - - - OCC::Systray - - - %1: %2 - %1: %2 - - - - OCC::Theme - - - <p>Version %1. For more information visit <a href="%2">https://%3</a></p><p>For known issues and help, please visit: <a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, Jan-Christoph Borchardt, Thomas Müller, Dominik Schmidt, Michael Stingl, Hannah von Reth, and others.</small></p><p>Copyright ownCloud GmbH</p><p>Distributed by %4 and licensed under the GNU General Public License (GPL) Version 2.0.<br/>%5 and the %5 logo are registered trademarks of %4 in the United States, other countries, or both.</p><p><small>%6</small></p> - <p>版本%1。 欲了解更多信息,請訪問<a href="%2">https://%3</a></p><p>有關已知問題和幫助,請訪問:<a href="https://central.owncloud.org/c/desktop-client">https://central.owncloud.org</a></p><p><small>由Klaas Freitag,Daniel Molkentin,Olivier Goffart,MarkusGötz,Jan-Christoph Borchardt,ThomasMüller,Dominik Schmidt,Michael Stingl,Hannah von Reth等人撰寫。</small></p><p>版權所有ownCloud GmbH</p><p>由%4分發,並根據GNU通用公開的許可證(GPL)2.0版獲得許可。<br/>%5和%5徽標是%4在美國和/或其他國家或地區的註冊商標。</p><p><small>%6</small></p> - - - - OCC::ValidateChecksumHeader - - - The checksum header is malformed. - 校驗碼異常。 - - - - The checksum header contained an unknown checksum type '%1' - 校正資料含有未知的型態 '%1' - - - - The downloaded file does not match the checksum, it will be resumed. '%1' != '%2' - 下載的檔案與校驗碼和不匹配,它將被恢復。'%1'!='%2' - - - - OCC::Vfs - - - The Virtual filesystem feature does not support a drive as sync root - - - - - The Virtual filesystem feature requires a NTFS file system, %1 is using %2 - 虛擬文件系統功能需要一個NTFS檔案系統,%1正在使用%2 - - - - The Virtual filesystem feature is not supported on network drives - - - - - OCC::ownCloudGui - - - Please sign in - 請登入 - - - - Folder %1: %2 - 資料夾 %1: %2 - - - - Unresolved %1 conflicts - - - - - Ignored errors %1 - - - - - There are no sync folders configured. - 尚未設置同步資料夾。 - - - - Open in browser - 用瀏覽器開啟 - - - - - - Log in... - 登入... - - - - - - Log out - 登出 - - - - Recent Changes - 最近的更動 - - - - Managed Folders: - 管理的資料夾: - - - - Open folder '%1' - 開啟 %1 資料夾 - - - - Open %1 in browser - 瀏覽器中開啟 %1 - - - - The server on account %1 runs an unsupported version %2. Using this client with unsupported server versions is untested and potentially dangerous. Proceed at your own risk. - 帳號%1上的伺服器運行不受支持的版本%2。 將此客戶端與不受支持的伺服器版本一起使用是未經測試的,並且可能有危險。 繼續需要您自擔風險。 - - - - Unknown status - 未知狀態 - - - - Settings... - 設定… - - - - Details... - 細節… - - - - Help - 說明 - - - - Quit %1 - 離開 %1 - - - - Disconnected from %1 - 從 %1 斷線 - - - - Unsupported Server Version - 不支持的伺服器版本 - - - - Disconnected - 已經離線 - - - - Disconnected from some accounts - 與某些帳戶斷開連接 - - - - Disconnected from accounts: - 已從帳號離線: - - - - Account %1: %2 - 帳號 %1: %2 - - - - Signed out - 已登出 - - - - Account synchronization is disabled - 帳號同步已停用 - - - - Synchronization is paused - 同步已暫停 - - - - Up to date (%1) - - - - - No sync folders configured - 未配置同步目錄 - - - - Unpause all folders - 全部取消暫停目錄 - - - - Pause all folders - 暫停所有目錄 - - - - Unpause all synchronization - 取消暫停所有同步 - - - - Unpause synchronization - 取消暫停同步 - - - - Pause all synchronization - 暫停所有同步 - - - - Pause synchronization - 暫停同步 - - - - Log out of all accounts - 登出所有的帳戶 - - - - Log in to all accounts... - 登入所有的帳戶 - - - - New account... - 新帳號... - - - - About %1 - 關於 %1 - - - - No items synced recently - 最近沒有項目被同步 - - - - Checking for changes in remote '%1' - 檢查遠端'%1'中的更改 - - - - Checking for changes in local '%1' - 檢查本地端'%1'中的更改 - - - - Syncing %1 of %2 (%3 left) - 同步中 %1 的 %2 (剩餘 %3) - - - - Syncing %1 of %2 - 正在同步%2的%1 - - - - Syncing %1 (%2 left) - 同步中 %1 (剩餘 %2) - - - - Syncing %1 - 同步 %1 - - - - %1 (%2, %3) - %1 (%2, %3) - - - - OwncloudAdvancedSetupPage - - - Form - 表單 - - - - - TextLabel - 文字標籤 - - - - Server - 伺服器 - - - - <html><head/><body><p>If this box is checked, existing content in the local folder will be erased to start a clean sync from the server.</p><p>Do not check this if the local content should be uploaded to the servers folder.</p></body></html> - <html><head/><body><p>如果選中此框,則將刪除本地端目錄中的現有內容,以從伺服器啟動干淨同步。</p><p>如果應將本地端內容上載到服務器目錄,請不要檢查。</p></body></html> - - - - Ask for confirmation before synchroni&zing folders larger than - 同步大於的目錄之前要求確認 - - - - S&ynchronize everything from server - - - - - MB - Trailing part of "Ask confirmation before syncing folder larger than" - MB - - - - Ask for confirmation before synchronizing e&xternal storages - 在同步e&xternal存儲之前要求確認 - - - - Choose what to sync - 選擇要同步的項目 - - - - - <html><head/><body><p>When this option is selected, the wizard will close without synchronizing anything. You can use the &quot;Add Folder Sync Connection&quot; button from the account settings to choose which pair of local and remote folder you wish to synchronize</p></body></html> - <html><head/><body><p>選擇此選項後,嚮導將關閉而不同步任何內容。 您可以使用&quot;添加目錄同步連接&quot;您從帳號設定中選擇按鈕,以選擇要同步的本地端和遠端目錄對</p></body></html> - - - - Manually create folder sync connections - 手動建立目錄同步連接 - - - - &Local Folder - 本地資料夾 (&L) - - - - pbSelectLocalFolder - pb選擇本地文件夾 - - - - &Keep local data - 保留本地資料 (&K) - - - - Start a &clean sync (Crreate a backup and erases the local folder!) - - - - - OwncloudHttpCredsPage - - - Form - 表單 - - - - &Username - 使用者名稱 (&U) - - - - &Password - 密碼 (&P) - - - - OwncloudOAuthCredsPage - - - Form - 表單 - - - - Please switch to your browser to proceed. - 請切換到 瀏覽器 繼續。 - - - - An error occurred while connecting. Please try again. - 連接時發生錯誤。請再試一次。 - - - - Re-open Browser - 重新開啟瀏覽器 - - - - OwncloudSetupPage - - - Form - 表單 - - - - Ser&ver Address - 服務器地址 - - - - https://... - https://... - - - - Error Label - 錯誤標籤 - - - - QObject - - - in the future - 在將來 - - - - %n day(s) ago - %n 幾天前(s) - - - - %n hour(s) ago - %n 幾小時前(s) - - - - now - 現在 - - - - Less than a minute ago - 不到一分鐘前 - - - - %n minute(s) ago - %n 幾分鐘前(s) - - - - Some time ago - 前一段時間 - - - - %1: %2 - this displays an error string (%2) for a file %1 - %1: %2 - - - - SyncFileItem::Status - - - Undefined - - - - - Fatal Error - 嚴重錯誤 - - - - - Error - 錯誤 - - - - Info - 信息 - - - - Success - 成功 - - - - Conflict - 衝突 - - - - File Ignored - - - - - Restored - - - - - Blacklisted - - - - - Excluded - - - - - Utility - - - %L1 GB - %L1 GB - - - - %L1 MB - %L1 MB - - - - %L1 KB - %L1 KB - - - - %L1 B - %L1 B - - - - %n year(s) - %n年(s) - - - - %n month(s) - %n個月(s) - - - - %n day(s) - %n 天(s) - - - - %n hour(s) - %n小時(s) - - - - %n minute(s) - %n分鐘(s) - - - - %n second(s) - %n秒(s) - - - - %1 %2 - %1 %2 - - - - main.cpp - - - System Tray not available - 系統常駐程式無法使用 - - - - %1 requires on a working system tray. If you are running XFCE, please follow <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">these instructions</a>. Otherwise, please install a system tray application such as 'trayer' and try again. - %1需要可運作的系統常駐程式區。若您正在執行XFCE,請參考 <a href="http://docs.xfce.org/xfce/xfce4-panel/systray">這份教學</a>。若非如此則請安裝一個系統常駐的應用程式,如'trayer',並再度嘗試。 - - - - ownCloudTheme::aboutVersions() - - - %1 %2 %3%8%9Libraries Qt %4, %5%8Using virtual files plugin: %6%8%7 - - - - - ownCloudTheme::qtVer - - - %1 (Built against Qt %1) - %1 (針對Qt構建%1) - - - - ownCloudTheme::versionWithSha - - - %1 %2 - %1 %2 - - - - progress - - - Virtual file created - 虛擬檔案已創建 - - - - Replaced by virtual file - 替換為虛擬檔案 - - - - Downloaded - 已下載 - - - - Uploaded - 已上傳 - - - - Server version downloaded, copied changed local file into conflict file - 下載伺服器版本,將更改後的本地端檔案複製到衝突檔案中 - - - - Deleted - 已刪除 - - - - Moved to %1 - 搬移到 %1 - - - - Ignored - 已忽略 - - - - Filesystem access error - 存取檔案系統錯誤 - - - - Error - 錯誤 - - - - Updated local metadata - 更新了本地端元數據 - - - - - Unknown - 未知 - - - - downloading - 下載中 - - - - uploading - 上傳中 - - - - deleting - 刪除中 - - - - moving - 搬移中 - - - - ignoring - 忽略中 - - - - - error - 錯誤 - - - - updating local metadata - 更新了本地端元數據 - - - - theme - - - Status undefined - 未定義的狀態 - - - - Waiting to start sync - 等待開始同步 - - - - Sync is running - 同步中 - - - - Sync Success - 同步完成 - - - - Sync Success, some files were ignored. - 同步成功,部份檔案被忽略 - - - - Sync Error - 同步失敗 - - - - Setup Error - 安裝錯誤 - - - - Preparing to sync - 正在準備同步。 - - - - Aborting... - 中斷中… - - - - Sync is paused - 同步已暫停 - - - - utility - - - Could not open browser - 無法開啟瀏覽器 - - - - There was an error when launching the browser to go to URL %1. Maybe no default browser is configured? - 啟動瀏覽器以訪問超連結%1時發生錯誤。 也許沒有配置默認瀏覽器? - - - - Could not open email client - 不能開啟電子郵件客戶端 - - - - There was an error when launching the email client to create a new message. Maybe no default email client is configured? - 啟動電子郵件客戶端以建立新訊息時發生錯誤。 也許沒有配置默認的電子郵件客戶端? - - - - Always available locally - 始終在本地可用的 - - - - Some available online only - 一些僅在線可用的 - - - - - Available online only - 僅在線可用的 - - - - Currently available locally - 目前本地可用的 - - - - Make always available locally - 使本地始終可用的 - - - - Free up local space - 釋放本地端空間 - - - \ No newline at end of file